Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
9e0d879
Refactor GitHub Actions workflow for Nexus image building; update rep…
iabouhashish Nov 13, 2025
7c6198c
Enhance GitHub Actions workflow for Nexus by replacing the login acti…
iabouhashish Nov 13, 2025
c19724b
Refactor GitHub Actions workflow to streamline Docker image build and…
iabouhashish Nov 13, 2025
80388a9
Update Next.js configuration to enable standalone output and upgrade …
iabouhashish Nov 13, 2025
162ab5e
Add curl installation to Dockerfile for health checks; update routing…
iabouhashish Nov 13, 2025
91fddfc
Refactor API endpoints to remove '/api/v1' prefix, update environment…
iabouhashish Nov 13, 2025
3051149
Enhance GitHub Actions workflow for AWS deployment by adding support …
iabouhashish Nov 14, 2025
c18d182
Refactor CloudFormation templates to replace CIDR parameters with exi…
iabouhashish Nov 14, 2025
33be781
Implement dynamic rendering for the Pipeline Steps page to support us…
iabouhashish Nov 14, 2025
aa9d075
Wrap StepExecutor component in Suspense with a fallback loading indic…
iabouhashish Nov 14, 2025
37beaa9
Disable health checks in the CloudFormation template for the marketin…
iabouhashish Nov 14, 2025
b3006c7
Enhance package.json with new clean and dev:clean scripts, enable hea…
iabouhashish Nov 17, 2025
0398e78
s3_content source added to content selector
iabouhashish Nov 17, 2025
2fed2bc
updated build-push-nexus.yml
iabouhashish Nov 17, 2025
7a10771
Remove unused LinearProgress component and refactor Grid layout in Pi…
iabouhashish Nov 17, 2025
ce7b1e1
Refactor ResultsPage to utilize apiClient for API calls, removing red…
iabouhashish Nov 17, 2025
0bb615d
Refactor API calls in EnhancedTimeline and JobHierarchyTree component…
iabouhashish Nov 18, 2025
4afab22
Update ResultsPage to use response.data instead of response.json() fo…
iabouhashish Nov 18, 2025
a278c13
managing polling for approvals
iabouhashish Nov 18, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
173 changes: 77 additions & 96 deletions .github/workflows/build-push-nexus.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,6 @@ on:
required: false
type: string
default: 'latest'
backend_api_url:
description: 'Backend API URL (optional - defaults to relative /api for same-domain deployment)'
required: false
type: string
backend_websocket_url:
description: 'Backend WebSocket URL (optional - auto-inferred if not provided)'
required: false
type: string
push:
branches:
- main
Expand All @@ -26,16 +18,16 @@ on:
- main

env:
NEXUS_REGISTRY: repository.arthur.ai
# If your Nexus uses a repository path, set it here (e.g., "docker-hosted" or "docker")
# Leave empty if no repository path is needed
NEXUS_REPOSITORY: ''
NEXUS_REPOSITORY: arthur
NEXUS_NAMESPACE: marketing-tool
IMAGE_NAME: marketing-frontend

jobs:
build-and-push:
name: Build and Push to Nexus
runs-on: ubuntu-latest
permissions:
contents: read
# Only push on main branch or tags, not on PRs
if: github.event_name != 'pull_request'

