@oneuptime/common 7.0.4135 → 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 +17 -17
- 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 +19 -17
- 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: {
|
|
@@ -15,6 +15,7 @@ import Dictionary from "Common/Types/Dictionary";
|
|
|
15
15
|
import BadDataException from "Common/Types/Exception/BadDataException";
|
|
16
16
|
import BasicInfrastructureMetrics from "Common/Types/Infrastructure/BasicMetrics";
|
|
17
17
|
import ReturnResult from "Common/Types/IsolatedVM/ReturnResult";
|
|
18
|
+
import Semaphore, { SemaphoreMutex } from "../../Infrastructure/Semaphore";
|
|
18
19
|
import { JSONObject } from "Common/Types/JSON";
|
|
19
20
|
import { CheckOn, CriteriaFilter } from "Common/Types/Monitor/CriteriaFilter";
|
|
20
21
|
import IncomingMonitorRequest from "Common/Types/Monitor/IncomingMonitor/IncomingMonitorRequest";
|
|
@@ -43,7 +44,6 @@ import { TelemetryQuery } from "../../../Types/Telemetry/TelemetryQuery";
|
|
|
43
44
|
import MonitorIncident from "./MonitorIncident";
|
|
44
45
|
import MonitorAlert from "./MonitorAlert";
|
|
45
46
|
import MonitorStatusTimelineUtil from "./MonitorStatusTimeline";
|
|
46
|
-
// import Semaphore, { SemaphoreMutex } from "../../Infrastructure/Semaphore";
|
|
47
47
|
import Metric, {
|
|
48
48
|
MetricPointType,
|
|
49
49
|
ServiceType,
|
|
@@ -62,16 +62,16 @@ export default class MonitorResourceUtil {
|
|
|
62
62
|
public static async monitorResource(
|
|
63
63
|
dataToProcess: DataToProcess,
|
|
64
64
|
): Promise<ProbeApiIngestResponse> {
|
|
65
|
-
|
|
65
|
+
let mutex: SemaphoreMutex | null = null;
|
|
66
66
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
67
|
+
try {
|
|
68
|
+
mutex = await Semaphore.lock({
|
|
69
|
+
key: dataToProcess.monitorId.toString(),
|
|
70
|
+
namespace: "MonitorResourceUtil.monitorResource",
|
|
71
|
+
});
|
|
72
|
+
} catch (err) {
|
|
73
|
+
logger.error(err);
|
|
74
|
+
}
|
|
75
75
|
|
|
76
76
|
let response: ProbeApiIngestResponse = {
|
|
77
77
|
monitorId: dataToProcess.monitorId,
|
|
@@ -598,13 +598,13 @@ export default class MonitorResourceUtil {
|
|
|
598
598
|
}
|
|
599
599
|
}
|
|
600
600
|
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
601
|
+
if (mutex) {
|
|
602
|
+
try {
|
|
603
|
+
await Semaphore.release(mutex);
|
|
604
|
+
} catch (err) {
|
|
605
|
+
logger.error(err);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
608
|
|
|
609
609
|
return response;
|
|
610
610
|
}
|