From 221ac5646f7ab508517a1c76c3a17e8953e1c67d Mon Sep 17 00:00:00 2001 From: Petra Hapalova Date: Wed, 17 Dec 2025 16:14:54 +0100 Subject: [PATCH 1/3] add workflow commands Signed-off-by: Petra Hapalova --- .github/workflows/codestyle.yml | 1 + .github/workflows/cu128.yml | 1 + .github/workflows/cu130.yml | 1 + .github/workflows/pr-commands.yml | 275 ++++++++++++++++++++++++++++++ 4 files changed, 278 insertions(+) create mode 100644 .github/workflows/pr-commands.yml diff --git a/.github/workflows/codestyle.yml b/.github/workflows/codestyle.yml index 68d389162..343aa538c 100644 --- a/.github/workflows/codestyle.yml +++ b/.github/workflows/codestyle.yml @@ -3,6 +3,7 @@ on: pull_request: branches: - '**' + workflow_dispatch: # Allow subsequent pushes to the same PR or REF to cancel any previous jobs. concurrency: diff --git a/.github/workflows/cu128.yml b/.github/workflows/cu128.yml index bfcdea89b..3cd0bbb4e 100644 --- a/.github/workflows/cu128.yml +++ b/.github/workflows/cu128.yml @@ -11,6 +11,7 @@ on: - 'examples/**' - 'notebooks/**' - 'scripts/**' + workflow_dispatch: # Allow subsequent pushes to the same PR or REF to cancel any previous jobs. concurrency: diff --git a/.github/workflows/cu130.yml b/.github/workflows/cu130.yml index b9393398c..f9016971c 100644 --- a/.github/workflows/cu130.yml +++ b/.github/workflows/cu130.yml @@ -11,6 +11,7 @@ on: - 'examples/**' - 'notebooks/**' - 'scripts/**' + workflow_dispatch: # Allow subsequent pushes to the same PR or REF to cancel any previous jobs. concurrency: diff --git a/.github/workflows/pr-commands.yml b/.github/workflows/pr-commands.yml new file mode 100644 index 000000000..36dbb823b --- /dev/null +++ b/.github/workflows/pr-commands.yml @@ -0,0 +1,275 @@ +name: PR Workflow Commands + +on: + issue_comment: + types: [created] + +jobs: + rerun-failed-workflows: + name: Rerun Failed Workflows + if: > + github.event.issue.pull_request && + contains(github.event.comment.body, '/rerun-all') && + ( + github.event.comment.author_association == 'OWNER' || + github.event.comment.author_association == 'MEMBER' || + github.event.comment.author_association == 'COLLABORATOR' + ) + runs-on: ubuntu-latest + permissions: + actions: write + pull-requests: write + checks: write + steps: + - name: Add reaction to comment + uses: actions/github-script@v7 + with: + script: | + await github.rest.reactions.createForIssueComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: context.payload.comment.id, + content: 'rocket' + }); + + - uses: estroz/rerun-actions@v0.3.0 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + comment_id: ${{ github.event.comment.id }} + + run-all-workflows: + name: Run All Workflows + if: > + github.event.issue.pull_request && + contains(github.event.comment.body, '/run-all') && + ( + github.event.comment.author_association == 'OWNER' || + github.event.comment.author_association == 'MEMBER' || + github.event.comment.author_association == 'COLLABORATOR' + ) + runs-on: ubuntu-latest + permissions: + actions: write + pull-requests: write + steps: + - name: Add reaction to comment + uses: actions/github-script@v7 + with: + script: | + await github.rest.reactions.createForIssueComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: context.payload.comment.id, + content: 'rocket' + }); + + - name: Get PR info + id: pr-info + uses: actions/github-script@v7 + with: + script: | + const pr = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.issue.number + }); + core.setOutput('ref', pr.data.head.ref); + console.log(`PR branch: ${pr.data.head.ref}`); + + - name: Trigger all workflows + uses: actions/github-script@v7 + with: + script: | + const ref = '${{ steps.pr-info.outputs.ref }}'; + const workflows = [ + 'tests.yml', + 'codestyle.yml', + 'cu128.yml', + 'cu130.yml' + ]; + + // Helper to find the triggered run with retries + async function findWorkflowRun(workflowId, beforeTime) { + const maxRetries = 5; + const delayMs = 3000; + + for (let attempt = 1; attempt <= maxRetries; attempt++) { + await new Promise(resolve => setTimeout(resolve, delayMs)); + console.log(`Looking for ${workflowId} run (attempt ${attempt}/${maxRetries})...`); + + const runs = await github.rest.actions.listWorkflowRuns({ + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: workflowId, + branch: ref, + per_page: 10, + event: 'workflow_dispatch' + }); + + // Find a run created after we triggered it + const run = runs.data.workflow_runs.find(r => + new Date(r.created_at) >= beforeTime + ); + + if (run) { + console.log(`Found run: ${run.html_url}`); + return run; + } + } + return null; + } + + const results = []; + for (const workflow of workflows) { + const beforeTime = new Date(); + try { + console.log(`Triggering ${workflow} on ref ${ref}`); + await github.rest.actions.createWorkflowDispatch({ + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: workflow, + ref: ref + }); + console.log(`Successfully triggered ${workflow}`); + + // Try to find the run and get its URL + const run = await findWorkflowRun(workflow, beforeTime); + if (run) { + results.push(`* [\`${workflow}\`](${run.html_url})`); + } else { + results.push(`* \`${workflow}\` (run pending)`); + } + } catch (error) { + console.log(`Failed to trigger ${workflow}: ${error.message}`); + results.push(`* \`${workflow}\`: ${error.message}`); + } + } + + // Post summary comment + const body = `### Triggered workflows on branch \`${ref}\`\n\n${results.join('\n')}`; + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: body + }); + + run-single-workflow: + name: Run Single Workflow + if: > + github.event.issue.pull_request && + ( + contains(github.event.comment.body, '/run-tests') || + contains(github.event.comment.body, '/run-codestyle') || + contains(github.event.comment.body, '/run-cu128') || + contains(github.event.comment.body, '/run-cu130') + ) && + ( + github.event.comment.author_association == 'OWNER' || + github.event.comment.author_association == 'MEMBER' || + github.event.comment.author_association == 'COLLABORATOR' + ) + runs-on: ubuntu-latest + permissions: + actions: write + pull-requests: write + steps: + - name: Add reaction to comment + uses: actions/github-script@v7 + with: + script: | + await github.rest.reactions.createForIssueComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: context.payload.comment.id, + content: 'rocket' + }); + + - name: Get PR info + id: pr-info + uses: actions/github-script@v7 + with: + script: | + const pr = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.issue.number + }); + core.setOutput('ref', pr.data.head.ref); + console.log(`PR branch: ${pr.data.head.ref}`); + + - name: Trigger requested workflow + uses: actions/github-script@v7 + with: + script: | + const ref = '${{ steps.pr-info.outputs.ref }}'; + const comment = context.payload.comment.body; + + const workflowMap = { + '/run-tests': 'tests.yml', + '/run-codestyle': 'codestyle.yml', + '/run-cu128': 'cu128.yml', + '/run-cu130': 'cu130.yml' + }; + + // Helper to find the triggered run with retries + async function findWorkflowRun(workflowId, beforeTime) { + const maxRetries = 5; + const delayMs = 3000; + + for (let attempt = 1; attempt <= maxRetries; attempt++) { + await new Promise(resolve => setTimeout(resolve, delayMs)); + console.log(`Looking for ${workflowId} run (attempt ${attempt}/${maxRetries})...`); + + const runs = await github.rest.actions.listWorkflowRuns({ + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: workflowId, + branch: ref, + per_page: 10, + event: 'workflow_dispatch' + }); + + const run = runs.data.workflow_runs.find(r => + new Date(r.created_at) >= beforeTime + ); + + if (run) { + console.log(`Found run: ${run.html_url}`); + return run; + } + } + return null; + } + + for (const [command, workflow] of Object.entries(workflowMap)) { + if (comment.includes(command)) { + const beforeTime = new Date(); + try { + console.log(`Triggering ${workflow} on ref ${ref}`); + await github.rest.actions.createWorkflowDispatch({ + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: workflow, + ref: ref + }); + + const run = await findWorkflowRun(workflow, beforeTime); + const link = run ? `[\`${workflow}\`](${run.html_url})` : `\`${workflow}\``; + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: `* Triggered ${link} on branch \`${ref}\`` + }); + } catch (error) { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: `* Failed to trigger \`${workflow}\`: ${error.message}` + }); + } + } + } From 7cc71e125571d28e300c140a70b99757b57b1af2 Mon Sep 17 00:00:00 2001 From: Petra Hapalova Date: Wed, 17 Dec 2025 16:29:48 +0100 Subject: [PATCH 2/3] fixes Signed-off-by: Petra Hapalova --- .github/workflows/pr-commands.yml | 164 ++++++------------------------ 1 file changed, 31 insertions(+), 133 deletions(-) diff --git a/.github/workflows/pr-commands.yml b/.github/workflows/pr-commands.yml index 36dbb823b..7ed4f7198 100644 --- a/.github/workflows/pr-commands.yml +++ b/.github/workflows/pr-commands.yml @@ -37,11 +37,17 @@ jobs: repo_token: ${{ secrets.GITHUB_TOKEN }} comment_id: ${{ github.event.comment.id }} - run-all-workflows: - name: Run All Workflows + run-workflows: + name: Run Workflows if: > github.event.issue.pull_request && - contains(github.event.comment.body, '/run-all') && + ( + contains(github.event.comment.body, '/run-all') || + contains(github.event.comment.body, '/run-tests') || + contains(github.event.comment.body, '/run-codestyle') || + contains(github.event.comment.body, '/run-cu128') || + contains(github.event.comment.body, '/run-cu130') + ) && ( github.event.comment.author_association == 'OWNER' || github.event.comment.author_association == 'MEMBER' || @@ -76,17 +82,31 @@ jobs: core.setOutput('ref', pr.data.head.ref); console.log(`PR branch: ${pr.data.head.ref}`); - - name: Trigger all workflows + - name: Trigger workflows uses: actions/github-script@v7 with: script: | const ref = '${{ steps.pr-info.outputs.ref }}'; - const workflows = [ - 'tests.yml', - 'codestyle.yml', - 'cu128.yml', - 'cu130.yml' - ]; + const comment = context.payload.comment.body; + + const workflowMap = { + '/run-tests': 'tests.yml', + '/run-codestyle': 'codestyle.yml', + '/run-cu128': 'cu128.yml', + '/run-cu130': 'cu130.yml' + }; + + // Determine which workflows to run + let workflowsToRun = []; + if (comment.includes('/run-all')) { + workflowsToRun = Object.values(workflowMap); + } else { + for (const [command, workflow] of Object.entries(workflowMap)) { + if (comment.includes(command)) { + workflowsToRun.push(workflow); + } + } + } // Helper to find the triggered run with retries async function findWorkflowRun(workflowId, beforeTime) { @@ -106,7 +126,6 @@ jobs: event: 'workflow_dispatch' }); - // Find a run created after we triggered it const run = runs.data.workflow_runs.find(r => new Date(r.created_at) >= beforeTime ); @@ -120,7 +139,7 @@ jobs: } const results = []; - for (const workflow of workflows) { + for (const workflow of workflowsToRun) { const beforeTime = new Date(); try { console.log(`Triggering ${workflow} on ref ${ref}`); @@ -132,7 +151,6 @@ jobs: }); console.log(`Successfully triggered ${workflow}`); - // Try to find the run and get its URL const run = await findWorkflowRun(workflow, beforeTime); if (run) { results.push(`* [\`${workflow}\`](${run.html_url})`); @@ -153,123 +171,3 @@ jobs: issue_number: context.issue.number, body: body }); - - run-single-workflow: - name: Run Single Workflow - if: > - github.event.issue.pull_request && - ( - contains(github.event.comment.body, '/run-tests') || - contains(github.event.comment.body, '/run-codestyle') || - contains(github.event.comment.body, '/run-cu128') || - contains(github.event.comment.body, '/run-cu130') - ) && - ( - github.event.comment.author_association == 'OWNER' || - github.event.comment.author_association == 'MEMBER' || - github.event.comment.author_association == 'COLLABORATOR' - ) - runs-on: ubuntu-latest - permissions: - actions: write - pull-requests: write - steps: - - name: Add reaction to comment - uses: actions/github-script@v7 - with: - script: | - await github.rest.reactions.createForIssueComment({ - owner: context.repo.owner, - repo: context.repo.repo, - comment_id: context.payload.comment.id, - content: 'rocket' - }); - - - name: Get PR info - id: pr-info - uses: actions/github-script@v7 - with: - script: | - const pr = await github.rest.pulls.get({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: context.issue.number - }); - core.setOutput('ref', pr.data.head.ref); - console.log(`PR branch: ${pr.data.head.ref}`); - - - name: Trigger requested workflow - uses: actions/github-script@v7 - with: - script: | - const ref = '${{ steps.pr-info.outputs.ref }}'; - const comment = context.payload.comment.body; - - const workflowMap = { - '/run-tests': 'tests.yml', - '/run-codestyle': 'codestyle.yml', - '/run-cu128': 'cu128.yml', - '/run-cu130': 'cu130.yml' - }; - - // Helper to find the triggered run with retries - async function findWorkflowRun(workflowId, beforeTime) { - const maxRetries = 5; - const delayMs = 3000; - - for (let attempt = 1; attempt <= maxRetries; attempt++) { - await new Promise(resolve => setTimeout(resolve, delayMs)); - console.log(`Looking for ${workflowId} run (attempt ${attempt}/${maxRetries})...`); - - const runs = await github.rest.actions.listWorkflowRuns({ - owner: context.repo.owner, - repo: context.repo.repo, - workflow_id: workflowId, - branch: ref, - per_page: 10, - event: 'workflow_dispatch' - }); - - const run = runs.data.workflow_runs.find(r => - new Date(r.created_at) >= beforeTime - ); - - if (run) { - console.log(`Found run: ${run.html_url}`); - return run; - } - } - return null; - } - - for (const [command, workflow] of Object.entries(workflowMap)) { - if (comment.includes(command)) { - const beforeTime = new Date(); - try { - console.log(`Triggering ${workflow} on ref ${ref}`); - await github.rest.actions.createWorkflowDispatch({ - owner: context.repo.owner, - repo: context.repo.repo, - workflow_id: workflow, - ref: ref - }); - - const run = await findWorkflowRun(workflow, beforeTime); - const link = run ? `[\`${workflow}\`](${run.html_url})` : `\`${workflow}\``; - - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - body: `* Triggered ${link} on branch \`${ref}\`` - }); - } catch (error) { - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - body: `* Failed to trigger \`${workflow}\`: ${error.message}` - }); - } - } - } From 20d0dcdd57a2d17625bc6d354b21bc4dbadddb27 Mon Sep 17 00:00:00 2001 From: Petra Hapalova Date: Wed, 17 Dec 2025 16:47:12 +0100 Subject: [PATCH 3/3] add hasCommand helper Signed-off-by: Petra Hapalova --- .github/workflows/pr-commands.yml | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/.github/workflows/pr-commands.yml b/.github/workflows/pr-commands.yml index 7ed4f7198..1c78c3cf0 100644 --- a/.github/workflows/pr-commands.yml +++ b/.github/workflows/pr-commands.yml @@ -69,24 +69,19 @@ jobs: content: 'rocket' }); - - name: Get PR info - id: pr-info + - name: Trigger workflows uses: actions/github-script@v7 with: script: | + // Get PR branch const pr = await github.rest.pulls.get({ owner: context.repo.owner, repo: context.repo.repo, pull_number: context.issue.number }); - core.setOutput('ref', pr.data.head.ref); - console.log(`PR branch: ${pr.data.head.ref}`); + const ref = pr.data.head.ref; + console.log(`PR branch: ${ref}`); - - name: Trigger workflows - uses: actions/github-script@v7 - with: - script: | - const ref = '${{ steps.pr-info.outputs.ref }}'; const comment = context.payload.comment.body; const workflowMap = { @@ -96,13 +91,16 @@ jobs: '/run-cu130': 'cu130.yml' }; + // Helper to check if command is present (with word boundary) + const hasCommand = (cmd) => new RegExp(`${cmd}(?:\\s|$)`).test(comment); + // Determine which workflows to run let workflowsToRun = []; - if (comment.includes('/run-all')) { + if (hasCommand('/run-all')) { workflowsToRun = Object.values(workflowMap); } else { for (const [command, workflow] of Object.entries(workflowMap)) { - if (comment.includes(command)) { + if (hasCommand(command)) { workflowsToRun.push(workflow); } }