Skip to content

Commit

Permalink
Merge pull request #45 from ubiquity-os-marketplace/development
Browse files Browse the repository at this point in the history
Merge development into main
  • Loading branch information
gentlementlegen authored Nov 4, 2024
2 parents 13643f8 + 0b0dabb commit db64f39
Show file tree
Hide file tree
Showing 11 changed files with 82 additions and 38 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ yarn test
with:
disqualification: "7 days"
warning: "3.5 days"
prioritySpeed: true
watch:
optOut:
- "repoName"
Expand Down
2 changes: 1 addition & 1 deletion dist/index.js

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@
}
}
},
"prioritySpeed": {
"default": true,
"type": "boolean"
},
"disqualification": {
"default": "7 days",
"type": "string"
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"@octokit/rest": "^21.0.2",
"@octokit/webhooks": "^13.3.0",
"@sinclair/typebox": "^0.32.35",
"@ubiquity-os/ubiquity-os-kernel": "^2.5.1",
"@ubiquity-os/ubiquity-os-kernel": "^2.5.2",
"@ubiquity-os/ubiquity-os-logger": "^1.3.2",
"dotenv": "16.4.5",
"luxon": "3.4.4",
Expand Down
6 changes: 4 additions & 2 deletions src/helpers/remind-and-remove.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export async function remindAssigneesForIssue(context: ContextPlugin, issue: Lis
} else if (config.pullRequestRequired && !hasLinkedPr) {
await unassignUserFromIssue(context, issue);
} else {
logger.info(`Passed the reminder threshold on ${issue.html_url}, sending a reminder.`);
logger.info(`Passed the reminder threshold on ${issue.html_url} sending a reminder.`);
await remindAssignees(context, issue);
}
}
Expand Down Expand Up @@ -96,7 +96,9 @@ async function removeAllAssignees(context: ContextPlugin, issue: ListIssueForRep
return false;
}
const logins = issue.assignees.map((o) => o?.login).filter((o) => !!o) as string[];
const logMessage = logger.info(`Passed the deadline and no activity is detected, removing assignees: ${logins.map((o) => `@${o}`).join(", ")}.`);
const logMessage = logger.info(`Passed the deadline and no activity is detected, removing assignees: ${logins.map((o) => `@${o}`).join(", ")}.`, {
issue: issue.html_url,
});
const metadata = createStructuredMetadata(UNASSIGN_HEADER, logMessage);

