@oneuptime/common 7.0.3717 → 7.0.3786
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Models/DatabaseModels/Alert.ts +3 -2
- package/Models/DatabaseModels/Incident.ts +3 -2
- package/Models/DatabaseModels/Index.ts +3 -0
- package/Models/DatabaseModels/ProjectUser.ts +335 -0
- package/Models/DatabaseModels/ScheduledMaintenance.ts +3 -2
- package/Server/API/SlackAPI.ts +65 -97
- package/Server/API/UserOnCallLogTimelineAPI.ts +2 -9
- package/Server/Infrastructure/Postgres/SchemaMigrations/1740597525803-MigrationName.ts +17 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1740598793630-MigrationName.ts +17 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1741031019972-MigrationName.ts +17 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1741209339971-MigrationName.ts +101 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
- package/Server/Middleware/SlackAuthorization.ts +11 -2
- package/Server/Services/AlertFeedService.ts +2 -2
- package/Server/Services/AlertInternalNoteService.ts +2 -2
- package/Server/Services/AlertOwnerTeamService.ts +4 -4
- package/Server/Services/AlertOwnerUserService.ts +3 -3
- package/Server/Services/AlertService.ts +62 -20
- package/Server/Services/AlertStateTimelineService.ts +8 -18
- package/Server/Services/IncidentFeedService.ts +101 -2
- package/Server/Services/IncidentInternalNoteService.ts +47 -4
- package/Server/Services/IncidentOwnerTeamService.ts +57 -4
- package/Server/Services/IncidentOwnerUserService.ts +59 -15
- package/Server/Services/IncidentPublicNoteService.ts +41 -4
- package/Server/Services/IncidentService.ts +279 -193
- package/Server/Services/IncidentStateService.ts +25 -0
- package/Server/Services/IncidentStateTimelineService.ts +37 -19
- package/Server/Services/MonitorStatusTimelineService.ts +7 -17
- package/Server/Services/OnCallDutyPolicyEscalationRuleService.ts +2 -0
- package/Server/Services/OnCallDutyPolicyExecutionLogService.ts +74 -7
- package/Server/Services/OnCallDutyPolicyExecutionLogTimelineService.ts +63 -4
- package/Server/Services/OnCallDutyPolicyService.ts +13 -0
- package/Server/Services/ProjectUserService.ts +130 -0
- package/Server/Services/ScheduledMaintenanceFeedService.ts +2 -2
- package/Server/Services/ScheduledMaintenanceInternalNoteService.ts +12 -10
- package/Server/Services/ScheduledMaintenanceOwnerTeamService.ts +22 -18
- package/Server/Services/ScheduledMaintenanceOwnerUserService.ts +28 -30
- package/Server/Services/ScheduledMaintenancePublicNoteService.ts +12 -10
- package/Server/Services/ScheduledMaintenanceService.ts +16 -10
- package/Server/Services/ScheduledMaintenanceStateTimelineService.ts +1 -1
- package/Server/Services/StatusPageSubscriberService.ts +3 -0
- package/Server/Services/TeamMemberService.ts +20 -0
- package/Server/Services/UserNotificationRuleService.ts +74 -0
- package/Server/Services/UserOnCallLogService.ts +1 -1
- package/Server/Services/UserService.ts +35 -0
- package/Server/Services/WorkspaceNotificationRuleService.ts +508 -149
- package/Server/Services/WorkspaceUserAuthTokenService.ts +23 -0
- package/Server/Utils/Express.ts +1 -1
- package/Server/Utils/StartServer.ts +6 -1
- package/Server/Utils/Workspace/MicrosoftTeams/Messages/Incident.ts +195 -0
- package/Server/Utils/Workspace/Slack/Actions/ActionTypes.ts +20 -0
- package/Server/Utils/Workspace/Slack/Actions/Auth.ts +266 -0
- package/Server/Utils/Workspace/Slack/Actions/Incident.ts +1117 -0
- package/Server/Utils/Workspace/Slack/Messages/Incident.ts +116 -0
- package/Server/Utils/Workspace/Slack/Slack.ts +555 -18
- package/Server/Utils/Workspace/Slack/app-manifest.json +18 -10
- package/Server/Utils/Workspace/Workspace.ts +194 -1
- package/Server/Utils/Workspace/WorkspaceBase.ts +145 -19
- package/Server/Utils/Workspace/WorkspaceMessages/Incident.ts +68 -0
- package/Types/Icon/IconProp.ts +1 -0
- package/Types/Workspace/NotificationRules/NotificationRuleCondition.ts +2 -1
- package/Types/Workspace/NotificationRules/NotificationRuleUtil.ts +251 -121
- package/Types/Workspace/NotificationRules/NotificationRuleWorkspaceChannel.ts +6 -0
- package/Types/Workspace/WorkspaceMessagePayload.ts +71 -2
- package/UI/Components/ComingSoon/ComingSoon.tsx +13 -3
- package/UI/Components/Forms/Fields/FormField.tsx +2 -2
- package/UI/Components/Icon/Icon.tsx +39 -2
- package/UI/Components/ModelTable/BaseModelTable.tsx +16 -0
- package/UI/Components/Radio/Radio.tsx +11 -2
- package/UI/Components/Table/TableCard.tsx +2 -2
- package/build/dist/Models/DatabaseModels/Alert.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +2 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ProjectUser.js +340 -0
- package/build/dist/Models/DatabaseModels/ProjectUser.js.map +1 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js.map +1 -1
- package/build/dist/Server/API/SlackAPI.js +39 -79
- package/build/dist/Server/API/SlackAPI.js.map +1 -1
- package/build/dist/Server/API/UserOnCallLogTimelineAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1740597525803-MigrationName.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1740597525803-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1740598793630-MigrationName.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1740598793630-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1741031019972-MigrationName.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1741031019972-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1741209339971-MigrationName.js +42 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1741209339971-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/SlackAuthorization.js +8 -2
- package/build/dist/Server/Middleware/SlackAuthorization.js.map +1 -1
- package/build/dist/Server/Services/AlertFeedService.js +2 -2
- package/build/dist/Server/Services/AlertFeedService.js.map +1 -1
- package/build/dist/Server/Services/AlertInternalNoteService.js +2 -2
- package/build/dist/Server/Services/AlertInternalNoteService.js.map +1 -1
- package/build/dist/Server/Services/AlertOwnerTeamService.js +4 -4
- package/build/dist/Server/Services/AlertOwnerTeamService.js.map +1 -1
- package/build/dist/Server/Services/AlertOwnerUserService.js +3 -3
- package/build/dist/Server/Services/AlertOwnerUserService.js.map +1 -1
- package/build/dist/Server/Services/AlertService.js +44 -19
- package/build/dist/Server/Services/AlertService.js.map +1 -1
- package/build/dist/Server/Services/AlertStateTimelineService.js +6 -16
- package/build/dist/Server/Services/AlertStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/IncidentFeedService.js +62 -2
- package/build/dist/Server/Services/IncidentFeedService.js.map +1 -1
- package/build/dist/Server/Services/IncidentInternalNoteService.js +35 -4
- package/build/dist/Server/Services/IncidentInternalNoteService.js.map +1 -1
- package/build/dist/Server/Services/IncidentOwnerTeamService.js +42 -4
- package/build/dist/Server/Services/IncidentOwnerTeamService.js.map +1 -1
- package/build/dist/Server/Services/IncidentOwnerUserService.js +43 -15
- package/build/dist/Server/Services/IncidentOwnerUserService.js.map +1 -1
- package/build/dist/Server/Services/IncidentPublicNoteService.js +32 -4
- package/build/dist/Server/Services/IncidentPublicNoteService.js.map +1 -1
- package/build/dist/Server/Services/IncidentService.js +221 -170
- package/build/dist/Server/Services/IncidentService.js.map +1 -1
- package/build/dist/Server/Services/IncidentStateService.js +14 -0
- package/build/dist/Server/Services/IncidentStateService.js.map +1 -1
- package/build/dist/Server/Services/IncidentStateTimelineService.js +31 -17
- package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/MonitorStatusTimelineService.js +5 -15
- package/build/dist/Server/Services/MonitorStatusTimelineService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleService.js +1 -0
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogService.js +62 -7
- package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogTimelineService.js +51 -5
- package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogTimelineService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyService.js +6 -0
- package/build/dist/Server/Services/OnCallDutyPolicyService.js.map +1 -1
- package/build/dist/Server/Services/ProjectUserService.js +106 -0
- package/build/dist/Server/Services/ProjectUserService.js.map +1 -0
- package/build/dist/Server/Services/ScheduledMaintenanceFeedService.js +2 -2
- package/build/dist/Server/Services/ScheduledMaintenanceFeedService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceInternalNoteService.js +2 -2
- package/build/dist/Server/Services/ScheduledMaintenanceInternalNoteService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceOwnerTeamService.js +4 -4
- package/build/dist/Server/Services/ScheduledMaintenanceOwnerTeamService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceOwnerUserService.js +8 -16
- package/build/dist/Server/Services/ScheduledMaintenanceOwnerUserService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js +2 -2
- package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceService.js +5 -2
- package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageSubscriberService.js +3 -0
- package/build/dist/Server/Services/StatusPageSubscriberService.js.map +1 -1
- package/build/dist/Server/Services/TeamMemberService.js +17 -0
- package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationRuleService.js +52 -0
- package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
- package/build/dist/Server/Services/UserOnCallLogService.js +1 -1
- package/build/dist/Server/Services/UserOnCallLogService.js.map +1 -1
- package/build/dist/Server/Services/UserService.js +23 -0
- package/build/dist/Server/Services/UserService.js.map +1 -1
- package/build/dist/Server/Services/WorkspaceNotificationRuleService.js +306 -84
- package/build/dist/Server/Services/WorkspaceNotificationRuleService.js.map +1 -1
- package/build/dist/Server/Services/WorkspaceUserAuthTokenService.js +18 -0
- package/build/dist/Server/Services/WorkspaceUserAuthTokenService.js.map +1 -1
- package/build/dist/Server/Utils/StartServer.js +4 -0
- package/build/dist/Server/Utils/StartServer.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Messages/Incident.js +148 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Messages/Incident.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/Slack/Actions/ActionTypes.js +19 -0
- package/build/dist/Server/Utils/Workspace/Slack/Actions/ActionTypes.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/Slack/Actions/Auth.js +167 -0
- package/build/dist/Server/Utils/Workspace/Slack/Actions/Auth.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/Slack/Actions/Incident.js +727 -0
- package/build/dist/Server/Utils/Workspace/Slack/Actions/Incident.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/Slack/Messages/Incident.js +82 -0
- package/build/dist/Server/Utils/Workspace/Slack/Messages/Incident.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/Slack/Slack.js +397 -14
- package/build/dist/Server/Utils/Workspace/Slack/Slack.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/Slack/app-manifest.json +18 -10
- package/build/dist/Server/Utils/Workspace/Workspace.js +126 -0
- package/build/dist/Server/Utils/Workspace/Workspace.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/WorkspaceBase.js +69 -11
- package/build/dist/Server/Utils/Workspace/WorkspaceBase.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/WorkspaceMessages/Incident.js +47 -0
- package/build/dist/Server/Utils/Workspace/WorkspaceMessages/Incident.js.map +1 -0
- package/build/dist/Types/Icon/IconProp.js +1 -0
- package/build/dist/Types/Icon/IconProp.js.map +1 -1
- package/build/dist/Types/Workspace/NotificationRules/NotificationRuleCondition.js +2 -1
- package/build/dist/Types/Workspace/NotificationRules/NotificationRuleCondition.js.map +1 -1
- package/build/dist/Types/Workspace/NotificationRules/NotificationRuleUtil.js +214 -120
- package/build/dist/Types/Workspace/NotificationRules/NotificationRuleUtil.js.map +1 -1
- package/build/dist/Types/Workspace/NotificationRules/NotificationRuleWorkspaceChannel.js +2 -0
- package/build/dist/Types/Workspace/NotificationRules/NotificationRuleWorkspaceChannel.js.map +1 -0
- package/build/dist/UI/Components/ComingSoon/ComingSoon.js +3 -2
- package/build/dist/UI/Components/ComingSoon/ComingSoon.js.map +1 -1
- package/build/dist/UI/Components/Forms/Fields/FormField.js +2 -2
- package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
- package/build/dist/UI/Components/Icon/Icon.js +19 -2
- package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +11 -0
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/UI/Components/Radio/Radio.js +5 -2
- package/build/dist/UI/Components/Radio/Radio.js.map +1 -1
- package/build/dist/UI/Components/Table/TableCard.js +2 -2
- package/build/dist/UI/Components/Table/TableCard.js.map +1 -1
- package/package.json +3 -2
- package/Server/Utils/Workspace/Slack/app-manifest.example.json +0 -198
|
@@ -0,0 +1,1117 @@
|
|
|
1
|
+
import BadDataException from "../../../../../Types/Exception/BadDataException";
|
|
2
|
+
import ObjectID from "../../../../../Types/ObjectID";
|
|
3
|
+
import IncidentService from "../../../../Services/IncidentService";
|
|
4
|
+
import { ExpressRequest, ExpressResponse } from "../../../Express";
|
|
5
|
+
import SlackUtil from "../Slack";
|
|
6
|
+
import SlackActionType from "./ActionTypes";
|
|
7
|
+
import { SlackAction, SlackRequest } from "./Auth";
|
|
8
|
+
import Response from "../../../Response";
|
|
9
|
+
import {
|
|
10
|
+
WorkspaceDropdownBlock,
|
|
11
|
+
WorkspaceMessageBlock,
|
|
12
|
+
WorkspaceModalBlock,
|
|
13
|
+
WorkspacePayloadMarkdown,
|
|
14
|
+
WorkspaceTextAreaBlock,
|
|
15
|
+
WorkspaceTextBoxBlock,
|
|
16
|
+
} from "../../../../../Types/Workspace/WorkspaceMessagePayload";
|
|
17
|
+
import IncidentPublicNoteService from "../../../../Services/IncidentPublicNoteService";
|
|
18
|
+
import IncidentInternalNoteService from "../../../../Services/IncidentInternalNoteService";
|
|
19
|
+
import OnCallDutyPolicy from "../../../../../Models/DatabaseModels/OnCallDutyPolicy";
|
|
20
|
+
import OnCallDutyPolicyService from "../../../../Services/OnCallDutyPolicyService";
|
|
21
|
+
import { LIMIT_PER_PROJECT } from "../../../../../Types/Database/LimitMax";
|
|
22
|
+
import { DropdownOption } from "../../../../../UI/Components/Dropdown/Dropdown";
|
|
23
|
+
import UserNotificationEventType from "../../../../../Types/UserNotification/UserNotificationEventType";
|
|
24
|
+
import IncidentState from "../../../../../Models/DatabaseModels/IncidentState";
|
|
25
|
+
import IncidentStateService from "../../../../Services/IncidentStateService";
|
|
26
|
+
import logger from "../../../Logger";
|
|
27
|
+
import IncidentSeverity from "../../../../../Models/DatabaseModels/IncidentSeverity";
|
|
28
|
+
import IncidentSeverityService from "../../../../Services/IncidentSeverityService";
|
|
29
|
+
import SortOrder from "../../../../../Types/BaseDatabase/SortOrder";
|
|
30
|
+
import Monitor from "../../../../../Models/DatabaseModels/Monitor";
|
|
31
|
+
import MonitorService from "../../../../Services/MonitorService";
|
|
32
|
+
import MonitorStatus from "../../../../../Models/DatabaseModels/MonitorStatus";
|
|
33
|
+
import MonitorStatusService from "../../../../Services/MonitorStatusService";
|
|
34
|
+
import Label from "../../../../../Models/DatabaseModels/Label";
|
|
35
|
+
import LabelService from "../../../../Services/LabelService";
|
|
36
|
+
import Incident from "../../../../../Models/DatabaseModels/Incident";
|
|
37
|
+
|
|
38
|
+
export default class SlackIncidentActions {
|
|
39
|
+
public static isIncidentAction(data: {
|
|
40
|
+
actionType: SlackActionType;
|
|
41
|
+
}): boolean {
|
|
42
|
+
const { actionType } = data;
|
|
43
|
+
|
|
44
|
+
switch (actionType) {
|
|
45
|
+
case SlackActionType.AcknowledgeIncident:
|
|
46
|
+
case SlackActionType.ResolveIncident:
|
|
47
|
+
case SlackActionType.ViewAddIncidentNote:
|
|
48
|
+
case SlackActionType.SubmitIncidentNote:
|
|
49
|
+
case SlackActionType.ViewChangeIncidentState:
|
|
50
|
+
case SlackActionType.SubmitChangeIncidentState:
|
|
51
|
+
case SlackActionType.ViewExecuteIncidentOnCallPolicy:
|
|
52
|
+
case SlackActionType.SubmitExecuteIncidentOnCallPolicy:
|
|
53
|
+
case SlackActionType.ViewIncident:
|
|
54
|
+
case SlackActionType.NewIncident:
|
|
55
|
+
case SlackActionType.SubmitNewIncident:
|
|
56
|
+
return true;
|
|
57
|
+
default:
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
public static async submitNewIncident(data: {
|
|
63
|
+
slackRequest: SlackRequest;
|
|
64
|
+
action: SlackAction;
|
|
65
|
+
req: ExpressRequest;
|
|
66
|
+
res: ExpressResponse;
|
|
67
|
+
}): Promise<void> {
|
|
68
|
+
const { slackRequest, req, res } = data;
|
|
69
|
+
const { botUserId, userId, projectAuthToken } = slackRequest;
|
|
70
|
+
|
|
71
|
+
if (!userId) {
|
|
72
|
+
return Response.sendErrorResponse(
|
|
73
|
+
req,
|
|
74
|
+
res,
|
|
75
|
+
new BadDataException("Invalid User ID"),
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (!projectAuthToken) {
|
|
80
|
+
return Response.sendErrorResponse(
|
|
81
|
+
req,
|
|
82
|
+
res,
|
|
83
|
+
new BadDataException("Invalid Project Auth Token"),
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (!botUserId) {
|
|
88
|
+
return Response.sendErrorResponse(
|
|
89
|
+
req,
|
|
90
|
+
res,
|
|
91
|
+
new BadDataException("Invalid Bot User ID"),
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (data.action.actionType === SlackActionType.SubmitNewIncident) {
|
|
96
|
+
// We send this early let slack know we're ok. We'll do the rest in the background.
|
|
97
|
+
|
|
98
|
+
// if view values is empty, then return error.
|
|
99
|
+
if (!data.slackRequest.viewValues) {
|
|
100
|
+
return Response.sendErrorResponse(
|
|
101
|
+
req,
|
|
102
|
+
res,
|
|
103
|
+
new BadDataException("Invalid View Values"),
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (!data.slackRequest.viewValues["incidentTitle"]) {
|
|
108
|
+
return Response.sendErrorResponse(
|
|
109
|
+
req,
|
|
110
|
+
res,
|
|
111
|
+
new BadDataException("Invalid Incident Title"),
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (!data.slackRequest.viewValues["incidentDescription"]) {
|
|
116
|
+
return Response.sendErrorResponse(
|
|
117
|
+
req,
|
|
118
|
+
res,
|
|
119
|
+
new BadDataException("Invalid Incident Description"),
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (!data.slackRequest.viewValues["incidentSeverity"]) {
|
|
124
|
+
return Response.sendErrorResponse(
|
|
125
|
+
req,
|
|
126
|
+
res,
|
|
127
|
+
new BadDataException("Invalid Incident Severity"),
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
Response.sendJsonObjectResponse(req, res, {
|
|
132
|
+
response_action: "clear",
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
const title: string =
|
|
136
|
+
data.slackRequest.viewValues["incidentTitle"].toString();
|
|
137
|
+
const description: string =
|
|
138
|
+
data.slackRequest.viewValues["incidentDescription"].toString();
|
|
139
|
+
const severity: string =
|
|
140
|
+
data.slackRequest.viewValues["incidentSeverity"].toString();
|
|
141
|
+
const monitors: Array<string> = (data.slackRequest.viewValues[
|
|
142
|
+
"incidentMonitors"
|
|
143
|
+
] || []) as Array<string>;
|
|
144
|
+
const monitorStatus: string | undefined =
|
|
145
|
+
data.slackRequest.viewValues["monitorStatus"]?.toString();
|
|
146
|
+
|
|
147
|
+
const labels: Array<string> =
|
|
148
|
+
(data.slackRequest.viewValues["labels"] as Array<string>) || [];
|
|
149
|
+
|
|
150
|
+
const incidentMonitors: Array<ObjectID> = monitors.map(
|
|
151
|
+
(monitor: string) => {
|
|
152
|
+
return new ObjectID(monitor);
|
|
153
|
+
},
|
|
154
|
+
);
|
|
155
|
+
const incidentLabels: Array<ObjectID> = labels.map((label: string) => {
|
|
156
|
+
return new ObjectID(label);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
const incidentSeverityId: ObjectID = new ObjectID(severity);
|
|
160
|
+
const monitorStatusId: ObjectID | undefined = monitorStatus
|
|
161
|
+
? new ObjectID(monitorStatus)
|
|
162
|
+
: undefined;
|
|
163
|
+
|
|
164
|
+
const incident: Incident = new Incident();
|
|
165
|
+
incident.title = title;
|
|
166
|
+
incident.description = description;
|
|
167
|
+
incident.projectId = slackRequest.projectId!;
|
|
168
|
+
incident.createdByUserId = userId;
|
|
169
|
+
incident.incidentSeverityId = incidentSeverityId;
|
|
170
|
+
|
|
171
|
+
if (monitors.length > 0) {
|
|
172
|
+
incident.monitors = incidentMonitors.map((monitorId: ObjectID) => {
|
|
173
|
+
const monitor: Monitor = new Monitor();
|
|
174
|
+
monitor.id = monitorId;
|
|
175
|
+
return monitor;
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (monitorStatusId) {
|
|
180
|
+
incident.changeMonitorStatusToId = monitorStatusId;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (incidentLabels.length > 0) {
|
|
184
|
+
incident.labels = incidentLabels.map((labelId: ObjectID) => {
|
|
185
|
+
const label: Label = new Label();
|
|
186
|
+
label.id = labelId;
|
|
187
|
+
return label;
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
await IncidentService.create({
|
|
192
|
+
data: incident,
|
|
193
|
+
props: {
|
|
194
|
+
isRoot: true,
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
public static async viewNewIncidentModal(data: {
|
|
201
|
+
slackRequest: SlackRequest;
|
|
202
|
+
action: SlackAction;
|
|
203
|
+
req: ExpressRequest;
|
|
204
|
+
res: ExpressResponse;
|
|
205
|
+
}): Promise<void> {
|
|
206
|
+
const blocks: Array<WorkspaceMessageBlock> = [];
|
|
207
|
+
|
|
208
|
+
// send response to clear the action.
|
|
209
|
+
Response.sendTextResponse(data.req, data.res, "");
|
|
210
|
+
|
|
211
|
+
// show new incident modal.
|
|
212
|
+
// new incident modal is :
|
|
213
|
+
// Incident Title (this can be prefilled with actionValue)
|
|
214
|
+
// Incident Description
|
|
215
|
+
// Incident Severity (dropdown) (single select)
|
|
216
|
+
// Monitors (dropdown) (miltiselect)
|
|
217
|
+
// Change Monitor Status to (dropdown) (single select)
|
|
218
|
+
// Labels (dropdown) (multiselect)
|
|
219
|
+
|
|
220
|
+
const incidentTitle: WorkspaceTextBoxBlock = {
|
|
221
|
+
_type: "WorkspaceTextBoxBlock",
|
|
222
|
+
label: "Incident Title",
|
|
223
|
+
blockId: "incidentTitle",
|
|
224
|
+
placeholder: "Incident Title",
|
|
225
|
+
initialValue: data.action.actionValue || "",
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
blocks.push(incidentTitle);
|
|
229
|
+
|
|
230
|
+
const incidentDescription: WorkspaceTextAreaBlock = {
|
|
231
|
+
_type: "WorkspaceTextAreaBlock",
|
|
232
|
+
label: "Incident Description",
|
|
233
|
+
blockId: "incidentDescription",
|
|
234
|
+
placeholder: "Incident Description",
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
blocks.push(incidentDescription);
|
|
238
|
+
|
|
239
|
+
const incidentSeveritiesForProject: Array<IncidentSeverity> =
|
|
240
|
+
await IncidentSeverityService.findBy({
|
|
241
|
+
query: {
|
|
242
|
+
projectId: data.slackRequest.projectId!,
|
|
243
|
+
},
|
|
244
|
+
sort: {
|
|
245
|
+
order: SortOrder.Ascending,
|
|
246
|
+
},
|
|
247
|
+
skip: 0,
|
|
248
|
+
limit: LIMIT_PER_PROJECT,
|
|
249
|
+
select: {
|
|
250
|
+
name: true,
|
|
251
|
+
},
|
|
252
|
+
props: {
|
|
253
|
+
isRoot: true,
|
|
254
|
+
},
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
const dropdownOptions: Array<DropdownOption> =
|
|
258
|
+
incidentSeveritiesForProject.map((severity: IncidentSeverity) => {
|
|
259
|
+
return {
|
|
260
|
+
label: severity.name || "",
|
|
261
|
+
value: severity._id?.toString() || "",
|
|
262
|
+
};
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
const incidentSeverity: WorkspaceDropdownBlock = {
|
|
266
|
+
_type: "WorkspaceDropdownBlock",
|
|
267
|
+
label: "Incident Severity",
|
|
268
|
+
blockId: "incidentSeverity",
|
|
269
|
+
placeholder: "Select Incident Severity",
|
|
270
|
+
options: dropdownOptions,
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
if (incidentSeveritiesForProject.length > 0) {
|
|
274
|
+
blocks.push(incidentSeverity);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const monitorsForProject: Array<Monitor> = await MonitorService.findBy({
|
|
278
|
+
query: {
|
|
279
|
+
projectId: data.slackRequest.projectId!,
|
|
280
|
+
},
|
|
281
|
+
select: {
|
|
282
|
+
name: true,
|
|
283
|
+
},
|
|
284
|
+
props: {
|
|
285
|
+
isRoot: true,
|
|
286
|
+
},
|
|
287
|
+
limit: LIMIT_PER_PROJECT,
|
|
288
|
+
skip: 0,
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
const monitorDropdownOptions: Array<DropdownOption> =
|
|
292
|
+
monitorsForProject.map((monitor: Monitor) => {
|
|
293
|
+
return {
|
|
294
|
+
label: monitor.name || "",
|
|
295
|
+
value: monitor._id?.toString() || "",
|
|
296
|
+
};
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
const incidentMonitors: WorkspaceDropdownBlock = {
|
|
300
|
+
_type: "WorkspaceDropdownBlock",
|
|
301
|
+
label: "Monitors",
|
|
302
|
+
blockId: "incidentMonitors",
|
|
303
|
+
placeholder: "Select Monitors",
|
|
304
|
+
options: monitorDropdownOptions,
|
|
305
|
+
multiSelect: true,
|
|
306
|
+
optional: true,
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
if (monitorsForProject.length > 0) {
|
|
310
|
+
blocks.push(incidentMonitors);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const monitorStatusForProject: Array<MonitorStatus> =
|
|
314
|
+
await MonitorStatusService.findBy({
|
|
315
|
+
query: {
|
|
316
|
+
projectId: data.slackRequest.projectId!,
|
|
317
|
+
},
|
|
318
|
+
select: {
|
|
319
|
+
name: true,
|
|
320
|
+
},
|
|
321
|
+
props: {
|
|
322
|
+
isRoot: true,
|
|
323
|
+
},
|
|
324
|
+
sort: {
|
|
325
|
+
priority: SortOrder.Ascending,
|
|
326
|
+
},
|
|
327
|
+
limit: LIMIT_PER_PROJECT,
|
|
328
|
+
skip: 0,
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
const monitorStatusDropdownOptions: Array<DropdownOption> =
|
|
332
|
+
monitorStatusForProject.map((status: MonitorStatus) => {
|
|
333
|
+
return {
|
|
334
|
+
label: status.name || "",
|
|
335
|
+
value: status._id?.toString() || "",
|
|
336
|
+
};
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
const monitorStatusDropdown: WorkspaceDropdownBlock = {
|
|
340
|
+
_type: "WorkspaceDropdownBlock",
|
|
341
|
+
label: "Change Monitor Status to",
|
|
342
|
+
blockId: "monitorStatus",
|
|
343
|
+
placeholder: "Select Monitor Status",
|
|
344
|
+
options: monitorStatusDropdownOptions,
|
|
345
|
+
optional: true,
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
if (
|
|
349
|
+
monitorStatusForProject.length > 0 &&
|
|
350
|
+
monitorDropdownOptions.length > 0
|
|
351
|
+
) {
|
|
352
|
+
blocks.push(monitorStatusDropdown);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const labelsForProject: Array<Label> = await LabelService.findBy({
|
|
356
|
+
query: {
|
|
357
|
+
projectId: data.slackRequest.projectId!,
|
|
358
|
+
},
|
|
359
|
+
select: {
|
|
360
|
+
name: true,
|
|
361
|
+
},
|
|
362
|
+
props: {
|
|
363
|
+
isRoot: true,
|
|
364
|
+
},
|
|
365
|
+
limit: LIMIT_PER_PROJECT,
|
|
366
|
+
skip: 0,
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
const labelsDropdownOptions: Array<DropdownOption> = labelsForProject.map(
|
|
370
|
+
(label: Label) => {
|
|
371
|
+
return {
|
|
372
|
+
label: label.name || "",
|
|
373
|
+
value: label._id?.toString() || "",
|
|
374
|
+
};
|
|
375
|
+
},
|
|
376
|
+
);
|
|
377
|
+
|
|
378
|
+
const labelsDropdown: WorkspaceDropdownBlock = {
|
|
379
|
+
_type: "WorkspaceDropdownBlock",
|
|
380
|
+
label: "Labels",
|
|
381
|
+
blockId: "labels",
|
|
382
|
+
placeholder: "Select Labels",
|
|
383
|
+
options: labelsDropdownOptions,
|
|
384
|
+
multiSelect: true,
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
if (labelsForProject.length > 0) {
|
|
388
|
+
blocks.push(labelsDropdown);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const modalBlock: WorkspaceModalBlock = {
|
|
392
|
+
_type: "WorkspaceModalBlock",
|
|
393
|
+
title: "New Incident",
|
|
394
|
+
submitButtonTitle: "Submit",
|
|
395
|
+
cancelButtonTitle: "Cancel",
|
|
396
|
+
actionId: SlackActionType.SubmitNewIncident,
|
|
397
|
+
actionValue: "",
|
|
398
|
+
blocks: blocks,
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
await SlackUtil.showModalToUser({
|
|
402
|
+
authToken: data.slackRequest.projectAuthToken!,
|
|
403
|
+
modalBlock: modalBlock,
|
|
404
|
+
triggerId: data.slackRequest.triggerId!,
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
public static async acknowledgeIncident(data: {
|
|
409
|
+
slackRequest: SlackRequest;
|
|
410
|
+
action: SlackAction;
|
|
411
|
+
req: ExpressRequest;
|
|
412
|
+
res: ExpressResponse;
|
|
413
|
+
}): Promise<void> {
|
|
414
|
+
const { slackRequest, req, res } = data;
|
|
415
|
+
const { botUserId, userId, projectAuthToken, slackUsername } = slackRequest;
|
|
416
|
+
|
|
417
|
+
const { actionValue } = data.action;
|
|
418
|
+
|
|
419
|
+
if (!actionValue) {
|
|
420
|
+
return Response.sendErrorResponse(
|
|
421
|
+
req,
|
|
422
|
+
res,
|
|
423
|
+
new BadDataException("Invalid Incident ID"),
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (!userId) {
|
|
428
|
+
return Response.sendErrorResponse(
|
|
429
|
+
req,
|
|
430
|
+
res,
|
|
431
|
+
new BadDataException("Invalid User ID"),
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if (!projectAuthToken) {
|
|
436
|
+
return Response.sendErrorResponse(
|
|
437
|
+
req,
|
|
438
|
+
res,
|
|
439
|
+
new BadDataException("Invalid Project Auth Token"),
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
if (!botUserId) {
|
|
444
|
+
return Response.sendErrorResponse(
|
|
445
|
+
req,
|
|
446
|
+
res,
|
|
447
|
+
new BadDataException("Invalid Bot User ID"),
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
if (data.action.actionType === SlackActionType.AcknowledgeIncident) {
|
|
452
|
+
const incidentId: ObjectID = new ObjectID(actionValue);
|
|
453
|
+
|
|
454
|
+
// We send this early let slack know we're ok. We'll do the rest in the background.
|
|
455
|
+
Response.sendJsonObjectResponse(req, res, {
|
|
456
|
+
response_action: "clear",
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
const isAlreadyAcknowledged: boolean =
|
|
460
|
+
await IncidentService.isIncidentAcknowledged({
|
|
461
|
+
incidentId: incidentId,
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
if (isAlreadyAcknowledged) {
|
|
465
|
+
const incidentNumber: number | null =
|
|
466
|
+
await IncidentService.getIncidentNumber({
|
|
467
|
+
incidentId: incidentId,
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
// send a message to the channel visible to user, that the incident has already been acknowledged.
|
|
471
|
+
const markdwonPayload: WorkspacePayloadMarkdown = {
|
|
472
|
+
_type: "WorkspacePayloadMarkdown",
|
|
473
|
+
text: `@${slackUsername}, unfortunately you cannot acknowledge the **[Incident ${incidentNumber?.toString()}](${await IncidentService.getIncidentLinkInDashboard(slackRequest.projectId!, incidentId)})**. It has already been acknowledged.`,
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
await SlackUtil.sendDirectMessageToUser({
|
|
477
|
+
messageBlocks: [markdwonPayload],
|
|
478
|
+
authToken: projectAuthToken,
|
|
479
|
+
workspaceUserId: slackRequest.slackUserId!,
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
await IncidentService.acknowledgeIncident(incidentId, userId);
|
|
486
|
+
|
|
487
|
+
// Incident Feed will send a message to the channel that the incident has been Acknowledged.
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// invlaid action type.
|
|
492
|
+
return Response.sendErrorResponse(
|
|
493
|
+
req,
|
|
494
|
+
res,
|
|
495
|
+
new BadDataException("Invalid Action Type"),
|
|
496
|
+
);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
public static async resolveIncident(data: {
|
|
500
|
+
slackRequest: SlackRequest;
|
|
501
|
+
action: SlackAction;
|
|
502
|
+
req: ExpressRequest;
|
|
503
|
+
res: ExpressResponse;
|
|
504
|
+
}): Promise<void> {
|
|
505
|
+
const { slackRequest, req, res } = data;
|
|
506
|
+
const { botUserId, userId, projectAuthToken, slackUsername } = slackRequest;
|
|
507
|
+
|
|
508
|
+
const { actionValue } = data.action;
|
|
509
|
+
|
|
510
|
+
if (!actionValue) {
|
|
511
|
+
return Response.sendErrorResponse(
|
|
512
|
+
req,
|
|
513
|
+
res,
|
|
514
|
+
new BadDataException("Invalid Incident ID"),
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
if (!userId) {
|
|
519
|
+
return Response.sendErrorResponse(
|
|
520
|
+
req,
|
|
521
|
+
res,
|
|
522
|
+
new BadDataException("Invalid User ID"),
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
if (!projectAuthToken) {
|
|
527
|
+
return Response.sendErrorResponse(
|
|
528
|
+
req,
|
|
529
|
+
res,
|
|
530
|
+
new BadDataException("Invalid Project Auth Token"),
|
|
531
|
+
);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
if (!botUserId) {
|
|
535
|
+
return Response.sendErrorResponse(
|
|
536
|
+
req,
|
|
537
|
+
res,
|
|
538
|
+
new BadDataException("Invalid Bot User ID"),
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
if (data.action.actionType === SlackActionType.ResolveIncident) {
|
|
543
|
+
const incidentId: ObjectID = new ObjectID(actionValue);
|
|
544
|
+
|
|
545
|
+
// We send this early let slack know we're ok. We'll do the rest in the background.
|
|
546
|
+
Response.sendJsonObjectResponse(req, res, {
|
|
547
|
+
response_action: "clear",
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
const isAlreadyResolved: boolean =
|
|
551
|
+
await IncidentService.isIncidentResolved({
|
|
552
|
+
incidentId: incidentId,
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
if (isAlreadyResolved) {
|
|
556
|
+
const incidentNumber: number | null =
|
|
557
|
+
await IncidentService.getIncidentNumber({
|
|
558
|
+
incidentId: incidentId,
|
|
559
|
+
});
|
|
560
|
+
// send a message to the channel visible to user, that the incident has already been Resolved.
|
|
561
|
+
const markdwonPayload: WorkspacePayloadMarkdown = {
|
|
562
|
+
_type: "WorkspacePayloadMarkdown",
|
|
563
|
+
text: `@${slackUsername}, unfortunately you cannot resolve the **[Incident ${incidentNumber?.toString()}](${await IncidentService.getIncidentLinkInDashboard(slackRequest.projectId!, incidentId)})**. It has already been resolved.`,
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
await SlackUtil.sendDirectMessageToUser({
|
|
567
|
+
messageBlocks: [markdwonPayload],
|
|
568
|
+
authToken: projectAuthToken,
|
|
569
|
+
workspaceUserId: slackRequest.slackUserId!,
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
await IncidentService.resolveIncident(incidentId, userId);
|
|
576
|
+
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// invlaid action type.
|
|
581
|
+
return Response.sendErrorResponse(
|
|
582
|
+
req,
|
|
583
|
+
res,
|
|
584
|
+
new BadDataException("Invalid Action Type"),
|
|
585
|
+
);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
public static async viewExecuteOnCallPolicy(data: {
|
|
589
|
+
slackRequest: SlackRequest;
|
|
590
|
+
action: SlackAction;
|
|
591
|
+
req: ExpressRequest;
|
|
592
|
+
res: ExpressResponse;
|
|
593
|
+
}): Promise<void> {
|
|
594
|
+
const { req, res } = data;
|
|
595
|
+
const { actionValue } = data.action;
|
|
596
|
+
|
|
597
|
+
if (!actionValue) {
|
|
598
|
+
return Response.sendErrorResponse(
|
|
599
|
+
req,
|
|
600
|
+
res,
|
|
601
|
+
new BadDataException("Invalid Incident ID"),
|
|
602
|
+
);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// We send this early let slack know we're ok. We'll do the rest in the background.
|
|
606
|
+
Response.sendJsonObjectResponse(req, res, {
|
|
607
|
+
response_action: "clear",
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
// const incidentId: ObjectID = new ObjectID(actionValue);
|
|
611
|
+
|
|
612
|
+
// send a modal with a dropdown that says "Public Note" or "Private Note" and a text area to add the note.
|
|
613
|
+
|
|
614
|
+
const onCallPolicies: Array<OnCallDutyPolicy> =
|
|
615
|
+
await OnCallDutyPolicyService.findBy({
|
|
616
|
+
query: {
|
|
617
|
+
projectId: data.slackRequest.projectId!,
|
|
618
|
+
},
|
|
619
|
+
select: {
|
|
620
|
+
name: true,
|
|
621
|
+
},
|
|
622
|
+
props: {
|
|
623
|
+
isRoot: true,
|
|
624
|
+
},
|
|
625
|
+
limit: LIMIT_PER_PROJECT,
|
|
626
|
+
skip: 0,
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
const dropdownOption: Array<DropdownOption> = onCallPolicies
|
|
630
|
+
.map((policy: OnCallDutyPolicy) => {
|
|
631
|
+
return {
|
|
632
|
+
label: policy.name || "",
|
|
633
|
+
value: policy._id?.toString() || "",
|
|
634
|
+
};
|
|
635
|
+
})
|
|
636
|
+
.filter((option: DropdownOption) => {
|
|
637
|
+
return option.label !== "" || option.value !== "";
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
const onCallPolicyDropdown: WorkspaceDropdownBlock = {
|
|
641
|
+
_type: "WorkspaceDropdownBlock",
|
|
642
|
+
label: "On Call Policy",
|
|
643
|
+
blockId: "onCallPolicy",
|
|
644
|
+
placeholder: "Select On Call Policy",
|
|
645
|
+
options: dropdownOption,
|
|
646
|
+
};
|
|
647
|
+
|
|
648
|
+
const modalBlock: WorkspaceModalBlock = {
|
|
649
|
+
_type: "WorkspaceModalBlock",
|
|
650
|
+
title: "Execute On Call Policy",
|
|
651
|
+
submitButtonTitle: "Submit",
|
|
652
|
+
cancelButtonTitle: "Cancel",
|
|
653
|
+
actionId: SlackActionType.SubmitExecuteIncidentOnCallPolicy,
|
|
654
|
+
actionValue: actionValue,
|
|
655
|
+
blocks: [onCallPolicyDropdown],
|
|
656
|
+
};
|
|
657
|
+
|
|
658
|
+
await SlackUtil.showModalToUser({
|
|
659
|
+
authToken: data.slackRequest.projectAuthToken!,
|
|
660
|
+
modalBlock: modalBlock,
|
|
661
|
+
triggerId: data.slackRequest.triggerId!,
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
public static async viewChangeIncidentState(data: {
|
|
666
|
+
slackRequest: SlackRequest;
|
|
667
|
+
action: SlackAction;
|
|
668
|
+
req: ExpressRequest;
|
|
669
|
+
res: ExpressResponse;
|
|
670
|
+
}): Promise<void> {
|
|
671
|
+
const { req, res } = data;
|
|
672
|
+
const { actionValue } = data.action;
|
|
673
|
+
|
|
674
|
+
if (!actionValue) {
|
|
675
|
+
return Response.sendErrorResponse(
|
|
676
|
+
req,
|
|
677
|
+
res,
|
|
678
|
+
new BadDataException("Invalid Incident ID"),
|
|
679
|
+
);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// We send this early let slack know we're ok. We'll do the rest in the background.
|
|
683
|
+
Response.sendJsonObjectResponse(req, res, {
|
|
684
|
+
response_action: "clear",
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
// const incidentId: ObjectID = new ObjectID(actionValue);
|
|
688
|
+
|
|
689
|
+
// send a modal with a dropdown that says "Public Note" or "Private Note" and a text area to add the note.
|
|
690
|
+
|
|
691
|
+
const incidentStates: Array<IncidentState> =
|
|
692
|
+
await IncidentStateService.getAllIncidentStates({
|
|
693
|
+
projectId: data.slackRequest.projectId!,
|
|
694
|
+
props: {
|
|
695
|
+
isRoot: true,
|
|
696
|
+
},
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
logger.debug("Incident States: ");
|
|
700
|
+
logger.debug(incidentStates);
|
|
701
|
+
|
|
702
|
+
const dropdownOptions: Array<DropdownOption> = incidentStates
|
|
703
|
+
.map((state: IncidentState) => {
|
|
704
|
+
return {
|
|
705
|
+
label: state.name || "",
|
|
706
|
+
value: state._id?.toString() || "",
|
|
707
|
+
};
|
|
708
|
+
})
|
|
709
|
+
.filter((option: DropdownOption) => {
|
|
710
|
+
return option.label !== "" || option.value !== "";
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
logger.debug("Dropdown Options: ");
|
|
714
|
+
logger.debug(dropdownOptions);
|
|
715
|
+
|
|
716
|
+
const statePickerDropdown: WorkspaceDropdownBlock = {
|
|
717
|
+
_type: "WorkspaceDropdownBlock",
|
|
718
|
+
label: "Incident State",
|
|
719
|
+
blockId: "incidentState",
|
|
720
|
+
placeholder: "Select Incident State",
|
|
721
|
+
options: dropdownOptions,
|
|
722
|
+
};
|
|
723
|
+
|
|
724
|
+
const modalBlock: WorkspaceModalBlock = {
|
|
725
|
+
_type: "WorkspaceModalBlock",
|
|
726
|
+
title: "Change Incident State",
|
|
727
|
+
submitButtonTitle: "Submit",
|
|
728
|
+
cancelButtonTitle: "Cancel",
|
|
729
|
+
actionId: SlackActionType.SubmitChangeIncidentState,
|
|
730
|
+
actionValue: actionValue,
|
|
731
|
+
blocks: [statePickerDropdown],
|
|
732
|
+
};
|
|
733
|
+
|
|
734
|
+
await SlackUtil.showModalToUser({
|
|
735
|
+
authToken: data.slackRequest.projectAuthToken!,
|
|
736
|
+
modalBlock: modalBlock,
|
|
737
|
+
triggerId: data.slackRequest.triggerId!,
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
public static async submitChangeIncidentState(data: {
|
|
742
|
+
slackRequest: SlackRequest;
|
|
743
|
+
action: SlackAction;
|
|
744
|
+
req: ExpressRequest;
|
|
745
|
+
res: ExpressResponse;
|
|
746
|
+
}): Promise<void> {
|
|
747
|
+
const { req, res } = data;
|
|
748
|
+
const { actionValue } = data.action;
|
|
749
|
+
|
|
750
|
+
if (!actionValue) {
|
|
751
|
+
return Response.sendErrorResponse(
|
|
752
|
+
req,
|
|
753
|
+
res,
|
|
754
|
+
new BadDataException("Invalid Incident ID"),
|
|
755
|
+
);
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
// We send this early let slack know we're ok. We'll do the rest in the background.
|
|
759
|
+
Response.sendJsonObjectResponse(req, res, {
|
|
760
|
+
response_action: "clear",
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
// const incidentId: ObjectID = new ObjectID(actionValue);
|
|
764
|
+
|
|
765
|
+
// send a modal with a dropdown that says "Public Note" or "Private Note" and a text area to add the note.
|
|
766
|
+
|
|
767
|
+
if (
|
|
768
|
+
!data.slackRequest.viewValues ||
|
|
769
|
+
!data.slackRequest.viewValues["incidentState"]
|
|
770
|
+
) {
|
|
771
|
+
return Response.sendErrorResponse(
|
|
772
|
+
req,
|
|
773
|
+
res,
|
|
774
|
+
new BadDataException("Invalid View Values"),
|
|
775
|
+
);
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
const incidentId: ObjectID = new ObjectID(actionValue);
|
|
779
|
+
const stateString: string =
|
|
780
|
+
data.slackRequest.viewValues["incidentState"].toString();
|
|
781
|
+
|
|
782
|
+
const stateId: ObjectID = new ObjectID(stateString);
|
|
783
|
+
|
|
784
|
+
await IncidentService.updateOneById({
|
|
785
|
+
id: incidentId,
|
|
786
|
+
data: {
|
|
787
|
+
currentIncidentStateId: stateId,
|
|
788
|
+
},
|
|
789
|
+
props: {
|
|
790
|
+
userId: data.slackRequest.userId!,
|
|
791
|
+
},
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
public static async executeOnCallPolicy(data: {
|
|
796
|
+
slackRequest: SlackRequest;
|
|
797
|
+
action: SlackAction;
|
|
798
|
+
req: ExpressRequest;
|
|
799
|
+
res: ExpressResponse;
|
|
800
|
+
}): Promise<void> {
|
|
801
|
+
const { slackRequest, req, res } = data;
|
|
802
|
+
const { botUserId, userId, projectAuthToken, slackUsername } = slackRequest;
|
|
803
|
+
|
|
804
|
+
const { actionValue } = data.action;
|
|
805
|
+
|
|
806
|
+
if (!actionValue) {
|
|
807
|
+
return Response.sendErrorResponse(
|
|
808
|
+
req,
|
|
809
|
+
res,
|
|
810
|
+
new BadDataException("Invalid Incident ID"),
|
|
811
|
+
);
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
if (!userId) {
|
|
815
|
+
return Response.sendErrorResponse(
|
|
816
|
+
req,
|
|
817
|
+
res,
|
|
818
|
+
new BadDataException("Invalid User ID"),
|
|
819
|
+
);
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
if (!projectAuthToken) {
|
|
823
|
+
return Response.sendErrorResponse(
|
|
824
|
+
req,
|
|
825
|
+
res,
|
|
826
|
+
new BadDataException("Invalid Project Auth Token"),
|
|
827
|
+
);
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
if (!botUserId) {
|
|
831
|
+
return Response.sendErrorResponse(
|
|
832
|
+
req,
|
|
833
|
+
res,
|
|
834
|
+
new BadDataException("Invalid Bot User ID"),
|
|
835
|
+
);
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
if (
|
|
839
|
+
data.action.actionType ===
|
|
840
|
+
SlackActionType.SubmitExecuteIncidentOnCallPolicy
|
|
841
|
+
) {
|
|
842
|
+
const incidentId: ObjectID = new ObjectID(actionValue);
|
|
843
|
+
|
|
844
|
+
// We send this early let slack know we're ok. We'll do the rest in the background.
|
|
845
|
+
Response.sendJsonObjectResponse(req, res, {
|
|
846
|
+
response_action: "clear",
|
|
847
|
+
});
|
|
848
|
+
|
|
849
|
+
const isAlreadyResolved: boolean =
|
|
850
|
+
await IncidentService.isIncidentResolved({
|
|
851
|
+
incidentId: incidentId,
|
|
852
|
+
});
|
|
853
|
+
|
|
854
|
+
if (isAlreadyResolved) {
|
|
855
|
+
const incidentNumber: number | null =
|
|
856
|
+
await IncidentService.getIncidentNumber({
|
|
857
|
+
incidentId: incidentId,
|
|
858
|
+
});
|
|
859
|
+
// send a message to the channel visible to user, that the incident has already been Resolved.
|
|
860
|
+
const markdwonPayload: WorkspacePayloadMarkdown = {
|
|
861
|
+
_type: "WorkspacePayloadMarkdown",
|
|
862
|
+
text: `@${slackUsername}, unfortunately you cannot execute the on call policy for **[Incident ${incidentNumber?.toString()}](${await IncidentService.getIncidentLinkInDashboard(slackRequest.projectId!, incidentId)})**. It has already been resolved.`,
|
|
863
|
+
};
|
|
864
|
+
|
|
865
|
+
await SlackUtil.sendDirectMessageToUser({
|
|
866
|
+
messageBlocks: [markdwonPayload],
|
|
867
|
+
authToken: projectAuthToken,
|
|
868
|
+
workspaceUserId: slackRequest.slackUserId!,
|
|
869
|
+
});
|
|
870
|
+
|
|
871
|
+
return;
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
if (
|
|
875
|
+
!data.slackRequest.viewValues ||
|
|
876
|
+
!data.slackRequest.viewValues["onCallPolicy"]
|
|
877
|
+
) {
|
|
878
|
+
return Response.sendErrorResponse(
|
|
879
|
+
req,
|
|
880
|
+
res,
|
|
881
|
+
new BadDataException("Invalid View Values"),
|
|
882
|
+
);
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
const onCallPolicyString: string =
|
|
886
|
+
data.slackRequest.viewValues["onCallPolicy"].toString();
|
|
887
|
+
|
|
888
|
+
// get the on call policy id.
|
|
889
|
+
const onCallPolicyId: ObjectID = new ObjectID(onCallPolicyString);
|
|
890
|
+
|
|
891
|
+
await OnCallDutyPolicyService.executePolicy(onCallPolicyId, {
|
|
892
|
+
triggeredByIncidentId: incidentId,
|
|
893
|
+
userNotificationEventType: UserNotificationEventType.IncidentCreated,
|
|
894
|
+
});
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
public static async submitIncidentNote(data: {
|
|
899
|
+
slackRequest: SlackRequest;
|
|
900
|
+
action: SlackAction;
|
|
901
|
+
req: ExpressRequest;
|
|
902
|
+
res: ExpressResponse;
|
|
903
|
+
}): Promise<void> {
|
|
904
|
+
const { req, res } = data;
|
|
905
|
+
const { actionValue } = data.action;
|
|
906
|
+
|
|
907
|
+
if (!actionValue) {
|
|
908
|
+
return Response.sendErrorResponse(
|
|
909
|
+
req,
|
|
910
|
+
res,
|
|
911
|
+
new BadDataException("Invalid Incident ID"),
|
|
912
|
+
);
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
// const incidentId: ObjectID = new ObjectID(actionValue);
|
|
916
|
+
|
|
917
|
+
// send a modal with a dropdown that says "Public Note" or "Private Note" and a text area to add the note.
|
|
918
|
+
|
|
919
|
+
// if view values is empty, then return error.
|
|
920
|
+
|
|
921
|
+
if (!data.slackRequest.viewValues) {
|
|
922
|
+
return Response.sendErrorResponse(
|
|
923
|
+
req,
|
|
924
|
+
res,
|
|
925
|
+
new BadDataException("Invalid View Values"),
|
|
926
|
+
);
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
if (!data.slackRequest.viewValues["noteType"]) {
|
|
930
|
+
return Response.sendErrorResponse(
|
|
931
|
+
req,
|
|
932
|
+
res,
|
|
933
|
+
new BadDataException("Invalid Note Type"),
|
|
934
|
+
);
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
if (!data.slackRequest.viewValues["note"]) {
|
|
938
|
+
// return error.
|
|
939
|
+
return Response.sendErrorResponse(
|
|
940
|
+
req,
|
|
941
|
+
res,
|
|
942
|
+
new BadDataException("Invalid Note"),
|
|
943
|
+
);
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
const incidentId: ObjectID = new ObjectID(actionValue);
|
|
947
|
+
const note: string = data.slackRequest.viewValues["note"].toString();
|
|
948
|
+
const noteType: string =
|
|
949
|
+
data.slackRequest.viewValues["noteType"].toString();
|
|
950
|
+
|
|
951
|
+
if (noteType !== "public" && noteType !== "private") {
|
|
952
|
+
return Response.sendErrorResponse(
|
|
953
|
+
req,
|
|
954
|
+
res,
|
|
955
|
+
new BadDataException("Invalid Note Type"),
|
|
956
|
+
);
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
// send empty response.
|
|
960
|
+
Response.sendJsonObjectResponse(req, res, {
|
|
961
|
+
response_action: "clear",
|
|
962
|
+
});
|
|
963
|
+
|
|
964
|
+
// if public note then, add a note.
|
|
965
|
+
if (noteType === "public") {
|
|
966
|
+
await IncidentPublicNoteService.addNote({
|
|
967
|
+
incidentId: incidentId!,
|
|
968
|
+
note: note || "",
|
|
969
|
+
projectId: data.slackRequest.projectId!,
|
|
970
|
+
userId: data.slackRequest.userId!,
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
// if private note then, add a note.
|
|
975
|
+
if (noteType === "private") {
|
|
976
|
+
await IncidentInternalNoteService.addNote({
|
|
977
|
+
incidentId: incidentId!,
|
|
978
|
+
note: note || "",
|
|
979
|
+
projectId: data.slackRequest.projectId!,
|
|
980
|
+
userId: data.slackRequest.userId!,
|
|
981
|
+
});
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
public static async viewAddIncidentNote(data: {
|
|
986
|
+
slackRequest: SlackRequest;
|
|
987
|
+
action: SlackAction;
|
|
988
|
+
req: ExpressRequest;
|
|
989
|
+
res: ExpressResponse;
|
|
990
|
+
}): Promise<void> {
|
|
991
|
+
const { req, res } = data;
|
|
992
|
+
const { actionValue } = data.action;
|
|
993
|
+
|
|
994
|
+
if (!actionValue) {
|
|
995
|
+
return Response.sendErrorResponse(
|
|
996
|
+
req,
|
|
997
|
+
res,
|
|
998
|
+
new BadDataException("Invalid Incident ID"),
|
|
999
|
+
);
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
// We send this early let slack know we're ok. We'll do the rest in the background.
|
|
1003
|
+
Response.sendJsonObjectResponse(req, res, {
|
|
1004
|
+
response_action: "clear",
|
|
1005
|
+
});
|
|
1006
|
+
|
|
1007
|
+
// const incidentId: ObjectID = new ObjectID(actionValue);
|
|
1008
|
+
|
|
1009
|
+
// send a modal with a dropdown that says "Public Note" or "Private Note" and a text area to add the note.
|
|
1010
|
+
|
|
1011
|
+
const notePickerDropdown: WorkspaceDropdownBlock = {
|
|
1012
|
+
_type: "WorkspaceDropdownBlock",
|
|
1013
|
+
label: "Note Type",
|
|
1014
|
+
blockId: "noteType",
|
|
1015
|
+
placeholder: "Select Note Type",
|
|
1016
|
+
options: [
|
|
1017
|
+
{
|
|
1018
|
+
label: "Public Note (Will be posted on Status Page)",
|
|
1019
|
+
value: "public",
|
|
1020
|
+
},
|
|
1021
|
+
{
|
|
1022
|
+
label: "Private Note (Only visible to team members)",
|
|
1023
|
+
value: "private",
|
|
1024
|
+
},
|
|
1025
|
+
],
|
|
1026
|
+
};
|
|
1027
|
+
|
|
1028
|
+
const noteTextArea: WorkspaceTextAreaBlock = {
|
|
1029
|
+
_type: "WorkspaceTextAreaBlock",
|
|
1030
|
+
label: "Note",
|
|
1031
|
+
blockId: "note",
|
|
1032
|
+
placeholder: "Note",
|
|
1033
|
+
description: "Please type in plain text or markdown.",
|
|
1034
|
+
};
|
|
1035
|
+
|
|
1036
|
+
const modalBlock: WorkspaceModalBlock = {
|
|
1037
|
+
_type: "WorkspaceModalBlock",
|
|
1038
|
+
title: "Add Note",
|
|
1039
|
+
submitButtonTitle: "Submit",
|
|
1040
|
+
cancelButtonTitle: "Cancel",
|
|
1041
|
+
actionId: SlackActionType.SubmitIncidentNote,
|
|
1042
|
+
actionValue: actionValue,
|
|
1043
|
+
blocks: [notePickerDropdown, noteTextArea],
|
|
1044
|
+
};
|
|
1045
|
+
|
|
1046
|
+
await SlackUtil.showModalToUser({
|
|
1047
|
+
authToken: data.slackRequest.projectAuthToken!,
|
|
1048
|
+
modalBlock: modalBlock,
|
|
1049
|
+
triggerId: data.slackRequest.triggerId!,
|
|
1050
|
+
});
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
public static async handleIncidentAction(data: {
|
|
1054
|
+
slackRequest: SlackRequest;
|
|
1055
|
+
action: SlackAction;
|
|
1056
|
+
req: ExpressRequest;
|
|
1057
|
+
res: ExpressResponse;
|
|
1058
|
+
}): Promise<void> {
|
|
1059
|
+
// now we should be all set, project is authorized and user is authorized. Lets perform some actions based on the action type.
|
|
1060
|
+
const actionType: SlackActionType | undefined = data.action.actionType;
|
|
1061
|
+
|
|
1062
|
+
if (actionType === SlackActionType.AcknowledgeIncident) {
|
|
1063
|
+
return await this.acknowledgeIncident(data);
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
if (actionType === SlackActionType.ResolveIncident) {
|
|
1067
|
+
return await this.resolveIncident(data);
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
if (actionType === SlackActionType.ViewAddIncidentNote) {
|
|
1071
|
+
return await this.viewAddIncidentNote(data);
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
if (actionType === SlackActionType.SubmitIncidentNote) {
|
|
1075
|
+
return await this.submitIncidentNote(data);
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
if (actionType === SlackActionType.ViewExecuteIncidentOnCallPolicy) {
|
|
1079
|
+
return await this.viewExecuteOnCallPolicy(data);
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
if (actionType === SlackActionType.SubmitExecuteIncidentOnCallPolicy) {
|
|
1083
|
+
return await this.executeOnCallPolicy(data);
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
if (actionType === SlackActionType.ViewChangeIncidentState) {
|
|
1087
|
+
return await this.viewChangeIncidentState(data);
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
if (actionType === SlackActionType.SubmitChangeIncidentState) {
|
|
1091
|
+
return await this.submitChangeIncidentState(data);
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
if (actionType === SlackActionType.NewIncident) {
|
|
1095
|
+
return await this.viewNewIncidentModal(data);
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
if (actionType === SlackActionType.SubmitNewIncident) {
|
|
1099
|
+
return await this.submitNewIncident(data);
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
if (actionType === SlackActionType.ViewIncident) {
|
|
1103
|
+
// do nothing. This is just a view incident action.
|
|
1104
|
+
// clear response.
|
|
1105
|
+
return Response.sendJsonObjectResponse(data.req, data.res, {
|
|
1106
|
+
response_action: "clear",
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
// invalid action type.
|
|
1111
|
+
return Response.sendErrorResponse(
|
|
1112
|
+
data.req,
|
|
1113
|
+
data.res,
|
|
1114
|
+
new BadDataException("Invalid Action Type"),
|
|
1115
|
+
);
|
|
1116
|
+
}
|
|
1117
|
+
}
|