@oneuptime/common 11.7.1 → 11.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (237) hide show
  1. package/Models/DatabaseModels/DashboardDomain.ts +1 -0
  2. package/Models/DatabaseModels/Incident.ts +2 -0
  3. package/Models/DatabaseModels/IncidentEpisode.ts +1 -0
  4. package/Models/DatabaseModels/IncidentEpisodePublicNote.ts +1 -0
  5. package/Models/DatabaseModels/IncidentEpisodeStateTimeline.ts +1 -0
  6. package/Models/DatabaseModels/IncidentPublicNote.ts +1 -0
  7. package/Models/DatabaseModels/IncidentStateTimeline.ts +1 -0
  8. package/Models/DatabaseModels/Monitor.ts +1 -0
  9. package/Models/DatabaseModels/MonitorStatusTimeline.ts +1 -0
  10. package/Models/DatabaseModels/NetworkSite.ts +1 -0
  11. package/Models/DatabaseModels/NetworkSiteStatusTimeline.ts +1 -0
  12. package/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.ts +1 -0
  13. package/Models/DatabaseModels/OnCallDutyPolicySchedule.ts +2 -0
  14. package/Models/DatabaseModels/Probe.ts +13 -13
  15. package/Models/DatabaseModels/ScheduledMaintenance.ts +1 -0
  16. package/Models/DatabaseModels/ScheduledMaintenancePublicNote.ts +1 -0
  17. package/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.ts +1 -0
  18. package/Models/DatabaseModels/ScheduledMaintenanceTemplate.ts +1 -0
  19. package/Models/DatabaseModels/StatusPage.ts +1 -0
  20. package/Models/DatabaseModels/StatusPageAnnouncement.ts +1 -0
  21. package/Models/DatabaseModels/StatusPageDomain.ts +1 -0
  22. package/Models/DatabaseModels/WorkspaceNotificationSummary.ts +1 -0
  23. package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +11 -0
  24. package/Server/API/BaseAPI.ts +17 -1
  25. package/Server/API/CommonAPI.ts +32 -0
  26. package/Server/API/MicrosoftTeamsAPI.ts +99 -2
  27. package/Server/API/ProbeAPI.ts +2 -0
  28. package/Server/API/SlackAPI.ts +62 -0
  29. package/Server/API/StatusPageAPI.ts +157 -192
  30. package/Server/Infrastructure/Postgres/SchemaMigrations/1785140242697-AddHotQueryIndexes.ts +89 -0
  31. package/Server/Infrastructure/Postgres/SchemaMigrations/1785148065137-AddHotQueryIndexesSecondPass.ts +61 -0
  32. package/Server/Infrastructure/Postgres/SchemaMigrations/1785240000000-RepairCrossProjectMonitorStatusReferences.ts +280 -0
  33. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
  34. package/Server/Services/DatabaseService.ts +11 -3
  35. package/Server/Services/MonitorGroupService.ts +145 -0
  36. package/Server/Services/MonitorService.ts +231 -30
  37. package/Server/Services/ProbeService.ts +319 -16
  38. package/Server/Services/StatusPageService.ts +72 -42
  39. package/Server/Services/WorkspaceNotificationRuleService.ts +232 -2
  40. package/Server/Utils/Database/PostgresErrorTranslator.ts +131 -0
  41. package/Server/Utils/Monitor/MonitorResource.ts +88 -35
  42. package/Server/Utils/Monitor/MonitorStatusTimeline.ts +21 -1
  43. package/Server/Utils/Monitor/MonitorStepsProjectValidator.ts +158 -0
  44. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +706 -6
  45. package/Tests/Models/DatabaseModels/ProbeColumnAccessControl.test.ts +103 -0
  46. package/Tests/Server/API/BaseAPIUpdateNoOp.test.ts +135 -0
  47. package/Tests/Server/API/CommonAPIAuthGuard.test.ts +317 -0
  48. package/Tests/Server/API/MicrosoftTeamsManifest.test.ts +228 -0
  49. package/Tests/Server/API/ProbeAPI.test.ts +6 -0
  50. package/Tests/Server/API/ZZVerifyChatsAuth.test.ts +167 -0
  51. package/Tests/Server/Services/AddHotQueryIndexesMigration.test.ts +255 -0
  52. package/Tests/Server/Services/AddHotQueryIndexesSecondPassMigration.test.ts +258 -0
  53. package/Tests/Server/Services/DatabaseServiceSanitizeUpdateData.test.ts +1 -1
  54. package/Tests/Server/Services/DiscoveryScanClaimHookFreeSafety.test.ts +121 -0
  55. package/Tests/Server/Services/HeartbeatWriteFastPathSafety.test.ts +200 -0
  56. package/Tests/Server/Services/HookFreeWriteSafetySecondPass.test.ts +181 -0
  57. package/Tests/Server/Services/MonitorGroupServiceBatchedStatuses.test.ts +513 -0
  58. package/Tests/Server/Services/MonitorResourceProbeAgreementReuse.test.ts +492 -0
  59. package/Tests/Server/Services/MonitorServiceProbeSelection.test.ts +401 -0
  60. package/Tests/Server/Services/MonitorStatusTimelineFastPath.test.ts +453 -0
  61. package/Tests/Server/Services/ProbeAuthKeyCacheAndHeartbeat.test.ts +679 -0
  62. package/Tests/Server/Services/ServiceLevelObjectiveService.test.ts +3 -3
  63. package/Tests/Server/Services/StatusPageDomainResolutionCache.test.ts +245 -0
  64. package/Tests/Server/Services/StatusPageOverviewTimelineWindow.test.ts +283 -0
  65. package/Tests/Server/Services/StatusPageServiceMcp.test.ts +17 -1
  66. package/Tests/Server/Services/StatusPageServiceMonitorGroupBatching.test.ts +331 -0
  67. package/Tests/Server/Services/UserOnCallLogClaimNotificationRule.test.ts +12 -12
  68. package/Tests/Server/Services/WorkspaceNotificationRuleChats.test.ts +1390 -0
  69. package/Tests/Server/Utils/AI/Insights/InsightFixRouting.test.ts +4 -4
  70. package/Tests/Server/Utils/AI/Insights/InsightScanner.test.ts +2 -2
  71. package/Tests/Server/Utils/AI/Insights/InsightStore.test.ts +1 -3
  72. package/Tests/Server/Utils/AI/Insights/InsightStoreHardening.test.ts +1 -3
  73. package/Tests/Server/Utils/AI/Insights/InsightTriage.test.ts +1 -1
  74. package/Tests/Server/Utils/AI/Insights/InsightTriageRunner.test.ts +5 -5
  75. package/Tests/Server/Utils/Alert/AlertPrivacyFilter.test.ts +187 -0
  76. package/Tests/Server/Utils/AlertEpisode/AlertEpisodePrivacyFilter.test.ts +170 -0
  77. package/Tests/Server/Utils/Database/PostgresErrorTranslator.test.ts +125 -0
  78. package/Tests/Server/Utils/Incident/IncidentPrivacyFilter.test.ts +264 -0
  79. package/Tests/Server/Utils/IncidentEpisode/IncidentEpisodePrivacyFilter.test.ts +175 -0
  80. package/Tests/Server/Utils/Monitor/MonitorStepsProjectValidator.test.ts +181 -0
  81. package/Tests/Server/Utils/Monitor/NetworkDeviceWalkUtil.test.ts +1 -1
  82. package/Tests/Server/Utils/Monitor/NetworkInventoryUtil.test.ts +2 -2
  83. package/Tests/Server/Utils/Workspace/MicrosoftTeamsChannelsList.test.ts +341 -0
  84. package/Tests/Server/Utils/Workspace/MicrosoftTeamsChats.test.ts +2175 -0
  85. package/Tests/Server/Utils/Workspace/SlackChannelsList.test.ts +585 -0
  86. package/Tests/Types/Billing/MeteredPlan.test.ts +46 -0
  87. package/Tests/Types/Workspace/NotificationRules/NotificationRuleCondition.test.ts +219 -1
  88. package/Tests/UI/Components/BulkUpdateForm.test.tsx +174 -0
  89. package/Tests/UI/Components/CardModelDetailEdit.test.tsx +315 -0
  90. package/Tests/UI/Components/ModelDetailSelect.test.tsx +229 -0
  91. package/Tests/UI/Components/ModelTable/BaseModelTableBulkSelectAll.test.tsx +1258 -0
  92. package/Tests/UI/Components/ModelTableExportFromColumns.test.ts +153 -0
  93. package/Tests/UI/Components/MonacoLoader.test.ts +67 -0
  94. package/Tests/UI/Components/MonacoRuntime.test.ts +121 -0
  95. package/Tests/UI/Components/TableBulkCsvExport.test.tsx +212 -3
  96. package/Tests/UI/Utils/Project.test.ts +809 -0
  97. package/Tests/UI/Utils/TableColumnsToCsv.test.ts +360 -0
  98. package/Tests/Utils/Alerts/AlertMetricType.test.ts +143 -0
  99. package/Tests/Utils/Dashboard/VariableUrlState.test.ts +219 -0
  100. package/Tests/Utils/Incident/IncidentMetricType.test.ts +174 -0
  101. package/Tests/Utils/Monitor/MonitorProbeSelectionUtil.test.ts +145 -0
  102. package/Tests/Utils/StatusPage/ResourceUptime.test.ts +263 -0
  103. package/Types/Workspace/NotificationRules/BaseNotificationRule.ts +4 -0
  104. package/Types/Workspace/NotificationRules/NotificationRuleCondition.ts +25 -10
  105. package/Types/Workspace/WorkspaceMessagePayload.ts +1 -0
  106. package/UI/Components/BulkUpdate/BulkUpdateForm.tsx +63 -8
  107. package/UI/Components/CodeEditor/CodeEditor.tsx +3 -0
  108. package/UI/Components/CodeEditor/MonacoLoader.ts +36 -0
  109. package/UI/Components/Forms/ModelForm.tsx +41 -18
  110. package/UI/Components/ModelDetail/CardModelDetail.tsx +23 -1
  111. package/UI/Components/ModelDetail/ModelDetail.tsx +46 -1
  112. package/UI/Components/ModelTable/BaseModelTable.tsx +300 -30
  113. package/UI/Components/ModelTable/Column.ts +12 -0
  114. package/UI/Components/ModelTable/ExportFromColumns.ts +57 -0
  115. package/UI/Components/Navbar/NavBar.tsx +66 -1
  116. package/UI/Components/Table/Table.tsx +52 -13
  117. package/UI/Components/Table/Types/Column.ts +18 -0
  118. package/UI/Utils/Project.ts +214 -18
  119. package/UI/Utils/TableColumnsToCsv.ts +86 -6
  120. package/UI/esbuild-config.js +34 -0
  121. package/Utils/Monitor/MonitorProbeSelectionUtil.ts +72 -0
  122. package/build/dist/Models/DatabaseModels/DashboardDomain.js +1 -0
  123. package/build/dist/Models/DatabaseModels/DashboardDomain.js.map +1 -1
  124. package/build/dist/Models/DatabaseModels/Incident.js +2 -0
  125. package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
  126. package/build/dist/Models/DatabaseModels/IncidentEpisode.js +1 -0
  127. package/build/dist/Models/DatabaseModels/IncidentEpisode.js.map +1 -1
  128. package/build/dist/Models/DatabaseModels/IncidentEpisodePublicNote.js +1 -0
  129. package/build/dist/Models/DatabaseModels/IncidentEpisodePublicNote.js.map +1 -1
  130. package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js +1 -0
  131. package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js.map +1 -1
  132. package/build/dist/Models/DatabaseModels/IncidentPublicNote.js +1 -0
  133. package/build/dist/Models/DatabaseModels/IncidentPublicNote.js.map +1 -1
  134. package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js +1 -0
  135. package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js.map +1 -1
  136. package/build/dist/Models/DatabaseModels/Monitor.js +1 -0
  137. package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
  138. package/build/dist/Models/DatabaseModels/MonitorStatusTimeline.js +1 -0
  139. package/build/dist/Models/DatabaseModels/MonitorStatusTimeline.js.map +1 -1
  140. package/build/dist/Models/DatabaseModels/NetworkSite.js +1 -0
  141. package/build/dist/Models/DatabaseModels/NetworkSite.js.map +1 -1
  142. package/build/dist/Models/DatabaseModels/NetworkSiteStatusTimeline.js +1 -0
  143. package/build/dist/Models/DatabaseModels/NetworkSiteStatusTimeline.js.map +1 -1
  144. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js +1 -0
  145. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js.map +1 -1
  146. package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js +2 -0
  147. package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js.map +1 -1
  148. package/build/dist/Models/DatabaseModels/Probe.js +13 -13
  149. package/build/dist/Models/DatabaseModels/Probe.js.map +1 -1
  150. package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js +1 -0
  151. package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js.map +1 -1
  152. package/build/dist/Models/DatabaseModels/ScheduledMaintenancePublicNote.js +1 -0
  153. package/build/dist/Models/DatabaseModels/ScheduledMaintenancePublicNote.js.map +1 -1
  154. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js +1 -0
  155. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js.map +1 -1
  156. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceTemplate.js +1 -0
  157. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceTemplate.js.map +1 -1
  158. package/build/dist/Models/DatabaseModels/StatusPage.js +1 -0
  159. package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
  160. package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js +1 -0
  161. package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js.map +1 -1
  162. package/build/dist/Models/DatabaseModels/StatusPageDomain.js +1 -0
  163. package/build/dist/Models/DatabaseModels/StatusPageDomain.js.map +1 -1
  164. package/build/dist/Models/DatabaseModels/WorkspaceNotificationSummary.js +1 -0
  165. package/build/dist/Models/DatabaseModels/WorkspaceNotificationSummary.js.map +1 -1
  166. package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
  167. package/build/dist/Server/API/BaseAPI.js +12 -1
  168. package/build/dist/Server/API/BaseAPI.js.map +1 -1
  169. package/build/dist/Server/API/CommonAPI.js +21 -0
  170. package/build/dist/Server/API/CommonAPI.js.map +1 -1
  171. package/build/dist/Server/API/MicrosoftTeamsAPI.js +72 -2
  172. package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
  173. package/build/dist/Server/API/ProbeAPI.js +2 -0
  174. package/build/dist/Server/API/ProbeAPI.js.map +1 -1
  175. package/build/dist/Server/API/SlackAPI.js +44 -0
  176. package/build/dist/Server/API/SlackAPI.js.map +1 -1
  177. package/build/dist/Server/API/StatusPageAPI.js +98 -152
  178. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  179. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785140242697-AddHotQueryIndexes.js +36 -0
  180. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785140242697-AddHotQueryIndexes.js.map +1 -0
  181. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785148065137-AddHotQueryIndexesSecondPass.js +26 -0
  182. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785148065137-AddHotQueryIndexesSecondPass.js.map +1 -0
  183. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785240000000-RepairCrossProjectMonitorStatusReferences.js +254 -0
  184. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785240000000-RepairCrossProjectMonitorStatusReferences.js.map +1 -0
  185. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
  186. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  187. package/build/dist/Server/Services/DatabaseService.js +10 -2
  188. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  189. package/build/dist/Server/Services/MonitorGroupService.js +118 -0
  190. package/build/dist/Server/Services/MonitorGroupService.js.map +1 -1
  191. package/build/dist/Server/Services/MonitorService.js +198 -24
  192. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  193. package/build/dist/Server/Services/ProbeService.js +238 -9
  194. package/build/dist/Server/Services/ProbeService.js.map +1 -1
  195. package/build/dist/Server/Services/StatusPageService.js +46 -39
  196. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  197. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js +178 -3
  198. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js.map +1 -1
  199. package/build/dist/Server/Utils/Database/PostgresErrorTranslator.js +80 -0
  200. package/build/dist/Server/Utils/Database/PostgresErrorTranslator.js.map +1 -0
  201. package/build/dist/Server/Utils/Monitor/MonitorResource.js +67 -29
  202. package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
  203. package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js +31 -14
  204. package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js.map +1 -1
  205. package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js +117 -0
  206. package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js.map +1 -0
  207. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +549 -7
  208. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  209. package/build/dist/Types/Workspace/NotificationRules/NotificationRuleCondition.js +17 -7
  210. package/build/dist/Types/Workspace/NotificationRules/NotificationRuleCondition.js.map +1 -1
  211. package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js +37 -11
  212. package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js.map +1 -1
  213. package/build/dist/UI/Components/CodeEditor/CodeEditor.js +2 -0
  214. package/build/dist/UI/Components/CodeEditor/CodeEditor.js.map +1 -1
  215. package/build/dist/UI/Components/CodeEditor/MonacoLoader.js +34 -0
  216. package/build/dist/UI/Components/CodeEditor/MonacoLoader.js.map +1 -0
  217. package/build/dist/UI/Components/Forms/ModelForm.js +37 -17
  218. package/build/dist/UI/Components/Forms/ModelForm.js.map +1 -1
  219. package/build/dist/UI/Components/ModelDetail/CardModelDetail.js +22 -1
  220. package/build/dist/UI/Components/ModelDetail/CardModelDetail.js.map +1 -1
  221. package/build/dist/UI/Components/ModelDetail/ModelDetail.js +31 -1
  222. package/build/dist/UI/Components/ModelDetail/ModelDetail.js.map +1 -1
  223. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +234 -21
  224. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  225. package/build/dist/UI/Components/ModelTable/ExportFromColumns.js +30 -0
  226. package/build/dist/UI/Components/ModelTable/ExportFromColumns.js.map +1 -0
  227. package/build/dist/UI/Components/Navbar/NavBar.js +39 -1
  228. package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
  229. package/build/dist/UI/Components/Table/Table.js +31 -11
  230. package/build/dist/UI/Components/Table/Table.js.map +1 -1
  231. package/build/dist/UI/Utils/Project.js +161 -15
  232. package/build/dist/UI/Utils/Project.js.map +1 -1
  233. package/build/dist/UI/Utils/TableColumnsToCsv.js +67 -6
  234. package/build/dist/UI/Utils/TableColumnsToCsv.js.map +1 -1
  235. package/build/dist/Utils/Monitor/MonitorProbeSelectionUtil.js +52 -0
  236. package/build/dist/Utils/Monitor/MonitorProbeSelectionUtil.js.map +1 -0
  237. package/package.json +4 -3
