Skip to content

Commit

Permalink
Make stalebot multi-org
Browse files Browse the repository at this point in the history
  • Loading branch information
chadwhitacre committed Jul 26, 2023
1 parent 599b023 commit ba3cf85
Show file tree
Hide file tree
Showing 17 changed files with 192 additions and 174 deletions.
1 change: 0 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ GH_USER_TOKEN=''
GH_WEBHOOK_SECRET=''
GH_APP_PRIVATE_KEY='-----BEGIN RSA PRIVATE KEY----------END RSA PRIVATE KEY-----'
GH_ORGS_YML='github-orgs.local.yml'
PERSONAL_TEST_REPO='testing-eng-pipes'

# Slack
SLACK_SIGNING_SECRET=''
Expand Down
7 changes: 6 additions & 1 deletion github-orgs.example.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
your-org-CHANGEME:
appAuth:
appId: ''
privateKey: 'GH_APP_PRIVATE_KEY' # this is the name of an env var to pull from
privateKey: 'GH_APP_PRIVATE_KEY' # leave this, it names an env var to pull from
project:
nodeId: ''
fieldIds:
productArea: ''
status: ''
responseDue: ''
repos:
withRouting:
- 'foo'
withoutRouting:
- 'bar'
38 changes: 38 additions & 0 deletions github-orgs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,41 @@ getsentry:
productArea: 'PVTSSF_lADOABVQ184AOGW8zgJEBno'
status: 'PVTSSF_lADOABVQ184AOGW8zgI_7g0'
responseDue: 'PVTF_lADOABVQ184AOGW8zgLLxGg'
repos:
withRouting:
- 'sentry'
- 'sentry-docs'

withoutRouting:
- '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
# www.notion.so/346452f21e7947b4bf515d5f3a4d497d?v=cad7f04cf9064e7483ab426a26d3923a
- 'sentry-cocoa'
- 'sentry-java'
- 'sentry-react-native'
- 'sentry-unity'
- 'sentry-dart'
- 'sentry-android-gradle-plugin'
- 'sentry-dotnet'
- 'sentry-dart-plugin'
21 changes: 21 additions & 0 deletions src/api/github/org.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,27 @@ describe('constructor', function () {
it('does not try to get an org installation', async function () {
expect(octokitClass.apps.getOrgInstallation).toHaveBeenCalledTimes(0);
});

it('combines repos into .all', async function () {
const org = new GitHubOrg('barn', {
repos: {
withRouting: ['cheese'],
withoutRouting: ['bread'],
},
});
expect(org.repos.all).toEqual(['cheese', 'bread']);
});

it('is fine without one of them', async function () {
const org = new GitHubOrg('barn', {
repos: {
withRouting: ['cheese', 'wine'],
},
});
expect(org.repos.all).toEqual(['cheese', 'wine']);
expect(org.repos.withRouting).toEqual(['cheese', 'wine']);
expect(org.repos.withoutRouting).toEqual([]);
});
});

