Skip to content
110 changes: 110 additions & 0 deletions .github/workflows/inactivity_reminder.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

name: Inactivity Reminder with Different Times

on:
schedule:
- cron: '0 9 * * *' # Runs daily at 09:00 UTC
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it'd be helpful to add workflow_dispatch: for these workflows, so you can test changes by manually triggering them from the GitHub UI.

That'd only work if you opened the PR from a cuopt branch (not your fork), though.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, let me test this out.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jameslamb tested it and behavior is noted in the description #268 (comment)


jobs:
remind:
runs-on: ubuntu-latest
steps:
- name: Remind inactive issues and PRs
uses: actions/github-script@v6
with:
script: |
const MS_IN_DAY = 24 * 60 * 60 * 1000;
const now = new Date();

// Thresholds
const ISSUE_INACTIVITY_DAYS = 21; // 3 weeks
const PR_INACTIVITY_DAYS = 7; // 1 week

// Always notify this user
const defaultNotify = '@anandhkb';

function isInactive(updatedAt, thresholdDays) {
const updatedDate = new Date(updatedAt);
return (now - updatedDate) > thresholdDays * MS_IN_DAY;
}

// Fetch all open issues + PRs (paginate if >100)
const issuesAndPRs = await github.paginate(
github.rest.issues.listForRepo,
{
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
per_page: 100,
}
);

for (const item of issuesAndPRs) {
// Skip if issue/PR has the skip inactivity reminder label
if (item.labels && item.labels.some(label => label.name === 'skip inactivity reminder')) {
console.log(`Skipping #${item.number} due to skip inactivity reminder label`);
continue;
}

const isPR = !!item.pull_request;

// Skip if PR is in Draft mode
if (isPR && item.draft) {
console.log(`Skipping #${item.number} due to Draft PR status`);
continue;
}

// Skip if issue (not PR) is an EPIC
if (!isPR && item.labels && item.labels.some(label => label.name === 'epic')) {
console.log(`Skipping #${item.number} due to EPIC label (issue only)`);
continue;
}

const thresholdDays = isPR ? PR_INACTIVITY_DAYS : ISSUE_INACTIVITY_DAYS;

if (isInactive(item.updated_at, thresholdDays)) {
// For issues, only send reminder if they have "awaiting response" label
if (!isPR && (!item.labels || !item.labels.some(label => label.name === 'awaiting response'))) {
console.log(`Skipping #${item.number} (issue) - no awaiting response label`);
continue;
}

const assigneeMentions = item.assignees && item.assignees.length > 0
? item.assignees.map(a => `@${a.login}`).join(' ')
: '';
const mentions = assigneeMentions
? `${defaultNotify} ${assigneeMentions}`.trim()
: defaultNotify;
const issueNumber = item.number;
const type = isPR ? "pull request" : "issue";

await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
body: [
`🔔 Hi ${mentions}, this ${type} has had no activity for ${thresholdDays} days. Please update or let us know if it can be closed. Thank you!`,
'',
'If this is an "epic" issue, then please add the "epic" label to this issue.',
'If it is a PR and not ready for review, then please convert this to draft.',
'If you just want to switch off this notification, then use the "skip inactivity reminder" label.'
].join('\n'),
});

console.log(`Posted reminder on #${issueNumber} (${type}) to ${mentions}`);
}
}
95 changes: 95 additions & 0 deletions .github/workflows/issue_automation.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

name: Auto-label and Round-Robin Assign Issues

on:
issues:
types: [opened]

jobs:
auto-label:
runs-on: ubuntu-latest
steps:
- name: Add awaiting response label to new issues
uses: actions/github-script@v6
with:
script: |
// Only process issues (not PRs)
if (context.payload.issue && !context.payload.issue.pull_request) {
const issue = context.payload.issue;
const issueNumber = issue.number;

console.log(`Adding 'awaiting response' label to issue #${issueNumber}`);

await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
labels: ['awaiting response']
});

console.log(`Successfully added 'awaiting response' label to issue #${issueNumber}`);
} else {
console.log('Skipping - this is a pull request, not an issue');
}

round-robin-assign:
runs-on: ubuntu-latest
steps:
- name: Assign issue round-robin only if unassigned
uses: actions/github-script@v6
with:
script: |
// Only process issues (not PRs)
if (context.payload.issue && !context.payload.issue.pull_request) {
const issue = context.payload.issue;
const issueNumber = issue.number;

// Round-robin assignment logic
const assignees = [
'kaatish',
'rg20',
'akifcorduk',
'hlinsen',
'Kh4ster',
'aliceb-nv',
'chris-maes',
'rgsl888prabhu',
'Iroy30',
'tmckayus'
];

// Only assign if no one is assigned yet
if (!issue.assignees || issue.assignees.length === 0) {
const index = (issueNumber - 1) % assignees.length;
const assignee = assignees[index];

console.log(`Assigning issue #${issueNumber} to @${assignee}`);

await github.rest.issues.addAssignees({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
assignees: [assignee],
});

console.log(`Successfully assigned issue #${issueNumber} to @${assignee}`);
} else {
console.log(`Issue #${issueNumber} already has assignees, skipping assignment.`);
}
} else {
console.log('Skipping - this is a pull request, not an issue');
}