@oneuptime/common 14.0.0 → 14.0.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/Index.ts +4 -0
- package/Models/DatabaseModels/ServiceLevelObjectiveFeed.ts +2 -0
- package/Models/DatabaseModels/ServiceLevelObjectiveLabelRule.ts +522 -0
- package/Models/DatabaseModels/ServiceLevelObjectiveMonitorRule.ts +38 -4
- package/Models/DatabaseModels/ServiceLevelObjectiveOwnerRule.ts +603 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1794100000000-AddSloLabelAndOwnerRules.ts +248 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1794200000000-AddSloMonitorRuleMonitorType.ts +19 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
- package/Server/Services/DatabaseService.ts +10 -0
- package/Server/Services/Index.ts +4 -0
- package/Server/Services/MonitorService.ts +3 -2
- package/Server/Services/ServiceLevelObjectiveLabelRuleEngineService.ts +385 -0
- package/Server/Services/ServiceLevelObjectiveLabelRuleService.ts +51 -0
- package/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.ts +92 -16
- package/Server/Services/ServiceLevelObjectiveMonitorRuleService.ts +36 -1
- package/Server/Services/ServiceLevelObjectiveOwnerRuleEngineService.ts +521 -0
- package/Server/Services/ServiceLevelObjectiveOwnerRuleService.ts +51 -0
- package/Server/Services/ServiceLevelObjectiveService.ts +28 -0
- package/Server/Types/Workflow/Components/Telegram/SendMessageToChat.ts +27 -1
- package/Server/Utils/AI/Toolbox/SloTools.ts +1 -0
- package/Server/Utils/Monitor/AffectedResourceList.ts +31 -73
- package/Server/Utils/Monitor/Criteria/CompareCriteria.ts +1 -1
- package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +1 -1
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +181 -136
- package/Server/Utils/Monitor/RootCauseList.ts +139 -0
- package/Server/Utils/Rules/RuleRun/RuleRunRegistry.ts +28 -0
- package/Server/Utils/Slo/SloRulePatternValidator.ts +42 -0
- package/Tests/App/Dashboard/FeedFilterSortIntegration.test.tsx +1632 -0
- package/Tests/App/Dashboard/FeedOptionsLocales.test.ts +250 -0
- package/Tests/App/Dashboard/FeedOrdering.test.ts +279 -9
- package/Tests/App/Dashboard/InvestigationFeedRefresh.test.tsx +60 -1
- package/Tests/App/Dashboard/InvestigationPanel.test.tsx +594 -3
- package/Tests/App/Dashboard/ProductOrientation.test.tsx +646 -0
- package/Tests/App/Dashboard/ResourceFeed.test.tsx +612 -2
- package/Tests/App/Dashboard/ResourceFeedPageWiring.test.ts +30 -0
- package/Tests/App/Dashboard/ResourceFeedPagesFilterSort.test.tsx +1202 -0
- package/Tests/App/Dashboard/ScheduledMaintenanceFeedRefresh.test.tsx +52 -1
- package/Tests/App/Dashboard/SloFeed.test.tsx +106 -1
- package/Tests/App/Dashboard/SloMonitorRuleCriteria.test.tsx +566 -0
- package/Tests/App/Dashboard/SloNavigationWiring.test.tsx +6 -4
- package/Tests/App/Dashboard/SloNoticeBanner.test.tsx +186 -0
- package/Tests/App/Dashboard/SloOverviewKpiStrip.test.tsx +388 -44
- package/Tests/App/Dashboard/SloOverviewLayout.test.tsx +613 -0
- package/Tests/Models/DatabaseModels/ResourceFeedModels.test.ts +0 -8
- package/Tests/Models/DatabaseModels/SloLabelAndOwnerRuleModels.test.ts +404 -0
- package/Tests/Models/DatabaseModels/SloOverhaulModels.test.ts +17 -1
- package/Tests/Models/DatabaseModels/StaticRuleCriteriaInheritance.test.ts +4 -2
- package/Tests/Server/Infrastructure/Postgres/AddConfigurableRuleCriteriaMigration.test.ts +3 -0
- package/Tests/Server/Infrastructure/Postgres/AddSloLabelAndOwnerRulesMigration.test.ts +407 -0
- package/Tests/Server/Infrastructure/Postgres/AddSloMonitorRuleMonitorTypeMigration.test.ts +171 -0
- package/Tests/Server/Infrastructure/Postgres/SloProductOverhaulMigration.test.ts +88 -4
- package/Tests/Server/Services/DatabaseServiceRuleCriteriaValidation.test.ts +104 -0
- package/Tests/Server/Services/RuleRun/LabelRuleEnginesGroupBRun.test.ts +48 -1
- package/Tests/Server/Services/RuleRun/OwnerRuleEnginesGroupBRun.test.ts +30 -0
- package/Tests/Server/Services/ServiceLevelObjectiveLabelAndOwnerRuleEngines.test.ts +1170 -0
- package/Tests/Server/Services/ServiceLevelObjectiveLabelAndOwnerRuleServices.test.ts +172 -0
- package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.test.ts +323 -0
- package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleService.test.ts +115 -0
- package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleTriggers.test.ts +21 -2
- package/Tests/Server/Services/ServiceLevelObjectiveMonitorTypePostgres.test.ts +480 -0
- package/Tests/Server/Services/ServiceLevelObjectiveService.test.ts +162 -0
- package/Tests/Server/Services/StaticRuleCriteriaRuntimeCoverage.test.ts +45 -6
- package/Tests/Server/Types/Database/FeedEventTypeFilterQuery.test.ts +745 -0
- package/Tests/Server/Types/Markdown.test.ts +2 -1
- package/Tests/Server/Types/Workflow/Components/ApiComponentSsrf.test.ts +49 -0
- package/Tests/Server/Types/Workflow/Components/TelegramSendMessageToChat.test.ts +331 -0
- package/Tests/Server/Utils/AI/SloTools.test.ts +10 -0
- package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteriaAnomalyUnits.test.ts +1 -1
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluator.test.ts +405 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorBreachingSamples.test.ts +1101 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorMetricUnits.test.ts +106 -80
- package/Tests/Server/Utils/Monitor/RootCauseList.test.ts +252 -0
- package/Tests/Server/Utils/Rules/RuleRun/RuleRunPermission.test.ts +154 -0
- package/Tests/Server/Utils/SSRFProtection.test.ts +111 -0
- package/Tests/Server/Utils/Slo/SloRulePatternValidator.test.ts +67 -0
- package/Tests/Server/Utils/VM/VMRunnerSsrf.test.ts +213 -0
- package/Tests/Types/API/Route.test.ts +56 -0
- package/Tests/Types/API/URLSchemeLikePath.test.ts +304 -0
- package/Tests/UI/Components/FeedOptions.test.ts +1404 -0
- package/Tests/UI/Components/FeedOptionsButton.test.tsx +2603 -0
- package/Tests/UI/Components/Icon.test.tsx +26 -0
- package/Tests/UI/Components/RuleCriteria/RuleCriteriaModelTable.test.tsx +3 -3
- package/Tests/UI/Components/useFeedItemsViewKey.test.tsx +1017 -0
- package/Tests/UI/Components/useFeedOptions.test.tsx +1107 -0
- package/Tests/Utils/LabelRuleImportExport.test.ts +102 -0
- package/Tests/Utils/Slo/SloDuration.test.ts +115 -24
- package/Tests/Utils/Slo/SloHealth.test.ts +93 -10
- package/Tests/Utils/Slo/SloMonitorRuleCriteria.test.ts +109 -0
- package/Tests/Utils/Slo/SloOverviewText.test.ts +64 -0
- package/Types/API/Route.ts +19 -2
- package/Types/API/URL.ts +26 -2
- package/Types/Monitor/MetricMonitor/MetricCriteriaContext.ts +6 -5
- package/Types/Monitor/MetricMonitor/MetricMonitorResponse.ts +9 -0
- package/Types/Permission.ts +90 -0
- package/Types/Rules/RuleCriteriaFieldRegistry.ts +11 -0
- package/Types/Rules/RuleRun.ts +4 -0
- package/UI/Components/Feed/FeedOptions.ts +427 -0
- package/UI/Components/Feed/FeedOptionsButton.tsx +692 -0
- package/UI/Components/Feed/useFeedItems.ts +28 -9
- package/UI/Components/Feed/useFeedOptions.ts +225 -0
- package/UI/Components/Icon/Icon.tsx +1 -1
- package/Utils/LabelRuleImportExport.ts +27 -9
- package/Utils/Monitor/MetricValueFormatter.ts +2 -2
- package/Utils/Rules/MonitorTypeRuleCriteria.ts +31 -0
- package/Utils/Slo/SloDuration.ts +22 -6
- package/Utils/Slo/SloHealth.ts +8 -7
- package/Utils/Slo/SloMonitorRuleCriteria.ts +33 -1
- package/Utils/Slo/SloOverviewText.ts +33 -0
- package/Utils/Slo/SloProjection.ts +4 -3
- package/build/dist/Models/DatabaseModels/Index.js +4 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveFeed.js +2 -0
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveFeed.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveLabelRule.js +530 -0
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveLabelRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveMonitorRule.js +39 -4
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveMonitorRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveOwnerRule.js +612 -0
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveOwnerRule.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794100000000-AddSloLabelAndOwnerRules.js +93 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794100000000-AddSloLabelAndOwnerRules.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794200000000-AddSloMonitorRuleMonitorType.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794200000000-AddSloMonitorRuleMonitorType.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/DatabaseService.js +7 -0
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +4 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/MonitorService.js +3 -2
- package/build/dist/Server/Services/MonitorService.js.map +1 -1
- package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleEngineService.js +295 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleService.js +56 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleService.js.map +1 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js +59 -14
- package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleService.js +22 -1
- package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleService.js.map +1 -1
- package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleEngineService.js +400 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleService.js +56 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleService.js.map +1 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveService.js +21 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveService.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/Telegram/SendMessageToChat.js +20 -1
- package/build/dist/Server/Types/Workflow/Components/Telegram/SendMessageToChat.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/SloTools.js +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/SloTools.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/AffectedResourceList.js +22 -51
- package/build/dist/Server/Utils/Monitor/AffectedResourceList.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +139 -109
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/RootCauseList.js +101 -0
- package/build/dist/Server/Utils/Monitor/RootCauseList.js.map +1 -0
- package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js +28 -0
- package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js.map +1 -1
- package/build/dist/Server/Utils/Slo/SloRulePatternValidator.js +28 -0
- package/build/dist/Server/Utils/Slo/SloRulePatternValidator.js.map +1 -0
- package/build/dist/Types/API/Route.js +18 -2
- package/build/dist/Types/API/Route.js.map +1 -1
- package/build/dist/Types/API/URL.js +23 -2
- package/build/dist/Types/API/URL.js.map +1 -1
- package/build/dist/Types/Permission.js +84 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js +11 -0
- package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js.map +1 -1
- package/build/dist/Types/Rules/RuleRun.js +4 -0
- package/build/dist/Types/Rules/RuleRun.js.map +1 -1
- package/build/dist/UI/Components/Feed/FeedOptions.js +235 -0
- package/build/dist/UI/Components/Feed/FeedOptions.js.map +1 -0
- package/build/dist/UI/Components/Feed/FeedOptionsButton.js +346 -0
- package/build/dist/UI/Components/Feed/FeedOptionsButton.js.map +1 -0
- package/build/dist/UI/Components/Feed/useFeedItems.js +17 -7
- package/build/dist/UI/Components/Feed/useFeedItems.js.map +1 -1
- package/build/dist/UI/Components/Feed/useFeedOptions.js +130 -0
- package/build/dist/UI/Components/Feed/useFeedOptions.js.map +1 -0
- package/build/dist/UI/Components/Icon/Icon.js +1 -1
- package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
- package/build/dist/Utils/LabelRuleImportExport.js +22 -6
- package/build/dist/Utils/LabelRuleImportExport.js.map +1 -1
- package/build/dist/Utils/Monitor/MetricValueFormatter.js +2 -2
- package/build/dist/Utils/Rules/MonitorTypeRuleCriteria.js +18 -0
- package/build/dist/Utils/Rules/MonitorTypeRuleCriteria.js.map +1 -0
- package/build/dist/Utils/Slo/SloDuration.js +13 -5
- package/build/dist/Utils/Slo/SloDuration.js.map +1 -1
- package/build/dist/Utils/Slo/SloHealth.js +8 -7
- package/build/dist/Utils/Slo/SloHealth.js.map +1 -1
- package/build/dist/Utils/Slo/SloMonitorRuleCriteria.js +16 -1
- package/build/dist/Utils/Slo/SloMonitorRuleCriteria.js.map +1 -1
- package/build/dist/Utils/Slo/SloOverviewText.js +16 -0
- package/build/dist/Utils/Slo/SloOverviewText.js.map +1 -1
- package/build/dist/Utils/Slo/SloProjection.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,521 @@
|
|
|
1
|
+
import Label from "../../Models/DatabaseModels/Label";
|
|
2
|
+
import ServiceLevelObjective from "../../Models/DatabaseModels/ServiceLevelObjective";
|
|
3
|
+
import { ServiceLevelObjectiveFeedEventType } from "../../Models/DatabaseModels/ServiceLevelObjectiveFeed";
|
|
4
|
+
import ServiceLevelObjectiveOwnerRule from "../../Models/DatabaseModels/ServiceLevelObjectiveOwnerRule";
|
|
5
|
+
import ServiceLevelObjectiveOwnerTeam from "../../Models/DatabaseModels/ServiceLevelObjectiveOwnerTeam";
|
|
6
|
+
import ServiceLevelObjectiveOwnerUser from "../../Models/DatabaseModels/ServiceLevelObjectiveOwnerUser";
|
|
7
|
+
import BaseModel from "../../Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
|
|
8
|
+
import { Purple500 } from "../../Types/BrandColors";
|
|
9
|
+
import BadDataException from "../../Types/Exception/BadDataException";
|
|
10
|
+
import ObjectID from "../../Types/ObjectID";
|
|
11
|
+
import { escapeMarkdownInline } from "../../Utils/Markdown/MarkdownEscape";
|
|
12
|
+
import { MAX_RULES_EVALUATED_PER_PROJECT } from "../../Utils/Rules/RuleEngineLimits";
|
|
13
|
+
import { RuleCriteriaMatcher } from "../../Utils/Rules/RuleCriteriaMatcher";
|
|
14
|
+
import RulePatternMatchUtil from "../../Utils/Rules/RulePatternMatchUtil";
|
|
15
|
+
import Select from "../Types/Database/Select";
|
|
16
|
+
import logger, { LogAttributes } from "../Utils/Logger";
|
|
17
|
+
import OwnerRuleAssignment, {
|
|
18
|
+
OwnersToAssign,
|
|
19
|
+
} from "../Utils/Rules/OwnerRuleAssignment";
|
|
20
|
+
import logIfRuleReadWasTruncated from "../Utils/Rules/RuleEngineRuleRead";
|
|
21
|
+
import {
|
|
22
|
+
ApplyRulesToExistingResourceData,
|
|
23
|
+
RuleApplicationResult,
|
|
24
|
+
RuleApplicationResultUtil,
|
|
25
|
+
RuleRunEngine,
|
|
26
|
+
} from "../Utils/Rules/RuleRun/RuleApplication";
|
|
27
|
+
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
|
|
28
|
+
import DatabaseService from "./DatabaseService";
|
|
29
|
+
import ServiceLevelObjectiveFeedService from "./ServiceLevelObjectiveFeedService";
|
|
30
|
+
import ServiceLevelObjectiveOwnerRuleService from "./ServiceLevelObjectiveOwnerRuleService";
|
|
31
|
+
import ServiceLevelObjectiveOwnerTeamService from "./ServiceLevelObjectiveOwnerTeamService";
|
|
32
|
+
import ServiceLevelObjectiveOwnerUserService from "./ServiceLevelObjectiveOwnerUserService";
|
|
33
|
+
import ServiceLevelObjectiveService from "./ServiceLevelObjectiveService";
|
|
34
|
+
|
|
35
|
+
// What adding one owner row did.
|
|
36
|
+
enum OwnerWriteOutcome {
|
|
37
|
+
Added = "Added",
|
|
38
|
+
AlreadyOwner = "AlreadyOwner",
|
|
39
|
+
Rejected = "Rejected",
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
class ServiceLevelObjectiveOwnerRuleEngineServiceClass
|
|
43
|
+
implements
|
|
44
|
+
RuleRunEngine<ServiceLevelObjective, ServiceLevelObjectiveOwnerRule>
|
|
45
|
+
{
|
|
46
|
+
public readonly ruleSelect: Select<ServiceLevelObjectiveOwnerRule> = {
|
|
47
|
+
_id: true,
|
|
48
|
+
name: true,
|
|
49
|
+
criteria: true,
|
|
50
|
+
notifyOwners: true,
|
|
51
|
+
serviceLevelObjectiveLabels: { _id: true },
|
|
52
|
+
serviceLevelObjectiveNamePattern: true,
|
|
53
|
+
serviceLevelObjectiveDescriptionPattern: true,
|
|
54
|
+
ownerUsers: { _id: true },
|
|
55
|
+
ownerTeams: { _id: true },
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// Evaluation re-reads the SLO, so a run only has to name it.
|
|
59
|
+
public readonly resourceSelectForRuleRun: Select<ServiceLevelObjective> = {
|
|
60
|
+
_id: true,
|
|
61
|
+
projectId: true,
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Evaluates ServiceLevelObjectiveOwnerRule rows for the given SLO and adds
|
|
66
|
+
* matched owner users / teams through the SLO owner services, so each one
|
|
67
|
+
* gets the usual owner-added feed item and notification. Rules with
|
|
68
|
+
* notifyOwners set notify the added owners; rules with notifyOwners off add
|
|
69
|
+
* silently.
|
|
70
|
+
*/
|
|
71
|
+
@CaptureSpan()
|
|
72
|
+
public async applyRulesToServiceLevelObjective(
|
|
73
|
+
serviceLevelObjective: ServiceLevelObjective,
|
|
74
|
+
): Promise<void> {
|
|
75
|
+
if (!serviceLevelObjective.id || !serviceLevelObjective.projectId) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
const rules: Array<ServiceLevelObjectiveOwnerRule> =
|
|
81
|
+
await ServiceLevelObjectiveOwnerRuleService.findBy({
|
|
82
|
+
query: {
|
|
83
|
+
projectId: serviceLevelObjective.projectId,
|
|
84
|
+
isEnabled: true,
|
|
85
|
+
},
|
|
86
|
+
props: { isRoot: true },
|
|
87
|
+
select: this.ruleSelect,
|
|
88
|
+
limit: MAX_RULES_EVALUATED_PER_PROJECT,
|
|
89
|
+
skip: 0,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
logIfRuleReadWasTruncated({
|
|
93
|
+
ruleKind: "ServiceLevelObjectiveOwnerRule",
|
|
94
|
+
projectId: serviceLevelObjective.projectId,
|
|
95
|
+
rulesRead: rules.length,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
if (rules.length === 0) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
await this.applyRules({
|
|
103
|
+
serviceLevelObjective: serviceLevelObjective,
|
|
104
|
+
rules: rules,
|
|
105
|
+
allowOwnerNotification: true,
|
|
106
|
+
});
|
|
107
|
+
} catch (error) {
|
|
108
|
+
logger.error(`Error applying SLO owner rules: ${error}`, {
|
|
109
|
+
projectId: serviceLevelObjective.projectId?.toString(),
|
|
110
|
+
serviceLevelObjectiveId: serviceLevelObjective.id?.toString(),
|
|
111
|
+
} as LogAttributes);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/*
|
|
116
|
+
* "Run now": the same evaluation, for an SLO that already exists and only
|
|
117
|
+
* the rules being run.
|
|
118
|
+
*/
|
|
119
|
+
@CaptureSpan()
|
|
120
|
+
public async applyRulesToExistingResource(
|
|
121
|
+
data: ApplyRulesToExistingResourceData<
|
|
122
|
+
ServiceLevelObjective,
|
|
123
|
+
ServiceLevelObjectiveOwnerRule
|
|
124
|
+
>,
|
|
125
|
+
): Promise<RuleApplicationResult> {
|
|
126
|
+
try {
|
|
127
|
+
return await this.applyRules({
|
|
128
|
+
serviceLevelObjective: data.resource,
|
|
129
|
+
rules: data.rules,
|
|
130
|
+
allowOwnerNotification: data.allowOwnerNotification,
|
|
131
|
+
});
|
|
132
|
+
} catch (error) {
|
|
133
|
+
logger.error(`Error running SLO owner rules: ${error}`, {
|
|
134
|
+
projectId: data.resource.projectId?.toString(),
|
|
135
|
+
serviceLevelObjectiveId: data.resource.id?.toString(),
|
|
136
|
+
} as LogAttributes);
|
|
137
|
+
|
|
138
|
+
return RuleApplicationResultUtil.failed();
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private async applyRules(data: {
|
|
143
|
+
serviceLevelObjective: ServiceLevelObjective;
|
|
144
|
+
rules: Array<ServiceLevelObjectiveOwnerRule>;
|
|
145
|
+
allowOwnerNotification: boolean;
|
|
146
|
+
}): Promise<RuleApplicationResult> {
|
|
147
|
+
const { serviceLevelObjective, rules } = data;
|
|
148
|
+
|
|
149
|
+
if (
|
|
150
|
+
!serviceLevelObjective.id ||
|
|
151
|
+
!serviceLevelObjective.projectId ||
|
|
152
|
+
rules.length === 0
|
|
153
|
+
) {
|
|
154
|
+
return RuleApplicationResultUtil.noMatch();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const sloWithDetails: ServiceLevelObjective | null =
|
|
158
|
+
await ServiceLevelObjectiveService.findOneById({
|
|
159
|
+
id: serviceLevelObjective.id,
|
|
160
|
+
select: {
|
|
161
|
+
name: true,
|
|
162
|
+
description: true,
|
|
163
|
+
labels: { _id: true },
|
|
164
|
+
},
|
|
165
|
+
props: { isRoot: true },
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
if (!sloWithDetails) {
|
|
169
|
+
return RuleApplicationResultUtil.noMatch();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const usersByNotify: Map<boolean, Set<string>> = new Map([
|
|
173
|
+
[true, new Set()],
|
|
174
|
+
[false, new Set()],
|
|
175
|
+
]);
|
|
176
|
+
const teamsByNotify: Map<boolean, Set<string>> = new Map([
|
|
177
|
+
[true, new Set()],
|
|
178
|
+
[false, new Set()],
|
|
179
|
+
]);
|
|
180
|
+
|
|
181
|
+
const matchedRules: Array<ServiceLevelObjectiveOwnerRule> = [];
|
|
182
|
+
const allUserIds: Set<string> = new Set();
|
|
183
|
+
const allTeamIds: Set<string> = new Set();
|
|
184
|
+
let anyRuleMatched: boolean = false;
|
|
185
|
+
|
|
186
|
+
for (const rule of rules) {
|
|
187
|
+
if (!this.doesServiceLevelObjectiveMatchRule(sloWithDetails, rule)) {
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
anyRuleMatched = true;
|
|
191
|
+
let ruleAddedAny: boolean = false;
|
|
192
|
+
const notify: boolean =
|
|
193
|
+
rule.notifyOwners !== false && data.allowOwnerNotification;
|
|
194
|
+
for (const user of rule.ownerUsers || []) {
|
|
195
|
+
if (user.id) {
|
|
196
|
+
usersByNotify.get(notify)!.add(user.id.toString());
|
|
197
|
+
allUserIds.add(user.id.toString());
|
|
198
|
+
ruleAddedAny = true;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
for (const team of rule.ownerTeams || []) {
|
|
202
|
+
if (team.id) {
|
|
203
|
+
teamsByNotify.get(notify)!.add(team.id.toString());
|
|
204
|
+
allTeamIds.add(team.id.toString());
|
|
205
|
+
ruleAddedAny = true;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (ruleAddedAny) {
|
|
209
|
+
matchedRules.push(rule);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (!anyRuleMatched) {
|
|
214
|
+
return RuleApplicationResultUtil.noMatch();
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// The rules that matched name no owners, so there is nothing to add.
|
|
218
|
+
if (matchedRules.length === 0) {
|
|
219
|
+
return RuleApplicationResultUtil.alreadyApplied();
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Owners already on the SLO are skipped rather than duplicated.
|
|
223
|
+
const notYetAssigned: OwnersToAssign =
|
|
224
|
+
await OwnerRuleAssignment.getOwnersNotYetAssigned({
|
|
225
|
+
ownerUserService: ServiceLevelObjectiveOwnerUserService,
|
|
226
|
+
ownerTeamService: ServiceLevelObjectiveOwnerTeamService,
|
|
227
|
+
resourceIdColumn: "serviceLevelObjectiveId",
|
|
228
|
+
resourceId: serviceLevelObjective.id,
|
|
229
|
+
userIds: Array.from(allUserIds),
|
|
230
|
+
teamIds: Array.from(allTeamIds),
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
const userIdsToAdd: Set<string> = new Set(
|
|
234
|
+
notYetAssigned.userIds.map((id: ObjectID): string => {
|
|
235
|
+
return id.toString();
|
|
236
|
+
}),
|
|
237
|
+
);
|
|
238
|
+
const teamIdsToAdd: Set<string> = new Set(
|
|
239
|
+
notYetAssigned.teamIds.map((id: ObjectID): string => {
|
|
240
|
+
return id.toString();
|
|
241
|
+
}),
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
let ownersAdded: number = 0;
|
|
245
|
+
let ownersRejected: number = 0;
|
|
246
|
+
|
|
247
|
+
const count: (outcome: OwnerWriteOutcome) => void = (
|
|
248
|
+
outcome: OwnerWriteOutcome,
|
|
249
|
+
): void => {
|
|
250
|
+
if (outcome === OwnerWriteOutcome.Added) {
|
|
251
|
+
ownersAdded++;
|
|
252
|
+
} else if (outcome === OwnerWriteOutcome.Rejected) {
|
|
253
|
+
ownersRejected++;
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
/*
|
|
258
|
+
* The notifying set goes first, so an owner two matching rules disagree
|
|
259
|
+
* about is added once, and notified.
|
|
260
|
+
*/
|
|
261
|
+
for (const notify of [true, false]) {
|
|
262
|
+
const userIds: Array<string> = Array.from(
|
|
263
|
+
usersByNotify.get(notify)!,
|
|
264
|
+
).filter((id: string): boolean => {
|
|
265
|
+
return userIdsToAdd.delete(id);
|
|
266
|
+
});
|
|
267
|
+
const teamIds: Array<string> = Array.from(
|
|
268
|
+
teamsByNotify.get(notify)!,
|
|
269
|
+
).filter((id: string): boolean => {
|
|
270
|
+
return teamIdsToAdd.delete(id);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
for (const userId of userIds) {
|
|
274
|
+
const owner: ServiceLevelObjectiveOwnerUser =
|
|
275
|
+
new ServiceLevelObjectiveOwnerUser();
|
|
276
|
+
owner.serviceLevelObjectiveId = serviceLevelObjective.id;
|
|
277
|
+
owner.projectId = serviceLevelObjective.projectId;
|
|
278
|
+
owner.userId = new ObjectID(userId);
|
|
279
|
+
owner.isOwnerNotified = !notify;
|
|
280
|
+
count(
|
|
281
|
+
await this.addOwner({
|
|
282
|
+
ownerService: ServiceLevelObjectiveOwnerUserService,
|
|
283
|
+
owner: owner,
|
|
284
|
+
serviceLevelObjective: serviceLevelObjective,
|
|
285
|
+
}),
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
for (const teamId of teamIds) {
|
|
290
|
+
const owner: ServiceLevelObjectiveOwnerTeam =
|
|
291
|
+
new ServiceLevelObjectiveOwnerTeam();
|
|
292
|
+
owner.serviceLevelObjectiveId = serviceLevelObjective.id;
|
|
293
|
+
owner.projectId = serviceLevelObjective.projectId;
|
|
294
|
+
owner.teamId = new ObjectID(teamId);
|
|
295
|
+
owner.isOwnerNotified = !notify;
|
|
296
|
+
count(
|
|
297
|
+
await this.addOwner({
|
|
298
|
+
ownerService: ServiceLevelObjectiveOwnerTeamService,
|
|
299
|
+
owner: owner,
|
|
300
|
+
serviceLevelObjective: serviceLevelObjective,
|
|
301
|
+
}),
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (ownersAdded === 0) {
|
|
307
|
+
/*
|
|
308
|
+
* Every owner the rules name was refused - a user who has since left the
|
|
309
|
+
* project, say. Nothing was added, and "already applied" would claim the
|
|
310
|
+
* SLO has owners it does not, so report the SLO as one the run could not
|
|
311
|
+
* update.
|
|
312
|
+
*/
|
|
313
|
+
return ownersRejected > 0
|
|
314
|
+
? RuleApplicationResultUtil.failed()
|
|
315
|
+
: RuleApplicationResultUtil.alreadyApplied();
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
logger.debug(
|
|
319
|
+
`ServiceLevelObjectiveOwnerRuleEngine added owners to SLO ${serviceLevelObjective.id}`,
|
|
320
|
+
{
|
|
321
|
+
projectId: serviceLevelObjective.projectId.toString(),
|
|
322
|
+
} as LogAttributes,
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
await this.postOwnerRuleFeedItem({
|
|
326
|
+
serviceLevelObjectiveId: serviceLevelObjective.id,
|
|
327
|
+
projectId: serviceLevelObjective.projectId,
|
|
328
|
+
sloName: sloWithDetails.name,
|
|
329
|
+
matchedRules: matchedRules,
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
return RuleApplicationResultUtil.updated(ownersAdded);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/*
|
|
336
|
+
* SLO owner services refuse a user who is not a member of the project, or a
|
|
337
|
+
* team from another one (see SloOwnerReferenceValidator) - which a rule can
|
|
338
|
+
* still name after the user left, or when it was written through the API.
|
|
339
|
+
* That owner is skipped so the rule's other owners are still added; any
|
|
340
|
+
* other failure is thrown as before.
|
|
341
|
+
*/
|
|
342
|
+
private async addOwner<TOwner extends BaseModel>(data: {
|
|
343
|
+
ownerService: DatabaseService<TOwner>;
|
|
344
|
+
owner: TOwner;
|
|
345
|
+
serviceLevelObjective: ServiceLevelObjective;
|
|
346
|
+
}): Promise<OwnerWriteOutcome> {
|
|
347
|
+
try {
|
|
348
|
+
const added: boolean = await OwnerRuleAssignment.createOwner({
|
|
349
|
+
ownerService: data.ownerService,
|
|
350
|
+
owner: data.owner,
|
|
351
|
+
props: { isRoot: true },
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
return added ? OwnerWriteOutcome.Added : OwnerWriteOutcome.AlreadyOwner;
|
|
355
|
+
} catch (error) {
|
|
356
|
+
if (!(error instanceof BadDataException)) {
|
|
357
|
+
throw error;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
logger.warn(
|
|
361
|
+
`SLO owner rule could not add an owner to SLO ${data.serviceLevelObjective.id?.toString()}: ${error.message}`,
|
|
362
|
+
{
|
|
363
|
+
projectId: data.serviceLevelObjective.projectId?.toString(),
|
|
364
|
+
} as LogAttributes,
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
return OwnerWriteOutcome.Rejected;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/*
|
|
372
|
+
* The individual OwnerUserAdded / OwnerTeamAdded items say who was added;
|
|
373
|
+
* this one says which rule is responsible, which is what somebody asking
|
|
374
|
+
* "why am I on the hook for this SLO?" actually needs. The owners are
|
|
375
|
+
* already added by now, so a feed failure is logged, never thrown.
|
|
376
|
+
*/
|
|
377
|
+
private async postOwnerRuleFeedItem(data: {
|
|
378
|
+
serviceLevelObjectiveId: ObjectID;
|
|
379
|
+
projectId: ObjectID;
|
|
380
|
+
sloName: string | undefined;
|
|
381
|
+
matchedRules: Array<ServiceLevelObjectiveOwnerRule>;
|
|
382
|
+
}): Promise<void> {
|
|
383
|
+
try {
|
|
384
|
+
const sloLink: string =
|
|
385
|
+
await ServiceLevelObjectiveService.getSloMarkdownLink({
|
|
386
|
+
projectId: data.projectId,
|
|
387
|
+
sloId: data.serviceLevelObjectiveId,
|
|
388
|
+
sloName: data.sloName,
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
await ServiceLevelObjectiveFeedService.createServiceLevelObjectiveFeedItem(
|
|
392
|
+
{
|
|
393
|
+
serviceLevelObjectiveId: data.serviceLevelObjectiveId,
|
|
394
|
+
projectId: data.projectId,
|
|
395
|
+
serviceLevelObjectiveFeedEventType:
|
|
396
|
+
ServiceLevelObjectiveFeedEventType.OwnerRuleExecuted,
|
|
397
|
+
displayColor: Purple500,
|
|
398
|
+
feedInfoInMarkdown: `👥 Owners were added to ${sloLink} by ${data.matchedRules.length} owner ${data.matchedRules.length === 1 ? "rule" : "rules"}.`,
|
|
399
|
+
moreInformationInMarkdown: `**Owner rules that matched**: ${data.matchedRules
|
|
400
|
+
.map((rule: ServiceLevelObjectiveOwnerRule): string => {
|
|
401
|
+
// Rule names are user-controlled and the feed renders markdown.
|
|
402
|
+
return `**${escapeMarkdownInline(
|
|
403
|
+
rule.name || rule.id?.toString() || "Unnamed rule",
|
|
404
|
+
)}**`;
|
|
405
|
+
})
|
|
406
|
+
.join(", ")}`,
|
|
407
|
+
},
|
|
408
|
+
);
|
|
409
|
+
} catch (error) {
|
|
410
|
+
logger.error(
|
|
411
|
+
`Error writing the owner rule feed item for SLO ${data.serviceLevelObjectiveId.toString()}: ${error}`,
|
|
412
|
+
{ projectId: data.projectId.toString() } as LogAttributes,
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
private doesServiceLevelObjectiveMatchRule(
|
|
418
|
+
serviceLevelObjective: ServiceLevelObjective,
|
|
419
|
+
rule: ServiceLevelObjectiveOwnerRule,
|
|
420
|
+
): boolean {
|
|
421
|
+
return RuleCriteriaMatcher.matchesWithLegacySync({
|
|
422
|
+
rule,
|
|
423
|
+
legacyFields: [
|
|
424
|
+
"serviceLevelObjectiveLabels",
|
|
425
|
+
"serviceLevelObjectiveNamePattern",
|
|
426
|
+
"serviceLevelObjectiveDescriptionPattern",
|
|
427
|
+
],
|
|
428
|
+
emptyResult: true,
|
|
429
|
+
matchesLegacyRule: (
|
|
430
|
+
legacyRule: ServiceLevelObjectiveOwnerRule,
|
|
431
|
+
): boolean => {
|
|
432
|
+
return this.doesServiceLevelObjectiveMatchLegacyRule(
|
|
433
|
+
serviceLevelObjective,
|
|
434
|
+
legacyRule,
|
|
435
|
+
);
|
|
436
|
+
},
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
private doesServiceLevelObjectiveMatchLegacyRule(
|
|
441
|
+
serviceLevelObjective: ServiceLevelObjective,
|
|
442
|
+
rule: ServiceLevelObjectiveOwnerRule,
|
|
443
|
+
): boolean {
|
|
444
|
+
if (
|
|
445
|
+
rule.serviceLevelObjectiveLabels &&
|
|
446
|
+
rule.serviceLevelObjectiveLabels.length > 0
|
|
447
|
+
) {
|
|
448
|
+
if (
|
|
449
|
+
!serviceLevelObjective.labels ||
|
|
450
|
+
serviceLevelObjective.labels.length === 0
|
|
451
|
+
) {
|
|
452
|
+
return false;
|
|
453
|
+
}
|
|
454
|
+
const ruleLabelIds: Array<string> = rule.serviceLevelObjectiveLabels.map(
|
|
455
|
+
(l: Label) => {
|
|
456
|
+
return l.id?.toString() || "";
|
|
457
|
+
},
|
|
458
|
+
);
|
|
459
|
+
const labelIds: Array<string> = serviceLevelObjective.labels.map(
|
|
460
|
+
(l: Label) => {
|
|
461
|
+
return l.id?.toString() || "";
|
|
462
|
+
},
|
|
463
|
+
);
|
|
464
|
+
if (
|
|
465
|
+
!ruleLabelIds.some((id: string) => {
|
|
466
|
+
return labelIds.includes(id);
|
|
467
|
+
})
|
|
468
|
+
) {
|
|
469
|
+
return false;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
if (
|
|
474
|
+
rule.serviceLevelObjectiveNamePattern &&
|
|
475
|
+
(!serviceLevelObjective.name ||
|
|
476
|
+
!this.testPattern(
|
|
477
|
+
rule.serviceLevelObjectiveNamePattern,
|
|
478
|
+
serviceLevelObjective.name,
|
|
479
|
+
rule,
|
|
480
|
+
))
|
|
481
|
+
) {
|
|
482
|
+
return false;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
if (
|
|
486
|
+
rule.serviceLevelObjectiveDescriptionPattern &&
|
|
487
|
+
(!serviceLevelObjective.description ||
|
|
488
|
+
!this.testPattern(
|
|
489
|
+
rule.serviceLevelObjectiveDescriptionPattern,
|
|
490
|
+
serviceLevelObjective.description,
|
|
491
|
+
rule,
|
|
492
|
+
))
|
|
493
|
+
) {
|
|
494
|
+
return false;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
return true;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/*
|
|
501
|
+
* Patterns are regexes, with a '*' wildcard fallback - the same two syntaxes
|
|
502
|
+
* the SLO monitor rules take. SloRulePatternValidator rejects anything else
|
|
503
|
+
* on save, so this only warns about a rule written before that check.
|
|
504
|
+
*/
|
|
505
|
+
private testPattern(
|
|
506
|
+
pattern: string,
|
|
507
|
+
value: string,
|
|
508
|
+
rule: ServiceLevelObjectiveOwnerRule,
|
|
509
|
+
): boolean {
|
|
510
|
+
if (!RulePatternMatchUtil.isSupportedPattern(pattern)) {
|
|
511
|
+
logger.warn(
|
|
512
|
+
`Invalid pattern in SLO owner rule ${rule.id}: ${pattern}. It is neither a valid regular expression nor a wildcard pattern, so it will never match.`,
|
|
513
|
+
);
|
|
514
|
+
return false;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
return RulePatternMatchUtil.matches(value, pattern);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
export default new ServiceLevelObjectiveOwnerRuleEngineServiceClass();
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import DatabaseService from "./DatabaseService";
|
|
2
|
+
import Model from "../../Models/DatabaseModels/ServiceLevelObjectiveOwnerRule";
|
|
3
|
+
import CreateBy from "../Types/Database/CreateBy";
|
|
4
|
+
import { OnCreate, OnUpdate } from "../Types/Database/Hooks";
|
|
5
|
+
import UpdateBy from "../Types/Database/UpdateBy";
|
|
6
|
+
import SloRulePatternValidator from "../Utils/Slo/SloRulePatternValidator";
|
|
7
|
+
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
|
|
8
|
+
|
|
9
|
+
/*
|
|
10
|
+
* No hard-delete retention: a rule is configuration, not an event log, so it
|
|
11
|
+
* stays until somebody deletes it.
|
|
12
|
+
*/
|
|
13
|
+
export class Service extends DatabaseService<Model> {
|
|
14
|
+
public constructor() {
|
|
15
|
+
super(Model);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/*
|
|
19
|
+
* A pattern that is neither a valid regex nor a wildcard can only ever match
|
|
20
|
+
* nothing, and the rule engine has no way to say so - it just quietly stops
|
|
21
|
+
* assigning owners. Reject it while the user is still looking at the form.
|
|
22
|
+
*/
|
|
23
|
+
@CaptureSpan()
|
|
24
|
+
protected override async onBeforeCreate(
|
|
25
|
+
createBy: CreateBy<Model>,
|
|
26
|
+
): Promise<OnCreate<Model>> {
|
|
27
|
+
SloRulePatternValidator.validate({
|
|
28
|
+
namePattern: createBy.data.serviceLevelObjectiveNamePattern,
|
|
29
|
+
descriptionPattern: createBy.data.serviceLevelObjectiveDescriptionPattern,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
return { createBy: createBy, carryForward: null };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@CaptureSpan()
|
|
36
|
+
protected override async onBeforeUpdate(
|
|
37
|
+
updateBy: UpdateBy<Model>,
|
|
38
|
+
): Promise<OnUpdate<Model>> {
|
|
39
|
+
SloRulePatternValidator.validate({
|
|
40
|
+
namePattern: updateBy.data.serviceLevelObjectiveNamePattern as
|
|
41
|
+
| string
|
|
42
|
+
| undefined,
|
|
43
|
+
descriptionPattern: updateBy.data
|
|
44
|
+
.serviceLevelObjectiveDescriptionPattern as string | undefined,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
return { updateBy: updateBy, carryForward: null };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export default new Service();
|
|
@@ -27,6 +27,8 @@ import DatabaseService from "./DatabaseService";
|
|
|
27
27
|
import MonitorStatusService from "./MonitorStatusService";
|
|
28
28
|
import ProjectService from "./ProjectService";
|
|
29
29
|
import ServiceLevelObjectiveBurnRateRuleService from "./ServiceLevelObjectiveBurnRateRuleService";
|
|
30
|
+
import ServiceLevelObjectiveLabelRuleEngineService from "./ServiceLevelObjectiveLabelRuleEngineService";
|
|
31
|
+
import ServiceLevelObjectiveOwnerRuleEngineService from "./ServiceLevelObjectiveOwnerRuleEngineService";
|
|
30
32
|
import ServiceLevelObjectiveMonitorRuleEngineService from "./ServiceLevelObjectiveMonitorRuleEngineService";
|
|
31
33
|
import ServiceLevelObjectiveMonitorRuleService from "./ServiceLevelObjectiveMonitorRuleService";
|
|
32
34
|
import ServiceLevelObjectiveOwnerTeamService from "./ServiceLevelObjectiveOwnerTeamService";
|
|
@@ -287,6 +289,32 @@ export class Service extends DatabaseService<Model> {
|
|
|
287
289
|
);
|
|
288
290
|
}
|
|
289
291
|
|
|
292
|
+
/*
|
|
293
|
+
* Label rules first, so the labels they attach are persisted - and on
|
|
294
|
+
* createdItem - before owner rules run: an owner rule can then key on a
|
|
295
|
+
* rule-added label. Fire-and-forget, like every other resource's rules: a
|
|
296
|
+
* rule must never slow down or fail the create.
|
|
297
|
+
*/
|
|
298
|
+
if (createdItem.projectId && createdItem.id) {
|
|
299
|
+
Promise.resolve()
|
|
300
|
+
.then(async () => {
|
|
301
|
+
await ServiceLevelObjectiveLabelRuleEngineService.applyRulesToServiceLevelObjective(
|
|
302
|
+
createdItem,
|
|
303
|
+
);
|
|
304
|
+
})
|
|
305
|
+
.then(async () => {
|
|
306
|
+
await ServiceLevelObjectiveOwnerRuleEngineService.applyRulesToServiceLevelObjective(
|
|
307
|
+
createdItem,
|
|
308
|
+
);
|
|
309
|
+
})
|
|
310
|
+
.catch((err: Error) => {
|
|
311
|
+
logger.error(
|
|
312
|
+
`Error applying the label and owner rules for SLO ${createdItem.id?.toString()}: ${err}`,
|
|
313
|
+
{ projectId: createdItem.projectId?.toString() } as LogAttributes,
|
|
314
|
+
);
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
|
|
290
318
|
/*
|
|
291
319
|
* Fire-and-forget, like ServiceService's created item: the SLO exists
|
|
292
320
|
* whether or not its feed item does, and the create request should not
|
|
@@ -13,7 +13,17 @@ import TelegramComponents from "../../../../../Types/Workflow/Components/Telegra
|
|
|
13
13
|
import API from "../../../../../Utils/API";
|
|
14
14
|
import CaptureSpan from "../../../../Utils/Telemetry/CaptureSpan";
|
|
15
15
|
|
|
16
|
+
/*
|
|
17
|
+
* https://core.telegram.org/bots/api#authorizing-your-bot — a numeric bot id,
|
|
18
|
+
* a ":", then the secret, e.g. "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11".
|
|
19
|
+
*/
|
|
20
|
+
const TELEGRAM_BOT_TOKEN_REGEX: RegExp = /^\d+:[A-Za-z0-9_-]+$/;
|
|
21
|
+
|
|
16
22
|
export default class SendMessageToChat extends ComponentCode {
|
|
23
|
+
public static isValidBotToken(botToken: string): boolean {
|
|
24
|
+
return TELEGRAM_BOT_TOKEN_REGEX.test(botToken);
|
|
25
|
+
}
|
|
26
|
+
|
|
17
27
|
public constructor() {
|
|
18
28
|
super();
|
|
19
29
|
|
|
@@ -69,10 +79,26 @@ export default class SendMessageToChat extends ComponentCode {
|
|
|
69
79
|
throw options.onError(new BadDataException("Telegram message not found"));
|
|
70
80
|
}
|
|
71
81
|
|
|
72
|
-
|
|
82
|
+
// A token pasted or stored with a trailing newline is still the token.
|
|
83
|
+
const botToken: string = (args["bot-token"]?.toString() || "").trim();
|
|
73
84
|
const chatId: string = args["chat-id"]?.toString() as string;
|
|
74
85
|
const text: string = args["text"]?.toString() as string;
|
|
75
86
|
|
|
87
|
+
/*
|
|
88
|
+
* The token becomes a path segment of the request URL, so anything other
|
|
89
|
+
* than Telegram's "<bot id>:<secret>" shape is refused before it gets
|
|
90
|
+
* there. The message deliberately does not quote the value: the step's
|
|
91
|
+
* error is written to the run log, and the token is a credential. Before
|
|
92
|
+
* this check, a URL parse failure put the whole token into the log.
|
|
93
|
+
*/
|
|
94
|
+
if (!SendMessageToChat.isValidBotToken(botToken)) {
|
|
95
|
+
throw options.onError(
|
|
96
|
+
new BadDataException(
|
|
97
|
+
"Telegram Bot Token is not in a valid format. It should look like 123456789:ABCdefGhIJKlmnoPQRstuVWxyz, exactly as BotFather issued it.",
|
|
98
|
+
),
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
76
102
|
const telegramApiUrl: URL = URL.fromString(
|
|
77
103
|
`https://api.telegram.org/bot${botToken}/sendMessage`,
|
|
78
104
|
);
|