@oneuptime/common 8.0.5489 → 8.0.5493

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 (25) hide show
  1. package/Models/DatabaseModels/Incident.ts +33 -0
  2. package/Models/DatabaseModels/IncidentFeed.ts +1 -0
  3. package/Models/DatabaseModels/IncidentPostmortemTemplate.ts +353 -0
  4. package/Models/DatabaseModels/Index.ts +2 -0
  5. package/Server/Infrastructure/Postgres/SchemaMigrations/1761834523183-MigrationName.ts +49 -0
  6. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
  7. package/Server/Services/IncidentPostmortemTemplateService.ts +10 -0
  8. package/Server/Services/IncidentService.ts +81 -39
  9. package/build/dist/Models/DatabaseModels/Incident.js +35 -0
  10. package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
  11. package/build/dist/Models/DatabaseModels/IncidentFeed.js +1 -0
  12. package/build/dist/Models/DatabaseModels/IncidentFeed.js.map +1 -1
  13. package/build/dist/Models/DatabaseModels/IncidentPostmortemTemplate.js +373 -0
  14. package/build/dist/Models/DatabaseModels/IncidentPostmortemTemplate.js.map +1 -0
  15. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  16. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  17. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1761834523183-MigrationName.js +24 -0
  18. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1761834523183-MigrationName.js.map +1 -0
  19. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
  20. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  21. package/build/dist/Server/Services/IncidentPostmortemTemplateService.js +9 -0
  22. package/build/dist/Server/Services/IncidentPostmortemTemplateService.js.map +1 -0
  23. package/build/dist/Server/Services/IncidentService.js +51 -33
  24. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  25. package/package.json +1 -1
@@ -926,6 +926,39 @@ export default class Incident extends BaseModel {
926
926
  })
927
927
  public rootCause?: string = undefined;
928
928
 
