@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
|
@@ -541,13 +541,16 @@ function vitalsWith(
|
|
|
541
541
|
value: (d: WebVitalDefinition) => number | null,
|
|
542
542
|
): Array<WebVital> {
|
|
543
543
|
return WebVitalDefinitions.map((d: WebVitalDefinition): WebVital => {
|
|
544
|
+
const reported: number | null = value(d);
|
|
544
545
|
return {
|
|
545
546
|
key: d.key,
|
|
546
547
|
label: d.label,
|
|
547
548
|
description: d.description,
|
|
548
|
-
value:
|
|
549
|
+
value: reported,
|
|
549
550
|
unit: d.unit,
|
|
550
551
|
thresholds: d.thresholds,
|
|
552
|
+
// As fetchWebVitals reports it: the first alias, once one has data.
|
|
553
|
+
metricName: reported === null ? null : d.names[0] || null,
|
|
551
554
|
};
|
|
552
555
|
});
|
|
553
556
|
}
|
|
@@ -40,6 +40,7 @@ const countMock: MockFunction = getJestMockFunction();
|
|
|
40
40
|
const fetchSpanMetricsMock: MockFunction = getJestMockFunction();
|
|
41
41
|
const fetchSpanNameStatsMock: MockFunction = getJestMockFunction();
|
|
42
42
|
const fetchWebVitalsMock: MockFunction = getJestMockFunction();
|
|
43
|
+
const fetchWebVitalByRouteMock: MockFunction = getJestMockFunction();
|
|
43
44
|
const fetchSignalsMock: MockFunction = getJestMockFunction();
|
|
44
45
|
const fetchSessionReplayListMock: MockFunction = getJestMockFunction();
|
|
45
46
|
const lineChartMock: MockFunction = getJestMockFunction();
|
|
@@ -204,6 +205,9 @@ jest.mock(
|
|
|
204
205
|
fetchWebVitals: (...args: Array<unknown>): unknown => {
|
|
205
206
|
return fetchWebVitalsMock(...args);
|
|
206
207
|
},
|
|
208
|
+
fetchWebVitalByRoute: (...args: Array<unknown>): unknown => {
|
|
209
|
+
return fetchWebVitalByRouteMock(...args);
|
|
210
|
+
},
|
|
207
211
|
fetchLogAndExceptionSignals: (...args: Array<unknown>): unknown => {
|
|
208
212
|
return fetchSignalsMock(...args);
|
|
209
213
|
},
|
|
@@ -279,6 +283,7 @@ import {
|
|
|
279
283
|
SpanNameStats,
|
|
280
284
|
LogAndExceptionSignals,
|
|
281
285
|
WebVital,
|
|
286
|
+
WebVitalByRoute,
|
|
282
287
|
} from "../../../../App/FeatureSet/Dashboard/src/Components/TelemetryResource/telemetryMetrics";
|
|
283
288
|
import PageComponentProps from "../../../../App/FeatureSet/Dashboard/src/Pages/PageComponentProps";
|
|
284
289
|
import {
|
|
@@ -367,10 +372,25 @@ const VITALS: Array<WebVital> = WebVitalDefinitions.map(
|
|
|
367
372
|
value: d.unit === "score" ? 0.05 : d.thresholds.warn / 2,
|
|
368
373
|
unit: d.unit,
|
|
369
374
|
thresholds: d.thresholds,
|
|
375
|
+
metricName: null,
|
|
370
376
|
};
|
|
371
377
|
},
|
|
372
378
|
);
|
|
373
379
|
|
|
380
|
+
/* INP as instrumentation that emits web_vital.inp reports it. */
|
|
381
|
+
const VITALS_WITH_INP: Array<WebVital> = VITALS.map((v: WebVital): WebVital => {
|
|
382
|
+
return v.key === "inp"
|
|
383
|
+
? { ...v, value: 260, metricName: "web_vital.inp" }
|
|
384
|
+
: v;
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
const NO_ROUTES: WebVitalByRoute = {
|
|
388
|
+
routeAttribute: null,
|
|
389
|
+
routes: [],
|
|
390
|
+
totalRoutes: null,
|
|
391
|
+
failed: false,
|
|
392
|
+
};
|
|
393
|
+
|
|
374
394
|
function sessions(count: number): Array<{ sessionId: string }> {
|
|
375
395
|
return Array.from({ length: count }, (_: unknown, i: number) => {
|
|
376
396
|
return { sessionId: `s-${i}` };
|
|
@@ -419,6 +439,7 @@ interface Scenario {
|
|
|
419
439
|
pageLoadStats?: Promise<SpanNameStats> | undefined;
|
|
420
440
|
signals?: Promise<LogAndExceptionSignals> | undefined;
|
|
421
441
|
vitals?: Promise<Array<WebVital>> | undefined;
|
|
442
|
+
inpByRoute?: Promise<WebVitalByRoute> | undefined;
|
|
422
443
|
sessionList?: Promise<unknown> | undefined;
|
|
423
444
|
}
|
|
424
445
|
|
|
@@ -444,6 +465,9 @@ function arrange(scenario: Scenario = {}): void {
|
|
|
444
465
|
fetchWebVitalsMock.mockImplementation(() => {
|
|
445
466
|
return scenario.vitals ?? Promise.resolve(VITALS);
|
|
446
467
|
});
|
|
468
|
+
fetchWebVitalByRouteMock.mockImplementation(() => {
|
|
469
|
+
return scenario.inpByRoute ?? Promise.resolve(NO_ROUTES);
|
|
470
|
+
});
|
|
447
471
|
fetchSessionReplayListMock.mockImplementation(() => {
|
|
448
472
|
return (
|
|
449
473
|
scenario.sessionList ??
|
|
@@ -560,6 +584,7 @@ beforeEach(() => {
|
|
|
560
584
|
fetchSpanMetricsMock,
|
|
561
585
|
fetchSpanNameStatsMock,
|
|
562
586
|
fetchWebVitalsMock,
|
|
587
|
+
fetchWebVitalByRouteMock,
|
|
563
588
|
fetchSignalsMock,
|
|
564
589
|
fetchSessionReplayListMock,
|
|
565
590
|
lineChartMock,
|
|
@@ -1193,3 +1218,105 @@ describe("RUM overview: refreshing", () => {
|
|
|
1193
1218
|
expectTile("Page loads", "42", "3 failed");
|
|
1194
1219
|
});
|
|
1195
1220
|
});
|
|
1221
|
+
|
|
1222
|
+
/*
|
|
1223
|
+
* Issue #3975. The app-wide INP mixes every view of a single-page app
|
|
1224
|
+
* together; the per-route card is where the slow one shows up.
|
|
1225
|
+
*/
|
|
1226
|
+
describe("RUM overview: INP by route", () => {
|
|
1227
|
+
test("is not shown, and not asked for, until INP has reported", async () => {
|
|
1228
|
+
arrange();
|
|
1229
|
+
await renderPage();
|
|
1230
|
+
|
|
1231
|
+
expect(fetchWebVitalByRouteMock).not.toHaveBeenCalled();
|
|
1232
|
+
expect(screen.queryByText("INP by route")).toBeNull();
|
|
1233
|
+
});
|
|
1234
|
+
|
|
1235
|
+
test("asks for INP per route under the name the vitals card found it by", async () => {
|
|
1236
|
+
arrange({ vitals: Promise.resolve(VITALS_WITH_INP) });
|
|
1237
|
+
await renderPage();
|
|
1238
|
+
|
|
1239
|
+
expect(fetchWebVitalByRouteMock).toHaveBeenCalledTimes(1);
|
|
1240
|
+
|
|
1241
|
+
const request: {
|
|
1242
|
+
primaryEntityId: ObjectID;
|
|
1243
|
+
metricName: string;
|
|
1244
|
+
start: Date;
|
|
1245
|
+
end: Date;
|
|
1246
|
+
} = fetchWebVitalByRouteMock.mock.calls[0]![0] as {
|
|
1247
|
+
primaryEntityId: ObjectID;
|
|
1248
|
+
metricName: string;
|
|
1249
|
+
start: Date;
|
|
1250
|
+
end: Date;
|
|
1251
|
+
};
|
|
1252
|
+
|
|
1253
|
+
expect(request.metricName).toBe("web_vital.inp");
|
|
1254
|
+
expect(request.primaryEntityId.toString()).toBe(MODEL_ID);
|
|
1255
|
+
expect(request.end.getTime()).toBeGreaterThan(request.start.getTime());
|
|
1256
|
+
});
|
|
1257
|
+
|
|
1258
|
+
test("lists the routes slowest first, each rated, and explains itself", async () => {
|
|
1259
|
+
arrange({
|
|
1260
|
+
vitals: Promise.resolve(VITALS_WITH_INP),
|
|
1261
|
+
inpByRoute: Promise.resolve({
|
|
1262
|
+
routeAttribute: "app.route",
|
|
1263
|
+
routes: [
|
|
1264
|
+
{ route: "/products/:id", value: 620 },
|
|
1265
|
+
{ route: "/cart", value: 240 },
|
|
1266
|
+
{ route: "/", value: 90 },
|
|
1267
|
+
],
|
|
1268
|
+
totalRoutes: 12,
|
|
1269
|
+
failed: false,
|
|
1270
|
+
}),
|
|
1271
|
+
});
|
|
1272
|
+
await renderPage();
|
|
1273
|
+
|
|
1274
|
+
expect(screen.getByText("INP by route")).toBeInTheDocument();
|
|
1275
|
+
expect(
|
|
1276
|
+
await tooltipTextOf(
|
|
1277
|
+
screen.getByRole("button", { name: "About INP by route" }),
|
|
1278
|
+
),
|
|
1279
|
+
).toBe(RUM_METRIC_DESCRIPTIONS.inpByRoute);
|
|
1280
|
+
|
|
1281
|
+
const rows: Array<HTMLElement> = screen.getAllByTestId(
|
|
1282
|
+
"web-vital-route-row",
|
|
1283
|
+
);
|
|
1284
|
+
|
|
1285
|
+
expect(
|
|
1286
|
+
rows.map((row: HTMLElement): string => {
|
|
1287
|
+
return row.textContent || "";
|
|
1288
|
+
}),
|
|
1289
|
+
).toEqual([
|
|
1290
|
+
"/products/:id620 msPoor",
|
|
1291
|
+
"/cart240 msNeeds work",
|
|
1292
|
+
"/90 msGood",
|
|
1293
|
+
]);
|
|
1294
|
+
expect(screen.getByText("(app.route)")).toBeInTheDocument();
|
|
1295
|
+
expect(screen.getByText(/The 3 slowest of 12/)).toBeInTheDocument();
|
|
1296
|
+
});
|
|
1297
|
+
|
|
1298
|
+
test("INP without a route attribute says how to add one", async () => {
|
|
1299
|
+
arrange({ vitals: Promise.resolve(VITALS_WITH_INP) });
|
|
1300
|
+
await renderPage();
|
|
1301
|
+
|
|
1302
|
+
expect(
|
|
1303
|
+
screen.getByText("INP is reported, but not per route"),
|
|
1304
|
+
).toBeInTheDocument();
|
|
1305
|
+
expect(
|
|
1306
|
+
screen.getByRole("link", { name: "How to measure INP per route" }),
|
|
1307
|
+
).toHaveAttribute("href", "/docs/rum/web-vitals#single-page-apps");
|
|
1308
|
+
});
|
|
1309
|
+
|
|
1310
|
+
test("a failed lookup says so rather than claiming there are no routes", async () => {
|
|
1311
|
+
arrange({
|
|
1312
|
+
vitals: Promise.resolve(VITALS_WITH_INP),
|
|
1313
|
+
inpByRoute: Promise.resolve({ ...NO_ROUTES, failed: true }),
|
|
1314
|
+
});
|
|
1315
|
+
await renderPage();
|
|
1316
|
+
|
|
1317
|
+
expect(
|
|
1318
|
+
screen.getByText("Could not load INP by route."),
|
|
1319
|
+
).toBeInTheDocument();
|
|
1320
|
+
expect(screen.queryByText("INP is reported, but not per route")).toBeNull();
|
|
1321
|
+
});
|
|
1322
|
+
});
|
|
@@ -7,7 +7,14 @@ import {
|
|
|
7
7
|
jest,
|
|
8
8
|
test,
|
|
9
9
|
} from "@jest/globals";
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
RenderResult,
|
|
12
|
+
act,
|
|
13
|
+
cleanup,
|
|
14
|
+
render,
|
|
15
|
+
screen,
|
|
16
|
+
waitFor,
|
|
17
|
+
} from "@testing-library/react";
|
|
11
18
|
import * as React from "react";
|
|
12
19
|
import getJestMockFunction, { MockFunction } from "../../MockType";
|
|
13
20
|
|
|
@@ -41,6 +48,25 @@ jest.mock("../../../UI/Utils/ModelAPI/ModelAPI", () => {
|
|
|
41
48
|
},
|
|
42
49
|
};
|
|
43
50
|
});
|
|
51
|
+
/*
|
|
52
|
+
* The drawer resolves its links from the full row the entity endpoint
|
|
53
|
+
* returns; the fake answers with the node under test.
|
|
54
|
+
*/
|
|
55
|
+
const fetchEntityDetailMock: MockFunction = getJestMockFunction();
|
|
56
|
+
jest.mock(
|
|
57
|
+
"../../../../App/FeatureSet/Dashboard/src/Components/Topology/EntityDetailApi",
|
|
58
|
+
() => {
|
|
59
|
+
return {
|
|
60
|
+
__esModule: true,
|
|
61
|
+
...(jest.requireActual(
|
|
62
|
+
"../../../../App/FeatureSet/Dashboard/src/Components/Topology/EntityDetailApi",
|
|
63
|
+
) as Record<string, unknown>),
|
|
64
|
+
fetchEntityDetail: (...args: Array<any>) => {
|
|
65
|
+
return fetchEntityDetailMock(...args);
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
},
|
|
69
|
+
);
|
|
44
70
|
jest.mock("../../../UI/Utils/Translation", () => {
|
|
45
71
|
return {
|
|
46
72
|
__esModule: true,
|
|
@@ -77,8 +103,12 @@ import {
|
|
|
77
103
|
resolveTypedRowLink,
|
|
78
104
|
} from "../../../../App/FeatureSet/Dashboard/src/Components/Inventory/ResolveTypedRowLink";
|
|
79
105
|
import EntityDetailPanel from "../../../../App/FeatureSet/Dashboard/src/Components/Topology/EntityDetailPanel";
|
|
106
|
+
import {
|
|
107
|
+
EntityDetail,
|
|
108
|
+
EntityDetailData,
|
|
109
|
+
emptyConnectionSection,
|
|
110
|
+
} from "../../../../App/FeatureSet/Dashboard/src/Components/Topology/EntityDetailApi";
|
|
80
111
|
import InventoryItem from "../../../Models/DatabaseModels/InventoryItem";
|
|
81
|
-
import InventoryItemRelationship from "../../../Models/DatabaseModels/InventoryItemRelationship";
|
|
82
112
|
import StartsWith from "../../../Types/BaseDatabase/StartsWith";
|
|
83
113
|
import { JSONObject } from "../../../Types/JSON";
|
|
84
114
|
import ObjectID from "../../../Types/ObjectID";
|
|
@@ -1002,23 +1032,78 @@ describe("resolveDatabaseServerLink", () => {
|
|
|
1002
1032
|
});
|
|
1003
1033
|
|
|
1004
1034
|
describe("the Service Map detail drawer", () => {
|
|
1005
|
-
|
|
1006
|
-
|
|
1035
|
+
const RANGE_START: Date = new Date("2026-09-26T10:00:00.000Z");
|
|
1036
|
+
|
|
1037
|
+
function detailOf(
|
|
1038
|
+
node: InventoryItem,
|
|
1039
|
+
id: string = "1a1a0000-0000-4000-8000-000000000001",
|
|
1040
|
+
): EntityDetail {
|
|
1041
|
+
const detail: EntityDetail = {
|
|
1042
|
+
id: id,
|
|
1043
|
+
entityKey: node.entityKey!,
|
|
1044
|
+
entityType: node.entityType!,
|
|
1045
|
+
};
|
|
1046
|
+
if (node.displayName) {
|
|
1047
|
+
detail.displayName = node.displayName;
|
|
1048
|
+
}
|
|
1049
|
+
if (node.identifyingAttributes) {
|
|
1050
|
+
detail.identifyingAttributes = node.identifyingAttributes;
|
|
1051
|
+
}
|
|
1052
|
+
if (node.descriptiveAttributes) {
|
|
1053
|
+
detail.descriptiveAttributes = node.descriptiveAttributes;
|
|
1054
|
+
}
|
|
1055
|
+
return detail;
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
function answerWith(node: InventoryItem, id?: string): EntityDetailData {
|
|
1059
|
+
return {
|
|
1060
|
+
rangeStart: RANGE_START,
|
|
1061
|
+
entity: detailOf(node, id),
|
|
1062
|
+
sections: {
|
|
1063
|
+
calls: emptyConnectionSection(),
|
|
1064
|
+
calledBy: emptyConnectionSection(),
|
|
1065
|
+
runsOn: emptyConnectionSection(),
|
|
1066
|
+
related: emptyConnectionSection(),
|
|
1067
|
+
},
|
|
1068
|
+
isScanLimited: false,
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
function endpointLookups(): Array<GetListArgs> {
|
|
1073
|
+
return calls().filter((args: GetListArgs): boolean => {
|
|
1074
|
+
return args.modelType.name === "DatabaseServerEndpoint";
|
|
1075
|
+
});
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
function panelFor(node: InventoryItem, rangeStart: Date): React.ReactElement {
|
|
1079
|
+
/* A map hands over what it drew: key, type and name — no attributes. */
|
|
1080
|
+
return (
|
|
1007
1081
|
<EntityDetailPanel
|
|
1008
|
-
entity={
|
|
1009
|
-
|
|
1010
|
-
|
|
1082
|
+
entity={{
|
|
1083
|
+
entityKey: node.entityKey!,
|
|
1084
|
+
entityType: node.entityType,
|
|
1085
|
+
displayName: node.displayName,
|
|
1086
|
+
}}
|
|
1087
|
+
rangeStart={rangeStart}
|
|
1011
1088
|
metricsWindowSeconds={60}
|
|
1012
1089
|
onClose={() => {
|
|
1013
1090
|
return undefined;
|
|
1014
1091
|
}}
|
|
1015
|
-
|
|
1016
|
-
return undefined;
|
|
1017
|
-
}}
|
|
1018
|
-
/>,
|
|
1092
|
+
/>
|
|
1019
1093
|
);
|
|
1020
1094
|
}
|
|
1021
1095
|
|
|
1096
|
+
function renderPanel(node: InventoryItem): RenderResult {
|
|
1097
|
+
fetchEntityDetailMock.mockImplementation(async () => {
|
|
1098
|
+
return answerWith(node);
|
|
1099
|
+
});
|
|
1100
|
+
return render(panelFor(node, RANGE_START));
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
beforeEach(() => {
|
|
1104
|
+
fetchEntityDetailMock.mockReset();
|
|
1105
|
+
});
|
|
1106
|
+
|
|
1022
1107
|
test("offers 'Open database' for a database node a DatabaseServer owns", async () => {
|
|
1023
1108
|
useTable([{ owner: DATABASE_ID, endpoint: "db.prod.example.com:5432" }]);
|
|
1024
1109
|
|
|
@@ -1074,12 +1159,113 @@ describe("the Service Map detail drawer", () => {
|
|
|
1074
1159
|
entityType: EntityType.Container,
|
|
1075
1160
|
} as InventoryItem);
|
|
1076
1161
|
|
|
1162
|
+
await screen.findByText("Inventory details");
|
|
1077
1163
|
expect(screen.queryByText("Open database")).not.toBeInTheDocument();
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1164
|
+
expect(endpointLookups()).toHaveLength(0);
|
|
1165
|
+
});
|
|
1166
|
+
|
|
1167
|
+
test("resolves from the full row: the preview alone names no endpoint", async () => {
|
|
1168
|
+
useTable([{ owner: DATABASE_ID, endpoint: "db.prod.example.com:5432" }]);
|
|
1169
|
+
const node: InventoryItem = databaseNode({
|
|
1170
|
+
"db.system.name": "postgresql",
|
|
1171
|
+
"server.address": "db.prod.example.com",
|
|
1172
|
+
});
|
|
1173
|
+
let answer: (value: EntityDetailData) => void = () => {
|
|
1174
|
+
return undefined;
|
|
1175
|
+
};
|
|
1176
|
+
fetchEntityDetailMock.mockImplementation(() => {
|
|
1177
|
+
return new Promise<EntityDetailData>(
|
|
1178
|
+
(resolve: (value: EntityDetailData) => void) => {
|
|
1179
|
+
answer = resolve;
|
|
1180
|
+
},
|
|
1181
|
+
);
|
|
1182
|
+
});
|
|
1183
|
+
|
|
1184
|
+
render(panelFor(node, RANGE_START));
|
|
1185
|
+
/* Only the preview so far: a database type, but no address to resolve. */
|
|
1186
|
+
expect(endpointLookups()).toHaveLength(0);
|
|
1187
|
+
|
|
1188
|
+
await act(async () => {
|
|
1189
|
+
answer(answerWith(node));
|
|
1190
|
+
});
|
|
1191
|
+
|
|
1192
|
+
expect(await screen.findByText("Open database")).toBeInTheDocument();
|
|
1193
|
+
expect(endpointLookups()[0]!.query["endpoint"]).toBe(
|
|
1194
|
+
"db.prod.example.com:5432",
|
|
1195
|
+
);
|
|
1196
|
+
});
|
|
1197
|
+
|
|
1198
|
+
test("looks the database up once per row, not once per range", async () => {
|
|
1199
|
+
useTable([{ owner: DATABASE_ID, endpoint: "db.prod.example.com:5432" }]);
|
|
1200
|
+
const node: InventoryItem = databaseNode({
|
|
1201
|
+
"db.system.name": "postgresql",
|
|
1202
|
+
"server.address": "db.prod.example.com",
|
|
1203
|
+
});
|
|
1204
|
+
|
|
1205
|
+
const view: RenderResult = renderPanel(node);
|
|
1206
|
+
await screen.findByText("Open database");
|
|
1207
|
+
const lookups: number = endpointLookups().length;
|
|
1208
|
+
expect(lookups).toBeGreaterThan(0);
|
|
1209
|
+
|
|
1210
|
+
view.rerender(panelFor(node, new Date("2026-09-26T11:00:00.000Z")));
|
|
1211
|
+
await waitFor(() => {
|
|
1212
|
+
expect(fetchEntityDetailMock).toHaveBeenCalledTimes(2);
|
|
1213
|
+
});
|
|
1214
|
+
expect(await screen.findByText("Open database")).toBeInTheDocument();
|
|
1215
|
+
|
|
1216
|
+
expect(endpointLookups()).toHaveLength(lookups);
|
|
1217
|
+
});
|
|
1218
|
+
|
|
1219
|
+
test("moving the open drawer to another database never shows the first one's link", async () => {
|
|
1220
|
+
useTable([{ owner: DATABASE_ID, endpoint: "db.prod.example.com:5432" }]);
|
|
1221
|
+
const orders: InventoryItem = databaseNode({
|
|
1222
|
+
"db.system.name": "postgresql",
|
|
1223
|
+
"server.address": "db.prod.example.com",
|
|
1224
|
+
});
|
|
1225
|
+
/* Another database on the map, which no DatabaseServer owns. */
|
|
1226
|
+
const reports: InventoryItem = {
|
|
1227
|
+
...databaseNode({
|
|
1228
|
+
"db.system.name": "postgresql",
|
|
1229
|
+
"server.address": "reports.example.com",
|
|
1230
|
+
}),
|
|
1231
|
+
entityKey: "reports-node",
|
|
1232
|
+
displayName: "reports",
|
|
1233
|
+
} as unknown as InventoryItem;
|
|
1234
|
+
let answerReports: (value: EntityDetailData) => void = () => {
|
|
1235
|
+
return undefined;
|
|
1236
|
+
};
|
|
1237
|
+
fetchEntityDetailMock.mockImplementation(
|
|
1238
|
+
(target: { entityKey: string }) => {
|
|
1239
|
+
if (target.entityKey !== "reports-node") {
|
|
1240
|
+
return Promise.resolve(answerWith(orders));
|
|
1241
|
+
}
|
|
1242
|
+
return new Promise<EntityDetailData>(
|
|
1243
|
+
(resolve: (value: EntityDetailData) => void) => {
|
|
1244
|
+
answerReports = resolve;
|
|
1245
|
+
},
|
|
1246
|
+
);
|
|
1081
1247
|
},
|
|
1082
1248
|
);
|
|
1083
|
-
|
|
1249
|
+
|
|
1250
|
+
const view: RenderResult = render(panelFor(orders, RANGE_START));
|
|
1251
|
+
expect(
|
|
1252
|
+
(await screen.findByText("Open database")).closest("a"),
|
|
1253
|
+
).toHaveAttribute("href", databasePage(DATABASE_ID));
|
|
1254
|
+
|
|
1255
|
+
/* The same drawer, now for the other database: no remount. */
|
|
1256
|
+
view.rerender(panelFor(reports, RANGE_START));
|
|
1257
|
+
expect(screen.queryByText("Open database")).not.toBeInTheDocument();
|
|
1258
|
+
|
|
1259
|
+
const lookupsBefore: number = endpointLookups().length;
|
|
1260
|
+
await act(async () => {
|
|
1261
|
+
answerReports(
|
|
1262
|
+
answerWith(reports, "1a1a0000-0000-4000-8000-000000000002"),
|
|
1263
|
+
);
|
|
1264
|
+
});
|
|
1265
|
+
await waitFor(() => {
|
|
1266
|
+
expect(endpointLookups().length).toBeGreaterThan(lookupsBefore);
|
|
1267
|
+
});
|
|
1268
|
+
await screen.findByText("Inventory details");
|
|
1269
|
+
expect(screen.queryByText("Open database")).not.toBeInTheDocument();
|
|
1084
1270
|
});
|
|
1085
1271
|
});
|