diff --git a/src/github/graphql/projectV2Items.ts b/src/github/graphql/projectV2Items.ts index 10e1da4..f172d62 100644 --- a/src/github/graphql/projectV2Items.ts +++ b/src/github/graphql/projectV2Items.ts @@ -34,6 +34,14 @@ export const PROJECT_V2_ITEMS = ` } } } + ... on ProjectV2ItemFieldLabelValue { + labels(first: 5) { + totalCount + nodes { + name + } + } + } ... on ProjectV2ItemFieldSingleSelectValue { id name diff --git a/src/github/index.ts b/src/github/index.ts index c2c1a4b..81b573c 100644 --- a/src/github/index.ts +++ b/src/github/index.ts @@ -23,6 +23,11 @@ export interface ProjectV2Item { url: string; }[]; }; + labels: { + nodes: { + name: string; + }[]; + }; name: string; }[]; }; diff --git a/src/items/index.ts b/src/items/index.ts index 281827b..e58bc6b 100644 --- a/src/items/index.ts +++ b/src/items/index.ts @@ -4,6 +4,7 @@ export interface Item { title: string; status: string; assignedUsers: string[]; + labels?: string[]; dueDate?: Date; url?: string; } @@ -16,6 +17,9 @@ export const convertGithubItems = (items: ProjectV2Item[]): Item[] => { const status = item.fieldValues.nodes .filter((field) => field.name) .map((field) => field.name)[0]; + const labels = item.fieldValues.nodes + .filter((field) => field.labels) + .flatMap((field) => field.labels.nodes.map((label) => label.name)); // TODO: improve this let dueDate: Date | undefined; @@ -28,6 +32,7 @@ export const convertGithubItems = (items: ProjectV2Item[]): Item[] => { title: item.content.title, url: item.content.url, assignedUsers, + labels, dueDate: dueDate, status: status, }; @@ -65,6 +70,24 @@ export const filterForUrgentItems = (items: Item[]) => { }); }; +//for reminders ran at 8:30pm (midnight in UTC) checking the next 24 hours will pull up tasks +//due the next day +export const filterForTwentyFourHours = (items: Item[]) => { + const tomorrow = new Date(); + tomorrow.setDate(tomorrow.getDate() + 1); + + return items.filter((item) => { + if (!item.dueDate) { + return false; + } + return ( + item.dueDate <= tomorrow && + item.status !== "Done" && + item.assignedUsers.length !== 0 + ); + }); +}; + export const filterUpcomingItems = (items: Item[]) => { const tomorrow = new Date(); tomorrow.setDate(tomorrow.getDate() + 2); @@ -92,3 +115,9 @@ export const filterOutStatus = (items: Item[], status: string) => { export const filterForUnassigned = (items: Item[]) => { return items.filter((item) => item.assignedUsers.length === 0); }; + +export const filterByLabel = (items: Item[], labels: string[]) => { + return items.filter((item) => + labels.some((label) => item.labels?.includes(label)), + ); +}; diff --git a/src/reminders/index.ts b/src/reminders/index.ts index 3c7de85..32609b7 100644 --- a/src/reminders/index.ts +++ b/src/reminders/index.ts @@ -1,2 +1,3 @@ export { dueTodayReminder } from "./dueTodayReminder"; export { fullItemReportReminder } from "./fullItemReportReminder"; +export { urgentPromotionReminder } from "./urgentPromotionReminder"; diff --git a/src/reminders/urgentPromotionReminder.ts b/src/reminders/urgentPromotionReminder.ts new file mode 100644 index 0000000..b4a429f --- /dev/null +++ b/src/reminders/urgentPromotionReminder.ts @@ -0,0 +1,48 @@ +import { sendDiscordItemMessage } from "../discord"; +import { fetchProjectV2Items } from "../github"; +import { + convertGithubItems, + filterByLabel, + filterForTwentyFourHours, + filterForUrgentItems, + filterOutStatus, +} from "../items"; + +export const urgentPromotionReminder = async () => { + const githubItemsResult = await fetchProjectV2Items(); + if (githubItemsResult.err) { + return githubItemsResult; + } + + const items = convertGithubItems(githubItemsResult.val); + const nonBacklogItems = filterOutStatus(items, "Backlog"); + const urgentItems = filterForTwentyFourHours(nonBacklogItems); + const itemsWithLabels = filterByLabel(urgentItems, [ + "discord announcement", + "social post", + "scs email", + ]); + + if (itemsWithLabels.length === 0) { + return null; + } + + const message = { + title: "Urgent Promotional Items Reminder 📬‼️", + message: + "Check out all upcoming tasks [here.](https://github.com/orgs/CarletonComputerScienceSociety/projects/18) 🐀🐀", + sections: [ + ...(itemsWithLabels.length > 0 + ? [ + { + title: "🔥 Urgent & Overdue", + items: itemsWithLabels, + }, + ] + : []), + ], + }; + + const discordMessageResult = await sendDiscordItemMessage(message); + return discordMessageResult; +}; diff --git a/test/items/factories/item-factory.ts b/test/items/factories/item-factory.ts index e255d51..58af02c 100644 --- a/test/items/factories/item-factory.ts +++ b/test/items/factories/item-factory.ts @@ -4,12 +4,14 @@ export const itemFactory = ({ title, status, assignedUsers, + labels, dueDate, url, }: { title?: string; status?: string; assignedUsers?: string[]; + labels?: string[]; dueDate?: Date; url?: string; } = {}): Item => { @@ -17,6 +19,7 @@ export const itemFactory = ({ title: title ?? "title", status: status ?? "status", assignedUsers: assignedUsers ?? ["https://github.com/MathyouMB"], + labels: labels ?? undefined, dueDate: dueDate ?? undefined, url: url ?? "https://github.com/MathyouMB", }; diff --git a/test/items/index.test.ts b/test/items/index.test.ts index ae03ff6..125ce48 100644 --- a/test/items/index.test.ts +++ b/test/items/index.test.ts @@ -1,13 +1,16 @@ import { describe, expect } from "@jest/globals"; import { filterByDateRange, + filterByLabel, filterByStatus, + filterForTwentyFourHours, filterForUnassigned, filterForUrgentItems, filterOutStatus, filterUpcomingItems, } from "../../src/items"; import { itemFactory } from "./factories/item-factory"; +import exp from "constants"; describe("filterByStatus", () => { it("will return items with the given status", () => { @@ -98,6 +101,23 @@ describe("filterForUrgentItems", () => { }); }); +describe("filterForTwentyFourHours", () => { + it("will return items that are overdue or due in the next 24 Hours", () => { + const today = new Date(); + const tomorrow = new Date(); + tomorrow.setDate(tomorrow.getDate() + 1); + const dayAfterTomorrow = new Date(); + dayAfterTomorrow.setDate(dayAfterTomorrow.getDate() + 2); + const item1 = itemFactory({ dueDate: today }); + const item2 = itemFactory({ dueDate: tomorrow }); + const item3 = itemFactory({ dueDate: dayAfterTomorrow }); + + const result = filterForTwentyFourHours([item1, item2, item3]); + + expect(result).toEqual([item1, item2]); + }); +}); + describe("filterUpcomingItems", () => { it("will return items due after tomorrow", () => { const today = new Date(); @@ -114,3 +134,21 @@ describe("filterUpcomingItems", () => { expect(result).toEqual([item3]); }); }); + +describe("filterByLabels", () => { + it("will return items with any of the labels matching", () => { + const item1 = itemFactory({ labels: [] }); + const item2 = itemFactory({ labels: ["social post"] }); + const item3 = itemFactory({ labels: ["social post", "not a label"] }); + const item4 = itemFactory({ labels: ["social post", "scs email"] }); + const item5 = itemFactory({ labels: ["scs email", "not a label"] }); + const item6 = itemFactory({ labels: ["not a label 1", "not a label 2"] }); + + const result = filterByLabel( + [item1, item2, item3, item4, item5, item6], + ["social post", "scs email"], + ); + + expect(result).toEqual([item2, item3, item4, item5]); + }); +});