@oneuptime/common 11.7.0 → 11.7.2
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/DashboardDomain.ts +1 -0
- package/Models/DatabaseModels/Incident.ts +2 -0
- package/Models/DatabaseModels/IncidentEpisode.ts +1 -0
- package/Models/DatabaseModels/IncidentEpisodePublicNote.ts +1 -0
- package/Models/DatabaseModels/IncidentEpisodeStateTimeline.ts +1 -0
- package/Models/DatabaseModels/IncidentPublicNote.ts +1 -0
- package/Models/DatabaseModels/IncidentStateTimeline.ts +1 -0
- package/Models/DatabaseModels/Monitor.ts +1 -0
- package/Models/DatabaseModels/MonitorStatusTimeline.ts +1 -0
- package/Models/DatabaseModels/NetworkSite.ts +1 -0
- package/Models/DatabaseModels/NetworkSiteStatusTimeline.ts +1 -0
- package/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.ts +1 -0
- package/Models/DatabaseModels/OnCallDutyPolicySchedule.ts +2 -0
- package/Models/DatabaseModels/Probe.ts +13 -13
- package/Models/DatabaseModels/ScheduledMaintenance.ts +1 -0
- package/Models/DatabaseModels/ScheduledMaintenancePublicNote.ts +1 -0
- package/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.ts +1 -0
- package/Models/DatabaseModels/ScheduledMaintenanceTemplate.ts +1 -0
- package/Models/DatabaseModels/StatusPage.ts +1 -0
- package/Models/DatabaseModels/StatusPageAnnouncement.ts +1 -0
- package/Models/DatabaseModels/StatusPageDomain.ts +1 -0
- package/Models/DatabaseModels/WorkspaceNotificationSummary.ts +1 -0
- package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +11 -0
- package/Server/API/BaseAPI.ts +17 -1
- package/Server/API/CommonAPI.ts +32 -0
- package/Server/API/MicrosoftTeamsAPI.ts +99 -2
- package/Server/API/ProbeAPI.ts +2 -0
- package/Server/API/SlackAPI.ts +62 -0
- package/Server/API/StatusPageAPI.ts +157 -192
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785140242697-AddHotQueryIndexes.ts +89 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785148065137-AddHotQueryIndexesSecondPass.ts +61 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785240000000-RepairCrossProjectMonitorStatusReferences.ts +280 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
- package/Server/Services/DatabaseService.ts +11 -3
- package/Server/Services/MonitorGroupService.ts +145 -0
- package/Server/Services/MonitorService.ts +231 -30
- package/Server/Services/ProbeService.ts +319 -16
- package/Server/Services/StatusPageService.ts +72 -42
- package/Server/Services/WorkspaceNotificationRuleService.ts +232 -2
- package/Server/Utils/Database/PostgresErrorTranslator.ts +131 -0
- package/Server/Utils/Monitor/MonitorResource.ts +88 -35
- package/Server/Utils/Monitor/MonitorStatusTimeline.ts +21 -1
- package/Server/Utils/Monitor/MonitorStepsProjectValidator.ts +158 -0
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +706 -6
- package/Tests/Models/DatabaseModels/ProbeColumnAccessControl.test.ts +103 -0
- package/Tests/Server/API/BaseAPIUpdateNoOp.test.ts +135 -0
- package/Tests/Server/API/CommonAPIAuthGuard.test.ts +317 -0
- package/Tests/Server/API/MicrosoftTeamsManifest.test.ts +228 -0
- package/Tests/Server/API/ProbeAPI.test.ts +6 -0
- package/Tests/Server/API/ZZVerifyChatsAuth.test.ts +167 -0
- package/Tests/Server/Services/AddHotQueryIndexesMigration.test.ts +255 -0
- package/Tests/Server/Services/AddHotQueryIndexesSecondPassMigration.test.ts +258 -0
- package/Tests/Server/Services/DatabaseServiceSanitizeUpdateData.test.ts +1 -1
- package/Tests/Server/Services/DiscoveryScanClaimHookFreeSafety.test.ts +121 -0
- package/Tests/Server/Services/HeartbeatWriteFastPathSafety.test.ts +200 -0
- package/Tests/Server/Services/HookFreeWriteSafetySecondPass.test.ts +181 -0
- package/Tests/Server/Services/MonitorGroupServiceBatchedStatuses.test.ts +513 -0
- package/Tests/Server/Services/MonitorResourceProbeAgreementReuse.test.ts +492 -0
- package/Tests/Server/Services/MonitorServiceProbeSelection.test.ts +401 -0
- package/Tests/Server/Services/MonitorStatusTimelineFastPath.test.ts +453 -0
- package/Tests/Server/Services/ProbeAuthKeyCacheAndHeartbeat.test.ts +679 -0
- package/Tests/Server/Services/ServiceLevelObjectiveService.test.ts +3 -3
- package/Tests/Server/Services/StatusPageDomainResolutionCache.test.ts +245 -0
- package/Tests/Server/Services/StatusPageOverviewTimelineWindow.test.ts +283 -0
- package/Tests/Server/Services/StatusPageServiceMcp.test.ts +17 -1
- package/Tests/Server/Services/StatusPageServiceMonitorGroupBatching.test.ts +331 -0
- package/Tests/Server/Services/UserOnCallLogClaimNotificationRule.test.ts +12 -12
- package/Tests/Server/Services/WorkspaceNotificationRuleChats.test.ts +1390 -0
- package/Tests/Server/Utils/AI/Insights/InsightFixRouting.test.ts +4 -4
- package/Tests/Server/Utils/AI/Insights/InsightScanner.test.ts +2 -2
- package/Tests/Server/Utils/AI/Insights/InsightStore.test.ts +1 -3
- package/Tests/Server/Utils/AI/Insights/InsightStoreHardening.test.ts +1 -3
- package/Tests/Server/Utils/AI/Insights/InsightTriage.test.ts +1 -1
- package/Tests/Server/Utils/AI/Insights/InsightTriageRunner.test.ts +5 -5
- package/Tests/Server/Utils/Alert/AlertPrivacyFilter.test.ts +187 -0
- package/Tests/Server/Utils/AlertEpisode/AlertEpisodePrivacyFilter.test.ts +170 -0
- package/Tests/Server/Utils/Database/PostgresErrorTranslator.test.ts +125 -0
- package/Tests/Server/Utils/Incident/IncidentPrivacyFilter.test.ts +264 -0
- package/Tests/Server/Utils/IncidentEpisode/IncidentEpisodePrivacyFilter.test.ts +175 -0
- package/Tests/Server/Utils/Monitor/MonitorStepsProjectValidator.test.ts +181 -0
- package/Tests/Server/Utils/Monitor/NetworkDeviceWalkUtil.test.ts +1 -1
- package/Tests/Server/Utils/Monitor/NetworkInventoryUtil.test.ts +2 -2
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsChannelsList.test.ts +341 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsChats.test.ts +2175 -0
- package/Tests/Server/Utils/Workspace/SlackChannelsList.test.ts +585 -0
- package/Tests/Types/Billing/MeteredPlan.test.ts +46 -0
- package/Tests/Types/DateUserTimezone.test.ts +205 -0
- package/Tests/Types/DateUserTimezoneOverridesBrowser.test.ts +73 -0
- package/Tests/Types/Workspace/NotificationRules/NotificationRuleCondition.test.ts +219 -1
- package/Tests/UI/Components/BulkUpdateForm.test.tsx +174 -0
- package/Tests/UI/Components/CardModelDetailEdit.test.tsx +315 -0
- package/Tests/UI/Components/ModelDetailSelect.test.tsx +229 -0
- package/Tests/UI/Components/ModelTable/BaseModelTableBulkSelectAll.test.tsx +1258 -0
- package/Tests/UI/Components/ModelTableExportFromColumns.test.ts +153 -0
- package/Tests/UI/Components/MonacoLoader.test.ts +67 -0
- package/Tests/UI/Components/MonacoRuntime.test.ts +121 -0
- package/Tests/UI/Components/TableBulkCsvExport.test.tsx +212 -3
- package/Tests/UI/Components/TimePicker/TimePicker.test.tsx +11 -0
- package/Tests/UI/Utils/Project.test.ts +809 -0
- package/Tests/UI/Utils/TableColumnsToCsv.test.ts +360 -0
- package/Tests/Utils/Alerts/AlertMetricType.test.ts +143 -0
- package/Tests/Utils/Dashboard/VariableUrlState.test.ts +219 -0
- package/Tests/Utils/Incident/IncidentMetricType.test.ts +174 -0
- package/Tests/Utils/Monitor/MonitorProbeSelectionUtil.test.ts +145 -0
- package/Tests/Utils/StatusPage/ResourceUptime.test.ts +263 -0
- package/Types/Date.ts +119 -50
- package/Types/Workspace/NotificationRules/BaseNotificationRule.ts +4 -0
- package/Types/Workspace/NotificationRules/NotificationRuleCondition.ts +25 -10
- package/Types/Workspace/WorkspaceMessagePayload.ts +1 -0
- package/UI/Components/BulkUpdate/BulkUpdateForm.tsx +63 -8
- package/UI/Components/CodeEditor/CodeEditor.tsx +3 -0
- package/UI/Components/CodeEditor/MonacoLoader.ts +36 -0
- package/UI/Components/Forms/Fields/FormField.tsx +13 -3
- package/UI/Components/Forms/ModelForm.tsx +41 -18
- package/UI/Components/Input/Input.tsx +6 -1
- package/UI/Components/ModelDetail/CardModelDetail.tsx +23 -1
- package/UI/Components/ModelDetail/ModelDetail.tsx +46 -1
- package/UI/Components/ModelTable/BaseModelTable.tsx +300 -30
- package/UI/Components/ModelTable/Column.ts +12 -0
- package/UI/Components/ModelTable/ExportFromColumns.ts +57 -0
- package/UI/Components/Navbar/NavBar.tsx +66 -1
- package/UI/Components/Table/Table.tsx +52 -13
- package/UI/Components/Table/Types/Column.ts +18 -0
- package/UI/Components/TimePicker/TimePicker.tsx +8 -4
- package/UI/Utils/Project.ts +214 -18
- package/UI/Utils/TableColumnsToCsv.ts +86 -6
- package/UI/Utils/User.ts +19 -0
- package/UI/esbuild-config.js +34 -0
- package/Utils/Monitor/MonitorProbeSelectionUtil.ts +72 -0
- package/build/dist/Models/DatabaseModels/DashboardDomain.js +1 -0
- package/build/dist/Models/DatabaseModels/DashboardDomain.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Incident.js +2 -0
- package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentEpisode.js +1 -0
- package/build/dist/Models/DatabaseModels/IncidentEpisode.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentEpisodePublicNote.js +1 -0
- package/build/dist/Models/DatabaseModels/IncidentEpisodePublicNote.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js +1 -0
- package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentPublicNote.js +1 -0
- package/build/dist/Models/DatabaseModels/IncidentPublicNote.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js +1 -0
- package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Monitor.js +1 -0
- package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MonitorStatusTimeline.js +1 -0
- package/build/dist/Models/DatabaseModels/MonitorStatusTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkSite.js +1 -0
- package/build/dist/Models/DatabaseModels/NetworkSite.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkSiteStatusTimeline.js +1 -0
- package/build/dist/Models/DatabaseModels/NetworkSiteStatusTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js +1 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js +2 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Probe.js +13 -13
- package/build/dist/Models/DatabaseModels/Probe.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js +1 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenancePublicNote.js +1 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenancePublicNote.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js +1 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceTemplate.js +1 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceTemplate.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPage.js +1 -0
- package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js +1 -0
- package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageDomain.js +1 -0
- package/build/dist/Models/DatabaseModels/StatusPageDomain.js.map +1 -1
- package/build/dist/Models/DatabaseModels/WorkspaceNotificationSummary.js +1 -0
- package/build/dist/Models/DatabaseModels/WorkspaceNotificationSummary.js.map +1 -1
- package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
- package/build/dist/Server/API/BaseAPI.js +12 -1
- package/build/dist/Server/API/BaseAPI.js.map +1 -1
- package/build/dist/Server/API/CommonAPI.js +21 -0
- package/build/dist/Server/API/CommonAPI.js.map +1 -1
- package/build/dist/Server/API/MicrosoftTeamsAPI.js +72 -2
- package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
- package/build/dist/Server/API/ProbeAPI.js +2 -0
- package/build/dist/Server/API/ProbeAPI.js.map +1 -1
- package/build/dist/Server/API/SlackAPI.js +44 -0
- package/build/dist/Server/API/SlackAPI.js.map +1 -1
- package/build/dist/Server/API/StatusPageAPI.js +98 -152
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785140242697-AddHotQueryIndexes.js +36 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785140242697-AddHotQueryIndexes.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785148065137-AddHotQueryIndexesSecondPass.js +26 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785148065137-AddHotQueryIndexesSecondPass.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785240000000-RepairCrossProjectMonitorStatusReferences.js +254 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785240000000-RepairCrossProjectMonitorStatusReferences.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +10 -2
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/MonitorGroupService.js +118 -0
- package/build/dist/Server/Services/MonitorGroupService.js.map +1 -1
- package/build/dist/Server/Services/MonitorService.js +198 -24
- package/build/dist/Server/Services/MonitorService.js.map +1 -1
- package/build/dist/Server/Services/ProbeService.js +238 -9
- package/build/dist/Server/Services/ProbeService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageService.js +46 -39
- package/build/dist/Server/Services/StatusPageService.js.map +1 -1
- package/build/dist/Server/Services/WorkspaceNotificationRuleService.js +178 -3
- package/build/dist/Server/Services/WorkspaceNotificationRuleService.js.map +1 -1
- package/build/dist/Server/Utils/Database/PostgresErrorTranslator.js +80 -0
- package/build/dist/Server/Utils/Database/PostgresErrorTranslator.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/MonitorResource.js +67 -29
- package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js +31 -14
- package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js +117 -0
- package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +549 -7
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
- package/build/dist/Types/Date.js +106 -35
- package/build/dist/Types/Date.js.map +1 -1
- package/build/dist/Types/Workspace/NotificationRules/NotificationRuleCondition.js +17 -7
- package/build/dist/Types/Workspace/NotificationRules/NotificationRuleCondition.js.map +1 -1
- package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js +37 -11
- package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js.map +1 -1
- package/build/dist/UI/Components/CodeEditor/CodeEditor.js +2 -0
- package/build/dist/UI/Components/CodeEditor/CodeEditor.js.map +1 -1
- package/build/dist/UI/Components/CodeEditor/MonacoLoader.js +34 -0
- package/build/dist/UI/Components/CodeEditor/MonacoLoader.js.map +1 -0
- package/build/dist/UI/Components/Forms/Fields/FormField.js +11 -3
- package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
- package/build/dist/UI/Components/Forms/ModelForm.js +37 -17
- package/build/dist/UI/Components/Forms/ModelForm.js.map +1 -1
- package/build/dist/UI/Components/Input/Input.js +6 -1
- package/build/dist/UI/Components/Input/Input.js.map +1 -1
- package/build/dist/UI/Components/ModelDetail/CardModelDetail.js +22 -1
- package/build/dist/UI/Components/ModelDetail/CardModelDetail.js.map +1 -1
- package/build/dist/UI/Components/ModelDetail/ModelDetail.js +31 -1
- package/build/dist/UI/Components/ModelDetail/ModelDetail.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +234 -21
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/ExportFromColumns.js +30 -0
- package/build/dist/UI/Components/ModelTable/ExportFromColumns.js.map +1 -0
- package/build/dist/UI/Components/Navbar/NavBar.js +39 -1
- package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
- package/build/dist/UI/Components/Table/Table.js +31 -11
- package/build/dist/UI/Components/Table/Table.js.map +1 -1
- package/build/dist/UI/Components/TimePicker/TimePicker.js +4 -4
- package/build/dist/UI/Components/TimePicker/TimePicker.js.map +1 -1
- package/build/dist/UI/Utils/Project.js +161 -15
- package/build/dist/UI/Utils/Project.js.map +1 -1
- package/build/dist/UI/Utils/TableColumnsToCsv.js +67 -6
- package/build/dist/UI/Utils/TableColumnsToCsv.js.map +1 -1
- package/build/dist/UI/Utils/User.js +19 -0
- package/build/dist/UI/Utils/User.js.map +1 -1
- package/build/dist/Utils/Monitor/MonitorProbeSelectionUtil.js +52 -0
- package/build/dist/Utils/Monitor/MonitorProbeSelectionUtil.js.map +1 -0
- package/package.json +4 -3
- package/Server/Infrastructure/Postgres/SchemaMigrations/1773414578773-MigrationName.ts +0 -79
- package/Server/Infrastructure/Postgres/SchemaMigrations/1773500000000-MigrationName.ts +0 -41
- package/Server/Infrastructure/Postgres/SchemaMigrations/1776886248361-MigrationName.ts +0 -17
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1773414578773-MigrationName.js +0 -34
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1773414578773-MigrationName.js.map +0 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1773500000000-MigrationName.js +0 -22
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1773500000000-MigrationName.js.map +0 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1776886248361-MigrationName.js +0 -12
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1776886248361-MigrationName.js.map +0 -1
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import Column from "../../../UI/Components/ModelTable/Column";
|
|
2
|
+
import { getExportKeysFromColumn } from "../../../UI/Components/ModelTable/ExportFromColumns";
|
|
3
|
+
import FieldType from "../../../UI/Components/Types/FieldType";
|
|
4
|
+
import Alert from "../../../Models/DatabaseModels/Alert";
|
|
5
|
+
import ProxmoxCluster from "../../../Models/DatabaseModels/ProxmoxCluster";
|
|
6
|
+
import { describe, expect, test } from "@jest/globals";
|
|
7
|
+
|
|
8
|
+
describe("ModelTable ExportFromColumns", () => {
|
|
9
|
+
describe("getExportKeysFromColumn", () => {
|
|
10
|
+
test("it returns every field a column declares, not just the first", () => {
|
|
11
|
+
/*
|
|
12
|
+
* The alert "Affected Resources" cell renders five relations at once.
|
|
13
|
+
* All five are selected, but the Table column's `key` only holds the
|
|
14
|
+
* first, so the CSV export used to carry the hosts and drop the rest.
|
|
15
|
+
*/
|
|
16
|
+
const column: Column<Alert> = {
|
|
17
|
+
field: {
|
|
18
|
+
hosts: { name: true },
|
|
19
|
+
kubernetesClusters: { name: true },
|
|
20
|
+
dockerHosts: { name: true },
|
|
21
|
+
podmanHosts: { name: true },
|
|
22
|
+
services: { name: true },
|
|
23
|
+
},
|
|
24
|
+
title: "Affected Resources",
|
|
25
|
+
type: FieldType.EntityArray,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
expect(
|
|
29
|
+
getExportKeysFromColumn<Alert>({
|
|
30
|
+
column: column,
|
|
31
|
+
columnKey: "hosts",
|
|
32
|
+
}),
|
|
33
|
+
).toEqual([
|
|
34
|
+
"hosts",
|
|
35
|
+
"kubernetesClusters",
|
|
36
|
+
"dockerHosts",
|
|
37
|
+
"podmanHosts",
|
|
38
|
+
"services",
|
|
39
|
+
]);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("it returns a single key for an ordinary column", () => {
|
|
43
|
+
const column: Column<ProxmoxCluster> = {
|
|
44
|
+
field: { name: true },
|
|
45
|
+
title: "Name",
|
|
46
|
+
type: FieldType.Text,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
expect(
|
|
50
|
+
getExportKeysFromColumn<ProxmoxCluster>({
|
|
51
|
+
column: column,
|
|
52
|
+
columnKey: "name",
|
|
53
|
+
}),
|
|
54
|
+
).toEqual(["name"]);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("it keeps the selectedProperty path the cell renders", () => {
|
|
58
|
+
/*
|
|
59
|
+
* columnKey already carries the "relation.property" path, and that is
|
|
60
|
+
* the path the export has to read - not the bare relation.
|
|
61
|
+
*/
|
|
62
|
+
const column: Column<Alert> = {
|
|
63
|
+
field: { alertSeverity: { name: true } },
|
|
64
|
+
selectedProperty: "name",
|
|
65
|
+
title: "Severity",
|
|
66
|
+
type: FieldType.Text,
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
expect(
|
|
70
|
+
getExportKeysFromColumn<Alert>({
|
|
71
|
+
column: column,
|
|
72
|
+
columnKey: "alertSeverity.name",
|
|
73
|
+
}),
|
|
74
|
+
).toEqual(["alertSeverity.name"]);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("it drops secondary fields the user cannot read", () => {
|
|
78
|
+
/*
|
|
79
|
+
* Mirrors getSelectFromColumns: a field that was never selected because
|
|
80
|
+
* of permissions is not on the row, so exporting it would only ever
|
|
81
|
+
* produce an empty cell.
|
|
82
|
+
*/
|
|
83
|
+
const column: Column<ProxmoxCluster> = {
|
|
84
|
+
field: { nodeCount: true, onlineNodeCount: true },
|
|
85
|
+
title: "Nodes",
|
|
86
|
+
type: FieldType.Element,
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
expect(
|
|
90
|
+
getExportKeysFromColumn<ProxmoxCluster>({
|
|
91
|
+
column: column,
|
|
92
|
+
columnKey: "nodeCount",
|
|
93
|
+
hasPermissionToReadField: (field: string): boolean => {
|
|
94
|
+
return field !== "onlineNodeCount";
|
|
95
|
+
},
|
|
96
|
+
}),
|
|
97
|
+
).toEqual(["nodeCount"]);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("it keeps the primary field even when the permission check rejects it", () => {
|
|
101
|
+
/*
|
|
102
|
+
* The primary field is what the column sorts and renders by, and
|
|
103
|
+
* BaseModelTable has already decided the column is visible by this
|
|
104
|
+
* point. Only secondary fields are gated here.
|
|
105
|
+
*/
|
|
106
|
+
const column: Column<ProxmoxCluster> = {
|
|
107
|
+
field: { nodeCount: true, onlineNodeCount: true },
|
|
108
|
+
title: "Nodes",
|
|
109
|
+
type: FieldType.Element,
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
expect(
|
|
113
|
+
getExportKeysFromColumn<ProxmoxCluster>({
|
|
114
|
+
column: column,
|
|
115
|
+
columnKey: "nodeCount",
|
|
116
|
+
hasPermissionToReadField: (): boolean => {
|
|
117
|
+
return false;
|
|
118
|
+
},
|
|
119
|
+
}),
|
|
120
|
+
).toEqual(["nodeCount"]);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("it does not repeat the primary field when it is declared again", () => {
|
|
124
|
+
const column: Column<ProxmoxCluster> = {
|
|
125
|
+
field: { name: true, nodeCount: true },
|
|
126
|
+
title: "Name",
|
|
127
|
+
type: FieldType.Element,
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
expect(
|
|
131
|
+
getExportKeysFromColumn<ProxmoxCluster>({
|
|
132
|
+
column: column,
|
|
133
|
+
columnKey: "name",
|
|
134
|
+
}),
|
|
135
|
+
).toEqual(["name", "nodeCount"]);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test("it returns nothing for a column with no key", () => {
|
|
139
|
+
const column: Column<ProxmoxCluster> = {
|
|
140
|
+
field: {},
|
|
141
|
+
title: "Actions",
|
|
142
|
+
type: FieldType.Actions,
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
expect(
|
|
146
|
+
getExportKeysFromColumn<ProxmoxCluster>({
|
|
147
|
+
column: column,
|
|
148
|
+
columnKey: null,
|
|
149
|
+
}),
|
|
150
|
+
).toEqual([]);
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import getJestMockFunction, { MockFunction } from "../../../Tests/MockType";
|
|
2
|
+
import { afterEach, describe, expect, jest, test } from "@jest/globals";
|
|
3
|
+
|
|
4
|
+
const config: MockFunction = getJestMockFunction();
|
|
5
|
+
|
|
6
|
+
jest.mock("@monaco-editor/react", () => {
|
|
7
|
+
return {
|
|
8
|
+
loader: {
|
|
9
|
+
config,
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
describe("configureMonacoLoader", () => {
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
config.mockClear();
|
|
17
|
+
delete process.env["MONACO_ASSET_PATH"];
|
|
18
|
+
jest.resetModules();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
type LoadLoader = () => Promise<() => void>;
|
|
22
|
+
|
|
23
|
+
const loadLoader: LoadLoader = async (): Promise<() => void> => {
|
|
24
|
+
const { default: configureMonacoLoader } = await import(
|
|
25
|
+
"../../../UI/Components/CodeEditor/MonacoLoader"
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
return configureMonacoLoader;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
test("points Monaco at the path the build baked in", async () => {
|
|
32
|
+
process.env["MONACO_ASSET_PATH"] = "/dashboard/assets/monaco/vs";
|
|
33
|
+
|
|
34
|
+
(await loadLoader())();
|
|
35
|
+
|
|
36
|
+
/*
|
|
37
|
+
* Absolute, not the root-relative value the build baked in: Monaco resolves
|
|
38
|
+
* its language-service workers from this inside a blob: worker, which has no
|
|
39
|
+
* base to resolve a root-relative path against.
|
|
40
|
+
*/
|
|
41
|
+
expect(config).toHaveBeenCalledWith({
|
|
42
|
+
paths: {
|
|
43
|
+
vs: `${window.location.origin}/dashboard/assets/monaco/vs`,
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("gives Monaco an absolute path its blob workers can resolve", async () => {
|
|
49
|
+
process.env["MONACO_ASSET_PATH"] = "/dashboard/assets/monaco/vs";
|
|
50
|
+
|
|
51
|
+
(await loadLoader())();
|
|
52
|
+
|
|
53
|
+
const configuredPath: string = (
|
|
54
|
+
config.mock.calls[0] as unknown as Array<{ paths: { vs: string } }>
|
|
55
|
+
)[0]!.paths.vs;
|
|
56
|
+
|
|
57
|
+
expect(() => {
|
|
58
|
+
return new URL(configuredPath);
|
|
59
|
+
}).not.toThrow();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("keeps the bundled default when the build set no path", async () => {
|
|
63
|
+
(await loadLoader())();
|
|
64
|
+
|
|
65
|
+
expect(config).not.toHaveBeenCalled();
|
|
66
|
+
});
|
|
67
|
+
});
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { describe, expect, test } from "@jest/globals";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
|
|
5
|
+
/*
|
|
6
|
+
* The build copies monaco-editor's min/vs next to each frontend bundle and
|
|
7
|
+
* MonacoLoader points @monaco-editor/loader at it, so the version we install is
|
|
8
|
+
* the version that actually runs in the browser.
|
|
9
|
+
*
|
|
10
|
+
* The loader resolves init() with whatever the AMD module `vs/editor/editor.main`
|
|
11
|
+
* exports, and that shape is not stable across Monaco releases. 0.53 and 0.54
|
|
12
|
+
* nest the API under `exports.m`; against a loader that does not unwrap that,
|
|
13
|
+
* `monaco.editor` comes back undefined and every editor throws
|
|
14
|
+
* "Cannot read properties of undefined (reading 'getModel')" instead of
|
|
15
|
+
* mounting - with the assets all serving 200, so nothing else looks wrong.
|
|
16
|
+
*
|
|
17
|
+
* @monaco-editor/loader learned to unwrap it in 1.7.0. These tests fail if the
|
|
18
|
+
* two are ever moved into a combination that cannot work.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
// [from, to) - the Monaco releases whose AMD module nests the API under `exports.m`.
|
|
22
|
+
const NESTED_API_RANGE: { from: string; to: string } = {
|
|
23
|
+
from: "0.53.0",
|
|
24
|
+
to: "0.55.0",
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// The first @monaco-editor/loader that unwraps `exports.m`.
|
|
28
|
+
const LOADER_UNWRAPS_FROM: string = "1.7.0";
|
|
29
|
+
|
|
30
|
+
type ReadVersion = (packageName: string) => string;
|
|
31
|
+
|
|
32
|
+
const readVersion: ReadVersion = (packageName: string): string => {
|
|
33
|
+
return JSON.parse(
|
|
34
|
+
fs.readFileSync(require.resolve(`${packageName}/package.json`), "utf8"),
|
|
35
|
+
).version;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
type Compare = (a: string, b: string) => number;
|
|
39
|
+
|
|
40
|
+
const compare: Compare = (a: string, b: string): number => {
|
|
41
|
+
const left: Array<number> = a.split(".").map(Number);
|
|
42
|
+
const right: Array<number> = b.split(".").map(Number);
|
|
43
|
+
|
|
44
|
+
for (let i: number = 0; i < 3; i++) {
|
|
45
|
+
const difference: number = (left[i] || 0) - (right[i] || 0);
|
|
46
|
+
|
|
47
|
+
if (difference !== 0) {
|
|
48
|
+
return difference;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return 0;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
describe("Monaco runtime", () => {
|
|
56
|
+
test("does not pair a nested-API Monaco with a loader that cannot unwrap it", () => {
|
|
57
|
+
const monacoVersion: string = readVersion("monaco-editor");
|
|
58
|
+
const loaderVersion: string = readVersion("@monaco-editor/loader");
|
|
59
|
+
|
|
60
|
+
const nestsApi: boolean =
|
|
61
|
+
compare(monacoVersion, NESTED_API_RANGE.from) >= 0 &&
|
|
62
|
+
compare(monacoVersion, NESTED_API_RANGE.to) < 0;
|
|
63
|
+
|
|
64
|
+
if (!nestsApi) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
expect({
|
|
69
|
+
monacoVersion,
|
|
70
|
+
loaderVersion,
|
|
71
|
+
unwrapsNestedApi: compare(loaderVersion, LOADER_UNWRAPS_FROM) >= 0,
|
|
72
|
+
}).toEqual({
|
|
73
|
+
monacoVersion,
|
|
74
|
+
loaderVersion,
|
|
75
|
+
unwrapsNestedApi: true,
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("ships the files the loader asks for by path", () => {
|
|
80
|
+
const monacoRoot: string = path.dirname(
|
|
81
|
+
require.resolve("monaco-editor/package.json"),
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
/*
|
|
85
|
+
* The loader builds a script tag for `${paths.vs}/loader.js` and then
|
|
86
|
+
* require()s `vs/editor/editor.main`. Everything else Monaco pulls in is
|
|
87
|
+
* relative to those two, so a missing one is the difference between a
|
|
88
|
+
* working editor and a silent 404 on an air-gapped install.
|
|
89
|
+
*/
|
|
90
|
+
for (const asset of ["loader.js", "editor/editor.main.js"]) {
|
|
91
|
+
expect(fs.existsSync(path.join(monacoRoot, "min", "vs", asset))).toBe(
|
|
92
|
+
true,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("does not bring a second copy of anything Common already depends on", () => {
|
|
98
|
+
/*
|
|
99
|
+
* Monaco releases from 0.55 on depend on marked and dompurify, both of which
|
|
100
|
+
* Common already pins at the top level. npm nests Monaco's own copies
|
|
101
|
+
* because it pins them exactly, so the top-level pin does not reach them and
|
|
102
|
+
* the tree ends up carrying two versions - the older one being whatever
|
|
103
|
+
* Monaco vendored at release time.
|
|
104
|
+
*/
|
|
105
|
+
const monacoDependencies: Array<string> = Object.keys(
|
|
106
|
+
JSON.parse(
|
|
107
|
+
fs.readFileSync(require.resolve("monaco-editor/package.json"), "utf8"),
|
|
108
|
+
).dependencies || {},
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
const commonDependencies: Record<string, string> = JSON.parse(
|
|
112
|
+
fs.readFileSync(path.join(__dirname, "../../../package.json"), "utf8"),
|
|
113
|
+
).dependencies;
|
|
114
|
+
|
|
115
|
+
expect(
|
|
116
|
+
monacoDependencies.filter((name: string) => {
|
|
117
|
+
return Boolean(commonDependencies[name]);
|
|
118
|
+
}),
|
|
119
|
+
).toEqual([]);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, test } from "@jest/globals";
|
|
2
2
|
import { fireEvent, render, RenderResult } from "@testing-library/react";
|
|
3
3
|
import * as React from "react";
|
|
4
|
+
import { ReactElement } from "react";
|
|
4
5
|
import getJestMockFunction, { MockFunction } from "../../MockType";
|
|
5
6
|
import Table, { BulkActionProps } from "../../../UI/Components/Table/Table";
|
|
7
|
+
import TableColumnsToCsv from "../../../UI/Utils/TableColumnsToCsv";
|
|
6
8
|
import Columns from "../../../UI/Components/Table/Types/Columns";
|
|
7
9
|
import FieldType from "../../../UI/Components/Types/FieldType";
|
|
8
10
|
import SortOrder from "../../../Types/BaseDatabase/SortOrder";
|
|
@@ -27,21 +29,26 @@ jest.mock("react-i18next", () => {
|
|
|
27
29
|
interface Row {
|
|
28
30
|
_id?: string | undefined;
|
|
29
31
|
name?: string | undefined;
|
|
32
|
+
description?: string | undefined;
|
|
33
|
+
hosts?: Array<{ name: string }> | undefined;
|
|
34
|
+
services?: Array<{ name: string }> | undefined;
|
|
30
35
|
}
|
|
31
36
|
|
|
32
37
|
const columns: Columns<Row> = [
|
|
33
38
|
{ title: "Name", type: FieldType.Text, key: "name" },
|
|
39
|
+
{ title: "Description", type: FieldType.Text, key: "description" },
|
|
34
40
|
];
|
|
35
41
|
|
|
36
42
|
const data: Array<Row> = [
|
|
37
|
-
{ _id: "1", name: "Alpha" },
|
|
38
|
-
{ _id: "2", name: "Beta" },
|
|
43
|
+
{ _id: "1", name: "Alpha", description: "First" },
|
|
44
|
+
{ _id: "2", name: "Beta", description: "Second" },
|
|
39
45
|
];
|
|
40
46
|
|
|
41
47
|
interface RenderTableOptions {
|
|
42
48
|
bulkActions?: BulkActionProps<Row> | undefined;
|
|
43
49
|
bulkSelectedItems?: Array<Row> | undefined;
|
|
44
50
|
disableBulkCsvExport?: boolean | undefined;
|
|
51
|
+
columns?: Columns<Row> | undefined;
|
|
45
52
|
}
|
|
46
53
|
|
|
47
54
|
type RenderTableFunction = (options: RenderTableOptions) => RenderResult;
|
|
@@ -53,7 +60,7 @@ const renderTable: RenderTableFunction = (
|
|
|
53
60
|
<Table<Row>
|
|
54
61
|
id="test-table"
|
|
55
62
|
data={data}
|
|
56
|
-
columns={columns}
|
|
63
|
+
columns={options.columns || columns}
|
|
57
64
|
currentPageNumber={1}
|
|
58
65
|
totalItemsCount={data.length}
|
|
59
66
|
itemsOnPage={10}
|
|
@@ -89,6 +96,8 @@ describe("Table bulk CSV export", () => {
|
|
|
89
96
|
let createObjectURLMock: MockFunction;
|
|
90
97
|
let revokeObjectURLMock: MockFunction;
|
|
91
98
|
let clickSpy: jest.SpyInstance;
|
|
99
|
+
let downloadedCsvFiles: Array<{ csv: string; filename: string }> = [];
|
|
100
|
+
let downloadCsvSpy: jest.SpyInstance;
|
|
92
101
|
|
|
93
102
|
beforeEach(() => {
|
|
94
103
|
createObjectURLMock = getJestMockFunction();
|
|
@@ -103,10 +112,28 @@ describe("Table bulk CSV export", () => {
|
|
|
103
112
|
clickSpy = jest
|
|
104
113
|
.spyOn(HTMLAnchorElement.prototype, "click")
|
|
105
114
|
.mockImplementation(() => {});
|
|
115
|
+
|
|
116
|
+
downloadedCsvFiles = [];
|
|
106
117
|
});
|
|
107
118
|
|
|
119
|
+
type SpyOnDownloadFunction = () => void;
|
|
120
|
+
|
|
121
|
+
/*
|
|
122
|
+
* Asserting on the CSV text the exporter hands to the browser, rather than
|
|
123
|
+
* on the opaque Blob, is what lets these tests see an export whose rows are
|
|
124
|
+
* all blank - the shape the bug produced.
|
|
125
|
+
*/
|
|
126
|
+
const spyOnDownload: SpyOnDownloadFunction = (): void => {
|
|
127
|
+
downloadCsvSpy = jest
|
|
128
|
+
.spyOn(TableColumnsToCsv, "downloadCsv")
|
|
129
|
+
.mockImplementation((...args: Array<unknown>): void => {
|
|
130
|
+
downloadedCsvFiles.push(args[0] as { csv: string; filename: string });
|
|
131
|
+
});
|
|
132
|
+
};
|
|
133
|
+
|
|
108
134
|
afterEach(() => {
|
|
109
135
|
clickSpy.mockRestore();
|
|
136
|
+
downloadCsvSpy?.mockRestore();
|
|
110
137
|
});
|
|
111
138
|
|
|
112
139
|
test("shows an Export CSV action when the table has bulk actions and rows are selected", () => {
|
|
@@ -165,4 +192,186 @@ describe("Table bulk CSV export", () => {
|
|
|
165
192
|
expect(queryByText("Bulk Actions")).toBeNull();
|
|
166
193
|
expect(queryByText("Export CSV")).toBeNull();
|
|
167
194
|
});
|
|
195
|
+
|
|
196
|
+
test("the exported CSV carries the values of every selected row", () => {
|
|
197
|
+
spyOnDownload();
|
|
198
|
+
|
|
199
|
+
const { getByText } = renderTable({
|
|
200
|
+
bulkActions: customBulkActions,
|
|
201
|
+
bulkSelectedItems: data,
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
fireEvent.click(getByText("Bulk Actions"));
|
|
205
|
+
fireEvent.click(getByText("Export CSV"));
|
|
206
|
+
|
|
207
|
+
expect(downloadedCsvFiles.length).toBe(1);
|
|
208
|
+
expect(downloadedCsvFiles[0]!.csv.split("\r\n")).toEqual([
|
|
209
|
+
"Name,Description",
|
|
210
|
+
"Alpha,First",
|
|
211
|
+
"Beta,Second",
|
|
212
|
+
]);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test("it exports the selection, not the rows on screen", () => {
|
|
216
|
+
spyOnDownload();
|
|
217
|
+
|
|
218
|
+
/*
|
|
219
|
+
* The selection outgrows the page as soon as the user selects across
|
|
220
|
+
* pages or picks "Select All", so the export has to follow
|
|
221
|
+
* bulkSelectedItems rather than the rendered data.
|
|
222
|
+
*/
|
|
223
|
+
const selection: Array<Row> = [];
|
|
224
|
+
|
|
225
|
+
for (let i: number = 0; i < 30; i++) {
|
|
226
|
+
selection.push({
|
|
227
|
+
_id: `${i}`,
|
|
228
|
+
name: `Row ${i}`,
|
|
229
|
+
description: `Desc ${i}`,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const { getByText } = renderTable({
|
|
234
|
+
bulkActions: customBulkActions,
|
|
235
|
+
bulkSelectedItems: selection,
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
fireEvent.click(getByText("Bulk Actions"));
|
|
239
|
+
fireEvent.click(getByText("Export CSV"));
|
|
240
|
+
|
|
241
|
+
const lines: Array<string> = downloadedCsvFiles[0]!.csv.split("\r\n");
|
|
242
|
+
|
|
243
|
+
// Header plus every selected row, even though the table renders two.
|
|
244
|
+
expect(lines.length).toBe(31);
|
|
245
|
+
expect(lines[30]).toBe("Row 29,Desc 29");
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
test("rows stripped down to an id export as blank cells", () => {
|
|
249
|
+
spyOnDownload();
|
|
250
|
+
|
|
251
|
+
/*
|
|
252
|
+
* Characterisation test. TableColumnsToCsv writes what it is handed, so
|
|
253
|
+
* a selection of id-only models produces a header and nothing else -
|
|
254
|
+
* which is exactly what the "Select All then Export CSV" bug looked
|
|
255
|
+
* like. The fix belongs where the selection is fetched
|
|
256
|
+
* (BaseModelTable.fetchAllBulkItems), not here.
|
|
257
|
+
*/
|
|
258
|
+
const { getByText } = renderTable({
|
|
259
|
+
bulkActions: customBulkActions,
|
|
260
|
+
bulkSelectedItems: [{ _id: "1" }, { _id: "2" }],
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
fireEvent.click(getByText("Bulk Actions"));
|
|
264
|
+
fireEvent.click(getByText("Export CSV"));
|
|
265
|
+
|
|
266
|
+
expect(downloadedCsvFiles[0]!.csv.split("\r\n")).toEqual([
|
|
267
|
+
"Name,Description",
|
|
268
|
+
",",
|
|
269
|
+
",",
|
|
270
|
+
]);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
test("a placeholder id column is left out instead of exporting a raw uuid", () => {
|
|
274
|
+
spyOnDownload();
|
|
275
|
+
|
|
276
|
+
/*
|
|
277
|
+
* The "Owners" column on alerts / incidents / monitors renders entirely
|
|
278
|
+
* through getElement and declares `field: { _id: true }` only so the row
|
|
279
|
+
* gets fetched. It used to put the row's UUID in the file under the
|
|
280
|
+
* header "Owners".
|
|
281
|
+
*/
|
|
282
|
+
const { getByText } = renderTable({
|
|
283
|
+
bulkActions: customBulkActions,
|
|
284
|
+
bulkSelectedItems: [data[0]!],
|
|
285
|
+
columns: [
|
|
286
|
+
{ title: "Name", type: FieldType.Text, key: "name" },
|
|
287
|
+
{
|
|
288
|
+
title: "Owners",
|
|
289
|
+
type: FieldType.Element,
|
|
290
|
+
key: "_id",
|
|
291
|
+
exportKeys: ["_id"],
|
|
292
|
+
getElement: (): ReactElement => {
|
|
293
|
+
return <span>Owners</span>;
|
|
294
|
+
},
|
|
295
|
+
},
|
|
296
|
+
],
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
fireEvent.click(getByText("Bulk Actions"));
|
|
300
|
+
fireEvent.click(getByText("Export CSV"));
|
|
301
|
+
|
|
302
|
+
expect(downloadedCsvFiles[0]!.csv.split("\r\n")).toEqual(["Name", "Alpha"]);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
test("a placeholder id column that supplies an export value is exported", () => {
|
|
306
|
+
spyOnDownload();
|
|
307
|
+
|
|
308
|
+
const { getByText } = renderTable({
|
|
309
|
+
bulkActions: customBulkActions,
|
|
310
|
+
bulkSelectedItems: [data[0]!],
|
|
311
|
+
columns: [
|
|
312
|
+
{ title: "Name", type: FieldType.Text, key: "name" },
|
|
313
|
+
{
|
|
314
|
+
title: "Owners",
|
|
315
|
+
type: FieldType.Element,
|
|
316
|
+
key: "_id",
|
|
317
|
+
exportKeys: ["_id"],
|
|
318
|
+
getElement: (): ReactElement => {
|
|
319
|
+
return <span>Owners</span>;
|
|
320
|
+
},
|
|
321
|
+
getExportValue: (item: Row): string => {
|
|
322
|
+
return `owner-of-${item.name}`;
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
|
+
],
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
fireEvent.click(getByText("Bulk Actions"));
|
|
329
|
+
fireEvent.click(getByText("Export CSV"));
|
|
330
|
+
|
|
331
|
+
expect(downloadedCsvFiles[0]!.csv.split("\r\n")).toEqual([
|
|
332
|
+
"Name,Owners",
|
|
333
|
+
"Alpha,owner-of-Alpha",
|
|
334
|
+
]);
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
test("a column that renders several relations exports all of them", () => {
|
|
338
|
+
spyOnDownload();
|
|
339
|
+
|
|
340
|
+
/*
|
|
341
|
+
* The alert "Affected Resources" cell spans hosts / kubernetesClusters /
|
|
342
|
+
* dockerHosts / podmanHosts / services. All of them are fetched, but only
|
|
343
|
+
* the first reached the file.
|
|
344
|
+
*/
|
|
345
|
+
const { getByText } = renderTable({
|
|
346
|
+
bulkActions: customBulkActions,
|
|
347
|
+
bulkSelectedItems: [
|
|
348
|
+
{
|
|
349
|
+
_id: "1",
|
|
350
|
+
name: "Alpha",
|
|
351
|
+
hosts: [{ name: "web-01" }],
|
|
352
|
+
services: [{ name: "checkout" }],
|
|
353
|
+
},
|
|
354
|
+
],
|
|
355
|
+
columns: [
|
|
356
|
+
{ title: "Name", type: FieldType.Text, key: "name" },
|
|
357
|
+
{
|
|
358
|
+
title: "Affected Resources",
|
|
359
|
+
type: FieldType.EntityArray,
|
|
360
|
+
key: "hosts",
|
|
361
|
+
exportKeys: ["hosts", "services"],
|
|
362
|
+
getElement: (): ReactElement => {
|
|
363
|
+
return <span>Resources</span>;
|
|
364
|
+
},
|
|
365
|
+
},
|
|
366
|
+
],
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
fireEvent.click(getByText("Bulk Actions"));
|
|
370
|
+
fireEvent.click(getByText("Export CSV"));
|
|
371
|
+
|
|
372
|
+
expect(downloadedCsvFiles[0]!.csv.split("\r\n")).toEqual([
|
|
373
|
+
"Name,Affected Resources",
|
|
374
|
+
"Alpha,web-01; checkout",
|
|
375
|
+
]);
|
|
376
|
+
});
|
|
168
377
|
});
|
|
@@ -89,6 +89,17 @@ jest.mock("../../../../Types/Date", () => {
|
|
|
89
89
|
return base;
|
|
90
90
|
},
|
|
91
91
|
),
|
|
92
|
+
/*
|
|
93
|
+
* The picker reads the wall-clock through OneUptimeDate so it resolves in
|
|
94
|
+
* the user's configured timezone; the stubbed dates above already carry
|
|
95
|
+
* the hour/minute the test intends.
|
|
96
|
+
*/
|
|
97
|
+
getLocalHours: jest.fn((d: HourMinuteMock) => {
|
|
98
|
+
return d.getHours();
|
|
99
|
+
}),
|
|
100
|
+
getLocalMinutes: jest.fn((d: HourMinuteMock) => {
|
|
101
|
+
return d.getMinutes();
|
|
102
|
+
}),
|
|
92
103
|
getCurrentTimezoneString: jest.fn(() => {
|
|
93
104
|
return "UTC";
|
|
94
105
|
}),
|