feat(metrics): expose query-metrics and metric-names-list MCP tools #312279
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: Frontend CI | |
| on: | |
| pull_request: | |
| # Draft PRs run a narrowed jest selection; ready PRs run the full matrix | |
| # (the merge gate). ready_for_review re-triggers the full run on the | |
| # current head when a PR leaves draft. To force the full matrix on a | |
| # draft, add the `run-ci-frontend` label — labeled/unlabeled re-trigger | |
| # the run so it starts without needing a new push. | |
| types: [opened, synchronize, reopened, ready_for_review, labeled, unlabeled] | |
| merge_group: | |
| push: | |
| branches: | |
| - master | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| actions: read | |
| jobs: | |
| # Job to decide if we should run frontend ci | |
| # See https://github.com/dorny/paths-filter#conditional-execution for more details | |
| # we skip each step individually, so they are still reported as success | |
| # because many of them are required for CI checks to be green | |
| changes: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| name: Determine need to run frontend checks | |
| outputs: | |
| frontend: ${{ steps.filter.outputs.frontend || 'true' }} | |
| frontend_code: ${{ steps.filter.outputs.frontend_code || 'true' }} | |
| steps: | |
| # For pull requests it's not necessary to check out the code, but we | |
| # also want this to run on master, so we need to check out | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1 | |
| id: app-token | |
| if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository | |
| with: | |
| client-id: ${{ secrets.GH_APP_POSTHOG_PATHS_FILTER_APP_ID }} | |
| private-key: ${{ secrets.GH_APP_POSTHOG_PATHS_FILTER_PRIVATE_KEY }} | |
| - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 | |
| id: filter | |
| if: github.event_name != 'push' # Run all tests on master push | |
| with: | |
| token: ${{ steps.app-token.outputs.token || github.token }} | |
| filters: | | |
| frontend: | |
| # Avoid running frontend tests for irrelevant changes | |
| # NOTE: we are at risk of missing a dependency here. | |
| - 'bin/**' | |
| - 'frontend/**' | |
| - 'ee/frontend/**' | |
| - 'common/esbuilder/**' | |
| - 'products/**/*.ts' | |
| - 'products/**/*.tsx' | |
| - 'products/**/*.json' | |
| - 'playwright/**' | |
| - 'services/mcp/**' | |
| - 'docs/**/*.tsx' | |
| # Make sure we run if someone is explicitly change the workflow | |
| - .github/workflows/ci-frontend.yml | |
| # various JS config files | |
| - .oxlintrc.json | |
| - .oxfmtrc* | |
| - babel.config.js | |
| - package.json | |
| - pnpm-lock.yaml | |
| - jest.*.ts | |
| - tsconfig.json | |
| - tsconfig.*.json | |
| - webpack.config.js | |
| - stylelint* | |
| - '**/*.md' | |
| - '**/*.mdx' | |
| - '**/*.yaml' | |
| - '**/*.yml' | |
| - .config/.markdownlint-cli2.jsonc | |
| # Like `frontend` but excludes doc/config-only files that don't affect | |
| # compiled output — used to skip Jest, bundle-size, and TypeScript checks | |
| # when only markdown or YAML files change. | |
| frontend_code: | |
| - 'bin/**' | |
| - 'frontend/**' | |
| - 'ee/frontend/**' | |
| - 'common/esbuilder/**' | |
| - 'products/**/*.ts' | |
| - 'products/**/*.tsx' | |
| - 'products/**/*.json' | |
| - 'playwright/**' | |
| - 'services/mcp/**' | |
| - .github/workflows/ci-frontend.yml | |
| - .oxlintrc.json | |
| - .oxfmtrc* | |
| - babel.config.js | |
| - package.json | |
| - pnpm-lock.yaml | |
| - jest.*.ts | |
| - tsconfig.json | |
| - tsconfig.*.json | |
| - webpack.config.js | |
| - stylelint* | |
| # Pick which jest tests to run on draft PRs by passing changed files to | |
| # `jest --findRelatedTests` (jest's resolver walks the import graph for us). | |
| # Skipped on ready PRs, master push, and merge_group; the jest job falls back | |
| # to its full FOSS×EE × chunk matrix in those cases. The ready-for-review | |
| # full run is the merge gate. The run-ci-frontend label forces the full | |
| # matrix on a draft. When selection can't be trusted on a draft (config | |
| # change, oversized diff, selector failure), the draft skips jest entirely | |
| # and defers to the ready full run. | |
| select-jest-tests: | |
| name: Select jest tests | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| needs: changes | |
| if: | | |
| always() && needs.changes.outputs.frontend_code == 'true' | |
| && github.event_name == 'pull_request' | |
| && github.event.pull_request.draft == true | |
| && !contains(github.event.pull_request.labels.*.name, 'run-ci-frontend') | |
| outputs: | |
| mode: ${{ steps.classify.outputs.mode }} | |
| matrix: ${{ steps.classify.outputs.matrix }} | |
| should_run: ${{ steps.classify.outputs.should_run }} | |
| changed_files: ${{ steps.classify.outputs.changed_files }} | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| fetch-depth: 1000 | |
| filter: blob:none | |
| - name: Compute changed files | |
| id: diff | |
| continue-on-error: true | |
| env: | |
| BASE_REF: ${{ github.event.pull_request.base.ref }} | |
| run: | | |
| set -euo pipefail | |
| git fetch --no-tags --depth=1000 --filter=blob:none origin "$BASE_REF:refs/remotes/origin/$BASE_REF" | |
| # `...` resolves against the merge-base so files merged in from base | |
| # don't inflate the diff (mirrors find-affected-stories / snob). | |
| git diff --name-only "origin/$BASE_REF...HEAD" > /tmp/changed.txt | |
| wc -l < /tmp/changed.txt | |
| - name: Build narrowed matrix | |
| id: classify | |
| env: | |
| DIFF_OUTCOME: ${{ steps.diff.outcome }} | |
| run: | | |
| set -euo pipefail | |
| # Untrusted selection on a draft skips jest entirely (should_run=false | |
| # gates the jest job off); the ready-for-review full run is the gate. | |
| # Use this whenever the selector can't reason about the diff — a | |
| # narrowed run it can't vouch for would be false confidence, and a | |
| # full run on every draft push is the spend this selection avoids. | |
| # The run-ci-frontend label forces the full matrix on a draft. | |
| fall_back_to_skip() { | |
| echo "mode=skip" >> "$GITHUB_OUTPUT" | |
| echo 'matrix={"include":[]}' >> "$GITHUB_OUTPUT" | |
| echo "should_run=false" >> "$GITHUB_OUTPUT" | |
| echo "changed_files=" >> "$GITHUB_OUTPUT" | |
| } | |
| if [[ "$DIFF_OUTCOME" != "success" ]] || [[ ! -s /tmp/changed.txt ]]; then | |
| echo "::warning::could not derive changed files; draft skips jest (full run happens on ready for review)" | |
| fall_back_to_skip | |
| exit 0 | |
| fi | |
| changed_count=$(wc -l < /tmp/changed.txt) | |
| if [[ "$changed_count" -gt 200 ]]; then | |
| echo "::notice::diff has $changed_count files (>200); draft skips jest (full run happens on ready for review)" | |
| fall_back_to_skip | |
| exit 0 | |
| fi | |
| # Skip when jest config / build / lock files change — these | |
| # invalidate jest's resolver, transformer cache, or test discovery | |
| # in ways `--findRelatedTests` can't see, so no narrowed run can be | |
| # trusted. Pattern matches mirror the frontend_code paths-filter | |
| # in `changes`. | |
| while IFS= read -r f; do | |
| case "$f" in | |
| jest.*.ts|babel.config.js|tsconfig.json|tsconfig.*.json|\ | |
| package.json|pnpm-lock.yaml|webpack.config.js|\ | |
| .github/workflows/ci-frontend.yml|.nvmrc) | |
| echo "::notice::config change ($f); draft skips jest (full run happens on ready for review)" | |
| fall_back_to_skip | |
| exit 0 | |
| ;; | |
| esac | |
| done < /tmp/changed.txt | |
| # Filter the diff to source files jest can resolve. Any code under | |
| # frontend/, products/, common/, ee/frontend/ — anything else (docs, | |
| # workflows, backend) doesn't reach the jest module graph. | |
| CHANGED_FILES=$(grep -E '^(frontend/|products/|common/|ee/frontend/)' /tmp/changed.txt | grep -E '\.(ts|tsx|js|jsx|mjs|cjs)$' | tr '\n' ' ' | sed 's/ $//') | |
| if [[ -z "$CHANGED_FILES" ]]; then | |
| # No frontend source changes (e.g. PR only edits backend or docs) | |
| # but `frontend_code` paths-filter still flagged the PR. Skip jest. | |
| echo "mode=selective" >> "$GITHUB_OUTPUT" | |
| echo 'matrix={"include":[]}' >> "$GITHUB_OUTPUT" | |
| echo "should_run=false" >> "$GITHUB_OUTPUT" | |
| echo "changed_files=" >> "$GITHUB_OUTPUT" | |
| echo "Selective mode: no frontend source files changed, skipping jest" | |
| exit 0 | |
| fi | |
| # Selective mode: single EE-tree job, no chunk sharding. The FOSS | |
| # structural check (ee/ removed) only matters when ee imports are | |
| # touched; defer that to the ready-for-review full run. --findRelatedTests | |
| # walks jest's import graph from these files and runs the matching | |
| # *.test.* files. | |
| echo "mode=selective" >> "$GITHUB_OUTPUT" | |
| echo 'matrix={"include":[{"segment":"EE","chunk":1}]}' >> "$GITHUB_OUTPUT" | |
| echo "should_run=true" >> "$GITHUB_OUTPUT" | |
| echo "changed_files=$CHANGED_FILES" >> "$GITHUB_OUTPUT" | |
| echo "Selective mode: $(echo "$CHANGED_FILES" | wc -w) frontend source files" | |
| frontend-format: | |
| name: Frontend formatting | |
| needs: changes | |
| if: needs.changes.outputs.frontend == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - name: Install pnpm | |
| uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 | |
| - name: Set up Node.js | |
| uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 | |
| with: | |
| node-version-file: .nvmrc | |
| token: ${{ github.token }} | |
| - name: Get pnpm store path | |
| id: pnpm-store | |
| run: echo "path=$(pnpm store path --silent)" >> $GITHUB_OUTPUT | |
| - name: Restore pnpm cache | |
| uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 | |
| with: | |
| path: ${{ steps.pnpm-store.outputs.path }} | |
| key: node-cache-${{ runner.os }}-${{ runner.arch }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }} | |
| restore-keys: node-cache-${{ runner.os }}-${{ runner.arch }}-pnpm- | |
| - name: Install package.json dependencies with pnpm | |
| run: | | |
| pnpm --filter=@posthog/playwright... install --frozen-lockfile | |
| bin/turbo --filter=@posthog/frontend prepare | |
| - name: Save pnpm cache | |
| if: github.ref == 'refs/heads/master' | |
| uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 | |
| with: | |
| path: ${{ steps.pnpm-store.outputs.path }} | |
| key: node-cache-${{ runner.os }}-${{ runner.arch }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }} | |
| - name: Check formatting with oxfmt | |
| run: pnpm run format:frontend:check | |
| - name: Lint with Stylelint | |
| run: pnpm run lint:css | |
| - name: Lint with Oxlint | |
| run: pnpm exec oxlint --quiet | |
| - name: Check markdown formatting with oxfmt | |
| run: pnpm exec oxfmt --check "**/*.{md,mdx}" | |
| - name: Check YAML formatting with oxfmt | |
| run: pnpm exec oxfmt --check "**/*.{yaml,yml}" | |
| - name: Lint markdown files | |
| run: pnpm exec markdownlint-cli2 --config .config/.markdownlint-cli2.jsonc "**/*.{md,mdx}" | |
| frontend-bundle-size: | |
| name: Frontend bundle size | |
| needs: [changes] | |
| if: needs.changes.outputs.frontend_code == 'true' && github.event_name != 'merge_group' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| filter: blob:none | |
| - name: Install pnpm | |
| uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 | |
| - name: Set up Node.js | |
| uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 | |
| with: | |
| node-version-file: .nvmrc | |
| token: ${{ github.token }} | |
| - name: Get pnpm store path | |
| id: pnpm-store | |
| run: echo "path=$(pnpm store path --silent)" >> $GITHUB_OUTPUT | |
| - name: Restore pnpm cache | |
| uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 | |
| with: | |
| path: ${{ steps.pnpm-store.outputs.path }} | |
| key: node-cache-${{ runner.os }}-${{ runner.arch }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }} | |
| restore-keys: node-cache-${{ runner.os }}-${{ runner.arch }}-pnpm- | |
| - name: Install package.json dependencies with pnpm | |
| run: | | |
| pnpm --filter=@posthog/playwright... install --frozen-lockfile | |
| bin/turbo --filter=@posthog/frontend prepare | |
| - name: Save pnpm cache | |
| if: github.ref == 'refs/heads/master' | |
| uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 | |
| with: | |
| path: ${{ steps.pnpm-store.outputs.path }} | |
| key: node-cache-${{ runner.os }}-${{ runner.arch }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }} | |
| - name: Build products | |
| run: pnpm --filter=@posthog/frontend build:products | |
| - name: Check frontend bundle size | |
| uses: preactjs/compressed-size-action@66325aad6443cb7cf89c4bfcd414aea2367cda94 # v2.9.1 | |
| with: | |
| build-script: '--filter=@posthog/frontend build:with-report' | |
| install-script: 'pnpm --filter=@posthog/frontend... install' | |
| compression: 'none' | |
| # dist-report/ carries the source bundle (posthog-app, exporter, …) and source | |
| # path in the filename, so strip-hash yields a deterministic identity. dist/ has | |
| # cross-bundle basename collisions that cause phantom size changes. | |
| pattern: 'frontend/dist-report/**/*.js' | |
| # Shared chunk hashes change every build, ignore those | |
| exclude: '{**/_chunks/chunk*.js}' | |
| # Only show large changes | |
| minimum-change-threshold: 1000 | |
| order-by: 'Size:desc' | |
| strip-hash: "-[A-Za-z0-9]{8}\\.js$" | |
| # The eager graph check runs in --report-only mode inside build:with-report | |
| # (compressed-size-action runs that for the PR AND base builds — a failing | |
| # base build must not abort the action for every open PR). These steps post | |
| # the sticky PR comment and enforce the budgets from the PR build's report, | |
| # which carries the checkout sha in its filename because the base build | |
| # overwrites the plain one. After compressed-size-action the tree is on the | |
| # BASE branch, so guard for the scripts' absence (also covers branches | |
| # predating the check). | |
| - name: Post eager graph comment | |
| if: always() && github.event_name == 'pull_request' | |
| env: | |
| GITHUB_TOKEN: ${{ github.token }} | |
| run: | | |
| if [ -f frontend/bin/post-eager-graph-comment.mjs ]; then | |
| node frontend/bin/post-eager-graph-comment.mjs | |
| else | |
| echo "Skipping eager graph comment — script not present on this checkout" | |
| fi | |
| - name: Enforce eager graph budgets | |
| if: always() && github.event_name == 'pull_request' | |
| run: | | |
| if [ ! -f frontend/bin/check-eager-graph.mjs ]; then | |
| echo "Skipping eager graph enforcement — script not present on this checkout" | |
| exit 0 | |
| fi | |
| REPORT="frontend/eager-graph-report-${GITHUB_SHA}.json" | |
| if [ ! -f "$REPORT" ]; then | |
| REPORT="frontend/eager-graph-report.json" | |
| fi | |
| if [ ! -f "$REPORT" ]; then | |
| echo "Skipping eager graph enforcement — no report found (branch may predate the check)" | |
| exit 0 | |
| fi | |
| node frontend/bin/check-eager-graph.mjs --assert-report "$REPORT" | |
| - name: Check toolbar for CSP eval violations | |
| run: pnpm --filter=@posthog/frontend check-toolbar-csp-eval | |
| - name: Calculate dist folder size | |
| id: dist-size | |
| run: | | |
| size_bytes=$(du -sb frontend/dist | cut -f1) | |
| echo "size_bytes=${size_bytes}" >> $GITHUB_OUTPUT | |
| echo "Frontend dist folder size: ${size_bytes} bytes ($(numfmt --to=iec-i --suffix=B ${size_bytes}))" | |
| - name: Capture bundle size to PostHog | |
| if: | | |
| (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'PostHog/posthog') || | |
| (github.event_name != 'pull_request' && github.repository == 'PostHog/posthog') | |
| uses: PostHog/posthog-github-action@58dea254b598fb5d469c0699c98af8288a7f7650 # v1.2.0 | |
| with: | |
| posthog-token: ${{secrets.POSTHOG_API_TOKEN}} | |
| event: 'posthog-ci-frontend-bundle-size' | |
| properties: | | |
| { | |
| "size_bytes": ${{ steps.dist-size.outputs.size_bytes }}, | |
| "branch": ${{ toJSON(github.head_ref || github.ref_name) }}, | |
| "sha": ${{ toJSON(github.sha) }}, | |
| "pr_number": ${{ github.event.pull_request.number || 'null' }}, | |
| "event_type": ${{ toJSON(github.event_name) }} | |
| } | |
| - name: Capture bundle size to DevEx PostHog | |
| if: | | |
| (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'PostHog/posthog') || | |
| (github.event_name != 'pull_request' && github.repository == 'PostHog/posthog') | |
| continue-on-error: true | |
| uses: PostHog/posthog-github-action@58dea254b598fb5d469c0699c98af8288a7f7650 # v1.2.0 | |
| with: | |
| posthog-token: ${{ secrets.POSTHOG_DEVEX_PROJECT_API_TOKEN }} | |
| event: 'posthog-ci-frontend-bundle-size' | |
| properties: | | |
| { | |
| "size_bytes": ${{ steps.dist-size.outputs.size_bytes }}, | |
| "branch": ${{ toJSON(github.head_ref || github.ref_name) }}, | |
| "sha": ${{ toJSON(github.sha) }}, | |
| "pr_number": ${{ github.event.pull_request.number || 'null' }}, | |
| "event_type": ${{ toJSON(github.event_name) }} | |
| } | |
| frontend-typescript-checks: | |
| name: Frontend typechecking | |
| needs: [changes] | |
| if: needs.changes.outputs.frontend_code == 'true' | |
| runs-on: ubuntu-latest | |
| # Kea typegen invalidates on changes to any logic plus everything that depends on it. | |
| # For changes to shared core (TaxonomicFilter, Insight, etc.) the dependent set is large | |
| # and the cache miss costs ~5min on top of the master baseline (~10min). 15 is too tight. | |
| timeout-minutes: 20 | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - name: Install pnpm | |
| uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 | |
| - name: Set up Node.js | |
| uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 | |
| with: | |
| node-version-file: .nvmrc | |
| token: ${{ github.token }} | |
| - name: Get pnpm store path | |
| id: pnpm-store | |
| run: echo "path=$(pnpm store path --silent)" >> $GITHUB_OUTPUT | |
| - name: Restore pnpm cache | |
| uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 | |
| with: | |
| path: ${{ steps.pnpm-store.outputs.path }} | |
| key: node-cache-${{ runner.os }}-${{ runner.arch }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }} | |
| restore-keys: node-cache-${{ runner.os }}-${{ runner.arch }}-pnpm- | |
| - name: Install package.json dependencies with pnpm | |
| run: | | |
| pnpm --filter=@posthog/playwright... install --no-frozen-lockfile | |
| bin/turbo --filter=@posthog/frontend prepare | |
| - name: Save pnpm cache | |
| if: github.ref == 'refs/heads/master' | |
| uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 | |
| with: | |
| path: ${{ steps.pnpm-store.outputs.path }} | |
| key: node-cache-${{ runner.os }}-${{ runner.arch }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }} | |
| - name: Restore .typegen cache | |
| uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 | |
| with: | |
| path: .typegen | |
| key: ${{ runner.os }}-typegen-${{ hashFiles('pnpm-lock.yaml') }} | |
| restore-keys: ${{ runner.os }}-typegen- | |
| - name: Build products | |
| run: pnpm --filter=@posthog/frontend build:products | |
| - name: Check if products.tsx and products.json are up to date | |
| run: pnpm --filter=@posthog/frontend build:products && git diff --exit-code | |
| - name: Kea typegen | |
| run: pnpm --filter=@posthog/frontend typegen:write | |
| - name: Re-format files after typegen | |
| run: pnpm exec oxfmt "./{products,frontend/src}/**/*.{ts,tsx}" | |
| - name: Save .typegen cache | |
| if: github.ref == 'refs/heads/master' | |
| uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 | |
| with: | |
| path: .typegen | |
| key: ${{ runner.os }}-typegen-${{ hashFiles('pnpm-lock.yaml') }} | |
| - name: Run typescript check (tsgo) | |
| run: pnpm --filter=@posthog/frontend typescript:check | |
| - name: Check if schema.json and validators.js are up to date | |
| run: pnpm --filter=@posthog/frontend schema:build:json && git diff --exit-code | |
| - name: Check if mobile replay "schema.json" is up to date | |
| run: pnpm --filter=@posthog/frontend mobile-replay:schema:build:json && git diff --exit-code | |
| env: | |
| NODE_OPTIONS: --max-old-space-size=8192 | |
| - name: Validate SetupTaskId completions | |
| run: node bin/validate-setup-tasks.mjs | |
| jest: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| needs: [changes, select-jest-tests] | |
| if: | | |
| always() && needs.changes.outputs.frontend_code == 'true' | |
| && needs.select-jest-tests.outputs.should_run != 'false' | |
| name: Jest test (${{ matrix.segment }} - ${{ matrix.chunk }}) | |
| strategy: | |
| # If one test fails, still run the others | |
| fail-fast: false | |
| # On draft PRs, select-jest-tests may narrow this to a single EE job | |
| # running only the tests reachable from changed frontend source files, | |
| # or skip jest entirely (should_run=false) when selection can't be | |
| # trusted. On ready PRs, master push, and merge_group it's skipped, so | |
| # we fall back to the full FOSS×EE × chunk fanout (the merge gate). | |
| matrix: ${{ fromJson(needs.select-jest-tests.outputs.matrix || '{"segment":["FOSS","EE"],"chunk":[1,2]}') }} | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - name: Remove ee | |
| if: matrix.segment == 'FOSS' | |
| run: rm -rf ee | |
| - name: Install pnpm | |
| uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 | |
| - name: Set up Node.js | |
| uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 | |
| with: | |
| node-version-file: .nvmrc | |
| token: ${{ github.token }} | |
| - name: Get pnpm store path | |
| id: pnpm-store | |
| run: echo "path=$(pnpm store path --silent)" >> $GITHUB_OUTPUT | |
| - name: Restore pnpm cache | |
| uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 | |
| with: | |
| path: ${{ steps.pnpm-store.outputs.path }} | |
| key: node-cache-${{ runner.os }}-${{ runner.arch }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }} | |
| restore-keys: node-cache-${{ runner.os }}-${{ runner.arch }}-pnpm- | |
| - name: Install package.json dependencies with pnpm | |
| run: pnpm --filter=@posthog/frontend... install --frozen-lockfile | |
| - name: Save pnpm cache | |
| if: github.ref == 'refs/heads/master' | |
| uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 | |
| with: | |
| path: ${{ steps.pnpm-store.outputs.path }} | |
| key: node-cache-${{ runner.os }}-${{ runner.arch }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }} | |
| - name: Test with Jest | |
| env: | |
| NODE_OPTIONS: --max-old-space-size=16384 | |
| SHARD_INDEX: ${{ matrix.chunk }} | |
| SHARD_COUNT: 2 | |
| MODE: ${{ needs.select-jest-tests.outputs.mode }} | |
| CHANGED_FILES: ${{ needs.select-jest-tests.outputs.changed_files }} | |
| run: | | |
| if [ "$MODE" = "selective" ]; then | |
| # --findRelatedTests walks jest's import graph from the changed | |
| # source files and runs the matching tests. No --shard: single | |
| # job runs the entire selected set. | |
| # | |
| # No --testPathPattern: it is mutually exclusive with | |
| # --findRelatedTests (jest folds both into one path regex and | |
| # matches nothing), and CHANGED_FILES is already scoped to | |
| # frontend source files by select-jest-tests. --passWithNoTests | |
| # so a changed source file with no related test doesn't fail the | |
| # run — the ready-for-review full matrix is the gate. | |
| pnpm --filter=@posthog/frontend build:products | |
| # shellcheck disable=SC2086 | |
| pnpm --filter=@posthog/frontend exec jest \ | |
| --forceExit \ | |
| --passWithNoTests \ | |
| --findRelatedTests $CHANGED_FILES | |
| else | |
| bin/turbo run test --filter=@posthog/frontend -- --maxWorkers=2 | |
| fi | |
| calculate-running-time: | |
| name: Calculate running time | |
| needs: [jest, frontend-typescript-checks, frontend-format, frontend-bundle-size, frontend_tests, changes] | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| if: # Run on pull requests to PostHog/posthog + on PostHog/posthog outside of PRs - but never on forks or Dependabot (no secrets access) | |
| always() && github.actor != 'dependabot[bot]' && | |
| needs.changes.outputs.frontend_code == 'true' && ( | |
| (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'PostHog/posthog') || | |
| (github.event_name != 'pull_request' && github.repository == 'PostHog/posthog')) | |
| steps: | |
| - name: Get telemetry app token | |
| id: telemetry-app-token | |
| if: github.run_attempt == '1' | |
| uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1 | |
| with: | |
| client-id: ${{ secrets.GH_APP_TELEMETRY_APP_ID }} | |
| private-key: ${{ secrets.GH_APP_TELEMETRY_PRIVATE_KEY }} | |
| - name: Capture running time to PostHog | |
| if: github.run_attempt == '1' | |
| continue-on-error: true | |
| uses: PostHog/posthog-github-action@58dea254b598fb5d469c0699c98af8288a7f7650 # v1.2.0 | |
| with: | |
| posthog-token: ${{ secrets.POSTHOG_API_TOKEN }} | |
| event: 'posthog-ci-running-time' | |
| capture-run-duration: true | |
| capture-job-durations: true | |
| github-token: ${{ steps.telemetry-app-token.outputs.token }} | |
| status-job: 'Frontend Tests Pass' | |
| runner: 'github' | |
| - name: Capture running time to DevEx PostHog | |
| if: github.run_attempt == '1' | |
| continue-on-error: true | |
| uses: PostHog/posthog-github-action@58dea254b598fb5d469c0699c98af8288a7f7650 # v1.2.0 | |
| with: | |
| posthog-token: ${{ secrets.POSTHOG_DEVEX_PROJECT_API_TOKEN }} | |
| event: 'posthog-ci-running-time' | |
| capture-run-duration: true | |
| capture-job-durations: true | |
| github-token: ${{ steps.telemetry-app-token.outputs.token }} | |
| status-job: 'Frontend Tests Pass' | |
| runner: 'github' | |
| frontend_tests: | |
| needs: [jest, frontend-format, frontend-bundle-size, frontend-typescript-checks] | |
| name: Frontend Tests Pass | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| if: always() | |
| steps: | |
| - run: exit 0 | |
| - name: Check outcomes | |
| run: | | |
| if [[ "${{ needs.jest.result }}" != "success" && "${{ needs.jest.result }}" != "skipped" ]]; then | |
| echo "Frontend jest tests failed." | |
| exit 1 | |
| fi | |
| echo "Frontend jest tests passed." | |
| if [[ "${{ needs.frontend-format.result }}" != "success" && "${{ needs.frontend-format.result }}" != "skipped" ]]; then | |
| echo "Frontend linting failed." | |
| exit 1 | |
| fi | |
| echo "Frontend linting passed." | |
| if [[ "${{ needs.frontend-bundle-size.result }}" != "success" && "${{ needs.frontend-bundle-size.result }}" != "skipped" ]]; then | |
| echo "Frontend bundle size checks failed." | |
| exit 1 | |
| fi | |
| echo "Frontend bundle size checks passed." | |
| if [[ "${{ needs.frontend-typescript-checks.result }}" != "success" && "${{ needs.frontend-typescript-checks.result }}" != "skipped" ]]; then | |
| echo "Frontend TypeScript checks failed." | |
| exit 1 | |
| fi | |
| echo "Frontend TypeScript checks passed." |