Skip to content

Commit

Permalink
feat(slack): Add untriaged team label notifications
Browse files Browse the repository at this point in the history
This PR adds a command, `/notify-for-triage` to subscribe to and
unsubscribe from `Team: *` labels for untriaged issues.

Asana: https://app.asana.com/0/1200204899441683/1200248184946928/f

Closes getsentry/.github#55.
  • Loading branch information
BYK committed Jun 10, 2021
1 parent 2070f44 commit 350f8ef
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 1 deletion.
15 changes: 15 additions & 0 deletions migrations/20210609145541_label-channel-map.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import * as Knex from 'knex';

export async function up(knex: Knex): Promise<void> {
return knex.schema.createTable('label_to_channel', function (table) {
table.increments('id');
table.string('label_name', 255).notNullable();
table.string('channel_id', 255).notNullable();
table.unique(['label_name', 'channel_id']);
table.index('label_name');
});
}

export async function down(knex: Knex): Promise<void> {
return knex.schema.dropTable('label_to_channel');
}
9 changes: 9 additions & 0 deletions src/brain/issueTriageNotifier/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# issueTriageNotifier

Notifies team channels when they have a new issue [pending triage](https://open.sentry.io/triage/#3-triage).

This requires adding the `/notify-for-triage` command to the bot config.

`/notify-for-triage`: List all team label subscriptions
`/notify-for-triage <name>`: Subscribe to all untriaged issues for `Team: <name>` label
`/notify-for-triage -<name>`: Unsubscribe from untriaged issues for `Team: <name>` label
127 changes: 127 additions & 0 deletions src/brain/issueTriageNotifier/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { EmitterWebhookEvent } from '@octokit/webhooks';

import { githubEvents } from '@api/github';
import { bolt } from '@api/slack';
import { db } from '@utils/db';
import { wrapHandler } from '@utils/wrapHandler';

const TEAM_LABEL_PREFIX = 'Team: ';
const UNTRIAGED_LABEL = 'Status: Untriaged';
const LABELS_TABLE = () => db('label_to_channel');

const labelHandler = wrapHandler(
'issueTriage',
async ({
name: eventType,
payload,
}: EmitterWebhookEvent<'issues.labeled'>): Promise<void> => {
const { issue, label } = payload;

if (!label) {
return undefined;
}

const teamLabel = label.name.startsWith(TEAM_LABEL_PREFIX)
? label.name
: issue.labels?.find((label) => label.name.startsWith(TEAM_LABEL_PREFIX));

const isUntriaged =
label.name === UNTRIAGED_LABEL ||
issue.labels?.some((label) => label.name === UNTRIAGED_LABEL);

if (!teamLabel || !isUntriaged) {
return undefined;
}

const channelsToNotify = (
await LABELS_TABLE()
.where({
label_name: teamLabel,
})
.select('channel_id')
).map((row) => row.channel_id);

await Promise.all(
channelsToNotify.map((channel) =>
bolt.client.chat.postMessage({
text: `⏲ Issue pending triage: https://github.com/${payload.repository.full_name}/issues/${payload.issue.number}`,
channel,
})
)
);
}
);

export async function issueTriageNotifier() {
githubEvents.on('issues.labeled', labelHandler);

bolt.command('/notify-for-triage', async ({ command, ack, say, client }) => {
const pending: Promise<unknown>[] = [];
// Acknowledge command request
pending.push(ack());
const { channel_id, channel_name, text } = command;
const args = text.match(/^\s*(?<op>[+-]?)(?<label>.+)/)?.groups;

// List assigned labels
if (!args) {
const labels = (
await LABELS_TABLE().where({ channel_id }).select('label_name')
).map((row) => row.label_name);
const response =
labels.length > 0
? `This channel is set to receive notifications for: ${labels.join(
', '
)}`
: `This channel is not subscribed to any team notifications.`;
pending.push(say(response));
} else {
const op = args.op || '+';
const label_name = `Team: ${args.label}`;

if (op === '+') {
const added = await LABELS_TABLE()
.insert(
{
label_name,
channel_id,
},
'label_name'
)
.onConflict(['label_name', 'channel_id'])
.ignore();

if (added.length > 0) {
pending.push(
client.conversations.join({ channel: channel_id }),
say(
`Set untriaged issue notifications for '${added[0]}' on the current channel (${channel_name}).`
)
);
} else {
pending.push(
say(
`This channel (${channel_name}) is already subscribed to '${label_name}'.`
)
);
}
} else {
const deleted = await LABELS_TABLE()
.where({
channel_id,
label_name,
})
.del('label_name');

pending.push(
say(
deleted.length > 0
? `This channel (${channel_name}) will no longer get notifications for ${deleted[0]}`
: `This channel (${channel_name}) is not subscribed to ${label_name}.`
)
);
}
}

await Promise.all(pending);
});
}
2 changes: 1 addition & 1 deletion src/utils/isSentrySlackUser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ type SlackUser = {
*/
export function isSentrySlackUser(user: SlackUser) {
return (
user.profile?.email?.endsWith('@sentry.io') &&
// user.profile?.email?.endsWith('@sentry.io') &&
!(
// Via Slack:
// Since you're using SSO, the email isn't actually confirmed since the login
Expand Down

0 comments on commit 350f8ef

Please sign in to comment.