Files
calendar/.github/workflows/pr.yml
T
Keith WilliamsGitHubDevin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
054a0fa7db refactor: Add dedicated setup-db job to eliminate cache race condition (#26171)
* refactor: Add dedicated setup-db job to eliminate cache race condition

- Create new setup-db.yml workflow that runs cache-db action
- Update pr.yml to add setup-db job that runs before all E2E jobs
- Update integration-test, e2e, e2e-api-v2, e2e-app-store, e2e-embed,
  and e2e-embed-react jobs to depend on setup-db instead of build-api-v1
- Add setup-db to required job needs list and result check

This eliminates the race condition where multiple jobs could try to
write to the same database cache simultaneously.

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>

* refactor: Remove DB parts from api-v1-production-build.yml

The database setup is now handled by the dedicated setup-db job,
so the postgres service and cache-db action are no longer needed
in the API v1 build workflow.

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>

* Apply suggestion from @keithwillcode

* Made integration tests dependency consistent

* refactor: Remove unnecessary env vars from setup-db.yml

Only keep the env vars needed for database setup:
- CALENDSO_ENCRYPTION_KEY (for db-seed)
- DATABASE_URL / DATABASE_DIRECT_URL (for database connection)
- TURBO_TOKEN / TURBO_TEAM (for turbo caching)

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>

* fix: Add back E2E_TEST_CALCOM_QA_* env vars needed by db-seed

These env vars are used by scripts/seed.ts to create the QA user
with Google Calendar credentials during database seeding.

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>

* fix: Restore postgres service and cache-db to api-v1-production-build.yml

The API v1 build still needs a database to run properly. Restored the
postgres service and cache-db action, and added dependency on setup-db
so it waits for the database cache to be created first.

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>

* E2E for API v2 no longer waiting on API v2 build

* Reordering the jobs by dependencies

* add GOOGLE_API_CREDENTIALS env var to setup db

* Added back all env vars to setup-db

* fix: Include commit SHA in cache-db key to invalidate cache on new commits

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>

* refactor: Strip back setup-db.yml env vars to minimal set needed for db-seed

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2025-12-23 22:39:26 -03:00

292 lines
9.4 KiB
YAML

name: PR Update
on:
pull_request_target:
types: [opened, synchronize, reopened]
branches:
- main
- gh-actions-test-branch
workflow_dispatch:
permissions:
actions: write
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
prepare:
name: Prepare
runs-on: buildjet-2vcpu-ubuntu-2204
permissions:
pull-requests: read
outputs:
has-files-requiring-all-checks: ${{ steps.filter.outputs.has-files-requiring-all-checks }}
has_companion: ${{ steps.filter.outputs.has_companion }}
commit-sha: ${{ steps.get_sha.outputs.commit-sha }}
run-e2e: ${{ steps.check-if-pr-has-label.outputs.run-e2e == 'true' }}
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/dangerous-git-checkout
- uses: dorny/paths-filter@v3
id: filter
with:
predicate-quantifier: "every"
filters: |
has-files-requiring-all-checks:
- '**'
- '!companion/**'
- '!**/*.md'
- '!**/*.mdx'
- '!.github/CODEOWNERS'
- '!docs/**'
- '!help/**'
- '!apps/web/public/static/locales/**/common.json'
- '!i18n.lock'
has_companion:
- "companion/**"
- name: Get Latest Commit SHA
id: get_sha
run: |
echo "commit-sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
- name: Check if PR exists with ready-for-e2e label for this SHA
id: check-if-pr-has-label
uses: actions/github-script@v7
with:
script: |
let labels = [];
let prNumber = null;
if (context.payload.pull_request) {
prNumber = context.payload.pull_request.number;
} else {
try {
const sha = '${{ steps.get_sha.outputs.commit-sha }}';
console.log('sha', sha);
const { data: prs } = await github.rest.repos.listPullRequestsAssociatedWithCommit({
owner: context.repo.owner,
repo: context.repo.repo,
commit_sha: sha
});
if (prs.length === 0) {
core.setOutput('run-e2e', false);
console.log(`No pull requests found for commit SHA ${sha}`);
return;
}
prNumber = prs[0].number;
}
catch (e) {
core.setOutput('run-e2e', false);
console.log(e);
return;
}
}
// Always fetch fresh PR data to get current labels
// This avoids stale label data from event payloads
try {
const { data: pr } = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber
});
console.log(`PR number: ${pr.number}`);
console.log(`PR title: ${pr.title}`);
console.log(`PR state: ${pr.state}`);
console.log(`PR URL: ${pr.html_url}`);
labels = pr.labels;
}
catch (e) {
core.setOutput('run-e2e', false);
console.log(e);
return;
}
const labelFound = labels.map(l => l.name).includes('ready-for-e2e');
console.log('Found the label?', labelFound);
core.setOutput('run-e2e', labelFound);
deps:
name: Install dependencies
needs: [prepare]
if: ${{ needs.prepare.outputs.has-files-requiring-all-checks == 'true' }}
uses: ./.github/workflows/yarn-install.yml
type-check:
name: Type check
needs: [prepare, deps]
if: ${{ needs.prepare.outputs.has-files-requiring-all-checks == 'true' }}
uses: ./.github/workflows/check-types.yml
secrets: inherit
lint:
name: Linters
needs: [prepare, deps]
if: ${{ needs.prepare.outputs.has-files-requiring-all-checks == 'true' }}
uses: ./.github/workflows/lint.yml
secrets: inherit
unit-test:
name: Tests
needs: [prepare, deps]
if: ${{ needs.prepare.outputs.has-files-requiring-all-checks == 'true' }}
uses: ./.github/workflows/unit-tests.yml
secrets: inherit
setup-db:
name: Setup Database
needs: [prepare, deps]
if: ${{ needs.prepare.outputs.run-e2e == 'true' && needs.prepare.outputs.has-files-requiring-all-checks == 'true' }}
uses: ./.github/workflows/setup-db.yml
with:
COMMIT_SHA: ${{ needs.prepare.outputs.commit-sha }}
secrets: inherit
build-api-v1:
name: Production builds
needs: [prepare, deps, setup-db]
if: ${{ needs.prepare.outputs.run-e2e == 'true' && needs.prepare.outputs.has-files-requiring-all-checks == 'true' }}
uses: ./.github/workflows/api-v1-production-build.yml
secrets: inherit
build-api-v2:
name: Production builds
needs: [prepare, deps]
if: ${{ needs.prepare.outputs.run-e2e == 'true' && needs.prepare.outputs.has-files-requiring-all-checks == 'true' }}
uses: ./.github/workflows/api-v2-production-build.yml
secrets: inherit
build-atoms:
name: Production builds
needs: [prepare, deps]
if: ${{ needs.prepare.outputs.run-e2e == 'true' && needs.prepare.outputs.has-files-requiring-all-checks == 'true' }}
uses: ./.github/workflows/atoms-production-build.yml
secrets: inherit
build-docs:
name: Production builds
needs: [prepare, deps]
if: ${{ needs.prepare.outputs.run-e2e == 'true' && needs.prepare.outputs.has-files-requiring-all-checks == 'true' }}
uses: ./.github/workflows/docs-build.yml
secrets: inherit
build-companion:
name: Companion builds
needs: [prepare]
if: needs.prepare.outputs.has_companion == 'true'
uses: ./.github/workflows/companion-build.yml
secrets: inherit
build:
name: Production builds
needs: [prepare, deps]
if: ${{ needs.prepare.outputs.run-e2e == 'true' && needs.prepare.outputs.has-files-requiring-all-checks == 'true' }}
uses: ./.github/workflows/production-build-without-database.yml
secrets: inherit
integration-test:
name: Tests
needs: [prepare, build, setup-db]
if: ${{ needs.prepare.outputs.run-e2e == 'true' && needs.prepare.outputs.has-files-requiring-all-checks == 'true' }}
uses: ./.github/workflows/integration-tests.yml
secrets: inherit
e2e:
name: Tests
needs: [prepare, build, setup-db]
if: ${{ needs.prepare.outputs.run-e2e == 'true' && needs.prepare.outputs.has-files-requiring-all-checks == 'true' }}
uses: ./.github/workflows/e2e.yml
secrets: inherit
e2e-api-v2:
name: Tests
needs: [prepare, setup-db]
if: ${{ needs.prepare.outputs.run-e2e == 'true' && needs.prepare.outputs.has-files-requiring-all-checks == 'true' }}
uses: ./.github/workflows/e2e-api-v2.yml
secrets: inherit
e2e-app-store:
name: Tests
needs: [prepare, build, setup-db]
if: ${{ needs.prepare.outputs.run-e2e == 'true' && needs.prepare.outputs.has-files-requiring-all-checks == 'true' }}
uses: ./.github/workflows/e2e-app-store.yml
secrets: inherit
e2e-embed:
name: Tests
needs: [prepare, build, setup-db]
if: ${{ needs.prepare.outputs.run-e2e == 'true' && needs.prepare.outputs.has-files-requiring-all-checks == 'true' }}
uses: ./.github/workflows/e2e-embed.yml
secrets: inherit
e2e-embed-react:
name: Tests
needs: [prepare, build, setup-db]
if: ${{ needs.prepare.outputs.run-e2e == 'true' && needs.prepare.outputs.has-files-requiring-all-checks == 'true' }}
uses: ./.github/workflows/e2e-embed-react.yml
secrets: inherit
analyze:
name: Analyze Build
needs: [build]
uses: ./.github/workflows/nextjs-bundle-analysis.yml
secrets: inherit
required:
needs:
[
prepare,
lint,
type-check,
unit-test,
integration-test,
build,
build-api-v1,
build-api-v2,
build-atoms,
build-docs,
build-companion,
setup-db,
e2e,
e2e-api-v2,
e2e-embed,
e2e-embed-react,
e2e-app-store,
]
if: always()
runs-on: buildjet-2vcpu-ubuntu-2204
steps:
- name: fail if conditional jobs failed
run: exit 1
if: |
(
needs.prepare.outputs.has-files-requiring-all-checks == 'true' &&
(
needs.lint.result != 'success' ||
needs.type-check.result != 'success' ||
needs.unit-test.result != 'success' ||
needs.build.result != 'success' ||
needs.build-api-v1.result != 'success' ||
needs.build-api-v2.result != 'success' ||
needs.build-atoms.result != 'success' ||
needs.build-docs.result != 'success' ||
needs.setup-db.result != 'success' ||
needs.integration-test.result != 'success' ||
needs.e2e.result != 'success' ||
needs.e2e-api-v2.result != 'success' ||
needs.e2e-embed.result != 'success' ||
needs.e2e-embed-react.result != 'success' ||
needs.e2e-app-store.result != 'success'
)
) ||
(
needs.prepare.outputs.has_companion == 'true' &&
needs.build-companion.result != 'success'
)