mirror of
https://github.com/OneUptime/oneuptime.git
synced 2026-04-06 00:32:12 +02:00
feat: Implement Incident Episode services, routes, and notification templates
This commit is contained in:
@@ -128,6 +128,30 @@ import AlertEpisodeOwnerUserService, {
|
||||
import AlertEpisodeStateTimelineService, {
|
||||
Service as AlertEpisodeStateTimelineServiceType,
|
||||
} from "Common/Server/Services/AlertEpisodeStateTimelineService";
|
||||
|
||||
// IncidentEpisode Services
|
||||
import IncidentEpisodeService, {
|
||||
Service as IncidentEpisodeServiceType,
|
||||
} from "Common/Server/Services/IncidentEpisodeService";
|
||||
import IncidentEpisodeFeedService, {
|
||||
Service as IncidentEpisodeFeedServiceType,
|
||||
} from "Common/Server/Services/IncidentEpisodeFeedService";
|
||||
import IncidentEpisodeInternalNoteService, {
|
||||
Service as IncidentEpisodeInternalNoteServiceType,
|
||||
} from "Common/Server/Services/IncidentEpisodeInternalNoteService";
|
||||
import IncidentEpisodeMemberService, {
|
||||
Service as IncidentEpisodeMemberServiceType,
|
||||
} from "Common/Server/Services/IncidentEpisodeMemberService";
|
||||
import IncidentEpisodeOwnerTeamService, {
|
||||
Service as IncidentEpisodeOwnerTeamServiceType,
|
||||
} from "Common/Server/Services/IncidentEpisodeOwnerTeamService";
|
||||
import IncidentEpisodeOwnerUserService, {
|
||||
Service as IncidentEpisodeOwnerUserServiceType,
|
||||
} from "Common/Server/Services/IncidentEpisodeOwnerUserService";
|
||||
import IncidentEpisodeStateTimelineService, {
|
||||
Service as IncidentEpisodeStateTimelineServiceType,
|
||||
} from "Common/Server/Services/IncidentEpisodeStateTimelineService";
|
||||
|
||||
import AlertGroupingRuleService, {
|
||||
Service as AlertGroupingRuleServiceType,
|
||||
} from "Common/Server/Services/AlertGroupingRuleService";
|
||||
@@ -458,6 +482,15 @@ import AlertEpisodeOwnerUser from "Common/Models/DatabaseModels/AlertEpisodeOwne
|
||||
import AlertEpisodeStateTimeline from "Common/Models/DatabaseModels/AlertEpisodeStateTimeline";
|
||||
import AlertGroupingRule from "Common/Models/DatabaseModels/AlertGroupingRule";
|
||||
|
||||
// IncidentEpisode Models
|
||||
import IncidentEpisode from "Common/Models/DatabaseModels/IncidentEpisode";
|
||||
import IncidentEpisodeFeed from "Common/Models/DatabaseModels/IncidentEpisodeFeed";
|
||||
import IncidentEpisodeInternalNote from "Common/Models/DatabaseModels/IncidentEpisodeInternalNote";
|
||||
import IncidentEpisodeMember from "Common/Models/DatabaseModels/IncidentEpisodeMember";
|
||||
import IncidentEpisodeOwnerTeam from "Common/Models/DatabaseModels/IncidentEpisodeOwnerTeam";
|
||||
import IncidentEpisodeOwnerUser from "Common/Models/DatabaseModels/IncidentEpisodeOwnerUser";
|
||||
import IncidentEpisodeStateTimeline from "Common/Models/DatabaseModels/IncidentEpisodeStateTimeline";
|
||||
|
||||
import IncidentCustomField from "Common/Models/DatabaseModels/IncidentCustomField";
|
||||
import IncidentNoteTemplate from "Common/Models/DatabaseModels/IncidentNoteTemplate";
|
||||
import IncidentPostmortemTemplate from "Common/Models/DatabaseModels/IncidentPostmortemTemplate";
|
||||
@@ -1001,6 +1034,66 @@ const BaseAPIFeatureSet: FeatureSet = {
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
// IncidentEpisode Routes
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<IncidentEpisode, IncidentEpisodeServiceType>(
|
||||
IncidentEpisode,
|
||||
IncidentEpisodeService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<IncidentEpisodeFeed, IncidentEpisodeFeedServiceType>(
|
||||
IncidentEpisodeFeed,
|
||||
IncidentEpisodeFeedService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<
|
||||
IncidentEpisodeInternalNote,
|
||||
IncidentEpisodeInternalNoteServiceType
|
||||
>(IncidentEpisodeInternalNote, IncidentEpisodeInternalNoteService).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<IncidentEpisodeMember, IncidentEpisodeMemberServiceType>(
|
||||
IncidentEpisodeMember,
|
||||
IncidentEpisodeMemberService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<IncidentEpisodeOwnerTeam, IncidentEpisodeOwnerTeamServiceType>(
|
||||
IncidentEpisodeOwnerTeam,
|
||||
IncidentEpisodeOwnerTeamService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<IncidentEpisodeOwnerUser, IncidentEpisodeOwnerUserServiceType>(
|
||||
IncidentEpisodeOwnerUser,
|
||||
IncidentEpisodeOwnerUserService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<
|
||||
IncidentEpisodeStateTimeline,
|
||||
IncidentEpisodeStateTimelineServiceType
|
||||
>(
|
||||
IncidentEpisodeStateTimeline,
|
||||
IncidentEpisodeStateTimelineService,
|
||||
).getRouter(),
|
||||
);
|
||||
|
||||
app.use(
|
||||
`/${APP_NAME.toLocaleLowerCase()}`,
|
||||
new BaseAPI<AlertGroupingRule, AlertGroupingRuleServiceType>(
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
{{> Start this}}
|
||||
|
||||
|
||||
{{> Logo this}}
|
||||
{{> EmailTitle title=(concat "Incident Episode " episodeNumber ": " incidentEpisodeTitle) }}
|
||||
|
||||
{{> InfoBlock info=(concat "A new incident episode has been created in the project - " projectName)}}
|
||||
|
||||
{{> InfoBlock info="Here are the details: "}}
|
||||
|
||||
{{> DetailBoxStart this }}
|
||||
{{> DetailBoxField title="Incident Episode Title:" text=incidentEpisodeTitle }}
|
||||
{{> DetailBoxField title="Current State: " text=currentState }}
|
||||
{{> DetailBoxField title="Resources Affected: " text=resourcesAffected }}
|
||||
{{> DetailBoxField title="Severity: " text=incidentEpisodeSeverity }}
|
||||
{{> DetailBoxField title="Root Cause: " text=rootCause }}
|
||||
{{> DetailBoxField title="Description: " text=incidentEpisodeDescription }}
|
||||
{{> DetailBoxEnd this }}
|
||||
|
||||
{{#if incidentsList}}
|
||||
{{> TitleBlock title=(concat "Incidents in this Episode (" incidentsCount ")") }}
|
||||
<!-- Incidents List Container -->
|
||||
<table class="st-Copy st-Width st-Width--mobile" border="0" cellpadding="0" cellspacing="0"
|
||||
width="600" style="min-width: 600px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="st-Spacer st-Spacer--gutter"
|
||||
style="border: 0; margin:0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;"
|
||||
width="64">
|
||||
<div class="st-Spacer st-Spacer--filler"></div>
|
||||
</td>
|
||||
<td style="border: 0; margin: 0; padding: 0;">
|
||||
{{{incidentsList}}}
|
||||
</td>
|
||||
<td class="st-Spacer st-Spacer--gutter"
|
||||
style="border: 0; margin:0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;"
|
||||
width="64">
|
||||
<div class="st-Spacer st-Spacer--filler"></div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="st-Spacer st-Spacer--stacked" colspan="3" height="16"
|
||||
style="border: 0; margin: 0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;">
|
||||
<div class="st-Spacer st-Spacer--filler"></div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<!-- /Incidents List Container -->
|
||||
{{/if}}
|
||||
|
||||
{{> InfoBlock info="ACTION REQUIRED: Please acknowledge this incident episode by clicking on the button below - "}}
|
||||
|
||||
{{> ButtonBlock buttonUrl=acknowledgeIncidentEpisodeLink buttonText="Acknowledge Incident Episode"}}
|
||||
|
||||
{{> InfoBlock info="You can also copy and paste this link:"}}
|
||||
{{> InfoBlock info=acknowledgeIncidentEpisodeLink}}
|
||||
|
||||
{{> InfoBlock info="You will be notified when the status of this incident episode changes."}}
|
||||
|
||||
{{> TitleBlock title="Why am I receiving this email?"}}
|
||||
{{> InfoBlock info="You are receiving this email because you are a member of the team that is responsible for this incident episode or you are currently on-call."}}
|
||||
|
||||
{{> Footer this }}
|
||||
|
||||
{{> End this}}
|
||||
@@ -0,0 +1,30 @@
|
||||
{{> Start this}}
|
||||
|
||||
|
||||
{{> Logo this}}
|
||||
{{> EmailTitle title=(concat "Incident Episode " episodeNumber ": " episodeTitle) }}
|
||||
|
||||
{{> InfoBlock info="You have been added as the owner of this incident episode."}}
|
||||
|
||||
{{> InfoBlock info="Here are the details: "}}
|
||||
|
||||
{{> DetailBoxStart this }}
|
||||
{{> DetailBoxField title="Episode Title:" text=episodeTitle }}
|
||||
{{> DetailBoxField title="Current State: " text=currentState }}
|
||||
{{> DetailBoxField title="Severity: " text=episodeSeverity }}
|
||||
{{> DetailBoxField title="Description: " text=episodeDescription }}
|
||||
{{> DetailBoxEnd this }}
|
||||
|
||||
|
||||
{{> InfoBlock info="You can view this incident episode by clicking on the button below - "}}
|
||||
|
||||
{{> ButtonBlock buttonUrl=episodeViewLink buttonText="View on Dashboard"}}
|
||||
|
||||
{{> InfoBlock info="You can also copy and paste this link:"}}
|
||||
{{> InfoBlock info=episodeViewLink}}
|
||||
|
||||
{{> InfoBlock info="You will be notified when the status of this incident episode changes."}}
|
||||
|
||||
{{> Footer this }}
|
||||
|
||||
{{> End this}}
|
||||
@@ -0,0 +1,37 @@
|
||||
{{> Start this}}
|
||||
|
||||
|
||||
{{> Logo this}}
|
||||
{{> EmailTitle title=(concat "Incident Episode " episodeNumber ": " episodeTitle) }}
|
||||
|
||||
{{> InfoBlock info="A new note has been posted on this incident episode."}}
|
||||
|
||||
{{> InfoBlock info="Here are the details: "}}
|
||||
|
||||
{{> DetailBoxStart this }}
|
||||
{{> DetailBoxField title="Episode Title:" text=episodeTitle }}
|
||||
{{> DetailBoxField title="Current State: " text=currentState }}
|
||||
{{> DetailBoxField title="Severity: " text=episodeSeverity }}
|
||||
{{#if isPrivateNote}}
|
||||
{{> DetailBoxField title="Private Note: " text=note }}
|
||||
{{else}}
|
||||
{{> DetailBoxField title="Public Note: " text=note }}
|
||||
{{/if}}
|
||||
{{> DetailBoxEnd this }}
|
||||
|
||||
|
||||
{{> InfoBlock info="You can view this incident episode by clicking on the button below - "}}
|
||||
|
||||
{{> ButtonBlock buttonUrl=episodeViewLink buttonText="View on Dashboard"}}
|
||||
|
||||
{{> InfoBlock info="You can also copy and paste this link:"}}
|
||||
{{> InfoBlock info=episodeViewLink}}
|
||||
|
||||
{{> InfoBlock info="You will be notified when the status of this incident episode changes."}}
|
||||
|
||||
{{> OwnerInfo this }}
|
||||
{{> UnsubscribeOwnerEmail this }}
|
||||
|
||||
{{> Footer this }}
|
||||
|
||||
{{> End this}}
|
||||
@@ -0,0 +1,35 @@
|
||||
{{> Start this}}
|
||||
|
||||
|
||||
{{> Logo this}}
|
||||
{{> EmailTitle title=(concat "Incident Episode " episodeNumber ": " episodeTitle) }}
|
||||
|
||||
{{> InfoBlock info=(concat "A new incident episode has been created in the project - " projectName)}}
|
||||
|
||||
{{> InfoBlock info="Here are the details: "}}
|
||||
|
||||
{{> DetailBoxStart this }}
|
||||
{{> DetailBoxField title="Episode Title:" text=episodeTitle }}
|
||||
{{> DetailBoxField title="Current State: " text=currentState }}
|
||||
{{> DetailBoxField title="Episode Created By: " text=declaredBy }}
|
||||
{{> DetailBoxField title="Episode Created At: " text=declaredAt }}
|
||||
{{> DetailBoxField title="Severity: " text=episodeSeverity }}
|
||||
{{> DetailBoxField title="Description: " text=episodeDescription }}
|
||||
{{> DetailBoxEnd this }}
|
||||
|
||||
|
||||
{{> InfoBlock info="You can view this incident episode by clicking on the button below - "}}
|
||||
|
||||
{{> ButtonBlock buttonUrl=episodeViewLink buttonText="View on Dashboard"}}
|
||||
|
||||
{{> InfoBlock info="You can also copy and paste this link:"}}
|
||||
{{> InfoBlock info=episodeViewLink}}
|
||||
|
||||
{{> InfoBlock info="You will be notified when the status of this incident episode changes."}}
|
||||
|
||||
{{> OwnerInfo this }}
|
||||
{{> UnsubscribeOwnerEmail this }}
|
||||
|
||||
{{> Footer this }}
|
||||
|
||||
{{> End this}}
|
||||
@@ -0,0 +1,37 @@
|
||||
{{> Start this}}
|
||||
|
||||
|
||||
{{> Logo this}}
|
||||
{{> EmailTitle title=(concat "Incident Episode " episodeNumber ": " episodeTitle) }}
|
||||
|
||||
{{> InfoBlock info="Incident episode state has changed"}}
|
||||
|
||||
{{> InfoBlock info="Here are the details: "}}
|
||||
|
||||
{{> DetailBoxStart this }}
|
||||
{{> StateTransition this}}
|
||||
{{#ifNotCond previousStateDurationText ""}}
|
||||
{{> DetailBoxField title="Duration in Previous State:" text=previousStateDurationText }}
|
||||
{{/ifNotCond}}
|
||||
{{> DetailBoxField title="Episode Title:" text=episodeTitle }}
|
||||
{{> DetailBoxField title="State changed at:" text=stateChangedAt }}
|
||||
{{> DetailBoxField title="Severity:" text=episodeSeverity }}
|
||||
{{> DetailBoxField title="Description:" text=episodeDescription }}
|
||||
{{> DetailBoxEnd this }}
|
||||
|
||||
|
||||
{{> InfoBlock info="You can view this incident episode by clicking on the button below - "}}
|
||||
|
||||
{{> ButtonBlock buttonUrl=episodeViewLink buttonText="View on Dashboard"}}
|
||||
|
||||
{{> InfoBlock info="You can also copy and paste this link:"}}
|
||||
{{> InfoBlock info=episodeViewLink}}
|
||||
|
||||
{{> InfoBlock info="You will be notified when the status of this incident episode changes."}}
|
||||
|
||||
{{> OwnerInfo this }}
|
||||
{{> UnsubscribeOwnerEmail this }}
|
||||
|
||||
{{> Footer this }}
|
||||
|
||||
{{> End this}}
|
||||
@@ -32,6 +32,7 @@ import SlackAuthAction, {
|
||||
import SlackIncidentActions from "../Utils/Workspace/Slack/Actions/Incident";
|
||||
import SlackAlertActions from "../Utils/Workspace/Slack/Actions/Alert";
|
||||
import SlackAlertEpisodeActions from "../Utils/Workspace/Slack/Actions/AlertEpisode";
|
||||
import SlackIncidentEpisodeActions from "../Utils/Workspace/Slack/Actions/IncidentEpisode";
|
||||
import SlackScheduledMaintenanceActions from "../Utils/Workspace/Slack/Actions/ScheduledMaintenance";
|
||||
import LIMIT_MAX from "../../Types/Database/LimitMax";
|
||||
import SlackMonitorActions from "../Utils/Workspace/Slack/Actions/Monitor";
|
||||
@@ -647,6 +648,19 @@ export default class SlackAPI {
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
SlackIncidentEpisodeActions.isIncidentEpisodeAction({
|
||||
actionType: action.actionType,
|
||||
})
|
||||
) {
|
||||
return SlackIncidentEpisodeActions.handleIncidentEpisodeAction({
|
||||
slackRequest: authResult,
|
||||
action: action,
|
||||
req: req,
|
||||
res: res,
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
SlackMonitorActions.isMonitorAction({
|
||||
actionType: action.actionType,
|
||||
@@ -837,6 +851,13 @@ export default class SlackAPI {
|
||||
logger.error(err);
|
||||
}
|
||||
|
||||
try {
|
||||
await SlackIncidentEpisodeActions.handleEmojiReaction(reactionData);
|
||||
} catch (err) {
|
||||
logger.error("Error handling incident episode emoji reaction:");
|
||||
logger.error(err);
|
||||
}
|
||||
|
||||
try {
|
||||
await SlackScheduledMaintenanceActions.handleEmojiReaction(
|
||||
reactionData,
|
||||
|
||||
@@ -264,6 +264,7 @@ ${onCallPolicy.description || "No description provided."}
|
||||
triggeredByIncidentId?: ObjectID | undefined;
|
||||
triggeredByAlertId?: ObjectID | undefined;
|
||||
triggeredByAlertEpisodeId?: ObjectID | undefined;
|
||||
triggeredByIncidentEpisodeId?: ObjectID | undefined;
|
||||
userNotificationEventType: UserNotificationEventType;
|
||||
},
|
||||
): Promise<void> {
|
||||
@@ -299,6 +300,16 @@ ${onCallPolicy.description || "No description provided."}
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
UserNotificationEventType.IncidentEpisodeCreated ===
|
||||
options.userNotificationEventType &&
|
||||
!options.triggeredByIncidentEpisodeId
|
||||
) {
|
||||
throw new BadDataException(
|
||||
"triggeredByIncidentEpisodeId is required when userNotificationEventType is IncidentEpisodeCreated",
|
||||
);
|
||||
}
|
||||
|
||||
const policy: OnCallDutyPolicy | null = await this.findOneById({
|
||||
id: policyId,
|
||||
select: {
|
||||
@@ -338,6 +349,10 @@ ${onCallPolicy.description || "No description provided."}
|
||||
log.triggeredByAlertEpisodeId = options.triggeredByAlertEpisodeId;
|
||||
}
|
||||
|
||||
if (options.triggeredByIncidentEpisodeId) {
|
||||
log.triggeredByIncidentEpisodeId = options.triggeredByIncidentEpisodeId;
|
||||
}
|
||||
|
||||
await OnCallDutyPolicyExecutionLogService.create({
|
||||
data: log,
|
||||
props: {
|
||||
|
||||
@@ -86,6 +86,7 @@ import {
|
||||
} from "./Actions/ActionTypes";
|
||||
import MicrosoftTeamsAlertActions from "./Actions/Alert";
|
||||
import MicrosoftTeamsAlertEpisodeActions from "./Actions/AlertEpisode";
|
||||
import MicrosoftTeamsIncidentEpisodeActions from "./Actions/IncidentEpisode";
|
||||
import MicrosoftTeamsMonitorActions from "./Actions/Monitor";
|
||||
import MicrosoftTeamsScheduledMaintenanceActions from "./Actions/ScheduledMaintenance";
|
||||
import MicrosoftTeamsOnCallDutyActions from "./Actions/OnCallDutyPolicy";
|
||||
@@ -2535,6 +2536,21 @@ All monitoring checks are passing normally.`;
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle incident episode actions
|
||||
if (
|
||||
MicrosoftTeamsIncidentEpisodeActions.isIncidentEpisodeAction({ actionType })
|
||||
) {
|
||||
await MicrosoftTeamsIncidentEpisodeActions.handleBotIncidentEpisodeAction({
|
||||
actionType,
|
||||
actionValue,
|
||||
value,
|
||||
projectId,
|
||||
oneUptimeUserId,
|
||||
turnContext: data.turnContext,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle monitor actions
|
||||
if (MicrosoftTeamsMonitorActions.isMonitorAction({ actionType })) {
|
||||
await MicrosoftTeamsMonitorActions.handleBotMonitorAction({
|
||||
|
||||
Reference in New Issue
Block a user