@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
@@ -0,0 +1,219 @@
1
+ import DashboardVariableUrlState, {
2
+ VariableUrlSnapshot,
3
+ } from "../../../Utils/Dashboard/VariableUrlState";
4
+ import DashboardVariable, {
5
+ DashboardVariableType,
6
+ } from "../../../Types/Dashboard/DashboardVariable";
7
+
8
+ function makeVariable(data: Partial<DashboardVariable>): DashboardVariable {
9
+ return {
10
+ id: data.id || "id",
11
+ name: data.name || "cluster",
12
+ type: data.type || DashboardVariableType.CustomList,
13
+ ...data,
14
+ };
15
+ }
16
+
17
+ describe("DashboardVariableUrlState", () => {
18
+ describe("parseFromSearch", () => {
19
+ test("returns an empty object for an empty search string", () => {
20
+ expect(DashboardVariableUrlState.parseFromSearch("")).toEqual({});
21
+ });
22
+
23
+ test("ignores params that are not prefixed with var-", () => {
24
+ const result: Record<string, VariableUrlSnapshot> =
25
+ DashboardVariableUrlState.parseFromSearch(
26
+ "?token=abc&filter=on&page=2",
27
+ );
28
+ expect(result).toEqual({});
29
+ });
30
+
31
+ test("parses a single-value variable", () => {
32
+ const result: Record<string, VariableUrlSnapshot> =
33
+ DashboardVariableUrlState.parseFromSearch("?var-cluster=prod");
34
+ expect(result).toEqual({ cluster: { selectedValue: "prod" } });
35
+ });
36
+
37
+ test("works whether or not a leading ? is present", () => {
38
+ expect(
39
+ DashboardVariableUrlState.parseFromSearch("var-cluster=prod"),
40
+ ).toEqual({ cluster: { selectedValue: "prod" } });
41
+ });
42
+
43
+ test("parses a comma-separated multi-value variable", () => {
44
+ const result: Record<string, VariableUrlSnapshot> =
45
+ DashboardVariableUrlState.parseFromSearch(
46
+ "?var-region=us-east,us-west,eu",
47
+ );
48
+ expect(result).toEqual({
49
+ region: { selectedValues: ["us-east", "us-west", "eu"] },
50
+ });
51
+ });
52
+
53
+ test("trims whitespace and drops empty entries in multi-value lists", () => {
54
+ const result: Record<string, VariableUrlSnapshot> =
55
+ DashboardVariableUrlState.parseFromSearch("?var-region=a, ,b, c ,");
56
+ expect(result["region"]?.selectedValues).toEqual(["a", "b", "c"]);
57
+ });
58
+
59
+ test("skips a var- param with an empty name", () => {
60
+ expect(DashboardVariableUrlState.parseFromSearch("?var-=x")).toEqual({});
61
+ });
62
+
63
+ test("decodes url-encoded values", () => {
64
+ const result: Record<string, VariableUrlSnapshot> =
65
+ DashboardVariableUrlState.parseFromSearch(
66
+ "?var-service=" + encodeURIComponent("my service"),
67
+ );
68
+ expect(result["service"]?.selectedValue).toBe("my service");
69
+ });
70
+
71
+ test("parses several variables together", () => {
72
+ const result: Record<string, VariableUrlSnapshot> =
73
+ DashboardVariableUrlState.parseFromSearch(
74
+ "?var-cluster=prod&var-region=a,b&other=1",
75
+ );
76
+ expect(result).toEqual({
77
+ cluster: { selectedValue: "prod" },
78
+ region: { selectedValues: ["a", "b"] },
79
+ });
80
+ });
81
+ });
82
+
83
+ describe("applyUrlToVariables", () => {
84
+ test("returns the same variable untouched when the url has no entry", () => {
85
+ const variable: DashboardVariable = makeVariable({ name: "cluster" });
86
+ const result: Array<DashboardVariable> =
87
+ DashboardVariableUrlState.applyUrlToVariables([variable], {});
88
+ expect(result[0]).toBe(variable);
89
+ });
90
+
91
+ test("applies a single selected value from the url", () => {
92
+ const variable: DashboardVariable = makeVariable({ name: "cluster" });
93
+ const result: Array<DashboardVariable> =
94
+ DashboardVariableUrlState.applyUrlToVariables([variable], {
95
+ cluster: { selectedValue: "prod" },
96
+ });
97
+ expect(result[0]?.selectedValue).toBe("prod");
98
+ expect(result[0]?.selectedValues).toBeUndefined();
99
+ });
100
+
101
+ test("applies multi-selected values from the url", () => {
102
+ const variable: DashboardVariable = makeVariable({ name: "region" });
103
+ const result: Array<DashboardVariable> =
104
+ DashboardVariableUrlState.applyUrlToVariables([variable], {
105
+ region: { selectedValues: ["a", "b"] },
106
+ });
107
+ expect(result[0]?.selectedValues).toEqual(["a", "b"]);
108
+ });
109
+
110
+ test("does not mutate the original variable object", () => {
111
+ const variable: DashboardVariable = makeVariable({
112
+ name: "cluster",
113
+ selectedValue: "old",
114
+ });
115
+ DashboardVariableUrlState.applyUrlToVariables([variable], {
116
+ cluster: { selectedValue: "new" },
117
+ });
118
+ expect(variable.selectedValue).toBe("old");
119
+ });
120
+
121
+ test("matches url state to variables by name", () => {
122
+ const clusterVar: DashboardVariable = makeVariable({ name: "cluster" });
123
+ const regionVar: DashboardVariable = makeVariable({ name: "region" });
124
+ const result: Array<DashboardVariable> =
125
+ DashboardVariableUrlState.applyUrlToVariables([clusterVar, regionVar], {
126
+ region: { selectedValue: "eu" },
127
+ });
128
+ expect(result[0]?.selectedValue).toBeUndefined();
129
+ expect(result[1]?.selectedValue).toBe("eu");
130
+ });
131
+ });
132
+
133
+ describe("parse -> apply round trip", () => {
134
+ test("a shared url reproduces the selection on the variables", () => {
135
+ const variables: Array<DashboardVariable> = [
136
+ makeVariable({ name: "cluster" }),
137
+ makeVariable({ name: "region" }),
138
+ ];
139
+
140
+ const fromUrl: Record<string, VariableUrlSnapshot> =
141
+ DashboardVariableUrlState.parseFromSearch(
142
+ "?var-cluster=prod&var-region=us,eu",
143
+ );
144
+ const applied: Array<DashboardVariable> =
145
+ DashboardVariableUrlState.applyUrlToVariables(variables, fromUrl);
146
+
147
+ expect(applied[0]?.selectedValue).toBe("prod");
148
+ expect(applied[1]?.selectedValues).toEqual(["us", "eu"]);
149
+ });
150
+ });
151
+
152
+ describe("writeToBrowserUrl", () => {
153
+ /*
154
+ * The test environment is jsdom, so window/history/location exist. Reset
155
+ * the URL before each case so assertions don't leak between tests.
156
+ */
157
+ beforeEach(() => {
158
+ window.history.replaceState({}, "", "/dashboard");
159
+ });
160
+
161
+ test("writes a single selected value as a var- param", () => {
162
+ DashboardVariableUrlState.writeToBrowserUrl([
163
+ makeVariable({ name: "cluster", selectedValue: "prod" }),
164
+ ]);
165
+ expect(window.location.search).toBe("?var-cluster=prod");
166
+ });
167
+
168
+ test("writes multi-selected values joined by commas", () => {
169
+ DashboardVariableUrlState.writeToBrowserUrl([
170
+ makeVariable({ name: "region", selectedValues: ["us", "eu"] }),
171
+ ]);
172
+ expect(
173
+ DashboardVariableUrlState.parseFromSearch(window.location.search)[
174
+ "region"
175
+ ]?.selectedValues,
176
+ ).toEqual(["us", "eu"]);
177
+ });
178
+
179
+ test("preserves unrelated query params while replacing var- params", () => {
180
+ window.history.replaceState({}, "", "/dashboard?token=abc&var-old=gone");
181
+ DashboardVariableUrlState.writeToBrowserUrl([
182
+ makeVariable({ name: "cluster", selectedValue: "prod" }),
183
+ ]);
184
+ const params: URLSearchParams = new URLSearchParams(
185
+ window.location.search,
186
+ );
187
+ expect(params.get("token")).toBe("abc");
188
+ expect(params.get("var-cluster")).toBe("prod");
189
+ // The previously-present var-old must be stripped.
190
+ expect(params.has("var-old")).toBe(false);
191
+ });
192
+
193
+ test("omits variables that have no selection", () => {
194
+ DashboardVariableUrlState.writeToBrowserUrl([
195
+ makeVariable({ name: "cluster" }),
196
+ ]);
197
+ expect(window.location.search).toBe("");
198
+ });
199
+
200
+ test("does not push a new history entry (uses replaceState)", () => {
201
+ const lengthBefore: number = window.history.length;
202
+ DashboardVariableUrlState.writeToBrowserUrl([
203
+ makeVariable({ name: "cluster", selectedValue: "prod" }),
204
+ ]);
205
+ expect(window.history.length).toBe(lengthBefore);
206
+ });
207
+
208
+ test("write -> parse round trip preserves the selection", () => {
209
+ DashboardVariableUrlState.writeToBrowserUrl([
210
+ makeVariable({ name: "cluster", selectedValue: "prod" }),
211
+ makeVariable({ name: "region", selectedValues: ["us", "eu"] }),
212
+ ]);
213
+ const parsed: Record<string, VariableUrlSnapshot> =
214
+ DashboardVariableUrlState.parseFromSearch(window.location.search);
215
+ expect(parsed["cluster"]?.selectedValue).toBe("prod");
216
+ expect(parsed["region"]?.selectedValues).toEqual(["us", "eu"]);
217
+ });
218
+ });
219
+ });
@@ -0,0 +1,174 @@
1
+ import IncidentMetricTypeUtil from "../../../Utils/Incident/IncidentMetricType";
2
+ import IncidentMetricType from "../../../Types/Incident/IncidentMetricType";
3
+ import AggregationType from "../../../Types/BaseDatabase/AggregationType";
4
+
5
+ describe("IncidentMetricTypeUtil", () => {
6
+ describe("getAggregationTypeByIncidentMetricType", () => {
7
+ test.each([
8
+ [IncidentMetricType.IncidentCount, AggregationType.Sum],
9
+ [IncidentMetricType.SeverityChange, AggregationType.Sum],
10
+ [IncidentMetricType.TimeToAcknowledge, AggregationType.Avg],
11
+ [IncidentMetricType.TimeToResolve, AggregationType.Avg],
12
+ [IncidentMetricType.IncidentDuration, AggregationType.Avg],
13
+ [IncidentMetricType.TimeInState, AggregationType.Avg],
14
+ [IncidentMetricType.PostmortemCompletionTime, AggregationType.Avg],
15
+ ])(
16
+ "%s aggregates as %s",
17
+ (metricType: IncidentMetricType, expected: AggregationType) => {
18
+ expect(
19
+ IncidentMetricTypeUtil.getAggregationTypeByIncidentMetricType(
20
+ metricType,
21
+ ),
22
+ ).toBe(expected);
23
+ },
24
+ );
25
+
26
+ test("throws for the unhandled TimeToRootCausePosted metric type", () => {
27
+ /*
28
+ * TimeToRootCausePosted is written directly by the investigation
29
+ * runner and is intentionally not part of the aggregation switch.
30
+ */
31
+ expect(() => {
32
+ return IncidentMetricTypeUtil.getAggregationTypeByIncidentMetricType(
33
+ IncidentMetricType.TimeToRootCausePosted,
34
+ );
35
+ }).toThrow("Invalid IncidentMetricType value");
36
+ });
37
+
38
+ test("throws for an unknown metric type", () => {
39
+ expect(() => {
40
+ return IncidentMetricTypeUtil.getAggregationTypeByIncidentMetricType(
41
+ "not-a-real-metric" as IncidentMetricType,
42
+ );
43
+ }).toThrow("Invalid IncidentMetricType value");
44
+ });
45
+ });
46
+
47
+ describe("getAllIncidentMetricTypes", () => {
48
+ test("returns the four core metric types", () => {
49
+ expect(IncidentMetricTypeUtil.getAllIncidentMetricTypes()).toEqual([
50
+ IncidentMetricType.IncidentCount,
51
+ IncidentMetricType.TimeToAcknowledge,
52
+ IncidentMetricType.TimeToResolve,
53
+ IncidentMetricType.IncidentDuration,
54
+ ]);
55
+ });
56
+
57
+ test("every returned core type resolves to a valid aggregation type", () => {
58
+ for (const metricType of IncidentMetricTypeUtil.getAllIncidentMetricTypes()) {
59
+ expect(() => {
60
+ return IncidentMetricTypeUtil.getAggregationTypeByIncidentMetricType(
61
+ metricType,
62
+ );
63
+ }).not.toThrow();
64
+ }
65
+ });
66
+ });
67
+
68
+ describe("getTitleByIncidentMetricType", () => {
69
+ test.each([
70
+ [IncidentMetricType.IncidentCount, "Incident Count"],
71
+ [IncidentMetricType.TimeToAcknowledge, "Time to Acknowledge"],
72
+ [IncidentMetricType.TimeToResolve, "Time to Resolve"],
73
+ [IncidentMetricType.IncidentDuration, "Incident Duration"],
74
+ [IncidentMetricType.TimeInState, "Time in State"],
75
+ [IncidentMetricType.SeverityChange, "Severity Changes"],
76
+ [
77
+ IncidentMetricType.PostmortemCompletionTime,
78
+ "Postmortem Completion Time",
79
+ ],
80
+ ])("%s -> %s", (metricType: IncidentMetricType, expected: string) => {
81
+ expect(
82
+ IncidentMetricTypeUtil.getTitleByIncidentMetricType(metricType),
83
+ ).toBe(expected);
84
+ });
85
+
86
+ test("returns empty string for an unknown metric type", () => {
87
+ expect(
88
+ IncidentMetricTypeUtil.getTitleByIncidentMetricType(
89
+ "unknown" as IncidentMetricType,
90
+ ),
91
+ ).toBe("");
92
+ });
93
+ });
94
+
95
+ describe("getDescriptionByIncidentMetricType", () => {
96
+ test.each([
97
+ IncidentMetricType.IncidentCount,
98
+ IncidentMetricType.TimeToAcknowledge,
99
+ IncidentMetricType.TimeToResolve,
100
+ IncidentMetricType.IncidentDuration,
101
+ IncidentMetricType.TimeInState,
102
+ IncidentMetricType.SeverityChange,
103
+ IncidentMetricType.PostmortemCompletionTime,
104
+ ])("%s returns a non-empty sentence", (metricType: IncidentMetricType) => {
105
+ const description: string =
106
+ IncidentMetricTypeUtil.getDescriptionByIncidentMetricType(metricType);
107
+ expect(description.length).toBeGreaterThan(0);
108
+ expect(description.endsWith(".")).toBe(true);
109
+ });
110
+
111
+ test("returns empty string for an unknown metric type", () => {
112
+ expect(
113
+ IncidentMetricTypeUtil.getDescriptionByIncidentMetricType(
114
+ "unknown" as IncidentMetricType,
115
+ ),
116
+ ).toBe("");
117
+ });
118
+ });
119
+
120
+ describe("getLegendByIncidentMetricType", () => {
121
+ test.each([
122
+ [IncidentMetricType.IncidentCount, "Incidents"],
123
+ [IncidentMetricType.TimeToAcknowledge, "Time to Acknowledge"],
124
+ [IncidentMetricType.TimeToResolve, "Time to Resolve"],
125
+ [IncidentMetricType.IncidentDuration, "Duration"],
126
+ [IncidentMetricType.TimeInState, "Time in State"],
127
+ [IncidentMetricType.SeverityChange, "Severity Changes"],
128
+ [IncidentMetricType.PostmortemCompletionTime, "Postmortem Time"],
129
+ ])("%s -> %s", (metricType: IncidentMetricType, expected: string) => {
130
+ expect(
131
+ IncidentMetricTypeUtil.getLegendByIncidentMetricType(metricType),
132
+ ).toBe(expected);
133
+ });
134
+
135
+ test("returns empty string for an unknown metric type", () => {
136
+ expect(
137
+ IncidentMetricTypeUtil.getLegendByIncidentMetricType(
138
+ "unknown" as IncidentMetricType,
139
+ ),
140
+ ).toBe("");
141
+ });
142
+ });
143
+
144
+ describe("getLegendUnitByIncidentMetricType", () => {
145
+ test.each([
146
+ IncidentMetricType.IncidentCount,
147
+ IncidentMetricType.SeverityChange,
148
+ ])("%s has no unit", (metricType: IncidentMetricType) => {
149
+ expect(
150
+ IncidentMetricTypeUtil.getLegendUnitByIncidentMetricType(metricType),
151
+ ).toBe("");
152
+ });
153
+
154
+ test.each([
155
+ IncidentMetricType.TimeToAcknowledge,
156
+ IncidentMetricType.TimeToResolve,
157
+ IncidentMetricType.IncidentDuration,
158
+ IncidentMetricType.TimeInState,
159
+ IncidentMetricType.PostmortemCompletionTime,
160
+ ])("%s is measured in seconds", (metricType: IncidentMetricType) => {
161
+ expect(
162
+ IncidentMetricTypeUtil.getLegendUnitByIncidentMetricType(metricType),
163
+ ).toBe("s");
164
+ });
165
+
166
+ test("returns empty string for an unknown metric type", () => {
167
+ expect(
168
+ IncidentMetricTypeUtil.getLegendUnitByIncidentMetricType(
169
+ "unknown" as IncidentMetricType,
170
+ ),
171
+ ).toBe("");
172
+ });
173
+ });
174
+ });
@@ -0,0 +1,145 @@
1
+ import MonitorProbeSelectionUtil, {
2
+ ProbeSelectionCandidate,
3
+ } from "../../../Utils/Monitor/MonitorProbeSelectionUtil";
4
+ import { describe, expect, it } from "@jest/globals";
5
+
6
+ /*
7
+ * The monitor create form pre-selects the probe picker with the set the server
8
+ * would have attached on its own (MonitorService.addDefaultProbesToMonitor).
9
+ * If these two ever disagree, opening the form and pressing Create silently
10
+ * changes which probes watch the monitor - so the rule is pinned here:
11
+ *
12
+ * - only probes that opted in via shouldAutoEnableProbeOnNewMonitors,
13
+ * - minus every global probe when the project turned global probes off,
14
+ * - deduplicated, because MonitorProbe has no unique index and the second
15
+ * insert is rejected with "Probe is already added to this monitor."
16
+ */
17
+
18
+ const GLOBAL_PROBE: ProbeSelectionCandidate = {
19
+ id: "11111111-1111-4111-8111-111111111111",
20
+ isGlobalProbe: true,
21
+ shouldAutoEnableProbeOnNewMonitors: true,
22
+ };
23
+
24
+ const CUSTOM_PROBE: ProbeSelectionCandidate = {
25
+ id: "22222222-2222-4222-8222-222222222222",
26
+ isGlobalProbe: false,
27
+ shouldAutoEnableProbeOnNewMonitors: true,
28
+ };
29
+
30
+ const OPTED_OUT_CUSTOM_PROBE: ProbeSelectionCandidate = {
31
+ id: "33333333-3333-4333-8333-333333333333",
32
+ isGlobalProbe: false,
33
+ shouldAutoEnableProbeOnNewMonitors: false,
34
+ };
35
+
36
+ const OPTED_OUT_GLOBAL_PROBE: ProbeSelectionCandidate = {
37
+ id: "44444444-4444-4444-8444-444444444444",
38
+ isGlobalProbe: true,
39
+ shouldAutoEnableProbeOnNewMonitors: false,
40
+ };
41
+
42
+ describe("MonitorProbeSelectionUtil.getDefaultSelectedProbeIds", () => {
43
+ it("selects every probe flagged to auto-enable on new monitors", () => {
44
+ expect(
45
+ MonitorProbeSelectionUtil.getDefaultSelectedProbeIds({
46
+ probes: [GLOBAL_PROBE, CUSTOM_PROBE],
47
+ }),
48
+ ).toEqual([GLOBAL_PROBE.id, CUSTOM_PROBE.id]);
49
+ });
50
+
51
+ it("leaves out probes that did not opt in, global or custom", () => {
52
+ expect(
53
+ MonitorProbeSelectionUtil.getDefaultSelectedProbeIds({
54
+ probes: [
55
+ GLOBAL_PROBE,
56
+ OPTED_OUT_GLOBAL_PROBE,
57
+ CUSTOM_PROBE,
58
+ OPTED_OUT_CUSTOM_PROBE,
59
+ ],
60
+ }),
61
+ ).toEqual([GLOBAL_PROBE.id, CUSTOM_PROBE.id]);
62
+ });
63
+
64
+ it("drops global probes when the project disabled them for new monitors", () => {
65
+ expect(
66
+ MonitorProbeSelectionUtil.getDefaultSelectedProbeIds({
67
+ probes: [GLOBAL_PROBE, CUSTOM_PROBE],
68
+ doNotAddGlobalProbesByDefaultOnNewMonitors: true,
69
+ }),
70
+ ).toEqual([CUSTOM_PROBE.id]);
71
+ });
72
+
73
+ it("keeps global probes when the project setting is false or unset", () => {
74
+ expect(
75
+ MonitorProbeSelectionUtil.getDefaultSelectedProbeIds({
76
+ probes: [GLOBAL_PROBE],
77
+ doNotAddGlobalProbesByDefaultOnNewMonitors: false,
78
+ }),
79
+ ).toEqual([GLOBAL_PROBE.id]);
80
+
81
+ expect(
82
+ MonitorProbeSelectionUtil.getDefaultSelectedProbeIds({
83
+ probes: [GLOBAL_PROBE],
84
+ doNotAddGlobalProbesByDefaultOnNewMonitors: undefined,
85
+ }),
86
+ ).toEqual([GLOBAL_PROBE.id]);
87
+ });
88
+
89
+ it("returns an empty list when nothing opted in", () => {
90
+ expect(
91
+ MonitorProbeSelectionUtil.getDefaultSelectedProbeIds({
92
+ probes: [OPTED_OUT_GLOBAL_PROBE, OPTED_OUT_CUSTOM_PROBE],
93
+ }),
94
+ ).toEqual([]);
95
+ });
96
+
97
+ it("returns an empty list for an empty probe list", () => {
98
+ expect(
99
+ MonitorProbeSelectionUtil.getDefaultSelectedProbeIds({
100
+ probes: [],
101
+ }),
102
+ ).toEqual([]);
103
+ });
104
+
105
+ it("deduplicates a probe that appears in more than one source list", () => {
106
+ expect(
107
+ MonitorProbeSelectionUtil.getDefaultSelectedProbeIds({
108
+ probes: [GLOBAL_PROBE, { ...GLOBAL_PROBE }, CUSTOM_PROBE],
109
+ }),
110
+ ).toEqual([GLOBAL_PROBE.id, CUSTOM_PROBE.id]);
111
+ });
112
+
113
+ it("ignores probes with no id rather than sending an empty id to the server", () => {
114
+ expect(
115
+ MonitorProbeSelectionUtil.getDefaultSelectedProbeIds({
116
+ probes: [
117
+ {
118
+ id: "",
119
+ isGlobalProbe: false,
120
+ shouldAutoEnableProbeOnNewMonitors: true,
121
+ },
122
+ CUSTOM_PROBE,
123
+ ],
124
+ }),
125
+ ).toEqual([CUSTOM_PROBE.id]);
126
+ });
127
+ });
128
+
129
+ describe("MonitorProbeSelectionUtil.dedupeProbeIds", () => {
130
+ it("keeps the first occurrence and preserves order", () => {
131
+ expect(
132
+ MonitorProbeSelectionUtil.dedupeProbeIds(["a", "b", "a", "c", "b"]),
133
+ ).toEqual(["a", "b", "c"]);
134
+ });
135
+
136
+ it("drops empty ids", () => {
137
+ expect(MonitorProbeSelectionUtil.dedupeProbeIds(["", "a", ""])).toEqual([
138
+ "a",
139
+ ]);
140
+ });
141
+
142
+ it("handles an empty list", () => {
143
+ expect(MonitorProbeSelectionUtil.dedupeProbeIds([])).toEqual([]);
144
+ });
145
+ });