@oneuptime/common 14.0.6 → 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.
- package/Models/DatabaseModels/InventoryItem.ts +23 -12
- package/Server/Infrastructure/Postgres/SchemaMigrations/1795200000000-AddInventoryItemProjectEntityKeyIndex.ts +217 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
- package/Server/Services/AlertService.ts +32 -36
- package/Server/Services/IncidentAlertService.ts +609 -2
- package/Server/Services/IncidentService.ts +134 -44
- package/Server/Services/InventoryItemService.ts +38 -0
- package/Server/Services/IoTDeviceCredentialService.ts +13 -4
- package/Server/Services/ScheduledMaintenanceService.ts +50 -46
- package/Server/Services/UserNotificationRuleService.ts +55 -53
- package/Server/Services/WorkspaceNotificationSummaryService.ts +38 -20
- package/Server/Utils/AffectedResources/LinkedAffectedResources.ts +576 -0
- package/Server/Utils/Alert/AlertStateChangeAuthorization.ts +129 -0
- package/Server/Utils/Inventory/InventoryOverviewAggregation.ts +111 -0
- package/Server/Utils/Response.ts +31 -0
- package/Server/Utils/Topology/TopologyConcurrencyLimiter.ts +229 -0
- package/Server/Utils/Topology/TopologyQueries.ts +1350 -0
- package/Server/Utils/Topology/TopologyRequest.ts +463 -0
- package/Server/Utils/Topology/TopologyResponseCache.ts +183 -0
- package/Server/Utils/Topology/TopologySql.ts +1024 -0
- package/Tests/App/Dashboard/AcknowledgeAlertsOnDeclare.test.ts +369 -0
- package/Tests/App/Dashboard/AffectedResourcesSlo.test.tsx +1 -1
- package/Tests/App/Dashboard/AffectedResourcesSummaryStep.test.tsx +510 -123
- package/Tests/App/Dashboard/AlertsTableAffectedResources.test.tsx +490 -0
- package/Tests/App/Dashboard/ChangeAlertStateDeclareIncident.test.tsx +886 -0
- package/Tests/App/Dashboard/CreateReviewStepRoleSummary.test.tsx +1250 -0
- package/Tests/App/Dashboard/CreateReviewStepSelectionSummary.test.tsx +738 -0
- package/Tests/App/Dashboard/DeclareIncidentFromAlert.test.ts +417 -0
- package/Tests/App/Dashboard/EntityDetailPanel.test.tsx +1948 -229
- package/Tests/App/Dashboard/EntityDetailPanelLifecycle.test.tsx +493 -0
- package/Tests/App/Dashboard/EntityDetailPanelNavigation.test.tsx +439 -0
- package/Tests/App/Dashboard/EventOverviewPages.test.tsx +292 -1
- package/Tests/App/Dashboard/EventStatusPanel.test.tsx +798 -2
- package/Tests/App/Dashboard/FetchStateAndSeverityElements.test.tsx +669 -0
- package/Tests/App/Dashboard/FetchUsers.test.tsx +230 -0
- package/Tests/App/Dashboard/IncidentCreateAcknowledgeAlerts.test.tsx +1611 -0
- package/Tests/App/Dashboard/InfrastructureExplorer.test.tsx +1365 -57
- package/Tests/App/Dashboard/InfrastructureGraph.test.tsx +964 -23
- package/Tests/App/Dashboard/InventoryRelationships.test.tsx +885 -0
- package/Tests/App/Dashboard/ServiceMapDatabaseLink.test.tsx +201 -15
- package/Tests/App/Dashboard/ServiceMapGraph.test.tsx +508 -36
- package/Tests/App/Dashboard/TopologyDataLifecycle.test.tsx +583 -0
- package/Tests/App/Dashboard/TopologyDataLoading.test.tsx +982 -218
- package/Tests/App/Dashboard/TopologyPageNavigation.test.tsx +818 -114
- package/Tests/Server/Infrastructure/Postgres/RenameInventoryItemMigration.test.ts +90 -21
- package/Tests/Server/Services/DomainService.test.ts +525 -0
- package/Tests/Server/Services/IncidentAlertAcknowledgeOnDeclare.test.ts +2630 -0
- package/Tests/Server/Services/IncidentAlertPostgres.test.ts +1625 -1
- package/Tests/Server/Services/IncidentDeclareAcknowledgeAlerts.test.ts +2189 -0
- package/Tests/Server/Services/InventoryItemServiceOverviewCounts.test.ts +345 -0
- package/Tests/Server/Services/InventoryOverviewPostgres.test.ts +254 -0
- package/Tests/Server/Services/IoTDeviceCredentialService.test.ts +819 -0
- package/Tests/Server/Services/LinkedAffectedResourcesCreatedFeed.test.ts +420 -0
- package/Tests/Server/Services/SloAffectedResourceCreatedFeed.test.ts +92 -36
- package/Tests/Server/Utils/AffectedResources/LinkedAffectedResources.test.ts +858 -0
- package/Tests/Server/Utils/Alert/AlertStateChangeAuthorization.test.ts +1172 -0
- package/Tests/Server/Utils/Topology/InventoryItemEntityKeyIndexMigration.test.ts +386 -0
- package/Tests/Server/Utils/Topology/InventoryItemEntityKeyIndexMigrationPostgres.test.ts +430 -0
- package/Tests/Server/Utils/Topology/TopologyActivityParityPostgres.test.ts +279 -0
- package/Tests/Server/Utils/Topology/TopologyConcurrencyLimiter.test.ts +534 -0
- package/Tests/Server/Utils/Topology/TopologyEstateGenerator.ts +624 -0
- package/Tests/Server/Utils/Topology/TopologyJsonResponse.test.ts +90 -0
- package/Tests/Server/Utils/Topology/TopologyQueries.test.ts +1634 -0
- package/Tests/Server/Utils/Topology/TopologyQueriesDifferentialPostgres.test.ts +683 -0
- package/Tests/Server/Utils/Topology/TopologyQueriesPostgres.test.ts +2624 -0
- package/Tests/Server/Utils/Topology/TopologyReference.ts +1018 -0
- package/Tests/Server/Utils/Topology/TopologyRequest.test.ts +643 -0
- package/Tests/Server/Utils/Topology/TopologyResponseCache.test.ts +305 -0
- package/Tests/Server/Utils/Topology/TopologySql.test.ts +973 -0
- package/Tests/Types/Topology/TopologyTypeRules.test.ts +176 -0
- package/Tests/Utils/Incident/IncidentFromAlerts.test.ts +302 -0
- package/Types/Incident/IncidentAlertLink.ts +19 -0
- package/Types/Telemetry/InventoryLiveness.ts +26 -0
- package/Types/Topology/TopologyApi.ts +428 -0
- package/Types/Topology/TopologyTypeRules.ts +191 -0
- package/Utils/Incident/IncidentFromAlerts.ts +78 -0
- package/build/dist/Models/DatabaseModels/InventoryItem.js +20 -11
- package/build/dist/Models/DatabaseModels/InventoryItem.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795200000000-AddInventoryItemProjectEntityKeyIndex.js +165 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795200000000-AddInventoryItemProjectEntityKeyIndex.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/AlertService.js +21 -33
- package/build/dist/Server/Services/AlertService.js.map +1 -1
- package/build/dist/Server/Services/IncidentAlertService.js +457 -3
- package/build/dist/Server/Services/IncidentAlertService.js.map +1 -1
- package/build/dist/Server/Services/IncidentService.js +96 -42
- package/build/dist/Server/Services/IncidentService.js.map +1 -1
- package/build/dist/Server/Services/InventoryItemService.js +31 -0
- package/build/dist/Server/Services/InventoryItemService.js.map +1 -1
- package/build/dist/Server/Services/IoTDeviceCredentialService.js +12 -5
- package/build/dist/Server/Services/IoTDeviceCredentialService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceService.js +37 -40
- package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationRuleService.js +55 -51
- package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
- package/build/dist/Server/Services/WorkspaceNotificationSummaryService.js +42 -25
- package/build/dist/Server/Services/WorkspaceNotificationSummaryService.js.map +1 -1
- package/build/dist/Server/Utils/AffectedResources/LinkedAffectedResources.js +405 -0
- package/build/dist/Server/Utils/AffectedResources/LinkedAffectedResources.js.map +1 -0
- package/build/dist/Server/Utils/Alert/AlertStateChangeAuthorization.js +112 -0
- package/build/dist/Server/Utils/Alert/AlertStateChangeAuthorization.js.map +1 -0
- package/build/dist/Server/Utils/Inventory/InventoryOverviewAggregation.js +81 -0
- package/build/dist/Server/Utils/Inventory/InventoryOverviewAggregation.js.map +1 -0
- package/build/dist/Server/Utils/Response.js +30 -0
- package/build/dist/Server/Utils/Response.js.map +1 -1
- package/build/dist/Server/Utils/Topology/TopologyConcurrencyLimiter.js +178 -0
- package/build/dist/Server/Utils/Topology/TopologyConcurrencyLimiter.js.map +1 -0
- package/build/dist/Server/Utils/Topology/TopologyQueries.js +884 -0
- package/build/dist/Server/Utils/Topology/TopologyQueries.js.map +1 -0
- package/build/dist/Server/Utils/Topology/TopologyRequest.js +275 -0
- package/build/dist/Server/Utils/Topology/TopologyRequest.js.map +1 -0
- package/build/dist/Server/Utils/Topology/TopologyResponseCache.js +146 -0
- package/build/dist/Server/Utils/Topology/TopologyResponseCache.js.map +1 -0
- package/build/dist/Server/Utils/Topology/TopologySql.js +688 -0
- package/build/dist/Server/Utils/Topology/TopologySql.js.map +1 -0
- package/build/dist/Types/Incident/IncidentAlertLink.js +17 -0
- package/build/dist/Types/Incident/IncidentAlertLink.js.map +1 -1
- package/build/dist/Types/Telemetry/InventoryLiveness.js +23 -0
- package/build/dist/Types/Telemetry/InventoryLiveness.js.map +1 -1
- package/build/dist/Types/Topology/TopologyApi.js +55 -0
- package/build/dist/Types/Topology/TopologyApi.js.map +1 -0
- package/build/dist/Types/Topology/TopologyTypeRules.js +165 -0
- package/build/dist/Types/Topology/TopologyTypeRules.js.map +1 -0
- package/build/dist/Utils/Incident/IncidentFromAlerts.js +38 -0
- package/build/dist/Utils/Incident/IncidentFromAlerts.js.map +1 -1
- package/package.json +1 -1
|
@@ -24,7 +24,9 @@ import OnCallDutyPolicyService from "./OnCallDutyPolicyService";
|
|
|
24
24
|
import TeamMemberService from "./TeamMemberService";
|
|
25
25
|
import UserService from "./UserService";
|
|
26
26
|
import URL from "../../Types/API/URL";
|
|
27
|
-
import {
|
|
27
|
+
import LinkedAffectedResources, {
|
|
28
|
+
LinkedAffectedResource,
|
|
29
|
+
} from "../Utils/AffectedResources/LinkedAffectedResources";
|
|
28
30
|
import DatabaseCommonInteractionProps from "../../Types/BaseDatabase/DatabaseCommonInteractionProps";
|
|
29
31
|
import SortOrder from "../../Types/BaseDatabase/SortOrder";
|
|
30
32
|
import LIMIT_MAX, { LIMIT_PER_PROJECT } from "../../Types/Database/LimitMax";
|
|
@@ -112,9 +114,14 @@ import IncidentAIContextBuilder, {
|
|
|
112
114
|
IncidentContextData,
|
|
113
115
|
} from "../Utils/AI/IncidentAIContextBuilder";
|
|
114
116
|
import IncidentAlertService, {
|
|
117
|
+
AcknowledgeDeclaredAlertsResult,
|
|
118
|
+
AlertsToAcknowledgeOnDeclare,
|
|
115
119
|
LinkAlertsToIncidentResult,
|
|
116
120
|
} from "./IncidentAlertService";
|
|
117
|
-
import {
|
|
121
|
+
import {
|
|
122
|
+
INCIDENT_ACKNOWLEDGE_ALERTS_TO_LINK_KEY,
|
|
123
|
+
INCIDENT_ALERT_IDS_TO_LINK_KEY,
|
|
124
|
+
} from "../../Types/Incident/IncidentAlertLink";
|
|
118
125
|
|
|
119
126
|
// key is incidentId for this dictionary.
|
|
120
127
|
type UpdateCarryForward = Dictionary<{
|
|
@@ -131,6 +138,14 @@ type UpdateCarryForward = Dictionary<{
|
|
|
131
138
|
type IncidentCreateCarryForward = {
|
|
132
139
|
// Validated, deduplicated alert ids to link once the incident exists.
|
|
133
140
|
alertIdsToLink: Array<ObjectID>;
|
|
141
|
+
/*
|
|
142
|
+
* Acknowledge alerts once they are linked, as the declaring user
|
|
143
|
+
* (INCIDENT_ACKNOWLEDGE_ALERTS_TO_LINK_KEY) - what stops their escalation.
|
|
144
|
+
* When asked to: the project's Acknowledged alert state, and the alerts
|
|
145
|
+
* not acknowledged yet, which the caller was checked for. Null otherwise.
|
|
146
|
+
*/
|
|
147
|
+
acknowledgedAlertStateId: ObjectID | null;
|
|
148
|
+
alertIdsToAcknowledge: Array<ObjectID>;
|
|
134
149
|
} | null;
|
|
135
150
|
|
|
136
151
|
type IncidentUpdatePayload = {
|
|
@@ -809,14 +824,36 @@ export class Service extends DatabaseService<Model> {
|
|
|
809
824
|
const alertIdsToLink: unknown =
|
|
810
825
|
createBy.miscDataProps?.[INCIDENT_ALERT_IDS_TO_LINK_KEY];
|
|
811
826
|
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
await IncidentAlertService.validateAlertIdsForNewIncident({
|
|
827
|
+
const validatedAlertIds: Array<ObjectID> =
|
|
828
|
+
alertIdsToLink !== undefined && alertIdsToLink !== null
|
|
829
|
+
? await IncidentAlertService.validateAlertIdsForNewIncident({
|
|
816
830
|
projectId: projectId,
|
|
817
831
|
alertIds: alertIdsToLink,
|
|
818
832
|
props: createBy.props,
|
|
819
|
-
})
|
|
833
|
+
})
|
|
834
|
+
: [];
|
|
835
|
+
|
|
836
|
+
/*
|
|
837
|
+
* Asking to acknowledge the alerts is checked here too, with the alert
|
|
838
|
+
* ids, so a request that cannot be honoured (no Acknowledged alert state,
|
|
839
|
+
* or a caller who may not change these alerts' states) is refused before
|
|
840
|
+
* the incident exists rather than leaving alerts that keep paging.
|
|
841
|
+
*/
|
|
842
|
+
const alertsToAcknowledge: AlertsToAcknowledgeOnDeclare | null =
|
|
843
|
+
await IncidentAlertService.validateAcknowledgeAlertsForNewIncident({
|
|
844
|
+
projectId: projectId,
|
|
845
|
+
acknowledgeAlerts:
|
|
846
|
+
createBy.miscDataProps?.[INCIDENT_ACKNOWLEDGE_ALERTS_TO_LINK_KEY],
|
|
847
|
+
alertIds: validatedAlertIds,
|
|
848
|
+
props: createBy.props,
|
|
849
|
+
});
|
|
850
|
+
|
|
851
|
+
if (validatedAlertIds.length > 0) {
|
|
852
|
+
carryForward = {
|
|
853
|
+
alertIdsToLink: validatedAlertIds,
|
|
854
|
+
acknowledgedAlertStateId:
|
|
855
|
+
alertsToAcknowledge?.acknowledgedAlertStateId || null,
|
|
856
|
+
alertIdsToAcknowledge: alertsToAcknowledge?.alertIdsToAcknowledge || [],
|
|
820
857
|
};
|
|
821
858
|
}
|
|
822
859
|
|
|
@@ -1272,20 +1309,10 @@ export class Service extends DatabaseService<Model> {
|
|
|
1272
1309
|
labels: {
|
|
1273
1310
|
name: true,
|
|
1274
1311
|
},
|
|
1275
|
-
monitors: {
|
|
1276
|
-
name: true,
|
|
1277
|
-
_id: true,
|
|
1278
|
-
},
|
|
1279
1312
|
/*
|
|
1280
|
-
*
|
|
1281
|
-
*
|
|
1282
|
-
* project's SLOs (getSloAffectedResourceMarkdownLines).
|
|
1313
|
+
* The resources named under "Resources Affected" are read by the feed
|
|
1314
|
+
* builder itself (LinkedAffectedResources), one relation at a time.
|
|
1283
1315
|
*/
|
|
1284
|
-
serviceLevelObjectives: {
|
|
1285
|
-
name: true,
|
|
1286
|
-
_id: true,
|
|
1287
|
-
projectId: true,
|
|
1288
|
-
},
|
|
1289
1316
|
},
|
|
1290
1317
|
props: {
|
|
1291
1318
|
isRoot: true,
|
|
@@ -1744,6 +1771,24 @@ export class Service extends DatabaseService<Model> {
|
|
|
1744
1771
|
return carryForward?.alertIdsToLink || [];
|
|
1745
1772
|
}
|
|
1746
1773
|
|
|
1774
|
+
/*
|
|
1775
|
+
* The alerts the declaration asked to acknowledge that were not
|
|
1776
|
+
* acknowledged yet when it was checked - the only ones the caller was
|
|
1777
|
+
* authorized for, so the only ones that may be written.
|
|
1778
|
+
*/
|
|
1779
|
+
private getAlertIdsToAcknowledgeDeclaredWith(
|
|
1780
|
+
onCreate: OnCreate<Model>,
|
|
1781
|
+
): Array<ObjectID> {
|
|
1782
|
+
const carryForward: IncidentCreateCarryForward =
|
|
1783
|
+
(onCreate.carryForward as IncidentCreateCarryForward) || null;
|
|
1784
|
+
|
|
1785
|
+
if (!carryForward?.acknowledgedAlertStateId) {
|
|
1786
|
+
return [];
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
return carryForward.alertIdsToAcknowledge || [];
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1747
1792
|
/*
|
|
1748
1793
|
* Who declared the incident, as "Linked by" on the links and the actor of
|
|
1749
1794
|
* their feed entries. A user is recorded as themselves and an API key as
|
|
@@ -1778,6 +1823,13 @@ export class Service extends DatabaseService<Model> {
|
|
|
1778
1823
|
* the incident's title when the incident is private, and a rule can make
|
|
1779
1824
|
* it private after it was saved. (The alerts' owners are added to a private
|
|
1780
1825
|
* incident later in the chain, once its workspace channels exist.)
|
|
1826
|
+
*
|
|
1827
|
+
* When the declaration asked for it, the alerts are then acknowledged as
|
|
1828
|
+
* the declaring user, which stops their on-call escalation at the
|
|
1829
|
+
* workers' next run. That is not waited for: each acknowledgement is a
|
|
1830
|
+
* full state change (feed entry, Slack / Microsoft Teams post, owner
|
|
1831
|
+
* notification), up to one per alert, and the page the user lands on does
|
|
1832
|
+
* not show the alerts' states. It never fails the incident.
|
|
1781
1833
|
*/
|
|
1782
1834
|
@CaptureSpan()
|
|
1783
1835
|
private async linkAlertsDeclaredWithIncident(
|
|
@@ -1793,6 +1845,8 @@ export class Service extends DatabaseService<Model> {
|
|
|
1793
1845
|
|
|
1794
1846
|
await privacyRulesApplied;
|
|
1795
1847
|
|
|
1848
|
+
let linkedAlertIds: Array<ObjectID> = [];
|
|
1849
|
+
|
|
1796
1850
|
try {
|
|
1797
1851
|
const result: LinkAlertsToIncidentResult =
|
|
1798
1852
|
await IncidentAlertService.linkAlertsToIncident({
|
|
@@ -1806,6 +1860,11 @@ export class Service extends DatabaseService<Model> {
|
|
|
1806
1860
|
},
|
|
1807
1861
|
});
|
|
1808
1862
|
|
|
1863
|
+
linkedAlertIds = [
|
|
1864
|
+
...result.linkedAlertIds,
|
|
1865
|
+
...result.alreadyLinkedAlertIds,
|
|
1866
|
+
];
|
|
1867
|
+
|
|
1809
1868
|
if (result.failed.length > 0) {
|
|
1810
1869
|
logger.error(
|
|
1811
1870
|
`${result.failed.length} of ${alertIds.length} alerts could not be linked to the incident they were declared with.`,
|
|
@@ -1824,6 +1883,44 @@ export class Service extends DatabaseService<Model> {
|
|
|
1824
1883
|
} as LogAttributes,
|
|
1825
1884
|
);
|
|
1826
1885
|
}
|
|
1886
|
+
|
|
1887
|
+
const alertIdsToAcknowledge: Array<ObjectID> =
|
|
1888
|
+
this.getAlertIdsToAcknowledgeDeclaredWith(onCreate);
|
|
1889
|
+
|
|
1890
|
+
if (alertIdsToAcknowledge.length === 0) {
|
|
1891
|
+
return;
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1894
|
+
const projectId: ObjectID = createdItem.projectId;
|
|
1895
|
+
const incidentId: ObjectID = createdItem.id;
|
|
1896
|
+
|
|
1897
|
+
IncidentAlertService.acknowledgeAlertsDeclaredWithIncident({
|
|
1898
|
+
projectId: projectId,
|
|
1899
|
+
incidentId: incidentId,
|
|
1900
|
+
alertIds: alertIdsToAcknowledge,
|
|
1901
|
+
linkedAlertIds: linkedAlertIds,
|
|
1902
|
+
acknowledgedByUserId: this.getDeclaringUserId(onCreate, createdItem),
|
|
1903
|
+
})
|
|
1904
|
+
.then((acknowledged: AcknowledgeDeclaredAlertsResult) => {
|
|
1905
|
+
if (acknowledged.failed.length > 0) {
|
|
1906
|
+
logger.error(
|
|
1907
|
+
`${acknowledged.failed.length} of ${alertIdsToAcknowledge.length} alerts could not be acknowledged when the incident was declared from them.`,
|
|
1908
|
+
{
|
|
1909
|
+
projectId: projectId.toString(),
|
|
1910
|
+
incidentId: incidentId.toString(),
|
|
1911
|
+
} as LogAttributes,
|
|
1912
|
+
);
|
|
1913
|
+
}
|
|
1914
|
+
})
|
|
1915
|
+
.catch((error: Error) => {
|
|
1916
|
+
logger.error(
|
|
1917
|
+
`Acknowledging the alerts an incident was declared from failed in IncidentService.onCreateSuccess: ${error}`,
|
|
1918
|
+
{
|
|
1919
|
+
projectId: projectId.toString(),
|
|
1920
|
+
incidentId: incidentId.toString(),
|
|
1921
|
+
} as LogAttributes,
|
|
1922
|
+
);
|
|
1923
|
+
});
|
|
1827
1924
|
}
|
|
1828
1925
|
|
|
1829
1926
|
@CaptureSpan()
|
|
@@ -1905,35 +2002,28 @@ ${incident.description || "No description provided."}
|
|
|
1905
2002
|
}
|
|
1906
2003
|
|
|
1907
2004
|
/*
|
|
1908
|
-
*
|
|
1909
|
-
*
|
|
1910
|
-
*
|
|
1911
|
-
*
|
|
1912
|
-
*
|
|
1913
|
-
* this service through the burn-rate rule service).
|
|
2005
|
+
* Everything the incident's Affected Resources card lists: monitors,
|
|
2006
|
+
* the hosts, clusters and services it is attached to, then its SLOs. A
|
|
2007
|
+
* burn-rate incident carries no monitors on purpose, so its SLO is the
|
|
2008
|
+
* only resource there is to name - and the feed's only way back to the
|
|
2009
|
+
* objective that declared it.
|
|
1914
2010
|
*/
|
|
1915
|
-
const
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
serviceLevelObjectives: incident.serviceLevelObjectives,
|
|
1922
|
-
})
|
|
1923
|
-
: [];
|
|
2011
|
+
const resources: Array<LinkedAffectedResource> =
|
|
2012
|
+
await LinkedAffectedResources.readForIncident({
|
|
2013
|
+
service: this,
|
|
2014
|
+
projectId: incident.projectId!,
|
|
2015
|
+
incidentId: incident.id!,
|
|
2016
|
+
});
|
|
1924
2017
|
|
|
1925
|
-
if (
|
|
1926
|
-
(incident.monitors && incident.monitors.length > 0) ||
|
|
1927
|
-
sloLines.length > 0
|
|
1928
|
-
) {
|
|
2018
|
+
if (resources.length > 0) {
|
|
1929
2019
|
feedInfoInMarkdown += `🌎 **Resources Affected**:\n`;
|
|
1930
2020
|
|
|
1931
|
-
for (const
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
feedInfoInMarkdown += `${
|
|
2021
|
+
for (const resourceLine of LinkedAffectedResources.getMarkdownLines({
|
|
2022
|
+
dashboardUrl: await DatabaseConfig.getDashboardUrl(),
|
|
2023
|
+
projectId: incident.projectId!,
|
|
2024
|
+
resources: resources,
|
|
2025
|
+
})) {
|
|
2026
|
+
feedInfoInMarkdown += `${resourceLine}\n`;
|
|
1937
2027
|
}
|
|
1938
2028
|
|
|
1939
2029
|
feedInfoInMarkdown += `\n\n`;
|
|
@@ -10,6 +10,8 @@ import ColumnLength from "../../Types/Database/ColumnLength";
|
|
|
10
10
|
import QueryDeepPartialEntity from "../../Types/Database/PartialEntity";
|
|
11
11
|
import CreateBy from "../Types/Database/CreateBy";
|
|
12
12
|
import { OnCreate } from "../Types/Database/Hooks";
|
|
13
|
+
import { AggregateRow } from "../Types/Database/AggregateBy";
|
|
14
|
+
import DatabaseCommonInteractionProps from "../../Types/BaseDatabase/DatabaseCommonInteractionProps";
|
|
13
15
|
import BadDataException from "../../Types/Exception/BadDataException";
|
|
14
16
|
import Dictionary from "../../Types/Dictionary";
|
|
15
17
|
import { JSONObject } from "../../Types/JSON";
|
|
@@ -22,6 +24,12 @@ import {
|
|
|
22
24
|
MANUAL_ENTITY_IDENTITY_ATTRIBUTE,
|
|
23
25
|
} from "../../Utils/Telemetry/EntityKey";
|
|
24
26
|
import logger from "../Utils/Logger";
|
|
27
|
+
import {
|
|
28
|
+
INVENTORY_OVERVIEW_GROUP_BY,
|
|
29
|
+
INVENTORY_OVERVIEW_SELECT,
|
|
30
|
+
InventoryOverviewCounts,
|
|
31
|
+
readInventoryOverviewGroups,
|
|
32
|
+
} from "../Utils/Inventory/InventoryOverviewAggregation";
|
|
25
33
|
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
|
|
26
34
|
import { ExtractedEntity } from "../Utils/Telemetry/TelemetryEntity";
|
|
27
35
|
import {
|
|
@@ -147,6 +155,36 @@ export class InventoryItemService extends DatabaseService<Model> {
|
|
|
147
155
|
return { createBy, carryForward: null };
|
|
148
156
|
}
|
|
149
157
|
|
|
158
|
+
/**
|
|
159
|
+
* The Inventory Overview's tiles and per-type breakdown, counted over the
|
|
160
|
+
* project's whole unarchived estate. See INVENTORY_OVERVIEW_SELECT.
|
|
161
|
+
*
|
|
162
|
+
* Archived rows are excluded because every tile drills into the live Items
|
|
163
|
+
* list, which excludes them too.
|
|
164
|
+
*
|
|
165
|
+
* The GROUP BY carries no limit: capping it would cap the totals summed
|
|
166
|
+
* from it. The number of groups is bounded by the entity-type vocabulary —
|
|
167
|
+
* ingest maps to a fixed set of types, manual creates are confined to
|
|
168
|
+
* MANUAL_ENTITY_TYPES, and the column is not updatable.
|
|
169
|
+
*/
|
|
170
|
+
@CaptureSpan()
|
|
171
|
+
public async getOverviewCounts(data: {
|
|
172
|
+
projectId: ObjectID;
|
|
173
|
+
props: DatabaseCommonInteractionProps;
|
|
174
|
+
}): Promise<InventoryOverviewCounts> {
|
|
175
|
+
const rows: Array<AggregateRow> = await this.aggregateBy({
|
|
176
|
+
query: {
|
|
177
|
+
projectId: data.projectId,
|
|
178
|
+
isArchived: false,
|
|
179
|
+
},
|
|
180
|
+
groupBy: INVENTORY_OVERVIEW_GROUP_BY,
|
|
181
|
+
select: INVENTORY_OVERVIEW_SELECT,
|
|
182
|
+
props: data.props,
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
return readInventoryOverviewGroups(rows);
|
|
186
|
+
}
|
|
187
|
+
|
|
150
188
|
/**
|
|
151
189
|
* Forward-only registry reconciliation: upsert a catalog row per
|
|
152
190
|
* discovered entity, bump `lastSeenAt`, merge descriptive attributes
|
|
@@ -204,14 +204,23 @@ export class Service extends DatabaseService<Model> {
|
|
|
204
204
|
return cached;
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
207
|
+
/*
|
|
208
|
+
* Validated rather than caught. ObjectID's constructor stores whatever
|
|
209
|
+
* string it is handed and never throws, so the try/catch this replaced
|
|
210
|
+
* caught nothing: a malformed id reached Postgres as an invalid uuid
|
|
211
|
+
* literal, which errors on every attempt AND skipped the negative cache
|
|
212
|
+
* below, because the row was never absent - the query failed. credentialId
|
|
213
|
+
* is an MQTT username, so it is attacker-controlled, and that made the
|
|
214
|
+
* negative cache bypassable with random garbage. Same guard, and the same
|
|
215
|
+
* reason for it, as ProjectAuthorization's API-key check.
|
|
216
|
+
*/
|
|
217
|
+
if (!ObjectID.isValidUUID(credentialId)) {
|
|
211
218
|
this.contextCache.set(credentialId, null, NEGATIVE_TTL_MS);
|
|
212
219
|
return null;
|
|
213
220
|
}
|
|
214
221
|
|
|
222
|
+
const idObject: ObjectID = new ObjectID(credentialId);
|
|
223
|
+
|
|
215
224
|
const credential: Model | null = await this.findOneBy({
|
|
216
225
|
query: { _id: idObject },
|
|
217
226
|
select: {
|
|
@@ -9,6 +9,10 @@ import DatabaseService from "./DatabaseService";
|
|
|
9
9
|
import ScheduledMaintenanceCustomField from "../../Models/DatabaseModels/ScheduledMaintenanceCustomField";
|
|
10
10
|
import CustomFieldMappingService from "./CustomFieldMappingService";
|
|
11
11
|
import MonitorService from "./MonitorService";
|
|
12
|
+
import LinkedAffectedResources, {
|
|
13
|
+
LinkedAffectedResource,
|
|
14
|
+
LinkedAffectedResourceRelation,
|
|
15
|
+
} from "../Utils/AffectedResources/LinkedAffectedResources";
|
|
12
16
|
import ScheduledMaintenanceOwnerTeamService from "./ScheduledMaintenanceOwnerTeamService";
|
|
13
17
|
import ScheduledMaintenanceOwnerUserService from "./ScheduledMaintenanceOwnerUserService";
|
|
14
18
|
import ScheduledMaintenanceStateService from "./ScheduledMaintenanceStateService";
|
|
@@ -1180,10 +1184,6 @@ ${resourcesAffected ? `**Resources Affected:** ${resourcesAffected}` : ""}
|
|
|
1180
1184
|
},
|
|
1181
1185
|
startsAt: true,
|
|
1182
1186
|
endsAt: true,
|
|
1183
|
-
monitors: {
|
|
1184
|
-
name: true,
|
|
1185
|
-
_id: true,
|
|
1186
|
-
},
|
|
1187
1187
|
labels: {
|
|
1188
1188
|
name: true,
|
|
1189
1189
|
},
|
|
@@ -1448,14 +1448,23 @@ ${scheduledMaintenance.description || "No description provided."}
|
|
|
1448
1448
|
feedInfoInMarkdown += `⏳ **Scheduled Maintenance State**: ${scheduledMaintenance.currentScheduledMaintenanceState.name} \n\n`;
|
|
1449
1449
|
}
|
|
1450
1450
|
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1451
|
+
// Everything the event's Affected Resources card lists, monitors first.
|
|
1452
|
+
const resources: Array<LinkedAffectedResource> =
|
|
1453
|
+
await LinkedAffectedResources.readForScheduledMaintenance({
|
|
1454
|
+
service: this,
|
|
1455
|
+
projectId: scheduledMaintenance.projectId!,
|
|
1456
|
+
scheduledMaintenanceId: scheduledMaintenance.id!,
|
|
1457
|
+
});
|
|
1458
|
+
|
|
1459
|
+
if (resources.length > 0) {
|
|
1455
1460
|
feedInfoInMarkdown += `🌎 **Resources Affected**:\n`;
|
|
1456
1461
|
|
|
1457
|
-
for (const
|
|
1458
|
-
|
|
1462
|
+
for (const resourceLine of LinkedAffectedResources.getMarkdownLines({
|
|
1463
|
+
dashboardUrl: await DatabaseConfig.getDashboardUrl(),
|
|
1464
|
+
projectId: scheduledMaintenance.projectId!,
|
|
1465
|
+
resources: resources,
|
|
1466
|
+
})) {
|
|
1467
|
+
feedInfoInMarkdown += `${resourceLine}\n`;
|
|
1459
1468
|
}
|
|
1460
1469
|
|
|
1461
1470
|
feedInfoInMarkdown += `\n\n`;
|
|
@@ -1820,47 +1829,42 @@ ${(
|
|
|
1820
1829
|
shouldAddScheduledMaintenanceFeed = true;
|
|
1821
1830
|
}
|
|
1822
1831
|
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
)
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1832
|
+
/*
|
|
1833
|
+
* Any affected-resource list in the payload - not only monitors - is
|
|
1834
|
+
* a change to what the event affects. The event is read back rather
|
|
1835
|
+
* than the ids in the payload being looked up: the read is held to
|
|
1836
|
+
* this project, and it names the whole list the card now shows.
|
|
1837
|
+
*/
|
|
1838
|
+
const affectedResourcesChanged: boolean =
|
|
1839
|
+
LinkedAffectedResources.getRelations(this.getModel()).some(
|
|
1840
|
+
(relation: LinkedAffectedResourceRelation): boolean => {
|
|
1841
|
+
const value: unknown = (
|
|
1842
|
+
onUpdate.updateBy.data as Record<string, unknown>
|
|
1843
|
+
)[relation.column];
|
|
1844
|
+
|
|
1845
|
+
return Array.isArray(value) && value.length > 0;
|
|
1846
|
+
},
|
|
1847
|
+
);
|
|
1835
1848
|
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
.
|
|
1839
|
-
return monitorId !== null;
|
|
1840
|
-
});
|
|
1849
|
+
if (affectedResourcesChanged && onUpdate.updateBy.props.tenantId) {
|
|
1850
|
+
const projectId: ObjectID = onUpdate.updateBy.props
|
|
1851
|
+
.tenantId as ObjectID;
|
|
1841
1852
|
|
|
1842
|
-
const
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
},
|
|
1849
|
-
limit: LIMIT_PER_PROJECT,
|
|
1850
|
-
skip: 0,
|
|
1851
|
-
props: {
|
|
1852
|
-
isRoot: true,
|
|
1853
|
-
},
|
|
1854
|
-
});
|
|
1853
|
+
const resources: Array<LinkedAffectedResource> =
|
|
1854
|
+
await LinkedAffectedResources.readForScheduledMaintenance({
|
|
1855
|
+
service: this,
|
|
1856
|
+
projectId: projectId,
|
|
1857
|
+
scheduledMaintenanceId: scheduledMaintenanceId,
|
|
1858
|
+
});
|
|
1855
1859
|
|
|
1856
|
-
if (
|
|
1860
|
+
if (resources.length > 0) {
|
|
1857
1861
|
feedInfoInMarkdown += `\n\n**Resources Affected**:
|
|
1858
1862
|
|
|
1859
|
-
${
|
|
1860
|
-
.
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1863
|
+
${LinkedAffectedResources.getMarkdownLines({
|
|
1864
|
+
dashboardUrl: await DatabaseConfig.getDashboardUrl(),
|
|
1865
|
+
projectId: projectId,
|
|
1866
|
+
resources: resources,
|
|
1867
|
+
}).join("\n")}
|
|
1864
1868
|
`;
|
|
1865
1869
|
|
|
1866
1870
|
shouldAddScheduledMaintenanceFeed = true;
|
|
@@ -10,6 +10,8 @@ import Markdown, { MarkdownContentType } from "../Types/Markdown";
|
|
|
10
10
|
import CallService from "./CallService";
|
|
11
11
|
import DatabaseService from "./DatabaseService";
|
|
12
12
|
import IncidentService from "./IncidentService";
|
|
13
|
+
import LinkedAffectedResources from "../Utils/AffectedResources/LinkedAffectedResources";
|
|
14
|
+
import SeriesLabelDisplay from "../../Types/Monitor/SeriesContext/SeriesLabelDisplay";
|
|
13
15
|
import IncidentSeverityService from "./IncidentSeverityService";
|
|
14
16
|
import MailService from "./MailService";
|
|
15
17
|
import ProjectCallSMSConfigService from "./ProjectCallSMSConfigService";
|
|
@@ -983,15 +985,10 @@ export class Service extends DatabaseService<Model> {
|
|
|
983
985
|
incidentNumber: true,
|
|
984
986
|
incidentNumberWithPrefix: true,
|
|
985
987
|
/*
|
|
986
|
-
* AcknowledgeIncident.hbs
|
|
987
|
-
*
|
|
988
|
-
*
|
|
989
|
-
* alert's Root Cause.
|
|
988
|
+
* No relations here: AcknowledgeIncident.hbs's Resources Affected
|
|
989
|
+
* row is built from every resource the incident is linked to,
|
|
990
|
+
* which generateEmailTemplateForIncidentCreated reads itself.
|
|
990
991
|
*/
|
|
991
|
-
monitors: {
|
|
992
|
-
_id: true,
|
|
993
|
-
name: true,
|
|
994
|
-
},
|
|
995
992
|
},
|
|
996
993
|
});
|
|
997
994
|
}
|
|
@@ -1031,11 +1028,13 @@ export class Service extends DatabaseService<Model> {
|
|
|
1031
1028
|
* select it.
|
|
1032
1029
|
*/
|
|
1033
1030
|
rootCause: true,
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1031
|
+
/*
|
|
1032
|
+
* The series a grouped monitor raised this alert for (the pod or
|
|
1033
|
+
* container), which the Resources Affected row names in place of
|
|
1034
|
+
* the monitor - as the alert owner emails do. The linked resources
|
|
1035
|
+
* themselves are read by generateEmailTemplateForAlertCreated.
|
|
1036
|
+
*/
|
|
1037
|
+
seriesLabels: true,
|
|
1039
1038
|
},
|
|
1040
1039
|
});
|
|
1041
1040
|
}
|
|
@@ -4613,7 +4612,22 @@ export class Service extends DatabaseService<Model> {
|
|
|
4613
4612
|
MarkdownContentType.Email,
|
|
4614
4613
|
),
|
|
4615
4614
|
alertSeverity: alert.alertSeverity!.name!,
|
|
4616
|
-
|
|
4615
|
+
/*
|
|
4616
|
+
* Every resource the alert's Affected Resources card lists. An SLO
|
|
4617
|
+
* burn-rate alert has no monitor, and this used to read "No resources
|
|
4618
|
+
* identified" beside a card naming its SLO.
|
|
4619
|
+
*/
|
|
4620
|
+
resourcesAffected: LinkedAffectedResources.getText({
|
|
4621
|
+
resources: await LinkedAffectedResources.readForAlert({
|
|
4622
|
+
service: AlertService,
|
|
4623
|
+
projectId: alert.projectId!,
|
|
4624
|
+
alertId: alert.id!,
|
|
4625
|
+
}),
|
|
4626
|
+
seriesSummary: SeriesLabelDisplay.buildInlineSummary(
|
|
4627
|
+
alert.seriesLabels,
|
|
4628
|
+
),
|
|
4629
|
+
fallback: "No resources identified",
|
|
4630
|
+
}),
|
|
4617
4631
|
rootCause: await Markdown.convertToHTML(
|
|
4618
4632
|
alert.rootCause || "No root cause identified for this alert",
|
|
4619
4633
|
MarkdownContentType.Email,
|
|
@@ -4664,15 +4678,15 @@ export class Service extends DatabaseService<Model> {
|
|
|
4664
4678
|
MarkdownContentType.Email,
|
|
4665
4679
|
),
|
|
4666
4680
|
incidentSeverity: incident.incidentSeverity!.name!,
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
4681
|
+
// Every resource the incident's Affected Resources card lists.
|
|
4682
|
+
resourcesAffected: LinkedAffectedResources.getText({
|
|
4683
|
+
resources: await LinkedAffectedResources.readForIncident({
|
|
4684
|
+
service: IncidentService,
|
|
4685
|
+
projectId: incident.projectId!,
|
|
4686
|
+
incidentId: incident.id!,
|
|
4687
|
+
}),
|
|
4688
|
+
fallback: "No resources identified",
|
|
4689
|
+
}),
|
|
4676
4690
|
rootCause: await Markdown.convertToHTML(
|
|
4677
4691
|
incident.rootCause || "No root cause identified for this incident",
|
|
4678
4692
|
MarkdownContentType.Email,
|
|
@@ -4762,18 +4776,15 @@ export class Service extends DatabaseService<Model> {
|
|
|
4762
4776
|
})
|
|
4763
4777
|
: [];
|
|
4764
4778
|
|
|
4765
|
-
//
|
|
4766
|
-
const
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
monitorNames.size > 0
|
|
4775
|
-
? Array.from(monitorNames).join(", ")
|
|
4776
|
-
: "No resources identified";
|
|
4779
|
+
// Every resource any alert in the episode is linked to, each named once.
|
|
4780
|
+
const resourcesAffected: string = LinkedAffectedResources.getText({
|
|
4781
|
+
resources: await LinkedAffectedResources.readForAlerts({
|
|
4782
|
+
service: AlertService,
|
|
4783
|
+
projectId: alertEpisode.projectId!,
|
|
4784
|
+
alertIds: alertIds,
|
|
4785
|
+
}),
|
|
4786
|
+
fallback: "No resources identified",
|
|
4787
|
+
});
|
|
4777
4788
|
|
|
4778
4789
|
// Build alerts list HTML with proper email styling
|
|
4779
4790
|
let alertsListHtml: string = "";
|
|
@@ -4933,24 +4944,15 @@ export class Service extends DatabaseService<Model> {
|
|
|
4933
4944
|
})
|
|
4934
4945
|
: [];
|
|
4935
4946
|
|
|
4936
|
-
|
|
4937
|
-
|
|
4938
|
-
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
monitorNames.add(monitor.name);
|
|
4946
|
-
}
|
|
4947
|
-
}
|
|
4948
|
-
}
|
|
4949
|
-
|
|
4950
|
-
const resourcesAffected: string =
|
|
4951
|
-
monitorNames.size > 0
|
|
4952
|
-
? Array.from(monitorNames).join(", ")
|
|
4953
|
-
: "No resources identified";
|
|
4947
|
+
// Every resource any incident in the episode is linked to, each named once.
|
|
4948
|
+
const resourcesAffected: string = LinkedAffectedResources.getText({
|
|
4949
|
+
resources: await LinkedAffectedResources.readForIncidents({
|
|
4950
|
+
service: IncidentService,
|
|
4951
|
+
projectId: incidentEpisode.projectId!,
|
|
4952
|
+
incidentIds: incidentIds,
|
|
4953
|
+
}),
|
|
4954
|
+
fallback: "No resources identified",
|
|
4955
|
+
});
|
|
4954
4956
|
|
|
4955
4957
|
// Build incidents list HTML with proper email styling
|
|
4956
4958
|
let incidentsListHtml: string = "";
|