Expand All @@ -49,20 +41,46 @@ jobs:
node-version: '18'
cache: 'npm'

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Determine image version
id: version
env:
BACKEND_REPO: ${{ secrets.BACKEND_REPO || 'marketing_tool' }}
BACKEND_REPO_OWNER: ${{ secrets.BACKEND_REPO_OWNER || github.repository_owner }}
GITHUB_TOKEN: ${{ github.token }}
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
VERSION="${{ github.event.inputs.version }}"
elif [ "${{ github.event_name }}" = "push" ] && [[ "${{ github.ref }}" == refs/tags/* ]]; then
# Extract version from tag (e.g., frontend-v1.2.3 -> v1.2.3)
VERSION=$(echo "${{ github.ref }}" | sed 's/refs\/tags\/frontend-//')
elif [ "${{ github.event_name }}" = "push" ] && [ "${{ github.ref }}" = "refs/heads/main" ]; then
# Use commit SHA for main branch
VERSION="main-$(git rev-parse --short HEAD)"
# Try to get the latest version tag from backend repository
echo "Fetching latest version from backend repository..."

# Use GitHub API to get all v*.*.* tags from backend repo and sort by version
TAGS_JSON=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \
"https://api.github.com/repos/${BACKEND_REPO_OWNER}/${BACKEND_REPO}/tags")

# Extract and sort version tags (v*.*.* format) by version number
LATEST_TAG=$(echo "$TAGS_JSON" | \
jq -r '.[] | select(.name | test("^v[0-9]+\\.[0-9]+\\.[0-9]+$")) | .name' | \
sort -V -r | head -n 1)

if [ -n "$LATEST_TAG" ] && [ "$LATEST_TAG" != "null" ] && [ "$LATEST_TAG" != "" ]; then
echo "✅ Found backend version tag: $LATEST_TAG"
# Increment patch version by 0.0.1 (increment last number by 1)
# Extract version numbers (e.g., v1.2.3 -> 1 2 3)
VERSION_PARTS=$(echo "$LATEST_TAG" | sed 's/^v//' | tr '.' ' ')
MAJOR=$(echo $VERSION_PARTS | cut -d' ' -f1)
MINOR=$(echo $VERSION_PARTS | cut -d' ' -f2)
PATCH=$(echo $VERSION_PARTS | cut -d' ' -f3)
# Increment patch version
PATCH=$((PATCH + 1))
VERSION="v${MAJOR}.${MINOR}.${PATCH}"
else
echo "⚠️ No backend version tag found, falling back to commit SHA"
VERSION="main-$(git rev-parse --short HEAD)"
fi
else
VERSION="latest"
fi
Expand All @@ -75,106 +93,69 @@ jobs:
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "Using version: $VERSION"

- name: Get backend URLs (optional)
id: backend-urls
- name: Build Docker image
run: |
# These are optional - if not provided, the frontend will use relative URLs (/api)
# Only needed for development or cross-domain deployments
BACKEND_API_URL="${{ github.event.inputs.backend_api_url || secrets.BACKEND_API_URL || '' }}"
BACKEND_WS_URL="${{ github.event.inputs.backend_websocket_url || secrets.BACKEND_WEBSOCKET_URL || '' }}"
docker build \
--build-arg NEXT_PUBLIC_BACKEND_API_BASE_URL=/api \
--build-arg NEXT_PUBLIC_BACKEND_WEBSOCKET_URL=/api \
-t marketing-frontend:${{ steps.version.outputs.version }} \
-f ./deploy/Dockerfile \
.

- name: Login to Artifactory
run: |
docker logout
echo "${{ secrets.NEXUS_PASSWORD }}" | docker login docker.arthur.ai -u "${{ secrets.NEXUS_USERNAME }}" --password-stdin

- name: Retag and push image to Artifactory
run: |
ARTIFACTORY_IMAGE="docker.arthur.ai/${{ env.NEXUS_REPOSITORY }}/${{ env.NEXUS_NAMESPACE }}/${{ env.IMAGE_NAME }}"

if [ -z "$BACKEND_API_URL" ]; then
# Use relative URL for same-domain deployment
BACKEND_API_URL="/api"
echo "ℹ️ No backend API URL provided - will use relative URL: $BACKEND_API_URL"
else
echo "Using provided Backend API URL: $BACKEND_API_URL"
fi
docker tag marketing-frontend:${{ steps.version.outputs.version }} ${ARTIFACTORY_IMAGE}:${{ steps.version.outputs.version }}
docker push ${ARTIFACTORY_IMAGE}:${{ steps.version.outputs.version }}

if [ -z "$BACKEND_WS_URL" ]; then
# Try to infer from API URL
if [[ "$BACKEND_API_URL" == http://* ]]; then
BACKEND_WS_URL=$(echo "$BACKEND_API_URL" | sed 's/http:/ws:/')
elif [[ "$BACKEND_API_URL" == https://* ]]; then
BACKEND_WS_URL=$(echo "$BACKEND_API_URL" | sed 's/https:/wss:/')
else
# Relative URL for WebSocket
BACKEND_WS_URL="/api"
fi
if [ "${{ github.ref }}" = "refs/heads/main" ]; then
docker tag marketing-frontend:${{ steps.version.outputs.version }} ${ARTIFACTORY_IMAGE}:latest
docker push ${ARTIFACTORY_IMAGE}:latest
fi

echo "backend_api_url=$BACKEND_API_URL" >> $GITHUB_OUTPUT
echo "backend_ws_url=$BACKEND_WS_URL" >> $GITHUB_OUTPUT
echo "Backend API: $BACKEND_API_URL"
echo "Backend WebSocket: $BACKEND_WS_URL"

- name: Build image path
id: image-path
run: |
if [ -n "${{ env.NEXUS_REPOSITORY }}" ]; then
IMAGE_PATH="${{ env.NEXUS_REGISTRY }}/${{ env.NEXUS_REPOSITORY }}/${{ env.IMAGE_NAME }}"
else
IMAGE_PATH="${{ env.NEXUS_REGISTRY }}/${{ env.IMAGE_NAME }}"
fi
echo "image_path=$IMAGE_PATH" >> $GITHUB_OUTPUT
echo "Image path: $IMAGE_PATH"

- name: Login to Nexus
uses: docker/login-action@v3
with:
registry: ${{ env.NEXUS_REGISTRY }}
username: ${{ secrets.NEXUS_USERNAME }}
password: ${{ secrets.NEXUS_PASSWORD }}

- name: Build and push Docker image
id: build-push
uses: docker/build-push-action@v6
with:
context: .
file: ./deploy/Dockerfile
push: true
tags: |
${{ steps.image-path.outputs.image_path }}:${{ steps.version.outputs.version }}
${{ steps.image-path.outputs.image_path }}:latest
cache-from: type=gha
cache-to: type=gha,mode=max
platforms: linux/amd64
build-args: |
NEXT_PUBLIC_BACKEND_API_BASE_URL=${{ steps.backend-urls.outputs.backend_api_url }}
NEXT_PUBLIC_BACKEND_WEBSOCKET_URL=${{ steps.backend-urls.outputs.backend_ws_url }}
# Store image path for output
echo "image_path=${ARTIFACTORY_IMAGE}" >> $GITHUB_ENV

- name: Output image information
run: |
IMAGE_PATH="${{ steps.image-path.outputs.image_path }}"

echo "✅ Successfully pushed image to Nexus"
echo "Image: ${IMAGE_PATH}:${{ steps.version.outputs.version }}"
echo "Image (latest): ${IMAGE_PATH}:latest"
ARTIFACTORY_IMAGE="docker.arthur.ai/${{ env.NEXUS_REPOSITORY }}/${{ env.NEXUS_NAMESPACE }}/${{ env.IMAGE_NAME }}"

REPO_INFO=""
if [ -n "${{ env.NEXUS_REPOSITORY }}" ]; then
REPO_INFO="**Repository:** \`${{ env.NEXUS_REPOSITORY }}\`\n"
echo "✅ Successfully pushed image to Artifactory"
echo "Image: ${ARTIFACTORY_IMAGE}:${{ steps.version.outputs.version }}"
if [ "${{ github.ref }}" = "refs/heads/main" ]; then
echo "Image (latest): ${ARTIFACTORY_IMAGE}:latest"
fi

cat >> $GITHUB_STEP_SUMMARY << EOF
## 🐳 Docker Image Pushed to Nexus
## 🐳 Docker Image Pushed to Artifactory

**Registry:** \`${{ env.NEXUS_REGISTRY }}\`
${REPO_INFO}**Image:** \`${{ env.IMAGE_NAME }}\`
**Registry:** \`docker.arthur.ai\`
**Repository:** \`${{ env.NEXUS_REPOSITORY }}/${{ env.NEXUS_NAMESPACE }}\`
**Image:** \`${{ env.IMAGE_NAME }}\`
**Version:** \`${{ steps.version.outputs.version }}\`

### 📦 Image Tags
- \`${IMAGE_PATH}:${{ steps.version.outputs.version }}\`
- \`${IMAGE_PATH}:latest\`
- \`${ARTIFACTORY_IMAGE}:${{ steps.version.outputs.version }}\`
EOF

### 🔧 Build Configuration
- **Backend API URL:** ${{ steps.backend-urls.outputs.backend_api_url }}
- **Backend WebSocket URL:** ${{ steps.backend-urls.outputs.backend_ws_url }}
if [ "${{ github.ref }}" = "refs/heads/main" ]; then
cat >> $GITHUB_STEP_SUMMARY << EOF
- \`${ARTIFACTORY_IMAGE}:latest\`
EOF
fi

cat >> $GITHUB_STEP_SUMMARY << EOF

### 📝 Usage
To pull this image:
\`\`\`bash
docker pull ${IMAGE_PATH}:${{ steps.version.outputs.version }}
docker pull ${ARTIFACTORY_IMAGE}:${{ steps.version.outputs.version }}
\`\`\`
EOF

111 changes: 105 additions & 6 deletions .github/workflows/deploy-aws.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,18 @@ on:
required: false
default: 'us-east-1'
backend_api_url:
description: 'Backend API URL (e.g., https://api.example.com)'
required: true
description: 'Backend API URL (e.g., https://api.example.com). Leave empty to auto-detect from backend stack.'
required: false
type: string
backend_websocket_url:
description: 'Backend WebSocket URL (e.g., wss://api.example.com)'
description: 'Backend WebSocket URL (e.g., wss://api.example.com). Leave empty to auto-detect.'
required: false
type: string
wait_for_backend:
description: 'Wait for backend deployment to complete before deploying frontend'
required: false
type: boolean
default: true
push:
tags:
- 'frontend-v*.*.*'
Expand All @@ -34,8 +39,33 @@ env:
PROJECT_NAME: marketing-frontend

jobs:
# Map frontend environment names to backend environment names
map-environment:
runs-on: ubuntu-latest
outputs:
backend_env: ${{ steps.map.outputs.backend_env }}
steps:
- id: map
run: |
case "${{ env.ENVIRONMENT }}" in
development)
echo "backend_env=dev" >> $GITHUB_OUTPUT
;;
staging)
echo "backend_env=stag" >> $GITHUB_OUTPUT
;;
production)
echo "backend_env=prod" >> $GITHUB_OUTPUT
;;
*)
echo "backend_env=${{ env.ENVIRONMENT }}" >> $GITHUB_OUTPUT
;;
esac

deploy:
needs: map-environment
name: Deploy Frontend to AWS

runs-on: ubuntu-latest
permissions:
contents: read
Expand Down Expand Up @@ -80,14 +110,83 @@ jobs:
type=sha,prefix={{branch}}-
type=raw,value=latest,enable={{is_default_branch}}

- name: Wait for backend deployment (if requested)
if: ${{ github.event.inputs.wait_for_backend == 'true' }}
run: |
BACKEND_STACK_NAME="marketing-tool-${{ needs.map-environment.outputs.backend_env }}"
echo "Waiting for backend stack to be ready: $BACKEND_STACK_NAME"

# Wait for stack to exist and be in a stable state
MAX_WAIT=1800 # 30 minutes
ELAPSED=0

while [ $ELAPSED -lt $MAX_WAIT ]; do
if aws cloudformation describe-stacks --stack-name "$BACKEND_STACK_NAME" --region ${{ env.AWS_REGION }} &> /dev/null; then
STACK_STATUS=$(aws cloudformation describe-stacks \
--stack-name "$BACKEND_STACK_NAME" \
--query 'Stacks[0].StackStatus' \
--output text \
--region ${{ env.AWS_REGION }})

echo "Backend stack status: $STACK_STATUS"

if [[ "$STACK_STATUS" == "CREATE_COMPLETE" ]] || [[ "$STACK_STATUS" == "UPDATE_COMPLETE" ]]; then
echo "✅ Backend stack is ready!"
break
elif [[ "$STACK_STATUS" == *"FAILED"* ]] || [[ "$STACK_STATUS" == *"ROLLBACK"* ]]; then
echo "❌ Backend stack failed: $STACK_STATUS"
exit 1
fi
else
echo "Backend stack does not exist yet, waiting..."
fi

sleep 30
ELAPSED=$((ELAPSED + 30))
done

if [ $ELAPSED -ge $MAX_WAIT ]; then
echo "❌ Timeout waiting for backend stack"
exit 1
fi

# Wait for backend ECS service to be stable
BACKEND_CLUSTER="marketing-tool-${{ needs.map-environment.outputs.backend_env }}-cluster"
BACKEND_SERVICE="marketing-tool-${{ needs.map-environment.outputs.backend_env }}-service"

echo "Waiting for backend ECS service to be stable..."
aws ecs wait services-stable \
--cluster "$BACKEND_CLUSTER" \
--services "$BACKEND_SERVICE" \
--region ${{ env.AWS_REGION }} || echo "⚠️ Backend service check skipped (may not exist)"

- name: Get backend URLs
id: backend-urls
run: |
BACKEND_API_URL="${{ github.event.inputs.backend_api_url || secrets.BACKEND_API_URL }}"
BACKEND_WS_URL="${{ github.event.inputs.backend_websocket_url || secrets.BACKEND_WEBSOCKET_URL || format('wss://{0}', github.event.inputs.backend_api_url) }}"
# Try to get backend URL from CloudFormation stack outputs first
BACKEND_STACK_NAME="marketing-tool-${{ needs.map-environment.outputs.backend_env }}"

if aws cloudformation describe-stacks --stack-name "$BACKEND_STACK_NAME" --region ${{ env.AWS_REGION }} &> /dev/null; then
# Get ALB URL from stack outputs
ALB_URL=$(aws cloudformation describe-stacks \
--stack-name "$BACKEND_STACK_NAME" \
--query 'Stacks[0].Outputs[?OutputKey==`ALBURL`].OutputValue' \
--output text \
--region ${{ env.AWS_REGION }} || echo "")

if [ -n "$ALB_URL" ]; then
BACKEND_API_URL="https://$ALB_URL/api"
BACKEND_WS_URL="wss://$ALB_URL/api"
echo "✅ Auto-detected backend URLs from stack outputs"
fi
fi

# Use provided URLs or fall back to secrets
BACKEND_API_URL="${BACKEND_API_URL:-${{ github.event.inputs.backend_api_url || secrets.BACKEND_API_URL }}}"
BACKEND_WS_URL="${BACKEND_WS_URL:-${{ github.event.inputs.backend_websocket_url || secrets.BACKEND_WEBSOCKET_URL || format('wss://{0}', github.event.inputs.backend_api_url) }}}"

if [ -z "$BACKEND_API_URL" ]; then
echo "❌ Backend API URL is required"
echo "❌ Backend API URL is required. Please provide it as input or set BACKEND_API_URL secret."
exit 1
fi

Expand Down
Loading