@oneuptime/common 12.0.31 → 12.0.32
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/AlertState.ts +0 -2
- package/Models/DatabaseModels/Domain.ts +1 -1
- package/Models/DatabaseModels/Incident.ts +1 -1
- package/Models/DatabaseModels/IncidentTemplate.ts +1 -1
- package/Models/DatabaseModels/Index.ts +2 -0
- package/Models/DatabaseModels/MetricType.ts +0 -2
- package/Models/DatabaseModels/MonitorStatusTimeline.ts +0 -2
- package/Models/DatabaseModels/MonitorTest.ts +0 -2
- package/Models/DatabaseModels/NetworkDevice.ts +121 -2
- package/Models/DatabaseModels/NetworkDeviceOidTemplate.ts +3 -1
- package/Models/DatabaseModels/NetworkDeviceRole.ts +648 -0
- package/Models/DatabaseModels/ScheduledMaintenance.ts +1 -1
- package/Models/DatabaseModels/ScheduledMaintenanceTemplate.ts +1 -1
- package/Models/DatabaseModels/StatusPageAnnouncement.ts +0 -2
- package/Models/DatabaseModels/StatusPageAnnouncementTemplate.ts +0 -2
- package/Models/DatabaseModels/StatusPagePrivateUser.ts +0 -2
- package/Models/DatabaseModels/StatusPageResource.ts +0 -2
- package/Models/DatabaseModels/StatusPageSubscriber.ts +0 -2
- package/Server/Infrastructure/Postgres/SchemaMigrations/1790800000000-AddNetworkDeviceRoleTable.ts +72 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
- package/Server/Services/DatabaseService.ts +39 -9
- package/Server/Services/Index.ts +2 -0
- package/Server/Services/NetworkDeviceRoleService.ts +90 -0
- package/Server/Services/ProjectService.ts +85 -0
- package/Server/Services/UserNotificationSettingService.ts +0 -6
- package/Tests/App/Dashboard/AddNeighborToMonitoringModal.test.tsx +31 -1
- package/Tests/App/Dashboard/DashboardChartWidgetZoom.test.tsx +35 -8
- package/Tests/App/Dashboard/DashboardCommandPalette.test.tsx +107 -3
- package/Tests/App/Dashboard/DashboardWideChartZoom.test.tsx +401 -0
- package/Tests/App/Dashboard/MonitorTemplateColumnAndFacet.test.tsx +318 -0
- package/Tests/App/Dashboard/NetworkDeviceBulkDelete.test.tsx +357 -0
- package/Tests/App/Dashboard/NetworkSideMenu.test.tsx +590 -0
- package/Tests/App/Dashboard/SideMenuHarness.tsx +11 -3
- package/Tests/Models/DatabaseModels/ModelRegistryInvariants.test.ts +115 -41
- package/Tests/Server/Infrastructure/Postgres/NetworkDeviceRoleTableMigration.test.ts +476 -0
- package/Tests/Server/Services/DatabaseServiceGenerateSlug.test.ts +215 -0
- package/Tests/Server/Services/NetworkDeviceRoleModel.test.ts +672 -0
- package/Tests/Server/Services/NetworkDeviceRuleEngines.test.ts +1 -1
- package/Tests/Server/Services/ProjectServiceCreateSubscriptionStarted.test.ts +67 -4
- package/Tests/Server/Services/ScheduledMaintenanceSubscriberEndTime.test.ts +369 -0
- package/Tests/Server/Services/UserNotificationSettingMonitorDefaults.test.ts +248 -0
- package/Tests/Server/Services/UserNotificationSettingWorkspaceChannels.test.ts +43 -2
- package/Tests/Server/Utils/BasicCron.test.ts +283 -0
- package/Tests/Server/Utils/IncomingCallPhoneNumber.test.ts +174 -0
- package/Tests/Types/NetworkDevice/DefaultNetworkDeviceRole.test.ts +417 -0
- package/Tests/UI/Components/Charts/ChartClickDoubleClickDisambiguation.test.tsx +221 -0
- package/Tests/UI/Components/Charts/ChartDoubleClickReset.test.tsx +220 -0
- package/Tests/UI/Components/ModelTable/BaseModelTableBulkDelete.test.tsx +751 -0
- package/Tests/UI/Components/ModelTable/BaseModelTableColumnCustomization.test.tsx +475 -43
- package/Tests/UI/Components/ModelTable/ColumnPreference.test.ts +89 -0
- package/Tests/UI/Components/NavBarCommandKGating.test.tsx +103 -8
- package/Tests/UI/Components/SideMenuItem.test.tsx +30 -0
- package/Tests/UI/Utils/TableColumnsToCsv.test.ts +30 -0
- package/Tests/UI/Utils/UseDashboardTimeRangeZoom.test.tsx +187 -0
- package/Tests/Utils/Dashboard/DashboardResourceListSharedArgs.test.ts +302 -0
- package/Tests/Utils/Dashboard/DashboardTimeRangeZoom.test.ts +242 -0
- package/Tests/Utils/Monitor/NetworkDeviceRoleCatalog.test.ts +981 -0
- package/Tests/Utils/Monitor/NetworkTopologyRoleCatalog.test.ts +865 -0
- package/Tests/Utils/NetworkDevice/DeviceRoleKeyUtil.test.ts +538 -0
- package/Tests/Utils/Slo/SloEvaluation.test.ts +171 -0
- package/Tests/Utils/Slug.test.ts +61 -1
- package/Tests/jest.setup.ts +19 -0
- package/Types/Monitor/SnmpMonitor/NetworkTopology.ts +61 -0
- package/Types/NetworkDevice/DefaultNetworkDeviceRole.ts +145 -0
- package/Types/Permission.ts +45 -0
- package/UI/Components/Charts/Area/AreaChart.tsx +7 -0
- package/UI/Components/Charts/Bar/BarChart.tsx +6 -0
- package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +9 -1
- package/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.tsx +97 -35
- package/UI/Components/Charts/ChartLibrary/BarChart/BarChart.tsx +14 -0
- package/UI/Components/Charts/ChartLibrary/LineChart/LineChart.tsx +97 -35
- package/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.ts +13 -0
- package/UI/Components/Charts/Line/LineChart.tsx +7 -0
- package/UI/Components/ModelTable/BaseModelTable.tsx +50 -15
- package/UI/Components/Navbar/NavBar.tsx +62 -13
- package/UI/Components/Navbar/NavBarMenuModal.tsx +7 -2
- package/UI/Components/SideMenu/CountModelSideMenuItem.tsx +2 -0
- package/UI/Components/SideMenu/SideMenu.tsx +12 -2
- package/UI/Components/SideMenu/SideMenuItem.tsx +4 -1
- package/UI/Components/SideMenu/SideMenuSection.tsx +14 -2
- package/UI/Utils/UseDashboardTimeRangeZoom.ts +77 -0
- package/Utils/Dashboard/DashboardTimeRangeZoom.ts +118 -0
- package/Utils/Monitor/NetworkDeviceRoleCatalog.ts +232 -0
- package/Utils/Monitor/NetworkDeviceRoleUtil.ts +2 -1
- package/Utils/Monitor/NetworkTopologyUtil.ts +116 -33
- package/Utils/NetworkDevice/DeviceRoleKeyUtil.ts +137 -0
- package/Utils/Rules/RulePatternMatchUtil.ts +1 -1
- package/Utils/Slug.ts +34 -1
- package/build/dist/Models/DatabaseModels/AlertState.js +0 -2
- package/build/dist/Models/DatabaseModels/AlertState.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Domain.js +1 -1
- package/build/dist/Models/DatabaseModels/Domain.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Incident.js +1 -1
- package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentTemplate.js +1 -1
- package/build/dist/Models/DatabaseModels/IncidentTemplate.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/MetricType.js +0 -2
- package/build/dist/Models/DatabaseModels/MetricType.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MonitorStatusTimeline.js +0 -2
- package/build/dist/Models/DatabaseModels/MonitorStatusTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MonitorTest.js +0 -2
- package/build/dist/Models/DatabaseModels/MonitorTest.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDevice.js +124 -3
- package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDeviceOidTemplate.js +3 -1
- package/build/dist/Models/DatabaseModels/NetworkDeviceOidTemplate.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDeviceRole.js +677 -0
- package/build/dist/Models/DatabaseModels/NetworkDeviceRole.js.map +1 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceTemplate.js +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceTemplate.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js +0 -2
- package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageAnnouncementTemplate.js +0 -2
- package/build/dist/Models/DatabaseModels/StatusPageAnnouncementTemplate.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPagePrivateUser.js +0 -2
- package/build/dist/Models/DatabaseModels/StatusPagePrivateUser.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageResource.js +0 -2
- package/build/dist/Models/DatabaseModels/StatusPageResource.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageSubscriber.js +0 -2
- package/build/dist/Models/DatabaseModels/StatusPageSubscriber.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790800000000-AddNetworkDeviceRoleTable.js +39 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790800000000-AddNetworkDeviceRoleTable.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +31 -5
- package/build/dist/Server/Services/DatabaseService.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/Services/NetworkDeviceRoleService.js +70 -0
- package/build/dist/Server/Services/NetworkDeviceRoleService.js.map +1 -0
- package/build/dist/Server/Services/ProjectService.js +65 -0
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationSettingService.js +0 -1
- package/build/dist/Server/Services/UserNotificationSettingService.js.map +1 -1
- package/build/dist/Types/NetworkDevice/DefaultNetworkDeviceRole.js +105 -0
- package/build/dist/Types/NetworkDevice/DefaultNetworkDeviceRole.js.map +1 -0
- package/build/dist/Types/Permission.js +40 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/UI/Components/Charts/Area/AreaChart.js +1 -1
- package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/Bar/BarChart.js +1 -1
- package/build/dist/UI/Components/Charts/Bar/BarChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +6 -1
- package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js +79 -31
- package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js +7 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js +79 -31
- package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.js +14 -0
- package/build/dist/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.js.map +1 -0
- package/build/dist/UI/Components/Charts/Line/LineChart.js +1 -1
- package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +39 -13
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/UI/Components/Navbar/NavBar.js +44 -9
- package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
- package/build/dist/UI/Components/Navbar/NavBarMenuModal.js +2 -2
- package/build/dist/UI/Components/Navbar/NavBarMenuModal.js.map +1 -1
- package/build/dist/UI/Components/SideMenu/CountModelSideMenuItem.js +1 -1
- package/build/dist/UI/Components/SideMenu/CountModelSideMenuItem.js.map +1 -1
- package/build/dist/UI/Components/SideMenu/SideMenu.js +9 -6
- package/build/dist/UI/Components/SideMenu/SideMenu.js.map +1 -1
- package/build/dist/UI/Components/SideMenu/SideMenuItem.js +1 -1
- package/build/dist/UI/Components/SideMenu/SideMenuItem.js.map +1 -1
- package/build/dist/UI/Components/SideMenu/SideMenuSection.js +7 -2
- package/build/dist/UI/Components/SideMenu/SideMenuSection.js.map +1 -1
- package/build/dist/UI/Utils/UseDashboardTimeRangeZoom.js +42 -0
- package/build/dist/UI/Utils/UseDashboardTimeRangeZoom.js.map +1 -0
- package/build/dist/Utils/Dashboard/DashboardTimeRangeZoom.js +74 -0
- package/build/dist/Utils/Dashboard/DashboardTimeRangeZoom.js.map +1 -0
- package/build/dist/Utils/Monitor/NetworkDeviceRoleCatalog.js +126 -0
- package/build/dist/Utils/Monitor/NetworkDeviceRoleCatalog.js.map +1 -0
- package/build/dist/Utils/Monitor/NetworkDeviceRoleUtil.js +2 -1
- package/build/dist/Utils/Monitor/NetworkDeviceRoleUtil.js.map +1 -1
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +67 -32
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
- package/build/dist/Utils/NetworkDevice/DeviceRoleKeyUtil.js +114 -0
- package/build/dist/Utils/NetworkDevice/DeviceRoleKeyUtil.js.map +1 -0
- package/build/dist/Utils/Rules/RulePatternMatchUtil.js +1 -1
- package/build/dist/Utils/Slug.js +31 -1
- package/build/dist/Utils/Slug.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
afterEach,
|
|
3
|
+
beforeAll,
|
|
3
4
|
beforeEach,
|
|
4
5
|
describe,
|
|
5
6
|
expect,
|
|
@@ -70,6 +71,12 @@ jest.mock("../../../UI/Utils/Navigation", () => {
|
|
|
70
71
|
return {
|
|
71
72
|
__esModule: true,
|
|
72
73
|
default: {
|
|
74
|
+
isOnThisPage: () => {
|
|
75
|
+
return false;
|
|
76
|
+
},
|
|
77
|
+
isStartWith: () => {
|
|
78
|
+
return false;
|
|
79
|
+
},
|
|
73
80
|
navigate: (...args: Array<unknown>) => {
|
|
74
81
|
return navigateMock(...args);
|
|
75
82
|
},
|
|
@@ -112,8 +119,8 @@ jest.mock("../../../UI/Utils/Project", () => {
|
|
|
112
119
|
|
|
113
120
|
/*
|
|
114
121
|
* The real catalog hook walks the full RouteMap through useTranslation; the
|
|
115
|
-
* host only
|
|
116
|
-
* the
|
|
122
|
+
* host and dashboard NavBar only need a compact catalog, so one page plus one
|
|
123
|
+
* product keeps the suite about shortcut ownership rather than catalog data.
|
|
117
124
|
*/
|
|
118
125
|
jest.mock(
|
|
119
126
|
"../../../../App/FeatureSet/Dashboard/src/Utils/NavigationItems",
|
|
@@ -137,7 +144,16 @@ jest.mock(
|
|
|
137
144
|
route: new RouteClass("/dashboard/abc123/home"),
|
|
138
145
|
},
|
|
139
146
|
],
|
|
140
|
-
moreMenuItems: [
|
|
147
|
+
moreMenuItems: [
|
|
148
|
+
{
|
|
149
|
+
title: "Monitors",
|
|
150
|
+
description: "Monitor anything that can go down.",
|
|
151
|
+
icon: "AltGlobe",
|
|
152
|
+
iconColor: "blue",
|
|
153
|
+
category: "Essentials",
|
|
154
|
+
route: new RouteClass("/dashboard/abc123/monitors"),
|
|
155
|
+
},
|
|
156
|
+
],
|
|
141
157
|
rightElement: {
|
|
142
158
|
id: "user-profile-nav-bar-item",
|
|
143
159
|
title: "User Profile",
|
|
@@ -158,6 +174,7 @@ jest.mock(
|
|
|
158
174
|
);
|
|
159
175
|
|
|
160
176
|
import DashboardCommandPalette from "../../../../App/FeatureSet/Dashboard/src/Components/CommandPalette/DashboardCommandPalette";
|
|
177
|
+
import DashboardNavbar from "../../../../App/FeatureSet/Dashboard/src/Components/NavBar/NavBar";
|
|
161
178
|
import EventName from "../../../../App/FeatureSet/Dashboard/src/Utils/EventName";
|
|
162
179
|
import GlobalEvents from "../../../UI/Utils/GlobalEvents";
|
|
163
180
|
|
|
@@ -165,6 +182,19 @@ function palette(): HTMLElement | null {
|
|
|
165
182
|
return screen.queryByTestId("command-palette");
|
|
166
183
|
}
|
|
167
184
|
|
|
185
|
+
function productsMenu(): HTMLElement | null {
|
|
186
|
+
return screen.queryByRole("dialog", { name: "Products menu" });
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function renderDashboardChrome(): void {
|
|
190
|
+
render(
|
|
191
|
+
<>
|
|
192
|
+
<DashboardNavbar show={true} />
|
|
193
|
+
<DashboardCommandPalette />
|
|
194
|
+
</>,
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
168
198
|
/*
|
|
169
199
|
* The chord is dispatched on document.body — where real key events land when
|
|
170
200
|
* no input is focused — and bubbles up to the host's document-level listener.
|
|
@@ -173,6 +203,11 @@ function pressChord(init: KeyboardEventInit): void {
|
|
|
173
203
|
fireEvent.keyDown(document.body, init);
|
|
174
204
|
}
|
|
175
205
|
|
|
206
|
+
beforeAll(() => {
|
|
207
|
+
// The products menu keeps its keyboard selection in view when it opens.
|
|
208
|
+
Element.prototype.scrollIntoView = (): void => {};
|
|
209
|
+
});
|
|
210
|
+
|
|
176
211
|
beforeEach(() => {
|
|
177
212
|
/*
|
|
178
213
|
* The open panel schedules an enter-transition frame (rAF + fallback
|
|
@@ -264,6 +299,75 @@ describe("DashboardCommandPalette Cmd/Ctrl+K chord", () => {
|
|
|
264
299
|
});
|
|
265
300
|
});
|
|
266
301
|
|
|
302
|
+
describe("dashboard Cmd/Ctrl+K ownership with the products menu mounted", () => {
|
|
303
|
+
test.each([
|
|
304
|
+
["Cmd+K", { key: "k", metaKey: true }],
|
|
305
|
+
["Ctrl+K", { key: "k", ctrlKey: true }],
|
|
306
|
+
] as Array<[string, KeyboardEventInit]>)(
|
|
307
|
+
"%s opens Search only",
|
|
308
|
+
(_label: string, chord: KeyboardEventInit) => {
|
|
309
|
+
renderDashboardChrome();
|
|
310
|
+
|
|
311
|
+
pressChord(chord);
|
|
312
|
+
|
|
313
|
+
expect(palette()).toBeInTheDocument();
|
|
314
|
+
expect(productsMenu()).not.toBeInTheDocument();
|
|
315
|
+
expect(screen.getAllByRole("dialog")).toHaveLength(1);
|
|
316
|
+
},
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
test("the Products button still opens Products without advertising Cmd/Ctrl+K", () => {
|
|
320
|
+
renderDashboardChrome();
|
|
321
|
+
|
|
322
|
+
fireEvent.click(screen.getByRole("button", { name: /Products/i }));
|
|
323
|
+
|
|
324
|
+
const dialog: HTMLElement = screen.getByRole("dialog", {
|
|
325
|
+
name: "Products menu",
|
|
326
|
+
});
|
|
327
|
+
expect(palette()).not.toBeInTheDocument();
|
|
328
|
+
expect(screen.getAllByRole("dialog")).toHaveLength(1);
|
|
329
|
+
|
|
330
|
+
/*
|
|
331
|
+
* The dashboard surrendered this chord to Search, so Products must not
|
|
332
|
+
* leave either the visible keycaps or their screen-reader label behind.
|
|
333
|
+
*/
|
|
334
|
+
const commandKKeycaps: HTMLElement[] = Array.from(
|
|
335
|
+
dialog.querySelectorAll<HTMLElement>("kbd"),
|
|
336
|
+
).filter((keycap: HTMLElement): boolean => {
|
|
337
|
+
return keycap.textContent === "K";
|
|
338
|
+
});
|
|
339
|
+
expect(commandKKeycaps).toHaveLength(0);
|
|
340
|
+
expect(dialog).not.toHaveTextContent(/(?:Command|Control) \+ K/);
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
test.each([
|
|
344
|
+
["Cmd+K", { key: "k", metaKey: true }],
|
|
345
|
+
["Ctrl+K", { key: "k", ctrlKey: true }],
|
|
346
|
+
] as Array<[string, KeyboardEventInit]>)(
|
|
347
|
+
"%s replaces an open Products dialog with one Search dialog",
|
|
348
|
+
(_label: string, chord: KeyboardEventInit) => {
|
|
349
|
+
renderDashboardChrome();
|
|
350
|
+
fireEvent.click(screen.getByRole("button", { name: /Products/i }));
|
|
351
|
+
|
|
352
|
+
const productsDialog: HTMLElement = screen.getByRole("dialog", {
|
|
353
|
+
name: "Products menu",
|
|
354
|
+
});
|
|
355
|
+
const productsSearch: HTMLElement = screen.getByRole("combobox", {
|
|
356
|
+
name: "navbar.search.placeholder",
|
|
357
|
+
});
|
|
358
|
+
expect(productsDialog).toBeInTheDocument();
|
|
359
|
+
expect(document.activeElement).toBe(productsSearch);
|
|
360
|
+
|
|
361
|
+
// Real keystrokes originate in the focused Products search input.
|
|
362
|
+
fireEvent.keyDown(productsSearch, chord);
|
|
363
|
+
|
|
364
|
+
expect(productsMenu()).not.toBeInTheDocument();
|
|
365
|
+
expect(palette()).toBeInTheDocument();
|
|
366
|
+
expect(screen.getAllByRole("dialog")).toHaveLength(1);
|
|
367
|
+
},
|
|
368
|
+
);
|
|
369
|
+
});
|
|
370
|
+
|
|
267
371
|
describe("DashboardCommandPalette global toggle event", () => {
|
|
268
372
|
test("the COMMAND_PALETTE_TOGGLE event toggles the palette", () => {
|
|
269
373
|
render(<DashboardCommandPalette />);
|
|
@@ -0,0 +1,401 @@
|
|
|
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
|
+
RenderResult,
|
|
12
|
+
act,
|
|
13
|
+
cleanup,
|
|
14
|
+
fireEvent,
|
|
15
|
+
render,
|
|
16
|
+
screen,
|
|
17
|
+
waitFor,
|
|
18
|
+
} from "@testing-library/react";
|
|
19
|
+
import * as React from "react";
|
|
20
|
+
import getJestMockFunction, { MockFunction } from "../../MockType";
|
|
21
|
+
|
|
22
|
+
/*
|
|
23
|
+
* Issue #3530: drag-selecting a window on a dashboard time-series panel is a
|
|
24
|
+
* statement about the whole board, not about that one panel — the same way
|
|
25
|
+
* filtering works in Traces and Logs. The widget therefore hands the
|
|
26
|
+
* selected window UP to the dashboard shell and lets the new board-wide
|
|
27
|
+
* range come back down as a prop, rather than narrowing itself and leaving
|
|
28
|
+
* every neighbouring panel showing a different hour.
|
|
29
|
+
*
|
|
30
|
+
* Double-clicking a panel is the way back out, and it must be inert while
|
|
31
|
+
* there is nothing to undo.
|
|
32
|
+
*
|
|
33
|
+
* The widget-local fallback (no shell callbacks) is a separate contract and
|
|
34
|
+
* is covered by DashboardChartWidgetZoom.test.tsx.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
const fetchResultsMock: MockFunction = getJestMockFunction();
|
|
38
|
+
const metricChartsRenderMock: MockFunction = getJestMockFunction();
|
|
39
|
+
const investigationDrawerRenderMock: MockFunction = getJestMockFunction();
|
|
40
|
+
|
|
41
|
+
/*
|
|
42
|
+
* The arrow wrappers are load bearing: jest.mock is hoisted above the
|
|
43
|
+
* compiled requires, so the mock variables above are still unassigned when
|
|
44
|
+
* the factory runs. Dereferencing them lazily, at call time, is what makes
|
|
45
|
+
* this work.
|
|
46
|
+
*/
|
|
47
|
+
jest.mock(
|
|
48
|
+
"../../../../App/FeatureSet/Dashboard/src/Components/Metrics/Utils/Metrics",
|
|
49
|
+
() => {
|
|
50
|
+
return {
|
|
51
|
+
__esModule: true,
|
|
52
|
+
default: {
|
|
53
|
+
fetchResults: (...args: Array<any>) => {
|
|
54
|
+
return fetchResultsMock(...args);
|
|
55
|
+
},
|
|
56
|
+
clearQueryTopNOverridesForScope: () => {
|
|
57
|
+
return undefined;
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
},
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
/*
|
|
65
|
+
* The widget's job under test is which window it ASKS for and which
|
|
66
|
+
* affordances it hands down; a real recharts surface in jsdom would test
|
|
67
|
+
* neither.
|
|
68
|
+
*/
|
|
69
|
+
jest.mock(
|
|
70
|
+
"../../../../App/FeatureSet/Dashboard/src/Components/Metrics/MetricCharts",
|
|
71
|
+
() => {
|
|
72
|
+
return {
|
|
73
|
+
__esModule: true,
|
|
74
|
+
default: (props: Record<string, unknown>): React.ReactElement => {
|
|
75
|
+
metricChartsRenderMock(props);
|
|
76
|
+
return React.createElement(
|
|
77
|
+
"div",
|
|
78
|
+
{ "data-testid": "metric-charts" },
|
|
79
|
+
"chart",
|
|
80
|
+
);
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
},
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
// The drawer itself pulls in the whole telemetry stack; only its mounting matters here.
|
|
87
|
+
jest.mock(
|
|
88
|
+
"../../../../App/FeatureSet/Dashboard/src/Components/Telemetry/InvestigationDrawer",
|
|
89
|
+
() => {
|
|
90
|
+
return {
|
|
91
|
+
__esModule: true,
|
|
92
|
+
default: (props: Record<string, unknown>): React.ReactElement => {
|
|
93
|
+
investigationDrawerRenderMock(props);
|
|
94
|
+
return React.createElement(
|
|
95
|
+
"div",
|
|
96
|
+
{ "data-testid": "investigation-drawer" },
|
|
97
|
+
"drawer",
|
|
98
|
+
);
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
},
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
import DashboardChartComponentElement from "../../../../App/FeatureSet/Dashboard/src/Components/Dashboard/Components/DashboardChartComponent";
|
|
105
|
+
import { DashboardBaseComponentProps } from "../../../../App/FeatureSet/Dashboard/src/Components/Dashboard/Components/DashboardBaseComponent";
|
|
106
|
+
import InBetween from "../../../Types/BaseDatabase/InBetween";
|
|
107
|
+
import DashboardChartType from "../../../Types/Dashboard/Chart/ChartType";
|
|
108
|
+
import DashboardComponentType from "../../../Types/Dashboard/DashboardComponentType";
|
|
109
|
+
import DashboardChartComponent from "../../../Types/Dashboard/DashboardComponents/DashboardChartComponent";
|
|
110
|
+
import DashboardViewConfig from "../../../Types/Dashboard/DashboardViewConfig";
|
|
111
|
+
import { ObjectType } from "../../../Types/JSON";
|
|
112
|
+
import MetricsAggregationType from "../../../Types/Metrics/MetricsAggregationType";
|
|
113
|
+
import MetricViewData from "../../../Types/Metrics/MetricViewData";
|
|
114
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
115
|
+
import RangeStartAndEndDateTime from "../../../Types/Time/RangeStartAndEndDateTime";
|
|
116
|
+
import TimeRange from "../../../Types/Time/TimeRange";
|
|
117
|
+
|
|
118
|
+
const COMPONENT_ID: ObjectID = new ObjectID(
|
|
119
|
+
"33333333-1111-4111-8111-333333333333",
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
const BOARD_START: Date = new Date("2026-08-20T09:00:00.000Z");
|
|
123
|
+
const BOARD_END: Date = new Date("2026-08-20T10:00:00.000Z");
|
|
124
|
+
const DRAG_START: Date = new Date("2026-08-20T09:20:00.000Z");
|
|
125
|
+
const DRAG_END: Date = new Date("2026-08-20T09:35:00.000Z");
|
|
126
|
+
|
|
127
|
+
/*
|
|
128
|
+
* A CUSTOM range pins the window to fixed instants — a relative range
|
|
129
|
+
* would resolve against the wall clock and make the window assertions
|
|
130
|
+
* untestable.
|
|
131
|
+
*/
|
|
132
|
+
const BOARD_RANGE: RangeStartAndEndDateTime = {
|
|
133
|
+
range: TimeRange.CUSTOM,
|
|
134
|
+
startAndEndDate: new InBetween<Date>(BOARD_START, BOARD_END),
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
/** What the shell hands back down once the drag has retimed the board. */
|
|
138
|
+
const ZOOMED_BOARD_RANGE: RangeStartAndEndDateTime = {
|
|
139
|
+
range: TimeRange.CUSTOM,
|
|
140
|
+
startAndEndDate: new InBetween<Date>(DRAG_START, DRAG_END),
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const DASHBOARD_VIEW_CONFIG: DashboardViewConfig = {
|
|
144
|
+
_type: ObjectType.DashboardViewConfig,
|
|
145
|
+
components: [],
|
|
146
|
+
heightInDashboardUnits: 60,
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
let onDashboardTimeRangeSelectMock: MockFunction;
|
|
150
|
+
let onDashboardTimeRangeResetMock: MockFunction;
|
|
151
|
+
|
|
152
|
+
function buildBaseProps(
|
|
153
|
+
overrides: Partial<DashboardBaseComponentProps> = {},
|
|
154
|
+
): DashboardBaseComponentProps {
|
|
155
|
+
return {
|
|
156
|
+
componentId: COMPONENT_ID,
|
|
157
|
+
isEditMode: false,
|
|
158
|
+
isSelected: false,
|
|
159
|
+
key: "chart-widget",
|
|
160
|
+
onComponentUpdate: (): void => {
|
|
161
|
+
// The widget under test never writes back through this.
|
|
162
|
+
},
|
|
163
|
+
totalCurrentDashboardWidthInPx: 1200,
|
|
164
|
+
dashboardCanvasTopInPx: 0,
|
|
165
|
+
dashboardCanvasLeftInPx: 0,
|
|
166
|
+
dashboardCanvasWidthInPx: 1200,
|
|
167
|
+
dashboardCanvasHeightInPx: 800,
|
|
168
|
+
dashboardComponentHeightInPx: 320,
|
|
169
|
+
dashboardComponentWidthInPx: 480,
|
|
170
|
+
dashboardViewConfig: DASHBOARD_VIEW_CONFIG,
|
|
171
|
+
dashboardStartAndEndDate: BOARD_RANGE,
|
|
172
|
+
metricTypes: [],
|
|
173
|
+
refreshTick: 0,
|
|
174
|
+
variables: undefined,
|
|
175
|
+
onDashboardTimeRangeSelect: onDashboardTimeRangeSelectMock as unknown as (
|
|
176
|
+
startTime: Date,
|
|
177
|
+
endTime: Date,
|
|
178
|
+
) => void,
|
|
179
|
+
onDashboardTimeRangeReset:
|
|
180
|
+
onDashboardTimeRangeResetMock as unknown as () => void,
|
|
181
|
+
isDashboardTimeRangeZoomed: false,
|
|
182
|
+
...overrides,
|
|
183
|
+
} as DashboardBaseComponentProps;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function buildChartComponent(): DashboardChartComponent {
|
|
187
|
+
return {
|
|
188
|
+
_type: ObjectType.DashboardComponent,
|
|
189
|
+
componentId: COMPONENT_ID,
|
|
190
|
+
componentType: DashboardComponentType.Chart,
|
|
191
|
+
topInDashboardUnits: 0,
|
|
192
|
+
leftInDashboardUnits: 0,
|
|
193
|
+
widthInDashboardUnits: 6,
|
|
194
|
+
heightInDashboardUnits: 3,
|
|
195
|
+
minWidthInDashboardUnits: 6,
|
|
196
|
+
minHeightInDashboardUnits: 3,
|
|
197
|
+
arguments: {
|
|
198
|
+
chartTitle: "CPU by host",
|
|
199
|
+
chartType: DashboardChartType.Line,
|
|
200
|
+
metricQueryConfig: {
|
|
201
|
+
metricAliasData: { metricVariable: "a" },
|
|
202
|
+
metricQueryData: {
|
|
203
|
+
filterData: {
|
|
204
|
+
metricName: "cpu.usage",
|
|
205
|
+
aggegationType: MetricsAggregationType.Avg,
|
|
206
|
+
},
|
|
207
|
+
groupByAttributeKeys: ["host.name"],
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
} as unknown as DashboardChartComponent;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function renderWidget(
|
|
215
|
+
overrides: Partial<DashboardBaseComponentProps> = {},
|
|
216
|
+
): RenderResult {
|
|
217
|
+
return render(
|
|
218
|
+
<DashboardChartComponentElement
|
|
219
|
+
{...buildBaseProps(overrides)}
|
|
220
|
+
component={buildChartComponent()}
|
|
221
|
+
/>,
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
interface CapturedChartProps {
|
|
226
|
+
metricViewData: MetricViewData;
|
|
227
|
+
onTimeRangeSelect?: ((startTime: Date, endTime: Date) => void) | undefined;
|
|
228
|
+
onTimeRangeReset?: (() => void) | undefined;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function lastChartProps(): CapturedChartProps {
|
|
232
|
+
const calls: Array<Array<unknown>> = metricChartsRenderMock.mock.calls;
|
|
233
|
+
return calls[calls.length - 1]?.[0] as CapturedChartProps;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function lastFetchWindow(): InBetween<Date> | null | undefined {
|
|
237
|
+
const calls: Array<Array<unknown>> = fetchResultsMock.mock.calls;
|
|
238
|
+
return (calls[calls.length - 1]?.[0] as { metricViewData: MetricViewData })
|
|
239
|
+
?.metricViewData?.startAndEndDate;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
beforeEach(() => {
|
|
243
|
+
onDashboardTimeRangeSelectMock = getJestMockFunction();
|
|
244
|
+
onDashboardTimeRangeResetMock = getJestMockFunction();
|
|
245
|
+
fetchResultsMock.mockReset();
|
|
246
|
+
metricChartsRenderMock.mockReset();
|
|
247
|
+
investigationDrawerRenderMock.mockReset();
|
|
248
|
+
fetchResultsMock.mockReturnValue(
|
|
249
|
+
Promise.resolve([{ data: [], truncated: false }]),
|
|
250
|
+
);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
afterEach(() => {
|
|
254
|
+
cleanup();
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
describe("dashboard-wide drag-to-zoom", () => {
|
|
258
|
+
test("a drag-selection is handed to the dashboard, not applied to this panel alone", async () => {
|
|
259
|
+
renderWidget();
|
|
260
|
+
|
|
261
|
+
await waitFor(() => {
|
|
262
|
+
expect(screen.getByTestId("metric-charts")).toBeInTheDocument();
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
expect(lastFetchWindow()?.startValue).toEqual(BOARD_START);
|
|
266
|
+
const fetchCountBeforeDrag: number = fetchResultsMock.mock.calls.length;
|
|
267
|
+
|
|
268
|
+
act(() => {
|
|
269
|
+
lastChartProps().onTimeRangeSelect?.(DRAG_START, DRAG_END);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
expect(onDashboardTimeRangeSelectMock.mock.calls.length).toBe(1);
|
|
273
|
+
expect(onDashboardTimeRangeSelectMock.mock.calls[0]).toEqual([
|
|
274
|
+
DRAG_START,
|
|
275
|
+
DRAG_END,
|
|
276
|
+
]);
|
|
277
|
+
|
|
278
|
+
/*
|
|
279
|
+
* The widget must NOT retime itself off its own bat — the shell owns
|
|
280
|
+
* the range, and a panel that jumped ahead of it would be showing a
|
|
281
|
+
* different window from every one of its neighbours until the new
|
|
282
|
+
* range came back down.
|
|
283
|
+
*/
|
|
284
|
+
await act(async () => {
|
|
285
|
+
await Promise.resolve();
|
|
286
|
+
});
|
|
287
|
+
expect(fetchResultsMock.mock.calls.length).toBe(fetchCountBeforeDrag);
|
|
288
|
+
expect(lastFetchWindow()?.startValue).toEqual(BOARD_START);
|
|
289
|
+
// …and no widget-local "Zoomed" pill appears either.
|
|
290
|
+
expect(screen.queryByText(/Zoomed:/)).toBeNull();
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
test("the new board range coming back down is what actually refetches the panel", async () => {
|
|
294
|
+
const rendered: RenderResult = renderWidget();
|
|
295
|
+
|
|
296
|
+
await waitFor(() => {
|
|
297
|
+
expect(screen.getByTestId("metric-charts")).toBeInTheDocument();
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
act(() => {
|
|
301
|
+
lastChartProps().onTimeRangeSelect?.(DRAG_START, DRAG_END);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
// The shell applies the zoom and re-renders every widget with it.
|
|
305
|
+
rendered.rerender(
|
|
306
|
+
<DashboardChartComponentElement
|
|
307
|
+
{...buildBaseProps({
|
|
308
|
+
dashboardStartAndEndDate: ZOOMED_BOARD_RANGE,
|
|
309
|
+
isDashboardTimeRangeZoomed: true,
|
|
310
|
+
})}
|
|
311
|
+
component={buildChartComponent()}
|
|
312
|
+
/>,
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
await waitFor(() => {
|
|
316
|
+
expect(lastFetchWindow()?.startValue).toEqual(DRAG_START);
|
|
317
|
+
});
|
|
318
|
+
expect(lastFetchWindow()?.endValue).toEqual(DRAG_END);
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
test("double-click-to-reset is only offered once the board is actually zoomed", async () => {
|
|
322
|
+
const rendered: RenderResult = renderWidget();
|
|
323
|
+
|
|
324
|
+
await waitFor(() => {
|
|
325
|
+
expect(screen.getByTestId("metric-charts")).toBeInTheDocument();
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
/*
|
|
329
|
+
* Nothing to undo yet. Withholding the handler is not cosmetic: the
|
|
330
|
+
* chart library delays every plain click while a reset handler is
|
|
331
|
+
* present, and a bucket click must stay instant on an unzoomed board.
|
|
332
|
+
*/
|
|
333
|
+
expect(lastChartProps().onTimeRangeReset).toBeUndefined();
|
|
334
|
+
|
|
335
|
+
rendered.rerender(
|
|
336
|
+
<DashboardChartComponentElement
|
|
337
|
+
{...buildBaseProps({
|
|
338
|
+
dashboardStartAndEndDate: ZOOMED_BOARD_RANGE,
|
|
339
|
+
isDashboardTimeRangeZoomed: true,
|
|
340
|
+
})}
|
|
341
|
+
component={buildChartComponent()}
|
|
342
|
+
/>,
|
|
343
|
+
);
|
|
344
|
+
|
|
345
|
+
await waitFor(() => {
|
|
346
|
+
expect(lastChartProps().onTimeRangeReset).toBeDefined();
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
act(() => {
|
|
350
|
+
lastChartProps().onTimeRangeReset?.();
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
expect(onDashboardTimeRangeResetMock.mock.calls.length).toBe(1);
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
test("edit mode offers neither zoom nor reset", async () => {
|
|
357
|
+
renderWidget({ isEditMode: true, isDashboardTimeRangeZoomed: true });
|
|
358
|
+
|
|
359
|
+
await waitFor(() => {
|
|
360
|
+
expect(screen.getByTestId("metric-charts")).toBeInTheDocument();
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
/*
|
|
364
|
+
* In edit mode the drag gesture belongs to widget move/resize, and a
|
|
365
|
+
* board-wide retime mid-edit would be a surprise on top of unsaved
|
|
366
|
+
* layout changes.
|
|
367
|
+
*/
|
|
368
|
+
expect(lastChartProps().onTimeRangeSelect).toBeUndefined();
|
|
369
|
+
expect(lastChartProps().onTimeRangeReset).toBeUndefined();
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
test("Investigate stays reachable from the panel header, on the window it is charting", async () => {
|
|
373
|
+
renderWidget({
|
|
374
|
+
dashboardStartAndEndDate: ZOOMED_BOARD_RANGE,
|
|
375
|
+
isDashboardTimeRangeZoomed: true,
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
await waitFor(() => {
|
|
379
|
+
expect(screen.getByTestId("metric-charts")).toBeInTheDocument();
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
/*
|
|
383
|
+
* The old entry point lived inside the widget-local "Zoomed" pill,
|
|
384
|
+
* which no longer renders on a real dashboard now that the shell owns
|
|
385
|
+
* the drag. It has to live somewhere that always shows.
|
|
386
|
+
*/
|
|
387
|
+
expect(screen.queryByText(/Zoomed:/)).toBeNull();
|
|
388
|
+
|
|
389
|
+
fireEvent.click(
|
|
390
|
+
screen.getByLabelText("Investigate this time window in a side panel"),
|
|
391
|
+
);
|
|
392
|
+
|
|
393
|
+
expect(screen.getByTestId("investigation-drawer")).toBeInTheDocument();
|
|
394
|
+
const drawerProps: { window?: InBetween<Date> | undefined } =
|
|
395
|
+
investigationDrawerRenderMock.mock.calls[
|
|
396
|
+
investigationDrawerRenderMock.mock.calls.length - 1
|
|
397
|
+
]?.[0] as { window?: InBetween<Date> | undefined };
|
|
398
|
+
expect(drawerProps.window?.startValue).toEqual(DRAG_START);
|
|
399
|
+
expect(drawerProps.window?.endValue).toEqual(DRAG_END);
|
|
400
|
+
});
|
|
401
|
+
});
|