@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.
Files changed (115) hide show
  1. package/Models/DatabaseModels/IncomingCallPolicyLabelRule.ts +522 -0
  2. package/Models/DatabaseModels/IncomingCallPolicyOwnerRule.ts +606 -0
  3. package/Models/DatabaseModels/IncomingCallPolicyOwnerTeam.ts +508 -0
  4. package/Models/DatabaseModels/IncomingCallPolicyOwnerUser.ts +507 -0
  5. package/Models/DatabaseModels/Index.ts +24 -0
  6. package/Models/DatabaseModels/OnCallDutyPolicyLabelRule.ts +522 -0
  7. package/Models/DatabaseModels/OnCallDutyPolicyOwnerRule.ts +606 -0
  8. package/Models/DatabaseModels/OnCallDutyPolicyScheduleLabelRule.ts +522 -0
  9. package/Models/DatabaseModels/OnCallDutyPolicyScheduleOwnerRule.ts +606 -0
  10. package/Models/DatabaseModels/OnCallDutyPolicyScheduleOwnerTeam.ts +508 -0
  11. package/Models/DatabaseModels/OnCallDutyPolicyScheduleOwnerUser.ts +507 -0
  12. package/Server/API/TelemetryAPI.ts +69 -0
  13. package/Server/Infrastructure/Postgres/SchemaMigrations/1778929624633-AddOnCallDutyPolicyScheduleOwners.ts +153 -0
  14. package/Server/Infrastructure/Postgres/SchemaMigrations/1778931537020-AddOnCallIncomingCallOwnersAndRules.ts +823 -0
  15. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
  16. package/Server/Services/IncomingCallPolicyLabelRuleEngineService.ts +197 -0
  17. package/Server/Services/IncomingCallPolicyLabelRuleService.ts +10 -0
  18. package/Server/Services/IncomingCallPolicyOwnerRuleEngineService.ts +220 -0
  19. package/Server/Services/IncomingCallPolicyOwnerRuleService.ts +10 -0
  20. package/Server/Services/IncomingCallPolicyOwnerTeamService.ts +9 -0
  21. package/Server/Services/IncomingCallPolicyOwnerUserService.ts +9 -0
  22. package/Server/Services/IncomingCallPolicyService.ts +35 -0
  23. package/Server/Services/OnCallDutyPolicyLabelRuleEngineService.ts +191 -0
  24. package/Server/Services/OnCallDutyPolicyLabelRuleService.ts +10 -0
  25. package/Server/Services/OnCallDutyPolicyOwnerRuleEngineService.ts +214 -0
  26. package/Server/Services/OnCallDutyPolicyOwnerRuleService.ts +10 -0
  27. package/Server/Services/OnCallDutyPolicyScheduleLabelRuleEngineService.ts +200 -0
  28. package/Server/Services/OnCallDutyPolicyScheduleLabelRuleService.ts +10 -0
  29. package/Server/Services/OnCallDutyPolicyScheduleOwnerRuleEngineService.ts +223 -0
  30. package/Server/Services/OnCallDutyPolicyScheduleOwnerRuleService.ts +10 -0
  31. package/Server/Services/OnCallDutyPolicyScheduleOwnerTeamService.ts +9 -0
  32. package/Server/Services/OnCallDutyPolicyScheduleOwnerUserService.ts +9 -0
  33. package/Server/Services/OnCallDutyPolicyScheduleService.ts +32 -1
  34. package/Server/Services/OnCallDutyPolicyService.ts +21 -0
  35. package/Server/Services/ProfileAggregationService.ts +119 -0
  36. package/Server/Types/Database/Permissions/QueryPermission.ts +36 -0
  37. package/Types/Monitor/MonitorStep.ts +66 -0
  38. package/Types/Permission.ts +475 -0
  39. package/build/dist/Models/DatabaseModels/IncomingCallPolicyLabelRule.js +532 -0
  40. package/build/dist/Models/DatabaseModels/IncomingCallPolicyLabelRule.js.map +1 -0
  41. package/build/dist/Models/DatabaseModels/IncomingCallPolicyOwnerRule.js +615 -0
  42. package/build/dist/Models/DatabaseModels/IncomingCallPolicyOwnerRule.js.map +1 -0
  43. package/build/dist/Models/DatabaseModels/IncomingCallPolicyOwnerTeam.js +524 -0
  44. package/build/dist/Models/DatabaseModels/IncomingCallPolicyOwnerTeam.js.map +1 -0
  45. package/build/dist/Models/DatabaseModels/IncomingCallPolicyOwnerUser.js +523 -0
  46. package/build/dist/Models/DatabaseModels/IncomingCallPolicyOwnerUser.js.map +1 -0
  47. package/build/dist/Models/DatabaseModels/Index.js +22 -0
  48. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  49. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyLabelRule.js +532 -0
  50. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyLabelRule.js.map +1 -0
  51. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyOwnerRule.js +615 -0
  52. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyOwnerRule.js.map +1 -0
  53. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLabelRule.js +532 -0
  54. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLabelRule.js.map +1 -0
  55. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleOwnerRule.js +615 -0
  56. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleOwnerRule.js.map +1 -0
  57. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleOwnerTeam.js +524 -0
  58. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleOwnerTeam.js.map +1 -0
  59. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleOwnerUser.js +523 -0
  60. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleOwnerUser.js.map +1 -0
  61. package/build/dist/Server/API/TelemetryAPI.js +34 -0
  62. package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
  63. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1778929624633-AddOnCallDutyPolicyScheduleOwners.js +58 -0
  64. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1778929624633-AddOnCallDutyPolicyScheduleOwners.js.map +1 -0
  65. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1778931537020-AddOnCallIncomingCallOwnersAndRules.js +292 -0
  66. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1778931537020-AddOnCallIncomingCallOwnersAndRules.js.map +1 -0
  67. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
  68. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  69. package/build/dist/Server/Services/IncomingCallPolicyLabelRuleEngineService.js +158 -0
  70. package/build/dist/Server/Services/IncomingCallPolicyLabelRuleEngineService.js.map +1 -0
  71. package/build/dist/Server/Services/IncomingCallPolicyLabelRuleService.js +9 -0
  72. package/build/dist/Server/Services/IncomingCallPolicyLabelRuleService.js.map +1 -0
  73. package/build/dist/Server/Services/IncomingCallPolicyOwnerRuleEngineService.js +181 -0
  74. package/build/dist/Server/Services/IncomingCallPolicyOwnerRuleEngineService.js.map +1 -0
  75. package/build/dist/Server/Services/IncomingCallPolicyOwnerRuleService.js +9 -0
  76. package/build/dist/Server/Services/IncomingCallPolicyOwnerRuleService.js.map +1 -0
  77. package/build/dist/Server/Services/IncomingCallPolicyOwnerTeamService.js +9 -0
  78. package/build/dist/Server/Services/IncomingCallPolicyOwnerTeamService.js.map +1 -0
  79. package/build/dist/Server/Services/IncomingCallPolicyOwnerUserService.js +9 -0
  80. package/build/dist/Server/Services/IncomingCallPolicyOwnerUserService.js.map +1 -0
  81. package/build/dist/Server/Services/IncomingCallPolicyService.js +38 -0
  82. package/build/dist/Server/Services/IncomingCallPolicyService.js.map +1 -1
  83. package/build/dist/Server/Services/OnCallDutyPolicyLabelRuleEngineService.js +157 -0
  84. package/build/dist/Server/Services/OnCallDutyPolicyLabelRuleEngineService.js.map +1 -0
  85. package/build/dist/Server/Services/OnCallDutyPolicyLabelRuleService.js +9 -0
  86. package/build/dist/Server/Services/OnCallDutyPolicyLabelRuleService.js.map +1 -0
  87. package/build/dist/Server/Services/OnCallDutyPolicyOwnerRuleEngineService.js +180 -0
  88. package/build/dist/Server/Services/OnCallDutyPolicyOwnerRuleEngineService.js.map +1 -0
  89. package/build/dist/Server/Services/OnCallDutyPolicyOwnerRuleService.js +9 -0
  90. package/build/dist/Server/Services/OnCallDutyPolicyOwnerRuleService.js.map +1 -0
  91. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLabelRuleEngineService.js +158 -0
  92. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLabelRuleEngineService.js.map +1 -0
  93. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLabelRuleService.js +9 -0
  94. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLabelRuleService.js.map +1 -0
  95. package/build/dist/Server/Services/OnCallDutyPolicyScheduleOwnerRuleEngineService.js +181 -0
  96. package/build/dist/Server/Services/OnCallDutyPolicyScheduleOwnerRuleEngineService.js.map +1 -0
  97. package/build/dist/Server/Services/OnCallDutyPolicyScheduleOwnerRuleService.js +9 -0
  98. package/build/dist/Server/Services/OnCallDutyPolicyScheduleOwnerRuleService.js.map +1 -0
  99. package/build/dist/Server/Services/OnCallDutyPolicyScheduleOwnerTeamService.js +9 -0
  100. package/build/dist/Server/Services/OnCallDutyPolicyScheduleOwnerTeamService.js.map +1 -0
  101. package/build/dist/Server/Services/OnCallDutyPolicyScheduleOwnerUserService.js +9 -0
  102. package/build/dist/Server/Services/OnCallDutyPolicyScheduleOwnerUserService.js.map +1 -0
  103. package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js +21 -0
  104. package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js.map +1 -1
  105. package/build/dist/Server/Services/OnCallDutyPolicyService.js +17 -3
  106. package/build/dist/Server/Services/OnCallDutyPolicyService.js.map +1 -1
  107. package/build/dist/Server/Services/ProfileAggregationService.js +80 -0
  108. package/build/dist/Server/Services/ProfileAggregationService.js.map +1 -1
  109. package/build/dist/Server/Types/Database/Permissions/QueryPermission.js +25 -0
  110. package/build/dist/Server/Types/Database/Permissions/QueryPermission.js.map +1 -1
  111. package/build/dist/Types/Monitor/MonitorStep.js +42 -0
  112. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  113. package/build/dist/Types/Permission.js +415 -0
  114. package/build/dist/Types/Permission.js.map +1 -1
  115. package/package.json +1 -1
