name: Visual Regression Dispatch # Dispatches visual regression processing to ci-privileged after CI completes. # Runs in the context of the base repo (not the fork) so it has access to secrets, # making it work for external contributor PRs. # # All dispatches use the same event_type=visual-regression with project/artifact_name # in the payload. ci-privileged routes to the correct Argos project based on these. on: workflow_run: workflows: ['CI UI', 'CI New UI'] types: [completed] permissions: actions: read contents: read pull-requests: read jobs: resolve-context: if: github.event.workflow_run.conclusion == 'success' runs-on: ubuntu-latest timeout-minutes: 5 outputs: workflow_name: ${{ steps.context.outputs.workflow_name }} artifact_name: ${{ steps.context.outputs.artifact_name }} has_artifact: ${{ steps.check-artifact.outputs.exists }} is_pr: ${{ steps.pr-info.outputs.has_pr }} pr_number: ${{ steps.pr-info.outputs.pr_number }} merge_base_sha: ${{ steps.merge-base.outputs.sha }} steps: - name: Resolve workflow context id: context uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 with: script: | const name = context.payload.workflow_run.name; const artifactName = name === 'CI New UI' ? 'argos-screenshots-twenty-new-ui' : 'argos-screenshots-twenty-ui'; core.setOutput('workflow_name', name); core.setOutput('artifact_name', artifactName); core.info(`Workflow: ${name}, artifact: ${artifactName}`); - name: Check if screenshots artifact exists id: check-artifact uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 with: script: | const artifactName = '${{ steps.context.outputs.artifact_name }}'; const runId = context.payload.workflow_run.id; const { data: artifacts } = await github.rest.actions.listWorkflowRunArtifacts({ owner: context.repo.owner, repo: context.repo.repo, run_id: runId, }); const found = artifacts.artifacts.some(a => a.name === artifactName); core.setOutput('exists', found ? 'true' : 'false'); if (!found) { core.info(`Artifact "${artifactName}" not found in run ${runId} — skipping`); } - name: Get PR number if: >- steps.check-artifact.outputs.exists == 'true' && github.event.workflow_run.event == 'pull_request' id: pr-info uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 with: script: | const headBranch = context.payload.workflow_run.head_branch; const headRepo = context.payload.workflow_run.head_repository; let pullRequests = context.payload.workflow_run.pull_requests; let prNumber; if (pullRequests && pullRequests.length > 0) { prNumber = pullRequests[0].number; } else { const headLabel = `${headRepo.owner.login}:${headBranch}`; core.info(`Searching for PR by head label: ${headLabel}`); const { data: prs } = await github.rest.pulls.list({ owner: context.repo.owner, repo: context.repo.repo, state: 'open', head: headLabel, per_page: 1, }); if (prs.length > 0) { prNumber = prs[0].number; } } if (!prNumber) { core.info('No pull request found — skipping'); core.setOutput('has_pr', 'false'); return; } core.setOutput('pr_number', prNumber); core.setOutput('has_pr', 'true'); core.info(`PR #${prNumber}`); - name: Compute merge-base for Argos reference if: steps.check-artifact.outputs.exists == 'true' && steps.pr-info.outputs.has_pr == 'true' id: merge-base uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 with: script: | const headSha = context.payload.workflow_run.head_sha; try { const { data: comparison } = await github.rest.repos.compareCommitsWithBasehead({ owner: context.repo.owner, repo: context.repo.repo, basehead: `main...${headSha}`, }); if (comparison.merge_base_commit?.sha) { core.setOutput('sha', comparison.merge_base_commit.sha); core.info(`Merge base: ${comparison.merge_base_commit.sha}`); } else { core.info('Could not determine merge base — will skip reference_commit'); core.setOutput('sha', ''); } } catch (error) { core.warning(`Failed to compute merge base: ${error instanceof Error ? error.message : String(error)}`); core.setOutput('sha', ''); } # ── Dispatch: twenty-ui pixel diff (CI UI, PRs + main) ── dispatch-twenty-ui: needs: resolve-context if: >- needs.resolve-context.outputs.workflow_name == 'CI UI' && needs.resolve-context.outputs.has_artifact == 'true' runs-on: ubuntu-latest timeout-minutes: 5 steps: - name: Dispatch to ci-privileged env: GH_TOKEN: ${{ secrets.CI_PRIVILEGED_DISPATCH_TOKEN }} PR_NUMBER: ${{ needs.resolve-context.outputs.pr_number }} WORKFLOW_RUN_ID: ${{ github.event.workflow_run.id }} REPOSITORY: ${{ github.repository }} BRANCH: ${{ github.event.workflow_run.head_branch }} COMMIT: ${{ github.event.workflow_run.head_sha }} REFERENCE_COMMIT: ${{ needs.resolve-context.outputs.merge_base_sha }} ARTIFACT_NAME: ${{ needs.resolve-context.outputs.artifact_name }} run: | ARGS=( --method POST -f event_type=visual-regression -f "client_payload[project]=twenty-ui" -f "client_payload[artifact_name]=$ARTIFACT_NAME" -f "client_payload[run_id]=$WORKFLOW_RUN_ID" -f "client_payload[repo]=$REPOSITORY" -f "client_payload[branch]=$BRANCH" -f "client_payload[commit]=$COMMIT" ) if [ -n "$PR_NUMBER" ]; then ARGS+=(-f "client_payload[pr_number]=$PR_NUMBER") fi if [ -n "$REFERENCE_COMMIT" ]; then ARGS+=(-f "client_payload[reference_commit]=$REFERENCE_COMMIT") fi gh api repos/twentyhq/ci-privileged/dispatches "${ARGS[@]}" # ── Dispatch: twenty-new-ui pixel diff (CI New UI, PRs + main) ── dispatch-twenty-new-ui: needs: resolve-context if: >- needs.resolve-context.outputs.workflow_name == 'CI New UI' && needs.resolve-context.outputs.has_artifact == 'true' runs-on: ubuntu-latest timeout-minutes: 5 steps: - name: Dispatch to ci-privileged env: GH_TOKEN: ${{ secrets.CI_PRIVILEGED_DISPATCH_TOKEN }} WORKFLOW_RUN_ID: ${{ github.event.workflow_run.id }} REPOSITORY: ${{ github.repository }} BRANCH: ${{ github.event.workflow_run.head_branch }} COMMIT: ${{ github.event.workflow_run.head_sha }} PR_NUMBER: ${{ needs.resolve-context.outputs.pr_number }} REFERENCE_COMMIT: ${{ needs.resolve-context.outputs.merge_base_sha }} ARTIFACT_NAME: ${{ needs.resolve-context.outputs.artifact_name }} run: | ARGS=( --method POST -f event_type=visual-regression -f "client_payload[project]=twenty-new-ui" -f "client_payload[artifact_name]=$ARTIFACT_NAME" -f "client_payload[run_id]=$WORKFLOW_RUN_ID" -f "client_payload[repo]=$REPOSITORY" -f "client_payload[branch]=$BRANCH" -f "client_payload[commit]=$COMMIT" ) if [ -n "$PR_NUMBER" ]; then ARGS+=(-f "client_payload[pr_number]=$PR_NUMBER") fi if [ -n "$REFERENCE_COMMIT" ]; then ARGS+=(-f "client_payload[reference_commit]=$REFERENCE_COMMIT") fi gh api repos/twentyhq/ci-privileged/dispatches "${ARGS[@]}" # ── Dispatch: cross-comparison baseline (CI UI on main → twenty-ui-vs-new-ui) ── dispatch-comparison-baseline: needs: resolve-context if: >- needs.resolve-context.outputs.workflow_name == 'CI UI' && needs.resolve-context.outputs.has_artifact == 'true' && github.event.workflow_run.event == 'push' && github.event.workflow_run.head_branch == 'main' runs-on: ubuntu-latest timeout-minutes: 5 steps: - name: Dispatch to ci-privileged (comparison baseline) env: GH_TOKEN: ${{ secrets.CI_PRIVILEGED_DISPATCH_TOKEN }} WORKFLOW_RUN_ID: ${{ github.event.workflow_run.id }} REPOSITORY: ${{ github.repository }} BRANCH: ${{ github.event.workflow_run.head_branch }} COMMIT: ${{ github.event.workflow_run.head_sha }} ARTIFACT_NAME: ${{ needs.resolve-context.outputs.artifact_name }} run: | gh api repos/twentyhq/ci-privileged/dispatches \ --method POST \ -f event_type=visual-regression \ -f "client_payload[project]=twenty-ui-vs-new-ui" \ -f "client_payload[artifact_name]=$ARTIFACT_NAME" \ -f "client_payload[run_id]=$WORKFLOW_RUN_ID" \ -f "client_payload[repo]=$REPOSITORY" \ -f "client_payload[branch]=$BRANCH" \ -f "client_payload[commit]=$COMMIT" # ── Dispatch: cross-comparison PR (CI New UI on PRs → twenty-ui-vs-new-ui) ── dispatch-comparison-pr: needs: resolve-context if: >- needs.resolve-context.outputs.workflow_name == 'CI New UI' && needs.resolve-context.outputs.has_artifact == 'true' && needs.resolve-context.outputs.is_pr == 'true' && github.event.workflow_run.event == 'pull_request' runs-on: ubuntu-latest timeout-minutes: 5 steps: - name: Dispatch to ci-privileged (comparison PR) env: GH_TOKEN: ${{ secrets.CI_PRIVILEGED_DISPATCH_TOKEN }} WORKFLOW_RUN_ID: ${{ github.event.workflow_run.id }} REPOSITORY: ${{ github.repository }} BRANCH: ${{ github.event.workflow_run.head_branch }} COMMIT: ${{ github.event.workflow_run.head_sha }} PR_NUMBER: ${{ needs.resolve-context.outputs.pr_number }} REFERENCE_COMMIT: ${{ needs.resolve-context.outputs.merge_base_sha }} ARTIFACT_NAME: ${{ needs.resolve-context.outputs.artifact_name }} run: | ARGS=( --method POST -f event_type=visual-regression -f "client_payload[project]=twenty-ui-vs-new-ui" -f "client_payload[artifact_name]=$ARTIFACT_NAME" -f "client_payload[run_id]=$WORKFLOW_RUN_ID" -f "client_payload[repo]=$REPOSITORY" -f "client_payload[branch]=$BRANCH" -f "client_payload[commit]=$COMMIT" -f "client_payload[pr_number]=$PR_NUMBER" ) if [ -n "$REFERENCE_COMMIT" ]; then ARGS+=(-f "client_payload[reference_commit]=$REFERENCE_COMMIT") fi gh api repos/twentyhq/ci-privileged/dispatches "${ARGS[@]}"