@@ -6,6 +6,7 @@ import Columns from "../../../UI/Components/Table/Types/Columns";
6
6
  import FieldType from "../../../UI/Components/Types/FieldType";
7
7
  import OneUptimeDate from "../../../Types/Date";
8
8
  import ObjectID from "../../../Types/ObjectID";
9
+ import { createElement, ReactElement } from "react";
9
10
 
10
11
  /*
11
12
  * Concrete row shape used across the tests. A real ModelTable passes model
@@ -20,8 +21,23 @@ interface Row {
20
21
  label?: { name?: string } | null | undefined;
21
22
  severityText?: string | undefined;
22
23
  _id?: string | undefined;
24
+ description?: string | undefined;
25
+ hosts?: Array<{ name: string }> | undefined;
26
+ services?: Array<{ name: string }> | undefined;
27
+ monitors?: Array<{ name: string }> | undefined;
23
28
  }
24
29
 
30
+ /*
31
+ * The exporter never calls getElement - it only cares whether a column has
32
+ * one, because that is what makes a `_id` column a placeholder rather than an
33
+ * id the user asked to see.
34
+ */
35
+ type RenderCellFunction = () => ReactElement;
36
+
37
+ const renderCell: RenderCellFunction = (): ReactElement => {
38
+ return createElement("span");
39
+ };
40
+
25
41
  describe("TableColumnsToCsv", () => {
26
42
  describe("escapeCsvValue", () => {
27
43
  test("leaves a plain value untouched", () => {
@@ -321,6 +337,192 @@ describe("TableColumnsToCsv", () => {
321
337
  ),
322
338
  ).toEqual([]);
323
339
  });
340
+
341
+ test("excludes a placeholder _id column such as Owners", () => {
342
+ /*
343
+ * The "Owners" column on alerts / incidents / monitors renders entirely
344
+ * through getElement and only declares `field: { _id: true }` so the row
345
+ * gets fetched. Exporting it wrote a raw UUID under the header "Owners".
346
+ */
347
+ const result: Columns<Row> = TableColumnsToCsv.getExportableColumns([
348
+ { title: "Name", type: FieldType.Text, key: "name" },
349
+ {
350
+ title: "Owners",
351
+ type: FieldType.Element,
352
+ key: "_id",
353
+ exportKeys: ["_id"],
354
+ getElement: renderCell,
355
+ },
356
+ ]);
357
+
358
+ expect(
359
+ result.map((c: Column<Row>) => {
360
+ return c.title;
361
+ }),
362
+ ).toEqual(["Name"]);
363
+ });
364
+
365
+ test("keeps an _id column that genuinely shows the id", () => {
366
+ // FieldType.ObjectID is how a column says the id IS the value.
367
+ const result: Columns<Row> = TableColumnsToCsv.getExportableColumns([
368
+ {
369
+ title: "Execution ID",
370
+ type: FieldType.ObjectID,
371
+ key: "_id",
372
+ exportKeys: ["_id"],
373
+ getElement: renderCell,
374
+ },
375
+ ]);
376
+
377
+ expect(result.length).toBe(1);
378
+ });
379
+
380
+ test("keeps an _id column that has no getElement", () => {
381
+ const result: Columns<Row> = TableColumnsToCsv.getExportableColumns([
382
+ { title: "ID", type: FieldType.Text, key: "_id", exportKeys: ["_id"] },
383
+ ]);
384
+
385
+ expect(result.length).toBe(1);
386
+ });
387
+
388
+ test("keeps an _id column that declares other fields alongside it", () => {
389
+ const result: Columns<Row> = TableColumnsToCsv.getExportableColumns([
390
+ {
391
+ title: "Resource",
392
+ type: FieldType.Element,
393
+ key: "_id",
394
+ exportKeys: ["_id", "name"],
395
+ getElement: renderCell,
396
+ },
397
+ ]);
398
+
399
+ expect(result.length).toBe(1);
400
+ });
401
+
402
+ test("excludes a column that opted out with disableCsvExport", () => {
403
+ const result: Columns<Row> = TableColumnsToCsv.getExportableColumns([
404
+ { title: "Name", type: FieldType.Text, key: "name" },
405
+ {
406
+ title: "Status",
407
+ type: FieldType.Text,
408
+ key: "status",
409
+ disableCsvExport: true,
410
+ },
411
+ ]);
412
+
413
+ expect(
414
+ result.map((c: Column<Row>) => {
415
+ return c.title;
416
+ }),
417
+ ).toEqual(["Name"]);
418
+ });
419
+
420
+ test("keeps a keyless column that supplies its own export value", () => {
421
+ const result: Columns<Row> = TableColumnsToCsv.getExportableColumns([
422
+ {
423
+ title: "Owners",
424
+ type: FieldType.Element,
425
+ getExportValue: (): string => {
426
+ return "someone";
427
+ },
428
+ },
429
+ ]);
430
+
431
+ expect(result.length).toBe(1);
432
+ });
433
+
434
+ test("disableCsvExport wins over an explicit export value", () => {
435
+ const result: Columns<Row> = TableColumnsToCsv.getExportableColumns([
436
+ {
437
+ title: "Owners",
438
+ type: FieldType.Element,
439
+ key: "_id",
440
+ disableCsvExport: true,
441
+ getExportValue: (): string => {
442
+ return "someone";
443
+ },
444
+ },
445
+ ]);
446
+
447
+ expect(result).toEqual([]);
448
+ });
449
+ });
450
+
451
+ describe("getExportKeys", () => {
452
+ test("falls back to the single column key", () => {
453
+ expect(
454
+ TableColumnsToCsv.getExportKeys<Row>({
455
+ title: "Name",
456
+ type: FieldType.Text,
457
+ key: "name",
458
+ }),
459
+ ).toEqual(["name"]);
460
+ });
461
+
462
+ test("returns every declared key when exportKeys is set", () => {
463
+ expect(
464
+ TableColumnsToCsv.getExportKeys<Row>({
465
+ title: "Affected Resources",
466
+ type: FieldType.EntityArray,
467
+ key: "hosts",
468
+ exportKeys: ["hosts", "services", "monitors"],
469
+ }),
470
+ ).toEqual(["hosts", "services", "monitors"]);
471
+ });
472
+
473
+ test("returns nothing for a keyless column", () => {
474
+ expect(
475
+ TableColumnsToCsv.getExportKeys<Row>({
476
+ title: "Custom",
477
+ type: FieldType.Element,
478
+ }),
479
+ ).toEqual([]);
480
+ });
481
+ });
482
+
483
+ describe("isPlaceholderIdColumn", () => {
484
+ test("is true for an _id-only column rendered through getElement", () => {
485
+ expect(
486
+ TableColumnsToCsv.isPlaceholderIdColumn<Row>({
487
+ title: "Owners",
488
+ type: FieldType.Element,
489
+ key: "_id",
490
+ getElement: renderCell,
491
+ }),
492
+ ).toBe(true);
493
+ });
494
+
495
+ test("is false without a getElement", () => {
496
+ expect(
497
+ TableColumnsToCsv.isPlaceholderIdColumn<Row>({
498
+ title: "ID",
499
+ type: FieldType.Element,
500
+ key: "_id",
501
+ }),
502
+ ).toBe(false);
503
+ });
504
+
505
+ test("is false for FieldType.ObjectID", () => {
506
+ expect(
507
+ TableColumnsToCsv.isPlaceholderIdColumn<Row>({
508
+ title: "Run ID",
509
+ type: FieldType.ObjectID,
510
+ key: "_id",
511
+ getElement: renderCell,
512
+ }),
513
+ ).toBe(false);
514
+ });
515
+
516
+ test("is false for a column keyed on something other than _id", () => {
517
+ expect(
518
+ TableColumnsToCsv.isPlaceholderIdColumn<Row>({
519
+ title: "Name",
520
+ type: FieldType.Element,
521
+ key: "name",
522
+ getElement: renderCell,
523
+ }),
524
+ ).toBe(false);
525
+ });
324
526
  });
325
527
 
326
528
  describe("getCellValue", () => {
@@ -343,6 +545,100 @@ describe("TableColumnsToCsv", () => {
343
545
  "No",
344
546
  );
345
547
  });
548
+
549
+ test("reads every declared field, not just the first", () => {
550
+ /*
551
+ * The alert "Affected Resources" cell spans several relations at once.
552
+ * Only the first used to reach the file, so a row whose resources were
553
+ * services exported an empty cell.
554
+ */
555
+ const column: Column<Row> = {
556
+ title: "Affected Resources",
557
+ type: FieldType.EntityArray,
558
+ key: "hosts",
559
+ exportKeys: ["hosts", "services", "monitors"],
560
+ };
561
+
562
+ const item: Row = {
563
+ hosts: [{ name: "web-01" }],
564
+ services: [{ name: "checkout" }],
565
+ monitors: [{ name: "api-uptime" }],
566
+ };
567
+
568
+ expect(TableColumnsToCsv.getCellValue(item, column)).toBe(
569
+ "web-01; checkout; api-uptime",
570
+ );
571
+ });
572
+
573
+ test("skips the declared fields that are empty on this row", () => {
574
+ const column: Column<Row> = {
575
+ title: "Affected Resources",
576
+ type: FieldType.EntityArray,
577
+ key: "hosts",
578
+ exportKeys: ["hosts", "services", "monitors"],
579
+ };
580
+
581
+ expect(
582
+ TableColumnsToCsv.getCellValue(
583
+ { services: [{ name: "checkout" }] },
584
+ column,
585
+ ),
586
+ ).toBe("checkout");
587
+ });
588
+
589
+ test("does not list the same value twice across declared fields", () => {
590
+ const column: Column<Row> = {
591
+ title: "Affected Resources",
592
+ type: FieldType.EntityArray,
593
+ key: "hosts",
594
+ exportKeys: ["hosts", "services"],
595
+ };
596
+
597
+ const item: Row = {
598
+ hosts: [{ name: "shared" }],
599
+ services: [{ name: "shared" }],
600
+ };
601
+
602
+ expect(TableColumnsToCsv.getCellValue(item, column)).toBe("shared");
603
+ });
604
+
605
+ test("returns an empty cell when none of the declared fields are set", () => {
606
+ const column: Column<Row> = {
607
+ title: "Affected Resources",
608
+ type: FieldType.EntityArray,
609
+ key: "hosts",
610
+ exportKeys: ["hosts", "services"],
611
+ };
612
+
613
+ expect(TableColumnsToCsv.getCellValue({}, column)).toBe("");
614
+ });
615
+
616
+ test("uses getExportValue in preference to the row's own fields", () => {
617
+ const column: Column<Row> = {
618
+ title: "Owners",
619
+ type: FieldType.Element,
620
+ key: "_id",
621
+ getExportValue: (item: Row): string => {
622
+ return `owner of ${item.name}`;
623
+ },
624
+ };
625
+
626
+ expect(
627
+ TableColumnsToCsv.getCellValue({ _id: "abc", name: "Alpha" }, column),
628
+ ).toBe("owner of Alpha");
629
+ });
630
+
631
+ test("treats a null-ish getExportValue result as an empty cell", () => {
632
+ const column: Column<Row> = {
633
+ title: "Owners",
634
+ type: FieldType.Element,
635
+ getExportValue: (): string => {
636
+ return undefined as unknown as string;
637
+ },
638
+ };
639
+
640
+ expect(TableColumnsToCsv.getCellValue({}, column)).toBe("");
641
+ });
346
642
  });
347
643
 
348
644
  describe("convertToCsv", () => {
@@ -408,6 +704,70 @@ describe("TableColumnsToCsv", () => {
408
704
  });
409
705
  expect(csv).toBe("Name,Active");
410
706
  });
707
+
708
+ test("drops the placeholder id column and spans every relation of a multi-field column", () => {
709
+ /*
710
+ * End to end over the two shapes that used to export the wrong value: an
711
+ * "Owners" column that wrote a UUID, and an "Affected Resources" column
712
+ * that wrote only its first relation.
713
+ */
714
+ const csv: string = TableColumnsToCsv.convertToCsv({
715
+ items: [
716
+ {
717
+ _id: "b6b0f3c2-0000-0000-0000-000000000000",
718
+ name: "Alpha",
719
+ hosts: [{ name: "web-01" }],
720
+ services: [{ name: "checkout" }],
721
+ },
722
+ ],
723
+ columns: [
724
+ { title: "Name", type: FieldType.Text, key: "name" },
725
+ {
726
+ title: "Affected Resources",
727
+ type: FieldType.EntityArray,
728
+ key: "hosts",
729
+ exportKeys: ["hosts", "services"],
730
+ getElement: renderCell,
731
+ },
732
+ {
733
+ title: "Owners",
734
+ type: FieldType.Element,
735
+ key: "_id",
736
+ exportKeys: ["_id"],
737
+ getElement: renderCell,
738
+ },
739
+ ],
740
+ });
741
+
742
+ expect(csv.split("\r\n")).toEqual([
743
+ "Name,Affected Resources",
744
+ "Alpha,web-01; checkout",
745
+ ]);
746
+ });
747
+
748
+ test("an opted-in placeholder column exports its supplied value", () => {
749
+ const csv: string = TableColumnsToCsv.convertToCsv({
750
+ items: [{ _id: "1", name: "Alpha" }],
751
+ columns: [
752
+ { title: "Name", type: FieldType.Text, key: "name" },
753
+ {
754
+ title: "Owners",
755
+ type: FieldType.Element,
756
+ key: "_id",
757
+ exportKeys: ["_id"],
758
+ getElement: renderCell,
759
+ getExportValue: (): string => {
760
+ return "jane@example.com";
761
+ },
762
+ },
763
+ ],
764
+ });
765
+
766
+ expect(csv.split("\r\n")).toEqual([
767
+ "Name,Owners",
768
+ "Alpha,jane@example.com",
769
+ ]);
770
+ });
411
771
  });
