@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
@@ -22,6 +22,7 @@ import ProjectService from "./ProjectService";
22
22
  import { IsBillingEnabled } from "../EnvironmentConfig";
23
23
  import { applyAlertRelatedRecordPrivacyFilter } from "../Utils/Alert/AlertPrivacyFilter";
24
24
  import { applyIncidentRelatedRecordPrivacyFilter } from "../Utils/Incident/IncidentPrivacyFilter";
25
+ import AlertStateChangeAuthorization from "../Utils/Alert/AlertStateChangeAuthorization";
25
26
  import PostgresErrorTranslator from "../Utils/Database/PostgresErrorTranslator";
26
27
  import ProjectScopedReferenceValidator from "../Utils/Database/ProjectScopedReferenceValidator";
27
28
  import RelationIdUtil from "../Utils/Database/RelationIdUtil";
@@ -36,7 +37,7 @@ import BadDataException from "../../Types/Exception/BadDataException";
36
37
  import ForbiddenException from "../../Types/Exception/ForbiddenException";
37
38
  import NotAuthorizedException from "../../Types/Exception/NotAuthorizedException";
38
39
  import NotFoundException from "../../Types/Exception/NotFoundException";
39
- import { INCIDENT_ALERT_ALREADY_LINKED_MESSAGE, INCIDENT_ALERT_IDS_TO_LINK_KEY, MAX_ALERTS_PER_INCIDENT_LINK_ACTION, } from "../../Types/Incident/IncidentAlertLink";
40
+ import { INCIDENT_ACKNOWLEDGE_ALERTS_TO_LINK_KEY, INCIDENT_ALERT_ALREADY_LINKED_MESSAGE, INCIDENT_ALERT_IDS_TO_LINK_KEY, MAX_ALERTS_PER_INCIDENT_LINK_ACTION, } from "../../Types/Incident/IncidentAlertLink";
40
41
  import ObjectID from "../../Types/ObjectID";
