Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,26 @@ jobs:
event_type: downstream-ci-hpc
payload: '{"fdb": "ecmwf/fdb@${{ github.event.pull_request.head.sha || github.sha }}"}'

downstream-triage:
name: downstream-triage
needs:
- downstream-ci
- private-downstream-ci
- downstream-ci-hpc
- private-downstream-ci-hpc
if: ${{ !github.event.pull_request.draft && always() && !github.event.pull_request.head.repo.fork && (github.event.action != 'labeled' || github.event.label.name == 'approved-for-ci') }}
uses: ./.github/workflows/downstream-triage.yml
permissions:
actions: read
checks: read
contents: read
with:
downstream_ci_result: ${{ needs.downstream-ci.result }}
private_downstream_ci_result: ${{ needs.private-downstream-ci.result }}
downstream_ci_hpc_result: ${{ needs.downstream-ci-hpc.result }}
private_downstream_ci_hpc_result: ${{ needs.private-downstream-ci-hpc.result }}
run_url: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}

notify:
runs-on: ubuntu-latest
needs:
Expand Down
129 changes: 129 additions & 0 deletions .github/workflows/downstream-triage.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
name: downstream-triage

on:
workflow_call:
inputs:
downstream_ci_result:
required: true
type: string
private_downstream_ci_result:
required: true
type: string
downstream_ci_hpc_result:
required: true
type: string
private_downstream_ci_hpc_result:
required: true
type: string
run_url:
required: true
type: string

jobs:
downstream-triage:
name: downstream-triage
runs-on: ubuntu-latest
permissions:
actions: read
checks: read
contents: read
steps:
- name: CI Triage
env:
DOWNSTREAM_CI_RESULT: ${{ inputs.downstream_ci_result }}
PRIVATE_DOWNSTREAM_CI_RESULT: ${{ inputs.private_downstream_ci_result }}
DOWNSTREAM_CI_HPC_RESULT: ${{ inputs.downstream_ci_hpc_result }}
PRIVATE_DOWNSTREAM_CI_HPC_RESULT: ${{ inputs.private_downstream_ci_hpc_result }}
RUN_URL: ${{ inputs.run_url }}
run: |
{
echo "## Downstream CI Triage"
echo
echo "- downstream-ci: ${DOWNSTREAM_CI_RESULT}"
echo "- private-downstream-ci: ${PRIVATE_DOWNSTREAM_CI_RESULT}"
echo "- downstream-ci-hpc: ${DOWNSTREAM_CI_HPC_RESULT}"
echo "- private-downstream-ci-hpc: ${PRIVATE_DOWNSTREAM_CI_HPC_RESULT}"
echo
echo "Workflow run: ${RUN_URL}"
} >> "$GITHUB_STEP_SUMMARY"

- name: List failed runs
if: ${{ inputs.downstream_ci_result == 'failure' || inputs.private_downstream_ci_result == 'failure' || inputs.downstream_ci_hpc_result == 'failure' || inputs.private_downstream_ci_hpc_result == 'failure' }}
uses: actions/github-script@v8
with:
script: |
const owner = context.repo.owner;
const repo = context.repo.repo;
const ref = context.sha;
const runId = context.runId;

const failedConclusions = new Set(['failure', 'timed_out', 'cancelled', 'startup_failure']);
const excludedPatterns = ['downstream-triage', 'notify'];
const isExcluded = (name) => {
const n = (name || '').toLowerCase();
return excludedPatterns.some((p) => n.includes(p));
};

// 1) Primary source: failed jobs in this workflow run.
const jobs = await github.paginate(github.rest.actions.listJobsForWorkflowRun, {
owner,
repo,
run_id: runId,
per_page: 100,
});

const failedJobs = jobs.filter((j) => {
if (!failedConclusions.has(j.conclusion)) {
return false;
}
if (isExcluded(j.name)) {
return false;
}
return true;
});

const checks = await github.paginate(github.rest.checks.listForRef, {
owner,
repo,
ref,
per_page: 100,
});

// 2) Secondary source: failed check-runs for this ref.
const failedChecks = checks.filter((c) => {
if (!failedConclusions.has(c.conclusion)) {
return false;
}
if (isExcluded(c.name)) {
return false;
}
return true;
});

const header = '### Failed Downstream Runs';
if (failedJobs.length === 0 && failedChecks.length === 0) {
await core.summary
.addRaw(`${header}\n\nNo failed runs found in jobs or check-runs.\n\n`)
.write();
return;
}

const jobLines = failedJobs.slice(0, 30).map((j) => {
const url = j.html_url || `${context.serverUrl}/${owner}/${repo}/actions/runs/${runId}`;
return `- [${j.name}](${url}) (${j.conclusion})`;
});

const checkLines = failedChecks.slice(0, 30).map((c) => {
const url = c.html_url || `${context.serverUrl}/${owner}/${repo}/actions/runs/${runId}`;
return `- [${c.name}](${url}) (${c.conclusion})`;
});

let out = `${header}\n\n`;
out += '#### Failed jobs in this workflow run\n\n';
out += jobLines.length ? `${jobLines.join('\n')}\n\n` : 'None\n\n';
out += '#### Failed check-runs on this ref\n\n';
out += checkLines.length ? `${checkLines.join('\n')}\n\n` : 'None\n\n';

await core.summary
.addRaw(out)
.write();
Loading