@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.
- 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/MetricService.ts +1 -1
- package/Server/Services/ScheduledMaintenanceService.ts +50 -46
- package/Server/Services/UserNotificationRuleService.ts +55 -53
- package/Server/Services/WorkspaceNotificationSummaryService.ts +38 -20
- package/Server/Types/Database/QueryHelper.ts +54 -1
- package/Server/Utils/AI/Toolbox/MetricTools.ts +13 -2
- package/Server/Utils/AI/Toolbox/ResourceTools.ts +12 -1
- package/Server/Utils/AffectedResources/LinkedAffectedResources.ts +576 -0
- package/Server/Utils/Alert/AlertStateChangeAuthorization.ts +129 -0
- package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +1 -1
- package/Server/Utils/Inventory/InventoryOverviewAggregation.ts +111 -0
- package/Server/Utils/Monitor/Criteria/CompareCriteria.ts +33 -0
- package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +4 -3
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +3 -1
- 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/NetworkTopologyExplorerSearch.test.tsx +1267 -0
- package/Tests/App/Dashboard/ResourceOverviewTileTooltips.test.tsx +4 -1
- package/Tests/App/Dashboard/RumOverviewPage.test.tsx +127 -0
- package/Tests/App/Dashboard/ServiceMapDatabaseLink.test.tsx +201 -15
- package/Tests/App/Dashboard/ServiceMapGraph.test.tsx +508 -36
- package/Tests/App/Dashboard/SiteSearchBox.test.tsx +2110 -0
- package/Tests/App/Dashboard/TelemetryMetricsSignals.test.ts +168 -8
- 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/MetricServiceAggregate.test.ts +12 -0
- package/Tests/Server/Services/SavedViewDefaults.test.ts +325 -0
- package/Tests/Server/Services/SloAffectedResourceCreatedFeed.test.ts +92 -36
- package/Tests/Server/Services/StatusPageCertificateService.test.ts +169 -0
- package/Tests/Server/Services/TeamComplianceSettingService.test.ts +191 -0
- package/Tests/Server/Types/Database/QueryHelperSearchAllWords.test.ts +651 -0
- package/Tests/Server/Utils/AI/AlertWriteTools.test.ts +256 -0
- package/Tests/Server/Utils/AI/IncidentWriteTools.test.ts +482 -0
- package/Tests/Server/Utils/AffectedResources/LinkedAffectedResources.test.ts +858 -0
- package/Tests/Server/Utils/Alert/AlertStateChangeAuthorization.test.ts +1172 -0
- package/Tests/Server/Utils/Monitor/Criteria/CompareCriteria.test.ts +54 -0
- package/Tests/Server/Utils/Monitor/MetricDecimalThreshold.test.ts +417 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorAffectedResourceEdgeCases.test.ts +96 -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/BaseDatabase/AggregationType.test.ts +5 -1
- package/Tests/Types/Dashboard/KubernetesDashboardTemplate.test.ts +184 -0
- package/Tests/Types/Dashboard/KubernetesDashboardTemplates.test.ts +107 -0
- package/Tests/Types/Monitor/KubernetesAlertTemplates.test.ts +8 -14
- package/Tests/Types/Monitor/KubernetesMetricCatalog.test.ts +4 -3
- package/Tests/Types/Monitor/Utils/KubernetesAgentEmittedMetrics.ts +27 -6
- package/Tests/Types/Topology/TopologyTypeRules.test.ts +176 -0
- package/Tests/UI/Rum/ChunkLoader.test.ts +76 -0
- package/Tests/UI/Rum/ReplayRailDetail.test.tsx +53 -0
- package/Tests/UI/Rum/ReplaySignals.test.ts +54 -0
- package/Tests/Utils/Dashboard/KubernetesClusterCpuTileRepair.test.ts +370 -0
- package/Tests/Utils/Incident/IncidentFromAlerts.test.ts +302 -0
- package/Tests/Utils/Rules/MonitorTypeRuleCriteria.test.ts +181 -0
- package/Types/BaseDatabase/AggregationType.ts +7 -0
- package/Types/Dashboard/DashboardTemplates.ts +20 -7
- package/Types/Incident/IncidentAlertLink.ts +19 -0
- package/Types/Rum/SessionReplayCustomEvents.ts +29 -3
- package/Types/Rum/WebVitals.ts +13 -0
- package/Types/Telemetry/InventoryLiveness.ts +26 -0
- package/Types/Topology/TopologyApi.ts +428 -0
- package/Types/Topology/TopologyTypeRules.ts +191 -0
- package/UI/Components/Input/Input.tsx +11 -0
- package/Utils/Dashboard/KubernetesClusterCpuTileRepair.ts +147 -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/MetricService.js +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/Types/Database/QueryHelper.js +47 -1
- package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/MetricTools.js +13 -2
- package/build/dist/Server/Utils/AI/Toolbox/MetricTools.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/ResourceTools.js +12 -1
- package/build/dist/Server/Utils/AI/Toolbox/ResourceTools.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/AnalyticsDatabase/StatementGenerator.js +1 -1
- 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/Monitor/Criteria/CompareCriteria.js +31 -0
- package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- 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/BaseDatabase/AggregationType.js +7 -0
- package/build/dist/Types/BaseDatabase/AggregationType.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardTemplates.js +20 -7
- package/build/dist/Types/Dashboard/DashboardTemplates.js.map +1 -1
- package/build/dist/Types/Incident/IncidentAlertLink.js +17 -0
- package/build/dist/Types/Incident/IncidentAlertLink.js.map +1 -1
- package/build/dist/Types/Rum/SessionReplayCustomEvents.js.map +1 -1
- package/build/dist/Types/Rum/WebVitals.js +12 -0
- package/build/dist/Types/Rum/WebVitals.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/UI/Components/Input/Input.js +1 -1
- package/build/dist/UI/Components/Input/Input.js.map +1 -1
- package/build/dist/Utils/Dashboard/KubernetesClusterCpuTileRepair.js +110 -0
- package/build/dist/Utils/Dashboard/KubernetesClusterCpuTileRepair.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
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { describe, expect, test } from "@jest/globals";
|
|
2
2
|
import IncidentFromAlerts, {
|
|
3
|
+
AlertForAcknowledgement,
|
|
3
4
|
AlertForIncidentPrefill,
|
|
5
|
+
AlertsToAcknowledge,
|
|
6
|
+
AlertStateForAcknowledgement,
|
|
4
7
|
INCIDENT_PREFILL_RESOURCE_KEYS,
|
|
5
8
|
IncidentPrefillFromAlerts,
|
|
6
9
|
NamedResource,
|
|
@@ -694,3 +697,302 @@ describe("IncidentFromAlerts", () => {
|
|
|
694
697
|
});
|
|
695
698
|
});
|
|
696
699
|
});
|
|
700
|
+
|
|
701
|
+
/*
|
|
702
|
+
* Which alerts the create page offers to acknowledge as the incident is
|
|
703
|
+
* declared. States are compared by order, as the server does: anything at or
|
|
704
|
+
* after the project's Acknowledged state (Resolved, or a custom state in
|
|
705
|
+
* between) has been acknowledged already. An alert whose state cannot be
|
|
706
|
+
* placed is offered - the server skips it if it turns out to be acknowledged.
|
|
707
|
+
*/
|
|
708
|
+
describe("IncidentFromAlerts.getAlertsToAcknowledge", () => {
|
|
709
|
+
const CREATED: AlertStateForAcknowledgement = {
|
|
710
|
+
id: "state-created",
|
|
711
|
+
order: 1,
|
|
712
|
+
isAcknowledgedState: false,
|
|
713
|
+
};
|
|
714
|
+
const ACKNOWLEDGED: AlertStateForAcknowledgement = {
|
|
715
|
+
id: "state-acknowledged",
|
|
716
|
+
order: 2,
|
|
717
|
+
isAcknowledgedState: true,
|
|
718
|
+
};
|
|
719
|
+
const RESOLVED: AlertStateForAcknowledgement = {
|
|
720
|
+
id: "state-resolved",
|
|
721
|
+
order: 3,
|
|
722
|
+
isAcknowledgedState: false,
|
|
723
|
+
};
|
|
724
|
+
|
|
725
|
+
const DEFAULT_ALERT_STATES: Array<AlertStateForAcknowledgement> = [
|
|
726
|
+
CREATED,
|
|
727
|
+
ACKNOWLEDGED,
|
|
728
|
+
RESOLVED,
|
|
729
|
+
];
|
|
730
|
+
|
|
731
|
+
type AlertInStateFunction = (
|
|
732
|
+
id: string,
|
|
733
|
+
currentAlertStateId?: string | undefined,
|
|
734
|
+
) => AlertForAcknowledgement;
|
|
735
|
+
|
|
736
|
+
const alertIn: AlertInStateFunction = (
|
|
737
|
+
id: string,
|
|
738
|
+
currentAlertStateId?: string | undefined,
|
|
739
|
+
): AlertForAcknowledgement => {
|
|
740
|
+
return { id: id, currentAlertStateId: currentAlertStateId };
|
|
741
|
+
};
|
|
742
|
+
|
|
743
|
+
type GetFunction = (
|
|
744
|
+
alerts: Array<AlertForAcknowledgement>,
|
|
745
|
+
alertStates?: Array<AlertStateForAcknowledgement>,
|
|
746
|
+
) => AlertsToAcknowledge | null;
|
|
747
|
+
|
|
748
|
+
const get: GetFunction = (
|
|
749
|
+
alerts: Array<AlertForAcknowledgement>,
|
|
750
|
+
alertStates?: Array<AlertStateForAcknowledgement>,
|
|
751
|
+
): AlertsToAcknowledge | null => {
|
|
752
|
+
return IncidentFromAlerts.getAlertsToAcknowledge({
|
|
753
|
+
alerts: alerts,
|
|
754
|
+
alertStates: alertStates || DEFAULT_ALERT_STATES,
|
|
755
|
+
});
|
|
756
|
+
};
|
|
757
|
+
|
|
758
|
+
describe("when nothing could be acknowledged", () => {
|
|
759
|
+
test("is null when the project has no alert states at all", () => {
|
|
760
|
+
expect(get([alertIn("a1", CREATED.id)], [])).toBeNull();
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
test("is null when no state is the Acknowledged state", () => {
|
|
764
|
+
expect(
|
|
765
|
+
get(
|
|
766
|
+
[alertIn("a1", CREATED.id)],
|
|
767
|
+
[CREATED, { ...ACKNOWLEDGED, isAcknowledgedState: false }, RESOLVED],
|
|
768
|
+
),
|
|
769
|
+
).toBeNull();
|
|
770
|
+
});
|
|
771
|
+
|
|
772
|
+
test("is null when the Acknowledged flag is missing rather than false", () => {
|
|
773
|
+
expect(
|
|
774
|
+
get(
|
|
775
|
+
[alertIn("a1", CREATED.id)],
|
|
776
|
+
[
|
|
777
|
+
CREATED,
|
|
778
|
+
{ id: ACKNOWLEDGED.id, order: ACKNOWLEDGED.order },
|
|
779
|
+
RESOLVED,
|
|
780
|
+
],
|
|
781
|
+
),
|
|
782
|
+
).toBeNull();
|
|
783
|
+
});
|
|
784
|
+
|
|
785
|
+
test("is null when the Acknowledged state has no order to compare by", () => {
|
|
786
|
+
expect(
|
|
787
|
+
get(
|
|
788
|
+
[alertIn("a1", CREATED.id)],
|
|
789
|
+
[CREATED, { ...ACKNOWLEDGED, order: undefined }, RESOLVED],
|
|
790
|
+
),
|
|
791
|
+
).toBeNull();
|
|
792
|
+
});
|
|
793
|
+
|
|
794
|
+
test("is null without an Acknowledged state even when there are no alerts", () => {
|
|
795
|
+
expect(get([], [CREATED, RESOLVED])).toBeNull();
|
|
796
|
+
});
|
|
797
|
+
});
|
|
798
|
+
|
|
799
|
+
describe("comparing by order", () => {
|
|
800
|
+
test("offers an alert whose state comes before Acknowledged", () => {
|
|
801
|
+
expect(get([alertIn("a1", CREATED.id)])).toEqual({
|
|
802
|
+
alertIds: ["a1"],
|
|
803
|
+
alreadyAcknowledgedCount: 0,
|
|
804
|
+
});
|
|
805
|
+
});
|
|
806
|
+
|
|
807
|
+
test("leaves out an alert that is in the Acknowledged state itself", () => {
|
|
808
|
+
expect(get([alertIn("a1", ACKNOWLEDGED.id)])).toEqual({
|
|
809
|
+
alertIds: [],
|
|
810
|
+
alreadyAcknowledgedCount: 1,
|
|
811
|
+
});
|
|
812
|
+
});
|
|
813
|
+
|
|
814
|
+
test("leaves out an alert that is resolved", () => {
|
|
815
|
+
expect(get([alertIn("a1", RESOLVED.id)])).toEqual({
|
|
816
|
+
alertIds: [],
|
|
817
|
+
alreadyAcknowledgedCount: 1,
|
|
818
|
+
});
|
|
819
|
+
});
|
|
820
|
+
|
|
821
|
+
test("treats a custom state between Acknowledged and Resolved as acknowledged already", () => {
|
|
822
|
+
const investigating: AlertStateForAcknowledgement = {
|
|
823
|
+
id: "state-investigating",
|
|
824
|
+
order: 3,
|
|
825
|
+
};
|
|
826
|
+
const resolved: AlertStateForAcknowledgement = {
|
|
827
|
+
id: "state-resolved",
|
|
828
|
+
order: 4,
|
|
829
|
+
};
|
|
830
|
+
|
|
831
|
+
expect(
|
|
832
|
+
get(
|
|
833
|
+
[alertIn("a1", investigating.id), alertIn("a2", resolved.id)],
|
|
834
|
+
[CREATED, ACKNOWLEDGED, investigating, resolved],
|
|
835
|
+
),
|
|
836
|
+
).toEqual({ alertIds: [], alreadyAcknowledgedCount: 2 });
|
|
837
|
+
});
|
|
838
|
+
|
|
839
|
+
test("offers an alert in a custom state before Acknowledged", () => {
|
|
840
|
+
const triaged: AlertStateForAcknowledgement = {
|
|
841
|
+
id: "state-triaged",
|
|
842
|
+
order: 2,
|
|
843
|
+
};
|
|
844
|
+
const acknowledged: AlertStateForAcknowledgement = {
|
|
845
|
+
id: "state-acknowledged",
|
|
846
|
+
order: 3,
|
|
847
|
+
isAcknowledgedState: true,
|
|
848
|
+
};
|
|
849
|
+
|
|
850
|
+
expect(
|
|
851
|
+
get(
|
|
852
|
+
[alertIn("a1", triaged.id), alertIn("a2", CREATED.id)],
|
|
853
|
+
[CREATED, triaged, acknowledged, { ...RESOLVED, order: 4 }],
|
|
854
|
+
),
|
|
855
|
+
).toEqual({ alertIds: ["a1", "a2"], alreadyAcknowledgedCount: 0 });
|
|
856
|
+
});
|
|
857
|
+
|
|
858
|
+
test("compares by order, not by where the state sits in the list", () => {
|
|
859
|
+
// Listed newest-first: the list position must not decide anything.
|
|
860
|
+
const reversed: Array<AlertStateForAcknowledgement> = [
|
|
861
|
+
RESOLVED,
|
|
862
|
+
ACKNOWLEDGED,
|
|
863
|
+
CREATED,
|
|
864
|
+
];
|
|
865
|
+
|
|
866
|
+
expect(
|
|
867
|
+
get(
|
|
868
|
+
[
|
|
869
|
+
alertIn("a1", RESOLVED.id),
|
|
870
|
+
alertIn("a2", CREATED.id),
|
|
871
|
+
alertIn("a3", ACKNOWLEDGED.id),
|
|
872
|
+
],
|
|
873
|
+
reversed,
|
|
874
|
+
),
|
|
875
|
+
).toEqual({ alertIds: ["a2"], alreadyAcknowledgedCount: 2 });
|
|
876
|
+
});
|
|
877
|
+
|
|
878
|
+
test("an Acknowledged order of 0 is still an order", () => {
|
|
879
|
+
const acknowledgedAtZero: AlertStateForAcknowledgement = {
|
|
880
|
+
id: "state-acknowledged",
|
|
881
|
+
order: 0,
|
|
882
|
+
isAcknowledgedState: true,
|
|
883
|
+
};
|
|
884
|
+
const before: AlertStateForAcknowledgement = {
|
|
885
|
+
id: "state-before",
|
|
886
|
+
order: -1,
|
|
887
|
+
};
|
|
888
|
+
|
|
889
|
+
expect(
|
|
890
|
+
get(
|
|
891
|
+
[
|
|
892
|
+
alertIn("a1", acknowledgedAtZero.id),
|
|
893
|
+
alertIn("a2", before.id),
|
|
894
|
+
alertIn("a3", RESOLVED.id),
|
|
895
|
+
],
|
|
896
|
+
[before, acknowledgedAtZero, RESOLVED],
|
|
897
|
+
),
|
|
898
|
+
).toEqual({ alertIds: ["a2"], alreadyAcknowledgedCount: 2 });
|
|
899
|
+
});
|
|
900
|
+
});
|
|
901
|
+
|
|
902
|
+
describe("alerts whose state cannot be placed", () => {
|
|
903
|
+
test("offers an alert whose current state is not one of the project's states", () => {
|
|
904
|
+
expect(get([alertIn("a1", "state-from-another-project")])).toEqual({
|
|
905
|
+
alertIds: ["a1"],
|
|
906
|
+
alreadyAcknowledgedCount: 0,
|
|
907
|
+
});
|
|
908
|
+
});
|
|
909
|
+
|
|
910
|
+
test("offers an alert with no current state id", () => {
|
|
911
|
+
expect(get([alertIn("a1", undefined), alertIn("a2", "")])).toEqual({
|
|
912
|
+
alertIds: ["a1", "a2"],
|
|
913
|
+
alreadyAcknowledgedCount: 0,
|
|
914
|
+
});
|
|
915
|
+
});
|
|
916
|
+
|
|
917
|
+
test("offers an alert whose current state has no order", () => {
|
|
918
|
+
const unordered: AlertStateForAcknowledgement = {
|
|
919
|
+
id: "state-unordered",
|
|
920
|
+
};
|
|
921
|
+
|
|
922
|
+
expect(
|
|
923
|
+
get(
|
|
924
|
+
[alertIn("a1", unordered.id)],
|
|
925
|
+
[CREATED, ACKNOWLEDGED, RESOLVED, unordered],
|
|
926
|
+
),
|
|
927
|
+
).toEqual({ alertIds: ["a1"], alreadyAcknowledgedCount: 0 });
|
|
928
|
+
});
|
|
929
|
+
});
|
|
930
|
+
|
|
931
|
+
test("matches state ids case-insensitively and ignoring surrounding spaces", () => {
|
|
932
|
+
const upperAcknowledged: AlertStateForAcknowledgement = {
|
|
933
|
+
id: "AAAAAAAA-AAAA-4AAA-8AAA-000000000002",
|
|
934
|
+
order: 2,
|
|
935
|
+
isAcknowledgedState: true,
|
|
936
|
+
};
|
|
937
|
+
const upperCreated: AlertStateForAcknowledgement = {
|
|
938
|
+
id: "AAAAAAAA-AAAA-4AAA-8AAA-000000000001",
|
|
939
|
+
order: 1,
|
|
940
|
+
};
|
|
941
|
+
|
|
942
|
+
expect(
|
|
943
|
+
get(
|
|
944
|
+
[
|
|
945
|
+
alertIn("a1", " aaaaaaaa-aaaa-4aaa-8aaa-000000000002 "),
|
|
946
|
+
alertIn("a2", "aaaaaaaa-aaaa-4aaa-8aaa-000000000001"),
|
|
947
|
+
],
|
|
948
|
+
[upperCreated, upperAcknowledged],
|
|
949
|
+
),
|
|
950
|
+
).toEqual({ alertIds: ["a2"], alreadyAcknowledgedCount: 1 });
|
|
951
|
+
});
|
|
952
|
+
|
|
953
|
+
test("keeps the alerts in the order they were given and counts the rest", () => {
|
|
954
|
+
const result: AlertsToAcknowledge | null = get([
|
|
955
|
+
alertIn("a5", CREATED.id),
|
|
956
|
+
alertIn("a1", ACKNOWLEDGED.id),
|
|
957
|
+
alertIn("a4", undefined),
|
|
958
|
+
alertIn("a2", RESOLVED.id),
|
|
959
|
+
alertIn("a3", CREATED.id),
|
|
960
|
+
alertIn("a6", RESOLVED.id),
|
|
961
|
+
]);
|
|
962
|
+
|
|
963
|
+
expect(result).toEqual({
|
|
964
|
+
alertIds: ["a5", "a4", "a3"],
|
|
965
|
+
alreadyAcknowledgedCount: 3,
|
|
966
|
+
});
|
|
967
|
+
});
|
|
968
|
+
|
|
969
|
+
test("returns the alert ids exactly as given", () => {
|
|
970
|
+
expect(get([alertIn("ALERT-One", CREATED.id)])!.alertIds).toEqual([
|
|
971
|
+
"ALERT-One",
|
|
972
|
+
]);
|
|
973
|
+
});
|
|
974
|
+
|
|
975
|
+
test("is empty, not null, when there are no alerts", () => {
|
|
976
|
+
expect(get([])).toEqual({ alertIds: [], alreadyAcknowledgedCount: 0 });
|
|
977
|
+
});
|
|
978
|
+
|
|
979
|
+
test("does not change its input", () => {
|
|
980
|
+
const alerts: Array<AlertForAcknowledgement> = [
|
|
981
|
+
alertIn("a1", CREATED.id),
|
|
982
|
+
alertIn("a2", ACKNOWLEDGED.id),
|
|
983
|
+
];
|
|
984
|
+
const alertStates: Array<AlertStateForAcknowledgement> = [
|
|
985
|
+
RESOLVED,
|
|
986
|
+
CREATED,
|
|
987
|
+
ACKNOWLEDGED,
|
|
988
|
+
];
|
|
989
|
+
|
|
990
|
+
get(alerts, alertStates);
|
|
991
|
+
|
|
992
|
+
expect(alerts).toEqual([
|
|
993
|
+
{ id: "a1", currentAlertStateId: CREATED.id },
|
|
994
|
+
{ id: "a2", currentAlertStateId: ACKNOWLEDGED.id },
|
|
995
|
+
]);
|
|
996
|
+
expect(alertStates).toEqual([RESOLVED, CREATED, ACKNOWLEDGED]);
|
|
997
|
+
});
|
|
998
|
+
});
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getMonitorTypeCriteriaValidationError,
|
|
3
|
+
isMonitorTypeCriteriaValue,
|
|
4
|
+
} from "../../../Utils/Rules/MonitorTypeRuleCriteria";
|
|
5
|
+
import MonitorType from "../../../Types/Monitor/MonitorType";
|
|
6
|
+
import {
|
|
7
|
+
RuleCriteriaFilter,
|
|
8
|
+
RuleCriteriaOperator,
|
|
9
|
+
RuleCriteriaValue,
|
|
10
|
+
} from "../../../Types/Rules/RuleCriteria";
|
|
11
|
+
import { describe, expect, test } from "@jest/globals";
|
|
12
|
+
|
|
13
|
+
/*
|
|
14
|
+
* The gate that decides whether a rule's `monitorType` criterion may be saved.
|
|
15
|
+
* DatabaseService runs it on every write and the SLO monitor-rule engine runs
|
|
16
|
+
* it again when it matches monitors, so a criterion this lets through has to
|
|
17
|
+
* be one the engine can actually evaluate: a monitor type is an enum choice,
|
|
18
|
+
* which means an exact match either way and nothing else. A text operator
|
|
19
|
+
* slipping past here is a rule that silently matches nothing.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
function buildFilter(
|
|
23
|
+
operator: RuleCriteriaOperator,
|
|
24
|
+
value: RuleCriteriaValue,
|
|
25
|
+
): RuleCriteriaFilter {
|
|
26
|
+
return { field: "monitorType", operator: operator, value: value };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const OPERATOR_ERROR: string =
|
|
30
|
+
"Monitor type criteria only support Equals and Does not equal operators.";
|
|
31
|
+
const VALUE_ERROR: string =
|
|
32
|
+
"Monitor type criteria require a valid monitor type.";
|
|
33
|
+
|
|
34
|
+
describe("isMonitorTypeCriteriaValue", () => {
|
|
35
|
+
test("accepts every monitor type the product defines", () => {
|
|
36
|
+
const allTypes: Array<string> = Object.values(MonitorType);
|
|
37
|
+
expect(allTypes.length).toBeGreaterThan(0);
|
|
38
|
+
for (const monitorType of allTypes) {
|
|
39
|
+
expect(isMonitorTypeCriteriaValue(monitorType)).toBe(true);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("rejects a string that is not a monitor type", () => {
|
|
44
|
+
expect(isMonitorTypeCriteriaValue("Website")).toBe(
|
|
45
|
+
Object.values(MonitorType).includes("Website" as MonitorType),
|
|
46
|
+
);
|
|
47
|
+
expect(isMonitorTypeCriteriaValue("NotAMonitorType")).toBe(false);
|
|
48
|
+
expect(isMonitorTypeCriteriaValue("")).toBe(false);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("is case- and whitespace-sensitive, as an enum comparison must be", () => {
|
|
52
|
+
const [first] = Object.values(MonitorType) as Array<string>;
|
|
53
|
+
expect(isMonitorTypeCriteriaValue(first!)).toBe(true);
|
|
54
|
+
expect(isMonitorTypeCriteriaValue(first!.toLowerCase())).toBe(
|
|
55
|
+
first === first!.toLowerCase(),
|
|
56
|
+
);
|
|
57
|
+
expect(isMonitorTypeCriteriaValue(` ${first!}`)).toBe(false);
|
|
58
|
+
expect(isMonitorTypeCriteriaValue(`${first!} `)).toBe(false);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("rejects every non-string value a criterion can carry", () => {
|
|
62
|
+
const nonStrings: Array<unknown> = [
|
|
63
|
+
undefined,
|
|
64
|
+
null,
|
|
65
|
+
0,
|
|
66
|
+
1,
|
|
67
|
+
true,
|
|
68
|
+
false,
|
|
69
|
+
[],
|
|
70
|
+
[Object.values(MonitorType)[0]],
|
|
71
|
+
{},
|
|
72
|
+
{ value: Object.values(MonitorType)[0] },
|
|
73
|
+
];
|
|
74
|
+
for (const value of nonStrings) {
|
|
75
|
+
expect(isMonitorTypeCriteriaValue(value)).toBe(false);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("does not accept an inherited Object property as a monitor type", () => {
|
|
80
|
+
// The lookup is a Set, not a plain object, so "toString" is not a member.
|
|
81
|
+
expect(isMonitorTypeCriteriaValue("toString")).toBe(false);
|
|
82
|
+
expect(isMonitorTypeCriteriaValue("constructor")).toBe(false);
|
|
83
|
+
expect(isMonitorTypeCriteriaValue("__proto__")).toBe(false);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe("getMonitorTypeCriteriaValidationError", () => {
|
|
88
|
+
const validType: MonitorType = Object.values(MonitorType)[0] as MonitorType;
|
|
89
|
+
|
|
90
|
+
test("passes Equals and NotEquals against a real monitor type", () => {
|
|
91
|
+
for (const operator of [
|
|
92
|
+
RuleCriteriaOperator.Equals,
|
|
93
|
+
RuleCriteriaOperator.NotEquals,
|
|
94
|
+
]) {
|
|
95
|
+
expect(
|
|
96
|
+
getMonitorTypeCriteriaValidationError(buildFilter(operator, validType)),
|
|
97
|
+
).toBeNull();
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("passes every monitor type under Equals", () => {
|
|
102
|
+
for (const monitorType of Object.values(MonitorType)) {
|
|
103
|
+
expect(
|
|
104
|
+
getMonitorTypeCriteriaValidationError(
|
|
105
|
+
buildFilter(RuleCriteriaOperator.Equals, monitorType),
|
|
106
|
+
),
|
|
107
|
+
).toBeNull();
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("rejects every operator other than Equals and NotEquals", () => {
|
|
112
|
+
const rejected: Array<RuleCriteriaOperator> = Object.values(
|
|
113
|
+
RuleCriteriaOperator,
|
|
114
|
+
).filter((operator: RuleCriteriaOperator) => {
|
|
115
|
+
return (
|
|
116
|
+
operator !== RuleCriteriaOperator.Equals &&
|
|
117
|
+
operator !== RuleCriteriaOperator.NotEquals
|
|
118
|
+
);
|
|
119
|
+
});
|
|
120
|
+
expect(rejected.length).toBeGreaterThan(0);
|
|
121
|
+
|
|
122
|
+
for (const operator of rejected) {
|
|
123
|
+
expect(
|
|
124
|
+
getMonitorTypeCriteriaValidationError(buildFilter(operator, validType)),
|
|
125
|
+
).toBe(OPERATOR_ERROR);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("reports the operator first when both the operator and the value are wrong", () => {
|
|
130
|
+
expect(
|
|
131
|
+
getMonitorTypeCriteriaValidationError(
|
|
132
|
+
buildFilter(RuleCriteriaOperator.Contains, "NotAMonitorType"),
|
|
133
|
+
),
|
|
134
|
+
).toBe(OPERATOR_ERROR);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("rejects a value that is not a monitor type under a supported operator", () => {
|
|
138
|
+
for (const operator of [
|
|
139
|
+
RuleCriteriaOperator.Equals,
|
|
140
|
+
RuleCriteriaOperator.NotEquals,
|
|
141
|
+
]) {
|
|
142
|
+
expect(
|
|
143
|
+
getMonitorTypeCriteriaValidationError(
|
|
144
|
+
buildFilter(operator, "NotAMonitorType"),
|
|
145
|
+
),
|
|
146
|
+
).toBe(VALUE_ERROR);
|
|
147
|
+
expect(
|
|
148
|
+
getMonitorTypeCriteriaValidationError(buildFilter(operator, "")),
|
|
149
|
+
).toBe(VALUE_ERROR);
|
|
150
|
+
/*
|
|
151
|
+
* An array is what the HasAnyOf-style operators carry; it is not a
|
|
152
|
+
* monitor type even when every member of it is one.
|
|
153
|
+
*/
|
|
154
|
+
expect(
|
|
155
|
+
getMonitorTypeCriteriaValidationError(
|
|
156
|
+
buildFilter(operator, [validType]),
|
|
157
|
+
),
|
|
158
|
+
).toBe(VALUE_ERROR);
|
|
159
|
+
expect(
|
|
160
|
+
getMonitorTypeCriteriaValidationError(buildFilter(operator, true)),
|
|
161
|
+
).toBe(VALUE_ERROR);
|
|
162
|
+
expect(
|
|
163
|
+
getMonitorTypeCriteriaValidationError(buildFilter(operator, 0)),
|
|
164
|
+
).toBe(VALUE_ERROR);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test("ignores the filter's field: the caller decides which field this applies to", () => {
|
|
169
|
+
expect(
|
|
170
|
+
getMonitorTypeCriteriaValidationError({
|
|
171
|
+
field: "somethingElse",
|
|
172
|
+
operator: RuleCriteriaOperator.Equals,
|
|
173
|
+
value: validType,
|
|
174
|
+
}),
|
|
175
|
+
).toBeNull();
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test("the two errors are distinct, so a form can say which one it is", () => {
|
|
179
|
+
expect(OPERATOR_ERROR).not.toBe(VALUE_ERROR);
|
|
180
|
+
});
|
|
181
|
+
});
|
|
@@ -15,6 +15,12 @@ enum AggregationType {
|
|
|
15
15
|
* over the raw column, which is the right thing for scalar columns.
|
|
16
16
|
*/
|
|
17
17
|
P50 = "P50",
|
|
18
|
+
/*
|
|
19
|
+
* The level Core Web Vitals are judged at: Google's good / poor
|
|
20
|
+
* thresholds for LCP, INP and CLS apply to the 75th percentile of page
|
|
21
|
+
* views, so a web-vital chart or monitor wants exactly this one.
|
|
22
|
+
*/
|
|
23
|
+
P75 = "P75",
|
|
18
24
|
P90 = "P90",
|
|
19
25
|
P95 = "P95",
|
|
20
26
|
P99 = "P99",
|
|
@@ -24,6 +30,7 @@ export default AggregationType;
|
|
|
24
30
|
|
|
25
31
|
export const PercentileAggregationLevels: Record<string, number> = {
|
|
26
32
|
[AggregationType.P50]: 0.5,
|
|
33
|
+
[AggregationType.P75]: 0.75,
|
|
27
34
|
[AggregationType.P90]: 0.9,
|
|
28
35
|
[AggregationType.P95]: 0.95,
|
|
29
36
|
[AggregationType.P99]: 0.99,
|
|
@@ -1742,6 +1742,18 @@ function createKubernetesDashboardConfig(): DashboardViewConfig {
|
|
|
1742
1742
|
* metrics and label them in cores ("2.3 cores"). The Kubernetes
|
|
1743
1743
|
* cluster overview page — which fetches `k8s.node.allocatable_cpu` —
|
|
1744
1744
|
* is where CPU is shown as a real "% of capacity".
|
|
1745
|
+
*
|
|
1746
|
+
* - "Cluster CPU (cores in use)" used to be a Value widget over
|
|
1747
|
+
* k8s.node.cpu.usage with `Sum` — the same trap as Pod Count. The
|
|
1748
|
+
* server sums every node's reading in each bucket and the Value widget
|
|
1749
|
+
* then sums the buckets, so a one-hour window of minute buckets and
|
|
1750
|
+
* 30s kubelet scrapes read 540 for three nodes on 1.5 cores each,
|
|
1751
|
+
* instead of 4.5. A Value widget can't express "sum across nodes at one
|
|
1752
|
+
* instant", so the tile is now the busiest node's peak (`Max`), which
|
|
1753
|
+
* is honest at any window and complements the per-node average in
|
|
1754
|
+
* row 1. Cluster-wide CPU against capacity lives on the cluster
|
|
1755
|
+
* overview page. Dashboards already created from this template are
|
|
1756
|
+
* repaired by the RepairKubernetesDashboardClusterCpuTile migration.
|
|
1745
1757
|
*/
|
|
1746
1758
|
const components: Array<DashboardBaseComponent> = [
|
|
1747
1759
|
// Row 0: Title
|
|
@@ -1875,22 +1887,23 @@ function createKubernetesDashboardConfig(): DashboardViewConfig {
|
|
|
1875
1887
|
}),
|
|
1876
1888
|
|
|
1877
1889
|
/*
|
|
1878
|
-
* Row 11-13:
|
|
1879
|
-
*
|
|
1890
|
+
* Row 11-13: busiest-node CPU tile and the network throughput chart.
|
|
1891
|
+
* This was a 0-100 CPU gauge, but the cores-valued
|
|
1880
1892
|
* `k8s.node.cpu.utilization` pinned it to nonsense (e.g. 711%) and
|
|
1881
1893
|
* the templated renderer can't divide by allocatable CPU to make a
|
|
1882
|
-
* real percentage
|
|
1883
|
-
*
|
|
1884
|
-
*
|
|
1894
|
+
* real percentage. It then became a `Sum` of cores, which multiplied
|
|
1895
|
+
* by the scrape count — see top-of-function comment. `Max` is the
|
|
1896
|
+
* highest reading any single node reported in the window. The old
|
|
1897
|
+
* "Memory Utilization" gauge over raw bytes is gone too.
|
|
1885
1898
|
*/
|
|
1886
1899
|
createValueComponent({
|
|
1887
|
-
title: "
|
|
1900
|
+
title: "Busiest Node CPU (cores, peak)",
|
|
1888
1901
|
top: 11,
|
|
1889
1902
|
left: 0,
|
|
1890
1903
|
width: 4,
|
|
1891
1904
|
metricConfig: {
|
|
1892
1905
|
metricName: "k8s.node.cpu.usage",
|
|
1893
|
-
aggregationType: MetricsAggregationType.
|
|
1906
|
+
aggregationType: MetricsAggregationType.Max,
|
|
1894
1907
|
},
|
|
1895
1908
|
trendDirection: DashboardValueTrendDirection.HigherIsWorse,
|
|
1896
1909
|
}),
|
|
@@ -12,6 +12,25 @@
|
|
|
12
12
|
*/
|
|
13
13
|
export const INCIDENT_ALERT_IDS_TO_LINK_KEY: string = "alertIdsToLink";
|
|
14
14
|
|
|
15
|
+
/*
|
|
16
|
+
* Declaring an incident from alerts does not, on its own, change the alerts:
|
|
17
|
+
* they keep escalating until somebody acknowledges them. Sending
|
|
18
|
+
* `miscDataProps[INCIDENT_ACKNOWLEDGE_ALERTS_TO_LINK_KEY] = true` along with
|
|
19
|
+
* INCIDENT_ALERT_IDS_TO_LINK_KEY also acknowledges them, as the declaring
|
|
20
|
+
* user, once they are linked - which is what stops their on-call escalation.
|
|
21
|
+
* Alerts already acknowledged (or past it) are left as they are, and the
|
|
22
|
+
* caller must be allowed to change the state of every alert that is not.
|
|
23
|
+
* Off unless asked for.
|
|
24
|
+
*
|
|
25
|
+
* One exception: when the incident is declared straight into an
|
|
26
|
+
* acknowledged or resolved state and the project's linked-alert switches
|
|
27
|
+
* already move linked alerts along with it, those alerts are left to the
|
|
28
|
+
* switches (one writer per alert) - acknowledged or resolved as the switch
|
|
29
|
+
* does it, not credited to the declaring user.
|
|
30
|
+
*/
|
|
31
|
+
export const INCIDENT_ACKNOWLEDGE_ALERTS_TO_LINK_KEY: string =
|
|
32
|
+
"acknowledgeAlertsToLink";
|
|
33
|
+
|
|
15
34
|
/*
|
|
16
35
|
* The create-incident page reads the alerts to declare an incident from out of
|
|
17
36
|
* this query string parameter, as a comma separated list of alert ids.
|
|
@@ -215,15 +215,27 @@ export type SessionReplayWebVitalMetric =
|
|
|
215
215
|
export type SessionReplayWebVitalRating = "good" | "needs-improvement" | "poor";
|
|
216
216
|
|
|
217
217
|
/*
|
|
218
|
-
*
|
|
219
|
-
*
|
|
220
|
-
|
|
218
|
+
* How the view an INP describes began: the document load ("hard") or a
|
|
219
|
+
* single-page-app route change ("soft").
|
|
220
|
+
*/
|
|
221
|
+
export type SessionReplayNavigationType = "hard" | "soft";
|
|
222
|
+
|
|
223
|
+
/* The input that produced an INP, as the browser classes it. */
|
|
224
|
+
export type SessionReplayInteractionType = "pointer" | "keyboard";
|
|
225
|
+
|
|
226
|
+
/*
|
|
227
|
+
* A web vital. LCP, FCP, CLS and TTFB are observed once per page load;
|
|
228
|
+
* INP once per VIEW (newer recorders), so a single-page app reports one
|
|
229
|
+
* INP for each route the user interacted on instead of one for the whole
|
|
230
|
+
* visit. Informational only: vitals never trigger an upload, and CLS's
|
|
231
|
+
* value is unitless while the others are milliseconds.
|
|
221
232
|
*/
|
|
222
233
|
export interface SessionReplayWebVitalPayload {
|
|
223
234
|
kind: "web-vital";
|
|
224
235
|
metric: SessionReplayWebVitalMetric;
|
|
225
236
|
value: number;
|
|
226
237
|
rating: SessionReplayWebVitalRating;
|
|
238
|
+
/* INP: the scrubbed URL of the view the interaction happened on. */
|
|
227
239
|
url?: string;
|
|
228
240
|
/*
|
|
229
241
|
* As on SessionReplayPerformanceBudgetPayload: when the measured entry
|
|
@@ -232,6 +244,20 @@ export interface SessionReplayWebVitalPayload {
|
|
|
232
244
|
* after the moment the number describes. Optional for older recorders.
|
|
233
245
|
*/
|
|
234
246
|
occurredAtUnixMs?: number;
|
|
247
|
+
|
|
248
|
+
/*
|
|
249
|
+
* INP attribution, all optional (older recorders and engines without
|
|
250
|
+
* the timing fields omit them). The three phases add up to roughly the
|
|
251
|
+
* value: waiting for the main thread, running the page's handlers, and
|
|
252
|
+
* rendering the next frame. The target is a structural selector built
|
|
253
|
+
* exactly as a click's is - never an attribute value or text.
|
|
254
|
+
*/
|
|
255
|
+
navigationType?: SessionReplayNavigationType;
|
|
256
|
+
interactionType?: SessionReplayInteractionType;
|
|
257
|
+
interactionTarget?: string;
|
|
258
|
+
inputDelayMs?: number;
|
|
259
|
+
processingDurationMs?: number;
|
|
260
|
+
presentationDelayMs?: number;
|
|
235
261
|
}
|
|
236
262
|
|
|
237
263
|
export type SessionReplayPerformancePayload =
|
package/Types/Rum/WebVitals.ts
CHANGED
|
@@ -86,6 +86,19 @@ export const WebVitalDefinitions: Array<WebVitalDefinition> = [
|
|
|
86
86
|
},
|
|
87
87
|
];
|
|
88
88
|
|
|
89
|
+
/*
|
|
90
|
+
* The metric attribute that names the route a web vital was measured on,
|
|
91
|
+
* tried in order for the RUM overview's per-route breakdown. `app.route`
|
|
92
|
+
* is what the Core Web Vitals docs tell instrumentation to set;
|
|
93
|
+
* `url.template` is OpenTelemetry's semantic-convention name for the same
|
|
94
|
+
* thing. Either must hold the route PATTERN ("/products/:id"), never the
|
|
95
|
+
* concrete URL - one series per product page is a cardinality problem.
|
|
96
|
+
*/
|
|
97
|
+
export const WebVitalRouteAttributeKeys: Array<string> = [
|
|
98
|
+
"app.route",
|
|
99
|
+
"url.template",
|
|
100
|
+
];
|
|
101
|
+
|
|
89
102
|
// A threshold the way a person would say it: "2.5 s", "800 ms", "0.1".
|
|
90
103
|
export function formatWebVitalThreshold(
|
|
91
104
|
value: number,
|
|
@@ -54,3 +54,29 @@ export const getInventoryLivenessState: (
|
|
|
54
54
|
|
|
55
55
|
return InventoryLiveness.Stale;
|
|
56
56
|
};
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* `getInventoryLivenessState` as a Postgres expression over the row aliased
|
|
60
|
+
* `tableAlias`, which must already be quoted (`"InventoryItem"`).
|
|
61
|
+
*
|
|
62
|
+
* The one SQL definition of the rule: the `inventoryStatus` virtual column
|
|
63
|
+
* selects and filters with it, and the Inventory Overview counts "Gone
|
|
64
|
+
* Quiet" with it, so the number on that tile and the rows the Stale facet
|
|
65
|
+
* returns cannot disagree about what stale means.
|
|
66
|
+
*
|
|
67
|
+
* Measured against the database clock. The function above floors elapsed
|
|
68
|
+
* minutes before applying inclusive thresholds — 30m59s is still Live and
|
|
69
|
+
* 24h00m59s is still Recent — and strict comparisons against the following
|
|
70
|
+
* minute preserve exactly those boundaries.
|
|
71
|
+
*/
|
|
72
|
+
export const getInventoryLivenessSql: (tableAlias: string) => string = (
|
|
73
|
+
tableAlias: string,
|
|
74
|
+
): string => {
|
|
75
|
+
return `CASE
|
|
76
|
+
WHEN ${tableAlias}."source" IS DISTINCT FROM '${EntitySource.Discovered}' THEN '${InventoryLiveness.NotTracked}'
|
|
77
|
+
WHEN ${tableAlias}."lastSeenAt" IS NULL THEN '${InventoryLiveness.Never}'
|
|
78
|
+
WHEN ${tableAlias}."lastSeenAt" > CURRENT_TIMESTAMP - INTERVAL '${INVENTORY_LIVE_WINDOW_MINUTES + 1} minutes' THEN '${InventoryLiveness.Live}'
|
|
79
|
+
WHEN ${tableAlias}."lastSeenAt" > CURRENT_TIMESTAMP - INTERVAL '${INVENTORY_STALE_AFTER_MINUTES + 1} minutes' THEN '${InventoryLiveness.Recent}'
|
|
80
|
+
ELSE '${InventoryLiveness.Stale}'
|
|
81
|
+
END`;
|
|
82
|
+
};
|