@oneuptime/common 12.0.17 → 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/BillingService.ts +8 -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/App/Dashboard/PayAsYouGoNotices.test.tsx +17 -0
- 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/BillingService.test.ts +20 -0
- 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/Types/Kubernetes/KubernetesObjectParsers.test.ts +794 -0
- package/Tests/Types/Kubernetes/KubernetesRightSizing.test.ts +53 -0
- package/Tests/Types/Measurement/MeasurementAggregationType.test.ts +100 -0
- package/Tests/Types/Monitor/MonitorStepConfigHelpers.test.ts +114 -0
- package/Tests/Types/Workspace/WorkspaceType.test.ts +53 -0
- package/Tests/UI/Utils/NotificationMethodUtil.test.ts +137 -9
- package/Tests/UI/Utils/PermissionGate.test.ts +105 -0
- package/Tests/Utils/Dashboard/Components/DashboardComponentDefaults.test.ts +257 -0
- package/Types/Billing/PayAsYouGoPricing.ts +1 -1
- 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/BillingService.js +6 -0
- package/build/dist/Server/Services/BillingService.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/Billing/PayAsYouGoPricing.js +1 -1
- package/build/dist/Types/Billing/PayAsYouGoPricing.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
|
@@ -540,6 +540,10 @@ import { AddLlmModelPrice1788300000000 } from "./1788300000000-AddLlmModelPrice"
|
|
|
540
540
|
import { AddMarketingConversionAttribution1788400000000 } from "./1788400000000-AddMarketingConversionAttribution";
|
|
541
541
|
import { RemoveLlmCostBudgetAlertColumns1788500000000 } from "./1788500000000-RemoveLlmCostBudgetAlertColumns";
|
|
542
542
|
import { AddDetectionRuleIncidentColumns1788600000000 } from "./1788600000000-AddDetectionRuleIncidentColumns";
|
|
543
|
+
import { RemoveMarketingConversionUploadState1788700000000 } from "./1788700000000-RemoveMarketingConversionUploadState";
|
|
544
|
+
import { DropMarketingConversionAddEnterpriseLicenseEmail1788800000000 } from "./1788800000000-DropMarketingConversionAddEnterpriseLicenseEmail";
|
|
545
|
+
import { RedactStoredMonitorIngestSecrets1788900000000 } from "./1788900000000-RedactStoredMonitorIngestSecrets";
|
|
546
|
+
import { AddUserSlackAndMicrosoftTeams1789000000000 } from "./1789000000000-AddUserSlackAndMicrosoftTeams";
|
|
543
547
|
import { MigrationName1787142779538 } from "./1787142779538-MigrationName";
|
|
544
548
|
import { MigrationName1787156982416 } from "./1787156982416-MigrationName";
|
|
545
549
|
|
|
@@ -1088,4 +1092,8 @@ export default [
|
|
|
1088
1092
|
AddMarketingConversionAttribution1788400000000,
|
|
1089
1093
|
RemoveLlmCostBudgetAlertColumns1788500000000,
|
|
1090
1094
|
AddDetectionRuleIncidentColumns1788600000000,
|
|
1095
|
+
RemoveMarketingConversionUploadState1788700000000,
|
|
1096
|
+
DropMarketingConversionAddEnterpriseLicenseEmail1788800000000,
|
|
1097
|
+
RedactStoredMonitorIngestSecrets1788900000000,
|
|
1098
|
+
AddUserSlackAndMicrosoftTeams1789000000000,
|
|
1091
1099
|
];
|
|
@@ -17,6 +17,13 @@ export enum QueueName {
|
|
|
17
17
|
Worker = "Worker",
|
|
18
18
|
Telemetry = "Telemetry",
|
|
19
19
|
Runbook = "Runbook",
|
|
20
|
+
/*
|
|
21
|
+
* Outbound marketing conversion webhooks. Data-carrying (the job holds the
|
|
22
|
+
* whole event), unlike Worker, which looks its function up by name. Nothing
|
|
23
|
+
* stores these events, so this queue is the only thing that survives a
|
|
24
|
+
* receiver being briefly unreachable.
|
|
25
|
+
*/
|
|
26
|
+
MarketingEvent = "MarketingEvent",
|
|
20
27
|
}
|
|
21
28
|
|
|
22
29
|
export type QueueJob = Job;
|
|
@@ -2074,6 +2074,14 @@ export class BillingService extends BaseService {
|
|
|
2074
2074
|
return "price_1U0iWJANuQdJ93r7iVAXwhdP";
|
|
2075
2075
|
}
|
|
2076
2076
|
|
|
2077
|
+
if (productType === ProductType.SecurityEvents) {
|
|
2078
|
+
if (this.isTestEnvironment()) {
|
|
2079
|
+
return "price_1U7efaANuQdJ93r74hFVOgdS";
|
|
2080
|
+
}
|
|
2081
|
+
|
|
2082
|
+
return "price_1U7edTANuQdJ93r7hR9jpBfv";
|
|
2083
|
+
}
|
|
2084
|
+
|
|
2077
2085
|
throw new BadDataException(
|
|
2078
2086
|
"Plan with productType " + productType + " not found",
|
|
2079
2087
|
);
|
|
@@ -1,10 +1,70 @@
|
|
|
1
1
|
import DatabaseService from "./DatabaseService";
|
|
2
2
|
import EnterpriseLicense from "../../Models/DatabaseModels/EnterpriseLicense";
|
|
3
|
+
import MarketingEventUtil from "../Utils/Marketing/MarketingEventUtil";
|
|
4
|
+
import { MarketingEventType } from "../../Types/Marketing/MarketingEvent";
|
|
5
|
+
import { OnCreate } from "../Types/Database/Hooks";
|
|
6
|
+
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
|
|
3
7
|
|
|
4
8
|
export class Service extends DatabaseService<EnterpriseLicense> {
|
|
5
9
|
public constructor() {
|
|
6
10
|
super(EnterpriseLicense);
|
|
7
11
|
}
|
|
12
|
+
|
|
13
|
+
@CaptureSpan()
|
|
14
|
+
protected override async onCreateSuccess(
|
|
15
|
+
_onCreate: OnCreate<EnterpriseLicense>,
|
|
16
|
+
createdItem: EnterpriseLicense,
|
|
17
|
+
): Promise<EnterpriseLicense> {
|
|
18
|
+
/*
|
|
19
|
+
* The outbound enterprise_license_issued conversion.
|
|
20
|
+
*
|
|
21
|
+
* This is where sales-led revenue enters the funnel. Everything else
|
|
22
|
+
* OneUptime emits is self-serve and knows its own value from the plan
|
|
23
|
+
* table; an enterprise contract is negotiated, so annualContractValue is
|
|
24
|
+
* the only place the number exists at all.
|
|
25
|
+
*
|
|
26
|
+
* Emitted even with no email and no ACV. A licence issued without either
|
|
27
|
+
* is still a licence issued, and a receiver that sees the event can chase
|
|
28
|
+
* the missing detail — one that never sees it cannot. The two nulls are
|
|
29
|
+
* reported honestly rather than defaulted to zero, which would quietly
|
|
30
|
+
* drag reported contract value down.
|
|
31
|
+
*/
|
|
32
|
+
MarketingEventUtil.emitInBackground(
|
|
33
|
+
MarketingEventUtil.buildEvent({
|
|
34
|
+
eventType: MarketingEventType.EnterpriseLicenseIssued,
|
|
35
|
+
eventId: `${MarketingEventType.EnterpriseLicenseIssued}:${createdItem.id?.toString()}`,
|
|
36
|
+
occurredAt: createdItem.createdAt || new Date(),
|
|
37
|
+
email: createdItem.email?.toString(),
|
|
38
|
+
/*
|
|
39
|
+
* No attribution source. A licence row is typed in by a human and
|
|
40
|
+
* carries no session, so its campaign is whatever the meeting_booked
|
|
41
|
+
* sharing its email carried — the join the receiver makes on
|
|
42
|
+
* emailHash, not something OneUptime can restate here.
|
|
43
|
+
*/
|
|
44
|
+
data: {
|
|
45
|
+
enterpriseLicenseId: createdItem.id?.toString() || "",
|
|
46
|
+
companyName: createdItem.companyName || "",
|
|
47
|
+
annualContractValueInUSD:
|
|
48
|
+
createdItem.annualContractValue === undefined ||
|
|
49
|
+
createdItem.annualContractValue === null
|
|
50
|
+
? null
|
|
51
|
+
: createdItem.annualContractValue,
|
|
52
|
+
currency: "USD",
|
|
53
|
+
isEvaluationLicense: Boolean(createdItem.isEvaluationLicense),
|
|
54
|
+
userLimit:
|
|
55
|
+
createdItem.userLimit === undefined ||
|
|
56
|
+
createdItem.userLimit === null
|
|
57
|
+
? null
|
|
58
|
+
: createdItem.userLimit,
|
|
59
|
+
expiresAt: createdItem.expiresAt
|
|
60
|
+
? createdItem.expiresAt.toISOString()
|
|
61
|
+
: null,
|
|
62
|
+
},
|
|
63
|
+
}),
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
return createdItem;
|
|
67
|
+
}
|
|
8
68
|
}
|
|
9
69
|
|
|
10
70
|
export default new Service();
|
package/Server/Services/Index.ts
CHANGED
|
@@ -145,7 +145,6 @@ import GlobalOidcProjectService from "./GlobalOidcProjectService";
|
|
|
145
145
|
import PromoCodeService from "./PromoCodeService";
|
|
146
146
|
import EnterpriseLicenseService from "./EnterpriseLicenseService";
|
|
147
147
|
import EnterpriseLicenseInstanceService from "./EnterpriseLicenseInstanceService";
|
|
148
|
-
import MarketingConversionService from "./MarketingConversionService";
|
|
149
148
|
import OpenSourceDeploymentService from "./OpenSourceDeploymentService";
|
|
150
149
|
import RecommendationDismissalService from "./RecommendationDismissalService";
|
|
151
150
|
import ResellerPlanService from "./ResellerPlanService";
|
|
@@ -213,6 +212,8 @@ import UserSmsService from "./UserSmsService";
|
|
|
213
212
|
import UserIncomingCallNumberService from "./UserIncomingCallNumberService";
|
|
214
213
|
import UserWhatsAppService from "./UserWhatsAppService";
|
|
215
214
|
import UserTelegramService from "./UserTelegramService";
|
|
215
|
+
import UserSlackService from "./UserSlackService";
|
|
216
|
+
import UserMicrosoftTeamsService from "./UserMicrosoftTeamsService";
|
|
216
217
|
import WorkflowLogService from "./WorkflowLogService";
|
|
217
218
|
// Workflows.
|
|
218
219
|
import WorkflowService from "./WorkflowService";
|
|
@@ -292,6 +293,7 @@ import WorkspaceSettingService from "./WorkspaceSettingService";
|
|
|
292
293
|
import WorkspaceNotificationRuleService from "./WorkspaceNotificationRuleService";
|
|
293
294
|
import WorkspaceNotificationLogService from "./WorkspaceNotificationLogService";
|
|
294
295
|
import WorkspaceNotificationSummaryService from "./WorkspaceNotificationSummaryService";
|
|
296
|
+
import WorkspaceUserNotificationService from "./WorkspaceUserNotificationService";
|
|
295
297
|
import OnCallDutyPolicyUserOverrideService from "./OnCallDutyPolicyUserOverrideService";
|
|
296
298
|
|
|
297
299
|
import MonitorLogService from "./MonitorLogService";
|
|
@@ -309,7 +311,6 @@ const services: Array<BaseService> = [
|
|
|
309
311
|
PromoCodeService,
|
|
310
312
|
EnterpriseLicenseService,
|
|
311
313
|
EnterpriseLicenseInstanceService,
|
|
312
|
-
MarketingConversionService,
|
|
313
314
|
OpenSourceDeploymentService,
|
|
314
315
|
|
|
315
316
|
ResellerService,
|
|
@@ -490,6 +491,8 @@ const services: Array<BaseService> = [
|
|
|
490
491
|
UserIncomingCallNumberService,
|
|
491
492
|
UserWhatsAppService,
|
|
492
493
|
UserTelegramService,
|
|
494
|
+
UserSlackService,
|
|
495
|
+
UserMicrosoftTeamsService,
|
|
493
496
|
UserTotpAuthService,
|
|
494
497
|
UserWebAuthnService,
|
|
495
498
|
|
|
@@ -587,6 +590,7 @@ const services: Array<BaseService> = [
|
|
|
587
590
|
WorkspaceNotificationRuleService,
|
|
588
591
|
WorkspaceNotificationLogService,
|
|
589
592
|
WorkspaceNotificationSummaryService,
|
|
593
|
+
WorkspaceUserNotificationService,
|
|
590
594
|
|
|
591
595
|
ProjectSCIMLogService,
|
|
592
596
|
StatusPageSCIMLogService,
|
|
@@ -57,24 +57,40 @@ export class Service extends DatabaseService<Model> {
|
|
|
57
57
|
const incidentRunMembership: string = `EXISTS (SELECT 1 FROM "AIRun" AS "run" WHERE "run"."_id" = "log"."aiRunId" AND "run"."triggeredByIncidentId" IS NOT NULL AND "run"."triggeredByAlertId" IS NULL)`;
|
|
58
58
|
const alertRunMembership: string = `EXISTS (SELECT 1 FROM "AIRun" AS "run" WHERE "run"."_id" = "log"."aiRunId" AND "run"."triggeredByIncidentId" IS NULL AND "run"."triggeredByAlertId" IS NOT NULL)`;
|
|
59
59
|
|
|
60
|
-
|
|
60
|
+
/*
|
|
61
|
+
* Placeholders and bindings are built together, per branch, because
|
|
62
|
+
* PostgreSQL derives a prepared statement's parameter count from the
|
|
63
|
+
* HIGHEST $n present in the text — not from what the driver sends. A
|
|
64
|
+
* branch that binds an argument it never references is rejected outright
|
|
65
|
+
* at Bind (bind message supplies 5 parameters, but prepared statement
|
|
66
|
+
* requires 4), and a branch that skips a placeholder is rejected at Parse
|
|
67
|
+
* (could not determine data type of parameter $4). Both are runtime-only
|
|
68
|
+
* failures no compiler catches, so the parameter array is grown next to
|
|
69
|
+
* the clause that reads it and never assembled apart from it.
|
|
70
|
+
*/
|
|
71
|
+
const params: Array<unknown> = [
|
|
72
|
+
data.projectId.toString(), // $1
|
|
73
|
+
data.since, // $2
|
|
74
|
+
data.features, // $3
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
let subjectClause: string;
|
|
61
78
|
|
|
62
79
|
if (data.incidentId) {
|
|
80
|
+
params.push(legacyIncidentFeatures); // $4
|
|
63
81
|
subjectClause = `AND (("log"."incidentId" IS NOT NULL AND "log"."alertId" IS NULL) OR ("log"."incidentId" IS NULL AND "log"."alertId" IS NULL AND (("log"."feature" = ANY($4)) OR ${incidentRunMembership})))`;
|
|
64
82
|
} else if (data.alertId) {
|
|
65
|
-
|
|
83
|
+
params.push(legacyAlertFeatures); // $4
|
|
84
|
+
subjectClause = `AND (("log"."incidentId" IS NULL AND "log"."alertId" IS NOT NULL) OR ("log"."incidentId" IS NULL AND "log"."alertId" IS NULL AND (("log"."feature" = ANY($4)) OR ${alertRunMembership})))`;
|
|
85
|
+
} else {
|
|
86
|
+
params.push(legacyIncidentFeatures, legacyAlertFeatures); // $4, $5
|
|
87
|
+
subjectClause = `AND "log"."incidentId" IS NULL AND "log"."alertId" IS NULL AND NOT ("log"."feature" = ANY($4)) AND NOT ("log"."feature" = ANY($5)) AND NOT (${incidentRunMembership}) AND NOT (${alertRunMembership})`;
|
|
66
88
|
}
|
|
67
89
|
|
|
68
90
|
const rows: Array<{ total: string | number | null }> =
|
|
69
91
|
await this.getRepository().manager.query(
|
|
70
92
|
`SELECT COALESCE(SUM("log"."totalTokens"), 0) AS "total" FROM "LlmLog" AS "log" WHERE "log"."projectId" = $1 AND "log"."createdAt" >= $2 AND "log"."feature" = ANY($3) ${subjectClause} AND "log"."deletedAt" IS NULL`,
|
|
71
|
-
|
|
72
|
-
data.projectId.toString(),
|
|
73
|
-
data.since,
|
|
74
|
-
data.features,
|
|
75
|
-
legacyIncidentFeatures,
|
|
76
|
-
legacyAlertFeatures,
|
|
77
|
-
],
|
|
93
|
+
params,
|
|
78
94
|
);
|
|
79
95
|
|
|
80
96
|
return Number(rows[0]?.total || 0);
|
|
@@ -16,6 +16,8 @@ import UserPushService from "./UserPushService";
|
|
|
16
16
|
import UserService from "./UserService";
|
|
17
17
|
import UserSmsService from "./UserSmsService";
|
|
18
18
|
import UserTelegramService from "./UserTelegramService";
|
|
19
|
+
import UserSlackService from "./UserSlackService";
|
|
20
|
+
import UserMicrosoftTeamsService from "./UserMicrosoftTeamsService";
|
|
19
21
|
import UserWebhookService from "./UserWebhookService";
|
|
20
22
|
import UserWhatsAppService from "./UserWhatsAppService";
|
|
21
23
|
import InMemoryTTLCache from "../Infrastructure/InMemoryTTLCache";
|
|
@@ -51,6 +53,8 @@ import UserNotificationRule from "../../Models/DatabaseModels/UserNotificationRu
|
|
|
51
53
|
import UserPush from "../../Models/DatabaseModels/UserPush";
|
|
52
54
|
import UserSMS from "../../Models/DatabaseModels/UserSMS";
|
|
53
55
|
import UserTelegram from "../../Models/DatabaseModels/UserTelegram";
|
|
56
|
+
import UserSlack from "../../Models/DatabaseModels/UserSlack";
|
|
57
|
+
import UserMicrosoftTeams from "../../Models/DatabaseModels/UserMicrosoftTeams";
|
|
54
58
|
import UserWebhook from "../../Models/DatabaseModels/UserWebhook";
|
|
55
59
|
import UserWhatsApp from "../../Models/DatabaseModels/UserWhatsApp";
|
|
56
60
|
|
|
@@ -116,7 +120,7 @@ export enum ResponderSource {
|
|
|
116
120
|
}
|
|
117
121
|
|
|
118
122
|
/**
|
|
119
|
-
* The
|
|
123
|
+
* The nine channels a page can be delivered on. These strings are the same literals the
|
|
120
124
|
* fallback uses for `channelsUsed` (UserNotificationRuleService.chooseFallbackChannels),
|
|
121
125
|
* deliberately: an operator reading "notified via fallback (Push, Email)" in an execution
|
|
122
126
|
* log and "Push, Email" in the readiness table must not have to translate between two
|
|
@@ -129,6 +133,8 @@ export enum ReadinessMethodType {
|
|
|
129
133
|
Call = "Call",
|
|
130
134
|
WhatsApp = "WhatsApp",
|
|
131
135
|
Telegram = "Telegram",
|
|
136
|
+
Slack = "Slack",
|
|
137
|
+
MicrosoftTeams = "Microsoft Teams",
|
|
132
138
|
Webhook = "Webhook",
|
|
133
139
|
}
|
|
134
140
|
|
|
@@ -436,6 +442,8 @@ const RULE_TYPE_SCOPES: Array<RuleTypeScope> = [
|
|
|
436
442
|
const METHOD_DISPLAY_ORDER: Array<ReadinessMethodType> = [
|
|
437
443
|
ReadinessMethodType.Push,
|
|
438
444
|
ReadinessMethodType.Email,
|
|
445
|
+
ReadinessMethodType.Slack,
|
|
446
|
+
ReadinessMethodType.MicrosoftTeams,
|
|
439
447
|
ReadinessMethodType.SMS,
|
|
440
448
|
ReadinessMethodType.Call,
|
|
441
449
|
ReadinessMethodType.WhatsApp,
|
|
@@ -2194,6 +2202,74 @@ export default class OnCallReadinessService {
|
|
|
2194
2202
|
},
|
|
2195
2203
|
});
|
|
2196
2204
|
|
|
2205
|
+
await this.readEveryPage<UserSlack>({
|
|
2206
|
+
description: "Slack notification methods",
|
|
2207
|
+
projectId: projectId,
|
|
2208
|
+
completeness: completeness,
|
|
2209
|
+
service: UserSlackService,
|
|
2210
|
+
query: {
|
|
2211
|
+
projectId: projectId,
|
|
2212
|
+
userId: new Includes(userIds),
|
|
2213
|
+
},
|
|
2214
|
+
/*
|
|
2215
|
+
* The username only — never slackUserId. The member id is the
|
|
2216
|
+
* addressable target the bot sends to; the username is the human-facing
|
|
2217
|
+
* label, and it is the one a user can recognise as theirs.
|
|
2218
|
+
*/
|
|
2219
|
+
select: {
|
|
2220
|
+
_id: true,
|
|
2221
|
+
userId: true,
|
|
2222
|
+
slackUserName: true,
|
|
2223
|
+
isVerified: true,
|
|
2224
|
+
},
|
|
2225
|
+
consumePage: (rows: Array<UserSlack>): void => {
|
|
2226
|
+
for (const row of rows) {
|
|
2227
|
+
addMethod(row, {
|
|
2228
|
+
methodType: ReadinessMethodType.Slack,
|
|
2229
|
+
maskedIdentifier: maskIdentifier(
|
|
2230
|
+
row.slackUserName,
|
|
2231
|
+
MaskedIdentifierKind.Handle,
|
|
2232
|
+
),
|
|
2233
|
+
isVerified: Boolean(row.isVerified),
|
|
2234
|
+
});
|
|
2235
|
+
}
|
|
2236
|
+
},
|
|
2237
|
+
});
|
|
2238
|
+
|
|
2239
|
+
await this.readEveryPage<UserMicrosoftTeams>({
|
|
2240
|
+
description: "Microsoft Teams notification methods",
|
|
2241
|
+
projectId: projectId,
|
|
2242
|
+
completeness: completeness,
|
|
2243
|
+
service: UserMicrosoftTeamsService,
|
|
2244
|
+
query: {
|
|
2245
|
+
projectId: projectId,
|
|
2246
|
+
userId: new Includes(userIds),
|
|
2247
|
+
},
|
|
2248
|
+
/*
|
|
2249
|
+
* The display name only — never microsoftTeamsUserId. The Entra id is
|
|
2250
|
+
* the addressable target the bot sends to; the display name is the
|
|
2251
|
+
* human-facing label, and it is the one a user can recognise as theirs.
|
|
2252
|
+
*/
|
|
2253
|
+
select: {
|
|
2254
|
+
_id: true,
|
|
2255
|
+
userId: true,
|
|
2256
|
+
microsoftTeamsUserName: true,
|
|
2257
|
+
isVerified: true,
|
|
2258
|
+
},
|
|
2259
|
+
consumePage: (rows: Array<UserMicrosoftTeams>): void => {
|
|
2260
|
+
for (const row of rows) {
|
|
2261
|
+
addMethod(row, {
|
|
2262
|
+
methodType: ReadinessMethodType.MicrosoftTeams,
|
|
2263
|
+
maskedIdentifier: maskIdentifier(
|
|
2264
|
+
row.microsoftTeamsUserName,
|
|
2265
|
+
MaskedIdentifierKind.Handle,
|
|
2266
|
+
),
|
|
2267
|
+
isVerified: Boolean(row.isVerified),
|
|
2268
|
+
});
|
|
2269
|
+
}
|
|
2270
|
+
},
|
|
2271
|
+
});
|
|
2272
|
+
|
|
2197
2273
|
await this.readEveryPage<UserWebhook>({
|
|
2198
2274
|
description: "webhook notification methods",
|
|
2199
2275
|
projectId: projectId,
|
|
@@ -2677,7 +2753,7 @@ export default class OnCallReadinessService {
|
|
|
2677
2753
|
* the real thing rather than a hopeful "some verified method".
|
|
2678
2754
|
*
|
|
2679
2755
|
* This mirrors UserNotificationRuleService.chooseFallbackChannels exactly, including
|
|
2680
|
-
* the project switches: zero-cost channels first and
|
|
2756
|
+
* the project switches: zero-cost channels first and ALL of them if present, then one
|
|
2681
2757
|
* paid channel in escalating-intrusiveness order, then webhook. If that function's
|
|
2682
2758
|
* order ever changes, this one has to change with it — a readiness surface promising
|
|
2683
2759
|
* "falls back to SMS" for a project that has SMS switched off is worse than saying
|
|
@@ -2709,6 +2785,14 @@ export default class OnCallReadinessService {
|
|
|
2709
2785
|
zeroCost.push(ReadinessMethodType.Email);
|
|
2710
2786
|
}
|
|
2711
2787
|
|
|
2788
|
+
if (has(ReadinessMethodType.Slack)) {
|
|
2789
|
+
zeroCost.push(ReadinessMethodType.Slack);
|
|
2790
|
+
}
|
|
2791
|
+
|
|
2792
|
+
if (has(ReadinessMethodType.MicrosoftTeams)) {
|
|
2793
|
+
zeroCost.push(ReadinessMethodType.MicrosoftTeams);
|
|
2794
|
+
}
|
|
2795
|
+
|
|
2712
2796
|
if (zeroCost.length > 0) {
|
|
2713
2797
|
return zeroCost;
|
|
2714
2798
|
}
|
|
@@ -16,6 +16,8 @@ import UpdateBy from "../Types/Database/UpdateBy";
|
|
|
16
16
|
import logger, { LogAttributes } from "../Utils/Logger";
|
|
17
17
|
import Errors from "../Utils/Errors";
|
|
18
18
|
import ProductAnalytics from "../Utils/ProductAnalytics";
|
|
19
|
+
import MarketingEventUtil from "../Utils/Marketing/MarketingEventUtil";
|
|
20
|
+
import { MarketingEventType } from "../../Types/Marketing/MarketingEvent";
|
|
19
21
|
import SessionReplayGateCacheStore from "../Utils/SessionReplay/SessionReplayGateCacheStore";
|
|
20
22
|
import AccessTokenService from "./AccessTokenService";
|
|
21
23
|
import BillingService from "./BillingService";
|
|
@@ -558,10 +560,21 @@ export class ProjectService extends DatabaseService<Model> {
|
|
|
558
560
|
trialEndsAt: true,
|
|
559
561
|
paymentProviderCustomerId: true,
|
|
560
562
|
createdOwnerEmail: true,
|
|
563
|
+
/*
|
|
564
|
+
* The full attribution set, not just the three UTMs the analytics
|
|
565
|
+
* properties carry: the outbound plan-change conversion is the only
|
|
566
|
+
* place the campaign that won a customer is ever reported, and a
|
|
567
|
+
* column left unselected reaches the receiver as empty rather than as
|
|
568
|
+
* an error.
|
|
569
|
+
*/
|
|
561
570
|
utmSource: true,
|
|
562
571
|
utmMedium: true,
|
|
563
572
|
utmCampaign: true,
|
|
573
|
+
utmTerm: true,
|
|
574
|
+
utmContent: true,
|
|
575
|
+
utmUrl: true,
|
|
564
576
|
clickIds: true,
|
|
577
|
+
firstTouchAttribution: true,
|
|
565
578
|
},
|
|
566
579
|
props: {
|
|
567
580
|
isRoot: true,
|
|
@@ -982,6 +995,60 @@ These are no longer recorded against the project and have to be cancelled by han
|
|
|
982
995
|
distinctId: data.project.createdOwnerEmail.toString(),
|
|
983
996
|
properties: properties,
|
|
984
997
|
});
|
|
998
|
+
|
|
999
|
+
this.emitPlanChangeMarketingEvent({
|
|
1000
|
+
project: data.project,
|
|
1001
|
+
properties: properties,
|
|
1002
|
+
isUpgrade: properties["is_upgrade"] === true,
|
|
1003
|
+
isDowngrade: properties["is_downgrade"] === true,
|
|
1004
|
+
});
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
/*
|
|
1008
|
+
* The outbound subscription_upgraded / subscription_downgraded conversions.
|
|
1009
|
+
*
|
|
1010
|
+
* Rides on the analytics pass above rather than on the plan-change path
|
|
1011
|
+
* directly, because that is where "is this an upgrade" is already decided —
|
|
1012
|
+
* by plan ORDER, not price, so a monthly-to-yearly switch at the same tier
|
|
1013
|
+
* is neither. Duplicating that comparison here is how the two would
|
|
1014
|
+
* eventually disagree about what a customer did.
|
|
1015
|
+
*
|
|
1016
|
+
* Only real tier movements are emitted. A first plan on a project with no
|
|
1017
|
+
* previous one has no direction (both flags read false, since oldPlanOrder
|
|
1018
|
+
* is null), and an interval change is not a conversion — sending either as
|
|
1019
|
+
* an upgrade would inflate exactly the number this exists to report.
|
|
1020
|
+
*/
|
|
1021
|
+
private emitPlanChangeMarketingEvent(data: {
|
|
1022
|
+
project: Model;
|
|
1023
|
+
properties: JSONObject;
|
|
1024
|
+
isUpgrade: boolean;
|
|
1025
|
+
isDowngrade: boolean;
|
|
1026
|
+
}): void {
|
|
1027
|
+
if (!data.isUpgrade && !data.isDowngrade) {
|
|
1028
|
+
return;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
const eventType: MarketingEventType = data.isUpgrade
|
|
1032
|
+
? MarketingEventType.SubscriptionUpgraded
|
|
1033
|
+
: MarketingEventType.SubscriptionDowngraded;
|
|
1034
|
+
|
|
1035
|
+
const occurredAt: Date = new Date();
|
|
1036
|
+
|
|
1037
|
+
MarketingEventUtil.emitInBackground(
|
|
1038
|
+
MarketingEventUtil.buildEvent({
|
|
1039
|
+
eventType: eventType,
|
|
1040
|
+
/*
|
|
1041
|
+
* A project can legitimately upgrade, downgrade and upgrade again, so
|
|
1042
|
+
* unlike a signup or a booking there is no naturally unique key —
|
|
1043
|
+
* the instant is what separates one change from the next.
|
|
1044
|
+
*/
|
|
1045
|
+
eventId: `${eventType}:${data.project.id?.toString()}:${occurredAt.toISOString()}`,
|
|
1046
|
+
occurredAt: occurredAt,
|
|
1047
|
+
email: data.project.createdOwnerEmail?.toString(),
|
|
1048
|
+
attributionSource: data.project,
|
|
1049
|
+
data: data.properties,
|
|
1050
|
+
}),
|
|
1051
|
+
);
|
|
985
1052
|
}
|
|
986
1053
|
|
|
987
1054
|
private async sendSubscriptionChangeWebhookSlackNotification(
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import CreateBy from "../Types/Database/CreateBy";
|
|
2
|
+
import DeleteBy from "../Types/Database/DeleteBy";
|
|
3
|
+
import { OnCreate, OnDelete } from "../Types/Database/Hooks";
|
|
4
|
+
import DatabaseService from "./DatabaseService";
|
|
5
|
+
import UserNotificationRuleService, {
|
|
6
|
+
NotificationDeletionImpact,
|
|
7
|
+
NotificationMethodChannel,
|
|
8
|
+
} from "./UserNotificationRuleService";
|
|
9
|
+
import WorkspaceProjectAuthTokenService from "./WorkspaceProjectAuthTokenService";
|
|
10
|
+
import WorkspaceUserAuthTokenService from "./WorkspaceUserAuthTokenService";
|
|
11
|
+
import logger from "../Utils/Logger";
|
|
12
|
+
import LIMIT_MAX from "../../Types/Database/LimitMax";
|
|
13
|
+
import BadDataException from "../../Types/Exception/BadDataException";
|
|
14
|
+
import { JSONObject } from "../../Types/JSON";
|
|
15
|
+
import ObjectID from "../../Types/ObjectID";
|
|
16
|
+
import WorkspaceType from "../../Types/Workspace/WorkspaceType";
|
|
17
|
+
import WorkspaceProjectAuthToken from "../../Models/DatabaseModels/WorkspaceProjectAuthToken";
|
|
18
|
+
import WorkspaceUserAuthToken from "../../Models/DatabaseModels/WorkspaceUserAuthToken";
|
|
19
|
+
import Model from "../../Models/DatabaseModels/UserMicrosoftTeams";
|
|
20
|
+
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
|
|
21
|
+
|
|
22
|
+
export class Service extends DatabaseService<Model> {
|
|
23
|
+
public constructor() {
|
|
24
|
+
super(Model);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@CaptureSpan()
|
|
28
|
+
protected override async onBeforeDelete(
|
|
29
|
+
deleteBy: DeleteBy<Model>,
|
|
30
|
+
): Promise<OnDelete<Model>> {
|
|
31
|
+
const itemsToDelete: Array<Model> = await this.findBy({
|
|
32
|
+
query: deleteBy.query,
|
|
33
|
+
select: {
|
|
34
|
+
_id: true,
|
|
35
|
+
projectId: true,
|
|
36
|
+
},
|
|
37
|
+
skip: 0,
|
|
38
|
+
limit: LIMIT_MAX,
|
|
39
|
+
props: {
|
|
40
|
+
isRoot: true,
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
for (const item of itemsToDelete) {
|
|
45
|
+
await UserNotificationRuleService.deleteBy({
|
|
46
|
+
query: {
|
|
47
|
+
userMicrosoftTeamsId: item.id!,
|
|
48
|
+
projectId: item.projectId!,
|
|
49
|
+
},
|
|
50
|
+
limit: LIMIT_MAX,
|
|
51
|
+
skip: 0,
|
|
52
|
+
props: {
|
|
53
|
+
isRoot: true,
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
deleteBy,
|
|
60
|
+
carryForward: null,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* What this user would lose if this Microsoft Teams account were deleted.
|
|
66
|
+
* Ask BEFORE calling delete; nothing here refuses anything.
|
|
67
|
+
*
|
|
68
|
+
* The hook directly above deletes every UserNotificationRule that points at
|
|
69
|
+
* this account, and the foreign key is onDelete: "CASCADE" so the rows would
|
|
70
|
+
* go even if it did not.
|
|
71
|
+
*/
|
|
72
|
+
@CaptureSpan()
|
|
73
|
+
public async getDeletionImpact(data: {
|
|
74
|
+
itemId: ObjectID;
|
|
75
|
+
projectId: ObjectID;
|
|
76
|
+
}): Promise<NotificationDeletionImpact> {
|
|
77
|
+
return UserNotificationRuleService.getNotificationMethodDeletionImpact({
|
|
78
|
+
projectId: data.projectId,
|
|
79
|
+
methodType: NotificationMethodChannel.MicrosoftTeams,
|
|
80
|
+
methodId: data.itemId,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/*
|
|
85
|
+
* A Microsoft Teams notification method is a POINTER at the user's existing
|
|
86
|
+
* workspace link, not a hand-typed address: the Microsoft Entra user id is
|
|
87
|
+
* resolved from the user's own WorkspaceUserAuthToken and never accepted
|
|
88
|
+
* from the request. That is why creation IS verification — the OAuth dance
|
|
89
|
+
* that wrote the auth-token row already proved the person controls that
|
|
90
|
+
* Microsoft Teams account.
|
|
91
|
+
*/
|
|
92
|
+
@CaptureSpan()
|
|
93
|
+
protected override async onBeforeCreate(
|
|
94
|
+
createBy: CreateBy<Model>,
|
|
95
|
+
): Promise<OnCreate<Model>> {
|
|
96
|
+
if (!createBy.props.isRoot && createBy.data.isVerified) {
|
|
97
|
+
throw new BadDataException("isVerified cannot be set to true");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (!createBy.props.isRoot && createBy.data.microsoftTeamsUserId) {
|
|
101
|
+
throw new BadDataException("microsoftTeamsUserId cannot be set directly");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (!createBy.data.projectId || !createBy.data.userId) {
|
|
105
|
+
throw new BadDataException("projectId and userId are required");
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const projectId: ObjectID = new ObjectID(
|
|
109
|
+
createBy.data.projectId.toString(),
|
|
110
|
+
);
|
|
111
|
+
const userId: ObjectID = new ObjectID(createBy.data.userId.toString());
|
|
112
|
+
|
|
113
|
+
/*
|
|
114
|
+
* One workspace link per (user, project) means a second row would be an
|
|
115
|
+
* exact duplicate of the first.
|
|
116
|
+
*/
|
|
117
|
+
const existingCount: number = (
|
|
118
|
+
await this.countBy({
|
|
119
|
+
query: {
|
|
120
|
+
projectId: projectId,
|
|
121
|
+
userId: userId,
|
|
122
|
+
},
|
|
123
|
+
props: {
|
|
124
|
+
isRoot: true,
|
|
125
|
+
},
|
|
126
|
+
})
|
|
127
|
+
).toNumber();
|
|
128
|
+
|
|
129
|
+
if (existingCount > 0) {
|
|
130
|
+
throw new BadDataException(
|
|
131
|
+
"Microsoft Teams is already added as a notification method for this project.",
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const projectAuth: WorkspaceProjectAuthToken | null =
|
|
136
|
+
await WorkspaceProjectAuthTokenService.getProjectAuth({
|
|
137
|
+
projectId: projectId,
|
|
138
|
+
workspaceType: WorkspaceType.MicrosoftTeams,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
if (!projectAuth || !projectAuth.authToken) {
|
|
142
|
+
throw new BadDataException(
|
|
143
|
+
"This project is not connected to Microsoft Teams. Please ask a project admin to connect Microsoft Teams in Project Settings > Microsoft Teams Integration.",
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const userAuth: WorkspaceUserAuthToken | null =
|
|
148
|
+
await WorkspaceUserAuthTokenService.getUserAuth({
|
|
149
|
+
projectId: projectId,
|
|
150
|
+
userId: userId,
|
|
151
|
+
workspaceType: WorkspaceType.MicrosoftTeams,
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
if (!userAuth || !userAuth.workspaceUserId) {
|
|
155
|
+
throw new BadDataException(
|
|
156
|
+
"Your Microsoft Teams account is not connected to OneUptime for this project. Please go to User Settings > Microsoft Teams Integration and connect your Microsoft Teams account first.",
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
createBy.data.microsoftTeamsUserId = userAuth.workspaceUserId;
|
|
161
|
+
createBy.data.isVerified = true;
|
|
162
|
+
|
|
163
|
+
/* Display label only — captured when the user linked their account. */
|
|
164
|
+
const displayName: string | undefined = (
|
|
165
|
+
userAuth.miscData as JSONObject | undefined
|
|
166
|
+
)?.["displayName"] as string | undefined;
|
|
167
|
+
|
|
168
|
+
if (displayName) {
|
|
169
|
+
createBy.data.microsoftTeamsUserName = displayName;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
createBy,
|
|
174
|
+
carryForward: null,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
@CaptureSpan()
|
|
179
|
+
protected override async onCreateSuccess(
|
|
180
|
+
_onCreate: OnCreate<Model>,
|
|
181
|
+
createdItem: Model,
|
|
182
|
+
): Promise<Model> {
|
|
183
|
+
/*
|
|
184
|
+
* The row is born verified (creation is gated on the live workspace
|
|
185
|
+
* link), so default on-call rules are seeded at create time — same as
|
|
186
|
+
* webhooks.
|
|
187
|
+
*/
|
|
188
|
+
if (createdItem.projectId && createdItem.userId && createdItem.id) {
|
|
189
|
+
try {
|
|
190
|
+
await UserNotificationRuleService.addDefaultNotificationRulesForVerifiedMethod(
|
|
191
|
+
{
|
|
192
|
+
projectId: createdItem.projectId,
|
|
193
|
+
userId: createdItem.userId,
|
|
194
|
+
notificationMethod: {
|
|
195
|
+
userMicrosoftTeamsId: createdItem.id,
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
);
|
|
199
|
+
} catch (err) {
|
|
200
|
+
logger.error(err);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return createdItem;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export default new Service();
|