@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
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import BaseService from "./BaseService";
|
|
2
|
+
import UserOnCallLogTimelineService from "./UserOnCallLogTimelineService";
|
|
3
|
+
import WorkspaceNotificationLogService from "./WorkspaceNotificationLogService";
|
|
4
|
+
import WorkspaceProjectAuthTokenService from "./WorkspaceProjectAuthTokenService";
|
|
5
|
+
import SlackUtil from "../Utils/Workspace/Slack/Slack";
|
|
6
|
+
import MicrosoftTeamsUtil from "../Utils/Workspace/MicrosoftTeams/MicrosoftTeams";
|
|
7
|
+
import logger from "../Utils/Logger";
|
|
8
|
+
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
|
|
9
|
+
import WorkspaceProjectAuthToken from "../../Models/DatabaseModels/WorkspaceProjectAuthToken";
|
|
10
|
+
import BadDataException from "../../Types/Exception/BadDataException";
|
|
11
|
+
import ObjectID from "../../Types/ObjectID";
|
|
12
|
+
import UserNotificationStatus from "../../Types/UserNotification/UserNotificationStatus";
|
|
13
|
+
import WorkspaceNotificationActionType from "../../Types/Workspace/WorkspaceNotificationActionType";
|
|
14
|
+
import WorkspaceNotificationStatus from "../../Types/Workspace/WorkspaceNotificationStatus";
|
|
15
|
+
import { WorkspaceMessageBlock } from "../../Types/Workspace/WorkspaceMessagePayload";
|
|
16
|
+
import WorkspaceType, {
|
|
17
|
+
getWorkspaceTypeDisplayName,
|
|
18
|
+
} from "../../Types/Workspace/WorkspaceType";
|
|
19
|
+
|
|
20
|
+
/*
|
|
21
|
+
* Delivers a OneUptime notification to ONE person as a workspace direct
|
|
22
|
+
* message — the send half of the Slack / Microsoft Teams notification methods
|
|
23
|
+
* (UserSlack / UserMicrosoftTeams rows created under User Settings →
|
|
24
|
+
* Notification Methods).
|
|
25
|
+
*
|
|
26
|
+
* This is deliberately NOT part of the channel-oriented workspace machinery
|
|
27
|
+
* (WorkspaceNotificationRuleService and friends): those post into project
|
|
28
|
+
* channels selected by project-level rules, while this addresses a single
|
|
29
|
+
* user id captured from that user's own workspace link, using the project's
|
|
30
|
+
* bot credentials. SMS / Call / Telegram route through the Notification
|
|
31
|
+
* FeatureSet over HTTP because provider credentials and spend accounting live
|
|
32
|
+
* there; workspace bot tokens live in this database, so this service sends
|
|
33
|
+
* in-process instead.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
export interface SendWorkspaceDirectMessageOptions {
|
|
37
|
+
projectId: ObjectID;
|
|
38
|
+
workspaceType: WorkspaceType;
|
|
39
|
+
/*
|
|
40
|
+
* Slack: the member id (U…). Microsoft Teams: the Microsoft Entra object
|
|
41
|
+
* id. Both are what the corresponding notification-method row stores.
|
|
42
|
+
*/
|
|
43
|
+
workspaceUserId: string;
|
|
44
|
+
messageBlocks: Array<WorkspaceMessageBlock>;
|
|
45
|
+
/* Short plain-text form of the message, stored on the workspace log row. */
|
|
46
|
+
messageSummary?: string | undefined;
|
|
47
|
+
userId?: ObjectID | undefined;
|
|
48
|
+
/*
|
|
49
|
+
* When set, the on-call timeline row is flipped to Sent / Error after the
|
|
50
|
+
* send attempt, mirroring what the Notification FeatureSet does for the
|
|
51
|
+
* other channels.
|
|
52
|
+
*/
|
|
53
|
+
userOnCallLogTimelineId?: ObjectID | undefined;
|
|
54
|
+
incidentId?: ObjectID | undefined;
|
|
55
|
+
alertId?: ObjectID | undefined;
|
|
56
|
+
alertEpisodeId?: ObjectID | undefined;
|
|
57
|
+
incidentEpisodeId?: ObjectID | undefined;
|
|
58
|
+
onCallPolicyId?: ObjectID | undefined;
|
|
59
|
+
onCallPolicyEscalationRuleId?: ObjectID | undefined;
|
|
60
|
+
onCallScheduleId?: ObjectID | undefined;
|
|
61
|
+
teamId?: ObjectID | undefined;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export class WorkspaceUserNotificationService extends BaseService {
|
|
65
|
+
public constructor() {
|
|
66
|
+
super();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
@CaptureSpan()
|
|
70
|
+
public async sendDirectMessageToUser(
|
|
71
|
+
options: SendWorkspaceDirectMessageOptions,
|
|
72
|
+
): Promise<void> {
|
|
73
|
+
const workspaceDisplayName: string = getWorkspaceTypeDisplayName(
|
|
74
|
+
options.workspaceType,
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
let sendError: Error | null = null;
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
if (!options.workspaceUserId) {
|
|
81
|
+
throw new BadDataException(
|
|
82
|
+
`This account is not connected to ${workspaceDisplayName}. Please go to User Settings and connect the account.`,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const projectAuth: WorkspaceProjectAuthToken | null =
|
|
87
|
+
await WorkspaceProjectAuthTokenService.getProjectAuth({
|
|
88
|
+
projectId: options.projectId,
|
|
89
|
+
workspaceType: options.workspaceType,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
if (!projectAuth || !projectAuth.authToken) {
|
|
93
|
+
throw new BadDataException(
|
|
94
|
+
`This project is not connected to ${workspaceDisplayName}. Please go to Project Settings and connect the account.`,
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (options.workspaceType === WorkspaceType.Slack) {
|
|
99
|
+
await SlackUtil.sendDirectMessageToUser({
|
|
100
|
+
authToken: projectAuth.authToken,
|
|
101
|
+
workspaceUserId: options.workspaceUserId,
|
|
102
|
+
messageBlocks: options.messageBlocks,
|
|
103
|
+
});
|
|
104
|
+
} else if (options.workspaceType === WorkspaceType.MicrosoftTeams) {
|
|
105
|
+
await MicrosoftTeamsUtil.sendDirectMessageToUserAsBot({
|
|
106
|
+
projectId: options.projectId,
|
|
107
|
+
workspaceUserId: options.workspaceUserId,
|
|
108
|
+
messageBlocks: options.messageBlocks,
|
|
109
|
+
});
|
|
110
|
+
} else {
|
|
111
|
+
throw new BadDataException(
|
|
112
|
+
`Direct messages are not supported for workspace type ${options.workspaceType}.`,
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
} catch (error: unknown) {
|
|
116
|
+
const errorMessage: string =
|
|
117
|
+
error instanceof Error && error.message
|
|
118
|
+
? error.message
|
|
119
|
+
: `${error as string}`;
|
|
120
|
+
|
|
121
|
+
sendError = error instanceof Error ? error : new Error(errorMessage);
|
|
122
|
+
|
|
123
|
+
logger.error(
|
|
124
|
+
`Failed to send ${workspaceDisplayName} direct message notification.`,
|
|
125
|
+
);
|
|
126
|
+
logger.error(error);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/*
|
|
130
|
+
* Log the attempt for admin visibility. Best-effort: a failed log write
|
|
131
|
+
* must never turn a delivered page into a reported failure (or mask a
|
|
132
|
+
* real send error with a logging one).
|
|
133
|
+
*/
|
|
134
|
+
try {
|
|
135
|
+
await WorkspaceNotificationLogService.createWorkspaceLog(
|
|
136
|
+
{
|
|
137
|
+
projectId: options.projectId,
|
|
138
|
+
workspaceType: options.workspaceType,
|
|
139
|
+
actionType: WorkspaceNotificationActionType.SendMessage,
|
|
140
|
+
status: sendError
|
|
141
|
+
? WorkspaceNotificationStatus.Error
|
|
142
|
+
: WorkspaceNotificationStatus.Success,
|
|
143
|
+
message: options.messageSummary,
|
|
144
|
+
statusMessage: sendError
|
|
145
|
+
? sendError.message
|
|
146
|
+
: `Direct message sent on ${workspaceDisplayName}.`,
|
|
147
|
+
userId: options.userId,
|
|
148
|
+
incidentId: options.incidentId,
|
|
149
|
+
alertId: options.alertId,
|
|
150
|
+
alertEpisodeId: options.alertEpisodeId,
|
|
151
|
+
incidentEpisodeId: options.incidentEpisodeId,
|
|
152
|
+
onCallDutyPolicyId: options.onCallPolicyId,
|
|
153
|
+
onCallDutyPolicyEscalationRuleId:
|
|
154
|
+
options.onCallPolicyEscalationRuleId,
|
|
155
|
+
onCallDutyPolicyScheduleId: options.onCallScheduleId,
|
|
156
|
+
teamId: options.teamId,
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
isRoot: true,
|
|
160
|
+
},
|
|
161
|
+
);
|
|
162
|
+
} catch (logError) {
|
|
163
|
+
logger.error("Failed to write workspace notification log.");
|
|
164
|
+
logger.error(logError);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (options.userOnCallLogTimelineId) {
|
|
168
|
+
await UserOnCallLogTimelineService.updateOneById({
|
|
169
|
+
id: options.userOnCallLogTimelineId,
|
|
170
|
+
data: {
|
|
171
|
+
status: sendError
|
|
172
|
+
? UserNotificationStatus.Error
|
|
173
|
+
: UserNotificationStatus.Sent,
|
|
174
|
+
statusMessage: sendError
|
|
175
|
+
? sendError.message
|
|
176
|
+
: `Message sent on ${workspaceDisplayName}.`,
|
|
177
|
+
},
|
|
178
|
+
props: {
|
|
179
|
+
isRoot: true,
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (sendError) {
|
|
185
|
+
throw sendError;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export default new WorkspaceUserNotificationService();
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import Attribution from "../Attribution";
|
|
2
|
+
import MarketingEventWebhook from "./MarketingEventWebhook";
|
|
3
|
+
import Queue, { QueueName } from "../../Infrastructure/Queue";
|
|
4
|
+
import { JSONObject } from "../../../Types/JSON";
|
|
5
|
+
import {
|
|
6
|
+
MARKETING_EVENT_SCHEMA_VERSION,
|
|
7
|
+
MarketingEvent,
|
|
8
|
+
MarketingEventAttribution,
|
|
9
|
+
MarketingEventType,
|
|
10
|
+
} from "../../../Types/Marketing/MarketingEvent";
|
|
11
|
+
import logger from "../Logger";
|
|
12
|
+
|
|
13
|
+
/*
|
|
14
|
+
* How many times the queue delivers one event before giving up, and the base
|
|
15
|
+
* for its exponential backoff. Five attempts at 30s exponential spans roughly
|
|
16
|
+
* eight minutes, which covers a rolling deploy of the receiver.
|
|
17
|
+
*
|
|
18
|
+
* There is no dead-letter store and deliberately so: the point of this design
|
|
19
|
+
* is that OneUptime does not hold marketing data. An event that exhausts its
|
|
20
|
+
* attempts is logged as an error and lost, which is the cost of not keeping a
|
|
21
|
+
* ledger and should be alerted on rather than engineered around here.
|
|
22
|
+
*/
|
|
23
|
+
const DELIVERY_ATTEMPTS: number = 5;
|
|
24
|
+
const DELIVERY_BACKOFF_MS: number = 30_000;
|
|
25
|
+
|
|
26
|
+
/*
|
|
27
|
+
* Anything carrying the attribution columns. User and Project both do —
|
|
28
|
+
* ProjectService copies them off the creating user at project creation — so
|
|
29
|
+
* one shape serves every caller and they cannot drift apart.
|
|
30
|
+
*/
|
|
31
|
+
export interface AttributionSource {
|
|
32
|
+
utmSource?: string | undefined;
|
|
33
|
+
utmMedium?: string | undefined;
|
|
34
|
+
utmCampaign?: string | undefined;
|
|
35
|
+
utmTerm?: string | undefined;
|
|
36
|
+
utmContent?: string | undefined;
|
|
37
|
+
utmUrl?: string | undefined;
|
|
38
|
+
clickIds?: JSONObject | undefined;
|
|
39
|
+
firstTouchAttribution?: JSONObject | undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export default class MarketingEventUtil {
|
|
43
|
+
public static buildAttribution(
|
|
44
|
+
source: AttributionSource | undefined,
|
|
45
|
+
): MarketingEventAttribution {
|
|
46
|
+
return {
|
|
47
|
+
utmSource: source?.utmSource,
|
|
48
|
+
utmMedium: source?.utmMedium,
|
|
49
|
+
utmCampaign: source?.utmCampaign,
|
|
50
|
+
utmTerm: source?.utmTerm,
|
|
51
|
+
utmContent: source?.utmContent,
|
|
52
|
+
utmUrl: source?.utmUrl,
|
|
53
|
+
clickIds: source?.clickIds || {},
|
|
54
|
+
firstTouch: source?.firstTouchAttribution || {},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
public static buildEvent(data: {
|
|
59
|
+
eventType: MarketingEventType;
|
|
60
|
+
eventId: string;
|
|
61
|
+
occurredAt: Date;
|
|
62
|
+
email?: string | undefined;
|
|
63
|
+
attributionSource?: AttributionSource | undefined;
|
|
64
|
+
data?: JSONObject | undefined;
|
|
65
|
+
}): MarketingEvent {
|
|
66
|
+
const emailHash: string | null = Attribution.hashEmail(data.email);
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
schemaVersion: MARKETING_EVENT_SCHEMA_VERSION,
|
|
70
|
+
eventId: data.eventId,
|
|
71
|
+
eventType: data.eventType,
|
|
72
|
+
occurredAt: data.occurredAt.toISOString(),
|
|
73
|
+
email: data.email,
|
|
74
|
+
emailHash: emailHash || undefined,
|
|
75
|
+
attribution: this.buildAttribution(data.attributionSource),
|
|
76
|
+
data: data.data || {},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/*
|
|
81
|
+
* Hand one event to the queue.
|
|
82
|
+
*
|
|
83
|
+
* Never throws and never awaits delivery. Every caller is a commercial code
|
|
84
|
+
* path — a signup completing, a plan change being written, a booking being
|
|
85
|
+
* accepted — and none of them may fail, block, or slow down because a
|
|
86
|
+
* marketing endpoint is unreachable. Enqueue is the only synchronous part
|
|
87
|
+
* and it is a single Redis write.
|
|
88
|
+
*/
|
|
89
|
+
public static async emit(event: MarketingEvent): Promise<void> {
|
|
90
|
+
if (MarketingEventWebhook.isMisconfigured()) {
|
|
91
|
+
logger.error(
|
|
92
|
+
`MarketingEvent: MARKETING_WEBHOOK_URL is set but MARKETING_WEBHOOK_SECRET is not — refusing to send ${event.eventType} unsigned. Set the secret or unset the URL.`,
|
|
93
|
+
);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (!MarketingEventWebhook.isConfigured()) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
await Queue.addJob(
|
|
103
|
+
QueueName.MarketingEvent,
|
|
104
|
+
event.eventId,
|
|
105
|
+
event.eventType,
|
|
106
|
+
event as JSONObject,
|
|
107
|
+
{
|
|
108
|
+
attempts: DELIVERY_ATTEMPTS,
|
|
109
|
+
backoffDelayInMs: DELIVERY_BACKOFF_MS,
|
|
110
|
+
},
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
logger.debug(
|
|
114
|
+
`MarketingEvent: queued ${event.eventType} (${event.eventId})`,
|
|
115
|
+
);
|
|
116
|
+
} catch (err) {
|
|
117
|
+
logger.error(
|
|
118
|
+
`MarketingEvent: failed to queue ${event.eventType} (${event.eventId}): ${err}`,
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/*
|
|
124
|
+
* Fire-and-forget wrapper for callers that are not async or must not await.
|
|
125
|
+
*
|
|
126
|
+
* Guarantees it neither throws nor rejects. Every caller is inside a
|
|
127
|
+
* commercial transaction that has already succeeded by the time it gets
|
|
128
|
+
* here — a user is created, a plan is changed, a booking is verified — so
|
|
129
|
+
* the one thing this must never do is turn a completed action into a failed
|
|
130
|
+
* one. The rejection path is covered by .catch and the synchronous path by
|
|
131
|
+
* the try, because an argument expression that throws (buildEvent on a
|
|
132
|
+
* malformed row) would otherwise propagate straight into the caller.
|
|
133
|
+
*/
|
|
134
|
+
public static emitInBackground(event: MarketingEvent): void {
|
|
135
|
+
try {
|
|
136
|
+
this.emit(event).catch((err: Error) => {
|
|
137
|
+
logger.error(
|
|
138
|
+
`MarketingEvent: failed to emit ${event.eventType}: ${err}`,
|
|
139
|
+
);
|
|
140
|
+
});
|
|
141
|
+
} catch (err) {
|
|
142
|
+
logger.error(`MarketingEvent: failed to emit ${event.eventType}: ${err}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import axios, { AxiosError, AxiosResponse } from "axios";
|
|
2
|
+
import crypto from "crypto";
|
|
3
|
+
import {
|
|
4
|
+
MarketingWebhookSecret,
|
|
5
|
+
MarketingWebhookUrl,
|
|
6
|
+
} from "../../EnvironmentConfig";
|
|
7
|
+
import { MarketingEvent } from "../../../Types/Marketing/MarketingEvent";
|
|
8
|
+
import logger from "../Logger";
|
|
9
|
+
|
|
10
|
+
const REQUEST_TIMEOUT_MS: number = 15000;
|
|
11
|
+
|
|
12
|
+
/*
|
|
13
|
+
* Delivers marketing conversion events to one operator-configured endpoint.
|
|
14
|
+
*
|
|
15
|
+
* OneUptime keeps no conversion ledger, so this is the only exit for a signup,
|
|
16
|
+
* a booked meeting or a plan change. That has one consequence worth stating
|
|
17
|
+
* plainly: a delivery this class gives up on is gone, because there is no row
|
|
18
|
+
* anywhere to reconcile against later. deliver() therefore THROWS on anything
|
|
19
|
+
* that might succeed on a retry, and the queue is what retries it.
|
|
20
|
+
*
|
|
21
|
+
* SIGNING
|
|
22
|
+
*
|
|
23
|
+
* The signature is HMAC-SHA256 over the exact request body bytes, hex encoded,
|
|
24
|
+
* in `x-oneuptime-signature-256`. This is the same scheme OneUptime verifies
|
|
25
|
+
* on the way in from Cal, deliberately: one thing to understand rather than
|
|
26
|
+
* two. The receiver must compute its digest over the raw bytes it received —
|
|
27
|
+
* JSON parsed and re-serialised is NOT equivalent, because whitespace, key
|
|
28
|
+
* order and escaping all change the digest.
|
|
29
|
+
*
|
|
30
|
+
* The body is serialised once, here, and that exact string is both signed and
|
|
31
|
+
* sent, so nothing between the signature and the socket can reformat it.
|
|
32
|
+
*
|
|
33
|
+
* Both the URL and the secret are required. An endpoint configured without a
|
|
34
|
+
* secret is not sent to at all rather than sent to unsigned: the payload
|
|
35
|
+
* carries email addresses and campaign data, and a receiver with no way to
|
|
36
|
+
* tell OneUptime's POST from anyone else's is not a receiver worth having.
|
|
37
|
+
*/
|
|
38
|
+
export default class MarketingEventWebhook {
|
|
39
|
+
public static isConfigured(): boolean {
|
|
40
|
+
return Boolean(MarketingWebhookUrl && MarketingWebhookSecret);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/*
|
|
44
|
+
* True when an endpoint is set but unusable, so callers can say so once at
|
|
45
|
+
* emit time rather than letting events vanish into a silent no-op.
|
|
46
|
+
*/
|
|
47
|
+
public static isMisconfigured(): boolean {
|
|
48
|
+
return Boolean(MarketingWebhookUrl) && !MarketingWebhookSecret;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
public static sign(body: string): string {
|
|
52
|
+
return crypto
|
|
53
|
+
.createHmac("sha256", MarketingWebhookSecret)
|
|
54
|
+
.update(body, "utf8")
|
|
55
|
+
.digest("hex");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/*
|
|
59
|
+
* One delivery attempt.
|
|
60
|
+
*
|
|
61
|
+
* Throws on a transport error or any non-2xx, which is what makes the queue
|
|
62
|
+
* retry. A 4xx is retried too: the receiver rejecting a payload it should
|
|
63
|
+
* have taken is far more often a deploy in progress or a bad rule than a
|
|
64
|
+
* payload that will never be acceptable, and the alternative — dropping it
|
|
65
|
+
* silently — has no backstop now that nothing is stored.
|
|
66
|
+
*/
|
|
67
|
+
public static async deliver(event: MarketingEvent): Promise<void> {
|
|
68
|
+
if (!this.isConfigured()) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Serialise once. This exact string is signed and sent.
|
|
73
|
+
const body: string = JSON.stringify(event);
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
const response: AxiosResponse = await axios.post(
|
|
77
|
+
MarketingWebhookUrl,
|
|
78
|
+
body,
|
|
79
|
+
{
|
|
80
|
+
headers: {
|
|
81
|
+
"content-type": "application/json",
|
|
82
|
+
"x-oneuptime-signature-256": this.sign(body),
|
|
83
|
+
"x-oneuptime-event-id": event.eventId,
|
|
84
|
+
"x-oneuptime-event-type": event.eventType,
|
|
85
|
+
},
|
|
86
|
+
timeout: REQUEST_TIMEOUT_MS,
|
|
87
|
+
// Non-2xx must reach the catch below rather than resolving.
|
|
88
|
+
validateStatus: (status: number): boolean => {
|
|
89
|
+
return status >= 200 && status < 300;
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
logger.debug(
|
|
95
|
+
`MarketingEvent: delivered ${event.eventType} (${event.eventId}) — HTTP ${response.status}`,
|
|
96
|
+
);
|
|
97
|
+
} catch (err) {
|
|
98
|
+
const message: string =
|
|
99
|
+
err instanceof AxiosError
|
|
100
|
+
? `HTTP ${err.response?.status || "?"}: ${JSON.stringify(
|
|
101
|
+
err.response?.data || err.message,
|
|
102
|
+
).slice(0, 500)}`
|
|
103
|
+
: (err as Error)?.message || "Unknown error";
|
|
104
|
+
|
|
105
|
+
/*
|
|
106
|
+
* Rethrown, not swallowed. The queue's retry is the only thing standing
|
|
107
|
+
* between a receiver hiccup and a permanently lost conversion.
|
|
108
|
+
*/
|
|
109
|
+
throw new Error(
|
|
110
|
+
`Marketing webhook delivery failed for ${event.eventType} (${event.eventId}): ${message}`,
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -7,6 +7,7 @@ import OneUptimeDate from "../../../Types/Date";
|
|
|
7
7
|
import ObjectID from "../../../Types/ObjectID";
|
|
8
8
|
import { JSONObject } from "../../../Types/JSON";
|
|
9
9
|
import DataToProcess from "./DataToProcess";
|
|
10
|
+
import { redactForPersistence } from "./MonitorPayloadRedaction";
|
|
10
11
|
|
|
11
12
|
/*
|
|
12
13
|
* Maximum rows held in memory before we force a flush, and the
|
|
@@ -138,7 +139,19 @@ export default class MonitorLogUtil {
|
|
|
138
139
|
projectId: data.projectId.toString(),
|
|
139
140
|
monitorId: data.monitorId.toString(),
|
|
140
141
|
time: logTimestamp,
|
|
141
|
-
|
|
142
|
+
/*
|
|
143
|
+
* Redact on the way out, never in place: `dataToProcess` is still
|
|
144
|
+
* live here (saveMonitorLog is fire-and-forget and the caller keeps
|
|
145
|
+
* evaluating criteria against it), so the stringify/parse clone is
|
|
146
|
+
* what gets masked. logBody is readable by Permission.Viewer, which
|
|
147
|
+
* makes it the widest-reach sink in the product — anything
|
|
148
|
+
* credential-shaped in it is readable by the least-privileged role
|
|
149
|
+
* OneUptime grants. See MonitorPayloadRedaction.
|
|
150
|
+
* https://github.com/OneUptime/oneuptime/issues/3360
|
|
151
|
+
*/
|
|
152
|
+
logBody: redactForPersistence(
|
|
153
|
+
JSON.parse(JSON.stringify(data.dataToProcess)),
|
|
154
|
+
),
|
|
142
155
|
retentionDate: OneUptimeDate.toClickhouseDateTime(retentionDate),
|
|
143
156
|
};
|
|
144
157
|
|