-
Notifications
You must be signed in to change notification settings - Fork 395
Fix e2e tests trigger #4850
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Fix e2e tests trigger #4850
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
c2288b5
add github secret to label action
KrumTy 77460cf
Merge branch 'main' into bugfix/e2e-tests-trigger
KrumTy cf51ebd
use another github token
KrumTy 4f21a9d
detach e2e tests job from labels
KrumTy 3125018
update e2e trigger, add deduplicate workflow
KrumTy 56e5367
update workflow conditions, unify builds
KrumTy e1be5ba
add dynamic name for e2e test workflow
KrumTy 12f2a87
log duplicated run link
KrumTy e23971d
Merge branch 'main' into bugfix/e2e-tests-trigger
KrumTy 0ad6c69
remove default workflow_filename
KrumTy e48704e
describe "magic" numbers
KrumTy File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,189 @@ | ||
# Reusable workflow: Approval-based deduplication by PR head SHA | ||
# | ||
# Purpose | ||
# - Gate heavy CI jobs to run only on Approved PR reviews (or manual dispatch in the caller), | ||
# - De-duplicate runs for the same PR head SHA: | ||
# - mode=run: no prior run found → caller should run heavy jobs | ||
# - mode=wait: a prior run is queued/in_progress → this workflow waits and mirrors its result | ||
# - mode=mirror_completed: a prior run already completed → mirror its conclusion immediately | ||
# - mode=noop: not an approved review (and not allowed workflow_dispatch) → do nothing | ||
# | ||
# How to use (in a caller workflow): | ||
# - Ensure the caller triggers on: pull_request_review: [submitted] and/or workflow_dispatch | ||
# - Add a job that calls this workflow: | ||
# jobs: | ||
# approval-dedupe: | ||
# uses: ./.github/workflows/approval-dedupe.yml | ||
# secrets: inherit | ||
# with: | ||
# workflow_filename: tests-e2e.yml # set to the caller's filename | ||
# require_approval: true # only run on Approved reviews | ||
# allow_workflow_dispatch: true # allow manual runs to go to mode=run | ||
# - Gate heavy jobs in the caller with: | ||
# needs: approval-dedupe | ||
# if: needs.approval-dedupe.outputs.mode == 'run' | ||
# - Recommended in caller: set concurrency to key by head SHA and cancel-in-progress: false | ||
# | ||
|
||
name: Approval Deduplicate | ||
|
||
on: | ||
workflow_call: | ||
inputs: | ||
workflow_filename: | ||
description: The workflow filename to inspect for prior runs (e.g., tests-e2e.yml) | ||
required: true | ||
type: string | ||
require_approval: | ||
description: Require an Approved review to proceed (ignored for workflow_dispatch when allowed) | ||
required: false | ||
type: boolean | ||
default: true | ||
allow_workflow_dispatch: | ||
description: Allow workflow_dispatch to always run heavy jobs | ||
required: false | ||
type: boolean | ||
default: true | ||
head_sha: | ||
description: Optional explicit head SHA to use for de-duplication. Defaults to PR head SHA | ||
required: false | ||
type: string | ||
outputs: | ||
mode: | ||
description: One of run | wait | mirror_completed | noop | ||
value: ${{ jobs.gate.outputs.mode }} | ||
target_run_id: | ||
description: If mode == wait, the prior run id to wait on | ||
value: ${{ jobs.gate.outputs.target_run_id }} | ||
prev_conclusion: | ||
description: If mode == mirror_completed, the conclusion to mirror | ||
value: ${{ jobs.gate.outputs.prev_conclusion }} | ||
prev_run_id: | ||
description: If mode == mirror_completed, the prior run id being mirrored | ||
value: ${{ jobs.gate.outputs.prev_run_id }} | ||
|
||
permissions: | ||
actions: read | ||
contents: read | ||
|
||
jobs: | ||
gate: | ||
name: Gate and determine mode | ||
runs-on: ubuntu-latest | ||
outputs: | ||
mode: ${{ steps.check.outputs.mode }} | ||
target_run_id: ${{ steps.check.outputs.target_run_id }} | ||
prev_conclusion: ${{ steps.check.outputs.prev_conclusion }} | ||
prev_run_id: ${{ steps.check.outputs.prev_run_id }} | ||
steps: | ||
- name: Determine mode | ||
id: check | ||
uses: actions/github-script@v7 | ||
with: | ||
script: | | ||
const requireApproval = core.getInput('require_approval') === 'true' | ||
const allowDispatch = core.getInput('allow_workflow_dispatch') === 'true' | ||
const workflowFilename = core.getInput('workflow_filename') | ||
const explicitSha = (core.getInput('head_sha') || '').trim() | ||
|
||
if (context.eventName === 'workflow_dispatch' && allowDispatch) { | ||
core.setOutput('mode', 'run') | ||
return | ||
} | ||
|
||
if (requireApproval) { | ||
const approved = (context.eventName === 'pull_request_review') && | ||
(context.payload.review?.state?.toLowerCase() === 'approved') | ||
if (!approved) { | ||
core.setOutput('mode', 'noop') | ||
return | ||
} | ||
} | ||
|
||
const sha = explicitSha || context.payload.pull_request?.head?.sha | ||
if (!sha) { | ||
core.setOutput('mode', 'run') | ||
return | ||
} | ||
|
||
const resp = await github.rest.actions.listWorkflowRuns({ | ||
owner: context.repo.owner, | ||
repo: context.repo.repo, | ||
workflow_id: workflowFilename, | ||
head_sha: sha, | ||
per_page: 50, | ||
}) | ||
|
||
const currentRunId = context.runId | ||
const runs = (resp.data.workflow_runs || []).filter(r => r.id !== currentRunId) | ||
const active = runs.find(r => ['queued', 'in_progress'].includes(r.status)) | ||
if (active) { | ||
core.setOutput('mode', 'wait') | ||
core.setOutput('target_run_id', String(active.id)) | ||
core.setOutput('prev_run_id', String(active.id)) | ||
return | ||
} | ||
|
||
const completed = runs | ||
.filter(r => r.status === 'completed') | ||
.sort((a, b) => new Date(b.created_at) - new Date(a.created_at))[0] | ||
if (completed) { | ||
core.setOutput('mode', 'mirror_completed') | ||
core.setOutput('prev_conclusion', completed.conclusion || '') | ||
core.setOutput('prev_run_id', String(completed.id)) | ||
return | ||
} | ||
|
||
core.setOutput('mode', 'run') | ||
|
||
wait-previous: | ||
name: Wait for previous run | ||
needs: gate | ||
if: needs.gate.outputs.mode == 'wait' | ||
runs-on: ubuntu-latest | ||
steps: | ||
- name: Wait and mirror | ||
uses: actions/github-script@v7 | ||
with: | ||
script: | | ||
const MAX_WAIT_MS = 60 * 60 * 1000 // 1 hour | ||
const POLL_INTERVAL_MS = 15 * 1000 // 15 seconds | ||
const runId = Number('${{ needs.gate.outputs.target_run_id }}') | ||
const poll = async () => { | ||
const { data } = await github.rest.actions.getWorkflowRun({ | ||
owner: context.repo.owner, | ||
repo: context.repo.repo, | ||
run_id: runId, | ||
}) | ||
return data | ||
} | ||
let data = await poll() | ||
const started = Date.now() | ||
while (data.status !== 'completed') { | ||
if (Date.now() - started > MAX_WAIT_MS) { | ||
core.setFailed('Timeout waiting for prior run') | ||
return | ||
} | ||
await new Promise(r => setTimeout(r, POLL_INTERVAL_MS)) | ||
data = await poll() | ||
} | ||
const conclusion = data.conclusion || 'failure' | ||
if (conclusion !== 'success') { | ||
core.setFailed(`Mirroring prior run conclusion: ${conclusion}`) | ||
} | ||
|
||
mirror-completed: | ||
name: Mirror completed run | ||
needs: gate | ||
if: needs.gate.outputs.mode == 'mirror_completed' | ||
runs-on: ubuntu-latest | ||
steps: | ||
- name: Mirror result | ||
run: | | ||
echo "Previous conclusion: ${{ needs.gate.outputs.prev_conclusion }}" | ||
echo "Mirroring workflow run: https://github.com/${{ github.repository }}/actions/runs/${{ needs.gate.outputs.prev_run_id }}" | ||
if [ "${{ needs.gate.outputs.prev_conclusion }}" != "success" ]; then | ||
echo "Mirroring failure" | ||
exit 1 | ||
fi | ||
echo "Mirroring success" |
This file was deleted.
Oops, something went wrong.
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
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
intermediate job to wait for both builds to complete (~5mins) to avoid running tests when the other build has failed and the whole workflow will be marked as failed in the end anyway