@oneuptime/common 9.1.3 → 9.2.1

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 (28) hide show
  1. package/Models/DatabaseModels/Incident.ts +112 -0
  2. package/Server/API/IncidentAPI.ts +106 -0
  3. package/Server/API/StatusPageAPI.ts +134 -0
  4. package/Server/Infrastructure/Postgres/SchemaMigrations/1764762146063-MigrationName.ts +45 -0
  5. package/Server/Infrastructure/Postgres/SchemaMigrations/1764767371788-MigrationName.ts +23 -0
  6. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
  7. package/Types/Icon/IconProp.ts +1 -0
  8. package/UI/Components/EventItem/EventItem.tsx +32 -14
  9. package/UI/Components/Icon/Icon.tsx +8 -0
  10. package/build/dist/Models/DatabaseModels/Incident.js +115 -0
  11. package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
  12. package/build/dist/Server/API/IncidentAPI.js +76 -0
  13. package/build/dist/Server/API/IncidentAPI.js.map +1 -0
  14. package/build/dist/Server/API/StatusPageAPI.js +154 -45
  15. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  16. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1764762146063-MigrationName.js +22 -0
  17. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1764762146063-MigrationName.js.map +1 -0
  18. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1764767371788-MigrationName.js +14 -0
  19. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1764767371788-MigrationName.js.map +1 -0
  20. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
  21. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  22. package/build/dist/Types/Icon/IconProp.js +1 -0
  23. package/build/dist/Types/Icon/IconProp.js.map +1 -1
  24. package/build/dist/UI/Components/EventItem/EventItem.js +10 -6
  25. package/build/dist/UI/Components/EventItem/EventItem.js.map +1 -1
  26. package/build/dist/UI/Components/Icon/Icon.js +3 -0
  27. package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
  28. package/package.json +1 -1
@@ -7,6 +7,7 @@ import OnCallDutyPolicy from "./OnCallDutyPolicy";
7
7
  import Probe from "./Probe";
8
8
  import Project from "./Project";
9
9
  import User from "./User";
10
+ import File from "./File";
10
11
  import BaseModel from "./DatabaseBaseModel/DatabaseBaseModel";
11
12
  import Route from "../../Types/API/Route";
12
13
  import ColumnAccessControl from "../../Types/Database/AccessControl/ColumnAccessControl";
@@ -996,6 +997,117 @@ export default class Incident extends BaseModel {
996
997
  })
997
998
  public postmortemNote?: string = undefined;
998
999
 
