Skip to content

Commit

Permalink
feat: add feature to silent teams notifications in settings
Browse files Browse the repository at this point in the history
  • Loading branch information
christophehurpeau committed Nov 20, 2021
1 parent 59d98c5 commit eb29508
Show file tree
Hide file tree
Showing 8 changed files with 167 additions and 66 deletions.
123 changes: 93 additions & 30 deletions src/app/org-settings.tsx
@@ -1,3 +1,4 @@
/* eslint-disable max-lines */
import bodyParser from 'body-parser';
import type { Router } from 'express';
import type { ProbotOctokit } from 'probot';
Expand Down Expand Up @@ -234,23 +235,69 @@ export default function orgSettings(
{!orgMember || !orgMember.slack ? (
<>Link your github account to unlock DM Settings</>
) : (
Object.entries(dmMessages).map(([key, name]) => (
<div key={key}>
<label htmlFor={key}>
<span
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{
__html: `<input id="${key}" type="checkbox" autocomplete="off" ${
userDmSettings[key as MessageCategory]
? 'checked="checked" '
: ''
}onclick="fetch(location.pathname, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ key: '${key}', value: event.currentTarget.checked }) })" />`,
}}
/>
{name}
</label>
</div>
))
<>
{Object.entries(dmMessages).map(([key, name]) => (
<div key={key}>
<label htmlFor={key}>
<span
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{
__html: `<input id="${key}" type="checkbox" autocomplete="off" ${
userDmSettings.settings[
key as MessageCategory
]
? 'checked="checked" '
: ''
}onclick="fetch(location.pathname, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ key: '${key}', value: event.currentTarget.checked }) })" />`,
}}
/>
{name}
</label>
</div>
))}

{orgMember.teams.length === 0 ? null : (
<>
<h4 style={{ marginTop: '20px' }}>
My DM settings - Github Teams
</h4>
<i>
Untick to disable notifications for teams you
belong to.
</i>

{orgMember.teams.map((team) => (
<div key={team.id}>
<label htmlFor={`team_${team.id}`}>
<span
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{
__html: `<input id="team_${
team.id
}" type="checkbox" autocomplete="off" ${
userDmSettings.silentTeams?.some(
(t) => t.id === team.id,
)
? 'checked="checked" '
: ''
}onclick="fetch(location.pathname, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ team: ${JSON.stringify(
team,
)
.replace(/'/g, "\\'")
// TODO " in name is replaced by '. Acceptable because not used, but should be fixed.
.replace(
/"/g,
"'",
)}, value: event.currentTarget.checked }) })" />`,
}}
/>
{team.name}
</label>
</div>
))}
</>
)}
</>
)}
</div>
</div>
Expand All @@ -270,6 +317,10 @@ export default function orgSettings(
res.status(400).send('not ok');
return;
}
if (!req.body.key && !req.body.team) {
res.status(400).send('not ok');
return;
}

const user = await getUser(req, res);
if (!user) return;
Expand All @@ -280,22 +331,34 @@ export default function orgSettings(
res.redirect('/app');
return;
}
const $setOnInsert = {
orgId: org.id,
userId: user.authInfo.id,
created: new Date(),
};

(await mongoStores.userDmSettings.collection).updateOne(
await (
await mongoStores.userDmSettings.collection
).updateOne(
{
_id: `${org.id}_${user.authInfo.id}`,
},
{
$set: {
[`settings.${req.body.key}`]: req.body.value,
updated: new Date(),
},
$setOnInsert: {
orgId: org.id,
userId: user.authInfo.id,
created: new Date(),
},
},
req.body.key
? {
$set: {
[`settings.${req.body.key}`]: req.body.value,
updated: new Date(),
},
$setOnInsert,
}
: {
[req.body.value ? '$push' : '$pull']: {
silentTeams: req.body.value
? req.body.team
: { id: req.body.team.id },
},
$setOnInsert,
},
{ upsert: true },
);

Expand All @@ -305,7 +368,7 @@ export default function orgSettings(
});

if (userDmSettingsConfig) {
updateCache(org.login, user.authInfo.id, userDmSettingsConfig.settings);
updateCache(org.login, user.authInfo.id, userDmSettingsConfig);
}

