@oneuptime/common 12.0.18 → 12.0.19
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/EnterpriseLicense.ts +23 -0
- package/Models/DatabaseModels/Index.ts +4 -2
- package/Models/DatabaseModels/Monitor.ts +39 -9
- package/Models/DatabaseModels/UserMicrosoftTeams.ts +344 -0
- package/Models/DatabaseModels/UserNotificationRule.ts +144 -8
- package/Models/DatabaseModels/UserNotificationSetting.ts +34 -0
- package/Models/DatabaseModels/UserOnCallLogTimeline.ts +95 -0
- package/Models/DatabaseModels/UserSlack.ts +341 -0
- package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +9 -0
- package/Server/API/UserMicrosoftTeamsAPI.ts +127 -0
- package/Server/API/UserSlackAPI.ts +125 -0
- package/Server/EnvironmentConfig.ts +12 -107
- package/Server/Infrastructure/Postgres/SchemaMigrations/1788700000000-RemoveMarketingConversionUploadState.ts +33 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1788800000000-DropMarketingConversionAddEnterpriseLicenseEmail.ts +51 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1788900000000-RedactStoredMonitorIngestSecrets.ts +117 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1789000000000-AddUserSlackAndMicrosoftTeams.ts +197 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
- package/Server/Infrastructure/Queue.ts +7 -0
- package/Server/Services/EnterpriseLicenseService.ts +60 -0
- package/Server/Services/Index.ts +6 -2
- package/Server/Services/LlmLogService.ts +25 -9
- package/Server/Services/OnCallReadinessService.ts +86 -2
- package/Server/Services/ProjectService.ts +67 -0
- package/Server/Services/UserMicrosoftTeamsService.ts +208 -0
- package/Server/Services/UserNotificationMethodAdminService.ts +167 -5
- package/Server/Services/UserNotificationRuleAdminService.ts +40 -6
- package/Server/Services/UserNotificationRuleService.ts +589 -11
- package/Server/Services/UserNotificationSettingService.ts +138 -0
- package/Server/Services/UserService.ts +28 -0
- package/Server/Services/UserSlackService.ts +218 -0
- package/Server/Services/WorkspaceProjectAuthTokenService.ts +67 -1
- package/Server/Services/WorkspaceUserAuthTokenService.ts +73 -0
- package/Server/Services/WorkspaceUserNotificationService.ts +190 -0
- package/Server/Utils/Marketing/MarketingEventUtil.ts +145 -0
- package/Server/Utils/Marketing/MarketingEventWebhook.ts +114 -0
- package/Server/Utils/Monitor/MonitorLogUtil.ts +14 -1
- package/Server/Utils/Monitor/MonitorPayloadRedaction.ts +321 -0
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +191 -4
- package/Tests/App/Dashboard/AdminNotificationRulesPage.test.tsx +22 -14
- package/Tests/App/Dashboard/AdminUserNotificationMethodsPage.test.tsx +22 -11
- package/Tests/App/Dashboard/OnCallPreventionGuards.test.tsx +37 -1
- package/Tests/App/Dashboard/OnCallRulesTable.test.tsx +18 -10
- package/Tests/Models/DatabaseModels/DomainRoleTierCoverage.test.ts +27 -1
- package/Tests/Models/DatabaseModels/MonitorSecretKeyColumnAccessControl.test.ts +214 -0
- package/Tests/Server/API/OnCallReadinessAPI.test.ts +145 -26
- package/Tests/Server/API/UserNotificationMethodAdminAPI.test.ts +2 -2
- package/Tests/Server/Services/AIServiceDailyBudget.test.ts +11 -1
- package/Tests/Server/Services/AdminRuleEditGuards.test.ts +22 -8
- package/Tests/Server/Services/DeliverNotificationForRuleExtraction.test.ts +18 -2
- package/Tests/Server/Services/EpisodeRuleSeverityRepair.test.ts +5 -1
- package/Tests/Server/Services/LlmLogServiceTokenAggregateParams.test.ts +640 -0
- package/Tests/Server/Services/NotificationChannelEventCoverage.test.ts +246 -22
- package/Tests/Server/Services/NotificationDeletionImpact.test.ts +136 -8
- package/Tests/Server/Services/OnCallNotificationFallback.test.ts +191 -8
- package/Tests/Server/Services/OnCallReadinessService.test.ts +343 -18
- package/Tests/Server/Services/SeverityRuleBackfill.test.ts +5 -1
- package/Tests/Server/Services/UserMicrosoftTeamsService.test.ts +343 -0
- package/Tests/Server/Services/UserNotificationMethodAdminService.test.ts +209 -8
- package/Tests/Server/Services/UserNotificationRuleAdminGuards.test.ts +22 -6
- package/Tests/Server/Services/UserNotificationRuleDefaultCreation.test.ts +12 -6
- package/Tests/Server/Services/UserNotificationRuleExecuteItem.test.ts +37 -3
- package/Tests/Server/Services/UserNotificationRuleWorkspaceDelivery.test.ts +669 -0
- package/Tests/Server/Services/UserNotificationSettingWorkspaceChannels.test.ts +410 -0
- package/Tests/Server/Services/UserSlackService.test.ts +407 -0
- package/Tests/Server/Services/WorkspaceProjectAuthTokenDisconnectCascade.test.ts +224 -0
- package/Tests/Server/Services/WorkspaceUserAuthTokenNotificationMethodCascade.test.ts +189 -0
- package/Tests/Server/Services/WorkspaceUserNotificationService.test.ts +496 -0
- package/Tests/Server/Types/Database/Permissions/AdminNotificationRuleAccess.test.ts +30 -17
- package/Tests/Server/Types/Database/Permissions/CreateOwnershipScoping.test.ts +8 -3
- package/Tests/Server/Types/Database/Permissions/OwnerOnlyColumns.test.ts +72 -3
- package/Tests/Server/Types/Database/Permissions/UserNotificationRuleScoping.test.ts +22 -18
- package/Tests/Server/Types/Database/Permissions/WorkspaceMethodStampedColumnCreate.test.ts +178 -0
- package/Tests/Server/Utils/Marketing/MarketingEventUtil.test.ts +259 -0
- package/Tests/Server/Utils/Marketing/MarketingEventWebhook.test.ts +257 -0
- package/Tests/Server/Utils/Monitor/MonitorLogUtilRedaction.test.ts +300 -0
- package/Tests/Server/Utils/Monitor/MonitorPayloadRedaction.test.ts +381 -0
- package/Tests/Server/Utils/Runbook/RunbookExecutePermission.test.ts +228 -0
- package/Tests/Server/Utils/SessionReplay/SessionReplayErasureTombstone.test.ts +189 -0
- package/Tests/Server/Utils/SessionReplay/SessionReplayUsage.test.ts +205 -0
- package/Tests/Server/Utils/Telemetry/AppMetrics.test.ts +275 -0
- package/Tests/Server/Utils/Telemetry/TelemetryContext.test.ts +279 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsDirectMessage.test.ts +454 -0
- package/Tests/UI/Utils/NotificationMethodUtil.test.ts +137 -9
- package/Tests/UI/Utils/PermissionGate.test.ts +105 -0
- package/Types/Marketing/MarketingEvent.ts +97 -0
- package/UI/Utils/NotificationMethodUtil.ts +41 -1
- package/UI/Utils/PermissionGate.ts +56 -0
- package/build/dist/Models/DatabaseModels/EnterpriseLicense.js +24 -0
- package/build/dist/Models/DatabaseModels/EnterpriseLicense.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +4 -2
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Monitor.js +39 -9
- package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserMicrosoftTeams.js +363 -0
- package/build/dist/Models/DatabaseModels/UserMicrosoftTeams.js.map +1 -0
- package/build/dist/Models/DatabaseModels/UserNotificationRule.js +145 -8
- package/build/dist/Models/DatabaseModels/UserNotificationRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserNotificationSetting.js +38 -0
- package/build/dist/Models/DatabaseModels/UserNotificationSetting.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js +96 -0
- package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserSlack.js +361 -0
- package/build/dist/Models/DatabaseModels/UserSlack.js.map +1 -0
- package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
- package/build/dist/Server/API/UserMicrosoftTeamsAPI.js +82 -0
- package/build/dist/Server/API/UserMicrosoftTeamsAPI.js.map +1 -0
- package/build/dist/Server/API/UserSlackAPI.js +81 -0
- package/build/dist/Server/API/UserSlackAPI.js.map +1 -0
- package/build/dist/Server/EnvironmentConfig.js +10 -70
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788700000000-RemoveMarketingConversionUploadState.js +26 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788700000000-RemoveMarketingConversionUploadState.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788800000000-DropMarketingConversionAddEnterpriseLicenseEmail.js +37 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788800000000-DropMarketingConversionAddEnterpriseLicenseEmail.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788900000000-RedactStoredMonitorIngestSecrets.js +106 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788900000000-RedactStoredMonitorIngestSecrets.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789000000000-AddUserSlackAndMicrosoftTeams.js +78 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789000000000-AddUserSlackAndMicrosoftTeams.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Infrastructure/Queue.js +7 -0
- package/build/dist/Server/Infrastructure/Queue.js.map +1 -1
- package/build/dist/Server/Services/EnterpriseLicenseService.js +65 -0
- package/build/dist/Server/Services/EnterpriseLicenseService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +6 -2
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/LlmLogService.js +25 -9
- package/build/dist/Server/Services/LlmLogService.js.map +1 -1
- package/build/dist/Server/Services/OnCallReadinessService.js +74 -2
- package/build/dist/Server/Services/OnCallReadinessService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +56 -0
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/UserMicrosoftTeamsService.js +183 -0
- package/build/dist/Server/Services/UserMicrosoftTeamsService.js.map +1 -0
- package/build/dist/Server/Services/UserNotificationMethodAdminService.js +138 -2
- package/build/dist/Server/Services/UserNotificationMethodAdminService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationRuleAdminService.js +32 -4
- package/build/dist/Server/Services/UserNotificationRuleAdminService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationRuleService.js +499 -67
- package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationSettingService.js +118 -0
- package/build/dist/Server/Services/UserNotificationSettingService.js.map +1 -1
- package/build/dist/Server/Services/UserService.js +26 -1
- package/build/dist/Server/Services/UserService.js.map +1 -1
- package/build/dist/Server/Services/UserSlackService.js +195 -0
- package/build/dist/Server/Services/UserSlackService.js.map +1 -0
- package/build/dist/Server/Services/WorkspaceProjectAuthTokenService.js +64 -1
- package/build/dist/Server/Services/WorkspaceProjectAuthTokenService.js.map +1 -1
- package/build/dist/Server/Services/WorkspaceUserAuthTokenService.js +70 -0
- package/build/dist/Server/Services/WorkspaceUserAuthTokenService.js.map +1 -1
- package/build/dist/Server/Services/WorkspaceUserNotificationService.js +129 -0
- package/build/dist/Server/Services/WorkspaceUserNotificationService.js.map +1 -0
- package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js +94 -0
- package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js.map +1 -0
- package/build/dist/Server/Utils/Marketing/MarketingEventWebhook.js +93 -0
- package/build/dist/Server/Utils/Marketing/MarketingEventWebhook.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/MonitorLogUtil.js +12 -1
- package/build/dist/Server/Utils/Monitor/MonitorLogUtil.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorPayloadRedaction.js +233 -0
- package/build/dist/Server/Utils/Monitor/MonitorPayloadRedaction.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +141 -4
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
- package/build/dist/Types/Marketing/MarketingEvent.js +43 -0
- package/build/dist/Types/Marketing/MarketingEvent.js.map +1 -0
- package/build/dist/UI/Utils/NotificationMethodUtil.js +33 -1
- package/build/dist/UI/Utils/NotificationMethodUtil.js.map +1 -1
- package/build/dist/UI/Utils/PermissionGate.js +32 -0
- package/build/dist/UI/Utils/PermissionGate.js.map +1 -1
- package/package.json +1 -1
- package/Models/DatabaseModels/MarketingConversion.ts +0 -410
- package/Server/Services/MarketingConversionService.ts +0 -10
- package/Server/Utils/Marketing/ConversionUploadProvider.ts +0 -215
- package/Server/Utils/Marketing/ConversionUploadProviders.ts +0 -22
- package/Server/Utils/Marketing/Providers/GoogleAds.ts +0 -333
- package/Server/Utils/Marketing/Providers/LinkedIn.ts +0 -185
- package/Server/Utils/Marketing/Providers/Meta.ts +0 -182
- package/Server/Utils/Marketing/Providers/MicrosoftAds.ts +0 -223
- package/Server/Utils/Marketing/Providers/Reddit.ts +0 -208
- package/Tests/Server/Utils/Marketing/AdUploadableConversionTypes.test.ts +0 -315
- package/Tests/Server/Utils/Marketing/ConversionUploadProvider.test.ts +0 -300
- package/Tests/Server/Utils/Marketing/GoogleAds.test.ts +0 -592
- package/Tests/Server/Utils/Marketing/LinkedIn.test.ts +0 -282
- package/Tests/Server/Utils/Marketing/Meta.test.ts +0 -304
- package/Tests/Server/Utils/Marketing/MicrosoftAds.test.ts +0 -263
- package/Tests/Server/Utils/Marketing/Reddit.test.ts +0 -259
- package/Types/Marketing/MarketingConversion.ts +0 -53
- package/build/dist/Models/DatabaseModels/MarketingConversion.js +0 -445
- package/build/dist/Models/DatabaseModels/MarketingConversion.js.map +0 -1
- package/build/dist/Server/Services/MarketingConversionService.js +0 -9
- package/build/dist/Server/Services/MarketingConversionService.js.map +0 -1
- package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js +0 -93
- package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js.map +0 -1
- package/build/dist/Server/Utils/Marketing/ConversionUploadProviders.js +0 -20
- package/build/dist/Server/Utils/Marketing/ConversionUploadProviders.js.map +0 -1
- package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js +0 -221
- package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js.map +0 -1
- package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js +0 -137
- package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js.map +0 -1
- package/build/dist/Server/Utils/Marketing/Providers/Meta.js +0 -133
- package/build/dist/Server/Utils/Marketing/Providers/Meta.js.map +0 -1
- package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js +0 -143
- package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js.map +0 -1
- package/build/dist/Server/Utils/Marketing/Providers/Reddit.js +0 -145
- package/build/dist/Server/Utils/Marketing/Providers/Reddit.js.map +0 -1
- package/build/dist/Types/Marketing/MarketingConversion.js +0 -52
- package/build/dist/Types/Marketing/MarketingConversion.js.map +0 -1
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { describe, expect, jest, test } from "@jest/globals";
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* AppMetrics is the single catalog of self-observability instruments the
|
|
5
|
+
* OneUptime services emit. Dashboards and alerts are wired to these metric
|
|
6
|
+
* NAMES and UNITS, so a rename or a unit change here silently breaks them —
|
|
7
|
+
* these tests pin the catalog. They also pin the lazy-create-once caching the
|
|
8
|
+
* module promises (importing it from many call sites must not register the
|
|
9
|
+
* same instrument twice).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
jest.mock("../../../../Server/Utils/Telemetry", () => {
|
|
13
|
+
return {
|
|
14
|
+
__esModule: true,
|
|
15
|
+
default: {
|
|
16
|
+
getCounter: jest.fn((data: { name: string; unit: string }) => {
|
|
17
|
+
return { __kind: "counter", ...data };
|
|
18
|
+
}),
|
|
19
|
+
getHistogram: jest.fn((data: { name: string; unit: string }) => {
|
|
20
|
+
return { __kind: "histogram", ...data };
|
|
21
|
+
}),
|
|
22
|
+
getGauge: jest.fn((data: { name: string; unit: string }) => {
|
|
23
|
+
return { __kind: "gauge", ...data };
|
|
24
|
+
}),
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
type Factory = "getCounter" | "getHistogram" | "getGauge";
|
|
30
|
+
|
|
31
|
+
interface Instrument {
|
|
32
|
+
getter: string;
|
|
33
|
+
factory: Factory;
|
|
34
|
+
kind: string;
|
|
35
|
+
name: string;
|
|
36
|
+
unit: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/*
|
|
40
|
+
* The expected catalog, mirrored from AppMetrics. Kept as data so a new
|
|
41
|
+
* instrument (or a changed name/unit) has to be reflected here deliberately.
|
|
42
|
+
*/
|
|
43
|
+
const CATALOG: Array<Instrument> = [
|
|
44
|
+
{
|
|
45
|
+
getter: "getHttpRequestCounter",
|
|
46
|
+
factory: "getCounter",
|
|
47
|
+
kind: "counter",
|
|
48
|
+
name: "http.server.request.count",
|
|
49
|
+
unit: "1",
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
getter: "getHttpRequestDuration",
|
|
53
|
+
factory: "getHistogram",
|
|
54
|
+
kind: "histogram",
|
|
55
|
+
name: "http.server.request.duration",
|
|
56
|
+
unit: "ms",
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
getter: "getHttpRequestsInFlight",
|
|
60
|
+
factory: "getGauge",
|
|
61
|
+
kind: "gauge",
|
|
62
|
+
name: "http.server.active_requests",
|
|
63
|
+
unit: "1",
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
getter: "getWorkerJobCounter",
|
|
67
|
+
factory: "getCounter",
|
|
68
|
+
kind: "counter",
|
|
69
|
+
name: "worker.job.count",
|
|
70
|
+
unit: "1",
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
getter: "getWorkerJobDuration",
|
|
74
|
+
factory: "getHistogram",
|
|
75
|
+
kind: "histogram",
|
|
76
|
+
name: "worker.job.duration",
|
|
77
|
+
unit: "ms",
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
getter: "getWorkerJobsInFlight",
|
|
81
|
+
factory: "getGauge",
|
|
82
|
+
kind: "gauge",
|
|
83
|
+
name: "worker.job.active",
|
|
84
|
+
unit: "1",
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
getter: "getProbeCheckCounter",
|
|
88
|
+
factory: "getCounter",
|
|
89
|
+
kind: "counter",
|
|
90
|
+
name: "probe.monitor.check.count",
|
|
91
|
+
unit: "1",
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
getter: "getProbeCheckDuration",
|
|
95
|
+
factory: "getHistogram",
|
|
96
|
+
kind: "histogram",
|
|
97
|
+
name: "probe.monitor.check.duration",
|
|
98
|
+
unit: "ms",
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
getter: "getNotificationCounter",
|
|
102
|
+
factory: "getCounter",
|
|
103
|
+
kind: "counter",
|
|
104
|
+
name: "notification.send.count",
|
|
105
|
+
unit: "1",
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
getter: "getNotificationDuration",
|
|
109
|
+
factory: "getHistogram",
|
|
110
|
+
kind: "histogram",
|
|
111
|
+
name: "notification.send.duration",
|
|
112
|
+
unit: "ms",
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
getter: "getIngestCounter",
|
|
116
|
+
factory: "getCounter",
|
|
117
|
+
kind: "counter",
|
|
118
|
+
name: "telemetry.ingest.request.count",
|
|
119
|
+
unit: "1",
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
getter: "getIngestDuration",
|
|
123
|
+
factory: "getHistogram",
|
|
124
|
+
kind: "histogram",
|
|
125
|
+
name: "telemetry.ingest.request.duration",
|
|
126
|
+
unit: "ms",
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
getter: "getIngestPayloadBytes",
|
|
130
|
+
factory: "getHistogram",
|
|
131
|
+
kind: "histogram",
|
|
132
|
+
name: "telemetry.ingest.request.payload.size",
|
|
133
|
+
unit: "By",
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
getter: "getIngestDroppedCounter",
|
|
137
|
+
factory: "getCounter",
|
|
138
|
+
kind: "counter",
|
|
139
|
+
name: "oneuptime.telemetry.ingest.dropped.count",
|
|
140
|
+
unit: "1",
|
|
141
|
+
},
|
|
142
|
+
];
|
|
143
|
+
|
|
144
|
+
interface LoadedModules {
|
|
145
|
+
Telemetry: {
|
|
146
|
+
getCounter: jest.Mock;
|
|
147
|
+
getHistogram: jest.Mock;
|
|
148
|
+
getGauge: jest.Mock;
|
|
149
|
+
};
|
|
150
|
+
// A catalog of no-arg getters returning tagged fake instruments.
|
|
151
|
+
AppMetrics: Record<string, () => { __kind: string; name: string }>;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/*
|
|
155
|
+
* Re-import with a cleared module registry so AppMetrics' static instrument
|
|
156
|
+
* cache starts empty and the mocked Telemetry factories start with a clean
|
|
157
|
+
* call history — required to assert "created exactly once".
|
|
158
|
+
*/
|
|
159
|
+
async function loadFresh(): Promise<LoadedModules> {
|
|
160
|
+
jest.resetModules();
|
|
161
|
+
|
|
162
|
+
const Telemetry: LoadedModules["Telemetry"] = (
|
|
163
|
+
(await import("../../../../Server/Utils/Telemetry")) as unknown as {
|
|
164
|
+
default: LoadedModules["Telemetry"];
|
|
165
|
+
}
|
|
166
|
+
).default;
|
|
167
|
+
|
|
168
|
+
const AppMetrics: LoadedModules["AppMetrics"] = (
|
|
169
|
+
(await import(
|
|
170
|
+
"../../../../Server/Utils/Telemetry/AppMetrics"
|
|
171
|
+
)) as unknown as { default: LoadedModules["AppMetrics"] }
|
|
172
|
+
).default;
|
|
173
|
+
|
|
174
|
+
return { Telemetry, AppMetrics };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
describe("AppMetrics catalog", () => {
|
|
178
|
+
test.each(CATALOG)(
|
|
179
|
+
"$getter creates $name via $factory with unit $unit",
|
|
180
|
+
async (entry: Instrument) => {
|
|
181
|
+
const { Telemetry, AppMetrics } = await loadFresh();
|
|
182
|
+
|
|
183
|
+
const instrument: { __kind: string; name: string } =
|
|
184
|
+
AppMetrics[entry.getter]!();
|
|
185
|
+
|
|
186
|
+
expect(Telemetry[entry.factory]).toHaveBeenCalledTimes(1);
|
|
187
|
+
expect(Telemetry[entry.factory]).toHaveBeenCalledWith(
|
|
188
|
+
expect.objectContaining({ name: entry.name, unit: entry.unit }),
|
|
189
|
+
);
|
|
190
|
+
expect(instrument.__kind).toBe(entry.kind);
|
|
191
|
+
expect(instrument.name).toBe(entry.name);
|
|
192
|
+
},
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
test.each(CATALOG)(
|
|
196
|
+
"$getter lazily creates the instrument only once and caches it",
|
|
197
|
+
async (entry: Instrument) => {
|
|
198
|
+
const { Telemetry, AppMetrics } = await loadFresh();
|
|
199
|
+
|
|
200
|
+
const first: unknown = AppMetrics[entry.getter]!();
|
|
201
|
+
const second: unknown = AppMetrics[entry.getter]!();
|
|
202
|
+
const third: unknown = AppMetrics[entry.getter]!();
|
|
203
|
+
|
|
204
|
+
// Same instance handed back every time.
|
|
205
|
+
expect(second).toBe(first);
|
|
206
|
+
expect(third).toBe(first);
|
|
207
|
+
|
|
208
|
+
// The factory ran exactly once despite three getter calls.
|
|
209
|
+
const totalFactoryCalls: number =
|
|
210
|
+
Telemetry.getCounter.mock.calls.length +
|
|
211
|
+
Telemetry.getHistogram.mock.calls.length +
|
|
212
|
+
Telemetry.getGauge.mock.calls.length;
|
|
213
|
+
expect(totalFactoryCalls).toBe(1);
|
|
214
|
+
},
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
test("no getter accidentally routes through the wrong factory", async () => {
|
|
218
|
+
const { Telemetry, AppMetrics } = await loadFresh();
|
|
219
|
+
|
|
220
|
+
for (const entry of CATALOG) {
|
|
221
|
+
AppMetrics[entry.getter]!();
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const counterNames: Array<string> = Telemetry.getCounter.mock.calls.map(
|
|
225
|
+
(c: Array<{ name: string }>) => {
|
|
226
|
+
return c[0]!.name;
|
|
227
|
+
},
|
|
228
|
+
);
|
|
229
|
+
const histogramNames: Array<string> = Telemetry.getHistogram.mock.calls.map(
|
|
230
|
+
(c: Array<{ name: string }>) => {
|
|
231
|
+
return c[0]!.name;
|
|
232
|
+
},
|
|
233
|
+
);
|
|
234
|
+
const gaugeNames: Array<string> = Telemetry.getGauge.mock.calls.map(
|
|
235
|
+
(c: Array<{ name: string }>) => {
|
|
236
|
+
return c[0]!.name;
|
|
237
|
+
},
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
expect(counterNames.sort()).toEqual(
|
|
241
|
+
CATALOG.filter((e: Instrument) => {
|
|
242
|
+
return e.kind === "counter";
|
|
243
|
+
})
|
|
244
|
+
.map((e: Instrument) => {
|
|
245
|
+
return e.name;
|
|
246
|
+
})
|
|
247
|
+
.sort(),
|
|
248
|
+
);
|
|
249
|
+
expect(histogramNames.sort()).toEqual(
|
|
250
|
+
CATALOG.filter((e: Instrument) => {
|
|
251
|
+
return e.kind === "histogram";
|
|
252
|
+
})
|
|
253
|
+
.map((e: Instrument) => {
|
|
254
|
+
return e.name;
|
|
255
|
+
})
|
|
256
|
+
.sort(),
|
|
257
|
+
);
|
|
258
|
+
expect(gaugeNames.sort()).toEqual(
|
|
259
|
+
CATALOG.filter((e: Instrument) => {
|
|
260
|
+
return e.kind === "gauge";
|
|
261
|
+
})
|
|
262
|
+
.map((e: Instrument) => {
|
|
263
|
+
return e.name;
|
|
264
|
+
})
|
|
265
|
+
.sort(),
|
|
266
|
+
);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
test("every metric name in the catalog is unique", () => {
|
|
270
|
+
const names: Array<string> = CATALOG.map((e: Instrument) => {
|
|
271
|
+
return e.name;
|
|
272
|
+
});
|
|
273
|
+
expect(new Set(names).size).toBe(names.length);
|
|
274
|
+
});
|
|
275
|
+
});
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, jest, test } from "@jest/globals";
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* TelemetryContext is the AsyncLocalStorage-backed attribute bag that carries
|
|
5
|
+
* tenant identifiers (projectId, userId, ...) from where middleware learns
|
|
6
|
+
* them down to the spans and logs created much deeper in the call stack. These
|
|
7
|
+
* tests pin the contracts every one of its ~1958 span/log consumers leans on:
|
|
8
|
+
* scoping and inheritance, safe no-ops outside a scope, the merge rules, the
|
|
9
|
+
* best-effort payload extraction, and the DisableTelemetry short-circuit.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
type TelemetryContextClass =
|
|
13
|
+
typeof import("../../../../Server/Utils/Telemetry/TelemetryContext").default;
|
|
14
|
+
|
|
15
|
+
/*
|
|
16
|
+
* Load a fresh module with the DisableTelemetry env gate forced to a known
|
|
17
|
+
* value. resetModules also gives each test its own AsyncLocalStorage so scopes
|
|
18
|
+
* never leak between tests.
|
|
19
|
+
*/
|
|
20
|
+
async function load(disabled: boolean): Promise<TelemetryContextClass> {
|
|
21
|
+
jest.resetModules();
|
|
22
|
+
jest.doMock("../../../../Server/EnvironmentConfig", () => {
|
|
23
|
+
return {
|
|
24
|
+
__esModule: true,
|
|
25
|
+
DisableTelemetry: disabled,
|
|
26
|
+
};
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
(await import(
|
|
31
|
+
"../../../../Server/Utils/Telemetry/TelemetryContext"
|
|
32
|
+
)) as unknown as { default: TelemetryContextClass }
|
|
33
|
+
).default;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
describe("TelemetryContext (telemetry enabled)", () => {
|
|
37
|
+
beforeEach(() => {
|
|
38
|
+
jest.resetModules();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe("getAttributes / runWithContext", () => {
|
|
42
|
+
test("returns an empty object when there is no active scope", async () => {
|
|
43
|
+
const TelemetryContext: TelemetryContextClass = await load(false);
|
|
44
|
+
expect(TelemetryContext.getAttributes()).toEqual({});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("seeds the scope with the supplied attributes", async () => {
|
|
48
|
+
const TelemetryContext: TelemetryContextClass = await load(false);
|
|
49
|
+
|
|
50
|
+
const inside: Record<string, string | number | boolean> =
|
|
51
|
+
TelemetryContext.runWithContext(
|
|
52
|
+
{ projectId: "p1", userId: "u1" },
|
|
53
|
+
() => {
|
|
54
|
+
return TelemetryContext.getAttributes();
|
|
55
|
+
},
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
expect(inside).toEqual({ projectId: "p1", userId: "u1" });
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("the scope does not leak past runWithContext", async () => {
|
|
62
|
+
const TelemetryContext: TelemetryContextClass = await load(false);
|
|
63
|
+
|
|
64
|
+
TelemetryContext.runWithContext({ projectId: "p1" }, () => {
|
|
65
|
+
return undefined;
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
expect(TelemetryContext.getAttributes()).toEqual({});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("returns the callback's return value", async () => {
|
|
72
|
+
const TelemetryContext: TelemetryContextClass = await load(false);
|
|
73
|
+
|
|
74
|
+
const result: number = TelemetryContext.runWithContext(
|
|
75
|
+
{ projectId: "p1" },
|
|
76
|
+
() => {
|
|
77
|
+
return 42;
|
|
78
|
+
},
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
expect(result).toBe(42);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("drops undefined and null attribute values while seeding", async () => {
|
|
85
|
+
const TelemetryContext: TelemetryContextClass = await load(false);
|
|
86
|
+
|
|
87
|
+
const inside: Record<string, string | number | boolean> =
|
|
88
|
+
TelemetryContext.runWithContext(
|
|
89
|
+
{
|
|
90
|
+
projectId: "p1",
|
|
91
|
+
userId: undefined,
|
|
92
|
+
monitorId: null as unknown as undefined,
|
|
93
|
+
},
|
|
94
|
+
() => {
|
|
95
|
+
return TelemetryContext.getAttributes();
|
|
96
|
+
},
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
expect(inside).toEqual({ projectId: "p1" });
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe("nested scopes", () => {
|
|
104
|
+
test("a nested scope inherits the enclosing attributes", async () => {
|
|
105
|
+
const TelemetryContext: TelemetryContextClass = await load(false);
|
|
106
|
+
|
|
107
|
+
const inner: Record<string, string | number | boolean> =
|
|
108
|
+
TelemetryContext.runWithContext({ projectId: "p1" }, () => {
|
|
109
|
+
return TelemetryContext.runWithContext({ userId: "u1" }, () => {
|
|
110
|
+
return TelemetryContext.getAttributes();
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
expect(inner).toEqual({ projectId: "p1", userId: "u1" });
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("a nested scope can override an inherited attribute", async () => {
|
|
118
|
+
const TelemetryContext: TelemetryContextClass = await load(false);
|
|
119
|
+
|
|
120
|
+
const inner: Record<string, string | number | boolean> =
|
|
121
|
+
TelemetryContext.runWithContext({ projectId: "p1" }, () => {
|
|
122
|
+
return TelemetryContext.runWithContext({ projectId: "p2" }, () => {
|
|
123
|
+
return TelemetryContext.getAttributes();
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
expect(inner).toEqual({ projectId: "p2" });
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("leaving a nested scope restores the enclosing attributes", async () => {
|
|
131
|
+
const TelemetryContext: TelemetryContextClass = await load(false);
|
|
132
|
+
|
|
133
|
+
const outerAfter: Record<string, string | number | boolean> =
|
|
134
|
+
TelemetryContext.runWithContext({ projectId: "p1" }, () => {
|
|
135
|
+
TelemetryContext.runWithContext(
|
|
136
|
+
{ projectId: "p2", userId: "u1" },
|
|
137
|
+
() => {
|
|
138
|
+
return undefined;
|
|
139
|
+
},
|
|
140
|
+
);
|
|
141
|
+
return TelemetryContext.getAttributes();
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
expect(outerAfter).toEqual({ projectId: "p1" });
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
describe("setAttributes", () => {
|
|
149
|
+
test("enriches the current scope", async () => {
|
|
150
|
+
const TelemetryContext: TelemetryContextClass = await load(false);
|
|
151
|
+
|
|
152
|
+
const inside: Record<string, string | number | boolean> =
|
|
153
|
+
TelemetryContext.runWithContext({ projectId: "p1" }, () => {
|
|
154
|
+
TelemetryContext.setAttributes({ userId: "u1", incidentId: "i1" });
|
|
155
|
+
return TelemetryContext.getAttributes();
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
expect(inside).toEqual({
|
|
159
|
+
projectId: "p1",
|
|
160
|
+
userId: "u1",
|
|
161
|
+
incidentId: "i1",
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test("is a safe no-op outside any scope", async () => {
|
|
166
|
+
const TelemetryContext: TelemetryContextClass = await load(false);
|
|
167
|
+
|
|
168
|
+
expect(() => {
|
|
169
|
+
TelemetryContext.setAttributes({ userId: "u1" });
|
|
170
|
+
}).not.toThrow();
|
|
171
|
+
expect(TelemetryContext.getAttributes()).toEqual({});
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
describe("pickKnownAttributes", () => {
|
|
176
|
+
test("extracts only known identifier keys", async () => {
|
|
177
|
+
const TelemetryContext: TelemetryContextClass = await load(false);
|
|
178
|
+
|
|
179
|
+
expect(
|
|
180
|
+
TelemetryContext.pickKnownAttributes({
|
|
181
|
+
projectId: "p1",
|
|
182
|
+
monitorId: "m1",
|
|
183
|
+
somethingUnknown: "ignored",
|
|
184
|
+
}),
|
|
185
|
+
).toEqual({ projectId: "p1", monitorId: "m1" });
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test("keeps number and boolean values without stringifying them", async () => {
|
|
189
|
+
const TelemetryContext: TelemetryContextClass = await load(false);
|
|
190
|
+
|
|
191
|
+
expect(
|
|
192
|
+
TelemetryContext.pickKnownAttributes({
|
|
193
|
+
projectId: 5,
|
|
194
|
+
workspaceType: true,
|
|
195
|
+
}),
|
|
196
|
+
).toEqual({ projectId: 5, workspaceType: true });
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test("skips undefined and null identifier values", async () => {
|
|
200
|
+
const TelemetryContext: TelemetryContextClass = await load(false);
|
|
201
|
+
|
|
202
|
+
expect(
|
|
203
|
+
TelemetryContext.pickKnownAttributes({
|
|
204
|
+
projectId: "p1",
|
|
205
|
+
userId: undefined,
|
|
206
|
+
monitorId: null,
|
|
207
|
+
}),
|
|
208
|
+
).toEqual({ projectId: "p1" });
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test("stringifies an ObjectID-like value via toString()", async () => {
|
|
212
|
+
const TelemetryContext: TelemetryContextClass = await load(false);
|
|
213
|
+
|
|
214
|
+
expect(
|
|
215
|
+
TelemetryContext.pickKnownAttributes({
|
|
216
|
+
projectId: {
|
|
217
|
+
toString: () => {
|
|
218
|
+
return "obj-123";
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
}),
|
|
222
|
+
).toEqual({ projectId: "obj-123" });
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
test("skips a plain object whose toString is '[object Object]'", async () => {
|
|
226
|
+
const TelemetryContext: TelemetryContextClass = await load(false);
|
|
227
|
+
|
|
228
|
+
expect(TelemetryContext.pickKnownAttributes({ projectId: {} })).toEqual(
|
|
229
|
+
{},
|
|
230
|
+
);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
test("returns an empty object for non-object input", async () => {
|
|
234
|
+
const TelemetryContext: TelemetryContextClass = await load(false);
|
|
235
|
+
|
|
236
|
+
expect(TelemetryContext.pickKnownAttributes(null)).toEqual({});
|
|
237
|
+
expect(TelemetryContext.pickKnownAttributes(undefined)).toEqual({});
|
|
238
|
+
expect(TelemetryContext.pickKnownAttributes("string")).toEqual({});
|
|
239
|
+
expect(TelemetryContext.pickKnownAttributes(7)).toEqual({});
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
describe("TelemetryContext (telemetry disabled)", () => {
|
|
245
|
+
beforeEach(() => {
|
|
246
|
+
jest.resetModules();
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test("runWithContext still runs the callback but seeds no scope", async () => {
|
|
250
|
+
const TelemetryContext: TelemetryContextClass = await load(true);
|
|
251
|
+
|
|
252
|
+
const inside: Record<string, string | number | boolean> =
|
|
253
|
+
TelemetryContext.runWithContext({ projectId: "p1" }, () => {
|
|
254
|
+
return TelemetryContext.getAttributes();
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
// No scope was created, so nothing is captured.
|
|
258
|
+
expect(inside).toEqual({});
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
test("runWithContext returns the callback value when disabled", async () => {
|
|
262
|
+
const TelemetryContext: TelemetryContextClass = await load(true);
|
|
263
|
+
|
|
264
|
+
expect(
|
|
265
|
+
TelemetryContext.runWithContext({ projectId: "p1" }, () => {
|
|
266
|
+
return "ok";
|
|
267
|
+
}),
|
|
268
|
+
).toBe("ok");
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
test("setAttributes is a no-op when disabled", async () => {
|
|
272
|
+
const TelemetryContext: TelemetryContextClass = await load(true);
|
|
273
|
+
|
|
274
|
+
expect(() => {
|
|
275
|
+
TelemetryContext.setAttributes({ userId: "u1" });
|
|
276
|
+
}).not.toThrow();
|
|
277
|
+
expect(TelemetryContext.getAttributes()).toEqual({});
|
|
278
|
+
});
|
|
279
|
+
});
|