await octokit.rest.issues.createComment({
Expand Down
42 changes: 28 additions & 14 deletions src/helpers/task-metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ import { DateTime } from "luxon";
import ms from "ms";
import { ListForOrg, ListIssueForRepo } from "../types/github-types";
import { ContextPlugin } from "../types/plugin-input";
import { RestEndpointMethodTypes } from "@octokit/rest";

type IssueLabel = Partial<Omit<RestEndpointMethodTypes["issues"]["listLabelsForRepo"]["response"]["data"][0], "color">> & {
color?: string | null;
};

/**
* Retrieves assignment events from the timeline of an issue and calculates the deadline based on the time label.
Expand Down Expand Up @@ -67,20 +72,7 @@ export async function getTaskAssignmentDetails(
return metadata;
}

function parseTimeLabel(
labels: (
| string
| {
id?: number;
node_id?: string;
url?: string;
name?: string;
description?: string | null;
color?: string | null;
default?: boolean;
}
)[]
): number {
function parseTimeLabel(labels: (IssueLabel | string)[]): number {
let taskTimeEstimate = 0;

for (const label of labels) {
Expand Down Expand Up @@ -108,3 +100,25 @@ function parseTimeLabel(

return taskTimeEstimate;
}

export function parsePriorityLabel(labels: (IssueLabel | string)[]): number {
for (const label of labels) {
let priorityLabel = "";
if (typeof label === "string") {
priorityLabel = label;
} else {
priorityLabel = label.name || "";
}

if (priorityLabel.startsWith("Priority:")) {
const matched = priorityLabel.match(/Priority: (\d+)/i);
if (!matched) {
return 1;
}

return Number(matched[1]);
}
}

return 1;
}
24 changes: 18 additions & 6 deletions src/helpers/task-update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { getAssigneesActivityForIssue } from "./get-assignee-activity";
import { parseIssueUrl } from "./github-url";
import { remindAssigneesForIssue, unassignUserFromIssue } from "./remind-and-remove";
import { getCommentsFromMetadata } from "./structured-metadata";
import { getTaskAssignmentDetails } from "./task-metadata";
import { getTaskAssignmentDetails, parsePriorityLabel } from "./task-metadata";

const getMostRecentActivityDate = (assignedEventDate: DateTime, activityEventDate?: DateTime): DateTime => {
return activityEventDate && activityEventDate > assignedEventDate ? activityEventDate : assignedEventDate;
Expand All @@ -18,7 +18,7 @@ export async function updateTaskReminder(context: ContextPlugin, repo: ListForOr
const {
octokit,
logger,
config: { eventWhitelist, warning, disqualification },
config: { eventWhitelist, warning, disqualification, prioritySpeed },
} = context;
const handledMetadata = await getTaskAssignmentDetails(context, repo, issue);
const now = DateTime.local();
Expand Down Expand Up @@ -46,6 +46,8 @@ export async function updateTaskReminder(context: ContextPlugin, repo: ListForOr
.shift();

const assignedDate = DateTime.fromISO(assignedEvent.created_at);
const priorityValue = parsePriorityLabel(issue.labels);
const priorityLevel = Math.max(1, priorityValue);
const activityDate = activityEvent?.created_at ? DateTime.fromISO(activityEvent.created_at) : undefined;
let mostRecentActivityDate = getMostRecentActivityDate(assignedDate, activityDate);

Expand Down Expand Up @@ -75,16 +77,26 @@ export async function updateTaskReminder(context: ContextPlugin, repo: ListForOr
if (lastReminderComment) {
const lastReminderTime = DateTime.fromISO(lastReminderComment.created_at);
mostRecentActivityDate = lastReminderTime > mostRecentActivityDate ? lastReminderTime : mostRecentActivityDate;
if (mostRecentActivityDate.plus({ milliseconds: disqualificationTimeDifference }) <= now) {
if (mostRecentActivityDate.plus({ milliseconds: prioritySpeed ? disqualificationTimeDifference / priorityLevel : disqualificationTimeDifference }) <= now) {
await unassignUserFromIssue(context, issue);
} else {
logger.info(`Reminder was sent for ${issue.html_url} already, not beyond disqualification deadline yet.`);
logger.info(`Reminder was sent for ${issue.html_url} already, not beyond disqualification deadline yet.`, {
now: now.toLocaleString(DateTime.DATETIME_MED),
assignedDate: DateTime.fromISO(assignedEvent.created_at).toLocaleString(DateTime.DATETIME_MED),
lastReminderComment: lastReminderComment ? DateTime.fromISO(lastReminderComment.created_at).toLocaleString(DateTime.DATETIME_MED) : "none",
mostRecentActivityDate: mostRecentActivityDate.toLocaleString(DateTime.DATETIME_MED),
});
}
} else {
if (mostRecentActivityDate.plus({ milliseconds: warning }) <= now) {
if (mostRecentActivityDate.plus({ milliseconds: prioritySpeed ? warning / priorityLevel : warning }) <= now) {
await remindAssigneesForIssue(context, issue);
} else {
logger.info(`Nothing to do for ${issue.html_url}, still within due-time.`);
logger.info(`Nothing to do for ${issue.html_url} still within due-time.`, {
now: now.toLocaleString(DateTime.DATETIME_MED),
assignedDate: DateTime.fromISO(assignedEvent.created_at).toLocaleString(DateTime.DATETIME_MED),
lastReminderComment: "none",
mostRecentActivityDate: mostRecentActivityDate.toLocaleString(DateTime.DATETIME_MED),
});
}
}
}
4 changes: 4 additions & 0 deletions src/types/plugin-input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ export const pluginSettingsSchema = T.Object(
},
{ default: {} }
),
/*
* Whether to rush the follow ups by the priority level
*/
prioritySpeed: T.Boolean({ default: true }),
/**
* Delay to unassign users. 0 means disabled. Any other value is counted in days, e.g. 7 days
*/
Expand Down
1 change: 1 addition & 0 deletions tests/__mocks__/results/valid-configuration.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"warning": "3.5 days",
"disqualification": "7 days",
"prioritySpeed": true,
"watch": {
"optOut": ["private-repo"]
},
Expand Down
26 changes: 16 additions & 10 deletions tests/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ describe("User start/stop", () => {
expect(pluginSettings).toEqual({
pullRequestRequired: true,
warning: 302400000,
prioritySpeed: true,
disqualification: 604800000,
watch: { optOut: [STRINGS.PRIVATE_REPO_NAME] },
eventWhitelist: ["review_requested", "ready_for_review", "commented", "committed"],
Expand Down Expand Up @@ -103,6 +104,7 @@ describe("User start/stop", () => {
pullRequestRequired: true,
warning: ms("3.5 days"),
disqualification: ms("7 days"),
prioritySpeed: true,
watch: { optOut: [STRINGS.PRIVATE_REPO_NAME] },
eventWhitelist: ["review_requested", "ready_for_review", "commented", "committed"],
});
Expand All @@ -121,13 +123,16 @@ describe("User start/stop", () => {
await expect(run(context)).resolves.toEqual({ message: "OK" });

expect(errorSpy).toHaveBeenCalledWith(`Failed to update activity for ${getIssueHtmlUrl(1)}, there is no assigned event.`);
expect(infoSpy).toHaveBeenCalledWith(`Nothing to do for ${getIssueHtmlUrl(2)}, still within due-time.`);
expect(infoSpy).toHaveBeenCalledWith(`Passed the reminder threshold on ${getIssueHtmlUrl(3)}, sending a reminder.`);
expect(infoSpy).toHaveBeenCalledWith(expect.stringContaining(`Nothing to do for ${getIssueHtmlUrl(2)} still within due-time.`), expect.anything());
expect(infoSpy).toHaveBeenCalledWith(`Passed the reminder threshold on ${getIssueHtmlUrl(3)} sending a reminder.`);
expect(infoSpy).toHaveBeenCalledWith(`@user2, this task has been idle for a while. Please provide an update.\n\n`, {
taskAssignees: [2],
caller: STRINGS.LOGS_ANON_CALLER,
});
expect(infoSpy).toHaveBeenCalledWith("Passed the deadline and no activity is detected, removing assignees: @user2.");
expect(infoSpy).toHaveBeenCalledWith(
expect.stringContaining("Passed the deadline and no activity is detected, removing assignees: @user2."),
expect.anything()
);
expect(infoSpy).not.toHaveBeenCalledWith(expect.stringContaining(STRINGS.PRIVATE_REPO_NAME));
});

Expand All @@ -137,13 +142,13 @@ describe("User start/stop", () => {

await expect(run(context)).resolves.toEqual({ message: "OK" });

expect(infoSpy).toHaveBeenCalledWith(`Nothing to do for ${getIssueHtmlUrl(2)}, still within due-time.`);
expect(infoSpy).toHaveBeenCalledWith(`Passed the reminder threshold on ${getIssueHtmlUrl(3)}, sending a reminder.`);
expect(infoSpy).toHaveBeenCalledWith(`Nothing to do for ${getIssueHtmlUrl(2)} still within due-time.`, expect.anything());
expect(infoSpy).toHaveBeenCalledWith(`Passed the reminder threshold on ${getIssueHtmlUrl(3)} sending a reminder.`);
expect(infoSpy).toHaveBeenCalledWith(`@user2, this task has been idle for a while. Please provide an update.\n\n`, {
taskAssignees: [2],
caller: STRINGS.LOGS_ANON_CALLER,
});
expect(infoSpy).toHaveBeenCalledWith("Passed the deadline and no activity is detected, removing assignees: @user2.");
expect(infoSpy).toHaveBeenCalledWith("Passed the deadline and no activity is detected, removing assignees: @user2.", expect.anything());
expect(infoSpy).toHaveBeenCalledWith(expect.stringContaining(STRINGS.PRIVATE_REPO_NAME));
});

Expand All @@ -156,13 +161,13 @@ describe("User start/stop", () => {

await run(context);

expect(infoSpy).toHaveBeenCalledWith(`Nothing to do for ${getIssueHtmlUrl(2)}, still within due-time.`);
expect(infoSpy).toHaveBeenCalledWith(`Passed the reminder threshold on ${getIssueHtmlUrl(3)}, sending a reminder.`);
expect(infoSpy).toHaveBeenCalledWith(`Nothing to do for ${getIssueHtmlUrl(2)} still within due-time.`, expect.anything());
expect(infoSpy).toHaveBeenCalledWith(`Passed the reminder threshold on ${getIssueHtmlUrl(3)} sending a reminder.`);
expect(infoSpy).toHaveBeenCalledWith(`@user2, this task has been idle for a while. Please provide an update.\n\n`, {
taskAssignees: [2],
caller: STRINGS.LOGS_ANON_CALLER,
});
expect(infoSpy).toHaveBeenCalledWith("Passed the deadline and no activity is detected, removing assignees: @user2.");
expect(infoSpy).toHaveBeenCalledWith("Passed the deadline and no activity is detected, removing assignees: @user2.", expect.anything());
const updatedIssue = db.issue.findFirst({ where: { id: { equals: 4 } } });
expect(updatedIssue?.assignees).toEqual([]);
});
Expand Down Expand Up @@ -193,7 +198,7 @@ describe("User start/stop", () => {

await run(context);

expect(infoSpy).toHaveBeenCalledWith(`Nothing to do for ${getIssueHtmlUrl(2)}, still within due-time.`);
expect(infoSpy).toHaveBeenCalledWith(`Nothing to do for ${getIssueHtmlUrl(2)} still within due-time.`, expect.anything());

const updatedIssue = db.issue.findFirst({ where: { id: { equals: 1 } } });
expect(updatedIssue?.assignees).toEqual([{ login: STRINGS.UBIQUITY, id: 1 }]);
Expand Down Expand Up @@ -279,6 +284,7 @@ function createContext(issueId: number, senderId: number, optOut = [STRINGS.PRIV
config: {
disqualification: ONE_DAY * 7,
warning: ONE_DAY * 3.5,
prioritySpeed: true,
watch: { optOut },
eventWhitelist: ["review_requested", "ready_for_review", "commented", "committed"],
pullRequestRequired: false,
Expand Down
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2032,10 +2032,10 @@
dependencies:
"@types/yargs-parser" "*"

"@ubiquity-os/ubiquity-os-kernel@^2.5.1":
version "2.5.1"
resolved "https://registry.yarnpkg.com/@ubiquity-os/ubiquity-os-kernel/-/ubiquity-os-kernel-2.5.1.tgz#13cc77146837deeb7a6e6346511a686be78a832b"
integrity sha512-G+gL/NmZTP7QiijB1CcHXBj78iKW7RYdRC80ETWxzojIR3fZYPxTSb3rWEJOim9iAfQ7qGawJQJGL1yrPuMRBQ==
"@ubiquity-os/ubiquity-os-kernel@^2.5.2":
version "2.5.2"
resolved "https://registry.yarnpkg.com/@ubiquity-os/ubiquity-os-kernel/-/ubiquity-os-kernel-2.5.2.tgz#8a660ee5e18768118b748b05146b85e2fe3b8e07"
integrity sha512-OStUT7zrE/siu/kUs/flcCUVWelt1kI6uBICNeYrreDw3IirRmUAoogFg5n+byu3RW37bpDGlmWrT5Q3jeXwLQ==
dependencies:
"@actions/core" "1.10.1"
"@actions/github" "6.0.0"
Expand Down

0 comments on commit db64f39

Please sign in to comment.