@oneuptime/common 8.0.5580 → 8.0.5581

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 (137) hide show
  1. package/Models/DatabaseModels/AlertInternalNote.ts +58 -1
  2. package/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.ts +1 -0
  3. package/Models/DatabaseModels/File.ts +1 -1
  4. package/Models/DatabaseModels/IncidentInternalNote.ts +58 -1
  5. package/Models/DatabaseModels/IncidentPublicNote.ts +58 -1
  6. package/Models/DatabaseModels/ScheduledMaintenanceInternalNote.ts +58 -1
  7. package/Models/DatabaseModels/ScheduledMaintenancePublicNote.ts +58 -1
  8. package/Models/DatabaseModels/StatusPageAnnouncement.ts +49 -0
  9. package/Server/API/AlertInternalNoteAPI.ts +96 -0
  10. package/Server/API/IncidentInternalNoteAPI.ts +96 -0
  11. package/Server/API/IncidentPublicNoteAPI.ts +96 -0
  12. package/Server/API/ScheduledMaintenanceInternalNoteAPI.ts +100 -0
  13. package/Server/API/ScheduledMaintenancePublicNoteAPI.ts +100 -0
  14. package/Server/API/StatusPageAPI.ts +585 -59
  15. package/Server/API/StatusPageAnnouncementAPI.ts +98 -0
  16. package/Server/API/UserAPI.ts +95 -0
  17. package/Server/Infrastructure/Postgres/SchemaMigrations/1763471659817-MigrationName.ts +79 -0
  18. package/Server/Infrastructure/Postgres/SchemaMigrations/1763477560906-MigrationName.ts +81 -0
  19. package/Server/Infrastructure/Postgres/SchemaMigrations/1763480947474-MigrationName.ts +79 -0
  20. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
  21. package/Server/Middleware/ProjectAuthorization.ts +3 -1
  22. package/Server/Services/AlertInternalNoteService.ts +75 -2
  23. package/Server/Services/IncidentInternalNoteService.ts +76 -2
  24. package/Server/Services/IncidentPublicNoteService.ts +76 -2
  25. package/Server/Services/ScheduledMaintenanceInternalNoteService.ts +76 -2
  26. package/Server/Services/ScheduledMaintenancePublicNoteService.ts +76 -2
  27. package/Server/Services/ScheduledMaintenanceService.ts +10 -7
  28. package/Server/Services/StatusPagePrivateUserService.ts +10 -7
  29. package/Server/Services/StatusPageService.ts +12 -7
  30. package/Server/Services/StatusPageSubscriberService.ts +19 -13
  31. package/Server/Utils/FileAttachmentMarkdownUtil.ts +98 -0
  32. package/Server/Utils/Response.ts +13 -0
  33. package/Types/File/MimeType.ts +18 -0
  34. package/UI/Components/AttachmentList/EventAttachmentList.tsx +121 -0
  35. package/UI/Components/EventItem/EventItem.tsx +22 -0
  36. package/UI/Components/Feed/FeedItem.tsx +9 -16
  37. package/UI/Components/FilePicker/FilePicker.tsx +441 -145
  38. package/UI/Components/Forms/Fields/FormField.tsx +32 -15
  39. package/UI/Components/Forms/FormSummary.tsx +168 -1
  40. package/UI/Components/Forms/ModelForm.tsx +46 -24
  41. package/UI/Components/Forms/Types/FormFieldSchemaType.ts +1 -0
  42. package/UI/Components/Icon/Icon.tsx +1 -1
  43. package/UI/Utils/API/RequestOptions.ts +2 -0
  44. package/UI/Utils/ModelAPI/ModelAPI.ts +18 -0
  45. package/UI/Utils/User.ts +8 -0
  46. package/Utils/API.ts +11 -1
  47. package/build/dist/Models/DatabaseModels/AlertInternalNote.js +49 -1
  48. package/build/dist/Models/DatabaseModels/AlertInternalNote.js.map +1 -1
  49. package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js +1 -0
  50. package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js.map +1 -1
  51. package/build/dist/Models/DatabaseModels/File.js +1 -1
  52. package/build/dist/Models/DatabaseModels/File.js.map +1 -1
  53. package/build/dist/Models/DatabaseModels/IncidentInternalNote.js +49 -1
  54. package/build/dist/Models/DatabaseModels/IncidentInternalNote.js.map +1 -1
  55. package/build/dist/Models/DatabaseModels/IncidentPublicNote.js +49 -1
  56. package/build/dist/Models/DatabaseModels/IncidentPublicNote.js.map +1 -1
  57. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceInternalNote.js +49 -1
  58. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceInternalNote.js.map +1 -1
  59. package/build/dist/Models/DatabaseModels/ScheduledMaintenancePublicNote.js +49 -1
  60. package/build/dist/Models/DatabaseModels/ScheduledMaintenancePublicNote.js.map +1 -1
  61. package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js +48 -0
  62. package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js.map +1 -1
  63. package/build/dist/Server/API/AlertInternalNoteAPI.js +68 -0
  64. package/build/dist/Server/API/AlertInternalNoteAPI.js.map +1 -0
  65. package/build/dist/Server/API/IncidentInternalNoteAPI.js +68 -0
  66. package/build/dist/Server/API/IncidentInternalNoteAPI.js.map +1 -0
  67. package/build/dist/Server/API/IncidentPublicNoteAPI.js +68 -0
  68. package/build/dist/Server/API/IncidentPublicNoteAPI.js.map +1 -0
  69. package/build/dist/Server/API/ScheduledMaintenanceInternalNoteAPI.js +68 -0
  70. package/build/dist/Server/API/ScheduledMaintenanceInternalNoteAPI.js.map +1 -0
  71. package/build/dist/Server/API/ScheduledMaintenancePublicNoteAPI.js +68 -0
  72. package/build/dist/Server/API/ScheduledMaintenancePublicNoteAPI.js.map +1 -0
  73. package/build/dist/Server/API/StatusPageAPI.js +488 -85
  74. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  75. package/build/dist/Server/API/StatusPageAnnouncementAPI.js +68 -0
  76. package/build/dist/Server/API/StatusPageAnnouncementAPI.js.map +1 -0
  77. package/build/dist/Server/API/UserAPI.js +66 -0
  78. package/build/dist/Server/API/UserAPI.js.map +1 -0
  79. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1763471659817-MigrationName.js +34 -0
  80. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1763471659817-MigrationName.js.map +1 -0
  81. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1763477560906-MigrationName.js +34 -0
  82. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1763477560906-MigrationName.js.map +1 -0
  83. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1763480947474-MigrationName.js +34 -0
  84. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1763480947474-MigrationName.js.map +1 -0
  85. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
  86. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  87. package/build/dist/Server/Middleware/ProjectAuthorization.js +4 -1
  88. package/build/dist/Server/Middleware/ProjectAuthorization.js.map +1 -1
  89. package/build/dist/Server/Services/AlertInternalNoteService.js +54 -2
  90. package/build/dist/Server/Services/AlertInternalNoteService.js.map +1 -1
  91. package/build/dist/Server/Services/IncidentInternalNoteService.js +54 -2
  92. package/build/dist/Server/Services/IncidentInternalNoteService.js.map +1 -1
  93. package/build/dist/Server/Services/IncidentPublicNoteService.js +54 -2
  94. package/build/dist/Server/Services/IncidentPublicNoteService.js.map +1 -1
  95. package/build/dist/Server/Services/ScheduledMaintenanceInternalNoteService.js +54 -2
  96. package/build/dist/Server/Services/ScheduledMaintenanceInternalNoteService.js.map +1 -1
  97. package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js +54 -2
  98. package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js.map +1 -1
  99. package/build/dist/Server/Services/ScheduledMaintenanceService.js +6 -5
  100. package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
  101. package/build/dist/Server/Services/StatusPagePrivateUserService.js +6 -4
  102. package/build/dist/Server/Services/StatusPagePrivateUserService.js.map +1 -1
  103. package/build/dist/Server/Services/StatusPageService.js +7 -4
  104. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  105. package/build/dist/Server/Services/StatusPageSubscriberService.js +11 -7
  106. package/build/dist/Server/Services/StatusPageSubscriberService.js.map +1 -1
  107. package/build/dist/Server/Utils/FileAttachmentMarkdownUtil.js +67 -0
  108. package/build/dist/Server/Utils/FileAttachmentMarkdownUtil.js.map +1 -0
  109. package/build/dist/Server/Utils/Response.js +8 -0
  110. package/build/dist/Server/Utils/Response.js.map +1 -1
  111. package/build/dist/Types/File/MimeType.js +18 -0
  112. package/build/dist/Types/File/MimeType.js.map +1 -1
  113. package/build/dist/UI/Components/AttachmentList/EventAttachmentList.js +42 -0
  114. package/build/dist/UI/Components/AttachmentList/EventAttachmentList.js.map +1 -0
  115. package/build/dist/UI/Components/EventItem/EventItem.js +5 -1
  116. package/build/dist/UI/Components/EventItem/EventItem.js.map +1 -1
  117. package/build/dist/UI/Components/Feed/FeedItem.js +6 -4
  118. package/build/dist/UI/Components/Feed/FeedItem.js.map +1 -1
  119. package/build/dist/UI/Components/FilePicker/FilePicker.js +262 -77
  120. package/build/dist/UI/Components/FilePicker/FilePicker.js.map +1 -1
  121. package/build/dist/UI/Components/Forms/Fields/FormField.js +24 -12
  122. package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
  123. package/build/dist/UI/Components/Forms/FormSummary.js +77 -1
  124. package/build/dist/UI/Components/Forms/FormSummary.js.map +1 -1
  125. package/build/dist/UI/Components/Forms/ModelForm.js +32 -18
  126. package/build/dist/UI/Components/Forms/ModelForm.js.map +1 -1
  127. package/build/dist/UI/Components/Forms/Types/FormFieldSchemaType.js +1 -0
  128. package/build/dist/UI/Components/Forms/Types/FormFieldSchemaType.js.map +1 -1
  129. package/build/dist/UI/Components/Icon/Icon.js +1 -1
  130. package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
  131. package/build/dist/UI/Utils/ModelAPI/ModelAPI.js +30 -45
  132. package/build/dist/UI/Utils/ModelAPI/ModelAPI.js.map +1 -1
  133. package/build/dist/UI/Utils/User.js +7 -0
  134. package/build/dist/UI/Utils/User.js.map +1 -1
  135. package/build/dist/Utils/API.js +3 -0
  136. package/build/dist/Utils/API.js.map +1 -1
  137. package/package.json +6 -6
