Skip to content

Commit

Permalink
Fix product owner assignment
Browse files Browse the repository at this point in the history
  • Loading branch information
chadwhitacre committed May 1, 2023
1 parent c38b89d commit bfe6543
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 65 deletions.
5 changes: 4 additions & 1 deletion src/api/github/__mocks__/getClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,10 @@ function mockClient() {
},
teams: {
getByName: jest.fn(async function (payload) {
if (payload.team_slug === 'test' || payload.team_slug === 'rerouted') {
if (
payload.team_slug === 'product-owners-test' ||
payload.team_slug === 'product-owners-rerouted'
) {
return { status: 200, data: {} };
}
throw new MockOctokitError(404);
Expand Down
45 changes: 12 additions & 33 deletions src/brain/issueLabelHandler/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,6 @@ describe('issueLabelHandler', function () {
repo?: string,
label?: string,
sender?: string,
labelDescription?: string,
state?: string
) {
repo = repo || 'test-ttt-simple';
Expand All @@ -112,7 +111,7 @@ describe('issueLabelHandler', function () {
};

if (label) {
payload.label = { name: label, description: labelDescription };
payload.label = { name: label };
}
return payload;
}
Expand All @@ -126,17 +125,12 @@ describe('issueLabelHandler', function () {
);
}

async function addLabel(
label: string,
repo?: string,
labelDescription?: string,
state?: string
) {
async function addLabel(label: string, repo?: string, state?: string) {
await createGitHubEvent(
fastify,
// @ts-expect-error
'issues.labeled',
makePayload(repo, label, undefined, labelDescription, state)
makePayload(repo, label, undefined, state)
);
octokit.issues.addLabels({ labels: [label] });
}
Expand Down Expand Up @@ -301,7 +295,7 @@ describe('issueLabelHandler', function () {
expectRouted();
expect(octokit.issues._comments).toEqual([
'Assigning to @getsentry/support for [routing](https://open.sentry.io/triage/#2-route), due by **<time datetime=2022-12-20T00:00:00.000Z>Monday, December 19th at 4:00 pm</time> (sfo)**. ⏲️',
'Routing to @getsentry/test for [triage](https://develop.sentry.dev/processing-tickets/#3-triage), due by **<time datetime=2022-12-21T00:00:00.000Z>Tuesday, December 20th at 4:00 pm</time> (sfo)**. ⏲️',
'Routing to @getsentry/product-owners-test for [triage](https://develop.sentry.dev/processing-tickets/#3-triage), due by **<time datetime=2022-12-21T00:00:00.000Z>Tuesday, December 20th at 4:00 pm</time> (sfo)**. ⏲️',
]);
});

Expand All @@ -314,24 +308,14 @@ describe('issueLabelHandler', function () {
]);
});

it('should try to use label description if product area label name does not exist', async function () {
await createIssue('sentry-docs');
await addLabel('Product Area: Does Not Exist', 'sentry-docs', 'test');
expectUntriaged();
expect(octokit.issues._comments).toEqual([
'Assigning to @getsentry/support for [routing](https://open.sentry.io/triage/#2-route), due by **<time datetime=2022-12-20T00:00:00.000Z>Monday, December 19th at 4:00 pm</time> (sfo)**. ⏲️',
'Routing to @getsentry/test for [triage](https://develop.sentry.dev/processing-tickets/#3-triage), due by **<time datetime=2022-12-21T00:00:00.000Z>Tuesday, December 20th at 4:00 pm</time> (sfo)**. ⏲️',
]);
});

it('should default to route to open source team if product area does not exist', async function () {
await createIssue('sentry-docs');
await addLabel('Product Area: Does Not Exist', 'sentry-docs');
expectUntriaged();
expectRouted();
expect(octokit.issues._comments).toEqual([
'Assigning to @getsentry/support for [routing](https://open.sentry.io/triage/#2-route), due by **<time datetime=2022-12-20T00:00:00.000Z>Monday, December 19th at 4:00 pm</time> (sfo)**. ⏲️',
'Failed to route to Product Area: Does Not Exist. Defaulting to @getsentry/open-source for [triage](https://develop.sentry.dev/processing-tickets/#3-triage), due by **<time datetime=2022-12-21T00:00:00.000Z>Tuesday, December 20th at 4:00 pm</time> (sfo)**. ⏲️',
'Failed to route to @getsentry/product-owners-does-not-exist for Product Area: Does Not Exist. Defaulting to @getsentry/open-source for [triage](https://develop.sentry.dev/processing-tickets/#3-triage), due by **<time datetime=2022-12-21T00:00:00.000Z>Tuesday, December 20th at 4:00 pm</time> (sfo)**. ⏲️',
]);
});

Expand All @@ -345,8 +329,8 @@ describe('issueLabelHandler', function () {
expect(octokit.issues._labels).not.toContain('Product Area: Test');
expect(octokit.issues._comments).toEqual([
'Assigning to @getsentry/support for [routing](https://open.sentry.io/triage/#2-route), due by **<time datetime=2022-12-20T00:00:00.000Z>Monday, December 19th at 4:00 pm</time> (sfo)**. ⏲️',
'Routing to @getsentry/test for [triage](https://develop.sentry.dev/processing-tickets/#3-triage), due by **<time datetime=2022-12-21T00:00:00.000Z>Tuesday, December 20th at 4:00 pm</time> (sfo)**. ⏲️',
'Routing to @getsentry/rerouted for [triage](https://develop.sentry.dev/processing-tickets/#3-triage), due by **<time datetime=2022-12-21T00:00:00.000Z>Tuesday, December 20th at 4:00 pm</time> (sfo)**. ⏲️',
'Routing to @getsentry/product-owners-test for [triage](https://develop.sentry.dev/processing-tickets/#3-triage), due by **<time datetime=2022-12-21T00:00:00.000Z>Tuesday, December 20th at 4:00 pm</time> (sfo)**. ⏲️',
'Routing to @getsentry/product-owners-rerouted for [triage](https://develop.sentry.dev/processing-tickets/#3-triage), due by **<time datetime=2022-12-21T00:00:00.000Z>Tuesday, December 20th at 4:00 pm</time> (sfo)**. ⏲️',
]);
});

Expand All @@ -361,7 +345,7 @@ describe('issueLabelHandler', function () {
expect(octokit.issues._labels).toContain('Product Area: Test');
expect(octokit.issues._comments).toEqual([
'Assigning to @getsentry/support for [routing](https://open.sentry.io/triage/#2-route), due by **<time datetime=2022-12-20T00:00:00.000Z>Monday, December 19th at 4:00 pm</time> (sfo)**. ⏲️',
'Routing to @getsentry/test for [triage](https://develop.sentry.dev/processing-tickets/#3-triage), due by **<time datetime=2022-12-21T00:00:00.000Z>Tuesday, December 20th at 4:00 pm</time> (sfo)**. ⏲️',
'Routing to @getsentry/product-owners-test for [triage](https://develop.sentry.dev/processing-tickets/#3-triage), due by **<time datetime=2022-12-21T00:00:00.000Z>Tuesday, December 20th at 4:00 pm</time> (sfo)**. ⏲️',
]);
});

Expand All @@ -376,7 +360,7 @@ describe('issueLabelHandler', function () {
expect(octokit.issues._labels).toContain('Product Area: Test');
expect(octokit.issues._comments).toEqual([
'Assigning to @getsentry/support for [routing](https://open.sentry.io/triage/#2-route), due by **<time datetime=2022-12-20T00:00:00.000Z>Monday, December 19th at 4:00 pm</time> (sfo)**. ⏲️',
'Routing to @getsentry/test for [triage](https://develop.sentry.dev/processing-tickets/#3-triage), due by **<time datetime=2022-12-21T00:00:00.000Z>Tuesday, December 20th at 4:00 pm</time> (sfo)**. ⏲️',
'Routing to @getsentry/product-owners-test for [triage](https://develop.sentry.dev/processing-tickets/#3-triage), due by **<time datetime=2022-12-21T00:00:00.000Z>Tuesday, December 20th at 4:00 pm</time> (sfo)**. ⏲️',
]);
});

Expand All @@ -385,17 +369,12 @@ describe('issueLabelHandler', function () {
await addLabel('Product Area: Test', 'sentry-docs');
expectUntriaged();
expectRouted();
await addLabel(
'Product Area: Rerouted',
'sentry-docs',
undefined,
'closed'
);
await addLabel('Product Area: Rerouted', 'sentry-docs', 'closed');
expect(octokit.issues._labels).toContain('Product Area: Rerouted');
expect(octokit.issues._labels).toContain('Product Area: Test');
expect(octokit.issues._comments).toEqual([
'Assigning to @getsentry/support for [routing](https://open.sentry.io/triage/#2-route), due by **<time datetime=2022-12-20T00:00:00.000Z>Monday, December 19th at 4:00 pm</time> (sfo)**. ⏲️',
'Routing to @getsentry/test for [triage](https://develop.sentry.dev/processing-tickets/#3-triage), due by **<time datetime=2022-12-21T00:00:00.000Z>Tuesday, December 20th at 4:00 pm</time> (sfo)**. ⏲️',
'Routing to @getsentry/product-owners-test for [triage](https://develop.sentry.dev/processing-tickets/#3-triage), due by **<time datetime=2022-12-21T00:00:00.000Z>Tuesday, December 20th at 4:00 pm</time> (sfo)**. ⏲️',
]);
});

Expand All @@ -422,7 +401,7 @@ describe('issueLabelHandler', function () {
expectRouted();
expect(octokit.issues._comments).toEqual([
'Assigning to @getsentry/support for [routing](https://open.sentry.io/triage/#2-route), due by **<time datetime=2022-12-20T00:00:00.000Z>Monday, December 19th at 4:00 pm</time> (sfo)**. ⏲️',
'Routing to @getsentry/test for [triage](https://develop.sentry.dev/processing-tickets/#3-triage), due by **<time datetime=2022-12-21T13:00:00.000Z>Wednesday, December 21st at 14:00</time> (vie)**. ⏲️',
'Routing to @getsentry/product-owners-test for [triage](https://develop.sentry.dev/processing-tickets/#3-triage), due by **<time datetime=2022-12-21T13:00:00.000Z>Wednesday, December 21st at 14:00</time> (vie)**. ⏲️',
]);
});
});
Expand Down
43 changes: 12 additions & 31 deletions src/brain/issueLabelHandler/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
} from '@utils/businessHours';
import { getOssUserType } from '@utils/getOssUserType';
import { isFromABot } from '@utils/isFromABot';
import { slugizeProductArea } from '@utils/slugizeProductArea';

const LENGTH_OF_PRODUCT_AREA_LABEL_PREFIX = 14;
const REPOS_TO_TRACK_FOR_ROUTING = new Set(['sentry', 'sentry-docs']);
Expand Down Expand Up @@ -115,35 +116,20 @@ export async function markUnrouted({
tx.finish();
}

async function routeIssue(
octokit,
productAreaLabelName,
productAreaLabelDescription
) {
async function routeIssue(octokit, productAreaLabelName) {
try {
const strippedTeamName =
productAreaLabelName
?.substr(LENGTH_OF_PRODUCT_AREA_LABEL_PREFIX)
.replace(' ', '-')
.toLowerCase() || '';
const productArea = productAreaLabelName?.substr(
LENGTH_OF_PRODUCT_AREA_LABEL_PREFIX
);
const ghTeamSlug = 'product-owners-' + slugizeProductArea(productArea);
await octokit.teams.getByName({
org: SENTRY_ORG,
team_slug: strippedTeamName,
});
return `Routing to @${SENTRY_ORG}/${strippedTeamName} for [triage](https://develop.sentry.dev/processing-tickets/#3-triage)`;
team_slug: ghTeamSlug,
}); // expected to throw if team doesn't exist
return `Routing to @${SENTRY_ORG}/${ghTeamSlug} for [triage](https://develop.sentry.dev/processing-tickets/#3-triage)`;
} catch (error) {
// If the label name doesn't work, try description
try {
const descriptionSlugName = productAreaLabelDescription || '';
await octokit.teams.getByName({
org: SENTRY_ORG,
team_slug: descriptionSlugName,
});
return `Routing to @${SENTRY_ORG}/${descriptionSlugName} for [triage](https://develop.sentry.dev/processing-tickets/#3-triage)`;
} catch (error) {
Sentry.captureException(error);
return `Failed to route to ${productAreaLabelName}. Defaulting to @${SENTRY_ORG}/open-source for [triage](https://develop.sentry.dev/processing-tickets/#3-triage)`;
}
Sentry.captureException(error);
return `Failed to route for ${productAreaLabelName}. Defaulting to @${SENTRY_ORG}/open-source for [triage](https://develop.sentry.dev/processing-tickets/#3-triage)`;
}
}

Expand Down Expand Up @@ -240,12 +226,7 @@ export async function markRouted({
labels: [UNTRIAGED_LABEL],
});

const productAreaLabelDescription = productAreaLabel?.description;
const routedTeam = await routeIssue(
octokit,
productAreaLabelName,
productAreaLabelDescription
);
const routedTeam = await routeIssue(octokit, productAreaLabelName);

const timeToTriageBy = await calculateSLOViolationTriage(UNTRIAGED_LABEL, [
productAreaLabel,
Expand Down
21 changes: 21 additions & 0 deletions src/utils/slugizeProductArea.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { slugizeProductArea } from './slugizeProductArea';

describe('slugizeProductArea', function () {
describe('slugize tests', function () {
it('behaves as expected', async function () {
const cases = [
['MOST of ALL', 'most-of-all'],
['Cheese & Bread', 'cheese-bread'],
['otherwise - notherwise', 'otherwise-notherwise'],
['over - & - yonder', 'over-yonder'],
["other's druthers", 'others-druthers'],
];
cases.forEach((x) => {
expect(slugizeProductArea(x[0])).toEqual(x[1]);
});
expect(() => {
slugizeProductArea('other^s druthers');
}).toThrow('Bad slug: other^s-druthers');
});
});
});
15 changes: 15 additions & 0 deletions src/utils/slugizeProductArea.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export function slugizeProductArea(s) {
// Keep this in sync with slugize in ...
// https://github.com/getsentry/security-as-code/blob/main/rbac/lib/make-product-owners
// Good luck!
const slug = s
.replace(/-/g, ' ')
.replace(/&/g, ' ')
.replace(/ +/g, '-')
.replace(/'/g, '')
.toLowerCase();
if (!/^[a-z][a-z-]+[a-z]$/.test(slug)) {
throw 'Bad slug: ' + slug;
}
return slug;
}

0 comments on commit bfe6543

Please sign in to comment.