Skip to content

Commit

Permalink
✨ reconcile issue reusable workflow
Browse files Browse the repository at this point in the history
Introduce a reusable workflow that can be used by Konveyor projects to:

1. look for `triage/accepted` label. Adds `needs-triage` if missing.
1. look for `kind/` label. Add `needs-kind` if missing.
1. look for `priority/` label. Add `needs-priority` if missing.

When the event type is created/opened, then add it to the Planning board.

Signed-off-by: David Zager <dzager@redhat.com>
  • Loading branch information
djzager committed Feb 2, 2024
1 parent 1bc3c4a commit 288e54f
Show file tree
Hide file tree
Showing 5 changed files with 315 additions and 0 deletions.
12 changes: 12 additions & 0 deletions .github/workflows/_issue_comment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
name: Handle Issue Comment

on:
issue_comment:

jobs:
reconcile-issue-comment:
permissions:
contents: write
issues: write
pull-requests: write
uses: ./.github/workflows/reconcile-issue-comment.yaml
25 changes: 25 additions & 0 deletions .github/workflows/_issues.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Reconcile GitHub Issue

on:
issues:
types:
- opened
- edited
- closed
- reopened
- labeled
- unlabeled

# This prevents potential race conditions by only allowing this action to handle
# one update at a time for a given issue.
concurrency:
group: reconcile-issue-${{ github.event.issue.number }}
cancel-in-progress: true

jobs:
reconcile-issue:
permissions:
contents: write
issues: write
pull-requests: write
uses: ./.github/workflows/reconcile-issue.yaml
116 changes: 116 additions & 0 deletions .github/workflows/reconcile-issue-comment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
name: Reusable Reconcile Issue Comment

on:
workflow_call:

jobs:
event_type:
name: Check event type
runs-on: ubuntu-latest
if: >
github.event_name == 'issue_comment' &&
(
github.event.action == 'created' ||
github.event.action == 'edited'
)
steps:
- name: ok
run: /bin/true

addLabels:
needs: event_type
name: Add Labels
runs-on: ubuntu-latest
steps:
- name: ok
uses: actions/github-script@v7
with:
script: |
const commentRegex = /(<!--.*?-->)|(<!--[\S\s]+?-->)|(<!--[\S\s]*?$)/g;
const labelRegex = /^(\/(area|kind|priority|sig|triage|wg))\s*(.*?)\s*$/gm;
const removeLabelRegex = /^\/remove-(area|committee|kind|language|priority|sig|triage|wg)\s*(.*?)\s*$/gm;
const labelsToAdd = [];
const labelsToRemove = [];
let match;
const { data: comment } = await github.rest.issues.getComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: context.payload.comment.id,
});
const bodyWithoutComments = comment.body.replaceAll(commentRegex, '');
while ((match = labelRegex.exec(bodyWithoutComments)) !== null) {
const keyword = match[2];
const text = match[3];
const labelToAdd = `${keyword}/${text}`;
const labelExists = await github.rest.issues.getLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: labelToAdd,
}).catch(() => false);
if (labelExists) {
labelsToAdd.push(labelToAdd);
} else {
console.log(`label ${labelToAdd} does not exist on this repo`);
}
}
console.log(labelsToAdd);
while ((match = removeLabelRegex.exec(bodyWithoutComments)) !== null) {
const keyword = match[1];
const text = match[2];
const labelToRemove = `${keyword}/${text}`;
labelsToRemove.push(labelToRemove);
}
console.log(labelsToRemove);
if (labelsToAdd.length == 0 && labelsToRemove.length == 0) {
console.log("Nothing to do!");
return;
}
// If we make it this far, we should verify the commenter is at
// least a collaborator.
// TODO(djzager): Is this enough?
const commenterLogin = comment.user.login;
try {
await github.rest.repos.checkCollaborator({
owner: context.repo.owner,
repo: context.repo.repo,
username: commenterLogin,
});
console.log(`Commenter ${commenterLogin} is a collaborator.`);
} catch (error) {
console.log(`Commenter ${commenterLogin} is not a collaborator.`);
await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: "Only collaborators can add/remove labels",
});
return;
}
// Add the labels
if (labelsToAdd.length > 0) {
await github.rest.issues.addLabels({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels: labelsToAdd
});
}
// Remove the labels
if (labelsToRemove.length > 0) {
for (const labelToRemove of labelsToRemove) {
await github.rest.issues.removeLabel({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
name: labelToRemove
});
}
}
156 changes: 156 additions & 0 deletions .github/workflows/reconcile-issue.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
name: Reusable Reconcile Issue

