@oneuptime/common 14.0.5 → 14.0.7

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 (192) hide show
  1. package/Models/DatabaseModels/InventoryItem.ts +23 -12
  2. package/Server/Infrastructure/Postgres/SchemaMigrations/1795200000000-AddInventoryItemProjectEntityKeyIndex.ts +217 -0
  3. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
  4. package/Server/Services/AlertService.ts +32 -36
  5. package/Server/Services/IncidentAlertService.ts +609 -2
  6. package/Server/Services/IncidentService.ts +134 -44
  7. package/Server/Services/InventoryItemService.ts +38 -0
  8. package/Server/Services/IoTDeviceCredentialService.ts +13 -4
  9. package/Server/Services/MetricService.ts +1 -1
  10. package/Server/Services/ScheduledMaintenanceService.ts +50 -46
  11. package/Server/Services/UserNotificationRuleService.ts +55 -53
  12. package/Server/Services/WorkspaceNotificationSummaryService.ts +38 -20
  13. package/Server/Types/Database/QueryHelper.ts +54 -1
  14. package/Server/Utils/AI/Toolbox/MetricTools.ts +13 -2
  15. package/Server/Utils/AI/Toolbox/ResourceTools.ts +12 -1
  16. package/Server/Utils/AffectedResources/LinkedAffectedResources.ts +576 -0
  17. package/Server/Utils/Alert/AlertStateChangeAuthorization.ts +129 -0
  18. package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +1 -1
  19. package/Server/Utils/Inventory/InventoryOverviewAggregation.ts +111 -0
  20. package/Server/Utils/Monitor/Criteria/CompareCriteria.ts +33 -0
  21. package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +4 -3
  22. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +3 -1
  23. package/Server/Utils/Response.ts +31 -0
  24. package/Server/Utils/Topology/TopologyConcurrencyLimiter.ts +229 -0
  25. package/Server/Utils/Topology/TopologyQueries.ts +1350 -0
  26. package/Server/Utils/Topology/TopologyRequest.ts +463 -0
  27. package/Server/Utils/Topology/TopologyResponseCache.ts +183 -0
  28. package/Server/Utils/Topology/TopologySql.ts +1024 -0
  29. package/Tests/App/Dashboard/AcknowledgeAlertsOnDeclare.test.ts +369 -0
  30. package/Tests/App/Dashboard/AffectedResourcesSlo.test.tsx +1 -1
  31. package/Tests/App/Dashboard/AffectedResourcesSummaryStep.test.tsx +510 -123
  32. package/Tests/App/Dashboard/AlertsTableAffectedResources.test.tsx +490 -0
  33. package/Tests/App/Dashboard/ChangeAlertStateDeclareIncident.test.tsx +886 -0
  34. package/Tests/App/Dashboard/CreateReviewStepRoleSummary.test.tsx +1250 -0
  35. package/Tests/App/Dashboard/CreateReviewStepSelectionSummary.test.tsx +738 -0
  36. package/Tests/App/Dashboard/DeclareIncidentFromAlert.test.ts +417 -0
  37. package/Tests/App/Dashboard/EntityDetailPanel.test.tsx +1948 -229
  38. package/Tests/App/Dashboard/EntityDetailPanelLifecycle.test.tsx +493 -0
  39. package/Tests/App/Dashboard/EntityDetailPanelNavigation.test.tsx +439 -0
  40. package/Tests/App/Dashboard/EventOverviewPages.test.tsx +292 -1
  41. package/Tests/App/Dashboard/EventStatusPanel.test.tsx +798 -2
  42. package/Tests/App/Dashboard/FetchStateAndSeverityElements.test.tsx +669 -0
  43. package/Tests/App/Dashboard/FetchUsers.test.tsx +230 -0
  44. package/Tests/App/Dashboard/IncidentCreateAcknowledgeAlerts.test.tsx +1611 -0
  45. package/Tests/App/Dashboard/InfrastructureExplorer.test.tsx +1365 -57
  46. package/Tests/App/Dashboard/InfrastructureGraph.test.tsx +964 -23
  47. package/Tests/App/Dashboard/InventoryRelationships.test.tsx +885 -0
  48. package/Tests/App/Dashboard/NetworkTopologyExplorerSearch.test.tsx +1267 -0
  49. package/Tests/App/Dashboard/ResourceOverviewTileTooltips.test.tsx +4 -1
  50. package/Tests/App/Dashboard/RumOverviewPage.test.tsx +127 -0
  51. package/Tests/App/Dashboard/ServiceMapDatabaseLink.test.tsx +201 -15
  52. package/Tests/App/Dashboard/ServiceMapGraph.test.tsx +508 -36
  53. package/Tests/App/Dashboard/SiteSearchBox.test.tsx +2110 -0
  54. package/Tests/App/Dashboard/TelemetryMetricsSignals.test.ts +168 -8
  55. package/Tests/App/Dashboard/TopologyDataLifecycle.test.tsx +583 -0
  56. package/Tests/App/Dashboard/TopologyDataLoading.test.tsx +982 -218
  57. package/Tests/App/Dashboard/TopologyPageNavigation.test.tsx +818 -114
  58. package/Tests/Server/Infrastructure/Postgres/RenameInventoryItemMigration.test.ts +90 -21
  59. package/Tests/Server/Services/DomainService.test.ts +525 -0
  60. package/Tests/Server/Services/IncidentAlertAcknowledgeOnDeclare.test.ts +2630 -0
  61. package/Tests/Server/Services/IncidentAlertPostgres.test.ts +1625 -1
  62. package/Tests/Server/Services/IncidentDeclareAcknowledgeAlerts.test.ts +2189 -0
  63. package/Tests/Server/Services/InventoryItemServiceOverviewCounts.test.ts +345 -0
  64. package/Tests/Server/Services/InventoryOverviewPostgres.test.ts +254 -0
  65. package/Tests/Server/Services/IoTDeviceCredentialService.test.ts +819 -0
  66. package/Tests/Server/Services/LinkedAffectedResourcesCreatedFeed.test.ts +420 -0
  67. package/Tests/Server/Services/MetricServiceAggregate.test.ts +12 -0
  68. package/Tests/Server/Services/SavedViewDefaults.test.ts +325 -0
  69. package/Tests/Server/Services/SloAffectedResourceCreatedFeed.test.ts +92 -36
  70. package/Tests/Server/Services/StatusPageCertificateService.test.ts +169 -0
  71. package/Tests/Server/Services/TeamComplianceSettingService.test.ts +191 -0
  72. package/Tests/Server/Types/Database/QueryHelperSearchAllWords.test.ts +651 -0
  73. package/Tests/Server/Utils/AI/AlertWriteTools.test.ts +256 -0
  74. package/Tests/Server/Utils/AI/IncidentWriteTools.test.ts +482 -0
  75. package/Tests/Server/Utils/AffectedResources/LinkedAffectedResources.test.ts +858 -0
  76. package/Tests/Server/Utils/Alert/AlertStateChangeAuthorization.test.ts +1172 -0
  77. package/Tests/Server/Utils/Monitor/Criteria/CompareCriteria.test.ts +54 -0
  78. package/Tests/Server/Utils/Monitor/MetricDecimalThreshold.test.ts +417 -0
  79. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorAffectedResourceEdgeCases.test.ts +96 -0
  80. package/Tests/Server/Utils/Topology/InventoryItemEntityKeyIndexMigration.test.ts +386 -0
  81. package/Tests/Server/Utils/Topology/InventoryItemEntityKeyIndexMigrationPostgres.test.ts +430 -0
  82. package/Tests/Server/Utils/Topology/TopologyActivityParityPostgres.test.ts +279 -0
  83. package/Tests/Server/Utils/Topology/TopologyConcurrencyLimiter.test.ts +534 -0
  84. package/Tests/Server/Utils/Topology/TopologyEstateGenerator.ts +624 -0
  85. package/Tests/Server/Utils/Topology/TopologyJsonResponse.test.ts +90 -0
  86. package/Tests/Server/Utils/Topology/TopologyQueries.test.ts +1634 -0
  87. package/Tests/Server/Utils/Topology/TopologyQueriesDifferentialPostgres.test.ts +683 -0
  88. package/Tests/Server/Utils/Topology/TopologyQueriesPostgres.test.ts +2624 -0
  89. package/Tests/Server/Utils/Topology/TopologyReference.ts +1018 -0
  90. package/Tests/Server/Utils/Topology/TopologyRequest.test.ts +643 -0
  91. package/Tests/Server/Utils/Topology/TopologyResponseCache.test.ts +305 -0
  92. package/Tests/Server/Utils/Topology/TopologySql.test.ts +973 -0
  93. package/Tests/Types/BaseDatabase/AggregationType.test.ts +5 -1
  94. package/Tests/Types/Dashboard/KubernetesDashboardTemplate.test.ts +184 -0
  95. package/Tests/Types/Dashboard/KubernetesDashboardTemplates.test.ts +107 -0
  96. package/Tests/Types/Monitor/KubernetesAlertTemplates.test.ts +8 -14
  97. package/Tests/Types/Monitor/KubernetesMetricCatalog.test.ts +4 -3
  98. package/Tests/Types/Monitor/Utils/KubernetesAgentEmittedMetrics.ts +27 -6
  99. package/Tests/Types/Topology/TopologyTypeRules.test.ts +176 -0
  100. package/Tests/UI/Rum/ChunkLoader.test.ts +76 -0
  101. package/Tests/UI/Rum/ReplayRailDetail.test.tsx +53 -0
  102. package/Tests/UI/Rum/ReplaySignals.test.ts +54 -0
  103. package/Tests/Utils/Dashboard/KubernetesClusterCpuTileRepair.test.ts +370 -0
  104. package/Tests/Utils/Incident/IncidentFromAlerts.test.ts +302 -0
  105. package/Tests/Utils/Rules/MonitorTypeRuleCriteria.test.ts +181 -0
  106. package/Types/BaseDatabase/AggregationType.ts +7 -0
  107. package/Types/Dashboard/DashboardTemplates.ts +20 -7
  108. package/Types/Incident/IncidentAlertLink.ts +19 -0
  109. package/Types/Rum/SessionReplayCustomEvents.ts +29 -3
  110. package/Types/Rum/WebVitals.ts +13 -0
  111. package/Types/Telemetry/InventoryLiveness.ts +26 -0
  112. package/Types/Topology/TopologyApi.ts +428 -0
  113. package/Types/Topology/TopologyTypeRules.ts +191 -0
  114. package/UI/Components/Input/Input.tsx +11 -0
  115. package/Utils/Dashboard/KubernetesClusterCpuTileRepair.ts +147 -0
  116. package/Utils/Incident/IncidentFromAlerts.ts +78 -0
  117. package/build/dist/Models/DatabaseModels/InventoryItem.js +20 -11
  118. package/build/dist/Models/DatabaseModels/InventoryItem.js.map +1 -1
  119. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795200000000-AddInventoryItemProjectEntityKeyIndex.js +165 -0
  120. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795200000000-AddInventoryItemProjectEntityKeyIndex.js.map +1 -0
  121. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
  122. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  123. package/build/dist/Server/Services/AlertService.js +21 -33
  124. package/build/dist/Server/Services/AlertService.js.map +1 -1
  125. package/build/dist/Server/Services/IncidentAlertService.js +457 -3
  126. package/build/dist/Server/Services/IncidentAlertService.js.map +1 -1
  127. package/build/dist/Server/Services/IncidentService.js +96 -42
  128. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  129. package/build/dist/Server/Services/InventoryItemService.js +31 -0
  130. package/build/dist/Server/Services/InventoryItemService.js.map +1 -1
  131. package/build/dist/Server/Services/IoTDeviceCredentialService.js +12 -5
  132. package/build/dist/Server/Services/IoTDeviceCredentialService.js.map +1 -1
  133. package/build/dist/Server/Services/MetricService.js +1 -1
  134. package/build/dist/Server/Services/ScheduledMaintenanceService.js +37 -40
  135. package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
  136. package/build/dist/Server/Services/UserNotificationRuleService.js +55 -51
  137. package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
  138. package/build/dist/Server/Services/WorkspaceNotificationSummaryService.js +42 -25
  139. package/build/dist/Server/Services/WorkspaceNotificationSummaryService.js.map +1 -1
  140. package/build/dist/Server/Types/Database/QueryHelper.js +47 -1
  141. package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
  142. package/build/dist/Server/Utils/AI/Toolbox/MetricTools.js +13 -2
  143. package/build/dist/Server/Utils/AI/Toolbox/MetricTools.js.map +1 -1
  144. package/build/dist/Server/Utils/AI/Toolbox/ResourceTools.js +12 -1
  145. package/build/dist/Server/Utils/AI/Toolbox/ResourceTools.js.map +1 -1
  146. package/build/dist/Server/Utils/AffectedResources/LinkedAffectedResources.js +405 -0
  147. package/build/dist/Server/Utils/AffectedResources/LinkedAffectedResources.js.map +1 -0
  148. package/build/dist/Server/Utils/Alert/AlertStateChangeAuthorization.js +112 -0
  149. package/build/dist/Server/Utils/Alert/AlertStateChangeAuthorization.js.map +1 -0
  150. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +1 -1
  151. package/build/dist/Server/Utils/Inventory/InventoryOverviewAggregation.js +81 -0
  152. package/build/dist/Server/Utils/Inventory/InventoryOverviewAggregation.js.map +1 -0
  153. package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js +31 -0
  154. package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js.map +1 -1
  155. package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +1 -1
  156. package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js.map +1 -1
  157. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +1 -1
  158. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  159. package/build/dist/Server/Utils/Response.js +30 -0
  160. package/build/dist/Server/Utils/Response.js.map +1 -1
  161. package/build/dist/Server/Utils/Topology/TopologyConcurrencyLimiter.js +178 -0
  162. package/build/dist/Server/Utils/Topology/TopologyConcurrencyLimiter.js.map +1 -0
  163. package/build/dist/Server/Utils/Topology/TopologyQueries.js +884 -0
  164. package/build/dist/Server/Utils/Topology/TopologyQueries.js.map +1 -0
  165. package/build/dist/Server/Utils/Topology/TopologyRequest.js +275 -0
  166. package/build/dist/Server/Utils/Topology/TopologyRequest.js.map +1 -0
  167. package/build/dist/Server/Utils/Topology/TopologyResponseCache.js +146 -0
  168. package/build/dist/Server/Utils/Topology/TopologyResponseCache.js.map +1 -0
  169. package/build/dist/Server/Utils/Topology/TopologySql.js +688 -0
  170. package/build/dist/Server/Utils/Topology/TopologySql.js.map +1 -0
  171. package/build/dist/Types/BaseDatabase/AggregationType.js +7 -0
  172. package/build/dist/Types/BaseDatabase/AggregationType.js.map +1 -1
  173. package/build/dist/Types/Dashboard/DashboardTemplates.js +20 -7
  174. package/build/dist/Types/Dashboard/DashboardTemplates.js.map +1 -1
  175. package/build/dist/Types/Incident/IncidentAlertLink.js +17 -0
  176. package/build/dist/Types/Incident/IncidentAlertLink.js.map +1 -1
  177. package/build/dist/Types/Rum/SessionReplayCustomEvents.js.map +1 -1
  178. package/build/dist/Types/Rum/WebVitals.js +12 -0
  179. package/build/dist/Types/Rum/WebVitals.js.map +1 -1
  180. package/build/dist/Types/Telemetry/InventoryLiveness.js +23 -0
  181. package/build/dist/Types/Telemetry/InventoryLiveness.js.map +1 -1
  182. package/build/dist/Types/Topology/TopologyApi.js +55 -0
  183. package/build/dist/Types/Topology/TopologyApi.js.map +1 -0
  184. package/build/dist/Types/Topology/TopologyTypeRules.js +165 -0
  185. package/build/dist/Types/Topology/TopologyTypeRules.js.map +1 -0
  186. package/build/dist/UI/Components/Input/Input.js +1 -1
  187. package/build/dist/UI/Components/Input/Input.js.map +1 -1
  188. package/build/dist/Utils/Dashboard/KubernetesClusterCpuTileRepair.js +110 -0
  189. package/build/dist/Utils/Dashboard/KubernetesClusterCpuTileRepair.js.map +1 -0
  190. package/build/dist/Utils/Incident/IncidentFromAlerts.js +38 -0
  191. package/build/dist/Utils/Incident/IncidentFromAlerts.js.map +1 -1
  192. package/package.json +1 -1
