Files
twenty/.github/workflows/visual-regression-dispatch.yaml
T
Paul Rastoin d5c7b735d2 ci: migrate cross-repo dispatch senders to workflow_dispatch (actions:write) (#21648)
# Introduction
Getting rid of the fine grained PAT used to dispatch to internal
repositories.
Repo dispatch requires the contents write permissions which is too wide
for such use
Refactored all senders and target to pass through a workflow dispatch
instead
Creating a centralize app that forges a token with actions: write only
provided permissions to mitigate any token exfiltrations
2026-06-16 15:18:43 +02:00

276 lines
11 KiB
YAML

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 paths trigger the same ci-privileged workflow (post-visual-regression-comment.yaml)
# via workflow_dispatch, passing the project as an input. ci-privileged routes to the
# correct Argos project based on it.
on:
workflow_run:
workflows: ['CI UI', 'CI New UI', 'CI Front']
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 }}
project: ${{ steps.context.outputs.project }}
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 projectByWorkflow = {
'CI UI': 'twenty-ui',
'CI New UI': 'twenty-new-ui',
'CI Front': 'twenty-front',
};
const project = projectByWorkflow[name] ?? 'twenty-ui';
const artifactName = `argos-screenshots-${project}`;
core.setOutput('workflow_name', name);
core.setOutput('project', project);
core.setOutput('artifact_name', artifactName);
core.info(`Workflow: ${name}, project: ${project}, 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: pixel diff for the triggering workflow's project (PRs + main) ──
dispatch-pixel-diff:
needs: resolve-context
if: needs.resolve-context.outputs.has_artifact == 'true'
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Mint ci-privileged dispatch token
id: app-token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
client-id: ${{ vars.TWENTY_WORKFLOW_DISPATCHER_CLIENT_ID }}
private-key: ${{ secrets.TWENTY_WORKFLOW_DISPATCHER_PRIVATE_KEY }}
owner: twentyhq
repositories: ci-privileged
permission-actions: write
- name: Dispatch to ci-privileged
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
PROJECT: ${{ needs.resolve-context.outputs.project }}
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=(
-f project="$PROJECT"
-f artifact_name="$ARTIFACT_NAME"
-f run_id="$WORKFLOW_RUN_ID"
-f repo="$REPOSITORY"
-f branch="$BRANCH"
-f commit="$COMMIT"
)
if [ -n "$PR_NUMBER" ]; then
ARGS+=(-f pr_number="$PR_NUMBER")
fi
if [ -n "$REFERENCE_COMMIT" ]; then
ARGS+=(-f reference_commit="$REFERENCE_COMMIT")
fi
gh workflow run post-visual-regression-comment.yaml --repo twentyhq/ci-privileged --ref main "${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: Mint ci-privileged dispatch token
id: app-token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
client-id: ${{ vars.TWENTY_WORKFLOW_DISPATCHER_CLIENT_ID }}
private-key: ${{ secrets.TWENTY_WORKFLOW_DISPATCHER_PRIVATE_KEY }}
owner: twentyhq
repositories: ci-privileged
permission-actions: write
- name: Dispatch to ci-privileged (comparison baseline)
env:
GH_TOKEN: ${{ steps.app-token.outputs.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 workflow run post-visual-regression-comment.yaml --repo twentyhq/ci-privileged --ref main \
-f project="twenty-ui-vs-new-ui" \
-f artifact_name="$ARTIFACT_NAME" \
-f run_id="$WORKFLOW_RUN_ID" \
-f repo="$REPOSITORY" \
-f branch="$BRANCH" \
-f 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: Mint ci-privileged dispatch token
id: app-token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
client-id: ${{ vars.TWENTY_WORKFLOW_DISPATCHER_CLIENT_ID }}
private-key: ${{ secrets.TWENTY_WORKFLOW_DISPATCHER_PRIVATE_KEY }}
owner: twentyhq
repositories: ci-privileged
permission-actions: write
- name: Dispatch to ci-privileged (comparison PR)
env:
GH_TOKEN: ${{ steps.app-token.outputs.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=(
-f project="twenty-ui-vs-new-ui"
-f artifact_name="$ARTIFACT_NAME"
-f run_id="$WORKFLOW_RUN_ID"
-f repo="$REPOSITORY"
-f branch="$BRANCH"
-f commit="$COMMIT"
-f pr_number="$PR_NUMBER"
)
if [ -n "$REFERENCE_COMMIT" ]; then
ARGS+=(-f reference_commit="$REFERENCE_COMMIT")
fi
gh workflow run post-visual-regression-comment.yaml --repo twentyhq/ci-privileged --ref main "${ARGS[@]}"