Skip to content

Commit

Permalink
Add waiting for labels to sdk repos (#461)
Browse files Browse the repository at this point in the history
1. adds Waiting for: * label logic to SDK repos
2. For SDK repos, when Waiting for: Community is applied, bot will track followups from external users
  • Loading branch information
hubertdeng123 authored Jun 2, 2023
1 parent 97856ad commit 06c12c0
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 59 deletions.
14 changes: 9 additions & 5 deletions src/brain/issueLabelHandler/followups.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import * as Sentry from '@sentry/node';

import { isFromABot } from '@utils/isFromABot';

const REPOS_TO_TRACK_FOR_TRIAGE = new Set(['sentry', 'sentry-docs']);
import { ClientType } from '@/api/github/clientType';
import {
SENTRY_MONOREPOS,
SENTRY_REPOS,
WAITING_FOR_COMMUNITY_LABEL,
WAITING_FOR_LABEL_PREFIX,
WAITING_FOR_PRODUCT_OWNER_LABEL,
Expand All @@ -16,8 +17,10 @@ import {
} from '@/utils/githubEventHelpers';
import { getClient } from '@api/github/getClient';

function isNotInARepoWeCareAboutForTriage(payload) {
return !REPOS_TO_TRACK_FOR_TRIAGE.has(payload.repository.name);
const REPOS_TO_TRACK_FOR_FOLLOWUPS = new Set([...SENTRY_REPOS, ...SENTRY_MONOREPOS]);

function isNotInARepoWeCareAboutForFollowups(payload) {
return !REPOS_TO_TRACK_FOR_FOLLOWUPS.has(payload.repository.name);
}

function isNotWaitingForCommunity(payload) {
Expand All @@ -40,11 +43,12 @@ export async function updateCommunityFollowups({
});

const reasonsToDoNothing = [
isNotInARepoWeCareAboutForTriage,
isNotInARepoWeCareAboutForFollowups,
isNotFromAnExternalOrGTMUser,
isNotWaitingForCommunity,
isFromABot,
];

if (await shouldSkip(payload, reasonsToDoNothing)) {
return;
}
Expand Down Expand Up @@ -79,7 +83,7 @@ export async function ensureOneWaitingForLabel({
name: 'issueLabelHandler.ensureOneWaitingForLabel',
});

const reasonsToDoNothing = [isFromABot];
const reasonsToDoNothing = [ isFromABot, isNotInARepoWeCareAboutForFollowups ];
if (await shouldSkip(payload, reasonsToDoNothing)) {
return;
}
Expand Down
66 changes: 59 additions & 7 deletions src/brain/issueLabelHandler/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,35 +223,63 @@ describe('issueLabelHandler', function () {

// Test cases
describe('triage test cases', function () {
let addIssueToGlobalIssuesProjectSpy;
beforeAll(function () {
addIssueToGlobalIssuesProjectSpy = jest
.spyOn(helpers, 'addIssueToGlobalIssuesProject')
.mockReturnValue({
addProjectV2ItemById: { item: { id: 'PROJECT_ID' } },
});
});
afterEach(function () {
jest.clearAllMocks();
});

it('adds `Status: Untriaged` to new issues', async function () {
await createIssue();
expectUntriaged();
expectAdding();
expect(octokit.issues._labels).toContain(WAITING_FOR_PRODUCT_OWNER_LABEL);
expect(addIssueToGlobalIssuesProjectSpy).toHaveBeenCalled();
});

it('adds `Status: Untriaged` for GTM users', async function () {
await createIssue(undefined, 'Troi');
expectUntriaged();
expectAdding();
expect(octokit.issues._labels).toContain(WAITING_FOR_PRODUCT_OWNER_LABEL);
expect(addIssueToGlobalIssuesProjectSpy).toHaveBeenCalled();
});

it('skips adding `Status: Untriaged` in untracked repos', async function () {
await createIssue('other-repo');
expectTriaged();
expectNoAdding();
expect(octokit.issues._labels).not.toContain(
WAITING_FOR_PRODUCT_OWNER_LABEL
);
expect(addIssueToGlobalIssuesProjectSpy).not.toHaveBeenCalled();
});

it('skips adding `Status: Untriaged` when added during creation', async function () {
untriage();
await createIssue(undefined, 'Picard');
expectUntriaged();
expectNoAdding();
expect(octokit.issues._labels).not.toContain(
WAITING_FOR_PRODUCT_OWNER_LABEL
);
expect(addIssueToGlobalIssuesProjectSpy).not.toHaveBeenCalled();
});

it('skips adding `Status: Untriaged` for internal users', async function () {
await createIssue(undefined, 'Picard');
expectTriaged();
expectNoAdding();
expect(octokit.issues._labels).not.toContain(
WAITING_FOR_PRODUCT_OWNER_LABEL
);
expect(addIssueToGlobalIssuesProjectSpy).not.toHaveBeenCalled();
});

// removing
Expand All @@ -261,26 +289,42 @@ describe('issueLabelHandler', function () {
await addLabel('Cheeseburger Pie');
expectTriaged();
expectRemoval();
expect(octokit.issues._labels).not.toContain(
WAITING_FOR_PRODUCT_OWNER_LABEL
);
expect(addIssueToGlobalIssuesProjectSpy).not.toHaveBeenCalled();
});

it('skips removing `Status: Untriaged` when its not present', async function () {
await addLabel('Cheeseburger Pie');
expectTriaged();
expectNoRemoval();
expect(octokit.issues._labels).not.toContain(
WAITING_FOR_PRODUCT_OWNER_LABEL
);
expect(addIssueToGlobalIssuesProjectSpy).not.toHaveBeenCalled();
});

it('skips removing `Status: Untriaged` when adding `Status: Untriaged`', async function () {
untriage();
await addLabel(UNTRIAGED_LABEL);
expectUntriaged();
expectNoRemoval();
expect(octokit.issues._labels).not.toContain(
WAITING_FOR_PRODUCT_OWNER_LABEL
);
expect(addIssueToGlobalIssuesProjectSpy).not.toHaveBeenCalled();
});

it('skips removing `Status: Untriaged` in untracked repos', async function () {
untriage();
await addLabel('Cheeseburger Pie', 'other-repo');
expectUntriaged();
expectNoRemoval();
expect(octokit.issues._labels).not.toContain(
WAITING_FOR_PRODUCT_OWNER_LABEL
);
expect(addIssueToGlobalIssuesProjectSpy).not.toHaveBeenCalled();
});

it('gracefully handles race with other remover of `Status: Untriaged`', async function () {
Expand All @@ -290,6 +334,10 @@ describe('issueLabelHandler', function () {
expectNoError();
expectTriaged();
expectRemoval();
expect(octokit.issues._labels).not.toContain(
WAITING_FOR_PRODUCT_OWNER_LABEL
);
expect(addIssueToGlobalIssuesProjectSpy).not.toHaveBeenCalled();
});

it("doesn't handle non-404 errors when removing `Status: Untriaged`", async function () {
Expand All @@ -299,14 +347,18 @@ describe('issueLabelHandler', function () {
expectError(400);
expectUntriaged();
expectRemoval();
expect(octokit.issues._labels).not.toContain(
WAITING_FOR_PRODUCT_OWNER_LABEL
);
expect(addIssueToGlobalIssuesProjectSpy).not.toHaveBeenCalled();
});
});

describe('[routing](https://open.sentry.io/triage/#2-route) test cases', function () {
let addIssueToProjectSpy, modifyProjectIssueFieldSpy;
let addIssueToGlobalIssuesProjectSpy, modifyProjectIssueFieldSpy;
beforeAll(function () {
addIssueToProjectSpy = jest
.spyOn(helpers, 'addIssueToProject')
addIssueToGlobalIssuesProjectSpy = jest
.spyOn(helpers, 'addIssueToGlobalIssuesProject')
.mockReturnValue({
addProjectV2ItemById: { item: { id: 'PROJECT_ID' } },
});
Expand All @@ -325,7 +377,7 @@ describe('issueLabelHandler', function () {
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)**. ⏲️',
]);
expect(addIssueToProjectSpy).toHaveBeenCalled();
expect(addIssueToGlobalIssuesProjectSpy).toHaveBeenCalled();
});

it('adds `Status: Unrouted` and `Waiting for: Support` for GTM users', async function () {
Expand All @@ -335,23 +387,23 @@ describe('issueLabelHandler', function () {
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)**. ⏲️',
]);
expect(addIssueToProjectSpy).toHaveBeenCalled();
expect(addIssueToGlobalIssuesProjectSpy).toHaveBeenCalled();
});

it('skips adding `Status: Unrouted` for internal users', async function () {
await createIssue('sentry-docs', 'Picard');
expectRouted();
expect(octokit.issues._labels).not.toContain(WAITING_FOR_SUPPORT_LABEL);
expect(octokit.issues._comments).toEqual([]);
expect(addIssueToProjectSpy).not.toHaveBeenCalled();
expect(addIssueToGlobalIssuesProjectSpy).not.toHaveBeenCalled();
});

it('skips adding `Status: Unrouted` in untracked repos', async function () {
await createIssue('Pizza Sandwich');
expectRouted();
expect(octokit.issues._labels).not.toContain(WAITING_FOR_SUPPORT_LABEL);
expect(octokit.issues._comments).toEqual([]);
expect(addIssueToProjectSpy).not.toHaveBeenCalled();
expect(addIssueToGlobalIssuesProjectSpy).not.toHaveBeenCalled();
});

it('removes unrouted label when product area label is added', async function () {
Expand Down
9 changes: 5 additions & 4 deletions src/brain/issueLabelHandler/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import moment from 'moment-timezone';
import {
BACKLOG_LABEL,
IN_PROGRESS_LABEL,
SENTRY_MONOREPOS,
OFFICE_TIME_ZONES,
OFFICES_24_HOUR,
PRODUCT_AREA_FIELD_ID,
Expand All @@ -20,7 +21,7 @@ import {
WAITING_FOR_SUPPORT_LABEL,
} from '@/config';
import {
addIssueToProject,
addIssueToGlobalIssuesProject,
getProductArea,
isNotFromAnExternalOrGTMUser,
modifyProjectIssueField,
Expand All @@ -34,7 +35,7 @@ import {
} from '@utils/businessHours';
import { slugizeProductArea } from '@utils/slugizeProductArea';

const REPOS_TO_TRACK_FOR_ROUTING = new Set(['sentry', 'sentry-docs']);
const REPOS_TO_TRACK_FOR_ROUTING = new Set(SENTRY_MONOREPOS);

import { ClientType } from '@/api/github/clientType';
import { getClient } from '@api/github/getClient';
Expand Down Expand Up @@ -112,7 +113,7 @@ export async function markUnrouted({
body: `Assigning to @${SENTRY_ORG}/support for [routing](https://open.sentry.io/triage/#2-route), due by **<time datetime=${timeToRouteBy}>${readableDueByDate}</time> (${lastOfficeInBusinessHours})**. ⏲️`,
});

await addIssueToProject(payload.issue.node_id, repo, issueNumber, octokit);
await addIssueToGlobalIssuesProject(payload.issue.node_id, repo, issueNumber, octokit);

tx.finish();
}
Expand Down Expand Up @@ -242,7 +243,7 @@ export async function markRouted({
* We'll try adding the issue to our global issues project. If it already exists, the existing ID will be returned
* https://docs.github.com/en/issues/planning-and-tracking-with-projects/automating-your-project/using-the-api-to-manage-projects#adding-an-item-to-a-project
*/
const itemId: string = await addIssueToProject(
const itemId: string = await addIssueToGlobalIssuesProject(
payload.issue.node_id,
payload.repository.name,
payload.issue.number,
Expand Down
50 changes: 12 additions & 38 deletions src/brain/issueLabelHandler/triage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,44 +6,14 @@ import {
shouldSkip,
} from '@/utils/githubEventHelpers';
import { isFromABot } from '@utils/isFromABot';
import { SENTRY_REPOS } from '@/config';

const REPOS_TO_TRACK_FOR_TRIAGE = new Set([
'arroyo',
'cdc',
'craft',
'relay',
'responses',
'self-hosted',
'sentry-native',
'snuba',
'snuba-sdk',
'symbolic',
'symbolicator',
'test-ttt-simple',
'wal2json',

// Web team, T1
'sentry-javascript',
'sentry-python',
'sentry-php',
'sentry-laravel',
'sentry-symfony',
'sentry-ruby',

// Mobile team, T1
// https://www.notion.so/sentry/346452f21e7947b4bf515d5f3a4d497d?v=cad7f04cf9064e7483ab426a26d3923a
'sentry-cocoa',
'sentry-java',
'sentry-react-native',
'sentry-unity',
'sentry-dart',
'sentry-android-gradle-plugin',
'sentry-dotnet',
'sentry-dart-plugin',
]);
import { ClientType } from '@/api/github/clientType';
import { UNTRIAGED_LABEL } from '@/config';
import { UNTRIAGED_LABEL, WAITING_FOR_PRODUCT_OWNER_LABEL } from '@/config';
import { getClient } from '@api/github/getClient';
import { addIssueToGlobalIssuesProject } from '@/utils/githubEventHelpers';

const REPOS_TO_TRACK_FOR_TRIAGE = new Set(SENTRY_REPOS);

function isAlreadyUntriaged(payload) {
return !isAlreadyTriaged(payload);
Expand Down Expand Up @@ -85,14 +55,18 @@ export async function markUntriaged({
// New issues get an Untriaged label.
const owner = payload.repository.owner.login;
const octokit = await getClient(ClientType.App, owner);
const repo = payload.repository.name;
const issueNumber = payload.issue.number;

await octokit.issues.addLabels({
owner,
repo: payload.repository.name,
issue_number: payload.issue.number,
labels: [UNTRIAGED_LABEL],
repo: repo,
issue_number: issueNumber,
labels: [UNTRIAGED_LABEL, WAITING_FOR_PRODUCT_OWNER_LABEL],
});

await addIssueToGlobalIssuesProject(payload.issue.node_id, repo, issueNumber, octokit);

tx.finish();
}

Expand Down
36 changes: 36 additions & 0 deletions src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,42 @@ export const WAITING_FOR_PRODUCT_OWNER_LABEL = 'Waiting for: Product Owner';
export const MAX_TRIAGE_DAYS = 2;
export const MAX_ROUTE_DAYS = 1;

export const SENTRY_MONOREPOS = [ 'sentry', 'sentry-docs' ];
export const SENTRY_REPOS = [
'arroyo',
'cdc',
'craft',
'relay',
'responses',
'self-hosted',
'sentry-native',
'snuba',
'snuba-sdk',
'symbolic',
'symbolicator',
'test-ttt-simple',
'wal2json',

// Web team, T1
'sentry-javascript',
'sentry-python',
'sentry-php',
'sentry-laravel',
'sentry-symfony',
'sentry-ruby',

// Mobile team, T1
// https://www.notion.so/sentry/346452f21e7947b4bf515d5f3a4d497d?v=cad7f04cf9064e7483ab426a26d3923a
'sentry-cocoa',
'sentry-java',
'sentry-react-native',
'sentry-unity',
'sentry-dart',
'sentry-android-gradle-plugin',
'sentry-dotnet',
'sentry-dart-plugin',
];

/**
* Issues Someone Else Cares About Project
*/
Expand Down
Loading

0 comments on commit 06c12c0

Please sign in to comment.