feat(sidebar): sort by recency, unread indicator, last-active date #148
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Deploy | |
| on: | |
| push: | |
| branches: | |
| - master | |
| pull_request: | |
| types: [opened, synchronize] | |
| permissions: | |
| contents: read | |
| id-token: write | |
| deployments: write | |
| pull-requests: write | |
| jobs: | |
| deploy: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| # Create GitHub Deployment (Preview) | |
| - name: Create GitHub Deployment | |
| if: github.event_name == 'pull_request' | |
| uses: actions/github-script@v7 | |
| id: create_deployment | |
| with: | |
| script: | | |
| const { owner, repo } = context.repo; | |
| const ref = context.payload.pull_request.head.ref; | |
| try { | |
| const deployment = await github.rest.repos.createDeployment({ | |
| owner, | |
| repo, | |
| ref, | |
| environment: `pr-${context.issue.number}`, | |
| auto_merge: false, | |
| required_contexts: [], | |
| transient_environment: true, | |
| description: `Preview for PR #${context.issue.number}` | |
| }); | |
| if (deployment.status === 201) { | |
| return deployment.data.id; | |
| } | |
| console.log('Deployment creation returned status:', deployment.status); | |
| return null; | |
| } catch (error) { | |
| console.error('Error creating deployment:', error); | |
| return null; | |
| } | |
| - name: Set Deployment Status Pending | |
| if: github.event_name == 'pull_request' && steps.create_deployment.outputs.result != 'null' | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const deployment_id = ${{ steps.create_deployment.outputs.result }}; | |
| await github.rest.repos.createDeploymentStatus({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| deployment_id: deployment_id, | |
| state: 'pending', | |
| description: 'Deploying to Cloud Run preview...', | |
| }); | |
| - id: 'auth' | |
| uses: 'google-github-actions/auth@v2' | |
| with: | |
| workload_identity_provider: '${{ secrets.GC_WORKLOAD_IDENTITY_PROVIDER }}' | |
| service_account: '${{ secrets.GC_SERVICE_ACCOUNT }}' | |
| - name: Set up Cloud SDK | |
| uses: google-github-actions/setup-gcloud@v2 | |
| - name: Configure Docker | |
| run: gcloud auth configure-docker asia-east1-docker.pkg.dev | |
| - name: Build and Push Images | |
| env: | |
| PROJECT_ID: ${{ secrets.GC_PROJECT_ID }} | |
| SHA: ${{ github.sha }} | |
| run: | | |
| # Build frontend | |
| docker build \ | |
| --build-arg VITE_LANGFUSE_PUBLIC_KEY=${{ secrets.LANGFUSE_PUBLIC_KEY }} \ | |
| --build-arg VITE_LANGFUSE_BASE_URL="https://langfuse.cofacts.tw" \ | |
| -t asia-east1-docker.pkg.dev/$PROJECT_ID/cofacts-ai/frontend:$SHA -f Dockerfile . | |
| docker push asia-east1-docker.pkg.dev/$PROJECT_ID/cofacts-ai/frontend:$SHA | |
| # Build backend | |
| docker build -t asia-east1-docker.pkg.dev/$PROJECT_ID/cofacts-ai/backend:$SHA -f adk/Dockerfile adk | |
| docker push asia-east1-docker.pkg.dev/$PROJECT_ID/cofacts-ai/backend:$SHA | |
| echo "FRONTEND_IMAGE=asia-east1-docker.pkg.dev/$PROJECT_ID/cofacts-ai/frontend:$SHA" >> $GITHUB_ENV | |
| echo "BACKEND_IMAGE=asia-east1-docker.pkg.dev/$PROJECT_ID/cofacts-ai/backend:$SHA" >> $GITHUB_ENV | |
| - name: Prepare Traffic Block | |
| id: traffic | |
| run: | | |
| # Fetch the current traffic state so we can preserve existing tags | |
| CURRENT_TRAFFIC=$(gcloud run services describe cofacts-ai --region asia-east1 --format="json" 2>/dev/null || echo '{"status":{"traffic":[]}}') | |
| if [ "${{ github.event_name }}" == "pull_request" ]; then | |
| TAG="pr-${{ github.event.number }}" | |
| # Find the revision currently handling 100% traffic | |
| CURRENT_100_REV=$(echo "$CURRENT_TRAFFIC" | jq -r '(.status.traffic // []) | map(select(.percent == 100))[0].revisionName // empty') | |
| if [ -z "$CURRENT_100_REV" ]; then | |
| # First deployment or no 100% revision, default 100% to the new revision and tag it | |
| TRAFFIC_JSON="[{\"latestRevision\": true, \"percent\": 100, \"tag\": \"$TAG\"}]" | |
| else | |
| # Extract all existing tags except the current PR tag so we update it safely | |
| EXISTING_TAGS=$(echo "$CURRENT_TRAFFIC" | jq -c "[ (.status.traffic // [])[] | select(.tag != null and .tag != \"$TAG\") | {revisionName: .revisionName, tag: .tag, percent: 0} ]") | |
| # Combine them together: maintain current 100% revision, tag the new revision, and keep existing tags | |
| TRAFFIC_JSON=$(jq -n -c --arg rev "$CURRENT_100_REV" --arg tag "$TAG" --argjson extags "$EXISTING_TAGS" \ | |
| '[{"revisionName": $rev, "percent": 100}, {"latestRevision": true, "percent": 0, "tag": $tag}] + $extags') | |
| fi | |
| else | |
| # Master: Send 100% to the new revision, and preserve all existing tags by keeping them with 0% traffic | |
| EXISTING_TAGS=$(echo "$CURRENT_TRAFFIC" | jq -c "[ (.status.traffic // [])[] | select(.tag != null) | {revisionName: .revisionName, tag: .tag, percent: 0} ]") | |
| TRAFFIC_JSON=$(jq -n -c --argjson extags "$EXISTING_TAGS" \ | |
| '[{"latestRevision": true, "percent": 100}] + $extags') | |
| fi | |
| echo "TRAFFIC_BLOCK=traffic: $TRAFFIC_JSON" >> $GITHUB_ENV | |
| - name: Generate Service YAML | |
| env: | |
| GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }} | |
| LANGFUSE_PUBLIC_KEY: ${{ secrets.LANGFUSE_PUBLIC_KEY }} | |
| LANGFUSE_SECRET_KEY: ${{ secrets.LANGFUSE_SECRET_KEY }} | |
| # Cofacts.ai's PostgreSQL connection string | |
| # postgresql+asyncpg://<sa>%40<project>.iam@localhost/<db> | |
| DATABASE_URL: ${{ secrets.DATABASE_URL }} | |
| # Service account that is dedicated to Cofacts.ai (<name>@<project>.iam.gserviceaccount.com) | |
| # GC_SERVICE_ACCOUNT has been configured so that it can act as SERVICE_ACCOUNT_EMAIL. | |
| SERVICE_ACCOUNT_EMAIL: ${{ secrets.SERVICE_ACCOUNT_EMAIL }} | |
| GC_PROJECT_ID: ${{ secrets.GC_PROJECT_ID }} | |
| run: | | |
| envsubst '${FRONTEND_IMAGE} ${BACKEND_IMAGE} ${TRAFFIC_BLOCK} ${GOOGLE_API_KEY} ${LANGFUSE_PUBLIC_KEY} ${LANGFUSE_SECRET_KEY} ${DATABASE_URL} ${SERVICE_ACCOUNT_EMAIL} ${GC_PROJECT_ID}' < service.template.yaml > service.yaml | |
| cat service.yaml | |
| - name: Deploy to Cloud Run | |
| run: | | |
| gcloud run services replace service.yaml --region asia-east1 | |
| - name: Get Preview URL | |
| if: github.event_name == 'pull_request' | |
| id: get_url | |
| run: | | |
| TAG="pr-${{ github.event.number }}" | |
| # Try to get URL from status.traffic | |
| URL=$(gcloud run services describe cofacts-ai --region asia-east1 --format="value(status.traffic.filter(tag='$TAG').url)" | head -n 1) | |
| if [ -z "$URL" ]; then | |
| # Fallback: construct from base URL | |
| BASE_URL=$(gcloud run services describe cofacts-ai --region asia-east1 --format="value(status.url)") | |
| # Basic construction | |
| URL="${BASE_URL/https:\/\//https:\/\/$TAG---}" | |
| fi | |
| echo "PREVIEW_URL=$URL" >> $GITHUB_ENV | |
| - name: Set Deployment Status Success | |
| if: github.event_name == 'pull_request' && success() && steps.create_deployment.outputs.result != 'null' | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const deployment_id = ${{ steps.create_deployment.outputs.result }}; | |
| const preview_url = process.env.PREVIEW_URL; | |
| await github.rest.repos.createDeploymentStatus({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| deployment_id: deployment_id, | |
| state: 'success', | |
| environment_url: preview_url, | |
| description: 'Preview is ready!', | |
| }); | |
| - name: Set Deployment Status Failure | |
| if: github.event_name == 'pull_request' && failure() && steps.create_deployment.outputs.result != 'null' | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const deployment_id = ${{ steps.create_deployment.outputs.result }}; | |
| await github.rest.repos.createDeploymentStatus({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| deployment_id: deployment_id, | |
| state: 'failure', | |
| description: 'Deployment failed.', | |
| }); |