@oneuptime/common 10.3.0 → 10.4.1
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/IncomingCallPolicyLabelRule.ts +522 -0
- package/Models/DatabaseModels/IncomingCallPolicyOwnerRule.ts +606 -0
- package/Models/DatabaseModels/IncomingCallPolicyOwnerTeam.ts +508 -0
- package/Models/DatabaseModels/IncomingCallPolicyOwnerUser.ts +507 -0
- package/Models/DatabaseModels/Index.ts +24 -0
- package/Models/DatabaseModels/OnCallDutyPolicyLabelRule.ts +522 -0
- package/Models/DatabaseModels/OnCallDutyPolicyOwnerRule.ts +606 -0
- package/Models/DatabaseModels/OnCallDutyPolicyScheduleLabelRule.ts +522 -0
- package/Models/DatabaseModels/OnCallDutyPolicyScheduleOwnerRule.ts +606 -0
- package/Models/DatabaseModels/OnCallDutyPolicyScheduleOwnerTeam.ts +508 -0
- package/Models/DatabaseModels/OnCallDutyPolicyScheduleOwnerUser.ts +507 -0
- package/Server/API/TelemetryAPI.ts +69 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1778929624633-AddOnCallDutyPolicyScheduleOwners.ts +153 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1778931537020-AddOnCallIncomingCallOwnersAndRules.ts +823 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
- package/Server/Services/IncomingCallPolicyLabelRuleEngineService.ts +197 -0
- package/Server/Services/IncomingCallPolicyLabelRuleService.ts +10 -0
- package/Server/Services/IncomingCallPolicyOwnerRuleEngineService.ts +220 -0
- package/Server/Services/IncomingCallPolicyOwnerRuleService.ts +10 -0
- package/Server/Services/IncomingCallPolicyOwnerTeamService.ts +9 -0
- package/Server/Services/IncomingCallPolicyOwnerUserService.ts +9 -0
- package/Server/Services/IncomingCallPolicyService.ts +35 -0
- package/Server/Services/OnCallDutyPolicyLabelRuleEngineService.ts +191 -0
- package/Server/Services/OnCallDutyPolicyLabelRuleService.ts +10 -0
- package/Server/Services/OnCallDutyPolicyOwnerRuleEngineService.ts +214 -0
- package/Server/Services/OnCallDutyPolicyOwnerRuleService.ts +10 -0
- package/Server/Services/OnCallDutyPolicyScheduleLabelRuleEngineService.ts +200 -0
- package/Server/Services/OnCallDutyPolicyScheduleLabelRuleService.ts +10 -0
- package/Server/Services/OnCallDutyPolicyScheduleOwnerRuleEngineService.ts +223 -0
- package/Server/Services/OnCallDutyPolicyScheduleOwnerRuleService.ts +10 -0
- package/Server/Services/OnCallDutyPolicyScheduleOwnerTeamService.ts +9 -0
- package/Server/Services/OnCallDutyPolicyScheduleOwnerUserService.ts +9 -0
- package/Server/Services/OnCallDutyPolicyScheduleService.ts +32 -1
- package/Server/Services/OnCallDutyPolicyService.ts +21 -0
- package/Server/Services/ProfileAggregationService.ts +119 -0
- package/Server/Types/Database/Permissions/QueryPermission.ts +36 -0
- package/Types/Monitor/MonitorStep.ts +66 -0
- package/Types/Permission.ts +475 -0
- package/build/dist/Models/DatabaseModels/IncomingCallPolicyLabelRule.js +532 -0
- package/build/dist/Models/DatabaseModels/IncomingCallPolicyLabelRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/IncomingCallPolicyOwnerRule.js +615 -0
- package/build/dist/Models/DatabaseModels/IncomingCallPolicyOwnerRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/IncomingCallPolicyOwnerTeam.js +524 -0
- package/build/dist/Models/DatabaseModels/IncomingCallPolicyOwnerTeam.js.map +1 -0
- package/build/dist/Models/DatabaseModels/IncomingCallPolicyOwnerUser.js +523 -0
- package/build/dist/Models/DatabaseModels/IncomingCallPolicyOwnerUser.js.map +1 -0
- package/build/dist/Models/DatabaseModels/Index.js +22 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyLabelRule.js +532 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyLabelRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyOwnerRule.js +615 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyOwnerRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLabelRule.js +532 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLabelRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleOwnerRule.js +615 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleOwnerRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleOwnerTeam.js +524 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleOwnerTeam.js.map +1 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleOwnerUser.js +523 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleOwnerUser.js.map +1 -0
- package/build/dist/Server/API/TelemetryAPI.js +34 -0
- package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1778929624633-AddOnCallDutyPolicyScheduleOwners.js +58 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1778929624633-AddOnCallDutyPolicyScheduleOwners.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1778931537020-AddOnCallIncomingCallOwnersAndRules.js +292 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1778931537020-AddOnCallIncomingCallOwnersAndRules.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/IncomingCallPolicyLabelRuleEngineService.js +158 -0
- package/build/dist/Server/Services/IncomingCallPolicyLabelRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/IncomingCallPolicyLabelRuleService.js +9 -0
- package/build/dist/Server/Services/IncomingCallPolicyLabelRuleService.js.map +1 -0
- package/build/dist/Server/Services/IncomingCallPolicyOwnerRuleEngineService.js +181 -0
- package/build/dist/Server/Services/IncomingCallPolicyOwnerRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/IncomingCallPolicyOwnerRuleService.js +9 -0
- package/build/dist/Server/Services/IncomingCallPolicyOwnerRuleService.js.map +1 -0
- package/build/dist/Server/Services/IncomingCallPolicyOwnerTeamService.js +9 -0
- package/build/dist/Server/Services/IncomingCallPolicyOwnerTeamService.js.map +1 -0
- package/build/dist/Server/Services/IncomingCallPolicyOwnerUserService.js +9 -0
- package/build/dist/Server/Services/IncomingCallPolicyOwnerUserService.js.map +1 -0
- package/build/dist/Server/Services/IncomingCallPolicyService.js +38 -0
- package/build/dist/Server/Services/IncomingCallPolicyService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyLabelRuleEngineService.js +157 -0
- package/build/dist/Server/Services/OnCallDutyPolicyLabelRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/OnCallDutyPolicyLabelRuleService.js +9 -0
- package/build/dist/Server/Services/OnCallDutyPolicyLabelRuleService.js.map +1 -0
- package/build/dist/Server/Services/OnCallDutyPolicyOwnerRuleEngineService.js +180 -0
- package/build/dist/Server/Services/OnCallDutyPolicyOwnerRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/OnCallDutyPolicyOwnerRuleService.js +9 -0
- package/build/dist/Server/Services/OnCallDutyPolicyOwnerRuleService.js.map +1 -0
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleLabelRuleEngineService.js +158 -0
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleLabelRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleLabelRuleService.js +9 -0
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleLabelRuleService.js.map +1 -0
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleOwnerRuleEngineService.js +181 -0
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleOwnerRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleOwnerRuleService.js +9 -0
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleOwnerRuleService.js.map +1 -0
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleOwnerTeamService.js +9 -0
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleOwnerTeamService.js.map +1 -0
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleOwnerUserService.js +9 -0
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleOwnerUserService.js.map +1 -0
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js +21 -0
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyService.js +17 -3
- package/build/dist/Server/Services/OnCallDutyPolicyService.js.map +1 -1
- package/build/dist/Server/Services/ProfileAggregationService.js +80 -0
- package/build/dist/Server/Services/ProfileAggregationService.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/QueryPermission.js +25 -0
- package/build/dist/Server/Types/Database/Permissions/QueryPermission.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStep.js +42 -0
- package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
- package/build/dist/Types/Permission.js +415 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import Label from "../../Models/DatabaseModels/Label";
|
|
2
|
+
import OnCallDutyPolicySchedule from "../../Models/DatabaseModels/OnCallDutyPolicySchedule";
|
|
3
|
+
import OnCallDutyPolicyScheduleOwnerRule from "../../Models/DatabaseModels/OnCallDutyPolicyScheduleOwnerRule";
|
|
4
|
+
import OnCallDutyPolicyScheduleOwnerUser from "../../Models/DatabaseModels/OnCallDutyPolicyScheduleOwnerUser";
|
|
5
|
+
import OnCallDutyPolicyScheduleOwnerTeam from "../../Models/DatabaseModels/OnCallDutyPolicyScheduleOwnerTeam";
|
|
6
|
+
import OnCallDutyPolicyScheduleOwnerRuleService from "./OnCallDutyPolicyScheduleOwnerRuleService";
|
|
7
|
+
import OnCallDutyPolicyScheduleOwnerUserService from "./OnCallDutyPolicyScheduleOwnerUserService";
|
|
8
|
+
import OnCallDutyPolicyScheduleOwnerTeamService from "./OnCallDutyPolicyScheduleOwnerTeamService";
|
|
9
|
+
import OnCallDutyPolicyScheduleService from "./OnCallDutyPolicyScheduleService";
|
|
10
|
+
import ObjectID from "../../Types/ObjectID";
|
|
11
|
+
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
|
|
12
|
+
import logger, { LogAttributes } from "../Utils/Logger";
|
|
13
|
+
|
|
14
|
+
class OnCallDutyPolicyScheduleOwnerRuleEngineServiceClass {
|
|
15
|
+
@CaptureSpan()
|
|
16
|
+
public async applyRulesToSchedule(
|
|
17
|
+
schedule: OnCallDutyPolicySchedule,
|
|
18
|
+
): Promise<void> {
|
|
19
|
+
if (!schedule.id || !schedule.projectId) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
const rules: Array<OnCallDutyPolicyScheduleOwnerRule> =
|
|
25
|
+
await OnCallDutyPolicyScheduleOwnerRuleService.findBy({
|
|
26
|
+
query: {
|
|
27
|
+
projectId: schedule.projectId,
|
|
28
|
+
isEnabled: true,
|
|
29
|
+
},
|
|
30
|
+
props: { isRoot: true },
|
|
31
|
+
select: {
|
|
32
|
+
_id: true,
|
|
33
|
+
name: true,
|
|
34
|
+
notifyOwners: true,
|
|
35
|
+
onCallDutyPolicyScheduleLabels: { _id: true },
|
|
36
|
+
onCallDutyPolicyScheduleNamePattern: true,
|
|
37
|
+
onCallDutyPolicyScheduleDescriptionPattern: true,
|
|
38
|
+
ownerUsers: { _id: true },
|
|
39
|
+
ownerTeams: { _id: true },
|
|
40
|
+
},
|
|
41
|
+
limit: 100,
|
|
42
|
+
skip: 0,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
if (rules.length === 0) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const scheduleWithDetails: OnCallDutyPolicySchedule | null =
|
|
50
|
+
await OnCallDutyPolicyScheduleService.findOneById({
|
|
51
|
+
id: schedule.id,
|
|
52
|
+
select: {
|
|
53
|
+
name: true,
|
|
54
|
+
description: true,
|
|
55
|
+
labels: { _id: true },
|
|
56
|
+
},
|
|
57
|
+
props: { isRoot: true },
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
if (!scheduleWithDetails) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const usersByNotify: Map<boolean, Set<string>> = new Map([
|
|
65
|
+
[true, new Set()],
|
|
66
|
+
[false, new Set()],
|
|
67
|
+
]);
|
|
68
|
+
const teamsByNotify: Map<boolean, Set<string>> = new Map([
|
|
69
|
+
[true, new Set()],
|
|
70
|
+
[false, new Set()],
|
|
71
|
+
]);
|
|
72
|
+
|
|
73
|
+
const matchedRules: Array<OnCallDutyPolicyScheduleOwnerRule> = [];
|
|
74
|
+
|
|
75
|
+
for (const rule of rules) {
|
|
76
|
+
const matches: boolean = this.doesScheduleMatchRule(
|
|
77
|
+
scheduleWithDetails,
|
|
78
|
+
rule,
|
|
79
|
+
);
|
|
80
|
+
if (!matches) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
let ruleAddedAny: boolean = false;
|
|
84
|
+
const notify: boolean = rule.notifyOwners !== false;
|
|
85
|
+
for (const user of rule.ownerUsers || []) {
|
|
86
|
+
if (user.id) {
|
|
87
|
+
usersByNotify.get(notify)!.add(user.id.toString());
|
|
88
|
+
ruleAddedAny = true;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
for (const team of rule.ownerTeams || []) {
|
|
92
|
+
if (team.id) {
|
|
93
|
+
teamsByNotify.get(notify)!.add(team.id.toString());
|
|
94
|
+
ruleAddedAny = true;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (ruleAddedAny) {
|
|
98
|
+
matchedRules.push(rule);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (matchedRules.length === 0) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
for (const notify of [true, false]) {
|
|
107
|
+
const userIds: Set<string> = usersByNotify.get(notify)!;
|
|
108
|
+
const teamIds: Set<string> = teamsByNotify.get(notify)!;
|
|
109
|
+
|
|
110
|
+
for (const userId of userIds) {
|
|
111
|
+
const owner: OnCallDutyPolicyScheduleOwnerUser =
|
|
112
|
+
new OnCallDutyPolicyScheduleOwnerUser();
|
|
113
|
+
owner.onCallDutyPolicyScheduleId = schedule.id;
|
|
114
|
+
owner.projectId = schedule.projectId;
|
|
115
|
+
owner.userId = new ObjectID(userId);
|
|
116
|
+
owner.isOwnerNotified = !notify;
|
|
117
|
+
await OnCallDutyPolicyScheduleOwnerUserService.create({
|
|
118
|
+
data: owner,
|
|
119
|
+
props: { isRoot: true },
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
for (const teamId of teamIds) {
|
|
124
|
+
const owner: OnCallDutyPolicyScheduleOwnerTeam =
|
|
125
|
+
new OnCallDutyPolicyScheduleOwnerTeam();
|
|
126
|
+
owner.onCallDutyPolicyScheduleId = schedule.id;
|
|
127
|
+
owner.projectId = schedule.projectId;
|
|
128
|
+
owner.teamId = new ObjectID(teamId);
|
|
129
|
+
owner.isOwnerNotified = !notify;
|
|
130
|
+
await OnCallDutyPolicyScheduleOwnerTeamService.create({
|
|
131
|
+
data: owner,
|
|
132
|
+
props: { isRoot: true },
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
logger.debug(
|
|
138
|
+
`OnCallDutyPolicyScheduleOwnerRuleEngine added owners to schedule ${schedule.id}`,
|
|
139
|
+
{ projectId: schedule.projectId.toString() } as LogAttributes,
|
|
140
|
+
);
|
|
141
|
+
} catch (error) {
|
|
142
|
+
logger.error(
|
|
143
|
+
`Error applying on-call duty schedule owner rules: ${error}`,
|
|
144
|
+
{
|
|
145
|
+
projectId: schedule.projectId?.toString(),
|
|
146
|
+
onCallDutyPolicyScheduleId: schedule.id?.toString(),
|
|
147
|
+
} as LogAttributes,
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
private doesScheduleMatchRule(
|
|
153
|
+
schedule: OnCallDutyPolicySchedule,
|
|
154
|
+
rule: OnCallDutyPolicyScheduleOwnerRule,
|
|
155
|
+
): boolean {
|
|
156
|
+
if (
|
|
157
|
+
rule.onCallDutyPolicyScheduleLabels &&
|
|
158
|
+
rule.onCallDutyPolicyScheduleLabels.length > 0
|
|
159
|
+
) {
|
|
160
|
+
if (!schedule.labels || schedule.labels.length === 0) {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
const ruleLabelIds: Array<string> =
|
|
164
|
+
rule.onCallDutyPolicyScheduleLabels.map((l: Label) => {
|
|
165
|
+
return l.id?.toString() || "";
|
|
166
|
+
});
|
|
167
|
+
const labelIds: Array<string> = schedule.labels.map((l: Label) => {
|
|
168
|
+
return l.id?.toString() || "";
|
|
169
|
+
});
|
|
170
|
+
if (
|
|
171
|
+
!ruleLabelIds.some((id: string) => {
|
|
172
|
+
return labelIds.includes(id);
|
|
173
|
+
})
|
|
174
|
+
) {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (
|
|
180
|
+
rule.onCallDutyPolicyScheduleNamePattern &&
|
|
181
|
+
(!schedule.name ||
|
|
182
|
+
!this.testRegex(
|
|
183
|
+
rule.onCallDutyPolicyScheduleNamePattern,
|
|
184
|
+
schedule.name,
|
|
185
|
+
rule,
|
|
186
|
+
))
|
|
187
|
+
) {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (
|
|
192
|
+
rule.onCallDutyPolicyScheduleDescriptionPattern &&
|
|
193
|
+
(!schedule.description ||
|
|
194
|
+
!this.testRegex(
|
|
195
|
+
rule.onCallDutyPolicyScheduleDescriptionPattern,
|
|
196
|
+
schedule.description,
|
|
197
|
+
rule,
|
|
198
|
+
))
|
|
199
|
+
) {
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
private testRegex(
|
|
207
|
+
pattern: string,
|
|
208
|
+
value: string,
|
|
209
|
+
rule: OnCallDutyPolicyScheduleOwnerRule,
|
|
210
|
+
): boolean {
|
|
211
|
+
try {
|
|
212
|
+
const regex: RegExp = new RegExp(pattern, "i");
|
|
213
|
+
return regex.test(value);
|
|
214
|
+
} catch {
|
|
215
|
+
logger.warn(
|
|
216
|
+
`Invalid regex in on-call duty schedule owner rule ${rule.id}: ${pattern}`,
|
|
217
|
+
);
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export default new OnCallDutyPolicyScheduleOwnerRuleEngineServiceClass();
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import DatabaseService from "./DatabaseService";
|
|
2
|
+
import Model from "../../Models/DatabaseModels/OnCallDutyPolicyScheduleOwnerRule";
|
|
3
|
+
|
|
4
|
+
export class Service extends DatabaseService<Model> {
|
|
5
|
+
public constructor() {
|
|
6
|
+
super(Model);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default new Service();
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import DatabaseService from "./DatabaseService";
|
|
2
|
+
import Model from "../../Models/DatabaseModels/OnCallDutyPolicyScheduleOwnerTeam";
|
|
3
|
+
|
|
4
|
+
export class Service extends DatabaseService<Model> {
|
|
5
|
+
public constructor() {
|
|
6
|
+
super(Model);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export default new Service();
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import DatabaseService from "./DatabaseService";
|
|
2
|
+
import Model from "../../Models/DatabaseModels/OnCallDutyPolicyScheduleOwnerUser";
|
|
3
|
+
|
|
4
|
+
export class Service extends DatabaseService<Model> {
|
|
5
|
+
public constructor() {
|
|
6
|
+
super(Model);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export default new Service();
|
|
@@ -38,8 +38,10 @@ import OnCallDutyPolicyFeedService from "./OnCallDutyPolicyFeedService";
|
|
|
38
38
|
import { OnCallDutyPolicyFeedEventType } from "../../Models/DatabaseModels/OnCallDutyPolicyFeed";
|
|
39
39
|
import { Green500 } from "../../Types/BrandColors";
|
|
40
40
|
import OnCallDutyPolicyTimeLogService from "./OnCallDutyPolicyTimeLogService";
|
|
41
|
+
import OnCallDutyPolicyScheduleLabelRuleEngineService from "./OnCallDutyPolicyScheduleLabelRuleEngineService";
|
|
42
|
+
import OnCallDutyPolicyScheduleOwnerRuleEngineService from "./OnCallDutyPolicyScheduleOwnerRuleEngineService";
|
|
41
43
|
import DeleteBy from "../Types/Database/DeleteBy";
|
|
42
|
-
import { OnDelete } from "../Types/Database/Hooks";
|
|
44
|
+
import { OnCreate, OnDelete } from "../Types/Database/Hooks";
|
|
43
45
|
import PushNotificationMessage from "../../Types/PushNotification/PushNotificationMessage";
|
|
44
46
|
import PushNotificationUtil from "../Utils/PushNotificationUtil";
|
|
45
47
|
import { createWhatsAppMessageFromTemplate } from "../Utils/WhatsAppTemplateUtil";
|
|
@@ -52,6 +54,35 @@ export class Service extends DatabaseService<OnCallDutyPolicySchedule> {
|
|
|
52
54
|
super(OnCallDutyPolicySchedule);
|
|
53
55
|
}
|
|
54
56
|
|
|
57
|
+
protected override async onCreateSuccess(
|
|
58
|
+
_onCreate: OnCreate<OnCallDutyPolicySchedule>,
|
|
59
|
+
createdItem: OnCallDutyPolicySchedule,
|
|
60
|
+
): Promise<OnCallDutyPolicySchedule> {
|
|
61
|
+
if (createdItem.projectId && createdItem.id) {
|
|
62
|
+
Promise.resolve()
|
|
63
|
+
.then(async () => {
|
|
64
|
+
await OnCallDutyPolicyScheduleLabelRuleEngineService.applyRulesToSchedule(
|
|
65
|
+
createdItem,
|
|
66
|
+
);
|
|
67
|
+
})
|
|
68
|
+
.then(async () => {
|
|
69
|
+
await OnCallDutyPolicyScheduleOwnerRuleEngineService.applyRulesToSchedule(
|
|
70
|
+
createdItem,
|
|
71
|
+
);
|
|
72
|
+
})
|
|
73
|
+
.catch((error: Error) => {
|
|
74
|
+
logger.error(
|
|
75
|
+
`Error applying on-call schedule rules in OnCallDutyPolicyScheduleService.onCreateSuccess: ${error}`,
|
|
76
|
+
{
|
|
77
|
+
projectId: createdItem.projectId?.toString(),
|
|
78
|
+
onCallDutyPolicyScheduleId: createdItem.id?.toString(),
|
|
79
|
+
} as LogAttributes,
|
|
80
|
+
);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
return createdItem;
|
|
84
|
+
}
|
|
85
|
+
|
|
55
86
|
protected override async onBeforeDelete(
|
|
56
87
|
deleteBy: DeleteBy<OnCallDutyPolicySchedule>,
|
|
57
88
|
): Promise<OnDelete<OnCallDutyPolicySchedule>> {
|
|
@@ -35,6 +35,8 @@ import OnCallDutyPolicyWorkspaceMessages from "../Utils/Workspace/WorkspaceMessa
|
|
|
35
35
|
import OnCallDutyPolicyFeedService from "./OnCallDutyPolicyFeedService";
|
|
36
36
|
import { OnCallDutyPolicyFeedEventType } from "../../Models/DatabaseModels/OnCallDutyPolicyFeed";
|
|
37
37
|
import { Green500 } from "../../Types/BrandColors";
|
|
38
|
+
import OnCallDutyPolicyLabelRuleEngineService from "./OnCallDutyPolicyLabelRuleEngineService";
|
|
39
|
+
import OnCallDutyPolicyOwnerRuleEngineService from "./OnCallDutyPolicyOwnerRuleEngineService";
|
|
38
40
|
|
|
39
41
|
export class Service extends DatabaseService<OnCallDutyPolicy> {
|
|
40
42
|
public constructor() {
|
|
@@ -49,6 +51,25 @@ export class Service extends DatabaseService<OnCallDutyPolicy> {
|
|
|
49
51
|
throw new BadDataException("On Call Policy id not found.");
|
|
50
52
|
}
|
|
51
53
|
|
|
54
|
+
if (createdItem.projectId) {
|
|
55
|
+
try {
|
|
56
|
+
await OnCallDutyPolicyLabelRuleEngineService.applyRulesToOnCallDutyPolicy(
|
|
57
|
+
createdItem,
|
|
58
|
+
);
|
|
59
|
+
await OnCallDutyPolicyOwnerRuleEngineService.applyRulesToOnCallDutyPolicy(
|
|
60
|
+
createdItem,
|
|
61
|
+
);
|
|
62
|
+
} catch (error) {
|
|
63
|
+
logger.error(
|
|
64
|
+
`Error applying on-call duty policy rules in OnCallDutyPolicyService.onCreateSuccess: ${error}`,
|
|
65
|
+
{
|
|
66
|
+
projectId: createdItem.projectId?.toString(),
|
|
67
|
+
onCallDutyPolicyId: createdItem.id?.toString(),
|
|
68
|
+
} as LogAttributes,
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
52
73
|
const onCallPolicy: OnCallDutyPolicy | null = await this.findOneById({
|
|
53
74
|
id: createdItem.id,
|
|
54
75
|
select: {
|
|
@@ -59,6 +59,30 @@ export interface FunctionListRequest {
|
|
|
59
59
|
sortBy?: "selfValue" | "totalValue" | "sampleCount";
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
export interface ServiceActivityRequest {
|
|
63
|
+
projectId: ObjectID;
|
|
64
|
+
startTime: Date;
|
|
65
|
+
endTime: Date;
|
|
66
|
+
/**
|
|
67
|
+
* Single profile type to filter on. Kept for backwards compat. When
|
|
68
|
+
* `profileTypes` is also supplied, `profileTypes` wins.
|
|
69
|
+
*/
|
|
70
|
+
profileType?: string;
|
|
71
|
+
/**
|
|
72
|
+
* Multiple raw profile-type strings to OR together. The UI maps a
|
|
73
|
+
* user-facing category (e.g. "CPU") to all the raw type strings real
|
|
74
|
+
* agents actually emit (e.g. ["cpu", "samples"]).
|
|
75
|
+
*/
|
|
76
|
+
profileTypes?: Array<string>;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface ServiceActivityItem {
|
|
80
|
+
serviceId: string;
|
|
81
|
+
sampleCount: number;
|
|
82
|
+
profileCount: number;
|
|
83
|
+
totalValue: number;
|
|
84
|
+
}
|
|
85
|
+
|
|
62
86
|
export interface DiffFlamegraphRequest {
|
|
63
87
|
projectId: ObjectID;
|
|
64
88
|
baselineStartTime: Date;
|
|
@@ -417,6 +441,42 @@ export class ProfileAggregationService {
|
|
|
417
441
|
return node;
|
|
418
442
|
}
|
|
419
443
|
|
|
444
|
+
/**
|
|
445
|
+
* Aggregate sample / profile counts per serviceId for a time window.
|
|
446
|
+
* Drives the "loudest services first" sort on the Profiles dashboard
|
|
447
|
+
* so a developer opening the page lands on the workloads that are
|
|
448
|
+
* actually doing work rather than scrolling past kernel-thread noise.
|
|
449
|
+
*/
|
|
450
|
+
@CaptureSpan()
|
|
451
|
+
public static async getServiceActivity(
|
|
452
|
+
request: ServiceActivityRequest,
|
|
453
|
+
): Promise<Array<ServiceActivityItem>> {
|
|
454
|
+
const statement: Statement =
|
|
455
|
+
ProfileAggregationService.buildServiceActivityQuery(request);
|
|
456
|
+
|
|
457
|
+
const dbResult: Results =
|
|
458
|
+
await ProfileSampleDatabaseService.executeQuery(statement);
|
|
459
|
+
const response: DbJSONResponse = await dbResult.json<{
|
|
460
|
+
data?: Array<JSONObject>;
|
|
461
|
+
}>();
|
|
462
|
+
|
|
463
|
+
const rows: Array<JSONObject> = response.data || [];
|
|
464
|
+
const out: Array<ServiceActivityItem> = [];
|
|
465
|
+
for (const row of rows) {
|
|
466
|
+
const serviceId: string = String(row["serviceId"] || "");
|
|
467
|
+
if (!serviceId) {
|
|
468
|
+
continue;
|
|
469
|
+
}
|
|
470
|
+
out.push({
|
|
471
|
+
serviceId,
|
|
472
|
+
sampleCount: Number(row["sampleCount"] || 0),
|
|
473
|
+
profileCount: Number(row["profileCount"] || 0),
|
|
474
|
+
totalValue: Number(row["totalValue"] || 0),
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
return out;
|
|
478
|
+
}
|
|
479
|
+
|
|
420
480
|
// --- Query builders ---
|
|
421
481
|
|
|
422
482
|
private static buildFlamegraphQuery(request: FlamegraphRequest): Statement {
|
|
@@ -506,6 +566,65 @@ export class ProfileAggregationService {
|
|
|
506
566
|
return statement;
|
|
507
567
|
}
|
|
508
568
|
|
|
569
|
+
private static buildServiceActivityQuery(
|
|
570
|
+
request: ServiceActivityRequest,
|
|
571
|
+
): Statement {
|
|
572
|
+
/*
|
|
573
|
+
* sum(value) directly — value is stored as Int128 in ClickHouse, so
|
|
574
|
+
* toFloat64OrZero would fail ("Illegal type Int128"); a plain sum is
|
|
575
|
+
* the right idiom here, and we coerce to Number when serialising in
|
|
576
|
+
* getServiceActivity.
|
|
577
|
+
*/
|
|
578
|
+
const statement: Statement = SQL`
|
|
579
|
+
SELECT
|
|
580
|
+
toString(serviceId) AS serviceId,
|
|
581
|
+
count() AS sampleCount,
|
|
582
|
+
uniqExact(profileId) AS profileCount,
|
|
583
|
+
toFloat64(sum(value)) AS totalValue
|
|
584
|
+
FROM ${ProfileAggregationService.TABLE_NAME}
|
|
585
|
+
WHERE projectId = ${{
|
|
586
|
+
type: TableColumnType.ObjectID,
|
|
587
|
+
value: request.projectId,
|
|
588
|
+
}}
|
|
589
|
+
AND time >= ${{
|
|
590
|
+
type: TableColumnType.Date,
|
|
591
|
+
value: request.startTime,
|
|
592
|
+
}}
|
|
593
|
+
AND time <= ${{
|
|
594
|
+
type: TableColumnType.Date,
|
|
595
|
+
value: request.endTime,
|
|
596
|
+
}}
|
|
597
|
+
`;
|
|
598
|
+
|
|
599
|
+
/*
|
|
600
|
+
* profileTypes (array) wins over profileType (single) so the UI
|
|
601
|
+
* can OR together every raw type string in a category.
|
|
602
|
+
*/
|
|
603
|
+
if (request.profileTypes && request.profileTypes.length > 0) {
|
|
604
|
+
statement.append(
|
|
605
|
+
SQL` AND profileType IN (${{
|
|
606
|
+
type: TableColumnType.Text,
|
|
607
|
+
value: new Includes(request.profileTypes),
|
|
608
|
+
}})`,
|
|
609
|
+
);
|
|
610
|
+
} else if (request.profileType) {
|
|
611
|
+
statement.append(
|
|
612
|
+
SQL` AND profileType = ${{
|
|
613
|
+
type: TableColumnType.Text,
|
|
614
|
+
value: request.profileType,
|
|
615
|
+
}}`,
|
|
616
|
+
);
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
statement.append(
|
|
620
|
+
SQL` GROUP BY serviceId
|
|
621
|
+
ORDER BY sampleCount DESC
|
|
622
|
+
LIMIT 10000`,
|
|
623
|
+
);
|
|
624
|
+
|
|
625
|
+
return statement;
|
|
626
|
+
}
|
|
627
|
+
|
|
509
628
|
private static appendCommonFilters(
|
|
510
629
|
statement: Statement,
|
|
511
630
|
request: Pick<
|
|
@@ -9,6 +9,7 @@ import DatabaseCommonInteractionProps from "../../../../Types/BaseDatabase/Datab
|
|
|
9
9
|
import DatabaseCommonInteractionPropsUtil, {
|
|
10
10
|
PermissionType,
|
|
11
11
|
} from "../../../../Types/BaseDatabase/DatabaseCommonInteractionPropsUtil";
|
|
12
|
+
import MultiSearch from "../../../../Types/BaseDatabase/MultiSearch";
|
|
12
13
|
import Columns from "../../../../Types/Database/Columns";
|
|
13
14
|
import { TableColumnMetadata } from "../../../../Types/Database/TableColumn";
|
|
14
15
|
import TableColumnType from "../../../../Types/Database/TableColumnType";
|
|
@@ -156,6 +157,41 @@ export default class QueryPermission {
|
|
|
156
157
|
continue;
|
|
157
158
|
}
|
|
158
159
|
|
|
160
|
+
/*
|
|
161
|
+
* MultiSearch is a synthetic operator: the key itself is not a column,
|
|
162
|
+
* but the operator carries the list of fields it will ILIKE against
|
|
163
|
+
* (QueryUtil.serializeQuery expands it into an OR over those fields).
|
|
164
|
+
* Validate each inner field the same way we validate any other queried
|
|
165
|
+
* column so a client can't search across columns the user can't read.
|
|
166
|
+
*/
|
|
167
|
+
if (query[key] instanceof MultiSearch) {
|
|
168
|
+
const multiSearch: MultiSearch = query[key] as unknown as MultiSearch;
|
|
169
|
+
|
|
170
|
+
for (const field of multiSearch.fields) {
|
|
171
|
+
if (excludedColumnNames.includes(field)) {
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (!tableColumns.includes(field)) {
|
|
176
|
+
throw new BadDataException(
|
|
177
|
+
`Invalid column on ${model.singularName} - ${field}. Column does not exist.`,
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (!canReadOnTheseColumns.columns.includes(field)) {
|
|
182
|
+
throw new NotAuthorizedException(
|
|
183
|
+
`You do not have permissions to query on - ${field}. You need any one of these permissions: ${PermissionHelper.getPermissionTitles(
|
|
184
|
+
model.getColumnAccessControlFor(field)
|
|
185
|
+
? model.getColumnAccessControlFor(field)!.read
|
|
186
|
+
: [],
|
|
187
|
+
).join(", ")}`,
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
|
|
159
195
|
if (!canReadOnTheseColumns.columns.includes(key)) {
|
|
160
196
|
if (!tableColumns.includes(key)) {
|
|
161
197
|
throw new BadDataException(
|
|
@@ -64,6 +64,14 @@ export interface MonitorStepType {
|
|
|
64
64
|
doNotFollowRedirects?: boolean | undefined;
|
|
65
65
|
allowSelfSignedCertificates?: boolean | undefined;
|
|
66
66
|
|
|
67
|
+
/*
|
|
68
|
+
* mTLS / client certificate authentication (API and Website monitors).
|
|
69
|
+
* Values can be raw PEM strings or {{monitorSecrets.name}} references.
|
|
70
|
+
*/
|
|
71
|
+
tlsClientCertificate?: string | undefined;
|
|
72
|
+
tlsClientKey?: string | undefined;
|
|
73
|
+
tlsClientKeyPassphrase?: string | undefined;
|
|
74
|
+
|
|
67
75
|
// this is for port monitors.
|
|
68
76
|
monitorDestinationPort?: Port | undefined;
|
|
69
77
|
|
|
@@ -122,6 +130,9 @@ export default class MonitorStep extends DatabaseProperty {
|
|
|
122
130
|
monitorDestination: undefined,
|
|
123
131
|
doNotFollowRedirects: undefined,
|
|
124
132
|
allowSelfSignedCertificates: undefined,
|
|
133
|
+
tlsClientCertificate: undefined,
|
|
134
|
+
tlsClientKey: undefined,
|
|
135
|
+
tlsClientKeyPassphrase: undefined,
|
|
125
136
|
monitorDestinationPort: undefined,
|
|
126
137
|
monitorCriteria: new MonitorCriteria(),
|
|
127
138
|
requestType: HTTPMethod.GET,
|
|
@@ -160,6 +171,9 @@ export default class MonitorStep extends DatabaseProperty {
|
|
|
160
171
|
monitorDestination: undefined,
|
|
161
172
|
doNotFollowRedirects: undefined,
|
|
162
173
|
allowSelfSignedCertificates: undefined,
|
|
174
|
+
tlsClientCertificate: undefined,
|
|
175
|
+
tlsClientKey: undefined,
|
|
176
|
+
tlsClientKeyPassphrase: undefined,
|
|
163
177
|
monitorDestinationPort: undefined,
|
|
164
178
|
monitorCriteria: MonitorCriteria.getDefaultMonitorCriteria(arg),
|
|
165
179
|
requestType: HTTPMethod.GET,
|
|
@@ -231,6 +245,25 @@ export default class MonitorStep extends DatabaseProperty {
|
|
|
231
245
|
return this;
|
|
232
246
|
}
|
|
233
247
|
|
|
248
|
+
public setTlsClientCertificate(
|
|
249
|
+
tlsClientCertificate: string | undefined,
|
|
250
|
+
): MonitorStep {
|
|
251
|
+
this.data!.tlsClientCertificate = tlsClientCertificate || undefined;
|
|
252
|
+
return this;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
public setTlsClientKey(tlsClientKey: string | undefined): MonitorStep {
|
|
256
|
+
this.data!.tlsClientKey = tlsClientKey || undefined;
|
|
257
|
+
return this;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
public setTlsClientKeyPassphrase(
|
|
261
|
+
tlsClientKeyPassphrase: string | undefined,
|
|
262
|
+
): MonitorStep {
|
|
263
|
+
this.data!.tlsClientKeyPassphrase = tlsClientKeyPassphrase || undefined;
|
|
264
|
+
return this;
|
|
265
|
+
}
|
|
266
|
+
|
|
234
267
|
public setPort(monitorDestinationPort: Port): MonitorStep {
|
|
235
268
|
this.data!.monitorDestinationPort = monitorDestinationPort;
|
|
236
269
|
return this;
|
|
@@ -340,6 +373,9 @@ export default class MonitorStep extends DatabaseProperty {
|
|
|
340
373
|
monitorDestination: undefined,
|
|
341
374
|
doNotFollowRedirects: undefined,
|
|
342
375
|
allowSelfSignedCertificates: undefined,
|
|
376
|
+
tlsClientCertificate: undefined,
|
|
377
|
+
tlsClientKey: undefined,
|
|
378
|
+
tlsClientKeyPassphrase: undefined,
|
|
343
379
|
monitorDestinationPort: undefined,
|
|
344
380
|
monitorCriteria: MonitorCriteria.getNewMonitorCriteriaAsJSON(),
|
|
345
381
|
requestType: HTTPMethod.GET,
|
|
@@ -409,6 +445,25 @@ export default class MonitorStep extends DatabaseProperty {
|
|
|
409
445
|
return "Request Type is required";
|
|
410
446
|
}
|
|
411
447
|
|
|
448
|
+
if (
|
|
449
|
+
monitorType === MonitorType.API ||
|
|
450
|
+
monitorType === MonitorType.Website
|
|
451
|
+
) {
|
|
452
|
+
const hasCert: boolean = Boolean(
|
|
453
|
+
value.data.tlsClientCertificate &&
|
|
454
|
+
value.data.tlsClientCertificate.trim(),
|
|
455
|
+
);
|
|
456
|
+
const hasKey: boolean = Boolean(
|
|
457
|
+
value.data.tlsClientKey && value.data.tlsClientKey.trim(),
|
|
458
|
+
);
|
|
459
|
+
if (hasCert && !hasKey) {
|
|
460
|
+
return "Client private key is required when a client certificate is provided";
|
|
461
|
+
}
|
|
462
|
+
if (hasKey && !hasCert) {
|
|
463
|
+
return "Client certificate is required when a client private key is provided";
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
412
467
|
if (
|
|
413
468
|
monitorType === MonitorType.Port &&
|
|
414
469
|
!value.data.monitorDestinationPort
|
|
@@ -497,6 +552,9 @@ export default class MonitorStep extends DatabaseProperty {
|
|
|
497
552
|
doNotFollowRedirects: this.data.doNotFollowRedirects || undefined,
|
|
498
553
|
allowSelfSignedCertificates:
|
|
499
554
|
this.data.allowSelfSignedCertificates || undefined,
|
|
555
|
+
tlsClientCertificate: this.data.tlsClientCertificate || undefined,
|
|
556
|
+
tlsClientKey: this.data.tlsClientKey || undefined,
|
|
557
|
+
tlsClientKeyPassphrase: this.data.tlsClientKeyPassphrase || undefined,
|
|
500
558
|
monitorDestinationPort:
|
|
501
559
|
this.data?.monitorDestinationPort?.toJSON() || undefined,
|
|
502
560
|
monitorCriteria: this.data.monitorCriteria.toJSON(),
|
|
@@ -634,6 +692,11 @@ export default class MonitorStep extends DatabaseProperty {
|
|
|
634
692
|
doNotFollowRedirects: json["doNotFollowRedirects"] || undefined,
|
|
635
693
|
allowSelfSignedCertificates:
|
|
636
694
|
json["allowSelfSignedCertificates"] || undefined,
|
|
695
|
+
tlsClientCertificate:
|
|
696
|
+
(json["tlsClientCertificate"] as string) || undefined,
|
|
697
|
+
tlsClientKey: (json["tlsClientKey"] as string) || undefined,
|
|
698
|
+
tlsClientKeyPassphrase:
|
|
699
|
+
(json["tlsClientKeyPassphrase"] as string) || undefined,
|
|
637
700
|
monitorDestinationPort: monitorDestinationPort || undefined,
|
|
638
701
|
monitorCriteria: MonitorCriteria.fromJSON(
|
|
639
702
|
json["monitorCriteria"] as JSONObject,
|
|
@@ -697,6 +760,9 @@ export default class MonitorStep extends DatabaseProperty {
|
|
|
697
760
|
requestBody: Zod.string().optional(),
|
|
698
761
|
doNotFollowRedirects: Zod.boolean().optional(),
|
|
699
762
|
allowSelfSignedCertificates: Zod.boolean().optional(),
|
|
763
|
+
tlsClientCertificate: Zod.string().optional(),
|
|
764
|
+
tlsClientKey: Zod.string().optional(),
|
|
765
|
+
tlsClientKeyPassphrase: Zod.string().optional(),
|
|
700
766
|
monitorDestinationPort: Zod.any().optional(),
|
|
701
767
|
customCode: Zod.string().optional(),
|
|
702
768
|
screenSizeTypes: Zod.any().optional(),
|