diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 00000000000..38f13835bcc --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,708 @@ +name: Deploy AMP + +on: + workflow_dispatch: + inputs: + environment: + description: 'Server to deploy' + required: true + type: choice + options: + - staging + - de + country: + description: 'Country to deploy (will be validated against available countries from server)' + required: true + type: choice + options: + - bfaso + - boad + - chad + - civ + - drc + - ecowas + - egypt + - ethiopia + - gambia + - ggw + - haiti + - haitiiati + - haititraining + - honduras + - honduraslight + - honduraslightssc + - jordan + - kosovo + - kyrgyzstan + - liberia + - madagascar + - malawi + - moldova + - nepal + - niger + - rdidemo + - rwanda + - rwandatest + - senegal + - senegalgiz + - tanzania + - timor + - togo + - uganda + - xchad + pr_number: + description: 'PR number (optional - if provided, will use pr-{number} format for tag and URL). See workflow logs for available PRs.' + required: false + type: string + +env: + PG_VERSION: 14 + # Reference list from GitHub Repository Variables + COMMON_COUNTRIES: ${{ vars.COMMON_COUNTRIES }} + +jobs: + build-and-deploy: + runs-on: ubuntu-latest + timeout-minutes: 60 # Add timeout to prevent hanging jobs + steps: + - name: Setup SSH agent with build and deploy keys + uses: webfactory/ssh-agent@v0.9.0 + with: + ssh-private-key: | + ${{ secrets.DOCKER_BUILD_SSH_KEY }} + ${{ secrets.DEPLOY_SSH_PRIVATE_KEY }} + + - name: Configure git to use SSH for submodules + run: | + # Configure git to rewrite HTTPS URLs to SSH for submodules + git config --global url."git@github.com:".insteadOf "https://github.com/" + # Add GitHub to known hosts + mkdir -p ~/.ssh + ssh-keyscan -H github.com >> ~/.ssh/known_hosts 2>/dev/null || true + + - name: Checkout code with submodules (default branch) + if: inputs.pr_number == '' + uses: actions/checkout@v4 + with: + fetch-depth: 0 + submodules: true + # Use SSH for private submodules + ssh-key: ${{ secrets.DOCKER_BUILD_SSH_KEY || '' }} + + - name: List available PRs and validate + if: inputs.pr_number != '' + id: pr_info + run: | + set -e + echo "πŸ“‹ Fetching list of open pull requests..." + echo "" + + # Fetch open PRs using GitHub API + REPO="${{ github.repository }}" + PR_NUMBER="${{ inputs.pr_number }}" + + # Validate PR number is numeric + if ! [[ "$PR_NUMBER" =~ ^[0-9]+$ ]]; then + echo "❌ Error: PR number must be numeric, got: ${PR_NUMBER}" + exit 1 + fi + + # Get list of open PRs (limit to 50 most recent) + PRS_RESPONSE=$(curl -s -H "Authorization: token ${{ github.token }}" \ + "https://api.github.com/repos/${REPO}/pulls?state=open&per_page=50&sort=updated&direction=desc") + + if [ -z "$PRS_RESPONSE" ] || [ "$PRS_RESPONSE" == "[]" ]; then + echo "❌ Error: Could not fetch PR list or no open PRs found" + echo " Cannot validate PR #${PR_NUMBER}" + exit 1 + fi + + echo "Available open PRs:" + echo "$PRS_RESPONSE" | jq -r '.[] | " #\(.number) - \(.title) (branch: \(.head.ref))"' || echo "Could not parse PR list" + echo "" + + # Validate the provided PR number exists in open PRs + PR_EXISTS=$(echo "$PRS_RESPONSE" | jq -r --arg pr "$PR_NUMBER" '.[] | select(.number == ($pr | tonumber)) | .number' || echo "") + + if [ -z "$PR_EXISTS" ]; then + echo "❌ Error: PR #${PR_NUMBER} not found in open PRs list" + echo "" + echo "The PR might be:" + echo " - Closed or merged" + echo " - Not yet created" + echo " - Incorrect number" + echo "" + echo "Please verify the PR number and try again." + exit 1 + fi + + # Get PR details + PR_TITLE=$(echo "$PRS_RESPONSE" | jq -r --arg pr "$PR_NUMBER" '.[] | select(.number == ($pr | tonumber)) | .title' || echo "") + PR_BRANCH=$(echo "$PRS_RESPONSE" | jq -r --arg pr "$PR_NUMBER" '.[] | select(.number == ($pr | tonumber)) | .head.ref' || echo "") + PR_HEAD_REPO=$(echo "$PRS_RESPONSE" | jq -r --arg pr "$PR_NUMBER" '.[] | select(.number == ($pr | tonumber)) | .head.repo.full_name' || echo "") + PR_HEAD_SHA=$(echo "$PRS_RESPONSE" | jq -r --arg pr "$PR_NUMBER" '.[] | select(.number == ($pr | tonumber)) | .head.sha' || echo "") + + echo "βœ… PR #${PR_NUMBER} found: ${PR_TITLE}" + echo " Branch: ${PR_BRANCH}" + echo " Repository: ${PR_HEAD_REPO}" + echo " SHA: ${PR_HEAD_SHA}" + + # Store PR branch info for checkout step + echo "PR_BRANCH=${PR_BRANCH}" >> $GITHUB_OUTPUT + echo "PR_HEAD_REPO=${PR_HEAD_REPO}" >> $GITHUB_OUTPUT + echo "PR_HEAD_SHA=${PR_HEAD_SHA}" >> $GITHUB_OUTPUT + + - name: Checkout PR branch + if: inputs.pr_number != '' + uses: actions/checkout@v4 + with: + ref: ${{ steps.pr_info.outputs.PR_BRANCH }} + repository: ${{ steps.pr_info.outputs.PR_HEAD_REPO }} + fetch-depth: 0 + submodules: true + # Use SSH for private submodules + ssh-key: ${{ secrets.DOCKER_BUILD_SSH_KEY || '' }} + + - name: Set up JDK for Maven + uses: actions/setup-java@v4 + with: + java-version: '11' + distribution: 'temurin' + # Note: Maven cache is not needed here since Maven runs inside Docker + # Docker build uses its own Maven cache via --mount=type=cache,target=/root/.m2 + + - name: Read AMP version from pom.xml + id: amp_version + run: | + # Parse version directly from pom.xml (much faster than running Maven) + # Try to get project.version property first, then fallback to version tag + # This avoids Maven initialization which can take 30+ seconds + + # Method 1: Try to extract project.version property using sed + VERSION=$(sed -n 's/.*\([^<]*\)<\/project\.version>.*/\1/p' amp/pom.xml | head -1) + + # Method 2: If not found, extract from version tag and remove -SNAPSHOT + if [ -z "$VERSION" ]; then + VERSION=$(sed -n 's/.*\([^<]*\)<\/version>.*/\1/p' amp/pom.xml | head -1 | sed 's/-SNAPSHOT//') + fi + + # Clean up: trim whitespace + VERSION=$(echo "$VERSION" | xargs) + + # Fallback to default if still empty + if [ -z "$VERSION" ]; then + echo "⚠️ Could not parse version from amp/pom.xml, using default 4.0" + VERSION="4.0" + fi + + echo "AMP_VERSION=$VERSION" >> $GITHUB_OUTPUT + echo "AMP Version: $VERSION (parsed from amp/pom.xml in <1s vs 30s+ for Maven)" + + - name: Generate deployment tag + id: tag + run: | + # Check if PR number is provided + if [ -n "${{ inputs.pr_number }}" ]; then + PR_NUMBER="${{ inputs.pr_number }}" + TAG="pr-${PR_NUMBER}" + echo "PR_NUMBER=${PR_NUMBER}" >> $GITHUB_OUTPUT + echo "Deployment tag: $TAG (PR #${PR_NUMBER})" + else + # Handle branch-based deployments + BRANCH_NAME="${GITHUB_REF#refs/heads/}" + if [[ "$BRANCH_NAME" =~ ^feature/AMP-[0-9]+.* ]]; then + JIRA_ID=$(echo "$BRANCH_NAME" | sed -n 's/^feature\/AMP-\([0-9]\+\).*/\1/p') + TAG="feature-${JIRA_ID}" + else + TAG=$(echo "$BRANCH_NAME" | sed 's/[^a-zA-Z0-9_-]/-/g' | tr '[:upper:]' '[:lower:]') + fi + echo "Deployment tag: $TAG" + fi + echo "TAG=$TAG" >> $GITHUB_OUTPUT + + - name: Validate AWS credentials + run: | + if [ -z "${{ secrets.AWS_ACCESS_KEY_ID }}" ]; then + echo "❌ Error: AWS_ACCESS_KEY_ID secret is not set" + exit 1 + fi + if [ -z "${{ secrets.AWS_SECRET_ACCESS_KEY }}" ]; then + echo "❌ Error: AWS_SECRET_ACCESS_KEY secret is not set" + exit 1 + fi + if [ -z "${{ secrets.ECR_REGISTRY }}" ]; then + echo "❌ Error: ECR_REGISTRY secret is not set" + exit 1 + fi + echo "βœ… AWS credentials and ECR registry are configured" + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: us-east-1 + + - name: Verify AWS credentials + run: | + echo "Verifying AWS credentials..." + aws sts get-caller-identity || { + echo "❌ Error: Failed to authenticate with AWS" + echo "Please verify that AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY are correct" + exit 1 + } + echo "βœ… AWS credentials verified" + + - name: Get ECR login token + id: ecr-login + run: | + set +x # Avoid command echoing to prevent token exposure + echo "Getting ECR login token for ${{ secrets.ECR_REGISTRY }}..." + PASSWORD=$(aws ecr get-login-password --region us-east-1) || { + echo "❌ Error: Failed to get ECR login token" + echo "Please verify:" + echo " 1. AWS credentials have ECR permissions" + echo " 2. ECR registry exists: ${{ secrets.ECR_REGISTRY }}" + echo " 3. Region is correct: us-east-1" + exit 1 + } + # Use ::add-mask:: to prevent token from appearing in logs + echo "::add-mask::$PASSWORD" + echo "password=$PASSWORD" >> $GITHUB_OUTPUT + echo "βœ… ECR login token obtained" + + - name: Login to Container Registry (ECR) + run: | + set +x # Avoid command echoing to prevent token exposure + echo "${{ steps.ecr-login.outputs.password }}" | docker login --username AWS --password-stdin ${{ secrets.ECR_REGISTRY }} 2>&1 | sed 's/.*password.*/***/g' + echo "βœ… Successfully logged in to ECR" + + - name: Set deployment hostname and user + id: deploy_config + run: | + # Use inputs from workflow_dispatch + ENV="${{ inputs.environment }}" + COUNTRY="${{ inputs.country }}" + + # Store environment and country + echo "ENV=${ENV}" >> $GITHUB_OUTPUT + echo "COUNTRY=${COUNTRY}" >> $GITHUB_OUTPUT + + if [[ "$ENV" == "de" ]]; then + echo "DEPLOY_HOST=${{ vars.AMP_DE_HOSTNAME }}" >> $GITHUB_OUTPUT + # For PRs, use pr-{number} format in URL + if [ -n "${{ inputs.pr_number }}" ]; then + echo "AMP_URL=http://amp-${COUNTRY}-pr-${{ inputs.pr_number }}.de.ampsite.net/" >> $GITHUB_OUTPUT + else + echo "AMP_URL=http://amp-${COUNTRY}-${{ steps.tag.outputs.TAG }}.de.ampsite.net/" >> $GITHUB_OUTPUT + fi + else + echo "DEPLOY_HOST=${{ vars.AMP_STAGING_HOSTNAME }}" >> $GITHUB_OUTPUT + # For PRs, use pr-{number} format in URL + if [ -n "${{ inputs.pr_number }}" ]; then + echo "AMP_URL=http://amp-${COUNTRY}-pr-${{ inputs.pr_number }}.stg.ampsite.net/" >> $GITHUB_OUTPUT + else + echo "AMP_URL=http://amp-${COUNTRY}-${{ steps.tag.outputs.TAG }}.stg.ampsite.net/" >> $GITHUB_OUTPUT + fi + fi + + # Store PR number if provided + if [ -n "${{ inputs.pr_number }}" ]; then + echo "PR_NUMBER=${{ inputs.pr_number }}" >> $GITHUB_OUTPUT + fi + + # Set deploy user from vars with fallback to 'jenkins' + if [ -n "${{ vars.DEPLOY_USER }}" ]; then + echo "DEPLOY_USER=${{ vars.DEPLOY_USER }}" >> $GITHUB_OUTPUT + else + echo "DEPLOY_USER=jenkins" >> $GITHUB_OUTPUT + fi + + - name: Setup SSH config for bastion + run: | + mkdir -p ~/.ssh + chmod 700 ~/.ssh + + DEPLOY_HOST="${{ steps.deploy_config.outputs.DEPLOY_HOST }}" + DEPLOY_USER="${{ steps.deploy_config.outputs.DEPLOY_USER }}" + BASTION_HOST="${{ secrets.BASTION_HOST }}" + BASTION_USER="${{ vars.BASTION_USER }}" + + # Use default bastion user if not specified + if [ -z "$BASTION_USER" ] || [ "$BASTION_USER" = "" ]; then + BASTION_USER="jenkins" + fi + + # Create SSH config matching local configuration + # Pattern: *.aws uses ProxyCommand ssh -W %h.devgateway.org:%p bastion + if [ -n "$BASTION_HOST" ] && [ "$BASTION_HOST" != "" ]; then + # Check if deployment host matches *.aws pattern for ProxyCommand + if echo "$DEPLOY_HOST" | grep -q "\.aws"; then + # Use ProxyCommand pattern for *.aws hosts (matching local SSH config) + # Pattern: %h.devgateway.org means if host is "ampdev.aws", target is "ampdev.aws.devgateway.org" + if echo "$DEPLOY_HOST" | grep -q "\.aws\.devgateway\.org$"; then + # Already in full format: something.aws.devgateway.org + TARGET_HOST="$DEPLOY_HOST" + elif echo "$DEPLOY_HOST" | grep -q "\.aws$"; then + # Transform: something.aws -> something.aws.devgateway.org (matching %h.devgateway.org pattern) + TARGET_HOST="${DEPLOY_HOST}.devgateway.org" + else + # Fallback: use as-is + TARGET_HOST="$DEPLOY_HOST" + fi + + cat >> ~/.ssh/config << EOF + Host bastion + HostName $BASTION_HOST + User $BASTION_USER + StrictHostKeyChecking no + UserKnownHostsFile /dev/null + ControlMaster no + + Host $DEPLOY_HOST + HostName $DEPLOY_HOST + User $DEPLOY_USER + ProxyCommand ssh -W $TARGET_HOST:%p -o ClearAllForwardings=no bastion + StrictHostKeyChecking no + UserKnownHostsFile /dev/null + ControlMaster no + EOF + else + # Use ProxyJump for non-*.aws hosts + cat >> ~/.ssh/config << EOF + Host bastion + HostName $BASTION_HOST + User $BASTION_USER + StrictHostKeyChecking no + UserKnownHostsFile /dev/null + ControlMaster no + + Host $DEPLOY_HOST + HostName $DEPLOY_HOST + User $DEPLOY_USER + ProxyJump bastion + StrictHostKeyChecking no + UserKnownHostsFile /dev/null + ControlMaster no + EOF + fi + else + # No bastion - direct connection + cat >> ~/.ssh/config << EOF + Host $DEPLOY_HOST + HostName $DEPLOY_HOST + User $DEPLOY_USER + StrictHostKeyChecking no + UserKnownHostsFile /dev/null + ControlMaster no + EOF + fi + + chmod 600 ~/.ssh/config + echo "βœ… Configured SSH to use bastion: ${BASTION_HOST:-'none (direct connection)'}" + echo "βœ… Using ProxyCommand pattern matching your local config" + echo "βœ… Bastion user: $BASTION_USER" + echo "βœ… Deployment host: $DEPLOY_HOST" + echo "" + echo "SSH configuration:" + cat ~/.ssh/config + + - name: Test SSH connection + run: | + DEPLOY_HOST="${{ steps.deploy_config.outputs.DEPLOY_HOST }}" + DEPLOY_USER="${{ steps.deploy_config.outputs.DEPLOY_USER }}" + echo "Testing SSH connection through bastion..." + ssh $DEPLOY_USER@$DEPLOY_HOST "echo 'SSH connection successful'" + + - name: Get available countries list + id: countries_list + run: | + DEPLOY_HOST="${{ steps.deploy_config.outputs.DEPLOY_HOST }}" + DEPLOY_USER="${{ steps.deploy_config.outputs.DEPLOY_USER }}" + # Get available countries for this version + COUNTRIES=$(ssh $DEPLOY_USER@$DEPLOY_HOST "cd /opt/amp_dbs && amp-db ls ${{ steps.amp_version.outputs.AMP_VERSION }} | sort" || echo "") + COUNTRIES=$(echo "$COUNTRIES" | tr '\n' ' ' | xargs) # Trim whitespace + + if [ -z "$COUNTRIES" ] || [ "$COUNTRIES" = "" ]; then + echo "❌ No database backups compatible with version ${{ steps.amp_version.outputs.AMP_VERSION }}" + exit 1 + fi + + # Store countries list for later use + echo "COUNTRIES<> $GITHUB_OUTPUT + echo "$COUNTRIES" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + echo "==========================================" + echo "Available countries for version ${{ steps.amp_version.outputs.AMP_VERSION }}:" + echo "==========================================" + echo "$COUNTRIES" | tr ' ' '\n' + echo "==========================================" + + - name: Validate selected country + run: | + # Get country from deploy_config output + COUNTRY="${{ steps.deploy_config.outputs.COUNTRY }}" + + # Convert countries list to newline-separated for validation + COUNTRIES=$(echo "${{ steps.countries_list.outputs.COUNTRIES }}" | tr ' ' '\n') + + # Check if selected country is available + if ! echo "$COUNTRIES" | grep -q "^${COUNTRY}$"; then + echo "❌ Country '${COUNTRY}' not found in available countries" + echo "" + echo "Available countries:" + echo "$COUNTRIES" + exit 1 + fi + + echo "βœ… Country '${COUNTRY}' is available" + + - name: Get database version + id: db_version + run: | + DEPLOY_HOST="${{ steps.deploy_config.outputs.DEPLOY_HOST }}" + DEPLOY_USER="${{ steps.deploy_config.outputs.DEPLOY_USER }}" + # Get country from deploy_config output + COUNTRY="${{ steps.deploy_config.outputs.COUNTRY }}" + DB_VERSION=$(ssh $DEPLOY_USER@$DEPLOY_HOST "cd /opt/amp_dbs && amp-db find ${{ steps.amp_version.outputs.AMP_VERSION }} ${COUNTRY}") + echo "DB_VERSION=$DB_VERSION" >> $GITHUB_OUTPUT + echo "Database version: $DB_VERSION" + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + driver-opts: | + image=moby/buildkit:latest + network=host + buildkitd-flags: --debug + + - name: Get commit hash + id: commit_hash + run: | + if git log --pretty=%an -n 1 | grep -q "GitHub Actions"; then + REF="HEAD~1" + else + REF="HEAD" + fi + HASH=$(git rev-parse $REF) + echo "COMMIT_HASH=$HASH" >> $GITHUB_OUTPUT + echo "Commit hash: $HASH" + + + - name: Check if image with commit hash already exists + id: check_image + env: + DOCKER_BUILDKIT: 1 + run: | + COMMIT_HASH="${{ steps.commit_hash.outputs.COMMIT_HASH }}" + # Create a commit-hash-based image tag for content-based lookup + IMAGE_BY_HASH="${{ secrets.ECR_REGISTRY }}/amp/webapp:commit-${COMMIT_HASH:0:12}" + DEPLOY_TAG="${{ steps.tag.outputs.TAG }}" + IMAGE_BY_TAG="${{ secrets.ECR_REGISTRY }}/amp/webapp:${DEPLOY_TAG}" + + echo "Checking for existing image with commit hash ${COMMIT_HASH:0:12}..." + + # Try to pull image by commit hash + if docker pull "$IMAGE_BY_HASH" 2>/dev/null; then + echo "βœ… Found existing image for commit ${COMMIT_HASH:0:12}" + echo "SKIP_BUILD=true" >> $GITHUB_OUTPUT + echo "EXISTING_IMAGE=$IMAGE_BY_HASH" >> $GITHUB_OUTPUT + # Tag it with the deployment tag for consistency + docker tag "$IMAGE_BY_HASH" "$IMAGE_BY_TAG" + echo "IMAGE=$IMAGE_BY_TAG" >> $GITHUB_ENV + else + echo "ℹ️ No existing image found for commit ${COMMIT_HASH:0:12}, will build new image" + echo "SKIP_BUILD=false" >> $GITHUB_OUTPUT + echo "IMAGE=$IMAGE_BY_TAG" >> $GITHUB_ENV + fi + + - name: Debug SSH agent + if: steps.check_image.outputs.SKIP_BUILD == 'false' + run: | + echo "SSH_AUTH_SOCK=$SSH_AUTH_SOCK" + ssh-add -l || echo "No keys in agent" + ssh -o StrictHostKeyChecking=no -T git@github.com 2>&1 || true + + - name: Build Docker image + if: steps.check_image.outputs.SKIP_BUILD == 'false' + env: + DOCKER_BUILDKIT: 1 + BUILDKIT_PROGRESS: plain + run: | + COMMIT_HASH="${{ steps.commit_hash.outputs.COMMIT_HASH }}" + IMAGE_BY_HASH="${{ secrets.ECR_REGISTRY }}/amp/webapp:commit-${COMMIT_HASH:0:12}" + IMAGE_BY_TAG="${{ secrets.ECR_REGISTRY }}/amp/webapp:${{ steps.tag.outputs.TAG }}" + + # Build SSH args - only add if SSH_AUTH_SOCK is available (from ssh-agent) + SSH_ARGS="" + if [ -n "$SSH_AUTH_SOCK" ]; then + SSH_ARGS="--ssh default=$SSH_AUTH_SOCK" + fi + + # Collect cache sources for better layer reuse + CACHE_FROM_ARGS=() + + # Try to use commit-hash-based image as cache (if it exists from a previous build) + if docker pull "$IMAGE_BY_HASH" 2>/dev/null; then + echo "βœ… Using commit-hash-based image as cache" + CACHE_FROM_ARGS+=("--cache-from" "$IMAGE_BY_HASH") + fi + + # Try to use deployment tag image as cache + if docker pull "$IMAGE_BY_TAG" 2>/dev/null; then + echo "βœ… Using deployment tag image as cache" + CACHE_FROM_ARGS+=("--cache-from" "$IMAGE_BY_TAG") + fi + + # Try to use a recent 'latest' or 'main' branch image as cache (if exists) + LATEST_IMAGE="${{ secrets.ECR_REGISTRY }}/amp/webapp:latest" + if docker pull "$LATEST_IMAGE" 2>/dev/null; then + echo "βœ… Using latest image as additional cache source" + CACHE_FROM_ARGS+=("--cache-from" "$LATEST_IMAGE") + fi + + if [ ${#CACHE_FROM_ARGS[@]} -eq 0 ]; then + echo "ℹ️ No existing image found, building from scratch" + fi + + # Build the image with both tags using cache modes for better optimization + # SKIP_TESTS=true to skip Maven and npm tests for faster deployment builds + docker buildx build \ + --progress=plain \ + $SSH_ARGS \ + "${CACHE_FROM_ARGS[@]}" \ + --cache-to type=inline \ + --cache-from type=registry,ref="$IMAGE_BY_TAG" \ + --cache-from type=registry,ref="$LATEST_IMAGE" \ + -t "$IMAGE_BY_HASH" \ + -t "$IMAGE_BY_TAG" \ + --build-arg BUILDKIT_INLINE_CACHE=1 \ + --build-arg BUILD_SOURCE="${{ steps.tag.outputs.TAG }}" \ + --build-arg AMP_URL="${{ steps.deploy_config.outputs.AMP_URL }}" \ + --build-arg AMP_PULL_REQUEST="${{ steps.deploy_config.outputs.PR_NUMBER || '' }}" \ + --build-arg AMP_BRANCH="${GITHUB_REF#refs/heads/}" \ + --build-arg AMP_REGISTRY_PRIVATE_KEY="${{ secrets.AMP_REGISTRY_PRIVATE_KEY || '' }}" \ + --build-arg SKIP_TESTS=true \ + --label git-hash="$COMMIT_HASH" \ + --load \ + amp + + echo "βœ… Image built successfully" + + - name: Push Docker image to registry + run: | + COMMIT_HASH="${{ steps.commit_hash.outputs.COMMIT_HASH }}" + IMAGE_BY_HASH="${{ secrets.ECR_REGISTRY }}/amp/webapp:commit-${COMMIT_HASH:0:12}" + IMAGE_BY_TAG="${{ secrets.ECR_REGISTRY }}/amp/webapp:${{ steps.tag.outputs.TAG }}" + SKIP_BUILD="${{ steps.check_image.outputs.SKIP_BUILD }}" + + # Push commit-hash-based image (for future reuse) - only if we built it + if [ "$SKIP_BUILD" != "true" ]; then + echo "Pushing commit-hash-based image (for future reuse)..." + docker push "$IMAGE_BY_HASH" > /dev/null + fi + + # Push deployment tag image + echo "Pushing deployment tag image..." + docker push "$IMAGE_BY_TAG" > /dev/null + + if [ "$SKIP_BUILD" == "true" ]; then + echo "βœ… Reused existing image (no build needed) - saved build time!" + else + echo "βœ… Image built and pushed successfully" + fi + + # Set IMAGE for cleanup step + echo "IMAGE=$IMAGE_BY_TAG" >> $GITHUB_ENV + + - name: Logout from Container Registry + if: always() + run: docker logout ${{ secrets.ECR_REGISTRY }} || true + + - name: Update GitHub commit status + uses: actions/github-script@v7 + with: + script: | + github.rest.repos.createCommitStatus({ + owner: context.repo.owner, + repo: context.repo.repo, + sha: '${{ steps.commit_hash.outputs.COMMIT_HASH }}', + state: 'success', + target_url: '${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}', + description: 'Built successfully', + context: 'github-actions/build' + }); + + - name: Deploy to server + id: deploy + run: | + set -eox pipefail + + DEPLOY_HOST="${{ steps.deploy_config.outputs.DEPLOY_HOST }}" + DEPLOY_USER="${{ steps.deploy_config.outputs.DEPLOY_USER }}" + + # Set variables locally for passing to remote server + TAG="${{ steps.tag.outputs.TAG }}" + COUNTRY="${{ steps.deploy_config.outputs.COUNTRY }}" + DB_VERSION="${{ steps.db_version.outputs.DB_VERSION }}" + PG_VERSION="${{ env.PG_VERSION }}" + REGISTRY="${{ secrets.ECR_REGISTRY }}" + AWS_ACCESS_KEY_ID="${{ secrets.AWS_ACCESS_KEY_ID || '' }}" + AWS_SECRET_ACCESS_KEY="${{ secrets.AWS_SECRET_ACCESS_KEY || '' }}" + PR_NUMBER="${{ inputs.pr_number || '' }}" + + # Pass variables as environment variables through SSH + ssh $DEPLOY_USER@$DEPLOY_HOST bash -s << DEPLOY_SCRIPT + set -eo pipefail + TAG="$TAG" + COUNTRY="$COUNTRY" + DB_VERSION="$DB_VERSION" + PG_VERSION="$PG_VERSION" + REGISTRY="$REGISTRY" + AWS_ACCESS_KEY_ID="$AWS_ACCESS_KEY_ID" + AWS_SECRET_ACCESS_KEY="$AWS_SECRET_ACCESS_KEY" + PR_NUMBER="$PR_NUMBER" + + # Validate required variables + if [ -z "$TAG" ] || [ -z "$COUNTRY" ] || [ -z "$DB_VERSION" ] || [ -z "$PG_VERSION" ]; then + echo "❌ Error: One or more required variables are empty!" + echo " TAG: '$TAG'" + echo " COUNTRY: '$COUNTRY'" + echo " DB_VERSION: '$DB_VERSION'" + echo " PG_VERSION: '$PG_VERSION'" + exit 1 + fi + + # Set image name to match the registry used in the workflow + export AMP_WEBAPP_IMAGE_NAME="${REGISTRY}/amp/webapp" + + # For PRs, log the PR number + if [ -n "$PR_NUMBER" ]; then + echo "Deploying PR #$PR_NUMBER to ${COUNTRY}" + fi + + # Export AWS credentials for the script to use + export AWS_ACCESS_KEY_ID + export AWS_SECRET_ACCESS_KEY + export AWS_DEFAULT_REGION=us-east-1 + + # Check if user can access docker without sudo + if docker ps > /dev/null 2>&1; then + amp-up2 "$TAG" "$COUNTRY" "$DB_VERSION" "$PG_VERSION" + elif sudo docker ps > /dev/null 2>&1; then + # Use sudo -E to preserve environment variables + sudo -E amp-up2 "$TAG" "$COUNTRY" "$DB_VERSION" "$PG_VERSION" + else + echo "❌ Cannot access Docker daemon. User may need to be added to docker group." + echo "Run: sudo usermod -aG docker $USER" + exit 1 + fi + DEPLOY_SCRIPT + + echo "βœ… Deployment successful" + + - name: Cleanup Docker image + if: always() + run: | + docker rmi "${{ env.IMAGE }}" || true \ No newline at end of file diff --git a/amp/TEMPLATE/ampTemplate/js_2/esrigis/mainmapPopup.js b/amp/TEMPLATE/ampTemplate/js_2/esrigis/mainmapPopup.js index e108e58e32e..66094f57c9c 100644 --- a/amp/TEMPLATE/ampTemplate/js_2/esrigis/mainmapPopup.js +++ b/amp/TEMPLATE/ampTemplate/js_2/esrigis/mainmapPopup.js @@ -71,18 +71,14 @@ function initMap() { function loadBaseMap() { map = L.map('map').setView([latitude, longitude], 7); // create the tile layer with correct attribution - var osmUrl = 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'; + var osmUrl = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'; if (isOsm) { - var subdomains = ['a', 'b', 'c']; - if (basemapurl !== undefined && basemapurl.indexOf("mqcdn") != -1) { - subdomains = ['otile1', 'otile2', 'otile3', 'otile4']; - } - var osmAttrib = 'Map data Β© OpenStreetMap contributors'; + var osmAttrib = 'Map data Β© OpenStreetMap contributors'; tileLayer = new L.TileLayer(osmUrl, { minZoom: 0, maxZoom: 16, attribution: osmAttrib, - subdomains: subdomains + subdomains: ['a', 'b', 'c'] }); } else { tileLayer = L.esri.tiledMapLayer({ diff --git a/amp/TEMPLATE/reampv2/package.json b/amp/TEMPLATE/reampv2/package.json index b7398cacae5..1e8dea872f2 100755 --- a/amp/TEMPLATE/reampv2/package.json +++ b/amp/TEMPLATE/reampv2/package.json @@ -9,6 +9,7 @@ "scripts": { "start": "DISABLE_ESLINT=true lerna run start", "build": "DISABLE_ESLINT=true lerna run build", + "postbuild": "node scripts/sync-build-output.js", "graph": "nx graph" }, "author": "", diff --git a/amp/TEMPLATE/reampv2/scripts/sync-build-output.js b/amp/TEMPLATE/reampv2/scripts/sync-build-output.js new file mode 100644 index 00000000000..01551c743c9 --- /dev/null +++ b/amp/TEMPLATE/reampv2/scripts/sync-build-output.js @@ -0,0 +1,49 @@ +const fs = require('node:fs'); +const path = require('node:path'); + +const rootDir = path.resolve(__dirname, '..'); +const legacyBuildDir = path.join(rootDir, 'build'); +const reampv2AppBuildDir = path.join(rootDir, 'packages', 'reampv2-app', 'build'); +const ampOfflineBuildDir = path.join(rootDir, 'packages', 'ampoffline', 'build'); + +const copyDirectory = (sourceDir, targetDir) => { + fs.mkdirSync(targetDir, { recursive: true }); + + fs.readdirSync(sourceDir, { withFileTypes: true }).forEach((entry) => { + const sourcePath = path.join(sourceDir, entry.name); + const targetPath = path.join(targetDir, entry.name); + + if (entry.isDirectory()) { + copyDirectory(sourcePath, targetPath); + return; + } + + if (entry.isSymbolicLink()) { + fs.symlinkSync(fs.readlinkSync(sourcePath), targetPath); + return; + } + + fs.copyFileSync(sourcePath, targetPath); + }); +}; + +const syncBuildDirectory = (sourceDir, targetDir) => { + fs.rmSync(targetDir, { recursive: true, force: true }); + copyDirectory(sourceDir, targetDir); +}; + +if (!fs.existsSync(reampv2AppBuildDir)) { + throw new Error(`Missing required reampv2 build output: ${reampv2AppBuildDir}`); +} + +// Publish the legacy top-level path from the reampv2 app bundle because AMP +// menu entries and JSP redirects still point to hash routes like +// /TEMPLATE/reampv2/build/index.html#/report_generator. +syncBuildDirectory(reampv2AppBuildDir, legacyBuildDir); +console.log(`Synced ${reampv2AppBuildDir} -> ${legacyBuildDir}`); + +if (fs.existsSync(ampOfflineBuildDir)) { + const legacyAmpOfflineDir = path.join(legacyBuildDir, 'ampoffline'); + syncBuildDirectory(ampOfflineBuildDir, legacyAmpOfflineDir); + console.log(`Synced ${ampOfflineBuildDir} -> ${legacyAmpOfflineDir}`); +} \ No newline at end of file diff --git a/amp/pom.xml b/amp/pom.xml index 194e7069210..5f73d802768 100644 --- a/amp/pom.xml +++ b/amp/pom.xml @@ -69,6 +69,14 @@ amp https://artifactory.dgdev.org/artifactory/amp/ + + + false + + central-fallback + Maven Central + https://repo.maven.apache.org/maven2 + @@ -79,6 +87,14 @@ amp https://artifactory.dgdev.org/artifactory/amp/ + + + false + + central-maven-plugins + Maven Central + https://repo.maven.apache.org/maven2 + @@ -1218,10 +1234,12 @@ com.google.cloud google-cloud-translate + 1.95.3 com.google.cloud google-cloud-storage + 1.111.2 org.apache.commons diff --git a/amp/src/main/java/org/digijava/module/aim/helper/GlobalSettingsConstants.java b/amp/src/main/java/org/digijava/module/aim/helper/GlobalSettingsConstants.java index bda43fae1e6..e32fbdbe05c 100644 --- a/amp/src/main/java/org/digijava/module/aim/helper/GlobalSettingsConstants.java +++ b/amp/src/main/java/org/digijava/module/aim/helper/GlobalSettingsConstants.java @@ -11,15 +11,12 @@ public class GlobalSettingsConstants { public static final List ECOWAS_COUNTRIES = Arrays.asList( "BJ", // Benin - "BF", // Burkina Faso "CI", // CΓ΄te d'Ivoire "GM", // Gambia "GH", // Ghana "GN", // Guinea "GW", // Guinea-Bissau "LR", // Liberia - "ML", // Mali - "NE", // Niger "NG", // Nigeria "SN", // Senegal "SL", // Sierra Leone @@ -278,6 +275,8 @@ public class GlobalSettingsConstants { public static final String EXEMPT_ORGANIZATION_DOCUMENTS = "Exempt organization to see documents"; public static final String AMP_DASHBOARD_URL = "AMP Dashboard URL"; + public static final String DONOR_FUNDING_ADM_LEVEL = "Donor Funding Administrative Level"; + public static final String DASHBOARD_CURRENCIES = "Dashboard Currencies"; public static final String NUMBER_OF_INDICATORS_IN_DASHBOARD = "Number of indicators in M&E Dashboard"; diff --git a/amp/src/main/java/org/digijava/module/message/jobs/AmpDonorFundingJob.java b/amp/src/main/java/org/digijava/module/message/jobs/AmpDonorFundingJob.java index a4b0907f7fe..3f10dc32ee6 100644 --- a/amp/src/main/java/org/digijava/module/message/jobs/AmpDonorFundingJob.java +++ b/amp/src/main/java/org/digijava/module/message/jobs/AmpDonorFundingJob.java @@ -86,11 +86,11 @@ private List processReportData(GeneratedReport report, String for (ReportArea statusData : location.getChildren()) { TextCell statusCell = (TextCell) statusData.getContents().get(status); for (ReportArea typeOfAssistanceData : statusData.getChildren()) { - //for (ReportArea reportSystemData : statusData.getChildren()) { + //for (ReportArea reportSystemData : statusData.getChildren()) { TextCell typeOfAssistanceCell= (TextCell) typeOfAssistanceData.getContents().get(typeOfAssistance); //TextCell reportSystemCell = (TextCell) reportSystemData.getContents().get(reportingSystem); for (ReportArea reportSystemData : typeOfAssistanceData.getChildren()) { - //for (ReportArea typeOfAssistanceData : reportSystemData.getChildren()) { + //for (ReportArea typeOfAssistanceData : reportSystemData.getChildren()) { TextCell reportSystemCell = (TextCell) reportSystemData.getContents().get(reportingSystem); //TextCell typeOfAssistanceCell = (TextCell) typeOfAssistanceData.getContents().get(typeOfAssistance); for (Map.Entry content : reportSystemData.getContents().entrySet()) { @@ -182,6 +182,7 @@ public static void sendReportsToServer(List ampDashboardFundin // Convert the ampDashboardFunding to JSON using a JSON library (e.g., Gson) Gson gson = new Gson(); String jsonData = gson.toJson(ampDashboardFunding); + logger.info("Data sent: "+jsonData); // Get the output stream of the connection try (OutputStream os = connection.getOutputStream()) { diff --git a/amp/src/main/resources/org/digijava/module/aim/dbentity/AmpStructure.hbm.xml b/amp/src/main/resources/org/digijava/module/aim/dbentity/AmpStructure.hbm.xml index 33f1df7da44..3ffa053e6b8 100644 --- a/amp/src/main/resources/org/digijava/module/aim/dbentity/AmpStructure.hbm.xml +++ b/amp/src/main/resources/org/digijava/module/aim/dbentity/AmpStructure.hbm.xml @@ -30,13 +30,13 @@ - - - - - - - + + + + + + + diff --git a/amp/src/main/resources/xmlpatches/4.0/AMP-31057-Add-Currecies-for-dashboard.xml b/amp/src/main/resources/xmlpatches/4.0/AMP-31057-Add-Currecies-for-dashboard.xml new file mode 100644 index 00000000000..635edfc0b86 --- /dev/null +++ b/amp/src/main/resources/xmlpatches/4.0/AMP-31057-Add-Currecies-for-dashboard.xml @@ -0,0 +1,17 @@ + + + AMP-31057 + bmokandu + Dashboard Currencies + + + + diff --git a/amp/src/main/resources/xmlpatches/4.0/AMP-31057-Donor-Funding-Admin-Levels-v1.xml b/amp/src/main/resources/xmlpatches/4.0/AMP-31057-Donor-Funding-Admin-Levels-v1.xml new file mode 100644 index 00000000000..5583bcd4502 --- /dev/null +++ b/amp/src/main/resources/xmlpatches/4.0/AMP-31057-Donor-Funding-Admin-Levels-v1.xml @@ -0,0 +1,82 @@ + + + AMP-31057 + Define possible values for Donor Funding Administrative Level and link to view + brianmokandu + + + + + \ No newline at end of file diff --git a/amp/src/main/resources/xmlpatches/4.0/GGW-207-Require-Login-For-GIS.xml b/amp/src/main/resources/xmlpatches/4.0/GGW-207-Require-Login-For-GIS.xml index 9abee5848ef..e557f87b790 100644 --- a/amp/src/main/resources/xmlpatches/4.0/GGW-207-Require-Login-For-GIS.xml +++ b/amp/src/main/resources/xmlpatches/4.0/GGW-207-Require-Login-For-GIS.xml @@ -10,7 +10,7 @@ diff --git a/amp/src/main/resources/xmlpatches/general/views/v_nationalobjectives_level_0.xml b/amp/src/main/resources/xmlpatches/general/views/v_nationalobjectives_level_0.xml index 43fefd92a60..c6b89086f88 100644 --- a/amp/src/main/resources/xmlpatches/general/views/v_nationalobjectives_level_0.xml +++ b/amp/src/main/resources/xmlpatches/general/views/v_nationalobjectives_level_0.xml @@ -34,8 +34,9 @@ JOIN ni_all_programs_with_levels apl ON a.amp_program_id = apl.amp_theme_id LEFT JOIN amp_theme level ON level.amp_theme_id = apl.id0 JOIN amp_program_settings aps ON aps.amp_program_settings_id = a.program_setting + WHERE aps.name = 'National Plan Objective' AND a.program_percentage <> 0 - GROUP BY a.amp_activity_id, apl.id0, level.amp_theme_id + GROUP BY a.amp_activity_id, apl.id0, level.amp_theme_id, level.name ) v ON aav.amp_activity_id = v.amp_activity_id; ]]> diff --git a/amp/src/main/resources/xmlpatches/general/views/v_nationalobjectives_level_1.xml b/amp/src/main/resources/xmlpatches/general/views/v_nationalobjectives_level_1.xml index f36df7d3a55..9f5e34f63cd 100644 --- a/amp/src/main/resources/xmlpatches/general/views/v_nationalobjectives_level_1.xml +++ b/amp/src/main/resources/xmlpatches/general/views/v_nationalobjectives_level_1.xml @@ -35,7 +35,8 @@ LEFT JOIN amp_theme level ON level.amp_theme_id = apl.id1 JOIN amp_program_settings aps ON aps.amp_program_settings_id = a.program_setting WHERE aps.name = 'National Plan Objective' AND a.program_percentage <> 0 - GROUP BY a.amp_activity_id, apl.id1, level.amp_theme_id + GROUP BY a.amp_activity_id, apl.id1, level.amp_theme_id, level.name + ) v ON aav.amp_activity_id = v.amp_activity_id; ]]> diff --git a/amp/src/main/resources/xmlpatches/general/views/v_nationalobjectives_level_2.xml b/amp/src/main/resources/xmlpatches/general/views/v_nationalobjectives_level_2.xml index 01aedd0a17d..1a705447e2e 100644 --- a/amp/src/main/resources/xmlpatches/general/views/v_nationalobjectives_level_2.xml +++ b/amp/src/main/resources/xmlpatches/general/views/v_nationalobjectives_level_2.xml @@ -35,7 +35,7 @@ LEFT JOIN amp_theme level ON level.amp_theme_id = apl.id2 JOIN amp_program_settings aps ON aps.amp_program_settings_id = a.program_setting WHERE aps.name = 'National Plan Objective' AND a.program_percentage <> 0 - GROUP BY a.amp_activity_id, apl.id2, level.amp_theme_id + GROUP BY a.amp_activity_id, apl.id2, level.amp_theme_id, level.name ) v ON aav.amp_activity_id = v.amp_activity_id; ]]> diff --git a/amp/src/main/resources/xmlpatches/general/views/v_nationalobjectives_level_3.xml b/amp/src/main/resources/xmlpatches/general/views/v_nationalobjectives_level_3.xml index 6b809d964e3..666abcb5971 100644 --- a/amp/src/main/resources/xmlpatches/general/views/v_nationalobjectives_level_3.xml +++ b/amp/src/main/resources/xmlpatches/general/views/v_nationalobjectives_level_3.xml @@ -35,7 +35,7 @@ LEFT JOIN amp_theme level ON level.amp_theme_id = apl.id3 JOIN amp_program_settings aps ON aps.amp_program_settings_id = a.program_setting WHERE aps.name = 'National Plan Objective' AND a.program_percentage <> 0 - GROUP BY a.amp_activity_id, apl.id3, level.amp_theme_id + GROUP BY a.amp_activity_id, apl.id3, level.amp_theme_id, level.name ) v ON aav.amp_activity_id = v.amp_activity_id; ]]> diff --git a/amp/src/main/resources/xmlpatches/general/views/v_nationalobjectives_level_4.xml b/amp/src/main/resources/xmlpatches/general/views/v_nationalobjectives_level_4.xml index 2b98031b254..1a1df57542e 100644 --- a/amp/src/main/resources/xmlpatches/general/views/v_nationalobjectives_level_4.xml +++ b/amp/src/main/resources/xmlpatches/general/views/v_nationalobjectives_level_4.xml @@ -35,7 +35,7 @@ LEFT JOIN amp_theme level ON level.amp_theme_id = apl.id4 JOIN amp_program_settings aps ON aps.amp_program_settings_id = a.program_setting WHERE aps.name = 'National Plan Objective' AND a.program_percentage <> 0 - GROUP BY a.amp_activity_id, apl.id4, level.amp_theme_id + GROUP BY a.amp_activity_id, apl.id4, level.amp_theme_id, level.name ) v ON aav.amp_activity_id = v.amp_activity_id; ]]> diff --git a/amp/src/main/resources/xmlpatches/general/views/v_nationalobjectives_level_5.xml b/amp/src/main/resources/xmlpatches/general/views/v_nationalobjectives_level_5.xml index 82ad2f269d8..026a9df9c89 100644 --- a/amp/src/main/resources/xmlpatches/general/views/v_nationalobjectives_level_5.xml +++ b/amp/src/main/resources/xmlpatches/general/views/v_nationalobjectives_level_5.xml @@ -35,7 +35,7 @@ LEFT JOIN amp_theme level ON level.amp_theme_id = apl.id5 JOIN amp_program_settings aps ON aps.amp_program_settings_id = a.program_setting WHERE aps.name = 'National Plan Objective' AND a.program_percentage <> 0 - GROUP BY a.amp_activity_id, apl.id5, level.amp_theme_id + GROUP BY a.amp_activity_id, apl.id5, level.amp_theme_id, level.name ) v ON aav.amp_activity_id = v.amp_activity_id; ]]> diff --git a/amp/src/main/resources/xmlpatches/general/views/v_nationalobjectives_level_6.xml b/amp/src/main/resources/xmlpatches/general/views/v_nationalobjectives_level_6.xml index 233721be0a3..11072b38157 100644 --- a/amp/src/main/resources/xmlpatches/general/views/v_nationalobjectives_level_6.xml +++ b/amp/src/main/resources/xmlpatches/general/views/v_nationalobjectives_level_6.xml @@ -35,7 +35,7 @@ LEFT JOIN amp_theme level ON level.amp_theme_id = apl.id6 JOIN amp_program_settings aps ON aps.amp_program_settings_id = a.program_setting WHERE aps.name = 'National Plan Objective' AND a.program_percentage <> 0 - GROUP BY a.amp_activity_id, apl.id6, level.amp_theme_id + GROUP BY a.amp_activity_id, apl.id6, level.amp_theme_id, level.name ) v ON aav.amp_activity_id = v.amp_activity_id; ]]> diff --git a/amp/src/main/resources/xmlpatches/general/views/v_nationalobjectives_level_7.xml b/amp/src/main/resources/xmlpatches/general/views/v_nationalobjectives_level_7.xml index 2f48ce56fec..ab2478faf2d 100644 --- a/amp/src/main/resources/xmlpatches/general/views/v_nationalobjectives_level_7.xml +++ b/amp/src/main/resources/xmlpatches/general/views/v_nationalobjectives_level_7.xml @@ -35,7 +35,7 @@ LEFT JOIN amp_theme level ON level.amp_theme_id = apl.id7 JOIN amp_program_settings aps ON aps.amp_program_settings_id = a.program_setting WHERE aps.name = 'National Plan Objective' AND a.program_percentage <> 0 - GROUP BY a.amp_activity_id, apl.id7, level.amp_theme_id + GROUP BY a.amp_activity_id, apl.id7, level.amp_theme_id, level.name ) v ON aav.amp_activity_id = v.amp_activity_id; ]]> diff --git a/amp/src/main/resources/xmlpatches/general/views/v_nationalobjectives_level_8.xml b/amp/src/main/resources/xmlpatches/general/views/v_nationalobjectives_level_8.xml index 5b4995b947c..ad89e36caa9 100644 --- a/amp/src/main/resources/xmlpatches/general/views/v_nationalobjectives_level_8.xml +++ b/amp/src/main/resources/xmlpatches/general/views/v_nationalobjectives_level_8.xml @@ -35,7 +35,7 @@ LEFT JOIN amp_theme level ON level.amp_theme_id = apl.id8 JOIN amp_program_settings aps ON aps.amp_program_settings_id = a.program_setting WHERE aps.name = 'National Plan Objective' AND a.program_percentage <> 0 - GROUP BY a.amp_activity_id, apl.id8, level.amp_theme_id + GROUP BY a.amp_activity_id, apl.id8, level.amp_theme_id, level.name ) v ON aav.amp_activity_id = v.amp_activity_id; ]]> diff --git a/amp/src/main/resources/xmlpatches/general/views/v_pledges_tertiary_programs_level_3.xml b/amp/src/main/resources/xmlpatches/general/views/v_pledges_tertiary_programs_level_3.xml index 85568d8da9f..674aebf889b 100644 --- a/amp/src/main/resources/xmlpatches/general/views/v_pledges_tertiary_programs_level_3.xml +++ b/amp/src/main/resources/xmlpatches/general/views/v_pledges_tertiary_programs_level_3.xml @@ -39,7 +39,7 @@ JOIN all_programs_with_levels oapl ON afpp.amp_program_id = oapl.amp_theme_id LEFT JOIN amp_theme level ON level.amp_theme_id = apl.id3 WHERE oapl.program_setting_name = 'Tertiary Program' - GROUP BY afpp.pledge_id, apl.id3, level.amp_theme_id,level.name + GROUP BY afpp.pledge_id, apl.id3, level.amp_theme_id,level.name ) v ON afp.id = v.pledge_id; diff --git a/amp/src/main/webapp/WEB-INF/jsp/esrigis/view/mainmapPopup.jsp b/amp/src/main/webapp/WEB-INF/jsp/esrigis/view/mainmapPopup.jsp index 11f81be6a2b..0d46ab42cee 100644 --- a/amp/src/main/webapp/WEB-INF/jsp/esrigis/view/mainmapPopup.jsp +++ b/amp/src/main/webapp/WEB-INF/jsp/esrigis/view/mainmapPopup.jsp @@ -86,15 +86,15 @@