@@ -0,0 +1,191 @@
1
+ import Label from "../../Models/DatabaseModels/Label";
2
+ import OnCallDutyPolicy from "../../Models/DatabaseModels/OnCallDutyPolicy";
3
+ import OnCallDutyPolicyLabelRule from "../../Models/DatabaseModels/OnCallDutyPolicyLabelRule";
4
+ import OnCallDutyPolicyLabelRuleService from "./OnCallDutyPolicyLabelRuleService";
5
+ import OnCallDutyPolicyService from "./OnCallDutyPolicyService";
6
+ import ObjectID from "../../Types/ObjectID";
7
+ import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
8
+ import logger, { LogAttributes } from "../Utils/Logger";
9
+
10
+ class OnCallDutyPolicyLabelRuleEngineServiceClass {
11
+ @CaptureSpan()
12
+ public async applyRulesToOnCallDutyPolicy(
13
+ onCallDutyPolicy: OnCallDutyPolicy,
14
+ ): Promise<void> {
15
+ if (!onCallDutyPolicy.id || !onCallDutyPolicy.projectId) {
16
+ return;
17
+ }
18
+
19
+ try {
20
+ const rules: Array<OnCallDutyPolicyLabelRule> =
21
+ await OnCallDutyPolicyLabelRuleService.findBy({
22
+ query: {
23
+ projectId: onCallDutyPolicy.projectId,
24
+ isEnabled: true,
25
+ },
26
+ props: { isRoot: true },
27
+ select: {
28
+ _id: true,
29
+ name: true,
30
+ onCallDutyPolicyLabels: { _id: true },
31
+ onCallDutyPolicyNamePattern: true,
32
+ onCallDutyPolicyDescriptionPattern: true,
33
+ labelsToAdd: { _id: true },
34
+ },
35
+ limit: 100,
36
+ skip: 0,
37
+ });
38
+
39
+ if (rules.length === 0) {
40
+ return;
41
+ }
42
+
43
+ const policyWithDetails: OnCallDutyPolicy | null =
44
+ await OnCallDutyPolicyService.findOneById({
45
+ id: onCallDutyPolicy.id,
46
+ select: {
47
+ name: true,
48
+ description: true,
49
+ labels: { _id: true },
50
+ },
51
+ props: { isRoot: true },
52
+ });
53
+
54
+ if (!policyWithDetails) {
55
+ return;
56
+ }
57
+
58
+ const labelIdsToAdd: Set<string> = new Set();
59
+
60
+ for (const rule of rules) {
61
+ const matches: boolean = this.doesPolicyMatchRule(
62
+ policyWithDetails,
63
+ rule,
64
+ );
65
+ if (!matches) {
66
+ continue;
67
+ }
68
+ for (const label of rule.labelsToAdd || []) {
69
+ if (label.id) {
70
+ labelIdsToAdd.add(label.id.toString());
71
+ }
72
+ }
73
+ }
74
+
75
+ if (labelIdsToAdd.size === 0) {
76
+ return;
77
+ }
78
+
79
+ const existingLabelIds: Set<string> = new Set(
80
+ (policyWithDetails.labels || [])
81
+ .map((l: Label) => {
82
+ return l.id?.toString() || "";
83
+ })
84
+ .filter((id: string) => {
85
+ return id !== "";
86
+ }),
87
+ );
88
+
89
+ const newLabelIds: Array<string> = Array.from(labelIdsToAdd).filter(
90
+ (id: string) => {
91
+ return !existingLabelIds.has(id);
92
+ },
93
+ );
94
+ if (newLabelIds.length === 0) {
95
+ return;
96
+ }
97
+
98
+ await OnCallDutyPolicyService.getRepository()
99
+ .createQueryBuilder()
100
+ .relation(OnCallDutyPolicy, "labels")
101
+ .of(onCallDutyPolicy.id.toString())
102
+ .add(newLabelIds);
103
+
104
+ const mergedLabelIds: Set<string> = new Set([
105
+ ...existingLabelIds,
106
+ ...newLabelIds,
107
+ ]);
108
+ onCallDutyPolicy.labels = Array.from(mergedLabelIds).map((id: string) => {
109
+ const label: Label = new Label();
110
+ label.id = new ObjectID(id);
111
+ return label;
112
+ });
113
+
114
+ logger.debug(
115
+ `OnCallDutyPolicyLabelRuleEngine attached ${newLabelIds.length} labels to policy ${onCallDutyPolicy.id}`,
116
+ { projectId: onCallDutyPolicy.projectId.toString() } as LogAttributes,
117
+ );
118
+ } catch (error) {
119
+ logger.error(`Error applying on-call duty policy label rules: ${error}`, {
120
+ projectId: onCallDutyPolicy.projectId?.toString(),
121
+ onCallDutyPolicyId: onCallDutyPolicy.id?.toString(),
122
+ } as LogAttributes);
123
+ }
124
+ }
125
+
126
+ private doesPolicyMatchRule(
127
+ policy: OnCallDutyPolicy,
128
+ rule: OnCallDutyPolicyLabelRule,
129
+ ): boolean {
130
+ if (rule.onCallDutyPolicyLabels && rule.onCallDutyPolicyLabels.length > 0) {
131
+ if (!policy.labels || policy.labels.length === 0) {
132
+ return false;
133
+ }
134
+ const ruleLabelIds: Array<string> = rule.onCallDutyPolicyLabels.map(
135
+ (l: Label) => {
136
+ return l.id?.toString() || "";
137
+ },
138
+ );
139
+ const labelIds: Array<string> = policy.labels.map((l: Label) => {
140
+ return l.id?.toString() || "";
141
+ });
142
+ if (
143
+ !ruleLabelIds.some((id: string) => {
144
+ return labelIds.includes(id);
145
+ })
146
+ ) {
147
+ return false;
148
+ }
149
+ }
150
+
151
+ if (
152
+ rule.onCallDutyPolicyNamePattern &&
153
+ (!policy.name ||
154
+ !this.testRegex(rule.onCallDutyPolicyNamePattern, policy.name, rule))
155
+ ) {
156
+ return false;
157
+ }
158
+
159
+ if (
160
+ rule.onCallDutyPolicyDescriptionPattern &&
161
+ (!policy.description ||
162
+ !this.testRegex(
163
+ rule.onCallDutyPolicyDescriptionPattern,
164
+ policy.description,
165
+ rule,
166
+ ))
167
+ ) {
168
+ return false;
169
+ }
170
+
171
+ return true;
172
+ }
173
+
174
+ private testRegex(
175
+ pattern: string,
176
+ value: string,
177
+ rule: OnCallDutyPolicyLabelRule,
178
+ ): boolean {
179
+ try {
180
+ const regex: RegExp = new RegExp(pattern, "i");
181
+ return regex.test(value);
182
+ } catch {
183
+ logger.warn(
184
+ `Invalid regex in on-call duty policy label rule ${rule.id}: ${pattern}`,
185
+ );
186
+ return false;
187
+ }
188
+ }
189
+ }
190
+
191
+ export default new OnCallDutyPolicyLabelRuleEngineServiceClass();
@@ -0,0 +1,10 @@
1
+ import DatabaseService from "./DatabaseService";
2
+ import Model from "../../Models/DatabaseModels/OnCallDutyPolicyLabelRule";
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,214 @@
1
+ import Label from "../../Models/DatabaseModels/Label";
2
+ import OnCallDutyPolicy from "../../Models/DatabaseModels/OnCallDutyPolicy";
3
+ import OnCallDutyPolicyOwnerRule from "../../Models/DatabaseModels/OnCallDutyPolicyOwnerRule";
4
+ import OnCallDutyPolicyOwnerUser from "../../Models/DatabaseModels/OnCallDutyPolicyOwnerUser";
5
+ import OnCallDutyPolicyOwnerTeam from "../../Models/DatabaseModels/OnCallDutyPolicyOwnerTeam";
6
+ import OnCallDutyPolicyOwnerRuleService from "./OnCallDutyPolicyOwnerRuleService";
7
+ import OnCallDutyPolicyOwnerUserService from "./OnCallDutyPolicyOwnerUserService";
8
+ import OnCallDutyPolicyOwnerTeamService from "./OnCallDutyPolicyOwnerTeamService";
9
+ import OnCallDutyPolicyService from "./OnCallDutyPolicyService";
10
+ import ObjectID from "../../Types/ObjectID";
11
+ import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
12
+ import logger, { LogAttributes } from "../Utils/Logger";
13
+
14
+ class OnCallDutyPolicyOwnerRuleEngineServiceClass {
15
+ @CaptureSpan()
16
+ public async applyRulesToOnCallDutyPolicy(
17
+ onCallDutyPolicy: OnCallDutyPolicy,
18
+ ): Promise<void> {
19
+ if (!onCallDutyPolicy.id || !onCallDutyPolicy.projectId) {
20
+ return;
21
+ }
22
+
23
+ try {
24
+ const rules: Array<OnCallDutyPolicyOwnerRule> =
25
+ await OnCallDutyPolicyOwnerRuleService.findBy({
26
+ query: {
27
+ projectId: onCallDutyPolicy.projectId,
28
+ isEnabled: true,
29
+ },
30
+ props: { isRoot: true },
31
+ select: {
32
+ _id: true,
33
+ name: true,
34
+ notifyOwners: true,
35
+ onCallDutyPolicyLabels: { _id: true },
36
+ onCallDutyPolicyNamePattern: true,
37
+ onCallDutyPolicyDescriptionPattern: 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 policyWithDetails: OnCallDutyPolicy | null =
50
+ await OnCallDutyPolicyService.findOneById({
51
+ id: onCallDutyPolicy.id,
52
+ select: {
53
+ name: true,
54
+ description: true,
55
+ labels: { _id: true },
56
+ },
57
+ props: { isRoot: true },
58
+ });
59
+
60
+ if (!policyWithDetails) {
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<OnCallDutyPolicyOwnerRule> = [];
74
+
75
+ for (const rule of rules) {
76
+ const matches: boolean = this.doesPolicyMatchRule(
77
+ policyWithDetails,
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: OnCallDutyPolicyOwnerUser =
112
+ new OnCallDutyPolicyOwnerUser();
113
+ owner.onCallDutyPolicyId = onCallDutyPolicy.id;
114
+ owner.projectId = onCallDutyPolicy.projectId;
115
+ owner.userId = new ObjectID(userId);
116
+ owner.isOwnerNotified = !notify;
117
+ await OnCallDutyPolicyOwnerUserService.create({
118
+ data: owner,
119
+ props: { isRoot: true },
120
+ });
121
+ }
122
+
123
+ for (const teamId of teamIds) {
124
+ const owner: OnCallDutyPolicyOwnerTeam =
125
+ new OnCallDutyPolicyOwnerTeam();
126
+ owner.onCallDutyPolicyId = onCallDutyPolicy.id;
127
+ owner.projectId = onCallDutyPolicy.projectId;
128
+ owner.teamId = new ObjectID(teamId);
129
+ owner.isOwnerNotified = !notify;
130
+ await OnCallDutyPolicyOwnerTeamService.create({
131
+ data: owner,
132
+ props: { isRoot: true },
133
+ });
134
+ }
135
+ }
136
+
137
+ logger.debug(
138
+ `OnCallDutyPolicyOwnerRuleEngine added owners to policy ${onCallDutyPolicy.id}`,
139
+ { projectId: onCallDutyPolicy.projectId.toString() } as LogAttributes,
140
+ );
141
+ } catch (error) {
142
+ logger.error(`Error applying on-call duty policy owner rules: ${error}`, {
143
+ projectId: onCallDutyPolicy.projectId?.toString(),
144
+ onCallDutyPolicyId: onCallDutyPolicy.id?.toString(),
145
+ } as LogAttributes);
146
+ }
147
+ }
148
+
149
+ private doesPolicyMatchRule(
150
+ policy: OnCallDutyPolicy,
151
+ rule: OnCallDutyPolicyOwnerRule,
152
+ ): boolean {
153
+ if (rule.onCallDutyPolicyLabels && rule.onCallDutyPolicyLabels.length > 0) {
154
+ if (!policy.labels || policy.labels.length === 0) {
155
+ return false;
156
+ }
157
+ const ruleLabelIds: Array<string> = rule.onCallDutyPolicyLabels.map(
158
+ (l: Label) => {
159
+ return l.id?.toString() || "";
160
+ },
161
+ );
162
+ const labelIds: Array<string> = policy.labels.map((l: Label) => {
163
+ return l.id?.toString() || "";
164
+ });
165
+ if (
166
+ !ruleLabelIds.some((id: string) => {
167
+ return labelIds.includes(id);
168
+ })
169
+ ) {
170
+ return false;
171
+ }
172
+ }
173
+
174
+ if (
175
+ rule.onCallDutyPolicyNamePattern &&
176
+ (!policy.name ||
177
+ !this.testRegex(rule.onCallDutyPolicyNamePattern, policy.name, rule))
178
+ ) {
179
+ return false;
180
+ }
181
+
182
+ if (
183
+ rule.onCallDutyPolicyDescriptionPattern &&
184
+ (!policy.description ||
185
+ !this.testRegex(
186
+ rule.onCallDutyPolicyDescriptionPattern,
187
+ policy.description,
188
+ rule,
189
+ ))
190
+ ) {
191
+ return false;
192
+ }
193
+
194
+ return true;
195
+ }
196
+
197
+ private testRegex(
198
+ pattern: string,
199
+ value: string,
200
+ rule: OnCallDutyPolicyOwnerRule,
201
+ ): boolean {
202
+ try {
203
+ const regex: RegExp = new RegExp(pattern, "i");
204
+ return regex.test(value);
205
+ } catch {
206
+ logger.warn(
207
+ `Invalid regex in on-call duty policy owner rule ${rule.id}: ${pattern}`,
208
+ );
209
+ return false;
210
+ }
211
+ }
212
+ }
213
+
214
+ export default new OnCallDutyPolicyOwnerRuleEngineServiceClass();
@@ -0,0 +1,10 @@
1
+ import DatabaseService from "./DatabaseService";
2
+ import Model from "../../Models/DatabaseModels/OnCallDutyPolicyOwnerRule";
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,200 @@
1
+ import Label from "../../Models/DatabaseModels/Label";
2
+ import OnCallDutyPolicySchedule from "../../Models/DatabaseModels/OnCallDutyPolicySchedule";
3
+ import OnCallDutyPolicyScheduleLabelRule from "../../Models/DatabaseModels/OnCallDutyPolicyScheduleLabelRule";
4
+ import OnCallDutyPolicyScheduleLabelRuleService from "./OnCallDutyPolicyScheduleLabelRuleService";
5
+ import OnCallDutyPolicyScheduleService from "./OnCallDutyPolicyScheduleService";
6
+ import ObjectID from "../../Types/ObjectID";
7
+ import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
8
+ import logger, { LogAttributes } from "../Utils/Logger";
9
+
10
+ class OnCallDutyPolicyScheduleLabelRuleEngineServiceClass {
11
+ @CaptureSpan()
12
+ public async applyRulesToSchedule(
13
+ schedule: OnCallDutyPolicySchedule,
14
+ ): Promise<void> {
15
+ if (!schedule.id || !schedule.projectId) {
16
+ return;
17
+ }
18
+
19
+ try {
20
+ const rules: Array<OnCallDutyPolicyScheduleLabelRule> =
21
+ await OnCallDutyPolicyScheduleLabelRuleService.findBy({
22
+ query: {
23
+ projectId: schedule.projectId,
24
+ isEnabled: true,
25
+ },
26
+ props: { isRoot: true },
27
+ select: {
28
+ _id: true,
29
+ name: true,
30
+ onCallDutyPolicyScheduleLabels: { _id: true },
31
+ onCallDutyPolicyScheduleNamePattern: true,
32
+ onCallDutyPolicyScheduleDescriptionPattern: true,
33
+ labelsToAdd: { _id: true },
34
+ },
35
+ limit: 100,
36
+ skip: 0,
37
+ });
38
+
39
+ if (rules.length === 0) {
40
+ return;
41
+ }
42
+
43
+ const scheduleWithDetails: OnCallDutyPolicySchedule | null =
44
+ await OnCallDutyPolicyScheduleService.findOneById({
45
+ id: schedule.id,
46
+ select: {
47
+ name: true,
48
+ description: true,
49
+ labels: { _id: true },
50
+ },
51
+ props: { isRoot: true },
52
+ });
53
+
54
+ if (!scheduleWithDetails) {
55
+ return;
56
+ }
57
+
58
+ const labelIdsToAdd: Set<string> = new Set();
59
+
60
+ for (const rule of rules) {
61
+ const matches: boolean = this.doesScheduleMatchRule(
62
+ scheduleWithDetails,
63
+ rule,
64
+ );
65
+ if (!matches) {
66
+ continue;
67
+ }
68
+ for (const label of rule.labelsToAdd || []) {
69
+ if (label.id) {
70
+ labelIdsToAdd.add(label.id.toString());
71
+ }
72
+ }
73
+ }
74
+
75
+ if (labelIdsToAdd.size === 0) {
76
+ return;
77
+ }
78
+
79
+ const existingLabelIds: Set<string> = new Set(
80
+ (scheduleWithDetails.labels || [])
81
+ .map((l: Label) => {
82
+ return l.id?.toString() || "";
83
+ })
84
+ .filter((id: string) => {
85
+ return id !== "";
86
+ }),
87
+ );
88
+
89
+ const newLabelIds: Array<string> = Array.from(labelIdsToAdd).filter(
90
+ (id: string) => {
91
+ return !existingLabelIds.has(id);
92
+ },
93
+ );
94
+ if (newLabelIds.length === 0) {
95
+ return;
96
+ }
97
+
98
+ await OnCallDutyPolicyScheduleService.getRepository()
99
+ .createQueryBuilder()
100
+ .relation(OnCallDutyPolicySchedule, "labels")
101
+ .of(schedule.id.toString())
102
+ .add(newLabelIds);
103
+
104
+ const mergedLabelIds: Set<string> = new Set([
105
+ ...existingLabelIds,
106
+ ...newLabelIds,
107
+ ]);
108
+ schedule.labels = Array.from(mergedLabelIds).map((id: string) => {
109
+ const label: Label = new Label();
110
+ label.id = new ObjectID(id);
111
+ return label;
112
+ });
113
+
114
+ logger.debug(
115
+ `OnCallDutyPolicyScheduleLabelRuleEngine attached ${newLabelIds.length} labels to schedule ${schedule.id}`,
116
+ { projectId: schedule.projectId.toString() } as LogAttributes,
117
+ );
118
+ } catch (error) {
119
+ logger.error(
120
+ `Error applying on-call duty schedule label rules: ${error}`,
121
+ {
122
+ projectId: schedule.projectId?.toString(),
123
+ onCallDutyPolicyScheduleId: schedule.id?.toString(),
124
+ } as LogAttributes,
125
+ );
126
+ }
127
+ }
128
+
129
+ private doesScheduleMatchRule(
130
+ schedule: OnCallDutyPolicySchedule,
131
+ rule: OnCallDutyPolicyScheduleLabelRule,
132
+ ): boolean {
133
+ if (
134
+ rule.onCallDutyPolicyScheduleLabels &&
135
+ rule.onCallDutyPolicyScheduleLabels.length > 0
136
+ ) {
137
+ if (!schedule.labels || schedule.labels.length === 0) {
138
+ return false;
139
+ }
140
+ const ruleLabelIds: Array<string> =
141
+ rule.onCallDutyPolicyScheduleLabels.map((l: Label) => {
142
+ return l.id?.toString() || "";
143
+ });
144
+ const labelIds: Array<string> = schedule.labels.map((l: Label) => {
145
+ return l.id?.toString() || "";
146
+ });
147
+ if (
148
+ !ruleLabelIds.some((id: string) => {
149
+ return labelIds.includes(id);
150
+ })
151
+ ) {
152
+ return false;
153
+ }
154
+ }
155
+
156
+ if (
157
+ rule.onCallDutyPolicyScheduleNamePattern &&
158
+ (!schedule.name ||
159
+ !this.testRegex(
160
+ rule.onCallDutyPolicyScheduleNamePattern,
161
+ schedule.name,
162
+ rule,
163
+ ))
164
+ ) {
165
+ return false;
166
+ }
167
+
168
+ if (
169
+ rule.onCallDutyPolicyScheduleDescriptionPattern &&
170
+ (!schedule.description ||
171
+ !this.testRegex(
172
+ rule.onCallDutyPolicyScheduleDescriptionPattern,
173
+ schedule.description,
174
+ rule,
175
+ ))
176
+ ) {
177
+ return false;
178
+ }
179
+
180
+ return true;
181
+ }
182
+
183
+ private testRegex(
184
+ pattern: string,
185
+ value: string,
186
+ rule: OnCallDutyPolicyScheduleLabelRule,
187
+ ): boolean {
188
+ try {
189
+ const regex: RegExp = new RegExp(pattern, "i");
190
+ return regex.test(value);
191
+ } catch {
192
+ logger.warn(
193
+ `Invalid regex in on-call duty schedule label rule ${rule.id}: ${pattern}`,
194
+ );
195
+ return false;
196
+ }
197
+ }
198
+ }
199
+
200
+ export default new OnCallDutyPolicyScheduleLabelRuleEngineServiceClass();
@@ -0,0 +1,10 @@
1
+ import DatabaseService from "./DatabaseService";
2
+ import Model from "../../Models/DatabaseModels/OnCallDutyPolicyScheduleLabelRule";
3
+
4
+ export class Service extends DatabaseService<Model> {
5
+ public constructor() {
6
+ super(Model);
7
+ }
8
+ }
9
+
10
+ export default new Service();