@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,698 @@
|
|
|
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 { cleanup, fireEvent, render, screen } from "@testing-library/react";
|
|
11
|
+
import * as React from "react";
|
|
12
|
+
import getJestMockFunction, { MockFunction } from "../../MockType";
|
|
13
|
+
import {
|
|
14
|
+
explanationOnFocus,
|
|
15
|
+
explanationOnHover,
|
|
16
|
+
expectNotNestedInControl,
|
|
17
|
+
infoButtonsFor,
|
|
18
|
+
infoLabels,
|
|
19
|
+
never,
|
|
20
|
+
settle,
|
|
21
|
+
} from "./HostTooltipHarness";
|
|
22
|
+
|
|
23
|
+
/*
|
|
24
|
+
* The Host overview, rendered for real with its data layer mocked: every
|
|
25
|
+
* tile, chart card and Filesystems column shows an (i), each (i) explains
|
|
26
|
+
* its own metric, none sits inside another control, and the chart cards
|
|
27
|
+
* already carry theirs while the charts are still loading.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
const MODEL_ID: string = "84858d6c-1111-4aaa-8bbb-000000000001";
|
|
31
|
+
const GIB: number = 1024 * 1024 * 1024;
|
|
32
|
+
|
|
33
|
+
const getItemMock: MockFunction = getJestMockFunction();
|
|
34
|
+
const aggregateMock: MockFunction = getJestMockFunction();
|
|
35
|
+
|
|
36
|
+
jest.mock("../../../UI/Utils/ModelAPI/ModelAPI", () => {
|
|
37
|
+
return {
|
|
38
|
+
__esModule: true,
|
|
39
|
+
default: {
|
|
40
|
+
getItem: (...args: Array<unknown>) => {
|
|
41
|
+
return getItemMock(...args);
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
jest.mock("../../../UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI", () => {
|
|
48
|
+
return {
|
|
49
|
+
__esModule: true,
|
|
50
|
+
default: {
|
|
51
|
+
aggregate: (...args: Array<unknown>) => {
|
|
52
|
+
return aggregateMock(...args);
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
jest.mock("../../../UI/Utils/API/API", () => {
|
|
59
|
+
return {
|
|
60
|
+
__esModule: true,
|
|
61
|
+
default: {
|
|
62
|
+
getFriendlyMessage: (error: unknown) => {
|
|
63
|
+
return String((error as Error)?.message || error);
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
jest.mock("../../../UI/Utils/Navigation", () => {
|
|
70
|
+
return {
|
|
71
|
+
__esModule: true,
|
|
72
|
+
default: {
|
|
73
|
+
getLastParamAsObjectID: () => {
|
|
74
|
+
const { default: ObjectIDType } = jest.requireActual(
|
|
75
|
+
"../../../Types/ObjectID",
|
|
76
|
+
) as { default: new (id: string) => unknown };
|
|
77
|
+
return new ObjectIDType("84858d6c-1111-4aaa-8bbb-000000000001");
|
|
78
|
+
},
|
|
79
|
+
navigate: () => {},
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
jest.mock("../../../UI/Utils/Project", () => {
|
|
85
|
+
return {
|
|
86
|
+
__esModule: true,
|
|
87
|
+
default: {
|
|
88
|
+
getCurrentProjectId: () => {
|
|
89
|
+
const { default: ObjectIDType } = jest.requireActual(
|
|
90
|
+
"../../../Types/ObjectID",
|
|
91
|
+
) as { default: new (id: string) => unknown };
|
|
92
|
+
return new ObjectIDType("10000000-0000-4000-8000-000000000001");
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Recharts has nothing to measure under jsdom; the chart body is not under test.
|
|
99
|
+
jest.mock("../../../UI/Components/Charts/Line/LineChart", () => {
|
|
100
|
+
return {
|
|
101
|
+
__esModule: true,
|
|
102
|
+
default: () => {
|
|
103
|
+
return <div data-testid="line-chart" />;
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Covered by its own suite; it fetches three counts of its own.
|
|
109
|
+
jest.mock(
|
|
110
|
+
"../../../../App/FeatureSet/Dashboard/src/Components/ResourceActivity/ResourceActivityCards",
|
|
111
|
+
() => {
|
|
112
|
+
return {
|
|
113
|
+
__esModule: true,
|
|
114
|
+
default: () => {
|
|
115
|
+
return null;
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
},
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
// The refresh control and time picker are not what this suite looks at.
|
|
122
|
+
jest.mock(
|
|
123
|
+
"../../../../App/FeatureSet/Dashboard/src/Components/TelemetryResource/AutoRefreshControl",
|
|
124
|
+
() => {
|
|
125
|
+
return {
|
|
126
|
+
__esModule: true,
|
|
127
|
+
default: () => {
|
|
128
|
+
return null;
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
},
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
import HostOverview from "../../../../App/FeatureSet/Dashboard/src/Pages/Host/View/Overview";
|
|
135
|
+
import PageComponentProps from "../../../../App/FeatureSet/Dashboard/src/Pages/PageComponentProps";
|
|
136
|
+
import {
|
|
137
|
+
HOST_METRIC_DESCRIPTIONS,
|
|
138
|
+
HostMetric,
|
|
139
|
+
} from "../../../../App/FeatureSet/Dashboard/src/Components/MetricDescriptions/HostMetricDescriptions";
|
|
140
|
+
|
|
141
|
+
type Row = {
|
|
142
|
+
timestamp: Date;
|
|
143
|
+
value: number;
|
|
144
|
+
attributes?: Record<string, string>;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
interface AggregateCall {
|
|
148
|
+
aggregateBy: {
|
|
149
|
+
query: { name: string; attributes: Record<string, string> };
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function minutesAgo(minutes: number): Date {
|
|
154
|
+
return new Date(Date.now() - minutes * 60_000);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// One row per minute over the last half hour.
|
|
158
|
+
function perMinute(
|
|
159
|
+
value: (minute: number) => number,
|
|
160
|
+
attributes?: Record<string, string>,
|
|
161
|
+
): Array<Row> {
|
|
162
|
+
const rows: Array<Row> = [];
|
|
163
|
+
|
|
164
|
+
for (let minute: number = 29; minute >= 0; minute--) {
|
|
165
|
+
rows.push({
|
|
166
|
+
timestamp: minutesAgo(minute),
|
|
167
|
+
value: value(minute),
|
|
168
|
+
...(attributes ? { attributes } : {}),
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return rows;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function rowsFor(call: AggregateCall): Array<Row> {
|
|
176
|
+
const name: string = call.aggregateBy.query.name;
|
|
177
|
+
const attributes: Record<string, string> = call.aggregateBy.query.attributes;
|
|
178
|
+
|
|
179
|
+
switch (name) {
|
|
180
|
+
case "system.cpu.utilization":
|
|
181
|
+
return perMinute(() => {
|
|
182
|
+
return attributes["state"] === "user" ? 0.2 : 0.05;
|
|
183
|
+
});
|
|
184
|
+
case "system.memory.utilization":
|
|
185
|
+
return perMinute(() => {
|
|
186
|
+
return 0.5;
|
|
187
|
+
});
|
|
188
|
+
case "system.cpu.load_average.1m":
|
|
189
|
+
return perMinute(() => {
|
|
190
|
+
return 1.5;
|
|
191
|
+
});
|
|
192
|
+
case "system.processes.count":
|
|
193
|
+
return perMinute(() => {
|
|
194
|
+
return 3;
|
|
195
|
+
});
|
|
196
|
+
case "system.filesystem.usage":
|
|
197
|
+
return [
|
|
198
|
+
...perMinute(
|
|
199
|
+
() => {
|
|
200
|
+
return 40 * GIB;
|
|
201
|
+
},
|
|
202
|
+
{ mountpoint: "/", device: "/dev/sda1", type: "ext4", state: "used" },
|
|
203
|
+
),
|
|
204
|
+
...perMinute(
|
|
205
|
+
() => {
|
|
206
|
+
return 55 * GIB;
|
|
207
|
+
},
|
|
208
|
+
{ mountpoint: "/", device: "/dev/sda1", type: "ext4", state: "free" },
|
|
209
|
+
),
|
|
210
|
+
...perMinute(
|
|
211
|
+
() => {
|
|
212
|
+
return 5 * GIB;
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
mountpoint: "/",
|
|
216
|
+
device: "/dev/sda1",
|
|
217
|
+
type: "ext4",
|
|
218
|
+
state: "reserved",
|
|
219
|
+
},
|
|
220
|
+
),
|
|
221
|
+
];
|
|
222
|
+
case "system.network.io":
|
|
223
|
+
return [
|
|
224
|
+
...perMinute(
|
|
225
|
+
(minute: number) => {
|
|
226
|
+
return (30 - minute) * 6_000_000;
|
|
227
|
+
},
|
|
228
|
+
{ device: "eth0", direction: "receive" },
|
|
229
|
+
),
|
|
230
|
+
...perMinute(
|
|
231
|
+
(minute: number) => {
|
|
232
|
+
return (30 - minute) * 1_200_000;
|
|
233
|
+
},
|
|
234
|
+
{ device: "eth0", direction: "transmit" },
|
|
235
|
+
),
|
|
236
|
+
];
|
|
237
|
+
case "oneuptime.host.heartbeat":
|
|
238
|
+
return perMinute(() => {
|
|
239
|
+
return 2;
|
|
240
|
+
});
|
|
241
|
+
default:
|
|
242
|
+
return [];
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function hostItem(): Record<string, unknown> {
|
|
247
|
+
return {
|
|
248
|
+
_id: MODEL_ID,
|
|
249
|
+
name: "web-01",
|
|
250
|
+
hostIdentifier: "web-01",
|
|
251
|
+
otelCollectorStatus: "connected",
|
|
252
|
+
lastSeenAt: new Date(),
|
|
253
|
+
cpuCores: 4,
|
|
254
|
+
totalMemoryBytes: 16 * GIB,
|
|
255
|
+
processCount: 212,
|
|
256
|
+
osType: "linux",
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const D: Record<HostMetric, string> = HOST_METRIC_DESCRIPTIONS;
|
|
261
|
+
|
|
262
|
+
// The Host pages read their ids from the route, not from these props.
|
|
263
|
+
const PAGE_PROPS: PageComponentProps = {} as PageComponentProps;
|
|
264
|
+
|
|
265
|
+
/*
|
|
266
|
+
* Every (i) on the loaded page, in document order: the five tiles, the
|
|
267
|
+
* five chart cards, then the two metric columns of the Filesystems table.
|
|
268
|
+
*/
|
|
269
|
+
const LOADED: Array<[string, number, HostMetric]> = [
|
|
270
|
+
["CPU", 0, "cpu"],
|
|
271
|
+
["Memory", 0, "memory"],
|
|
272
|
+
["Filesystem", 0, "filesystem"],
|
|
273
|
+
["Load avg (1m)", 0, "loadAverage"],
|
|
274
|
+
["Processes", 0, "processes"],
|
|
275
|
+
["Availability", 0, "availabilityChart"],
|
|
276
|
+
["CPU", 1, "cpuChart"],
|
|
277
|
+
["Memory", 1, "memoryChart"],
|
|
278
|
+
["Disk space", 0, "diskSpaceChart"],
|
|
279
|
+
["Network", 0, "networkChart"],
|
|
280
|
+
["Used / Total", 0, "filesystemUsedTotal"],
|
|
281
|
+
["Utilization", 0, "filesystemUtilization"],
|
|
282
|
+
];
|
|
283
|
+
|
|
284
|
+
const SKELETON: Array<[string, HostMetric]> = [
|
|
285
|
+
["Availability", "availabilityChart"],
|
|
286
|
+
["CPU", "cpuChart"],
|
|
287
|
+
["Memory", "memoryChart"],
|
|
288
|
+
["Disk space", "diskSpaceChart"],
|
|
289
|
+
["Network", "networkChart"],
|
|
290
|
+
];
|
|
291
|
+
|
|
292
|
+
async function renderLoaded(): Promise<void> {
|
|
293
|
+
render(<HostOverview {...PAGE_PROPS} />);
|
|
294
|
+
await settle();
|
|
295
|
+
// The page is on its loaded branch: the last tile has its value.
|
|
296
|
+
expect(screen.getByText("1.50")).toBeInTheDocument();
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
beforeEach(() => {
|
|
300
|
+
jest.useFakeTimers();
|
|
301
|
+
getItemMock.mockReset();
|
|
302
|
+
aggregateMock.mockReset();
|
|
303
|
+
getItemMock.mockResolvedValue(hostItem());
|
|
304
|
+
aggregateMock.mockImplementation((call: unknown) => {
|
|
305
|
+
return Promise.resolve({ data: rowsFor(call as AggregateCall) });
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
afterEach(() => {
|
|
310
|
+
cleanup();
|
|
311
|
+
jest.useRealTimers();
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
describe("Host overview, loaded", () => {
|
|
315
|
+
test("shows an (i) beside every metric, in page order, and nowhere else", async () => {
|
|
316
|
+
await renderLoaded();
|
|
317
|
+
|
|
318
|
+
expect(infoLabels()).toEqual(
|
|
319
|
+
LOADED.map((entry: [string, number, HostMetric]): string => {
|
|
320
|
+
return entry[0];
|
|
321
|
+
}),
|
|
322
|
+
);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
test.each(LOADED)(
|
|
326
|
+
"the (i) for %s (#%i) explains it with %s",
|
|
327
|
+
async (label: string, index: number, key: HostMetric) => {
|
|
328
|
+
await renderLoaded();
|
|
329
|
+
|
|
330
|
+
const button: HTMLElement = infoButtonsFor(label)[index]!;
|
|
331
|
+
|
|
332
|
+
expect(await explanationOnHover(button)).toBe(D[key]);
|
|
333
|
+
},
|
|
334
|
+
);
|
|
335
|
+
|
|
336
|
+
test("the tooltips also open from the keyboard", async () => {
|
|
337
|
+
await renderLoaded();
|
|
338
|
+
|
|
339
|
+
expect(await explanationOnFocus(infoButtonsFor("Load avg (1m)")[0]!)).toBe(
|
|
340
|
+
D.loadAverage,
|
|
341
|
+
);
|
|
342
|
+
expect(await explanationOnFocus(infoButtonsFor("Utilization")[0]!)).toBe(
|
|
343
|
+
D.filesystemUtilization,
|
|
344
|
+
);
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
test("no (i) is nested inside a button or a link", async () => {
|
|
348
|
+
await renderLoaded();
|
|
349
|
+
|
|
350
|
+
for (const [label, index] of LOADED) {
|
|
351
|
+
expectNotNestedInControl(infoButtonsFor(label)[index]!);
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
test("each (i) is keyboard reachable and never submits a form", async () => {
|
|
356
|
+
await renderLoaded();
|
|
357
|
+
|
|
358
|
+
for (const [label, index] of LOADED) {
|
|
359
|
+
const button: HTMLElement = infoButtonsFor(label)[index]!;
|
|
360
|
+
|
|
361
|
+
expect(button).toHaveAttribute("type", "button");
|
|
362
|
+
expect(button).not.toHaveAttribute("tabindex", "-1");
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
test("the tile (i) sits in the tile's title row, next to its title", async () => {
|
|
367
|
+
await renderLoaded();
|
|
368
|
+
|
|
369
|
+
const button: HTMLElement = infoButtonsFor("Load avg (1m)")[0]!;
|
|
370
|
+
|
|
371
|
+
expect(button.parentElement).toHaveTextContent("Load avg (1m)");
|
|
372
|
+
expect(button.parentElement?.textContent).not.toContain("1.50");
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
test("the Availability (i) comes before the uptime badge in the card header", async () => {
|
|
376
|
+
await renderLoaded();
|
|
377
|
+
|
|
378
|
+
const button: HTMLElement = infoButtonsFor("Availability")[0]!;
|
|
379
|
+
const badge: HTMLElement = screen.getByText("% uptime", { exact: false });
|
|
380
|
+
|
|
381
|
+
expect(
|
|
382
|
+
button.compareDocumentPosition(badge) & Node.DOCUMENT_POSITION_FOLLOWING,
|
|
383
|
+
).toBeTruthy();
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
test("the Filesystems header (i)s sit inside their own column headers", async () => {
|
|
387
|
+
await renderLoaded();
|
|
388
|
+
|
|
389
|
+
const usedTotal: HTMLElement = infoButtonsFor("Used / Total")[0]!;
|
|
390
|
+
const utilization: HTMLElement = infoButtonsFor("Utilization")[0]!;
|
|
391
|
+
|
|
392
|
+
expect(usedTotal.closest("th")).toHaveTextContent("Used / Total");
|
|
393
|
+
expect(utilization.closest("th")).toHaveTextContent("Utilization");
|
|
394
|
+
expect(usedTotal.closest("th")).toHaveAttribute("scope", "col");
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
test("clicking an (i) in a table header does nothing but explain", async () => {
|
|
398
|
+
await renderLoaded();
|
|
399
|
+
|
|
400
|
+
const button: HTMLElement = infoButtonsFor("Utilization")[0]!;
|
|
401
|
+
const notCancelled: boolean = fireEvent.click(button);
|
|
402
|
+
|
|
403
|
+
expect(notCancelled).toBe(false);
|
|
404
|
+
expect(infoLabels()).toContain("Utilization");
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
test("the cached process count is captioned in the Hardware & Runtime card", async () => {
|
|
408
|
+
await renderLoaded();
|
|
409
|
+
|
|
410
|
+
expect(screen.getByText("Process Count (cached)")).toBeInTheDocument();
|
|
411
|
+
expect(screen.getByText(D.processCountCached)).toBeInTheDocument();
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
test("the tiles show the numbers the texts describe", async () => {
|
|
415
|
+
await renderLoaded();
|
|
416
|
+
|
|
417
|
+
// user 20% + system 5%, a 5-minute mean.
|
|
418
|
+
expect(screen.getByText("25.0%")).toBeInTheDocument();
|
|
419
|
+
// used / (used + free + reserved) = 40 / 100.
|
|
420
|
+
expect(screen.getAllByText("40.0%").length).toBeGreaterThan(0);
|
|
421
|
+
// load 1.5 over 4 cores.
|
|
422
|
+
expect(screen.getByText("38% of 4 cores")).toBeInTheDocument();
|
|
423
|
+
// running, with the cached total on the line below, as the text says.
|
|
424
|
+
expect(screen.getByText("running · 212 total")).toBeInTheDocument();
|
|
425
|
+
expect(D.processes).toContain("the total on the line below");
|
|
426
|
+
expect(
|
|
427
|
+
screen.getByText("running · 212 total").previousElementSibling,
|
|
428
|
+
).toHaveTextContent("3");
|
|
429
|
+
});
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
describe("Host overview, charts still loading", () => {
|
|
433
|
+
beforeEach(() => {
|
|
434
|
+
aggregateMock.mockImplementation(() => {
|
|
435
|
+
return never();
|
|
436
|
+
});
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
test("each skeleton chart card already carries its (i)", async () => {
|
|
440
|
+
render(<HostOverview {...PAGE_PROPS} />);
|
|
441
|
+
await settle();
|
|
442
|
+
|
|
443
|
+
// The tiles are still a loader; only the chart skeletons are drawn.
|
|
444
|
+
expect(infoLabels()).toEqual(
|
|
445
|
+
SKELETON.map((entry: [string, HostMetric]): string => {
|
|
446
|
+
return entry[0];
|
|
447
|
+
}),
|
|
448
|
+
);
|
|
449
|
+
expect(screen.queryByTestId("line-chart")).not.toBeInTheDocument();
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
test.each(SKELETON)(
|
|
453
|
+
"the skeleton (i) for %s explains it with %s",
|
|
454
|
+
async (label: string, key: HostMetric) => {
|
|
455
|
+
render(<HostOverview {...PAGE_PROPS} />);
|
|
456
|
+
await settle();
|
|
457
|
+
|
|
458
|
+
expect(await explanationOnHover(infoButtonsFor(label)[0]!)).toBe(D[key]);
|
|
459
|
+
},
|
|
460
|
+
);
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
describe("Host overview, failed load", () => {
|
|
464
|
+
test("an error hides the tiles and charts, and with them their (i)s", async () => {
|
|
465
|
+
aggregateMock.mockRejectedValue(new Error("Analytics is down"));
|
|
466
|
+
|
|
467
|
+
render(<HostOverview {...PAGE_PROPS} />);
|
|
468
|
+
await settle();
|
|
469
|
+
|
|
470
|
+
expect(screen.getByText("Analytics is down")).toBeInTheDocument();
|
|
471
|
+
expect(infoLabels()).toEqual([]);
|
|
472
|
+
});
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
/*
|
|
476
|
+
* The texts make promises about the window each tile reads. These feed the
|
|
477
|
+
* page data where the answer differs by window and check the tile shows the
|
|
478
|
+
* number the text says it shows.
|
|
479
|
+
*/
|
|
480
|
+
describe("the tiles read the windows their texts name", () => {
|
|
481
|
+
type Override = (call: AggregateCall) => Array<Row> | undefined;
|
|
482
|
+
|
|
483
|
+
interface TimedCall {
|
|
484
|
+
aggregateBy: {
|
|
485
|
+
query: { name: string };
|
|
486
|
+
startTimestamp: Date;
|
|
487
|
+
endTimestamp: Date;
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function withRows(override: Override): void {
|
|
492
|
+
aggregateMock.mockImplementation((call: unknown) => {
|
|
493
|
+
const typed: AggregateCall = call as AggregateCall;
|
|
494
|
+
|
|
495
|
+
return Promise.resolve({ data: override(typed) ?? rowsFor(typed) });
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function tileOf(label: string): HTMLElement {
|
|
500
|
+
return infoButtonsFor(label)[0]!.closest(".rounded-xl") as HTMLElement;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
test("CPU averages only the last 5 minutes of the range", async () => {
|
|
504
|
+
expect(D.cpu).toContain("last 5 minutes of the range");
|
|
505
|
+
|
|
506
|
+
withRows((call: AggregateCall) => {
|
|
507
|
+
if (call.aggregateBy.query.name !== "system.cpu.utilization") {
|
|
508
|
+
return undefined;
|
|
509
|
+
}
|
|
510
|
+
if (call.aggregateBy.query.attributes["state"] !== "user") {
|
|
511
|
+
return perMinute(() => {
|
|
512
|
+
return 0;
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
return perMinute((minute: number) => {
|
|
516
|
+
return minute <= 5 ? 0.5 : 0.1;
|
|
517
|
+
});
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
render(<HostOverview {...PAGE_PROPS} />);
|
|
521
|
+
await settle();
|
|
522
|
+
|
|
523
|
+
// A whole-range mean would read about 18%; the tile reads the last 5 minutes.
|
|
524
|
+
expect(tileOf("CPU")).toHaveTextContent("50.0%");
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
test("with no data in the last 5 minutes, CPU and Memory show the whole-range average", async () => {
|
|
528
|
+
/*
|
|
529
|
+
* A host that went silent 10 minutes ago: no bucket starts inside the
|
|
530
|
+
* tile window, so meanFromBuckets averages every bucket instead of
|
|
531
|
+
* leaving the tile blank. The texts promise exactly that.
|
|
532
|
+
*/
|
|
533
|
+
expect(D.cpu).toContain("no recent data");
|
|
534
|
+
expect(D.cpu).toContain("whole-range average");
|
|
535
|
+
expect(D.memory).toContain("no recent data");
|
|
536
|
+
|
|
537
|
+
const silentForTenMinutes: (
|
|
538
|
+
value: (minute: number) => number,
|
|
539
|
+
) => Array<Row> = (value: (minute: number) => number): Array<Row> => {
|
|
540
|
+
const rows: Array<Row> = [];
|
|
541
|
+
|
|
542
|
+
for (let minute: number = 29; minute >= 10; minute--) {
|
|
543
|
+
rows.push({ timestamp: minutesAgo(minute), value: value(minute) });
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
return rows;
|
|
547
|
+
};
|
|
548
|
+
|
|
549
|
+
withRows((call: AggregateCall) => {
|
|
550
|
+
if (call.aggregateBy.query.name === "system.cpu.utilization") {
|
|
551
|
+
const isUser: boolean =
|
|
552
|
+
call.aggregateBy.query.attributes["state"] === "user";
|
|
553
|
+
|
|
554
|
+
return silentForTenMinutes((minute: number) => {
|
|
555
|
+
if (!isUser) {
|
|
556
|
+
return 0;
|
|
557
|
+
}
|
|
558
|
+
return minute >= 20 ? 0.6 : 0.2;
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
if (call.aggregateBy.query.name === "system.memory.utilization") {
|
|
562
|
+
return silentForTenMinutes((minute: number) => {
|
|
563
|
+
return minute >= 20 ? 0.7 : 0.3;
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
return undefined;
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
render(<HostOverview {...PAGE_PROPS} />);
|
|
570
|
+
await settle();
|
|
571
|
+
|
|
572
|
+
// (60% x 10 + 20% x 10) / 20 buckets, not a dash.
|
|
573
|
+
expect(tileOf("CPU")).toHaveTextContent("40.0%");
|
|
574
|
+
expect(tileOf("CPU")).not.toHaveTextContent("—");
|
|
575
|
+
// (70% x 10 + 30% x 10) / 20 buckets.
|
|
576
|
+
expect(tileOf("Memory")).toHaveTextContent("50.0%");
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
test("load average is the newest interval, not a mean", async () => {
|
|
580
|
+
expect(D.loadAverage).toContain("newest interval");
|
|
581
|
+
|
|
582
|
+
withRows((call: AggregateCall) => {
|
|
583
|
+
if (call.aggregateBy.query.name !== "system.cpu.load_average.1m") {
|
|
584
|
+
return undefined;
|
|
585
|
+
}
|
|
586
|
+
return perMinute((minute: number) => {
|
|
587
|
+
return minute === 0 ? 3 : 1;
|
|
588
|
+
});
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
render(<HostOverview {...PAGE_PROPS} />);
|
|
592
|
+
await settle();
|
|
593
|
+
|
|
594
|
+
expect(tileOf("Load avg (1m)")).toHaveTextContent("3.00");
|
|
595
|
+
// Compared with the core count: 3 of 4 cores.
|
|
596
|
+
expect(tileOf("Load avg (1m)")).toHaveTextContent("75% of 4 cores");
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
test("the filesystem tile averages the whole range, reserved space included", async () => {
|
|
600
|
+
expect(D.filesystem).toContain("whole selected range");
|
|
601
|
+
|
|
602
|
+
withRows((call: AggregateCall) => {
|
|
603
|
+
if (call.aggregateBy.query.name !== "system.filesystem.usage") {
|
|
604
|
+
return undefined;
|
|
605
|
+
}
|
|
606
|
+
const mount: Record<string, string> = { mountpoint: "/", type: "ext4" };
|
|
607
|
+
|
|
608
|
+
return [
|
|
609
|
+
...perMinute(
|
|
610
|
+
(minute: number) => {
|
|
611
|
+
return (minute >= 15 ? 20 : 60) * GIB;
|
|
612
|
+
},
|
|
613
|
+
{ ...mount, state: "used" },
|
|
614
|
+
),
|
|
615
|
+
...perMinute(
|
|
616
|
+
(minute: number) => {
|
|
617
|
+
return (minute >= 15 ? 70 : 30) * GIB;
|
|
618
|
+
},
|
|
619
|
+
{ ...mount, state: "free" },
|
|
620
|
+
),
|
|
621
|
+
...perMinute(
|
|
622
|
+
() => {
|
|
623
|
+
return 10 * GIB;
|
|
624
|
+
},
|
|
625
|
+
{ ...mount, state: "reserved" },
|
|
626
|
+
),
|
|
627
|
+
];
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
render(<HostOverview {...PAGE_PROPS} />);
|
|
631
|
+
await settle();
|
|
632
|
+
|
|
633
|
+
/*
|
|
634
|
+
* Mean used is 40 GiB of a 100 GiB total. The last 5 minutes alone would
|
|
635
|
+
* read 60%, and leaving reserved out of the total would read 44.4%.
|
|
636
|
+
*/
|
|
637
|
+
expect(tileOf("Filesystem")).toHaveTextContent("40.0%");
|
|
638
|
+
expect(tileOf("Filesystem")).not.toHaveTextContent("60.0%");
|
|
639
|
+
expect(tileOf("Filesystem")).not.toHaveTextContent("44.4%");
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
test("without process states, Processes counts every process seen in the last 5 minutes", async () => {
|
|
643
|
+
expect(D.processes).toContain("every process seen in the last 5 minutes");
|
|
644
|
+
|
|
645
|
+
getItemMock.mockResolvedValue({ ...hostItem(), processCount: undefined });
|
|
646
|
+
withRows((call: AggregateCall) => {
|
|
647
|
+
if (call.aggregateBy.query.name === "system.processes.count") {
|
|
648
|
+
return [];
|
|
649
|
+
}
|
|
650
|
+
if (call.aggregateBy.query.name === "process.cpu.utilization") {
|
|
651
|
+
return ["101", "202", "303", "404", "505", "606", "707"].map(
|
|
652
|
+
(pid: string): Row => {
|
|
653
|
+
return {
|
|
654
|
+
timestamp: minutesAgo(1),
|
|
655
|
+
value: 0.01,
|
|
656
|
+
attributes: { "resource.process.pid": pid },
|
|
657
|
+
};
|
|
658
|
+
},
|
|
659
|
+
);
|
|
660
|
+
}
|
|
661
|
+
return undefined;
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
render(<HostOverview {...PAGE_PROPS} />);
|
|
665
|
+
await settle();
|
|
666
|
+
|
|
667
|
+
expect(tileOf("Processes")).toHaveTextContent("7");
|
|
668
|
+
|
|
669
|
+
// The fallback query covers only the last 5 minutes of the range.
|
|
670
|
+
const fallback: TimedCall | undefined = aggregateMock.mock.calls
|
|
671
|
+
.map((call: Array<unknown>): TimedCall => {
|
|
672
|
+
return call[0] as TimedCall;
|
|
673
|
+
})
|
|
674
|
+
.find((call: TimedCall): boolean => {
|
|
675
|
+
return call.aggregateBy.query.name === "process.cpu.utilization";
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
expect(fallback).toBeDefined();
|
|
679
|
+
expect(
|
|
680
|
+
fallback!.aggregateBy.endTimestamp.getTime() -
|
|
681
|
+
fallback!.aggregateBy.startTimestamp.getTime(),
|
|
682
|
+
).toBe(5 * 60_000);
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
test("every aggregate is scoped to this host", async () => {
|
|
686
|
+
await renderLoaded();
|
|
687
|
+
|
|
688
|
+
expect(aggregateMock).toHaveBeenCalledTimes(9);
|
|
689
|
+
|
|
690
|
+
for (const call of aggregateMock.mock.calls) {
|
|
691
|
+
const query: AggregateCall["aggregateBy"]["query"] = (
|
|
692
|
+
call[0] as AggregateCall
|
|
693
|
+
).aggregateBy.query;
|
|
694
|
+
|
|
695
|
+
expect(query.attributes["resource.host.name"]).toBe("web-01");
|
|
696
|
+
}
|
|
697
|
+
});
|
|
698
|
+
});
|