@oneuptime/common 10.2.0 → 10.2.2
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/Index.ts +2 -0
- package/Models/DatabaseModels/ProjectOidc.ts +705 -0
- package/Server/API/ProjectOIDC.ts +73 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1778506655291-AddProjectOIDC.ts +79 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
- package/Server/Services/AlertLabelRuleEngineService.ts +16 -0
- package/Server/Services/IncidentLabelRuleEngineService.ts +16 -0
- package/Server/Services/Index.ts +2 -0
- package/Server/Services/OnCallDutyPolicyScheduleService.ts +139 -26
- package/Server/Services/ProjectOidcService.ts +10 -0
- package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +23 -0
- package/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.ts +98 -3
- package/Tests/Utils/MetricUnitUtil.test.ts +38 -1
- package/Types/Monitor/MetricMonitor/MetricMonitorResponse.ts +8 -0
- package/Types/OnCallDutyPolicy/UserOverrideUtil.ts +155 -0
- package/Types/Permission.ts +42 -0
- package/UI/Components/Calendar/Calendar.css +257 -0
- package/UI/Components/Calendar/Calendar.tsx +22 -11
- package/Utils/MetricUnitUtil.ts +24 -0
- package/build/dist/Models/DatabaseModels/Index.js +2 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ProjectOidc.js +727 -0
- package/build/dist/Models/DatabaseModels/ProjectOidc.js.map +1 -0
- package/build/dist/Server/API/ProjectOIDC.js +45 -0
- package/build/dist/Server/API/ProjectOIDC.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1778506655291-AddProjectOIDC.js +34 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1778506655291-AddProjectOIDC.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/AlertLabelRuleEngineService.js +16 -0
- package/build/dist/Server/Services/AlertLabelRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/IncidentLabelRuleEngineService.js +16 -0
- package/build/dist/Server/Services/IncidentLabelRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +2 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js +106 -17
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js.map +1 -1
- package/build/dist/Server/Services/ProjectOidcService.js +9 -0
- package/build/dist/Server/Services/ProjectOidcService.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +25 -8
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js +71 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js.map +1 -1
- package/build/dist/Tests/Utils/MetricUnitUtil.test.js +29 -1
- package/build/dist/Tests/Utils/MetricUnitUtil.test.js.map +1 -1
- package/build/dist/Types/OnCallDutyPolicy/UserOverrideUtil.js +86 -0
- package/build/dist/Types/OnCallDutyPolicy/UserOverrideUtil.js.map +1 -0
- package/build/dist/Types/Permission.js +40 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/UI/Components/Calendar/Calendar.js +12 -10
- package/build/dist/UI/Components/Calendar/Calendar.js.map +1 -1
- package/build/dist/Utils/MetricUnitUtil.js +22 -0
- package/build/dist/Utils/MetricUnitUtil.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import ProjectOidcService, {
|
|
2
|
+
Service as ProjectOidcServiceType,
|
|
3
|
+
} from "../Services/ProjectOidcService";
|
|
4
|
+
import {
|
|
5
|
+
ExpressRequest,
|
|
6
|
+
ExpressResponse,
|
|
7
|
+
NextFunction,
|
|
8
|
+
} from "../Utils/Express";
|
|
9
|
+
import Response from "../Utils/Response";
|
|
10
|
+
import BaseAPI from "./BaseAPI";
|
|
11
|
+
import { LIMIT_PER_PROJECT } from "../../Types/Database/LimitMax";
|
|
12
|
+
import BadDataException from "../../Types/Exception/BadDataException";
|
|
13
|
+
import ObjectID from "../../Types/ObjectID";
|
|
14
|
+
import PositiveNumber from "../../Types/PositiveNumber";
|
|
15
|
+
import ProjectOIDC from "../../Models/DatabaseModels/ProjectOidc";
|
|
16
|
+
|
|
17
|
+
export default class ProjectOidcAPI extends BaseAPI<
|
|
18
|
+
ProjectOIDC,
|
|
19
|
+
ProjectOidcServiceType
|
|
20
|
+
> {
|
|
21
|
+
public constructor() {
|
|
22
|
+
super(ProjectOIDC, ProjectOidcService);
|
|
23
|
+
|
|
24
|
+
// OIDC Fetch API
|
|
25
|
+
this.router.post(
|
|
26
|
+
`${new this.entityType()
|
|
27
|
+
.getCrudApiPath()
|
|
28
|
+
?.toString()}/:projectId/oidc-list`,
|
|
29
|
+
async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
|
|
30
|
+
try {
|
|
31
|
+
const projectId: ObjectID = new ObjectID(
|
|
32
|
+
req.params["projectId"] as string,
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
if (!projectId) {
|
|
36
|
+
return Response.sendErrorResponse(
|
|
37
|
+
req,
|
|
38
|
+
res,
|
|
39
|
+
new BadDataException("Invalid project id."),
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const oidc: Array<ProjectOIDC> = await this.service.findBy({
|
|
44
|
+
query: {
|
|
45
|
+
projectId: projectId,
|
|
46
|
+
isEnabled: true,
|
|
47
|
+
},
|
|
48
|
+
limit: LIMIT_PER_PROJECT,
|
|
49
|
+
skip: 0,
|
|
50
|
+
select: {
|
|
51
|
+
name: true,
|
|
52
|
+
description: true,
|
|
53
|
+
_id: true,
|
|
54
|
+
},
|
|
55
|
+
props: {
|
|
56
|
+
isRoot: true,
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
return Response.sendEntityArrayResponse(
|
|
61
|
+
req,
|
|
62
|
+
res,
|
|
63
|
+
oidc,
|
|
64
|
+
new PositiveNumber(oidc.length),
|
|
65
|
+
ProjectOIDC,
|
|
66
|
+
);
|
|
67
|
+
} catch (err) {
|
|
68
|
+
return next(err);
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { MigrationInterface, QueryRunner } from "typeorm";
|
|
2
|
+
|
|
3
|
+
export class AddProjectOIDC1778506655291 implements MigrationInterface {
|
|
4
|
+
name = "AddProjectOIDC1778506655291";
|
|
5
|
+
|
|
6
|
+
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
7
|
+
await queryRunner.query(
|
|
8
|
+
`CREATE TABLE "ProjectOIDC" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP WITH TIME ZONE, "version" integer NOT NULL, "projectId" uuid NOT NULL, "name" character varying(100) NOT NULL, "description" character varying NOT NULL, "discoveryURL" text NOT NULL, "issuerURL" text NOT NULL, "clientId" character varying(100) NOT NULL, "clientSecret" character varying NOT NULL, "scopes" character varying(100) NOT NULL, "emailClaimName" character varying(100) NOT NULL, "nameClaimName" character varying(100) NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, "isEnabled" boolean NOT NULL DEFAULT false, "isTested" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_22abf8119bac3f7f4f9f03b201f" PRIMARY KEY ("_id"))`,
|
|
9
|
+
);
|
|
10
|
+
await queryRunner.query(
|
|
11
|
+
`CREATE INDEX "IDX_b5b93c3e2885549c370b816194" ON "ProjectOIDC" ("projectId") `,
|
|
12
|
+
);
|
|
13
|
+
await queryRunner.query(
|
|
14
|
+
`CREATE TABLE "ProjectOidcTeam" ("projectOidcId" uuid NOT NULL, "teamId" uuid NOT NULL, CONSTRAINT "PK_bdae022f28d23653e2aa4a40abb" PRIMARY KEY ("projectOidcId", "teamId"))`,
|
|
15
|
+
);
|
|
16
|
+
await queryRunner.query(
|
|
17
|
+
`CREATE INDEX "IDX_8c317a3effac6698ad8dfbc82d" ON "ProjectOidcTeam" ("projectOidcId") `,
|
|
18
|
+
);
|
|
19
|
+
await queryRunner.query(
|
|
20
|
+
`CREATE INDEX "IDX_456cebe2924528d694b29f91bc" ON "ProjectOidcTeam" ("teamId") `,
|
|
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 "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type":"RestrictionTimes","value":{"restictionType":"None","dayRestrictionTimes":null,"weeklyRestrictionTimes":[]}}'`,
|
|
27
|
+
);
|
|
28
|
+
await queryRunner.query(
|
|
29
|
+
`ALTER TABLE "ProjectOIDC" ADD CONSTRAINT "FK_b5b93c3e2885549c370b8161940" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
|
30
|
+
);
|
|
31
|
+
await queryRunner.query(
|
|
32
|
+
`ALTER TABLE "ProjectOIDC" ADD CONSTRAINT "FK_df47207e1005bef42ca062f7a4b" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`,
|
|
33
|
+
);
|
|
34
|
+
await queryRunner.query(
|
|
35
|
+
`ALTER TABLE "ProjectOIDC" ADD CONSTRAINT "FK_3f386ff54e38e36f4c016187648" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`,
|
|
36
|
+
);
|
|
37
|
+
await queryRunner.query(
|
|
38
|
+
`ALTER TABLE "ProjectOidcTeam" ADD CONSTRAINT "FK_8c317a3effac6698ad8dfbc82d4" FOREIGN KEY ("projectOidcId") REFERENCES "ProjectOIDC"("_id") ON DELETE CASCADE ON UPDATE CASCADE`,
|
|
39
|
+
);
|
|
40
|
+
await queryRunner.query(
|
|
41
|
+
`ALTER TABLE "ProjectOidcTeam" ADD CONSTRAINT "FK_456cebe2924528d694b29f91bc9" FOREIGN KEY ("teamId") REFERENCES "Team"("_id") ON DELETE CASCADE ON UPDATE CASCADE`,
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
46
|
+
await queryRunner.query(
|
|
47
|
+
`ALTER TABLE "ProjectOidcTeam" DROP CONSTRAINT "FK_456cebe2924528d694b29f91bc9"`,
|
|
48
|
+
);
|
|
49
|
+
await queryRunner.query(
|
|
50
|
+
`ALTER TABLE "ProjectOidcTeam" DROP CONSTRAINT "FK_8c317a3effac6698ad8dfbc82d4"`,
|
|
51
|
+
);
|
|
52
|
+
await queryRunner.query(
|
|
53
|
+
`ALTER TABLE "ProjectOIDC" DROP CONSTRAINT "FK_3f386ff54e38e36f4c016187648"`,
|
|
54
|
+
);
|
|
55
|
+
await queryRunner.query(
|
|
56
|
+
`ALTER TABLE "ProjectOIDC" DROP CONSTRAINT "FK_df47207e1005bef42ca062f7a4b"`,
|
|
57
|
+
);
|
|
58
|
+
await queryRunner.query(
|
|
59
|
+
`ALTER TABLE "ProjectOIDC" DROP CONSTRAINT "FK_b5b93c3e2885549c370b8161940"`,
|
|
60
|
+
);
|
|
61
|
+
await queryRunner.query(
|
|
62
|
+
`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type": "RestrictionTimes", "value": {"restictionType": "None", "dayRestrictionTimes": null, "weeklyRestrictionTimes": []}}'`,
|
|
63
|
+
);
|
|
64
|
+
await queryRunner.query(
|
|
65
|
+
`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type": "Recurring", "value": {"intervalType": "Day", "intervalCount": {"_type": "PositiveNumber", "value": 1}}}'`,
|
|
66
|
+
);
|
|
67
|
+
await queryRunner.query(
|
|
68
|
+
`DROP INDEX "public"."IDX_456cebe2924528d694b29f91bc"`,
|
|
69
|
+
);
|
|
70
|
+
await queryRunner.query(
|
|
71
|
+
`DROP INDEX "public"."IDX_8c317a3effac6698ad8dfbc82d"`,
|
|
72
|
+
);
|
|
73
|
+
await queryRunner.query(`DROP TABLE "ProjectOidcTeam"`);
|
|
74
|
+
await queryRunner.query(
|
|
75
|
+
`DROP INDEX "public"."IDX_b5b93c3e2885549c370b816194"`,
|
|
76
|
+
);
|
|
77
|
+
await queryRunner.query(`DROP TABLE "ProjectOIDC"`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -312,6 +312,7 @@ import { AddOwnerRuleInheritFlags1778413144103 } from "./1778413144103-AddOwnerR
|
|
|
312
312
|
import { AddAlertIsPrivate1778438949454 } from "./1778438949454-AddAlertIsPrivate";
|
|
313
313
|
import { AddPrivacyRules1778440665575 } from "./1778440665575-AddPrivacyRules";
|
|
314
314
|
import { AddEpisodePrivacyRules1778442385970 } from "./1778442385970-AddEpisodePrivacyRules";
|
|
315
|
+
import { AddProjectOIDC1778506655291 } from "./1778506655291-AddProjectOIDC";
|
|
315
316
|
export default [
|
|
316
317
|
InitialMigration,
|
|
317
318
|
MigrationName1717678334852,
|
|
@@ -627,4 +628,5 @@ export default [
|
|
|
627
628
|
AddAlertIsPrivate1778438949454,
|
|
628
629
|
AddPrivacyRules1778440665575,
|
|
629
630
|
AddEpisodePrivacyRules1778442385970,
|
|
631
|
+
AddProjectOIDC1778506655291,
|
|
630
632
|
];
|
|
@@ -127,6 +127,22 @@ class AlertLabelRuleEngineServiceClass {
|
|
|
127
127
|
.of(alert.id.toString())
|
|
128
128
|
.add(newLabelIds);
|
|
129
129
|
|
|
130
|
+
/*
|
|
131
|
+
* Sync in-memory alert.labels with the now-persisted set so downstream
|
|
132
|
+
* rule engines (AlertOnCallRuleEngineService) can match on these labels
|
|
133
|
+
* in the same onCreateSuccess chain. Without this, the on-call engine sees
|
|
134
|
+
* only the criteria-time labels and skips rules keyed on rule-added labels.
|
|
135
|
+
*/
|
|
136
|
+
const mergedLabelIds: Set<string> = new Set([
|
|
137
|
+
...existingLabelIds,
|
|
138
|
+
...newLabelIds,
|
|
139
|
+
]);
|
|
140
|
+
alert.labels = Array.from(mergedLabelIds).map((id: string) => {
|
|
141
|
+
const label: Label = new Label();
|
|
142
|
+
label.id = new ObjectID(id);
|
|
143
|
+
return label;
|
|
144
|
+
});
|
|
145
|
+
|
|
130
146
|
logger.debug(
|
|
131
147
|
`AlertLabelRuleEngine attached ${newLabelIds.length} labels to alert ${alert.id}`,
|
|
132
148
|
{ projectId: alert.projectId.toString() } as LogAttributes,
|
|
@@ -163,6 +163,22 @@ class IncidentLabelRuleEngineServiceClass {
|
|
|
163
163
|
.of(incident.id.toString())
|
|
164
164
|
.add(newLabelIds);
|
|
165
165
|
|
|
166
|
+
/*
|
|
167
|
+
* Sync in-memory incident.labels with the now-persisted set so downstream
|
|
168
|
+
* rule engines (IncidentOnCallRuleEngineService) can match on these labels
|
|
169
|
+
* in the same onCreateSuccess chain. Without this, the on-call engine sees
|
|
170
|
+
* only the criteria-time labels and skips rules keyed on rule-added labels.
|
|
171
|
+
*/
|
|
172
|
+
const mergedLabelIds: Set<string> = new Set([
|
|
173
|
+
...existingLabelIds,
|
|
174
|
+
...newLabelIds,
|
|
175
|
+
]);
|
|
176
|
+
incident.labels = Array.from(mergedLabelIds).map((id: string) => {
|
|
177
|
+
const label: Label = new Label();
|
|
178
|
+
label.id = new ObjectID(id);
|
|
179
|
+
return label;
|
|
180
|
+
});
|
|
181
|
+
|
|
166
182
|
logger.debug(
|
|
167
183
|
`IncidentLabelRuleEngine attached ${newLabelIds.length} labels to incident ${incident.id}`,
|
|
168
184
|
{ projectId: incident.projectId.toString() } as LogAttributes,
|
package/Server/Services/Index.ts
CHANGED
|
@@ -85,6 +85,7 @@ import ProfileSampleService from "./ProfileSampleService";
|
|
|
85
85
|
// Project SMTP Config.
|
|
86
86
|
import ProjectSmtpConfigService from "./ProjectSmtpConfigService";
|
|
87
87
|
import ProjectSsoService from "./ProjectSsoService";
|
|
88
|
+
import ProjectOidcService from "./ProjectOidcService";
|
|
88
89
|
import PromoCodeService from "./PromoCodeService";
|
|
89
90
|
import EnterpriseLicenseService from "./EnterpriseLicenseService";
|
|
90
91
|
import OpenSourceDeploymentService from "./OpenSourceDeploymentService";
|
|
@@ -300,6 +301,7 @@ const services: Array<BaseService> = [
|
|
|
300
301
|
AIAgentTaskLogService,
|
|
301
302
|
AIAgentTaskPullRequestService,
|
|
302
303
|
ProjectSsoService,
|
|
304
|
+
ProjectOidcService,
|
|
303
305
|
|
|
304
306
|
ScheduledMaintenanceCustomFieldService,
|
|
305
307
|
ScheduledMaintenanceInternalNoteService,
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import DatabaseService from "./DatabaseService";
|
|
2
2
|
import OnCallDutyPolicyScheduleLayerService from "./OnCallDutyPolicyScheduleLayerService";
|
|
3
3
|
import OnCallDutyPolicyScheduleLayerUserService from "./OnCallDutyPolicyScheduleLayerUserService";
|
|
4
|
+
import OnCallDutyPolicyUserOverrideService from "./OnCallDutyPolicyUserOverrideService";
|
|
4
5
|
import SortOrder from "../../Types/BaseDatabase/SortOrder";
|
|
5
6
|
import CalendarEvent from "../../Types/Calendar/CalendarEvent";
|
|
6
7
|
import { LIMIT_PER_PROJECT } from "../../Types/Database/LimitMax";
|
|
7
8
|
import OneUptimeDate from "../../Types/Date";
|
|
8
9
|
import ObjectID from "../../Types/ObjectID";
|
|
9
10
|
import LayerUtil, { LayerProps } from "../../Types/OnCallDutyPolicy/Layer";
|
|
11
|
+
import UserOverrideUtil, {
|
|
12
|
+
UserOverrideRecord,
|
|
13
|
+
} from "../../Types/OnCallDutyPolicy/UserOverrideUtil";
|
|
14
|
+
import OnCallDutyPolicyUserOverride from "../../Models/DatabaseModels/OnCallDutyPolicyUserOverride";
|
|
15
|
+
import QueryHelper from "../Types/Database/QueryHelper";
|
|
10
16
|
import OnCallDutyPolicyScheduleLayer from "../../Models/DatabaseModels/OnCallDutyPolicyScheduleLayer";
|
|
11
17
|
import OnCallDutyPolicyScheduleLayerUser from "../../Models/DatabaseModels/OnCallDutyPolicyScheduleLayerUser";
|
|
12
18
|
import User from "../../Models/DatabaseModels/User";
|
|
@@ -914,9 +920,11 @@ export class Service extends DatabaseService<OnCallDutyPolicySchedule> {
|
|
|
914
920
|
return resultReturn;
|
|
915
921
|
}
|
|
916
922
|
|
|
917
|
-
private async getScheduleLayerProps(data: {
|
|
918
|
-
|
|
919
|
-
|
|
923
|
+
private async getScheduleLayerProps(data: { scheduleId: ObjectID }): Promise<{
|
|
924
|
+
layerProps: Array<LayerProps>;
|
|
925
|
+
projectId: ObjectID | null;
|
|
926
|
+
scheduleUserIds: Array<ObjectID>;
|
|
927
|
+
}> {
|
|
920
928
|
// get schedule layers.
|
|
921
929
|
|
|
922
930
|
const scheduleId: ObjectID = data.scheduleId;
|
|
@@ -968,23 +976,34 @@ export class Service extends DatabaseService<OnCallDutyPolicySchedule> {
|
|
|
968
976
|
});
|
|
969
977
|
|
|
970
978
|
const layerProps: Array<LayerProps> = [];
|
|
979
|
+
const scheduleUserIds: Array<ObjectID> = [];
|
|
980
|
+
const seenUserIds: Set<string> = new Set<string>();
|
|
971
981
|
|
|
972
982
|
for (const layer of layers) {
|
|
983
|
+
const usersForLayer: Array<User> = layerUsers
|
|
984
|
+
.filter((layerUser: OnCallDutyPolicyScheduleLayerUser) => {
|
|
985
|
+
return (
|
|
986
|
+
layerUser.onCallDutyPolicyScheduleLayerId?.toString() ===
|
|
987
|
+
layer.id?.toString()
|
|
988
|
+
);
|
|
989
|
+
})
|
|
990
|
+
.map((layerUser: OnCallDutyPolicyScheduleLayerUser) => {
|
|
991
|
+
return layerUser.user!;
|
|
992
|
+
})
|
|
993
|
+
.filter((user: User) => {
|
|
994
|
+
return Boolean(user);
|
|
995
|
+
});
|
|
996
|
+
|
|
997
|
+
for (const user of usersForLayer) {
|
|
998
|
+
const idStr: string = user.id?.toString() || "";
|
|
999
|
+
if (idStr && !seenUserIds.has(idStr)) {
|
|
1000
|
+
seenUserIds.add(idStr);
|
|
1001
|
+
scheduleUserIds.push(user.id!);
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
|
|
973
1005
|
layerProps.push({
|
|
974
|
-
users:
|
|
975
|
-
layerUsers
|
|
976
|
-
.filter((layerUser: OnCallDutyPolicyScheduleLayerUser) => {
|
|
977
|
-
return (
|
|
978
|
-
layerUser.onCallDutyPolicyScheduleLayerId?.toString() ===
|
|
979
|
-
layer.id?.toString()
|
|
980
|
-
);
|
|
981
|
-
})
|
|
982
|
-
.map((layerUser: OnCallDutyPolicyScheduleLayerUser) => {
|
|
983
|
-
return layerUser.user!;
|
|
984
|
-
})
|
|
985
|
-
.filter((user: User) => {
|
|
986
|
-
return Boolean(user);
|
|
987
|
-
}) || [],
|
|
1006
|
+
users: usersForLayer,
|
|
988
1007
|
startDateTimeOfLayer: layer.startsAt!,
|
|
989
1008
|
restrictionTimes: layer.restrictionTimes!,
|
|
990
1009
|
rotation: layer.rotation!,
|
|
@@ -992,7 +1011,65 @@ export class Service extends DatabaseService<OnCallDutyPolicySchedule> {
|
|
|
992
1011
|
});
|
|
993
1012
|
}
|
|
994
1013
|
|
|
995
|
-
|
|
1014
|
+
const projectId: ObjectID | null = layers[0]?.projectId || null;
|
|
1015
|
+
|
|
1016
|
+
return { layerProps, projectId, scheduleUserIds };
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
private async fetchOverridesForSchedule(data: {
|
|
1020
|
+
projectId: ObjectID;
|
|
1021
|
+
scheduleUserIds: Array<ObjectID>;
|
|
1022
|
+
windowStart: Date;
|
|
1023
|
+
windowEnd: Date;
|
|
1024
|
+
}): Promise<Array<UserOverrideRecord>> {
|
|
1025
|
+
if (data.scheduleUserIds.length === 0) {
|
|
1026
|
+
return [];
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
const overrides: Array<OnCallDutyPolicyUserOverride> =
|
|
1030
|
+
await OnCallDutyPolicyUserOverrideService.findBy({
|
|
1031
|
+
query: {
|
|
1032
|
+
projectId: data.projectId,
|
|
1033
|
+
startsAt: QueryHelper.lessThanEqualTo(data.windowEnd),
|
|
1034
|
+
endsAt: QueryHelper.greaterThanEqualTo(data.windowStart),
|
|
1035
|
+
},
|
|
1036
|
+
select: {
|
|
1037
|
+
startsAt: true,
|
|
1038
|
+
endsAt: true,
|
|
1039
|
+
overrideUserId: true,
|
|
1040
|
+
routeAlertsToUserId: true,
|
|
1041
|
+
onCallDutyPolicyId: true,
|
|
1042
|
+
},
|
|
1043
|
+
sort: {
|
|
1044
|
+
startsAt: SortOrder.Ascending,
|
|
1045
|
+
},
|
|
1046
|
+
limit: LIMIT_PER_PROJECT,
|
|
1047
|
+
skip: 0,
|
|
1048
|
+
props: {
|
|
1049
|
+
isRoot: true,
|
|
1050
|
+
},
|
|
1051
|
+
});
|
|
1052
|
+
|
|
1053
|
+
const scheduleUserIdSet: Set<string> = new Set<string>(
|
|
1054
|
+
data.scheduleUserIds.map((id: ObjectID) => {
|
|
1055
|
+
return id.toString();
|
|
1056
|
+
}),
|
|
1057
|
+
);
|
|
1058
|
+
|
|
1059
|
+
return overrides
|
|
1060
|
+
.filter((o: OnCallDutyPolicyUserOverride) => {
|
|
1061
|
+
const overrideUserId: string = o.overrideUserId?.toString() || "";
|
|
1062
|
+
return scheduleUserIdSet.has(overrideUserId);
|
|
1063
|
+
})
|
|
1064
|
+
.map((o: OnCallDutyPolicyUserOverride): UserOverrideRecord => {
|
|
1065
|
+
return {
|
|
1066
|
+
overrideUserId: o.overrideUserId?.toString() || "",
|
|
1067
|
+
routeAlertsToUserId: o.routeAlertsToUserId?.toString() || "",
|
|
1068
|
+
startsAt: o.startsAt!,
|
|
1069
|
+
endsAt: o.endsAt!,
|
|
1070
|
+
onCallDutyPolicyId: o.onCallDutyPolicyId?.toString() || null,
|
|
1071
|
+
};
|
|
1072
|
+
});
|
|
996
1073
|
}
|
|
997
1074
|
|
|
998
1075
|
public async getEventByIndexInSchedule(data: {
|
|
@@ -1006,9 +1083,10 @@ export class Service extends DatabaseService<OnCallDutyPolicySchedule> {
|
|
|
1006
1083
|
} as LogAttributes,
|
|
1007
1084
|
);
|
|
1008
1085
|
|
|
1009
|
-
const layerProps
|
|
1010
|
-
|
|
1011
|
-
|
|
1086
|
+
const { layerProps, projectId, scheduleUserIds } =
|
|
1087
|
+
await this.getScheduleLayerProps({
|
|
1088
|
+
scheduleId: data.scheduleId,
|
|
1089
|
+
});
|
|
1012
1090
|
|
|
1013
1091
|
logger.debug("Layer properties fetched: " + JSON.stringify(layerProps), {
|
|
1014
1092
|
onCallDutyPolicyScheduleId: data.scheduleId.toString(),
|
|
@@ -1042,7 +1120,7 @@ export class Service extends DatabaseService<OnCallDutyPolicySchedule> {
|
|
|
1042
1120
|
onCallDutyPolicyScheduleId: data.scheduleId.toString(),
|
|
1043
1121
|
} as LogAttributes);
|
|
1044
1122
|
|
|
1045
|
-
|
|
1123
|
+
let events: Array<CalendarEvent> = this.layerUtil.getMultiLayerEvents(
|
|
1046
1124
|
{
|
|
1047
1125
|
layers: layerProps,
|
|
1048
1126
|
calendarStartDate: currentStartTime,
|
|
@@ -1053,6 +1131,23 @@ export class Service extends DatabaseService<OnCallDutyPolicySchedule> {
|
|
|
1053
1131
|
},
|
|
1054
1132
|
);
|
|
1055
1133
|
|
|
1134
|
+
if (projectId && events.length > 0) {
|
|
1135
|
+
const overrides: Array<UserOverrideRecord> =
|
|
1136
|
+
await this.fetchOverridesForSchedule({
|
|
1137
|
+
projectId,
|
|
1138
|
+
scheduleUserIds,
|
|
1139
|
+
windowStart: currentStartTime,
|
|
1140
|
+
windowEnd: currentEndTime,
|
|
1141
|
+
});
|
|
1142
|
+
|
|
1143
|
+
if (overrides.length > 0) {
|
|
1144
|
+
events = UserOverrideUtil.applyOverridesToEvents({
|
|
1145
|
+
events,
|
|
1146
|
+
overrides,
|
|
1147
|
+
});
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1056
1151
|
logger.debug("Events fetched: " + JSON.stringify(events), {
|
|
1057
1152
|
onCallDutyPolicyScheduleId: data.scheduleId.toString(),
|
|
1058
1153
|
} as LogAttributes);
|
|
@@ -1064,9 +1159,10 @@ export class Service extends DatabaseService<OnCallDutyPolicySchedule> {
|
|
|
1064
1159
|
public async getCurrentUserIdInSchedule(
|
|
1065
1160
|
scheduleId: ObjectID,
|
|
1066
1161
|
): Promise<ObjectID | null> {
|
|
1067
|
-
const layerProps
|
|
1068
|
-
|
|
1069
|
-
|
|
1162
|
+
const { layerProps, projectId, scheduleUserIds } =
|
|
1163
|
+
await this.getScheduleLayerProps({
|
|
1164
|
+
scheduleId: scheduleId,
|
|
1165
|
+
});
|
|
1070
1166
|
|
|
1071
1167
|
if (layerProps.length === 0) {
|
|
1072
1168
|
return null;
|
|
@@ -1078,7 +1174,7 @@ export class Service extends DatabaseService<OnCallDutyPolicySchedule> {
|
|
|
1078
1174
|
1,
|
|
1079
1175
|
);
|
|
1080
1176
|
|
|
1081
|
-
|
|
1177
|
+
let events: Array<CalendarEvent> = this.layerUtil.getMultiLayerEvents(
|
|
1082
1178
|
{
|
|
1083
1179
|
layers: layerProps,
|
|
1084
1180
|
calendarStartDate: currentStartTime,
|
|
@@ -1089,6 +1185,23 @@ export class Service extends DatabaseService<OnCallDutyPolicySchedule> {
|
|
|
1089
1185
|
},
|
|
1090
1186
|
);
|
|
1091
1187
|
|
|
1188
|
+
if (projectId && events.length > 0) {
|
|
1189
|
+
const overrides: Array<UserOverrideRecord> =
|
|
1190
|
+
await this.fetchOverridesForSchedule({
|
|
1191
|
+
projectId,
|
|
1192
|
+
scheduleUserIds,
|
|
1193
|
+
windowStart: currentStartTime,
|
|
1194
|
+
windowEnd: currentEndTime,
|
|
1195
|
+
});
|
|
1196
|
+
|
|
1197
|
+
if (overrides.length > 0) {
|
|
1198
|
+
events = UserOverrideUtil.applyOverridesToEvents({
|
|
1199
|
+
events,
|
|
1200
|
+
overrides,
|
|
1201
|
+
});
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1092
1205
|
const currentEvent: CalendarEvent | null = events[0] || null;
|
|
1093
1206
|
|
|
1094
1207
|
if (!currentEvent) {
|
|
@@ -121,6 +121,9 @@ export default class MetricMonitorCriteria {
|
|
|
121
121
|
input.monitorStep.data?.metricMonitor?.metricViewConfig?.formulaConfigs ||
|
|
122
122
|
[];
|
|
123
123
|
|
|
124
|
+
const nativeUnitsByMetricName: { [key: string]: string } | undefined =
|
|
125
|
+
metricResponse.nativeUnitsByMetricName;
|
|
126
|
+
|
|
124
127
|
/*
|
|
125
128
|
* Series-less path: one synthetic "all-series" evaluation over the
|
|
126
129
|
* flat metricResult. Preserves the pre-group-by behavior exactly.
|
|
@@ -135,6 +138,7 @@ export default class MetricMonitorCriteria {
|
|
|
135
138
|
seriesFingerprint: undefined,
|
|
136
139
|
seriesLabels: {},
|
|
137
140
|
projectId: metricResponse.projectId,
|
|
141
|
+
nativeUnitsByMetricName,
|
|
138
142
|
});
|
|
139
143
|
return [result];
|
|
140
144
|
}
|
|
@@ -149,6 +153,7 @@ export default class MetricMonitorCriteria {
|
|
|
149
153
|
seriesFingerprint: series.fingerprint,
|
|
150
154
|
seriesLabels: series.labels,
|
|
151
155
|
projectId: metricResponse.projectId,
|
|
156
|
+
nativeUnitsByMetricName,
|
|
152
157
|
});
|
|
153
158
|
}),
|
|
154
159
|
);
|
|
@@ -169,6 +174,7 @@ export default class MetricMonitorCriteria {
|
|
|
169
174
|
seriesFingerprint: string | undefined;
|
|
170
175
|
seriesLabels: JSONObject;
|
|
171
176
|
projectId: { toString(): string } | undefined;
|
|
177
|
+
nativeUnitsByMetricName?: { [key: string]: string } | undefined;
|
|
172
178
|
}): Promise<MetricSeriesEvaluationResult> {
|
|
173
179
|
const rawThreshold: number | null = CompareCriteria.convertToNumber(
|
|
174
180
|
input.criteriaFilter.value,
|
|
@@ -238,6 +244,7 @@ export default class MetricMonitorCriteria {
|
|
|
238
244
|
criteriaFilter: input.criteriaFilter,
|
|
239
245
|
queryConfigs: input.queryConfigs,
|
|
240
246
|
formulaConfigs: input.formulaConfigs,
|
|
247
|
+
nativeUnitsByMetricName: input.nativeUnitsByMetricName,
|
|
241
248
|
});
|
|
242
249
|
|
|
243
250
|
if (input.seriesFingerprint) {
|
|
@@ -576,6 +583,7 @@ export default class MetricMonitorCriteria {
|
|
|
576
583
|
criteriaFilter: CriteriaFilter;
|
|
577
584
|
queryConfigs: Array<MetricQueryConfigData>;
|
|
578
585
|
formulaConfigs: Array<MetricFormulaConfigData>;
|
|
586
|
+
nativeUnitsByMetricName?: { [key: string]: string } | undefined;
|
|
579
587
|
}): MetricCriteriaContext {
|
|
580
588
|
const q: MetricQueryConfigData | null = input.matchedQuery;
|
|
581
589
|
const f: MetricFormulaConfigData | null = input.matchedFormula;
|
|
@@ -587,9 +595,24 @@ export default class MetricMonitorCriteria {
|
|
|
587
595
|
f?.metricAliasData?.title ||
|
|
588
596
|
"Metric";
|
|
589
597
|
|
|
598
|
+
/*
|
|
599
|
+
* Prefer the legendUnit the user picked, but fall back to the
|
|
600
|
+
* metric's native unit (loaded from MetricType by the worker). The
|
|
601
|
+
* fallback matters for ratio metrics like system.filesystem.utilization,
|
|
602
|
+
* whose native unit is OTel's dimensionless "1" — without it, a "%"
|
|
603
|
+
* threshold can't be compared against the raw [0, 1] samples.
|
|
604
|
+
*/
|
|
605
|
+
const rawMetricName: string | undefined =
|
|
606
|
+
(q?.metricQueryData?.filterData?.metricName as string | undefined) ||
|
|
607
|
+
undefined;
|
|
608
|
+
const nativeUnitFromMap: string | undefined = rawMetricName
|
|
609
|
+
? input.nativeUnitsByMetricName?.[rawMetricName.toLowerCase()]
|
|
610
|
+
: undefined;
|
|
611
|
+
|
|
590
612
|
const unit: string | null =
|
|
591
613
|
(q?.metricAliasData?.legendUnit as string | undefined) ||
|
|
592
614
|
(f?.metricAliasData?.legendUnit as string | undefined) ||
|
|
615
|
+
nativeUnitFromMap ||
|
|
593
616
|
null;
|
|
594
617
|
|
|
595
618
|
const aggregationType: MetricsAggregationType | null =
|
|
@@ -23,6 +23,10 @@ import ExceptionMonitorResponse from "../../../Types/Monitor/ExceptionMonitor/Ex
|
|
|
23
23
|
import MonitorCriteriaMessageFormatter from "./MonitorCriteriaMessageFormatter";
|
|
24
24
|
import MonitorCriteriaDataExtractor from "./MonitorCriteriaDataExtractor";
|
|
25
25
|
import MonitorCriteriaExpectationBuilder from "./MonitorCriteriaExpectationBuilder";
|
|
26
|
+
import MetricMonitorResponse from "../../../Types/Monitor/MetricMonitor/MetricMonitorResponse";
|
|
27
|
+
import MetricQueryConfigData from "../../../Types/Metrics/MetricQueryConfigData";
|
|
28
|
+
import MetricFormulaConfigData from "../../../Types/Metrics/MetricFormulaConfigData";
|
|
29
|
+
import MetricUnitUtil from "../../../Utils/MetricUnitUtil";
|
|
26
30
|
|
|
27
31
|
export default class MonitorCriteriaObservationBuilder {
|
|
28
32
|
public static describeFilterObservation(input: {
|
|
@@ -1127,10 +1131,26 @@ export default class MonitorCriteriaObservationBuilder {
|
|
|
1127
1131
|
} returned no data points.`;
|
|
1128
1132
|
}
|
|
1129
1133
|
|
|
1134
|
+
/*
|
|
1135
|
+
* Render the observation in the threshold's unit. The raw samples
|
|
1136
|
+
* arrive in the metric's legendUnit (or its native unit when no
|
|
1137
|
+
* legendUnit was set). If the user picked a different threshold
|
|
1138
|
+
* unit — e.g. "%" against a ratio metric whose native unit is the
|
|
1139
|
+
* dimensionless "1" — convert here so the message reads
|
|
1140
|
+
* "latest 5.85% expected to equal 2" rather than the confusing
|
|
1141
|
+
* "latest 0.0585 expected to equal 2".
|
|
1142
|
+
*/
|
|
1143
|
+
const displayValues: Array<number> =
|
|
1144
|
+
MonitorCriteriaObservationBuilder.convertMetricValuesToThresholdUnit({
|
|
1145
|
+
values: metricValues.values,
|
|
1146
|
+
criteriaFilter: input.criteriaFilter,
|
|
1147
|
+
dataToProcess: input.dataToProcess,
|
|
1148
|
+
monitorStep: input.monitorStep,
|
|
1149
|
+
alias: metricValues.alias,
|
|
1150
|
+
});
|
|
1151
|
+
|
|
1130
1152
|
const summary: string | null =
|
|
1131
|
-
MonitorCriteriaMessageFormatter.summarizeNumericSeries(
|
|
1132
|
-
metricValues.values,
|
|
1133
|
-
);
|
|
1153
|
+
MonitorCriteriaMessageFormatter.summarizeNumericSeries(displayValues);
|
|
1134
1154
|
|
|
1135
1155
|
if (!summary) {
|
|
1136
1156
|
return null;
|
|
@@ -1140,4 +1160,79 @@ export default class MonitorCriteriaObservationBuilder {
|
|
|
1140
1160
|
metricValues.alias ? ` (${metricValues.alias})` : ""
|
|
1141
1161
|
} recorded ${summary}.`;
|
|
1142
1162
|
}
|
|
1163
|
+
|
|
1164
|
+
/*
|
|
1165
|
+
* Best-effort unit conversion for observation text. Mirrors the
|
|
1166
|
+
* sample→thresholdUnit conversion in MetricMonitorCriteria so the
|
|
1167
|
+
* "recorded" message and the breach comparison speak the same unit.
|
|
1168
|
+
* Returns the input array unchanged when units are missing or
|
|
1169
|
+
* incompatible — never throws.
|
|
1170
|
+
*/
|
|
1171
|
+
private static convertMetricValuesToThresholdUnit(input: {
|
|
1172
|
+
values: Array<number>;
|
|
1173
|
+
criteriaFilter: CriteriaFilter;
|
|
1174
|
+
dataToProcess: DataToProcess;
|
|
1175
|
+
monitorStep: MonitorStep;
|
|
1176
|
+
alias: string | null;
|
|
1177
|
+
}): Array<number> {
|
|
1178
|
+
const thresholdUnit: string | undefined =
|
|
1179
|
+
input.criteriaFilter.metricMonitorOptions?.thresholdUnit;
|
|
1180
|
+
if (!thresholdUnit) {
|
|
1181
|
+
return input.values;
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
const metricResponse: MetricMonitorResponse | null =
|
|
1185
|
+
MonitorCriteriaDataExtractor.getMetricMonitorResponse(
|
|
1186
|
+
input.dataToProcess,
|
|
1187
|
+
);
|
|
1188
|
+
if (!metricResponse) {
|
|
1189
|
+
return input.values;
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
const queryConfigs: Array<MetricQueryConfigData> =
|
|
1193
|
+
input.monitorStep.data?.metricMonitor?.metricViewConfig?.queryConfigs ||
|
|
1194
|
+
[];
|
|
1195
|
+
const formulaConfigs: Array<MetricFormulaConfigData> =
|
|
1196
|
+
input.monitorStep.data?.metricMonitor?.metricViewConfig?.formulaConfigs ||
|
|
1197
|
+
[];
|
|
1198
|
+
|
|
1199
|
+
const alias: string | null = input.alias;
|
|
1200
|
+
const matchedQuery: MetricQueryConfigData | undefined = alias
|
|
1201
|
+
? queryConfigs.find((q: MetricQueryConfigData) => {
|
|
1202
|
+
return q.metricAliasData?.metricVariable === alias;
|
|
1203
|
+
})
|
|
1204
|
+
: queryConfigs[0];
|
|
1205
|
+
const matchedFormula: MetricFormulaConfigData | undefined =
|
|
1206
|
+
!matchedQuery && alias
|
|
1207
|
+
? formulaConfigs.find((f: MetricFormulaConfigData) => {
|
|
1208
|
+
return f.metricAliasData?.metricVariable === alias;
|
|
1209
|
+
})
|
|
1210
|
+
: undefined;
|
|
1211
|
+
|
|
1212
|
+
const rawMetricName: string | undefined =
|
|
1213
|
+
(matchedQuery?.metricQueryData?.filterData?.metricName as
|
|
1214
|
+
| string
|
|
1215
|
+
| undefined) || undefined;
|
|
1216
|
+
const nativeUnitFromMap: string | undefined = rawMetricName
|
|
1217
|
+
? metricResponse.nativeUnitsByMetricName?.[rawMetricName.toLowerCase()]
|
|
1218
|
+
: undefined;
|
|
1219
|
+
|
|
1220
|
+
const sampleUnit: string | undefined =
|
|
1221
|
+
(matchedQuery?.metricAliasData?.legendUnit as string | undefined) ||
|
|
1222
|
+
(matchedFormula?.metricAliasData?.legendUnit as string | undefined) ||
|
|
1223
|
+
nativeUnitFromMap ||
|
|
1224
|
+
undefined;
|
|
1225
|
+
|
|
1226
|
+
if (!sampleUnit || sampleUnit === thresholdUnit) {
|
|
1227
|
+
return input.values;
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
return input.values.map((v: number) => {
|
|
1231
|
+
return MetricUnitUtil.convertToMetricUnit({
|
|
1232
|
+
value: v,
|
|
1233
|
+
fromUnit: sampleUnit,
|
|
1234
|
+
metricUnit: thresholdUnit,
|
|
1235
|
+
});
|
|
1236
|
+
});
|
|
1237
|
+
}
|
|
1143
1238
|
}
|