@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
|
@@ -0,0 +1,437 @@
|
|
|
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
|
+
cleanup,
|
|
12
|
+
fireEvent,
|
|
13
|
+
render,
|
|
14
|
+
screen,
|
|
15
|
+
within,
|
|
16
|
+
} from "@testing-library/react";
|
|
17
|
+
import * as React from "react";
|
|
18
|
+
import getJestMockFunction, { MockFunction } from "../../MockType";
|
|
19
|
+
import {
|
|
20
|
+
explanationOnFocus,
|
|
21
|
+
explanationOnHover,
|
|
22
|
+
expectNotNestedInControl,
|
|
23
|
+
infoButtonsFor,
|
|
24
|
+
infoLabels,
|
|
25
|
+
} from "./HostTooltipHarness";
|
|
26
|
+
import { expectReadableDescriptionRecord } from "./MetricDescriptionRules";
|
|
27
|
+
|
|
28
|
+
/*
|
|
29
|
+
* The (i) tooltips on two telemetry-resource tabs that are not overviews:
|
|
30
|
+
*
|
|
31
|
+
* - Cloud environment > Instances, whose CPU and Memory columns show the
|
|
32
|
+
* one reading ingest mirrors onto each instance row. The table is
|
|
33
|
+
* replaced by a recorder so the columns the page builds - header
|
|
34
|
+
* tooltip and cell renderer - can be read, run, and fed to the real
|
|
35
|
+
* table header;
|
|
36
|
+
* - Service > Profiles, whose flame graph has no title of its own - the
|
|
37
|
+
* heading above it, "Where the time is going", carries the (i). The
|
|
38
|
+
* page is rendered for real with the flame graph and the profile list
|
|
39
|
+
* replaced by recorders, so the window and type the text promises can
|
|
40
|
+
* be read off the props the flame graph receives.
|
|
41
|
+
*
|
|
42
|
+
* jest.mock is hoisted above the imports, so the factories only close over
|
|
43
|
+
* names that start with "mock".
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
const mockModelTable: MockFunction = getJestMockFunction();
|
|
47
|
+
const mockFlamegraph: MockFunction = getJestMockFunction();
|
|
48
|
+
|
|
49
|
+
jest.mock("react-i18next", () => {
|
|
50
|
+
return {
|
|
51
|
+
useTranslation: () => {
|
|
52
|
+
return {
|
|
53
|
+
t: (key: string, opts?: { defaultValue?: string }): string => {
|
|
54
|
+
return opts?.defaultValue ?? key;
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
jest.mock("../../../UI/Utils/Navigation", () => {
|
|
62
|
+
return {
|
|
63
|
+
__esModule: true,
|
|
64
|
+
default: {
|
|
65
|
+
getLastParamAsObjectID: () => {
|
|
66
|
+
const { default: ObjectIDType } = jest.requireActual(
|
|
67
|
+
"../../../Types/ObjectID",
|
|
68
|
+
) as { default: new (id: string) => unknown };
|
|
69
|
+
return new ObjectIDType("0193c0de-4444-4aaa-8bbb-000000000004");
|
|
70
|
+
},
|
|
71
|
+
navigate: () => {},
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
jest.mock("../../../UI/Components/ModelTable/ModelTable", () => {
|
|
77
|
+
return {
|
|
78
|
+
__esModule: true,
|
|
79
|
+
default: (props: unknown) => {
|
|
80
|
+
mockModelTable(props);
|
|
81
|
+
return <div data-testid="model-table" />;
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
jest.mock(
|
|
87
|
+
"../../../../App/FeatureSet/Dashboard/src/Components/Profiles/AggregatedFlamegraph",
|
|
88
|
+
() => {
|
|
89
|
+
return {
|
|
90
|
+
__esModule: true,
|
|
91
|
+
default: (props: unknown) => {
|
|
92
|
+
mockFlamegraph(props);
|
|
93
|
+
return <div data-testid="aggregated-flamegraph" />;
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
},
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
jest.mock(
|
|
100
|
+
"../../../../App/FeatureSet/Dashboard/src/Components/Profiles/ProfileTable",
|
|
101
|
+
() => {
|
|
102
|
+
return {
|
|
103
|
+
__esModule: true,
|
|
104
|
+
default: () => {
|
|
105
|
+
return <div data-testid="profile-table" />;
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
},
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
import CloudResourceInstances from "../../../../App/FeatureSet/Dashboard/src/Pages/Cloud/View/Instances";
|
|
112
|
+
import ServiceProfiles from "../../../../App/FeatureSet/Dashboard/src/Pages/Service/View/Profiles";
|
|
113
|
+
import PageComponentProps from "../../../../App/FeatureSet/Dashboard/src/Pages/PageComponentProps";
|
|
114
|
+
import {
|
|
115
|
+
CLOUD_INSTANCE_METRIC_DESCRIPTIONS,
|
|
116
|
+
CloudInstanceMetric,
|
|
117
|
+
} from "../../../../App/FeatureSet/Dashboard/src/Components/MetricDescriptions/CloudMetricDescriptions";
|
|
118
|
+
import {
|
|
119
|
+
SERVICE_PROFILE_METRIC_DESCRIPTIONS,
|
|
120
|
+
ServiceProfileMetric,
|
|
121
|
+
} from "../../../../App/FeatureSet/Dashboard/src/Components/MetricDescriptions/ServiceMetricDescriptions";
|
|
122
|
+
import TableHeader from "../../../UI/Components/Table/TableHeader";
|
|
123
|
+
import Columns from "../../../UI/Components/Table/Types/Columns";
|
|
124
|
+
import FieldType from "../../../UI/Components/Types/FieldType";
|
|
125
|
+
import SortOrder from "../../../Types/BaseDatabase/SortOrder";
|
|
126
|
+
|
|
127
|
+
const CLOUD: Record<CloudInstanceMetric, string> =
|
|
128
|
+
CLOUD_INSTANCE_METRIC_DESCRIPTIONS;
|
|
129
|
+
const PROFILES: Record<ServiceProfileMetric, string> =
|
|
130
|
+
SERVICE_PROFILE_METRIC_DESCRIPTIONS;
|
|
131
|
+
|
|
132
|
+
// The pages read their ids from the route, not from these props.
|
|
133
|
+
const PAGE_PROPS: PageComponentProps = {} as PageComponentProps;
|
|
134
|
+
|
|
135
|
+
const MIB: number = 1024 * 1024;
|
|
136
|
+
const MINUTE_MS: number = 60 * 1000;
|
|
137
|
+
const FLAMEGRAPH_TITLE: string = "Where the time is going";
|
|
138
|
+
// The pill's name also carries its icon.
|
|
139
|
+
const EVERYTHING_PILL: RegExp = /Everything/;
|
|
140
|
+
|
|
141
|
+
beforeEach(() => {
|
|
142
|
+
jest.useFakeTimers();
|
|
143
|
+
mockModelTable.mockReset();
|
|
144
|
+
mockFlamegraph.mockReset();
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
afterEach(() => {
|
|
148
|
+
cleanup();
|
|
149
|
+
jest.useRealTimers();
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// ------------------------------------------------------- Cloud Instances
|
|
153
|
+
|
|
154
|
+
type CapturedColumn = {
|
|
155
|
+
field: Record<string, unknown>;
|
|
156
|
+
title: string;
|
|
157
|
+
type: FieldType;
|
|
158
|
+
headerTooltip?: string | undefined;
|
|
159
|
+
disableSort?: boolean | undefined;
|
|
160
|
+
getElement?: ((item: unknown) => React.ReactElement) | undefined;
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
function instanceColumns(): Array<CapturedColumn> {
|
|
164
|
+
render(<CloudResourceInstances {...PAGE_PROPS} />);
|
|
165
|
+
expect(mockModelTable).toHaveBeenCalled();
|
|
166
|
+
|
|
167
|
+
const props: { columns: Array<CapturedColumn> } = mockModelTable.mock.calls[
|
|
168
|
+
mockModelTable.mock.calls.length - 1
|
|
169
|
+
]![0] as { columns: Array<CapturedColumn> };
|
|
170
|
+
|
|
171
|
+
return props.columns;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function instanceColumn(title: string): CapturedColumn {
|
|
175
|
+
const found: CapturedColumn | undefined = instanceColumns().find(
|
|
176
|
+
(column: CapturedColumn): boolean => {
|
|
177
|
+
return column.title === title;
|
|
178
|
+
},
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
expect(found).toBeDefined();
|
|
182
|
+
|
|
183
|
+
return found!;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function cellText(column: CapturedColumn, item: unknown): string {
|
|
187
|
+
const cell: ReturnType<typeof render> = render(column.getElement!(item));
|
|
188
|
+
const text: string = cell.container.textContent || "";
|
|
189
|
+
|
|
190
|
+
cell.unmount();
|
|
191
|
+
|
|
192
|
+
return text;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
type HeaderRow = Record<string, unknown>;
|
|
196
|
+
|
|
197
|
+
/*
|
|
198
|
+
* The captured columns, drawn by the real table header the way ModelTable
|
|
199
|
+
* hands them over (ModelTable spreads each column, headerTooltip included,
|
|
200
|
+
* and keys it by its field).
|
|
201
|
+
*/
|
|
202
|
+
function renderInstanceHeader(): void {
|
|
203
|
+
const columns: Columns<HeaderRow> = instanceColumns().map(
|
|
204
|
+
(column: CapturedColumn) => {
|
|
205
|
+
return {
|
|
206
|
+
title: column.title,
|
|
207
|
+
type: column.type,
|
|
208
|
+
key: Object.keys(column.field)[0] as string,
|
|
209
|
+
headerTooltip: column.headerTooltip,
|
|
210
|
+
disableSort: column.disableSort,
|
|
211
|
+
};
|
|
212
|
+
},
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
cleanup();
|
|
216
|
+
|
|
217
|
+
render(
|
|
218
|
+
<table>
|
|
219
|
+
<TableHeader<HeaderRow>
|
|
220
|
+
id="cloud-resource-instances-header"
|
|
221
|
+
columns={columns}
|
|
222
|
+
onSortChanged={() => {}}
|
|
223
|
+
isBulkActionsEnabled={false}
|
|
224
|
+
onAllItemsOnThePageSelected={undefined}
|
|
225
|
+
onAllItemsDeselected={undefined}
|
|
226
|
+
hasTableItems={true}
|
|
227
|
+
isAllItemsOnThePageSelected={false}
|
|
228
|
+
sortBy={null}
|
|
229
|
+
sortOrder={SortOrder.Descending}
|
|
230
|
+
/>
|
|
231
|
+
</table>,
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
describe("Cloud Instances tab", () => {
|
|
236
|
+
test("the texts read as short, finished sentences", () => {
|
|
237
|
+
expectReadableDescriptionRecord(
|
|
238
|
+
CLOUD_INSTANCE_METRIC_DESCRIPTIONS,
|
|
239
|
+
"CLOUD_INSTANCE_METRIC_DESCRIPTIONS",
|
|
240
|
+
);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
test("CPU and Memory carry their own text; the other columns carry none", () => {
|
|
244
|
+
const tooltips: Record<string, string | undefined> = {};
|
|
245
|
+
|
|
246
|
+
for (const column of instanceColumns()) {
|
|
247
|
+
tooltips[column.title] = column.headerTooltip;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
expect(tooltips).toEqual({
|
|
251
|
+
Instance: undefined,
|
|
252
|
+
Status: undefined,
|
|
253
|
+
CPU: CLOUD.cpu,
|
|
254
|
+
Memory: CLOUD.memory,
|
|
255
|
+
"Last Seen": undefined,
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
test("the header shows an (i) beside CPU and Memory only, each with its own text", async () => {
|
|
260
|
+
renderInstanceHeader();
|
|
261
|
+
|
|
262
|
+
expect(infoLabels()).toEqual(["CPU", "Memory"]);
|
|
263
|
+
|
|
264
|
+
const cpu: HTMLElement = infoButtonsFor("CPU")[0]!;
|
|
265
|
+
const memory: HTMLElement = infoButtonsFor("Memory")[0]!;
|
|
266
|
+
|
|
267
|
+
expect(await explanationOnHover(cpu)).toBe(CLOUD.cpu);
|
|
268
|
+
expect(await explanationOnFocus(memory)).toBe(CLOUD.memory);
|
|
269
|
+
|
|
270
|
+
// Beside the sort button, never inside it.
|
|
271
|
+
expectNotNestedInControl(cpu);
|
|
272
|
+
expectNotNestedInControl(memory);
|
|
273
|
+
expect(
|
|
274
|
+
within(cpu.closest("th") as HTMLElement).getByRole("button", {
|
|
275
|
+
name: "CPU",
|
|
276
|
+
}),
|
|
277
|
+
).not.toContainElement(cpu);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
test("asking what CPU means does not sort the table", () => {
|
|
281
|
+
const onSortChanged: MockFunction = getJestMockFunction();
|
|
282
|
+
const columns: Columns<HeaderRow> = [
|
|
283
|
+
{
|
|
284
|
+
title: "CPU",
|
|
285
|
+
type: FieldType.Element,
|
|
286
|
+
key: "latestCpuPercent",
|
|
287
|
+
headerTooltip: instanceColumn("CPU").headerTooltip,
|
|
288
|
+
},
|
|
289
|
+
];
|
|
290
|
+
|
|
291
|
+
cleanup();
|
|
292
|
+
render(
|
|
293
|
+
<table>
|
|
294
|
+
<TableHeader<HeaderRow>
|
|
295
|
+
id="cloud-resource-instances-sort"
|
|
296
|
+
columns={columns}
|
|
297
|
+
onSortChanged={onSortChanged}
|
|
298
|
+
isBulkActionsEnabled={false}
|
|
299
|
+
onAllItemsOnThePageSelected={undefined}
|
|
300
|
+
onAllItemsDeselected={undefined}
|
|
301
|
+
hasTableItems={true}
|
|
302
|
+
isAllItemsOnThePageSelected={false}
|
|
303
|
+
sortBy={null}
|
|
304
|
+
sortOrder={SortOrder.Descending}
|
|
305
|
+
/>
|
|
306
|
+
</table>,
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
fireEvent.click(screen.getByRole("button", { name: "About CPU" }));
|
|
310
|
+
expect(onSortChanged).not.toHaveBeenCalled();
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
test("'can read above 100%': the CPU cell shows the stored percentage uncapped", () => {
|
|
314
|
+
const cpu: CapturedColumn = instanceColumn("CPU");
|
|
315
|
+
|
|
316
|
+
expect(cellText(cpu, { latestCpuPercent: 250 })).toBe("250.0%");
|
|
317
|
+
expect(cellText(cpu, { latestCpuPercent: 37.25 })).toBe("37.3%");
|
|
318
|
+
expect(CLOUD.cpu).toContain("so it can read above 100%");
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
test("'A dash means no reading has arrived': both cells show a dash without one", () => {
|
|
322
|
+
const cpu: CapturedColumn = instanceColumn("CPU");
|
|
323
|
+
const memory: CapturedColumn = instanceColumn("Memory");
|
|
324
|
+
|
|
325
|
+
expect(cellText(cpu, {})).toBe("—");
|
|
326
|
+
expect(cellText(cpu, { latestCpuPercent: undefined })).toBe("—");
|
|
327
|
+
expect(cellText(memory, {})).toBe("—");
|
|
328
|
+
expect(cellText(memory, { latestMemoryBytes: 512 * MIB })).toBe("512 MiB");
|
|
329
|
+
|
|
330
|
+
expect(CLOUD.cpu).toContain("A dash means no CPU reading has arrived.");
|
|
331
|
+
expect(CLOUD.memory).toContain(
|
|
332
|
+
"A dash means no memory reading has arrived.",
|
|
333
|
+
);
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
test("a Stale row keeps showing its last reading, as 'kept until a newer one arrives' says", () => {
|
|
337
|
+
const status: CapturedColumn = instanceColumn("Status");
|
|
338
|
+
const cpu: CapturedColumn = instanceColumn("CPU");
|
|
339
|
+
const staleRow: Record<string, unknown> = {
|
|
340
|
+
lastSeenAt: new Date(Date.now() - 60 * MINUTE_MS),
|
|
341
|
+
latestCpuPercent: 42,
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
expect(cellText(status, staleRow)).toBe("Stale");
|
|
345
|
+
expect(cellText(cpu, staleRow)).toBe("42.0%");
|
|
346
|
+
|
|
347
|
+
for (const key of ["cpu", "memory"] as const) {
|
|
348
|
+
expect(CLOUD[key]).toContain("kept until a newer one arrives");
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
// ------------------------------------------------------ Service Profiles
|
|
354
|
+
|
|
355
|
+
type FlamegraphProps = {
|
|
356
|
+
startTime: Date;
|
|
357
|
+
endTime: Date;
|
|
358
|
+
profileType?: string | undefined;
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
function lastFlamegraphProps(): FlamegraphProps {
|
|
362
|
+
expect(mockFlamegraph).toHaveBeenCalled();
|
|
363
|
+
|
|
364
|
+
return mockFlamegraph.mock.calls[
|
|
365
|
+
mockFlamegraph.mock.calls.length - 1
|
|
366
|
+
]![0] as FlamegraphProps;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function windowMinutes(props: FlamegraphProps): number {
|
|
370
|
+
return (props.endTime.getTime() - props.startTime.getTime()) / MINUTE_MS;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
describe("Service Profiles tab", () => {
|
|
374
|
+
test("the text reads as a short, finished sentence", () => {
|
|
375
|
+
expectReadableDescriptionRecord(
|
|
376
|
+
SERVICE_PROFILE_METRIC_DESCRIPTIONS,
|
|
377
|
+
"SERVICE_PROFILE_METRIC_DESCRIPTIONS",
|
|
378
|
+
);
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
test("the heading above the flame graph carries the page's only (i)", async () => {
|
|
382
|
+
render(<ServiceProfiles {...PAGE_PROPS} />);
|
|
383
|
+
|
|
384
|
+
expect(
|
|
385
|
+
screen.getByRole("heading", { name: FLAMEGRAPH_TITLE }),
|
|
386
|
+
).toBeInTheDocument();
|
|
387
|
+
expect(infoLabels()).toEqual([FLAMEGRAPH_TITLE]);
|
|
388
|
+
|
|
389
|
+
const info: HTMLElement = infoButtonsFor(FLAMEGRAPH_TITLE)[0]!;
|
|
390
|
+
|
|
391
|
+
expect(await explanationOnHover(info)).toBe(PROFILES.flamegraph);
|
|
392
|
+
expect(await explanationOnFocus(info)).toBe(PROFILES.flamegraph);
|
|
393
|
+
expectNotNestedInControl(info);
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
test("clicking the (i) changes neither the window nor the type", () => {
|
|
397
|
+
render(<ServiceProfiles {...PAGE_PROPS} />);
|
|
398
|
+
|
|
399
|
+
const before: FlamegraphProps = lastFlamegraphProps();
|
|
400
|
+
|
|
401
|
+
fireEvent.click(infoButtonsFor(FLAMEGRAPH_TITLE)[0]!);
|
|
402
|
+
|
|
403
|
+
const after: FlamegraphProps = lastFlamegraphProps();
|
|
404
|
+
|
|
405
|
+
expect(windowMinutes(after)).toBe(windowMinutes(before));
|
|
406
|
+
expect(after.profileType).toBe(before.profileType);
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
test("'the time window picked beside it': the flame graph follows the tab's chips", () => {
|
|
410
|
+
render(<ServiceProfiles {...PAGE_PROPS} />);
|
|
411
|
+
|
|
412
|
+
expect(windowMinutes(lastFlamegraphProps())).toBe(60);
|
|
413
|
+
|
|
414
|
+
fireEvent.click(screen.getByRole("button", { name: "15m" }));
|
|
415
|
+
expect(windowMinutes(lastFlamegraphProps())).toBe(15);
|
|
416
|
+
|
|
417
|
+
fireEvent.click(screen.getByRole("button", { name: "7d" }));
|
|
418
|
+
expect(windowMinutes(lastFlamegraphProps())).toBe(7 * 24 * 60);
|
|
419
|
+
|
|
420
|
+
expect(PROFILES.flamegraph).toContain(
|
|
421
|
+
"in the time window picked beside it",
|
|
422
|
+
);
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
test("'CPU time by default', and Everything sends no type at all", () => {
|
|
426
|
+
render(<ServiceProfiles {...PAGE_PROPS} />);
|
|
427
|
+
|
|
428
|
+
expect(lastFlamegraphProps().profileType).toBe("cpu");
|
|
429
|
+
expect(PROFILES.flamegraph).toContain("CPU time by default");
|
|
430
|
+
|
|
431
|
+
fireEvent.click(screen.getByRole("button", { name: EVERYTHING_PILL }));
|
|
432
|
+
expect(lastFlamegraphProps().profileType).toBeUndefined();
|
|
433
|
+
expect(PROFILES.flamegraph).toContain(
|
|
434
|
+
"every type added together for Everything",
|
|
435
|
+
);
|
|
436
|
+
});
|
|
437
|
+
});
|