Skip to content

Commit

Permalink
feat: add incident notification functionality for workspace creation …
Browse files Browse the repository at this point in the history
…in Slack
  • Loading branch information
simlarsen committed Feb 27, 2025
1 parent 3c6a8ef commit 5e641fa
Show file tree
Hide file tree
Showing 5 changed files with 500 additions and 269 deletions.
272 changes: 3 additions & 269 deletions Common/Server/Services/IncidentService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,24 +54,12 @@ import Label from "../../Models/DatabaseModels/Label";
import LabelService from "./LabelService";
import IncidentSeverity from "../../Models/DatabaseModels/IncidentSeverity";
import IncidentSeverityService from "./IncidentSeverityService";
import {
WorkspaceMessageBlock,
WorkspaceMessagePayloadButton,
WorkspacePayloadButtons,
WorkspacePayloadDivider,
WorkspacePayloadHeader,
WorkspacePayloadMarkdown,
} from "../../Types/Workspace/WorkspaceMessagePayload";
import WorkspaceNotificationRuleService, {
MessageBlocksByWorkspaceType,
} from "./WorkspaceNotificationRuleService";
import NotificationRuleEventType from "../../Types/Workspace/NotificationRules/EventType";

import {
WorkspaceChannel,
WorkspaceSendMessageResponse,
} from "../Utils/Workspace/WorkspaceBase";
import WorkspaceType from "../../Types/Workspace/WorkspaceType";
import SlackActionType from "../Utils/Workspace/Slack/Actions/ActionTypes";
import IncidentWorkspaceMessages from "../Utils/Workspace/WorkspaceMessages/Incident";

export class Service extends DatabaseService<Model> {
public constructor() {
Expand Down Expand Up @@ -545,7 +533,7 @@ ${createdItem.remediationNotes || "No remediation notes provided."}`,
const workspaceResult: {
channelsCreated: Array<WorkspaceChannel>;
workspaceSendMessageResponse: WorkspaceSendMessageResponse;
} | null = await this.notifyWorkspaceOnIncidentCreate({
} | null = await IncidentWorkspaceMessages.notifyWorkspaceOnIncidentCreate({
projectId: createdItem.projectId,
incidentId: createdItem.id!,
incidentNumber: createdItem.incidentNumber!,
Expand Down Expand Up @@ -1416,259 +1404,5 @@ ${incidentSeverity.name}
logger.error(err);
});
}

public async notifyWorkspaceOnIncidentCreate(data: {
projectId: ObjectID;
incidentId: ObjectID;
incidentNumber: number;
}): Promise<{
channelsCreated: WorkspaceChannel[];
workspaceSendMessageResponse: WorkspaceSendMessageResponse;
} | null> {
try {
// we will notify the workspace about the incident creation with the bot tokken which is in WorkspaceProjectAuth Table.
return await WorkspaceNotificationRuleService.createInviteAndPostToChannelsBasedOnRules(
{
projectId: data.projectId,
notificationFor: {
incidentId: data.incidentId,
},
notificationRuleEventType: NotificationRuleEventType.Incident,
channelNameSiffix: data.incidentNumber.toString(),
messageBlocksByWorkspaceType:
await this.getWorkspaceMessageBlocksForIncidentCreate({
incidentId: data.incidentId,
}),
},
);
} catch (err) {
// log the error and continue.
logger.error(err);
return null;
}
}

public async getWorkspaceMessageBlocksForIncidentCreate(data: {
incidentId: ObjectID;
}): Promise<Array<MessageBlocksByWorkspaceType>> {
const messageBlocks: Array<MessageBlocksByWorkspaceType> = [];

// slack and teams workspasce types.

const incident: Model | null = await this.findOneById({
id: data.incidentId,
select: {
projectId: true,
incidentNumber: true,
title: true,
description: true,
incidentSeverity: {
name: true,
},
rootCause: true,
remediationNotes: true,
currentIncidentState: {
name: true,
},
monitors: {
name: true,
_id: true,
},
},
props: {
isRoot: true,
},
});

if (!incident) {
throw new BadDataException("Incident not found");
}

// Slack.

const blockSlack: Array<WorkspaceMessageBlock> = [];

if (incident.incidentNumber) {
const markdownBlock1: WorkspacePayloadHeader = {
_type: "WorkspacePayloadHeader",
text: `:rotating_light: **Incident #${incident.incidentNumber}**`,
};
blockSlack.push(markdownBlock1);
}

if (incident.title) {
const markdownBlock2: WorkspacePayloadMarkdown = {
_type: "WorkspacePayloadMarkdown",
text: `**[${incident.title}](${(await this.getIncidentLinkInDashboard(incident.projectId!, incident.id!)).toString()})**`,
};
blockSlack.push(markdownBlock2);
}

