@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.
Files changed (196) hide show
  1. package/Models/DatabaseModels/Index.ts +4 -0
  2. package/Models/DatabaseModels/ServiceLevelObjectiveFeed.ts +2 -0
  3. package/Models/DatabaseModels/ServiceLevelObjectiveLabelRule.ts +522 -0
  4. package/Models/DatabaseModels/ServiceLevelObjectiveMonitorRule.ts +38 -4
  5. package/Models/DatabaseModels/ServiceLevelObjectiveOwnerRule.ts +603 -0
  6. package/Server/Infrastructure/Postgres/SchemaMigrations/1794100000000-AddSloLabelAndOwnerRules.ts +248 -0
  7. package/Server/Infrastructure/Postgres/SchemaMigrations/1794200000000-AddSloMonitorRuleMonitorType.ts +19 -0
  8. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
  9. package/Server/Services/DatabaseService.ts +10 -0
  10. package/Server/Services/Index.ts +4 -0
  11. package/Server/Services/MonitorService.ts +3 -2
  12. package/Server/Services/ServiceLevelObjectiveLabelRuleEngineService.ts +385 -0
  13. package/Server/Services/ServiceLevelObjectiveLabelRuleService.ts +51 -0
  14. package/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.ts +92 -16
  15. package/Server/Services/ServiceLevelObjectiveMonitorRuleService.ts +36 -1
  16. package/Server/Services/ServiceLevelObjectiveOwnerRuleEngineService.ts +521 -0
  17. package/Server/Services/ServiceLevelObjectiveOwnerRuleService.ts +51 -0
  18. package/Server/Services/ServiceLevelObjectiveService.ts +28 -0
  19. package/Server/Types/Workflow/Components/Telegram/SendMessageToChat.ts +27 -1
  20. package/Server/Utils/AI/Toolbox/SloTools.ts +1 -0
  21. package/Server/Utils/Monitor/AffectedResourceList.ts +31 -73
  22. package/Server/Utils/Monitor/Criteria/CompareCriteria.ts +1 -1
  23. package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +1 -1
  24. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +181 -136
  25. package/Server/Utils/Monitor/RootCauseList.ts +139 -0
  26. package/Server/Utils/Rules/RuleRun/RuleRunRegistry.ts +28 -0
  27. package/Server/Utils/Slo/SloRulePatternValidator.ts +42 -0
  28. package/Tests/App/Dashboard/FeedFilterSortIntegration.test.tsx +1632 -0
  29. package/Tests/App/Dashboard/FeedOptionsLocales.test.ts +250 -0
  30. package/Tests/App/Dashboard/FeedOrdering.test.ts +279 -9
  31. package/Tests/App/Dashboard/InvestigationFeedRefresh.test.tsx +60 -1
  32. package/Tests/App/Dashboard/InvestigationPanel.test.tsx +594 -3
  33. package/Tests/App/Dashboard/ProductOrientation.test.tsx +646 -0
  34. package/Tests/App/Dashboard/ResourceFeed.test.tsx +612 -2
  35. package/Tests/App/Dashboard/ResourceFeedPageWiring.test.ts +30 -0
  36. package/Tests/App/Dashboard/ResourceFeedPagesFilterSort.test.tsx +1202 -0
  37. package/Tests/App/Dashboard/ScheduledMaintenanceFeedRefresh.test.tsx +52 -1
  38. package/Tests/App/Dashboard/SloFeed.test.tsx +106 -1
  39. package/Tests/App/Dashboard/SloMonitorRuleCriteria.test.tsx +566 -0
  40. package/Tests/App/Dashboard/SloNavigationWiring.test.tsx +6 -4
  41. package/Tests/App/Dashboard/SloNoticeBanner.test.tsx +186 -0
  42. package/Tests/App/Dashboard/SloOverviewKpiStrip.test.tsx +388 -44
  43. package/Tests/App/Dashboard/SloOverviewLayout.test.tsx +613 -0
  44. package/Tests/Models/DatabaseModels/ResourceFeedModels.test.ts +0 -8
  45. package/Tests/Models/DatabaseModels/SloLabelAndOwnerRuleModels.test.ts +404 -0
  46. package/Tests/Models/DatabaseModels/SloOverhaulModels.test.ts +17 -1
  47. package/Tests/Models/DatabaseModels/StaticRuleCriteriaInheritance.test.ts +4 -2
  48. package/Tests/Server/Infrastructure/Postgres/AddConfigurableRuleCriteriaMigration.test.ts +3 -0
  49. package/Tests/Server/Infrastructure/Postgres/AddSloLabelAndOwnerRulesMigration.test.ts +407 -0
  50. package/Tests/Server/Infrastructure/Postgres/AddSloMonitorRuleMonitorTypeMigration.test.ts +171 -0
  51. package/Tests/Server/Infrastructure/Postgres/SloProductOverhaulMigration.test.ts +88 -4
  52. package/Tests/Server/Services/DatabaseServiceRuleCriteriaValidation.test.ts +104 -0
  53. package/Tests/Server/Services/RuleRun/LabelRuleEnginesGroupBRun.test.ts +48 -1
  54. package/Tests/Server/Services/RuleRun/OwnerRuleEnginesGroupBRun.test.ts +30 -0
  55. package/Tests/Server/Services/ServiceLevelObjectiveLabelAndOwnerRuleEngines.test.ts +1170 -0
  56. package/Tests/Server/Services/ServiceLevelObjectiveLabelAndOwnerRuleServices.test.ts +172 -0
  57. package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.test.ts +323 -0
  58. package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleService.test.ts +115 -0
  59. package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleTriggers.test.ts +21 -2
  60. package/Tests/Server/Services/ServiceLevelObjectiveMonitorTypePostgres.test.ts +480 -0
  61. package/Tests/Server/Services/ServiceLevelObjectiveService.test.ts +162 -0
  62. package/Tests/Server/Services/StaticRuleCriteriaRuntimeCoverage.test.ts +45 -6
  63. package/Tests/Server/Types/Database/FeedEventTypeFilterQuery.test.ts +745 -0
  64. package/Tests/Server/Types/Markdown.test.ts +2 -1
  65. package/Tests/Server/Types/Workflow/Components/ApiComponentSsrf.test.ts +49 -0
  66. package/Tests/Server/Types/Workflow/Components/TelegramSendMessageToChat.test.ts +331 -0
  67. package/Tests/Server/Utils/AI/SloTools.test.ts +10 -0
  68. package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteriaAnomalyUnits.test.ts +1 -1
  69. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluator.test.ts +405 -0
  70. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorBreachingSamples.test.ts +1101 -0
  71. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorMetricUnits.test.ts +106 -80
  72. package/Tests/Server/Utils/Monitor/RootCauseList.test.ts +252 -0
  73. package/Tests/Server/Utils/Rules/RuleRun/RuleRunPermission.test.ts +154 -0
  74. package/Tests/Server/Utils/SSRFProtection.test.ts +111 -0
  75. package/Tests/Server/Utils/Slo/SloRulePatternValidator.test.ts +67 -0
  76. package/Tests/Server/Utils/VM/VMRunnerSsrf.test.ts +213 -0
  77. package/Tests/Types/API/Route.test.ts +56 -0
  78. package/Tests/Types/API/URLSchemeLikePath.test.ts +304 -0
  79. package/Tests/UI/Components/FeedOptions.test.ts +1404 -0
  80. package/Tests/UI/Components/FeedOptionsButton.test.tsx +2603 -0
  81. package/Tests/UI/Components/Icon.test.tsx +26 -0
  82. package/Tests/UI/Components/RuleCriteria/RuleCriteriaModelTable.test.tsx +3 -3
  83. package/Tests/UI/Components/useFeedItemsViewKey.test.tsx +1017 -0
  84. package/Tests/UI/Components/useFeedOptions.test.tsx +1107 -0
  85. package/Tests/Utils/LabelRuleImportExport.test.ts +102 -0
  86. package/Tests/Utils/Slo/SloDuration.test.ts +115 -24
  87. package/Tests/Utils/Slo/SloHealth.test.ts +93 -10
  88. package/Tests/Utils/Slo/SloMonitorRuleCriteria.test.ts +109 -0
  89. package/Tests/Utils/Slo/SloOverviewText.test.ts +64 -0
  90. package/Types/API/Route.ts +19 -2
  91. package/Types/API/URL.ts +26 -2
  92. package/Types/Monitor/MetricMonitor/MetricCriteriaContext.ts +6 -5
  93. package/Types/Monitor/MetricMonitor/MetricMonitorResponse.ts +9 -0
  94. package/Types/Permission.ts +90 -0
  95. package/Types/Rules/RuleCriteriaFieldRegistry.ts +11 -0
  96. package/Types/Rules/RuleRun.ts +4 -0
  97. package/UI/Components/Feed/FeedOptions.ts +427 -0
  98. package/UI/Components/Feed/FeedOptionsButton.tsx +692 -0
  99. package/UI/Components/Feed/useFeedItems.ts +28 -9
  100. package/UI/Components/Feed/useFeedOptions.ts +225 -0
  101. package/UI/Components/Icon/Icon.tsx +1 -1
  102. package/Utils/LabelRuleImportExport.ts +27 -9
  103. package/Utils/Monitor/MetricValueFormatter.ts +2 -2
  104. package/Utils/Rules/MonitorTypeRuleCriteria.ts +31 -0
  105. package/Utils/Slo/SloDuration.ts +22 -6
  106. package/Utils/Slo/SloHealth.ts +8 -7
  107. package/Utils/Slo/SloMonitorRuleCriteria.ts +33 -1
  108. package/Utils/Slo/SloOverviewText.ts +33 -0
  109. package/Utils/Slo/SloProjection.ts +4 -3
  110. package/build/dist/Models/DatabaseModels/Index.js +4 -0
  111. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  112. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveFeed.js +2 -0
  113. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveFeed.js.map +1 -1
  114. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveLabelRule.js +530 -0
  115. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveLabelRule.js.map +1 -0
  116. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveMonitorRule.js +39 -4
  117. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveMonitorRule.js.map +1 -1
  118. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveOwnerRule.js +612 -0
  119. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveOwnerRule.js.map +1 -0
  120. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794100000000-AddSloLabelAndOwnerRules.js +93 -0
  121. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794100000000-AddSloLabelAndOwnerRules.js.map +1 -0
  122. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794200000000-AddSloMonitorRuleMonitorType.js +12 -0
  123. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794200000000-AddSloMonitorRuleMonitorType.js.map +1 -0
  124. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
  125. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  126. package/build/dist/Server/Services/DatabaseService.js +7 -0
  127. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  128. package/build/dist/Server/Services/Index.js +4 -0
  129. package/build/dist/Server/Services/Index.js.map +1 -1
  130. package/build/dist/Server/Services/MonitorService.js +3 -2
  131. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  132. package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleEngineService.js +295 -0
  133. package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleEngineService.js.map +1 -0
  134. package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleService.js +56 -0
  135. package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleService.js.map +1 -0
  136. package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js +59 -14
  137. package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js.map +1 -1
  138. package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleService.js +22 -1
  139. package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleService.js.map +1 -1
  140. package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleEngineService.js +400 -0
  141. package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleEngineService.js.map +1 -0
  142. package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleService.js +56 -0
  143. package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleService.js.map +1 -0
  144. package/build/dist/Server/Services/ServiceLevelObjectiveService.js +21 -0
  145. package/build/dist/Server/Services/ServiceLevelObjectiveService.js.map +1 -1
  146. package/build/dist/Server/Types/Workflow/Components/Telegram/SendMessageToChat.js +20 -1
  147. package/build/dist/Server/Types/Workflow/Components/Telegram/SendMessageToChat.js.map +1 -1
  148. package/build/dist/Server/Utils/AI/Toolbox/SloTools.js +1 -0
  149. package/build/dist/Server/Utils/AI/Toolbox/SloTools.js.map +1 -1
  150. package/build/dist/Server/Utils/Monitor/AffectedResourceList.js +22 -51
  151. package/build/dist/Server/Utils/Monitor/AffectedResourceList.js.map +1 -1
  152. package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js +1 -1
  153. package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +1 -1
  154. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +139 -109
  155. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  156. package/build/dist/Server/Utils/Monitor/RootCauseList.js +101 -0
  157. package/build/dist/Server/Utils/Monitor/RootCauseList.js.map +1 -0
  158. package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js +28 -0
  159. package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js.map +1 -1
  160. package/build/dist/Server/Utils/Slo/SloRulePatternValidator.js +28 -0
  161. package/build/dist/Server/Utils/Slo/SloRulePatternValidator.js.map +1 -0
  162. package/build/dist/Types/API/Route.js +18 -2
  163. package/build/dist/Types/API/Route.js.map +1 -1
  164. package/build/dist/Types/API/URL.js +23 -2
  165. package/build/dist/Types/API/URL.js.map +1 -1
  166. package/build/dist/Types/Permission.js +84 -0
  167. package/build/dist/Types/Permission.js.map +1 -1
  168. package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js +11 -0
  169. package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js.map +1 -1
  170. package/build/dist/Types/Rules/RuleRun.js +4 -0
  171. package/build/dist/Types/Rules/RuleRun.js.map +1 -1
  172. package/build/dist/UI/Components/Feed/FeedOptions.js +235 -0
  173. package/build/dist/UI/Components/Feed/FeedOptions.js.map +1 -0
  174. package/build/dist/UI/Components/Feed/FeedOptionsButton.js +346 -0
  175. package/build/dist/UI/Components/Feed/FeedOptionsButton.js.map +1 -0
  176. package/build/dist/UI/Components/Feed/useFeedItems.js +17 -7
  177. package/build/dist/UI/Components/Feed/useFeedItems.js.map +1 -1
  178. package/build/dist/UI/Components/Feed/useFeedOptions.js +130 -0
  179. package/build/dist/UI/Components/Feed/useFeedOptions.js.map +1 -0
  180. package/build/dist/UI/Components/Icon/Icon.js +1 -1
  181. package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
  182. package/build/dist/Utils/LabelRuleImportExport.js +22 -6
  183. package/build/dist/Utils/LabelRuleImportExport.js.map +1 -1
  184. package/build/dist/Utils/Monitor/MetricValueFormatter.js +2 -2
  185. package/build/dist/Utils/Rules/MonitorTypeRuleCriteria.js +18 -0
  186. package/build/dist/Utils/Rules/MonitorTypeRuleCriteria.js.map +1 -0
  187. package/build/dist/Utils/Slo/SloDuration.js +13 -5
  188. package/build/dist/Utils/Slo/SloDuration.js.map +1 -1
  189. package/build/dist/Utils/Slo/SloHealth.js +8 -7
  190. package/build/dist/Utils/Slo/SloHealth.js.map +1 -1
  191. package/build/dist/Utils/Slo/SloMonitorRuleCriteria.js +16 -1
  192. package/build/dist/Utils/Slo/SloMonitorRuleCriteria.js.map +1 -1
  193. package/build/dist/Utils/Slo/SloOverviewText.js +16 -0
  194. package/build/dist/Utils/Slo/SloOverviewText.js.map +1 -1
  195. package/build/dist/Utils/Slo/SloProjection.js.map +1 -1
  196. 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
