Skip to content

Commit

Permalink
Clear issue status on label removal from user (#560)
Browse files Browse the repository at this point in the history
* clear issue status on label removal from user
  • Loading branch information
hubertdeng123 authored Jul 28, 2023
1 parent 77f544c commit 2cb4d38
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 10 deletions.
26 changes: 26 additions & 0 deletions src/api/github/org.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,32 @@ export class GitHubOrg {
await this.sendGraphQuery(modifyProjectIssueFieldMutation, data);
}

async clearProjectIssueField(
itemId: string,
projectFieldOption: string,
fieldId: string
) {
const modifyProjectIssueFieldMutation = `mutation {
clearProjectV2ItemFieldValue(
input: {
projectId: "${this.project.nodeId}"
itemId: "${itemId}"
fieldId: "${fieldId}"
}
) {
projectV2Item {
id
}
}
}`;
const data = {
itemId,
projectFieldOption,
fieldId,
};
await this.sendGraphQuery(modifyProjectIssueFieldMutation, data);
}

async modifyDueByDate(
itemId: string,
projectFieldOption: string,
Expand Down
55 changes: 53 additions & 2 deletions src/brain/issueLabelHandler/followups.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,53 @@ export async function updateCommunityFollowups({
tx.finish();
}

export async function clearWaitingForProductOwnerStatus({
id,
payload,
...rest
}: EmitterWebhookEvent<'issues.unlabeled'>) {
const tx = Sentry.startTransaction({
op: 'brain',
name: 'issueLabelHandler.clearWaitingForProductOwnerStatus',
});

const org = GH_ORGS.getForPayload(payload);

const reasonsToDoNothing = [
isNotInARepoWeCareAboutForFollowups,
isNotWaitingForLabel,
isFromABot,
];
if (await shouldSkip(payload, org, reasonsToDoNothing)) {
return;
}

const { label } = payload;
const repo = payload.repository.name;
const issueNumber = payload.issue.number;
if (label?.name == null) {
Sentry.captureException(
`Webhook label name is undefined for ${repo}/${issueNumber}`
);
return;
}
const labelName = label.name;

const itemId: string = await org.addIssueToGlobalIssuesProject(
payload.issue.node_id,
repo,
issueNumber
);

await org.clearProjectIssueField(
itemId,
labelName,
org.project.fieldIds.status
);

tx.finish();
}

export async function ensureOneWaitingForLabel({
id,
payload,
Expand All @@ -128,8 +175,12 @@ export async function ensureOneWaitingForLabel({
const { issue, label } = payload;
const repo = payload.repository.name;
const issueNumber = payload.issue.number;
// Here label will never be undefined, ts is erroring here but is handled in the shouldSkip above
// @ts-ignore
if (label?.name == null) {
Sentry.captureException(
`Webhook label name is undefined for ${repo}/${issueNumber}`
);
return;
}
const labelName = label.name;

const labelToRemove = issue.labels?.find(
Expand Down
67 changes: 63 additions & 4 deletions src/brain/issueLabelHandler/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { defaultErrorHandler, githubEvents } from '@api/github';
import { MockOctokitError } from '@api/github/__mocks__/mockError';
import * as businessHourFunctions from '@utils/businessHours';
import { db } from '@utils/db';
import * as isFromABot from '@utils/isFromABot';

import { issueLabelHandler } from '.';

Expand Down Expand Up @@ -546,17 +547,21 @@ describe('issueLabelHandler', function () {
describe('followups test cases', function () {
let modifyProjectIssueFieldSpy,
modifyDueByDateSpy,
addIssueToGlobalIssuesProjectSpy;
addIssueToGlobalIssuesProjectSpy,
clearProjectIssueFieldSpy;
beforeAll(function () {
modifyProjectIssueFieldSpy = jest
.spyOn(org, 'modifyProjectIssueField')
.mockImplementation(jest.fn());
modifyDueByDateSpy = jest
.spyOn(org, 'modifyDueByDate')
.mockImplementation(jest.fn());
addIssueToGlobalIssuesProjectSpy = jest
.spyOn(org, 'addIssueToGlobalIssuesProject')
.mockReturnValue('itemId');
modifyDueByDateSpy = jest
.spyOn(org, 'modifyDueByDate')
.mockImplementation(jest.fn());
clearProjectIssueFieldSpy = jest
.spyOn(org, 'clearProjectIssueField')
.mockImplementation(jest.fn());
});
afterEach(function () {
jest.clearAllMocks();
Expand Down Expand Up @@ -748,5 +753,59 @@ describe('issueLabelHandler', function () {
org.project.fieldIds.responseDue
);
});

it.each([
WAITING_FOR_PRODUCT_OWNER_LABEL,
WAITING_FOR_SUPPORT_LABEL,
WAITING_FOR_COMMUNITY_LABEL,
])(
"should clear project issue status field if user removes '%s'",
async function (label) {
await setupIssue();
await addLabel(label, 'routing-repo');
await createGitHubEvent(
fastify,
'issues.unlabeled',
makePayload({
repo: 'routing-repo',
label,
sender: undefined,
state: undefined,
author_association: undefined,
})
);
org.api.issues.removeLabel(label);
expect(clearProjectIssueFieldSpy).toHaveBeenLastCalledWith(
'itemId',
label,
org.project.fieldIds.status
);
}
);

it.each([
WAITING_FOR_PRODUCT_OWNER_LABEL,
WAITING_FOR_SUPPORT_LABEL,
WAITING_FOR_COMMUNITY_LABEL,
])(
"should not clear project issue status field if bot removes '%s'",
async function (label) {
await setupIssue();
await addLabel(label, 'routing-repo');
await createGitHubEvent(
fastify,
'issues.unlabeled',
makePayload({
repo: 'routing-repo',
label,
sender: 'getsentry-bot',
state: undefined,
author_association: undefined,
})
);
org.api.issues.removeLabel(label);
expect(clearProjectIssueFieldSpy).not.toHaveBeenCalled();
}
);
});
});
6 changes: 6 additions & 0 deletions src/brain/issueLabelHandler/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { githubEvents } from '@api/github';

import {
clearWaitingForProductOwnerStatus,
ensureOneWaitingForLabel,
updateCommunityFollowups,
} from './followups';
Expand Down Expand Up @@ -28,4 +29,9 @@ export async function issueLabelHandler() {
githubEvents.on('issue_comment.created', updateCommunityFollowups);
githubEvents.removeListener('issues.labeled', ensureOneWaitingForLabel);
githubEvents.on('issues.labeled', ensureOneWaitingForLabel);
githubEvents.removeListener(
'issues.unlabeled',
clearWaitingForProductOwnerStatus
);
githubEvents.on('issues.unlabeled', clearWaitingForProductOwnerStatus);
}
8 changes: 4 additions & 4 deletions test/github-orgs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ getsentry:
privateKey: 'STUB'
installationId: -1
project:
nodeId: ''
nodeId: 'projectNodeId'
fieldIds:
productArea: ''
status: ''
responseDue: ''
productArea: 'productAreaFieldId'
status: 'statusFieldId'
responseDue: 'responseDueFieldId'
repos:
withRouting:
- routing-repo
Expand Down

0 comments on commit 2cb4d38

Please sign in to comment.