@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
|
@@ -25,6 +25,14 @@ export interface FeedPage<TFeedModel> {
|
|
|
25
25
|
|
|
26
26
|
export interface UseFeedItemsProps<TFeedModel> {
|
|
27
27
|
resourceKey: string;
|
|
28
|
+
/*
|
|
29
|
+
* What the reader has chosen to see of this resource's feed - its sort
|
|
30
|
+
* order and event type filter (see useFeedOptions). A change re-reads the
|
|
31
|
+
* feed from its first window, exactly like moving to another resource:
|
|
32
|
+
* rows already loaded were read for the old view, so keeping them, or the
|
|
33
|
+
* size of the window they filled, would mix two different feeds.
|
|
34
|
+
*/
|
|
35
|
+
viewKey?: string | undefined;
|
|
28
36
|
refreshToken?: number | undefined;
|
|
29
37
|
getItems: (limit: number) => Promise<FeedPage<TFeedModel>>;
|
|
30
38
|
mapItems: (items: Array<TFeedModel>) => Array<FeedItemProps>;
|
|
@@ -50,7 +58,8 @@ interface FetchOptions {
|
|
|
50
58
|
|
|
51
59
|
/*
|
|
52
60
|
* Every dashboard activity feed follows the same paging contract: read the
|
|
53
|
-
*
|
|
61
|
+
* first N rows in the reader's chosen order (newest first unless they asked
|
|
62
|
+
* for oldest first) and replace that window when the reader asks for more.
|
|
54
63
|
* Re-reading from zero matters for a live feed. Offset-appending can duplicate
|
|
55
64
|
* or skip a row when a new event arrives between page requests.
|
|
56
65
|
*/
|
|
@@ -71,10 +80,20 @@ const useFeedItems: <TFeedModel>(
|
|
|
71
80
|
null,
|
|
72
81
|
);
|
|
73
82
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
83
|
+
/*
|
|
84
|
+
* The resource and the view together name the feed being shown. Everything
|
|
85
|
+
* below that guards against a stale response or resets the window keys on
|
|
86
|
+
* this, so a response for the previous view is dropped the same way as a
|
|
87
|
+
* response for the previous resource.
|
|
88
|
+
*/
|
|
89
|
+
const feedKey: string =
|
|
90
|
+
props.viewKey === undefined
|
|
91
|
+
? props.resourceKey
|
|
92
|
+
: `${props.resourceKey}\u0000${props.viewKey}`;
|
|
93
|
+
|
|
94
|
+
const activeResourceKeyRef: MutableRefObject<string> =
|
|
95
|
+
useRef<string>(feedKey);
|
|
96
|
+
activeResourceKeyRef.current = feedKey;
|
|
78
97
|
const previousResourceKeyRef: MutableRefObject<string | null> = useRef<
|
|
79
98
|
string | null
|
|
80
99
|
>(null);
|
|
@@ -184,8 +203,8 @@ const useFeedItems: <TFeedModel>(
|
|
|
184
203
|
|
|
185
204
|
useEffect(() => {
|
|
186
205
|
const hasResourceChanged: boolean =
|
|
187
|
-
previousResourceKeyRef.current !==
|
|
188
|
-
previousResourceKeyRef.current =
|
|
206
|
+
previousResourceKeyRef.current !== feedKey;
|
|
207
|
+
previousResourceKeyRef.current = feedKey;
|
|
189
208
|
|
|
190
209
|
if (hasResourceChanged) {
|
|
191
210
|
currentLimitRef.current = DEFAULT_LIMIT;
|
|
@@ -200,7 +219,7 @@ const useFeedItems: <TFeedModel>(
|
|
|
200
219
|
}).catch(() => {
|
|
201
220
|
// fetchItems converts request failures into the appropriate UI state.
|
|
202
221
|
});
|
|
203
|
-
}, [fetchItems,
|
|
222
|
+
}, [fetchItems, feedKey, props.refreshToken]);
|
|
204
223
|
|
|
205
224
|
useEffect(() => {
|
|
206
225
|
return () => {
|
|
@@ -208,7 +227,7 @@ const useFeedItems: <TFeedModel>(
|
|
|
208
227
|
};
|
|
209
228
|
}, []);
|
|
210
229
|
|
|
211
|
-
const isCurrentFeedLoaded: boolean = loadedResourceKey ===
|
|
230
|
+
const isCurrentFeedLoaded: boolean = loadedResourceKey === feedKey;
|
|
212
231
|
|
|
213
232
|
return {
|
|
214
233
|
feedItems,
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MutableRefObject,
|
|
3
|
+
useCallback,
|
|
4
|
+
useEffect,
|
|
5
|
+
useMemo,
|
|
6
|
+
useRef,
|
|
7
|
+
useState,
|
|
8
|
+
} from "react";
|
|
9
|
+
import SortOrder from "../../../Types/BaseDatabase/SortOrder";
|
|
10
|
+
import LocalStorage from "../../Utils/LocalStorage";
|
|
11
|
+
import {
|
|
12
|
+
DEFAULT_FEED_SORT_ORDER,
|
|
13
|
+
FeedEventTypeOption,
|
|
14
|
+
FeedOptions,
|
|
15
|
+
GetFeedEventTypeIconFunction,
|
|
16
|
+
GetFeedEventTypeLabelFunction,
|
|
17
|
+
getFeedEventTypeOptions,
|
|
18
|
+
getFeedOptionsKey,
|
|
19
|
+
isFeedFiltered,
|
|
20
|
+
isFeedSortOrder,
|
|
21
|
+
sanitizeFeedEventTypes,
|
|
22
|
+
} from "./FeedOptions";
|
|
23
|
+
|
|
24
|
+
export interface UseFeedOptionsProps {
|
|
25
|
+
// Every event type the feed model can record, e.g. Object.values(the enum).
|
|
26
|
+
eventTypes: Array<string>;
|
|
27
|
+
getEventTypeLabel?: GetFeedEventTypeLabelFunction | undefined;
|
|
28
|
+
getEventTypeIcon?: GetFeedEventTypeIconFunction | undefined;
|
|
29
|
+
/*
|
|
30
|
+
* Names this kind of feed ("incident", "monitor", ...) so the reader's sort
|
|
31
|
+
* order is remembered across pages and visits. The event type filter is
|
|
32
|
+
* deliberately not remembered: it narrows one investigation, and a filter
|
|
33
|
+
* that silently followed the reader to the next incident would read as a
|
|
34
|
+
* feed with missing events.
|
|
35
|
+
*/
|
|
36
|
+
storageKey?: string | undefined;
|
|
37
|
+
/*
|
|
38
|
+
* The resource whose feed this is (the incident id, the cluster id, ...).
|
|
39
|
+
* The dashboard moves between resources without remounting the feed, and
|
|
40
|
+
* the event type filter belongs to the resource it was chosen on, so a new
|
|
41
|
+
* resource starts unfiltered. The sort order is a preference and stays.
|
|
42
|
+
*/
|
|
43
|
+
resetKey?: string | undefined;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface UseFeedOptionsResult {
|
|
47
|
+
options: FeedOptions;
|
|
48
|
+
// Pass to useFeedItems as viewKey: a change re-reads the feed.
|
|
49
|
+
optionsKey: string;
|
|
50
|
+
eventTypeOptions: Array<FeedEventTypeOption>;
|
|
51
|
+
isFiltered: boolean;
|
|
52
|
+
setOptions: (options: FeedOptions) => void;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const SORT_ORDER_STORAGE_KEY_PREFIX: string = "feed-sort-order:";
|
|
56
|
+
|
|
57
|
+
// One shared empty selection, so an unfiltered render keeps its identity.
|
|
58
|
+
const NO_EVENT_TYPES: Array<string> = [];
|
|
59
|
+
|
|
60
|
+
type GetSortOrderStorageKey = (storageKey: string) => string;
|
|
61
|
+
|
|
62
|
+
export const getSortOrderStorageKey: GetSortOrderStorageKey = (
|
|
63
|
+
storageKey: string,
|
|
64
|
+
): string => {
|
|
65
|
+
return `${SORT_ORDER_STORAGE_KEY_PREFIX}${storageKey}`;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
type ReadStoredSortOrder = (storageKey: string | undefined) => SortOrder;
|
|
69
|
+
|
|
70
|
+
/*
|
|
71
|
+
* Storage can be missing, blocked or hold anything, so every read falls back
|
|
72
|
+
* to the default order.
|
|
73
|
+
*/
|
|
74
|
+
const readStoredSortOrder: ReadStoredSortOrder = (
|
|
75
|
+
storageKey: string | undefined,
|
|
76
|
+
): SortOrder => {
|
|
77
|
+
if (!storageKey) {
|
|
78
|
+
return DEFAULT_FEED_SORT_ORDER;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
const storedValue: unknown = LocalStorage.getItem(
|
|
83
|
+
getSortOrderStorageKey(storageKey),
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
return isFeedSortOrder(storedValue) ? storedValue : DEFAULT_FEED_SORT_ORDER;
|
|
87
|
+
} catch {
|
|
88
|
+
return DEFAULT_FEED_SORT_ORDER;
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
type WriteStoredSortOrder = (
|
|
93
|
+
storageKey: string | undefined,
|
|
94
|
+
sortOrder: SortOrder,
|
|
95
|
+
) => void;
|
|
96
|
+
|
|
97
|
+
const writeStoredSortOrder: WriteStoredSortOrder = (
|
|
98
|
+
storageKey: string | undefined,
|
|
99
|
+
sortOrder: SortOrder,
|
|
100
|
+
): void => {
|
|
101
|
+
if (!storageKey) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
if (sortOrder === DEFAULT_FEED_SORT_ORDER) {
|
|
107
|
+
LocalStorage.removeItem(getSortOrderStorageKey(storageKey));
|
|
108
|
+
} else {
|
|
109
|
+
LocalStorage.setItem(getSortOrderStorageKey(storageKey), sortOrder);
|
|
110
|
+
}
|
|
111
|
+
} catch {
|
|
112
|
+
// A preference that cannot be saved still applies to this page.
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const useFeedOptions: (props: UseFeedOptionsProps) => UseFeedOptionsResult = (
|
|
117
|
+
props: UseFeedOptionsProps,
|
|
118
|
+
): UseFeedOptionsResult => {
|
|
119
|
+
/*
|
|
120
|
+
* Callers pass Object.values(SomeEnum), a new array on every render, so the
|
|
121
|
+
* list is keyed by its contents.
|
|
122
|
+
*/
|
|
123
|
+
const eventTypesKey: string = props.eventTypes.join("\u0000");
|
|
124
|
+
const knownEventTypes: Array<string> = useMemo(() => {
|
|
125
|
+
return props.eventTypes;
|
|
126
|
+
}, [eventTypesKey]);
|
|
127
|
+
|
|
128
|
+
const [sortOrder, setSortOrder] = useState<SortOrder>(() => {
|
|
129
|
+
return readStoredSortOrder(props.storageKey);
|
|
130
|
+
});
|
|
131
|
+
/*
|
|
132
|
+
* The selection remembers which resource it was made on. It is read through
|
|
133
|
+
* that, during render rather than in an effect, so the first render for a
|
|
134
|
+
* new resource is already unfiltered and never asks the API for the old
|
|
135
|
+
* resource's filter. The stale selection is also dropped (React's "adjust
|
|
136
|
+
* state when a prop changes" pattern), so going back to the first resource
|
|
137
|
+
* does not bring its old filter back either.
|
|
138
|
+
*/
|
|
139
|
+
const [selection, setSelection] = useState<{
|
|
140
|
+
resetKey: string | undefined;
|
|
141
|
+
eventTypes: Array<string>;
|
|
142
|
+
}>(() => {
|
|
143
|
+
return {
|
|
144
|
+
resetKey: props.resetKey,
|
|
145
|
+
eventTypes: [],
|
|
146
|
+
};
|
|
147
|
+
});
|
|
148
|
+
const isSelectionForThisResource: boolean =
|
|
149
|
+
selection.resetKey === props.resetKey;
|
|
150
|
+
|
|
151
|
+
if (!isSelectionForThisResource) {
|
|
152
|
+
setSelection({
|
|
153
|
+
resetKey: props.resetKey,
|
|
154
|
+
eventTypes: NO_EVENT_TYPES,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const selectedEventTypes: Array<string> = isSelectionForThisResource
|
|
159
|
+
? selection.eventTypes
|
|
160
|
+
: NO_EVENT_TYPES;
|
|
161
|
+
|
|
162
|
+
const options: FeedOptions = useMemo(() => {
|
|
163
|
+
return {
|
|
164
|
+
sortOrder,
|
|
165
|
+
eventTypes: sanitizeFeedEventTypes({
|
|
166
|
+
selectedEventTypes,
|
|
167
|
+
knownEventTypes,
|
|
168
|
+
}),
|
|
169
|
+
};
|
|
170
|
+
}, [sortOrder, selectedEventTypes, knownEventTypes]);
|
|
171
|
+
|
|
172
|
+
const eventTypeOptions: Array<FeedEventTypeOption> = useMemo(() => {
|
|
173
|
+
return getFeedEventTypeOptions({
|
|
174
|
+
eventTypes: knownEventTypes,
|
|
175
|
+
getEventTypeLabel: props.getEventTypeLabel,
|
|
176
|
+
getEventTypeIcon: props.getEventTypeIcon,
|
|
177
|
+
});
|
|
178
|
+
}, [knownEventTypes, props.getEventTypeLabel, props.getEventTypeIcon]);
|
|
179
|
+
|
|
180
|
+
const setOptions: (nextOptions: FeedOptions) => void = useCallback(
|
|
181
|
+
(nextOptions: FeedOptions): void => {
|
|
182
|
+
const nextSortOrder: SortOrder = isFeedSortOrder(nextOptions.sortOrder)
|
|
183
|
+
? nextOptions.sortOrder
|
|
184
|
+
: DEFAULT_FEED_SORT_ORDER;
|
|
185
|
+
|
|
186
|
+
setSortOrder(nextSortOrder);
|
|
187
|
+
setSelection({
|
|
188
|
+
resetKey: props.resetKey,
|
|
189
|
+
eventTypes: sanitizeFeedEventTypes({
|
|
190
|
+
selectedEventTypes: nextOptions.eventTypes,
|
|
191
|
+
knownEventTypes,
|
|
192
|
+
}),
|
|
193
|
+
});
|
|
194
|
+
},
|
|
195
|
+
[knownEventTypes, props.resetKey],
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
/*
|
|
199
|
+
* The stored order follows the committed state, not each setOptions call:
|
|
200
|
+
* two changes batched into one render would otherwise leave storage holding
|
|
201
|
+
* the first while the page shows the second. The first run is the value
|
|
202
|
+
* just read from storage, so there is nothing to write.
|
|
203
|
+
*/
|
|
204
|
+
const hasCommittedSortOrderRef: MutableRefObject<boolean> =
|
|
205
|
+
useRef<boolean>(false);
|
|
206
|
+
|
|
207
|
+
useEffect(() => {
|
|
208
|
+
if (!hasCommittedSortOrderRef.current) {
|
|
209
|
+
hasCommittedSortOrderRef.current = true;
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
writeStoredSortOrder(props.storageKey, sortOrder);
|
|
214
|
+
}, [sortOrder]);
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
options,
|
|
218
|
+
optionsKey: getFeedOptionsKey(options),
|
|
219
|
+
eventTypeOptions,
|
|
220
|
+
isFiltered: isFeedFiltered(options),
|
|
221
|
+
setOptions,
|
|
222
|
+
};
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
export default useFeedOptions;
|
|
@@ -800,7 +800,7 @@ const Icon: FunctionComponent<ComponentProps> = ({
|
|
|
800
800
|
<path
|
|
801
801
|
strokeLinecap="round"
|
|
802
802
|
strokeLinejoin="round"
|
|
803
|
-
d="M3 4.5h14.25M3 9h9.75M3 13.
|
|
803
|
+
d="M3 4.5h14.25M3 9h9.75M3 13.5h5.25m5.25-.75L17.25 9m0 0L21 12.75M17.25 9v12"
|
|
804
804
|
/>,
|
|
805
805
|
);
|
|
806
806
|
} else if (icon === IconProp.CheckCircle) {
|
|
@@ -25,6 +25,7 @@ import RunbookLabelRule from "../Models/DatabaseModels/RunbookLabelRule";
|
|
|
25
25
|
import ScheduledMaintenanceLabelRule from "../Models/DatabaseModels/ScheduledMaintenanceLabelRule";
|
|
26
26
|
import ServerlessFunctionLabelRule from "../Models/DatabaseModels/ServerlessFunctionLabelRule";
|
|
27
27
|
import ServiceLabelRule from "../Models/DatabaseModels/ServiceLabelRule";
|
|
28
|
+
import ServiceLevelObjectiveLabelRule from "../Models/DatabaseModels/ServiceLevelObjectiveLabelRule";
|
|
28
29
|
import StatusPageLabelRule from "../Models/DatabaseModels/StatusPageLabelRule";
|
|
29
30
|
import WorkflowLabelRule from "../Models/DatabaseModels/WorkflowLabelRule";
|
|
30
31
|
import Select from "../Types/BaseDatabase/Select";
|
|
@@ -74,10 +75,22 @@ export const LABEL_RULE_MODELS: Array<DatabaseBaseModelType> = [
|
|
|
74
75
|
ScheduledMaintenanceLabelRule,
|
|
75
76
|
ServerlessFunctionLabelRule,
|
|
76
77
|
ServiceLabelRule,
|
|
78
|
+
ServiceLevelObjectiveLabelRule,
|
|
77
79
|
StatusPageLabelRule,
|
|
78
80
|
WorkflowLabelRule,
|
|
79
81
|
];
|
|
80
82
|
|
|
83
|
+
/*
|
|
84
|
+
* Rule types whose engines match patterns through RulePatternMatchUtil: a
|
|
85
|
+
* regex, with a '*' wildcard fallback, on the trimmed pattern. Every other
|
|
86
|
+
* label rule compiles its pattern as a plain regex, so a pattern moving
|
|
87
|
+
* between the two groups can change what it matches.
|
|
88
|
+
*/
|
|
89
|
+
const WILDCARD_PATTERN_RULE_TYPES: ReadonlySet<string> = new Set<string>([
|
|
90
|
+
"NetworkDeviceLabelRule",
|
|
91
|
+
"ServiceLevelObjectiveLabelRule",
|
|
92
|
+
]);
|
|
93
|
+
|
|
81
94
|
export interface ParsedLabelRuleImport {
|
|
82
95
|
sourceResourceType: string;
|
|
83
96
|
destinationResourceType: string;
|
|
@@ -759,13 +772,14 @@ export default class LabelRuleImportExport {
|
|
|
759
772
|
);
|
|
760
773
|
}
|
|
761
774
|
if (validatePattern && column.endsWith("Pattern") && value) {
|
|
762
|
-
const
|
|
763
|
-
resourceType
|
|
764
|
-
|
|
765
|
-
|
|
775
|
+
const acceptsWildcards: boolean =
|
|
776
|
+
WILDCARD_PATTERN_RULE_TYPES.has(resourceType);
|
|
777
|
+
const valid: boolean = acceptsWildcards
|
|
778
|
+
? RulePatternMatchUtil.isSupportedPattern(value)
|
|
779
|
+
: RulePatternMatchUtil.isValidRegex(value);
|
|
766
780
|
if (!valid) {
|
|
767
781
|
throw new BadDataException(
|
|
768
|
-
`${title} is not a valid ${
|
|
782
|
+
`${title} is not a valid ${acceptsWildcards ? "regular expression or wildcard pattern" : "regular expression"}.`,
|
|
769
783
|
);
|
|
770
784
|
}
|
|
771
785
|
}
|
|
@@ -776,13 +790,17 @@ export default class LabelRuleImportExport {
|
|
|
776
790
|
source: string,
|
|
777
791
|
destination: string,
|
|
778
792
|
): void {
|
|
793
|
+
// Two rule types that match patterns the same way can trade any pattern.
|
|
779
794
|
if (
|
|
780
|
-
(source ===
|
|
781
|
-
|
|
782
|
-
(pattern.includes("*") || pattern !== pattern.trim())
|
|
795
|
+
WILDCARD_PATTERN_RULE_TYPES.has(source) ===
|
|
796
|
+
WILDCARD_PATTERN_RULE_TYPES.has(destination)
|
|
783
797
|
) {
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
if (pattern.includes("*") || pattern !== pattern.trim()) {
|
|
784
802
|
throw new BadDataException(
|
|
785
|
-
"Network device patterns also use wildcard matching and trim surrounding whitespace. A pattern containing '*' or surrounding whitespace cannot be transferred between
|
|
803
|
+
"Network device and SLO patterns also use wildcard matching and trim surrounding whitespace. A pattern containing '*' or surrounding whitespace cannot be transferred between those rules and another rule type without changing its meaning. Edit this pattern for the destination before importing.",
|
|
786
804
|
);
|
|
787
805
|
}
|
|
788
806
|
}
|
|
@@ -14,9 +14,9 @@ import ValueFormatter from "../ValueFormatter";
|
|
|
14
14
|
* head at 3am to find out whether the pod is anywhere near its limit.
|
|
15
15
|
*
|
|
16
16
|
* This module is the single place that decides how a (value, unit) pair
|
|
17
|
-
* is rendered for a notification, so the breaching-samples
|
|
17
|
+
* is rendered for a notification, so the breaching-samples list, the
|
|
18
18
|
* "Filter Conditions Met" sentence, the observation summary and the
|
|
19
|
-
* per-platform resource
|
|
19
|
+
* per-platform resource lists cannot drift apart inside one email.
|
|
20
20
|
*
|
|
21
21
|
* WHY IT WRAPS ValueFormatter RATHER THAN BEING A CALL TO IT.
|
|
22
22
|
*
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import MonitorType from "../../Types/Monitor/MonitorType";
|
|
2
|
+
import {
|
|
3
|
+
RuleCriteriaFilter,
|
|
4
|
+
RuleCriteriaOperator,
|
|
5
|
+
} from "../../Types/Rules/RuleCriteria";
|
|
6
|
+
|
|
7
|
+
const MONITOR_TYPES: ReadonlySet<string> = new Set(Object.values(MonitorType));
|
|
8
|
+
|
|
9
|
+
export function isMonitorTypeCriteriaValue(
|
|
10
|
+
value: unknown,
|
|
11
|
+
): value is MonitorType {
|
|
12
|
+
return typeof value === "string" && MONITOR_TYPES.has(value);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Monitor types are enum choices, so text and relation operators do not apply. */
|
|
16
|
+
export function getMonitorTypeCriteriaValidationError(
|
|
17
|
+
filter: RuleCriteriaFilter,
|
|
18
|
+
): string | null {
|
|
19
|
+
if (
|
|
20
|
+
filter.operator !== RuleCriteriaOperator.Equals &&
|
|
21
|
+
filter.operator !== RuleCriteriaOperator.NotEquals
|
|
22
|
+
) {
|
|
23
|
+
return "Monitor type criteria only support Equals and Does not equal operators.";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (!isMonitorTypeCriteriaValue(filter.value)) {
|
|
27
|
+
return "Monitor type criteria require a valid monitor type.";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return null;
|
|
31
|
+
}
|
package/Utils/Slo/SloDuration.ts
CHANGED
|
@@ -15,9 +15,6 @@
|
|
|
15
15
|
* DOM.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
/** U+2212 MINUS SIGN — reads as a minus rather than a hyphen at tile sizes. */
|
|
19
|
-
const MINUS_SIGN: string = "−";
|
|
20
|
-
|
|
21
18
|
/**
|
|
22
19
|
* Seconds per unit, largest first. Only the two most significant non-zero
|
|
23
20
|
* units are rendered: "2d 5h" is what an on-call engineer needs, "2d 5h
|
|
@@ -97,6 +94,16 @@ export const formatDurationCompact: FormatDurationCompactFunction = (
|
|
|
97
94
|
return parts.join(" ");
|
|
98
95
|
};
|
|
99
96
|
|
|
97
|
+
type FormatBudgetDurationFunction = (seconds: number) => string;
|
|
98
|
+
|
|
99
|
+
const formatBudgetDuration: FormatBudgetDurationFunction = (
|
|
100
|
+
seconds: number,
|
|
101
|
+
): string => {
|
|
102
|
+
return seconds !== 0 && Math.abs(seconds) < 1
|
|
103
|
+
? "under 1s"
|
|
104
|
+
: formatDurationCompact(seconds);
|
|
105
|
+
};
|
|
106
|
+
|
|
100
107
|
/**
|
|
101
108
|
* `errorBudgetRemainingSeconds` is SIGNED: negative means the budget is
|
|
102
109
|
* overspent, and that overage is the single most useful number during a
|
|
@@ -121,10 +128,10 @@ export const formatErrorBudgetRemaining: FormatErrorBudgetRemainingFunction = (
|
|
|
121
128
|
}
|
|
122
129
|
|
|
123
130
|
if (numericSeconds < 0) {
|
|
124
|
-
return `${
|
|
131
|
+
return `${formatBudgetDuration(numericSeconds)} over budget`;
|
|
125
132
|
}
|
|
126
133
|
|
|
127
|
-
return `${
|
|
134
|
+
return `${formatBudgetDuration(numericSeconds)} left`;
|
|
128
135
|
};
|
|
129
136
|
|
|
130
137
|
/**
|
|
@@ -161,5 +168,14 @@ export const formatErrorBudgetRemainingOfTotal: FormatErrorBudgetRemainingOfTota
|
|
|
161
168
|
return remainingText;
|
|
162
169
|
}
|
|
163
170
|
|
|
164
|
-
|
|
171
|
+
const totalText: string = formatBudgetDuration(totalSeconds);
|
|
172
|
+
|
|
173
|
+
if (
|
|
174
|
+
typeof data.remainingSeconds === "number" &&
|
|
175
|
+
data.remainingSeconds < 0
|
|
176
|
+
) {
|
|
177
|
+
return `${remainingText} · ${totalText} allowed`;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return `${remainingText} of ${totalText}`;
|
|
165
181
|
};
|
package/Utils/Slo/SloHealth.ts
CHANGED
|
@@ -248,16 +248,17 @@ export const getSloNotice: GetSloNoticeFunction = (
|
|
|
248
248
|
}
|
|
249
249
|
|
|
250
250
|
/*
|
|
251
|
-
*
|
|
252
|
-
*
|
|
253
|
-
*
|
|
251
|
+
* Monitors may have been attached since the last evaluation, or the
|
|
252
|
+
* worker may still be waiting for their status data. A stored
|
|
253
|
+
* Misconfigured status alone does not mean the current setup is wrong.
|
|
254
|
+
* Keep the monitor link so users can check data that is still missing.
|
|
254
255
|
*/
|
|
255
256
|
return {
|
|
256
|
-
type: SloNoticeType.
|
|
257
|
-
title: "
|
|
258
|
-
body: "
|
|
257
|
+
type: SloNoticeType.Info,
|
|
258
|
+
title: "Waiting for SLO evaluation",
|
|
259
|
+
body: "Monitors are attached. OneUptime evaluates this SLO every few minutes. Results will appear once monitor status data is available and the next evaluation completes.",
|
|
259
260
|
action: {
|
|
260
|
-
label: "
|
|
261
|
+
label: "View monitors",
|
|
261
262
|
target: SloNoticeActionTarget.Monitors,
|
|
262
263
|
},
|
|
263
264
|
};
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import FilterCondition from "../../Types/Filter/FilterCondition";
|
|
2
2
|
import ObjectID from "../../Types/ObjectID";
|
|
3
|
+
import MonitorType from "../../Types/Monitor/MonitorType";
|
|
3
4
|
import RuleCriteria, {
|
|
4
5
|
RuleCriteriaFilter,
|
|
5
6
|
RuleCriteriaOperator,
|
|
6
7
|
} from "../../Types/Rules/RuleCriteria";
|
|
7
8
|
import { isValidRuleCriteria } from "../Rules/RuleCriteriaMatcher";
|
|
9
|
+
import {
|
|
10
|
+
getMonitorTypeCriteriaValidationError,
|
|
11
|
+
isMonitorTypeCriteriaValue,
|
|
12
|
+
} from "../Rules/MonitorTypeRuleCriteria";
|
|
8
13
|
|
|
9
14
|
/*
|
|
10
15
|
* Plain-English descriptions of what an SLO monitor rule matches, for the
|
|
@@ -33,6 +38,7 @@ export const SLO_MONITOR_RULE_CRITERIA_FIELD_TITLES: Readonly<
|
|
|
33
38
|
Record<string, string>
|
|
34
39
|
> = {
|
|
35
40
|
monitorLabels: "Labels",
|
|
41
|
+
monitorType: "Type",
|
|
36
42
|
monitorNamePattern: "Name",
|
|
37
43
|
monitorDescriptionPattern: "Description",
|
|
38
44
|
};
|
|
@@ -78,6 +84,7 @@ export interface SloMonitorRuleLabelReference {
|
|
|
78
84
|
export interface SloMonitorRuleCriteriaCarrier {
|
|
79
85
|
criteria?: RuleCriteria | null | undefined;
|
|
80
86
|
monitorLabels?: Array<SloMonitorRuleLabelReference> | undefined;
|
|
87
|
+
monitorType?: MonitorType | null | undefined;
|
|
81
88
|
monitorNamePattern?: string | null | undefined;
|
|
82
89
|
monitorDescriptionPattern?: string | null | undefined;
|
|
83
90
|
}
|
|
@@ -247,7 +254,15 @@ export const describeSloMonitorRuleCriteria: DescribeSloMonitorRuleCriteriaFunct
|
|
|
247
254
|
}
|
|
248
255
|
|
|
249
256
|
if (hasConfiguredCriteria(rule)) {
|
|
250
|
-
if (
|
|
257
|
+
if (
|
|
258
|
+
!isValidRuleCriteria(rule.criteria) ||
|
|
259
|
+
rule.criteria.filters.some((filter: RuleCriteriaFilter): boolean => {
|
|
260
|
+
return (
|
|
261
|
+
filter.field === "monitorType" &&
|
|
262
|
+
getMonitorTypeCriteriaValidationError(filter) !== null
|
|
263
|
+
);
|
|
264
|
+
})
|
|
265
|
+
) {
|
|
251
266
|
return SLO_MONITOR_RULE_INVALID_CRITERIA_DESCRIPTION;
|
|
252
267
|
}
|
|
253
268
|
|
|
@@ -267,6 +282,14 @@ export const describeSloMonitorRuleCriteria: DescribeSloMonitorRuleCriteriaFunct
|
|
|
267
282
|
.join(connector);
|
|
268
283
|
}
|
|
269
284
|
|
|
285
|
+
if (
|
|
286
|
+
rule.monitorType !== undefined &&
|
|
287
|
+
rule.monitorType !== null &&
|
|
288
|
+
!isMonitorTypeCriteriaValue(rule.monitorType)
|
|
289
|
+
) {
|
|
290
|
+
return SLO_MONITOR_RULE_INVALID_CRITERIA_DESCRIPTION;
|
|
291
|
+
}
|
|
292
|
+
|
|
270
293
|
const parts: Array<string> = [];
|
|
271
294
|
|
|
272
295
|
const legacyLabelIds: Array<string> = getSloMonitorRuleLabelIds(rule);
|
|
@@ -279,6 +302,14 @@ export const describeSloMonitorRuleCriteria: DescribeSloMonitorRuleCriteriaFunct
|
|
|
279
302
|
);
|
|
280
303
|
}
|
|
281
304
|
|
|
305
|
+
if (rule.monitorType) {
|
|
306
|
+
parts.push(
|
|
307
|
+
`${SLO_MONITOR_RULE_CRITERIA_FIELD_TITLES["monitorType"]} ${
|
|
308
|
+
SLO_MONITOR_RULE_OPERATOR_PHRASES[RuleCriteriaOperator.Equals]
|
|
309
|
+
} ${quote(rule.monitorType)}`,
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
|
|
282
313
|
if (rule.monitorNamePattern) {
|
|
283
314
|
parts.push(
|
|
284
315
|
`${SLO_MONITOR_RULE_CRITERIA_FIELD_TITLES["monitorNamePattern"]} ${
|
|
@@ -358,6 +389,7 @@ export const getSloMonitorRuleCriteriaKey: GetSloMonitorRuleCriteriaKeyFunction
|
|
|
358
389
|
return id.toLowerCase();
|
|
359
390
|
})
|
|
360
391
|
.sort(),
|
|
392
|
+
monitorType: rule.monitorType || "",
|
|
361
393
|
name: rule.monitorNamePattern || "",
|
|
362
394
|
description: rule.monitorDescriptionPattern || "",
|
|
363
395
|
})}`;
|
|
@@ -102,6 +102,39 @@ export const getSloTargetText: GetSloTargetTextFunction = (
|
|
|
102
102
|
return target === null ? null : `Target ${target}`;
|
|
103
103
|
};
|
|
104
104
|
|
|
105
|
+
/**
|
|
106
|
+
* A budget below zero is exceeded, not a useful negative percentage left.
|
|
107
|
+
* Classify before rounding so even a small overage cannot read as 0%.
|
|
108
|
+
* The signed value remains available to calculations and history charts.
|
|
109
|
+
*/
|
|
110
|
+
export type GetSloBudgetRemainingTextFunction = (
|
|
111
|
+
percentage: number | undefined | null,
|
|
112
|
+
) => string | null;
|
|
113
|
+
|
|
114
|
+
export const getSloBudgetRemainingText: GetSloBudgetRemainingTextFunction = (
|
|
115
|
+
percentage: number | undefined | null,
|
|
116
|
+
): string | null => {
|
|
117
|
+
const remaining: number | null = toFiniteNumber(percentage);
|
|
118
|
+
|
|
119
|
+
if (remaining === null) {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (remaining < 0) {
|
|
124
|
+
return "Budget exceeded";
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (remaining === 0) {
|
|
128
|
+
return "No budget left";
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (remaining < 0.1) {
|
|
132
|
+
return "<0.1%";
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return formatSloPercent(Math.min(remaining, 100), 1);
|
|
136
|
+
};
|
|
137
|
+
|
|
105
138
|
/** "No monitors" / "1 monitor" / "12 monitors". */
|
|
106
139
|
export type GetSloMonitorCountTextFunction = (count: number) => string;
|
|
107
140
|
|
|
@@ -518,15 +518,16 @@ export interface SloBudgetBarGeometry {
|
|
|
518
518
|
atRiskThresholdPercentage: number;
|
|
519
519
|
/** Same tier as every other red/amber/green on the SLO pages. */
|
|
520
520
|
tier: SloBudgetTier;
|
|
521
|
-
/** Remaining budget is below zero
|
|
521
|
+
/** Remaining budget is below zero; preserve this even though the fill is zero. */
|
|
522
522
|
isOverspent: boolean;
|
|
523
523
|
}
|
|
524
524
|
|
|
525
525
|
/**
|
|
526
526
|
* Everything the budget bar draws, from the SIGNED remaining percentage.
|
|
527
527
|
*
|
|
528
|
-
* The percentage is unrounded and uncapped below zero
|
|
529
|
-
*
|
|
528
|
+
* The input percentage is unrounded and uncapped below zero, so an overspent
|
|
529
|
+
* budget still gets an explicit exceeded state when the drawing is clamped.
|
|
530
|
+
* The colour comes from
|
|
530
531
|
* SloHealth.getSloBudgetTier with the SLO's own threshold, so the bar can
|
|
531
532
|
* never be green while the status pill says At Risk.
|
|
532
533
|
*/
|
|
@@ -392,6 +392,8 @@ import ServiceLevelObjectiveOwnerUser from "./ServiceLevelObjectiveOwnerUser";
|
|
|
392
392
|
import ServiceLevelObjectiveOwnerTeam from "./ServiceLevelObjectiveOwnerTeam";
|
|
393
393
|
import ServiceLevelObjectiveMonitorRule from "./ServiceLevelObjectiveMonitorRule";
|
|
394
394
|
import ServiceLevelObjectiveFeed from "./ServiceLevelObjectiveFeed";
|
|
395
|
+
import ServiceLevelObjectiveOwnerRule from "./ServiceLevelObjectiveOwnerRule";
|
|
396
|
+
import ServiceLevelObjectiveLabelRule from "./ServiceLevelObjectiveLabelRule";
|
|
395
397
|
import IncidentReminderRule from "./IncidentReminderRule";
|
|
396
398
|
import AlertReminderRule from "./AlertReminderRule";
|
|
397
399
|
import ScheduledMaintenanceReminderRule from "./ScheduledMaintenanceReminderRule";
|
|
@@ -592,6 +594,8 @@ const AllModelTypes = [
|
|
|
592
594
|
ServiceLevelObjectiveOwnerTeam,
|
|
593
595
|
ServiceLevelObjectiveMonitorRule,
|
|
594
596
|
ServiceLevelObjectiveFeed,
|
|
597
|
+
ServiceLevelObjectiveOwnerRule,
|
|
598
|
+
ServiceLevelObjectiveLabelRule,
|
|
595
599
|
IncidentReminderRule,
|
|
596
600
|
AlertReminderRule,
|
|
597
601
|
ScheduledMaintenanceReminderRule,
|