@oneuptime/common 12.0.18 → 12.0.20
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/EnterpriseLicenseAPI.ts +94 -19
- package/Server/API/UserMicrosoftTeamsAPI.ts +127 -0
- package/Server/API/UserSlackAPI.ts +125 -0
- package/Server/EnvironmentConfig.ts +29 -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 +171 -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/Response.ts +2 -2
- package/Server/Utils/StartServer.ts +2 -2
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +191 -4
- package/Server/Views/Partials/AnalyticsConsent.ejs +533 -0
- package/Tests/App/Dashboard/AdminNotificationRulesPage.test.tsx +22 -14
- package/Tests/App/Dashboard/AdminUserNotificationMethodsPage.test.tsx +22 -11
- package/Tests/App/Dashboard/AlertEpisodeViewFields.test.tsx +465 -0
- package/Tests/App/Dashboard/DashboardChartWidgetZoom.test.tsx +302 -0
- package/Tests/App/Dashboard/DiscoveryScanWizardValidation.test.tsx +408 -0
- package/Tests/App/Dashboard/IncidentEpisodeViewFields.test.tsx +474 -0
- package/Tests/App/Dashboard/MetricSeriesInvestigateMenu.test.tsx +368 -0
- 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/EnterpriseLicenseReportUserCount.test.ts +763 -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/ProjectServiceChangePlan.test.ts +138 -0
- package/Tests/Server/Services/ProjectServiceCreateSubscriptionStarted.test.ts +516 -0
- 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/ResponseRenderAnalytics.test.ts +53 -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/Server/Views/AnalyticsConsentPartial.test.ts +241 -0
- package/Tests/UI/Components/Charts/ChartTooltipSorted.test.tsx +129 -0
- package/Tests/UI/Components/Charts/TooltipEntries.test.ts +155 -0
- package/Tests/UI/Components/Detail/DetailFieldErrors.test.tsx +347 -0
- package/Tests/UI/Components/Forms/BasicFormStepValidation.test.tsx +392 -0
- package/Tests/UI/Components/Forms/ValidationFormSteps.test.ts +549 -0
- package/Tests/UI/Utils/NotificationMethodUtil.test.ts +137 -9
- package/Tests/UI/Utils/PermissionGate.test.ts +105 -0
- package/Tests/Utils/EnterpriseLicenseSync.test.ts +562 -0
- package/Types/Analytics/RevenueEvent.ts +1 -0
- package/Types/Marketing/MarketingEvent.ts +140 -0
- package/UI/Components/Charts/Area/AreaChart.tsx +8 -1
- package/UI/Components/Charts/Bar/BarChart.tsx +8 -1
- package/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.tsx +19 -4
- package/UI/Components/Charts/ChartLibrary/BarChart/BarChart.tsx +20 -2
- package/UI/Components/Charts/ChartLibrary/LineChart/LineChart.tsx +19 -4
- package/UI/Components/Charts/ChartLibrary/Utils/TooltipEntries.ts +105 -0
- package/UI/Components/Charts/Line/LineChart.tsx +8 -1
- package/UI/Components/Detail/Detail.tsx +18 -1
- package/UI/Utils/NotificationMethodUtil.ts +41 -1
- package/UI/Utils/PermissionGate.ts +56 -0
- package/Utils/EnterpriseLicense/EnterpriseLicenseSync.ts +244 -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/EnterpriseLicenseAPI.js +66 -12
- package/build/dist/Server/API/EnterpriseLicenseAPI.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 +25 -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 +144 -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/Response.js +2 -2
- package/build/dist/Server/Utils/Response.js.map +1 -1
- package/build/dist/Server/Utils/StartServer.js +2 -2
- package/build/dist/Server/Utils/StartServer.js.map +1 -1
- 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/Analytics/RevenueEvent.js +1 -0
- package/build/dist/Types/Analytics/RevenueEvent.js.map +1 -1
- package/build/dist/Types/Marketing/MarketingEvent.js +82 -0
- package/build/dist/Types/Marketing/MarketingEvent.js.map +1 -0
- package/build/dist/UI/Components/Charts/Area/AreaChart.js +9 -1
- package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/Bar/BarChart.js +9 -1
- package/build/dist/UI/Components/Charts/Bar/BarChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js +22 -13
- package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js +27 -15
- package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js +27 -18
- package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/Utils/TooltipEntries.js +71 -0
- package/build/dist/UI/Components/Charts/ChartLibrary/Utils/TooltipEntries.js.map +1 -0
- package/build/dist/UI/Components/Charts/Line/LineChart.js +9 -1
- package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
- package/build/dist/UI/Components/Detail/Detail.js +17 -1
- package/build/dist/UI/Components/Detail/Detail.js.map +1 -1
- 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/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSync.js +174 -0
- package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSync.js.map +1 -0
- 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,140 @@
|
|
|
1
|
+
import { JSONObject } from "../JSON";
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* The outbound marketing event contract.
|
|
5
|
+
*
|
|
6
|
+
* OneUptime does not keep a conversion ledger. The moments worth
|
|
7
|
+
* measuring are emitted as signed webhooks at the instant they happen and are
|
|
8
|
+
* not stored here afterwards, so this interface — not a database table — is
|
|
9
|
+
* the whole record of what a conversion looks like.
|
|
10
|
+
*
|
|
11
|
+
* Two consequences follow from having no store, and both are the receiver's
|
|
12
|
+
* job rather than ours:
|
|
13
|
+
*
|
|
14
|
+
* - DEDUPLICATION. `eventId` is stable for a given real-world occurrence, so
|
|
15
|
+
* a redelivery carries the id the first attempt did. Retries are expected
|
|
16
|
+
* (the queue retries, and Cal retries into us), so a receiver that keys on
|
|
17
|
+
* anything else will double-count.
|
|
18
|
+
* - ORDERING. Events are delivered per-occurrence with no sequence number.
|
|
19
|
+
* A signup and a plan change seconds apart may arrive either way round;
|
|
20
|
+
* order by `occurredAt`, never by arrival.
|
|
21
|
+
*
|
|
22
|
+
* Names are the same snake_case the browser funnel uses
|
|
23
|
+
* (Common/Types/Analytics/RevenueEvent.ts) so one event means one thing on
|
|
24
|
+
* both sides. Adding a property is safe; changing what an existing one means
|
|
25
|
+
* requires a new schema version.
|
|
26
|
+
*/
|
|
27
|
+
export enum MarketingEventType {
|
|
28
|
+
SignUp = "sign_up",
|
|
29
|
+
MeetingBooked = "meeting_booked",
|
|
30
|
+
/*
|
|
31
|
+
* A project's first paid subscription, created together with the project.
|
|
32
|
+
*
|
|
33
|
+
* Deliberately not a subscription_upgraded. An upgrade is a movement between
|
|
34
|
+
* two tiers and reports expansion; this has no previous tier at all and is
|
|
35
|
+
* new business. Reporting one as the other mixes net-new with expansion
|
|
36
|
+
* revenue, and nothing downstream could separate them again.
|
|
37
|
+
*/
|
|
38
|
+
SubscriptionStarted = "subscription_started",
|
|
39
|
+
SubscriptionUpgraded = "subscription_upgraded",
|
|
40
|
+
SubscriptionDowngraded = "subscription_downgraded",
|
|
41
|
+
/*
|
|
42
|
+
* A sales-led licence was issued, carrying its annual contract value.
|
|
43
|
+
*
|
|
44
|
+
* This is the only event that reports enterprise revenue, and the only one
|
|
45
|
+
* whose email is typed in by a human rather than captured from a session —
|
|
46
|
+
* which is exactly what makes it joinable: set EnterpriseLicense.email to
|
|
47
|
+
* the address the customer booked with and this event shares an identity
|
|
48
|
+
* with the meeting_booked that preceded it, months earlier.
|
|
49
|
+
*/
|
|
50
|
+
EnterpriseLicenseIssued = "enterprise_license_issued",
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/*
|
|
54
|
+
* Which conversation a booked meeting actually is.
|
|
55
|
+
*
|
|
56
|
+
* All three Cal embeds book the same event type (oneuptimehq/demo), so without
|
|
57
|
+
* this the webhook cannot tell a free user's support call from a net-new
|
|
58
|
+
* enterprise demo — they arrive as one undifferentiated `meeting_booked`, and
|
|
59
|
+
* anything bidding on or reporting against that count values them identically.
|
|
60
|
+
*
|
|
61
|
+
* The browser event has carried this since the embeds were instrumented; the
|
|
62
|
+
* webhook did not, which meant the authoritative record was the one that could
|
|
63
|
+
* not make the distinction.
|
|
64
|
+
*
|
|
65
|
+
* `Unknown` is a real state, not a failure: a booking made through a Cal link
|
|
66
|
+
* that was not one of the instrumented embeds genuinely has no kind, and is
|
|
67
|
+
* still a booking worth emitting.
|
|
68
|
+
*/
|
|
69
|
+
export enum MeetingBookingKind {
|
|
70
|
+
EnterpriseDemo = "enterprise_demo",
|
|
71
|
+
SupportCall = "support_call",
|
|
72
|
+
ArchitectureAssessment = "architecture_assessment",
|
|
73
|
+
Unknown = "unknown",
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/*
|
|
77
|
+
* The Cal booking-metadata key the embeds write and the webhook reads. Shared
|
|
78
|
+
* so a rename cannot land on one side only - the failure mode there is silent,
|
|
79
|
+
* because a booking with no recognised kind still succeeds.
|
|
80
|
+
*/
|
|
81
|
+
export const BOOKING_KIND_METADATA_KEY: string = "ou_booking_kind";
|
|
82
|
+
|
|
83
|
+
export const MeetingBookingKinds: Array<string> =
|
|
84
|
+
Object.values(MeetingBookingKind);
|
|
85
|
+
|
|
86
|
+
export const MARKETING_EVENT_SCHEMA_VERSION: number = 1;
|
|
87
|
+
|
|
88
|
+
/*
|
|
89
|
+
* The campaign the converting visitor carried.
|
|
90
|
+
*
|
|
91
|
+
* Copied from the User or Project row for a signup or a plan change, and read
|
|
92
|
+
* out of Cal booking metadata for a booked meeting. Every field is optional:
|
|
93
|
+
* a conversion with no attribution at all is still a conversion, and is still
|
|
94
|
+
* emitted.
|
|
95
|
+
*/
|
|
96
|
+
export interface MarketingEventAttribution extends JSONObject {
|
|
97
|
+
utmSource?: string | undefined;
|
|
98
|
+
utmMedium?: string | undefined;
|
|
99
|
+
utmCampaign?: string | undefined;
|
|
100
|
+
utmTerm?: string | undefined;
|
|
101
|
+
utmContent?: string | undefined;
|
|
102
|
+
utmUrl?: string | undefined;
|
|
103
|
+
clickIds: JSONObject;
|
|
104
|
+
firstTouch: JSONObject;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export interface MarketingEvent extends JSONObject {
|
|
108
|
+
schemaVersion: number;
|
|
109
|
+
/*
|
|
110
|
+
* Stable per real-world occurrence, and the receiver's deduplication key:
|
|
111
|
+
*
|
|
112
|
+
* sign_up:{userId}
|
|
113
|
+
* meeting_booked:{calBookingUid}
|
|
114
|
+
* subscription_started:{projectId}
|
|
115
|
+
* subscription_upgraded:{projectId}:{occurredAt}
|
|
116
|
+
* subscription_downgraded:{projectId}:{occurredAt}
|
|
117
|
+
* enterprise_license_issued:{enterpriseLicenseId}
|
|
118
|
+
*
|
|
119
|
+
* Four of these are naturally unique — a user signs up once, a booking has
|
|
120
|
+
* one uid, a project has one first subscription, a licence row is issued
|
|
121
|
+
* once — so Cal retrying a delivery or the queue retrying a job cannot
|
|
122
|
+
* produce a second conversion. A plan change can legitimately recur for one
|
|
123
|
+
* project, so its id carries the instant it happened.
|
|
124
|
+
*/
|
|
125
|
+
eventId: string;
|
|
126
|
+
eventType: MarketingEventType;
|
|
127
|
+
// ISO 8601, UTC. The moment the conversion happened, not the moment it was sent.
|
|
128
|
+
occurredAt: string;
|
|
129
|
+
/*
|
|
130
|
+
* Identity. Both forms are sent: the address for a direct CRM join, and its
|
|
131
|
+
* SHA-256 (trimmed and lowercased before hashing, per the usual ad-platform
|
|
132
|
+
* normalisation) for anywhere a digest is wanted instead. Absent only when
|
|
133
|
+
* OneUptime genuinely has no address for the conversion.
|
|
134
|
+
*/
|
|
135
|
+
email?: string | undefined;
|
|
136
|
+
emailHash?: string | undefined;
|
|
137
|
+
attribution: MarketingEventAttribution;
|
|
138
|
+
// Event-specific detail. See the per-event builders in MarketingEventUtil.
|
|
139
|
+
data: JSONObject;
|
|
140
|
+
}
|
|
@@ -181,7 +181,14 @@ const AreaChartElement: FunctionComponent<AreaInternalProps> = (
|
|
|
181
181
|
|
|
182
182
|
return (
|
|
183
183
|
<div
|
|
184
|
-
|
|
184
|
+
/*
|
|
185
|
+
* `isolate` opens a stacking context so the recharts tooltip's
|
|
186
|
+
* z-index competes only inside this chart — without it the tooltip
|
|
187
|
+
* (zIndex 10, pinned to the chart's top edge) ties with the app
|
|
188
|
+
* header's sticky z-10 in the root context and paints over the
|
|
189
|
+
* navbar on half-scrolled charts.
|
|
190
|
+
*/
|
|
191
|
+
className="relative isolate flex flex-1"
|
|
185
192
|
style={props.heightInPx ? { height: `${props.heightInPx}px` } : undefined}
|
|
186
193
|
>
|
|
187
194
|
<AreaChart
|
|
@@ -115,7 +115,14 @@ const BarChartElement: FunctionComponent<BarInternalProps> = (
|
|
|
115
115
|
|
|
116
116
|
return (
|
|
117
117
|
<div
|
|
118
|
-
|
|
118
|
+
/*
|
|
119
|
+
* `isolate` opens a stacking context so the recharts tooltip's
|
|
120
|
+
* z-index competes only inside this chart — without it the tooltip
|
|
121
|
+
* (zIndex 10, pinned to the chart's top edge) ties with the app
|
|
122
|
+
* header's sticky z-10 in the root context and paints over the
|
|
123
|
+
* navbar on half-scrolled charts.
|
|
124
|
+
*/
|
|
125
|
+
className="relative isolate flex flex-1"
|
|
119
126
|
style={props.heightInPx ? { height: `${props.heightInPx}px` } : undefined}
|
|
120
127
|
>
|
|
121
128
|
<BarChart
|
|
@@ -39,6 +39,10 @@ import {
|
|
|
39
39
|
import { cx } from "../Utils/Cx";
|
|
40
40
|
import { getYAxisDomain } from "../Utils/GetYAxisDomain";
|
|
41
41
|
import { hasOnlyOneValueForKey } from "../Utils/HasOnlyOneValueForKey";
|
|
42
|
+
import {
|
|
43
|
+
PreparedTooltipEntries,
|
|
44
|
+
prepareTooltipEntries,
|
|
45
|
+
} from "../Utils/TooltipEntries";
|
|
42
46
|
import ChartCurve from "../../Types/ChartCurve";
|
|
43
47
|
|
|
44
48
|
/*
|
|
@@ -475,9 +479,15 @@ const ChartTooltip: ({
|
|
|
475
479
|
valueFormatter,
|
|
476
480
|
}: ChartTooltipProps): React.JSX.Element | null => {
|
|
477
481
|
if (active && payload && payload.length) {
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
482
|
+
/*
|
|
483
|
+
* Highest value at the hovered timestamp first, capped — on a grouped
|
|
484
|
+
* chart (one series per host/pod), the spiking series must be the
|
|
485
|
+
* first line of the tooltip, not buried at its alphabetical position.
|
|
486
|
+
*/
|
|
487
|
+
const {
|
|
488
|
+
entries: legendPayload,
|
|
489
|
+
overflowCount,
|
|
490
|
+
}: PreparedTooltipEntries<PayloadItem> = prepareTooltipEntries(payload);
|
|
481
491
|
return (
|
|
482
492
|
<div
|
|
483
493
|
className={cx(
|
|
@@ -531,6 +541,11 @@ const ChartTooltip: ({
|
|
|
531
541
|
);
|
|
532
542
|
},
|
|
533
543
|
)}
|
|
544
|
+
{overflowCount > 0 ? (
|
|
545
|
+
<p className={cx("pt-1 text-xs", "text-gray-400")}>
|
|
546
|
+
+{overflowCount} more series — highest values shown
|
|
547
|
+
</p>
|
|
548
|
+
) : null}
|
|
534
549
|
</div>
|
|
535
550
|
</div>
|
|
536
551
|
);
|
|
@@ -1426,4 +1441,4 @@ const AreaChart: React.ForwardRefExoticComponent<
|
|
|
1426
1441
|
|
|
1427
1442
|
AreaChart.displayName = "AreaChart";
|
|
1428
1443
|
|
|
1429
|
-
export { AreaChart, type AreaChartEventProps, type TooltipProps };
|
|
1444
|
+
export { AreaChart, ChartTooltip, type AreaChartEventProps, type TooltipProps };
|
|
@@ -33,6 +33,10 @@ import {
|
|
|
33
33
|
} from "../Utils/ChartColors";
|
|
34
34
|
import { cx } from "../Utils/Cx";
|
|
35
35
|
import { getYAxisDomain } from "../Utils/GetYAxisDomain";
|
|
36
|
+
import {
|
|
37
|
+
PreparedTooltipEntries,
|
|
38
|
+
prepareTooltipEntries,
|
|
39
|
+
} from "../Utils/TooltipEntries";
|
|
36
40
|
import { useOnWindowResize } from "../Utils/UseWindowOnResize";
|
|
37
41
|
|
|
38
42
|
/*
|
|
@@ -562,6 +566,15 @@ const ChartTooltip: React.FunctionComponent<ChartTooltipProps> = ({
|
|
|
562
566
|
valueFormatter,
|
|
563
567
|
}: ChartTooltipProps): React.ReactElement | null => {
|
|
564
568
|
if (active && payload && payload.length) {
|
|
569
|
+
/*
|
|
570
|
+
* Highest value at the hovered bucket first, capped — on a grouped
|
|
571
|
+
* chart (one series per host/pod), the spiking series must be the
|
|
572
|
+
* first line of the tooltip, not buried at its alphabetical position.
|
|
573
|
+
*/
|
|
574
|
+
const {
|
|
575
|
+
entries: sortedPayload,
|
|
576
|
+
overflowCount,
|
|
577
|
+
}: PreparedTooltipEntries<PayloadItem> = prepareTooltipEntries(payload);
|
|
565
578
|
return (
|
|
566
579
|
<div
|
|
567
580
|
className={cx(
|
|
@@ -586,7 +599,7 @@ const ChartTooltip: React.FunctionComponent<ChartTooltipProps> = ({
|
|
|
586
599
|
</p>
|
|
587
600
|
</div>
|
|
588
601
|
<div className={cx("space-y-1 px-4 py-2")}>
|
|
589
|
-
{
|
|
602
|
+
{sortedPayload.map(
|
|
590
603
|
({ value, category, color }: PayloadItem, index: number) => {
|
|
591
604
|
return (
|
|
592
605
|
<div
|
|
@@ -631,6 +644,11 @@ const ChartTooltip: React.FunctionComponent<ChartTooltipProps> = ({
|
|
|
631
644
|
);
|
|
632
645
|
},
|
|
633
646
|
)}
|
|
647
|
+
{overflowCount > 0 ? (
|
|
648
|
+
<p className={cx("pt-1 text-xs", "text-gray-400")}>
|
|
649
|
+
+{overflowCount} more series — highest values shown
|
|
650
|
+
</p>
|
|
651
|
+
) : null}
|
|
634
652
|
</div>
|
|
635
653
|
</div>
|
|
636
654
|
);
|
|
@@ -1194,4 +1212,4 @@ const BarChart: React.ForwardRefExoticComponent<
|
|
|
1194
1212
|
|
|
1195
1213
|
BarChart.displayName = "BarChart";
|
|
1196
1214
|
|
|
1197
|
-
export { BarChart, type BarChartEventProps, type TooltipProps };
|
|
1215
|
+
export { BarChart, ChartTooltip, type BarChartEventProps, type TooltipProps };
|
|
@@ -40,6 +40,10 @@ import {
|
|
|
40
40
|
import { cx } from "../Utils/Cx";
|
|
41
41
|
import { getYAxisDomain } from "../Utils/GetYAxisDomain";
|
|
42
42
|
import { hasOnlyOneValueForKey } from "../Utils/HasOnlyOneValueForKey";
|
|
43
|
+
import {
|
|
44
|
+
PreparedTooltipEntries,
|
|
45
|
+
prepareTooltipEntries,
|
|
46
|
+
} from "../Utils/TooltipEntries";
|
|
43
47
|
import ChartCurve from "../../Types/ChartCurve";
|
|
44
48
|
|
|
45
49
|
/*
|
|
@@ -478,9 +482,15 @@ const ChartTooltip: ({
|
|
|
478
482
|
valueFormatter,
|
|
479
483
|
}: ChartTooltipProps): React.JSX.Element | null => {
|
|
480
484
|
if (active && payload && payload.length) {
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
485
|
+
/*
|
|
486
|
+
* Highest value at the hovered timestamp first, capped — on a grouped
|
|
487
|
+
* chart (one series per host/pod), the spiking series must be the
|
|
488
|
+
* first line of the tooltip, not buried at its alphabetical position.
|
|
489
|
+
*/
|
|
490
|
+
const {
|
|
491
|
+
entries: legendPayload,
|
|
492
|
+
overflowCount,
|
|
493
|
+
}: PreparedTooltipEntries<PayloadItem> = prepareTooltipEntries(payload);
|
|
484
494
|
return (
|
|
485
495
|
<div
|
|
486
496
|
className={cx(
|
|
@@ -550,6 +560,11 @@ const ChartTooltip: ({
|
|
|
550
560
|
);
|
|
551
561
|
},
|
|
552
562
|
)}
|
|
563
|
+
{overflowCount > 0 ? (
|
|
564
|
+
<p className={cx("pt-1 text-xs", "text-gray-400")}>
|
|
565
|
+
+{overflowCount} more series — highest values shown
|
|
566
|
+
</p>
|
|
567
|
+
) : null}
|
|
553
568
|
</div>
|
|
554
569
|
</div>
|
|
555
570
|
);
|
|
@@ -1419,4 +1434,4 @@ const LineChart: React.ForwardRefExoticComponent<
|
|
|
1419
1434
|
|
|
1420
1435
|
LineChart.displayName = "LineChart";
|
|
1421
1436
|
|
|
1422
|
-
export { LineChart, type LineChartEventProps, type TooltipProps };
|
|
1437
|
+
export { ChartTooltip, LineChart, type LineChartEventProps, type TooltipProps };
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Shared ordering/capping for the hover tooltip of the Area / Line / Bar
|
|
3
|
+
* charts. With a grouped metric (say 20 hosts on one chart) the recharts
|
|
4
|
+
* payload arrives in series-render order — effectively alphabetical — so
|
|
5
|
+
* the series that is actually spiking at the hovered timestamp is buried
|
|
6
|
+
* mid-list. Sorting by the value AT THE HOVERED POINT puts the top series
|
|
7
|
+
* first, and capping the list keeps a high-cardinality chart's tooltip
|
|
8
|
+
* readable (the "+N more" line reports what was elided).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/*
|
|
12
|
+
* The slice of a recharts tooltip payload item the ordering needs. The
|
|
13
|
+
* three chart implementations each declare their own structurally-equal
|
|
14
|
+
* PayloadItem, so this stays generic instead of importing any of them.
|
|
15
|
+
*/
|
|
16
|
+
export interface SortableTooltipItem {
|
|
17
|
+
category: string;
|
|
18
|
+
value: number;
|
|
19
|
+
type?: string | undefined;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const DEFAULT_TOOLTIP_MAX_ENTRIES: number = 10;
|
|
23
|
+
|
|
24
|
+
export interface PreparedTooltipEntries<T extends SortableTooltipItem> {
|
|
25
|
+
/** Entries to render, highest value first, capped at maxEntries. */
|
|
26
|
+
entries: Array<T>;
|
|
27
|
+
/** How many entries the cap hid (0 when everything fits). */
|
|
28
|
+
overflowCount: number;
|
|
29
|
+
/** Entry count after filtering, before capping. */
|
|
30
|
+
totalCount: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/*
|
|
34
|
+
* Natural compare so host names order like cpu0 < cpu2 < cpu10 instead of
|
|
35
|
+
* lexicographically — mirrors the series chips' name ordering.
|
|
36
|
+
*/
|
|
37
|
+
function compareCategoryNames(a: string, b: string): number {
|
|
38
|
+
return String(a).localeCompare(String(b), undefined, {
|
|
39
|
+
numeric: true,
|
|
40
|
+
sensitivity: "base",
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function prepareTooltipEntries<T extends SortableTooltipItem>(
|
|
45
|
+
payload: Array<T> | undefined | null,
|
|
46
|
+
maxEntries: number = DEFAULT_TOOLTIP_MAX_ENTRIES,
|
|
47
|
+
): PreparedTooltipEntries<T> {
|
|
48
|
+
/*
|
|
49
|
+
* `type === "none"` entries are series excluded from legends/tooltips
|
|
50
|
+
* (recharts tooltipType) — every chart filtered them before this
|
|
51
|
+
* module existed, so the filter lives here now to stay uniform.
|
|
52
|
+
*/
|
|
53
|
+
const visibleEntries: Array<T> = (payload || []).filter((item: T) => {
|
|
54
|
+
return item.type !== "none";
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const sortedEntries: Array<T> = visibleEntries
|
|
58
|
+
.slice()
|
|
59
|
+
.sort((a: T, b: T): number => {
|
|
60
|
+
/*
|
|
61
|
+
* Non-finite values (missing points, the anomaly band's [low, high]
|
|
62
|
+
* array) sort last — they carry no "how high is this series right
|
|
63
|
+
* now" signal, so they must never displace a real reading.
|
|
64
|
+
*/
|
|
65
|
+
const aValue: number | null =
|
|
66
|
+
typeof a.value === "number" && Number.isFinite(a.value)
|
|
67
|
+
? a.value
|
|
68
|
+
: null;
|
|
69
|
+
const bValue: number | null =
|
|
70
|
+
typeof b.value === "number" && Number.isFinite(b.value)
|
|
71
|
+
? b.value
|
|
72
|
+
: null;
|
|
73
|
+
|
|
74
|
+
if (aValue === null && bValue === null) {
|
|
75
|
+
return compareCategoryNames(a.category, b.category);
|
|
76
|
+
}
|
|
77
|
+
if (aValue === null) {
|
|
78
|
+
return 1;
|
|
79
|
+
}
|
|
80
|
+
if (bValue === null) {
|
|
81
|
+
return -1;
|
|
82
|
+
}
|
|
83
|
+
if (bValue !== aValue) {
|
|
84
|
+
return bValue - aValue;
|
|
85
|
+
}
|
|
86
|
+
return compareCategoryNames(a.category, b.category);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const totalCount: number = sortedEntries.length;
|
|
90
|
+
|
|
91
|
+
const cap: number =
|
|
92
|
+
Number.isInteger(maxEntries) && maxEntries > 0
|
|
93
|
+
? maxEntries
|
|
94
|
+
: DEFAULT_TOOLTIP_MAX_ENTRIES;
|
|
95
|
+
|
|
96
|
+
if (totalCount <= cap) {
|
|
97
|
+
return { entries: sortedEntries, overflowCount: 0, totalCount };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
entries: sortedEntries.slice(0, cap),
|
|
102
|
+
overflowCount: totalCount - cap,
|
|
103
|
+
totalCount,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
@@ -161,7 +161,14 @@ const LineChartElement: FunctionComponent<LineInternalProps> = (
|
|
|
161
161
|
|
|
162
162
|
return (
|
|
163
163
|
<div
|
|
164
|
-
|
|
164
|
+
/*
|
|
165
|
+
* `isolate` opens a stacking context so the recharts tooltip's
|
|
166
|
+
* z-index competes only inside this chart — without it the tooltip
|
|
167
|
+
* (zIndex 10, pinned to the chart's top edge) ties with the app
|
|
168
|
+
* header's sticky z-10 in the root context and paints over the
|
|
169
|
+
* navbar on half-scrolled charts.
|
|
170
|
+
*/
|
|
171
|
+
className="relative isolate flex flex-1"
|
|
165
172
|
style={props.heightInPx ? { height: `${props.heightInPx}px` } : undefined}
|
|
166
173
|
>
|
|
167
174
|
<LineChart
|
|
@@ -715,7 +715,24 @@ const Detail: DetailFunction = <T extends GenericObject>(
|
|
|
715
715
|
}
|
|
716
716
|
|
|
717
717
|
if (field.getElement) {
|
|
718
|
-
|
|
718
|
+
/*
|
|
719
|
+
* getField runs inside Detail's own render, so a throw out of a field
|
|
720
|
+
* renderer unwinds the whole route to the app level ErrorBoundary and
|
|
721
|
+
* replaces the page with "Something went wrong". One misbehaving field
|
|
722
|
+
* must never cost the entire page: log the misconfiguration and keep
|
|
723
|
+
* whatever this field type already rendered - the entity badge, or ""
|
|
724
|
+
* so the placeholder below can stand in.
|
|
725
|
+
*/
|
|
726
|
+
try {
|
|
727
|
+
data = field.getElement(props.item);
|
|
728
|
+
} catch (err) {
|
|
729
|
+
Logger.error(
|
|
730
|
+
"Detail: getElement for '" +
|
|
731
|
+
fieldKeyStr +
|
|
732
|
+
"' threw and was ignored. " +
|
|
733
|
+
(err instanceof Error ? err.message : String(err)),
|
|
734
|
+
);
|
|
735
|
+
}
|
|
719
736
|
}
|
|
720
737
|
|
|
721
738
|
let className: string = "sm:col-span-1";
|
|
@@ -7,6 +7,8 @@ import UserNotificationRule from "../../Models/DatabaseModels/UserNotificationRu
|
|
|
7
7
|
import UserPush from "../../Models/DatabaseModels/UserPush";
|
|
8
8
|
import UserSMS from "../../Models/DatabaseModels/UserSMS";
|
|
9
9
|
import UserTelegram from "../../Models/DatabaseModels/UserTelegram";
|
|
10
|
+
import UserSlack from "../../Models/DatabaseModels/UserSlack";
|
|
11
|
+
import UserMicrosoftTeams from "../../Models/DatabaseModels/UserMicrosoftTeams";
|
|
10
12
|
import UserWebhook from "../../Models/DatabaseModels/UserWebhook";
|
|
11
13
|
import UserWhatsApp from "../../Models/DatabaseModels/UserWhatsApp";
|
|
12
14
|
import ObjectID from "../../Types/ObjectID";
|
|
@@ -37,6 +39,8 @@ export interface NotificationMethodModels {
|
|
|
37
39
|
userPush: Array<UserPush>;
|
|
38
40
|
userWhatsApps: Array<UserWhatsApp>;
|
|
39
41
|
userTelegrams: Array<UserTelegram>;
|
|
42
|
+
userSlacks: Array<UserSlack>;
|
|
43
|
+
userMicrosoftTeamsAccounts: Array<UserMicrosoftTeams>;
|
|
40
44
|
userWebhooks: Array<UserWebhook>;
|
|
41
45
|
}
|
|
42
46
|
|
|
@@ -74,6 +78,16 @@ const NOTIFICATION_METHOD_RELATIONS: Array<NotificationMethodRelation> = [
|
|
|
74
78
|
title: "Telegram",
|
|
75
79
|
columns: ["telegramUserHandle", "telegramChatId"],
|
|
76
80
|
},
|
|
81
|
+
{
|
|
82
|
+
relationName: "userSlack",
|
|
83
|
+
title: "Slack",
|
|
84
|
+
columns: ["slackUserName", "slackUserId"],
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
relationName: "userMicrosoftTeams",
|
|
88
|
+
title: "Microsoft Teams",
|
|
89
|
+
columns: ["microsoftTeamsUserName", "microsoftTeamsUserId"],
|
|
90
|
+
},
|
|
77
91
|
/*
|
|
78
92
|
* Only `name` is read here. A webhook URL is a bearer credential for
|
|
79
93
|
* Slack/Discord/Teams style hooks, so it is deliberately never selected into
|
|
@@ -185,6 +199,24 @@ export default class NotificationMethodUtil {
|
|
|
185
199
|
);
|
|
186
200
|
}
|
|
187
201
|
|
|
202
|
+
if (model instanceof UserSlack) {
|
|
203
|
+
return (
|
|
204
|
+
"Slack: " +
|
|
205
|
+
(NotificationMethodUtil.readColumn(model, "slackUserName") ||
|
|
206
|
+
NotificationMethodUtil.readColumn(model, "slackUserId") ||
|
|
207
|
+
"Unknown Account")
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (model instanceof UserMicrosoftTeams) {
|
|
212
|
+
return (
|
|
213
|
+
"Microsoft Teams: " +
|
|
214
|
+
(NotificationMethodUtil.readColumn(model, "microsoftTeamsUserName") ||
|
|
215
|
+
NotificationMethodUtil.readColumn(model, "microsoftTeamsUserId") ||
|
|
216
|
+
"Unknown Account")
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
188
220
|
const identifier: string =
|
|
189
221
|
NotificationMethodUtil.readColumn(model, "phone") ||
|
|
190
222
|
NotificationMethodUtil.readColumn(model, "email") ||
|
|
@@ -207,7 +239,7 @@ export default class NotificationMethodUtil {
|
|
|
207
239
|
|
|
208
240
|
/*
|
|
209
241
|
* Maps the id chosen in the dropdown back onto the rule being created. The
|
|
210
|
-
* dropdown is single-select, so at most one of the
|
|
242
|
+
* dropdown is single-select, so at most one of the nine foreign keys is set.
|
|
211
243
|
* An id that matches nothing leaves the rule untouched, and the server then
|
|
212
244
|
* rejects the create as having no notification method.
|
|
213
245
|
*/
|
|
@@ -258,6 +290,12 @@ export default class NotificationMethodUtil {
|
|
|
258
290
|
assignById(models.userTelegrams, (id: ObjectID) => {
|
|
259
291
|
rule.userTelegramId = id;
|
|
260
292
|
});
|
|
293
|
+
assignById(models.userSlacks, (id: ObjectID) => {
|
|
294
|
+
rule.userSlackId = id;
|
|
295
|
+
});
|
|
296
|
+
assignById(models.userMicrosoftTeamsAccounts, (id: ObjectID) => {
|
|
297
|
+
rule.userMicrosoftTeamsId = id;
|
|
298
|
+
});
|
|
261
299
|
assignById(models.userWebhooks, (id: ObjectID) => {
|
|
262
300
|
rule.userWebhookId = id;
|
|
263
301
|
});
|
|
@@ -279,6 +317,8 @@ export default class NotificationMethodUtil {
|
|
|
279
317
|
...models.userPush,
|
|
280
318
|
...models.userWhatsApps,
|
|
281
319
|
...models.userTelegrams,
|
|
320
|
+
...models.userSlacks,
|
|
321
|
+
...models.userMicrosoftTeamsAccounts,
|
|
282
322
|
...models.userWebhooks,
|
|
283
323
|
];
|
|
284
324
|
}
|
|
@@ -4,6 +4,7 @@ import Permission, {
|
|
|
4
4
|
PermissionHelper,
|
|
5
5
|
PermissionProps,
|
|
6
6
|
} from "../../Types/Permission";
|
|
7
|
+
import { ColumnAccessControl } from "../../Types/BaseDatabase/AccessControl";
|
|
7
8
|
import PermissionUtil from "./Permission";
|
|
8
9
|
import User from "./User";
|
|
9
10
|
|
|
@@ -52,6 +53,15 @@ export interface PermissionCheckableModel {
|
|
|
52
53
|
getDeletePermissions: () => Array<Permission>;
|
|
53
54
|
}
|
|
54
55
|
|
|
56
|
+
/*
|
|
57
|
+
* A model that declares column-level access control. Separate from
|
|
58
|
+
* PermissionCheckableModel because column gating answers a different question
|
|
59
|
+
* ("may this field be SELECTED?") and only the database models carry it.
|
|
60
|
+
*/
|
|
61
|
+
export interface ColumnPermissionCheckableModel {
|
|
62
|
+
getColumnAccessControlForAllColumns: () => Dictionary<ColumnAccessControl>;
|
|
63
|
+
}
|
|
64
|
+
|
|
55
65
|
export interface PermissionGateOptions {
|
|
56
66
|
/*
|
|
57
67
|
* Overrides the permissions read from storage. Only used by tests and by
|
|
@@ -270,6 +280,52 @@ export default class PermissionGate {
|
|
|
270
280
|
};
|
|
271
281
|
}
|
|
272
282
|
|
|
283
|
+
/*
|
|
284
|
+
* Whether the signed-in user may SELECT a column.
|
|
285
|
+
*
|
|
286
|
+
* Column access control is enforced differently from record access control:
|
|
287
|
+
* asking for a column you cannot read is not degraded, it is fatal.
|
|
288
|
+
* ColumnPermission throws `User is not allowed to read on <column> column of
|
|
289
|
+
* <model>` and the whole request fails, so one unreadable field in a select
|
|
290
|
+
* takes down the entire page rather than blanking one value. That is why
|
|
291
|
+
* ModelAction has no column-level members (see the comment on the enum) and
|
|
292
|
+
* why callers must ask this BEFORE building the select, not after.
|
|
293
|
+
*
|
|
294
|
+
* Deliberately fails closed. When the permission snapshot has not landed yet
|
|
295
|
+
* this returns false and the caller omits the field: the page renders
|
|
296
|
+
* without that one value, which is recoverable. Failing open would send the
|
|
297
|
+
* column anyway and hard-fail the request, which is not.
|
|
298
|
+
*
|
|
299
|
+
* A column with no declared read ACL is readable - there is nothing to
|
|
300
|
+
* enforce, and ColumnPermission agrees.
|
|
301
|
+
*/
|
|
302
|
+
public static canReadColumn(
|
|
303
|
+
model: ColumnPermissionCheckableModel,
|
|
304
|
+
columnName: string,
|
|
305
|
+
options?: PermissionGateOptions | undefined,
|
|
306
|
+
): boolean {
|
|
307
|
+
if (User.isMasterAdmin()) {
|
|
308
|
+
return true;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const accessControl: ColumnAccessControl | undefined =
|
|
312
|
+
model.getColumnAccessControlForAllColumns()[columnName];
|
|
313
|
+
|
|
314
|
+
const columnPermissions: Array<Permission> = accessControl?.read || [];
|
|
315
|
+
|
|
316
|
+
if (columnPermissions.length === 0) {
|
|
317
|
+
return true;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const userPermissions: Array<Permission> =
|
|
321
|
+
options?.permissions ?? PermissionUtil.getAllPermissions();
|
|
322
|
+
|
|
323
|
+
return PermissionHelper.doesPermissionsIntersect(
|
|
324
|
+
userPermissions,
|
|
325
|
+
columnPermissions,
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
|
|
273
329
|
/* Test seam - the props lookup is memoized for the lifetime of the page. */
|
|
274
330
|
public static clearPermissionPropsCache(): void {
|
|
275
331
|
permissionPropsCache = null;
|