@oneuptime/common 14.0.2 → 14.0.3
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/AlertFeed.ts +2 -0
- package/Models/DatabaseModels/IncidentAlert.ts +540 -0
- package/Models/DatabaseModels/IncidentFeed.ts +2 -0
- package/Models/DatabaseModels/Index.ts +2 -0
- package/Models/DatabaseModels/Project.ts +60 -0
- package/Models/DatabaseModels/RumApplication.ts +52 -1
- package/Server/Infrastructure/Postgres/SchemaMigrations/1794800000000-AddSessionReplaySameOriginTracePropagation.ts +28 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1794900000000-AddIncidentAlert.ts +89 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
- package/Server/Services/IncidentAlertService.ts +1811 -0
- package/Server/Services/IncidentService.ts +210 -10
- package/Server/Services/IncidentStateTimelineService.ts +24 -0
- package/Server/Services/Index.ts +2 -0
- package/Server/Utils/SessionReplay/SessionReplayGateCache.ts +24 -2
- package/Tests/App/Dashboard/BulkIncidentLinkActions.test.tsx +869 -0
- package/Tests/App/Dashboard/CephMetricTooltips.test.tsx +1777 -0
- package/Tests/App/Dashboard/CloudFleetSummary.test.ts +8 -0
- package/Tests/App/Dashboard/CloudMetricTooltips.test.tsx +887 -0
- package/Tests/App/Dashboard/ContainerHostMetricTooltips.test.tsx +1309 -0
- package/Tests/App/Dashboard/DiscoveryScanLivePage.test.tsx +39 -0
- package/Tests/App/Dashboard/DockerSwarmClustersListTooltips.test.tsx +414 -0
- package/Tests/App/Dashboard/DockerSwarmMetricTooltips.test.ts +796 -0
- package/Tests/App/Dashboard/DockerSwarmOverviewTooltips.test.tsx +747 -0
- package/Tests/App/Dashboard/ExceptionMonitorEnvironmentFilter.test.tsx +38 -18
- package/Tests/App/Dashboard/HostDetailViewTooltips.test.tsx +725 -0
- package/Tests/App/Dashboard/HostListColumnTooltips.test.tsx +472 -0
- package/Tests/App/Dashboard/HostMetricTooltips.test.ts +709 -0
- package/Tests/App/Dashboard/HostOverviewTooltips.test.tsx +698 -0
- package/Tests/App/Dashboard/HostTooltipHarness.ts +124 -0
- package/Tests/App/Dashboard/HostsListTooltips.test.tsx +395 -0
- package/Tests/App/Dashboard/IncidentAlertLinkDialog.test.tsx +691 -0
- package/Tests/App/Dashboard/IncidentAlertLinkHelpers.test.ts +458 -0
- package/Tests/App/Dashboard/IncidentAlertLinkPages.test.tsx +828 -0
- package/Tests/App/Dashboard/IncidentAlertLinkSideMenus.test.tsx +231 -0
- package/Tests/App/Dashboard/IncidentCreateFromAlerts.test.tsx +689 -0
- package/Tests/App/Dashboard/IoTMetricTooltips.test.tsx +1123 -0
- package/Tests/App/Dashboard/KubernetesClusterMetricTooltips.test.ts +837 -0
- package/Tests/App/Dashboard/KubernetesClusterOverviewTooltips.test.tsx +699 -0
- package/Tests/App/Dashboard/KubernetesContainersTabTooltips.test.tsx +320 -0
- package/Tests/App/Dashboard/KubernetesCostTooltips.test.tsx +502 -0
- package/Tests/App/Dashboard/KubernetesInsightsTooltips.test.tsx +222 -0
- package/Tests/App/Dashboard/KubernetesResourceMetricTooltips.test.ts +561 -0
- package/Tests/App/Dashboard/KubernetesResourcePagesTooltips.test.tsx +1168 -0
- package/Tests/App/Dashboard/LinkIncidentAlertModal.test.tsx +154 -0
- package/Tests/App/Dashboard/MetricDescriptionRules.ts +170 -0
- package/Tests/App/Dashboard/MetricDescriptionsCatalog.test.ts +206 -0
- package/Tests/App/Dashboard/MetricDescriptionsConsistency.test.ts +275 -0
- package/Tests/App/Dashboard/NetworkDeviceTooltips.test.tsx +852 -0
- package/Tests/App/Dashboard/NetworkDiagnosticsTooltips.test.tsx +556 -0
- package/Tests/App/Dashboard/NetworkMetricTooltips.test.ts +1348 -0
- package/Tests/App/Dashboard/NetworkSiteTooltips.test.tsx +679 -0
- package/Tests/App/Dashboard/ProxmoxMetricTooltips.test.ts +592 -0
- package/Tests/App/Dashboard/ProxmoxOverviewTooltips.test.tsx +1002 -0
- package/Tests/App/Dashboard/ProxmoxResourcePageTooltips.test.tsx +923 -0
- package/Tests/App/Dashboard/ResourceOverviewTileTooltips.test.tsx +1066 -0
- package/Tests/App/Dashboard/RumOverviewPage.test.tsx +1195 -0
- package/Tests/App/Dashboard/ServerlessMetricTooltips.test.tsx +474 -0
- package/Tests/App/Dashboard/ServiceMetricTooltips.test.tsx +772 -0
- package/Tests/App/Dashboard/SessionReplayAuditTable.test.tsx +56 -1
- package/Tests/App/Dashboard/TelemetryMetricsSignals.test.ts +1207 -0
- package/Tests/App/Dashboard/TelemetryResourceTabTooltips.test.tsx +437 -0
- package/Tests/App/Dashboard/VMwareMetricTooltips.test.ts +801 -0
- package/Tests/App/Dashboard/VMwareOverviewTooltips.test.tsx +601 -0
- package/Tests/App/Dashboard/VMwareResourcePagesTooltips.test.tsx +646 -0
- package/Tests/App/Dashboard/VMwareTooltipHarness.ts +145 -0
- package/Tests/Models/DatabaseModels/IncidentAlertModel.test.ts +554 -0
- package/Tests/Models/DatabaseModels/SessionReplayModels.test.ts +117 -0
- package/Tests/Server/Infrastructure/Postgres/AddIncidentAlertMigration.test.ts +341 -0
- package/Tests/Server/Infrastructure/Postgres/AddSessionReplaySameOriginTracePropagationMigration.test.ts +247 -0
- package/Tests/Server/Services/IncidentAlertPostgres.test.ts +903 -0
- package/Tests/Server/Services/IncidentAlertRegistration.test.ts +96 -0
- package/Tests/Server/Services/IncidentAlertService.test.ts +2225 -0
- package/Tests/Server/Services/IncidentAlertStateCascade.test.ts +970 -0
- package/Tests/Server/Services/IncidentCreateFromAlerts.test.ts +1284 -0
- package/Tests/Server/Utils/SessionReplay/SessionReplayGateCachePolicy.test.ts +103 -0
- package/Tests/Types/Rum/WebVitals.test.ts +173 -0
- package/Tests/UI/Components/FieldLabelInfoCardTooltip.test.tsx +413 -0
- package/Tests/UI/Components/InfoTooltip.test.tsx +242 -0
- package/Tests/UI/Components/ModelTable/BaseModelTableBulkDelete.test.tsx +207 -3
- package/Tests/UI/Components/ModelTable/BaseModelTableBulkDeleteVerb.test.tsx +367 -0
- package/Tests/UI/Components/ModelTable/BaseModelTableCreateButtonTitle.test.tsx +275 -0
- package/Tests/UI/Components/TableHeaderTooltip.test.tsx +662 -0
- package/Tests/UI/Rum/PrivacySummaryCard.test.tsx +166 -9
- package/Tests/UI/Rum/RecordingHealthTooltips.test.tsx +447 -0
- package/Tests/UI/Rum/ReplayCorrelationPanel.test.tsx +164 -0
- package/Tests/UI/Rum/ReplayFrameCapture.test.ts +6750 -0
- package/Tests/UI/Rum/ReplayRail.test.tsx +723 -12
- package/Tests/UI/Rum/ReplayRailDetail.test.tsx +86 -0
- package/Tests/UI/Rum/ReplayScreenshot.test.ts +2124 -0
- package/Tests/UI/Rum/ReplayScreenshotActions.test.tsx +3288 -0
- package/Tests/UI/Rum/ReplayStage.test.tsx +62 -0
- package/Tests/UI/Rum/ReplayStageOverlays.test.tsx +983 -2
- package/Tests/UI/Rum/SessionReplaySettingsPolicyLoading.test.tsx +149 -0
- package/Tests/UI/Rum/SessionReplaySetupGuide.test.tsx +107 -6
- package/Tests/UI/Rum/SessionReplayUsersTableTooltips.test.tsx +330 -0
- package/Tests/UI/Rum/UserFlowTooltips.test.tsx +886 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +10 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +2 -0
- package/Tests/UI/Utils/PermissionGate.test.ts +76 -0
- package/Tests/Utils/Incident/IncidentFromAlerts.test.ts +696 -0
- package/Tests/Utils/Rum/SessionTraceState.test.ts +239 -0
- package/Types/Incident/IncidentAlertLink.ts +38 -0
- package/Types/Permission.ts +48 -0
- package/Types/Rum/SessionReplay.ts +25 -6
- package/Types/Rum/WebVitals.ts +52 -0
- package/UI/Components/Detail/FieldLabel.tsx +8 -0
- package/UI/Components/InfoCard/InfoCard.tsx +47 -1
- package/UI/Components/ModelTable/BaseModelTable.tsx +95 -17
- package/UI/Components/ModelTable/Column.ts +5 -0
- package/UI/Components/Table/TableHeader.tsx +47 -2
- package/UI/Components/Table/Types/Column.ts +7 -0
- package/UI/Components/Tooltip/InfoTooltip.tsx +78 -0
- package/UI/Utils/PermissionGate.ts +11 -2
- package/Utils/Incident/IncidentFromAlerts.ts +516 -0
- package/Utils/Rum/SessionTraceState.ts +212 -0
- package/build/dist/Models/DatabaseModels/AlertFeed.js +2 -0
- package/build/dist/Models/DatabaseModels/AlertFeed.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentAlert.js +553 -0
- package/build/dist/Models/DatabaseModels/IncidentAlert.js.map +1 -0
- package/build/dist/Models/DatabaseModels/IncidentFeed.js +2 -0
- package/build/dist/Models/DatabaseModels/IncidentFeed.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +2 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Project.js +62 -0
- package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
- package/build/dist/Models/DatabaseModels/RumApplication.js +53 -1
- package/build/dist/Models/DatabaseModels/RumApplication.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794800000000-AddSessionReplaySameOriginTracePropagation.js +20 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794800000000-AddSessionReplaySameOriginTracePropagation.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794900000000-AddIncidentAlert.js +42 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794900000000-AddIncidentAlert.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/IncidentAlertService.js +1370 -0
- package/build/dist/Server/Services/IncidentAlertService.js.map +1 -0
- package/build/dist/Server/Services/IncidentService.js +164 -12
- package/build/dist/Server/Services/IncidentService.js.map +1 -1
- package/build/dist/Server/Services/IncidentStateTimelineService.js +21 -0
- package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +2 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCache.js +8 -0
- package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCache.js.map +1 -1
- package/build/dist/Types/Incident/IncidentAlertLink.js +34 -0
- package/build/dist/Types/Incident/IncidentAlertLink.js.map +1 -0
- package/build/dist/Types/Permission.js +42 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/Rum/WebVitals.js +25 -0
- package/build/dist/Types/Rum/WebVitals.js.map +1 -1
- package/build/dist/UI/Components/Detail/FieldLabel.js +2 -0
- package/build/dist/UI/Components/Detail/FieldLabel.js.map +1 -1
- package/build/dist/UI/Components/InfoCard/InfoCard.js +22 -1
- package/build/dist/UI/Components/InfoCard/InfoCard.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +66 -14
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/UI/Components/Table/TableHeader.js +18 -2
- package/build/dist/UI/Components/Table/TableHeader.js.map +1 -1
- package/build/dist/UI/Components/Tooltip/InfoTooltip.js +28 -0
- package/build/dist/UI/Components/Tooltip/InfoTooltip.js.map +1 -0
- package/build/dist/UI/Utils/PermissionGate.js +4 -2
- package/build/dist/UI/Utils/PermissionGate.js.map +1 -1
- package/build/dist/Utils/Incident/IncidentFromAlerts.js +306 -0
- package/build/dist/Utils/Incident/IncidentFromAlerts.js.map +1 -0
- package/build/dist/Utils/Rum/SessionTraceState.js +154 -0
- package/build/dist/Utils/Rum/SessionTraceState.js.map +1 -0
- package/package.json +1 -1
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
import React from "react";
|
|
10
10
|
import { MemoryRouter } from "react-router-dom";
|
|
11
11
|
import DiscoveryPage from "../../../../App/FeatureSet/Dashboard/src/Pages/NetworkDevice/Discovery";
|
|
12
|
+
import { NETWORK_DEVICE_METRIC_DESCRIPTIONS } from "../../../../App/FeatureSet/Dashboard/src/Components/MetricDescriptions/NetworkDeviceMetricDescriptions";
|
|
12
13
|
import ProbeUtil from "../../../../App/FeatureSet/Dashboard/src/Utils/Probe";
|
|
13
14
|
import NetworkDeviceDiscoveryScan, {
|
|
14
15
|
DiscoveredNetworkDevice,
|
|
@@ -361,6 +362,44 @@ describe("Discovery page live progress wiring", () => {
|
|
|
361
362
|
expect(screen.getByText("Not started")).toBeInTheDocument();
|
|
362
363
|
});
|
|
363
364
|
|
|
365
|
+
test("only the Responded Hosts column carries a header (i), saying what it counts", async () => {
|
|
366
|
+
await renderPage(
|
|
367
|
+
scan({
|
|
368
|
+
status: "Completed",
|
|
369
|
+
scannedHostCount: 15360,
|
|
370
|
+
respondedHostCount: 1,
|
|
371
|
+
discoveredDevices: [
|
|
372
|
+
{ ipAddress: "10.240.0.221", snmpReachable: true },
|
|
373
|
+
HOST,
|
|
374
|
+
],
|
|
375
|
+
}),
|
|
376
|
+
);
|
|
377
|
+
|
|
378
|
+
const explained: Array<[string, string | undefined]> =
|
|
379
|
+
capturedTable!.columns
|
|
380
|
+
.filter((item: Column<NetworkDeviceDiscoveryScan>) => {
|
|
381
|
+
return Boolean(item.headerTooltip);
|
|
382
|
+
})
|
|
383
|
+
.map(
|
|
384
|
+
(
|
|
385
|
+
item: Column<NetworkDeviceDiscoveryScan>,
|
|
386
|
+
): [string, string | undefined] => {
|
|
387
|
+
return [item.title, item.headerTooltip];
|
|
388
|
+
},
|
|
389
|
+
);
|
|
390
|
+
|
|
391
|
+
expect(explained).toEqual([
|
|
392
|
+
[
|
|
393
|
+
"Responded Hosts",
|
|
394
|
+
NETWORK_DEVICE_METRIC_DESCRIPTIONS.discoveryRespondedHosts,
|
|
395
|
+
],
|
|
396
|
+
]);
|
|
397
|
+
|
|
398
|
+
// The cell the (i) explains: SNMP responders, then the ping-only line.
|
|
399
|
+
expect(screen.getByText("1 of 15360 hosts")).toBeInTheDocument();
|
|
400
|
+
expect(screen.getByText("+ 1 alive without SNMP")).toBeInTheDocument();
|
|
401
|
+
});
|
|
402
|
+
|
|
364
403
|
test("reviewing a live result explains that importing is safe while discovery continues", async () => {
|
|
365
404
|
await renderPage(scan({ discoveredDevices: [HOST] }));
|
|
366
405
|
await act(async () => {
|
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
import "@testing-library/jest-dom";
|
|
2
|
+
import {
|
|
3
|
+
afterEach,
|
|
4
|
+
beforeEach,
|
|
5
|
+
describe,
|
|
6
|
+
expect,
|
|
7
|
+
jest,
|
|
8
|
+
test,
|
|
9
|
+
} from "@jest/globals";
|
|
10
|
+
import {
|
|
11
|
+
act,
|
|
12
|
+
cleanup,
|
|
13
|
+
fireEvent,
|
|
14
|
+
render,
|
|
15
|
+
RenderResult,
|
|
16
|
+
screen,
|
|
17
|
+
} from "@testing-library/react";
|
|
18
|
+
import * as React from "react";
|
|
19
|
+
import getJestMockFunction, { MockFunction } from "../../MockType";
|
|
20
|
+
|
|
21
|
+
/*
|
|
22
|
+
* The Docker Swarm Clusters list (Pages/DockerSwarm/Clusters.tsx) is a
|
|
23
|
+
* ModelTable. It is rendered with the table replaced by a recorder, so the
|
|
24
|
+
* columns the page builds - header tooltip and cell renderer - can be read
|
|
25
|
+
* and run; the recorded count columns are then drawn by the shared Table,
|
|
26
|
+
* the way BaseModelTable spreads them, to check the (i) a customer hovers.
|
|
27
|
+
*
|
|
28
|
+
* Nodes, Services and Tasks read the counts cached on the cluster row
|
|
29
|
+
* (nodeCount / readyNodeCount, serviceCount, taskCount / runningTaskCount),
|
|
30
|
+
* which ingest rewrites from each inventory snapshot. The accuracy of the
|
|
31
|
+
* words against the ingest code is pinned in DockerSwarmMetricTooltips.test.ts.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
const modelTableMock: MockFunction = getJestMockFunction();
|
|
35
|
+
const countMock: MockFunction = getJestMockFunction();
|
|
36
|
+
|
|
37
|
+
jest.mock("react-i18next", () => {
|
|
38
|
+
return {
|
|
39
|
+
useTranslation: () => {
|
|
40
|
+
return {
|
|
41
|
+
t: (key: string, opts?: { defaultValue?: string }): string => {
|
|
42
|
+
return opts?.defaultValue ?? key;
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
jest.mock("../../../UI/Utils/ModelAPI/ModelAPI", () => {
|
|
50
|
+
return {
|
|
51
|
+
__esModule: true,
|
|
52
|
+
default: {
|
|
53
|
+
count: (...args: Array<unknown>): unknown => {
|
|
54
|
+
return countMock(...args);
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
jest.mock("../../../UI/Utils/Navigation", () => {
|
|
61
|
+
return {
|
|
62
|
+
__esModule: true,
|
|
63
|
+
default: {
|
|
64
|
+
getFirstParam: (): undefined => {
|
|
65
|
+
return undefined;
|
|
66
|
+
},
|
|
67
|
+
navigate: (): void => {},
|
|
68
|
+
isOnThisPage: (): boolean => {
|
|
69
|
+
return false;
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
jest.mock("../../../UI/Components/ModelTable/ModelTable", () => {
|
|
76
|
+
return {
|
|
77
|
+
__esModule: true,
|
|
78
|
+
default: (props: unknown) => {
|
|
79
|
+
modelTableMock(props);
|
|
80
|
+
return <div data-testid="model-table" />;
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
jest.mock(
|
|
86
|
+
"../../../../App/FeatureSet/Dashboard/src/Components/ResourceOwners/useResourceOwners",
|
|
87
|
+
() => {
|
|
88
|
+
const actual: Record<string, unknown> = jest.requireActual(
|
|
89
|
+
"../../../../App/FeatureSet/Dashboard/src/Components/ResourceOwners/useResourceOwners",
|
|
90
|
+
) as Record<string, unknown>;
|
|
91
|
+
return {
|
|
92
|
+
...actual,
|
|
93
|
+
__esModule: true,
|
|
94
|
+
default: () => {
|
|
95
|
+
return {
|
|
96
|
+
getOwnersForResource: () => {
|
|
97
|
+
return [];
|
|
98
|
+
},
|
|
99
|
+
isLoadingOwners: false,
|
|
100
|
+
onResourcesFetched: () => {},
|
|
101
|
+
filterBar: null,
|
|
102
|
+
mergeFiltersIntoQuery: (query: unknown) => {
|
|
103
|
+
return query;
|
|
104
|
+
},
|
|
105
|
+
facetSaveState: undefined,
|
|
106
|
+
restoreFacetState: () => {},
|
|
107
|
+
};
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
},
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
jest.mock("../../../UI/Components/BulkUpdate/BulkLabelActions", () => {
|
|
114
|
+
return {
|
|
115
|
+
__esModule: true,
|
|
116
|
+
default: () => {
|
|
117
|
+
return { bulkActions: [], modals: null };
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
jest.mock("../../../UI/Components/BulkUpdate/BulkOwnerActions", () => {
|
|
123
|
+
return {
|
|
124
|
+
__esModule: true,
|
|
125
|
+
default: () => {
|
|
126
|
+
return { bulkActions: [], modals: null };
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
jest.mock("../../../UI/Components/BulkUpdate/BulkArchiveActions", () => {
|
|
132
|
+
return {
|
|
133
|
+
__esModule: true,
|
|
134
|
+
default: () => {
|
|
135
|
+
return { archiveBulkActions: [] };
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
import DockerSwarmClusters from "../../../../App/FeatureSet/Dashboard/src/Pages/DockerSwarm/Clusters";
|
|
141
|
+
import PageComponentProps from "../../../../App/FeatureSet/Dashboard/src/Pages/PageComponentProps";
|
|
142
|
+
import { DOCKER_SWARM_METRIC_DESCRIPTIONS } from "../../../../App/FeatureSet/Dashboard/src/Components/MetricDescriptions/DockerSwarmMetricDescriptions";
|
|
143
|
+
import Table from "../../../UI/Components/Table/Table";
|
|
144
|
+
import Column from "../../../UI/Components/Table/Types/Column";
|
|
145
|
+
import FieldType from "../../../UI/Components/Types/FieldType";
|
|
146
|
+
import SortOrder from "../../../Types/BaseDatabase/SortOrder";
|
|
147
|
+
import GenericObject from "../../../Types/GenericObject";
|
|
148
|
+
|
|
149
|
+
const T: typeof DOCKER_SWARM_METRIC_DESCRIPTIONS =
|
|
150
|
+
DOCKER_SWARM_METRIC_DESCRIPTIONS;
|
|
151
|
+
|
|
152
|
+
const PAGE_PROPS: PageComponentProps = {} as PageComponentProps;
|
|
153
|
+
|
|
154
|
+
const COUNT_COLUMNS: Array<[string, string]> = [
|
|
155
|
+
["Nodes", T.clusterListNodes],
|
|
156
|
+
["Services", T.clusterListServices],
|
|
157
|
+
["Tasks", T.clusterListTasks],
|
|
158
|
+
];
|
|
159
|
+
|
|
160
|
+
type CapturedColumn = {
|
|
161
|
+
field: Record<string, unknown>;
|
|
162
|
+
title: string;
|
|
163
|
+
type: FieldType;
|
|
164
|
+
headerTooltip?: string | undefined;
|
|
165
|
+
hideOnMobile?: boolean | undefined;
|
|
166
|
+
getElement?: ((item: unknown) => React.ReactElement) | undefined;
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
async function flush(): Promise<void> {
|
|
170
|
+
for (let i: number = 0; i < 8; i++) {
|
|
171
|
+
await act(async () => {
|
|
172
|
+
await Promise.resolve();
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async function capturedColumns(): Promise<Array<CapturedColumn>> {
|
|
178
|
+
render(<DockerSwarmClusters {...PAGE_PROPS} />);
|
|
179
|
+
await flush();
|
|
180
|
+
|
|
181
|
+
expect(modelTableMock).toHaveBeenCalled();
|
|
182
|
+
|
|
183
|
+
const props: { columns: Array<CapturedColumn> } = modelTableMock.mock.calls[
|
|
184
|
+
modelTableMock.mock.calls.length - 1
|
|
185
|
+
]![0] as { columns: Array<CapturedColumn> };
|
|
186
|
+
|
|
187
|
+
cleanup();
|
|
188
|
+
|
|
189
|
+
return props.columns;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function column(columns: Array<CapturedColumn>, title: string): CapturedColumn {
|
|
193
|
+
const found: Array<CapturedColumn> = columns.filter(
|
|
194
|
+
(c: CapturedColumn): boolean => {
|
|
195
|
+
return c.title === title;
|
|
196
|
+
},
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
expect(found).toHaveLength(1);
|
|
200
|
+
|
|
201
|
+
return found[0]!;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function cellText(
|
|
205
|
+
columnDef: CapturedColumn,
|
|
206
|
+
item: Record<string, unknown>,
|
|
207
|
+
): { text: string; className: string } {
|
|
208
|
+
const result: RenderResult = render(columnDef.getElement!(item));
|
|
209
|
+
const root: HTMLElement = result.container.firstElementChild as HTMLElement;
|
|
210
|
+
const out: { text: string; className: string } = {
|
|
211
|
+
text: (root.textContent || "").trim(),
|
|
212
|
+
className: root.className,
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
result.unmount();
|
|
216
|
+
|
|
217
|
+
return out;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
async function hover(trigger: HTMLElement): Promise<void> {
|
|
221
|
+
fireEvent.mouseEnter(trigger);
|
|
222
|
+
await act(async () => {
|
|
223
|
+
jest.advanceTimersByTime(200);
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
beforeEach(() => {
|
|
228
|
+
jest.useFakeTimers();
|
|
229
|
+
modelTableMock.mockReset();
|
|
230
|
+
countMock.mockReset();
|
|
231
|
+
// A project that already has a cluster, so the list (not the guide) shows.
|
|
232
|
+
countMock.mockResolvedValue(1);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
afterEach(() => {
|
|
236
|
+
cleanup();
|
|
237
|
+
jest.useRealTimers();
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
describe("Docker Swarm Clusters list columns", () => {
|
|
241
|
+
test("Nodes, Services and Tasks carry their header tooltips; the rest carry none", async () => {
|
|
242
|
+
const tooltips: Record<string, string | undefined> = {};
|
|
243
|
+
|
|
244
|
+
for (const c of await capturedColumns()) {
|
|
245
|
+
tooltips[c.title] = c.headerTooltip;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
expect(tooltips).toEqual({
|
|
249
|
+
Name: undefined,
|
|
250
|
+
Status: undefined,
|
|
251
|
+
Nodes: T.clusterListNodes,
|
|
252
|
+
Services: T.clusterListServices,
|
|
253
|
+
Tasks: T.clusterListTasks,
|
|
254
|
+
"Docker Version": undefined,
|
|
255
|
+
"Last Seen": undefined,
|
|
256
|
+
Labels: undefined,
|
|
257
|
+
Owners: undefined,
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
test.each(COUNT_COLUMNS)(
|
|
262
|
+
"%s reads the counts cached on the cluster row",
|
|
263
|
+
async (title: string) => {
|
|
264
|
+
const fields: Record<string, Array<string>> = {
|
|
265
|
+
Nodes: ["nodeCount", "readyNodeCount"],
|
|
266
|
+
Services: ["serviceCount"],
|
|
267
|
+
Tasks: ["taskCount", "runningTaskCount"],
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
expect(
|
|
271
|
+
Object.keys(column(await capturedColumns(), title).field).sort(),
|
|
272
|
+
).toEqual([...fields[title]!].sort());
|
|
273
|
+
},
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
test("Nodes is ready out of total, and red unless every node is ready, as its text says", async () => {
|
|
277
|
+
const nodes: CapturedColumn = column(await capturedColumns(), "Nodes");
|
|
278
|
+
|
|
279
|
+
const allReady: { text: string; className: string } = cellText(nodes, {
|
|
280
|
+
nodeCount: 3,
|
|
281
|
+
readyNodeCount: 3,
|
|
282
|
+
});
|
|
283
|
+
expect(allReady.text).toBe("3/3 ready");
|
|
284
|
+
expect(allReady.className).not.toContain("text-red-700");
|
|
285
|
+
|
|
286
|
+
const oneDown: { text: string; className: string } = cellText(nodes, {
|
|
287
|
+
nodeCount: 3,
|
|
288
|
+
readyNodeCount: 2,
|
|
289
|
+
});
|
|
290
|
+
expect(oneDown.text).toBe("2/3 ready");
|
|
291
|
+
expect(oneDown.className).toContain("text-red-700");
|
|
292
|
+
|
|
293
|
+
expect(cellText(nodes, {}).text).toBe("—");
|
|
294
|
+
expect(T.clusterListNodes).toContain("shown as ready out of total");
|
|
295
|
+
expect(T.clusterListNodes).toContain(
|
|
296
|
+
"turns red when any node is not ready",
|
|
297
|
+
);
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
test("Services is the plain count, whatever the services' state", async () => {
|
|
301
|
+
const services: CapturedColumn = column(
|
|
302
|
+
await capturedColumns(),
|
|
303
|
+
"Services",
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
expect(cellText(services, { serviceCount: 7 }).text).toBe("7");
|
|
307
|
+
expect(cellText(services, {}).text).toBe("0");
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
test("Tasks is running out of the tasks Swarm wants running", async () => {
|
|
311
|
+
const tasks: CapturedColumn = column(await capturedColumns(), "Tasks");
|
|
312
|
+
|
|
313
|
+
expect(cellText(tasks, { taskCount: 12, runningTaskCount: 10 }).text).toBe(
|
|
314
|
+
"10/12 running",
|
|
315
|
+
);
|
|
316
|
+
expect(cellText(tasks, {}).text).toBe("—");
|
|
317
|
+
expect(T.clusterListTasks).toContain(
|
|
318
|
+
"shown as how many are actually running out of that total",
|
|
319
|
+
);
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
describe("the count (i)s as a customer meets them", () => {
|
|
324
|
+
async function renderCounts(onSortChanged: () => void): Promise<void> {
|
|
325
|
+
const columns: Array<CapturedColumn> = (await capturedColumns()).filter(
|
|
326
|
+
(c: CapturedColumn): boolean => {
|
|
327
|
+
return COUNT_COLUMNS.some((entry: [string, string]): boolean => {
|
|
328
|
+
return entry[0] === c.title;
|
|
329
|
+
});
|
|
330
|
+
},
|
|
331
|
+
);
|
|
332
|
+
const tableColumns: Array<Column<GenericObject>> = columns.map(
|
|
333
|
+
(c: CapturedColumn): Column<GenericObject> => {
|
|
334
|
+
return {
|
|
335
|
+
title: c.title,
|
|
336
|
+
type: c.type,
|
|
337
|
+
key: Object.keys(c.field)[0] as keyof GenericObject,
|
|
338
|
+
headerTooltip: c.headerTooltip,
|
|
339
|
+
hideOnMobile: c.hideOnMobile,
|
|
340
|
+
getElement: c.getElement as
|
|
341
|
+
| ((item: GenericObject) => React.ReactElement)
|
|
342
|
+
| undefined,
|
|
343
|
+
};
|
|
344
|
+
},
|
|
345
|
+
);
|
|
346
|
+
const rows: Array<GenericObject> = [
|
|
347
|
+
{
|
|
348
|
+
_id: "c1",
|
|
349
|
+
nodeCount: 3,
|
|
350
|
+
readyNodeCount: 2,
|
|
351
|
+
serviceCount: 7,
|
|
352
|
+
taskCount: 12,
|
|
353
|
+
runningTaskCount: 10,
|
|
354
|
+
},
|
|
355
|
+
];
|
|
356
|
+
|
|
357
|
+
render(
|
|
358
|
+
<Table<GenericObject>
|
|
359
|
+
id="docker-swarm-clusters-table"
|
|
360
|
+
data={rows}
|
|
361
|
+
columns={tableColumns}
|
|
362
|
+
currentPageNumber={1}
|
|
363
|
+
totalItemsCount={rows.length}
|
|
364
|
+
itemsOnPage={10}
|
|
365
|
+
error=""
|
|
366
|
+
isLoading={false}
|
|
367
|
+
singularLabel="Cluster"
|
|
368
|
+
pluralLabel="Clusters"
|
|
369
|
+
sortOrder={SortOrder.Ascending}
|
|
370
|
+
sortBy={null}
|
|
371
|
+
onSortChanged={onSortChanged}
|
|
372
|
+
onNavigateToPage={() => {}}
|
|
373
|
+
/>,
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
test.each(COUNT_COLUMNS)(
|
|
378
|
+
"hovering the %s (i) explains the count",
|
|
379
|
+
async (title: string, text: string) => {
|
|
380
|
+
await renderCounts(() => {});
|
|
381
|
+
|
|
382
|
+
const info: HTMLElement = screen.getByRole("button", {
|
|
383
|
+
name: `About ${title}`,
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
expect(info.closest("thead")).not.toBeNull();
|
|
387
|
+
expect(info.parentElement!.closest("button, a")).toBeNull();
|
|
388
|
+
|
|
389
|
+
await hover(info);
|
|
390
|
+
expect(screen.getByRole("tooltip")).toHaveTextContent(text);
|
|
391
|
+
},
|
|
392
|
+
);
|
|
393
|
+
|
|
394
|
+
test("asking what a count means does not sort the list", async () => {
|
|
395
|
+
const onSortChanged: MockFunction = getJestMockFunction();
|
|
396
|
+
|
|
397
|
+
await renderCounts(() => {
|
|
398
|
+
onSortChanged();
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
for (const [title] of COUNT_COLUMNS) {
|
|
402
|
+
const info: HTMLElement = screen.getByRole("button", {
|
|
403
|
+
name: `About ${title}`,
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
fireEvent.click(info);
|
|
407
|
+
fireEvent.keyDown(info, { key: "Enter" });
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
expect(onSortChanged).not.toHaveBeenCalled();
|
|
411
|
+
expect(screen.getByText("2/3 ready")).toBeInTheDocument();
|
|
412
|
+
expect(screen.getByText("10/12 running")).toBeInTheDocument();
|
|
413
|
+
});
|
|
414
|
+
});
|