@oneuptime/common 9.1.1 → 9.1.2

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.
Files changed (42) hide show
  1. package/Models/DatabaseModels/Incident.ts +37 -0
  2. package/Server/API/StatusPageAPI.ts +5 -0
  3. package/Server/Infrastructure/Postgres/SchemaMigrations/1764324618043-MigrationName.ts +30 -0
  4. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
  5. package/Server/Services/IncidentService.ts +38 -0
  6. package/Server/Services/MonitorService.ts +49 -31
  7. package/Server/Services/ScheduledMaintenanceStateTimelineService.ts +18 -0
  8. package/Server/Services/TeamMemberService.ts +8 -7
  9. package/Server/Services/TeamService.ts +2 -1
  10. package/Server/Utils/Browser.ts +28 -2
  11. package/Server/Utils/Telemetry.ts +10 -2
  12. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.ts +4 -1
  13. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +7 -3
  14. package/Tests/Server/Services/TeamMemberService.test.ts +119 -0
  15. package/build/dist/Models/DatabaseModels/Incident.js +39 -0
  16. package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
  17. package/build/dist/Server/API/StatusPageAPI.js +5 -0
  18. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  19. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1764324618043-MigrationName.js +17 -0
  20. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1764324618043-MigrationName.js.map +1 -0
  21. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
  22. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  23. package/build/dist/Server/Services/IncidentService.js +32 -1
  24. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  25. package/build/dist/Server/Services/MonitorService.js +44 -24
  26. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  27. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js +12 -0
  28. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js.map +1 -1
  29. package/build/dist/Server/Services/TeamMemberService.js +9 -8
  30. package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
  31. package/build/dist/Server/Services/TeamService.js +2 -1
  32. package/build/dist/Server/Services/TeamService.js.map +1 -1
  33. package/build/dist/Server/Utils/Browser.js +24 -2
  34. package/build/dist/Server/Utils/Browser.js.map +1 -1
  35. package/build/dist/Server/Utils/Telemetry.js.map +1 -1
  36. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js +3 -1
  37. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js.map +1 -1
  38. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +6 -3
  39. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  40. package/build/dist/Tests/Server/Services/TeamMemberService.test.js +79 -0
  41. package/build/dist/Tests/Server/Services/TeamMemberService.test.js.map +1 -1
  42. package/package.json +2 -2
@@ -228,6 +228,43 @@ export default class Incident extends BaseModel {
228
228
  })
229
229
  public description?: string = undefined;
230
230
 
231
+ @ColumnAccessControl({
232
+ create: [
233
+ Permission.ProjectOwner,
234
+ Permission.ProjectAdmin,
235
+ Permission.ProjectMember,
236
+ Permission.CreateProjectIncident,
237
+ ],
238
+ read: [
239
+ Permission.ProjectOwner,
240
+ Permission.ProjectAdmin,
241
+ Permission.ProjectMember,
242
+ Permission.ReadProjectIncident,
243
+ ],
244
+ update: [
245
+ Permission.ProjectOwner,
246
+ Permission.ProjectAdmin,
247
+ Permission.ProjectMember,
248
+ Permission.EditProjectIncident,
249
+ ],
250
+ })
251
+ @Index()
252
+ @TableColumn({
253
+ required: true,
254
+ type: TableColumnType.Date,
255
+ title: "Declared At",
256
+ description: "Date and time when this incident was declared.",
257
+ isDefaultValueColumn: true,
258
+ })
259
+ @Column({
260
+ type: ColumnType.Date,
261
+ nullable: false,
262
+ default: () => {
263
+ return "now()";
264
+ },
265
+ })
266
+ public declaredAt?: Date = undefined;
267
+
231
268
  @Index()
