@oneuptime/common 12.0.28 → 12.0.29
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 +43 -0
- package/Models/DatabaseModels/MonitorTemplate.ts +15 -3
- package/Models/DatabaseModels/StatusPageDomain.ts +43 -0
- package/Server/API/DashboardDomainAPI.ts +74 -0
- package/Server/API/MicrosoftTeamsAPI.ts +81 -180
- package/Server/API/StatusPageDomainAPI.ts +74 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1790200000000-AllowNullMonitorNameOnMonitorTemplate.ts +43 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1790300000000-AddCertificateReissueRequestedAtToDomains.ts +40 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
- package/Server/Middleware/ProjectAuthorization.ts +6 -1
- package/Server/Services/DashboardDomainService.ts +131 -0
- package/Server/Services/LogPipelineProcessorService.ts +88 -0
- package/Server/Services/MonitorTemplateService.ts +70 -11
- package/Server/Services/StatusPageDomainService.ts +163 -3
- package/Server/Services/StatusPagePrivateUserSessionService.ts +197 -56
- package/Server/Types/Domain.ts +17 -0
- package/Server/Utils/APIKey/AccessPermission.ts +24 -0
- package/Server/Utils/Greenlock/Greenlock.ts +156 -58
- package/Server/Utils/LogPipelineProcessorValidation.ts +112 -0
- package/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.ts +1094 -45
- package/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.ts +153 -17
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.ts +23 -11
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.ts +0 -170
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/Authorization.ts +158 -0
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.ts +23 -11
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +35 -13
- package/Tests/App/Dashboard/MonitorTemplateColumnAndFacet.test.tsx +505 -0
- package/Tests/App/StatusPage/StatusPageLoginCodeBootstrap.test.ts +106 -0
- package/Tests/App/StatusPage/StatusPageLoginCodeUtil.test.ts +84 -0
- package/Tests/App/StatusPage/StatusPageRedirectSafety.test.ts +119 -0
- package/Tests/Models/MonitorTemplateMonitorNameColumn.test.ts +258 -0
- package/Tests/Server/API/CustomDomainReissueSslAPI.test.ts +469 -0
- package/Tests/Server/API/MicrosoftTeamsBotMessagesEndpoint.test.ts +729 -0
- package/Tests/Server/API/MicrosoftTeamsBotTestEndpoint.test.ts +26 -0
- package/Tests/Server/API/MicrosoftTeamsLegacyWebhookRemoval.test.ts +402 -0
- package/Tests/Server/Middleware/ProjectAuthorizationApiKeyMiddleware.test.ts +4 -0
- package/Tests/Server/Services/AddCertificateReissueRequestedAtToDomainsMigration.test.ts +287 -0
- package/Tests/Server/Services/CustomDomainCertificateReissue.test.ts +477 -0
- package/Tests/Server/Services/LogPipelineProcessorSaveValidation.test.ts +249 -0
- package/Tests/Server/Services/MonitorTemplateServiceBulkSync.test.ts +549 -0
- package/Tests/Server/Services/MonitorTemplateServiceNetworkDeviceSync.test.ts +16 -3
- package/Tests/Server/Services/NetworkDeviceAutoImportRuleEngineService.test.ts +57 -0
- package/Tests/Server/Services/SecurityEventLastErrorColumnWidth.test.ts +151 -15
- package/Tests/Server/Services/StatusPagePrivateUserSessionService.test.ts +587 -0
- package/Tests/Server/Types/Database/Permissions/ApiKeyFileAccess.test.ts +286 -0
- package/Tests/Server/Types/Database/Permissions/MonitorTemplateColumnAccess.test.ts +328 -0
- package/Tests/Server/Types/Database/QueryHelperFindWithSameTextAndIsNull.test.ts +83 -0
- package/Tests/Server/Utils/AI/SRE/ConfidenceSignal.test.ts +377 -0
- package/Tests/Server/Utils/APIKey/AccessPermission.test.ts +16 -1
- package/Tests/Server/Utils/Greenlock/CertificateRenewalSchedule.test.ts +250 -0
- package/Tests/Server/Utils/LogPipelineProcessorValidation.test.ts +198 -0
- package/Tests/Server/Utils/SecurityEvent/ConnectorErrorMessage.test.ts +48 -9
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsAuth.test.ts +726 -0
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsClient.test.ts +74 -11
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsErrorHandling.test.ts +1107 -0
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPoller.test.ts +20 -3
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPollerFailureTaxonomy.test.ts +51 -11
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPollerHardening.test.ts +1246 -0
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsRequestContract.test.ts +961 -0
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsResponseParsing.test.ts +653 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsActionAuthorization.test.ts +880 -0
- package/Tests/Types/API/HTTPResponse.test.ts +175 -0
- package/Tests/Types/API/Route.test.ts +36 -4
- package/Tests/Types/Database/DatabaseProperty.test.ts +171 -0
- package/Tests/Types/DateLocalShortDateTimeString.test.ts +518 -0
- package/Tests/Types/DateLocalTimeString.test.ts +259 -0
- package/Tests/Types/DateUserPrefers12HourFormat.test.ts +316 -0
- package/Tests/UI/Components/LogsHistogram.test.tsx +41 -2
- package/Tests/UI/Components/LogsViewerEmptyStateTimeRange.test.tsx +212 -0
- package/Tests/UI/Components/TelemetryChartClockFormat.test.tsx +359 -0
- package/Tests/UI/Components/TimeRangePickerClockFormat.test.tsx +422 -0
- package/Tests/UI/Components/TimeRangePickerDropdown.test.tsx +146 -8
- package/Tests/UI/MicrosoftTeams/MicrosoftTeamsMessagingEndpointGuidance.test.tsx +520 -0
- package/Tests/UI/Utils/Navigation.test.ts +82 -0
- package/Tests/Utils/Array.test.ts +100 -0
- package/Tests/Utils/CertificateReissue.test.ts +225 -0
- package/Tests/Utils/Grok/Grok.test.ts +492 -0
- package/Tests/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.test.ts +181 -13
- package/Types/API/Route.ts +20 -5
- package/Types/Date.ts +143 -13
- package/Types/Rum/SessionReplay.ts +62 -1
- package/UI/Components/Date/TimeRangePickerDropdown.tsx +9 -5
- package/UI/Components/LogsViewer/LogsViewer.tsx +12 -7
- package/UI/Components/LogsViewer/components/HistogramTooltip.tsx +18 -17
- package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +21 -28
- package/UI/Components/LogsViewer/components/LogsHistogram.tsx +2 -4
- package/UI/Components/TelemetryViewer/components/TelemetryHistogram.tsx +2 -4
- package/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.tsx +17 -16
- package/UI/Utils/Navigation.ts +42 -0
- package/Utils/Array.ts +37 -0
- package/Utils/CertificateReissue.ts +128 -0
- package/Utils/Grok/Grok.ts +461 -0
- package/Utils/Grok/GrokPatterns.ts +118 -0
- package/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.ts +58 -6
- package/build/dist/Models/DatabaseModels/DashboardDomain.js +44 -0
- package/build/dist/Models/DatabaseModels/DashboardDomain.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MonitorTemplate.js +17 -4
- package/build/dist/Models/DatabaseModels/MonitorTemplate.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageDomain.js +44 -0
- package/build/dist/Models/DatabaseModels/StatusPageDomain.js.map +1 -1
- package/build/dist/Server/API/DashboardDomainAPI.js +43 -1
- package/build/dist/Server/API/DashboardDomainAPI.js.map +1 -1
- package/build/dist/Server/API/MicrosoftTeamsAPI.js +74 -118
- package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
- package/build/dist/Server/API/StatusPageDomainAPI.js +43 -1
- package/build/dist/Server/API/StatusPageDomainAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790200000000-AllowNullMonitorNameOnMonitorTemplate.js +34 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790200000000-AllowNullMonitorNameOnMonitorTemplate.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790300000000-AddCertificateReissueRequestedAtToDomains.js +28 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790300000000-AddCertificateReissueRequestedAtToDomains.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/Middleware/ProjectAuthorization.js +6 -1
- package/build/dist/Server/Middleware/ProjectAuthorization.js.map +1 -1
- package/build/dist/Server/Services/DashboardDomainService.js +104 -0
- package/build/dist/Server/Services/DashboardDomainService.js.map +1 -1
- package/build/dist/Server/Services/LogPipelineProcessorService.js +65 -0
- package/build/dist/Server/Services/LogPipelineProcessorService.js.map +1 -1
- package/build/dist/Server/Services/MonitorTemplateService.js +57 -11
- package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageDomainService.js +132 -3
- package/build/dist/Server/Services/StatusPageDomainService.js.map +1 -1
- package/build/dist/Server/Services/StatusPagePrivateUserSessionService.js +112 -32
- package/build/dist/Server/Services/StatusPagePrivateUserSessionService.js.map +1 -1
- package/build/dist/Server/Types/Domain.js +16 -0
- package/build/dist/Server/Types/Domain.js.map +1 -1
- package/build/dist/Server/Utils/APIKey/AccessPermission.js +24 -0
- package/build/dist/Server/Utils/APIKey/AccessPermission.js.map +1 -1
- package/build/dist/Server/Utils/Greenlock/Greenlock.js +110 -35
- package/build/dist/Server/Utils/Greenlock/Greenlock.js.map +1 -1
- package/build/dist/Server/Utils/LogPipelineProcessorValidation.js +77 -0
- package/build/dist/Server/Utils/LogPipelineProcessorValidation.js.map +1 -0
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.js +777 -36
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.js.map +1 -1
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.js +108 -17
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js +17 -6
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.js +0 -159
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Authorization.js +128 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Authorization.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js +17 -6
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +25 -13
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
- package/build/dist/Types/API/Route.js +16 -5
- package/build/dist/Types/API/Route.js.map +1 -1
- package/build/dist/Types/Date.js +115 -13
- package/build/dist/Types/Date.js.map +1 -1
- package/build/dist/Types/Rum/SessionReplay.js +38 -0
- package/build/dist/Types/Rum/SessionReplay.js.map +1 -1
- package/build/dist/UI/Components/Date/TimeRangePickerDropdown.js +9 -5
- package/build/dist/UI/Components/Date/TimeRangePickerDropdown.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js +12 -7
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/HistogramTooltip.js +15 -14
- package/build/dist/UI/Components/LogsViewer/components/HistogramTooltip.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +17 -22
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js +2 -4
- package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js +2 -4
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.js +15 -14
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.js.map +1 -1
- package/build/dist/UI/Utils/Navigation.js +30 -0
- package/build/dist/UI/Utils/Navigation.js.map +1 -1
- package/build/dist/Utils/Array.js +25 -0
- package/build/dist/Utils/Array.js.map +1 -1
- package/build/dist/Utils/CertificateReissue.js +97 -0
- package/build/dist/Utils/CertificateReissue.js.map +1 -0
- package/build/dist/Utils/Grok/Grok.js +324 -0
- package/build/dist/Utils/Grok/Grok.js.map +1 -0
- package/build/dist/Utils/Grok/GrokPatterns.js +112 -0
- package/build/dist/Utils/Grok/GrokPatterns.js.map +1 -0
- package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js +63 -3
- package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js.map +1 -1
- package/package.json +1 -1
- package/UI/Utils/JsonWebToken.ts +0 -14
- package/build/dist/UI/Utils/JsonWebToken.js +0 -10
- package/build/dist/UI/Utils/JsonWebToken.js.map +0 -1
|
@@ -31,6 +31,8 @@ import SortOrder from "../../../../../Types/BaseDatabase/SortOrder";
|
|
|
31
31
|
import { LIMIT_PER_PROJECT } from "../../../../../Types/Database/LimitMax";
|
|
32
32
|
import BadDataException from "../../../../../Types/Exception/BadDataException";
|
|
33
33
|
import URL from "../../../../../Types/API/URL";
|
|
34
|
+
import DatabaseCommonInteractionProps from "../../../../../Types/BaseDatabase/DatabaseCommonInteractionProps";
|
|
35
|
+
import MicrosoftTeamsActionAuthorization from "./Authorization";
|
|
34
36
|
|
|
35
37
|
export default class MicrosoftTeamsIncidentActions {
|
|
36
38
|
@CaptureSpan()
|
|
@@ -296,6 +298,7 @@ export default class MicrosoftTeamsIncidentActions {
|
|
|
296
298
|
value: JSONObject;
|
|
297
299
|
projectId: ObjectID;
|
|
298
300
|
oneUptimeUserId: ObjectID;
|
|
301
|
+
databaseProps: DatabaseCommonInteractionProps;
|
|
299
302
|
turnContext: TurnContext;
|
|
300
303
|
}): Promise<void> {
|
|
301
304
|
const {
|
|
@@ -304,6 +307,7 @@ export default class MicrosoftTeamsIncidentActions {
|
|
|
304
307
|
value,
|
|
305
308
|
projectId,
|
|
306
309
|
oneUptimeUserId,
|
|
310
|
+
databaseProps,
|
|
307
311
|
turnContext,
|
|
308
312
|
} = data;
|
|
309
313
|
|
|
@@ -315,10 +319,15 @@ export default class MicrosoftTeamsIncidentActions {
|
|
|
315
319
|
return;
|
|
316
320
|
}
|
|
317
321
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
+
const incidentId: ObjectID = new ObjectID(actionValue);
|
|
323
|
+
|
|
324
|
+
await MicrosoftTeamsActionAuthorization.assertCanUpdateIncident({
|
|
325
|
+
incidentId: incidentId,
|
|
326
|
+
projectId: projectId,
|
|
327
|
+
props: databaseProps,
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
await IncidentService.acknowledgeIncident(incidentId, oneUptimeUserId);
|
|
322
331
|
await turnContext.sendActivity("✅ Incident acknowledged.");
|
|
323
332
|
return;
|
|
324
333
|
}
|
|
@@ -331,10 +340,15 @@ export default class MicrosoftTeamsIncidentActions {
|
|
|
331
340
|
return;
|
|
332
341
|
}
|
|
333
342
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
343
|
+
const incidentId: ObjectID = new ObjectID(actionValue);
|
|
344
|
+
|
|
345
|
+
await MicrosoftTeamsActionAuthorization.assertCanUpdateIncident({
|
|
346
|
+
incidentId: incidentId,
|
|
347
|
+
projectId: projectId,
|
|
348
|
+
props: databaseProps,
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
await IncidentService.resolveIncident(incidentId, oneUptimeUserId);
|
|
338
352
|
await turnContext.sendActivity("✅ Incident resolved.");
|
|
339
353
|
return;
|
|
340
354
|
}
|
|
@@ -574,9 +588,7 @@ export default class MicrosoftTeamsIncidentActions {
|
|
|
574
588
|
data: {
|
|
575
589
|
currentIncidentStateId: new ObjectID(incidentStateId.toString()),
|
|
576
590
|
},
|
|
577
|
-
props:
|
|
578
|
-
isRoot: true,
|
|
579
|
-
},
|
|
591
|
+
props: databaseProps,
|
|
580
592
|
});
|
|
581
593
|
|
|
582
594
|
await turnContext.sendActivity(
|
|
@@ -111,6 +111,7 @@ import AccessTokenService from "../../../Services/AccessTokenService";
|
|
|
111
111
|
import AIService, { AI_DISABLED_MESSAGE } from "../../../Services/AIService";
|
|
112
112
|
import DatabaseCommonInteractionProps from "../../../../Types/BaseDatabase/DatabaseCommonInteractionProps";
|
|
113
113
|
import { AIChatCitation } from "../../../../Types/AI/AIChatTypes";
|
|
114
|
+
import TeamMemberService from "../../../Services/TeamMemberService";
|
|
114
115
|
|
|
115
116
|
// Microsoft Teams apps should always be single-tenant
|
|
116
117
|
const MICROSOFT_TEAMS_APP_TYPE: string = "SingleTenant";
|
|
@@ -3999,27 +4000,48 @@ All monitoring checks are passing normally.`;
|
|
|
3999
4000
|
userLookupParamsRes,
|
|
4000
4001
|
);
|
|
4001
4002
|
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4003
|
+
const isIncidentAction: boolean =
|
|
4004
|
+
MicrosoftTeamsIncidentActions.isIncidentAction({ actionType });
|
|
4005
|
+
const isAlertAction: boolean = MicrosoftTeamsAlertActions.isAlertAction({
|
|
4006
|
+
actionType,
|
|
4007
|
+
});
|
|
4008
|
+
|
|
4009
|
+
if (isIncidentAction || isAlertAction) {
|
|
4010
|
+
const databaseProps: DatabaseCommonInteractionProps =
|
|
4011
|
+
await AccessTokenService.getDatabaseCommonInteractionPropsByUserAndProject(
|
|
4012
|
+
{
|
|
4013
|
+
userId: oneUptimeUserId,
|
|
4014
|
+
projectId: projectId,
|
|
4015
|
+
},
|
|
4016
|
+
);
|
|
4017
|
+
|
|
4018
|
+
databaseProps.userTeamIds = await TeamMemberService.getTeamIdsForUser(
|
|
4009
4019
|
oneUptimeUserId,
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4020
|
+
projectId,
|
|
4021
|
+
);
|
|
4022
|
+
|
|
4023
|
+
// Handle incident actions
|
|
4024
|
+
if (isIncidentAction) {
|
|
4025
|
+
await MicrosoftTeamsIncidentActions.handleBotIncidentAction({
|
|
4026
|
+
actionType,
|
|
4027
|
+
actionValue,
|
|
4028
|
+
value,
|
|
4029
|
+
projectId,
|
|
4030
|
+
oneUptimeUserId,
|
|
4031
|
+
databaseProps,
|
|
4032
|
+
turnContext: data.turnContext,
|
|
4033
|
+
});
|
|
4034
|
+
return;
|
|
4035
|
+
}
|
|
4014
4036
|
|
|
4015
|
-
|
|
4016
|
-
if (MicrosoftTeamsAlertActions.isAlertAction({ actionType })) {
|
|
4037
|
+
// Handle alert actions
|
|
4017
4038
|
await MicrosoftTeamsAlertActions.handleBotAlertAction({
|
|
4018
4039
|
actionType,
|
|
4019
4040
|
actionValue,
|
|
4020
4041
|
value,
|
|
4021
4042
|
projectId,
|
|
4022
4043
|
oneUptimeUserId,
|
|
4044
|
+
databaseProps,
|
|
4023
4045
|
turnContext: data.turnContext,
|
|
4024
4046
|
});
|
|
4025
4047
|
return;
|
|
@@ -0,0 +1,505 @@
|
|
|
1
|
+
import "@testing-library/jest-dom";
|
|
2
|
+
import { cleanup, render, screen, waitFor } from "@testing-library/react";
|
|
3
|
+
import React, { ReactElement } from "react";
|
|
4
|
+
import { MemoryRouter } from "react-router-dom";
|
|
5
|
+
import {
|
|
6
|
+
afterEach,
|
|
7
|
+
beforeEach,
|
|
8
|
+
describe,
|
|
9
|
+
expect,
|
|
10
|
+
jest,
|
|
11
|
+
test,
|
|
12
|
+
} from "@jest/globals";
|
|
13
|
+
|
|
14
|
+
/*
|
|
15
|
+
* Issue #3491: the monitor list had no way to see or filter which template a
|
|
16
|
+
* monitor came from, so "which monitors did that template edit just change?"
|
|
17
|
+
* could only be answered by opening monitors one at a time — which does not
|
|
18
|
+
* survive a fleet of thousands sharing one template.
|
|
19
|
+
*
|
|
20
|
+
* The fix is a Template column and a Template chip on MonitorTable, and both
|
|
21
|
+
* fail invisibly:
|
|
22
|
+
*
|
|
23
|
+
* - a column whose `field` stops asking for the relation renders a blank cell
|
|
24
|
+
* on every row, exactly like a project with no templates;
|
|
25
|
+
* - a chip whose `queryField` stops naming the foreign key lights up over a
|
|
26
|
+
* list it is not filtering;
|
|
27
|
+
* - and on the template page's own Linked Monitors table — the same component,
|
|
28
|
+
* scoped by `monitorTemplateId` — a chip would OVERWRITE that scope rather
|
|
29
|
+
* than narrow it, because the facet bar merges everything into one query
|
|
30
|
+
* object, so the page would list another template's monitors under this
|
|
31
|
+
* template's heading.
|
|
32
|
+
*
|
|
33
|
+
* These render the real MonitorTable and read the column set and facet list it
|
|
34
|
+
* actually hands downstream, rather than reading its source.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
jest.mock("react-i18next", () => {
|
|
38
|
+
return {
|
|
39
|
+
useTranslation: () => {
|
|
40
|
+
return {
|
|
41
|
+
t: (key: string): string => {
|
|
42
|
+
return key;
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
jest.mock("../../../UI/Utils/Permission", () => {
|
|
50
|
+
return {
|
|
51
|
+
__esModule: true,
|
|
52
|
+
default: {
|
|
53
|
+
getAllPermissions: () => {
|
|
54
|
+
return [];
|
|
55
|
+
},
|
|
56
|
+
getProjectPermissions: () => {
|
|
57
|
+
return [];
|
|
58
|
+
},
|
|
59
|
+
getGlobalPermissions: () => {
|
|
60
|
+
return [];
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
jest.mock("../../../UI/Utils/User", () => {
|
|
67
|
+
return {
|
|
68
|
+
__esModule: true,
|
|
69
|
+
default: {
|
|
70
|
+
isMasterAdmin: () => {
|
|
71
|
+
return false;
|
|
72
|
+
},
|
|
73
|
+
getUserId: () => {
|
|
74
|
+
return null;
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
type CapturedTableProps = {
|
|
81
|
+
columns?: Array<CapturedColumn> | undefined;
|
|
82
|
+
query?: Record<string, unknown> | undefined;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
type CapturedColumn = {
|
|
86
|
+
title: string;
|
|
87
|
+
field?: Record<string, unknown> | undefined;
|
|
88
|
+
getElement?: ((item: Monitor) => ReactElement) | undefined;
|
|
89
|
+
getExportValue?: ((item: Monitor) => string) | undefined;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
let capturedTableProps: CapturedTableProps | null = null;
|
|
93
|
+
|
|
94
|
+
jest.mock("../../../UI/Components/ModelTable/ModelTable", () => {
|
|
95
|
+
return {
|
|
96
|
+
__esModule: true,
|
|
97
|
+
default: (props: CapturedTableProps) => {
|
|
98
|
+
capturedTableProps = props;
|
|
99
|
+
return null;
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
type CapturedHookOptions = {
|
|
105
|
+
extraFacets?: Array<ResourceFacet> | undefined;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
let capturedHookOptions: CapturedHookOptions | null = null;
|
|
109
|
+
|
|
110
|
+
/*
|
|
111
|
+
* The hook is replaced rather than run: the real one fetches owners, labels and
|
|
112
|
+
* every chip's options over the network on mount. Its named exports stay real,
|
|
113
|
+
* because MonitorTable builds its other chips out of them.
|
|
114
|
+
*/
|
|
115
|
+
jest.mock(
|
|
116
|
+
"../../../../App/FeatureSet/Dashboard/src/Components/ResourceOwners/useResourceOwners",
|
|
117
|
+
() => {
|
|
118
|
+
const actual: Record<string, unknown> = jest.requireActual(
|
|
119
|
+
"../../../../App/FeatureSet/Dashboard/src/Components/ResourceOwners/useResourceOwners",
|
|
120
|
+
) as Record<string, unknown>;
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
...actual,
|
|
124
|
+
__esModule: true,
|
|
125
|
+
default: (options: CapturedHookOptions) => {
|
|
126
|
+
capturedHookOptions = options;
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
getOwnersForResource: () => {
|
|
130
|
+
return [];
|
|
131
|
+
},
|
|
132
|
+
isLoadingOwners: false,
|
|
133
|
+
onResourcesFetched: () => {
|
|
134
|
+
// no-op
|
|
135
|
+
},
|
|
136
|
+
filterBar: null,
|
|
137
|
+
mergeFiltersIntoQuery: (
|
|
138
|
+
base: Record<string, unknown> | undefined,
|
|
139
|
+
) => {
|
|
140
|
+
return base || {};
|
|
141
|
+
},
|
|
142
|
+
facetSaveState: {},
|
|
143
|
+
restoreFacetState: () => {
|
|
144
|
+
// no-op
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
},
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
jest.mock(
|
|
153
|
+
"../../../../App/FeatureSet/Dashboard/src/Components/CustomFields/useCustomFieldFacets",
|
|
154
|
+
() => {
|
|
155
|
+
return {
|
|
156
|
+
__esModule: true,
|
|
157
|
+
default: () => {
|
|
158
|
+
return { facets: [], isLoading: false };
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
},
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
jest.mock("../../../UI/Components/BulkUpdate/BulkLabelActions", () => {
|
|
165
|
+
return {
|
|
166
|
+
__esModule: true,
|
|
167
|
+
default: () => {
|
|
168
|
+
return { bulkActions: [], modals: null };
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
jest.mock("../../../UI/Components/BulkUpdate/BulkOwnerActions", () => {
|
|
174
|
+
return {
|
|
175
|
+
__esModule: true,
|
|
176
|
+
default: () => {
|
|
177
|
+
return { bulkActions: [], modals: null };
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
jest.mock("../../../../App/FeatureSet/Dashboard/src/Utils/Probe", () => {
|
|
183
|
+
return {
|
|
184
|
+
__esModule: true,
|
|
185
|
+
default: {
|
|
186
|
+
getAllProbes: async () => {
|
|
187
|
+
return [];
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
import MonitorTable from "../../../../App/FeatureSet/Dashboard/src/Components/Monitor/MonitorTable";
|
|
194
|
+
import {
|
|
195
|
+
MONITOR_TEMPLATE_FACET_KEY,
|
|
196
|
+
MONITOR_TEMPLATE_FACET_QUERY_FIELD,
|
|
197
|
+
} from "../../../../App/FeatureSet/Dashboard/src/Components/Monitor/MonitorFacets";
|
|
198
|
+
import { ResourceFacet } from "../../../../App/FeatureSet/Dashboard/src/Components/ResourceOwners/ResourceFacet";
|
|
199
|
+
import Monitor from "../../../Models/DatabaseModels/Monitor";
|
|
200
|
+
import MonitorTemplate from "../../../Models/DatabaseModels/MonitorTemplate";
|
|
201
|
+
import Includes from "../../../Types/BaseDatabase/Includes";
|
|
202
|
+
import IsNull from "../../../Types/BaseDatabase/IsNull";
|
|
203
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
204
|
+
import Navigation from "../../../UI/Utils/Navigation";
|
|
205
|
+
import ProjectUtil from "../../../UI/Utils/Project";
|
|
206
|
+
|
|
207
|
+
const PROJECT_ID: ObjectID = ObjectID.generate();
|
|
208
|
+
const TEMPLATE_ID: ObjectID = ObjectID.generate();
|
|
209
|
+
|
|
210
|
+
type RenderTableFunction = (
|
|
211
|
+
query: Record<string, unknown>,
|
|
212
|
+
) => Promise<CapturedTableProps>;
|
|
213
|
+
|
|
214
|
+
const renderTable: RenderTableFunction = async (
|
|
215
|
+
query: Record<string, unknown>,
|
|
216
|
+
): Promise<CapturedTableProps> => {
|
|
217
|
+
render(
|
|
218
|
+
<MemoryRouter>
|
|
219
|
+
<MonitorTable query={query as never} />
|
|
220
|
+
</MemoryRouter>,
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
await waitFor(() => {
|
|
224
|
+
expect(capturedTableProps).not.toBeNull();
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
return capturedTableProps!;
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
type ColumnTitledFunction = (title: string) => CapturedColumn | undefined;
|
|
231
|
+
|
|
232
|
+
const columnTitled: ColumnTitledFunction = (
|
|
233
|
+
title: string,
|
|
234
|
+
): CapturedColumn | undefined => {
|
|
235
|
+
return (capturedTableProps?.columns || []).find(
|
|
236
|
+
(column: CapturedColumn): boolean => {
|
|
237
|
+
return column.title === title;
|
|
238
|
+
},
|
|
239
|
+
);
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
type FacetKeyedFunction = (key: string) => ResourceFacet | undefined;
|
|
243
|
+
|
|
244
|
+
const facetKeyed: FacetKeyedFunction = (
|
|
245
|
+
key: string,
|
|
246
|
+
): ResourceFacet | undefined => {
|
|
247
|
+
return (capturedHookOptions?.extraFacets || []).find(
|
|
248
|
+
(facet: ResourceFacet): boolean => {
|
|
249
|
+
return facet.key === key;
|
|
250
|
+
},
|
|
251
|
+
);
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
type MonitorWithTemplateFunction = (
|
|
255
|
+
templateName: string | undefined,
|
|
256
|
+
) => Monitor;
|
|
257
|
+
|
|
258
|
+
const monitorWithTemplate: MonitorWithTemplateFunction = (
|
|
259
|
+
templateName: string | undefined,
|
|
260
|
+
): Monitor => {
|
|
261
|
+
const monitor: Monitor = new Monitor();
|
|
262
|
+
monitor.name = "core-switch-01";
|
|
263
|
+
|
|
264
|
+
if (templateName) {
|
|
265
|
+
const template: MonitorTemplate = new MonitorTemplate();
|
|
266
|
+
template._id = TEMPLATE_ID.toString();
|
|
267
|
+
template.templateName = templateName;
|
|
268
|
+
monitor.monitorTemplate = template;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return monitor;
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
beforeEach(() => {
|
|
275
|
+
capturedTableProps = null;
|
|
276
|
+
capturedHookOptions = null;
|
|
277
|
+
|
|
278
|
+
jest.spyOn(ProjectUtil, "getCurrentProjectId").mockReturnValue(PROJECT_ID);
|
|
279
|
+
|
|
280
|
+
/*
|
|
281
|
+
* The table builds its row link off the current route. In the app the router
|
|
282
|
+
* pushes this in; under test nothing does.
|
|
283
|
+
*/
|
|
284
|
+
Navigation.setLocation({
|
|
285
|
+
pathname: `/dashboard/${PROJECT_ID.toString()}/monitors`,
|
|
286
|
+
} as unknown as Parameters<typeof Navigation.setLocation>[0]);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
afterEach(() => {
|
|
290
|
+
cleanup();
|
|
291
|
+
jest.restoreAllMocks();
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
describe("the Template column on the monitor list", () => {
|
|
295
|
+
test("is one of the table's columns", async () => {
|
|
296
|
+
await renderTable({ projectId: PROJECT_ID });
|
|
297
|
+
|
|
298
|
+
expect(columnTitled("Template")).toBeDefined();
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
/*
|
|
302
|
+
* Both keys, and through the relation. `templateName` is the label the cell
|
|
303
|
+
* prints; `_id` is what makes it a link to the template — which is the reason
|
|
304
|
+
* to look at the column at all, since the next thing a user does is go and
|
|
305
|
+
* read what the template now says.
|
|
306
|
+
*/
|
|
307
|
+
test("asks for the template's name and id through the relation", async () => {
|
|
308
|
+
await renderTable({ projectId: PROJECT_ID });
|
|
309
|
+
|
|
310
|
+
expect(columnTitled("Template")!.field).toEqual({
|
|
311
|
+
monitorTemplate: {
|
|
312
|
+
_id: true,
|
|
313
|
+
templateName: true,
|
|
314
|
+
},
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
test("renders the template's name, linked to the template", async () => {
|
|
319
|
+
await renderTable({ projectId: PROJECT_ID });
|
|
320
|
+
|
|
321
|
+
render(
|
|
322
|
+
<MemoryRouter>
|
|
323
|
+
{columnTitled("Template")!.getElement!(
|
|
324
|
+
monitorWithTemplate("Production API Health"),
|
|
325
|
+
)}
|
|
326
|
+
</MemoryRouter>,
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
const link: HTMLElement = screen.getByText("Production API Health");
|
|
330
|
+
|
|
331
|
+
expect(link).toBeInTheDocument();
|
|
332
|
+
expect(link.closest("a")?.getAttribute("href")).toContain(
|
|
333
|
+
TEMPLATE_ID.toString(),
|
|
334
|
+
);
|
|
335
|
+
expect(link.closest("a")?.getAttribute("href")).toContain(
|
|
336
|
+
"settings/templates",
|
|
337
|
+
);
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
/*
|
|
341
|
+
* Most monitors are not created from a template, so the empty case is the
|
|
342
|
+
* common one and has to read as "no template" rather than as a value that
|
|
343
|
+
* failed to load.
|
|
344
|
+
*/
|
|
345
|
+
test("renders a dash when the monitor came from no template", async () => {
|
|
346
|
+
await renderTable({ projectId: PROJECT_ID });
|
|
347
|
+
|
|
348
|
+
render(
|
|
349
|
+
<MemoryRouter>
|
|
350
|
+
{columnTitled("Template")!.getElement!(monitorWithTemplate(undefined))}
|
|
351
|
+
</MemoryRouter>,
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
expect(screen.getByText("—")).toBeInTheDocument();
|
|
355
|
+
expect(screen.queryByRole("link")).not.toBeInTheDocument();
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
/*
|
|
359
|
+
* "Export the list and diff it" is exactly what an admin does after a
|
|
360
|
+
* template change, and a cell rendered through getElement contributes nothing
|
|
361
|
+
* usable to the CSV on its own.
|
|
362
|
+
*/
|
|
363
|
+
test("exports the template name, and an empty cell when there is none", async () => {
|
|
364
|
+
await renderTable({ projectId: PROJECT_ID });
|
|
365
|
+
|
|
366
|
+
const column: CapturedColumn = columnTitled("Template")!;
|
|
367
|
+
|
|
368
|
+
expect(column.getExportValue!(monitorWithTemplate("Edge Ping"))).toBe(
|
|
369
|
+
"Edge Ping",
|
|
370
|
+
);
|
|
371
|
+
expect(column.getExportValue!(monitorWithTemplate(undefined))).toBe("");
|
|
372
|
+
});
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
describe("the Template chip in the monitor list's filter bar", () => {
|
|
376
|
+
test("is one of the chips the bar is given", async () => {
|
|
377
|
+
await renderTable({ projectId: PROJECT_ID });
|
|
378
|
+
|
|
379
|
+
expect(facetKeyed(MONITOR_TEMPLATE_FACET_KEY)).toBeDefined();
|
|
380
|
+
expect(facetKeyed(MONITOR_TEMPLATE_FACET_KEY)!.label).toBe("Template");
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
/*
|
|
384
|
+
* The foreign key, not the relation. "is empty" is only answerable of the
|
|
385
|
+
* column — a monitor from no template has no template row to join against —
|
|
386
|
+
* and the same field is what the template page scopes its own table by.
|
|
387
|
+
*/
|
|
388
|
+
test("filters the monitor's template foreign key", async () => {
|
|
389
|
+
await renderTable({ projectId: PROJECT_ID });
|
|
390
|
+
|
|
391
|
+
expect(facetKeyed(MONITOR_TEMPLATE_FACET_KEY)!.queryField).toBe(
|
|
392
|
+
MONITOR_TEMPLATE_FACET_QUERY_FIELD,
|
|
393
|
+
);
|
|
394
|
+
expect(MONITOR_TEMPLATE_FACET_QUERY_FIELD).toBe("monitorTemplateId");
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
test("turns a picked template into an id filter", async () => {
|
|
398
|
+
await renderTable({ projectId: PROJECT_ID });
|
|
399
|
+
|
|
400
|
+
const value: unknown = facetKeyed(MONITOR_TEMPLATE_FACET_KEY)!
|
|
401
|
+
.toQueryValue!([TEMPLATE_ID.toString()], "is");
|
|
402
|
+
|
|
403
|
+
expect(value).toBeInstanceOf(Includes);
|
|
404
|
+
expect(((value as Includes).values as Array<ObjectID>)[0]!.toString()).toBe(
|
|
405
|
+
TEMPLATE_ID.toString(),
|
|
406
|
+
);
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
test("turns 'is empty' into the monitors that came from no template", async () => {
|
|
410
|
+
await renderTable({ projectId: PROJECT_ID });
|
|
411
|
+
|
|
412
|
+
expect(
|
|
413
|
+
facetKeyed(MONITOR_TEMPLATE_FACET_KEY)!.toQueryValue!([], "is_empty"),
|
|
414
|
+
).toBeInstanceOf(IsNull);
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
test("offers the empty operators, which is how that is asked for", async () => {
|
|
418
|
+
await renderTable({ projectId: PROJECT_ID });
|
|
419
|
+
|
|
420
|
+
expect(facetKeyed(MONITOR_TEMPLATE_FACET_KEY)!.supportedOperators).toEqual([
|
|
421
|
+
"is",
|
|
422
|
+
"is_not",
|
|
423
|
+
"is_empty",
|
|
424
|
+
"is_not_empty",
|
|
425
|
+
]);
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
/*
|
|
429
|
+
* A project can hold any number of templates, so the options have to be
|
|
430
|
+
* searched server-side, and a selection restored from a shared link has to be
|
|
431
|
+
* resolvable to a label even when it is not on the first page of results.
|
|
432
|
+
*/
|
|
433
|
+
test("searches its options on the server and resolves restored ones", async () => {
|
|
434
|
+
await renderTable({ projectId: PROJECT_ID });
|
|
435
|
+
|
|
436
|
+
expect(typeof facetKeyed(MONITOR_TEMPLATE_FACET_KEY)!.loadOptions).toBe(
|
|
437
|
+
"function",
|
|
438
|
+
);
|
|
439
|
+
expect(typeof facetKeyed(MONITOR_TEMPLATE_FACET_KEY)!.resolveOptions).toBe(
|
|
440
|
+
"function",
|
|
441
|
+
);
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
/*
|
|
445
|
+
* BaseModelTable builds its request as `{...props.query, ...columnFilterQuery}`,
|
|
446
|
+
* so a column-filter popup over the chip's field would replace the chip's
|
|
447
|
+
* constraint outright, silently, while the chip carried on claiming to apply.
|
|
448
|
+
*/
|
|
449
|
+
test("no column filter competes with it for the same field", async () => {
|
|
450
|
+
const props: CapturedTableProps & {
|
|
451
|
+
filters?: Array<{ field?: Record<string, unknown> }>;
|
|
452
|
+
} = (await renderTable({ projectId: PROJECT_ID })) as CapturedTableProps & {
|
|
453
|
+
filters?: Array<{ field?: Record<string, unknown> }>;
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
for (const filter of props.filters || []) {
|
|
457
|
+
expect(Object.keys(filter.field || {})).not.toContain(
|
|
458
|
+
MONITOR_TEMPLATE_FACET_QUERY_FIELD,
|
|
459
|
+
);
|
|
460
|
+
expect(Object.keys(filter.field || {})).not.toContain("monitorTemplate");
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
describe("the same table scoped to one template", () => {
|
|
466
|
+
/*
|
|
467
|
+
* The template page's Linked Monitors card. A chip here would overwrite the
|
|
468
|
+
* page's own scope rather than narrow it, and the column would repeat one
|
|
469
|
+
* value down every row.
|
|
470
|
+
*/
|
|
471
|
+
test("shows no Template chip", async () => {
|
|
472
|
+
await renderTable({
|
|
473
|
+
projectId: PROJECT_ID,
|
|
474
|
+
monitorTemplateId: TEMPLATE_ID,
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
expect(facetKeyed(MONITOR_TEMPLATE_FACET_KEY)).toBeUndefined();
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
test("shows no Template column", async () => {
|
|
481
|
+
await renderTable({
|
|
482
|
+
projectId: PROJECT_ID,
|
|
483
|
+
monitorTemplateId: TEMPLATE_ID,
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
expect(columnTitled("Template")).toBeUndefined();
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
/*
|
|
490
|
+
* Guards the two above from passing because the table rendered nothing at
|
|
491
|
+
* all: the rest of its columns and chips have to still be there.
|
|
492
|
+
*/
|
|
493
|
+
test("keeps every other column and chip", async () => {
|
|
494
|
+
await renderTable({
|
|
495
|
+
projectId: PROJECT_ID,
|
|
496
|
+
monitorTemplateId: TEMPLATE_ID,
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
expect(columnTitled("Name")).toBeDefined();
|
|
500
|
+
expect(columnTitled("Monitor Status")).toBeDefined();
|
|
501
|
+
expect(columnTitled("Labels")).toBeDefined();
|
|
502
|
+
expect(facetKeyed("currentMonitorStatus")).toBeDefined();
|
|
503
|
+
expect(facetKeyed("monitorType")).toBeDefined();
|
|
504
|
+
});
|
|
505
|
+
});
|