@@ -9,6 +9,8 @@ import { LIMIT_PER_PROJECT } from "../../Types/Database/LimitMax";
9
9
  import IncidentService from "./IncidentService";
10
10
  import Incident from "../../Models/DatabaseModels/Incident";
11
11
  import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
12
+ import File from "../../Models/DatabaseModels/File";
13
+ import FileAttachmentMarkdownUtil from "../Utils/FileAttachmentMarkdownUtil";
12
14
 
13
15
  export class Service extends DatabaseService<Model> {
14
16
  public constructor() {
@@ -21,6 +23,7 @@ export class Service extends DatabaseService<Model> {
21
23
  incidentId: ObjectID;
22
24
  projectId: ObjectID;
23
25
  note: string;
26
+ attachmentFileIds?: Array<ObjectID>;
24
27
  }): Promise<Model> {
25
28
  const internalNote: Model = new Model();
26
29
  internalNote.createdByUserId = data.userId;
@@ -28,6 +31,16 @@ export class Service extends DatabaseService<Model> {
28
31
  internalNote.projectId = data.projectId;
29
32
  internalNote.note = data.note;
30
33
 
34
+ if (data.attachmentFileIds && data.attachmentFileIds.length > 0) {
35
+ internalNote.attachments = data.attachmentFileIds.map(
36
+ (fileId: ObjectID) => {
37
+ const file: File = new File();
38
+ file.id = fileId;
39
+ return file;
40
+ },
41
+ );
42
+ }
43
+
31
44
  return this.create({
32
45
  data: internalNote,
33
46
  props: {
@@ -51,6 +64,11 @@ export class Service extends DatabaseService<Model> {
51
64
  incidentId: incidentId,
52
65
  });
53
66
 
67
+ const attachmentsMarkdown: string = await this.getAttachmentsMarkdown(
68
+ createdItem.id!,
69
+ "/incident-internal-note/attachment",
70
+ );
71
+
54
72
  await IncidentFeedService.createIncidentFeedItem({
55
73
  incidentId: createdItem.incidentId!,
56
74
  projectId: createdItem.projectId!,
@@ -60,7 +78,7 @@ export class Service extends DatabaseService<Model> {
60
78
 
61
79
  feedInfoInMarkdown: `📄 posted **private note** for this [Incident ${incidentNumber}](${(await IncidentService.getIncidentLinkInDashboard(createdItem.projectId!, incidentId)).toString()}):
62
80
 
63
- ${createdItem.note}
81
+ ${(createdItem.note || "") + attachmentsMarkdown}
64
82
  `,
65
83
  workspaceNotification: {
66
84
  sendWorkspaceNotification: true,
@@ -105,6 +123,11 @@ ${createdItem.note}
105
123
  for (const updatedItem of updatedItems) {
106
124
  const incident: Incident = updatedItem.incident!;
107
125
 
126
+ const attachmentsMarkdown: string = await this.getAttachmentsMarkdown(
127
+ updatedItem.id!,
128
+ "/incident-internal-note/attachment",
129
+ );
130
+
108
131
  await IncidentFeedService.createIncidentFeedItem({
109
132
  incidentId: updatedItem.incidentId!,
110
133
  projectId: updatedItem.projectId!,
@@ -114,7 +137,7 @@ ${createdItem.note}
114
137
 
115
138
  feedInfoInMarkdown: `📄 updated **Private Note** for this [Incident ${incident.incidentNumber}](${(await IncidentService.getIncidentLinkInDashboard(incident.projectId!, incident.id!)).toString()})
116
139
 
117
- ${updatedItem.note}
140
+ ${(updatedItem.note || "") + attachmentsMarkdown}
118
141
  `,
119
142
  workspaceNotification: {
120
143
  sendWorkspaceNotification: true,
@@ -125,6 +148,57 @@ ${updatedItem.note}
125
148
  }
126
149
  return onUpdate;
127
150
  }
151
+
152
+ private async getAttachmentsMarkdown(
153
+ modelId: ObjectID,
154
+ attachmentApiPath: string,
155
+ ): Promise<string> {
156
+ if (!modelId) {
157
+ return "";
158
+ }
159
+
160
+ const noteWithAttachments: Model | null = await this.findOneById({
161
+ id: modelId,
162
+ select: {
163
+ attachments: {
164
+ _id: true,
165
+ },
166
+ },
167
+ props: {
168
+ isRoot: true,
169
+ },
170
+ });
171
+
172
+ if (!noteWithAttachments || !noteWithAttachments.attachments) {
173
+ return "";
174
+ }
175
+
176
+ const attachmentIds: Array<ObjectID> = noteWithAttachments.attachments
177
+ .map((file: File) => {
178
+ if (file.id) {
179
+ return file.id;
180
+ }
181
+
182
+ if (file._id) {
183
+ return new ObjectID(file._id);
184
+ }
185
+
186
+ return null;
187
+ })
188
+ .filter((id: ObjectID | null): id is ObjectID => {
189
+ return Boolean(id);
190
+ });
191
+
192
+ if (!attachmentIds.length) {
193
+ return "";
194
+ }
195
+
196
+ return await FileAttachmentMarkdownUtil.buildAttachmentMarkdown({
197
+ modelId,
198
+ attachmentIds,
199
+ attachmentApiPath,
200
+ });
201
+ }
128
202
  }
129
203
 
130
204
  export default new Service();
@@ -12,6 +12,8 @@ import IncidentService from "./IncidentService";
12
12
  import Incident from "../../Models/DatabaseModels/Incident";
13
13
  import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
14
14
  import StatusPageSubscriberNotificationStatus from "../../Types/StatusPage/StatusPageSubscriberNotificationStatus";
15
+ import File from "../../Models/DatabaseModels/File";
16
+ import FileAttachmentMarkdownUtil from "../Utils/FileAttachmentMarkdownUtil";
15
17
 
16
18
  export class Service extends DatabaseService<Model> {
17
19
  public constructor() {
@@ -24,6 +26,7 @@ export class Service extends DatabaseService<Model> {
24
26
  incidentId: ObjectID;
25
27
  projectId: ObjectID;
26
28
  note: string;
29
+ attachmentFileIds?: Array<ObjectID>;
27
30
  }): Promise<Model> {
28
31
  const publicNote: Model = new Model();
29
32
  publicNote.createdByUserId = data.userId;
@@ -32,6 +35,16 @@ export class Service extends DatabaseService<Model> {
32
35
  publicNote.note = data.note;
33
36
  publicNote.postedAt = OneUptimeDate.getCurrentDate();
34
37
 
38
+ if (data.attachmentFileIds && data.attachmentFileIds.length > 0) {
39
+ publicNote.attachments = data.attachmentFileIds.map(
40
+ (fileId: ObjectID) => {
41
+ const file: File = new File();
42
+ file.id = fileId;
43
+ return file;
44
+ },
45
+ );
46
+ }
47
+
35
48
  return this.create({
36
49
  data: publicNote,
37
50
  props: {
@@ -84,6 +97,11 @@ export class Service extends DatabaseService<Model> {
84
97
  incidentId: incidentId,
85
98
  });
86
99
 
100
+ const attachmentsMarkdown: string = await this.getAttachmentsMarkdown(
101
+ createdItem.id!,
102
+ "/incident-public-note/attachment",
103
+ );
104
+
87
105
  await IncidentFeedService.createIncidentFeedItem({
88
106
  incidentId: createdItem.incidentId!,
89
107
  projectId: createdItem.projectId!,
@@ -92,7 +110,7 @@ export class Service extends DatabaseService<Model> {
92
110
  userId: userId || undefined,
93
111
  feedInfoInMarkdown: `📄 posted **public note** for this [Incident ${incidentNumber}](${(await IncidentService.getIncidentLinkInDashboard(projectId!, incidentId!)).toString()}) on status page:
94
112
 
95
- ${createdItem.note}
113
+ ${(createdItem.note || "") + attachmentsMarkdown}
96
114
  `,
97
115
  workspaceNotification: {
98
116
  sendWorkspaceNotification: true,
@@ -138,6 +156,11 @@ ${createdItem.note}
138
156
  for (const updatedItem of updatedItems) {
139
157
  const incident: Incident = updatedItem.incident!;
140
158
 
159
+ const attachmentsMarkdown: string = await this.getAttachmentsMarkdown(
160
+ updatedItem.id!,
161
+ "/incident-public-note/attachment",
162
+ );
163
+
141
164
  await IncidentFeedService.createIncidentFeedItem({
142
165
  incidentId: updatedItem.incidentId!,
143
166
  projectId: updatedItem.projectId!,
@@ -147,7 +170,7 @@ ${createdItem.note}
147
170
 
148
171
  feedInfoInMarkdown: `📄 updated **Public Note** for this [Incident ${incident.incidentNumber}](${(await IncidentService.getIncidentLinkInDashboard(incident.projectId!, incident.id!)).toString()})
149
172
 
150
- ${updatedItem.note}
173
+ ${(updatedItem.note || "") + attachmentsMarkdown}
151
174
  `,
152
175
  workspaceNotification: {
153
176
  sendWorkspaceNotification: true,
@@ -158,6 +181,57 @@ ${updatedItem.note}
158
181
  }
159
182
  return onUpdate;
160
183
  }
184
+
185
+ private async getAttachmentsMarkdown(
186
+ modelId: ObjectID,
187
+ attachmentApiPath: string,
188
+ ): Promise<string> {
189
+ if (!modelId) {
190
+ return "";
191
+ }
192
+
193
+ const noteWithAttachments: Model | null = await this.findOneById({
194
+ id: modelId,
195
+ select: {
196
+ attachments: {
197
+ _id: true,
198
+ },
199
+ },
200
+ props: {
201
+ isRoot: true,
202
+ },
203
+ });
204
+
205
+ if (!noteWithAttachments || !noteWithAttachments.attachments) {
206
+ return "";
207
+ }
208
+
209
+ const attachmentIds: Array<ObjectID> = noteWithAttachments.attachments
210
+ .map((file: File) => {
211
+ if (file.id) {
212
+ return file.id;
213
+ }
214
+
215
+ if (file._id) {
216
+ return new ObjectID(file._id);
217
+ }
218
+
219
+ return null;
220
+ })
221
+ .filter((id: ObjectID | null): id is ObjectID => {
222
+ return Boolean(id);
223
+ });
224
+
225
+ if (!attachmentIds.length) {
226
+ return "";
227
+ }
228
+
229
+ return await FileAttachmentMarkdownUtil.buildAttachmentMarkdown({
230
+ modelId,
231
+ attachmentIds,
232
+ attachmentApiPath,
233
+ });
234
+ }
161
235
  }
162
236
 
163
237
  export default new Service();
@@ -9,6 +9,8 @@ import { LIMIT_PER_PROJECT } from "../../Types/Database/LimitMax";
9
9
  import ScheduledMaintenance from "../../Models/DatabaseModels/ScheduledMaintenance";
10
10
  import ScheduledMaintenanceService from "./ScheduledMaintenanceService";
11
11
  import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
12
+ import File from "../../Models/DatabaseModels/File";
13
+ import FileAttachmentMarkdownUtil from "../Utils/FileAttachmentMarkdownUtil";
12
14
 
13
15
  export class Service extends DatabaseService<Model> {
14
16
  public constructor() {
@@ -21,6 +23,7 @@ export class Service extends DatabaseService<Model> {
21
23
  scheduledMaintenanceId: ObjectID;
22
24
  projectId: ObjectID;
23
25
  note: string;
26
+ attachmentFileIds?: Array<ObjectID>;
24
27
  }): Promise<Model> {
25
28
  const internalNote: Model = new Model();
26
29
  internalNote.createdByUserId = data.userId;
@@ -28,6 +31,16 @@ export class Service extends DatabaseService<Model> {
28
31
  internalNote.projectId = data.projectId;
29
32
  internalNote.note = data.note;
30
33
 
34
+ if (data.attachmentFileIds && data.attachmentFileIds.length > 0) {
35
+ internalNote.attachments = data.attachmentFileIds.map(
36
+ (fileId: ObjectID) => {
37
+ const file: File = new File();
38
+ file.id = fileId;
39
+ return file;
40
+ },
41
+ );
42
+ }
43
+
31
44
  return this.create({
32
45
  data: internalNote,
33
46
  props: {
@@ -52,6 +65,11 @@ export class Service extends DatabaseService<Model> {
52
65
  scheduledMaintenanceId: scheduledMaintenanceId,
53
66
  });
54
67
 
68
+ const attachmentsMarkdown: string = await this.getAttachmentsMarkdown(
69
+ createdItem.id!,
70
+ "/scheduled-maintenance-internal-note/attachment",
71
+ );
72
+
55
73
  await ScheduledMaintenanceFeedService.createScheduledMaintenanceFeedItem({
56
74
  scheduledMaintenanceId: createdItem.scheduledMaintenanceId!,
57
75
  projectId: createdItem.projectId!,
@@ -62,7 +80,7 @@ export class Service extends DatabaseService<Model> {
62
80
 
63
81
  feedInfoInMarkdown: `📄 posted **private note** for this [Scheduled Maintenance ${scheduledMaintenanceNumber}](${(await ScheduledMaintenanceService.getScheduledMaintenanceLinkInDashboard(createdItem.projectId!, scheduledMaintenanceId)).toString()}):
64
82
 
65
- ${createdItem.note}
83
+ ${(createdItem.note || "") + attachmentsMarkdown}
66
84
  `,
67
85
  workspaceNotification: {
68
86
  sendWorkspaceNotification: true,
@@ -109,6 +127,11 @@ ${createdItem.note}
109
127
  const scheduledMaintenance: ScheduledMaintenance =
110
128
  updatedItem.scheduledMaintenance!;
111
129
 
130
+ const attachmentsMarkdown: string = await this.getAttachmentsMarkdown(
131
+ updatedItem.id!,
132
+ "/scheduled-maintenance-internal-note/attachment",
133
+ );
134
+
112
135
  await ScheduledMaintenanceFeedService.createScheduledMaintenanceFeedItem(
113
136
  {
114
137
  scheduledMaintenanceId: updatedItem.scheduledMaintenanceId!,
@@ -120,7 +143,7 @@ ${createdItem.note}
120
143
 
121
144
  feedInfoInMarkdown: `📄 updated **Private Note** for this [Scheduled Maintenance ${scheduledMaintenance.scheduledMaintenanceNumber}](${(await ScheduledMaintenanceService.getScheduledMaintenanceLinkInDashboard(scheduledMaintenance.projectId!, scheduledMaintenance.id!)).toString()})
122
145
 
123
- ${updatedItem.note}
146
+ ${(updatedItem.note || "") + attachmentsMarkdown}
124
147
  `,
125
148
  workspaceNotification: {
126
149
  sendWorkspaceNotification: true,
@@ -132,6 +155,57 @@ ${updatedItem.note}
132
155
  }
133
156
  return onUpdate;
134
157
  }
158
+
159
+ private async getAttachmentsMarkdown(
160
+ modelId: ObjectID,
161
+ attachmentApiPath: string,
162
+ ): Promise<string> {
163
+ if (!modelId) {
164
+ return "";
165
+ }
166
+
167
+ const noteWithAttachments: Model | null = await this.findOneById({
168
+ id: modelId,
169
+ select: {
170
+ attachments: {
171
+ _id: true,
172
+ },
173
+ },
174
+ props: {
175
+ isRoot: true,
176
+ },
177
+ });
178
+
179
+ if (!noteWithAttachments || !noteWithAttachments.attachments) {
180
+ return "";
181
+ }
182
+
183
+ const attachmentIds: Array<ObjectID> = noteWithAttachments.attachments
184
+ .map((file: File) => {
185
+ if (file.id) {
186
+ return file.id;
187
+ }
188
+
189
+ if (file._id) {
190
+ return new ObjectID(file._id);
191
+ }
192
+
193
+ return null;
194
+ })
195
+ .filter((id: ObjectID | null): id is ObjectID => {
196
+ return Boolean(id);
197
+ });
198
+
199
+ if (!attachmentIds.length) {
200
+ return "";
201
+ }
202
+
203
+ return await FileAttachmentMarkdownUtil.buildAttachmentMarkdown({
204
+ modelId,
205
+ attachmentIds,
206
+ attachmentApiPath,
207
+ });
208
+ }
135
209
  }
136
210
 
137
211
  export default new Service();
@@ -12,6 +12,8 @@ import ScheduledMaintenanceService from "./ScheduledMaintenanceService";
12
12
  import ScheduledMaintenance from "../../Models/DatabaseModels/ScheduledMaintenance";
13
13
  import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
14
14
  import StatusPageSubscriberNotificationStatus from "../../Types/StatusPage/StatusPageSubscriberNotificationStatus";
15
+ import File from "../../Models/DatabaseModels/File";
16
+ import FileAttachmentMarkdownUtil from "../Utils/FileAttachmentMarkdownUtil";
15
17
 
16
18
  export class Service extends DatabaseService<Model> {
17
19
  public constructor() {
@@ -63,6 +65,11 @@ export class Service extends DatabaseService<Model> {
63
65
  scheduledMaintenanceId: scheduledMaintenanceId,
64
66
  });
65
67
 
68
+ const attachmentsMarkdown: string = await this.getAttachmentsMarkdown(
69
+ createdItem.id!,
70
+ "/scheduled-maintenance-public-note/attachment",
71
+ );
72
+
66
73
  await ScheduledMaintenanceFeedService.createScheduledMaintenanceFeedItem({
67
74
  scheduledMaintenanceId: createdItem.scheduledMaintenanceId!,
68
75
  projectId: createdItem.projectId!,
@@ -72,7 +79,7 @@ export class Service extends DatabaseService<Model> {
72
79
  userId: userId || undefined,
73
80
  feedInfoInMarkdown: `📄 posted **public note** for this [Scheduled Maintenance ${scheduledMaintenanceNumber}](${(await ScheduledMaintenanceService.getScheduledMaintenanceLinkInDashboard(projectId!, scheduledMaintenanceId!)).toString()}) on status page:
74
81
 
75
- ${createdItem.note}
82
+ ${(createdItem.note || "") + attachmentsMarkdown}
76
83
  `,
77
84
  workspaceNotification: {
78
85
  sendWorkspaceNotification: true,
@@ -119,6 +126,11 @@ ${createdItem.note}
119
126
  const scheduledMaintenance: ScheduledMaintenance =
120
127
  updatedItem.scheduledMaintenance!;
121
128
 
129
+ const attachmentsMarkdown: string = await this.getAttachmentsMarkdown(
130
+ updatedItem.id!,
131
+ "/scheduled-maintenance-public-note/attachment",
132
+ );
133
+
122
134
  await ScheduledMaintenanceFeedService.createScheduledMaintenanceFeedItem(
123
135
  {
124
136
  scheduledMaintenanceId: updatedItem.scheduledMaintenanceId!,
@@ -130,7 +142,7 @@ ${createdItem.note}
130
142
 
131
143
  feedInfoInMarkdown: `📄 updated **Public Note** for this [Scheduled Maintenance ${scheduledMaintenance.scheduledMaintenanceNumber}](${(await ScheduledMaintenanceService.getScheduledMaintenanceLinkInDashboard(scheduledMaintenance.projectId!, scheduledMaintenance.id!)).toString()})
132
144
 
133
- ${updatedItem.note}
145
+ ${(updatedItem.note || "") + attachmentsMarkdown}
134
146
  `,
135
147
  workspaceNotification: {
136
148
  sendWorkspaceNotification: true,
@@ -149,6 +161,7 @@ ${updatedItem.note}
149
161
  scheduledMaintenanceId: ObjectID;
150
162
  projectId: ObjectID;
151
163
  note: string;
164
+ attachmentFileIds?: Array<ObjectID>;
152
165
  }): Promise<Model> {
153
166
  const publicNote: Model = new Model();
154
167
  publicNote.createdByUserId = data.userId;
@@ -157,6 +170,16 @@ ${updatedItem.note}
157
170
  publicNote.note = data.note;
158
171
  publicNote.postedAt = OneUptimeDate.getCurrentDate();
159
172
 
173
+ if (data.attachmentFileIds && data.attachmentFileIds.length > 0) {
174
+ publicNote.attachments = data.attachmentFileIds.map(
175
+ (fileId: ObjectID) => {
176
+ const file: File = new File();
177
+ file.id = fileId;
178
+ return file;
179
+ },
180
+ );
181
+ }
182
+
160
183
  return this.create({
161
184
  data: publicNote,
162
185
  props: {
@@ -164,6 +187,57 @@ ${updatedItem.note}
164
187
  },
165
188
  });
166
189
  }
190
+
191
+ private async getAttachmentsMarkdown(
192
+ modelId: ObjectID,
193
+ attachmentApiPath: string,
194
+ ): Promise<string> {
195
+ if (!modelId) {
196
+ return "";
197
+ }
198
+
199
+ const noteWithAttachments: Model | null = await this.findOneById({
200
+ id: modelId,
201
+ select: {
202
+ attachments: {
203
+ _id: true,
204
+ },
205
+ },
206
+ props: {
207
+ isRoot: true,
208
+ },
209
+ });
210
+
211
+ if (!noteWithAttachments || !noteWithAttachments.attachments) {
212
+ return "";
213
+ }
214
+
215
+ const attachmentIds: Array<ObjectID> = noteWithAttachments.attachments
216
+ .map((file: File) => {
217
+ if (file.id) {
218
+ return file.id;
219
+ }
220
+
221
+ if (file._id) {
222
+ return new ObjectID(file._id);
223
+ }
224
+
225
+ return null;
226
+ })
227
+ .filter((id: ObjectID | null): id is ObjectID => {
228
+ return Boolean(id);
229
+ });
230
+
231
+ if (!attachmentIds.length) {
232
+ return "";
233
+ }
234
+
235
+ return await FileAttachmentMarkdownUtil.buildAttachmentMarkdown({
236
+ modelId,
237
+ attachmentIds,
238
+ attachmentApiPath,
239
+ });
240
+ }
167
241
  }
168
242
 
169
243
  export default new Service();
@@ -27,7 +27,7 @@ import User from "../../Models/DatabaseModels/User";
27
27
  import Recurring from "../../Types/Events/Recurring";
28
28
  import OneUptimeDate from "../../Types/Date";
29
29
  import UpdateBy from "../Types/Database/UpdateBy";
30
- import { FileRoute } from "../../ServiceRoute";
30
+ import { StatusPageApiRoute } from "../../ServiceRoute";
31
31
  import Dictionary from "../../Types/Dictionary";
32
32
  import EmailTemplateType from "../../Types/Email/EmailTemplateType";
33
33
  import SMS from "../../Types/SMS/SMS";
@@ -280,6 +280,8 @@ ${resourcesAffected ? `**Resources Affected:** ${resourcesAffected}` : ""}
280
280
 
281
281
  if (subscriber.subscriberEmail) {
282
282
  // send email here.
283
+ const statusPageIdString: string | null =
284
+ statuspage.id?.toString() || statuspage._id?.toString() || null;
283
285
 
284
286
  MailService.sendMail(
285
287
  {
@@ -289,12 +291,13 @@ ${resourcesAffected ? `**Resources Affected:** ${resourcesAffected}` : ""}
289
291
  vars: {
290
292
  statusPageName: statusPageName,
291
293
  statusPageUrl: statusPageURL,
292
- logoUrl: statuspage.logoFileId
293
- ? new URL(httpProtocol, host)
294
- .addRoute(FileRoute)
295
- .addRoute("/image/" + statuspage.logoFileId)
296
- .toString()
297
- : "",
294
+ logoUrl:
295
+ statuspage.logoFileId && statusPageIdString
296
+ ? new URL(httpProtocol, host)
297
+ .addRoute(StatusPageApiRoute)
298
+ .addRoute(`/logo/${statusPageIdString}`)
299
+ .toString()
300
+ : "",
298
301
  isPublicStatusPage: statuspage.isPublicStatusPage
299
302
  ? "true"
300
303
  : "false",
@@ -5,7 +5,7 @@ import logger from "../Utils/Logger";
5
5
  import DatabaseService from "./DatabaseService";
6
6
  import MailService from "./MailService";
7
7
  import StatusPageService from "./StatusPageService";
8
- import { FileRoute } from "../../ServiceRoute";
8
+ import { StatusPageApiRoute } from "../../ServiceRoute";
9
9
  import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
10
10
  import Hostname from "../../Types/API/Hostname";
11
11
  import Protocol from "../../Types/API/Protocol";
@@ -106,6 +106,8 @@ export class Service extends DatabaseService<Model> {
106
106
  const host: Hostname = await DatabaseConfig.getHost();
107
107
 
108
108
  const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol();
109
+ const statusPageIdString: string | null =
110
+ statusPage.id?.toString() || statusPage._id?.toString() || null;
109
111
 
110
112
  MailService.sendMail(
111
113
  {
@@ -115,12 +117,13 @@ export class Service extends DatabaseService<Model> {
115
117
  vars: {
116
118
  statusPageName: statusPageName!,
117
119
  statusPageUrl: statusPageURL,
118
- logoUrl: statusPage.logoFileId
119
- ? new URL(httpProtocol, host)
120
- .addRoute(FileRoute)
121
- .addRoute("/image/" + statusPage.logoFileId)
122
- .toString()
123
- : "",
120
+ logoUrl:
121
+ statusPage.logoFileId && statusPageIdString
122
+ ? new URL(httpProtocol, host)
123
+ .addRoute(StatusPageApiRoute)
124
+ .addRoute(`/logo/${statusPageIdString}`)
125
+ .toString()
126
+ : "",
124
127
  homeURL: statusPageURL,
125
128
  tokenVerifyUrl: URL.fromString(statusPageURL)
126
129
  .addRoute("/reset-password/" + token)
@@ -42,7 +42,7 @@ import StatusPageSubscriberService from "./StatusPageSubscriberService";
42
42
  import StatusPageSubscriber from "../../Models/DatabaseModels/StatusPageSubscriber";
43
43
  import MailService from "./MailService";
44
44
  import EmailTemplateType from "../../Types/Email/EmailTemplateType";
45
- import { FileRoute } from "../../ServiceRoute";
45
+ import { StatusPageApiRoute } from "../../ServiceRoute";
46
46
  import ProjectSMTPConfigService from "./ProjectSmtpConfigService";
47
47
  import StatusPageResource from "../../Models/DatabaseModels/StatusPageResource";
48
48
  import StatusPageResourceService from "./StatusPageResourceService";
@@ -750,6 +750,9 @@ export class Service extends DatabaseService<StatusPage> {
750
750
  const statusPageName: string =
751
751
  statuspage.pageTitle || statuspage.name || "Status Page";
752
752
 
753
+ const statusPageIdString: string | null =
754
+ statuspage.id?.toString() || statuspage._id?.toString() || null;
755
+
753
756
  const report: StatusPageReport = await this.getReportByStatusPage({
754
757
  statusPageId: statuspage.id!,
755
758
  historyDays: statuspage.reportDataInDays || 14,
@@ -777,12 +780,13 @@ export class Service extends DatabaseService<StatusPage> {
777
780
  statusPageUrl: statusPageURL,
778
781
  hasResources: report.totalResources > 0 ? "true" : "false",
779
782
  report: report as any,
780
- logoUrl: statuspage.logoFileId
781
- ? new URL(httpProtocol, host)
782
- .addRoute(FileRoute)
783
- .addRoute("/image/" + statuspage.logoFileId)
784
- .toString()
785
- : "",
783
+ logoUrl:
784
+ statuspage.logoFileId && statusPageIdString
785
+ ? new URL(httpProtocol, host)
786
+ .addRoute(StatusPageApiRoute)
787
+ .addRoute(`/logo/${statusPageIdString}`)
788
+ .toString()
789
+ : "",
786
790
  isPublicStatusPage: statuspage.isPublicStatusPage
787
791
  ? "true"
788
792
  : "false",
@@ -806,6 +810,7 @@ export class Service extends DatabaseService<StatusPage> {
806
810
  if (data.email) {
807
811
  // force send to this email instead of sending to all subscribers.
808
812
  await sendEmail(data.email, null);
813
+ return; // don't notify subscribers when explicitly sending a test email.
809
814
  }
810
815
 
811
816
  const subscribers: Array<StatusPageSubscriber> =