1000
+ @ColumnAccessControl({
1001
+ create: [
1002
+ Permission.ProjectOwner,
1003
+ Permission.ProjectAdmin,
1004
+ Permission.ProjectMember,
1005
+ Permission.CreateProjectIncident,
1006
+ ],
1007
+ read: [
1008
+ Permission.ProjectOwner,
1009
+ Permission.ProjectAdmin,
1010
+ Permission.ProjectMember,
1011
+ Permission.ReadProjectIncident,
1012
+ ],
1013
+ update: [
1014
+ Permission.ProjectOwner,
1015
+ Permission.ProjectAdmin,
1016
+ Permission.ProjectMember,
1017
+ Permission.EditProjectIncident,
1018
+ ],
1019
+ })
1020
+ @TableColumn({
1021
+ type: TableColumnType.Boolean,
1022
+ title: "Show postmortem on status page?",
1023
+ description:
1024
+ "Should the postmortem note and attachments be visible on the status page once published?",
1025
+ defaultValue: false,
1026
+ isDefaultValueColumn: true,
1027
+ })
1028
+ @Column({
1029
+ type: ColumnType.Boolean,
1030
+ default: false,
1031
+ })
1032
+ public showPostmortemOnStatusPage?: boolean = undefined;
1033
+
1034
+ @ColumnAccessControl({
1035
+ create: [
1036
+ Permission.ProjectOwner,
1037
+ Permission.ProjectAdmin,
1038
+ Permission.ProjectMember,
1039
+ Permission.CreateProjectIncident,
1040
+ ],
1041
+ read: [
1042
+ Permission.ProjectOwner,
1043
+ Permission.ProjectAdmin,
1044
+ Permission.ProjectMember,
1045
+ Permission.ReadProjectIncident,
1046
+ ],
1047
+ update: [
1048
+ Permission.ProjectOwner,
1049
+ Permission.ProjectAdmin,
1050
+ Permission.ProjectMember,
1051
+ Permission.EditProjectIncident,
1052
+ ],
1053
+ })
1054
+ @TableColumn({
1055
+ type: TableColumnType.Date,
1056
+ title: "Postmortem Posted At",
1057
+ description:
1058
+ "Timestamp that will be shown alongside the published postmortem on the status page.",
1059
+ required: false,
1060
+ })
1061
+ @Column({
1062
+ type: ColumnType.Date,
1063
+ nullable: true,
1064
+ })
1065
+ public postmortemPostedAt?: Date = undefined;
1066
+
1067
+ @ColumnAccessControl({
1068
+ create: [
1069
+ Permission.ProjectOwner,
1070
+ Permission.ProjectAdmin,
1071
+ Permission.ProjectMember,
1072
+ Permission.CreateProjectIncident,
1073
+ ],
1074
+ read: [
1075
+ Permission.ProjectOwner,
1076
+ Permission.ProjectAdmin,
1077
+ Permission.ProjectMember,
1078
+ Permission.ReadProjectIncident,
1079
+ ],
1080
+ update: [
1081
+ Permission.ProjectOwner,
1082
+ Permission.ProjectAdmin,
1083
+ Permission.ProjectMember,
1084
+ Permission.EditProjectIncident,
1085
+ ],
1086
+ })
1087
+ @TableColumn({
1088
+ type: TableColumnType.EntityArray,
1089
+ modelType: File,
1090
+ title: "Postmortem Attachments",
1091
+ description:
1092
+ "Files that accompany the postmortem note and can be shared publicly when enabled.",
1093
+ required: false,
1094
+ })
1095
+ @ManyToMany(() => {
1096
+ return File;
1097
+ })
1098
+ @JoinTable({
1099
+ name: "IncidentPostmortemAttachmentFile",
1100
+ joinColumn: {
1101
+ name: "incidentId",
1102
+ referencedColumnName: "_id",
1103
+ },
1104
+ inverseJoinColumn: {
1105
+ name: "fileId",
1106
+ referencedColumnName: "_id",
1107
+ },
1108
+ })
1109
+ public postmortemAttachments?: Array<File> = undefined;
1110
+
999
1111
  @ColumnAccessControl({
1000
1112
  create: [],
1001
1113
  read: [
@@ -0,0 +1,106 @@
1
+ import Incident from "../../Models/DatabaseModels/Incident";
2
+ import File from "../../Models/DatabaseModels/File";
3
+ import NotFoundException from "../../Types/Exception/NotFoundException";
4
+ import ObjectID from "../../Types/ObjectID";
5
+ import IncidentService, {
6
+ Service as IncidentServiceType,
7
+ } from "../Services/IncidentService";
8
+ import UserMiddleware from "../Middleware/UserAuthorization";
9
+ import Response from "../Utils/Response";
10
+ import BaseAPI from "./BaseAPI";
11
+ import {
12
+ ExpressRequest,
13
+ ExpressResponse,
14
+ NextFunction,
15
+ } from "../Utils/Express";
16
+ import CommonAPI from "./CommonAPI";
17
+ import DatabaseCommonInteractionProps from "../../Types/BaseDatabase/DatabaseCommonInteractionProps";
18
+
19
+ export default class IncidentAPI extends BaseAPI<
20
+ Incident,
21
+ IncidentServiceType
22
+ > {
23
+ public constructor() {
24
+ super(Incident, IncidentService);
25
+
26
+ this.router.get(
27
+ `${new this.entityType()
28
+ .getCrudApiPath()
29
+ ?.toString()}/postmortem/attachment/:projectId/:incidentId/:fileId`,
30
+ UserMiddleware.getUserMiddleware,
31
+ async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
32
+ try {
33
+ await this.getPostmortemAttachment(req, res);
34
+ } catch (err) {
35
+ next(err);
36
+ }
37
+ },
38
+ );
39
+ }
40
+
41
+ private async getPostmortemAttachment(
42
+ req: ExpressRequest,
43
+ res: ExpressResponse,
44
+ ): Promise<void> {
45
+ const projectIdParam: string | undefined = req.params["projectId"];
46
+ const incidentIdParam: string | undefined = req.params["incidentId"];
47
+ const fileIdParam: string | undefined = req.params["fileId"];
48
+
49
+ if (!projectIdParam || !incidentIdParam || !fileIdParam) {
50
+ throw new NotFoundException("Attachment not found");
51
+ }
52
+
53
+ let incidentId: ObjectID;
54
+ let fileId: ObjectID;
55
+ let projectId: ObjectID;
56
+
57
+ try {
58
+ incidentId = new ObjectID(incidentIdParam);
59
+ fileId = new ObjectID(fileIdParam);
60
+ projectId = new ObjectID(projectIdParam);
61
+ } catch {
62
+ throw new NotFoundException("Attachment not found");
63
+ }
64
+
65
+ const props: DatabaseCommonInteractionProps =
66
+ await CommonAPI.getDatabaseCommonInteractionProps(req);
67
+
68
+ const incident: Incident | null = await this.service.findOneBy({
69
+ query: {
70
+ _id: incidentId,
71
+ projectId,
72
+ },
73
+ select: {
74
+ postmortemAttachments: {
75
+ _id: true,
76
+ file: true,
77
+ fileType: true,
78
+ name: true,
79
+ },
80
+ },
81
+ props,
82
+ });
83
+
84
+ if (!incident) {
85
+ throw new NotFoundException("Attachment not found");
86
+ }
87
+
88
+ const attachment: File | undefined = incident.postmortemAttachments?.find(
89
+ (file: File) => {
90
+ const attachmentId: string | null = file._id
91
+ ? file._id.toString()
92
+ : file.id
93
+ ? file.id.toString()
94
+ : null;
95
+ return attachmentId === fileId.toString();
96
+ },
97
+ );
98
+
99
+ if (!attachment || !attachment.file) {
100
+ throw new NotFoundException("Attachment not found");
101
+ }
102
+
103
+ Response.setNoCacheHeaders(res);
104
+ return Response.sendFileResponse(req, res, attachment);
105
+ }
106
+ }
@@ -408,6 +408,20 @@ export default class StatusPageAPI extends BaseAPI<
408
408
  },
409
409
  );