41
42
  /*
42
43
  * The decision half of the linked-alert state sync, shared by the cascade
@@ -86,6 +87,25 @@ export function chooseLinkedAlertTargetState(data) {
86
87
  }
87
88
  return null;
88
89
  }
90
+ /*
91
+ * Alerts acknowledged at once when an incident is declared from many. Each
92
+ * acknowledgement is a full state change (and waits on its Slack / Microsoft
93
+ * Teams post), so one at a time could leave the last of 50 alerts escalating
94
+ * for minutes. Distinct alerts never share a timeline lock.
95
+ */
96
+ const DECLARED_ALERT_ACKNOWLEDGE_CONCURRENCY = 5;
97
+ /*
98
+ * The "why" on an alert acknowledged because an incident was declared from
99
+ * it. It goes to the alert's feed, the alert's Slack / Microsoft Teams
100
+ * channels and its owners' notifications - places that must not name a
101
+ * private incident - so a private incident is not named at all.
102
+ */
103
+ export function getDeclaredAlertAcknowledgementCause(data) {
104
+ if (data.isIncidentPrivate) {
105
+ return "Acknowledged because a private incident was declared from this alert.";
106
+ }
107
+ return `Acknowledged because ${withNumber("Incident", data.incidentNumber)} was declared from this alert.`;
108
+ }
89
109
  /*
90
110
  * miscDataProps key on an IncidentAlert create: the link is one of the links
91
111
  * written while an incident is being declared from alerts. The incident then
@@ -844,6 +864,413 @@ export class Service extends DatabaseService {
844
864
  }
845
865
  return result;
846
866
  }
867
+ /*
868
+ * Reads miscDataProps[INCIDENT_ACKNOWLEDGE_ALERTS_TO_LINK_KEY] on an
869
+ * incident create: whether to acknowledge the alerts the incident is
870
+ * declared from once they are linked, and which. Null when not asked to
871
+ * (absent, null or false). Otherwise the project's Acknowledged alert
872
+ * state and the alerts that are not acknowledged yet - the only ones that
873
+ * will be written, and so the only ones the caller must be allowed to
874
+ * change: an alert that is already acknowledged or resolved is left alone,
875
+ * so being unable to change it must not refuse the declaration.
876
+ *
877
+ * Called from IncidentService's onBeforeCreate after the alert ids were
878
+ * validated - before the incident number is taken - so an impossible
879
+ * request is refused instead of the incident being declared while its
880
+ * alerts keep paging: the project has no Acknowledged alert state, or the
881
+ * caller may not change the state of every alert that would be
882
+ * acknowledged (see AlertStateChangeAuthorization; the acknowledgements
883
+ * themselves are written as root, for exactly these alerts).
884
+ */
885
+ async validateAcknowledgeAlertsForNewIncident(data) {
886
+ var _a, _b;
887
+ if (data.acknowledgeAlerts === undefined ||
888
+ data.acknowledgeAlerts === null ||
889
+ data.acknowledgeAlerts === false) {
890
+ return null;
891
+ }
892
+ if (data.acknowledgeAlerts !== true) {
893
+ throw new BadDataException(`${INCIDENT_ACKNOWLEDGE_ALERTS_TO_LINK_KEY} must be true or false.`);
894
+ }
895
+ if (data.alertIds.length === 0) {
896
+ throw new BadDataException(`${INCIDENT_ACKNOWLEDGE_ALERTS_TO_LINK_KEY} only applies when the incident is declared from alerts: send the alert ids in ${INCIDENT_ALERT_IDS_TO_LINK_KEY}.`);
897
+ }
898
+ if (!data.projectId) {
899
+ throw new BadDataException("projectId is required to acknowledge alerts.");
900
+ }
901
+ const projectId = data.projectId;
902
+ const acknowledgedState = await AlertStateService.findOneBy({
903
+ query: {
904
+ projectId: projectId,
905
+ isAcknowledgedState: true,
906
+ },
907
+ select: {
908
+ _id: true,
909
+ order: true,
910
+ },
911
+ props: {
912
+ isRoot: true,
913
+ },
914
+ });
915
+ if (!acknowledgedState ||
916
+ !acknowledgedState._id ||
917
+ acknowledgedState.order === undefined ||
918
+ acknowledgedState.order === null) {
919
+ throw new BadDataException("This project has no Acknowledged alert state, so the alerts cannot be acknowledged. Declare the incident without acknowledging them, or add an Acknowledged state in the alert settings.");
920
+ }
921
+ const acknowledgedOrder = acknowledgedState.order;
922
+ const alerts = await AlertService.findBy({
923
+ query: {
924
+ _id: QueryHelper.any(data.alertIds),
925
+ projectId: projectId,
926
+ },
927
+ select: {
928
+ _id: true,
929
+ currentAlertState: {
930
+ order: true,
931
+ },
932
+ },
933
+ limit: LIMIT_PER_PROJECT,
934
+ skip: 0,
935
+ props: {
936
+ isRoot: true,
937
+ },
938
+ });
939
+ const stateOrderByAlertId = new Map();
940
+ for (const alert of alerts) {
941
+ if (alert._id) {
942
+ stateOrderByAlertId.set(normalizeId(alert._id), (_b = (_a = alert.currentAlertState) === null || _a === void 0 ? void 0 : _a.order) !== null && _b !== void 0 ? _b : undefined);
943
+ }
944
+ }
945
+ // Same rule as acknowledgeAlertsDeclaredWithIncident: by order.
946
+ const alertIdsToAcknowledge = data.alertIds.filter((alertId) => {
947
+ const key = normalizeId(alertId);
948
+ if (!stateOrderByAlertId.has(key)) {
949
+ return false;
950
+ }
951
+ const order = stateOrderByAlertId.get(key);
952
+ return order === undefined || order < acknowledgedOrder;
953
+ });
954
+ if (alertIdsToAcknowledge.length > 0 &&
955
+ !data.props.isRoot &&
956
+ !data.props.isMasterAdmin) {
957
+ try {
958
+ await AlertStateChangeAuthorization.assertCanChangeStateOfAlerts({
959
+ projectId: projectId,
960
+ alertIds: alertIdsToAcknowledge,
961
+ props: data.props,
962
+ });
963
+ }
964
+ catch (error) {
965
+ // A lapsed session or an unpaid project keeps its own answer.
966
+ if (error instanceof NotAuthorizedException ||
967
+ error instanceof BadDataException) {
968
+ throw new BadDataException("You do not have permission to acknowledge one or more of these alerts. Declare the incident without acknowledging them, or ask a project admin for permission.");
969
+ }
970
+ throw error;
971
+ }
972
+ }
973
+ return {
974
+ acknowledgedAlertStateId: new ObjectID(acknowledgedState._id.toString()),
975
+ alertIdsToAcknowledge: alertIdsToAcknowledge,
976
+ };
977
+ }
978
+ /*
979
+ * Acknowledges the alerts an incident was just declared from, when the
980
+ * declaration asked for it (INCIDENT_ACKNOWLEDGE_ALERTS_TO_LINK_KEY). An
981
+ * alert's own on-call escalation and its per-user notifications stop once
982
+ * the alert is acknowledged (the workers check at their next run), so this
983
+ * is what makes declaring an incident stop the alerts paging.
984
+ *
985
+ * Written as root - the caller's right to change the state of every one of
986
+ * these alerts was checked before the incident was created - and credited
987
+ * to the declaring user, as if they had pressed Acknowledge on each alert
988
+ * themselves: the alert's owners are told, and the alert's feed says who
989
+ * and why. A private incident is not named in that "why", because the
990
+ * alert's feed entry is posted to the alert's Slack / Microsoft Teams
991
+ * channels and its owners' notifications.
992
+ *
993
+ * Left as they are:
994
+ * - alerts already acknowledged, resolved or in any later state - nothing
995
+ * is ever moved backwards;
996
+ * - linked alerts the project's linked-alert sync is about to move anyway
997
+ * (the incident was declared straight into an acknowledged or resolved
998
+ * state with the project switches on): one writer per alert, so the two
999
+ * never race on its timeline.
1000
+ *
1001
+ * IncidentService runs this after the links are written and does not wait
1002
+ * for it. It never throws: one alert failing never stops the others.
1003
+ */
1004
+ async acknowledgeAlertsDeclaredWithIncident(data) {
1005
+ var _a, _b;
1006
+ const result = {
1007
+ acknowledgedAlertIds: [],
1008
+ alreadyAcknowledgedAlertIds: [],
1009
+ leftToLinkedAlertSyncAlertIds: [],
1010
+ failed: [],
1011
+ };
1012
+ const alertIds = uniqueIds(data.alertIds);
1013
+ if (alertIds.length === 0) {
1014
+ return result;
1015
+ }
1016
+ const logAttributes = {
1017
+ projectId: data.projectId.toString(),
1018
+ incidentId: data.incidentId.toString(),
1019
+ };
1020
+ try {
1021
+ const acknowledgedState = await AlertStateService.findOneBy({
1022
+ query: {
1023
+ projectId: data.projectId,
1024
+ isAcknowledgedState: true,
1025
+ },
1026
+ select: {
1027
+ _id: true,
1028
+ order: true,
1029
+ },
1030
+ props: {
1031
+ isRoot: true,
1032
+ },
1033
+ });
1034
+ if (!acknowledgedState ||
1035
+ !acknowledgedState._id ||
1036
+ acknowledgedState.order === undefined ||
1037
+ acknowledgedState.order === null) {
1038
+ const message = "This project has no Acknowledged alert state, so the alerts could not be acknowledged.";
1039
+ logger.error(`IncidentAlertService: ${message}`, logAttributes);
1040
+ for (const alertId of alertIds) {
1041
+ result.failed.push({ alertId: alertId, message: message });
1042
+ }
1043
+ return result;
1044
+ }
1045
+ const acknowledgedStateId = new ObjectID(acknowledgedState._id.toString());
1046
+ const acknowledgedOrder = acknowledgedState.order;
1047
+ const incident = await IncidentService.findOneById({
1048
+ id: data.incidentId,
1049
+ select: {
1050
+ incidentNumber: true,
1051
+ incidentNumberWithPrefix: true,
1052
+ isPrivate: true,
1053
+ currentIncidentStateId: true,
1054
+ },
1055
+ props: {
1056
+ isRoot: true,
1057
+ },
1058
+ });
1059
+ const rootCause = getDeclaredAlertAcknowledgementCause({
1060
+ incidentNumber: formatNumber(incident === null || incident === void 0 ? void 0 : incident.incidentNumberWithPrefix, incident === null || incident === void 0 ? void 0 : incident.incidentNumber),
1061
+ isIncidentPrivate: (incident === null || incident === void 0 ? void 0 : incident.isPrivate) === true,
1062
+ });
1063
+ const alerts = await AlertService.findBy({
1064
+ query: {
1065
+ _id: QueryHelper.any(alertIds),
1066
+ projectId: data.projectId,
1067
+ },
1068
+ select: {
1069
+ _id: true,
1070
+ currentAlertStateId: true,
1071
+ currentAlertState: {
1072
+ order: true,
1073
+ },
1074
+ },
1075
+ limit: LIMIT_PER_PROJECT,
1076
+ skip: 0,
1077
+ props: {
1078
+ isRoot: true,
1079
+ },
1080
+ });
1081
+ const alertById = new Map();
1082
+ for (const alert of alerts) {
1083
+ if (alert._id) {
1084
+ alertById.set(normalizeId(alert._id), alert);
1085
+ }
1086
+ }
1087
+ const ownedBySync = await this.getLinkedAlertsTheSyncWillMove({
1088
+ projectId: data.projectId,
1089
+ incidentId: data.incidentId,
1090
+ incidentStateId: (incident === null || incident === void 0 ? void 0 : incident.currentIncidentStateId) || undefined,
1091
+ linkedAlerts: uniqueIds(data.linkedAlertIds)
1092
+ .map((alertId) => {
1093
+ return alertById.get(normalizeId(alertId));
1094
+ })
1095
+ .filter((alert) => {
1096
+ return Boolean(alert);
1097
+ }),
1098
+ });
1099
+ const alertIdsToWrite = [];
1100
+ for (const alertId of alertIds) {
1101
+ const key = normalizeId(alertId);
1102
+ const alert = alertById.get(key);
1103
+ if (!alert) {
1104
+ result.failed.push({
1105
+ alertId: alertId,
1106
+ message: "The alert could not be found in this project.",
1107
+ });
1108
+ continue;
1109
+ }
1110
+ const stateOrder = (_b = (_a = alert.currentAlertState) === null || _a === void 0 ? void 0 : _a.order) !== null && _b !== void 0 ? _b : undefined;
1111
+ if (stateOrder !== undefined && stateOrder >= acknowledgedOrder) {
1112
+ result.alreadyAcknowledgedAlertIds.push(alertId);
1113
+ continue;
1114
+ }
1115
+ if (ownedBySync.has(key)) {
1116
+ result.leftToLinkedAlertSyncAlertIds.push(alertId);
1117
+ continue;
1118
+ }
1119
+ alertIdsToWrite.push(alertId);
1120
+ }
1121
+ for (let index = 0; index < alertIdsToWrite.length; index += DECLARED_ALERT_ACKNOWLEDGE_CONCURRENCY) {
1122
+ const batch = alertIdsToWrite.slice(index, index + DECLARED_ALERT_ACKNOWLEDGE_CONCURRENCY);
1123
+ const outcomes = await Promise.all(batch.map((alertId) => {
1124
+ return this.acknowledgeDeclaredAlert({
1125
+ projectId: data.projectId,
1126
+ alertId: alertId,
1127
+ acknowledgedStateId: acknowledgedStateId,
1128
+ acknowledgedOrder: acknowledgedOrder,
1129
+ rootCause: rootCause,
1130
+ acknowledgedByUserId: data.acknowledgedByUserId,
1131
+ });
1132
+ }));
1133
+ batch.forEach((alertId, batchIndex) => {
1134
+ var _a;
1135
+ const failure = (_a = outcomes[batchIndex]) !== null && _a !== void 0 ? _a : null;
1136
+ if (failure === null) {
1137
+ result.acknowledgedAlertIds.push(alertId);
1138
+ return;
1139
+ }
1140
+ result.failed.push({ alertId: alertId, message: failure });
1141
+ logger.error(`IncidentAlertService could not acknowledge alert ${alertId.toString()} for the incident it was declared with: ${failure}`, Object.assign(Object.assign({}, logAttributes), { alertId: alertId.toString() }));
1142
+ });
1143
+ }
1144
+ }
1145
+ catch (error) {
1146
+ logger.error(`IncidentAlertService could not acknowledge the alerts an incident was declared from: ${error}`, logAttributes);
1147
+ const settled = new Set([
1148
+ ...result.acknowledgedAlertIds,
1149
+ ...result.alreadyAcknowledgedAlertIds,
1150
+ ...result.leftToLinkedAlertSyncAlertIds,
1151
+ ...result.failed.map((failure) => {
1152
+ return failure.alertId;
1153
+ }),
1154
+ ].map((alertId) => {
1155
+ return normalizeId(alertId);
1156
+ }));
1157
+ for (const alertId of alertIds) {
1158
+ if (!settled.has(normalizeId(alertId))) {
1159
+ result.failed.push({
1160
+ alertId: alertId,
1161
+ message: error instanceof Error ? error.message : String(error),
1162
+ });
1163
+ }
1164
+ }
1165
+ }
1166
+ return result;
1167
+ }
1168
+ /*
1169
+ * The linked alerts the project's linked-alert sync (and the incident
1170
+ * state cascade) will move by themselves, because the incident is already
1171
+ * in a state their switches act on: the same plan, the same per-alert
1172
+ * choice. Empty when both switches are off - the default - or the incident
1173
+ * is before Acknowledged, which is the usual declaration.
1174
+ */
1175
+ async getLinkedAlertsTheSyncWillMove(data) {
1176
+ const willMove = new Set();
1177
+ if (!data.incidentStateId || data.linkedAlerts.length === 0) {
1178
+ return willMove;
1179
+ }
1180
+ const switches = await this.getLinkedAlertSwitches(data.projectId);
1181
+ if (!switches) {
1182
+ return willMove;
1183
+ }
1184
+ const plan = await this.buildLinkedAlertStatePlan({
1185
+ projectId: data.projectId,
1186
+ incidentStateId: data.incidentStateId,
1187
+ switches: switches,
1188
+ });
1189
+ if (!plan) {
1190
+ return willMove;
1191
+ }
1192
+ const blockedFromResolve = plan.resolvedAlertState
1193
+ ? await this.getAlertsHeldOpenByAnotherIncident({
1194
+ plan: plan,
1195
+ projectId: data.projectId,
1196
+ incidentId: data.incidentId,
1197
+ alerts: data.linkedAlerts,
1198
+ })
1199
+ : new Set();
1200
+ for (const alert of data.linkedAlerts) {
1201
+ const alertId = alert._id ? normalizeId(alert._id) : "";
1202
+ const alertStateOrder = alert.currentAlertStateId
1203
+ ? plan.alertStateOrderById.get(normalizeId(alert.currentAlertStateId))
1204
+ : undefined;
1205
+ if (!alertId || alertStateOrder === undefined) {
1206
+ continue;
1207
+ }
1208
+ const target = chooseLinkedAlertTargetState({
1209
+ alertStateOrder: alertStateOrder,
1210
+ acknowledgedAlertState: plan.acknowledgedAlertState,
1211
+ resolvedAlertState: plan.resolvedAlertState,
1212
+ isBlockedFromResolve: blockedFromResolve.has(alertId),
1213
+ });
1214
+ if (target) {
1215
+ willMove.add(alertId);
1216
+ }
1217
+ }
1218
+ return willMove;
1219
+ }
1220
+ /*
1221
+ * Acknowledges one declared alert. Null when the alert ended up
1222
+ * acknowledged (or past it), otherwise why not. Never throws.
1223
+ *
1224
+ * The outcome is read back from the alert rather than taken from the
1225
+ * write: the write may be refused because somebody acknowledged or
1226
+ * resolved the alert in the meantime (the outcome asked for), and a write
1227
+ * that "succeeded" only counts if the alert's current state moved.
1228
+ */
1229
+ async acknowledgeDeclaredAlert(data) {
1230
+ var _a, _b;
1231
+ let failure = "The alert's state did not change to Acknowledged.";
1232
+ try {
1233
+ await AlertService.changeAlertState({
1234
+ projectId: data.projectId,
1235
+ alertId: data.alertId,
1236
+ alertStateId: data.acknowledgedStateId,
1237
+ notifyOwners: true,
1238
+ rootCause: data.rootCause,
1239
+ stateChangeLog: undefined,
1240
+ createdByUserId: data.acknowledgedByUserId,
1241
+ props: {
1242
+ isRoot: true,
1243
+ },
1244
+ });
1245
+ }
1246
+ catch (error) {
1247
+ failure = error instanceof Error ? error.message : String(error);
1248
+ }
1249
+ try {
1250
+ const alert = await AlertService.findOneBy({
1251
+ query: {
1252
+ _id: data.alertId,
1253
+ projectId: data.projectId,
1254
+ },
1255
+ select: {
1256
+ currentAlertState: {
1257
+ order: true,
1258
+ },
1259
+ },
1260
+ props: {
1261
+ isRoot: true,
1262
+ },
1263
+ });
1264
+ const order = (_b = (_a = alert === null || alert === void 0 ? void 0 : alert.currentAlertState) === null || _a === void 0 ? void 0 : _a.order) !== null && _b !== void 0 ? _b : undefined;
1265
+ if (order !== undefined && order >= data.acknowledgedOrder) {
1266
+ return null;
1267
+ }
1268
+ }
1269
+ catch (_c) {
1270
+ // The alert cannot be read back, so it cannot be counted as done.
1271
+ }
1272
+ return failure;
1273
+ }
847
1274
  /*
848
1275
  * An incident changed state: bring its linked alerts along, when the
849
1276
  * project has opted in. Called fire-and-forget from
@@ -919,8 +1346,11 @@ export class Service extends DatabaseService {
919
1346
  /*
920
1347
  * The link-time half: an alert was just linked to an incident that may
921
1348
  * already be acknowledged or resolved. Same rules as the cascade, applied to
922
- * the one alert. A newly declared incident is in its created state, so
923
- * this is a no-op for "declare incident from alerts". Never throws.
1349
+ * the one alert. An incident declared from alerts usually starts in its
1350
+ * created state, which calls for nothing; one declared straight into an
1351
+ * acknowledged or resolved state brings its alerts along here (and
1352
+ * acknowledgeAlertsDeclaredWithIncident leaves those alerts to this).
1353
+ * Never throws.
924
1354
  */
