diff --git a/.github/workflows/ci-ui.yaml b/.github/workflows/ci-ui.yaml index 61cc80291db..8d15e6faa5b 100644 --- a/.github/workflows/ci-ui.yaml +++ b/.github/workflows/ci-ui.yaml @@ -3,6 +3,8 @@ name: CI UI on: pull_request: merge_group: + push: + branches: [main] permissions: contents: read @@ -13,7 +15,7 @@ concurrency: jobs: changed-files-check: - if: github.event_name != 'merge_group' + if: github.event_name == 'pull_request' uses: ./.github/workflows/changed-files.yaml with: files: | @@ -40,7 +42,10 @@ jobs: run: npx nx ${{ matrix.task }} twenty-ui ui-sb-build: needs: changed-files-check - if: needs.changed-files-check.outputs.any_changed == 'true' + if: >- + always() && + (github.event_name == 'push' || + needs.changed-files-check.outputs.any_changed == 'true') timeout-minutes: 30 runs-on: ubuntu-latest steps: @@ -62,8 +67,6 @@ jobs: timeout-minutes: 30 runs-on: ubuntu-latest needs: ui-sb-build - env: - STORYBOOK_URL: http://localhost:6007 steps: - name: Fetch custom Github Actions and base branch history uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 @@ -82,11 +85,8 @@ jobs: run: | cd packages/twenty-ui npx playwright install - - name: Serve storybook & run tests - run: | - npx http-server packages/twenty-ui/storybook-static --port 6007 --silent & - timeout 30 bash -c 'until curl -sf http://localhost:6007 > /dev/null 2>&1; do sleep 1; done' - npx nx storybook:test twenty-ui + - name: Run storybook tests + run: npx nx storybook:test twenty-ui - name: Upload screenshots for visual regression if: always() && !cancelled() uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 diff --git a/.github/workflows/visual-regression-dispatch.yaml b/.github/workflows/visual-regression-dispatch.yaml index 45ed9500f32..4afb3f57882 100644 --- a/.github/workflows/visual-regression-dispatch.yaml +++ b/.github/workflows/visual-regression-dispatch.yaml @@ -14,40 +14,19 @@ permissions: pull-requests: read jobs: - dispatch: + dispatch-pr: if: >- github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' runs-on: ubuntu-latest timeout-minutes: 5 steps: - - name: Determine project - id: project - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 - with: - script: | - const workflowName = context.payload.workflow_run.name; - const projects = { - 'CI UI': { project: 'twenty-ui', artifact: 'argos-screenshots-twenty-ui' }, - // Add more projects here when ready: - // 'CI Front': { project: 'twenty-front', artifact: 'argos-screenshots-twenty-front' }, - }; - - const config = projects[workflowName]; - if (!config) { - core.setFailed(`Unexpected workflow: ${workflowName}`); - return; - } - - core.setOutput('project', config.project); - core.setOutput('artifact_name', config.artifact); - - name: Check if screenshots artifact exists id: check-artifact uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 with: script: | - const artifactName = '${{ steps.project.outputs.artifact_name }}'; + const artifactName = 'argos-screenshots-twenty-ui'; const runId = context.payload.workflow_run.id; const { data: artifacts } = await github.rest.actions.listWorkflowRunArtifacts({ @@ -111,7 +90,6 @@ jobs: PR_NUMBER: ${{ steps.pr-info.outputs.pr_number }} WORKFLOW_RUN_ID: ${{ github.event.workflow_run.id }} REPOSITORY: ${{ github.repository }} - PROJECT: ${{ steps.project.outputs.project }} BRANCH: ${{ github.event.workflow_run.head_branch }} COMMIT: ${{ github.event.workflow_run.head_sha }} run: | @@ -121,6 +99,49 @@ jobs: -f "client_payload[pr_number]=$PR_NUMBER" \ -f "client_payload[run_id]=$WORKFLOW_RUN_ID" \ -f "client_payload[repo]=$REPOSITORY" \ - -f "client_payload[project]=$PROJECT" \ + -f "client_payload[branch]=$BRANCH" \ + -f "client_payload[commit]=$COMMIT" + + dispatch-main: + if: >- + github.event.workflow_run.event == 'push' && + github.event.workflow_run.head_branch == 'main' && + github.event.workflow_run.conclusion == 'success' + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Check if screenshots artifact exists + id: check-artifact + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + with: + script: | + 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 === 'argos-screenshots-twenty-ui'); + core.setOutput('exists', found ? 'true' : 'false'); + + if (!found) { + core.info(`Artifact not found in run ${runId} — skipping`); + } + + - name: Dispatch to ci-privileged + if: steps.check-artifact.outputs.exists == 'true' + 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 }} + run: | + gh api repos/twentyhq/ci-privileged/dispatches \ + --method POST \ + -f event_type=visual-regression \ + -f "client_payload[run_id]=$WORKFLOW_RUN_ID" \ + -f "client_payload[repo]=$REPOSITORY" \ -f "client_payload[branch]=$BRANCH" \ -f "client_payload[commit]=$COMMIT" diff --git a/packages/twenty-ui/project.json b/packages/twenty-ui/project.json index 1775723c615..3ccdbd9a8df 100644 --- a/packages/twenty-ui/project.json +++ b/packages/twenty-ui/project.json @@ -84,6 +84,14 @@ }, "storybook:test": {}, "storybook:test:no-coverage": {}, + "storybook:visual-diff": { + "executor": "nx:run-commands", + "dependsOn": ["storybook:build"], + "options": { + "cwd": "{projectRoot}", + "command": "bash scripts/visual-diff.sh" + } + }, "storybook:coverage": {} } } diff --git a/packages/twenty-ui/scripts/visual-diff.sh b/packages/twenty-ui/scripts/visual-diff.sh new file mode 100755 index 00000000000..7fbac392b5b --- /dev/null +++ b/packages/twenty-ui/scripts/visual-diff.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +if [[ -f "$SCRIPT_DIR/.env" ]]; then + set -a + source "$SCRIPT_DIR/.env" + set +a +fi + +ARGOS_API_BASE_URL="${ARGOS_API_BASE_URL:-http://127.0.0.1:4002/v2/}" +ARGOS_TOKEN="${ARGOS_TOKEN:?ARGOS_TOKEN is required – set it in packages/twenty-ui/.env}" + +USERNAME=$(whoami) +BRANCH="${ARGOS_BRANCH:-$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "main")}" +COMMIT="${ARGOS_COMMIT:-$(git rev-parse HEAD 2>/dev/null || echo "unknown")}" + +export ARGOS_API_BASE_URL +export ARGOS_TOKEN +export ARGOS_BUILD_NAME="${USERNAME}/twenty-ui" +export ARGOS_BRANCH="$BRANCH" +export ARGOS_COMMIT="$COMMIT" + +echo "Argos visual diff" +echo " API: $ARGOS_API_BASE_URL" +echo " Build name: $ARGOS_BUILD_NAME" +echo " Branch: $ARGOS_BRANCH" +echo " Commit: ${ARGOS_COMMIT:0:12}" +echo "" + +npx http-server storybook-static --port 6007 --silent & +HTTP_PID=$! +trap "kill $HTTP_PID 2>/dev/null || true" EXIT + +for i in $(seq 1 30); do curl -sf http://localhost:6007 > /dev/null 2>&1 && break; sleep 1; done + +export STORYBOOK_URL="http://localhost:6007" +npx vitest run diff --git a/packages/twenty-ui/src/feedback/progress-bar/components/__stories__/ProgressBar.stories.tsx b/packages/twenty-ui/src/feedback/progress-bar/components/__stories__/ProgressBar.stories.tsx index 15ea9edf1ca..83b71be9653 100644 --- a/packages/twenty-ui/src/feedback/progress-bar/components/__stories__/ProgressBar.stories.tsx +++ b/packages/twenty-ui/src/feedback/progress-bar/components/__stories__/ProgressBar.stories.tsx @@ -24,6 +24,7 @@ export const Default: Story = { }; export const Animated: Story = { + tags: ['!test'], argTypes: { value: { control: false }, }, diff --git a/packages/twenty-ui/vitest.config.ts b/packages/twenty-ui/vitest.config.ts index 2c32f2f6daf..dc301bb7250 100644 --- a/packages/twenty-ui/vitest.config.ts +++ b/packages/twenty-ui/vitest.config.ts @@ -20,12 +20,15 @@ export default defineConfig({ plugins: [ storybookTest({ configDir: path.join(dirname, '.storybook'), - storybookScript: 'yarn storybook --no-open --port 6007', + ...(process.env.STORYBOOK_URL + ? { storybookUrl: process.env.STORYBOOK_URL } + : { storybookScript: 'yarn storybook --no-open --port 6007' }), }), argosVitestPlugin({ uploadToArgos: !!process.env.ARGOS_TOKEN, token: process.env.ARGOS_TOKEN, apiBaseUrl: process.env.ARGOS_API_BASE_URL, + buildName: process.env.ARGOS_BUILD_NAME || undefined, branch: process.env.ARGOS_BRANCH || undefined, commit: process.env.ARGOS_COMMIT || undefined, }),