929
+ @ColumnAccessControl({
930
+ create: [
931
+ Permission.ProjectOwner,
932
+ Permission.ProjectAdmin,
933
+ Permission.ProjectMember,
934
+ Permission.CreateProjectIncident,
935
+ ],
936
+ read: [
937
+ Permission.ProjectOwner,
938
+ Permission.ProjectAdmin,
939
+ Permission.ProjectMember,
940
+ Permission.ReadProjectIncident,
941
+ ],
942
+ update: [
943
+ Permission.ProjectOwner,
944
+ Permission.ProjectAdmin,
945
+ Permission.ProjectMember,
946
+ Permission.EditProjectIncident,
947
+ ],
948
+ })
949
+ @TableColumn({
950
+ type: TableColumnType.Markdown,
951
+ required: false,
952
+ isDefaultValueColumn: false,
953
+ title: "Postmortem Note",
954
+ description: "Document the postmortem summary for this incident.",
955
+ })
956
+ @Column({
957
+ type: ColumnType.Markdown,
958
+ nullable: true,
959
+ })
960
+ public postmortemNote?: string = undefined;
961
+
929
962
  @ColumnAccessControl({
930
963
  create: [],
931
964
  read: [
@@ -33,6 +33,7 @@ export enum IncidentFeedEventType {
33
33
  IncidentUpdated = "IncidentUpdated",
34
34
  RootCause = "RootCause",
35
35
  RemediationNotes = "RemediationNotes",
36
+ PostmortemNote = "PostmortemNote",
36
37
  OwnerUserRemoved = "OwnerUserRemoved",
37
38
  OwnerTeamRemoved = "OwnerTeamRemoved",
38
39
  OnCallPolicy = "OnCallPolicy",
@@ -0,0 +1,353 @@
1
+ import Project from "./Project";
2
+ import User from "./User";
3
+ import BaseModel from "./DatabaseBaseModel/DatabaseBaseModel";
4
+ import Route from "../../Types/API/Route";
5
+ import ColumnAccessControl from "../../Types/Database/AccessControl/ColumnAccessControl";
6
+ import TableAccessControl from "../../Types/Database/AccessControl/TableAccessControl";
7
+ import ColumnLength from "../../Types/Database/ColumnLength";
8
+ import ColumnType from "../../Types/Database/ColumnType";
9
+ import CrudApiEndpoint from "../../Types/Database/CrudApiEndpoint";
10
+ import EnableDocumentation from "../../Types/Database/EnableDocumentation";
11
+ import EnableWorkflow from "../../Types/Database/EnableWorkflow";
12
+ import TableColumn from "../../Types/Database/TableColumn";
13
+ import TableColumnType from "../../Types/Database/TableColumnType";
14
+ import TableMetadata from "../../Types/Database/TableMetadata";
15
+ import TenantColumn from "../../Types/Database/TenantColumn";
16
+ import IconProp from "../../Types/Icon/IconProp";
17
+ import ObjectID from "../../Types/ObjectID";
18
+ import Permission from "../../Types/Permission";
19
+ import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm";
20
+ import TableBillingAccessControl from "../../Types/Database/AccessControl/TableBillingAccessControl";
21
+ import { PlanType } from "../../Types/Billing/SubscriptionPlan";
22
+
23
+ @TableBillingAccessControl({
24
+ create: PlanType.Growth,
25
+ read: PlanType.Growth,
26
+ update: PlanType.Growth,
27
+ delete: PlanType.Growth,
28
+ })
29
+ @EnableDocumentation()
30
+ @TenantColumn("projectId")
31
+ @TableAccessControl({
32
+ create: [
33
+ Permission.ProjectOwner,
34
+ Permission.ProjectAdmin,
35
+ Permission.ProjectMember,
36
+ Permission.CreateIncidentNoteTemplate,
37
+ ],
38
+ read: [
39
+ Permission.ProjectOwner,
40
+ Permission.ProjectAdmin,
41
+ Permission.ProjectMember,
42
+ Permission.ReadIncidentNoteTemplate,
43
+ ],
44
+ delete: [
45
+ Permission.ProjectOwner,
46
+ Permission.ProjectAdmin,
47
+ Permission.ProjectMember,
48
+ Permission.DeleteIncidentNoteTemplate,
49
+ ],
50
+ update: [
51
+ Permission.ProjectOwner,
52
+ Permission.ProjectAdmin,
53
+ Permission.ProjectMember,
54
+ Permission.EditIncidentNoteTemplate,
55
+ ],
56
+ })
57
+ @CrudApiEndpoint(new Route("/incident-postmortem-template"))
58
+ @Entity({
59
+ name: "IncidentPostmortemTemplate",
60
+ })
61
+ @EnableWorkflow({
62
+ create: true,
63
+ delete: true,
64
+ update: true,
65
+ read: true,
66
+ })
67
+ @TableMetadata({
68
+ tableName: "IncidentPostmortemTemplate",
69
+ singularName: "Incident Postmortem Template",
70
+ pluralName: "Incident Postmortem Templates",
71
+ icon: IconProp.Book,
72
+ tableDescription: "Manage postmortem templates for your incidents",
73
+ })
74
+ export default class IncidentPostmortemTemplate extends BaseModel {
75
+ @ColumnAccessControl({
76
+ create: [
77
+ Permission.ProjectOwner,
78
+ Permission.ProjectAdmin,
79
+ Permission.ProjectMember,
80
+ Permission.CreateIncidentNoteTemplate,
81
+ ],
82
+ read: [
83
+ Permission.ProjectOwner,
84
+ Permission.ProjectAdmin,
85
+ Permission.ProjectMember,
86
+ Permission.ReadIncidentNoteTemplate,
87
+ ],
88
+ update: [],
89
+ })
90
+ @TableColumn({
91
+ manyToOneRelationColumn: "projectId",
92
+ type: TableColumnType.Entity,
93
+ modelType: Project,
94
+ title: "Project",
95
+ description: "Relation to Project Resource in which this object belongs",
96
+ })
97
+ @ManyToOne(
98
+ () => {
99
+ return Project;
100
+ },
101
+ {
102
+ eager: false,
103
+ nullable: true,
104
+ onDelete: "CASCADE",
105
+ orphanedRowAction: "nullify",
106
+ },
107
+ )
108
+ @JoinColumn({ name: "projectId" })
109
+ public project?: Project = undefined;
110
+
111
+ @ColumnAccessControl({
112
+ create: [
113
+ Permission.ProjectOwner,
114
+ Permission.ProjectAdmin,
115
+ Permission.ProjectMember,
116
+ Permission.CreateIncidentNoteTemplate,
117
+ ],
118
+ read: [
119
+ Permission.ProjectOwner,
120
+ Permission.ProjectAdmin,
121
+ Permission.ProjectMember,
122
+ Permission.ReadIncidentNoteTemplate,
123
+ ],
124
+ update: [],
125
+ })
126
+ @Index()
127
+ @TableColumn({
128
+ type: TableColumnType.ObjectID,
129
+ required: true,
130
+ canReadOnRelationQuery: true,
131
+ title: "Project ID",
132
+ description: "ID of your OneUptime Project in which this object belongs",
133
+ })
134
+ @Column({
135
+ type: ColumnType.ObjectID,
136
+ nullable: false,
137
+ transformer: ObjectID.getDatabaseTransformer(),
138
+ })
139
+ public projectId?: ObjectID = undefined;
140
+
141
+ @ColumnAccessControl({
142
+ create: [
143
+ Permission.ProjectOwner,
144
+ Permission.ProjectAdmin,
145
+ Permission.ProjectMember,
146
+ Permission.CreateIncidentNoteTemplate,
147
+ ],
148
+ read: [
149
+ Permission.ProjectOwner,
150
+ Permission.ProjectAdmin,
151
+ Permission.ProjectMember,
152
+ Permission.ReadIncidentNoteTemplate,
153
+ ],
154
+ update: [
155
+ Permission.ProjectOwner,
156
+ Permission.ProjectAdmin,
157
+ Permission.ProjectMember,
158
+ Permission.EditIncidentNoteTemplate,
159
+ ],
160
+ })
161
+ @Index()
162
+ @TableColumn({
163
+ type: TableColumnType.Markdown,
164
+ title: "Postmortem Note",
165
+ description:
166
+ "Markdown template used when documenting an incident postmortem.",
167
+ })
168
+ @Column({
169
+ type: ColumnType.Markdown,
170
+ nullable: false,
171
+ unique: false,
172
+ })
173
+ public postmortemNote?: string = undefined;
174
+
175
+ @ColumnAccessControl({
176
+ create: [
177
+ Permission.ProjectOwner,
178
+ Permission.ProjectAdmin,
179
+ Permission.ProjectMember,
180
+ Permission.CreateIncidentNoteTemplate,
181
+ ],
182
+ read: [
183
+ Permission.ProjectOwner,
184
+ Permission.ProjectAdmin,
185
+ Permission.ProjectMember,
186
+ Permission.ReadIncidentNoteTemplate,
187
+ ],
188
+ update: [
189
+ Permission.ProjectOwner,
190
+ Permission.ProjectAdmin,
191
+ Permission.ProjectMember,
192
+ Permission.EditIncidentNoteTemplate,
193
+ ],
194
+ })
195
+ @TableColumn({
196
+ required: true,
197
+ type: TableColumnType.ShortText,
198
+ canReadOnRelationQuery: true,
199
+ title: "Name",
200
+ description: "Name of the Postmortem Template",
201
+ })
202
+ @Column({
203
+ nullable: false,
204
+ type: ColumnType.ShortText,
205
+ length: ColumnLength.ShortText,
206
+ })
207
+ public templateName?: string = undefined;
208
+
209
+ @ColumnAccessControl({
210
+ create: [
211
+ Permission.ProjectOwner,
212
+ Permission.ProjectAdmin,
213
+ Permission.ProjectMember,
214
+ Permission.CreateIncidentNoteTemplate,
215
+ ],
216
+ read: [
217
+ Permission.ProjectOwner,
218
+ Permission.ProjectAdmin,
219
+ Permission.ProjectMember,
220
+ Permission.ReadIncidentNoteTemplate,
221
+ ],
222
+ update: [
223
+ Permission.ProjectOwner,
224
+ Permission.ProjectAdmin,
225
+ Permission.ProjectMember,
226
+ Permission.EditIncidentNoteTemplate,
227
+ ],
228
+ })
229
+ @TableColumn({
230
+ required: true,
231
+ type: TableColumnType.LongText,
232
+ canReadOnRelationQuery: true,
233
+ title: "Template Description",
234
+ description: "Description of the Postmortem Template",
235
+ })
236
+ @Column({
237
+ nullable: false,
238
+ type: ColumnType.LongText,
239
+ length: ColumnLength.LongText,
240
+ })
241
+ public templateDescription?: string = undefined;
242
+
243
+ @ColumnAccessControl({
244
+ create: [
245
+ Permission.ProjectOwner,
246
+ Permission.ProjectAdmin,
247
+ Permission.ProjectMember,
248
+ Permission.CreateIncidentNoteTemplate,
249
+ ],
250
+ read: [
251
+ Permission.ProjectOwner,
252
+ Permission.ProjectAdmin,
253
+ Permission.ProjectMember,
254
+ Permission.ReadIncidentNoteTemplate,
255
+ ],
256
+ update: [],
257
+ })
258
+ @TableColumn({
259
+ manyToOneRelationColumn: "createdByUserId",
260
+ type: TableColumnType.Entity,
261
+ modelType: User,
262
+ title: "Created by User",
263
+ description:
264
+ "Relation to User who created this object (if this object was created by a User)",
265
+ })
266
+ @ManyToOne(
267
+ () => {
268
+ return User;
269
+ },
270
+ {
271
+ eager: false,
272
+ nullable: true,
273
+ onDelete: "SET NULL",
274
+ orphanedRowAction: "nullify",
275
+ },
276
+ )
277
+ @JoinColumn({ name: "createdByUserId" })
278
+ public createdByUser?: User = undefined;
279
+
280
+ @ColumnAccessControl({
281
+ create: [
282
+ Permission.ProjectOwner,
283
+ Permission.ProjectAdmin,
284
+ Permission.ProjectMember,
285
+ Permission.CreateIncidentNoteTemplate,
286
+ ],
287
+ read: [
288
+ Permission.ProjectOwner,
289
+ Permission.ProjectAdmin,
290
+ Permission.ProjectMember,
291
+ Permission.ReadIncidentNoteTemplate,
292
+ ],
293
+ update: [],
294
+ })
295
+ @TableColumn({
296
+ type: TableColumnType.ObjectID,
297
+ title: "Created by User ID",
298
+ description:
299
+ "User ID who created this object (if this object was created by a User)",
300
+ })
301
+ @Column({
302
+ type: ColumnType.ObjectID,
303
+ nullable: true,
304
+ transformer: ObjectID.getDatabaseTransformer(),
305
+ })
306
+ public createdByUserId?: ObjectID = undefined;
307
+
308
+ @ColumnAccessControl({
309
+ create: [],
310
+ read: [],
311
+ update: [],
312
+ })
313
+ @TableColumn({
314
+ manyToOneRelationColumn: "deletedByUserId",
315
+ type: TableColumnType.Entity,
316
+ title: "Deleted by User",
317
+ modelType: User,
318
+ description:
319
+ "Relation to User who deleted this object (if this object was deleted by a User)",
320
+ })
321
+ @ManyToOne(
322
+ () => {
323
+ return User;
324
+ },
325
+ {
326
+ cascade: false,
327
+ eager: false,
328
+ nullable: true,
329
+ onDelete: "SET NULL",
330
+ orphanedRowAction: "nullify",
331
+ },
332
+ )
333
+ @JoinColumn({ name: "deletedByUserId" })
334
+ public deletedByUser?: User = undefined;
335
+
336
+ @ColumnAccessControl({
337
+ create: [],
338
+ read: [],
339
+ update: [],
340
+ })
341
+ @TableColumn({
342
+ type: TableColumnType.ObjectID,
343
+ title: "Deleted by User ID",
344
+ description:
345
+ "User ID who deleted this object (if this object was deleted by a User)",
346
+ })
347
+ @Column({
348
+ type: ColumnType.ObjectID,
349
+ nullable: true,
350
+ transformer: ObjectID.getDatabaseTransformer(),
351
+ })
352
+ public deletedByUserId?: ObjectID = undefined;
353
+ }
@@ -24,6 +24,7 @@ import IncidentFeed from "./IncidentFeed";
24
24
  import IncidentCustomField from "./IncidentCustomField";
25
25
  import IncidentInternalNote from "./IncidentInternalNote";
26
26
  import IncidentNoteTemplate from "./IncidentNoteTemplate";
27
+ import IncidentPostmortemTemplate from "./IncidentPostmortemTemplate";
27
28
  import IncidentOwnerTeam from "./IncidentOwnerTeam";
28
29
  import IncidentOwnerUser from "./IncidentOwnerUser";
29
30
  import IncidentPublicNote from "./IncidentPublicNote";
@@ -235,6 +236,7 @@ const AllModelTypes: Array<{
235
236
  IncidentOwnerUser,
236
237
  IncidentSeverity,
237
238
  IncidentNoteTemplate,
239
+ IncidentPostmortemTemplate,
238
240
 
239
241
  AlertState,
240
242
  Alert,
@@ -0,0 +1,49 @@
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ export class MigrationName1761834523183 implements MigrationInterface {
4
+ public name = "MigrationName1761834523183";
5
+
6
+ public async up(queryRunner: QueryRunner): Promise<void> {
7
+ await queryRunner.query(
8
+ `CREATE TABLE "IncidentPostmortemTemplate" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP WITH TIME ZONE, "version" integer NOT NULL, "projectId" uuid NOT NULL, "postmortemNote" text NOT NULL, "templateName" character varying(100) NOT NULL, "templateDescription" character varying(500) NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, CONSTRAINT "PK_76a09ebf10e7874f0c8ee1f0120" PRIMARY KEY ("_id"))`,
9
+ );
10
+ await queryRunner.query(
11
+ `CREATE INDEX "IDX_2a18729813c7c666cc37683c4e" ON "IncidentPostmortemTemplate" ("projectId") `,
12
+ );
13
+ await queryRunner.query(
14
+ `CREATE INDEX "IDX_c791fe4d7179b57064ace561c3" ON "IncidentPostmortemTemplate" ("postmortemNote") `,
15
+ );
16
+ await queryRunner.query(`ALTER TABLE "Incident" ADD "postmortemNote" text`);
17
+ await queryRunner.query(
18
+ `ALTER TABLE "IncidentPostmortemTemplate" ADD CONSTRAINT "FK_2a18729813c7c666cc37683c4ea" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`,
19
+ );
20
+ await queryRunner.query(
21
+ `ALTER TABLE "IncidentPostmortemTemplate" ADD CONSTRAINT "FK_961ac93c4d7ea881170692333d0" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`,
22
+ );
23
+ await queryRunner.query(
24
+ `ALTER TABLE "IncidentPostmortemTemplate" ADD CONSTRAINT "FK_2e886387888f1311f361d569b8e" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`,
25
+ );
26
+ }
27
+
28
+ public async down(queryRunner: QueryRunner): Promise<void> {
29
+ await queryRunner.query(
30
+ `ALTER TABLE "IncidentPostmortemTemplate" DROP CONSTRAINT "FK_2e886387888f1311f361d569b8e"`,
31
+ );
32
+ await queryRunner.query(
33
+ `ALTER TABLE "IncidentPostmortemTemplate" DROP CONSTRAINT "FK_961ac93c4d7ea881170692333d0"`,
34
+ );
35
+ await queryRunner.query(
36
+ `ALTER TABLE "IncidentPostmortemTemplate" DROP CONSTRAINT "FK_2a18729813c7c666cc37683c4ea"`,
37
+ );
38
+ await queryRunner.query(
39
+ `ALTER TABLE "Incident" DROP COLUMN "postmortemNote"`,
40
+ );
41
+ await queryRunner.query(
42
+ `DROP INDEX "public"."IDX_c791fe4d7179b57064ace561c3"`,
43
+ );
44
+ await queryRunner.query(
45
+ `DROP INDEX "public"."IDX_2a18729813c7c666cc37683c4e"`,
46
+ );
47
+ await queryRunner.query(`DROP TABLE "IncidentPostmortemTemplate"`);
48
+ }
49
+ }
@@ -178,6 +178,7 @@ import { MigrationName1759943124812 } from "./1759943124812-MigrationName";
178
178
  import { MigrationName1760345757975 } from "./1760345757975-MigrationName";
179
179
  import { MigrationName1760357680881 } from "./1760357680881-MigrationName";
180
180
  import { MigrationName1761232578396 } from "./1761232578396-MigrationName";
181
+ import { MigrationName1761834523183 } from "./1761834523183-MigrationName";
181
182
 
182
183
  export default [
183
184
  InitialMigration,
@@ -360,4 +361,5 @@ export default [
360
361
  MigrationName1760345757975,
361
362
  MigrationName1760357680881,
362
363
  MigrationName1761232578396,
364
+ MigrationName1761834523183,
363
365
  ];
@@ -0,0 +1,10 @@
1
+ import DatabaseService from "./DatabaseService";
2
+ import Model from "../../Models/DatabaseModels/IncidentPostmortemTemplate";
3
+
4
+ export class Service extends DatabaseService<Model> {
5
+ public constructor() {
6
+ super(Model);
7
+ }
8
+ }
9
+
10
+ export default new Service();
@@ -49,7 +49,7 @@ import Semaphore, {
49
49
  } from "../../Server/Infrastructure/Semaphore";
50
50
  import IncidentFeedService from "./IncidentFeedService";
51
51
  import { IncidentFeedEventType } from "../../Models/DatabaseModels/IncidentFeed";
52
- import { Gray500, Red500 } from "../../Types/BrandColors";
52
+ import { Blue500, Gray500, Red500 } from "../../Types/BrandColors";
53
53
  import Label from "../../Models/DatabaseModels/Label";
54
54
  import LabelService from "./LabelService";
55
55
  import IncidentSeverity from "../../Models/DatabaseModels/IncidentSeverity";
@@ -1297,59 +1297,103 @@ ${incident.remediationNotes || "No remediation notes provided."}
1297
1297
 
1298
1298
  const projectId: ObjectID = incident!.projectId!;
1299
1299
  const incidentNumber: number = incident!.incidentNumber!;
1300
+ const incidentLabel: string = `Incident ${incidentNumber}`;
1301
+ const incidentLink: URL = await this.getIncidentLinkInDashboard(
1302
+ projectId,
1303
+ incidentId,
1304
+ );
1300
1305
 
1301
- let shouldAddIncidentFeed: boolean = false;
1302
- let feedInfoInMarkdown: string = `**[Incident ${incidentNumber}](${(await this.getIncidentLinkInDashboard(projectId!, incidentId!)).toString()}) was updated.**`;
1306
+ const updatedIncidentData: Partial<Model> =
1307
+ onUpdate.updateBy.data || {};
1303
1308
 
1304
1309
  const createdByUserId: ObjectID | undefined | null =
1305
1310
  onUpdate.updateBy.props.userId;
1306
1311
 
1307
- if (onUpdate.updateBy.data.title) {
1308
- // add incident feed.
1312
+ if (
1313
+ Object.prototype.hasOwnProperty.call(
1314
+ updatedIncidentData,
1315
+ "postmortemNote",
1316
+ )
1317
+ ) {
1318
+ const noteValue: string =
1319
+ (updatedIncidentData.postmortemNote as string) || "";
1320
+ const hasNoteContent: boolean = noteValue.trim().length > 0;
1321
+
1322
+ const postmortemFeedMarkdown: string = hasNoteContent
1323
+ ? `**📘 Postmortem Note updated for [${incidentLabel}](${incidentLink.toString()})**\n\n${noteValue}`
1324
+ : `**📘 Postmortem Note cleared for [${incidentLabel}](${incidentLink.toString()})**\n\n_No postmortem note provided._`;
1309
1325
 
1310
- feedInfoInMarkdown += `\n\n**Title**:
1311
- ${onUpdate.updateBy.data.title || "No title provided."}
1312
- `;
1313
- shouldAddIncidentFeed = true;
1326
+ await IncidentFeedService.createIncidentFeedItem({
1327
+ incidentId,
1328
+ projectId,
1329
+ incidentFeedEventType: IncidentFeedEventType.PostmortemNote,
1330
+ displayColor: Blue500,
1331
+ feedInfoInMarkdown: postmortemFeedMarkdown,
1332
+ userId: createdByUserId || undefined,
1333
+ workspaceNotification: {
1334
+ sendWorkspaceNotification: true,
1335
+ },
1336
+ });
1314
1337
  }
1315
1338
 
1316
- if (onUpdate.updateBy.data.rootCause) {
1317
- if (onUpdate.updateBy.data.title) {
1318
- // add incident feed.
1339
+ let shouldAddIncidentFeed: boolean = false;
1340
+ let feedInfoInMarkdown: string = `**[${incidentLabel}](${incidentLink.toString()}) was updated.**`;
1319
1341
 
1320
- feedInfoInMarkdown += `\n\n**📄 Root Cause**:
1321
- ${onUpdate.updateBy.data.rootCause || "No root cause provided."}
1322
- `;
1323
- shouldAddIncidentFeed = true;
1324
- }
1342
+ if (
1343
+ Object.prototype.hasOwnProperty.call(updatedIncidentData, "title")
1344
+ ) {
1345
+ const title: string =
1346
+ (updatedIncidentData.title as string) || "No title provided.";
1347
+ feedInfoInMarkdown += `\n\n**Title**: \n${title}\n`;
1348
+ shouldAddIncidentFeed = true;
1325
1349
  }
1326
1350
 
1327
- if (onUpdate.updateBy.data.description) {
1328
- // add incident feed.
1329
-
1330
- feedInfoInMarkdown += `\n\n**Incident Description**:
1331
- ${onUpdate.updateBy.data.description || "No description provided."}
1332
- `;
1351
+ if (
1352
+ Object.prototype.hasOwnProperty.call(updatedIncidentData, "rootCause")
1353
+ ) {
1354
+ const rootCause: string =
1355
+ (updatedIncidentData.rootCause as string) || "";
1356
+ const rootCauseText: string = rootCause.trim().length
1357
+ ? rootCause
1358
+ : "Root cause removed.";
1359
+ feedInfoInMarkdown += `\n\n**📄 Root Cause**: \n${rootCauseText}\n`;
1333
1360
  shouldAddIncidentFeed = true;
1334
1361
  }
1335
1362
 
1336
- if (onUpdate.updateBy.data.remediationNotes) {
1337
- // add incident feed.
1363
+ if (
1364
+ Object.prototype.hasOwnProperty.call(
1365
+ updatedIncidentData,
1366
+ "description",
1367
+ )
1368
+ ) {
1369
+ const description: string =
1370
+ (updatedIncidentData.description as string) ||
1371
+ "No description provided.";
1372
+ feedInfoInMarkdown += `\n\n**Incident Description**: \n${description}\n`;
1373
+ shouldAddIncidentFeed = true;
1374
+ }
1338
1375
 
1339
- feedInfoInMarkdown += `\n\n**🎯 Remediation Notes**:
1340
- ${onUpdate.updateBy.data.remediationNotes || "No remediation notes provided."}
1341
- `;
1376
+ if (
1377
+ Object.prototype.hasOwnProperty.call(
1378
+ updatedIncidentData,
1379
+ "remediationNotes",
1380
+ )
1381
+ ) {
1382
+ const remediationNotes: string =
1383
+ (updatedIncidentData.remediationNotes as string) || "";
1384
+ const remediationText: string = remediationNotes.trim().length
1385
+ ? remediationNotes
1386
+ : "Remediation notes removed.";
1387
+ feedInfoInMarkdown += `\n\n**🎯 Remediation Notes**: \n${remediationText}\n`;
1342
1388
  shouldAddIncidentFeed = true;
1343
1389
  }
1344
1390
 
1345
1391
  if (
1346
- onUpdate.updateBy.data.labels &&
1347
- onUpdate.updateBy.data.labels.length > 0 &&
1348
- Array.isArray(onUpdate.updateBy.data.labels)
1392
+ updatedIncidentData.labels &&
1393
+ (updatedIncidentData.labels as Array<Label>).length > 0 &&
1394
+ Array.isArray(updatedIncidentData.labels)
1349
1395
  ) {
1350
- const labelIds: Array<ObjectID> = (
1351
- onUpdate.updateBy.data.labels as any
1352
- )
1396
+ const labelIds: Array<ObjectID> = (updatedIncidentData.labels as any)
1353
1397
  .map((label: Label) => {
1354
1398
  if (label._id) {
1355
1399
  return new ObjectID(label._id?.toString());
@@ -1390,16 +1434,14 @@ ${labels
1390
1434
  }
1391
1435
 
1392
1436
  if (
1393
- onUpdate.updateBy.data.incidentSeverity &&
1394
- (onUpdate.updateBy.data.incidentSeverity as any)._id
1437
+ updatedIncidentData.incidentSeverity &&
1438
+ (updatedIncidentData.incidentSeverity as any)._id
1395
1439
  ) {
1396
1440
  const incidentSeverity: IncidentSeverity | null =
1397
1441
  await IncidentSeverityService.findOneBy({
1398
1442
  query: {
1399
1443
  _id: new ObjectID(
1400
- (
1401
- onUpdate.updateBy.data.incidentSeverity as any
1402
- )?._id.toString(),
1444
+ (updatedIncidentData.incidentSeverity as any)?._id.toString(),
1403
1445
  ),
1404
1446
  },
1405
1447
  select: {