Skip to content
Merged
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
92 changes: 92 additions & 0 deletions .github/workflows/assign-command.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,19 @@ on:
permissions:
issues: write

env:
PROJECT_ID: PVT_kwHOA8O2Dc4BX1OY
STATUS_FIELD_ID: PVTSSF_lAHOA8O2Dc4BX1OYzhS_ZbU
STATUS_IN_PROGRESS: "98236657"
STATUS_BACKLOG: "f75ad846"

jobs:
assign:
if: ${{ !github.event.issue.pull_request && (github.event.comment.body == '/assign' || github.event.comment.body == '/unassign') }}
runs-on: ubuntu-latest
steps:
- name: Handle /assign
id: do_assign
if: github.event.comment.body == '/assign'
uses: actions/github-script@v7
with:
Expand All @@ -29,6 +36,7 @@ jobs:
owner, repo, issue_number,
body: `⚠️ This issue is already assigned to ${names}. Ask them to comment \`/unassign\` first if they're no longer working on it.`,
});
core.setOutput('assigned', 'false');
return;
}

Expand All @@ -37,8 +45,50 @@ jobs:
owner, repo, issue_number,
body: `✅ Assigned to @${commenter}. Comment \`/unassign\` if you can no longer work on this. Open a PR that includes \`Closes #${issue_number}\` in its description when you're ready for review.`,
});
core.setOutput('assigned', 'true');

- name: Move to In progress
if: github.event.comment.body == '/assign' && steps.do_assign.outputs.assigned == 'true'
uses: actions/github-script@v7
with:
github-token: ${{ secrets.PROJECT_TOKEN }}
script: |
const { data: issue } = await github.rest.issues.get({
owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number,
});

const { node } = await github.graphql(
`query($issueId: ID!) {
node(id: $issueId) {
... on Issue { projectItems(first: 10) { nodes { id project { id } } } }
}
}`,
{ issueId: issue.node_id }
);

const item = node.projectItems.nodes.find(n => n.project.id === process.env.PROJECT_ID);
if (!item) {
console.log('Issue is not on the project board; skipping status update.');
return;
}

await github.graphql(
`mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $optionId: String!) {
updateProjectV2ItemFieldValue(input: {
projectId: $projectId, itemId: $itemId, fieldId: $fieldId,
value: { singleSelectOptionId: $optionId }
}) { projectV2Item { id } }
}`,
{
projectId: process.env.PROJECT_ID,
itemId: item.id,
fieldId: process.env.STATUS_FIELD_ID,
optionId: process.env.STATUS_IN_PROGRESS,
}
);

- name: Handle /unassign
id: do_unassign
if: github.event.comment.body == '/unassign'
uses: actions/github-script@v7
with:
Expand All @@ -55,6 +105,7 @@ jobs:
owner, repo, issue_number,
body: `⚠️ @${commenter}, you're not currently assigned to this issue.`,
});
core.setOutput('unassigned', 'false');
return;
}

Expand All @@ -63,3 +114,44 @@ jobs:
owner, repo, issue_number,
body: `Unassigned @${commenter}. This issue is open again — comment \`/assign\` to pick it up.`,
});
core.setOutput('unassigned', 'true');

- name: Move to Backlog
if: github.event.comment.body == '/unassign' && steps.do_unassign.outputs.unassigned == 'true'
uses: actions/github-script@v7
with:
github-token: ${{ secrets.PROJECT_TOKEN }}
script: |
const { data: issue } = await github.rest.issues.get({
owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number,
});

const { node } = await github.graphql(
`query($issueId: ID!) {
node(id: $issueId) {
... on Issue { projectItems(first: 10) { nodes { id project { id } } } }
}
}`,
{ issueId: issue.node_id }
);

const item = node.projectItems.nodes.find(n => n.project.id === process.env.PROJECT_ID);
if (!item) {
console.log('Issue is not on the project board; skipping status update.');
return;
}

await github.graphql(
`mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $optionId: String!) {
updateProjectV2ItemFieldValue(input: {
projectId: $projectId, itemId: $itemId, fieldId: $fieldId,
value: { singleSelectOptionId: $optionId }
}) { projectV2Item { id } }
}`,
{
projectId: process.env.PROJECT_ID,
itemId: item.id,
fieldId: process.env.STATUS_FIELD_ID,
optionId: process.env.STATUS_BACKLOG,
}
);
76 changes: 76 additions & 0 deletions .github/workflows/pr-board-sync.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
name: PR board sync

on:
pull_request:
types: [opened, edited, ready_for_review]

permissions:
contents: read

env:
PROJECT_ID: PVT_kwHOA8O2Dc4BX1OY
STATUS_FIELD_ID: PVTSSF_lAHOA8O2Dc4BX1OYzhS_ZbU
STATUS_READY_TO_REVIEW: c6aa22db

jobs:
move-linked-issues:
if: ${{ !github.event.pull_request.draft }}
runs-on: ubuntu-latest
steps:
- name: Move linked issues to Ready to review
uses: actions/github-script@v7
with:
github-token: ${{ secrets.PROJECT_TOKEN }}
script: |
const body = context.payload.pull_request.body || '';
const keywords = '(?:close|closes|closed|fix|fixes|fixed|resolve|resolves|resolved)';
const re = new RegExp(`${keywords}\\s+#(\\d+)`, 'gi');
const issueNumbers = [...new Set([...body.matchAll(re)].map(m => Number(m[1])))];

if (issueNumbers.length === 0) {
console.log('No closing keyword found in the PR description; nothing to move.');
return;
}

for (const issue_number of issueNumbers) {
let issue;
try {
({ data: issue } = await github.rest.issues.get({
owner: context.repo.owner, repo: context.repo.repo, issue_number,
}));
} catch (err) {
console.log(`Issue #${issue_number} not found in this repo, skipping.`);
continue;
}

const { node } = await github.graphql(
`query($issueId: ID!) {
node(id: $issueId) {
... on Issue { projectItems(first: 10) { nodes { id project { id } } } }
}
}`,
{ issueId: issue.node_id }
);

const item = node.projectItems.nodes.find(n => n.project.id === process.env.PROJECT_ID);
if (!item) {
console.log(`Issue #${issue_number} is not on the project board, skipping.`);
continue;
}

await github.graphql(
`mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $optionId: String!) {
updateProjectV2ItemFieldValue(input: {
projectId: $projectId, itemId: $itemId, fieldId: $fieldId,
value: { singleSelectOptionId: $optionId }
}) { projectV2Item { id } }
}`,
{
projectId: process.env.PROJECT_ID,
itemId: item.id,
fieldId: process.env.STATUS_FIELD_ID,
optionId: process.env.STATUS_READY_TO_REVIEW,
}
);
console.log(`Moved issue #${issue_number} to Ready to review.`);
}
Loading