@oneuptime/common 14.0.2 → 14.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Models/DatabaseModels/AlertFeed.ts +2 -0
- package/Models/DatabaseModels/IncidentAlert.ts +540 -0
- package/Models/DatabaseModels/IncidentFeed.ts +2 -0
- package/Models/DatabaseModels/Index.ts +2 -0
- package/Models/DatabaseModels/Project.ts +60 -0
- package/Models/DatabaseModels/RumApplication.ts +52 -1
- package/Server/Infrastructure/Postgres/SchemaMigrations/1794800000000-AddSessionReplaySameOriginTracePropagation.ts +28 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1794900000000-AddIncidentAlert.ts +89 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
- package/Server/Services/IncidentAlertService.ts +1811 -0
- package/Server/Services/IncidentService.ts +210 -10
- package/Server/Services/IncidentStateTimelineService.ts +24 -0
- package/Server/Services/Index.ts +2 -0
- package/Server/Utils/SessionReplay/SessionReplayGateCache.ts +24 -2
- package/Tests/App/Dashboard/BulkIncidentLinkActions.test.tsx +869 -0
- package/Tests/App/Dashboard/CephMetricTooltips.test.tsx +1777 -0
- package/Tests/App/Dashboard/CloudFleetSummary.test.ts +8 -0
- package/Tests/App/Dashboard/CloudMetricTooltips.test.tsx +887 -0
- package/Tests/App/Dashboard/ContainerHostMetricTooltips.test.tsx +1309 -0
- package/Tests/App/Dashboard/DiscoveryScanLivePage.test.tsx +39 -0
- package/Tests/App/Dashboard/DockerSwarmClustersListTooltips.test.tsx +414 -0
- package/Tests/App/Dashboard/DockerSwarmMetricTooltips.test.ts +796 -0
- package/Tests/App/Dashboard/DockerSwarmOverviewTooltips.test.tsx +747 -0
- package/Tests/App/Dashboard/ExceptionMonitorEnvironmentFilter.test.tsx +38 -18
- package/Tests/App/Dashboard/HostDetailViewTooltips.test.tsx +725 -0
- package/Tests/App/Dashboard/HostListColumnTooltips.test.tsx +472 -0
- package/Tests/App/Dashboard/HostMetricTooltips.test.ts +709 -0
- package/Tests/App/Dashboard/HostOverviewTooltips.test.tsx +698 -0
- package/Tests/App/Dashboard/HostTooltipHarness.ts +124 -0
- package/Tests/App/Dashboard/HostsListTooltips.test.tsx +395 -0
- package/Tests/App/Dashboard/IncidentAlertLinkDialog.test.tsx +691 -0
- package/Tests/App/Dashboard/IncidentAlertLinkHelpers.test.ts +458 -0
- package/Tests/App/Dashboard/IncidentAlertLinkPages.test.tsx +828 -0
- package/Tests/App/Dashboard/IncidentAlertLinkSideMenus.test.tsx +231 -0
- package/Tests/App/Dashboard/IncidentCreateFromAlerts.test.tsx +689 -0
- package/Tests/App/Dashboard/IoTMetricTooltips.test.tsx +1123 -0
- package/Tests/App/Dashboard/KubernetesClusterMetricTooltips.test.ts +837 -0
- package/Tests/App/Dashboard/KubernetesClusterOverviewTooltips.test.tsx +699 -0
- package/Tests/App/Dashboard/KubernetesContainersTabTooltips.test.tsx +320 -0
- package/Tests/App/Dashboard/KubernetesCostTooltips.test.tsx +502 -0
- package/Tests/App/Dashboard/KubernetesInsightsTooltips.test.tsx +222 -0
- package/Tests/App/Dashboard/KubernetesResourceMetricTooltips.test.ts +561 -0
- package/Tests/App/Dashboard/KubernetesResourcePagesTooltips.test.tsx +1168 -0
- package/Tests/App/Dashboard/LinkIncidentAlertModal.test.tsx +154 -0
- package/Tests/App/Dashboard/MetricDescriptionRules.ts +170 -0
- package/Tests/App/Dashboard/MetricDescriptionsCatalog.test.ts +206 -0
- package/Tests/App/Dashboard/MetricDescriptionsConsistency.test.ts +275 -0
- package/Tests/App/Dashboard/NetworkDeviceTooltips.test.tsx +852 -0
- package/Tests/App/Dashboard/NetworkDiagnosticsTooltips.test.tsx +556 -0
- package/Tests/App/Dashboard/NetworkMetricTooltips.test.ts +1348 -0
- package/Tests/App/Dashboard/NetworkSiteTooltips.test.tsx +679 -0
- package/Tests/App/Dashboard/ProxmoxMetricTooltips.test.ts +592 -0
- package/Tests/App/Dashboard/ProxmoxOverviewTooltips.test.tsx +1002 -0
- package/Tests/App/Dashboard/ProxmoxResourcePageTooltips.test.tsx +923 -0
- package/Tests/App/Dashboard/ResourceOverviewTileTooltips.test.tsx +1066 -0
- package/Tests/App/Dashboard/RumOverviewPage.test.tsx +1195 -0
- package/Tests/App/Dashboard/ServerlessMetricTooltips.test.tsx +474 -0
- package/Tests/App/Dashboard/ServiceMetricTooltips.test.tsx +772 -0
- package/Tests/App/Dashboard/SessionReplayAuditTable.test.tsx +56 -1
- package/Tests/App/Dashboard/TelemetryMetricsSignals.test.ts +1207 -0
- package/Tests/App/Dashboard/TelemetryResourceTabTooltips.test.tsx +437 -0
- package/Tests/App/Dashboard/VMwareMetricTooltips.test.ts +801 -0
- package/Tests/App/Dashboard/VMwareOverviewTooltips.test.tsx +601 -0
- package/Tests/App/Dashboard/VMwareResourcePagesTooltips.test.tsx +646 -0
- package/Tests/App/Dashboard/VMwareTooltipHarness.ts +145 -0
- package/Tests/Models/DatabaseModels/IncidentAlertModel.test.ts +554 -0
- package/Tests/Models/DatabaseModels/SessionReplayModels.test.ts +117 -0
- package/Tests/Server/Infrastructure/Postgres/AddIncidentAlertMigration.test.ts +341 -0
- package/Tests/Server/Infrastructure/Postgres/AddSessionReplaySameOriginTracePropagationMigration.test.ts +247 -0
- package/Tests/Server/Services/IncidentAlertPostgres.test.ts +903 -0
- package/Tests/Server/Services/IncidentAlertRegistration.test.ts +96 -0
- package/Tests/Server/Services/IncidentAlertService.test.ts +2225 -0
- package/Tests/Server/Services/IncidentAlertStateCascade.test.ts +970 -0
- package/Tests/Server/Services/IncidentCreateFromAlerts.test.ts +1284 -0
- package/Tests/Server/Utils/SessionReplay/SessionReplayGateCachePolicy.test.ts +103 -0
- package/Tests/Types/Rum/WebVitals.test.ts +173 -0
- package/Tests/UI/Components/FieldLabelInfoCardTooltip.test.tsx +413 -0
- package/Tests/UI/Components/InfoTooltip.test.tsx +242 -0
- package/Tests/UI/Components/ModelTable/BaseModelTableBulkDelete.test.tsx +207 -3
- package/Tests/UI/Components/ModelTable/BaseModelTableBulkDeleteVerb.test.tsx +367 -0
- package/Tests/UI/Components/ModelTable/BaseModelTableCreateButtonTitle.test.tsx +275 -0
- package/Tests/UI/Components/TableHeaderTooltip.test.tsx +662 -0
- package/Tests/UI/Rum/PrivacySummaryCard.test.tsx +166 -9
- package/Tests/UI/Rum/RecordingHealthTooltips.test.tsx +447 -0
- package/Tests/UI/Rum/ReplayCorrelationPanel.test.tsx +164 -0
- package/Tests/UI/Rum/ReplayFrameCapture.test.ts +6750 -0
- package/Tests/UI/Rum/ReplayRail.test.tsx +723 -12
- package/Tests/UI/Rum/ReplayRailDetail.test.tsx +86 -0
- package/Tests/UI/Rum/ReplayScreenshot.test.ts +2124 -0
- package/Tests/UI/Rum/ReplayScreenshotActions.test.tsx +3288 -0
- package/Tests/UI/Rum/ReplayStage.test.tsx +62 -0
- package/Tests/UI/Rum/ReplayStageOverlays.test.tsx +983 -2
- package/Tests/UI/Rum/SessionReplaySettingsPolicyLoading.test.tsx +149 -0
- package/Tests/UI/Rum/SessionReplaySetupGuide.test.tsx +107 -6
- package/Tests/UI/Rum/SessionReplayUsersTableTooltips.test.tsx +330 -0
- package/Tests/UI/Rum/UserFlowTooltips.test.tsx +886 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +10 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +2 -0
- package/Tests/UI/Utils/PermissionGate.test.ts +76 -0
- package/Tests/Utils/Incident/IncidentFromAlerts.test.ts +696 -0
- package/Tests/Utils/Rum/SessionTraceState.test.ts +239 -0
- package/Types/Incident/IncidentAlertLink.ts +38 -0
- package/Types/Permission.ts +48 -0
- package/Types/Rum/SessionReplay.ts +25 -6
- package/Types/Rum/WebVitals.ts +52 -0
- package/UI/Components/Detail/FieldLabel.tsx +8 -0
- package/UI/Components/InfoCard/InfoCard.tsx +47 -1
- package/UI/Components/ModelTable/BaseModelTable.tsx +95 -17
- package/UI/Components/ModelTable/Column.ts +5 -0
- package/UI/Components/Table/TableHeader.tsx +47 -2
- package/UI/Components/Table/Types/Column.ts +7 -0
- package/UI/Components/Tooltip/InfoTooltip.tsx +78 -0
- package/UI/Utils/PermissionGate.ts +11 -2
- package/Utils/Incident/IncidentFromAlerts.ts +516 -0
- package/Utils/Rum/SessionTraceState.ts +212 -0
- package/build/dist/Models/DatabaseModels/AlertFeed.js +2 -0
- package/build/dist/Models/DatabaseModels/AlertFeed.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentAlert.js +553 -0
- package/build/dist/Models/DatabaseModels/IncidentAlert.js.map +1 -0
- package/build/dist/Models/DatabaseModels/IncidentFeed.js +2 -0
- package/build/dist/Models/DatabaseModels/IncidentFeed.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +2 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Project.js +62 -0
- package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
- package/build/dist/Models/DatabaseModels/RumApplication.js +53 -1
- package/build/dist/Models/DatabaseModels/RumApplication.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794800000000-AddSessionReplaySameOriginTracePropagation.js +20 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794800000000-AddSessionReplaySameOriginTracePropagation.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794900000000-AddIncidentAlert.js +42 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794900000000-AddIncidentAlert.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/IncidentAlertService.js +1370 -0
- package/build/dist/Server/Services/IncidentAlertService.js.map +1 -0
- package/build/dist/Server/Services/IncidentService.js +164 -12
- package/build/dist/Server/Services/IncidentService.js.map +1 -1
- package/build/dist/Server/Services/IncidentStateTimelineService.js +21 -0
- package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +2 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCache.js +8 -0
- package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCache.js.map +1 -1
- package/build/dist/Types/Incident/IncidentAlertLink.js +34 -0
- package/build/dist/Types/Incident/IncidentAlertLink.js.map +1 -0
- package/build/dist/Types/Permission.js +42 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/Rum/WebVitals.js +25 -0
- package/build/dist/Types/Rum/WebVitals.js.map +1 -1
- package/build/dist/UI/Components/Detail/FieldLabel.js +2 -0
- package/build/dist/UI/Components/Detail/FieldLabel.js.map +1 -1
- package/build/dist/UI/Components/InfoCard/InfoCard.js +22 -1
- package/build/dist/UI/Components/InfoCard/InfoCard.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +66 -14
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/UI/Components/Table/TableHeader.js +18 -2
- package/build/dist/UI/Components/Table/TableHeader.js.map +1 -1
- package/build/dist/UI/Components/Tooltip/InfoTooltip.js +28 -0
- package/build/dist/UI/Components/Tooltip/InfoTooltip.js.map +1 -0
- package/build/dist/UI/Utils/PermissionGate.js +4 -2
- package/build/dist/UI/Utils/PermissionGate.js.map +1 -1
- package/build/dist/Utils/Incident/IncidentFromAlerts.js +306 -0
- package/build/dist/Utils/Incident/IncidentFromAlerts.js.map +1 -0
- package/build/dist/Utils/Rum/SessionTraceState.js +154 -0
- package/build/dist/Utils/Rum/SessionTraceState.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,691 @@
|
|
|
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 { act, cleanup, render, screen, waitFor } from "@testing-library/react";
|
|
11
|
+
import * as React from "react";
|
|
12
|
+
import { MemoryRouter } from "react-router-dom";
|
|
13
|
+
import getJestMockFunction, { MockFunction } from "../../MockType";
|
|
14
|
+
|
|
15
|
+
/*
|
|
16
|
+
* The link dialog on an incident's Linked Alerts page and on an alert's
|
|
17
|
+
* Linked Incidents page. It replaced the table's built-in create form, whose
|
|
18
|
+
* picker listed bare titles - three "Checkout API is offline" alerts could
|
|
19
|
+
* not be told apart. What is pinned:
|
|
20
|
+
*
|
|
21
|
+
* - nothing is read until the dialog opens, and then the most recent
|
|
22
|
+
* records are listed newest first, labelled with their number, while the
|
|
23
|
+
* dropdown still searches every record by title on the server;
|
|
24
|
+
* - submitting creates one IncidentAlert between the page's record and the
|
|
25
|
+
* chosen one, closes the dialog and refreshes the table;
|
|
26
|
+
* - a refusal keeps the dialog open with the reason in it (a duplicate in
|
|
27
|
+
* the same plain words however the server caught it), and keeps the
|
|
28
|
+
* choice so the user can try again.
|
|
29
|
+
*
|
|
30
|
+
* ModelTable is stubbed to capture its props (the card buttons, the refresh
|
|
31
|
+
* toggle) and BasicFormModal to render what the user would read.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
jest.mock("react-i18next", () => {
|
|
35
|
+
return {
|
|
36
|
+
useTranslation: () => {
|
|
37
|
+
return {
|
|
38
|
+
t: (key: string, opts?: { defaultValue?: string }): string => {
|
|
39
|
+
return opts?.defaultValue ?? key;
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
type CapturedTableProps = Record<string, any>;
|
|
47
|
+
|
|
48
|
+
let capturedTables: Array<CapturedTableProps> = [];
|
|
49
|
+
|
|
50
|
+
jest.mock("../../../UI/Components/ModelTable/ModelTable", () => {
|
|
51
|
+
return {
|
|
52
|
+
__esModule: true,
|
|
53
|
+
default: (props: CapturedTableProps): React.ReactElement => {
|
|
54
|
+
capturedTables.push(props);
|
|
55
|
+
return <div data-testid="model-table" />;
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
interface CapturedModalProps {
|
|
61
|
+
title: string;
|
|
62
|
+
description?: string;
|
|
63
|
+
isLoading?: boolean;
|
|
64
|
+
error?: string;
|
|
65
|
+
submitButtonText?: string;
|
|
66
|
+
onClose: () => void;
|
|
67
|
+
onSubmit: (data: Record<string, unknown>) => Promise<void> | void;
|
|
68
|
+
formProps: {
|
|
69
|
+
error?: string;
|
|
70
|
+
initialValues?: Record<string, unknown>;
|
|
71
|
+
fields: Array<Record<string, any>>;
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
let capturedModal: CapturedModalProps | null = null;
|
|
76
|
+
|
|
77
|
+
jest.mock("../../../UI/Components/FormModal/BasicFormModal", () => {
|
|
78
|
+
return {
|
|
79
|
+
__esModule: true,
|
|
80
|
+
default: (props: CapturedModalProps): React.ReactElement => {
|
|
81
|
+
capturedModal = props;
|
|
82
|
+
|
|
83
|
+
const options: Array<{ label: string; value: string }> =
|
|
84
|
+
props.formProps.fields[0]!["dropdownOptions"] || [];
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<div data-testid="link-dialog">
|
|
88
|
+
<h2>{props.title}</h2>
|
|
89
|
+
<p>{props.description}</p>
|
|
90
|
+
{props.isLoading && <span data-testid="link-dialog-loading" />}
|
|
91
|
+
{props.error && (
|
|
92
|
+
<p data-testid="link-dialog-modal-error">{props.error}</p>
|
|
93
|
+
)}
|
|
94
|
+
{props.formProps.error && (
|
|
95
|
+
<p data-testid="link-dialog-error">{props.formProps.error}</p>
|
|
96
|
+
)}
|
|
97
|
+
<ul>
|
|
98
|
+
{options.map((option: { label: string; value: string }) => {
|
|
99
|
+
return <li key={option.value}>{option.label}</li>;
|
|
100
|
+
})}
|
|
101
|
+
</ul>
|
|
102
|
+
</div>
|
|
103
|
+
);
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const getListMock: MockFunction = getJestMockFunction();
|
|
109
|
+
const createMock: MockFunction = getJestMockFunction();
|
|
110
|
+
|
|
111
|
+
jest.mock("../../../UI/Utils/ModelAPI/ModelAPI", () => {
|
|
112
|
+
return {
|
|
113
|
+
__esModule: true,
|
|
114
|
+
default: {
|
|
115
|
+
getList: (...args: Array<any>) => {
|
|
116
|
+
return getListMock(...args);
|
|
117
|
+
},
|
|
118
|
+
create: (...args: Array<any>) => {
|
|
119
|
+
return createMock(...args);
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
import IncidentViewAlerts from "../../../../App/FeatureSet/Dashboard/src/Pages/Incidents/View/Alerts";
|
|
126
|
+
import AlertViewIncidents from "../../../../App/FeatureSet/Dashboard/src/Pages/Alerts/View/Incidents";
|
|
127
|
+
import PageComponentProps from "../../../../App/FeatureSet/Dashboard/src/Pages/PageComponentProps";
|
|
128
|
+
import { LINK_OPTIONS_LIMIT } from "../../../../App/FeatureSet/Dashboard/src/Components/IncidentAlert/IncidentAlertLink";
|
|
129
|
+
import Alert from "../../../Models/DatabaseModels/Alert";
|
|
130
|
+
import AlertState from "../../../Models/DatabaseModels/AlertState";
|
|
131
|
+
import Incident from "../../../Models/DatabaseModels/Incident";
|
|
132
|
+
import IncidentAlert from "../../../Models/DatabaseModels/IncidentAlert";
|
|
133
|
+
import IncidentState from "../../../Models/DatabaseModels/IncidentState";
|
|
134
|
+
import Project from "../../../Models/DatabaseModels/Project";
|
|
135
|
+
import Route from "../../../Types/API/Route";
|
|
136
|
+
import SortOrder from "../../../Types/BaseDatabase/SortOrder";
|
|
137
|
+
import { INCIDENT_ALERT_ALREADY_LINKED_MESSAGE } from "../../../Types/Incident/IncidentAlertLink";
|
|
138
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
139
|
+
import Permission from "../../../Types/Permission";
|
|
140
|
+
import { CardButtonSchema } from "../../../UI/Components/Card/Card";
|
|
141
|
+
import Navigation from "../../../UI/Utils/Navigation";
|
|
142
|
+
import PermissionUtil from "../../../UI/Utils/Permission";
|
|
143
|
+
import PermissionGate from "../../../UI/Utils/PermissionGate";
|
|
144
|
+
import ProjectUtil from "../../../UI/Utils/Project";
|
|
145
|
+
import UserUtil from "../../../UI/Utils/User";
|
|
146
|
+
|
|
147
|
+
const PROJECT_ID: string = "8f2a1b3c-4d5e-4f60-9a7b-1c2d3e4f5a6b";
|
|
148
|
+
const MODEL_ID: string = "0193c0de-3333-4aaa-8bbb-000000000003";
|
|
149
|
+
|
|
150
|
+
const ALERT_63_ID: string = "22222222-2222-4222-8222-000000000063";
|
|
151
|
+
const ALERT_57_ID: string = "22222222-2222-4222-8222-000000000057";
|
|
152
|
+
const INCIDENT_12_ID: string = "44444444-4444-4444-8444-000000000012";
|
|
153
|
+
const INCIDENT_11_ID: string = "44444444-4444-4444-8444-000000000011";
|
|
154
|
+
|
|
155
|
+
type ListResultFunction = (data: Array<unknown>) => {
|
|
156
|
+
data: Array<unknown>;
|
|
157
|
+
count: number;
|
|
158
|
+
skip: number;
|
|
159
|
+
limit: number;
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const listResult: ListResultFunction = (data: Array<unknown>) => {
|
|
163
|
+
return { data: data, count: data.length, skip: 0, limit: data.length };
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
/*
|
|
167
|
+
* Two alerts from one monitor with the same title, newest first - the case
|
|
168
|
+
* a title-only picker could not handle.
|
|
169
|
+
*/
|
|
170
|
+
type RecentAlertsFunction = () => Array<Alert>;
|
|
171
|
+
|
|
172
|
+
const recentAlerts: RecentAlertsFunction = (): Array<Alert> => {
|
|
173
|
+
const newest: Alert = new Alert();
|
|
174
|
+
newest._id = ALERT_63_ID;
|
|
175
|
+
newest.title = "Checkout API is offline";
|
|
176
|
+
newest.alertNumber = 63;
|
|
177
|
+
newest.alertNumberWithPrefix = "ALT-63";
|
|
178
|
+
|
|
179
|
+
const older: Alert = new Alert();
|
|
180
|
+
older._id = ALERT_57_ID;
|
|
181
|
+
older.title = "Checkout API is offline";
|
|
182
|
+
older.alertNumber = 57;
|
|
183
|
+
|
|
184
|
+
return [newest, older];
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
type RecentIncidentsFunction = () => Array<Incident>;
|
|
188
|
+
|
|
189
|
+
const recentIncidents: RecentIncidentsFunction = (): Array<Incident> => {
|
|
190
|
+
const newest: Incident = new Incident();
|
|
191
|
+
newest._id = INCIDENT_12_ID;
|
|
192
|
+
newest.title = "Checkout outage";
|
|
193
|
+
newest.incidentNumber = 12;
|
|
194
|
+
newest.incidentNumberWithPrefix = "INC-12";
|
|
195
|
+
|
|
196
|
+
const older: Incident = new Incident();
|
|
197
|
+
older._id = INCIDENT_11_ID;
|
|
198
|
+
older.title = "Checkout outage";
|
|
199
|
+
older.incidentNumber = 11;
|
|
200
|
+
|
|
201
|
+
return [newest, older];
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
let failOptions: boolean = false;
|
|
205
|
+
|
|
206
|
+
type AnswerListFunction = (request: any) => Promise<unknown>;
|
|
207
|
+
|
|
208
|
+
const answerList: AnswerListFunction = async (
|
|
209
|
+
request: any,
|
|
210
|
+
): Promise<unknown> => {
|
|
211
|
+
if (request.modelType === AlertState || request.modelType === IncidentState) {
|
|
212
|
+
return listResult([]);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (failOptions) {
|
|
216
|
+
throw new Error("Forbidden");
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (request.modelType === Alert) {
|
|
220
|
+
return listResult(recentAlerts());
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (request.modelType === Incident) {
|
|
224
|
+
return listResult(recentIncidents());
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return listResult([]);
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
type ActAsyncFunction = (body: () => Promise<void>) => Promise<void>;
|
|
231
|
+
|
|
232
|
+
/*
|
|
233
|
+
* zone.js reschedules the microtask React's act() uses to check that its
|
|
234
|
+
* thenable was awaited; hooking then() up synchronously answers that check
|
|
235
|
+
* in time (as BulkIncidentLinkActions.test.tsx does).
|
|
236
|
+
*/
|
|
237
|
+
const actAsync: ActAsyncFunction = (
|
|
238
|
+
body: () => Promise<void>,
|
|
239
|
+
): Promise<void> => {
|
|
240
|
+
const scope: PromiseLike<void> = act(body) as unknown as PromiseLike<void>;
|
|
241
|
+
|
|
242
|
+
return new Promise<void>(
|
|
243
|
+
(resolve: () => void, reject: (reason: unknown) => void): void => {
|
|
244
|
+
scope.then(resolve, reject);
|
|
245
|
+
},
|
|
246
|
+
);
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
type FailWithFunction = (message: string) => () => Promise<never>;
|
|
250
|
+
|
|
251
|
+
const failWith: FailWithFunction = (message: string) => {
|
|
252
|
+
return async (): Promise<never> => {
|
|
253
|
+
throw new Error(message);
|
|
254
|
+
};
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
type LatestTableFunction = () => CapturedTableProps;
|
|
258
|
+
|
|
259
|
+
const latestTable: LatestTableFunction = (): CapturedTableProps => {
|
|
260
|
+
return capturedTables[capturedTables.length - 1]!;
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
type OpenPageFunction = (
|
|
264
|
+
page: React.FunctionComponent<PageComponentProps>,
|
|
265
|
+
) => Promise<void>;
|
|
266
|
+
|
|
267
|
+
const openPage: OpenPageFunction = async (
|
|
268
|
+
page: React.FunctionComponent<PageComponentProps>,
|
|
269
|
+
): Promise<void> => {
|
|
270
|
+
const Page: React.FunctionComponent<PageComponentProps> = page;
|
|
271
|
+
|
|
272
|
+
render(
|
|
273
|
+
<MemoryRouter>
|
|
274
|
+
<Page
|
|
275
|
+
pageRoute={new Route("/dashboard/page")}
|
|
276
|
+
currentProject={new Project()}
|
|
277
|
+
hasPaymentMethod={true}
|
|
278
|
+
/>
|
|
279
|
+
</MemoryRouter>,
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
// The state list lands after the first render.
|
|
283
|
+
await waitFor(() => {
|
|
284
|
+
expect(getListMock).toHaveBeenCalledTimes(1);
|
|
285
|
+
});
|
|
286
|
+
await waitFor(() => {
|
|
287
|
+
expect(capturedTables.length).toBeGreaterThan(1);
|
|
288
|
+
});
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
type OpenDialogFunction = (title: string) => Promise<void>;
|
|
292
|
+
|
|
293
|
+
/* Presses the page's link card button and waits for the options to load. */
|
|
294
|
+
const openDialog: OpenDialogFunction = async (title: string): Promise<void> => {
|
|
295
|
+
const button: CardButtonSchema | undefined = (
|
|
296
|
+
latestTable()["cardProps"]["buttons"] as Array<CardButtonSchema>
|
|
297
|
+
).find((candidate: CardButtonSchema): boolean => {
|
|
298
|
+
return candidate.title === title;
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
expect(button).toBeDefined();
|
|
302
|
+
|
|
303
|
+
act(() => {
|
|
304
|
+
button!.onClick();
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
await waitFor(() => {
|
|
308
|
+
expect(capturedModal).not.toBeNull();
|
|
309
|
+
expect(capturedModal!.isLoading).toBe(false);
|
|
310
|
+
});
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
type SubmitFunction = (value: unknown) => Promise<void>;
|
|
314
|
+
|
|
315
|
+
const submit: SubmitFunction = async (value: unknown): Promise<void> => {
|
|
316
|
+
if (!capturedModal) {
|
|
317
|
+
throw new Error("The link dialog is not open.");
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const onSubmit: CapturedModalProps["onSubmit"] = capturedModal.onSubmit;
|
|
321
|
+
|
|
322
|
+
await actAsync(async () => {
|
|
323
|
+
await onSubmit({ linkedRecordId: value });
|
|
324
|
+
});
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
type OptionRequestsFunction = () => Array<any>;
|
|
328
|
+
|
|
329
|
+
const optionRequests: OptionRequestsFunction = (): Array<any> => {
|
|
330
|
+
return getListMock.mock.calls
|
|
331
|
+
.map((call: Array<any>): any => {
|
|
332
|
+
return call[0];
|
|
333
|
+
})
|
|
334
|
+
.filter((request: any): boolean => {
|
|
335
|
+
return request.modelType === Alert || request.modelType === Incident;
|
|
336
|
+
});
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
type CreatedLinkFunction = () => IncidentAlert;
|
|
340
|
+
|
|
341
|
+
const createdLink: CreatedLinkFunction = (): IncidentAlert => {
|
|
342
|
+
expect(createMock).toHaveBeenCalledTimes(1);
|
|
343
|
+
|
|
344
|
+
const request: any = createMock.mock.calls[0]![0];
|
|
345
|
+
|
|
346
|
+
expect(request.modelType).toBe(IncidentAlert);
|
|
347
|
+
|
|
348
|
+
return request.model as IncidentAlert;
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
type DialogIsOpenFunction = () => boolean;
|
|
352
|
+
|
|
353
|
+
const dialogIsOpen: DialogIsOpenFunction = (): boolean => {
|
|
354
|
+
return Boolean(document.querySelector('[data-testid="link-dialog"]'));
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
describe("the link dialog on the Linked pages", () => {
|
|
358
|
+
beforeEach(() => {
|
|
359
|
+
capturedTables = [];
|
|
360
|
+
capturedModal = null;
|
|
361
|
+
failOptions = false;
|
|
362
|
+
getListMock.mockReset();
|
|
363
|
+
createMock.mockReset();
|
|
364
|
+
getListMock.mockImplementation(answerList);
|
|
365
|
+
createMock.mockResolvedValue({});
|
|
366
|
+
PermissionGate.clearPermissionPropsCache();
|
|
367
|
+
|
|
368
|
+
jest
|
|
369
|
+
.spyOn(ProjectUtil, "getCurrentProjectId")
|
|
370
|
+
.mockReturnValue(new ObjectID(PROJECT_ID));
|
|
371
|
+
jest
|
|
372
|
+
.spyOn(Navigation, "getLastParamAsObjectID")
|
|
373
|
+
.mockReturnValue(new ObjectID(MODEL_ID));
|
|
374
|
+
jest.spyOn(PermissionUtil, "getAllPermissions").mockImplementation(() => {
|
|
375
|
+
return [Permission.ProjectMember];
|
|
376
|
+
});
|
|
377
|
+
jest.spyOn(UserUtil, "isMasterAdmin").mockReturnValue(false);
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
afterEach(() => {
|
|
381
|
+
cleanup();
|
|
382
|
+
jest.restoreAllMocks();
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
describe("an incident's Link Alert", () => {
|
|
386
|
+
test("reads no alerts until the dialog opens", async () => {
|
|
387
|
+
await openPage(IncidentViewAlerts);
|
|
388
|
+
|
|
389
|
+
expect(optionRequests()).toHaveLength(0);
|
|
390
|
+
expect(dialogIsOpen()).toBe(false);
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
test("lists recent alerts newest first, by number, and still searches by title", async () => {
|
|
394
|
+
await openPage(IncidentViewAlerts);
|
|
395
|
+
await openDialog("Link Alert");
|
|
396
|
+
|
|
397
|
+
const requests: Array<any> = optionRequests();
|
|
398
|
+
|
|
399
|
+
expect(requests).toHaveLength(1);
|
|
400
|
+
expect(requests[0].modelType).toBe(Alert);
|
|
401
|
+
expect(requests[0].limit).toBe(LINK_OPTIONS_LIMIT);
|
|
402
|
+
expect(requests[0].sort).toEqual({ createdAt: SortOrder.Descending });
|
|
403
|
+
|
|
404
|
+
expect(capturedModal!.title).toBe("Link Alert");
|
|
405
|
+
expect(capturedModal!.submitButtonText).toBe("Link Alert");
|
|
406
|
+
expect(capturedModal!.description).toBe(
|
|
407
|
+
"Select an alert to link to this incident.",
|
|
408
|
+
);
|
|
409
|
+
|
|
410
|
+
const field: Record<string, any> = capturedModal!.formProps.fields[0]!;
|
|
411
|
+
|
|
412
|
+
expect(capturedModal!.formProps.fields).toHaveLength(1);
|
|
413
|
+
expect(field["title"]).toBe("Alert");
|
|
414
|
+
expect(field["placeholder"]).toBe("Select an alert");
|
|
415
|
+
expect(field["required"]).toBe(true);
|
|
416
|
+
expect(field["dropdownModal"]).toEqual({
|
|
417
|
+
type: Alert,
|
|
418
|
+
labelField: "title",
|
|
419
|
+
valueField: "_id",
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
const labels: Array<string> = Array.from(
|
|
423
|
+
document.querySelectorAll('[data-testid="link-dialog"] li'),
|
|
424
|
+
).map((item: Element): string => {
|
|
425
|
+
return item.textContent || "";
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
expect(labels).toEqual([
|
|
429
|
+
"ALT-63: Checkout API is offline",
|
|
430
|
+
"#57: Checkout API is offline",
|
|
431
|
+
]);
|
|
432
|
+
expect(field["dropdownOptions"]).toEqual([
|
|
433
|
+
{ label: "ALT-63: Checkout API is offline", value: ALERT_63_ID },
|
|
434
|
+
{ label: "#57: Checkout API is offline", value: ALERT_57_ID },
|
|
435
|
+
]);
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
test("links the chosen alert to this incident, closes and refreshes the table", async () => {
|
|
439
|
+
await openPage(IncidentViewAlerts);
|
|
440
|
+
await openDialog("Link Alert");
|
|
441
|
+
|
|
442
|
+
const refreshBefore: unknown = latestTable()["refreshToggle"];
|
|
443
|
+
|
|
444
|
+
await submit(ALERT_57_ID);
|
|
445
|
+
|
|
446
|
+
const link: IncidentAlert = createdLink();
|
|
447
|
+
|
|
448
|
+
expect(link.incidentId?.toString()).toBe(MODEL_ID);
|
|
449
|
+
expect(link.alertId?.toString()).toBe(ALERT_57_ID);
|
|
450
|
+
expect(link.projectId?.toString()).toBe(PROJECT_ID);
|
|
451
|
+
|
|
452
|
+
await waitFor(() => {
|
|
453
|
+
expect(dialogIsOpen()).toBe(false);
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
expect(latestTable()["refreshToggle"]).not.toBe(refreshBefore);
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
test("accepts the chosen option as an object", async () => {
|
|
460
|
+
await openPage(IncidentViewAlerts);
|
|
461
|
+
await openDialog("Link Alert");
|
|
462
|
+
|
|
463
|
+
await submit({ label: "ALT-63: x", value: ALERT_63_ID });
|
|
464
|
+
|
|
465
|
+
expect(createdLink().alertId?.toString()).toBe(ALERT_63_ID);
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
test("keeps the dialog open with the reason when the link is refused", async () => {
|
|
469
|
+
createMock.mockImplementation(
|
|
470
|
+
failWith(
|
|
471
|
+
"The alert to link does not exist in this project, or you do not have access to it.",
|
|
472
|
+
),
|
|
473
|
+
);
|
|
474
|
+
|
|
475
|
+
await openPage(IncidentViewAlerts);
|
|
476
|
+
await openDialog("Link Alert");
|
|
477
|
+
|
|
478
|
+
const refreshBefore: unknown = latestTable()["refreshToggle"];
|
|
479
|
+
|
|
480
|
+
await submit(ALERT_63_ID);
|
|
481
|
+
|
|
482
|
+
await waitFor(() => {
|
|
483
|
+
expect(screen.getByTestId("link-dialog-error")).toHaveTextContent(
|
|
484
|
+
"The alert to link does not exist in this project, or you do not have access to it.",
|
|
485
|
+
);
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
expect(dialogIsOpen()).toBe(true);
|
|
489
|
+
expect(capturedModal!.isLoading).toBe(false);
|
|
490
|
+
expect(latestTable()["refreshToggle"]).toBe(refreshBefore);
|
|
491
|
+
|
|
492
|
+
/*
|
|
493
|
+
* Once, on the form: BasicFormModal would also hand its own error prop
|
|
494
|
+
* to the modal body, showing the same sentence twice.
|
|
495
|
+
*/
|
|
496
|
+
expect(capturedModal!.error).toBeUndefined();
|
|
497
|
+
expect(
|
|
498
|
+
screen.queryByTestId("link-dialog-modal-error"),
|
|
499
|
+
).not.toBeInTheDocument();
|
|
500
|
+
|
|
501
|
+
// The choice survives, so trying again does not mean finding it again.
|
|
502
|
+
expect(capturedModal!.formProps.initialValues).toEqual({
|
|
503
|
+
linkedRecordId: ALERT_63_ID,
|
|
504
|
+
});
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
test("says a duplicate is already linked, however the server caught it", async () => {
|
|
508
|
+
createMock.mockImplementation(
|
|
509
|
+
failWith(
|
|
510
|
+
"A Incident Alert with the same Incident Id, Alert Id, Project Id already exists. Please use different values and try again.",
|
|
511
|
+
),
|
|
512
|
+
);
|
|
513
|
+
|
|
514
|
+
await openPage(IncidentViewAlerts);
|
|
515
|
+
await openDialog("Link Alert");
|
|
516
|
+
await submit(ALERT_63_ID);
|
|
517
|
+
|
|
518
|
+
await waitFor(() => {
|
|
519
|
+
expect(screen.getByTestId("link-dialog-error")).toHaveTextContent(
|
|
520
|
+
INCIDENT_ALERT_ALREADY_LINKED_MESSAGE,
|
|
521
|
+
);
|
|
522
|
+
});
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
test("replaces the last reason with the new one", async () => {
|
|
526
|
+
createMock
|
|
527
|
+
.mockImplementationOnce(failWith(INCIDENT_ALERT_ALREADY_LINKED_MESSAGE))
|
|
528
|
+
.mockImplementationOnce(failWith("Alert not found."));
|
|
529
|
+
|
|
530
|
+
await openPage(IncidentViewAlerts);
|
|
531
|
+
await openDialog("Link Alert");
|
|
532
|
+
await submit(ALERT_63_ID);
|
|
533
|
+
|
|
534
|
+
await waitFor(() => {
|
|
535
|
+
expect(screen.getByTestId("link-dialog-error")).toHaveTextContent(
|
|
536
|
+
INCIDENT_ALERT_ALREADY_LINKED_MESSAGE,
|
|
537
|
+
);
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
await submit(ALERT_57_ID);
|
|
541
|
+
|
|
542
|
+
await waitFor(() => {
|
|
543
|
+
expect(screen.getByTestId("link-dialog-error")).toHaveTextContent(
|
|
544
|
+
"Alert not found.",
|
|
545
|
+
);
|
|
546
|
+
});
|
|
547
|
+
expect(screen.getAllByTestId("link-dialog-error")).toHaveLength(1);
|
|
548
|
+
expect(capturedModal!.formProps.initialValues).toEqual({
|
|
549
|
+
linkedRecordId: ALERT_57_ID,
|
|
550
|
+
});
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
test("clears the last error when trying again", async () => {
|
|
554
|
+
createMock
|
|
555
|
+
.mockImplementationOnce(failWith(INCIDENT_ALERT_ALREADY_LINKED_MESSAGE))
|
|
556
|
+
.mockResolvedValueOnce({});
|
|
557
|
+
|
|
558
|
+
await openPage(IncidentViewAlerts);
|
|
559
|
+
await openDialog("Link Alert");
|
|
560
|
+
await submit(ALERT_63_ID);
|
|
561
|
+
|
|
562
|
+
await waitFor(() => {
|
|
563
|
+
expect(screen.getByTestId("link-dialog-error")).toBeInTheDocument();
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
await submit(ALERT_57_ID);
|
|
567
|
+
|
|
568
|
+
await waitFor(() => {
|
|
569
|
+
expect(dialogIsOpen()).toBe(false);
|
|
570
|
+
});
|
|
571
|
+
expect(createMock).toHaveBeenCalledTimes(2);
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
test("still opens, and still links, when recent alerts cannot be read", async () => {
|
|
575
|
+
failOptions = true;
|
|
576
|
+
|
|
577
|
+
await openPage(IncidentViewAlerts);
|
|
578
|
+
await openDialog("Link Alert");
|
|
579
|
+
|
|
580
|
+
expect(capturedModal!.formProps.fields[0]!["dropdownOptions"]).toEqual(
|
|
581
|
+
[],
|
|
582
|
+
);
|
|
583
|
+
expect(capturedModal!.formProps.error).toBeUndefined();
|
|
584
|
+
|
|
585
|
+
await submit(ALERT_63_ID);
|
|
586
|
+
|
|
587
|
+
expect(createdLink().alertId?.toString()).toBe(ALERT_63_ID);
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
test("closing the dialog links nothing and leaves the table alone", async () => {
|
|
591
|
+
await openPage(IncidentViewAlerts);
|
|
592
|
+
await openDialog("Link Alert");
|
|
593
|
+
|
|
594
|
+
const refreshBefore: unknown = latestTable()["refreshToggle"];
|
|
595
|
+
const onClose: () => void = capturedModal!.onClose;
|
|
596
|
+
|
|
597
|
+
act(() => {
|
|
598
|
+
onClose();
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
expect(dialogIsOpen()).toBe(false);
|
|
602
|
+
expect(createMock).not.toHaveBeenCalled();
|
|
603
|
+
expect(latestTable()["refreshToggle"]).toBe(refreshBefore);
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
test("submitting nothing links nothing", async () => {
|
|
607
|
+
await openPage(IncidentViewAlerts);
|
|
608
|
+
await openDialog("Link Alert");
|
|
609
|
+
|
|
610
|
+
await submit("");
|
|
611
|
+
|
|
612
|
+
expect(createMock).not.toHaveBeenCalled();
|
|
613
|
+
expect(dialogIsOpen()).toBe(true);
|
|
614
|
+
});
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
describe("an alert's Link Incident", () => {
|
|
618
|
+
test("lists recent incidents newest first, by number", async () => {
|
|
619
|
+
await openPage(AlertViewIncidents);
|
|
620
|
+
|
|
621
|
+
expect(optionRequests()).toHaveLength(0);
|
|
622
|
+
|
|
623
|
+
await openDialog("Link Incident");
|
|
624
|
+
|
|
625
|
+
const requests: Array<any> = optionRequests();
|
|
626
|
+
|
|
627
|
+
expect(requests).toHaveLength(1);
|
|
628
|
+
expect(requests[0].modelType).toBe(Incident);
|
|
629
|
+
expect(requests[0].sort).toEqual({ createdAt: SortOrder.Descending });
|
|
630
|
+
|
|
631
|
+
expect(capturedModal!.title).toBe("Link Incident");
|
|
632
|
+
expect(capturedModal!.submitButtonText).toBe("Link Incident");
|
|
633
|
+
expect(capturedModal!.description).toBe(
|
|
634
|
+
"Select an incident to link this alert to.",
|
|
635
|
+
);
|
|
636
|
+
|
|
637
|
+
const field: Record<string, any> = capturedModal!.formProps.fields[0]!;
|
|
638
|
+
|
|
639
|
+
expect(field["title"]).toBe("Incident");
|
|
640
|
+
expect(field["placeholder"]).toBe("Select an incident");
|
|
641
|
+
expect(field["dropdownModal"]).toEqual({
|
|
642
|
+
type: Incident,
|
|
643
|
+
labelField: "title",
|
|
644
|
+
valueField: "_id",
|
|
645
|
+
});
|
|
646
|
+
expect(field["dropdownOptions"]).toEqual([
|
|
647
|
+
{ label: "INC-12: Checkout outage", value: INCIDENT_12_ID },
|
|
648
|
+
{ label: "#11: Checkout outage", value: INCIDENT_11_ID },
|
|
649
|
+
]);
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
test("links this alert to the chosen incident, closes and refreshes the table", async () => {
|
|
653
|
+
await openPage(AlertViewIncidents);
|
|
654
|
+
await openDialog("Link Incident");
|
|
655
|
+
|
|
656
|
+
const refreshBefore: unknown = latestTable()["refreshToggle"];
|
|
657
|
+
|
|
658
|
+
await submit(INCIDENT_12_ID);
|
|
659
|
+
|
|
660
|
+
const link: IncidentAlert = createdLink();
|
|
661
|
+
|
|
662
|
+
expect(link.incidentId?.toString()).toBe(INCIDENT_12_ID);
|
|
663
|
+
expect(link.alertId?.toString()).toBe(MODEL_ID);
|
|
664
|
+
expect(link.projectId?.toString()).toBe(PROJECT_ID);
|
|
665
|
+
|
|
666
|
+
await waitFor(() => {
|
|
667
|
+
expect(dialogIsOpen()).toBe(false);
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
expect(latestTable()["refreshToggle"]).not.toBe(refreshBefore);
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
test("shows an already-linked refusal in the dialog", async () => {
|
|
674
|
+
createMock.mockImplementation(
|
|
675
|
+
failWith(INCIDENT_ALERT_ALREADY_LINKED_MESSAGE),
|
|
676
|
+
);
|
|
677
|
+
|
|
678
|
+
await openPage(AlertViewIncidents);
|
|
679
|
+
await openDialog("Link Incident");
|
|
680
|
+
await submit(INCIDENT_12_ID);
|
|
681
|
+
|
|
682
|
+
await waitFor(() => {
|
|
683
|
+
expect(screen.getByTestId("link-dialog-error")).toHaveTextContent(
|
|
684
|
+
INCIDENT_ALERT_ALREADY_LINKED_MESSAGE,
|
|
685
|
+
);
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
expect(dialogIsOpen()).toBe(true);
|
|
689
|
+
});
|
|
690
|
+
});
|
|
691
|
+
});
|