- * newest top N rows and replace that window when the reader asks for more.
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
- const activeResourceKeyRef: MutableRefObject<string> = useRef<string>(
75
- props.resourceKey,
76
- );
77
- activeResourceKeyRef.current = props.resourceKey;
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 !== props.resourceKey;
188
- previousResourceKeyRef.current = props.resourceKey;
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, props.resourceKey, props.refreshToken]);
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 === props.resourceKey;
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.5h9.75m4.5-4.5v12m0 0l-3.75-3.75M17.25 21L21 17.25"
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 valid: boolean =
763
- resourceType === "NetworkDeviceLabelRule"
764
- ? RulePatternMatchUtil.isSupportedPattern(value)
765
- : RulePatternMatchUtil.isValidRegex(value);
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 ${resourceType === "NetworkDeviceLabelRule" ? "regular expression or wildcard pattern" : "regular expression"}.`,
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 === "NetworkDeviceLabelRule" ||
781
- destination === "NetworkDeviceLabelRule") &&
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 network devices and another rule type without changing its meaning. Edit this pattern for the destination before importing.",
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 table, the
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 tables cannot drift apart inside one email.
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
+ }
@@ -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 `${MINUS_SIGN}${formatDurationCompact(numericSeconds)} over budget`;
131
+ return `${formatBudgetDuration(numericSeconds)} over budget`;
125
132
  }
126
133
 
127
- return `${formatDurationCompact(numericSeconds)} left`;
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
- return `${remainingText} of ${formatDurationCompact(totalSeconds)}`;
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
  };
@@ -248,16 +248,17 @@ export const getSloNotice: GetSloNoticeFunction = (
248
248
  }
249
249
 
250
250
  /*
251
- * What is left are the worker's two data guards: the attached monitors
252
- * no longer exist, or none of them has reported a status yet. Both are
253
- * answered on the Monitors page.
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.Warning,
257
- title: "This SLO cannot be evaluated",
258
- body: "Check that its monitors still exist and have started reporting a status, then wait a few minutes for the next evaluation.",
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: "Review monitors",
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 (!isValidRuleCriteria(rule.criteria)) {
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 — the fill is clamped, the text is not. */
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 (an overspent budget
529
- * reads -340%), so only the drawing is clamped; the colour comes from
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,