on:
workflow_call:

jobs:
event_type:
name: Check event type
runs-on: ubuntu-latest
if: github.event_name == 'issues' || github.event_name == 'pull_request'
steps:
- name: ok
run: /bin/true

labels:
needs: event_type
name: Check labels
runs-on: ubuntu-latest
if: github.event.action != 'closed'
steps:
- name: triage
uses: actions/github-script@v7
with:
script: |
// begin helper function for adding comments
async function ensureComment(body, shouldExist) {
// Try to find the comment
for await (const { data: comments } of github.paginate.iterator(
github.rest.issues.listComments,
{
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
}
)) {
const comment = comments.find(
(comment) =>
comment.user &&
comment.user.login == "github-actions[bot]" &&
comment.body &&
comment.body.includes(body)
);
// If we find the comment...we are done
if (comment) {
if (!shouldExist) {
await github.rest.issues.deleteComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: comment.id,
});
}
return;
}
}
// Create the comment if it doesn't exist
if (shouldExist) {
await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: body,
});
}
}
// end helper function for adding comments
const { data: issue } = await github.rest.issues.get({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
});
const labels = issue.labels.map(label => label.name);
console.log(`Labels: ${labels}`);
// triage label
if (labels.some(label => label.match('^triage/accepted$'))) {
if (labels.includes('needs-triage')) {
await github.rest.issues.removeLabel({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
name: 'needs-triage'
});
}
} else {
let comment = "This issue is currently awaiting triage.\n";
comment += "If contributors determine this is a relevant issue, they will accept it by applying the `triage/accepted` label and provide further guidance.\n";
comment += "The `triage/accepted` label can be added by org members.";
await github.rest.issues.addLabels({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels: ['needs-triage']
});
await ensureComment(comment, true);
}
// kind label
if (labels.some(label => label.match('^kind/'))) {
if (labels.includes('needs-kind')) {
await github.rest.issues.removeLabel({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
name: 'needs-kind'
});
}
} else {
await github.rest.issues.addLabels({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels: ['needs-kind']
});
}
// priority label
if (labels.some(label => label.match('^priority/'))) {
if (labels.includes('needs-priority')) {
await github.rest.issues.removeLabel({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
name: 'needs-priority'
});
}
} else {
await github.rest.issues.addLabels({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels: ['needs-priority']
});
}
// good first issue
let firstIssueComment = "This issue has been marked 'good first issue'\n";
firstIssueComment += "Please, make sure it aligns with the criteria found [here](https://contribute.cncf.io/maintainers/templates/issue-labels/#good-first-issue)\n";
if (labels.some(label => label.match('^good first issue$'))) {
await ensureComment(firstIssueComment, true);
} else {
await ensureComment(firstIssueComment, false);
}
project:
needs: event_type
name: Add new issues to planning project
runs-on: ubuntu-latest
if: github.event.action == 'opened'
steps:
- uses: actions/add-to-project@v0.5.0
with:
project-url: https://github.com/orgs/konveyor/projects/67
6 changes: 6 additions & 0 deletions pkg/config/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ labels:
description: Indicates an issue that is a support question.
name: triage/support
# Kind
- color: ededed
description: Indicates an issue or PR lacks a `kind/foo` label and requires one.
name: needs-kind
- color: e11d21
description: Categorizes issue or PR as related to a bug.
name: kind/bug
Expand All @@ -76,6 +79,9 @@ labels:
description: Categorizes issue or PR as related to a new feature.
name: kind/feature
# Priority
- color: ededed
description: Indicates an issue or PR lacks a `priority/foo` label and requires one.
name: needs-priority
- color: fef2c0
description: Lowest priority. Possibly useful, but not yet enough support to actually get it done. # These are mostly place-holders for potentially good ideas, so that they don't get completely forgotten, and can be referenced /deduped every time they come up.
name: priority/awaiting-more-evidence
Expand Down

0 comments on commit 288e54f

Please sign in to comment.