@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.
@@ -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.onBeforeCreate",
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
- if (!createBy.data.startsAt) {
97
- createBy.data.startsAt = OneUptimeDate.getCurrentDate();
98
- }
97
+ let mutex: SemaphoreMutex | null = null;
99
98
 
100
- const stateBeforeThis: ScheduledMaintenanceStateTimeline | null =
101
- await this.findOneBy({
102
- query: {
103
- scheduledMaintenanceId: createBy.data.scheduledMaintenanceId,
104
- startsAt: QueryHelper.lessThanEqualTo(createBy.data.startsAt),
105
- },
106
- sort: {
107
- startsAt: SortOrder.Descending,
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
- // 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
- }
109
+ if (!createBy.data.startsAt) {
110
+ createBy.data.startsAt = OneUptimeDate.getCurrentDate();
111
+ }
124
112
 
125
- const stateAfterThis: ScheduledMaintenanceStateTimeline | null =
126
- await this.findOneBy({
127
- query: {
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
- // compute ends at. It's the start of the next status.
145
- if (stateAfterThis && stateAfterThis.startsAt) {
146
- createBy.data.endsAt = stateAfterThis.startsAt;
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
- scheduledMaintenancePublicNote.shouldStatusPageSubscribersBeNotifiedOnNoteCreated
151
+ stateBeforeThis &&
152
+ stateBeforeThis.scheduledMaintenanceStateId &&
153
+ scheduledMaintenanceStateId
168
154
  ) {
169
- createBy.data.isStatusPageSubscribersNotified = true;
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
- await ScheduledMaintenancePublicNoteService.create({
173
- data: scheduledMaintenancePublicNote,
174
- props: createBy.props,
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
- return {
179
- createBy,
180
- carryForward: {
181
- statusTimelineBeforeThisStatus: stateBeforeThis || null,
182
- statusTimelineAfterThisStatus: stateAfterThis || null,
183
- publicNote: publicNote,
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
- // let mutex: SemaphoreMutex | null = null;
65
+ let mutex: SemaphoreMutex | null = null;
66
66
 
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
- // }
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
- // if (mutex) {
602
- // try {
603
- // await Semaphore.release(mutex);
604
- // } catch (err) {
605
- // logger.error(err);
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
  }