From 774fa92e6b7355f99d3ff7574138fe2b7f5f2655 Mon Sep 17 00:00:00 2001 From: Hubert Deng Date: Fri, 29 Dec 2023 13:29:49 -0800 Subject: [PATCH] name parameterize business hour functions --- src/utils/businessHours.test.ts | 194 +++++++++++----------- src/utils/businessHours.ts | 74 ++++++--- src/webhooks/pubsub/slackNotifications.ts | 6 +- 3 files changed, 150 insertions(+), 124 deletions(-) diff --git a/src/utils/businessHours.test.ts b/src/utils/businessHours.test.ts index e6e445b9..6b56b493 100644 --- a/src/utils/businessHours.test.ts +++ b/src/utils/businessHours.test.ts @@ -97,149 +97,149 @@ describe('businessHours tests', function () { for (let i = 0; i < 7; i++) { it(`should calculate TTT SLO violation for ${testTimestamps[i].day}`, async function () { - const result = await calculateTimeToRespondBy( - MAX_TRIAGE_DAYS, - 'Test', - 'routing-repo', - GETSENTRY_ORG.slug, - testTimestamps[i].timestamp - ); + const result = await calculateTimeToRespondBy({ + numDays: MAX_TRIAGE_DAYS, + productArea: 'Test', + repo: 'routing-repo', + org: GETSENTRY_ORG.slug, + testTimestamp: testTimestamps[i].timestamp, + }); expect(result).toEqual(triageResults[i]); }); it(`should calculate TTR SLO violation for ${testTimestamps[i].day}`, async function () { - const result = await calculateTimeToRespondBy( - MAX_ROUTE_DAYS, - 'Test', - 'routing-repo', - GETSENTRY_ORG.slug, - testTimestamps[i].timestamp - ); + const result = await calculateTimeToRespondBy({ + numDays: MAX_ROUTE_DAYS, + productArea: 'Test', + repo: 'routing-repo', + org: GETSENTRY_ORG.slug, + testTimestamp: testTimestamps[i].timestamp, + }); expect(result).toEqual(routingResults[i]); }); } it('should handle case when offices is undefined', async function () { - const result = await calculateTimeToRespondBy( - MAX_TRIAGE_DAYS, - 'Undefined', - 'routing-repo', - GETSENTRY_ORG.slug, - '2023-12-18T00:00:00.000Z' - ); + const result = await calculateTimeToRespondBy({ + numDays: MAX_TRIAGE_DAYS, + productArea: 'Undefined', + repo: 'routing-repo', + org: GETSENTRY_ORG.slug, + testTimestamp: '2023-12-18T00:00:00.000Z', + }); expect(result).toEqual('2023-12-20T01:00:00.000Z'); }); it('should handle case when offices is null', async function () { - const result = await calculateTimeToRespondBy( - MAX_TRIAGE_DAYS, - 'Null', - 'routing-repo', - GETSENTRY_ORG.slug, - '2023-12-18T00:00:00.000Z' - ); + const result = await calculateTimeToRespondBy({ + numDays: MAX_TRIAGE_DAYS, + productArea: 'Null', + repo: 'routing-repo', + org: GETSENTRY_ORG.slug, + testTimestamp: '2023-12-18T00:00:00.000Z', + }); expect(result).toEqual('2023-12-20T01:00:00.000Z'); }); it('should handle the last day of the month for TTR', async function () { - const result = await calculateTimeToRespondBy( - MAX_ROUTE_DAYS, - 'Test', - 'routing-repo', - GETSENTRY_ORG.slug, - '2023-01-31T00:00:00.000Z' - ); + const result = await calculateTimeToRespondBy({ + numDays: MAX_ROUTE_DAYS, + productArea: 'Test', + repo: 'routing-repo', + org: GETSENTRY_ORG.slug, + testTimestamp: '2023-01-31T00:00:00.000Z', + }); expect(result).toEqual('2023-02-01T00:00:00.000Z'); }); it('should handle the last day of the year for TTR', async function () { - const result = await calculateTimeToRespondBy( - MAX_ROUTE_DAYS, - 'Test', - 'routing-repo', - GETSENTRY_ORG.slug, - '2022-12-31T00:00:00.000Z' - ); + const result = await calculateTimeToRespondBy({ + numDays: MAX_ROUTE_DAYS, + productArea: 'Test', + repo: 'routing-repo', + org: GETSENTRY_ORG.slug, + testTimestamp: '2022-12-31T00:00:00.000Z', + }); expect(result).toEqual('2023-01-04T01:00:00.000Z'); }); describe('holiday tests', function () { it('should calculate TTT SLO violation for Christmas', async function () { - const result = await calculateTimeToRespondBy( - MAX_TRIAGE_DAYS, - 'Test', - 'routing-repo', - GETSENTRY_ORG.slug, - '2023-12-24T00:00:00.000Z' - ); + const result = await calculateTimeToRespondBy({ + numDays: MAX_TRIAGE_DAYS, + productArea: 'Test', + repo: 'routing-repo', + org: GETSENTRY_ORG.slug, + testTimestamp: '2023-12-24T00:00:00.000Z', + }); // 2023-12-24 is Sunday, 2023-12-25/2022-12-26 are holidays expect(result).toEqual('2024-01-04T01:00:00.000Z'); }); it('should calculate TTR SLO violation for Christmas', async function () { - const result = await calculateTimeToRespondBy( - MAX_ROUTE_DAYS, - 'Test', - 'routing-repo', - GETSENTRY_ORG.slug, - '2023-12-24T00:00:00.000Z' - ); + const result = await calculateTimeToRespondBy({ + numDays: MAX_ROUTE_DAYS, + productArea: 'Test', + repo: 'routing-repo', + org: GETSENTRY_ORG.slug, + testTimestamp: '2023-12-24T00:00:00.000Z', + }); // 2023-12-24 is Sunday, 2023-12-25/2022-12-26 are holidays expect(result).toEqual('2024-01-03T01:00:00.000Z'); }); it('should not include holiday in TTR if at least one office is still open', async function () { - const result = await calculateTimeToRespondBy( - MAX_ROUTE_DAYS, - 'Test', - 'routing-repo', - GETSENTRY_ORG.slug, - '2023-10-02T00:00:00.000Z' - ); + const result = await calculateTimeToRespondBy({ + numDays: MAX_ROUTE_DAYS, + productArea: 'Test', + repo: 'routing-repo', + org: GETSENTRY_ORG.slug, + testTimestamp: '2023-10-02T00:00:00.000Z', + }); expect(result).toEqual('2023-10-03T00:00:00.000Z'); }); it('should triage on the same day if two office timezones do not overlap', async function () { - const result = await calculateTimeToRespondBy( - MAX_TRIAGE_DAYS, - 'Non-Overlapping Timezone', - 'routing-repo', - GETSENTRY_ORG.slug, - '2023-10-02T00:00:00.000Z' - ); + const result = await calculateTimeToRespondBy({ + numDays: MAX_TRIAGE_DAYS, + productArea: 'Non-Overlapping Timezone', + repo: 'routing-repo', + org: GETSENTRY_ORG.slug, + testTimestamp: '2023-10-02T00:00:00.000Z', + }); expect(result).toEqual('2023-10-03T00:00:00.000Z'); }); it('should calculate weekends properly for friday in sfo, weekend in vie', async function () { - const result = await calculateTimeToRespondBy( - MAX_TRIAGE_DAYS, - 'Non-Overlapping Timezone', - 'routing-repo', - GETSENTRY_ORG.slug, - '2022-12-17T00:00:00.000Z' - ); + const result = await calculateTimeToRespondBy({ + numDays: MAX_TRIAGE_DAYS, + productArea: 'Non-Overlapping Timezone', + repo: 'routing-repo', + org: GETSENTRY_ORG.slug, + testTimestamp: '2022-12-17T00:00:00.000Z', + }); expect(result).toEqual('2022-12-20T00:00:00.000Z'); }); it('should route properly when product area is subscribed to sfo, vie, and yyz', async function () { - const result = await calculateTimeToRespondBy( - MAX_ROUTE_DAYS, - 'All-Timezones', - 'routing-repo', - GETSENTRY_ORG.slug, - '2022-12-20T15:30:00.000Z' - ); + const result = await calculateTimeToRespondBy({ + numDays: MAX_ROUTE_DAYS, + productArea: 'All-Timezones', + repo: 'routing-repo', + org: GETSENTRY_ORG.slug, + testTimestamp: '2022-12-20T15:30:00.000Z', + }); expect(result).toEqual('2022-12-20T23:30:00.000Z'); }); it('should triage properly when product area is subscribed to sfo, vie, and yyz', async function () { - const result = await calculateTimeToRespondBy( - MAX_TRIAGE_DAYS, - 'All-Timezones', - 'routing-repo', - GETSENTRY_ORG.slug, - '2022-12-20T15:30:00.000Z' - ); + const result = await calculateTimeToRespondBy({ + numDays: MAX_TRIAGE_DAYS, + productArea: 'All-Timezones', + repo: 'routing-repo', + org: GETSENTRY_ORG.slug, + testTimestamp: '2022-12-20T15:30:00.000Z', + }); expect(result).toEqual('2022-12-21T14:30:00.000Z'); }); }); @@ -497,7 +497,7 @@ describe('businessHours tests', function () { const org = 'getsentry'; const productArea = 'Other'; expect( - getBusinessHoursLeft(triageBy, now, repo, org, productArea) + getBusinessHoursLeft({ triageBy, now, repo, org, productArea }) ).toEqual(1); }); it('should correctly calculate business hours over multiple days', function () { @@ -507,7 +507,7 @@ describe('businessHours tests', function () { const org = 'getsentry'; const productArea = 'Other'; expect( - getBusinessHoursLeft(triageBy, now, repo, org, productArea) + getBusinessHoursLeft({ triageBy, now, repo, org, productArea }) ).toEqual(9); }); it('should correctly calculate business hours over holiday', function () { @@ -517,7 +517,7 @@ describe('businessHours tests', function () { const org = 'getsentry'; const productArea = 'Other'; expect( - getBusinessHoursLeft(triageBy, now, repo, org, productArea) + getBusinessHoursLeft({ triageBy, now, repo, org, productArea }) ).toEqual(1); }); it('should correctly account for weekends when calculating business hours', function () { @@ -527,7 +527,7 @@ describe('businessHours tests', function () { const org = 'getsentry'; const productArea = 'Other'; expect( - getBusinessHoursLeft(triageBy, now, repo, org, productArea) + getBusinessHoursLeft({ triageBy, now, repo, org, productArea }) ).toEqual(2); }); it('should correctly calculate business hours for issues with non overlapping timezones', function () { @@ -537,7 +537,7 @@ describe('businessHours tests', function () { const org = 'getsentry'; const productArea = 'Non-Overlapping Timezone'; expect( - getBusinessHoursLeft(triageBy, now, repo, org, productArea) + getBusinessHoursLeft({ triageBy, now, repo, org, productArea }) ).toEqual(25); }); it('should correctly calculate business hours for issues with overlapping timezones', function () { @@ -547,7 +547,7 @@ describe('businessHours tests', function () { const org = 'getsentry'; const productArea = 'Overlapping Timezone'; expect( - getBusinessHoursLeft(triageBy, now, repo, org, productArea) + getBusinessHoursLeft({ triageBy, now, repo, org, productArea }) ).toEqual(15); }); }); diff --git a/src/utils/businessHours.ts b/src/utils/businessHours.ts index c1e89cd7..a908d64f 100644 --- a/src/utils/businessHours.ts +++ b/src/utils/businessHours.ts @@ -25,13 +25,21 @@ interface BusinessHourWindow { end: moment.Moment; } -export function calculateTimeToRespondBy( - numDays: number, - productArea: string, - repo: string, - org: string, - testTimestamp?: string -): string { +interface CalculateTimetoRespondByParams { + numDays: number; + productArea: string; + repo: string; + org: string; + testTimestamp?: string; +} + +export function calculateTimeToRespondBy({ + numDays, + productArea, + repo, + org, + testTimestamp, +}: CalculateTimetoRespondByParams): string { let cursor = testTimestamp !== undefined ? moment(testTimestamp).utc() : moment().utc(); let msRemaining = numDays * BUSINESS_DAY_IN_MS; @@ -53,29 +61,34 @@ export function calculateTimeToRespondBy( } export function calculateSLOViolationTriage( - target_name: string, + targetName: string, labels: any, repo: string, org: string ): string | null { // calculate time to triage for issues that come in with untriaged label - if (target_name === WAITING_FOR_PRODUCT_OWNER_LABEL) { + if (targetName === WAITING_FOR_PRODUCT_OWNER_LABEL) { const productArea = labels ?.find((label) => label.name.startsWith(PRODUCT_AREA_LABEL_PREFIX)) ?.name.slice(PRODUCT_AREA_LABEL_PREFIX.length); - return calculateTimeToRespondBy(MAX_TRIAGE_DAYS, productArea, repo, org); + return calculateTimeToRespondBy({ + numDays: MAX_TRIAGE_DAYS, + productArea, + repo, + org, + }); } // calculate time to triage for issues that are rerouted else if ( - target_name.startsWith(PRODUCT_AREA_LABEL_PREFIX) && + targetName.startsWith(PRODUCT_AREA_LABEL_PREFIX) && labels?.some((label) => label.name === WAITING_FOR_PRODUCT_OWNER_LABEL) ) { - return calculateTimeToRespondBy( - MAX_TRIAGE_DAYS, - target_name.slice(PRODUCT_AREA_LABEL_PREFIX.length), + return calculateTimeToRespondBy({ + numDays: MAX_TRIAGE_DAYS, + productArea: targetName.slice(PRODUCT_AREA_LABEL_PREFIX.length), repo, - org - ); + org, + }); } return null; } @@ -86,7 +99,12 @@ export function calculateSLOViolationRoute( org: string ): string | null { if (target_name === WAITING_FOR_SUPPORT_LABEL) { - return calculateTimeToRespondBy(MAX_ROUTE_DAYS, 'Unknown', repo, org); + return calculateTimeToRespondBy({ + numDays: MAX_ROUTE_DAYS, + productArea: 'Unknown', + repo, + org, + }); } return null; } @@ -182,13 +200,21 @@ export function getNextAvailableBusinessHourWindow( return businessHourWindows[0]; } -export function getBusinessHoursLeft( - triageBy: string, - now: moment.Moment, - repo: string, - org: string, - productArea: string -): number { +interface GetBusinessHoursLeftParams { + triageBy: string; + now: moment.Moment; + repo: string; + org: string; + productArea: string; +} + +export function getBusinessHoursLeft({ + triageBy, + now, + repo, + org, + productArea, +}: GetBusinessHoursLeftParams): number { let businessHoursLeft = 0; let momentIterator = moment(now.format()); const triageByMoment = moment(triageBy); diff --git a/src/webhooks/pubsub/slackNotifications.ts b/src/webhooks/pubsub/slackNotifications.ts index 7f14e88c..4ef43207 100644 --- a/src/webhooks/pubsub/slackNotifications.ts +++ b/src/webhooks/pubsub/slackNotifications.ts @@ -173,13 +173,13 @@ export const constructSlackMessage = ( .replace(//g, '>'); const hoursLeft = now.diff(triageBy, 'hours') * -1; - const businessHoursLeft = getBusinessHoursLeft( + const businessHoursLeft = getBusinessHoursLeft({ triageBy, now, repo, org, - productArea - ); + productArea, + }); const minutesLeft = now.diff(triageBy, 'minutes') * -1 - hoursLeft * 60; const daysLeft = now.diff(triageBy, 'days') * -1; hasEnoughTimePassedSinceIssueCreation =