describe('bindAPI', function () {
Expand Down
10 changes: 10 additions & 0 deletions src/api/github/org.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
AppAuthStrategyOptions,
GitHubIssuesSomeoneElseCaresAbout,
GitHubOrgConfig,
GitHubOrgRepos,
} from '@/types';

// We can't use @ to import config here or we get an error from jest due to
Expand All @@ -19,6 +20,7 @@ export class GitHubOrg {
slug: string;
appAuth: AppAuthStrategyOptions;
project: GitHubIssuesSomeoneElseCaresAbout;
repos: GitHubOrgRepos;

// The docs say it's safe for Octokit instances to be long-lived:
//
Expand All @@ -33,6 +35,14 @@ export class GitHubOrg {
this.slug = orgSlug;
this.appAuth = config.appAuth;
this.project = config.project;
this.repos = config.repos || {};
if (!this.repos.withRouting) {
this.repos.withRouting = [];
}
if (!this.repos.withoutRouting) {
this.repos.withoutRouting = [];
}
this.repos.all = [...this.repos.withRouting, ...this.repos.withoutRouting];

// Call bindAPI ASAP. We can't call it here because constructors can't be
// async. Note that in testing this ends up being mocked as if it were
Expand Down
5 changes: 2 additions & 3 deletions src/brain/issueLabelHandler/followups.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import moment from 'moment-timezone';

import {
GH_ORGS,
SENTRY_REPOS,
WAITING_FOR_COMMUNITY_LABEL,
WAITING_FOR_LABEL_PREFIX,
WAITING_FOR_PRODUCT_OWNER_LABEL,
Expand All @@ -18,8 +17,8 @@ import { isFromABot } from '@utils/isFromABot';
import { isNotFromAnExternalOrGTMUser } from '@utils/isNotFromAnExternalOrGTMUser';
import { shouldSkip } from '@utils/shouldSkip';

function isNotInARepoWeCareAboutForFollowups(payload) {
return !SENTRY_REPOS.has(payload.repository.name);
function isNotInARepoWeCareAboutForFollowups(payload, org) {
return !org.repos.all.includes(payload.repository.name);
}

function isNotWaitingForLabel(payload) {
Expand Down
86 changes: 43 additions & 43 deletions src/brain/issueLabelHandler/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ describe('issueLabelHandler', function () {
});

it('adds `Status: Unrouted` and `Waiting for: Support` to new issues', async function () {
await createIssue('sentry-docs');
await createIssue('routing-repo');
expectWaitingForSupport();
expect(org.api.issues._labels).toContain(WAITING_FOR_SUPPORT_LABEL);
// Simulate GitHub adding Waiting for Support Label to send webhook
Expand All @@ -353,7 +353,7 @@ describe('issueLabelHandler', function () {
});

it('adds `Status: Unrouted` and `Waiting for: Support` for GTM users', async function () {
await createIssue('sentry-docs', 'Troi');
await createIssue('routing-repo', 'Troi');
expectWaitingForSupport();
expect(org.api.issues._labels).toContain(WAITING_FOR_SUPPORT_LABEL);
// Simulate GitHub adding Waiting for Support Label to send webhook
Expand All @@ -365,7 +365,7 @@ describe('issueLabelHandler', function () {
});

it('skips adding `Waiting for: Support` for internal users', async function () {
await createIssue('sentry-docs', 'Picard');
await createIssue('routing-repo', 'Picard');
expectNotWaitingForSupport();
expect(org.api.issues._labels).not.toContain(WAITING_FOR_SUPPORT_LABEL);
expect(org.api.issues._comments).toEqual([]);
Expand All @@ -381,8 +381,8 @@ describe('issueLabelHandler', function () {
});

it('removes waiting for support label when product area label is added', async function () {
await createIssue('sentry-docs');
await addLabel('Product Area: Test', 'sentry-docs');
await createIssue('routing-repo');
await addLabel('Product Area: Test', 'routing-repo');
expectWaitingforProductOwner();
expectNotWaitingForSupport();
expect(org.api.issues._labels).not.toContain(WAITING_FOR_SUPPORT_LABEL);
Expand All @@ -395,8 +395,8 @@ describe('issueLabelHandler', function () {
});

it('does not remove waiting for support label when label is added that is not a product area label', async function () {
await createIssue('sentry-docs');
await addLabel('Status: Needs More Information', 'sentry-docs');
await createIssue('routing-repo');
await addLabel('Status: Needs More Information', 'routing-repo');
expectWaitingForSupport();
expect(org.api.issues._labels).toContain(WAITING_FOR_SUPPORT_LABEL);
// Simulate GitHub adding Waiting for Support Label to send webhook
Expand All @@ -408,8 +408,8 @@ describe('issueLabelHandler', function () {
});

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');
await createIssue('routing-repo');
await addLabel('Product Area: Does Not Exist', 'routing-repo');
expectWaitingforProductOwner();
expectNotWaitingForSupport();
expect(org.api.issues._labels).not.toContain(WAITING_FOR_SUPPORT_LABEL);
Expand All @@ -422,11 +422,11 @@ describe('issueLabelHandler', function () {
});

it('removes previous Product Area labels when re[routing](https://open.sentry.io/triage/#2-route)', async function () {
await createIssue('sentry-docs');
await addLabel('Product Area: Test', 'sentry-docs');
await createIssue('routing-repo');
await addLabel('Product Area: Test', 'routing-repo');
expectWaitingforProductOwner();
expectNotWaitingForSupport();
await addLabel('Product Area: Rerouted', 'sentry-docs');
await addLabel('Product Area: Rerouted', 'routing-repo');
expect(org.api.issues._labels).toContain('Product Area: Rerouted');
expect(org.api.issues._labels).not.toContain('Product Area: Test');
expect(org.api.issues._comments).toEqual([
Expand All @@ -438,12 +438,12 @@ describe('issueLabelHandler', function () {
});

it('should not reapply label `Waiting for: Product Owner` if issue changes product areas and is not waiting for support', async function () {
await createIssue('sentry-docs');
await addLabel('Product Area: Test', 'sentry-docs');
await createIssue('routing-repo');
await addLabel('Product Area: Test', 'routing-repo');
expectWaitingforProductOwner();
expectNotWaitingForSupport();
await addLabel('Waiting for: Community', 'sentry-docs');
await addLabel('Product Area: Rerouted', 'sentry-docs');
await addLabel('Waiting for: Community', 'routing-repo');
await addLabel('Product Area: Rerouted', 'routing-repo');
expect(org.api.issues._labels).toContain('Product Area: Rerouted');
expect(org.api.issues._labels).toContain('Waiting for: Community');
expect(org.api.issues._labels).not.toContain(
Expand All @@ -458,12 +458,12 @@ describe('issueLabelHandler', function () {
});

it('should not reroute if Status: Backlog is exists on issue', async function () {
await createIssue('sentry-docs');
await addLabel('Product Area: Test', 'sentry-docs');
await createIssue('routing-repo');
await addLabel('Product Area: Test', 'routing-repo');
expectWaitingforProductOwner();
expectNotWaitingForSupport();
await addLabel('Status: Backlog', 'sentry-docs');
await addLabel('Product Area: Rerouted', 'sentry-docs');
await addLabel('Status: Backlog', 'routing-repo');
await addLabel('Product Area: Rerouted', 'routing-repo');
expect(org.api.issues._labels).toContain('Product Area: Rerouted');
expect(org.api.issues._labels).toContain('Product Area: Test');
expect(org.api.issues._comments).toEqual([
Expand All @@ -474,12 +474,12 @@ describe('issueLabelHandler', function () {
});

it('should not reroute if Status: In Progress exists on issue', async function () {
await createIssue('sentry-docs');
await addLabel('Product Area: Test', 'sentry-docs');
await createIssue('routing-repo');
await addLabel('Product Area: Test', 'routing-repo');
expectWaitingforProductOwner();
expectNotWaitingForSupport();
await addLabel('Status: In Progress', 'sentry-docs');
await addLabel('Product Area: Rerouted', 'sentry-docs');
await addLabel('Status: In Progress', 'routing-repo');
await addLabel('Product Area: Rerouted', 'routing-repo');
expect(org.api.issues._labels).toContain('Product Area: Rerouted');
expect(org.api.issues._labels).toContain('Product Area: Test');
expect(org.api.issues._comments).toEqual([
Expand All @@ -490,11 +490,11 @@ describe('issueLabelHandler', function () {
});

it('should not reroute if issue is closed', async function () {
await createIssue('sentry-docs');
await addLabel('Product Area: Test', 'sentry-docs');
await createIssue('routing-repo');
await addLabel('Product Area: Test', 'routing-repo');
expectWaitingforProductOwner();
expectNotWaitingForSupport();
await addLabel('Product Area: Rerouted', 'sentry-docs', 'closed');
await addLabel('Product Area: Rerouted', 'routing-repo', 'closed');
expect(org.api.issues._labels).toContain('Product Area: Rerouted');
expect(org.api.issues._labels).toContain('Product Area: Test');
expect(org.api.issues._comments).toEqual([
Expand Down Expand Up @@ -524,8 +524,8 @@ describe('issueLabelHandler', function () {
jest.clearAllMocks();
});
const setupIssue = async () => {
await createIssue('sentry-docs');
await addLabel('Product Area: Test', 'sentry-docs');
await createIssue('routing-repo');
await addLabel('Product Area: Test', 'routing-repo');
};

it('should remove `Waiting for: Product Owner` label when another `Waiting for: *` label is added', async function () {
Expand All @@ -534,11 +534,11 @@ describe('issueLabelHandler', function () {
expect(org.api.issues._labels).toEqual(
new Set([WAITING_FOR_PRODUCT_OWNER_LABEL, 'Product Area: Test'])
);
await addLabel('Waiting for: Community', 'sentry-docs');
await addLabel('Waiting for: Community', 'routing-repo');
expect(org.api.issues._labels).toEqual(
new Set(['Product Area: Test', WAITING_FOR_COMMUNITY_LABEL])
);
await addLabel('Waiting for: Support', 'sentry-docs');
await addLabel('Waiting for: Support', 'routing-repo');
expect(org.api.issues._labels).toEqual(
new Set(['Product Area: Test', WAITING_FOR_SUPPORT_LABEL])
);
Expand All @@ -556,8 +556,8 @@ describe('issueLabelHandler', function () {

it('should not add `Waiting for: Product Owner` label when product owner/GTM member comments and issue is waiting for community', async function () {
await setupIssue();
await addLabel(WAITING_FOR_COMMUNITY_LABEL, 'sentry-docs');
await addComment('sentry-docs', 'Picard');
await addLabel(WAITING_FOR_COMMUNITY_LABEL, 'routing-repo');
await addComment('routing-repo', 'Picard');
expect(org.api.issues._labels).toEqual(
new Set(['Product Area: Test', WAITING_FOR_COMMUNITY_LABEL])
);
Expand All @@ -575,8 +575,8 @@ describe('issueLabelHandler', function () {

it('should not add `Waiting for: Product Owner` label when contractor comments and issue is waiting for community', async function () {
await setupIssue();
await addLabel(WAITING_FOR_COMMUNITY_LABEL, 'sentry-docs');
await addComment('sentry-docs', 'Troi');
await addLabel(WAITING_FOR_COMMUNITY_LABEL, 'routing-repo');
await addComment('routing-repo', 'Troi');
expect(org.api.issues._labels).toEqual(
new Set(['Product Area: Test', WAITING_FOR_COMMUNITY_LABEL])
);
Expand All @@ -593,14 +593,14 @@ describe('issueLabelHandler', function () {
});

it('should not add `Waiting for: Product Owner` label when community member comments and issue is a PR', async function () {
await createPR('sentry-docs');
await addComment('sentry-docs', 'Skywalker', true);
await createPR('routing-repo');
await addComment('routing-repo', 'Skywalker', true);
expect(org.api.issues._labels).toEqual(new Set([]));
});

it('should add `Waiting for: Product Owner` label when community member comments and issue is not waiting for community', async function () {
await setupIssue();
await addComment('sentry-docs', 'Skywalker');
await addComment('routing-repo', 'Skywalker');
expect(org.api.issues._labels).toEqual(
new Set(['Product Area: Test', WAITING_FOR_PRODUCT_OWNER_LABEL])
);
Expand All @@ -620,8 +620,8 @@ describe('issueLabelHandler', function () {

it('should add `Waiting for: Product Owner` label when community member comments and issue is waiting for community', async function () {
await setupIssue();
await addLabel(WAITING_FOR_COMMUNITY_LABEL, 'sentry-docs');
await addComment('sentry-docs', 'Skywalker');
await addLabel(WAITING_FOR_COMMUNITY_LABEL, 'routing-repo');
await addComment('routing-repo', 'Skywalker');
expect(org.api.issues._labels).toEqual(
new Set(['Product Area: Test', WAITING_FOR_PRODUCT_OWNER_LABEL])
);
Expand Down Expand Up @@ -662,7 +662,7 @@ describe('issueLabelHandler', function () {
});

it('should modify time to respond by when adding `Waiting for: Support` label when calculateSLOViolationTriage returns null', async function () {
await createIssue('sentry-docs');
await createIssue('routing-repo');
calculateSLOViolationRouteSpy.mockReturnValue(null);
jest.spyOn(Date, 'now').mockReturnValue('2023-06-20T00:00:00.000Z');
// Simulate GH webhook being thrown when Waiting for: Product Owner label is added
Expand All @@ -685,8 +685,8 @@ describe('issueLabelHandler', function () {

it('should not modify labels when community member comments and issue is waiting for product owner', async function () {
await setupIssue();
await addLabel(WAITING_FOR_PRODUCT_OWNER_LABEL, 'sentry-docs');
await addComment('sentry-docs', 'Picard');
await addLabel(WAITING_FOR_PRODUCT_OWNER_LABEL, 'routing-repo');
await addComment('routing-repo', 'Picard');
expect(org.api.issues._labels).toEqual(
new Set(['Product Area: Test', WAITING_FOR_PRODUCT_OWNER_LABEL])
);
Expand Down
Loading

0 comments on commit ba3cf85

Please sign in to comment.