@oneuptime/common 7.0.4137 → 7.0.4142
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/Server/Services/AlertStateTimelineService.ts +196 -93
- package/Server/Services/IncidentStateTimelineService.ts +200 -87
- package/Server/Services/MonitorStatusTimelineService.ts +35 -1
- package/Server/Services/ScheduledMaintenanceStateTimelineService.ts +192 -80
- package/Server/Utils/Monitor/MonitorResource.ts +0 -1
- package/build/dist/Server/Services/AlertStateTimelineService.js +165 -85
- package/build/dist/Server/Services/AlertStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/IncidentStateTimelineService.js +166 -81
- package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/MonitorStatusTimelineService.js +21 -1
- package/build/dist/Server/Services/MonitorStatusTimelineService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js +157 -72
- package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorResource.js +0 -1
- package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
- package/package.json +2 -2
|
@@ -37,7 +37,7 @@ export class Service extends DatabaseService<MonitorStatusTimeline> {
|
|
|
37
37
|
try {
|
|
38
38
|
mutex = await Semaphore.lock({
|
|
39
39
|
key: createBy.data.monitorId.toString(),
|
|
40
|
-
namespace: "MonitorStatusTimeline.
|
|
40
|
+
namespace: "MonitorStatusTimeline.create",
|
|
41
41
|
});
|
|
42
42
|
} catch (e) {
|
|
43
43
|
logger.error(e);
|
|
@@ -73,6 +73,13 @@ export class Service extends DatabaseService<MonitorStatusTimeline> {
|
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
const monitorStatusId: ObjectID | undefined | null =
|
|
77
|
+
createBy.data.monitorStatusId || createBy.data.monitorStatus?.id;
|
|
78
|
+
|
|
79
|
+
if (!monitorStatusId) {
|
|
80
|
+
throw new BadDataException("monitorStatusId is null");
|
|
81
|
+
}
|
|
82
|
+
|
|
76
83
|
const stateBeforeThis: MonitorStatusTimeline | null = await this.findOneBy({
|
|
77
84
|
query: {
|
|
78
85
|
monitorId: createBy.data.monitorId,
|
|
@@ -100,6 +107,20 @@ export class Service extends DatabaseService<MonitorStatusTimeline> {
|
|
|
100
107
|
createBy.data.isOwnerNotified = true;
|
|
101
108
|
}
|
|
102
109
|
|
|
110
|
+
// check if this new state and the previous state are same.
|
|
111
|
+
// if yes, then throw bad data exception.
|
|
112
|
+
|
|
113
|
+
if (stateBeforeThis && stateBeforeThis.monitorStatusId && monitorStatusId) {
|
|
114
|
+
if (
|
|
115
|
+
stateBeforeThis.monitorStatusId.toString() ===
|
|
116
|
+
monitorStatusId.toString()
|
|
117
|
+
) {
|
|
118
|
+
throw new BadDataException(
|
|
119
|
+
"Monitor Status cannot be same as previous status.",
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
103
124
|
const stateAfterThis: MonitorStatusTimeline | null = await this.findOneBy({
|
|
104
125
|
query: {
|
|
105
126
|
monitorId: createBy.data.monitorId,
|
|
@@ -123,6 +144,19 @@ export class Service extends DatabaseService<MonitorStatusTimeline> {
|
|
|
123
144
|
createBy.data.endsAt = stateAfterThis.startsAt;
|
|
124
145
|
}
|
|
125
146
|
|
|
147
|
+
// check if this new state and the previous state are same.
|
|
148
|
+
// if yes, then throw bad data exception.
|
|
149
|
+
|
|
150
|
+
if (stateAfterThis && stateAfterThis.monitorStatusId && monitorStatusId) {
|
|
151
|
+
if (
|
|
152
|
+
stateAfterThis.monitorStatusId.toString() === monitorStatusId.toString()
|
|
153
|
+
) {
|
|
154
|
+
throw new BadDataException(
|
|
155
|
+
"Monitor Status cannot be same as next status.",
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
126
160
|
logger.debug("State After this");
|
|
127
161
|
logger.debug(stateAfterThis);
|
|
128
162
|
|
|
@@ -29,6 +29,7 @@ import logger from "../Utils/Logger";
|
|
|
29
29
|
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
|
|
30
30
|
import { LIMIT_PER_PROJECT } from "../../Types/Database/LimitMax";
|
|
31
31
|
import WorkspaceNotificationRuleService from "./WorkspaceNotificationRuleService";
|
|
32
|
+
import Semaphore, { SemaphoreMutex } from "../Infrastructure/Semaphore";
|
|
32
33
|
|
|
33
34
|
export class Service extends DatabaseService<ScheduledMaintenanceStateTimeline> {
|
|
34
35
|
public constructor() {
|
|
@@ -93,96 +94,197 @@ export class Service extends DatabaseService<ScheduledMaintenanceStateTimeline>
|
|
|
93
94
|
throw new BadDataException("scheduledMaintenanceId is null");
|
|
94
95
|
}
|
|
95
96
|
|
|
96
|
-
|
|
97
|
-
createBy.data.startsAt = OneUptimeDate.getCurrentDate();
|
|
98
|
-
}
|
|
97
|
+
let mutex: SemaphoreMutex | null = null;
|
|
99
98
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
props: {
|
|
110
|
-
isRoot: true,
|
|
111
|
-
},
|
|
112
|
-
select: {
|
|
113
|
-
scheduledMaintenanceStateId: true,
|
|
114
|
-
startsAt: true,
|
|
115
|
-
endsAt: true,
|
|
116
|
-
},
|
|
117
|
-
});
|
|
99
|
+
try {
|
|
100
|
+
try {
|
|
101
|
+
mutex = await Semaphore.lock({
|
|
102
|
+
key: createBy.data.scheduledMaintenanceId.toString(),
|
|
103
|
+
namespace: "ScheduledMaintenanceStateTimeline.create",
|
|
104
|
+
});
|
|
105
|
+
} catch (err) {
|
|
106
|
+
logger.error(err);
|
|
107
|
+
}
|
|
118
108
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
createBy.data.isOwnerNotified = true;
|
|
123
|
-
}
|
|
109
|
+
if (!createBy.data.startsAt) {
|
|
110
|
+
createBy.data.startsAt = OneUptimeDate.getCurrentDate();
|
|
111
|
+
}
|
|
124
112
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
scheduledMaintenanceId: createBy.data.scheduledMaintenanceId,
|
|
129
|
-
startsAt: QueryHelper.greaterThan(createBy.data.startsAt),
|
|
130
|
-
},
|
|
131
|
-
sort: {
|
|
132
|
-
startsAt: SortOrder.Ascending,
|
|
133
|
-
},
|
|
134
|
-
props: {
|
|
135
|
-
isRoot: true,
|
|
136
|
-
},
|
|
137
|
-
select: {
|
|
138
|
-
scheduledMaintenanceStateId: true,
|
|
139
|
-
startsAt: true,
|
|
140
|
-
endsAt: true,
|
|
141
|
-
},
|
|
142
|
-
});
|
|
113
|
+
const scheduledMaintenanceStateId: ObjectID | undefined | null =
|
|
114
|
+
createBy.data.scheduledMaintenanceStateId ||
|
|
115
|
+
createBy.data.scheduledMaintenanceState?.id;
|
|
143
116
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
117
|
+
if (!scheduledMaintenanceStateId) {
|
|
118
|
+
throw new BadDataException("scheduledMaintenanceStateId is null");
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const stateBeforeThis: ScheduledMaintenanceStateTimeline | null =
|
|
122
|
+
await this.findOneBy({
|
|
123
|
+
query: {
|
|
124
|
+
scheduledMaintenanceId: createBy.data.scheduledMaintenanceId,
|
|
125
|
+
startsAt: QueryHelper.lessThanEqualTo(createBy.data.startsAt),
|
|
126
|
+
},
|
|
127
|
+
sort: {
|
|
128
|
+
startsAt: SortOrder.Descending,
|
|
129
|
+
},
|
|
130
|
+
props: {
|
|
131
|
+
isRoot: true,
|
|
132
|
+
},
|
|
133
|
+
select: {
|
|
134
|
+
scheduledMaintenanceStateId: true,
|
|
135
|
+
scheduledMaintenanceState: {
|
|
136
|
+
order: true,
|
|
137
|
+
name: true,
|
|
138
|
+
},
|
|
139
|
+
startsAt: true,
|
|
140
|
+
endsAt: true,
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// If this is the first state, then do not notify the owner.
|
|
145
|
+
if (!stateBeforeThis) {
|
|
146
|
+
// since this is the first status, do not notify the owner.
|
|
147
|
+
createBy.data.isOwnerNotified = true;
|
|
148
|
+
}
|
|
148
149
|
|
|
149
|
-
const publicNote: string | undefined = (
|
|
150
|
-
createBy.miscDataProps as JSONObject | undefined
|
|
151
|
-
)?.["publicNote"] as string | undefined;
|
|
152
|
-
|
|
153
|
-
if (publicNote) {
|
|
154
|
-
const scheduledMaintenancePublicNote: ScheduledMaintenancePublicNote =
|
|
155
|
-
new ScheduledMaintenancePublicNote();
|
|
156
|
-
scheduledMaintenancePublicNote.scheduledMaintenanceId =
|
|
157
|
-
createBy.data.scheduledMaintenanceId;
|
|
158
|
-
scheduledMaintenancePublicNote.note = publicNote;
|
|
159
|
-
scheduledMaintenancePublicNote.postedAt = createBy.data.startsAt;
|
|
160
|
-
scheduledMaintenancePublicNote.createdAt = createBy.data.startsAt;
|
|
161
|
-
scheduledMaintenancePublicNote.projectId = createBy.data.projectId!;
|
|
162
|
-
scheduledMaintenancePublicNote.shouldStatusPageSubscribersBeNotifiedOnNoteCreated =
|
|
163
|
-
Boolean(createBy.data.shouldStatusPageSubscribersBeNotified);
|
|
164
|
-
|
|
165
|
-
// mark status page subscribers as notified for this state change because we dont want to send duplicate (two) emails one for public note and one for state change.
|
|
166
150
|
if (
|
|
167
|
-
|
|
151
|
+
stateBeforeThis &&
|
|
152
|
+
stateBeforeThis.scheduledMaintenanceStateId &&
|
|
153
|
+
scheduledMaintenanceStateId
|
|
168
154
|
) {
|
|
169
|
-
|
|
155
|
+
if (
|
|
156
|
+
stateBeforeThis.scheduledMaintenanceStateId.toString() ===
|
|
157
|
+
scheduledMaintenanceStateId.toString()
|
|
158
|
+
) {
|
|
159
|
+
throw new BadDataException(
|
|
160
|
+
"Scheduled Maintenance state cannot be same as previous state.",
|
|
161
|
+
);
|
|
162
|
+
}
|
|
170
163
|
}
|
|
171
164
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
165
|
+
if (stateBeforeThis && stateBeforeThis.scheduledMaintenanceState?.order) {
|
|
166
|
+
const newScheduledMaintenanceState: ScheduledMaintenanceState | null =
|
|
167
|
+
await ScheduledMaintenanceStateService.findOneBy({
|
|
168
|
+
query: {
|
|
169
|
+
_id: scheduledMaintenanceStateId,
|
|
170
|
+
},
|
|
171
|
+
select: {
|
|
172
|
+
order: true,
|
|
173
|
+
name: true,
|
|
174
|
+
},
|
|
175
|
+
props: {
|
|
176
|
+
isRoot: true,
|
|
177
|
+
},
|
|
178
|
+
});
|
|
177
179
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
180
|
+
if (
|
|
181
|
+
newScheduledMaintenanceState &&
|
|
182
|
+
newScheduledMaintenanceState.order
|
|
183
|
+
) {
|
|
184
|
+
// check if the new scheduledMaintenance state is in order is greater than the previous state order
|
|
185
|
+
if (
|
|
186
|
+
stateBeforeThis &&
|
|
187
|
+
stateBeforeThis.scheduledMaintenanceState &&
|
|
188
|
+
stateBeforeThis.scheduledMaintenanceState.order &&
|
|
189
|
+
newScheduledMaintenanceState.order <=
|
|
190
|
+
stateBeforeThis.scheduledMaintenanceState.order
|
|
191
|
+
) {
|
|
192
|
+
throw new BadDataException(
|
|
193
|
+
`ScheduledMaintenance cannot transition to ${newScheduledMaintenanceState.name} state from ${stateBeforeThis.scheduledMaintenanceState.name} state because ${newScheduledMaintenanceState.name} is before ${stateBeforeThis.scheduledMaintenanceState.name} in the order of scheduledMaintenance states.`,
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const stateAfterThis: ScheduledMaintenanceStateTimeline | null =
|
|
200
|
+
await this.findOneBy({
|
|
201
|
+
query: {
|
|
202
|
+
scheduledMaintenanceId: createBy.data.scheduledMaintenanceId,
|
|
203
|
+
startsAt: QueryHelper.greaterThan(createBy.data.startsAt),
|
|
204
|
+
},
|
|
205
|
+
sort: {
|
|
206
|
+
startsAt: SortOrder.Ascending,
|
|
207
|
+
},
|
|
208
|
+
props: {
|
|
209
|
+
isRoot: true,
|
|
210
|
+
},
|
|
211
|
+
select: {
|
|
212
|
+
scheduledMaintenanceStateId: true,
|
|
213
|
+
startsAt: true,
|
|
214
|
+
endsAt: true,
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// compute ends at. It's the start of the next status.
|
|
219
|
+
if (stateAfterThis && stateAfterThis.startsAt) {
|
|
220
|
+
createBy.data.endsAt = stateAfterThis.startsAt;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (
|
|
224
|
+
stateAfterThis &&
|
|
225
|
+
stateAfterThis.scheduledMaintenanceStateId &&
|
|
226
|
+
scheduledMaintenanceStateId
|
|
227
|
+
) {
|
|
228
|
+
if (
|
|
229
|
+
stateAfterThis.scheduledMaintenanceStateId.toString() ===
|
|
230
|
+
scheduledMaintenanceStateId.toString()
|
|
231
|
+
) {
|
|
232
|
+
throw new BadDataException(
|
|
233
|
+
"Scheduled Maintenance state cannot be same as next state.",
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const publicNote: string | undefined = (
|
|
239
|
+
createBy.miscDataProps as JSONObject | undefined
|
|
240
|
+
)?.["publicNote"] as string | undefined;
|
|
241
|
+
|
|
242
|
+
if (publicNote) {
|
|
243
|
+
const scheduledMaintenancePublicNote: ScheduledMaintenancePublicNote =
|
|
244
|
+
new ScheduledMaintenancePublicNote();
|
|
245
|
+
scheduledMaintenancePublicNote.scheduledMaintenanceId =
|
|
246
|
+
createBy.data.scheduledMaintenanceId;
|
|
247
|
+
scheduledMaintenancePublicNote.note = publicNote;
|
|
248
|
+
scheduledMaintenancePublicNote.postedAt = createBy.data.startsAt;
|
|
249
|
+
scheduledMaintenancePublicNote.createdAt = createBy.data.startsAt;
|
|
250
|
+
scheduledMaintenancePublicNote.projectId = createBy.data.projectId!;
|
|
251
|
+
scheduledMaintenancePublicNote.shouldStatusPageSubscribersBeNotifiedOnNoteCreated =
|
|
252
|
+
Boolean(createBy.data.shouldStatusPageSubscribersBeNotified);
|
|
253
|
+
|
|
254
|
+
// mark status page subscribers as notified for this state change because we dont want to send duplicate (two) emails one for public note and one for state change.
|
|
255
|
+
if (
|
|
256
|
+
scheduledMaintenancePublicNote.shouldStatusPageSubscribersBeNotifiedOnNoteCreated
|
|
257
|
+
) {
|
|
258
|
+
createBy.data.isStatusPageSubscribersNotified = true;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
await ScheduledMaintenancePublicNoteService.create({
|
|
262
|
+
data: scheduledMaintenancePublicNote,
|
|
263
|
+
props: createBy.props,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return {
|
|
268
|
+
createBy,
|
|
269
|
+
carryForward: {
|
|
270
|
+
statusTimelineBeforeThisStatus: stateBeforeThis || null,
|
|
271
|
+
statusTimelineAfterThisStatus: stateAfterThis || null,
|
|
272
|
+
publicNote: publicNote,
|
|
273
|
+
mutex: mutex,
|
|
274
|
+
},
|
|
275
|
+
};
|
|
276
|
+
} catch (error) {
|
|
277
|
+
// release the mutex if it was acquired.
|
|
278
|
+
if (mutex) {
|
|
279
|
+
try {
|
|
280
|
+
await Semaphore.release(mutex);
|
|
281
|
+
} catch (err) {
|
|
282
|
+
logger.error(err);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
throw error;
|
|
287
|
+
}
|
|
186
288
|
}
|
|
187
289
|
|
|
188
290
|
@CaptureSpan()
|
|
@@ -190,6 +292,8 @@ export class Service extends DatabaseService<ScheduledMaintenanceStateTimeline>
|
|
|
190
292
|
onCreate: OnCreate<ScheduledMaintenanceStateTimeline>,
|
|
191
293
|
createdItem: ScheduledMaintenanceStateTimeline,
|
|
192
294
|
): Promise<ScheduledMaintenanceStateTimeline> {
|
|
295
|
+
const mutex: SemaphoreMutex | null = onCreate.carryForward.mutex;
|
|
296
|
+
|
|
193
297
|
if (!createdItem.scheduledMaintenanceId) {
|
|
194
298
|
throw new BadDataException("scheduledMaintenanceId is null");
|
|
195
299
|
}
|
|
@@ -266,6 +370,14 @@ export class Service extends DatabaseService<ScheduledMaintenanceStateTimeline>
|
|
|
266
370
|
});
|
|
267
371
|
}
|
|
268
372
|
|
|
373
|
+
if (mutex) {
|
|
374
|
+
try {
|
|
375
|
+
await Semaphore.release(mutex);
|
|
376
|
+
} catch (err) {
|
|
377
|
+
logger.error(err);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
269
381
|
const scheduledMaintenanceState: ScheduledMaintenanceState | null =
|
|
270
382
|
await ScheduledMaintenanceStateService.findOneBy({
|
|
271
383
|
query: {
|
|
@@ -44,7 +44,6 @@ import { TelemetryQuery } from "../../../Types/Telemetry/TelemetryQuery";
|
|
|
44
44
|
import MonitorIncident from "./MonitorIncident";
|
|
45
45
|
import MonitorAlert from "./MonitorAlert";
|
|
46
46
|
import MonitorStatusTimelineUtil from "./MonitorStatusTimeline";
|
|
47
|
-
// import Semaphore, { SemaphoreMutex } from "../../Infrastructure/Semaphore";
|
|
48
47
|
import Metric, {
|
|
49
48
|
MetricPointType,
|
|
50
49
|
ServiceType,
|
|
@@ -26,6 +26,7 @@ import AlertFeedService from "./AlertFeedService";
|
|
|
26
26
|
import { AlertFeedEventType } from "../../Models/DatabaseModels/AlertFeed";
|
|
27
27
|
import WorkspaceNotificationRuleService from "./WorkspaceNotificationRuleService";
|
|
28
28
|
import { LIMIT_PER_PROJECT } from "../../Types/Database/LimitMax";
|
|
29
|
+
import Semaphore from "../Infrastructure/Semaphore";
|
|
29
30
|
export class Service extends DatabaseService {
|
|
30
31
|
constructor() {
|
|
31
32
|
super(AlertStateTimeline);
|
|
@@ -52,105 +53,176 @@ export class Service extends DatabaseService {
|
|
|
52
53
|
return resolvedState.id;
|
|
53
54
|
}
|
|
54
55
|
async onBeforeCreate(createBy) {
|
|
55
|
-
var _a, _b;
|
|
56
|
+
var _a, _b, _c, _d;
|
|
56
57
|
if (!createBy.data.alertId) {
|
|
57
58
|
throw new BadDataException("alertId is null");
|
|
58
59
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
createBy.data.createdByUser ||
|
|
64
|
-
createBy.props.userId) &&
|
|
65
|
-
!createBy.data.rootCause) {
|
|
66
|
-
let userId = createBy.data.createdByUserId;
|
|
67
|
-
if (createBy.props.userId) {
|
|
68
|
-
userId = createBy.props.userId;
|
|
60
|
+
let mutex = null;
|
|
61
|
+
try {
|
|
62
|
+
if (!createBy.data.startsAt) {
|
|
63
|
+
createBy.data.startsAt = OneUptimeDate.getCurrentDate();
|
|
69
64
|
}
|
|
70
|
-
|
|
71
|
-
|
|
65
|
+
try {
|
|
66
|
+
mutex = await Semaphore.lock({
|
|
67
|
+
key: createBy.data.alertId.toString(),
|
|
68
|
+
namespace: "AlertStateTimeline.create",
|
|
69
|
+
});
|
|
72
70
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
userId: userId,
|
|
76
|
-
projectId: createBy.data.projectId || createBy.props.tenantId,
|
|
77
|
-
})}`;
|
|
71
|
+
catch (err) {
|
|
72
|
+
logger.error(err);
|
|
78
73
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
74
|
+
if ((createBy.data.createdByUserId ||
|
|
75
|
+
createBy.data.createdByUser ||
|
|
76
|
+
createBy.props.userId) &&
|
|
77
|
+
!createBy.data.rootCause) {
|
|
78
|
+
let userId = createBy.data.createdByUserId;
|
|
79
|
+
if (createBy.props.userId) {
|
|
80
|
+
userId = createBy.props.userId;
|
|
81
|
+
}
|
|
82
|
+
if (createBy.data.createdByUser && createBy.data.createdByUser.id) {
|
|
83
|
+
userId = createBy.data.createdByUser.id;
|
|
84
|
+
}
|
|
85
|
+
if (userId) {
|
|
86
|
+
createBy.data.rootCause = `Alert state created by ${await UserService.getUserMarkdownString({
|
|
87
|
+
userId: userId,
|
|
88
|
+
projectId: createBy.data.projectId || createBy.props.tenantId,
|
|
89
|
+
})}`;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const alertStateId = createBy.data.alertStateId || ((_a = createBy.data.alertState) === null || _a === void 0 ? void 0 : _a.id);
|
|
93
|
+
if (!alertStateId) {
|
|
94
|
+
throw new BadDataException("alertStateId is null");
|
|
95
|
+
}
|
|
96
|
+
const stateBeforeThis = await this.findOneBy({
|
|
97
|
+
query: {
|
|
98
|
+
alertId: createBy.data.alertId,
|
|
99
|
+
startsAt: QueryHelper.lessThanEqualTo(createBy.data.startsAt),
|
|
100
|
+
},
|
|
101
|
+
sort: {
|
|
102
|
+
startsAt: SortOrder.Descending,
|
|
103
|
+
},
|
|
104
|
+
props: {
|
|
105
|
+
isRoot: true,
|
|
106
|
+
},
|
|
107
|
+
select: {
|
|
108
|
+
alertStateId: true,
|
|
109
|
+
alertState: {
|
|
110
|
+
order: true,
|
|
111
|
+
name: true,
|
|
112
|
+
},
|
|
113
|
+
startsAt: true,
|
|
114
|
+
endsAt: true,
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
logger.debug("State Before this");
|
|
118
|
+
logger.debug(stateBeforeThis);
|
|
119
|
+
// If this is the first state, then do not notify the owner.
|
|
120
|
+
if (!stateBeforeThis) {
|
|
121
|
+
// since this is the first status, do not notify the owner.
|
|
122
|
+
createBy.data.isOwnerNotified = true;
|
|
123
|
+
}
|
|
124
|
+
// check if this new state and the previous state are same.
|
|
125
|
+
// if yes, then throw bad data exception.
|
|
126
|
+
if (stateBeforeThis && stateBeforeThis.alertStateId && alertStateId) {
|
|
127
|
+
if (stateBeforeThis.alertStateId.toString() === alertStateId.toString()) {
|
|
128
|
+
throw new BadDataException("Alert state cannot be same as previous state.");
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (stateBeforeThis && ((_b = stateBeforeThis.alertState) === null || _b === void 0 ? void 0 : _b.order)) {
|
|
132
|
+
const newAlertState = await AlertStateService.findOneBy({
|
|
133
|
+
query: {
|
|
134
|
+
_id: alertStateId,
|
|
135
|
+
},
|
|
136
|
+
select: {
|
|
137
|
+
order: true,
|
|
138
|
+
name: true,
|
|
139
|
+
},
|
|
140
|
+
props: {
|
|
141
|
+
isRoot: true,
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
if (newAlertState && newAlertState.order) {
|
|
145
|
+
// check if the new alert state is in order is greater than the previous state order
|
|
146
|
+
if (stateBeforeThis &&
|
|
147
|
+
stateBeforeThis.alertState &&
|
|
148
|
+
stateBeforeThis.alertState.order &&
|
|
149
|
+
newAlertState.order <= stateBeforeThis.alertState.order) {
|
|
150
|
+
throw new BadDataException(`Alert cannot transition to ${newAlertState.name} state from ${stateBeforeThis.alertState.name} state because ${newAlertState.name} is before ${stateBeforeThis.alertState.name} in the order of alert states.`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
const stateAfterThis = await this.findOneBy({
|
|
155
|
+
query: {
|
|
156
|
+
alertId: createBy.data.alertId,
|
|
157
|
+
startsAt: QueryHelper.greaterThan(createBy.data.startsAt),
|
|
158
|
+
},
|
|
159
|
+
sort: {
|
|
160
|
+
startsAt: SortOrder.Ascending,
|
|
161
|
+
},
|
|
162
|
+
props: {
|
|
163
|
+
isRoot: true,
|
|
164
|
+
},
|
|
165
|
+
select: {
|
|
166
|
+
alertStateId: true,
|
|
167
|
+
startsAt: true,
|
|
168
|
+
endsAt: true,
|
|
169
|
+
},
|
|
137
170
|
});
|
|
171
|
+
// compute ends at. It's the start of the next status.
|
|
172
|
+
if (stateAfterThis && stateAfterThis.startsAt) {
|
|
173
|
+
createBy.data.endsAt = stateAfterThis.startsAt;
|
|
174
|
+
}
|
|
175
|
+
// check if this new state and the previous state are same.
|
|
176
|
+
// if yes, then throw bad data exception.
|
|
177
|
+
if (stateAfterThis && stateAfterThis.alertStateId && alertStateId) {
|
|
178
|
+
if (stateAfterThis.alertStateId.toString() === alertStateId.toString()) {
|
|
179
|
+
throw new BadDataException("Alert state cannot be same as next state.");
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
logger.debug("State After this");
|
|
183
|
+
logger.debug(stateAfterThis);
|
|
184
|
+
const internalNote = (_c = createBy.miscDataProps) === null || _c === void 0 ? void 0 : _c["internalNote"];
|
|
185
|
+
if (internalNote) {
|
|
186
|
+
const alertNote = new AlertInternalNote();
|
|
187
|
+
alertNote.alertId = createBy.data.alertId;
|
|
188
|
+
alertNote.note = internalNote;
|
|
189
|
+
alertNote.createdAt = createBy.data.startsAt;
|
|
190
|
+
alertNote.projectId = createBy.data.projectId;
|
|
191
|
+
await AlertInternalNoteService.create({
|
|
192
|
+
data: alertNote,
|
|
193
|
+
props: createBy.props,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
const privateNote = (_d = createBy.miscDataProps) === null || _d === void 0 ? void 0 : _d["privateNote"];
|
|
197
|
+
return {
|
|
198
|
+
createBy,
|
|
199
|
+
carryForward: {
|
|
200
|
+
statusTimelineBeforeThisStatus: stateBeforeThis || null,
|
|
201
|
+
statusTimelineAfterThisStatus: stateAfterThis || null,
|
|
202
|
+
privateNote: privateNote,
|
|
203
|
+
mutex: mutex,
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
// release the mutex if it was acquired.
|
|
209
|
+
if (mutex) {
|
|
210
|
+
try {
|
|
211
|
+
await Semaphore.release(mutex);
|
|
212
|
+
}
|
|
213
|
+
catch (err) {
|
|
214
|
+
logger.error(err);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
throw error;
|
|
138
218
|
}
|
|
139
|
-
const privateNote = (_b = createBy.miscDataProps) === null || _b === void 0 ? void 0 : _b["privateNote"];
|
|
140
|
-
return {
|
|
141
|
-
createBy,
|
|
142
|
-
carryForward: {
|
|
143
|
-
statusTimelineBeforeThisStatus: stateBeforeThis || null,
|
|
144
|
-
statusTimelineAfterThisStatus: stateAfterThis || null,
|
|
145
|
-
privateNote: privateNote,
|
|
146
|
-
},
|
|
147
|
-
};
|
|
148
219
|
}
|
|
149
220
|
async onCreateSuccess(onCreate, createdItem) {
|
|
150
221
|
var _a;
|
|
151
222
|
if (!createdItem.alertId) {
|
|
152
223
|
throw new BadDataException("alertId is null");
|
|
153
224
|
}
|
|
225
|
+
const mutex = onCreate.carryForward.mutex;
|
|
154
226
|
if (!createdItem.alertStateId) {
|
|
155
227
|
throw new BadDataException("alertStateId is null");
|
|
156
228
|
}
|
|
@@ -215,6 +287,14 @@ export class Service extends DatabaseService {
|
|
|
215
287
|
props: onCreate.createBy.props,
|
|
216
288
|
});
|
|
217
289
|
}
|
|
290
|
+
if (mutex) {
|
|
291
|
+
try {
|
|
292
|
+
await Semaphore.release(mutex);
|
|
293
|
+
}
|
|
294
|
+
catch (err) {
|
|
295
|
+
logger.error(err);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
218
298
|
const alertState = await AlertStateService.findOneBy({
|
|
219
299
|
query: {
|
|
220
300
|
_id: createdItem.alertStateId.toString(),
|