@@ -0,0 +1,369 @@
1
+ import {
2
+ afterEach,
3
+ beforeEach,
4
+ describe,
5
+ expect,
6
+ jest,
7
+ test,
8
+ } from "@jest/globals";
9
+ import {
10
+ ACKNOWLEDGED_ALERTS_NO_ON_CALL_NOTE,
11
+ getAcknowledgeAlertsDescription,
12
+ getAcknowledgeAlertsGate,
13
+ getAcknowledgeAlertsTitle,
14
+ getAlertsKeepEscalatingNote,
15
+ } from "../../../../App/FeatureSet/Dashboard/src/Components/Incident/AcknowledgeAlertsOnDeclare";
16
+ import Alert from "../../../Models/DatabaseModels/Alert";
17
+ import AlertStateTimeline from "../../../Models/DatabaseModels/AlertStateTimeline";
18
+ import Permission from "../../../Types/Permission";
19
+ import PermissionUtil from "../../../UI/Utils/Permission";
20
+ import PermissionGate, {
21
+ ModelAction,
22
+ PermissionGateResult,
23
+ } from "../../../UI/Utils/PermissionGate";
24
+ import UserUtil from "../../../UI/Utils/User";
25
+ import { AlertsToAcknowledge } from "../../../Utils/Incident/IncidentFromAlerts";
26
+
27
+ /*
28
+ * The wording and the permission gate of the "acknowledge the alerts" box on
29
+ * the create-incident page. The box's label and description say exactly
30
+ * which alerts are acknowledged, the note says which alerts keep escalating
31
+ * when the box is not ticked, and the gate asks for the same two permissions
32
+ * the server checks before it acknowledges an alert: create a state timeline
33
+ * row, then update the alert.
34
+ */
35
+
36
+ const TIMELINE_CREATE_REASON: string =
37
+ "You do not have permission to acknowledge this alert. You need one of these permissions: Project Owner, Project Admin, Project Member, Alert Admin, Alert Member, Create Alert State Timeline.";
38
+
39
+ const ALERT_UPDATE_REASON: string =
40
+ "You do not have permission to acknowledge this alert. You need one of these permissions: Project Owner, Project Admin, Project Member, Alert Admin, Alert Member, Edit Alert.";
41
+
42
+ const SINGULAR_DESCRIPTION: string =
43
+ "It is acknowledged as you when the incident is declared, and its own on-call escalation stops within a minute. Pages that already went out are not recalled.";
44
+
45
+ const PLURAL_DESCRIPTION: string =
46
+ "They are acknowledged as you when the incident is declared, and their own on-call escalation stops within a minute. Pages that already went out are not recalled.";
47
+
48
+ type ToAcknowledgeFunction = (
49
+ count: number,
50
+ alreadyAcknowledgedCount?: number,
51
+ ) => AlertsToAcknowledge;
52
+
53
+ const toAcknowledge: ToAcknowledgeFunction = (
54
+ count: number,
55
+ alreadyAcknowledgedCount?: number,
56
+ ): AlertsToAcknowledge => {
57
+ return {
58
+ alertIds: Array.from(
59
+ { length: count },
60
+ (_value: unknown, index: number): string => {
61
+ return `alert-${index + 1}`;
62
+ },
63
+ ),
64
+ alreadyAcknowledgedCount: alreadyAcknowledgedCount || 0,
65
+ };
66
+ };
67
+
68
+ describe("AcknowledgeAlertsOnDeclare", () => {
69
+ describe("getAcknowledgeAlertsTitle", () => {
70
+ test("names the one alert when it is the only alert", () => {
71
+ expect(getAcknowledgeAlertsTitle(toAcknowledge(1), 1)).toBe(
72
+ "Acknowledge this alert to stop its escalation",
73
+ );
74
+ });
75
+
76
+ test("says 'the 1 alert that is not acknowledged yet' when others are acknowledged", () => {
77
+ expect(getAcknowledgeAlertsTitle(toAcknowledge(1, 2), 3)).toBe(
78
+ "Acknowledge the 1 alert that is not acknowledged yet, to stop its escalation",
79
+ );
80
+ });
81
+
82
+ test("names every alert when none is acknowledged yet", () => {
83
+ expect(getAcknowledgeAlertsTitle(toAcknowledge(3), 3)).toBe(
84
+ "Acknowledge these 3 alerts to stop their escalation",
85
+ );
86
+ });
87
+
88
+ test("names only the alerts not acknowledged yet when some are", () => {
89
+ expect(getAcknowledgeAlertsTitle(toAcknowledge(2, 3), 5)).toBe(
90
+ "Acknowledge the 2 alerts that are not acknowledged yet, to stop their escalation",
91
+ );
92
+ });
93
+
94
+ test("treats more alerts to acknowledge than alerts listed as every alert", () => {
95
+ expect(getAcknowledgeAlertsTitle(toAcknowledge(2), 1)).toBe(
96
+ "Acknowledge these 2 alerts to stop their escalation",
97
+ );
98
+ expect(getAcknowledgeAlertsTitle(toAcknowledge(1), 0)).toBe(
99
+ "Acknowledge this alert to stop its escalation",
100
+ );
101
+ });
102
+ });
103
+
104
+ describe("getAcknowledgeAlertsDescription", () => {
105
+ test("uses the singular wording for one alert", () => {
106
+ expect(getAcknowledgeAlertsDescription(toAcknowledge(1), undefined)).toBe(
107
+ SINGULAR_DESCRIPTION,
108
+ );
109
+ });
110
+
111
+ test("uses the plural wording for several alerts", () => {
112
+ expect(getAcknowledgeAlertsDescription(toAcknowledge(4), undefined)).toBe(
113
+ PLURAL_DESCRIPTION,
114
+ );
115
+ });
116
+
117
+ test("says one alert is left as it is", () => {
118
+ expect(
119
+ getAcknowledgeAlertsDescription(toAcknowledge(2, 1), undefined),
120
+ ).toBe(
121
+ `${PLURAL_DESCRIPTION} 1 alert is already acknowledged or resolved and is left as it is.`,
122
+ );
123
+ });
124
+
125
+ test("says how many alerts are left as they are", () => {
126
+ expect(
127
+ getAcknowledgeAlertsDescription(toAcknowledge(1, 3), undefined),
128
+ ).toBe(
129
+ `${SINGULAR_DESCRIPTION} 3 alerts are already acknowledged or resolved and are left as they are.`,
130
+ );
131
+ });
132
+
133
+ test("says nothing about acknowledged alerts when there are none", () => {
134
+ const description: string = getAcknowledgeAlertsDescription(
135
+ toAcknowledge(2, 0),
136
+ undefined,
137
+ );
138
+
139
+ expect(description).toBe(PLURAL_DESCRIPTION);
140
+ expect(description).not.toContain("already acknowledged");
141
+ });
142
+
143
+ test("appends why the box is locked, last", () => {
144
+ expect(
145
+ getAcknowledgeAlertsDescription(
146
+ toAcknowledge(1, 2),
147
+ TIMELINE_CREATE_REASON,
148
+ ),
149
+ ).toBe(
150
+ `${SINGULAR_DESCRIPTION} 2 alerts are already acknowledged or resolved and are left as they are. ${TIMELINE_CREATE_REASON}`,
151
+ );
152
+ expect(
153
+ getAcknowledgeAlertsDescription(toAcknowledge(1), ALERT_UPDATE_REASON),
154
+ ).toBe(`${SINGULAR_DESCRIPTION} ${ALERT_UPDATE_REASON}`);
155
+ });
156
+
157
+ test("ignores an empty reason", () => {
158
+ expect(getAcknowledgeAlertsDescription(toAcknowledge(3), "")).toBe(
159
+ PLURAL_DESCRIPTION,
160
+ );
161
+ });
162
+ });
163
+
164
+ describe("getAlertsKeepEscalatingNote", () => {
165
+ test("the only alert", () => {
166
+ expect(getAlertsKeepEscalatingNote(toAcknowledge(1), 1)).toBe(
167
+ "Declaring the incident does not acknowledge the alert: it keeps escalating until it is acknowledged.",
168
+ );
169
+ });
170
+
171
+ test("every one of several alerts", () => {
172
+ expect(getAlertsKeepEscalatingNote(toAcknowledge(3), 3)).toBe(
173
+ "Declaring the incident does not acknowledge these alerts: they keep escalating until they are acknowledged.",
174
+ );
175
+ });
176
+
177
+ test("the one alert of several that is not acknowledged yet", () => {
178
+ expect(getAlertsKeepEscalatingNote(toAcknowledge(1, 2), 3)).toBe(
179
+ "Declaring the incident does not acknowledge the alert that is not acknowledged yet: it keeps escalating until it is acknowledged.",
180
+ );
181
+ });
182
+
183
+ test("the alerts of several that are not acknowledged yet", () => {
184
+ expect(getAlertsKeepEscalatingNote(toAcknowledge(2, 2), 4)).toBe(
185
+ "Declaring the incident does not acknowledge the 2 alerts that are not acknowledged yet: they keep escalating until they are acknowledged.",
186
+ );
187
+ });
188
+ });
189
+
190
+ describe("ACKNOWLEDGED_ALERTS_NO_ON_CALL_NOTE", () => {
191
+ test("says the alerts' own escalation stops, and what may still page", () => {
192
+ expect(ACKNOWLEDGED_ALERTS_NO_ON_CALL_NOTE).toBe(
193
+ "The alerts it is declared from are acknowledged too, so their own escalation stops. An alert episode they belong to keeps escalating until the episode is acknowledged, and an incident on-call rule, if any, may still page.",
194
+ );
195
+ });
196
+
197
+ /*
198
+ * Acknowledging an alert stops only that alert's escalation: an alert
199
+ * episode escalates on its own, so the note must not promise that
200
+ * nothing pages any more.
201
+ */
202
+ test("carries the alert episode caveat", () => {
203
+ expect(ACKNOWLEDGED_ALERTS_NO_ON_CALL_NOTE).toContain(
204
+ "An alert episode they belong to keeps escalating until the episode is acknowledged",
205
+ );
206
+ });
207
+
208
+ test("claims only the alerts' own escalation stops, and that an incident on-call rule may still page", () => {
209
+ expect(ACKNOWLEDGED_ALERTS_NO_ON_CALL_NOTE).toContain(
210
+ "so their own escalation stops.",
211
+ );
212
+ expect(ACKNOWLEDGED_ALERTS_NO_ON_CALL_NOTE).toContain(
213
+ "an incident on-call rule, if any, may still page.",
214
+ );
215
+ expect(ACKNOWLEDGED_ALERTS_NO_ON_CALL_NOTE).not.toContain("stop paging");
216
+ expect(ACKNOWLEDGED_ALERTS_NO_ON_CALL_NOTE).not.toContain("only an");
217
+ });
218
+ });
219
+
220
+ describe("getAcknowledgeAlertsGate", () => {
221
+ let permissionsForTest: Array<Permission> = [];
222
+
223
+ beforeEach(() => {
224
+ permissionsForTest = [Permission.ProjectMember];
225
+ PermissionGate.clearPermissionPropsCache();
226
+
227
+ jest.spyOn(PermissionUtil, "getAllPermissions").mockImplementation(() => {
228
+ return permissionsForTest;
229
+ });
230
+ jest.spyOn(UserUtil, "isMasterAdmin").mockReturnValue(false);
231
+ });
232
+
233
+ afterEach(() => {
234
+ jest.restoreAllMocks();
235
+ });
236
+
237
+ test.each([
238
+ ["a project owner", [Permission.ProjectOwner]],
239
+ ["a project admin", [Permission.ProjectAdmin]],
240
+ ["a project member", [Permission.ProjectMember]],
241
+ ["an alert admin", [Permission.AlertAdmin]],
242
+ ["an alert member", [Permission.AlertMember]],
243
+ [
244
+ "somebody with both fine-grained permissions",
245
+ [Permission.CreateAlertStateTimeline, Permission.EditAlert],
246
+ ],
247
+ [
248
+ "somebody who may edit all operational resources and create timelines",
249
+ [
250
+ Permission.CreateAlertStateTimeline,
251
+ Permission.EditAllOperationalResources,
252
+ ],
253
+ ],
254
+ ])("allows %s", (_who: string, permissions: Array<Permission>) => {
255
+ permissionsForTest = permissions;
256
+
257
+ expect(getAcknowledgeAlertsGate()).toEqual({ isAllowed: true });
258
+ });
259
+
260
+ test("checks the state timeline create first, then the alert update, both as 'acknowledge this alert'", () => {
261
+ const checkSpy: ReturnType<typeof jest.spyOn> = jest.spyOn(
262
+ PermissionGate,
263
+ "check",
264
+ );
265
+
266
+ expect(getAcknowledgeAlertsGate()).toEqual({ isAllowed: true });
267
+
268
+ expect(checkSpy).toHaveBeenCalledTimes(2);
269
+
270
+ const calls: Array<Array<unknown>> = checkSpy.mock.calls as Array<
271
+ Array<unknown>
272
+ >;
273
+
274
+ expect(calls[0]![0]).toBeInstanceOf(AlertStateTimeline);
275
+ expect(calls[0]![1]).toBe(ModelAction.Create);
276
+ expect(calls[0]![2]).toEqual({
277
+ verb: "acknowledge",
278
+ singularName: "alert",
279
+ });
280
+ expect(calls[1]![0]).toBeInstanceOf(Alert);
281
+ expect(calls[1]![1]).toBe(ModelAction.Update);
282
+ expect(calls[1]![2]).toEqual({
283
+ verb: "acknowledge",
284
+ singularName: "alert",
285
+ });
286
+ });
287
+
288
+ test("stops at the state timeline when that is missing, and names its permissions", () => {
289
+ permissionsForTest = [Permission.Viewer];
290
+ const checkSpy: ReturnType<typeof jest.spyOn> = jest.spyOn(
291
+ PermissionGate,
292
+ "check",
293
+ );
294
+
295
+ const gate: PermissionGateResult = getAcknowledgeAlertsGate();
296
+
297
+ expect(gate).toEqual({
298
+ isAllowed: false,
299
+ disabledReason: TIMELINE_CREATE_REASON,
300
+ });
301
+ expect(checkSpy).toHaveBeenCalledTimes(1);
302
+ expect((checkSpy.mock.calls[0] as Array<unknown>)[0]).toBeInstanceOf(
303
+ AlertStateTimeline,
304
+ );
305
+ });
306
+
307
+ test("names the state timeline even when the alert update is missing too", () => {
308
+ permissionsForTest = [Permission.AlertViewer, Permission.ReadAlert];
309
+
310
+ expect(getAcknowledgeAlertsGate()).toEqual({
311
+ isAllowed: false,
312
+ disabledReason: TIMELINE_CREATE_REASON,
313
+ });
314
+ });
315
+
316
+ test("names the state timeline for somebody who may edit alerts but not add timeline rows", () => {
317
+ permissionsForTest = [Permission.Viewer, Permission.EditAlert];
318
+
319
+ expect(getAcknowledgeAlertsGate()).toEqual({
320
+ isAllowed: false,
321
+ disabledReason: TIMELINE_CREATE_REASON,
322
+ });
323
+ });
324
+
325
+ /*
326
+ * AlertStateTimeline is not an operational resource, so the wildcard
327
+ * does not cover it - on the server either.
328
+ */
329
+ test("does not let the operational-resource wildcards stand in for the timeline permission", () => {
330
+ permissionsForTest = [
331
+ Permission.CreateAllOperationalResources,
332
+ Permission.EditAllOperationalResources,
333
+ ];
334
+
335
+ expect(getAcknowledgeAlertsGate()).toEqual({
336
+ isAllowed: false,
337
+ disabledReason: TIMELINE_CREATE_REASON,
338
+ });
339
+ });
340
+
341
+ test("names the alert update when only that is missing", () => {
342
+ permissionsForTest = [
343
+ Permission.Viewer,
344
+ Permission.CreateAlertStateTimeline,
345
+ ];
346
+
347
+ expect(getAcknowledgeAlertsGate()).toEqual({
348
+ isAllowed: false,
349
+ disabledReason: ALERT_UPDATE_REASON,
350
+ });
351
+ });
352
+
353
+ test("gives no reason while the permission snapshot has not loaded", () => {
354
+ permissionsForTest = [];
355
+
356
+ const gate: PermissionGateResult = getAcknowledgeAlertsGate();
357
+
358
+ expect(gate).toEqual({ isAllowed: false });
359
+ expect(gate.disabledReason).toBeUndefined();
360
+ });
361
+
362
+ test("allows a master admin without any project permission", () => {
363
+ permissionsForTest = [];
364
+ jest.spyOn(UserUtil, "isMasterAdmin").mockReturnValue(true);
365
+
366
+ expect(getAcknowledgeAlertsGate()).toEqual({ isAllowed: true });
367
+ });
368
+ });
369
+ });
@@ -193,7 +193,7 @@ describe("AffectedResourcesDisplay with SLOs", () => {
193
193
  ).toBeInTheDocument();
194
194
  });
195
195
 
196
- test("an alert page that hides monitors still shows the SLO", () => {
196
+ test("a caller that hides monitors still shows the SLO", () => {
197
197
  render(
198
198
  <AffectedResourcesDisplay
199
199
  monitors={[