@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,772 @@
|
|
|
1
|
+
import {
|
|
2
|
+
afterEach,
|
|
3
|
+
beforeEach,
|
|
4
|
+
describe,
|
|
5
|
+
expect,
|
|
6
|
+
jest,
|
|
7
|
+
test,
|
|
8
|
+
} from "@jest/globals";
|
|
9
|
+
import "@testing-library/jest-dom";
|
|
10
|
+
import {
|
|
11
|
+
act,
|
|
12
|
+
cleanup,
|
|
13
|
+
fireEvent,
|
|
14
|
+
render,
|
|
15
|
+
screen,
|
|
16
|
+
} from "@testing-library/react";
|
|
17
|
+
import * as React from "react";
|
|
18
|
+
import { MemoryRouter } from "react-router-dom";
|
|
19
|
+
|
|
20
|
+
/*
|
|
21
|
+
* The (i) tooltips on the Service overview: the span tiles and charts, the
|
|
22
|
+
* log and exception charts, and the runtime charts picked by the service's
|
|
23
|
+
* language - whose texts live on RuntimeChartDef so the chart and the
|
|
24
|
+
* fourth tile share one. The texts are held to the shared rules and to what
|
|
25
|
+
* the page computes (a tile averages a level over the range and totals a
|
|
26
|
+
* counter; the runtime fetch averages every series of a metric), and the
|
|
27
|
+
* page is rendered for real over a fake API to show each text beside the
|
|
28
|
+
* right title.
|
|
29
|
+
*
|
|
30
|
+
* jest.mock is hoisted above the imports, so the factories only close over
|
|
31
|
+
* names that start with "mock".
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
interface MockAggregateCall {
|
|
35
|
+
modelType: { name: string };
|
|
36
|
+
aggregateBy: {
|
|
37
|
+
aggregationType: string;
|
|
38
|
+
query: Record<string, unknown>;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface MockRow {
|
|
43
|
+
timestamp: Date;
|
|
44
|
+
value: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const mockAggregateCalls: Array<MockAggregateCall> = [];
|
|
48
|
+
let mockSdkLanguage: string = "nodejs";
|
|
49
|
+
// Keep every span / metric aggregate pending, to see the loading tiles.
|
|
50
|
+
let mockHoldAggregates: boolean = false;
|
|
51
|
+
// Metric name -> the rows the metric aggregate returns for it.
|
|
52
|
+
let mockRuntimeRows: Record<string, Array<MockRow>> = {};
|
|
53
|
+
|
|
54
|
+
jest.mock("../../../UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI", () => {
|
|
55
|
+
return {
|
|
56
|
+
__esModule: true,
|
|
57
|
+
default: {
|
|
58
|
+
aggregate: (call: MockAggregateCall): Promise<unknown> => {
|
|
59
|
+
mockAggregateCalls.push(call);
|
|
60
|
+
if (mockHoldAggregates) {
|
|
61
|
+
return new Promise<unknown>(() => {});
|
|
62
|
+
}
|
|
63
|
+
const at: Date = new Date("2026-09-24T10:00:00.000Z");
|
|
64
|
+
|
|
65
|
+
if (call.modelType.name === "Metric") {
|
|
66
|
+
const name: string = String(call.aggregateBy.query["name"]);
|
|
67
|
+
return Promise.resolve({ data: mockRuntimeRows[name] || [] });
|
|
68
|
+
}
|
|
69
|
+
if (call.aggregateBy.aggregationType === "P95") {
|
|
70
|
+
return Promise.resolve({ data: [{ timestamp: at, value: 2.5e8 }] });
|
|
71
|
+
}
|
|
72
|
+
if (call.aggregateBy.query["statusCode"] !== undefined) {
|
|
73
|
+
return Promise.resolve({ data: [{ timestamp: at, value: 3 }] });
|
|
74
|
+
}
|
|
75
|
+
return Promise.resolve({ data: [{ timestamp: at, value: 100 }] });
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
jest.mock("../../../UI/Utils/ModelAPI/ModelAPI", () => {
|
|
82
|
+
return {
|
|
83
|
+
__esModule: true,
|
|
84
|
+
default: {
|
|
85
|
+
getItem: (): Promise<unknown> => {
|
|
86
|
+
return Promise.resolve({
|
|
87
|
+
name: "checkout",
|
|
88
|
+
telemetrySdkLanguage: mockSdkLanguage,
|
|
89
|
+
lastSeenAt: new Date(),
|
|
90
|
+
});
|
|
91
|
+
},
|
|
92
|
+
getCommonHeaders: (): Record<string, string> => {
|
|
93
|
+
return {};
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
jest.mock("../../../UI/Utils/API/API", () => {
|
|
100
|
+
return {
|
|
101
|
+
__esModule: true,
|
|
102
|
+
default: {
|
|
103
|
+
post: (): Promise<unknown> => {
|
|
104
|
+
return Promise.resolve({
|
|
105
|
+
data: {
|
|
106
|
+
buckets: [
|
|
107
|
+
{
|
|
108
|
+
time: "2026-09-24T10:00:00.000Z",
|
|
109
|
+
severity: "Error",
|
|
110
|
+
series: "unhandled",
|
|
111
|
+
count: 2,
|
|
112
|
+
},
|
|
113
|
+
],
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
},
|
|
117
|
+
getFriendlyMessage: (): string => {
|
|
118
|
+
return "failed";
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
jest.mock("../../../UI/Utils/Project", () => {
|
|
125
|
+
const ObjectIDModule: { default: new (id: string) => unknown } =
|
|
126
|
+
jest.requireActual("../../../Types/ObjectID") as {
|
|
127
|
+
default: new (id: string) => unknown;
|
|
128
|
+
};
|
|
129
|
+
return {
|
|
130
|
+
__esModule: true,
|
|
131
|
+
default: {
|
|
132
|
+
getCurrentProjectId: (): unknown => {
|
|
133
|
+
return new ObjectIDModule.default(
|
|
134
|
+
"aaaaaaaa-0000-4000-8000-000000000001",
|
|
135
|
+
);
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
jest.mock("../../../UI/Utils/Navigation", () => {
|
|
142
|
+
const ObjectIDModule: { default: new (id: string) => unknown } =
|
|
143
|
+
jest.requireActual("../../../Types/ObjectID") as {
|
|
144
|
+
default: new (id: string) => unknown;
|
|
145
|
+
};
|
|
146
|
+
return {
|
|
147
|
+
__esModule: true,
|
|
148
|
+
default: {
|
|
149
|
+
getLastParamAsObjectID: (): unknown => {
|
|
150
|
+
return new ObjectIDModule.default(
|
|
151
|
+
"dddddddd-0000-4000-8000-000000000004",
|
|
152
|
+
);
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
jest.mock("react-i18next", () => {
|
|
159
|
+
return {
|
|
160
|
+
useTranslation: () => {
|
|
161
|
+
return {
|
|
162
|
+
t: (value: string): string => {
|
|
163
|
+
return value;
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
jest.mock("../../../UI/Components/Charts/Line/LineChart", () => {
|
|
171
|
+
return {
|
|
172
|
+
__esModule: true,
|
|
173
|
+
default: (): React.ReactElement => {
|
|
174
|
+
return React.createElement("div", { "data-testid": "line-chart" });
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
jest.mock(
|
|
180
|
+
"../../../UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker",
|
|
181
|
+
() => {
|
|
182
|
+
return {
|
|
183
|
+
__esModule: true,
|
|
184
|
+
default: (): null => {
|
|
185
|
+
return null;
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
},
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
jest.mock("../../../UI/Components/ModelDetail/CardModelDetail", () => {
|
|
192
|
+
return {
|
|
193
|
+
__esModule: true,
|
|
194
|
+
default: (): null => {
|
|
195
|
+
return null;
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
jest.mock(
|
|
201
|
+
"../../../../App/FeatureSet/Dashboard/src/Components/TelemetryResource/AutoRefreshControl",
|
|
202
|
+
() => {
|
|
203
|
+
return {
|
|
204
|
+
__esModule: true,
|
|
205
|
+
default: (): null => {
|
|
206
|
+
return null;
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
},
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
// Its own counts and cards; not part of this page's metric tooltips.
|
|
213
|
+
jest.mock(
|
|
214
|
+
"../../../../App/FeatureSet/Dashboard/src/Components/ResourceActivity/ResourceActivityCards",
|
|
215
|
+
() => {
|
|
216
|
+
return {
|
|
217
|
+
__esModule: true,
|
|
218
|
+
default: (): null => {
|
|
219
|
+
return null;
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
},
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
import ServiceView from "../../../../App/FeatureSet/Dashboard/src/Pages/Service/View/Index";
|
|
226
|
+
import ChartCard from "../../../../App/FeatureSet/Dashboard/src/Components/TelemetryResource/ChartCard";
|
|
227
|
+
import {
|
|
228
|
+
getRuntimeChartDefs,
|
|
229
|
+
RuntimeChartDef,
|
|
230
|
+
SERVICE_LANGUAGE_DISPLAY_NAMES,
|
|
231
|
+
ServiceLanguage,
|
|
232
|
+
} from "../../../../App/FeatureSet/Dashboard/src/Components/TelemetryResource/serviceGoldenMetrics";
|
|
233
|
+
import {
|
|
234
|
+
SERVICE_METRIC_DESCRIPTIONS,
|
|
235
|
+
SERVICE_RUNTIME_METRIC_DESCRIPTIONS,
|
|
236
|
+
ServiceMetric,
|
|
237
|
+
} from "../../../../App/FeatureSet/Dashboard/src/Components/MetricDescriptions/ServiceMetricDescriptions";
|
|
238
|
+
import AggregationType from "../../../Types/BaseDatabase/AggregationType";
|
|
239
|
+
import {
|
|
240
|
+
expectReadableDescriptionRecord,
|
|
241
|
+
expectTitleExplained,
|
|
242
|
+
} from "./MetricDescriptionRules";
|
|
243
|
+
import PageComponentProps from "../../../../App/FeatureSet/Dashboard/src/Pages/PageComponentProps";
|
|
244
|
+
import Route from "../../../Types/API/Route";
|
|
245
|
+
|
|
246
|
+
const PAGE_PROPS: PageComponentProps = {
|
|
247
|
+
pageRoute: new Route("/services/overview"),
|
|
248
|
+
currentProject: null,
|
|
249
|
+
hasPaymentMethod: false,
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
const D: Record<ServiceMetric, string> = SERVICE_METRIC_DESCRIPTIONS;
|
|
253
|
+
|
|
254
|
+
const LANGUAGES: Array<ServiceLanguage | null> = [
|
|
255
|
+
...(Object.keys(SERVICE_LANGUAGE_DISPLAY_NAMES) as Array<ServiceLanguage>),
|
|
256
|
+
null,
|
|
257
|
+
];
|
|
258
|
+
|
|
259
|
+
// Every runtime chart any language can get, once, by key.
|
|
260
|
+
function allRuntimeDefs(): Array<RuntimeChartDef> {
|
|
261
|
+
const byKey: Map<string, RuntimeChartDef> = new Map<
|
|
262
|
+
string,
|
|
263
|
+
RuntimeChartDef
|
|
264
|
+
>();
|
|
265
|
+
|
|
266
|
+
for (const language of LANGUAGES) {
|
|
267
|
+
for (const def of getRuntimeChartDefs(language)) {
|
|
268
|
+
byKey.set(def.key, def);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return Array.from(byKey.values());
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const RUNTIME_DEFS: Array<RuntimeChartDef> = allRuntimeDefs();
|
|
276
|
+
|
|
277
|
+
// [title, definition] rows for test.each.
|
|
278
|
+
const RUNTIME_CASES: Array<[string, RuntimeChartDef]> = RUNTIME_DEFS.map(
|
|
279
|
+
(def: RuntimeChartDef): [string, RuntimeChartDef] => {
|
|
280
|
+
return [def.title, def];
|
|
281
|
+
},
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
// Whether the Service page's fourth tile totals (true) or averages the chart.
|
|
285
|
+
function tileTotalsRange(def: RuntimeChartDef): boolean {
|
|
286
|
+
return (
|
|
287
|
+
def.aggregationType === AggregationType.Sum ||
|
|
288
|
+
def.cumulativeCounter === true
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// The three fixed tiles; the fourth is the first runtime chart, or Technology.
|
|
293
|
+
const FIXED_TILES: Array<[string, ServiceMetric]> = [
|
|
294
|
+
["Requests", "requests"],
|
|
295
|
+
["Error rate", "errorRate"],
|
|
296
|
+
["Latency (p95)", "latencyP95"],
|
|
297
|
+
];
|
|
298
|
+
|
|
299
|
+
const FIXED_CHARTS: Array<[string, ServiceMetric]> = [
|
|
300
|
+
["Requests", "requestsChart"],
|
|
301
|
+
["Latency (p95)", "latencyP95Chart"],
|
|
302
|
+
["Logs", "logsChart"],
|
|
303
|
+
["Exceptions", "exceptionsChart"],
|
|
304
|
+
];
|
|
305
|
+
|
|
306
|
+
describe("SERVICE_METRIC_DESCRIPTIONS", () => {
|
|
307
|
+
test("every text reads as a short, finished, jargon-explaining sentence", () => {
|
|
308
|
+
expectReadableDescriptionRecord(D, "SERVICE_METRIC_DESCRIPTIONS");
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
test("covers exactly the fixed tiles, the Technology tile and the fixed charts", () => {
|
|
312
|
+
expect(
|
|
313
|
+
[...FIXED_TILES, ["Technology", "technology"], ...FIXED_CHARTS]
|
|
314
|
+
.map((entry: Array<string>): string => {
|
|
315
|
+
return entry[1] as string;
|
|
316
|
+
})
|
|
317
|
+
.sort(),
|
|
318
|
+
).toEqual(Object.keys(D).sort());
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
test.each([...FIXED_TILES, ...FIXED_CHARTS])(
|
|
322
|
+
"the %s text explains any percentile its title names",
|
|
323
|
+
(title: string, key: ServiceMetric) => {
|
|
324
|
+
expectTitleExplained(title, D[key]);
|
|
325
|
+
},
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
test("Requests is honest that it counts every span", () => {
|
|
329
|
+
expect(D.requests).toMatch(/^Every span/);
|
|
330
|
+
// "usually": a request with a single span is one request and one span.
|
|
331
|
+
expect(D.requests).toMatch(/usually higher than the request count/);
|
|
332
|
+
expect(D.requestsChart).toMatch(/Every operation counts/);
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
test("the p95 tile is an average of per-interval p95s; the chart is per interval", () => {
|
|
336
|
+
expect(D.latencyP95).toMatch(/95%/);
|
|
337
|
+
expect(D.latencyP95).toMatch(/averaged over the selected range/);
|
|
338
|
+
expect(D.latencyP95).toContain("so quiet and busy intervals count equally");
|
|
339
|
+
expect(D.latencyP95Chart).toMatch(/in each interval/);
|
|
340
|
+
expect(D.latencyP95Chart).not.toMatch(/averaged/);
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
test("Error rate names the thresholds the tile uses", () => {
|
|
344
|
+
expect(D.errorRate).toContain("amber at 1%");
|
|
345
|
+
expect(D.errorRate).toContain("red at 5%");
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
test("the Logs chart's error line is the severities the histogram counts", () => {
|
|
349
|
+
// ERROR_LOG_SEVERITY_SET in telemetryMetrics.ts.
|
|
350
|
+
expect(D.logsChart).toContain("Error and Fatal");
|
|
351
|
+
expect(D.logsChart).toMatch(/every severity/);
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
test("the Exceptions chart explains its two series", () => {
|
|
355
|
+
/*
|
|
356
|
+
* ExceptionAggregationService.mapEscapedToSeries: escaped = true is
|
|
357
|
+
* "unhandled", anything else - false or not set - is "handled". So the
|
|
358
|
+
* text must not promise every handled exception was caught.
|
|
359
|
+
*/
|
|
360
|
+
expect(D.exceptionsChart).toMatch(/Unhandled ones were marked as escaping/);
|
|
361
|
+
expect(D.exceptionsChart).toMatch(/handled ones are the rest/);
|
|
362
|
+
expect(D.exceptionsChart).toMatch(/not marked either way/);
|
|
363
|
+
expect(D.exceptionsChart).not.toMatch(/handled ones were caught/);
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
test("Technology says where the language comes from and why it is shown", () => {
|
|
367
|
+
expect(D.technology).toContain("telemetry.sdk.language");
|
|
368
|
+
expect(D.technology).toMatch(/no runtime metrics/);
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
describe("SERVICE_RUNTIME_METRIC_DESCRIPTIONS and the runtime chart definitions", () => {
|
|
373
|
+
test("every text reads as a short, finished, jargon-explaining sentence", () => {
|
|
374
|
+
expectReadableDescriptionRecord(
|
|
375
|
+
SERVICE_RUNTIME_METRIC_DESCRIPTIONS,
|
|
376
|
+
"SERVICE_RUNTIME_METRIC_DESCRIPTIONS",
|
|
377
|
+
);
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
test("every chart any language can get has its own text, and every text is used", () => {
|
|
381
|
+
const used: Array<string> = RUNTIME_DEFS.map(
|
|
382
|
+
(def: RuntimeChartDef): string => {
|
|
383
|
+
return def.description;
|
|
384
|
+
},
|
|
385
|
+
);
|
|
386
|
+
|
|
387
|
+
expect(new Set(used).size).toBe(RUNTIME_DEFS.length);
|
|
388
|
+
expect([...used].sort()).toEqual(
|
|
389
|
+
Object.values(SERVICE_RUNTIME_METRIC_DESCRIPTIONS).sort(),
|
|
390
|
+
);
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
test.each(LANGUAGES)(
|
|
394
|
+
"every chart offered for %s carries a description",
|
|
395
|
+
(language: ServiceLanguage | null) => {
|
|
396
|
+
const defs: Array<RuntimeChartDef> = getRuntimeChartDefs(language);
|
|
397
|
+
|
|
398
|
+
expect(defs.length).toBeGreaterThan(0);
|
|
399
|
+
|
|
400
|
+
for (const def of defs) {
|
|
401
|
+
expect(typeof def.description).toBe("string");
|
|
402
|
+
expect(def.description.length).toBeGreaterThan(0);
|
|
403
|
+
}
|
|
404
|
+
},
|
|
405
|
+
);
|
|
406
|
+
|
|
407
|
+
test.each(RUNTIME_CASES)(
|
|
408
|
+
"%s: the text explains any percentile the title names",
|
|
409
|
+
(_title: string, def: RuntimeChartDef) => {
|
|
410
|
+
expectTitleExplained(def.title, def.description);
|
|
411
|
+
},
|
|
412
|
+
);
|
|
413
|
+
|
|
414
|
+
test.each(RUNTIME_CASES)(
|
|
415
|
+
"%s: the text says what the tile does with it (total or average)",
|
|
416
|
+
(_title: string, def: RuntimeChartDef) => {
|
|
417
|
+
if (tileTotalsRange(def)) {
|
|
418
|
+
expect(def.description).toMatch(/the total for the selected range/);
|
|
419
|
+
// Max-of-cumulative then deltas follows one series.
|
|
420
|
+
expect(def.description).toMatch(/approximate/);
|
|
421
|
+
} else {
|
|
422
|
+
expect(def.description).toMatch(/average/);
|
|
423
|
+
expect(def.description).not.toMatch(/the total for the selected range/);
|
|
424
|
+
// One wording for the tile's window across every runtime chart.
|
|
425
|
+
expect(def.description).toMatch(
|
|
426
|
+
/[Aa]s a tile it shows the average over the selected range/,
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
},
|
|
430
|
+
);
|
|
431
|
+
|
|
432
|
+
test.each(
|
|
433
|
+
RUNTIME_CASES.filter((row: [string, RuntimeChartDef]): boolean => {
|
|
434
|
+
return row[1].unit === "percent";
|
|
435
|
+
}),
|
|
436
|
+
)(
|
|
437
|
+
"%s: a percentage says what 100%% means",
|
|
438
|
+
(_title: string, def: RuntimeChartDef) => {
|
|
439
|
+
expect(def.description).toMatch(/100%/);
|
|
440
|
+
// In words, not "100% = ...".
|
|
441
|
+
expect(def.description).not.toContain("100% =");
|
|
442
|
+
},
|
|
443
|
+
);
|
|
444
|
+
|
|
445
|
+
test("metrics split by attribute say the value is a per-series average", () => {
|
|
446
|
+
const byKey: Record<string, RuntimeChartDef> = {};
|
|
447
|
+
|
|
448
|
+
for (const def of RUNTIME_DEFS) {
|
|
449
|
+
byKey[def.key] = def;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Avg over every datapoint of a metric that is split by pool / generation / space / state / mode.
|
|
453
|
+
expect(byKey["jvm-heap"]!.description).toMatch(/not the total heap/);
|
|
454
|
+
expect(byKey["dotnet-gc-heap"]!.description).toMatch(/not the total/);
|
|
455
|
+
expect(byKey["node-heap"]!.description).toMatch(/not the total heap/);
|
|
456
|
+
expect(byKey["jvm-threads"]!.description).toMatch(
|
|
457
|
+
/rather than their total/,
|
|
458
|
+
);
|
|
459
|
+
expect(byKey["process-cpu"]!.description).toMatch(
|
|
460
|
+
/rather than adding them/,
|
|
461
|
+
);
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
test("a chart with a fallback metric says what the fallback shows", () => {
|
|
465
|
+
const nodeHeap: RuntimeChartDef | undefined = RUNTIME_DEFS.find(
|
|
466
|
+
(def: RuntimeChartDef): boolean => {
|
|
467
|
+
return def.key === "node-heap";
|
|
468
|
+
},
|
|
469
|
+
);
|
|
470
|
+
|
|
471
|
+
expect(
|
|
472
|
+
nodeHeap!.candidates.map((c: { metricName: string }): string => {
|
|
473
|
+
return c.metricName;
|
|
474
|
+
}),
|
|
475
|
+
).toContain("process.memory.usage");
|
|
476
|
+
expect(nodeHeap!.description).toMatch(/process physical memory/);
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
test.each(RUNTIME_CASES)(
|
|
480
|
+
"%s: the chart card shows the text in its (i)",
|
|
481
|
+
async (_title: string, def: RuntimeChartDef) => {
|
|
482
|
+
jest.useFakeTimers();
|
|
483
|
+
|
|
484
|
+
try {
|
|
485
|
+
render(
|
|
486
|
+
<ChartCard
|
|
487
|
+
title={def.title}
|
|
488
|
+
icon={def.icon}
|
|
489
|
+
iconColor={def.iconColor}
|
|
490
|
+
series={[]}
|
|
491
|
+
windowStart={null}
|
|
492
|
+
windowEnd={null}
|
|
493
|
+
syncId="runtime-test"
|
|
494
|
+
loading={true}
|
|
495
|
+
description={def.description}
|
|
496
|
+
/>,
|
|
497
|
+
);
|
|
498
|
+
|
|
499
|
+
await hover(screen.getByRole("button", { name: `About ${def.title}` }));
|
|
500
|
+
|
|
501
|
+
expect(screen.getByRole("tooltip")).toHaveTextContent(def.description);
|
|
502
|
+
} finally {
|
|
503
|
+
cleanup();
|
|
504
|
+
jest.useRealTimers();
|
|
505
|
+
}
|
|
506
|
+
},
|
|
507
|
+
);
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
async function hover(trigger: HTMLElement): Promise<void> {
|
|
511
|
+
fireEvent.mouseEnter(trigger);
|
|
512
|
+
await act(async () => {
|
|
513
|
+
jest.advanceTimersByTime(200);
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
function infoButtons(title: string): Array<HTMLElement> {
|
|
518
|
+
return screen.getAllByRole("button", { name: `About ${title}` });
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
function rows(values: Array<number>): Array<MockRow> {
|
|
522
|
+
return values.map((value: number, index: number): MockRow => {
|
|
523
|
+
return {
|
|
524
|
+
timestamp: new Date(Date.UTC(2026, 8, 24, 10, index)),
|
|
525
|
+
value: value,
|
|
526
|
+
};
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function runtimeDef(key: string): RuntimeChartDef {
|
|
531
|
+
const def: RuntimeChartDef | undefined = RUNTIME_DEFS.find(
|
|
532
|
+
(candidate: RuntimeChartDef): boolean => {
|
|
533
|
+
return candidate.key === key;
|
|
534
|
+
},
|
|
535
|
+
);
|
|
536
|
+
|
|
537
|
+
if (!def) {
|
|
538
|
+
throw new Error(`No runtime chart "${key}"`);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
return def;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
const NODE_ROWS: Record<string, Array<MockRow>> = {
|
|
545
|
+
"nodejs.eventloop.utilization": rows([0.4, 0.44]),
|
|
546
|
+
"nodejs.eventloop.delay.p99": rows([0.012]),
|
|
547
|
+
"v8js.memory.heap.used": rows([50 * 1024 * 1024]),
|
|
548
|
+
"process.cpu.utilization": rows([0.1]),
|
|
549
|
+
"process.memory.usage": rows([80 * 1024 * 1024]),
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
// In the order getRuntimeChartDefs("nodejs") offers them, cut to four.
|
|
553
|
+
const NODE_CHART_KEYS: Array<string> = [
|
|
554
|
+
"node-eventloop-util",
|
|
555
|
+
"node-eventloop-delay",
|
|
556
|
+
"node-heap",
|
|
557
|
+
"process-cpu",
|
|
558
|
+
];
|
|
559
|
+
|
|
560
|
+
async function renderService(waitFor: string): Promise<void> {
|
|
561
|
+
render(
|
|
562
|
+
<MemoryRouter>
|
|
563
|
+
<ServiceView {...PAGE_PROPS} />
|
|
564
|
+
</MemoryRouter>,
|
|
565
|
+
);
|
|
566
|
+
|
|
567
|
+
// Some values (the language) also appear in the chips and details.
|
|
568
|
+
await screen.findAllByText(waitFor);
|
|
569
|
+
await screen.findByText("250 ms");
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
describe("Service overview page", () => {
|
|
573
|
+
beforeEach(() => {
|
|
574
|
+
jest.useFakeTimers();
|
|
575
|
+
mockAggregateCalls.length = 0;
|
|
576
|
+
mockSdkLanguage = "nodejs";
|
|
577
|
+
mockRuntimeRows = { ...NODE_ROWS };
|
|
578
|
+
mockHoldAggregates = false;
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
afterEach(() => {
|
|
582
|
+
cleanup();
|
|
583
|
+
jest.useRealTimers();
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
test("renders one (i) per tile and per chart, runtime charts included", async () => {
|
|
587
|
+
await renderService("42.0%");
|
|
588
|
+
|
|
589
|
+
expect(screen.getAllByRole("button", { name: /^About / })).toHaveLength(
|
|
590
|
+
FIXED_TILES.length + 1 + FIXED_CHARTS.length + NODE_CHART_KEYS.length,
|
|
591
|
+
);
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
test.each(FIXED_TILES)(
|
|
595
|
+
"the %s tile explains itself on hover",
|
|
596
|
+
async (title: string, key: ServiceMetric) => {
|
|
597
|
+
await renderService("42.0%");
|
|
598
|
+
|
|
599
|
+
await hover(infoButtons(title)[0]!);
|
|
600
|
+
|
|
601
|
+
expect(screen.getByRole("tooltip")).toHaveTextContent(D[key]);
|
|
602
|
+
},
|
|
603
|
+
);
|
|
604
|
+
|
|
605
|
+
test.each(FIXED_CHARTS)(
|
|
606
|
+
"the %s chart explains itself on hover",
|
|
607
|
+
async (title: string, key: ServiceMetric) => {
|
|
608
|
+
await renderService("42.0%");
|
|
609
|
+
|
|
610
|
+
const buttons: Array<HTMLElement> = infoButtons(title);
|
|
611
|
+
|
|
612
|
+
await hover(buttons[buttons.length - 1]!);
|
|
613
|
+
|
|
614
|
+
expect(screen.getByRole("tooltip")).toHaveTextContent(D[key]);
|
|
615
|
+
},
|
|
616
|
+
);
|
|
617
|
+
|
|
618
|
+
test("the fourth tile is the first runtime chart and shares its text", async () => {
|
|
619
|
+
await renderService("42.0%");
|
|
620
|
+
|
|
621
|
+
const def: RuntimeChartDef = runtimeDef("node-eventloop-util");
|
|
622
|
+
const buttons: Array<HTMLElement> = infoButtons(def.title);
|
|
623
|
+
|
|
624
|
+
// Once on the tile, once on the chart.
|
|
625
|
+
expect(buttons).toHaveLength(2);
|
|
626
|
+
|
|
627
|
+
for (const button of buttons) {
|
|
628
|
+
await hover(button);
|
|
629
|
+
|
|
630
|
+
const tooltips: Array<HTMLElement> = screen.getAllByRole("tooltip");
|
|
631
|
+
|
|
632
|
+
expect(tooltips[tooltips.length - 1]).toHaveTextContent(def.description);
|
|
633
|
+
fireEvent.mouseLeave(button);
|
|
634
|
+
}
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
test.each(NODE_CHART_KEYS)(
|
|
638
|
+
"the %s runtime chart explains itself on hover",
|
|
639
|
+
async (key: string) => {
|
|
640
|
+
await renderService("42.0%");
|
|
641
|
+
|
|
642
|
+
const def: RuntimeChartDef = runtimeDef(key);
|
|
643
|
+
const buttons: Array<HTMLElement> = infoButtons(def.title);
|
|
644
|
+
|
|
645
|
+
await hover(buttons[buttons.length - 1]!);
|
|
646
|
+
|
|
647
|
+
expect(screen.getByRole("tooltip")).toHaveTextContent(def.description);
|
|
648
|
+
},
|
|
649
|
+
);
|
|
650
|
+
|
|
651
|
+
test("a counter chart's tile totals the range, as its text says", async () => {
|
|
652
|
+
/*
|
|
653
|
+
* Python with only GC data: the first (and only) runtime chart is the
|
|
654
|
+
* cumulative GC counter. 10 -> 15 -> 22 gives deltas 5 and 7, so the
|
|
655
|
+
* tile reads 12 - the total its text promises.
|
|
656
|
+
*/
|
|
657
|
+
mockSdkLanguage = "python";
|
|
658
|
+
mockRuntimeRows = { "cpython.gc.collections": rows([10, 15, 22]) };
|
|
659
|
+
|
|
660
|
+
await renderService("12");
|
|
661
|
+
|
|
662
|
+
const def: RuntimeChartDef = runtimeDef("python-gc");
|
|
663
|
+
|
|
664
|
+
expect(def.description).toMatch(/the total for the selected range/);
|
|
665
|
+
|
|
666
|
+
await hover(infoButtons(def.title)[0]!);
|
|
667
|
+
|
|
668
|
+
expect(screen.getByRole("tooltip")).toHaveTextContent(def.description);
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
test("with no runtime metrics the Technology tile explains itself", async () => {
|
|
672
|
+
mockRuntimeRows = {};
|
|
673
|
+
|
|
674
|
+
await renderService("Node.js");
|
|
675
|
+
|
|
676
|
+
await hover(screen.getByRole("button", { name: "About Technology" }));
|
|
677
|
+
|
|
678
|
+
expect(screen.getByRole("tooltip")).toHaveTextContent(D.technology);
|
|
679
|
+
expect(screen.getAllByRole("button", { name: /^About / })).toHaveLength(
|
|
680
|
+
FIXED_TILES.length + 1 + FIXED_CHARTS.length,
|
|
681
|
+
);
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
test("the loading tiles and charts keep their (i)", async () => {
|
|
685
|
+
mockHoldAggregates = true;
|
|
686
|
+
|
|
687
|
+
render(
|
|
688
|
+
<MemoryRouter>
|
|
689
|
+
<ServiceView {...PAGE_PROPS} />
|
|
690
|
+
</MemoryRouter>,
|
|
691
|
+
);
|
|
692
|
+
|
|
693
|
+
await screen.findByText("Loading Requests");
|
|
694
|
+
|
|
695
|
+
for (const [title, key] of FIXED_TILES) {
|
|
696
|
+
expect(screen.getByText(`Loading ${title}`)).toBeInTheDocument();
|
|
697
|
+
|
|
698
|
+
const button: HTMLElement = infoButtons(title)[0]!;
|
|
699
|
+
|
|
700
|
+
await hover(button);
|
|
701
|
+
|
|
702
|
+
const tooltips: Array<HTMLElement> = screen.getAllByRole("tooltip");
|
|
703
|
+
|
|
704
|
+
expect(tooltips[tooltips.length - 1]).toHaveTextContent(D[key]);
|
|
705
|
+
fireEvent.mouseLeave(button);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
for (const [title, key] of FIXED_CHARTS) {
|
|
709
|
+
const buttons: Array<HTMLElement> = infoButtons(title);
|
|
710
|
+
const button: HTMLElement = buttons[buttons.length - 1]!;
|
|
711
|
+
|
|
712
|
+
await hover(button);
|
|
713
|
+
|
|
714
|
+
const tooltips: Array<HTMLElement> = screen.getAllByRole("tooltip");
|
|
715
|
+
|
|
716
|
+
expect(tooltips[tooltips.length - 1]).toHaveTextContent(D[key]);
|
|
717
|
+
fireEvent.mouseLeave(button);
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// No runtime chart is known yet, so the fourth tile is Technology.
|
|
721
|
+
expect(
|
|
722
|
+
screen.getByRole("button", { name: "About Technology" }),
|
|
723
|
+
).toBeInTheDocument();
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
test("no (i) sits inside a link or another button", async () => {
|
|
727
|
+
await renderService("42.0%");
|
|
728
|
+
|
|
729
|
+
for (const button of screen.getAllByRole("button", { name: /^About / })) {
|
|
730
|
+
expect(button.closest("a")).toBeNull();
|
|
731
|
+
expect(button.parentElement?.closest("button") ?? null).toBeNull();
|
|
732
|
+
}
|
|
733
|
+
});
|
|
734
|
+
|
|
735
|
+
test("the span metrics it explains are every span of this service", async () => {
|
|
736
|
+
await renderService("42.0%");
|
|
737
|
+
|
|
738
|
+
const spanCalls: Array<MockAggregateCall> = mockAggregateCalls.filter(
|
|
739
|
+
(call: MockAggregateCall): boolean => {
|
|
740
|
+
return call.modelType.name === "Span";
|
|
741
|
+
},
|
|
742
|
+
);
|
|
743
|
+
|
|
744
|
+
expect(spanCalls.length).toBeGreaterThan(0);
|
|
745
|
+
|
|
746
|
+
for (const call of spanCalls) {
|
|
747
|
+
expect(call.aggregateBy.query["primaryEntityId"]).toBeDefined();
|
|
748
|
+
// No kind / root filter: "Every span", as the text says.
|
|
749
|
+
expect(call.aggregateBy.query["kind"]).toBeUndefined();
|
|
750
|
+
expect(call.aggregateBy.query["parentSpanId"]).toBeUndefined();
|
|
751
|
+
expect(call.aggregateBy.query["name"]).toBeUndefined();
|
|
752
|
+
}
|
|
753
|
+
});
|
|
754
|
+
|
|
755
|
+
test("the runtime metrics it explains are averaged per interval", async () => {
|
|
756
|
+
await renderService("42.0%");
|
|
757
|
+
|
|
758
|
+
const gaugeCalls: Array<MockAggregateCall> = mockAggregateCalls.filter(
|
|
759
|
+
(call: MockAggregateCall): boolean => {
|
|
760
|
+
return (
|
|
761
|
+
call.modelType.name === "Metric" &&
|
|
762
|
+
call.aggregateBy.query["name"] === "nodejs.eventloop.utilization"
|
|
763
|
+
);
|
|
764
|
+
},
|
|
765
|
+
);
|
|
766
|
+
|
|
767
|
+
expect(gaugeCalls).toHaveLength(1);
|
|
768
|
+
expect(gaugeCalls[0]!.aggregateBy.aggregationType).toBe(
|
|
769
|
+
AggregationType.Avg,
|
|
770
|
+
);
|
|
771
|
+
});
|
|
772
|
+
});
|