@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
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
screen,
|
|
9
9
|
waitFor,
|
|
10
10
|
} from "@testing-library/react";
|
|
11
|
-
import React from "react";
|
|
11
|
+
import React, { ReactElement } from "react";
|
|
12
12
|
|
|
13
13
|
/*
|
|
14
14
|
* A real BaseModelTable mounts a card, a header, a full table and (in the
|
|
@@ -86,22 +86,31 @@ import BaseModelTable, {
|
|
|
86
86
|
ComponentProps as BaseModelTableProps,
|
|
87
87
|
} from "../../../../UI/Components/ModelTable/BaseModelTable";
|
|
88
88
|
import Columns from "../../../../UI/Components/ModelTable/Columns";
|
|
89
|
+
import TableColumnsToCsv from "../../../../UI/Utils/TableColumnsToCsv";
|
|
89
90
|
import TableFilterUrlState from "../../../../UI/Utils/TableFilterUrlState";
|
|
90
91
|
import Filter from "../../../../UI/Components/ModelFilter/Filter";
|
|
91
92
|
import FieldType from "../../../../UI/Components/Types/FieldType";
|
|
93
|
+
import { ButtonStyleType } from "../../../../UI/Components/Button/Button";
|
|
92
94
|
import Monitor from "../../../../Models/DatabaseModels/Monitor";
|
|
93
95
|
import Query from "../../../../Types/BaseDatabase/Query";
|
|
94
96
|
import ListResult from "../../../../Types/BaseDatabase/ListResult";
|
|
97
|
+
import SortOrder from "../../../../Types/BaseDatabase/SortOrder";
|
|
95
98
|
import UserPreferences, {
|
|
96
99
|
UserPreferenceType,
|
|
97
100
|
} from "../../../../Utils/UserPreferences";
|
|
98
|
-
import { JSONObject } from "../../../../Types/JSON";
|
|
101
|
+
import { JSONObject, JSONValue } from "../../../../Types/JSON";
|
|
99
102
|
|
|
100
103
|
const PREFS_KEY: string = "monitors-columns-table";
|
|
101
104
|
|
|
102
105
|
type GetListCall = {
|
|
103
106
|
query: Query<Monitor>;
|
|
104
107
|
select: JSONObject;
|
|
108
|
+
sort: JSONObject;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
type DownloadedCsvFile = {
|
|
112
|
+
csv: string;
|
|
113
|
+
filename: string;
|
|
105
114
|
};
|
|
106
115
|
|
|
107
116
|
const FILTERS: Array<Filter<Monitor>> = [
|
|
@@ -111,6 +120,14 @@ const FILTERS: Array<Filter<Monitor>> = [
|
|
|
111
120
|
type MakeColumnsOptions = {
|
|
112
121
|
// Adds a `isHiddenByDefault` column ("Monitor Type") at the end.
|
|
113
122
|
withHiddenByDefault?: boolean | undefined;
|
|
123
|
+
/*
|
|
124
|
+
* Adds a `isHiddenByDefault` *entity* column ("Template") at the end, shaped
|
|
125
|
+
* like the one on the real Monitors table: several nested fields, and a cell
|
|
126
|
+
* rendered from the relation rather than from a scalar on the row. A scalar
|
|
127
|
+
* column cannot stand in for it - the nested part of the request is built by
|
|
128
|
+
* a different function, and the cell it feeds is the one that goes blank.
|
|
129
|
+
*/
|
|
130
|
+
withHiddenByDefaultEntity?: boolean | undefined;
|
|
114
131
|
// Marks "Name" `isNotCustomizable`, i.e. pinned and kept out of the picker.
|
|
115
132
|
pinName?: boolean | undefined;
|
|
116
133
|
};
|
|
@@ -158,9 +175,80 @@ const makeColumns: MakeColumnsFunction = (
|
|
|
158
175
|
});
|
|
159
176
|
}
|
|
160
177
|
|
|
178
|
+
if (options.withHiddenByDefaultEntity) {
|
|
179
|
+
columns.push({
|
|
180
|
+
field: { monitorTemplate: { _id: true, templateName: true } },
|
|
181
|
+
title: "Template",
|
|
182
|
+
type: FieldType.Entity,
|
|
183
|
+
isHiddenByDefault: true,
|
|
184
|
+
getElement: (item: Monitor): ReactElement => {
|
|
185
|
+
return <span>{item.monitorTemplate?.templateName || ""}</span>;
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
161
190
|
return columns as unknown as Columns<Monitor>;
|
|
162
191
|
};
|
|
163
192
|
|
|
193
|
+
const TEMPLATE_NAME: string = "Standard HTTP Template";
|
|
194
|
+
|
|
195
|
+
/*
|
|
196
|
+
* One fully populated row. The relation is nested exactly as the API returns
|
|
197
|
+
* it, because the point of these tests is what survives the round trip from
|
|
198
|
+
* the request's `select` into the rendered cell.
|
|
199
|
+
*/
|
|
200
|
+
const ROWS: Array<JSONObject> = [
|
|
201
|
+
{
|
|
202
|
+
_id: "monitor-1",
|
|
203
|
+
name: "api-gateway",
|
|
204
|
+
description: "The public API gateway",
|
|
205
|
+
monitorTemplate: {
|
|
206
|
+
_id: "template-1",
|
|
207
|
+
templateName: TEMPLATE_NAME,
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
];
|
|
211
|
+
|
|
212
|
+
type ProjectRowFunction = (row: JSONObject, select: JSONObject) => JSONObject;
|
|
213
|
+
|
|
214
|
+
/*
|
|
215
|
+
* The API hands back exactly the fields the request asked for and nothing
|
|
216
|
+
* else, so the fake has to as well. A fake that returned the whole row
|
|
217
|
+
* whatever the select said would keep rendering a value for a field the table
|
|
218
|
+
* had stopped asking for - which is precisely the regression these tests are
|
|
219
|
+
* here to catch.
|
|
220
|
+
*/
|
|
221
|
+
const projectRow: ProjectRowFunction = (
|
|
222
|
+
row: JSONObject,
|
|
223
|
+
select: JSONObject,
|
|
224
|
+
): JSONObject => {
|
|
225
|
+
const projected: JSONObject = {};
|
|
226
|
+
|
|
227
|
+
for (const key of Object.keys(select)) {
|
|
228
|
+
const value: JSONValue | undefined = row[key];
|
|
229
|
+
const selected: JSONValue | undefined = select[key];
|
|
230
|
+
|
|
231
|
+
if (value === undefined) {
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// A nested select projects the relation, not just the presence of it.
|
|
236
|
+
if (
|
|
237
|
+
selected !== null &&
|
|
238
|
+
typeof selected === "object" &&
|
|
239
|
+
value !== null &&
|
|
240
|
+
typeof value === "object"
|
|
241
|
+
) {
|
|
242
|
+
projected[key] = projectRow(value as JSONObject, selected as JSONObject);
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
projected[key] = value;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return projected;
|
|
250
|
+
};
|
|
251
|
+
|
|
164
252
|
const ALL_TITLES: Array<string> = [
|
|
165
253
|
"Name",
|
|
166
254
|
"Description",
|
|
@@ -230,52 +318,79 @@ const getPickerRowIds: GetPickerRowIdsFunction = (): Array<string> => {
|
|
|
230
318
|
|
|
231
319
|
describe("BaseModelTable column customization", () => {
|
|
232
320
|
let calls: Array<GetListCall> = [];
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
321
|
+
let downloadedCsvFiles: Array<DownloadedCsvFile> = [];
|
|
322
|
+
|
|
323
|
+
type MakeCallbacksFunction = (
|
|
324
|
+
rows: Array<JSONObject>,
|
|
325
|
+
) => BaseTableCallbacks<Monitor>;
|
|
326
|
+
|
|
327
|
+
const makeCallbacks: MakeCallbacksFunction = (
|
|
328
|
+
rows: Array<JSONObject>,
|
|
329
|
+
): BaseTableCallbacks<Monitor> => {
|
|
330
|
+
return {
|
|
331
|
+
deleteItem: async () => {
|
|
332
|
+
return undefined;
|
|
333
|
+
},
|
|
334
|
+
getModelFromJSON: (item: JSONObject) => {
|
|
335
|
+
return item as unknown as Monitor;
|
|
336
|
+
},
|
|
337
|
+
getJSONFromModel: (item: Monitor) => {
|
|
338
|
+
return item as unknown as JSONObject;
|
|
339
|
+
},
|
|
340
|
+
addSlugToSelect: (select: unknown) => {
|
|
341
|
+
return select;
|
|
342
|
+
},
|
|
343
|
+
getList: async (data: {
|
|
344
|
+
query: Query<Monitor>;
|
|
345
|
+
limit: number;
|
|
346
|
+
select: JSONObject;
|
|
347
|
+
sort: JSONObject;
|
|
348
|
+
}): Promise<ListResult<Monitor>> => {
|
|
349
|
+
const select: JSONObject = (data.select || {}) as unknown as JSONObject;
|
|
350
|
+
|
|
351
|
+
calls.push({
|
|
352
|
+
query: data.query,
|
|
353
|
+
select: select,
|
|
354
|
+
sort: (data.sort || {}) as unknown as JSONObject,
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
return {
|
|
358
|
+
data: rows.map((row: JSONObject) => {
|
|
359
|
+
return projectRow(row, select);
|
|
360
|
+
}) as unknown as Array<Monitor>,
|
|
361
|
+
count: rows.length,
|
|
362
|
+
skip: 0,
|
|
363
|
+
limit: data.limit,
|
|
364
|
+
};
|
|
365
|
+
},
|
|
366
|
+
toJSONArray: () => {
|
|
367
|
+
return [];
|
|
368
|
+
},
|
|
369
|
+
updateById: async () => {
|
|
370
|
+
return undefined;
|
|
371
|
+
},
|
|
372
|
+
showCreateEditModal: () => {
|
|
373
|
+
return <></>;
|
|
374
|
+
},
|
|
375
|
+
} as unknown as BaseTableCallbacks<Monitor>;
|
|
376
|
+
};
|
|
273
377
|
|
|
274
378
|
type RenderTableOptions = {
|
|
275
379
|
userPreferencesKey?: string | undefined;
|
|
276
380
|
columns?: Columns<Monitor> | undefined;
|
|
277
381
|
isDeleteable?: boolean | undefined;
|
|
278
382
|
disableColumnCustomization?: boolean | undefined;
|
|
383
|
+
/*
|
|
384
|
+
* The rows the fake API has. Defaults to none, as most of this suite only
|
|
385
|
+
* looks at headers.
|
|
386
|
+
*/
|
|
387
|
+
rows?: Array<JSONObject> | undefined;
|
|
388
|
+
/*
|
|
389
|
+
* Gives the table a bulk action. Table grows its row checkboxes - and
|
|
390
|
+
* offers "Export CSV" - only when this array is non-empty, so it is the
|
|
391
|
+
* only way to reach the export from here.
|
|
392
|
+
*/
|
|
393
|
+
withBulkActions?: boolean | undefined;
|
|
279
394
|
};
|
|
280
395
|
|
|
281
396
|
type RenderTableFunction = (
|
|
@@ -303,10 +418,25 @@ describe("BaseModelTable column customization", () => {
|
|
|
303
418
|
isEditable: false,
|
|
304
419
|
// This suite is about columns; keep the URL out of it entirely.
|
|
305
420
|
disableUrlState: true,
|
|
306
|
-
callbacks: makeCallbacks(),
|
|
421
|
+
callbacks: makeCallbacks(options.rows || []),
|
|
307
422
|
...(options.disableColumnCustomization
|
|
308
423
|
? { disableColumnCustomization: true }
|
|
309
424
|
: {}),
|
|
425
|
+
...(options.withBulkActions
|
|
426
|
+
? {
|
|
427
|
+
bulkActions: {
|
|
428
|
+
buttons: [
|
|
429
|
+
{
|
|
430
|
+
title: "Archive",
|
|
431
|
+
buttonStyleType: ButtonStyleType.NORMAL,
|
|
432
|
+
onClick: async (): Promise<void> => {
|
|
433
|
+
return Promise.resolve();
|
|
434
|
+
},
|
|
435
|
+
},
|
|
436
|
+
],
|
|
437
|
+
},
|
|
438
|
+
}
|
|
439
|
+
: {}),
|
|
310
440
|
} as unknown as BaseModelTableProps<Monitor>;
|
|
311
441
|
|
|
312
442
|
return render(<BaseModelTable<Monitor> {...props} />);
|
|
@@ -324,6 +454,31 @@ describe("BaseModelTable column customization", () => {
|
|
|
324
454
|
});
|
|
325
455
|
};
|
|
326
456
|
|
|
457
|
+
type WaitForCallCountFunction = (count: number) => Promise<void>;
|
|
458
|
+
|
|
459
|
+
const waitForCallCount: WaitForCallCountFunction = async (
|
|
460
|
+
count: number,
|
|
461
|
+
): Promise<void> => {
|
|
462
|
+
await waitFor(() => {
|
|
463
|
+
expect(calls.length).toBe(count);
|
|
464
|
+
});
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
type SortByColumnFunction = (title: string) => Promise<void>;
|
|
468
|
+
|
|
469
|
+
/*
|
|
470
|
+
* The real click path for sorting: the header cell is a button, so this is
|
|
471
|
+
* what a viewer does, and it drives the same sortBy/sortOrder state the
|
|
472
|
+
* picker later has to reason about.
|
|
473
|
+
*/
|
|
474
|
+
const sortByColumn: SortByColumnFunction = async (
|
|
475
|
+
title: string,
|
|
476
|
+
): Promise<void> => {
|
|
477
|
+
await act(async () => {
|
|
478
|
+
fireEvent.click(screen.getByRole("button", { name: title }));
|
|
479
|
+
});
|
|
480
|
+
};
|
|
481
|
+
|
|
327
482
|
type OpenPickerFunction = () => Promise<void>;
|
|
328
483
|
|
|
329
484
|
/*
|
|
@@ -363,8 +518,65 @@ describe("BaseModelTable column customization", () => {
|
|
|
363
518
|
});
|
|
364
519
|
};
|
|
365
520
|
|
|
521
|
+
type SelectRowsFunction = (container: HTMLElement) => Promise<void>;
|
|
522
|
+
|
|
523
|
+
/*
|
|
524
|
+
* Ticks the header checkbox, which selects every row on the page and is what
|
|
525
|
+
* puts the bulk bar - and with it "Export CSV" - on screen at all.
|
|
526
|
+
*/
|
|
527
|
+
const selectRows: SelectRowsFunction = async (
|
|
528
|
+
container: HTMLElement,
|
|
529
|
+
): Promise<void> => {
|
|
530
|
+
// The header checkbox is disabled until the first page has rendered.
|
|
531
|
+
await waitFor(() => {
|
|
532
|
+
expect(
|
|
533
|
+
container.querySelectorAll('input[type="checkbox"]:not([disabled])')
|
|
534
|
+
.length,
|
|
535
|
+
).toBeGreaterThan(0);
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
await click(
|
|
539
|
+
container.querySelectorAll<HTMLInputElement>(
|
|
540
|
+
'input[type="checkbox"]',
|
|
541
|
+
)[0]!,
|
|
542
|
+
);
|
|
543
|
+
};
|
|
544
|
+
|
|
545
|
+
type ExportCsvFunction = () => Promise<Array<string>>;
|
|
546
|
+
|
|
547
|
+
/*
|
|
548
|
+
* The real click path, run end to end and read back as the lines a viewer
|
|
549
|
+
* would open in a spreadsheet. Every bulk action lives behind the bulk bar's
|
|
550
|
+
* "Bulk Actions" menu, so the export takes two clicks. Intercepted at the
|
|
551
|
+
* download rather than at the Blob, so the assertions are on the exact bytes
|
|
552
|
+
* that would have been written to disk.
|
|
553
|
+
*/
|
|
554
|
+
const exportCsv: ExportCsvFunction = async (): Promise<Array<string>> => {
|
|
555
|
+
await click(screen.getByText("Bulk Actions"));
|
|
556
|
+
|
|
557
|
+
await waitFor(() => {
|
|
558
|
+
expect(screen.queryByText("Export CSV")).not.toBeNull();
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
await click(screen.getByText("Export CSV"));
|
|
562
|
+
|
|
563
|
+
await waitFor(() => {
|
|
564
|
+
expect(downloadedCsvFiles.length).toBe(1);
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
return (downloadedCsvFiles[0]?.csv || "").split("\r\n");
|
|
568
|
+
};
|
|
569
|
+
|
|
366
570
|
beforeEach(() => {
|
|
367
571
|
calls = [];
|
|
572
|
+
downloadedCsvFiles = [];
|
|
573
|
+
|
|
574
|
+
jest
|
|
575
|
+
.spyOn(TableColumnsToCsv, "downloadCsv")
|
|
576
|
+
.mockImplementation((data: DownloadedCsvFile): void => {
|
|
577
|
+
downloadedCsvFiles.push(data);
|
|
578
|
+
});
|
|
579
|
+
|
|
368
580
|
window.history.replaceState(
|
|
369
581
|
window.history.state,
|
|
370
582
|
"",
|
|
@@ -499,6 +711,226 @@ describe("BaseModelTable column customization", () => {
|
|
|
499
711
|
expect(calls[0]?.select["description"]).toBe(true);
|
|
500
712
|
expect(calls[0]?.select["name"]).toBe(true);
|
|
501
713
|
});
|
|
714
|
+
|
|
715
|
+
test("a hidden entity column's relation is still selected, in full", async () => {
|
|
716
|
+
/*
|
|
717
|
+
* "monitorTemplate" is deliberately absent from the stored order: a
|
|
718
|
+
* column the layout does not mention falls back to its declared
|
|
719
|
+
* default, so this covers both ways an entity column ends up off
|
|
720
|
+
* screen - the viewer switched it off, and the table ships it off.
|
|
721
|
+
*/
|
|
722
|
+
seedPreference({
|
|
723
|
+
key: PREFS_KEY,
|
|
724
|
+
order: ["name", "description", "currentMonitorStatus", "labels"],
|
|
725
|
+
hidden: ["currentMonitorStatus"],
|
|
726
|
+
});
|
|
727
|
+
|
|
728
|
+
renderTable({
|
|
729
|
+
columns: makeColumns({ withHiddenByDefaultEntity: true }),
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
await waitForTable();
|
|
733
|
+
|
|
734
|
+
expect(getHeaders()).not.toContain("Monitor Status");
|
|
735
|
+
expect(getHeaders()).not.toContain("Template");
|
|
736
|
+
|
|
737
|
+
/*
|
|
738
|
+
* A relation is not selected by naming it - it is selected by the shape
|
|
739
|
+
* nested under it, and that shape is built by a *different* function
|
|
740
|
+
* (getRelationSelectFromColumns) with its own reference to the full
|
|
741
|
+
* column set. Narrowing only that one would leave every scalar
|
|
742
|
+
* assertion in this file green while every hidden entity cell came back
|
|
743
|
+
* as a bare id, so the column would paint blank the moment a viewer
|
|
744
|
+
* switched it on. Deep equality, because dropping the second key of a
|
|
745
|
+
* multi-field relation is the same bug one field smaller.
|
|
746
|
+
*/
|
|
747
|
+
expect(calls[0]?.select["currentMonitorStatus"]).toEqual({ name: true });
|
|
748
|
+
expect(calls[0]?.select["monitorTemplate"]).toEqual({
|
|
749
|
+
_id: true,
|
|
750
|
+
templateName: true,
|
|
751
|
+
});
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
test("a column that ships hidden is selected even with no stored layout", async () => {
|
|
755
|
+
renderTable({ columns: makeColumns({ withHiddenByDefault: true }) });
|
|
756
|
+
|
|
757
|
+
await waitForTable();
|
|
758
|
+
|
|
759
|
+
/*
|
|
760
|
+
* Hidden because the table author said so, not because this viewer
|
|
761
|
+
* chose it - there is nothing in localStorage at all. The two states
|
|
762
|
+
* reach the same select today, and an optimisation that only widened
|
|
763
|
+
* the request "when the viewer has a stored layout" would look
|
|
764
|
+
* perfectly reasonable and would break exactly the shipped default,
|
|
765
|
+
* which is the state almost every viewer is in.
|
|
766
|
+
*/
|
|
767
|
+
expect(getHeaders()).not.toContain("Monitor Type");
|
|
768
|
+
expect(readPreference(PREFS_KEY)).toBeNull();
|
|
769
|
+
expect(calls[0]?.select["monitorType"]).toBe(true);
|
|
770
|
+
});
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
describe("switching a hidden column back on", () => {
|
|
774
|
+
test("paints its value without asking the API again", async () => {
|
|
775
|
+
renderTable({
|
|
776
|
+
columns: makeColumns({ withHiddenByDefaultEntity: true }),
|
|
777
|
+
rows: ROWS,
|
|
778
|
+
});
|
|
779
|
+
|
|
780
|
+
await waitForTable();
|
|
781
|
+
|
|
782
|
+
expect(getHeaders()).not.toContain("Template");
|
|
783
|
+
expect(screen.queryByText(TEMPLATE_NAME)).toBeNull();
|
|
784
|
+
|
|
785
|
+
const callsBefore: number = calls.length;
|
|
786
|
+
|
|
787
|
+
await openPicker();
|
|
788
|
+
await click(screen.getByTestId("column-toggle-monitorTemplate"));
|
|
789
|
+
await savePicker();
|
|
790
|
+
|
|
791
|
+
await waitFor(() => {
|
|
792
|
+
expect(getHeaders()).toContain("Template");
|
|
793
|
+
});
|
|
794
|
+
|
|
795
|
+
/*
|
|
796
|
+
* The whole contract this table's hidden columns rest on, in one test.
|
|
797
|
+
*
|
|
798
|
+
* The request is built from the DECLARED columns, never the visible
|
|
799
|
+
* ones, and the fetch effect does not depend on the column set - so a
|
|
800
|
+
* column the viewer switches on is painted from the page already in
|
|
801
|
+
* hand, with its relation intact and no spinner. Wire the request up to
|
|
802
|
+
* the visible columns instead (there is a preference-filtered
|
|
803
|
+
* `selectFields` sitting unused in serializeToTableColumns that invites
|
|
804
|
+
* exactly that) and this cell comes back empty, with nothing to refetch
|
|
805
|
+
* it: the viewer's click would produce a column of blanks.
|
|
806
|
+
*/
|
|
807
|
+
expect(screen.getByText(TEMPLATE_NAME)).toBeInTheDocument();
|
|
808
|
+
expect(calls.length).toBe(callsBefore);
|
|
809
|
+
});
|
|
810
|
+
});
|
|
811
|
+
|
|
812
|
+
describe("what the viewer's layout does to the CSV export", () => {
|
|
813
|
+
test("a column that ships hidden is not in the exported file", async () => {
|
|
814
|
+
const { container } = renderTable({
|
|
815
|
+
columns: makeColumns({ withHiddenByDefaultEntity: true }),
|
|
816
|
+
rows: ROWS,
|
|
817
|
+
withBulkActions: true,
|
|
818
|
+
});
|
|
819
|
+
|
|
820
|
+
await waitForTable();
|
|
821
|
+
|
|
822
|
+
await selectRows(container);
|
|
823
|
+
|
|
824
|
+
const lines: Array<string> = await exportCsv();
|
|
825
|
+
|
|
826
|
+
/*
|
|
827
|
+
* "Export what you see". The export is built from the table's own
|
|
828
|
+
* column set, so a column the table ships switched off has no header
|
|
829
|
+
* and no cell in the file.
|
|
830
|
+
*
|
|
831
|
+
* This is the one assertion in the suite that is about data leaving the
|
|
832
|
+
* product rather than about pixels. Every other test here is happy for
|
|
833
|
+
* a hidden column's field to be fetched - it has to be, or switching
|
|
834
|
+
* the column on would paint blanks - so the row in memory is carrying
|
|
835
|
+
* the relation the whole time. Only the column filtering keeps it out
|
|
836
|
+
* of a file the viewer opens, mails and uploads.
|
|
837
|
+
*
|
|
838
|
+
* Build the Table columns from the unfiltered `allColumns` instead of
|
|
839
|
+
* the preference-filtered `columnsToRender` in serializeToTableColumns
|
|
840
|
+
* - a one-word change, and the first thing anyone would reach for after
|
|
841
|
+
* a ticket saying "the CSV lost my Template column" - and every table
|
|
842
|
+
* in the app starts exporting columns nobody asked to see.
|
|
843
|
+
*/
|
|
844
|
+
expect(lines[0]).toBe("Name,Description,Monitor Status,Labels");
|
|
845
|
+
expect(lines[1]).toBe("api-gateway,The public API gateway,,");
|
|
846
|
+
expect(lines.join("\r\n")).not.toContain(TEMPLATE_NAME);
|
|
847
|
+
});
|
|
848
|
+
|
|
849
|
+
test("switching it on puts it in the exported file", async () => {
|
|
850
|
+
const { container } = renderTable({
|
|
851
|
+
columns: makeColumns({ withHiddenByDefaultEntity: true }),
|
|
852
|
+
rows: ROWS,
|
|
853
|
+
withBulkActions: true,
|
|
854
|
+
});
|
|
855
|
+
|
|
856
|
+
await waitForTable();
|
|
857
|
+
|
|
858
|
+
await openPicker();
|
|
859
|
+
await click(screen.getByTestId("column-toggle-monitorTemplate"));
|
|
860
|
+
await savePicker();
|
|
861
|
+
|
|
862
|
+
await waitFor(() => {
|
|
863
|
+
expect(getHeaders()).toContain("Template");
|
|
864
|
+
});
|
|
865
|
+
|
|
866
|
+
await selectRows(container);
|
|
867
|
+
|
|
868
|
+
const lines: Array<string> = await exportCsv();
|
|
869
|
+
|
|
870
|
+
/*
|
|
871
|
+
* The other half of the same contract, and the reason the test above
|
|
872
|
+
* cannot be satisfied by simply never exporting an `isHiddenByDefault`
|
|
873
|
+
* column: the file follows this viewer's layout, not the table author's
|
|
874
|
+
* default. Someone who switches Template on has almost certainly done
|
|
875
|
+
* it *because* they want it in the export, and freezing the export
|
|
876
|
+
* columns at the shipped default would hand them a file missing the one
|
|
877
|
+
* column they went to the picker for.
|
|
878
|
+
*/
|
|
879
|
+
expect(lines[0]).toBe("Name,Description,Monitor Status,Labels,Template");
|
|
880
|
+
expect(lines[1]).toContain(TEMPLATE_NAME);
|
|
881
|
+
});
|
|
882
|
+
});
|
|
883
|
+
|
|
884
|
+
describe("the sort a hidden column leaves behind", () => {
|
|
885
|
+
test("showing a column does not refetch; hiding the sorted one does", async () => {
|
|
886
|
+
renderTable({
|
|
887
|
+
columns: makeColumns({ withHiddenByDefaultEntity: true }),
|
|
888
|
+
rows: ROWS,
|
|
889
|
+
});
|
|
890
|
+
|
|
891
|
+
await waitForTable();
|
|
892
|
+
|
|
893
|
+
await sortByColumn("Description");
|
|
894
|
+
await waitForCallCount(2);
|
|
895
|
+
expect(calls[1]?.sort).toEqual({
|
|
896
|
+
description: SortOrder.Descending,
|
|
897
|
+
});
|
|
898
|
+
|
|
899
|
+
await openPicker();
|
|
900
|
+
await click(screen.getByTestId("column-toggle-monitorTemplate"));
|
|
901
|
+
await savePicker();
|
|
902
|
+
|
|
903
|
+
await waitFor(() => {
|
|
904
|
+
expect(getHeaders()).toContain("Template");
|
|
905
|
+
});
|
|
906
|
+
|
|
907
|
+
/*
|
|
908
|
+
* Adding a column changes nothing the server was asked for, so the
|
|
909
|
+
* guard in onColumnCustomizationSave has to leave the sort alone. A
|
|
910
|
+
* reset-on-every-save would throw the viewer's ordering away - and
|
|
911
|
+
* their page - every time they ticked a box.
|
|
912
|
+
*/
|
|
913
|
+
expect(calls.length).toBe(2);
|
|
914
|
+
expect(screen.getByText(TEMPLATE_NAME)).toBeInTheDocument();
|
|
915
|
+
|
|
916
|
+
await openPicker();
|
|
917
|
+
await click(screen.getByTestId("column-toggle-description"));
|
|
918
|
+
await savePicker();
|
|
919
|
+
|
|
920
|
+
await waitFor(() => {
|
|
921
|
+
expect(getHeaders()).not.toContain("Description");
|
|
922
|
+
});
|
|
923
|
+
|
|
924
|
+
/*
|
|
925
|
+
* Hiding the column the table is sorted by is different: the header
|
|
926
|
+
* that ordering came from is gone, so the viewer can neither see it nor
|
|
927
|
+
* click it off. The sort falls back to the table's default (here: none)
|
|
928
|
+
* and the page is refetched, rather than leaving them staring at rows
|
|
929
|
+
* in an order they cannot explain or undo.
|
|
930
|
+
*/
|
|
931
|
+
await waitForCallCount(3);
|
|
932
|
+
expect(calls[2]?.sort).toEqual({});
|
|
933
|
+
});
|
|
502
934
|
});
|
|
503
935
|
|
|
504
936
|
describe("the generated Actions column", () => {
|
|
@@ -1675,4 +1675,93 @@ describe("a stored layout replayed against a changed column set", () => {
|
|
|
1675
1675
|
expect(rendered).toHaveLength(4);
|
|
1676
1676
|
expect(titlesOf(rendered)).toEqual(["Name", "Actions", "Actions", ""]);
|
|
1677
1677
|
});
|
|
1678
|
+
|
|
1679
|
+
test("a layout saved against a column set missing a default-hidden column switches it back off", () => {
|
|
1680
|
+
/*
|
|
1681
|
+
* A column set changes between surfaces, not just between releases: the
|
|
1682
|
+
* same table component is embedded on a detail page with one column
|
|
1683
|
+
* spliced out (there is no point showing "Template" on the template's own
|
|
1684
|
+
* page). If both surfaces persist under one storage key, the narrow one
|
|
1685
|
+
* writes the layout for the wide one.
|
|
1686
|
+
*
|
|
1687
|
+
* buildColumnPreference records only what the picker was showing, so the
|
|
1688
|
+
* spliced-out id lands in neither `order` nor `hidden` — and reading it
|
|
1689
|
+
* back falls through to the column's DECLARED default.
|
|
1690
|
+
*
|
|
1691
|
+
* That fallback is why this stayed invisible until now, and why it is the
|
|
1692
|
+
* hazard the moment a column ships hidden: a dropped column that defaults
|
|
1693
|
+
* to visible is restored by accident, so nobody ever noticed the drop,
|
|
1694
|
+
* while a dropped column that defaults to hidden silently un-picks the one
|
|
1695
|
+
* choice the viewer made — and the picker is the only way back to it. A
|
|
1696
|
+
* table embedded on a second surface must therefore key its stored layout
|
|
1697
|
+
* per surface; this is the loss it is avoiding.
|
|
1698
|
+
*/
|
|
1699
|
+
const fullColumns: Columns<Monitor> = [
|
|
1700
|
+
makeColumn({
|
|
1701
|
+
field: { name: true },
|
|
1702
|
+
title: "Name",
|
|
1703
|
+
isNotCustomizable: true,
|
|
1704
|
+
}),
|
|
1705
|
+
makeColumn({ field: { description: true }, title: "Description" }),
|
|
1706
|
+
makeColumn({
|
|
1707
|
+
field: { monitorTemplate: { _id: true, templateName: true } },
|
|
1708
|
+
title: "Template",
|
|
1709
|
+
isHiddenByDefault: true,
|
|
1710
|
+
}),
|
|
1711
|
+
makeColumn({ field: { labels: true }, title: "Labels" }),
|
|
1712
|
+
];
|
|
1713
|
+
|
|
1714
|
+
// The embedded surface drops the default-hidden column and one other.
|
|
1715
|
+
const scopedColumns: Columns<Monitor> = fullColumns.filter(
|
|
1716
|
+
(column: Column<Monitor>): boolean => {
|
|
1717
|
+
return column.title !== "Template" && column.title !== "Labels";
|
|
1718
|
+
},
|
|
1719
|
+
);
|
|
1720
|
+
|
|
1721
|
+
// The viewer switches Template on from the full table and saves.
|
|
1722
|
+
const preferenceA: ColumnPreference = buildColumnPreference<Monitor>(
|
|
1723
|
+
getCustomizableColumns<Monitor>({
|
|
1724
|
+
columns: fullColumns,
|
|
1725
|
+
preference: {
|
|
1726
|
+
order: ["description", "monitorTemplate", "labels"],
|
|
1727
|
+
hidden: [],
|
|
1728
|
+
},
|
|
1729
|
+
}),
|
|
1730
|
+
);
|
|
1731
|
+
|
|
1732
|
+
expect(
|
|
1733
|
+
findEntry(
|
|
1734
|
+
getCustomizableColumns<Monitor>({
|
|
1735
|
+
columns: fullColumns,
|
|
1736
|
+
preference: preferenceA,
|
|
1737
|
+
}),
|
|
1738
|
+
"monitorTemplate",
|
|
1739
|
+
).isVisible,
|
|
1740
|
+
).toBe(true);
|
|
1741
|
+
|
|
1742
|
+
/*
|
|
1743
|
+
* Now the picker is saved from the embedded surface without the viewer
|
|
1744
|
+
* changing anything there.
|
|
1745
|
+
*/
|
|
1746
|
+
const preferenceB: ColumnPreference = buildColumnPreference<Monitor>(
|
|
1747
|
+
getCustomizableColumns<Monitor>({
|
|
1748
|
+
columns: scopedColumns,
|
|
1749
|
+
preference: preferenceA,
|
|
1750
|
+
}),
|
|
1751
|
+
);
|
|
1752
|
+
|
|
1753
|
+
expect(preferenceB.order).not.toContain("monitorTemplate");
|
|
1754
|
+
expect(preferenceB.hidden).not.toContain("monitorTemplate");
|
|
1755
|
+
|
|
1756
|
+
const replayed: Array<CustomizableColumn<Monitor>> =
|
|
1757
|
+
getCustomizableColumns<Monitor>({
|
|
1758
|
+
columns: fullColumns,
|
|
1759
|
+
preference: preferenceB,
|
|
1760
|
+
});
|
|
1761
|
+
|
|
1762
|
+
// The viewer's one opt-in is gone...
|
|
1763
|
+
expect(findEntry(replayed, "monitorTemplate").isVisible).toBe(false);
|
|
1764
|
+
// ...while a column dropped exactly the same way comes back unharmed.
|
|
1765
|
+
expect(findEntry(replayed, "labels").isVisible).toBe(true);
|
|
1766
|
+
});
|
|
1678
1767
|
});
|