if (incident.description) {
const markdownBlock3: WorkspacePayloadMarkdown = {
_type: "WorkspacePayloadMarkdown",
text: `${incident.description}`,
};
blockSlack.push(markdownBlock3);
}

if (incident.currentIncidentState?.name) {
const markdownBlock7: WorkspacePayloadMarkdown = {
_type: "WorkspacePayloadMarkdown",
text: `:arrow_right: **Incident State**: ${incident.currentIncidentState.name}`,
};
blockSlack.push(markdownBlock7);
}

if (incident.incidentSeverity?.name) {
const markdownBlock4: WorkspacePayloadMarkdown = {
_type: "WorkspacePayloadMarkdown",
text: `:warning: **Severity**: ${incident.incidentSeverity.name}`,
};
blockSlack.push(markdownBlock4);
}

// check for monitors.
if (incident.monitors && incident.monitors.length > 0) {
let text: string = `:earth_americas: *Resources Affected*:\n`;

for (const monitor of incident.monitors) {
text += `- [${monitor.name}](${(await MonitorService.getMonitorLinkInDashboard(incident.projectId!, monitor.id!)).toString()})\n`;
}

// now add text to markdwon block.
const markdownBlock5: WorkspacePayloadMarkdown = {
_type: "WorkspacePayloadMarkdown",
text: text,
};

blockSlack.push(markdownBlock5);
}

if (incident.rootCause) {
const markdownBlock5: WorkspacePayloadMarkdown = {
_type: "WorkspacePayloadMarkdown",
text: `:page_facing_up: **Root Cause**:
${incident.rootCause}`,
};
blockSlack.push(markdownBlock5);
}

if (incident.remediationNotes) {
const markdownBlock6: WorkspacePayloadMarkdown = {
_type: "WorkspacePayloadMarkdown",
text: `:dart: **Remediation Notes**:
${incident.remediationNotes}`,
};
blockSlack.push(markdownBlock6);
}

// add divider.

const dividerBlock: WorkspacePayloadDivider = {
_type: "WorkspacePayloadDivider",
};

blockSlack.push(dividerBlock);

// now add buttons.
// View Incident.
// Execute On Call
// Acknowledge incident
// Resolve Incident.
// Change Incident State.
// Add Note.

const buttons: Array<WorkspaceMessagePayloadButton> = [];

// view incident.
const viewIncidentButton: WorkspaceMessagePayloadButton = {
_type: "WorkspaceMessagePayloadButton",
title: "View Incident",
url: await this.getIncidentLinkInDashboard(
incident.projectId!,
incident.id!,
),
value: "view_incident",
actionId: SlackActionType.ViewIncident,
};

buttons.push(viewIncidentButton);

// execute on call.
const executeOnCallButton: WorkspaceMessagePayloadButton = {
_type: "WorkspaceMessagePayloadButton",
title: ":telephone_receiver: Execute On Call",
value: "execute_on_call",
actionId: SlackActionType.ExecuteIncidentOnCallPolicy,
};

buttons.push(executeOnCallButton);

// acknowledge incident.
const acknowledgeIncidentButton: WorkspaceMessagePayloadButton = {
_type: "WorkspaceMessagePayloadButton",
title: ":eyes: Acknowledge Incident",
value: "acknowledge_incident",
actionId: SlackActionType.AcknowledgeIncident,
};

buttons.push(acknowledgeIncidentButton);

// resolve incident.
const resolveIncidentButton: WorkspaceMessagePayloadButton = {
_type: "WorkspaceMessagePayloadButton",
title: ":white_check_mark: Resolve Incident",
value: "resolve_incident",
actionId: SlackActionType.ResolveIncident,
};

buttons.push(resolveIncidentButton);

// change incident state.
const changeIncidentStateButton: WorkspaceMessagePayloadButton = {
_type: "WorkspaceMessagePayloadButton",
title: ":arrow_right: Change Incident State",
value: "change_incident_state",
actionId: SlackActionType.ChangeIncidentState,
};

buttons.push(changeIncidentStateButton);

// add note.
const addNoteButton: WorkspaceMessagePayloadButton = {
_type: "WorkspaceMessagePayloadButton",
title: ":page_facing_up: Add Note",
value: "add_note",
actionId: SlackActionType.AddIncidentNote,
};

buttons.push(addNoteButton);

const workspacePayloadButtons: WorkspacePayloadButtons = {
buttons: buttons,
_type: "WorkspacePayloadButtons",
};

blockSlack.push(workspacePayloadButtons);

messageBlocks.push({
workspaceType: WorkspaceType.Slack,
messageBlocks: blockSlack,
});

// teams.

const blocksTeams: Array<WorkspaceMessageBlock> = [];

// TODO: Add teams blocks.

messageBlocks.push({
workspaceType: WorkspaceType.MicrosoftTeams,
messageBlocks: blocksTeams,
});

return messageBlocks;
}
}
export default new Service();
Loading

0 comments on commit 5e641fa

Please sign in to comment.