@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
|
@@ -983,4 +983,57 @@ describe("ReplayRailDetail performance wording", () => {
|
|
|
983
983
|
expect(screen.getByText("Long task")).toBeInTheDocument();
|
|
984
984
|
expect(screen.queryByText("long-task")).toBeNull();
|
|
985
985
|
});
|
|
986
|
+
|
|
987
|
+
/* Issue #3975: which view, which element, and where the time went. */
|
|
988
|
+
it("explains a slow INP: the view, the input, the element and the phase split", () => {
|
|
989
|
+
renderDetail(
|
|
990
|
+
performanceSignal({
|
|
991
|
+
kind: "web-vital",
|
|
992
|
+
durationMs: null,
|
|
993
|
+
budgetMs: null,
|
|
994
|
+
isOverBudget: true,
|
|
995
|
+
metric: "INP",
|
|
996
|
+
value: 480,
|
|
997
|
+
rating: "needs-improvement",
|
|
998
|
+
url: "https://shop.example.com/products",
|
|
999
|
+
atUnixMs: null,
|
|
1000
|
+
navigationType: "soft",
|
|
1001
|
+
interactionType: "pointer",
|
|
1002
|
+
interactionTarget: "nav > a.product-link",
|
|
1003
|
+
inputDelayMs: 5,
|
|
1004
|
+
processingDurationMs: 305,
|
|
1005
|
+
presentationDelayMs: 170,
|
|
1006
|
+
}),
|
|
1007
|
+
);
|
|
1008
|
+
|
|
1009
|
+
expect(
|
|
1010
|
+
screen.getByText("Route change (single-page app)"),
|
|
1011
|
+
).toBeInTheDocument();
|
|
1012
|
+
expect(screen.getByText("Pointer")).toBeInTheDocument();
|
|
1013
|
+
expect(screen.getByText("nav > a.product-link")).toBeInTheDocument();
|
|
1014
|
+
expect(screen.getByText("Input delay")).toBeInTheDocument();
|
|
1015
|
+
expect(screen.getByText("Processing")).toBeInTheDocument();
|
|
1016
|
+
expect(screen.getByText("Presentation")).toBeInTheDocument();
|
|
1017
|
+
expect(screen.getByText("305ms")).toBeInTheDocument();
|
|
1018
|
+
});
|
|
1019
|
+
|
|
1020
|
+
it("shows none of the INP rows for a recording that predates them", () => {
|
|
1021
|
+
renderDetail(
|
|
1022
|
+
performanceSignal({
|
|
1023
|
+
kind: "web-vital",
|
|
1024
|
+
durationMs: null,
|
|
1025
|
+
budgetMs: null,
|
|
1026
|
+
isOverBudget: false,
|
|
1027
|
+
metric: "INP",
|
|
1028
|
+
value: 120,
|
|
1029
|
+
rating: "good",
|
|
1030
|
+
url: null,
|
|
1031
|
+
atUnixMs: null,
|
|
1032
|
+
}),
|
|
1033
|
+
);
|
|
1034
|
+
|
|
1035
|
+
expect(screen.queryByText("View")).toBeNull();
|
|
1036
|
+
expect(screen.queryByText("Element")).toBeNull();
|
|
1037
|
+
expect(screen.queryByText("Input delay")).toBeNull();
|
|
1038
|
+
});
|
|
986
1039
|
});
|
|
@@ -646,6 +646,60 @@ describe("performance rows", () => {
|
|
|
646
646
|
expect(signal.severity).toBe("info");
|
|
647
647
|
expect(signal.title).not.toContain("0ms");
|
|
648
648
|
});
|
|
649
|
+
|
|
650
|
+
/* Issue #3975: an INP per view, labelled with the route it measured. */
|
|
651
|
+
it("carries an INP's view and attribution into the detail, and the route into the subtitle", () => {
|
|
652
|
+
const signal: ReplaySignal = fromTimelineEvent(
|
|
653
|
+
makeEvent("performance", {
|
|
654
|
+
performanceKind: "web-vital",
|
|
655
|
+
metric: "INP",
|
|
656
|
+
value: 480,
|
|
657
|
+
rating: "needs-improvement",
|
|
658
|
+
url: "https://shop.example.com/products",
|
|
659
|
+
navigationType: "soft",
|
|
660
|
+
interactionType: "keyboard",
|
|
661
|
+
interactionTarget: "input#search",
|
|
662
|
+
inputDelayMs: 12,
|
|
663
|
+
processingDurationMs: 400,
|
|
664
|
+
presentationDelayMs: 68,
|
|
665
|
+
}),
|
|
666
|
+
{ startTimeUnixMs: null },
|
|
667
|
+
);
|
|
668
|
+
const detail: ReplayPerformanceSignalDetail =
|
|
669
|
+
signal.detail as ReplayPerformanceSignalDetail;
|
|
670
|
+
|
|
671
|
+
expect(signal.title).toBe("INP 480ms needs improvement");
|
|
672
|
+
expect(signal.subtitle).toBe("/products");
|
|
673
|
+
expect(detail).toEqual(
|
|
674
|
+
expect.objectContaining({
|
|
675
|
+
navigationType: "soft",
|
|
676
|
+
interactionType: "keyboard",
|
|
677
|
+
interactionTarget: "input#search",
|
|
678
|
+
inputDelayMs: 12,
|
|
679
|
+
processingDurationMs: 400,
|
|
680
|
+
presentationDelayMs: 68,
|
|
681
|
+
}),
|
|
682
|
+
);
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
it("leaves the attribution empty for a recording that predates it", () => {
|
|
686
|
+
const detail: ReplayPerformanceSignalDetail = fromTimelineEvent(
|
|
687
|
+
makeEvent("performance", {
|
|
688
|
+
performanceKind: "web-vital",
|
|
689
|
+
metric: "INP",
|
|
690
|
+
value: 320,
|
|
691
|
+
rating: "poor",
|
|
692
|
+
}),
|
|
693
|
+
{ startTimeUnixMs: null },
|
|
694
|
+
).detail as ReplayPerformanceSignalDetail;
|
|
695
|
+
|
|
696
|
+
expect(detail.navigationType).toBeNull();
|
|
697
|
+
expect(detail.interactionType).toBeNull();
|
|
698
|
+
expect(detail.interactionTarget).toBeNull();
|
|
699
|
+
expect(detail.inputDelayMs).toBeNull();
|
|
700
|
+
expect(detail.processingDurationMs).toBeNull();
|
|
701
|
+
expect(detail.presentationDelayMs).toBeNull();
|
|
702
|
+
});
|
|
649
703
|
});
|
|
650
704
|
|
|
651
705
|
describe("custom and marker rows", () => {
|
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
import AggregationType from "../../../Types/BaseDatabase/AggregationType";
|
|
2
|
+
import { JSONObject } from "../../../Types/JSON";
|
|
3
|
+
import KubernetesClusterCpuTileRepair, {
|
|
4
|
+
BUSIEST_NODE_CPU_TILE_TITLE,
|
|
5
|
+
KUBERNETES_NODE_CPU_USAGE_METRIC,
|
|
6
|
+
KubernetesClusterCpuTileRepairResult,
|
|
7
|
+
LEGACY_CLUSTER_CPU_TILE_TITLE,
|
|
8
|
+
} from "../../../Utils/Dashboard/KubernetesClusterCpuTileRepair";
|
|
9
|
+
import { describe, expect, test } from "@jest/globals";
|
|
10
|
+
|
|
11
|
+
/*
|
|
12
|
+
* The repair behind the RepairKubernetesDashboardClusterCpuTile migration.
|
|
13
|
+
* Existing Kubernetes dashboards hold a COPY of the template, so their
|
|
14
|
+
* "Cluster CPU (cores in use)" tile still sums k8s.node.cpu.usage over every
|
|
15
|
+
* node, scrape and bucket. What is pinned here: exactly which tiles are
|
|
16
|
+
* rewritten, to what, that nothing else on the dashboard moves, and that
|
|
17
|
+
* the Postgres filter the migration selects rows with agrees with the
|
|
18
|
+
* in-memory matcher.
|
|
19
|
+
*
|
|
20
|
+
* Fixtures are shaped like a stored dashboardViewConfig: plain jsonb, with
|
|
21
|
+
* componentId serialised as { _type: "ObjectID", value } and the
|
|
22
|
+
* misspelled `aggegationType` the fetch layer reads.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
function makeValueTile(data: {
|
|
26
|
+
componentId?: string;
|
|
27
|
+
title?: string;
|
|
28
|
+
metricName?: string;
|
|
29
|
+
aggregation?: string;
|
|
30
|
+
extraArguments?: JSONObject;
|
|
31
|
+
extraFilterData?: JSONObject;
|
|
32
|
+
}): JSONObject {
|
|
33
|
+
return {
|
|
34
|
+
_type: "DashboardComponent",
|
|
35
|
+
componentType: "Value",
|
|
36
|
+
componentId: {
|
|
37
|
+
_type: "ObjectID",
|
|
38
|
+
value: data.componentId || "0b6a2c3e-0000-4000-8000-000000000001",
|
|
39
|
+
},
|
|
40
|
+
topInDashboardUnits: 11,
|
|
41
|
+
leftInDashboardUnits: 0,
|
|
42
|
+
widthInDashboardUnits: 4,
|
|
43
|
+
heightInDashboardUnits: 1,
|
|
44
|
+
minHeightInDashboardUnits: 1,
|
|
45
|
+
minWidthInDashboardUnits: 1,
|
|
46
|
+
arguments: {
|
|
47
|
+
title: data.title ?? LEGACY_CLUSTER_CPU_TILE_TITLE,
|
|
48
|
+
metricQueryConfig: {
|
|
49
|
+
metricQueryData: {
|
|
50
|
+
filterData: {
|
|
51
|
+
metricName: data.metricName ?? KUBERNETES_NODE_CPU_USAGE_METRIC,
|
|
52
|
+
aggegationType: data.aggregation ?? AggregationType.Sum,
|
|
53
|
+
...(data.extraFilterData || {}),
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
trendDirection: "HigherIsWorse",
|
|
58
|
+
...(data.extraArguments || {}),
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function makeConfig(components: Array<unknown>): JSONObject {
|
|
64
|
+
return {
|
|
65
|
+
_type: "DashboardViewConfig",
|
|
66
|
+
heightInDashboardUnits: 21,
|
|
67
|
+
variables: [
|
|
68
|
+
{
|
|
69
|
+
id: "cluster",
|
|
70
|
+
name: "Cluster",
|
|
71
|
+
type: "Telemetry Attribute",
|
|
72
|
+
attributeKey: "resource.k8s.cluster.name",
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
components: components as Array<JSONObject>,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function componentsOf(config: JSONObject): Array<JSONObject> {
|
|
80
|
+
return config["components"] as Array<JSONObject>;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function filterDataOf(component: JSONObject): JSONObject {
|
|
84
|
+
const args: JSONObject = component["arguments"] as JSONObject;
|
|
85
|
+
const metricQueryConfig: JSONObject = args["metricQueryConfig"] as JSONObject;
|
|
86
|
+
const metricQueryData: JSONObject = metricQueryConfig[
|
|
87
|
+
"metricQueryData"
|
|
88
|
+
] as JSONObject;
|
|
89
|
+
|
|
90
|
+
return metricQueryData["filterData"] as JSONObject;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/*
|
|
94
|
+
* Postgres `@>` for jsonb, as documented (8.14.3): an object contains
|
|
95
|
+
* another when every key of the right side is present and contains its
|
|
96
|
+
* value; an array contains another when every element of the right side is
|
|
97
|
+
* contained by SOME element of the left; scalars must be equal. Used only to
|
|
98
|
+
* show that the migration's SQL filter and isLegacyTile pick the same rows.
|
|
99
|
+
*/
|
|
100
|
+
function jsonbContains(left: unknown, right: unknown): boolean {
|
|
101
|
+
if (Array.isArray(right)) {
|
|
102
|
+
return (
|
|
103
|
+
Array.isArray(left) &&
|
|
104
|
+
right.every((rightItem: unknown) => {
|
|
105
|
+
return left.some((leftItem: unknown) => {
|
|
106
|
+
return jsonbContains(leftItem, rightItem);
|
|
107
|
+
});
|
|
108
|
+
})
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (right && typeof right === "object") {
|
|
113
|
+
if (!left || typeof left !== "object" || Array.isArray(left)) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return Object.entries(right as JSONObject).every(
|
|
118
|
+
([key, value]: [string, unknown]) => {
|
|
119
|
+
return (
|
|
120
|
+
Object.prototype.hasOwnProperty.call(left, key) &&
|
|
121
|
+
jsonbContains((left as JSONObject)[key], value)
|
|
122
|
+
);
|
|
123
|
+
},
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return left === right;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const TEXT_HEADER: JSONObject = {
|
|
131
|
+
_type: "DashboardComponent",
|
|
132
|
+
componentType: "Text",
|
|
133
|
+
componentId: { _type: "ObjectID", value: "text-header" },
|
|
134
|
+
arguments: { text: "Resource Health", isBold: true },
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const NETWORK_CHART: JSONObject = {
|
|
138
|
+
_type: "DashboardComponent",
|
|
139
|
+
componentType: "Chart",
|
|
140
|
+
componentId: { _type: "ObjectID", value: "network-chart" },
|
|
141
|
+
arguments: {
|
|
142
|
+
chartTitle: "Network I/O",
|
|
143
|
+
metricQueryConfig: {
|
|
144
|
+
metricQueryData: {
|
|
145
|
+
filterData: {
|
|
146
|
+
metricName: "k8s.pod.network.io",
|
|
147
|
+
aggegationType: AggregationType.Sum,
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
describe("KubernetesClusterCpuTileRepair", () => {
|
|
155
|
+
describe("repair", () => {
|
|
156
|
+
test("rewrites the legacy tile to the busiest node's peak CPU", () => {
|
|
157
|
+
const result: KubernetesClusterCpuTileRepairResult | null =
|
|
158
|
+
KubernetesClusterCpuTileRepair.repair(
|
|
159
|
+
makeConfig([TEXT_HEADER, makeValueTile({}), NETWORK_CHART]),
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
expect(result).not.toBeNull();
|
|
163
|
+
expect(result!.repairedTileCount).toBe(1);
|
|
164
|
+
|
|
165
|
+
const tile: JSONObject = componentsOf(result!.config)[1]!;
|
|
166
|
+
expect((tile["arguments"] as JSONObject)["title"]).toBe(
|
|
167
|
+
BUSIEST_NODE_CPU_TILE_TITLE,
|
|
168
|
+
);
|
|
169
|
+
expect(filterDataOf(tile)["aggegationType"]).toBe(AggregationType.Max);
|
|
170
|
+
expect(filterDataOf(tile)["metricName"]).toBe(
|
|
171
|
+
KUBERNETES_NODE_CPU_USAGE_METRIC,
|
|
172
|
+
);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test("changes only the title and the aggregation", () => {
|
|
176
|
+
const original: JSONObject = makeConfig([
|
|
177
|
+
TEXT_HEADER,
|
|
178
|
+
makeValueTile({
|
|
179
|
+
extraArguments: { warningThreshold: 3, criticalThreshold: 6 },
|
|
180
|
+
extraFilterData: { attributes: { "resource.k8s.node.name": "a" } },
|
|
181
|
+
}),
|
|
182
|
+
NETWORK_CHART,
|
|
183
|
+
]);
|
|
184
|
+
|
|
185
|
+
const expected: JSONObject = JSON.parse(JSON.stringify(original));
|
|
186
|
+
const expectedTile: JSONObject = componentsOf(expected)[1]!;
|
|
187
|
+
(expectedTile["arguments"] as JSONObject)["title"] =
|
|
188
|
+
BUSIEST_NODE_CPU_TILE_TITLE;
|
|
189
|
+
filterDataOf(expectedTile)["aggegationType"] = AggregationType.Max;
|
|
190
|
+
|
|
191
|
+
expect(KubernetesClusterCpuTileRepair.repair(original)!.config).toEqual(
|
|
192
|
+
expected,
|
|
193
|
+
);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test("never mutates the config it was given", () => {
|
|
197
|
+
const original: JSONObject = makeConfig([makeValueTile({})]);
|
|
198
|
+
const snapshot: string = JSON.stringify(original);
|
|
199
|
+
|
|
200
|
+
KubernetesClusterCpuTileRepair.repair(original);
|
|
201
|
+
|
|
202
|
+
expect(JSON.stringify(original)).toBe(snapshot);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
test("repairs every copy of the tile on one dashboard", () => {
|
|
206
|
+
const result: KubernetesClusterCpuTileRepairResult | null =
|
|
207
|
+
KubernetesClusterCpuTileRepair.repair(
|
|
208
|
+
makeConfig([
|
|
209
|
+
makeValueTile({ componentId: "first" }),
|
|
210
|
+
makeValueTile({ componentId: "second" }),
|
|
211
|
+
]),
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
expect(result!.repairedTileCount).toBe(2);
|
|
215
|
+
for (const tile of componentsOf(result!.config)) {
|
|
216
|
+
expect(filterDataOf(tile)["aggegationType"]).toBe(AggregationType.Max);
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
test("is a no-op on its own output, so a second run writes nothing", () => {
|
|
221
|
+
const once: KubernetesClusterCpuTileRepairResult | null =
|
|
222
|
+
KubernetesClusterCpuTileRepair.repair(makeConfig([makeValueTile({})]));
|
|
223
|
+
|
|
224
|
+
expect(once).not.toBeNull();
|
|
225
|
+
expect(KubernetesClusterCpuTileRepair.repair(once!.config)).toBeNull();
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
/*
|
|
229
|
+
* The migration only corrects what the template put there. A user who
|
|
230
|
+
* renamed the tile, re-aggregated it, pointed it at another metric or
|
|
231
|
+
* drew the same query as a chart made that choice themselves.
|
|
232
|
+
*/
|
|
233
|
+
test.each([
|
|
234
|
+
["a renamed tile", makeValueTile({ title: "All Nodes CPU" })],
|
|
235
|
+
[
|
|
236
|
+
"a title that differs only in whitespace",
|
|
237
|
+
makeValueTile({ title: ` ${LEGACY_CLUSTER_CPU_TILE_TITLE}` }),
|
|
238
|
+
],
|
|
239
|
+
[
|
|
240
|
+
"a tile already re-aggregated to Avg",
|
|
241
|
+
makeValueTile({ aggregation: AggregationType.Avg }),
|
|
242
|
+
],
|
|
243
|
+
[
|
|
244
|
+
"a tile already re-aggregated to Max",
|
|
245
|
+
makeValueTile({ aggregation: AggregationType.Max }),
|
|
246
|
+
],
|
|
247
|
+
[
|
|
248
|
+
"a Sum tile over another metric",
|
|
249
|
+
makeValueTile({ metricName: "k8s.pod.cpu.usage" }),
|
|
250
|
+
],
|
|
251
|
+
[
|
|
252
|
+
"a chart with the same title, metric and Sum",
|
|
253
|
+
{ ...makeValueTile({}), componentType: "Chart" },
|
|
254
|
+
],
|
|
255
|
+
["a text widget", TEXT_HEADER],
|
|
256
|
+
])("leaves %s alone", (_label: string, component: JSONObject) => {
|
|
257
|
+
expect(
|
|
258
|
+
KubernetesClusterCpuTileRepair.repair(makeConfig([component])),
|
|
259
|
+
).toBeNull();
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
test.each([
|
|
263
|
+
["undefined", undefined],
|
|
264
|
+
["null", null],
|
|
265
|
+
["a string", "not a config"],
|
|
266
|
+
["an array", [makeValueTile({})]],
|
|
267
|
+
["a config without components", { _type: "DashboardViewConfig" }],
|
|
268
|
+
[
|
|
269
|
+
"a config whose components are not an array",
|
|
270
|
+
{ components: { 0: makeValueTile({}) } },
|
|
271
|
+
],
|
|
272
|
+
["a config with no components", makeConfig([])],
|
|
273
|
+
])("returns null for %s", (_label: string, config: unknown) => {
|
|
274
|
+
expect(KubernetesClusterCpuTileRepair.repair(config)).toBeNull();
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
test("skips malformed components without throwing", () => {
|
|
278
|
+
const result: KubernetesClusterCpuTileRepairResult | null =
|
|
279
|
+
KubernetesClusterCpuTileRepair.repair(
|
|
280
|
+
makeConfig([
|
|
281
|
+
null,
|
|
282
|
+
"text",
|
|
283
|
+
42,
|
|
284
|
+
{ componentType: "Value" },
|
|
285
|
+
{ componentType: "Value", arguments: null },
|
|
286
|
+
{
|
|
287
|
+
componentType: "Value",
|
|
288
|
+
arguments: { title: LEGACY_CLUSTER_CPU_TILE_TITLE },
|
|
289
|
+
},
|
|
290
|
+
makeValueTile({}),
|
|
291
|
+
]),
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
expect(result!.repairedTileCount).toBe(1);
|
|
295
|
+
expect(componentsOf(result!.config).slice(0, 6)).toEqual([
|
|
296
|
+
null,
|
|
297
|
+
"text",
|
|
298
|
+
42,
|
|
299
|
+
{ componentType: "Value" },
|
|
300
|
+
{ componentType: "Value", arguments: null },
|
|
301
|
+
{
|
|
302
|
+
componentType: "Value",
|
|
303
|
+
arguments: { title: LEGACY_CLUSTER_CPU_TILE_TITLE },
|
|
304
|
+
},
|
|
305
|
+
]);
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
describe("getLegacyTileContainment", () => {
|
|
310
|
+
/*
|
|
311
|
+
* The migration reads only the dashboards Postgres matches with this
|
|
312
|
+
* document, then repairs them with isLegacyTile. If the two disagree,
|
|
313
|
+
* a dashboard is either never read or read and left unrepaired.
|
|
314
|
+
*/
|
|
315
|
+
test.each([
|
|
316
|
+
["the legacy tile", makeConfig([TEXT_HEADER, makeValueTile({})]), true],
|
|
317
|
+
[
|
|
318
|
+
"the legacy tile with user thresholds and filters",
|
|
319
|
+
makeConfig([
|
|
320
|
+
makeValueTile({
|
|
321
|
+
extraArguments: { warningThreshold: 3 },
|
|
322
|
+
extraFilterData: { attributes: { "resource.k8s.node.name": "a" } },
|
|
323
|
+
}),
|
|
324
|
+
]),
|
|
325
|
+
true,
|
|
326
|
+
],
|
|
327
|
+
[
|
|
328
|
+
"a repaired tile",
|
|
329
|
+
makeConfig([
|
|
330
|
+
makeValueTile({
|
|
331
|
+
title: BUSIEST_NODE_CPU_TILE_TITLE,
|
|
332
|
+
aggregation: AggregationType.Max,
|
|
333
|
+
}),
|
|
334
|
+
]),
|
|
335
|
+
false,
|
|
336
|
+
],
|
|
337
|
+
[
|
|
338
|
+
"the title on one tile and the Sum on another",
|
|
339
|
+
makeConfig([
|
|
340
|
+
makeValueTile({ aggregation: AggregationType.Avg }),
|
|
341
|
+
makeValueTile({ title: "All Nodes CPU" }),
|
|
342
|
+
]),
|
|
343
|
+
false,
|
|
344
|
+
],
|
|
345
|
+
[
|
|
346
|
+
"a chart with the legacy signature",
|
|
347
|
+
makeConfig([{ ...makeValueTile({}), componentType: "Chart" }]),
|
|
348
|
+
false,
|
|
349
|
+
],
|
|
350
|
+
[
|
|
351
|
+
"an unrelated dashboard",
|
|
352
|
+
makeConfig([TEXT_HEADER, NETWORK_CHART]),
|
|
353
|
+
false,
|
|
354
|
+
],
|
|
355
|
+
])(
|
|
356
|
+
"agrees with repair() on %s",
|
|
357
|
+
(_label: string, config: JSONObject, shouldMatch: boolean) => {
|
|
358
|
+
expect(
|
|
359
|
+
jsonbContains(
|
|
360
|
+
config,
|
|
361
|
+
KubernetesClusterCpuTileRepair.getLegacyTileContainment(),
|
|
362
|
+
),
|
|
363
|
+
).toBe(shouldMatch);
|
|
364
|
+
expect(KubernetesClusterCpuTileRepair.repair(config) !== null).toBe(
|
|
365
|
+
shouldMatch,
|
|
366
|
+
);
|
|
367
|
+
},
|
|
368
|
+
);
|
|
369
|
+
});
|
|
370
|
+
});
|