res.send('ok');
Expand Down
1 change: 1 addition & 0 deletions src/context/slack/TeamSlack.ts
Expand Up @@ -16,6 +16,7 @@ export interface TeamSlack {
category: MessageCategory,
toUser: AccountInfo,
message: SlackMessage,
forTeamId?: number,
) => Promise<PostSlackMessageResult>;
updateMessage: (
toUser: AccountInfo,
Expand Down
9 changes: 8 additions & 1 deletion src/context/slack/initTeamSlack.ts
Expand Up @@ -180,6 +180,7 @@ export const initTeamSlack = async <GroupNames extends string>(
category: MessageCategory,
toUser: AccountInfo,
message: SlackMessage,
forTeamId?: number,
): Promise<PostSlackMessageResult> => {
context.log.debug(
{
Expand All @@ -198,7 +199,13 @@ export const initTeamSlack = async <GroupNames extends string>(
toUser.id,
);

if (!userDmSettings[category]) return null;
if (!userDmSettings.settings[category]) return null;
if (
forTeamId &&
userDmSettings.silentTeams.some((team) => team.id === forTeamId)
) {
return null;
}

const user = membersMap.get(toUser.login);
if (!user || !user.slackClient || !user.im) return null;
Expand Down
50 changes: 32 additions & 18 deletions src/dm/getUserDmSettings.ts
@@ -1,12 +1,11 @@
import type { MongoInsertType } from 'liwi-mongo';
import { accountConfigs, defaultConfig } from '../accountConfigs';
import type { MongoStores } from '../mongo';
import type { MessageCategory } from './MessageCategory';
import type { MongoStores, UserDmSettings } from '../mongo';
import { defaultDmSettings } from './defaultDmSettings';

export type UserDmSettings = Record<MessageCategory, boolean>;
const cache = new Map<string, Map<number, UserDmSettings>>();
const cache = new Map<string, Map<number, MongoInsertType<UserDmSettings>>>();

const getDefaultDmSettings = (org: string): UserDmSettings => {
const getDefaultDmSettings = (org: string): UserDmSettings['settings'] => {
const accountConfig = accountConfigs[org] || defaultConfig;
return accountConfig.defaultDmSettings
? { ...defaultDmSettings, ...accountConfig.defaultDmSettings }
Expand All @@ -16,36 +15,51 @@ const getDefaultDmSettings = (org: string): UserDmSettings => {
export const updateCache = (
org: string,
userId: number,
newSettings: Partial<UserDmSettings>,
): void => {
userDmSettings: MongoInsertType<UserDmSettings>,
): MongoInsertType<UserDmSettings> => {
const orgDefaultDmSettings = getDefaultDmSettings(org);

// set defaults to make sure we always have all settings even after an update
userDmSettings.settings = {
...orgDefaultDmSettings,
...userDmSettings.settings,
};

let orgCache = cache.get(org);
if (!orgCache) {
orgCache = new Map();
cache.set(org, orgCache);
}
orgCache.set(userId, { ...getDefaultDmSettings(org), ...newSettings });
orgCache.set(userId, userDmSettings);

return userDmSettings;
};

export const getUserDmSettings = async (
mongoStores: MongoStores,
org: string,
orgId: number,
userId: number,
): Promise<UserDmSettings> => {
const orgDefaultDmSettings = getDefaultDmSettings(org);
): Promise<MongoInsertType<UserDmSettings>> => {
const orgCache = cache.get(org);
if (orgCache) {
const userCache = orgCache.get(userId);
if (userCache) return userCache;
}

const userDmSettingsConfig = await mongoStores.userDmSettings.findOne({
orgId,
userId,
});

const config = userDmSettingsConfig
? {
...orgDefaultDmSettings,
...userDmSettingsConfig.settings,
}
: orgDefaultDmSettings;
if (userDmSettingsConfig) {
return updateCache(org, userId, userDmSettingsConfig);
}

updateCache(org, userId, config);
return config;
return updateCache(org, userId, {
orgId,
userId,
settings: {} as UserDmSettings['settings'],
silentTeams: [],
});
};
47 changes: 30 additions & 17 deletions src/events/pr-handlers/reviewRequestRemoved.ts
Expand Up @@ -102,26 +102,39 @@ export default function reviewRequestRemoved(
if (requestedReviewers.some((rr) => rr.login === sender.login)) {
requestedReviewers.forEach((potentialReviewer) => {
if (potentialReviewer.login === sender.login) return;
repoContext.slack.postMessage('pr-review', potentialReviewer, {
text: `:skull_and_crossbones: ${repoContext.slack.mention(
sender.login,
)} removed the request for your team _${
requestedTeam.name
}_ review on ${slackUtils.createPrLink(
pullRequest,
repoContext,
)}`,
});
repoContext.slack.postMessage(
'pr-review',
potentialReviewer,
{
text: `:skull_and_crossbones: ${repoContext.slack.mention(
sender.login,
)} removed the request for your team _${
requestedTeam.name
}_ review on ${slackUtils.createPrLink(
pullRequest,
repoContext,
)}`,
},
requestedTeam ? requestedTeam.id : undefined,
);
});
} else {
requestedReviewers.forEach((potentialReviewer) => {
repoContext.slack.postMessage('pr-review', potentialReviewer, {
text: `:skull_and_crossbones: ${repoContext.slack.mention(
sender.login,
)} removed the request for ${
requestedTeam ? `your team _${requestedTeam.name}_` : 'your'
} review on ${slackUtils.createPrLink(pullRequest, repoContext)}`,
});
repoContext.slack.postMessage(
'pr-review',
potentialReviewer,
{
text: `:skull_and_crossbones: ${repoContext.slack.mention(
sender.login,
)} removed the request for ${
requestedTeam ? `your team _${requestedTeam.name}_` : 'your'
} review on ${slackUtils.createPrLink(
pullRequest,
repoContext,
)}`,
},
requestedTeam ? requestedTeam.id : undefined,
);
});
}

Expand Down
1 change: 1 addition & 0 deletions src/events/pr-handlers/reviewRequested.ts
Expand Up @@ -81,6 +81,7 @@ export default function reviewRequested(
'pr-review',
potentialReviewer,
message,
requestedTeam ? requestedTeam.id : undefined,
);
if (result) {
await appContext.mongoStores.slackSentMessages.insertOne({
Expand Down
Expand Up @@ -34,6 +34,7 @@ export function getRolesFromPullRequestAndReviewers(
if (pullRequest.requested_teams) {
// TODO
// requestedReviewers.push ...
// And figure a way to keep `silentTeams` working
}

if (requestedReviewers) {
Expand Down
1 change: 1 addition & 0 deletions src/mongo.ts
Expand Up @@ -36,6 +36,7 @@ export interface UserDmSettings extends MongoBaseModel {
userId: number;
orgId: number;
settings: Record<MessageCategory, boolean>;
silentTeams: OrgTeamEmbed[];
}

interface BaseAccount extends MongoBaseModel<number> {
Expand Down

0 comments on commit eb29508

Please sign in to comment.