@oneuptime/common 12.0.14 → 12.0.15
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/GlobalOidc.ts +20 -0
- package/Models/DatabaseModels/GlobalSso.ts +20 -0
- package/Models/DatabaseModels/MarketingConversion.ts +19 -9
- package/Models/DatabaseModels/NetworkDeviceLinkRule.ts +46 -0
- package/Models/DatabaseModels/StatusPage.ts +7 -4
- package/Server/API/StatusPageAPI.ts +29 -4
- package/Server/EnvironmentConfig.ts +7 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1787800000000-AddScopeToNetworkDeviceLinkRule.ts +19 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1787900000000-AddRestrictToAttachedProjectsToGlobalSso.ts +40 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
- package/Server/Middleware/SlackAuthorization.ts +43 -6
- package/Server/Middleware/UserAuthorization.ts +236 -26
- package/Server/Middleware/WhatsAppAuthorization.ts +43 -3
- package/Server/Services/GlobalOidcProjectService.ts +126 -1
- package/Server/Services/GlobalOidcService.ts +132 -0
- package/Server/Services/GlobalSsoProjectService.ts +126 -1
- package/Server/Services/GlobalSsoService.ts +132 -0
- package/Server/Services/UserEmailService.ts +4 -4
- package/Server/Utils/CronTab.ts +73 -0
- package/Server/Utils/GlobalSsoAuthorization.ts +175 -0
- package/Server/Utils/Marketing/ConversionUploadProvider.ts +35 -5
- package/Server/Utils/Marketing/Providers/GoogleAds.ts +1 -1
- package/Server/Utils/Marketing/Providers/LinkedIn.ts +1 -1
- package/Server/Utils/Marketing/Providers/Meta.ts +1 -1
- package/Server/Utils/Marketing/Providers/MicrosoftAds.ts +1 -1
- package/Server/Utils/Marketing/Providers/Reddit.ts +1 -1
- package/Server/Utils/Monitor/Criteria/APIRequestCriteria.ts +48 -33
- package/Server/Utils/Monitor/Criteria/DnsMonitorCriteria.ts +35 -30
- package/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.ts +33 -28
- package/Server/Utils/Monitor/Criteria/EvaluateOverTime.ts +475 -47
- package/Server/Utils/Monitor/Criteria/ExternalStatusPageMonitorCriteria.ts +35 -30
- package/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.ts +34 -28
- package/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.ts +33 -28
- package/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.ts +69 -40
- package/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.ts +35 -29
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +8 -0
- package/Server/Utils/Monitor/MonitorResource.ts +161 -21
- package/Server/Utils/StatusPageContentSecurityPolicy.ts +56 -0
- package/Server/Utils/StatusPageCustomizationAccess.ts +117 -0
- package/Tests/Server/API/StatusPageCustomizationOrigin.test.ts +348 -0
- package/Tests/Server/Infrastructure/Postgres/FixTotpOtpUrlAlgorithmMigration.test.ts +9 -59
- package/Tests/Server/Infrastructure/Postgres/SchemaMigrationsOrdering.test.ts +227 -0
- package/Tests/Server/Middleware/SlackAuthorization.test.ts +149 -0
- package/Tests/Server/Middleware/UserAuthorization.test.ts +27 -13
- package/Tests/Server/Middleware/UserAuthorizationGlobalSsoGovernance.test.ts +1455 -0
- package/Tests/Server/Middleware/UserAuthorizationSSOProvider.test.ts +91 -2
- package/Tests/Server/Middleware/WhatsAppAuthorization.test.ts +233 -0
- package/Tests/Server/Services/DiscoveryScanClaimHookFreeSafety.test.ts +21 -8
- package/Tests/Server/Services/GlobalSsoProviderAuthorization.test.ts +1242 -0
- package/Tests/Server/Services/MonitorResourceProbeAgreementHydration.test.ts +459 -0
- package/Tests/Server/Services/MonitorResourceProbeAgreementReuse.test.ts +10 -0
- package/Tests/Server/Types/AnalyticsDatabase/AggregateUtilBucketTimestamp.test.ts +416 -0
- package/Tests/Server/Types/Database/QueryHelperManyToManyAndEmptyValues.test.ts +551 -0
- package/Tests/Server/Types/Database/SelectUtil.test.ts +348 -0
- package/Tests/Server/Utils/CronTab.test.ts +116 -0
- package/Tests/Server/Utils/GlobalSSOToken.test.ts +581 -0
- package/Tests/Server/Utils/GlobalSsoAuthorization.test.ts +1401 -0
- package/Tests/Server/Utils/Marketing/AdUploadableConversionTypes.test.ts +254 -0
- package/Tests/Server/Utils/Marketing/ConversionUploadProvider.test.ts +1 -1
- package/Tests/Server/Utils/Monitor/Criteria/APIRequestCriteriaEvaluateOverTime.test.ts +508 -0
- package/Tests/Server/Utils/Monitor/Criteria/APIRequestCriteriaPortTimings.test.ts +30 -9
- package/Tests/Server/Utils/Monitor/Criteria/EvaluateOverTime.test.ts +987 -0
- package/Tests/Server/Utils/Monitor/Criteria/ExternalStatusPageMonitorCriteria.test.ts +916 -0
- package/Tests/Server/Utils/Monitor/Criteria/ProfileMonitorCriteria.test.ts +382 -0
- package/Tests/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.test.ts +1219 -0
- package/Tests/Server/Utils/Monitor/MonitorResourceIngestedStepSelection.test.ts +86 -0
- package/Tests/Server/Utils/StatusPageContentSecurityPolicy.test.ts +136 -0
- package/Tests/Server/Utils/StatusPageCustomizationAccess.test.ts +223 -0
- package/Tests/Types/Monitor/MonitorStepConfigHelpers.test.ts +165 -0
- package/Tests/Types/NetworkDevice/NetworkDeviceLinkRuleScope.test.ts +215 -0
- package/Tests/UI/Components/Forms/FormSubmitAnalytics.test.tsx +206 -0
- package/Tests/UI/Utils/AIChatExport/ConversationMarkdown.test.ts +774 -0
- package/Tests/UI/Utils/AIChatExport/MarkdownBlocks.test.ts +791 -0
- package/Tests/UI/Utils/AIChatExport/MarkdownSafety.test.ts +544 -0
- package/Tests/Utils/Analytics.test.ts +187 -0
- package/Tests/Utils/Monitor/MonitorMetricType.test.ts +80 -0
- package/Tests/Utils/Monitor/NetworkDeviceLinkRuleUtil.test.ts +1291 -0
- package/Tests/Utils/NetworkDiscovery/UnclaimedScanDiagnosis.test.ts +214 -0
- package/Types/Marketing/MarketingConversion.ts +12 -0
- package/Types/Monitor/CriteriaFilter.ts +9 -0
- package/Types/NetworkDevice/NetworkDeviceLinkRuleScope.ts +51 -0
- package/UI/Components/FormModal/BasicFormModal.tsx +12 -0
- package/UI/Components/Forms/BasicForm.tsx +13 -1
- package/UI/Components/Forms/BasicModelForm.tsx +2 -1
- package/UI/Components/Forms/ModelForm.tsx +10 -1
- package/UI/Components/Forms/Utils/FormAnalyticsName.ts +39 -0
- package/UI/Components/ModelFormModal/ModelFormModal.tsx +6 -1
- package/Utils/Analytics.ts +55 -2
- package/Utils/Monitor/MonitorMetricType.ts +40 -0
- package/Utils/Monitor/NetworkDeviceLinkRuleUtil.ts +587 -37
- package/Utils/NetworkDiscovery/UnclaimedScanDiagnosis.ts +115 -0
- package/build/dist/Models/DatabaseModels/GlobalOidc.js +21 -0
- package/build/dist/Models/DatabaseModels/GlobalOidc.js.map +1 -1
- package/build/dist/Models/DatabaseModels/GlobalSso.js +21 -0
- package/build/dist/Models/DatabaseModels/GlobalSso.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MarketingConversion.js +20 -10
- package/build/dist/Models/DatabaseModels/MarketingConversion.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDeviceLinkRule.js +47 -0
- package/build/dist/Models/DatabaseModels/NetworkDeviceLinkRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPage.js +4 -4
- package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
- package/build/dist/Server/API/StatusPageAPI.js +25 -4
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +6 -0
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787800000000-AddScopeToNetworkDeviceLinkRule.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787800000000-AddScopeToNetworkDeviceLinkRule.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787900000000-AddRestrictToAttachedProjectsToGlobalSso.js +29 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787900000000-AddRestrictToAttachedProjectsToGlobalSso.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/SlackAuthorization.js +28 -4
- package/build/dist/Server/Middleware/SlackAuthorization.js.map +1 -1
- package/build/dist/Server/Middleware/UserAuthorization.js +197 -22
- package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
- package/build/dist/Server/Middleware/WhatsAppAuthorization.js +32 -2
- package/build/dist/Server/Middleware/WhatsAppAuthorization.js.map +1 -1
- package/build/dist/Server/Services/GlobalOidcProjectService.js +105 -0
- package/build/dist/Server/Services/GlobalOidcProjectService.js.map +1 -1
- package/build/dist/Server/Services/GlobalOidcService.js +127 -0
- package/build/dist/Server/Services/GlobalOidcService.js.map +1 -1
- package/build/dist/Server/Services/GlobalSsoProjectService.js +105 -0
- package/build/dist/Server/Services/GlobalSsoProjectService.js.map +1 -1
- package/build/dist/Server/Services/GlobalSsoService.js +127 -0
- package/build/dist/Server/Services/GlobalSsoService.js.map +1 -1
- package/build/dist/Server/Services/UserEmailService.js +4 -4
- package/build/dist/Server/Utils/CronTab.js +66 -0
- package/build/dist/Server/Utils/CronTab.js.map +1 -1
- package/build/dist/Server/Utils/GlobalSsoAuthorization.js +112 -0
- package/build/dist/Server/Utils/GlobalSsoAuthorization.js.map +1 -0
- package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js +17 -1
- package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js.map +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js.map +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js.map +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/Meta.js +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/Meta.js.map +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js.map +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/Reddit.js +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/Reddit.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/APIRequestCriteria.js +37 -35
- package/build/dist/Server/Utils/Monitor/Criteria/APIRequestCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/DnsMonitorCriteria.js +21 -25
- package/build/dist/Server/Utils/Monitor/Criteria/DnsMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.js +20 -22
- package/build/dist/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/EvaluateOverTime.js +370 -42
- package/build/dist/Server/Utils/Monitor/Criteria/EvaluateOverTime.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/ExternalStatusPageMonitorCriteria.js +21 -25
- package/build/dist/Server/Utils/Monitor/Criteria/ExternalStatusPageMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.js +21 -25
- package/build/dist/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.js +21 -25
- package/build/dist/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.js +64 -52
- package/build/dist/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.js +25 -29
- package/build/dist/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +8 -0
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorResource.js +123 -23
- package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
- package/build/dist/Server/Utils/StatusPageContentSecurityPolicy.js +39 -0
- package/build/dist/Server/Utils/StatusPageContentSecurityPolicy.js.map +1 -0
- package/build/dist/Server/Utils/StatusPageCustomizationAccess.js +85 -0
- package/build/dist/Server/Utils/StatusPageCustomizationAccess.js.map +1 -0
- package/build/dist/Types/Marketing/MarketingConversion.js +10 -0
- package/build/dist/Types/Marketing/MarketingConversion.js.map +1 -1
- package/build/dist/Types/Monitor/CriteriaFilter.js +1 -0
- package/build/dist/Types/Monitor/CriteriaFilter.js.map +1 -1
- package/build/dist/Types/NetworkDevice/NetworkDeviceLinkRuleScope.js +46 -0
- package/build/dist/Types/NetworkDevice/NetworkDeviceLinkRuleScope.js.map +1 -0
- package/build/dist/UI/Components/FormModal/BasicFormModal.js +2 -1
- package/build/dist/UI/Components/FormModal/BasicFormModal.js.map +1 -1
- package/build/dist/UI/Components/Forms/BasicForm.js +10 -1
- package/build/dist/UI/Components/Forms/BasicForm.js.map +1 -1
- package/build/dist/UI/Components/Forms/BasicModelForm.js +2 -1
- package/build/dist/UI/Components/Forms/BasicModelForm.js.map +1 -1
- package/build/dist/UI/Components/Forms/ModelForm.js +4 -1
- package/build/dist/UI/Components/Forms/ModelForm.js.map +1 -1
- package/build/dist/UI/Components/Forms/Utils/FormAnalyticsName.js +33 -0
- package/build/dist/UI/Components/Forms/Utils/FormAnalyticsName.js.map +1 -0
- package/build/dist/UI/Components/ModelFormModal/ModelFormModal.js +2 -1
- package/build/dist/UI/Components/ModelFormModal/ModelFormModal.js.map +1 -1
- package/build/dist/Utils/Analytics.js +50 -2
- package/build/dist/Utils/Analytics.js.map +1 -1
- package/build/dist/Utils/Monitor/MonitorMetricType.js +37 -0
- package/build/dist/Utils/Monitor/MonitorMetricType.js.map +1 -1
- package/build/dist/Utils/Monitor/NetworkDeviceLinkRuleUtil.js +318 -37
- package/build/dist/Utils/Monitor/NetworkDeviceLinkRuleUtil.js.map +1 -1
- package/build/dist/Utils/NetworkDiscovery/UnclaimedScanDiagnosis.js +87 -0
- package/build/dist/Utils/NetworkDiscovery/UnclaimedScanDiagnosis.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import NetworkDeviceLinkRuleScope, {
|
|
2
|
+
NetworkDeviceLinkRuleScopeUtil,
|
|
3
|
+
} from "../../../Types/NetworkDevice/NetworkDeviceLinkRuleScope";
|
|
4
|
+
|
|
5
|
+
/*
|
|
6
|
+
* `scope` was added to a table that already had rules in it, so every rule
|
|
7
|
+
* written before the column existed holds NULL — and each of those rules is
|
|
8
|
+
* currently drawing a topology somebody has already looked at and trusted.
|
|
9
|
+
* NULL therefore has to read as Project, because Project is exactly what
|
|
10
|
+
* those rules have always meant: one parent across whatever device set the
|
|
11
|
+
* query handed the resolver. Read NULL as Site instead and every legacy rule
|
|
12
|
+
* quietly starts asking a different question, re-pointing cables on maps
|
|
13
|
+
* nobody re-opened. That is why the default lives in `parse` rather than in a
|
|
14
|
+
* `=== "Site"` comparison scattered over the call sites: one place to be
|
|
15
|
+
* right, and it is pinned here.
|
|
16
|
+
*
|
|
17
|
+
* The mirror of the same worry is the unrecognised value. The column is free
|
|
18
|
+
* text (the NetworkDeviceMonitoringMethod precedent), so a typo, an older
|
|
19
|
+
* client, or a future scope name that this build has never heard of can all
|
|
20
|
+
* land in it. Every one of them has to fall back to the behaviour that was
|
|
21
|
+
* already on screen rather than to the new one.
|
|
22
|
+
*/
|
|
23
|
+
describe("NetworkDeviceLinkRuleScope enum", () => {
|
|
24
|
+
/*
|
|
25
|
+
* These strings are persisted values, not display labels. Renaming one is
|
|
26
|
+
* a silent data migration: every row already holding the old spelling stops
|
|
27
|
+
* being recognised and — by the fallback above — quietly reverts to
|
|
28
|
+
* Project, with no error anywhere to say so. Change these only alongside a
|
|
29
|
+
* migration that rewrites the column.
|
|
30
|
+
*/
|
|
31
|
+
test("Project is persisted as the string Project", () => {
|
|
32
|
+
expect(NetworkDeviceLinkRuleScope.Project).toBe("Project");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("Site is persisted as the string Site", () => {
|
|
36
|
+
expect(NetworkDeviceLinkRuleScope.Site).toBe("Site");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("has exactly the two known scopes", () => {
|
|
40
|
+
expect(Object.values(NetworkDeviceLinkRuleScope).sort()).toEqual([
|
|
41
|
+
"Project",
|
|
42
|
+
"Site",
|
|
43
|
+
]);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
/*
|
|
48
|
+
* One table, two consumers. `parse` and `isSiteScoped` are asserted against
|
|
49
|
+
* the same rows further down so the pair cannot drift apart: a caller that
|
|
50
|
+
* branches on `isSiteScoped` and a caller that switches on `parse` must never
|
|
51
|
+
* disagree about the same stored string.
|
|
52
|
+
*/
|
|
53
|
+
const PROJECT_INPUTS: Array<string | null | undefined> = [
|
|
54
|
+
// The pre-column rows, and the shapes an empty form field arrives as.
|
|
55
|
+
undefined,
|
|
56
|
+
null,
|
|
57
|
+
"",
|
|
58
|
+
" ",
|
|
59
|
+
// The value written by anything that does set the column explicitly.
|
|
60
|
+
"Project",
|
|
61
|
+
"project",
|
|
62
|
+
"PROJECT",
|
|
63
|
+
" Project ",
|
|
64
|
+
/*
|
|
65
|
+
* Near-misses that must NOT be read as site scope. "Sites" and
|
|
66
|
+
* "site-scoped" are the plausible typo and the plausible rename; matching
|
|
67
|
+
* them loosely would turn a mistyped rule into one that silently redraws.
|
|
68
|
+
*/
|
|
69
|
+
"Sites",
|
|
70
|
+
"site-scoped",
|
|
71
|
+
"anything",
|
|
72
|
+
// Falsy-looking text is still just unrecognised text, not a flag.
|
|
73
|
+
"0",
|
|
74
|
+
"false",
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
const SITE_INPUTS: Array<string> = [
|
|
78
|
+
"Site",
|
|
79
|
+
"site",
|
|
80
|
+
"SITE",
|
|
81
|
+
" site ",
|
|
82
|
+
" SiTe ",
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
interface ScopeCase {
|
|
86
|
+
value: string | null | undefined;
|
|
87
|
+
expected: NetworkDeviceLinkRuleScope;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const ALL_CASES: Array<ScopeCase> = [
|
|
91
|
+
...PROJECT_INPUTS.map((value: string | null | undefined): ScopeCase => {
|
|
92
|
+
return { value: value, expected: NetworkDeviceLinkRuleScope.Project };
|
|
93
|
+
}),
|
|
94
|
+
...SITE_INPUTS.map((value: string): ScopeCase => {
|
|
95
|
+
return { value: value, expected: NetworkDeviceLinkRuleScope.Site };
|
|
96
|
+
}),
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
describe("NetworkDeviceLinkRuleScopeUtil.parse", () => {
|
|
100
|
+
test.each(PROJECT_INPUTS)(
|
|
101
|
+
"reads %p as Project",
|
|
102
|
+
(value: string | null | undefined) => {
|
|
103
|
+
expect(NetworkDeviceLinkRuleScopeUtil.parse(value)).toBe(
|
|
104
|
+
NetworkDeviceLinkRuleScope.Project,
|
|
105
|
+
);
|
|
106
|
+
},
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
test.each(SITE_INPUTS)(
|
|
110
|
+
"reads %p as Site, case- and whitespace-insensitively",
|
|
111
|
+
(value: string) => {
|
|
112
|
+
expect(NetworkDeviceLinkRuleScopeUtil.parse(value)).toBe(
|
|
113
|
+
NetworkDeviceLinkRuleScope.Site,
|
|
114
|
+
);
|
|
115
|
+
},
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
/*
|
|
119
|
+
* Only an exact (trimmed, lowercased) "site" opts in. Substring matching
|
|
120
|
+
* would make "not site" or "site (deprecated)" flip the rule's meaning,
|
|
121
|
+
* which is the one direction of mistake this type refuses to make.
|
|
122
|
+
*/
|
|
123
|
+
test("does not treat a string merely containing 'site' as site-scoped", () => {
|
|
124
|
+
expect(NetworkDeviceLinkRuleScopeUtil.parse("per site")).toBe(
|
|
125
|
+
NetworkDeviceLinkRuleScope.Project,
|
|
126
|
+
);
|
|
127
|
+
expect(NetworkDeviceLinkRuleScopeUtil.parse("not site")).toBe(
|
|
128
|
+
NetworkDeviceLinkRuleScope.Project,
|
|
129
|
+
);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
/*
|
|
133
|
+
* The return value is always one of the two enum members — never the raw
|
|
134
|
+
* column text passed straight back. Callers switch on it, and a passthrough
|
|
135
|
+
* would send an unrecognised spelling into a branch that does not exist.
|
|
136
|
+
*/
|
|
137
|
+
test("returns an enum member for every case in the table, never the raw input", () => {
|
|
138
|
+
for (const testCase of ALL_CASES) {
|
|
139
|
+
const parsed: NetworkDeviceLinkRuleScope =
|
|
140
|
+
NetworkDeviceLinkRuleScopeUtil.parse(testCase.value);
|
|
141
|
+
|
|
142
|
+
expect(Object.values(NetworkDeviceLinkRuleScope)).toContain(parsed);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
/*
|
|
147
|
+
* The parser sits in the middle of rendering a topology map: throwing here
|
|
148
|
+
* would take out the whole map over one bad cell, when the safe answer
|
|
149
|
+
* (Project, what the rule drew yesterday) is right there. So nothing in the
|
|
150
|
+
* declared signature may throw — and neither may the falsy non-string
|
|
151
|
+
* shapes a loosely typed caller or a hand-rolled JSON body can smuggle past
|
|
152
|
+
* TypeScript, which the `|| ""` guard absorbs.
|
|
153
|
+
*/
|
|
154
|
+
test("never throws for any value in or near its declared signature", () => {
|
|
155
|
+
const inputs: Array<string | null | undefined> = [
|
|
156
|
+
...ALL_CASES.map((testCase: ScopeCase): string | null | undefined => {
|
|
157
|
+
return testCase.value;
|
|
158
|
+
}),
|
|
159
|
+
"\tSITE\n",
|
|
160
|
+
"\n project \t",
|
|
161
|
+
"Site\u0000",
|
|
162
|
+
"🙂",
|
|
163
|
+
];
|
|
164
|
+
|
|
165
|
+
for (const input of inputs) {
|
|
166
|
+
expect((): NetworkDeviceLinkRuleScope => {
|
|
167
|
+
return NetworkDeviceLinkRuleScopeUtil.parse(input);
|
|
168
|
+
}).not.toThrow();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/*
|
|
172
|
+
* Not strings at all. 0/false/NaN are what a JSON body or a permissive
|
|
173
|
+
* ORM can hand over in place of "no scope set", and they have to land on
|
|
174
|
+
* the same safe default rather than blow up.
|
|
175
|
+
*/
|
|
176
|
+
const nonStrings: Array<unknown> = [0, false, NaN];
|
|
177
|
+
|
|
178
|
+
for (const input of nonStrings) {
|
|
179
|
+
expect(
|
|
180
|
+
NetworkDeviceLinkRuleScopeUtil.parse(input as unknown as string),
|
|
181
|
+
).toBe(NetworkDeviceLinkRuleScope.Project);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
describe("NetworkDeviceLinkRuleScopeUtil.isSiteScoped", () => {
|
|
187
|
+
test.each(SITE_INPUTS)("is true for %p", (value: string) => {
|
|
188
|
+
expect(NetworkDeviceLinkRuleScopeUtil.isSiteScoped(value)).toBe(true);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test.each(PROJECT_INPUTS)(
|
|
192
|
+
"is false for %p",
|
|
193
|
+
(value: string | null | undefined) => {
|
|
194
|
+
expect(NetworkDeviceLinkRuleScopeUtil.isSiteScoped(value)).toBe(false);
|
|
195
|
+
},
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
/*
|
|
199
|
+
* The two entry points are asserted against the same table in the same
|
|
200
|
+
* loop, so neither can be extended (a new alias, a new scope) without the
|
|
201
|
+
* other. `NetworkDeviceLinkRuleUtil` branches on `isSiteScoped` while the
|
|
202
|
+
* dashboard's rule list renders off it too — if they ever disagreed, a rule
|
|
203
|
+
* would be labelled one way and resolved the other.
|
|
204
|
+
*/
|
|
205
|
+
test("agrees with parse on every case in the table", () => {
|
|
206
|
+
for (const testCase of ALL_CASES) {
|
|
207
|
+
expect(NetworkDeviceLinkRuleScopeUtil.parse(testCase.value)).toBe(
|
|
208
|
+
testCase.expected,
|
|
209
|
+
);
|
|
210
|
+
expect(NetworkDeviceLinkRuleScopeUtil.isSiteScoped(testCase.value)).toBe(
|
|
211
|
+
testCase.expected === NetworkDeviceLinkRuleScope.Site,
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
});
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import "@testing-library/jest-dom";
|
|
2
|
+
import { beforeEach, describe, expect, jest, test } from "@jest/globals";
|
|
3
|
+
import { render, screen, waitFor } from "@testing-library/react";
|
|
4
|
+
import userEvent from "@testing-library/user-event";
|
|
5
|
+
import { UserEvent } from "@testing-library/user-event/dist/types/setup/setup";
|
|
6
|
+
import React from "react";
|
|
7
|
+
import getJestMockFunction, { MockFunction } from "../../../MockType";
|
|
8
|
+
|
|
9
|
+
/*
|
|
10
|
+
* The "FORM SUBMIT" event is what names a conversion in PostHog and in the GTM
|
|
11
|
+
* dataLayer. Every wrapper around BasicForm has to hand it something
|
|
12
|
+
* meaningful, and BasicForm has to stay silent when nothing meaningful exists.
|
|
13
|
+
* An event named after a missing prop reads as "FORM SUBMIT: undefined" and
|
|
14
|
+
* collapses every form in the product into one indistinguishable conversion.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const captureMock: MockFunction = getJestMockFunction();
|
|
18
|
+
|
|
19
|
+
jest.mock("../../../../UI/Utils/Analytics", () => {
|
|
20
|
+
return {
|
|
21
|
+
__esModule: true,
|
|
22
|
+
default: {
|
|
23
|
+
capture: (...args: Array<any>) => {
|
|
24
|
+
return captureMock(...args);
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
import BasicForm from "../../../../UI/Components/Forms/BasicForm";
|
|
31
|
+
import BasicFormModal from "../../../../UI/Components/FormModal/BasicFormModal";
|
|
32
|
+
import Fields from "../../../../UI/Components/Forms/Types/Fields";
|
|
33
|
+
import FormFieldSchemaType from "../../../../UI/Components/Forms/Types/FormFieldSchemaType";
|
|
34
|
+
import FormValues from "../../../../UI/Components/Forms/Types/FormValues";
|
|
35
|
+
import FormAnalyticsName from "../../../../UI/Components/Forms/Utils/FormAnalyticsName";
|
|
36
|
+
|
|
37
|
+
const fields: Fields<FormValues<any>> = [
|
|
38
|
+
{
|
|
39
|
+
field: {
|
|
40
|
+
label: true,
|
|
41
|
+
},
|
|
42
|
+
title: "Label",
|
|
43
|
+
fieldType: FormFieldSchemaType.Text,
|
|
44
|
+
dataTestId: "label",
|
|
45
|
+
},
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
function setupUser(): UserEvent {
|
|
49
|
+
return userEvent.setup({ delay: null });
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
type CapturedEventNamesFunction = () => Array<string>;
|
|
53
|
+
|
|
54
|
+
const capturedEventNames: CapturedEventNamesFunction = (): Array<string> => {
|
|
55
|
+
return captureMock.mock.calls.map((call: Array<any>) => {
|
|
56
|
+
return call[0] as string;
|
|
57
|
+
});
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
describe("FORM SUBMIT analytics naming", () => {
|
|
61
|
+
beforeEach(() => {
|
|
62
|
+
captureMock.mockClear();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe("FormAnalyticsName.resolve", () => {
|
|
66
|
+
test("takes the first usable candidate", () => {
|
|
67
|
+
expect(FormAnalyticsName.resolve("Create Monitor", "Fallback")).toBe(
|
|
68
|
+
"Create Monitor",
|
|
69
|
+
);
|
|
70
|
+
expect(FormAnalyticsName.resolve(undefined, "Fallback")).toBe("Fallback");
|
|
71
|
+
expect(FormAnalyticsName.resolve(null, " Trimmed ")).toBe("Trimmed");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("rejects candidates that are, or embed, a stringified empty value", () => {
|
|
75
|
+
expect(FormAnalyticsName.resolve("undefined")).toBeUndefined();
|
|
76
|
+
expect(FormAnalyticsName.resolve("null")).toBeUndefined();
|
|
77
|
+
expect(FormAnalyticsName.resolve("Duplicate undefined")).toBeUndefined();
|
|
78
|
+
expect(FormAnalyticsName.resolve("Edit null")).toBeUndefined();
|
|
79
|
+
|
|
80
|
+
// A rejected candidate falls through to the next one.
|
|
81
|
+
expect(
|
|
82
|
+
FormAnalyticsName.resolve("Duplicate undefined", "Duplicate Monitor"),
|
|
83
|
+
).toBe("Duplicate Monitor");
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("returns undefined when no candidate is usable", () => {
|
|
87
|
+
expect(FormAnalyticsName.resolve()).toBeUndefined();
|
|
88
|
+
expect(
|
|
89
|
+
FormAnalyticsName.resolve(undefined, null, "", " "),
|
|
90
|
+
).toBeUndefined();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("leaves real words that merely look like empty values alone", () => {
|
|
94
|
+
expect(FormAnalyticsName.resolve("Set Nullable Columns")).toBe(
|
|
95
|
+
"Set Nullable Columns",
|
|
96
|
+
);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe("BasicForm", () => {
|
|
101
|
+
test("captures the form name on submit", async () => {
|
|
102
|
+
const onSubmit: MockFunction = getJestMockFunction();
|
|
103
|
+
|
|
104
|
+
render(
|
|
105
|
+
<BasicForm
|
|
106
|
+
fields={fields}
|
|
107
|
+
id="named-form"
|
|
108
|
+
name="Create Widget"
|
|
109
|
+
initialValues={{ label: "" }}
|
|
110
|
+
onSubmit={onSubmit}
|
|
111
|
+
submitButtonText="Submit"
|
|
112
|
+
/>,
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
const user: UserEvent = setupUser();
|
|
116
|
+
await user.click(screen.getByTestId("Submit"));
|
|
117
|
+
|
|
118
|
+
await waitFor(() => {
|
|
119
|
+
expect(onSubmit).toHaveBeenCalled();
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
expect(capturedEventNames()).toEqual(["FORM SUBMIT: Create Widget"]);
|
|
123
|
+
}, 30000);
|
|
124
|
+
|
|
125
|
+
test("stays silent when the form has no name", async () => {
|
|
126
|
+
const onSubmit: MockFunction = getJestMockFunction();
|
|
127
|
+
|
|
128
|
+
render(
|
|
129
|
+
<BasicForm
|
|
130
|
+
fields={fields}
|
|
131
|
+
id="unnamed-form"
|
|
132
|
+
initialValues={{ label: "" }}
|
|
133
|
+
onSubmit={onSubmit}
|
|
134
|
+
submitButtonText="Submit"
|
|
135
|
+
/>,
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
const user: UserEvent = setupUser();
|
|
139
|
+
await user.click(screen.getByTestId("Submit"));
|
|
140
|
+
|
|
141
|
+
// The form still submits -- only the analytics event is skipped.
|
|
142
|
+
await waitFor(() => {
|
|
143
|
+
expect(onSubmit).toHaveBeenCalled();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
expect(captureMock).not.toHaveBeenCalled();
|
|
147
|
+
}, 30000);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
describe("BasicFormModal", () => {
|
|
151
|
+
test("falls back to the modal title when the caller passes no name", async () => {
|
|
152
|
+
const onSubmit: MockFunction = getJestMockFunction();
|
|
153
|
+
|
|
154
|
+
render(
|
|
155
|
+
<BasicFormModal<FormValues<any>>
|
|
156
|
+
title="Add Probe to Monitors"
|
|
157
|
+
submitButtonText="Save"
|
|
158
|
+
onSubmit={onSubmit}
|
|
159
|
+
formProps={{
|
|
160
|
+
fields: fields,
|
|
161
|
+
initialValues: { label: "" },
|
|
162
|
+
}}
|
|
163
|
+
/>,
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
const user: UserEvent = setupUser();
|
|
167
|
+
await user.click(screen.getByTestId("modal-footer-submit-button"));
|
|
168
|
+
|
|
169
|
+
await waitFor(() => {
|
|
170
|
+
expect(onSubmit).toHaveBeenCalled();
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
expect(capturedEventNames()).toEqual([
|
|
174
|
+
"FORM SUBMIT: Add Probe to Monitors",
|
|
175
|
+
]);
|
|
176
|
+
}, 30000);
|
|
177
|
+
|
|
178
|
+
test("prefers a name the caller supplied over the modal title", async () => {
|
|
179
|
+
const onSubmit: MockFunction = getJestMockFunction();
|
|
180
|
+
|
|
181
|
+
render(
|
|
182
|
+
<BasicFormModal<FormValues<any>>
|
|
183
|
+
title="Add Probe to Monitors"
|
|
184
|
+
name="Monitor > Bulk Add Probe"
|
|
185
|
+
submitButtonText="Save"
|
|
186
|
+
onSubmit={onSubmit}
|
|
187
|
+
formProps={{
|
|
188
|
+
fields: fields,
|
|
189
|
+
initialValues: { label: "" },
|
|
190
|
+
}}
|
|
191
|
+
/>,
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
const user: UserEvent = setupUser();
|
|
195
|
+
await user.click(screen.getByTestId("modal-footer-submit-button"));
|
|
196
|
+
|
|
197
|
+
await waitFor(() => {
|
|
198
|
+
expect(onSubmit).toHaveBeenCalled();
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
expect(capturedEventNames()).toEqual([
|
|
202
|
+
"FORM SUBMIT: Monitor > Bulk Add Probe",
|
|
203
|
+
]);
|
|
204
|
+
}, 30000);
|
|
205
|
+
});
|
|
206
|
+
});
|