410
410
 
411
+ this.router.get(
412
+ `${new this.entityType()
413
+ .getCrudApiPath()
414
+ ?.toString()}/incident/postmortem/attachment/:statusPageId/:incidentId/:fileId`,
415
+ UserMiddleware.getUserMiddleware,
416
+ async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
417
+ try {
418
+ await this.getIncidentPostmortemAttachment(req, res);
419
+ } catch (err) {
420
+ next(err);
421
+ }
422
+ },
423
+ );
424
+
411
425
  this.router.get(
412
426
  `${new this.entityType()
413
427
  .getCrudApiPath()
@@ -1422,9 +1436,17 @@ export default class StatusPageAPI extends BaseAPI<
1422
1436
  let select: Select<Incident> = {
1423
1437
  createdAt: true,
1424
1438
  declaredAt: true,
1439
+ updatedAt: true,
1425
1440
  title: true,
1426
1441
  description: true,
1427
1442
  _id: true,
1443
+ postmortemNote: true,
1444
+ postmortemPostedAt: true,
1445
+ showPostmortemOnStatusPage: true,
1446
+ postmortemAttachments: {
1447
+ _id: true,
1448
+ name: true,
1449
+ },
1428
1450
  incidentSeverity: {
1429
1451
  name: true,
1430
1452
  color: true,
@@ -3306,9 +3328,17 @@ export default class StatusPageAPI extends BaseAPI<
3306
3328
  let selectIncidents: Select<Incident> = {
3307
3329
  createdAt: true,
3308
3330
  declaredAt: true,
3331
+ updatedAt: true,
3309
3332
  title: true,
3310
3333
  description: true,
3311
3334
  _id: true,
3335
+ postmortemNote: true,
3336
+ postmortemPostedAt: true,
3337
+ showPostmortemOnStatusPage: true,
3338
+ postmortemAttachments: {
3339
+ _id: true,
3340
+ name: true,
3341
+ },
3312
3342
  incidentSeverity: {
3313
3343
  name: true,
3314
3344
  color: true,
@@ -3969,6 +3999,110 @@ export default class StatusPageAPI extends BaseAPI<
3969
3999
  return Response.sendFileResponse(req, res, attachment);
3970
4000
  }
3971
4001
 
4002
+ private async getIncidentPostmortemAttachment(
4003
+ req: ExpressRequest,
4004
+ res: ExpressResponse,
4005
+ ): Promise<void> {
4006
+ const statusPageIdParam: string | undefined = req.params["statusPageId"];
4007
+ const incidentIdParam: string | undefined = req.params["incidentId"];
4008
+ const fileIdParam: string | undefined = req.params["fileId"];
4009
+
4010
+ if (!statusPageIdParam || !incidentIdParam || !fileIdParam) {
4011
+ throw new NotFoundException("Attachment not found");
4012
+ }
4013
+
4014
+ let statusPageId: ObjectID;
4015
+ let incidentId: ObjectID;
4016
+ let fileId: ObjectID;
4017
+
4018
+ try {
4019
+ statusPageId = new ObjectID(statusPageIdParam);
4020
+ incidentId = new ObjectID(incidentIdParam);
4021
+ fileId = new ObjectID(fileIdParam);
4022
+ } catch {
4023
+ throw new NotFoundException("Attachment not found");
4024
+ }
4025
+
4026
+ await this.checkHasReadAccess({
4027
+ statusPageId,
4028
+ req,
4029
+ });
4030
+
4031
+ const statusPage: StatusPage | null = await StatusPageService.findOneBy({
4032
+ query: {
4033
+ _id: statusPageId.toString(),
4034
+ },
4035
+ select: {
4036
+ _id: true,
4037
+ projectId: true,
4038
+ showIncidentsOnStatusPage: true,
4039
+ },
4040
+ props: {
4041
+ isRoot: true,
4042
+ },
4043
+ });
4044
+
4045
+ if (
4046
+ !statusPage ||
4047
+ !statusPage.projectId ||
4048
+ !statusPage.showIncidentsOnStatusPage
4049
+ ) {
4050
+ throw new NotFoundException("Attachment not found");
4051
+ }
4052
+
4053
+ const { monitorsOnStatusPage } =
4054
+ await StatusPageService.getMonitorIdsOnStatusPage({
4055
+ statusPageId,
4056
+ });
4057
+
4058
+ if (!monitorsOnStatusPage || monitorsOnStatusPage.length === 0) {
4059
+ throw new NotFoundException("Attachment not found");
4060
+ }
4061
+
4062
+ const incident: Incident | null = await IncidentService.findOneBy({
4063
+ query: {
4064
+ _id: incidentId.toString(),
4065
+ projectId: statusPage.projectId!,
4066
+ isVisibleOnStatusPage: true,
4067
+ showPostmortemOnStatusPage: true,
4068
+ monitors: monitorsOnStatusPage as any,
4069
+ },
4070
+ select: {
4071
+ postmortemAttachments: {
4072
+ _id: true,
4073
+ file: true,
4074
+ fileType: true,
4075
+ name: true,
4076
+ },
4077
+ },
4078
+ props: {
4079
+ isRoot: true,
4080
+ },
4081
+ });
4082
+
4083
+ if (!incident) {
4084
+ throw new NotFoundException("Attachment not found");
4085
+ }
4086
+
4087
+ const attachment: File | undefined = incident.postmortemAttachments?.find(
4088
+ (file: File) => {
4089
+ const attachmentId: string | null = file._id
4090
+ ? file._id.toString()
4091
+ : file.id
4092
+ ? file.id.toString()
4093
+ : null;
4094
+ return attachmentId === fileId.toString();
4095
+ },
4096
+ );
4097
+
4098
+ if (!attachment || !attachment.file) {
4099
+ throw new NotFoundException("Attachment not found");
4100
+ }
4101
+
4102
+ Response.setNoCacheHeaders(res);
4103
+ return Response.sendFileResponse(req, res, attachment);
4104
+ }
4105
+
3972
4106
  private async getIncidentPublicNoteAttachment(
3973
4107
  req: ExpressRequest,
3974
4108
  res: ExpressResponse,
@@ -0,0 +1,45 @@
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ export class MigrationName1764762146063 implements MigrationInterface {
4
+ public name = "MigrationName1764762146063";
5
+
6
+ public async up(queryRunner: QueryRunner): Promise<void> {
7
+ await queryRunner.query(
8
+ `CREATE TABLE "IncidentPostmortemAttachmentFile" ("incidentId" uuid NOT NULL, "fileId" uuid NOT NULL, CONSTRAINT "PK_40b17c7d5bcfbde48d7ebab4130" PRIMARY KEY ("incidentId", "fileId"))`,
9
+ );
10
+ await queryRunner.query(
11
+ `CREATE INDEX "IDX_62b9c09c42e05df3f134aa14a4" ON "IncidentPostmortemAttachmentFile" ("incidentId") `,
12
+ );
13
+ await queryRunner.query(
14
+ `CREATE INDEX "IDX_7e09116a3b9672622bba9f8b2e" ON "IncidentPostmortemAttachmentFile" ("fileId") `,
15
+ );
16
+ await queryRunner.query(
17
+ `ALTER TABLE "Incident" ADD "showPostmortemOnStatusPage" boolean NOT NULL DEFAULT false`,
18
+ );
19
+ await queryRunner.query(
20
+ `ALTER TABLE "IncidentPostmortemAttachmentFile" ADD CONSTRAINT "FK_62b9c09c42e05df3f134aa14a46" FOREIGN KEY ("incidentId") REFERENCES "Incident"("_id") ON DELETE CASCADE ON UPDATE CASCADE`,
21
+ );
22
+ await queryRunner.query(
23
+ `ALTER TABLE "IncidentPostmortemAttachmentFile" ADD CONSTRAINT "FK_7e09116a3b9672622bba9f8b2e3" FOREIGN KEY ("fileId") REFERENCES "File"("_id") ON DELETE CASCADE ON UPDATE CASCADE`,
24
+ );
25
+ }
26
+
27
+ public async down(queryRunner: QueryRunner): Promise<void> {
28
+ await queryRunner.query(
29
+ `ALTER TABLE "IncidentPostmortemAttachmentFile" DROP CONSTRAINT "FK_7e09116a3b9672622bba9f8b2e3"`,
30
+ );
31
+ await queryRunner.query(
32
+ `ALTER TABLE "IncidentPostmortemAttachmentFile" DROP CONSTRAINT "FK_62b9c09c42e05df3f134aa14a46"`,
33
+ );
34
+ await queryRunner.query(
35
+ `ALTER TABLE "Incident" DROP COLUMN "showPostmortemOnStatusPage"`,
36
+ );
37
+ await queryRunner.query(
38
+ `DROP INDEX "public"."IDX_7e09116a3b9672622bba9f8b2e"`,
39
+ );
40
+ await queryRunner.query(
41
+ `DROP INDEX "public"."IDX_62b9c09c42e05df3f134aa14a4"`,
42
+ );
43
+ await queryRunner.query(`DROP TABLE "IncidentPostmortemAttachmentFile"`);
44
+ }
45
+ }
@@ -0,0 +1,23 @@
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ export class MigrationName1764767371788 implements MigrationInterface {
4
+ public name = "MigrationName1764767371788";
5
+
6
+ public async up(queryRunner: QueryRunner): Promise<void> {
7
+ await queryRunner.query(
8
+ `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type":"Recurring","value":{"intervalType":"Day","intervalCount":{"_type":"PositiveNumber","value":1}}}'`,
9
+ );
10
+ await queryRunner.query(
11
+ `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type":"RestrictionTimes","value":{"restictionType":"None","dayRestrictionTimes":null,"weeklyRestrictionTimes":[]}}'`,
12
+ );
13
+ }
14
+
15
+ public async down(queryRunner: QueryRunner): Promise<void> {
16
+ await queryRunner.query(
17
+ `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type": "RestrictionTimes", "value": {"restictionType": "None", "dayRestrictionTimes": null, "weeklyRestrictionTimes": []}}'`,
18
+ );
19
+ await queryRunner.query(
20
+ `ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type": "Recurring", "value": {"intervalType": "Day", "intervalCount": {"_type": "PositiveNumber", "value": 1}}}'`,
21
+ );
22
+ }
23
+ }
@@ -187,6 +187,8 @@ import { MigrationName1763477560906 } from "./1763477560906-MigrationName";
187
187
  import { MigrationName1763480947474 } from "./1763480947474-MigrationName";
