@oneuptime/common 8.0.5139 → 8.0.5144

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 (31) hide show
  1. package/Models/DatabaseModels/IncidentTemplate.ts +71 -0
  2. package/Models/DatabaseModels/StatusPageAnnouncement.ts +48 -0
  3. package/Models/DatabaseModels/StatusPageAnnouncementTemplate.ts +48 -0
  4. package/Server/API/StatusPageAPI.ts +65 -0
  5. package/Server/Infrastructure/Postgres/SchemaMigrations/1757416939595-MigrationName.ts +41 -0
  6. package/Server/Infrastructure/Postgres/SchemaMigrations/1757423505855-MigrationName.ts +79 -0
  7. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
  8. package/Server/Services/IncidentService.ts +93 -18
  9. package/Server/Services/StatusPageSubscriberService.ts +4 -1
  10. package/UI/Utils/Markdown.tsx +1 -5
  11. package/build/dist/Models/DatabaseModels/IncidentTemplate.js +70 -0
  12. package/build/dist/Models/DatabaseModels/IncidentTemplate.js.map +1 -1
  13. package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js +46 -0
  14. package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js.map +1 -1
  15. package/build/dist/Models/DatabaseModels/StatusPageAnnouncementTemplate.js +46 -0
  16. package/build/dist/Models/DatabaseModels/StatusPageAnnouncementTemplate.js.map +1 -1
  17. package/build/dist/Server/API/StatusPageAPI.js +54 -0
  18. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  19. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1757416939595-MigrationName.js +20 -0
  20. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1757416939595-MigrationName.js.map +1 -0
  21. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1757423505855-MigrationName.js +34 -0
  22. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1757423505855-MigrationName.js.map +1 -0
  23. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
  24. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  25. package/build/dist/Server/Services/IncidentService.js +77 -15
  26. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  27. package/build/dist/Server/Services/StatusPageSubscriberService.js +2 -1
  28. package/build/dist/Server/Services/StatusPageSubscriberService.js.map +1 -1
  29. package/build/dist/UI/Utils/Markdown.js +1 -1
  30. package/build/dist/UI/Utils/Markdown.js.map +1 -1
  31. package/package.json +1 -1
@@ -716,6 +716,77 @@ export default class IncidentTemplate extends BaseModel {
716
716
  })
717
717
  public changeMonitorStatusToId?: ObjectID = undefined;
718
718
 