925
1355
  async syncAlertWithLinkedIncidentState(data) {
926
1356
  try {
@@ -1330,6 +1760,30 @@ __decorate([
1330
1760
  __metadata("design:paramtypes", [Object]),
1331
1761
  __metadata("design:returntype", Promise)
1332
1762
  ], Service.prototype, "linkAlertsToIncident", null);
1763
+ __decorate([
1764
+ CaptureSpan(),
1765
+ __metadata("design:type", Function),
1766
+ __metadata("design:paramtypes", [Object]),
1767
+ __metadata("design:returntype", Promise)
1768
+ ], Service.prototype, "validateAcknowledgeAlertsForNewIncident", null);
1769
+ __decorate([
1770
+ CaptureSpan(),
1771
+ __metadata("design:type", Function),
1772
+ __metadata("design:paramtypes", [Object]),
1773
+ __metadata("design:returntype", Promise)
1774
+ ], Service.prototype, "acknowledgeAlertsDeclaredWithIncident", null);
1775
+ __decorate([
1776
+ CaptureSpan(),
1777
+ __metadata("design:type", Function),
1778
+ __metadata("design:paramtypes", [Object]),
1779
+ __metadata("design:returntype", Promise)
1780
+ ], Service.prototype, "getLinkedAlertsTheSyncWillMove", null);
1781
+ __decorate([
1782
+ CaptureSpan(),
1783
+ __metadata("design:type", Function),
1784
+ __metadata("design:paramtypes", [Object]),
1785
+ __metadata("design:returntype", Promise)
1786
+ ], Service.prototype, "acknowledgeDeclaredAlert", null);
1333
1787
  __decorate([
1334
1788
  CaptureSpan(),
1335
1789
  __metadata("design:type", Function),