412
772
 
413
773
  describe("getExportFilename", () => {
@@ -0,0 +1,143 @@
1
+ import AlertMetricTypeUtil from "../../../Utils/Alerts/AlertMetricType";
2
+ import AlertMetricType from "../../../Types/Alerts/AlertMetricType";
3
+ import AggregationType from "../../../Types/BaseDatabase/AggregationType";
4
+
5
+ describe("AlertMetricTypeUtil", () => {
6
+ describe("getAggregationTypeByAlertMetricType", () => {
7
+ test("AlertCount aggregates as Sum", () => {
8
+ expect(
9
+ AlertMetricTypeUtil.getAggregationTypeByAlertMetricType(
10
+ AlertMetricType.AlertCount,
11
+ ),
12
+ ).toBe(AggregationType.Sum);
13
+ });
14
+
15
+ test.each([
16
+ AlertMetricType.TimeToAcknowledge,
17
+ AlertMetricType.TimeToResolve,
18
+ AlertMetricType.AlertDuration,
19
+ ])("%s aggregates as Avg", (metricType: AlertMetricType) => {
20
+ expect(
21
+ AlertMetricTypeUtil.getAggregationTypeByAlertMetricType(metricType),
22
+ ).toBe(AggregationType.Avg);
23
+ });
24
+
25
+ test("throws for an unknown metric type", () => {
26
+ expect(() => {
27
+ return AlertMetricTypeUtil.getAggregationTypeByAlertMetricType(
28
+ "not-a-real-metric" as AlertMetricType,
29
+ );
30
+ }).toThrow("Invalid AlertMetricType value");
31
+ });
32
+ });
33
+
34
+ describe("getAllAlertMetricTypes", () => {
35
+ test("returns exactly the four supported metric types", () => {
36
+ expect(AlertMetricTypeUtil.getAllAlertMetricTypes()).toEqual([
37
+ AlertMetricType.AlertCount,
38
+ AlertMetricType.TimeToAcknowledge,
39
+ AlertMetricType.TimeToResolve,
40
+ AlertMetricType.AlertDuration,
41
+ ]);
42
+ });
43
+
44
+ test("every returned type resolves to a valid aggregation type", () => {
45
+ for (const metricType of AlertMetricTypeUtil.getAllAlertMetricTypes()) {
46
+ expect(() => {
47
+ return AlertMetricTypeUtil.getAggregationTypeByAlertMetricType(
48
+ metricType,
49
+ );
50
+ }).not.toThrow();
51
+ }
52
+ });
53
+ });
54
+
55
+ describe("getTitleByAlertMetricType", () => {
56
+ test.each([
57
+ [AlertMetricType.AlertCount, "Alert Count"],
58
+ [AlertMetricType.TimeToAcknowledge, "Time to Acknowledge"],
59
+ [AlertMetricType.TimeToResolve, "Time to Resolve"],
60
+ [AlertMetricType.AlertDuration, "Alert Duration"],
61
+ ])("%s -> %s", (metricType: AlertMetricType, expected: string) => {
62
+ expect(AlertMetricTypeUtil.getTitleByAlertMetricType(metricType)).toBe(
63
+ expected,
64
+ );
65
+ });
66
+
67
+ test("returns empty string for an unknown metric type", () => {
68
+ expect(
69
+ AlertMetricTypeUtil.getTitleByAlertMetricType(
70
+ "unknown" as AlertMetricType,
71
+ ),
72
+ ).toBe("");
73
+ });
74
+ });
75
+
76
+ describe("getDescriptionByAlertMetricType", () => {
77
+ test("returns a non-empty sentence for each known type", () => {
78
+ for (const metricType of AlertMetricTypeUtil.getAllAlertMetricTypes()) {
79
+ const description: string =
80
+ AlertMetricTypeUtil.getDescriptionByAlertMetricType(metricType);
81
+ expect(description.length).toBeGreaterThan(0);
82
+ expect(description.endsWith(".")).toBe(true);
83
+ }
84
+ });
85
+
86
+ test("returns empty string for an unknown metric type", () => {
87
+ expect(
88
+ AlertMetricTypeUtil.getDescriptionByAlertMetricType(
89
+ "unknown" as AlertMetricType,
90
+ ),
91
+ ).toBe("");
92
+ });
93
+ });
94
+
95
+ describe("getLegendByAlertMetricType", () => {
96
+ test.each([
97
+ [AlertMetricType.AlertCount, "Alerts"],
98
+ [AlertMetricType.TimeToAcknowledge, "Time to Acknowledge"],
99
+ [AlertMetricType.TimeToResolve, "Time to Resolve"],
100
+ [AlertMetricType.AlertDuration, "Duration"],
101
+ ])("%s -> %s", (metricType: AlertMetricType, expected: string) => {
102
+ expect(AlertMetricTypeUtil.getLegendByAlertMetricType(metricType)).toBe(
103
+ expected,
104
+ );
105
+ });
106
+
107
+ test("returns empty string for an unknown metric type", () => {
108
+ expect(
109
+ AlertMetricTypeUtil.getLegendByAlertMetricType(
110
+ "unknown" as AlertMetricType,
111
+ ),
112
+ ).toBe("");
113
+ });
114
+ });
115
+
116
+ describe("getLegendUnitByAlertMetricType", () => {
117
+ test("AlertCount has no unit", () => {
118
+ expect(
119
+ AlertMetricTypeUtil.getLegendUnitByAlertMetricType(
120
+ AlertMetricType.AlertCount,
121
+ ),
122
+ ).toBe("");
123
+ });
124
+
125
+ test.each([
126
+ AlertMetricType.TimeToAcknowledge,
127
+ AlertMetricType.TimeToResolve,
128
+ AlertMetricType.AlertDuration,
129
+ ])("%s is measured in seconds", (metricType: AlertMetricType) => {
130
+ expect(
131
+ AlertMetricTypeUtil.getLegendUnitByAlertMetricType(metricType),
132
+ ).toBe("s");
133
+ });
134
+
135
+ test("returns empty string for an unknown metric type", () => {
136
+ expect(
137
+ AlertMetricTypeUtil.getLegendUnitByAlertMetricType(
138
+ "unknown" as AlertMetricType,
139
+ ),
140
+ ).toBe("");
141
+ });
142
+ });
143
+ });