Files
metabase/.github/workflows/frontend.yml
2026-10-05 22:41:31 +13:00

240 lines
8.3 KiB
YAML

name: Frontend
on:
workflow_call:
inputs:
skip:
type: boolean
default: false
skip-lint:
type: boolean
default: false
skip-custom-viz:
type: boolean
default: false
# ID of this run's test-plan.json artifact, or '' to run the full suite.
test-plan-artifact-id:
type: string
default: ""
skip-ci-scripts:
type: boolean
default: false
workflow_dispatch:
inputs:
skip:
description: "Skip main frontend jobs (type-check, unit tests, timezone tests)"
type: boolean
default: false
skip-lint:
description: "Skip linting jobs (oxfmt, oxlint, css variables check)"
type: boolean
default: false
skip-custom-viz:
description: "Skip custom-viz package tests"
type: boolean
default: false
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}-frontend
cancel-in-progress: true
env:
# Consumed by the report-failures-to-ci-conductor step in fe-tests-unit. Absent
# (e.g. fork PRs without secret access) the reporter no-ops. Matches the other
# suite workflows (backend, drivers, e2e, app-db, semantic-search).
CI_CONDUCTOR_BASE_URL: ${{ vars.CI_CONDUCTOR_BASE_URL }}
CI_CONDUCTOR_WEBHOOK_SECRET: ${{ secrets.CI_CONDUCTOR_WEBHOOK_SECRET }}
jobs:
fe-lint:
if: ${{ !inputs.skip-lint }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
timeout-minutes: 20
steps:
- uses: actions/checkout@v7
- name: Prepare front-end environment
uses: ./.github/actions/prepare-frontend
- name: Prepare back-end environment
uses: ./.github/actions/prepare-backend
- name: Compile CLJS
run: bun run build-pure:cljs
- name: Find CSS variables that are used but never defined
run: bun run find-never-defined-css-variables
- name: Run oxfmt formatting linter
run: bun run lint-format-pure
- name: Run oxlint linter (including module boundaries)
run: bun run lint-oxlint-pure
- name: Verify lint configuration and behavior
run: bun run test-oxlint
- name: Run CSS linter
run: bun run lint-css
fe-type-check:
if: ${{ !inputs.skip }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
timeout-minutes: 10
steps:
- uses: actions/checkout@v7
- name: Prepare front-end environment
uses: ./.github/actions/prepare-frontend
- name: Prepare back-end environment
uses: ./.github/actions/prepare-backend
- run: bun run build-pure:cljs
name: Compile CLJS
- run: bun run type-check-pure
name: Check types
fe-tests-unit:
if: ${{ !inputs.skip }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
shard: [1, 2] # 2 shards for parallel execution
env:
SHARDS: 2 # should match matrix above
JEST_JUNIT_OUTPUT_DIR: ./target/junit
JEST_JUNIT_OUTPUT_NAME: test-report-frontend-unit.xml
UNIT_TEST_RESULTS_FILE: unit-test-results.json
steps:
- uses: actions/checkout@v7
- name: Prepare front-end environment
uses: ./.github/actions/prepare-frontend
- name: Prepare back-end environment
uses: ./.github/actions/prepare-backend
- name: Resolve current job id
# Resolve JOB_ID before the long test step so the ci-conductor reporter
# below can link failures to this exact job. Best-effort.
uses: ./.github/actions/resolve-job-id
- name: Prepare unit test selection
id: unit-selection
if: ${{ inputs.test-plan-artifact-id != '' }}
uses: ./.github/actions/prepare-test-selection
with:
suite: unit
test-plan-artifact-id: ${{ inputs.test-plan-artifact-id }}
- name: Run frontend unit tests
id: fe-tests
env:
JEST_TEST_PATHS_FILE: ${{ steps.unit-selection.outputs.paths-file }}
# Jest exits non-zero on a shard with no files unless --passWithNoTests is set.
run: |
rm -f "$UNIT_TEST_RESULTS_FILE" "$JEST_JUNIT_OUTPUT_DIR/$JEST_JUNIT_OUTPUT_NAME"
bun run test-unit --silent --shard=${{ matrix.shard }}/${{ env.SHARDS }} --json --outputFile="$UNIT_TEST_RESULTS_FILE" ${{ steps.unit-selection.outputs.paths-file && '--passWithNoTests' || '' }}
# Ordinary assertion failures can be quarantined, but runner failures cannot.
continue-on-error: true
- name: Check unit test results
if: ${{ !cancelled() && (steps.fe-tests.outcome == 'failure' || steps.unit-selection.outputs.paths-file) }}
env:
TEST_OUTCOME: ${{ steps.fe-tests.outcome }}
JEST_TEST_PATHS_FILE: ${{ steps.unit-selection.outputs.paths-file }}
run: bun .github/scripts/check-unit-test-results.ts
- name: Report failed tests to ci-conductor
if: ${{ !cancelled() && ( steps.fe-tests.outcome == 'failure' || github.run_attempt != '1' ) }}
# This step should NEVER fail the job!
continue-on-error: true
uses: ./.github/actions/report-failures-to-ci-conductor
with:
test-suite: fe-tests-unit
adapter: frontend
- name: Quarantine gate
if: ${{ !cancelled() && steps.fe-tests.outcome == 'failure' }}
continue-on-error: ${{ vars.QUARANTINE_ENGINE != 'ci-conductor' }}
timeout-minutes: 3
uses: ./.github/actions/quarantine-gate
with:
adapter: frontend
test-suite: fe-tests-unit
enforce-quarantine: ${{ vars.QUARANTINE_ENGINE == 'ci-conductor' }}
fe-tests-timezones:
if: ${{ !inputs.skip }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
timeout-minutes: 20
steps:
- uses: actions/checkout@v7
- name: Prepare front-end environment
uses: ./.github/actions/prepare-frontend
- name: Run frontend timezones tests
run: bun run test-timezones
# No CLJS compile: the CI-script specs don't import app code.
fe-tests-ci-scripts:
if: ${{ !inputs.skip-ci-scripts }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
timeout-minutes: 10
steps:
- uses: actions/checkout@v7
- name: Prepare front-end environment
uses: ./.github/actions/prepare-frontend
- name: Run CI script tests
run: bun run test-ci-scripts
# The custom-viz package is an isolated Bun workspace under
# enterprise/frontend/src/custom-viz
fe-custom-viz:
if: ${{ !inputs.skip-custom-viz }}
runs-on: ${{ vars.DEFAULT_RUNNER_KEY }}
timeout-minutes: 10
defaults:
run:
working-directory: enterprise/frontend/src/custom-viz
steps:
- uses: actions/checkout@v7
- name: Prepare Node + Bun environment
uses: ./.github/actions/prepare-node-bun
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Type-check
run: bun run type-check
- name: Lint
run: bun run lint
- name: Build CLI
run: bun run build
- name: Run tests
run: bun run test
fe-tests-result:
needs:
- fe-lint
- fe-type-check
- fe-tests-unit
- fe-tests-timezones
- fe-tests-ci-scripts
- fe-custom-viz
if: always() && !cancelled()
runs-on: ${{ vars.SLIM_RUNNER_KEY }}
timeout-minutes: 5
env:
needs: ${{ toJson(needs) }}
steps:
- name: Check frontend job status
uses: actions/github-script@v9
env:
needs: ${{ toJson(needs) }}
with:
script: | # js
const needs = JSON.parse(process.env.needs);
const jobs = Object.entries(needs).map(
([jobName, jobValues]) => ({
name: jobName,
result: jobValues.result
}));
// are all jobs skipped or successful?
if (jobs.every(job => (job.result === 'skipped' || job.result === 'success'))) {
console.log('All jobs are skipped or successful');
process.exit(0);
}
// otherwise, something failed
console.log('Some frontend jobs failed');
jobs.forEach((job) => {
if (job.result !== 'success') {
console.log(`${job.name} - ${job.result}`);
}
});
process.exit(1);