232
269
  @ColumnAccessControl({
233
270
  create: [],
@@ -1421,6 +1421,7 @@ export default class StatusPageAPI extends BaseAPI<
1421
1421
  if (monitorsOnStatusPage.length > 0) {
1422
1422
  let select: Select<Incident> = {
1423
1423
  createdAt: true,
1424
+ declaredAt: true,
1424
1425
  title: true,
1425
1426
  description: true,
1426
1427
  _id: true,
@@ -1474,6 +1475,7 @@ export default class StatusPageAPI extends BaseAPI<
1474
1475
  },
1475
1476
  select: select,
1476
1477
  sort: {
1478
+ declaredAt: SortOrder.Descending,
1477
1479
  createdAt: SortOrder.Descending,
1478
1480
  },
1479
1481
 
@@ -3303,6 +3305,7 @@ export default class StatusPageAPI extends BaseAPI<
3303
3305
 
3304
3306
  let selectIncidents: Select<Incident> = {
3305
3307
  createdAt: true,
3308
+ declaredAt: true,
3306
3309
  title: true,
3307
3310
  description: true,
3308
3311
  _id: true,
@@ -3336,6 +3339,7 @@ export default class StatusPageAPI extends BaseAPI<
3336
3339
  query: incidentQuery,
3337
3340
  select: selectIncidents,
3338
3341
  sort: {
3342
+ declaredAt: SortOrder.Descending,
3339
3343
  createdAt: SortOrder.Descending,
3340
3344
  },
3341
3345
  skip: 0,
@@ -3373,6 +3377,7 @@ export default class StatusPageAPI extends BaseAPI<
3373
3377
  },
3374
3378
  select: selectIncidents,
3375
3379
  sort: {
3380
+ declaredAt: SortOrder.Descending,
3376
3381
  createdAt: SortOrder.Descending,
3377
3382
  },
3378
3383
 
@@ -0,0 +1,30 @@
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ export class MigrationName1764324618043 implements MigrationInterface {
4
+ public name = "MigrationName1764324618043";
5
+
6
+ public async up(queryRunner: QueryRunner): Promise<void> {
7
+ await queryRunner.query(
8
+ `ALTER TABLE "Incident" ADD "declaredAt" TIMESTAMP WITH TIME ZONE`,
9
+ );
10
+ await queryRunner.query(
11
+ `UPDATE "Incident" SET "declaredAt" = "createdAt" WHERE "declaredAt" IS NULL`,
12
+ );
13
+ await queryRunner.query(
14
+ `ALTER TABLE "Incident" ALTER COLUMN "declaredAt" SET DEFAULT now()`,
15
+ );
16
+ await queryRunner.query(
17
+ `ALTER TABLE "Incident" ALTER COLUMN "declaredAt" SET NOT NULL`,
18
+ );
19
+ await queryRunner.query(
20
+ `CREATE INDEX "IDX_b26979b9f119310661734465a4" ON "Incident" ("declaredAt") `,
21
+ );
22
+ }
23
+
24
+ public async down(queryRunner: QueryRunner): Promise<void> {
25
+ await queryRunner.query(
26
+ `DROP INDEX "public"."IDX_b26979b9f119310661734465a4"`,
27
+ );
28
+ await queryRunner.query(`ALTER TABLE "Incident" DROP COLUMN "declaredAt"`);
29
+ }
30
+ }
@@ -186,6 +186,7 @@ import { MigrationName1763471659817 } from "./1763471659817-MigrationName";
186
186
  import { MigrationName1763477560906 } from "./1763477560906-MigrationName";
187
187
  import { MigrationName1763480947474 } from "./1763480947474-MigrationName";
188
188
  import { MigrationName1763643080445 } from "./1763643080445-MigrationName";
189
+ import { MigrationName1764324618043 } from "./1764324618043-MigrationName";
189
190
 
190
191
  export default [
191
192
  InitialMigration,
@@ -376,4 +377,5 @@ export default [
376
377
  MigrationName1763477560906,
377
378
  MigrationName1763480947474,
378
379
  MigrationName1763643080445,
380
+ MigrationName1764324618043,
379
381
  ];
@@ -480,6 +480,14 @@ export class Service extends DatabaseService<Model> {
480
480
  const projectId: ObjectID =
481
481
  createBy.props.tenantId || createBy.data.projectId!;
482
482
 
483
+ if (!createBy.data.declaredAt) {
484
+ createBy.data.declaredAt = OneUptimeDate.getCurrentDate();
485
+ } else {
486
+ createBy.data.declaredAt = OneUptimeDate.fromString(
487
+ createBy.data.declaredAt as Date,
488
+ );
489
+ }
490
+
483
491
  // Determine the initial incident state
484
492
  let initialIncidentStateId: ObjectID | undefined = undefined;
485
493
 
@@ -975,6 +983,7 @@ ${incident.remediationNotes || "No remediation notes provided."}
975
983
  notifyOwners: false,
976
984
  rootCause: createdItem.rootCause,
977
985
  stateChangeLog: createdItem.createdStateLog,
986
+ timelineStartsAt: createdItem.declaredAt,
978
987
  props: {
979
988
  isRoot: true,
980
989
  },
@@ -1790,6 +1799,7 @@ ${incidentSeverity.name}
1790
1799
  limit: LIMIT_MAX,
1791
1800
  skip: 0,
1792
1801
  select: {
1802
+ _id: true,
1793
1803
  projectId: true,
1794
1804
  monitors: {
1795
1805
  _id: true,
@@ -1821,6 +1831,18 @@ ${incidentSeverity.name}
1821
1831
  incident.monitors,
1822
1832
  );
1823
1833
  }
1834
+
1835
+ if (incident.projectId && incident.id) {
1836
+ await MetricService.deleteBy({
1837
+ query: {
1838
+ projectId: incident.projectId,
1839
+ serviceId: incident.id,
1840
+ },
1841
+ props: {
1842
+ isRoot: true,
1843
+ },
1844
+ });
1845
+ }
1824
1846
  }
1825
1847
  }
1826
1848
 
@@ -1838,6 +1860,7 @@ ${incidentSeverity.name}
1838
1860
  rootCause: string | undefined;
1839
1861
  stateChangeLog: JSONObject | undefined;
1840
1862
  props: DatabaseCommonInteractionProps | undefined;
1863
+ timelineStartsAt?: Date | string | undefined;
1841
1864
  }): Promise<void> {
1842
1865
  const {
1843
1866
  projectId,
@@ -1849,8 +1872,13 @@ ${incidentSeverity.name}
1849
1872
  rootCause,
1850
1873
  stateChangeLog,
1851
1874
  props,
1875
+ timelineStartsAt,
1852
1876
  } = data;
1853
1877
 
1878
+ const declaredTimelineStart: Date | undefined = timelineStartsAt
1879
+ ? OneUptimeDate.fromString(timelineStartsAt as Date)
1880
+ : undefined;
1881
+
1854
1882
  // get last monitor status timeline.
1855
1883
  const lastIncidentStatusTimeline: IncidentStateTimeline | null =
1856
1884
  await IncidentStateTimelineService.findOneBy({
@@ -1888,6 +1916,10 @@ ${incidentSeverity.name}
1888
1916
  statusTimeline.shouldStatusPageSubscribersBeNotified =
1889
1917
  shouldNotifyStatusPageSubscribers;
1890
1918
 
1919
+ if (!lastIncidentStatusTimeline && declaredTimelineStart) {
1920
+ statusTimeline.startsAt = declaredTimelineStart;
1921
+ }
1922
+
1891
1923
  // Map boolean to enum value
1892
1924
  statusTimeline.subscriberNotificationStatus = isSubscribersNotified
1893
1925
  ? StatusPageSubscriberNotificationStatus.Success
@@ -1914,6 +1946,7 @@ ${incidentSeverity.name}
1914
1946
  id: data.incidentId,
1915
1947
  select: {
1916
1948
  projectId: true,
1949
+ declaredAt: true,
1917
1950
  monitors: {
1918
1951
  _id: true,
1919
1952
  name: true,
@@ -1970,6 +2003,7 @@ ${incidentSeverity.name}
1970
2003
 
1971
2004
  await MetricService.deleteBy({
1972
2005
  query: {
2006
+ projectId: incident.projectId,
1973
2007
  serviceId: data.incidentId,
1974
2008
  },
1975
2009
  props: {
@@ -1983,6 +2017,7 @@ ${incidentSeverity.name}
1983
2017
 
1984
2018
  const incidentStartsAt: Date =
1985
2019
  firstIncidentStateTimeline?.startsAt ||
2020
+ incident.declaredAt ||
1986
2021
  incident.createdAt ||
1987
2022
  OneUptimeDate.getCurrentDate();
1988
2023
 
@@ -2075,6 +2110,7 @@ ${incidentSeverity.name}
2075
2110
 
2076
2111
  timeToAcknowledgeMetric.time =
2077
2112
  ackIncidentStateTimeline?.startsAt ||
2113
+ incident.declaredAt ||
2078
2114
  incident.createdAt ||
2079
2115
  OneUptimeDate.getCurrentDate();
2080
2116
  timeToAcknowledgeMetric.timeUnixNano = OneUptimeDate.toUnixNano(
@@ -2140,6 +2176,7 @@ ${incidentSeverity.name}
2140
2176
 
2141
2177
  timeToResolveMetric.time =
2142
2178
  resolvedIncidentStateTimeline?.startsAt ||
2179
+ incident.declaredAt ||
2143
2180
  incident.createdAt ||
2144
2181
  OneUptimeDate.getCurrentDate();
2145
2182
  timeToResolveMetric.timeUnixNano = OneUptimeDate.toUnixNano(
@@ -2200,6 +2237,7 @@ ${incidentSeverity.name}
2200
2237
 
2201
2238
  incidentDurationMetric.time =
2202
2239
  lastIncidentStateTimeline?.startsAt ||
2240
+ incident.declaredAt ||
2203
2241
  incident.createdAt ||
2204
2242
  OneUptimeDate.getCurrentDate();
2205
2243
  incidentDurationMetric.timeUnixNano = OneUptimeDate.toUnixNano(
@@ -63,14 +63,13 @@ import MonitorFeedService from "./MonitorFeedService";
63
63
  import { MonitorFeedEventType } from "../../Models/DatabaseModels/MonitorFeed";
64
64
  import { Gray500, Green500 } from "../../Types/BrandColors";
65
65
  import LabelService from "./LabelService";
66
- import QueryOperator from "../../Types/BaseDatabase/QueryOperator";
67
- import { FindWhere } from "../../Types/BaseDatabase/Query";
68
66
  import logger from "../Utils/Logger";
69
67
  import PushNotificationUtil from "../Utils/PushNotificationUtil";
70
68
  import ExceptionMessages from "../../Types/Exception/ExceptionMessages";
71
69
  import Project from "../../Models/DatabaseModels/Project";
72
70
  import { createWhatsAppMessageFromTemplate } from "../Utils/WhatsAppTemplateUtil";
73
71
  import { WhatsAppMessagePayload } from "../../Types/WhatsApp/WhatsAppMessage";
72
+ import MetricService from "./MetricService";
74
73
 
75
74
  export class Service extends DatabaseService<Model> {
76
75
  public constructor() {
@@ -136,12 +135,26 @@ export class Service extends DatabaseService<Model> {
136
135
  protected override async onBeforeDelete(
137
136
  deleteBy: DeleteBy<Model>,
138
137
  ): Promise<OnDelete<Model>> {
139
- if (deleteBy.query._id) {
140
- // delete all the status page resource for this monitor.
138
+ const monitorsPendingDeletion: Array<Model> = await this.findBy({
139
+ query: deleteBy.query,
140
+ limit: LIMIT_MAX,
141
+ skip: 0,
142
+ select: {
143
+ _id: true,
144
+ projectId: true,
145
+ },
146
+ props: deleteBy.props,
147
+ });
141
148
 
149
+ for (const monitor of monitorsPendingDeletion) {
150
+ if (!monitor.id) {
151
+ continue;
152
+ }
153
+
154
+ // delete all the status page resources for this monitor.
142
155
  await StatusPageResourceService.deleteBy({
143
156
  query: {
144
- monitorId: new ObjectID(deleteBy.query._id as string),
157
+ monitorId: monitor.id,
145
158
  },
146
159
  limit: LIMIT_MAX,
147
160
  skip: 0,
@@ -150,37 +163,19 @@ export class Service extends DatabaseService<Model> {
150
163
  },
151
164
  });
152
165
 
153
- let projectId: FindWhere<ObjectID> | QueryOperator<ObjectID> | undefined =
154
- deleteBy.query.projectId || deleteBy.props.tenantId;
166
+ const projectId: ObjectID | undefined = monitor.projectId as
167
+ | ObjectID
168
+ | undefined;
155
169
 
156
170
  if (!projectId) {
157
- // fetch this monitor from the database to get the projectId.
158
- const monitor: Model | null = await this.findOneById({
159
- id: new ObjectID(deleteBy.query._id as string) as ObjectID,
160
- select: {
161
- projectId: true,
162
- },
163
- props: {
164
- isRoot: true,
165
- },
166
- });
167
-
168
- if (!monitor) {
169
- throw new BadDataException(ExceptionMessages.MonitorNotFound);
170
- }
171
-
172
- if (!monitor.id) {
173
- throw new BadDataException(ExceptionMessages.MonitorNotFound);
174
- }
175
-
176
- projectId = monitor.projectId!;
171
+ continue;
177
172
  }
178
173
 
179
174
  try {
180
175
  await WorkspaceNotificationRuleService.archiveWorkspaceChannels({
181
- projectId: projectId as ObjectID,
176
+ projectId: projectId,
182
177
  notificationFor: {
183
- monitorId: new ObjectID(deleteBy.query._id as string) as ObjectID,
178
+ monitorId: monitor.id,
184
179
  },
185
180
  sendMessageBeforeArchiving: {
186
181
  _type: "WorkspacePayloadMarkdown",
@@ -189,12 +184,17 @@ export class Service extends DatabaseService<Model> {
189
184
  });
190
185
  } catch (error) {
191
186
  logger.error(
192
- `Error while archiving workspace channels for monitor ${deleteBy.query._id}: ${error}`,
187
+ `Error while archiving workspace channels for monitor ${monitor.id?.toString()}: ${error}`,
193
188
  );
194
189
  }
195
190
  }
196
191
 
197
- return { deleteBy, carryForward: null };
192
+ return {
193
+ deleteBy,
194
+ carryForward: {
195
+ monitors: monitorsPendingDeletion,
196
+ },
197
+ };
198
198
  }
199
199
 
200
200
  @CaptureSpan()
@@ -208,6 +208,24 @@ export class Service extends DatabaseService<Model> {
208
208
  );
209
209
  }
210
210
 
211
+ if (onDelete.carryForward && onDelete.carryForward.monitors) {
212
+ for (const monitor of onDelete.carryForward.monitors as Array<Model>) {
213
+ if (!monitor.projectId || !monitor.id) {
214
+ continue;
215
+ }
216
+
217
+ await MetricService.deleteBy({
218
+ query: {
219
+ projectId: monitor.projectId,
220
+ serviceId: monitor.id,
221
+ },
222
+ props: {
223
+ isRoot: true,
224
+ },
225
+ });
226
+ }
227
+ }
228
+
211
229
  return onDelete;
212
230
  }
213
231
 
@@ -510,12 +510,30 @@ export class Service extends DatabaseService<ScheduledMaintenanceStateTimeline>
510
510
  monitors: {
511
511
  _id: true,
512
512
  },
513
+ nextSubscriberNotificationBeforeTheEventAt: true,
513
514
  },
514
515
  props: {
515
516
  isRoot: true,
516
517
  },
517
518
  });
518
519
 
520
+ const hasProgressedBeyondScheduledState: boolean = Boolean(
521
+ scheduledMaintenanceState && !scheduledMaintenanceState.isScheduledState,
522
+ );
523
+
524
+ if (
525
+ hasProgressedBeyondScheduledState &&
526
+ scheduledMaintenanceEvent?.nextSubscriberNotificationBeforeTheEventAt
527
+ ) {
528
+ await ScheduledMaintenanceService.updateOneById({
529
+ id: createdItem.scheduledMaintenanceId!,
530
+ data: {
531
+ nextSubscriberNotificationBeforeTheEventAt: null,
532
+ },
533
+ props: onCreate.createBy.props,
534
+ });
535
+ }
536
+
519
537
  if (isOngoingState) {
520
538
  if (
521
539
  scheduledMaintenanceEvent &&
@@ -44,10 +44,11 @@ export class TeamMemberService extends DatabaseService<TeamMember> {
44
44
  }
45
45
 
46
46
  @CaptureSpan()
47
- private async isSCIMEnabled(projectId: ObjectID): Promise<boolean> {
47
+ private async isSCIMPushGroupsEnabled(projectId: ObjectID): Promise<boolean> {
48
48
  const count: PositiveNumber = await ProjectSCIMService.countBy({
49
49
  query: {
50
50
  projectId: projectId,
51
+ enablePushGroups: true,
51
52
  },
52
53
  props: {
53
54
  isRoot: true,
@@ -63,12 +64,12 @@ export class TeamMemberService extends DatabaseService<TeamMember> {
63
64
  // Check if SCIM is enabled for the project
64
65
  if (
65
66
  !createBy.props.isRoot &&
66
- (await this.isSCIMEnabled(
67
+ (await this.isSCIMPushGroupsEnabled(
67
68
  createBy.data.projectId! || createBy.props.tenantId,
68
69
  ))
69
70
  ) {
70
71
  throw new BadDataException(
71
- "Cannot invite team members when SCIM is enabled for this project.",
72
+ "Cannot invite team members while SCIM Push Groups is enabled for this project. Disable Push Groups to manage members from OneUptime.",
72
73
  );
73
74
  }
74
75
 
@@ -311,10 +312,10 @@ export class TeamMemberService extends DatabaseService<TeamMember> {
311
312
  !deleteBy.props.isRoot &&
312
313
  members.length > 0 &&
313
314
  members[0]?.projectId &&
314
- (await this.isSCIMEnabled(members[0].projectId))
315
+ (await this.isSCIMPushGroupsEnabled(members[0].projectId))
315
316
  ) {
316
317
  throw new BadDataException(
317
- "Cannot delete team members when SCIM is enabled for this project.",
318
+ "Cannot delete team members while SCIM Push Groups is enabled for this project. Disable Push Groups to manage members from OneUptime.",
318
319
  );
319
320
  }
320
321
 
@@ -346,11 +347,11 @@ export class TeamMemberService extends DatabaseService<TeamMember> {
346
347
  });
347
348
 
348
349
  // Skip the one-member guard when SCIM manages membership for the project.
349
- const isSCIMEnabled: boolean = await this.isSCIMEnabled(
350
+ const isPushGroupsManaged: boolean = await this.isSCIMPushGroupsEnabled(
350
351
  member.projectId!,
351
352
  );
352
353
 
353
- if (!isSCIMEnabled && membersInTeam.toNumber() <= 1) {
354
+ if (!isPushGroupsManaged && membersInTeam.toNumber() <= 1) {
354
355
  throw new BadDataException(
355
356
  Errors.TeamMemberService.ONE_MEMBER_REQUIRED,
356
357
  );
@@ -71,6 +71,7 @@ export class Service extends DatabaseService<Model> {
71
71
  const scimCount: PositiveNumber = await ProjectSCIMService.countBy({
72
72
  query: {
73
73
  projectId: projectId,
74
+ enablePushGroups: true,
74
75
  },
75
76
  skip: new PositiveNumber(0),
76
77
  limit: new PositiveNumber(1),
@@ -82,7 +83,7 @@ export class Service extends DatabaseService<Model> {
82
83
 
83
84
  if (scimCount.toNumber() > 0) {
84
85
  throw new BadDataException(
85
- `Cannot ${data.action} teams when SCIM is enabled for this project.`,
86
+ `Cannot ${data.action} teams while SCIM Push Groups is enabled for this project. Disable Push Groups to manage teams from OneUptime.`,
86
87
  );
87
88
  }
88
89
  }
@@ -166,7 +166,20 @@ export default class BrowserUtil {
166
166
  throw new BadDataException("Chrome executable path not found.");
167
167
  }
168
168
 
169
- return `/root/.cache/ms-playwright/${chromeInstallationName}/chrome-linux/chrome`;
169
+ const chromeExecutableCandidates: Array<string> = [
170
+ `/root/.cache/ms-playwright/${chromeInstallationName}/chrome-linux/chrome`,
171
+ `/root/.cache/ms-playwright/${chromeInstallationName}/chrome-linux64/chrome`,
172
+ `/root/.cache/ms-playwright/${chromeInstallationName}/chrome64/chrome`,
173
+ `/root/.cache/ms-playwright/${chromeInstallationName}/chrome/chrome`,
174
+ ];
175
+
176
+ for (const executablePath of chromeExecutableCandidates) {
177
+ if (await LocalFile.doesFileExist(executablePath)) {
178
+ return executablePath;
179
+ }
180
+ }
181
+
182
+ throw new BadDataException("Chrome executable path not found.");
170
183
  }
171
184
 
172
185
  @CaptureSpan()
@@ -197,6 +210,19 @@ export default class BrowserUtil {
197
210
  throw new BadDataException("Firefox executable path not found.");
198
211
  }
199
212
 
200
- return `/root/.cache/ms-playwright/${firefoxInstallationName}/firefox/firefox`;
213
+ const firefoxExecutableCandidates: Array<string> = [
214
+ `/root/.cache/ms-playwright/${firefoxInstallationName}/firefox/firefox`,
215
+ `/root/.cache/ms-playwright/${firefoxInstallationName}/firefox-linux64/firefox`,
216
+ `/root/.cache/ms-playwright/${firefoxInstallationName}/firefox64/firefox`,
217
+ `/root/.cache/ms-playwright/${firefoxInstallationName}/firefox-64/firefox`,
218
+ ];
219
+
220
+ for (const executablePath of firefoxExecutableCandidates) {
221
+ if (await LocalFile.doesFileExist(executablePath)) {
222
+ return executablePath;
223
+ }
224
+ }
225
+
226
+ throw new BadDataException("Firefox executable path not found.");
201
227
  }
202
228
  }
@@ -226,7 +226,11 @@ export default class Telemetry {
226
226
  };
227
227
 
228
228
  if (logRecordProcessors.length > 0) {
229
- loggerProviderConfig.processors = logRecordProcessors;
229
+ (
230
+ loggerProviderConfig as LoggerProviderConfig & {
231
+ processors?: Array<LogRecordProcessor>;
232
+ }
233
+ ).processors = logRecordProcessors;
230
234
  }
231
235
 
232
236
  this.loggerProvider = new LoggerProvider(loggerProviderConfig);
@@ -254,7 +258,11 @@ export default class Telemetry {
254
258
  */
255
259
 
256
260
  if (logRecordProcessors.length > 0) {
257
- nodeSdkConfiguration.logRecordProcessors = logRecordProcessors;
261
+ (
262
+ nodeSdkConfiguration as opentelemetry.NodeSDKConfiguration & {
263
+ logRecordProcessors?: Array<LogRecordProcessor>;
264
+ }
265
+ ).logRecordProcessors = logRecordProcessors;
258
266
  }
259
267
 
260
268
  const sdk: opentelemetry.NodeSDK = new opentelemetry.NodeSDK(
@@ -320,6 +320,7 @@ export default class MicrosoftTeamsIncidentActions {
320
320
  name: true,
321
321
  },
322
322
  createdAt: true,
323
+ declaredAt: true,
323
324
  },
324
325
  props: {
325
326
  isRoot: true,
@@ -331,7 +332,9 @@ export default class MicrosoftTeamsIncidentActions {
331
332
  return;
332
333
  }
333
334
 
334
- const message: string = `**Incident Details**\n\n**Title:** ${incident.title}\n**Description:** ${incident.description || "No description"}\n**State:** ${incident.currentIncidentState?.name || "Unknown"}\n**Severity:** ${incident.incidentSeverity?.name || "Unknown"}\n**Created At:** ${incident.createdAt ? new Date(incident.createdAt).toLocaleString() : "Unknown"}`;
335
+ const declaredAt: Date | undefined =
336
+ incident.declaredAt || incident.createdAt || undefined;
337
+ const message: string = `**Incident Details**\n\n**Title:** ${incident.title}\n**Description:** ${incident.description || "No description"}\n**State:** ${incident.currentIncidentState?.name || "Unknown"}\n**Severity:** ${incident.incidentSeverity?.name || "Unknown"}\n**Declared At:** ${declaredAt ? new Date(declaredAt).toLocaleString() : "Unknown"}`;
335
338
 
336
339
  await turnContext.sendActivity(message);
337
340
  return;
@@ -1929,11 +1929,13 @@ Just type any of these commands to get the information you need!`;
1929
1929
  color: true,
1930
1930
  },
1931
1931
  createdAt: true,
1932
+ declaredAt: true,
1932
1933
  monitors: {
1933
1934
  name: true,
1934
1935
  },
1935
1936
  },
1936
1937
  sort: {
1938
+ declaredAt: SortOrder.Descending,
1937
1939
  createdAt: SortOrder.Descending,
1938
1940
  },
1939
1941
  limit: 10,
@@ -1958,8 +1960,10 @@ If you need to report an incident or check historical incidents, please visit th
1958
1960
  for (const incident of activeIncidents) {
1959
1961
  const severity: string = incident.incidentSeverity?.name || "Unknown";
1960
1962
  const state: string = incident.currentIncidentState?.name || "Unknown";
1961
- const createdAt: string = incident.createdAt
1962
- ? OneUptimeDate.getDateAsFormattedString(incident.createdAt)
1963
+ const declaredAt: Date | undefined =
1964
+ incident.declaredAt || incident.createdAt;
1965
+ const declaredAtText: string = declaredAt
1966
+ ? OneUptimeDate.getDateAsFormattedString(declaredAt)
1963
1967
  : "Unknown";
1964
1968
 
1965
1969
  const severityIcon: string = ["Critical", "Major"].includes(severity)
@@ -1977,7 +1981,7 @@ If you need to report an incident or check historical incidents, please visit th
1977
1981
  message += `${severityIcon} **[Incident #${incident.incidentNumber}: ${incident.title}](${incidentUrl.toString()})**
1978
1982
  • **Severity:** ${severity}
1979
1983
  • **Status:** ${state}
1980
- • **Created:** ${createdAt}
1984
+ • **Declared:** ${declaredAtText}
1981
1985
  `;
1982
1986
 
1983
1987
  if (incident.monitors && incident.monitors.length > 0) {