188
188
  import { MigrationName1763643080445 } from "./1763643080445-MigrationName";
189
189
  import { MigrationName1764324618043 } from "./1764324618043-MigrationName";
190
+ import { MigrationName1764762146063 } from "./1764762146063-MigrationName";
191
+ import { MigrationName1764767371788 } from "./1764767371788-MigrationName";
190
192
 
191
193
  export default [
192
194
  InitialMigration,
@@ -378,4 +380,6 @@ export default [
378
380
  MigrationName1763480947474,
379
381
  MigrationName1763643080445,
380
382
  MigrationName1764324618043,
383
+ MigrationName1764762146063,
384
+ MigrationName1764767371788,
381
385
  ];
@@ -2,6 +2,7 @@ enum IconProp {
2
2
  Equals = "Equals",
3
3
  Archive = "Archive",
4
4
  File = "File",
5
+ DocumentCheck = "DocumentCheck",
5
6
  Automation = "Automation",
6
7
  Workflow = "Workflow",
7
8
  TableCells = "TableCells",
@@ -30,6 +30,8 @@ export interface TimelineItem {
30
30
  icon: IconProp;
31
31
  iconColor: Color;
32
32
  attachments?: Array<TimelineAttachment>;
33
+ title?: string;
34
+ highlight?: boolean;
33
35
  }
34
36
 
35
37
  export interface EventItemLabel {
@@ -258,25 +260,41 @@ const EventItem: FunctionComponent<ComponentProps> = (
258
260
  aria-hidden="true"
259
261
  ></span>
260
262
  )}
261
- <div className="relative flex items-start space-x-3">
262
- <div>
263
- <div className="relative px-1">
264
- <div className="flex h-8 w-8 items-center justify-center rounded-full bg-gray-100 ring-8 ring-white">
265
- <Icon
266
- icon={item.icon}
267
- className="h-5 w-5 text-gray-500"
268
- style={{
269
- color: item.iconColor.toString(),
270
- }}
271
- />
263
+ <div
264
+ className={`relative flex items-start space-x-3 ${
265
+ item.highlight
266
+ ? "rounded-2xl border border-gray-200 bg-gray-50 px-4 py-4 shadow-sm"
267
+ : ""
268
+ }`}
269
+ >
270
+ {!item.highlight && (
271
+ <div>
272
+ <div className="relative px-1">
273
+ <div className="flex h-8 w-8 items-center justify-center rounded-full bg-gray-100 ring-8 ring-white">
274
+ <Icon
275
+ icon={item.icon}
276
+ className="h-5 w-5 text-gray-500"
277
+ style={{
278
+ color: item.iconColor.toString(),
279
+ }}
280
+ />
281
+ </div>
272
282
  </div>
273
283
  </div>
274
- </div>
284
+ )}
275
285
  <div className="min-w-0 flex-1">
276
286
  <div>
277
287
  <div className="text-sm">
278
- <span className="font-medium text-gray-900">
279
- Update to this {props.eventType}
288
+ <span
289
+ className={`font-medium ${
290
+ item.highlight
291
+ ? "text-base text-gray-900"
292
+ : "text-sm text-gray-900"
293
+ }`}
294
+ >
295
+ {item.title
296
+ ? item.title
297
+ : `Update to this ${props.eventType}`}
280
298
  </span>
281
299
  </div>
282
300
  <p className="mt-0.5 text-sm text-gray-500">
@@ -1130,6 +1130,14 @@ const Icon: FunctionComponent<ComponentProps> = ({
1130
1130
  d="M2.25 15.75l5.159-5.159a2.25 2.25 0 013.182 0l5.159 5.159m-1.5-1.5l1.409-1.409a2.25 2.25 0 013.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 001.5-1.5V6a1.5 1.5 0 00-1.5-1.5H3.75A1.5 1.5 0 002.25 6v12a1.5 1.5 0 001.5 1.5zm10.5-11.25h.008v.008h-.008V8.25zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0z"
1131
1131
  />,
1132
1132
  );
1133
+ } else if (icon === IconProp.DocumentCheck) {
1134
+ return getSvgWrapper(
1135
+ <path
1136
+ strokeLinecap="round"
1137
+ strokeLinejoin="round"
1138
+ d="M10.125 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.625M10.125 2.25H10.5c4.971 0 9 4.029 9 9v.375M10.125 2.25c1.864 0 3.375 1.511 3.375 3.375V7.125c0 .621.504 1.125 1.125 1.125h1.5c1.864 0 3.375 1.511 3.375 3.375M9 15l2.25 2.25L15 12"
1139
+ />,
1140
+ );
1133
1141
  } else if (icon === IconProp.TextFile || icon === IconProp.File) {
1134
1142
  return getSvgWrapper(
1135
1143
  <path