@oneuptime/common 9.4.9 → 9.4.12
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/Alert.ts +1 -0
- package/Models/DatabaseModels/AlertFeed.ts +1 -0
- package/Models/DatabaseModels/Project.ts +29 -0
- package/Server/API/BillingAPI.ts +78 -1
- package/Server/BillingConfig.ts +3 -0
- package/Server/EnvironmentConfig.ts +1 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1769599843642-MigrationName.ts +29 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
- package/Server/Services/AIBillingService.ts +10 -0
- package/Server/Services/AlertEpisodeMemberService.ts +49 -1
- package/Server/Services/AlertGroupingEngineService.ts +3 -168
- package/Server/Services/BillingService.ts +351 -1
- package/Server/Services/NotificationService.ts +10 -0
- package/Server/Services/ProjectService.ts +33 -2
- package/Server/Services/UserNotificationRuleService.ts +62 -33
- package/Server/Services/UserService.ts +45 -1
- package/Server/Types/Database/Permissions/TenantPermission.ts +20 -0
- package/Types/Database/TableColumn.ts +1 -0
- package/Types/Email/EmailTemplateType.ts +1 -0
- package/Utils/Schema/ModelSchema.ts +2 -0
- package/build/dist/Models/DatabaseModels/Alert.js +1 -0
- package/build/dist/Models/DatabaseModels/Alert.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AlertFeed.js +1 -0
- package/build/dist/Models/DatabaseModels/AlertFeed.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Project.js +30 -0
- package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
- package/build/dist/Server/API/BillingAPI.js +44 -1
- package/build/dist/Server/API/BillingAPI.js.map +1 -1
- package/build/dist/Server/BillingConfig.js +2 -0
- package/build/dist/Server/BillingConfig.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +1 -0
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1769599843642-MigrationName.js +16 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1769599843642-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/AIBillingService.js +10 -1
- package/build/dist/Server/Services/AIBillingService.js.map +1 -1
- package/build/dist/Server/Services/AlertEpisodeMemberService.js +43 -1
- package/build/dist/Server/Services/AlertEpisodeMemberService.js.map +1 -1
- package/build/dist/Server/Services/AlertGroupingEngineService.js +8 -137
- package/build/dist/Server/Services/AlertGroupingEngineService.js.map +1 -1
- package/build/dist/Server/Services/BillingService.js +225 -5
- package/build/dist/Server/Services/BillingService.js.map +1 -1
- package/build/dist/Server/Services/NotificationService.js +10 -1
- package/build/dist/Server/Services/NotificationService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +16 -3
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationRuleService.js +50 -39
- package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
- package/build/dist/Server/Services/UserService.js +40 -0
- package/build/dist/Server/Services/UserService.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/TenantPermission.js +17 -0
- package/build/dist/Server/Types/Database/Permissions/TenantPermission.js.map +1 -1
- package/build/dist/Types/Database/TableColumn.js.map +1 -1
- package/build/dist/Types/Email/EmailTemplateType.js +1 -0
- package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
- package/build/dist/Utils/Schema/ModelSchema.js +2 -0
- package/build/dist/Utils/Schema/ModelSchema.js.map +1 -1
- package/package.json +1 -1
|
@@ -1105,6 +1105,35 @@ export default class Project extends TenantModel {
|
|
|
1105
1105
|
})
|
|
1106
1106
|
public enableAutoRechargeAiBalance?: boolean = undefined;
|
|
1107
1107
|
|
|
1108
|
+
@ColumnAccessControl({
|
|
1109
|
+
create: [Permission.ProjectOwner, Permission.ManageProjectBilling],
|
|
1110
|
+
read: [
|
|
1111
|
+
Permission.ProjectOwner,
|
|
1112
|
+
Permission.ProjectAdmin,
|
|
1113
|
+
Permission.ProjectMember,
|
|
1114
|
+
Permission.ReadProject,
|
|
1115
|
+
Permission.UnAuthorizedSsoUser,
|
|
1116
|
+
Permission.ProjectUser,
|
|
1117
|
+
],
|
|
1118
|
+
update: [Permission.ProjectOwner, Permission.ManageProjectBilling],
|
|
1119
|
+
})
|
|
1120
|
+
@TableColumn({
|
|
1121
|
+
required: true,
|
|
1122
|
+
isDefaultValueColumn: true,
|
|
1123
|
+
type: TableColumnType.Boolean,
|
|
1124
|
+
title: "Send Invoices by Email",
|
|
1125
|
+
description:
|
|
1126
|
+
"When enabled, invoices will be automatically sent to the finance/accounting email when they are generated.",
|
|
1127
|
+
defaultValue: false,
|
|
1128
|
+
example: true,
|
|
1129
|
+
})
|
|
1130
|
+
@Column({
|
|
1131
|
+
nullable: false,
|
|
1132
|
+
default: false,
|
|
1133
|
+
type: ColumnType.Boolean,
|
|
1134
|
+
})
|
|
1135
|
+
public sendInvoicesByEmail?: boolean = undefined;
|
|
1136
|
+
|
|
1108
1137
|
@ColumnAccessControl({
|
|
1109
1138
|
create: [],
|
|
1110
1139
|
read: [],
|
package/Server/API/BillingAPI.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { IsBillingEnabled } from "../EnvironmentConfig";
|
|
1
|
+
import { BillingWebhookSecret, IsBillingEnabled } from "../EnvironmentConfig";
|
|
2
|
+
import Stripe from "stripe";
|
|
2
3
|
import UserMiddleware from "../Middleware/UserAuthorization";
|
|
3
4
|
import BillingService from "../Services/BillingService";
|
|
4
5
|
import ProjectService from "../Services/ProjectService";
|
|
@@ -16,6 +17,7 @@ import Project from "../../Models/DatabaseModels/Project";
|
|
|
16
17
|
import CommonAPI from "./CommonAPI";
|
|
17
18
|
import ObjectID from "../../Types/ObjectID";
|
|
18
19
|
import DatabaseCommonInteractionProps from "../../Types/BaseDatabase/DatabaseCommonInteractionProps";
|
|
20
|
+
import logger from "../Utils/Logger";
|
|
19
21
|
|
|
20
22
|
export default class BillingAPI {
|
|
21
23
|
public router: ExpressRouter;
|
|
@@ -23,6 +25,81 @@ export default class BillingAPI {
|
|
|
23
25
|
public constructor() {
|
|
24
26
|
this.router = Express.getRouter();
|
|
25
27
|
|
|
28
|
+
// Stripe webhook endpoint - uses raw body captured by JSON parser for signature verification
|
|
29
|
+
this.router.post(
|
|
30
|
+
`/billing/webhook`,
|
|
31
|
+
async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
|
|
32
|
+
try {
|
|
33
|
+
logger.debug(
|
|
34
|
+
`[Invoice Email] Webhook endpoint hit - /billing/webhook`,
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
if (!IsBillingEnabled) {
|
|
38
|
+
logger.debug(
|
|
39
|
+
`[Invoice Email] Billing not enabled, returning early`,
|
|
40
|
+
);
|
|
41
|
+
return Response.sendJsonObjectResponse(req, res, {
|
|
42
|
+
message: "Billing is not enabled",
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!BillingWebhookSecret) {
|
|
47
|
+
logger.error(
|
|
48
|
+
`[Invoice Email] Billing webhook secret is not configured`,
|
|
49
|
+
);
|
|
50
|
+
throw new BadDataException(
|
|
51
|
+
"Billing webhook secret is not configured",
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const signature: string = req.headers["stripe-signature"] as string;
|
|
56
|
+
logger.debug(
|
|
57
|
+
`[Invoice Email] Stripe signature header present: ${Boolean(signature)}`,
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
if (!signature) {
|
|
61
|
+
logger.error(`[Invoice Email] Missing Stripe signature header`);
|
|
62
|
+
throw new BadDataException("Missing Stripe signature header");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const rawBody: string | undefined = (req as OneUptimeRequest).rawBody;
|
|
66
|
+
logger.debug(
|
|
67
|
+
`[Invoice Email] Raw body present: ${Boolean(rawBody)}, length: ${rawBody?.length || 0}`,
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
if (!rawBody) {
|
|
71
|
+
logger.error(
|
|
72
|
+
`[Invoice Email] Missing raw body for webhook verification`,
|
|
73
|
+
);
|
|
74
|
+
throw new BadDataException(
|
|
75
|
+
"Missing raw body for webhook verification",
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
logger.debug(`[Invoice Email] Verifying webhook signature...`);
|
|
80
|
+
const event: Stripe.Event = BillingService.verifyWebhookSignature(
|
|
81
|
+
rawBody,
|
|
82
|
+
signature,
|
|
83
|
+
);
|
|
84
|
+
logger.debug(
|
|
85
|
+
`[Invoice Email] Webhook signature verified successfully, event type: ${event.type}`,
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
// Handle the event asynchronously
|
|
89
|
+
logger.debug(`[Invoice Email] Handling webhook event...`);
|
|
90
|
+
await BillingService.handleWebhookEvent(event);
|
|
91
|
+
logger.debug(`[Invoice Email] Webhook event handled successfully`);
|
|
92
|
+
|
|
93
|
+
return Response.sendJsonObjectResponse(req, res, {
|
|
94
|
+
received: true,
|
|
95
|
+
});
|
|
96
|
+
} catch (err) {
|
|
97
|
+
logger.error(`[Invoice Email] Stripe webhook error: ${err}`);
|
|
98
|
+
next(err);
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
);
|
|
102
|
+
|
|
26
103
|
this.router.get(
|
|
27
104
|
`/billing/customer-balance`,
|
|
28
105
|
UserMiddleware.getUserMiddleware,
|
package/Server/BillingConfig.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
const IsBillingEnabled: boolean = process.env["BILLING_ENABLED"] === "true";
|
|
2
2
|
const BillingPublicKey: string = process.env["BILLING_PUBLIC_KEY"] || "";
|
|
3
3
|
const BillingPrivateKey: string = process.env["BILLING_PRIVATE_KEY"] || "";
|
|
4
|
+
const BillingWebhookSecret: string =
|
|
5
|
+
process.env["BILLING_WEBHOOK_SECRET"] || "";
|
|
4
6
|
|
|
5
7
|
export default {
|
|
6
8
|
IsBillingEnabled,
|
|
7
9
|
BillingPublicKey,
|
|
8
10
|
BillingPrivateKey,
|
|
11
|
+
BillingWebhookSecret,
|
|
9
12
|
};
|
|
@@ -102,6 +102,7 @@ const parsePositiveNumberFromEnv: (
|
|
|
102
102
|
export const IsBillingEnabled: boolean = BillingConfig.IsBillingEnabled;
|
|
103
103
|
export const BillingPublicKey: string = BillingConfig.BillingPublicKey;
|
|
104
104
|
export const BillingPrivateKey: string = BillingConfig.BillingPrivateKey;
|
|
105
|
+
export const BillingWebhookSecret: string = BillingConfig.BillingWebhookSecret;
|
|
105
106
|
|
|
106
107
|
export const DatabaseHost: Hostname = Hostname.fromString(
|
|
107
108
|
process.env["DATABASE_HOST"] || "postgres",
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { MigrationInterface, QueryRunner } from "typeorm";
|
|
2
|
+
|
|
3
|
+
export class MigrationName1769599843642 implements MigrationInterface {
|
|
4
|
+
public name = "MigrationName1769599843642";
|
|
5
|
+
|
|
6
|
+
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
7
|
+
await queryRunner.query(
|
|
8
|
+
`ALTER TABLE "Project" ADD "sendInvoicesByEmail" boolean NOT NULL DEFAULT false`,
|
|
9
|
+
);
|
|
10
|
+
await queryRunner.query(
|
|
11
|
+
`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type":"Recurring","value":{"intervalType":"Day","intervalCount":{"_type":"PositiveNumber","value":1}}}'`,
|
|
12
|
+
);
|
|
13
|
+
await queryRunner.query(
|
|
14
|
+
`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type":"RestrictionTimes","value":{"restictionType":"None","dayRestrictionTimes":null,"weeklyRestrictionTimes":[]}}'`,
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
19
|
+
await queryRunner.query(
|
|
20
|
+
`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type": "RestrictionTimes", "value": {"restictionType": "None", "dayRestrictionTimes": null, "weeklyRestrictionTimes": []}}'`,
|
|
21
|
+
);
|
|
22
|
+
await queryRunner.query(
|
|
23
|
+
`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type": "Recurring", "value": {"intervalType": "Day", "intervalCount": {"_type": "PositiveNumber", "value": 1}}}'`,
|
|
24
|
+
);
|
|
25
|
+
await queryRunner.query(
|
|
26
|
+
`ALTER TABLE "Project" DROP COLUMN "sendInvoicesByEmail"`,
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -236,6 +236,7 @@ import { MigrationName1769428619414 } from "./1769428619414-MigrationName";
|
|
|
236
236
|
import { MigrationName1769428821686 } from "./1769428821686-MigrationName";
|
|
237
237
|
import { MigrationName1769469813786 } from "./1769469813786-MigrationName";
|
|
238
238
|
import { RenameNotificationRuleTypes1769517677937 } from "./1769517677937-RenameNotificationRuleTypes";
|
|
239
|
+
import { MigrationName1769599843642 } from "./1769599843642-MigrationName";
|
|
239
240
|
|
|
240
241
|
export default [
|
|
241
242
|
InitialMigration,
|
|
@@ -476,4 +477,5 @@ export default [
|
|
|
476
477
|
MigrationName1769428821686,
|
|
477
478
|
MigrationName1769469813786,
|
|
478
479
|
RenameNotificationRuleTypes1769517677937,
|
|
480
|
+
MigrationName1769599843642,
|
|
479
481
|
];
|
|
@@ -7,6 +7,7 @@ import BaseService from "./BaseService";
|
|
|
7
7
|
import BillingService from "./BillingService";
|
|
8
8
|
import ProjectService from "./ProjectService";
|
|
9
9
|
import BadDataException from "../../Types/Exception/BadDataException";
|
|
10
|
+
import Email from "../../Types/Email";
|
|
10
11
|
import ObjectID from "../../Types/ObjectID";
|
|
11
12
|
import Project from "../../Models/DatabaseModels/Project";
|
|
12
13
|
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
|
|
@@ -39,6 +40,8 @@ export class AIBillingService extends BaseService {
|
|
|
39
40
|
paymentProviderCustomerId: true,
|
|
40
41
|
name: true,
|
|
41
42
|
failedAiBalanceChargeNotificationSentToOwners: true,
|
|
43
|
+
sendInvoicesByEmail: true,
|
|
44
|
+
financeAccountingEmail: true,
|
|
42
45
|
},
|
|
43
46
|
props: {
|
|
44
47
|
isRoot: true,
|
|
@@ -89,6 +92,13 @@ export class AIBillingService extends BaseService {
|
|
|
89
92
|
project.paymentProviderCustomerId!,
|
|
90
93
|
"AI Balance Recharge",
|
|
91
94
|
amountInUSD,
|
|
95
|
+
{
|
|
96
|
+
sendInvoiceByEmail: project.sendInvoicesByEmail || false,
|
|
97
|
+
recipientEmail: project.financeAccountingEmail
|
|
98
|
+
? new Email(project.financeAccountingEmail)
|
|
99
|
+
: undefined,
|
|
100
|
+
projectId: project.id || undefined,
|
|
101
|
+
},
|
|
92
102
|
);
|
|
93
103
|
|
|
94
104
|
await ProjectService.updateOneById({
|
|
@@ -6,11 +6,14 @@ import BadDataException from "../../Types/Exception/BadDataException";
|
|
|
6
6
|
import ObjectID from "../../Types/ObjectID";
|
|
7
7
|
import Model from "../../Models/DatabaseModels/AlertEpisodeMember";
|
|
8
8
|
import Alert from "../../Models/DatabaseModels/Alert";
|
|
9
|
+
import AlertEpisode from "../../Models/DatabaseModels/AlertEpisode";
|
|
9
10
|
import { IsBillingEnabled } from "../EnvironmentConfig";
|
|
10
11
|
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
|
|
11
12
|
import logger from "../Utils/Logger";
|
|
12
13
|
import AlertEpisodeFeedService from "./AlertEpisodeFeedService";
|
|
14
|
+
import AlertFeedService from "./AlertFeedService";
|
|
13
15
|
import { AlertEpisodeFeedEventType } from "../../Models/DatabaseModels/AlertEpisodeFeed";
|
|
16
|
+
import { AlertFeedEventType } from "../../Models/DatabaseModels/AlertFeed";
|
|
14
17
|
import { Yellow500, Green500 } from "../../Types/BrandColors";
|
|
15
18
|
import OneUptimeDate from "../../Types/Date";
|
|
16
19
|
import AlertService from "./AlertService";
|
|
@@ -124,7 +127,19 @@ export class Service extends DatabaseService<Model> {
|
|
|
124
127
|
},
|
|
125
128
|
});
|
|
126
129
|
|
|
127
|
-
//
|
|
130
|
+
// Get episode details for feed
|
|
131
|
+
const episode: AlertEpisode | null = await AlertEpisodeService.findOneById({
|
|
132
|
+
id: createdItem.alertEpisodeId,
|
|
133
|
+
select: {
|
|
134
|
+
episodeNumber: true,
|
|
135
|
+
title: true,
|
|
136
|
+
},
|
|
137
|
+
props: {
|
|
138
|
+
isRoot: true,
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// Create feed item on episode
|
|
128
143
|
await AlertEpisodeFeedService.createAlertEpisodeFeedItem({
|
|
129
144
|
alertEpisodeId: createdItem.alertEpisodeId,
|
|
130
145
|
projectId: createdItem.projectId,
|
|
@@ -134,6 +149,16 @@ export class Service extends DatabaseService<Model> {
|
|
|
134
149
|
userId: createdItem.addedByUserId || undefined,
|
|
135
150
|
});
|
|
136
151
|
|
|
152
|
+
// Create feed item on alert
|
|
153
|
+
await AlertFeedService.createAlertFeedItem({
|
|
154
|
+
alertId: createdItem.alertId,
|
|
155
|
+
projectId: createdItem.projectId,
|
|
156
|
+
alertFeedEventType: AlertFeedEventType.AddedToEpisode,
|
|
157
|
+
displayColor: Yellow500,
|
|
158
|
+
feedInfoInMarkdown: `Added to **Episode #${episode?.episodeNumber || "N/A"}**: ${episode?.title || "No title"}`,
|
|
159
|
+
userId: createdItem.addedByUserId || undefined,
|
|
160
|
+
});
|
|
161
|
+
|
|
137
162
|
return createdItem;
|
|
138
163
|
}
|
|
139
164
|
|
|
@@ -197,6 +222,20 @@ export class Service extends DatabaseService<Model> {
|
|
|
197
222
|
|
|
198
223
|
// Create feed item for removal
|
|
199
224
|
if (member.alertEpisodeId && member.projectId) {
|
|
225
|
+
// Get episode details for feed
|
|
226
|
+
const episode: AlertEpisode | null =
|
|
227
|
+
await AlertEpisodeService.findOneById({
|
|
228
|
+
id: member.alertEpisodeId,
|
|
229
|
+
select: {
|
|
230
|
+
episodeNumber: true,
|
|
231
|
+
title: true,
|
|
232
|
+
},
|
|
233
|
+
props: {
|
|
234
|
+
isRoot: true,
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
// Create feed item on episode
|
|
200
239
|
await AlertEpisodeFeedService.createAlertEpisodeFeedItem({
|
|
201
240
|
alertEpisodeId: member.alertEpisodeId,
|
|
202
241
|
projectId: member.projectId,
|
|
@@ -204,6 +243,15 @@ export class Service extends DatabaseService<Model> {
|
|
|
204
243
|
displayColor: Green500,
|
|
205
244
|
feedInfoInMarkdown: `**Alert #${alert?.alertNumber || "N/A"}** removed from episode: ${alert?.title || "No title"}`,
|
|
206
245
|
});
|
|
246
|
+
|
|
247
|
+
// Create feed item on alert
|
|
248
|
+
await AlertFeedService.createAlertFeedItem({
|
|
249
|
+
alertId: member.alertId,
|
|
250
|
+
projectId: member.projectId,
|
|
251
|
+
alertFeedEventType: AlertFeedEventType.RemovedFromEpisode,
|
|
252
|
+
displayColor: Green500,
|
|
253
|
+
feedInfoInMarkdown: `Removed from **Episode #${episode?.episodeNumber || "N/A"}**: ${episode?.title || "No title"}`,
|
|
254
|
+
});
|
|
207
255
|
}
|
|
208
256
|
}
|
|
209
257
|
|
|
@@ -21,10 +21,8 @@ import MonitorService from "./MonitorService";
|
|
|
21
21
|
import ServiceMonitorService from "./ServiceMonitorService";
|
|
22
22
|
import Semaphore, { SemaphoreMutex } from "../Infrastructure/Semaphore";
|
|
23
23
|
import AlertEpisodeFeedService from "./AlertEpisodeFeedService";
|
|
24
|
-
import AlertFeedService from "./AlertFeedService";
|
|
25
24
|
import { AlertEpisodeFeedEventType } from "../../Models/DatabaseModels/AlertEpisodeFeed";
|
|
26
|
-
import {
|
|
27
|
-
import { Green500, Purple500 } from "../../Types/BrandColors";
|
|
25
|
+
import { Green500 } from "../../Types/BrandColors";
|
|
28
26
|
|
|
29
27
|
export interface GroupingResult {
|
|
30
28
|
grouped: boolean;
|
|
@@ -395,9 +393,6 @@ class AlertGroupingEngineServiceClass {
|
|
|
395
393
|
existingEpisode.id,
|
|
396
394
|
AlertEpisodeMemberAddedBy.Rule,
|
|
397
395
|
rule.id!,
|
|
398
|
-
rule,
|
|
399
|
-
false, // isNewEpisode
|
|
400
|
-
false, // wasReopened
|
|
401
396
|
);
|
|
402
397
|
|
|
403
398
|
// Update episode severity if alert has higher severity
|
|
@@ -440,9 +435,6 @@ class AlertGroupingEngineServiceClass {
|
|
|
440
435
|
recentlyResolvedEpisode.id,
|
|
441
436
|
AlertEpisodeMemberAddedBy.Rule,
|
|
442
437
|
rule.id!,
|
|
443
|
-
rule,
|
|
444
|
-
false, // isNewEpisode
|
|
445
|
-
true, // wasReopened
|
|
446
438
|
);
|
|
447
439
|
|
|
448
440
|
// Update episode severity if alert has higher severity
|
|
@@ -478,9 +470,6 @@ class AlertGroupingEngineServiceClass {
|
|
|
478
470
|
newEpisode.id,
|
|
479
471
|
AlertEpisodeMemberAddedBy.Rule,
|
|
480
472
|
rule.id!,
|
|
481
|
-
rule,
|
|
482
|
-
true, // isNewEpisode
|
|
483
|
-
false, // wasReopened
|
|
484
473
|
);
|
|
485
474
|
|
|
486
475
|
return { grouped: true, episodeId: newEpisode.id, isNewEpisode: true };
|
|
@@ -894,9 +883,6 @@ class AlertGroupingEngineServiceClass {
|
|
|
894
883
|
episodeId: ObjectID,
|
|
895
884
|
addedBy: AlertEpisodeMemberAddedBy,
|
|
896
885
|
ruleId?: ObjectID,
|
|
897
|
-
rule?: AlertGroupingRule,
|
|
898
|
-
isNewEpisode?: boolean,
|
|
899
|
-
wasReopened?: boolean,
|
|
900
886
|
): Promise<void> {
|
|
901
887
|
const member: AlertEpisodeMember = new AlertEpisodeMember();
|
|
902
888
|
member.projectId = alert.projectId!;
|
|
@@ -916,15 +902,7 @@ class AlertGroupingEngineServiceClass {
|
|
|
916
902
|
},
|
|
917
903
|
});
|
|
918
904
|
|
|
919
|
-
//
|
|
920
|
-
await this.createAlertAddedFeedEntries({
|
|
921
|
-
alert,
|
|
922
|
-
episodeId,
|
|
923
|
-
addedBy,
|
|
924
|
-
rule,
|
|
925
|
-
isNewEpisode,
|
|
926
|
-
wasReopened,
|
|
927
|
-
});
|
|
905
|
+
// Feed entries are created by AlertEpisodeMemberService.onCreateSuccess
|
|
928
906
|
} catch (error) {
|
|
929
907
|
// Check if it's a duplicate error (alert already in episode)
|
|
930
908
|
if (
|
|
@@ -938,143 +916,6 @@ class AlertGroupingEngineServiceClass {
|
|
|
938
916
|
}
|
|
939
917
|
}
|
|
940
918
|
|
|
941
|
-
@CaptureSpan()
|
|
942
|
-
private async createAlertAddedFeedEntries(data: {
|
|
943
|
-
alert: Alert;
|
|
944
|
-
episodeId: ObjectID;
|
|
945
|
-
addedBy: AlertEpisodeMemberAddedBy;
|
|
946
|
-
rule?: AlertGroupingRule | undefined;
|
|
947
|
-
isNewEpisode?: boolean | undefined;
|
|
948
|
-
wasReopened?: boolean | undefined;
|
|
949
|
-
addedByUserId?: ObjectID | undefined;
|
|
950
|
-
}): Promise<void> {
|
|
951
|
-
const {
|
|
952
|
-
alert,
|
|
953
|
-
episodeId,
|
|
954
|
-
addedBy,
|
|
955
|
-
rule,
|
|
956
|
-
isNewEpisode,
|
|
957
|
-
wasReopened,
|
|
958
|
-
addedByUserId,
|
|
959
|
-
} = data;
|
|
960
|
-
|
|
961
|
-
// Fetch episode number for feed entry
|
|
962
|
-
const episode: AlertEpisode | null = await AlertEpisodeService.findOneById({
|
|
963
|
-
id: episodeId,
|
|
964
|
-
select: {
|
|
965
|
-
episodeNumber: true,
|
|
966
|
-
},
|
|
967
|
-
props: {
|
|
968
|
-
isRoot: true,
|
|
969
|
-
},
|
|
970
|
-
});
|
|
971
|
-
|
|
972
|
-
const episodeNumber: number = episode?.episodeNumber || 0;
|
|
973
|
-
const alertNumber: number = alert.alertNumber || 0;
|
|
974
|
-
|
|
975
|
-
// Build explanation of why the alert was added
|
|
976
|
-
let matchReason: string = "";
|
|
977
|
-
let alertFeedMessage: string = "";
|
|
978
|
-
let episodeFeedMessage: string = "";
|
|
979
|
-
|
|
980
|
-
if (addedBy === AlertEpisodeMemberAddedBy.Rule && rule) {
|
|
981
|
-
const matchCriteria: Array<string> = [];
|
|
982
|
-
|
|
983
|
-
if (rule.monitors && rule.monitors.length > 0) {
|
|
984
|
-
matchCriteria.push("monitor matches rule criteria");
|
|
985
|
-
}
|
|
986
|
-
if (rule.alertSeverities && rule.alertSeverities.length > 0) {
|
|
987
|
-
matchCriteria.push("severity matches rule criteria");
|
|
988
|
-
}
|
|
989
|
-
if (rule.alertLabels && rule.alertLabels.length > 0) {
|
|
990
|
-
matchCriteria.push("alert labels match rule criteria");
|
|
991
|
-
}
|
|
992
|
-
if (rule.monitorLabels && rule.monitorLabels.length > 0) {
|
|
993
|
-
matchCriteria.push("monitor labels match rule criteria");
|
|
994
|
-
}
|
|
995
|
-
if (rule.alertTitlePattern) {
|
|
996
|
-
matchCriteria.push(
|
|
997
|
-
`alert title matches pattern \`${rule.alertTitlePattern}\``,
|
|
998
|
-
);
|
|
999
|
-
}
|
|
1000
|
-
if (rule.alertDescriptionPattern) {
|
|
1001
|
-
matchCriteria.push(
|
|
1002
|
-
`alert description matches pattern \`${rule.alertDescriptionPattern}\``,
|
|
1003
|
-
);
|
|
1004
|
-
}
|
|
1005
|
-
if (rule.monitorNamePattern) {
|
|
1006
|
-
matchCriteria.push(
|
|
1007
|
-
`monitor name matches pattern \`${rule.monitorNamePattern}\``,
|
|
1008
|
-
);
|
|
1009
|
-
}
|
|
1010
|
-
if (rule.monitorDescriptionPattern) {
|
|
1011
|
-
matchCriteria.push(
|
|
1012
|
-
`monitor description matches pattern \`${rule.monitorDescriptionPattern}\``,
|
|
1013
|
-
);
|
|
1014
|
-
}
|
|
1015
|
-
|
|
1016
|
-
if (matchCriteria.length === 0) {
|
|
1017
|
-
matchCriteria.push("all criteria matched (rule matches all alerts)");
|
|
1018
|
-
}
|
|
1019
|
-
|
|
1020
|
-
matchReason = `**Match Criteria:**\n- ${matchCriteria.join("\n- ")}`;
|
|
1021
|
-
|
|
1022
|
-
if (wasReopened) {
|
|
1023
|
-
alertFeedMessage = `➕ **Added to Episode #${episodeNumber}** (reopened) by rule **${rule.name || "Unnamed Rule"}**`;
|
|
1024
|
-
episodeFeedMessage = `➕ **Alert #${alertNumber}** added (episode reopened) by rule **${rule.name || "Unnamed Rule"}**`;
|
|
1025
|
-
} else if (isNewEpisode) {
|
|
1026
|
-
alertFeedMessage = `➕ **Added to new Episode #${episodeNumber}** by rule **${rule.name || "Unnamed Rule"}**`;
|
|
1027
|
-
episodeFeedMessage = `➕ **Alert #${alertNumber}** added (initial alert) by rule **${rule.name || "Unnamed Rule"}**`;
|
|
1028
|
-
} else {
|
|
1029
|
-
alertFeedMessage = `➕ **Added to Episode #${episodeNumber}** by rule **${rule.name || "Unnamed Rule"}**`;
|
|
1030
|
-
episodeFeedMessage = `➕ **Alert #${alertNumber}** added by rule **${rule.name || "Unnamed Rule"}**`;
|
|
1031
|
-
}
|
|
1032
|
-
} else {
|
|
1033
|
-
// Manual addition
|
|
1034
|
-
alertFeedMessage = `➕ **Manually added to Episode #${episodeNumber}**`;
|
|
1035
|
-
episodeFeedMessage = `➕ **Alert #${alertNumber}** manually added`;
|
|
1036
|
-
matchReason = "**Reason:** Manually added by user";
|
|
1037
|
-
}
|
|
1038
|
-
|
|
1039
|
-
// Create alert feed entry
|
|
1040
|
-
try {
|
|
1041
|
-
await AlertFeedService.createAlertFeedItem({
|
|
1042
|
-
alertId: alert.id!,
|
|
1043
|
-
projectId: alert.projectId!,
|
|
1044
|
-
alertFeedEventType: AlertFeedEventType.AddedToEpisode,
|
|
1045
|
-
displayColor: Purple500,
|
|
1046
|
-
feedInfoInMarkdown: alertFeedMessage,
|
|
1047
|
-
moreInformationInMarkdown: matchReason,
|
|
1048
|
-
userId: addedByUserId,
|
|
1049
|
-
});
|
|
1050
|
-
} catch (feedError) {
|
|
1051
|
-
logger.error(
|
|
1052
|
-
`Error creating alert feed for alert added to episode: ${feedError}`,
|
|
1053
|
-
);
|
|
1054
|
-
}
|
|
1055
|
-
|
|
1056
|
-
// Create episode feed entry
|
|
1057
|
-
try {
|
|
1058
|
-
let moreInfo: string = `**Alert:** #${alertNumber}`;
|
|
1059
|
-
if (alert.title) {
|
|
1060
|
-
moreInfo += ` - ${alert.title}`;
|
|
1061
|
-
}
|
|
1062
|
-
moreInfo += `\n\n${matchReason}`;
|
|
1063
|
-
|
|
1064
|
-
await AlertEpisodeFeedService.createAlertEpisodeFeedItem({
|
|
1065
|
-
alertEpisodeId: episodeId,
|
|
1066
|
-
projectId: alert.projectId!,
|
|
1067
|
-
alertEpisodeFeedEventType: AlertEpisodeFeedEventType.AlertAdded,
|
|
1068
|
-
displayColor: Purple500,
|
|
1069
|
-
feedInfoInMarkdown: episodeFeedMessage,
|
|
1070
|
-
moreInformationInMarkdown: moreInfo,
|
|
1071
|
-
userId: addedByUserId,
|
|
1072
|
-
});
|
|
1073
|
-
} catch (feedError) {
|
|
1074
|
-
logger.error(`Error creating episode feed for alert added: ${feedError}`);
|
|
1075
|
-
}
|
|
1076
|
-
}
|
|
1077
|
-
|
|
1078
919
|
@CaptureSpan()
|
|
1079
920
|
public async addAlertToEpisodeManually(
|
|
1080
921
|
alert: Alert,
|
|
@@ -1098,13 +939,7 @@ class AlertGroupingEngineServiceClass {
|
|
|
1098
939
|
},
|
|
1099
940
|
});
|
|
1100
941
|
|
|
1101
|
-
//
|
|
1102
|
-
await this.createAlertAddedFeedEntries({
|
|
1103
|
-
alert,
|
|
1104
|
-
episodeId,
|
|
1105
|
-
addedBy: AlertEpisodeMemberAddedBy.Manual,
|
|
1106
|
-
addedByUserId,
|
|
1107
|
-
});
|
|
942
|
+
// Feed entries are created by AlertEpisodeMemberService.onCreateSuccess
|
|
1108
943
|
|
|
1109
944
|
// Update episode severity if needed
|
|
1110
945
|
if (alert.alertSeverityId) {
|