@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,214 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MAX_SCAN_STATUS_MESSAGE_LENGTH,
|
|
3
|
+
UNCLAIMED_PENDING_MINUTES,
|
|
4
|
+
UnclaimedScanProbeState,
|
|
5
|
+
buildUnclaimedScanDiagnosis,
|
|
6
|
+
} from "../../../Utils/NetworkDiscovery/UnclaimedScanDiagnosis";
|
|
7
|
+
import OneUptimeDate from "../../../Types/Date";
|
|
8
|
+
import { describe, expect, test } from "@jest/globals";
|
|
9
|
+
|
|
10
|
+
/*
|
|
11
|
+
* The sentence a stuck discovery scan gets to say for itself.
|
|
12
|
+
*
|
|
13
|
+
* A scan leaves "Pending" only when a probe asks for it. When no probe ever
|
|
14
|
+
* does, nothing times out and nothing fails — the row simply never moves, and
|
|
15
|
+
* before this existed the product said nothing at all about it. OneUptime
|
|
16
|
+
* issue #3287 is that silence: four scans submitted over an hour, all
|
|
17
|
+
* "Pending", an em-dash where the result goes, and no way to tell a scan
|
|
18
|
+
* queued a second ago from one no probe will ever pick up.
|
|
19
|
+
*
|
|
20
|
+
* So these tests are about the WORDS, not a state machine: whether an operator
|
|
21
|
+
* reading the row learns which of the two very different problems they have,
|
|
22
|
+
* and whether the sentence survives the varchar(500) column it is written to.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
const disconnectedProbe: UnclaimedScanProbeState = {
|
|
26
|
+
probeName: "Datacentre Probe",
|
|
27
|
+
isProbeConnected: false,
|
|
28
|
+
lastAliveAt: OneUptimeDate.getSomeHoursAgo(6),
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const connectedProbe: UnclaimedScanProbeState = {
|
|
32
|
+
probeName: "Datacentre Probe",
|
|
33
|
+
isProbeConnected: true,
|
|
34
|
+
lastAliveAt: OneUptimeDate.getCurrentDate(),
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
describe("buildUnclaimedScanDiagnosis — the probe is not connected", () => {
|
|
38
|
+
test("names the probe the operator chose", () => {
|
|
39
|
+
expect(buildUnclaimedScanDiagnosis(disconnectedProbe)).toContain(
|
|
40
|
+
'Probe "Datacentre Probe"',
|
|
41
|
+
);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("says the probe is not connected, which is the actual fault", () => {
|
|
45
|
+
expect(buildUnclaimedScanDiagnosis(disconnectedProbe)).toContain(
|
|
46
|
+
"is not connected to OneUptime",
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
/*
|
|
51
|
+
* "Last seen 6 hours ago" is the difference between "my probe just
|
|
52
|
+
* restarted" and "that probe has been gone since the upgrade".
|
|
53
|
+
*/
|
|
54
|
+
test("says when the probe was last seen", () => {
|
|
55
|
+
expect(buildUnclaimedScanDiagnosis(disconnectedProbe)).toContain(
|
|
56
|
+
"It was last seen",
|
|
57
|
+
);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("a probe that has never connected says exactly that, not 'last seen never'", () => {
|
|
61
|
+
const message: string = buildUnclaimedScanDiagnosis({
|
|
62
|
+
probeName: "Brand New Probe",
|
|
63
|
+
isProbeConnected: false,
|
|
64
|
+
lastAliveAt: null,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
expect(message).toContain("It has never connected.");
|
|
68
|
+
expect(message).not.toContain("last seen");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("a missing lastAlive is treated the same as never connecting", () => {
|
|
72
|
+
expect(
|
|
73
|
+
buildUnclaimedScanDiagnosis({
|
|
74
|
+
isProbeConnected: false,
|
|
75
|
+
}),
|
|
76
|
+
).toContain("It has never connected.");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
/*
|
|
80
|
+
* The scan is annotated, not failed, so the operator needs to know they do
|
|
81
|
+
* not have to re-create it.
|
|
82
|
+
*/
|
|
83
|
+
test("promises the scan still runs once the probe returns", () => {
|
|
84
|
+
expect(buildUnclaimedScanDiagnosis(disconnectedProbe)).toContain(
|
|
85
|
+
"as soon as the probe reconnects",
|
|
86
|
+
);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe("buildUnclaimedScanDiagnosis — the probe IS connected", () => {
|
|
91
|
+
/*
|
|
92
|
+
* A completely different problem with a completely different fix: the probe
|
|
93
|
+
* is running and authenticating, but its discovery job is not claiming
|
|
94
|
+
* scans — an old image, a wedged sweep, or a probe that can reach the
|
|
95
|
+
* heartbeat route but not the probe-ingest one.
|
|
96
|
+
*/
|
|
97
|
+
test("does not blame the connection", () => {
|
|
98
|
+
const message: string = buildUnclaimedScanDiagnosis(connectedProbe);
|
|
99
|
+
|
|
100
|
+
expect(message).toContain("is connected but has not picked this scan up");
|
|
101
|
+
expect(message).not.toContain("is not connected");
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("names the two things worth checking", () => {
|
|
105
|
+
const message: string = buildUnclaimedScanDiagnosis(connectedProbe);
|
|
106
|
+
|
|
107
|
+
expect(message).toContain("version that supports");
|
|
108
|
+
expect(message).toContain("probe-ingest");
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("quotes the threshold it actually waited for", () => {
|
|
112
|
+
expect(buildUnclaimedScanDiagnosis(connectedProbe)).toContain(
|
|
113
|
+
`${UNCLAIMED_PENDING_MINUTES} minutes`,
|
|
114
|
+
);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("the two branches are genuinely different sentences", () => {
|
|
118
|
+
expect(buildUnclaimedScanDiagnosis(connectedProbe)).not.toBe(
|
|
119
|
+
buildUnclaimedScanDiagnosis(disconnectedProbe),
|
|
120
|
+
);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe("buildUnclaimedScanDiagnosis — a probe with no readable name", () => {
|
|
125
|
+
test("falls back to a phrase that still reads as a sentence", () => {
|
|
126
|
+
const message: string = buildUnclaimedScanDiagnosis({
|
|
127
|
+
isProbeConnected: false,
|
|
128
|
+
lastAliveAt: null,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
expect(message).toContain("The assigned probe is not connected");
|
|
132
|
+
expect(message).not.toContain('Probe ""');
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("an empty-string name is treated as no name", () => {
|
|
136
|
+
expect(
|
|
137
|
+
buildUnclaimedScanDiagnosis({
|
|
138
|
+
probeName: "",
|
|
139
|
+
isProbeConnected: true,
|
|
140
|
+
}),
|
|
141
|
+
).toContain("The assigned probe");
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
describe("buildUnclaimedScanDiagnosis — it has to fit the column", () => {
|
|
146
|
+
/*
|
|
147
|
+
* statusMessage is a varchar(500) and this message is written through the
|
|
148
|
+
* full update pipeline, which does NOT clamp. An over-long value throws, and
|
|
149
|
+
* the diagnosis is lost precisely when it is most needed.
|
|
150
|
+
*/
|
|
151
|
+
test("the column ceiling is the one the model declares", () => {
|
|
152
|
+
expect(MAX_SCAN_STATUS_MESSAGE_LENGTH).toBe(500);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test("every message fits, for both branches", () => {
|
|
156
|
+
for (const probeState of [disconnectedProbe, connectedProbe]) {
|
|
157
|
+
expect(
|
|
158
|
+
buildUnclaimedScanDiagnosis(probeState).length,
|
|
159
|
+
).toBeLessThanOrEqual(MAX_SCAN_STATUS_MESSAGE_LENGTH);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
/*
|
|
164
|
+
* A probe name is a ShortText column, so 100 characters is the real worst
|
|
165
|
+
* case. Feeding well past it proves the truncation is what bounds the
|
|
166
|
+
* message rather than the caller's good manners.
|
|
167
|
+
*/
|
|
168
|
+
test("an absurdly long probe name cannot overflow the column", () => {
|
|
169
|
+
const longName: string = "N".repeat(400);
|
|
170
|
+
|
|
171
|
+
for (const isProbeConnected of [true, false]) {
|
|
172
|
+
const message: string = buildUnclaimedScanDiagnosis({
|
|
173
|
+
probeName: longName,
|
|
174
|
+
isProbeConnected: isProbeConnected,
|
|
175
|
+
lastAliveAt: OneUptimeDate.getSomeHoursAgo(200),
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
expect(message.length).toBeLessThanOrEqual(
|
|
179
|
+
MAX_SCAN_STATUS_MESSAGE_LENGTH,
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test("a long name is cut with an ellipsis rather than silently dropped", () => {
|
|
185
|
+
const message: string = buildUnclaimedScanDiagnosis({
|
|
186
|
+
probeName: "N".repeat(400),
|
|
187
|
+
isProbeConnected: false,
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
expect(message).toContain("NNN");
|
|
191
|
+
expect(message).toContain("…");
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test("a name that fits is left exactly as the operator typed it", () => {
|
|
195
|
+
expect(
|
|
196
|
+
buildUnclaimedScanDiagnosis({
|
|
197
|
+
probeName: "core-sw-probe-01",
|
|
198
|
+
isProbeConnected: false,
|
|
199
|
+
}),
|
|
200
|
+
).toContain('Probe "core-sw-probe-01"');
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
describe("the grace period", () => {
|
|
205
|
+
/*
|
|
206
|
+
* The probe polls every minute, so this is a count of consecutive failed
|
|
207
|
+
* polls. Too short and a probe redeploy annotates every queued scan; too
|
|
208
|
+
* long and the operator has given up before the product says anything.
|
|
209
|
+
*/
|
|
210
|
+
test("is long enough to ride out a probe restart and short enough to be useful", () => {
|
|
211
|
+
expect(UNCLAIMED_PENDING_MINUTES).toBeGreaterThanOrEqual(5);
|
|
212
|
+
expect(UNCLAIMED_PENDING_MINUTES).toBeLessThanOrEqual(60);
|
|
213
|
+
});
|
|
214
|
+
});
|
|
@@ -1,8 +1,20 @@
|
|
|
1
1
|
export enum MarketingConversionType {
|
|
2
2
|
SignUp = "SignUp",
|
|
3
|
+
// A verified Cal.com booking. See Docs/analytics/enterprise-conversion-tracking.md.
|
|
4
|
+
MeetingBooked = "MeetingBooked",
|
|
3
5
|
PaidSubscription = "PaidSubscription",
|
|
4
6
|
}
|
|
5
7
|
|
|
8
|
+
/*
|
|
9
|
+
* Conversion types that ad platforms have an explicit conversion-action
|
|
10
|
+
* mapping for. Every provider picks its platform conversion action by
|
|
11
|
+
* branching on SignUp vs not-SignUp, so a type that is not listed here would
|
|
12
|
+
* be uploaded as a purchase — add a type only once every provider has a
|
|
13
|
+
* mapping for it. Types left out stay ledger-only and are recorded as Skipped.
|
|
14
|
+
*/
|
|
15
|
+
export const AdUploadableMarketingConversionTypes: Array<MarketingConversionType> =
|
|
16
|
+
[MarketingConversionType.SignUp, MarketingConversionType.PaidSubscription];
|
|
17
|
+
|
|
6
18
|
export enum MarketingConversionUploadStatus {
|
|
7
19
|
Uploaded = "Uploaded",
|
|
8
20
|
Failed = "Failed",
|
|
@@ -224,6 +224,14 @@ export enum EvaluateOverTimeMinutes {
|
|
|
224
224
|
export interface EvaluateOverTimeOptions {
|
|
225
225
|
timeValueInMinutes: number | undefined;
|
|
226
226
|
evaluateOverTimeType: EvaluateOverTimeType | undefined;
|
|
227
|
+
/*
|
|
228
|
+
* Governs what happens when the evaluation window cannot back the filter -
|
|
229
|
+
* either nothing was recorded in it, or the monitor has not been running
|
|
230
|
+
* long enough to cover it yet. Defaults to Ignore when unset, so a filter
|
|
231
|
+
* asked to look at the last N minutes waits for N minutes of data instead
|
|
232
|
+
* of firing off the reading that happens to have just arrived.
|
|
233
|
+
*/
|
|
234
|
+
onNoDataPolicy?: NoDataPolicy | undefined;
|
|
227
235
|
}
|
|
228
236
|
|
|
229
237
|
export interface CriteriaFilter {
|
|
@@ -449,5 +457,6 @@ export const CriteriaFilterSchema: ZodSchema = Zod.object({
|
|
|
449
457
|
evaluateOverTimeOptions: Zod.object({
|
|
450
458
|
timeValueInMinutes: Zod.number().optional(),
|
|
451
459
|
evaluateOverTimeType: Zod.string().optional(),
|
|
460
|
+
onNoDataPolicy: Zod.string().optional(),
|
|
452
461
|
}).optional(),
|
|
453
462
|
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* How wide a link rule's "exactly one parent" question is asked.
|
|
3
|
+
*
|
|
4
|
+
* A rule says every device carrying ALL the child labels gets one uplink to
|
|
5
|
+
* the SINGLE device carrying ALL the parent labels. "Single" needs a universe,
|
|
6
|
+
* and until now that universe was whatever device set the topology query
|
|
7
|
+
* happened to hand the resolver. The same rule therefore resolved fine on a
|
|
8
|
+
* site-filtered map and collapsed to `ambiguousParent` on the global one —
|
|
9
|
+
* issue #3260, where fourteen units each have a router carrying
|
|
10
|
+
* `SubCategory:Router` and the global map drew NO rule uplinks at all, not
|
|
11
|
+
* even the thirteen unambiguous ones.
|
|
12
|
+
*
|
|
13
|
+
* Site asks the same question once per site instead. Opt-in, never inferred:
|
|
14
|
+
* inferring it would silently change what an already-saved rule draws, and a
|
|
15
|
+
* rule that quietly re-points cables is worse than one that refuses to draw.
|
|
16
|
+
*
|
|
17
|
+
* Stored as free text on the column (the NetworkDeviceMonitoringMethod
|
|
18
|
+
* precedent), so read it through `parse` rather than comparing the raw column:
|
|
19
|
+
* rows written before this existed hold NULL and must read as Project, which
|
|
20
|
+
* is what they are.
|
|
21
|
+
*/
|
|
22
|
+
export enum NetworkDeviceLinkRuleScope {
|
|
23
|
+
// One parent for the whole project. What every rule written so far means.
|
|
24
|
+
Project = "Project",
|
|
25
|
+
// One parent per site. Devices with no site are skipped, and said so.
|
|
26
|
+
Site = "Site",
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class NetworkDeviceLinkRuleScopeUtil {
|
|
30
|
+
/*
|
|
31
|
+
* NULL, empty, whitespace and anything unrecognised read as Project.
|
|
32
|
+
* Defaulting the other way would silently redraw somebody's map.
|
|
33
|
+
*/
|
|
34
|
+
public static parse(
|
|
35
|
+
value: string | undefined | null,
|
|
36
|
+
): NetworkDeviceLinkRuleScope {
|
|
37
|
+
if ((value || "").trim().toLowerCase() === "site") {
|
|
38
|
+
return NetworkDeviceLinkRuleScope.Site;
|
|
39
|
+
}
|
|
40
|
+
return NetworkDeviceLinkRuleScope.Project;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
public static isSiteScoped(value: string | undefined | null): boolean {
|
|
44
|
+
return (
|
|
45
|
+
NetworkDeviceLinkRuleScopeUtil.parse(value) ===
|
|
46
|
+
NetworkDeviceLinkRuleScope.Site
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export default NetworkDeviceLinkRuleScope;
|
|
@@ -5,12 +5,19 @@ import ErrorMessage from "../ErrorMessage/ErrorMessage";
|
|
|
5
5
|
import BasicForm, {
|
|
6
6
|
BaseComponentProps as BasicFormComponentProps,
|
|
7
7
|
} from "../Forms/BasicForm";
|
|
8
|
+
import FormAnalyticsName from "../Forms/Utils/FormAnalyticsName";
|
|
8
9
|
import Modal, { ModalWidth } from "../Modal/Modal";
|
|
9
10
|
import GenericObject from "../../../Types/GenericObject";
|
|
10
11
|
import React, { ReactElement, useEffect, useRef, useState } from "react";
|
|
11
12
|
|
|
12
13
|
export interface ComponentProps<T extends GenericObject> {
|
|
13
14
|
title: string;
|
|
15
|
+
/*
|
|
16
|
+
* Identifies this form in the "FORM SUBMIT" analytics event. Falls back to
|
|
17
|
+
* formProps.name and then to the modal title, so every modal reports a
|
|
18
|
+
* distinguishable conversion without each caller having to name it.
|
|
19
|
+
*/
|
|
20
|
+
name?: string | undefined;
|
|
14
21
|
isLoading?: boolean | undefined;
|
|
15
22
|
error?: string | undefined;
|
|
16
23
|
onClose?: undefined | (() => void);
|
|
@@ -51,6 +58,11 @@ const BasicFormModal: <T extends GenericObject>(
|
|
|
51
58
|
{!isLoading && (
|
|
52
59
|
<BasicForm
|
|
53
60
|
{...props.formProps}
|
|
61
|
+
name={FormAnalyticsName.resolve(
|
|
62
|
+
props.name,
|
|
63
|
+
props.formProps.name,
|
|
64
|
+
props.title,
|
|
65
|
+
)}
|
|
54
66
|
hideSubmitButton={true}
|
|
55
67
|
ref={formRef}
|
|
56
68
|
onLoadingChange={(isFormLoading: boolean) => {
|
|
@@ -20,6 +20,7 @@ import FormFieldSchemaType from "./Types/FormFieldSchemaType";
|
|
|
20
20
|
import { FormStep } from "./Types/FormStep";
|
|
21
21
|
import FormValues from "./Types/FormValues";
|
|
22
22
|
import Validation from "./Validation";
|
|
23
|
+
import FormAnalyticsName from "./Utils/FormAnalyticsName";
|
|
23
24
|
import OneUptimeDate from "../../../Types/Date";
|
|
24
25
|
import Dictionary from "../../../Types/Dictionary";
|
|
25
26
|
import { VoidFunction } from "../../../Types/FunctionTypes";
|
|
@@ -447,7 +448,18 @@ const BasicForm: ForwardRefExoticComponent<any> = forwardRef(
|
|
|
447
448
|
}
|
|
448
449
|
}
|
|
449
450
|
|
|
450
|
-
|
|
451
|
+
const analyticsName: string | undefined = FormAnalyticsName.resolve(
|
|
452
|
+
props.name,
|
|
453
|
+
);
|
|
454
|
+
|
|
455
|
+
/*
|
|
456
|
+
* Unnamed forms are embedded sub-forms (filter builders, argument
|
|
457
|
+
* editors) rather than conversions. Skip them instead of reporting an
|
|
458
|
+
* event named after a missing prop.
|
|
459
|
+
*/
|
|
460
|
+
if (analyticsName) {
|
|
461
|
+
UiAnalytics.capture("FORM SUBMIT: " + analyticsName);
|
|
462
|
+
}
|
|
451
463
|
|
|
452
464
|
props.onSubmit(values, () => {
|
|
453
465
|
setDidSomethingChange(false);
|
|
@@ -8,6 +8,7 @@ import BasicForm, {
|
|
|
8
8
|
import Fields from "./Types/Fields";
|
|
9
9
|
import { FormStep } from "./Types/FormStep";
|
|
10
10
|
import FormValues from "./Types/FormValues";
|
|
11
|
+
import FormAnalyticsName from "./Utils/FormAnalyticsName";
|
|
11
12
|
import BaseModel from "../../../Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
|
|
12
13
|
import React, {
|
|
13
14
|
MutableRefObject,
|
|
@@ -113,7 +114,7 @@ const BasicModelForm: <TBaseModel extends BaseModel>(
|
|
|
113
114
|
onValidate={props.onValidate ? props.onValidate : DefaultValidateFunction}
|
|
114
115
|
disableAutofocus={props.disableAutofocus}
|
|
115
116
|
steps={props.steps}
|
|
116
|
-
name={props.name}
|
|
117
|
+
name={FormAnalyticsName.resolve(props.name, props.title)}
|
|
117
118
|
onFormStepChange={props.onFormStepChange}
|
|
118
119
|
submitButtonStyleType={props.submitButtonStyleType}
|
|
119
120
|
onSubmit={props.onSubmit}
|
|
@@ -21,6 +21,7 @@ import Field from "./Types/Field";
|
|
|
21
21
|
import Fields from "./Types/Fields";
|
|
22
22
|
import { FormStep } from "./Types/FormStep";
|
|
23
23
|
import FormValues from "./Types/FormValues";
|
|
24
|
+
import FormAnalyticsName from "./Utils/FormAnalyticsName";
|
|
24
25
|
import AnalyticsBaseModel from "../../../Models/AnalyticsModels/AnalyticsBaseModel/AnalyticsBaseModel";
|
|
25
26
|
import AccessControlModel from "../../../Models/DatabaseModels/DatabaseBaseModel/AccessControlModel";
|
|
26
27
|
import BaseModel from "../../../Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
|
|
@@ -854,7 +855,15 @@ const ModelForm: <TBaseModel extends BaseModel>(
|
|
|
854
855
|
disableAutofocus={props.disableAutofocus}
|
|
855
856
|
model={model}
|
|
856
857
|
id={props.id}
|
|
857
|
-
name={
|
|
858
|
+
name={FormAnalyticsName.resolve(
|
|
859
|
+
props.name,
|
|
860
|
+
props.title,
|
|
861
|
+
model.singularName
|
|
862
|
+
? `${
|
|
863
|
+
props.formType === FormType.Create ? "Create" : "Update"
|
|
864
|
+
} ${model.singularName}`
|
|
865
|
+
: undefined,
|
|
866
|
+
)}
|
|
858
867
|
onFormStepChange={props.onFormStepChange}
|
|
859
868
|
onIsLastFormStep={props.onIsLastFormStep}
|
|
860
869
|
fields={fields}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Matches "undefined" / "null" as a whole word, which is what a missing value
|
|
3
|
+
* turns into once it is interpolated into a title (`Duplicate ${undefined}`).
|
|
4
|
+
* Case sensitive on purpose: titles are written in Title Case, so a real word
|
|
5
|
+
* like "Null" in a form title is left alone.
|
|
6
|
+
*/
|
|
7
|
+
const STRINGIFIED_EMPTY_VALUE: RegExp = /\b(undefined|null)\b/;
|
|
8
|
+
|
|
9
|
+
/*
|
|
10
|
+
* Resolves the human readable name that identifies a form in the
|
|
11
|
+
* "FORM SUBMIT" analytics event.
|
|
12
|
+
*
|
|
13
|
+
* Wrappers around BasicForm pass a chain of candidates - the explicit name
|
|
14
|
+
* first, then whatever title they already render - and the first usable one
|
|
15
|
+
* wins. When no candidate is usable the caller is expected to skip the capture
|
|
16
|
+
* altogether, because an event named after a missing value collapses distinct
|
|
17
|
+
* conversions into one indistinguishable event.
|
|
18
|
+
*/
|
|
19
|
+
export default class FormAnalyticsName {
|
|
20
|
+
public static resolve(
|
|
21
|
+
...candidates: Array<string | undefined | null>
|
|
22
|
+
): string | undefined {
|
|
23
|
+
for (const candidate of candidates) {
|
|
24
|
+
if (typeof candidate !== "string") {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const name: string = candidate.trim();
|
|
29
|
+
|
|
30
|
+
if (!name || STRINGIFIED_EMPTY_VALUE.test(name)) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return name;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -7,6 +7,7 @@ import ModelForm, {
|
|
|
7
7
|
ComponentProps as ModelFormComponentProps,
|
|
8
8
|
} from "../Forms/ModelForm";
|
|
9
9
|
import FormValues from "../Forms/Types/FormValues";
|
|
10
|
+
import FormAnalyticsName from "../Forms/Utils/FormAnalyticsName";
|
|
10
11
|
import Modal, { ModalWidth } from "../Modal/Modal";
|
|
11
12
|
import BaseModel from "../../../Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
|
|
12
13
|
import { JSONObject } from "../../../Types/JSON";
|
|
@@ -77,7 +78,11 @@ const ModelFormModal: <TBaseModel extends BaseModel>(
|
|
|
77
78
|
<>
|
|
78
79
|
<ModelForm<TBaseModel>
|
|
79
80
|
{...props.formProps}
|
|
80
|
-
name={
|
|
81
|
+
name={FormAnalyticsName.resolve(
|
|
82
|
+
props.name,
|
|
83
|
+
props.formProps.name,
|
|
84
|
+
props.title,
|
|
85
|
+
)}
|
|
81
86
|
modelAPI={props.modelAPI}
|
|
82
87
|
modelType={props.modelType}
|
|
83
88
|
onIsLastFormStep={(isLastFormStep: boolean) => {
|
package/Utils/Analytics.ts
CHANGED
|
@@ -7,6 +7,18 @@ import {
|
|
|
7
7
|
RevenueEventProperties,
|
|
8
8
|
} from "../Types/Analytics/RevenueEvent";
|
|
9
9
|
|
|
10
|
+
// GA4 rejects event names longer than 40 characters.
|
|
11
|
+
const GA4_MAX_EVENT_NAME_LENGTH: number = 40;
|
|
12
|
+
|
|
13
|
+
/*
|
|
14
|
+
* Prefix used when a name has no usable leading letter (GA4 requires the first
|
|
15
|
+
* character to be a letter, so names like "404_page" cannot be sent as-is).
|
|
16
|
+
*/
|
|
17
|
+
const GA4_EVENT_NAME_FALLBACK: string = "event";
|
|
18
|
+
|
|
19
|
+
// GA4 requires the first character of an event name to be a letter.
|
|
20
|
+
const GA4_EVENT_NAME_LEADING_LETTER: RegExp = /^[a-z]/;
|
|
21
|
+
|
|
10
22
|
export default class Analytics {
|
|
11
23
|
private _isInitialized: boolean = false;
|
|
12
24
|
public get isInitialized(): boolean {
|
|
@@ -47,8 +59,44 @@ export default class Analytics {
|
|
|
47
59
|
});
|
|
48
60
|
}
|
|
49
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Coerce an arbitrary event name into one GA4 accepts: it must start with a
|
|
64
|
+
* letter and contain only letters, digits and underscores, up to 40
|
|
65
|
+
* characters.
|
|
66
|
+
*
|
|
67
|
+
* Product code calls capture() with free-form, human-readable names
|
|
68
|
+
* ("Page View: Project > Home", "FORM SUBMIT: Register", "accounts/login"),
|
|
69
|
+
* which GA4 would reject. Every name in RevenueEventName is already
|
|
70
|
+
* compliant and passes through unchanged, so GTM tags keyed on those names
|
|
71
|
+
* are unaffected.
|
|
72
|
+
*
|
|
73
|
+
* This is deliberately applied to the dataLayer branch only — the name sent
|
|
74
|
+
* to PostHog is left untouched so existing PostHog insights and dashboards
|
|
75
|
+
* keep matching.
|
|
76
|
+
*/
|
|
77
|
+
public static toGA4EventName(eventName: string): string {
|
|
78
|
+
let name: string = eventName
|
|
79
|
+
.toLowerCase()
|
|
80
|
+
.replace(/[^a-z0-9]+/g, "_")
|
|
81
|
+
.replace(/^_+|_+$/g, "");
|
|
82
|
+
|
|
83
|
+
if (name.length === 0) {
|
|
84
|
+
return GA4_EVENT_NAME_FALLBACK;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (!GA4_EVENT_NAME_LEADING_LETTER.test(name)) {
|
|
88
|
+
name = `${GA4_EVENT_NAME_FALLBACK}_${name}`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/*
|
|
92
|
+
* Truncating can leave a trailing underscore. The name starts with a
|
|
93
|
+
* letter by this point, so stripping those always leaves a valid name.
|
|
94
|
+
*/
|
|
95
|
+
return name.slice(0, GA4_MAX_EVENT_NAME_LENGTH).replace(/_+$/g, "");
|
|
96
|
+
}
|
|
97
|
+
|
|
50
98
|
public capture(eventName: string, data?: JSONObject): void {
|
|
51
|
-
// PostHog tracking
|
|
99
|
+
// PostHog tracking. The original, un-normalised name is used here.
|
|
52
100
|
if (this.isInitialized) {
|
|
53
101
|
posthog.capture(eventName, data);
|
|
54
102
|
}
|
|
@@ -60,10 +108,15 @@ export default class Analytics {
|
|
|
60
108
|
*/
|
|
61
109
|
if (typeof window !== "undefined" && (window as any).dataLayer) {
|
|
62
110
|
(window as any).dataLayer.push({
|
|
63
|
-
event: eventName,
|
|
64
111
|
eventCategory: "analytics",
|
|
112
|
+
// The raw name is kept here so the original is still readable in GTM.
|
|
65
113
|
eventAction: eventName,
|
|
66
114
|
...(data || {}),
|
|
115
|
+
/*
|
|
116
|
+
* `event` is the GA4 event name and has to stay valid, so it is
|
|
117
|
+
* applied after the spread and cannot be overridden by properties.
|
|
118
|
+
*/
|
|
119
|
+
event: Analytics.toGA4EventName(eventName),
|
|
67
120
|
});
|
|
68
121
|
}
|
|
69
122
|
}
|
|
@@ -95,6 +95,46 @@ class MonitorMetricTypeUtil {
|
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Metric series that backs this CheckOn, or null when nothing records it.
|
|
100
|
+
*
|
|
101
|
+
* The throwing variant below is kept for callers that genuinely cannot
|
|
102
|
+
* continue without a series. Over-time evaluation needs the null instead:
|
|
103
|
+
* it used to call the throwing one inside a try/catch that swallowed the
|
|
104
|
+
* error and quietly compared the instantaneous value, so "evaluate over
|
|
105
|
+
* time" was a no-op for every CheckOn missing from this map - DNS, SNMP
|
|
106
|
+
* and External Status Page checks among them.
|
|
107
|
+
*/
|
|
108
|
+
public static getMonitorMetricTypeByCheckOnOrNull(
|
|
109
|
+
checkOn: CheckOn,
|
|
110
|
+
): MonitorMetricType | null {
|
|
111
|
+
switch (checkOn) {
|
|
112
|
+
/*
|
|
113
|
+
* DNS / SNMP / External Status Page probes write into the shared
|
|
114
|
+
* online and response-time series like every other probe check, so
|
|
115
|
+
* their CheckOns map onto those series rather than onto ones of their
|
|
116
|
+
* own.
|
|
117
|
+
*/
|
|
118
|
+
case CheckOn.DnsIsOnline:
|
|
119
|
+
case CheckOn.SnmpIsOnline:
|
|
120
|
+
case CheckOn.ExternalStatusPageIsOnline:
|
|
121
|
+
return MonitorMetricType.IsOnline;
|
|
122
|
+
case CheckOn.DnsResponseTime:
|
|
123
|
+
case CheckOn.SnmpResponseTime:
|
|
124
|
+
case CheckOn.ExternalStatusPageResponseTime:
|
|
125
|
+
return MonitorMetricType.ResponseTime;
|
|
126
|
+
default:
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
return this.getMonitorMeticTypeByCheckOn(checkOn);
|
|
132
|
+
} catch {
|
|
133
|
+
// No series records this CheckOn.
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
98
138
|
public static getMonitorMeticTypeByCheckOn(
|
|
99
139
|
checkOn: CheckOn,
|
|
100
140
|
): MonitorMetricType {
|