719
+ @ColumnAccessControl({
720
+ create: [
721
+ Permission.ProjectOwner,
722
+ Permission.ProjectAdmin,
723
+ Permission.ProjectMember,
724
+ Permission.CreateIncidentTemplate,
725
+ ],
726
+ read: [
727
+ Permission.ProjectOwner,
728
+ Permission.ProjectAdmin,
729
+ Permission.ProjectMember,
730
+ Permission.ReadIncidentTemplate,
731
+ ],
732
+ update: [],
733
+ })
734
+ @TableColumn({
735
+ manyToOneRelationColumn: "initialIncidentStateId",
736
+ type: TableColumnType.Entity,
737
+ modelType: IncidentState,
738
+ title: "Initial Incident State",
739
+ description:
740
+ "Relation to Incident State Object. Incidents created from this template will start in this state.",
741
+ })
742
+ @ManyToOne(
743
+ () => {
744
+ return IncidentState;
745
+ },
746
+ {
747
+ eager: false,
748
+ nullable: true,
749
+ orphanedRowAction: "nullify",
750
+ },
751
+ )
752
+ @JoinColumn({ name: "initialIncidentStateId" })
753
+ public initialIncidentState?: IncidentState = undefined;
754
+
755
+ @ColumnAccessControl({
756
+ create: [
757
+ Permission.ProjectOwner,
758
+ Permission.ProjectAdmin,
759
+ Permission.ProjectMember,
760
+ Permission.CreateIncidentTemplate,
761
+ ],
762
+ read: [
763
+ Permission.ProjectOwner,
764
+ Permission.ProjectAdmin,
765
+ Permission.ProjectMember,
766
+ Permission.ReadIncidentTemplate,
767
+ ],
768
+ update: [
769
+ Permission.ProjectOwner,
770
+ Permission.ProjectAdmin,
771
+ Permission.ProjectMember,
772
+ Permission.EditIncidentTemplate,
773
+ ],
774
+ })
775
+ @Index()
776
+ @TableColumn({
777
+ type: TableColumnType.ObjectID,
778
+ required: false,
779
+ title: "Initial Incident State ID",
780
+ description:
781
+ "Relation to Incident State Object ID. Incidents created from this template will start in this state.",
782
+ })
783
+ @Column({
784
+ type: ColumnType.ObjectID,
785
+ nullable: true,
786
+ transformer: ObjectID.getDatabaseTransformer(),
787
+ })
788
+ public initialIncidentStateId?: ObjectID = undefined;
789
+
719
790
  @ColumnAccessControl({
720
791
  create: [
721
792
  Permission.ProjectOwner,
@@ -1,3 +1,4 @@
1
+ import Monitor from "./Monitor";
1
2
  import Project from "./Project";
2
3
  import StatusPage from "./StatusPage";
3
4
  import User from "./User";
@@ -198,6 +199,53 @@ export default class StatusPageAnnouncement extends BaseModel {
198
199
  })
199
200
  public statusPages?: Array<StatusPage> = undefined;
200
201
 
202
+ @ColumnAccessControl({
203
+ create: [
204
+ Permission.ProjectOwner,
205
+ Permission.ProjectAdmin,
206
+ Permission.ProjectMember,
207
+ Permission.CreateStatusPageAnnouncement,
208
+ ],
209
+ read: [
210
+ Permission.ProjectOwner,
211
+ Permission.ProjectAdmin,
212
+ Permission.ProjectMember,
213
+ Permission.ReadStatusPageAnnouncement,
214
+ ],
215
+ update: [
216
+ Permission.ProjectOwner,
217
+ Permission.ProjectAdmin,
218
+ Permission.ProjectMember,
219
+ Permission.EditStatusPageAnnouncement,
220
+ ],
221
+ })
222
+ @TableColumn({
223
+ required: false,
224
+ type: TableColumnType.EntityArray,
225
+ modelType: Monitor,
226
+ title: "Monitors",
227
+ description:
228
+ "List of monitors affected by this announcement. If none are selected, all subscribers will be notified.",
229
+ })
230
+ @ManyToMany(
231
+ () => {
232
+ return Monitor;
233
+ },
234
+ { eager: false },
235
+ )
236
+ @JoinTable({
237
+ name: "AnnouncementMonitor",
238
+ inverseJoinColumn: {
239
+ name: "monitorId",
240
+ referencedColumnName: "_id",
241
+ },
242
+ joinColumn: {
243
+ name: "announcementId",
244
+ referencedColumnName: "_id",
245
+ },
246
+ })
247
+ public monitors?: Array<Monitor> = undefined;
248
+
201
249
  @ColumnAccessControl({
202
250
  create: [
203
251
  Permission.ProjectOwner,
@@ -1,3 +1,4 @@
1
+ import Monitor from "./Monitor";
1
2
  import Project from "./Project";
2
3
  import StatusPage from "./StatusPage";
3
4
  import User from "./User";
@@ -328,6 +329,53 @@ export default class StatusPageAnnouncementTemplate extends BaseModel {
328
329
  })
329
330
  public statusPages?: Array<StatusPage> = undefined;
330
331
 
332
+ @ColumnAccessControl({
333
+ create: [
334
+ Permission.ProjectOwner,
335
+ Permission.ProjectAdmin,
336
+ Permission.ProjectMember,
337
+ Permission.CreateStatusPageAnnouncementTemplate,
338
+ ],
339
+ read: [
340
+ Permission.ProjectOwner,
341
+ Permission.ProjectAdmin,
342
+ Permission.ProjectMember,
343
+ Permission.ReadStatusPageAnnouncementTemplate,
344
+ ],
345
+ update: [
346
+ Permission.ProjectOwner,
347
+ Permission.ProjectAdmin,
348
+ Permission.ProjectMember,
349
+ Permission.EditStatusPageAnnouncementTemplate,
350
+ ],
351
+ })
352
+ @TableColumn({
353
+ required: false,
354
+ type: TableColumnType.EntityArray,
355
+ modelType: Monitor,
356
+ title: "Monitors",
357
+ description:
358
+ "List of monitors affected by this announcement template. If none are selected, all subscribers will be notified.",
359
+ })
360
+ @ManyToMany(
361
+ () => {
362
+ return Monitor;
363
+ },
364
+ { eager: false },
365
+ )
366
+ @JoinTable({
367
+ name: "AnnouncementTemplateMonitor",
368
+ inverseJoinColumn: {
369
+ name: "monitorId",
370
+ referencedColumnName: "_id",
371
+ },
372
+ joinColumn: {
373
+ name: "announcementTemplateId",
374
+ referencedColumnName: "_id",
375
+ },
376
+ })
377
+ public monitors?: Array<Monitor> = undefined;
378
+
331
379
  @ColumnAccessControl({
332
380
  create: [
333
381
  Permission.ProjectOwner,
@@ -2076,6 +2076,10 @@ export default class StatusPageAPI extends BaseAPI<
2076
2076
  _id: true,
2077
2077
  showAnnouncementAt: true,
2078
2078
  endAnnouncementAt: true,
2079
+ monitors: {
2080
+ _id: true,
2081
+ name: true,
2082
+ },
2079
2083
  },
2080
2084
  skip: 0,
2081
2085
  limit: LIMIT_PER_PROJECT,
@@ -2096,6 +2100,7 @@ export default class StatusPageAPI extends BaseAPI<
2096
2100
  displayTooltip: true,
2097
2101
  displayDescription: true,
2098
2102
  displayName: true,
2103
+ monitorGroupId: true,
2099
2104
  monitor: {
2100
2105
  _id: true,
2101
2106
  currentMonitorStatusId: true,
@@ -2109,6 +2114,65 @@ export default class StatusPageAPI extends BaseAPI<
2109
2114
  },
2110
2115
  });
2111
2116
 
2117
+ const monitorGroupIds: Array<ObjectID> = statusPageResources
2118
+ .map((resource: StatusPageResource) => {
2119
+ return resource.monitorGroupId!;
2120
+ })
2121
+ .filter((id: ObjectID) => {
2122
+ return Boolean(id); // remove nulls
2123
+ });
2124
+
2125
+ // get monitors in the group.
2126
+ const monitorsInGroup: Dictionary<Array<ObjectID>> = {};
2127
+
2128
+ // get monitor status charts.
2129
+ const monitorsOnStatusPage: Array<ObjectID> = statusPageResources
2130
+ .map((monitor: StatusPageResource) => {
2131
+ return monitor.monitorId!;
2132
+ })
2133
+ .filter((id: ObjectID) => {
2134
+ return Boolean(id); // remove nulls
2135
+ });
2136
+
2137
+ for (const monitorGroupId of monitorGroupIds) {
2138
+ // get monitors in the group.
2139
+
2140
+ const groupResources: Array<MonitorGroupResource> =
2141
+ await MonitorGroupResourceService.findBy({
2142
+ query: {
2143
+ monitorGroupId: monitorGroupId,
2144
+ },
2145
+ select: {
2146
+ monitorId: true,
2147
+ },
2148
+ props: {
2149
+ isRoot: true,
2150
+ },
2151
+ limit: LIMIT_PER_PROJECT,
2152
+ skip: 0,
2153
+ });
2154
+
2155
+ const monitorsInGroupIds: Array<ObjectID> = groupResources
2156
+ .map((resource: MonitorGroupResource) => {
2157
+ return resource.monitorId!;
2158
+ })
2159
+ .filter((id: ObjectID) => {
2160
+ return Boolean(id); // remove nulls
2161
+ });
2162
+
2163
+ for (const monitorId of monitorsInGroupIds) {
2164
+ if (
2165
+ !monitorsOnStatusPage.find((item: ObjectID) => {
2166
+ return item.toString() === monitorId.toString();
2167
+ })
2168
+ ) {
2169
+ monitorsOnStatusPage.push(monitorId);
2170
+ }
2171
+ }
2172
+
2173
+ monitorsInGroup[monitorGroupId.toString()] = monitorsInGroupIds;
2174
+ }
2175
+
2112
2176
  const response: JSONObject = {
2113
2177
  announcements: BaseModel.toJSONArray(
2114
2178
  announcements,
@@ -2118,6 +2182,7 @@ export default class StatusPageAPI extends BaseAPI<
2118
2182
  statusPageResources,
2119
2183
  StatusPageResource,
2120
2184
  ),
2185
+ monitorsInGroup: JSONFunctions.serialize(monitorsInGroup),
2121
2186
  };
2122
2187
 
2123
2188
  return response;
@@ -0,0 +1,41 @@
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ export class MigrationName1757416939595 implements MigrationInterface {
4
+ public name = "MigrationName1757416939595";
5
+
6
+ public async up(queryRunner: QueryRunner): Promise<void> {
7
+ await queryRunner.query(
8
+ `ALTER TABLE "IncidentTemplate" ADD "initialIncidentStateId" uuid`,
9
+ );
10
+ await queryRunner.query(
11
+ `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type":"Recurring","value":{"intervalType":"Day","intervalCount":{"_type":"PositiveNumber","value":1}}}'`,
12
+ );
13
+ await queryRunner.query(
14
+ `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type":"RestrictionTimes","value":{"restictionType":"None","dayRestrictionTimes":null,"weeklyRestrictionTimes":[]}}'`,
15
+ );
16
+ await queryRunner.query(
17
+ `CREATE INDEX "IDX_36317c99429a40d3344d838223" ON "IncidentTemplate" ("initialIncidentStateId") `,
18
+ );
19
+ await queryRunner.query(
20
+ `ALTER TABLE "IncidentTemplate" ADD CONSTRAINT "FK_36317c99429a40d3344d838223f" FOREIGN KEY ("initialIncidentStateId") REFERENCES "IncidentState"("_id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
21
+ );
22
+ }
23
+
24
+ public async down(queryRunner: QueryRunner): Promise<void> {
25
+ await queryRunner.query(
26
+ `ALTER TABLE "IncidentTemplate" DROP CONSTRAINT "FK_36317c99429a40d3344d838223f"`,
27
+ );
28
+ await queryRunner.query(
29
+ `DROP INDEX "public"."IDX_36317c99429a40d3344d838223"`,
30
+ );
31
+ await queryRunner.query(
32
+ `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type": "RestrictionTimes", "value": {"restictionType": "None", "dayRestrictionTimes": null, "weeklyRestrictionTimes": []}}'`,
33
+ );
34
+ await queryRunner.query(
35
+ `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type": "Recurring", "value": {"intervalType": "Day", "intervalCount": {"_type": "PositiveNumber", "value": 1}}}'`,
36
+ );
37
+ await queryRunner.query(
38
+ `ALTER TABLE "IncidentTemplate" DROP COLUMN "initialIncidentStateId"`,
39
+ );
40
+ }
41
+ }
@@ -0,0 +1,79 @@
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ export class MigrationName1757423505855 implements MigrationInterface {
4
+ public name = "MigrationName1757423505855";
5
+
6
+ public async up(queryRunner: QueryRunner): Promise<void> {
7
+ await queryRunner.query(
8
+ `CREATE TABLE "AnnouncementMonitor" ("announcementId" uuid NOT NULL, "monitorId" uuid NOT NULL, CONSTRAINT "PK_7acb54ddede76e67b5e2eb84519" PRIMARY KEY ("announcementId", "monitorId"))`,
9
+ );
10
+ await queryRunner.query(
11
+ `CREATE INDEX "IDX_b43baa07f7be40b5cfb61153fd" ON "AnnouncementMonitor" ("announcementId") `,
12
+ );
13
+ await queryRunner.query(
14
+ `CREATE INDEX "IDX_751be8c61cfeb7e1a0af9fcc3a" ON "AnnouncementMonitor" ("monitorId") `,
15
+ );
16
+ await queryRunner.query(
17
+ `CREATE TABLE "AnnouncementTemplateMonitor" ("announcementTemplateId" uuid NOT NULL, "monitorId" uuid NOT NULL, CONSTRAINT "PK_ad19f2b65c1b6b77e7a8d80c028" PRIMARY KEY ("announcementTemplateId", "monitorId"))`,
18
+ );
19
+ await queryRunner.query(
20
+ `CREATE INDEX "IDX_46bee9106e631ebe9f6c95ff15" ON "AnnouncementTemplateMonitor" ("announcementTemplateId") `,
21
+ );
22
+ await queryRunner.query(
23
+ `CREATE INDEX "IDX_0d979cb538fde87c7441d7bc93" ON "AnnouncementTemplateMonitor" ("monitorId") `,
24
+ );
25
+ await queryRunner.query(
26
+ `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type":"Recurring","value":{"intervalType":"Day","intervalCount":{"_type":"PositiveNumber","value":1}}}'`,
27
+ );
28
+ await queryRunner.query(
29
+ `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type":"RestrictionTimes","value":{"restictionType":"None","dayRestrictionTimes":null,"weeklyRestrictionTimes":[]}}'`,
30
+ );
31
+ await queryRunner.query(
32
+ `ALTER TABLE "AnnouncementMonitor" ADD CONSTRAINT "FK_b43baa07f7be40b5cfb61153fd3" FOREIGN KEY ("announcementId") REFERENCES "StatusPageAnnouncement"("_id") ON DELETE CASCADE ON UPDATE CASCADE`,
33
+ );
34
+ await queryRunner.query(
35
+ `ALTER TABLE "AnnouncementMonitor" ADD CONSTRAINT "FK_751be8c61cfeb7e1a0af9fcc3a0" FOREIGN KEY ("monitorId") REFERENCES "Monitor"("_id") ON DELETE CASCADE ON UPDATE CASCADE`,
36
+ );
37
+ await queryRunner.query(
38
+ `ALTER TABLE "AnnouncementTemplateMonitor" ADD CONSTRAINT "FK_46bee9106e631ebe9f6c95ff153" FOREIGN KEY ("announcementTemplateId") REFERENCES "StatusPageAnnouncementTemplate"("_id") ON DELETE CASCADE ON UPDATE CASCADE`,
39
+ );
40
+ await queryRunner.query(
41
+ `ALTER TABLE "AnnouncementTemplateMonitor" ADD CONSTRAINT "FK_0d979cb538fde87c7441d7bc936" FOREIGN KEY ("monitorId") REFERENCES "Monitor"("_id") ON DELETE CASCADE ON UPDATE CASCADE`,
42
+ );
43
+ }
44
+
45
+ public async down(queryRunner: QueryRunner): Promise<void> {
46
+ await queryRunner.query(
47
+ `ALTER TABLE "AnnouncementTemplateMonitor" DROP CONSTRAINT "FK_0d979cb538fde87c7441d7bc936"`,
48
+ );
49
+ await queryRunner.query(
50
+ `ALTER TABLE "AnnouncementTemplateMonitor" DROP CONSTRAINT "FK_46bee9106e631ebe9f6c95ff153"`,
51
+ );
52
+ await queryRunner.query(
53
+ `ALTER TABLE "AnnouncementMonitor" DROP CONSTRAINT "FK_751be8c61cfeb7e1a0af9fcc3a0"`,
54
+ );
55
+ await queryRunner.query(
56
+ `ALTER TABLE "AnnouncementMonitor" DROP CONSTRAINT "FK_b43baa07f7be40b5cfb61153fd3"`,
57
+ );
58
+ await queryRunner.query(
59
+ `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type": "RestrictionTimes", "value": {"restictionType": "None", "dayRestrictionTimes": null, "weeklyRestrictionTimes": []}}'`,
60
+ );
61
+ await queryRunner.query(
62
+ `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type": "Recurring", "value": {"intervalType": "Day", "intervalCount": {"_type": "PositiveNumber", "value": 1}}}'`,
63
+ );
64
+ await queryRunner.query(
65
+ `DROP INDEX "public"."IDX_0d979cb538fde87c7441d7bc93"`,
66
+ );
67
+ await queryRunner.query(
68
+ `DROP INDEX "public"."IDX_46bee9106e631ebe9f6c95ff15"`,
69
+ );
70
+ await queryRunner.query(`DROP TABLE "AnnouncementTemplateMonitor"`);
71
+ await queryRunner.query(
72
+ `DROP INDEX "public"."IDX_751be8c61cfeb7e1a0af9fcc3a"`,
73
+ );
74
+ await queryRunner.query(
75
+ `DROP INDEX "public"."IDX_b43baa07f7be40b5cfb61153fd"`,
76
+ );
77
+ await queryRunner.query(`DROP TABLE "AnnouncementMonitor"`);
78
+ }
79
+ }
@@ -165,6 +165,8 @@ import { MigrationName1756293325324 } from "./1756293325324-MigrationName";
165
165
  import { MigrationName1756296282627 } from "./1756296282627-MigrationName";
166
166
  import { MigrationName1756300358095 } from "./1756300358095-MigrationName";
167
167
  import { MigrationName1756821449686 } from "./1756821449686-MigrationName";
168
+ import { MigrationName1757416939595 } from "./1757416939595-MigrationName";
169
+ import { MigrationName1757423505855 } from "./1757423505855-MigrationName";
168
170
 
169
171
  export default [
170
172
  InitialMigration,
@@ -334,4 +336,6 @@ export default [
334
336
  MigrationName1756296282627,
335
337
  MigrationName1756300358095,
336
338
  MigrationName1756821449686,
339
+ MigrationName1757416939595,
340
+ MigrationName1757423505855,
337
341
  ];
@@ -64,6 +64,8 @@ import MetricType from "../../Models/DatabaseModels/MetricType";
64
64
  import UpdateBy from "../Types/Database/UpdateBy";
65
65
  import OnCallDutyPolicy from "../../Models/DatabaseModels/OnCallDutyPolicy";
66
66
  import Dictionary from "../../Types/Dictionary";
67
+ import IncidentTemplateService from "./IncidentTemplateService";
68
+ import IncidentTemplate from "../../Models/DatabaseModels/IncidentTemplate";
67
69
 
68
70
  // key is incidentId for this dictionary.
69
71
  type UpdateCarryForward = Dictionary<{
@@ -466,24 +468,97 @@ export class Service extends DatabaseService<Model> {
466
468
  const projectId: ObjectID =
467
469
  createBy.props.tenantId || createBy.data.projectId!;
468
470
 
469
- const incidentState: IncidentState | null =
470
- await IncidentStateService.findOneBy({
471
- query: {
472
- projectId: projectId,
473
- isCreatedState: true,
474
- },
475
- select: {
476
- _id: true,
477
- },
478
- props: {
479
- isRoot: true,
480
- },
481
- });
471
+ // Determine the initial incident state
472
+ let initialIncidentStateId: ObjectID | undefined = undefined;
482
473
 
483
- if (!incidentState || !incidentState.id) {
484
- throw new BadDataException(
485
- "Created incident state not found for this project. Please add created incident state from settings.",
486
- );
474
+ // If currentIncidentStateId is already provided (manual selection), use it
475
+ if (createBy.data.currentIncidentStateId) {
476
+ initialIncidentStateId = createBy.data.currentIncidentStateId;
477
+
478
+ // Validate that the provided state exists and belongs to the project
479
+ const providedState: IncidentState | null =
480
+ await IncidentStateService.findOneBy({
481
+ query: {
482
+ _id: initialIncidentStateId.toString(),
483
+ projectId: projectId,
484
+ },
485
+ select: {
486
+ _id: true,
487
+ },
488
+ props: {
489
+ isRoot: true,
490
+ },
491
+ });
492
+
493
+ if (!providedState) {
494
+ throw new BadDataException(
495
+ "Invalid incident state provided. The state does not exist or does not belong to this project.",
496
+ );
497
+ }
498
+ } else if (createBy.data.createdIncidentTemplateId) {
499
+ // If created from a template, check if template has a custom initial state
500
+ const incidentTemplate: IncidentTemplate | null =
501
+ await IncidentTemplateService.findOneBy({
502
+ query: {
503
+ _id: createBy.data.createdIncidentTemplateId.toString(),
504
+ projectId: projectId,
505
+ },
506
+ select: {
507
+ initialIncidentStateId: true,
508
+ },
509
+ props: {
510
+ isRoot: true,
511
+ },
512
+ });
513
+
514
+ if (incidentTemplate?.initialIncidentStateId) {
515
+ initialIncidentStateId = incidentTemplate.initialIncidentStateId;
516
+
517
+ // Validate that the template's state exists and belongs to the project
518
+ const templateState: IncidentState | null =
519
+ await IncidentStateService.findOneBy({
520
+ query: {
521
+ _id: initialIncidentStateId.toString(),
522
+ projectId: projectId,
523
+ },
524
+ select: {
525
+ _id: true,
526
+ },
527
+ props: {
528
+ isRoot: true,
529
+ },
530
+ });
531
+
532
+ if (!templateState) {
533
+ // Fall back to default if template state is invalid
534
+ initialIncidentStateId = undefined;
535
+ }
536
+ }
537
+ }
538
+
539
+ // If no custom state is provided or found, fall back to default created state
540
+ if (!initialIncidentStateId) {
541
+ const incidentState: IncidentState | null =
542
+ await IncidentStateService.findOneBy({
543
+ query: {
544
+ projectId: projectId,
545
+ isCreatedState: true,
546
+ },
547
+ select: {
548
+ _id: true,
549
+ },
550
+ props: {
551
+ isRoot: true,
552
+ },
553
+ });
554
+
555
+ if (!incidentState || !incidentState.id) {
556
+ throw new BadDataException(
557
+ "Created incident state not found for this project. Please add created incident state from settings.",
558
+ );
559
+ }
560
+
561
+ initialIncidentStateId = incidentState.id;
487
562
  }
488
563
 
489
564
  let mutex: SemaphoreMutex | null = null;
@@ -517,7 +592,7 @@ export class Service extends DatabaseService<Model> {
517
592
  projectId: projectId,
518
593
  })) + 1;
519
594
 
520
- createBy.data.currentIncidentStateId = incidentState.id;
595
+ createBy.data.currentIncidentStateId = initialIncidentStateId;
521
596
  createBy.data.incidentNumber = incidentNumberForThisIncident;
522
597
 
523
598
  if (
@@ -804,7 +804,10 @@ Stay informed about service availability! 🚀`;
804
804
  if (
805
805
  data.statusPage.allowSubscribersToChooseResources &&
806
806
  !data.subscriber.isSubscribedToAllResources &&
807
- data.eventType !== StatusPageEventType.Announcement // announcements dont have resources
807
+ !(
808
+ data.eventType === StatusPageEventType.Announcement &&
809
+ data.statusPageResources.length === 0
810
+ ) // announcements with no monitors don't use resource filtering
808
811
  ) {
809
812
  logger.debug(
810
813
  "Subscriber can choose resources and is not subscribed to all resources.",
@@ -3,10 +3,6 @@ import React, { ReactElement } from "react";
3
3
  export default class MarkdownUtil {
4
4
  public static getMarkdownCheatsheet(prefix?: string): ReactElement {
5
5
  const prefixText: string = prefix ? `${prefix}. ` : "";
6
- return (
7
- <p>
8
- {prefixText}
9
- </p>
10
- );
6
+ return <p>{prefixText}</p>;
11
7
  }
12
8
  }
@@ -57,6 +57,8 @@ let IncidentTemplate = class IncidentTemplate extends BaseModel {
57
57
  this.incidentSeverityId = undefined;
58
58
  this.changeMonitorStatusTo = undefined;
59
59
  this.changeMonitorStatusToId = undefined;
60
+ this.initialIncidentState = undefined;
61
+ this.initialIncidentStateId = undefined;
60
62
  this.customFields = undefined;
61
63
  }
62
64
  };
@@ -670,6 +672,74 @@ __decorate([
670
672
  }),
671
673
  __metadata("design:type", ObjectID)
672
674
  ], IncidentTemplate.prototype, "changeMonitorStatusToId", void 0);
675
+ __decorate([
676
+ ColumnAccessControl({
677
+ create: [
678
+ Permission.ProjectOwner,
679
+ Permission.ProjectAdmin,
680
+ Permission.ProjectMember,
681
+ Permission.CreateIncidentTemplate,
682
+ ],
683
+ read: [
684
+ Permission.ProjectOwner,
685
+ Permission.ProjectAdmin,
686
+ Permission.ProjectMember,
687
+ Permission.ReadIncidentTemplate,
688
+ ],
689
+ update: [],
690
+ }),
691
+ TableColumn({
692
+ manyToOneRelationColumn: "initialIncidentStateId",
693
+ type: TableColumnType.Entity,
694
+ modelType: IncidentState,
695
+ title: "Initial Incident State",
696
+ description: "Relation to Incident State Object. Incidents created from this template will start in this state.",
697
+ }),
698
+ ManyToOne(() => {
699
+ return IncidentState;
700
+ }, {
701
+ eager: false,
702
+ nullable: true,
703
+ orphanedRowAction: "nullify",
704
+ }),
705
+ JoinColumn({ name: "initialIncidentStateId" }),
706
+ __metadata("design:type", IncidentState)
707
+ ], IncidentTemplate.prototype, "initialIncidentState", void 0);
708
+ __decorate([
709
+ ColumnAccessControl({
710
+ create: [
711
+ Permission.ProjectOwner,
712
+ Permission.ProjectAdmin,
713
+ Permission.ProjectMember,
714
+ Permission.CreateIncidentTemplate,
715
+ ],
716
+ read: [
717
+ Permission.ProjectOwner,
718
+ Permission.ProjectAdmin,
719
+ Permission.ProjectMember,
720
+ Permission.ReadIncidentTemplate,
721
+ ],
722
+ update: [
723
+ Permission.ProjectOwner,
724
+ Permission.ProjectAdmin,
725
+ Permission.ProjectMember,
726
+ Permission.EditIncidentTemplate,
727
+ ],
728
+ }),
729
+ Index(),
730
+ TableColumn({
731
+ type: TableColumnType.ObjectID,
732
+ required: false,
733
+ title: "Initial Incident State ID",
734
+ description: "Relation to Incident State Object ID. Incidents created from this template will start in this state.",
735
+ }),
736
+ Column({
737
+ type: ColumnType.ObjectID,
738
+ nullable: true,
739
+ transformer: ObjectID.getDatabaseTransformer(),
740
+ }),
741
+ __metadata("design:type", ObjectID)
742
+ ], IncidentTemplate.prototype, "initialIncidentStateId", void 0);
673
743
  __decorate([
674
744
  ColumnAccessControl({
675
745
  create: [