@oneuptime/common 11.3.21 → 11.3.23
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/AnalyticsModels/AnalyticsBaseModel/CommonModel.ts +2 -0
- package/Models/AnalyticsModels/ExceptionInstance.ts +1 -0
- package/Models/AnalyticsModels/Log.ts +1 -0
- package/Models/AnalyticsModels/Metric.ts +1 -0
- package/Models/AnalyticsModels/MonitorLog.ts +1 -0
- package/Models/AnalyticsModels/Span.ts +1 -0
- package/Models/DatabaseModels/Alert.ts +100 -0
- package/Models/DatabaseModels/AlertInternalNote.ts +2 -0
- package/Models/DatabaseModels/AlertReminderRule.ts +597 -0
- package/Models/DatabaseModels/AlertSeverity.ts +2 -0
- package/Models/DatabaseModels/AlertState.ts +2 -0
- package/Models/DatabaseModels/AlertStateTimeline.ts +2 -0
- package/Models/DatabaseModels/Incident.ts +100 -0
- package/Models/DatabaseModels/IncidentInternalNote.ts +2 -0
- package/Models/DatabaseModels/IncidentPublicNote.ts +2 -0
- package/Models/DatabaseModels/IncidentReminderRule.ts +597 -0
- package/Models/DatabaseModels/IncidentSeverity.ts +2 -0
- package/Models/DatabaseModels/IncidentStateTimeline.ts +2 -0
- package/Models/DatabaseModels/Index.ts +6 -0
- package/Models/DatabaseModels/Label.ts +2 -0
- package/Models/DatabaseModels/MonitorStatusTimeline.ts +2 -0
- package/Models/DatabaseModels/ScheduledMaintenance.ts +100 -0
- package/Models/DatabaseModels/ScheduledMaintenanceReminderRule.ts +585 -0
- package/Models/DatabaseModels/ScheduledMaintenanceState.ts +2 -0
- package/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.ts +2 -0
- package/Models/DatabaseModels/StatusPageAnnouncement.ts +2 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1783234451106-AddIncidentAlertReminderRules.ts +211 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1783320000000-AddScheduledMaintenanceReminderRules.ts +113 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1783330000000-AddRemindWhileScheduledToScheduledMaintenanceReminderRule.ts +20 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1783340000000-AddLabelsToIncidentAndAlertReminderRules.ts +69 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
- package/Server/Services/AlertReminderRuleService.ts +253 -0
- package/Server/Services/AlertService.ts +106 -0
- package/Server/Services/IncidentReminderRuleService.ts +256 -0
- package/Server/Services/IncidentService.ts +143 -0
- package/Server/Services/Index.ts +6 -0
- package/Server/Services/ScheduledMaintenanceReminderRuleService.ts +236 -0
- package/Server/Services/ScheduledMaintenanceService.ts +127 -0
- package/Server/Services/TelemetryExceptionService.ts +123 -0
- package/Server/Services/UserNotificationSettingService.ts +24 -0
- package/Server/Utils/AnalyticsDatabase/Statement.ts +7 -3
- package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +35 -0
- package/Server/Utils/EventLoop.ts +28 -0
- package/Server/Utils/WhatsAppTemplateUtil.ts +10 -0
- package/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.ts +49 -0
- package/Types/Email/EmailTemplateType.ts +3 -0
- package/Types/NotificationSetting/NotificationSettingEventType.ts +3 -0
- package/Types/Permission.ts +144 -0
- package/Types/Reminder/ReminderStopState.ts +6 -0
- package/Types/Reminder/ScheduledMaintenanceReminderStopState.ts +6 -0
- package/Types/WhatsApp/WhatsAppTemplates.ts +14 -0
- package/build/dist/Models/AnalyticsModels/AnalyticsBaseModel/CommonModel.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/ExceptionInstance.js +1 -0
- package/build/dist/Models/AnalyticsModels/ExceptionInstance.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/Log.js +1 -0
- package/build/dist/Models/AnalyticsModels/Log.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/Metric.js +1 -0
- package/build/dist/Models/AnalyticsModels/Metric.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/MonitorLog.js +1 -0
- package/build/dist/Models/AnalyticsModels/MonitorLog.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/Span.js +1 -0
- package/build/dist/Models/AnalyticsModels/Span.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Alert.js +103 -0
- package/build/dist/Models/DatabaseModels/Alert.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AlertInternalNote.js +2 -0
- package/build/dist/Models/DatabaseModels/AlertInternalNote.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AlertReminderRule.js +605 -0
- package/build/dist/Models/DatabaseModels/AlertReminderRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/AlertSeverity.js +2 -0
- package/build/dist/Models/DatabaseModels/AlertSeverity.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AlertState.js +2 -0
- package/build/dist/Models/DatabaseModels/AlertState.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AlertStateTimeline.js +2 -0
- package/build/dist/Models/DatabaseModels/AlertStateTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Incident.js +103 -0
- package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentInternalNote.js +2 -0
- package/build/dist/Models/DatabaseModels/IncidentInternalNote.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentPublicNote.js +2 -0
- package/build/dist/Models/DatabaseModels/IncidentPublicNote.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentReminderRule.js +605 -0
- package/build/dist/Models/DatabaseModels/IncidentReminderRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/IncidentSeverity.js +2 -0
- package/build/dist/Models/DatabaseModels/IncidentSeverity.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js +2 -0
- package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +6 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Label.js +2 -0
- package/build/dist/Models/DatabaseModels/Label.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MonitorStatusTimeline.js +2 -0
- package/build/dist/Models/DatabaseModels/MonitorStatusTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js +103 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceReminderRule.js +596 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceReminderRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceState.js +2 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceState.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js +2 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js +2 -0
- package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783234451106-AddIncidentAlertReminderRules.js +78 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783234451106-AddIncidentAlertReminderRules.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783320000000-AddScheduledMaintenanceReminderRules.js +44 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783320000000-AddScheduledMaintenanceReminderRules.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783330000000-AddRemindWhileScheduledToScheduledMaintenanceReminderRule.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783330000000-AddRemindWhileScheduledToScheduledMaintenanceReminderRule.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783340000000-AddLabelsToIncidentAndAlertReminderRules.js +30 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783340000000-AddLabelsToIncidentAndAlertReminderRules.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/Services/AlertReminderRuleService.js +228 -0
- package/build/dist/Server/Services/AlertReminderRuleService.js.map +1 -0
- package/build/dist/Server/Services/AlertService.js +87 -0
- package/build/dist/Server/Services/AlertService.js.map +1 -1
- package/build/dist/Server/Services/IncidentReminderRuleService.js +231 -0
- package/build/dist/Server/Services/IncidentReminderRuleService.js.map +1 -0
- package/build/dist/Server/Services/IncidentService.js +116 -0
- package/build/dist/Server/Services/IncidentService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +6 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceReminderRuleService.js +214 -0
- package/build/dist/Server/Services/ScheduledMaintenanceReminderRuleService.js.map +1 -0
- package/build/dist/Server/Services/ScheduledMaintenanceService.js +102 -0
- package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
- package/build/dist/Server/Services/TelemetryExceptionService.js +101 -0
- package/build/dist/Server/Services/TelemetryExceptionService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationSettingService.js +6 -0
- package/build/dist/Server/Services/UserNotificationSettingService.js.map +1 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/Statement.js +4 -2
- package/build/dist/Server/Utils/AnalyticsDatabase/Statement.js.map +1 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +29 -0
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
- package/build/dist/Server/Utils/EventLoop.js +29 -0
- package/build/dist/Server/Utils/EventLoop.js.map +1 -0
- package/build/dist/Server/Utils/WhatsAppTemplateUtil.js +6 -0
- package/build/dist/Server/Utils/WhatsAppTemplateUtil.js.map +1 -1
- package/build/dist/Types/Email/EmailTemplateType.js +3 -0
- package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
- package/build/dist/Types/NotificationSetting/NotificationSettingEventType.js +3 -0
- package/build/dist/Types/NotificationSetting/NotificationSettingEventType.js.map +1 -1
- package/build/dist/Types/Permission.js +126 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/Reminder/ReminderStopState.js +7 -0
- package/build/dist/Types/Reminder/ReminderStopState.js.map +1 -0
- package/build/dist/Types/Reminder/ScheduledMaintenanceReminderStopState.js +7 -0
- package/build/dist/Types/Reminder/ScheduledMaintenanceReminderStopState.js.map +1 -0
- package/build/dist/Types/WhatsApp/WhatsAppTemplates.js +9 -0
- package/build/dist/Types/WhatsApp/WhatsAppTemplates.js.map +1 -1
- package/package.json +1 -1
|
@@ -56,6 +56,8 @@ import logger, { LogAttributes } from "../Utils/Logger";
|
|
|
56
56
|
import Semaphore, { SemaphoreMutex } from "../Infrastructure/Semaphore";
|
|
57
57
|
import IncidentFeedService from "./IncidentFeedService";
|
|
58
58
|
import IncidentSlaService from "./IncidentSlaService";
|
|
59
|
+
import IncidentReminderRuleService from "./IncidentReminderRuleService";
|
|
60
|
+
import IncidentReminderRule from "../../Models/DatabaseModels/IncidentReminderRule";
|
|
59
61
|
import { setIsPublicForMarkdownImages } from "../Utils/InlineImageAccessTokenSync";
|
|
60
62
|
import { IncidentFeedEventType } from "../../Models/DatabaseModels/IncidentFeed";
|
|
61
63
|
import IncidentGroupingEngineService from "./IncidentGroupingEngineService";
|
|
@@ -230,6 +232,64 @@ export class Service extends DatabaseService<Model> {
|
|
|
230
232
|
return false;
|
|
231
233
|
}
|
|
232
234
|
|
|
235
|
+
@CaptureSpan()
|
|
236
|
+
public async refreshReminderSchedule(data: {
|
|
237
|
+
incidentId: ObjectID;
|
|
238
|
+
projectId: ObjectID;
|
|
239
|
+
}): Promise<void> {
|
|
240
|
+
const incident: Model | null = await this.findOneById({
|
|
241
|
+
id: data.incidentId,
|
|
242
|
+
select: {
|
|
243
|
+
enableReminders: true,
|
|
244
|
+
incidentSeverityId: true,
|
|
245
|
+
labels: {
|
|
246
|
+
_id: true,
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
props: {
|
|
250
|
+
isRoot: true,
|
|
251
|
+
},
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
if (!incident) {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
let nextReminderNotificationAt: Date | null = null;
|
|
259
|
+
|
|
260
|
+
if (incident.enableReminders !== false) {
|
|
261
|
+
const matchingRule: IncidentReminderRule | null =
|
|
262
|
+
await IncidentReminderRuleService.findMatchingRule({
|
|
263
|
+
projectId: data.projectId,
|
|
264
|
+
incidentSeverityId: incident.incidentSeverityId,
|
|
265
|
+
labelIds: incident.labels?.map((label: Label) => {
|
|
266
|
+
return label.id!;
|
|
267
|
+
}),
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
if (
|
|
271
|
+
matchingRule &&
|
|
272
|
+
matchingRule.reminderIntervalInMinutes &&
|
|
273
|
+
!(await this.isIncidentResolved({ incidentId: data.incidentId }))
|
|
274
|
+
) {
|
|
275
|
+
nextReminderNotificationAt = OneUptimeDate.addRemoveMinutes(
|
|
276
|
+
OneUptimeDate.getCurrentDate(),
|
|
277
|
+
matchingRule.reminderIntervalInMinutes,
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
await this.updateOneById({
|
|
283
|
+
id: data.incidentId,
|
|
284
|
+
data: {
|
|
285
|
+
nextReminderNotificationAt: nextReminderNotificationAt,
|
|
286
|
+
},
|
|
287
|
+
props: {
|
|
288
|
+
isRoot: true,
|
|
289
|
+
},
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
|
|
233
293
|
@CaptureSpan()
|
|
234
294
|
public async resolveIncident(
|
|
235
295
|
incidentId: ObjectID,
|
|
@@ -1161,6 +1221,26 @@ export class Service extends DatabaseService<Model> {
|
|
|
1161
1221
|
);
|
|
1162
1222
|
}
|
|
1163
1223
|
})
|
|
1224
|
+
.then(async () => {
|
|
1225
|
+
// Schedule reminder notifications for incident if a matching reminder rule exists
|
|
1226
|
+
try {
|
|
1227
|
+
if (createdItem.projectId && createdItem.id) {
|
|
1228
|
+
await this.refreshReminderSchedule({
|
|
1229
|
+
incidentId: createdItem.id,
|
|
1230
|
+
projectId: createdItem.projectId,
|
|
1231
|
+
});
|
|
1232
|
+
}
|
|
1233
|
+
} catch (error) {
|
|
1234
|
+
logger.error(
|
|
1235
|
+
`Reminder scheduling failed in IncidentService.onCreateSuccess: ${error}`,
|
|
1236
|
+
{
|
|
1237
|
+
projectId: createdItem.projectId?.toString(),
|
|
1238
|
+
incidentId: createdItem.id?.toString(),
|
|
1239
|
+
userId: createdItem.createdByUserId?.toString(),
|
|
1240
|
+
} as LogAttributes,
|
|
1241
|
+
);
|
|
1242
|
+
}
|
|
1243
|
+
})
|
|
1164
1244
|
.catch((error: Error) => {
|
|
1165
1245
|
logger.error(
|
|
1166
1246
|
`Critical error in IncidentService sequential operations: ${error}`,
|
|
@@ -1728,6 +1808,29 @@ ${incident.remediationNotes || "No remediation notes provided."}
|
|
|
1728
1808
|
});
|
|
1729
1809
|
}
|
|
1730
1810
|
|
|
1811
|
+
// Re-evaluate reminder schedule when reminders are enabled or disabled for this incident
|
|
1812
|
+
if (
|
|
1813
|
+
Object.prototype.hasOwnProperty.call(
|
|
1814
|
+
updatedIncidentData,
|
|
1815
|
+
"enableReminders",
|
|
1816
|
+
)
|
|
1817
|
+
) {
|
|
1818
|
+
try {
|
|
1819
|
+
await this.refreshReminderSchedule({
|
|
1820
|
+
incidentId: incidentId,
|
|
1821
|
+
projectId: projectId,
|
|
1822
|
+
});
|
|
1823
|
+
} catch (reminderError) {
|
|
1824
|
+
logger.error(
|
|
1825
|
+
`Reminder rescheduling failed in IncidentService.onUpdateSuccess: ${reminderError}`,
|
|
1826
|
+
{
|
|
1827
|
+
projectId: projectId?.toString(),
|
|
1828
|
+
incidentId: incidentId?.toString(),
|
|
1829
|
+
} as LogAttributes,
|
|
1830
|
+
);
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1731
1834
|
// emit postmortem completion time metric when postmortemPostedAt is set
|
|
1732
1835
|
if (
|
|
1733
1836
|
Object.prototype.hasOwnProperty.call(
|
|
@@ -1976,6 +2079,30 @@ ${labels
|
|
|
1976
2079
|
}
|
|
1977
2080
|
}
|
|
1978
2081
|
|
|
2082
|
+
/*
|
|
2083
|
+
* Re-match reminder rule on any labels change (including clearing all
|
|
2084
|
+
* labels), since labels can change which reminder rule matches.
|
|
2085
|
+
*/
|
|
2086
|
+
if (
|
|
2087
|
+
updatedIncidentData.labels &&
|
|
2088
|
+
Array.isArray(updatedIncidentData.labels)
|
|
2089
|
+
) {
|
|
2090
|
+
try {
|
|
2091
|
+
await this.refreshReminderSchedule({
|
|
2092
|
+
incidentId: incidentId,
|
|
2093
|
+
projectId: projectId,
|
|
2094
|
+
});
|
|
2095
|
+
} catch (reminderError) {
|
|
2096
|
+
logger.error(
|
|
2097
|
+
`Reminder rescheduling failed in IncidentService.onUpdateSuccess: ${reminderError}`,
|
|
2098
|
+
{
|
|
2099
|
+
projectId: projectId?.toString(),
|
|
2100
|
+
incidentId: incidentId?.toString(),
|
|
2101
|
+
} as LogAttributes,
|
|
2102
|
+
);
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
|
|
1979
2106
|
if (
|
|
1980
2107
|
updatedIncidentData.incidentSeverity &&
|
|
1981
2108
|
(updatedIncidentData.incidentSeverity as any)._id
|
|
@@ -2017,6 +2144,22 @@ ${incidentSeverity.name}
|
|
|
2017
2144
|
);
|
|
2018
2145
|
}
|
|
2019
2146
|
|
|
2147
|
+
// Re-match reminder rule when severity changes
|
|
2148
|
+
try {
|
|
2149
|
+
await this.refreshReminderSchedule({
|
|
2150
|
+
incidentId: incidentId,
|
|
2151
|
+
projectId: projectId,
|
|
2152
|
+
});
|
|
2153
|
+
} catch (reminderError) {
|
|
2154
|
+
logger.error(
|
|
2155
|
+
`Reminder rescheduling failed in IncidentService.onUpdateSuccess: ${reminderError}`,
|
|
2156
|
+
{
|
|
2157
|
+
projectId: projectId?.toString(),
|
|
2158
|
+
incidentId: incidentId?.toString(),
|
|
2159
|
+
} as LogAttributes,
|
|
2160
|
+
);
|
|
2161
|
+
}
|
|
2162
|
+
|
|
2020
2163
|
// emit severity change metric
|
|
2021
2164
|
try {
|
|
2022
2165
|
const severityChangeMetric: MutableMetric = new MutableMetric();
|
package/Server/Services/Index.ts
CHANGED
|
@@ -226,6 +226,9 @@ import IncidentEpisodePublicNoteService from "./IncidentEpisodePublicNoteService
|
|
|
226
226
|
import AlertGroupingRuleService from "./AlertGroupingRuleService";
|
|
227
227
|
import IncidentSlaRuleService from "./IncidentSlaRuleService";
|
|
228
228
|
import IncidentSlaService from "./IncidentSlaService";
|
|
229
|
+
import IncidentReminderRuleService from "./IncidentReminderRuleService";
|
|
230
|
+
import AlertReminderRuleService from "./AlertReminderRuleService";
|
|
231
|
+
import ScheduledMaintenanceReminderRuleService from "./ScheduledMaintenanceReminderRuleService";
|
|
229
232
|
|
|
230
233
|
import TableViewService from "./TableViewService";
|
|
231
234
|
import ScheduledMaintenanceFeedService from "./ScheduledMaintenanceFeedService";
|
|
@@ -481,6 +484,9 @@ const services: Array<BaseService> = [
|
|
|
481
484
|
AlertGroupingRuleService,
|
|
482
485
|
IncidentSlaRuleService,
|
|
483
486
|
IncidentSlaService,
|
|
487
|
+
IncidentReminderRuleService,
|
|
488
|
+
AlertReminderRuleService,
|
|
489
|
+
ScheduledMaintenanceReminderRuleService,
|
|
484
490
|
|
|
485
491
|
TableViewService,
|
|
486
492
|
MonitorTestService,
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import CreateBy from "../Types/Database/CreateBy";
|
|
2
|
+
import { OnCreate, OnDelete, OnUpdate } from "../Types/Database/Hooks";
|
|
3
|
+
import SortOrder from "../../Types/BaseDatabase/SortOrder";
|
|
4
|
+
import DatabaseService from "./DatabaseService";
|
|
5
|
+
import Model from "../../Models/DatabaseModels/ScheduledMaintenanceReminderRule";
|
|
6
|
+
import ScheduledMaintenance from "../../Models/DatabaseModels/ScheduledMaintenance";
|
|
7
|
+
import Label from "../../Models/DatabaseModels/Label";
|
|
8
|
+
import ScheduledMaintenanceState from "../../Models/DatabaseModels/ScheduledMaintenanceState";
|
|
9
|
+
import ScheduledMaintenanceService from "./ScheduledMaintenanceService";
|
|
10
|
+
import ScheduledMaintenanceStateService from "./ScheduledMaintenanceStateService";
|
|
11
|
+
import QueryHelper from "../Types/Database/QueryHelper";
|
|
12
|
+
import LIMIT_MAX, { LIMIT_PER_PROJECT } from "../../Types/Database/LimitMax";
|
|
13
|
+
import ObjectID from "../../Types/ObjectID";
|
|
14
|
+
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
|
|
15
|
+
import logger, { LogAttributes } from "../Utils/Logger";
|
|
16
|
+
import { IsBillingEnabled } from "../EnvironmentConfig";
|
|
17
|
+
|
|
18
|
+
export class Service extends DatabaseService<Model> {
|
|
19
|
+
public constructor() {
|
|
20
|
+
super(Model);
|
|
21
|
+
if (IsBillingEnabled) {
|
|
22
|
+
this.hardDeleteItemsOlderThanInDays("createdAt", 3 * 365); // 3 years
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@CaptureSpan()
|
|
27
|
+
protected override async onBeforeCreate(
|
|
28
|
+
createBy: CreateBy<Model>,
|
|
29
|
+
): Promise<OnCreate<Model>> {
|
|
30
|
+
// Auto-assign order on creation
|
|
31
|
+
if (!createBy.data.order) {
|
|
32
|
+
const highestOrderRule: Model | null = await this.findOneBy({
|
|
33
|
+
query: {
|
|
34
|
+
projectId: createBy.data.projectId!,
|
|
35
|
+
},
|
|
36
|
+
select: {
|
|
37
|
+
order: true,
|
|
38
|
+
},
|
|
39
|
+
sort: {
|
|
40
|
+
order: SortOrder.Descending,
|
|
41
|
+
},
|
|
42
|
+
props: {
|
|
43
|
+
isRoot: true,
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
createBy.data.order = (highestOrderRule?.order || 0) + 1;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
createBy,
|
|
52
|
+
carryForward: null,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@CaptureSpan()
|
|
57
|
+
protected override async onCreateSuccess(
|
|
58
|
+
_onCreate: OnCreate<Model>,
|
|
59
|
+
createdItem: Model,
|
|
60
|
+
): Promise<Model> {
|
|
61
|
+
if (createdItem.projectId) {
|
|
62
|
+
await this.refreshSchedulesForOpenScheduledMaintenances(
|
|
63
|
+
createdItem.projectId,
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return createdItem;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
@CaptureSpan()
|
|
71
|
+
protected override async onUpdateSuccess(
|
|
72
|
+
onUpdate: OnUpdate<Model>,
|
|
73
|
+
_updatedItemIds: Array<ObjectID>,
|
|
74
|
+
): Promise<OnUpdate<Model>> {
|
|
75
|
+
const projectId: ObjectID | undefined = onUpdate.updateBy.props.tenantId as
|
|
76
|
+
| ObjectID
|
|
77
|
+
| undefined;
|
|
78
|
+
|
|
79
|
+
if (projectId) {
|
|
80
|
+
await this.refreshSchedulesForOpenScheduledMaintenances(projectId);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return onUpdate;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
@CaptureSpan()
|
|
87
|
+
protected override async onDeleteSuccess(
|
|
88
|
+
onDelete: OnDelete<Model>,
|
|
89
|
+
_itemIdsBeforeDelete: Array<ObjectID>,
|
|
90
|
+
): Promise<OnDelete<Model>> {
|
|
91
|
+
const projectId: ObjectID | undefined = onDelete.deleteBy.props.tenantId as
|
|
92
|
+
| ObjectID
|
|
93
|
+
| undefined;
|
|
94
|
+
|
|
95
|
+
if (projectId) {
|
|
96
|
+
await this.refreshSchedulesForOpenScheduledMaintenances(projectId);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return onDelete;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
@CaptureSpan()
|
|
103
|
+
public async refreshSchedulesForOpenScheduledMaintenances(
|
|
104
|
+
projectId: ObjectID,
|
|
105
|
+
): Promise<void> {
|
|
106
|
+
try {
|
|
107
|
+
const openStates: Array<ScheduledMaintenanceState> =
|
|
108
|
+
await ScheduledMaintenanceStateService.findBy({
|
|
109
|
+
query: {
|
|
110
|
+
projectId: projectId,
|
|
111
|
+
isResolvedState: false,
|
|
112
|
+
},
|
|
113
|
+
select: {
|
|
114
|
+
_id: true,
|
|
115
|
+
},
|
|
116
|
+
limit: LIMIT_PER_PROJECT,
|
|
117
|
+
skip: 0,
|
|
118
|
+
props: {
|
|
119
|
+
isRoot: true,
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const openStateIds: Array<ObjectID> = openStates
|
|
124
|
+
.map((state: ScheduledMaintenanceState) => {
|
|
125
|
+
return state.id!;
|
|
126
|
+
})
|
|
127
|
+
.filter(Boolean);
|
|
128
|
+
|
|
129
|
+
if (openStateIds.length === 0) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const openScheduledMaintenances: Array<ScheduledMaintenance> =
|
|
134
|
+
await ScheduledMaintenanceService.findBy({
|
|
135
|
+
query: {
|
|
136
|
+
projectId: projectId,
|
|
137
|
+
currentScheduledMaintenanceStateId: QueryHelper.any(openStateIds),
|
|
138
|
+
},
|
|
139
|
+
select: {
|
|
140
|
+
_id: true,
|
|
141
|
+
},
|
|
142
|
+
limit: LIMIT_MAX,
|
|
143
|
+
skip: 0,
|
|
144
|
+
props: {
|
|
145
|
+
isRoot: true,
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
for (const scheduledMaintenance of openScheduledMaintenances) {
|
|
150
|
+
try {
|
|
151
|
+
await ScheduledMaintenanceService.refreshReminderSchedule({
|
|
152
|
+
scheduledMaintenanceId: scheduledMaintenance.id!,
|
|
153
|
+
projectId: projectId,
|
|
154
|
+
});
|
|
155
|
+
} catch (error) {
|
|
156
|
+
logger.error(
|
|
157
|
+
`Failed to refresh reminder schedule for scheduled maintenance ${scheduledMaintenance.id}: ${error}`,
|
|
158
|
+
{ projectId: projectId?.toString() } as LogAttributes,
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
} catch (error) {
|
|
163
|
+
logger.error(
|
|
164
|
+
`Failed to refresh reminder schedules for open scheduled maintenances: ${error}`,
|
|
165
|
+
{ projectId: projectId?.toString() } as LogAttributes,
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
@CaptureSpan()
|
|
171
|
+
public async findMatchingRule(data: {
|
|
172
|
+
projectId: ObjectID;
|
|
173
|
+
labelIds?: Array<ObjectID> | undefined;
|
|
174
|
+
}): Promise<Model | null> {
|
|
175
|
+
// Get all enabled rules sorted by order. First matching rule wins.
|
|
176
|
+
const rules: Array<Model> = await this.findBy({
|
|
177
|
+
query: {
|
|
178
|
+
projectId: data.projectId,
|
|
179
|
+
isEnabled: true,
|
|
180
|
+
},
|
|
181
|
+
sort: {
|
|
182
|
+
order: SortOrder.Ascending,
|
|
183
|
+
},
|
|
184
|
+
select: {
|
|
185
|
+
_id: true,
|
|
186
|
+
name: true,
|
|
187
|
+
order: true,
|
|
188
|
+
reminderIntervalInMinutes: true,
|
|
189
|
+
stopRemindersOnState: true,
|
|
190
|
+
remindWhileScheduled: true,
|
|
191
|
+
labels: {
|
|
192
|
+
_id: true,
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
limit: 100,
|
|
196
|
+
skip: 0,
|
|
197
|
+
props: {
|
|
198
|
+
isRoot: true,
|
|
199
|
+
},
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
for (const rule of rules) {
|
|
203
|
+
if (rule.labels && rule.labels.length > 0) {
|
|
204
|
+
if (!data.labelIds || data.labelIds.length === 0) {
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const ruleLabelIds: Array<string> = rule.labels.map((label: Label) => {
|
|
209
|
+
return label.id?.toString() || "";
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
const hasMatchingLabel: boolean = data.labelIds.some(
|
|
213
|
+
(labelId: ObjectID) => {
|
|
214
|
+
return ruleLabelIds.includes(labelId.toString());
|
|
215
|
+
},
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
if (!hasMatchingLabel) {
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Rule with no labels matches all scheduled maintenances.
|
|
224
|
+
logger.debug(
|
|
225
|
+
`Scheduled maintenance reminder rule ${rule.name || rule.id} matched for project ${data.projectId}`,
|
|
226
|
+
{ projectId: data.projectId?.toString() } as LogAttributes,
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
return rule;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export default new Service();
|
|
@@ -8,6 +8,8 @@ import ScheduledMaintenanceOwnerTeamService from "./ScheduledMaintenanceOwnerTea
|
|
|
8
8
|
import ScheduledMaintenanceOwnerUserService from "./ScheduledMaintenanceOwnerUserService";
|
|
9
9
|
import ScheduledMaintenanceStateService from "./ScheduledMaintenanceStateService";
|
|
10
10
|
import ScheduledMaintenanceStateTimelineService from "./ScheduledMaintenanceStateTimelineService";
|
|
11
|
+
import ScheduledMaintenanceReminderRuleService from "./ScheduledMaintenanceReminderRuleService";
|
|
12
|
+
import ScheduledMaintenanceReminderRule from "../../Models/DatabaseModels/ScheduledMaintenanceReminderRule";
|
|
11
13
|
import TeamMemberService from "./TeamMemberService";
|
|
12
14
|
import URL from "../../Types/API/URL";
|
|
13
15
|
import DatabaseCommonInteractionProps from "../../Types/BaseDatabase/DatabaseCommonInteractionProps";
|
|
@@ -809,6 +811,80 @@ ${resourcesAffected ? `**Resources Affected:** ${resourcesAffected}` : ""}
|
|
|
809
811
|
return { createBy, carryForward: null };
|
|
810
812
|
}
|
|
811
813
|
|
|
814
|
+
@CaptureSpan()
|
|
815
|
+
public async refreshReminderSchedule(data: {
|
|
816
|
+
scheduledMaintenanceId: ObjectID;
|
|
817
|
+
projectId: ObjectID;
|
|
818
|
+
}): Promise<void> {
|
|
819
|
+
const scheduledMaintenance: Model | null = await this.findOneById({
|
|
820
|
+
id: data.scheduledMaintenanceId,
|
|
821
|
+
select: {
|
|
822
|
+
enableReminders: true,
|
|
823
|
+
startsAt: true,
|
|
824
|
+
labels: {
|
|
825
|
+
_id: true,
|
|
826
|
+
},
|
|
827
|
+
},
|
|
828
|
+
props: {
|
|
829
|
+
isRoot: true,
|
|
830
|
+
},
|
|
831
|
+
});
|
|
832
|
+
|
|
833
|
+
if (!scheduledMaintenance) {
|
|
834
|
+
return;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
let nextReminderNotificationAt: Date | null = null;
|
|
838
|
+
|
|
839
|
+
if (scheduledMaintenance.enableReminders !== false) {
|
|
840
|
+
const matchingRule: ScheduledMaintenanceReminderRule | null =
|
|
841
|
+
await ScheduledMaintenanceReminderRuleService.findMatchingRule({
|
|
842
|
+
projectId: data.projectId,
|
|
843
|
+
labelIds: scheduledMaintenance.labels?.map((label: Label) => {
|
|
844
|
+
return label.id!;
|
|
845
|
+
}),
|
|
846
|
+
});
|
|
847
|
+
|
|
848
|
+
if (
|
|
849
|
+
matchingRule &&
|
|
850
|
+
matchingRule.reminderIntervalInMinutes &&
|
|
851
|
+
!(await this.isScheduledMaintenanceCompleted({
|
|
852
|
+
scheduledMaintenanceId: data.scheduledMaintenanceId,
|
|
853
|
+
}))
|
|
854
|
+
) {
|
|
855
|
+
/*
|
|
856
|
+
* When the rule does not remind while the event is still scheduled,
|
|
857
|
+
* defer the first reminder until after the event has started so that
|
|
858
|
+
* owners are not notified about an event that has not begun yet.
|
|
859
|
+
*/
|
|
860
|
+
let referenceDate: Date = OneUptimeDate.getCurrentDate();
|
|
861
|
+
|
|
862
|
+
if (
|
|
863
|
+
!matchingRule.remindWhileScheduled &&
|
|
864
|
+
scheduledMaintenance.startsAt &&
|
|
865
|
+
OneUptimeDate.isInTheFuture(scheduledMaintenance.startsAt)
|
|
866
|
+
) {
|
|
867
|
+
referenceDate = scheduledMaintenance.startsAt;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
nextReminderNotificationAt = OneUptimeDate.addRemoveMinutes(
|
|
871
|
+
referenceDate,
|
|
872
|
+
matchingRule.reminderIntervalInMinutes,
|
|
873
|
+
);
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
await this.updateOneById({
|
|
878
|
+
id: data.scheduledMaintenanceId,
|
|
879
|
+
data: {
|
|
880
|
+
nextReminderNotificationAt: nextReminderNotificationAt,
|
|
881
|
+
},
|
|
882
|
+
props: {
|
|
883
|
+
isRoot: true,
|
|
884
|
+
},
|
|
885
|
+
});
|
|
886
|
+
}
|
|
887
|
+
|
|
812
888
|
@CaptureSpan()
|
|
813
889
|
protected override async onCreateSuccess(
|
|
814
890
|
onCreate: OnCreate<Model>,
|
|
@@ -985,6 +1061,26 @@ ${resourcesAffected ? `**Resources Affected:** ${resourcesAffected}` : ""}
|
|
|
985
1061
|
);
|
|
986
1062
|
}
|
|
987
1063
|
})
|
|
1064
|
+
.then(async () => {
|
|
1065
|
+
// Schedule reminder notifications for scheduled maintenance if a matching reminder rule exists
|
|
1066
|
+
try {
|
|
1067
|
+
if (createdItem.projectId && createdItem.id) {
|
|
1068
|
+
await this.refreshReminderSchedule({
|
|
1069
|
+
scheduledMaintenanceId: createdItem.id,
|
|
1070
|
+
projectId: createdItem.projectId,
|
|
1071
|
+
});
|
|
1072
|
+
}
|
|
1073
|
+
} catch (error) {
|
|
1074
|
+
logger.error(
|
|
1075
|
+
`Reminder scheduling failed in ScheduledMaintenanceService.onCreateSuccess: ${error}`,
|
|
1076
|
+
{
|
|
1077
|
+
projectId: createdItem.projectId?.toString(),
|
|
1078
|
+
scheduledMaintenanceId: createdItem.id?.toString(),
|
|
1079
|
+
userId: createdItem.createdByUserId?.toString(),
|
|
1080
|
+
} as LogAttributes,
|
|
1081
|
+
);
|
|
1082
|
+
}
|
|
1083
|
+
})
|
|
988
1084
|
.catch((error: Error) => {
|
|
989
1085
|
logger.error(
|
|
990
1086
|
`Critical error in ScheduledMaintenanceService sequential operations: ${error}`,
|
|
@@ -1577,6 +1673,37 @@ ${labels
|
|
|
1577
1673
|
},
|
|
1578
1674
|
);
|
|
1579
1675
|
}
|
|
1676
|
+
|
|
1677
|
+
/*
|
|
1678
|
+
* Re-evaluate reminder schedule when reminders are enabled or disabled,
|
|
1679
|
+
* or when labels change (which may change the matching reminder rule).
|
|
1680
|
+
*/
|
|
1681
|
+
if (
|
|
1682
|
+
onUpdate.updateBy.props.tenantId &&
|
|
1683
|
+
(Object.prototype.hasOwnProperty.call(
|
|
1684
|
+
onUpdate.updateBy.data,
|
|
1685
|
+
"enableReminders",
|
|
1686
|
+
) ||
|
|
1687
|
+
Object.prototype.hasOwnProperty.call(
|
|
1688
|
+
onUpdate.updateBy.data,
|
|
1689
|
+
"labels",
|
|
1690
|
+
))
|
|
1691
|
+
) {
|
|
1692
|
+
try {
|
|
1693
|
+
await this.refreshReminderSchedule({
|
|
1694
|
+
scheduledMaintenanceId: scheduledMaintenanceId,
|
|
1695
|
+
projectId: onUpdate.updateBy.props.tenantId as ObjectID,
|
|
1696
|
+
});
|
|
1697
|
+
} catch (reminderError) {
|
|
1698
|
+
logger.error(
|
|
1699
|
+
`Reminder rescheduling failed in ScheduledMaintenanceService.onUpdateSuccess: ${reminderError}`,
|
|
1700
|
+
{
|
|
1701
|
+
projectId: onUpdate.updateBy.props.tenantId?.toString(),
|
|
1702
|
+
scheduledMaintenanceId: scheduledMaintenanceId?.toString(),
|
|
1703
|
+
} as LogAttributes,
|
|
1704
|
+
);
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1580
1707
|
}
|
|
1581
1708
|
}
|
|
1582
1709
|
|