@oneuptime/common 11.6.2 → 11.7.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 (64) hide show
  1. package/Models/AnalyticsModels/SloHistory.ts +6 -3
  2. package/Models/DatabaseModels/ServiceLevelObjective.ts +9 -0
  3. package/Server/Infrastructure/Postgres/SchemaMigrations/1785066759532-MigrationName.ts +30 -0
  4. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
  5. package/Server/Services/ServiceLevelObjectiveService.ts +12 -4
  6. package/Tests/Server/Services/ServiceLevelObjectiveService.test.ts +2 -2
  7. package/Tests/Types/DateUserTimezone.test.ts +205 -0
  8. package/Tests/Types/DateUserTimezoneOverridesBrowser.test.ts +73 -0
  9. package/Tests/Types/Monitor/MonitorCriteria.test.ts +155 -0
  10. package/Tests/Types/Monitor/MonitorCriteriaInstance.test.ts +465 -0
  11. package/Tests/Types/Monitor/MonitorStep.test.ts +183 -0
  12. package/Tests/Types/Monitor/MonitorSteps.test.ts +183 -0
  13. package/Tests/UI/Components/TimePicker/TimePicker.test.tsx +11 -0
  14. package/Tests/Utils/Memory.test.ts +44 -0
  15. package/Tests/Utils/Slo/SloBurnRateRuleState.test.ts +124 -0
  16. package/Tests/Utils/Slo/SloDuration.test.ts +148 -0
  17. package/Tests/Utils/Slo/SloHealth.test.ts +334 -0
  18. package/Types/Date.ts +119 -50
  19. package/UI/Components/Forms/Fields/FormField.tsx +13 -3
  20. package/UI/Components/Input/Input.tsx +6 -1
  21. package/UI/Components/TimePicker/TimePicker.tsx +8 -4
  22. package/UI/Utils/User.ts +19 -0
  23. package/Utils/Slo/SloBurnRateRuleState.ts +91 -0
  24. package/Utils/Slo/SloDuration.ts +165 -0
  25. package/Utils/Slo/SloEvaluation.ts +22 -0
  26. package/Utils/Slo/SloHealth.ts +302 -0
  27. package/build/dist/Models/AnalyticsModels/SloHistory.js +6 -3
  28. package/build/dist/Models/AnalyticsModels/SloHistory.js.map +1 -1
  29. package/build/dist/Models/DatabaseModels/ServiceLevelObjective.js +11 -1
  30. package/build/dist/Models/DatabaseModels/ServiceLevelObjective.js.map +1 -1
  31. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785066759532-MigrationName.js +21 -0
  32. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785066759532-MigrationName.js.map +1 -0
  33. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
  34. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  35. package/build/dist/Server/Services/ServiceLevelObjectiveService.js +10 -4
  36. package/build/dist/Server/Services/ServiceLevelObjectiveService.js.map +1 -1
  37. package/build/dist/Types/Date.js +106 -35
  38. package/build/dist/Types/Date.js.map +1 -1
  39. package/build/dist/UI/Components/Forms/Fields/FormField.js +11 -3
  40. package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
  41. package/build/dist/UI/Components/Input/Input.js +6 -1
  42. package/build/dist/UI/Components/Input/Input.js.map +1 -1
  43. package/build/dist/UI/Components/TimePicker/TimePicker.js +4 -4
  44. package/build/dist/UI/Components/TimePicker/TimePicker.js.map +1 -1
  45. package/build/dist/UI/Utils/User.js +19 -0
  46. package/build/dist/UI/Utils/User.js.map +1 -1
  47. package/build/dist/Utils/Slo/SloBurnRateRuleState.js +35 -0
  48. package/build/dist/Utils/Slo/SloBurnRateRuleState.js.map +1 -0
  49. package/build/dist/Utils/Slo/SloDuration.js +87 -0
  50. package/build/dist/Utils/Slo/SloDuration.js.map +1 -0
  51. package/build/dist/Utils/Slo/SloEvaluation.js +21 -0
  52. package/build/dist/Utils/Slo/SloEvaluation.js.map +1 -0
  53. package/build/dist/Utils/Slo/SloHealth.js +179 -0
  54. package/build/dist/Utils/Slo/SloHealth.js.map +1 -0
  55. package/package.json +1 -1
  56. package/Server/Infrastructure/Postgres/SchemaMigrations/1773414578773-MigrationName.ts +0 -79
  57. package/Server/Infrastructure/Postgres/SchemaMigrations/1773500000000-MigrationName.ts +0 -41
  58. package/Server/Infrastructure/Postgres/SchemaMigrations/1776886248361-MigrationName.ts +0 -17
  59. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1773414578773-MigrationName.js +0 -34
  60. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1773414578773-MigrationName.js.map +0 -1
  61. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1773500000000-MigrationName.js +0 -22
  62. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1773500000000-MigrationName.js.map +0 -1
  63. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1776886248361-MigrationName.js +0 -12
  64. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1776886248361-MigrationName.js.map +0 -1
package/UI/Utils/User.ts CHANGED
@@ -10,6 +10,7 @@ import { JSONObject, JSONValue } from "../../Types/JSON";
10
10
  import Name from "../../Types/Name";
11
11
  import ObjectID from "../../Types/ObjectID";
12
12
  import Timezone from "../../Types/Timezone";
13
+ import OneUptimeDate from "../../Types/Date";
13
14
  import API from "../Utils/API/API";
14
15
  import Cookie from "./Cookie";
15
16
  import CookieName from "../../Types/CookieName";
@@ -62,6 +63,22 @@ export default class UserUtil {
62
63
 
63
64
  public static setSavedUserTimezone(timezone: Timezone): void {
64
65
  LocalStorage.setItem("user_timezone", timezone);
66
+ OneUptimeDate.setUserTimezone(timezone);
67
+ }
68
+
69
+ /**
70
+ * Hand the timezone the user picked in User Settings to OneUptimeDate so
71
+ * every date the UI renders and every date the user types is resolved in
72
+ * that zone instead of whatever zone the browser reports. Call this once at
73
+ * app startup, before anything renders.
74
+ */
75
+ public static initializeUserTimezone(): void {
76
+ try {
77
+ OneUptimeDate.setUserTimezone(this.getSavedUserTimezone() || null);
78
+ } catch (err) {
79
+ // A missing / unreadable local storage must not stop the app from booting.
80
+ Logger.error(err as Error);
81
+ }
65
82
  }
66
83
 
67
84
  public static getDismissedTimezonePrompt(): Timezone | null {
@@ -199,6 +216,8 @@ export default class UserUtil {
199
216
  LocalStorage.clear();
200
217
  SessionStorage.clear();
201
218
  Cookie.clearAllCookies();
219
+ // The next user on this browser must not inherit this user's timezone.
220
+ OneUptimeDate.setUserTimezone(null);
202
221
 
203
222
  API.post({
204
223
  url: URL.fromString(IDENTITY_URL.toString()).addRoute("/logout"),
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Whether a burn rate rule currently has an alert open.
3
+ *
4
+ * `lastAlertCreatedAt` / `lastAlertResolvedAt` are stamped by the
5
+ * evaluation worker every time it fires or resolves a rule's alert, and
6
+ * together they are the only record of the rule's lifecycle — but nothing
7
+ * read them outside the worker, so the Burn Rate Rules table could not
8
+ * tell the user whether a rule was paging someone right now, had ever
9
+ * fired, or had never once triggered.
10
+ *
11
+ * The predicate is the worker's own (EvaluateSlos.evaluateBurnRateRule's
12
+ * `hasUnresolvedFiringState`), lifted here so the table and the worker can
13
+ * never disagree about what "firing" means.
14
+ *
15
+ * Kept free of React and of the database model so both can import it and
16
+ * so the ordering edges are unit-testable without a DOM.
17
+ */
18
+
19
+ import SloStatus from "../../Types/ServiceLevelObjective/SloStatus";
20
+
21
+ export interface SloBurnRateRuleAlertState {
22
+ lastAlertCreatedAt?: Date | undefined | null;
23
+ lastAlertResolvedAt?: Date | undefined | null;
24
+ }
25
+
26
+ /**
27
+ * True when the rule has fired and that alert has not been resolved
28
+ * since.
29
+ *
30
+ * A resolve stamped at exactly the same instant as the fire counts as
31
+ * resolved: the worker writes the resolve strictly after the fire, so an
32
+ * equal pair can only be the same lifecycle, and treating it as firing
33
+ * would leave a red "Firing" pill on a rule with nothing open.
34
+ */
35
+ export type IsBurnRateRuleFiringFunction = (
36
+ rule: SloBurnRateRuleAlertState,
37
+ ) => boolean;
38
+
39
+ export const isBurnRateRuleFiring: IsBurnRateRuleFiringFunction = (
40
+ rule: SloBurnRateRuleAlertState,
41
+ ): boolean => {
42
+ if (!rule.lastAlertCreatedAt) {
43
+ return false;
44
+ }
45
+
46
+ if (!rule.lastAlertResolvedAt) {
47
+ return true;
48
+ }
49
+
50
+ return rule.lastAlertResolvedAt.getTime() < rule.lastAlertCreatedAt.getTime();
51
+ };
52
+
53
+ /**
54
+ * Whether the OWNING SLO is in a state where a burn rate rule could be
55
+ * firing at all.
56
+ *
57
+ * `isBurnRateRuleFiring` alone is not enough to render a live "Firing"
58
+ * badge. When an SLO is disabled, or the worker's guards move it to
59
+ * Misconfigured or Paused, the SLO-level resolve paths
60
+ * (ServiceLevelObjectiveService.resolveOpenBurnRateAlertsForSlo and the
61
+ * worker's setGuardStatusAndResolveOpenAlerts) close every open alert but
62
+ * deliberately do NOT stamp `lastAlertResolvedAt` — that column drives a
63
+ * live rule's re-fire suppression, and this is a state change of the SLO
64
+ * rather than a burn rate that recovered. The rule's own columns are
65
+ * therefore left mid-lifecycle, and a UI reading them alone would show a
66
+ * red "Firing" pill on a rule with nothing open, forever.
67
+ *
68
+ * Pairing the two predicates keeps the badge honest without touching the
69
+ * suppression semantics the worker depends on.
70
+ */
71
+ export interface SloBurnRateFiringContext {
72
+ isEnabled?: boolean | undefined | null;
73
+ sloStatus?: SloStatus | undefined | null;
74
+ }
75
+
76
+ export type CanSloFireBurnRateAlertsFunction = (
77
+ slo: SloBurnRateFiringContext,
78
+ ) => boolean;
79
+
80
+ export const canSloFireBurnRateAlerts: CanSloFireBurnRateAlertsFunction = (
81
+ slo: SloBurnRateFiringContext,
82
+ ): boolean => {
83
+ if (slo.isEnabled === false) {
84
+ return false;
85
+ }
86
+
87
+ return (
88
+ slo.sloStatus !== SloStatus.Misconfigured &&
89
+ slo.sloStatus !== SloStatus.Paused
90
+ );
91
+ };
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Compact duration rendering for error budgets.
3
+ *
4
+ * Error budgets span four orders of magnitude — a 99.99% / 7-day SLO gets
5
+ * 60 seconds, a 99% / 90-day SLO gets 21.6 hours — so the "minutes only"
6
+ * rendering the SLO pages started with reads badly at both ends: a
7
+ * one-minute budget shows as "1 min", and a 90-day budget shows as
8
+ * "1296 min of 1296 min". The docs quote budgets the way an SRE says them
9
+ * out loud ("43m 12s", "2h 9m"), and this is the helper that produces
10
+ * exactly that.
11
+ *
12
+ * Kept free of React (and of the database models) so the SLO pages, the
13
+ * dashboard SLO widget and any future status-page surface can all share
14
+ * one rendering, and so every rounding edge is unit-testable without a
15
+ * DOM.
16
+ */
17
+
18
+ /** U+2212 MINUS SIGN — reads as a minus rather than a hyphen at tile sizes. */
19
+ const MINUS_SIGN: string = "−";
20
+
21
+ /**
22
+ * Seconds per unit, largest first. Only the two most significant non-zero
23
+ * units are rendered: "2d 5h" is what an on-call engineer needs, "2d 5h
24
+ * 13m 7s" is noise, and truncating rather than rounding up keeps the
25
+ * remaining budget from ever reading larger than it is.
26
+ */
27
+ const UNITS: Array<{ suffix: string; seconds: number }> = [
28
+ { suffix: "d", seconds: 86400 },
29
+ { suffix: "h", seconds: 3600 },
30
+ { suffix: "m", seconds: 60 },
31
+ { suffix: "s", seconds: 1 },
32
+ ];
33
+
34
+ const MAX_UNITS_SHOWN: number = 2;
35
+
36
+ /**
37
+ * `null` for anything that is not a real, finite number. The
38
+ * worker-owned budget columns are NULL until the SLO is first evaluated,
39
+ * and a Postgres decimal can arrive as a string over JSON, so the guard
40
+ * has to reject non-numbers rather than coerce them.
41
+ */
42
+ type ToFiniteNumberFunction = (
43
+ value: number | undefined | null,
44
+ ) => number | null;
45
+
46
+ const toFiniteNumber: ToFiniteNumberFunction = (
47
+ value: number | undefined | null,
48
+ ): number | null => {
49
+ if (typeof value !== "number" || !isFinite(value)) {
50
+ return null;
51
+ }
52
+
53
+ return value;
54
+ };
55
+
56
+ /**
57
+ * An unsigned duration as "2d 5h" / "43m 12s" / "45s". Fractional seconds
58
+ * are floored, so a 59.9-second budget renders "59s" rather than rounding
59
+ * up to a minute the SLO does not actually have.
60
+ *
61
+ * A duration that floors to zero renders "0s" — a real, sayable state —
62
+ * instead of an empty string.
63
+ */
64
+ export type FormatDurationCompactFunction = (seconds: number) => string;
65
+
66
+ export const formatDurationCompact: FormatDurationCompactFunction = (
67
+ seconds: number,
68
+ ): string => {
69
+ let remainingSeconds: number = Math.floor(Math.abs(seconds));
70
+
71
+ const parts: Array<string> = [];
72
+
73
+ for (const unit of UNITS) {
74
+ if (parts.length >= MAX_UNITS_SHOWN) {
75
+ break;
76
+ }
77
+
78
+ const count: number = Math.floor(remainingSeconds / unit.seconds);
79
+
80
+ /*
81
+ * Skip leading zero units ("0d 5h"), but once a larger unit has been
82
+ * emitted a zero is still skipped rather than padded — "2d" is
83
+ * correct for exactly 48 hours, "2d 0h" is not how anyone says it.
84
+ */
85
+ if (count <= 0) {
86
+ continue;
87
+ }
88
+
89
+ parts.push(`${count}${unit.suffix}`);
90
+ remainingSeconds -= count * unit.seconds;
91
+ }
92
+
93
+ if (parts.length === 0) {
94
+ return "0s";
95
+ }
96
+
97
+ return parts.join(" ");
98
+ };
99
+
100
+ /**
101
+ * `errorBudgetRemainingSeconds` is SIGNED: negative means the budget is
102
+ * overspent, and that overage is the single most useful number during a
103
+ * bad month. Render it plainly rather than clamping to zero, which would
104
+ * hide a breach.
105
+ *
106
+ * Returns `null` (not a placeholder string) when the SLO has not been
107
+ * evaluated, so callers decide how to phrase "no data yet" in their own
108
+ * context.
109
+ */
110
+ export type FormatErrorBudgetRemainingFunction = (
111
+ seconds: number | undefined | null,
112
+ ) => string | null;
113
+
114
+ export const formatErrorBudgetRemaining: FormatErrorBudgetRemainingFunction = (
115
+ seconds: number | undefined | null,
116
+ ): string | null => {
117
+ const numericSeconds: number | null = toFiniteNumber(seconds);
118
+
119
+ if (numericSeconds === null) {
120
+ return null;
121
+ }
122
+
123
+ if (numericSeconds < 0) {
124
+ return `${MINUS_SIGN}${formatDurationCompact(numericSeconds)} over budget`;
125
+ }
126
+
127
+ return `${formatDurationCompact(numericSeconds)} left`;
128
+ };
129
+
130
+ /**
131
+ * "12m 30s left of 43m 12s" — the phrasing the error-budget docs use.
132
+ * Falls back to the bare remaining string when the total is not known
133
+ * (the two columns are written together by the worker, but a partially
134
+ * evaluated row must not render "of null").
135
+ */
136
+ export type FormatErrorBudgetRemainingOfTotalFunction = (data: {
137
+ remainingSeconds: number | undefined | null;
138
+ totalSeconds: number | undefined | null;
139
+ }) => string | null;
140
+
141
+ export const formatErrorBudgetRemainingOfTotal: FormatErrorBudgetRemainingOfTotalFunction =
142
+ (data: {
143
+ remainingSeconds: number | undefined | null;
144
+ totalSeconds: number | undefined | null;
145
+ }): string | null => {
146
+ const remainingText: string | null = formatErrorBudgetRemaining(
147
+ data.remainingSeconds,
148
+ );
149
+
150
+ if (remainingText === null) {
151
+ return null;
152
+ }
153
+
154
+ const totalSeconds: number | null = toFiniteNumber(data.totalSeconds);
155
+
156
+ /*
157
+ * A zero total is a real state for a brand-new SLO (no elapsed window
158
+ * yet). "of 0s" adds nothing, so drop the suffix rather than print it.
159
+ */
160
+ if (totalSeconds === null || totalSeconds <= 0) {
161
+ return remainingText;
162
+ }
163
+
164
+ return `${remainingText} of ${formatDurationCompact(totalSeconds)}`;
165
+ };
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Cadence constants shared by the SLO evaluation worker and every UI
3
+ * surface that describes what the worker produced.
4
+ *
5
+ * These numbers were previously duplicated as prose: the worker owned
6
+ * them, and the dashboard said "evaluated every few minutes" and "1×
7
+ * spends the budget exactly over the window" without ever naming the
8
+ * lookback the headline burn rate is actually measured over. A reader who
9
+ * compared the tile against a burn rate rule had no way to know they were
10
+ * measuring different spans. Naming them once here keeps the copy and the
11
+ * computation from drifting.
12
+ */
13
+
14
+ /** How often a due SLO is re-evaluated, and how far ahead the worker schedules the next run. */
15
+ export const SLO_EVALUATION_CADENCE_MINUTES: number = 5;
16
+
17
+ /**
18
+ * Lookback for the "current burn rate" column shown on the SLO overview
19
+ * and the dashboard SLO widget. Deliberately equal to the default
20
+ * fast-burn rule's long window, so the tile and that rule move together.
21
+ */
22
+ export const SLO_CURRENT_BURN_RATE_WINDOW_MINUTES: number = 60;
@@ -0,0 +1,302 @@
1
+ import SliType from "../../Types/ServiceLevelObjective/SliType";
2
+ import SloMultiMonitorMode from "../../Types/ServiceLevelObjective/SloMultiMonitorMode";
3
+ import SloStatus from "../../Types/ServiceLevelObjective/SloStatus";
4
+ import SloWindowType from "../../Types/ServiceLevelObjective/SloWindowType";
5
+
6
+ /**
7
+ * The "should I be worried, and about what?" layer of the SLO UI.
8
+ *
9
+ * Two things live here, both pure and both previously inlined (and
10
+ * subtly wrong) in the React pages:
11
+ *
12
+ * 1. The remaining-budget tier that drives every red/amber/green in the
13
+ * SLO surfaces. The pages hardcoded `remaining <= 20`, which silently
14
+ * disagreed with any SLO whose `atRiskThresholdPercentage` was not the
15
+ * default 20 — an SLO could render green while the worker had already
16
+ * moved it to At Risk.
17
+ *
18
+ * 2. The reason an SLO is not producing numbers. `Misconfigured` and
19
+ * `Paused` are the two statuses that mean "we are not measuring
20
+ * anything", and the pages showed them as a bare grey pill with no
21
+ * explanation and no way to act. The reasons here mirror exactly the
22
+ * guard clauses in the evaluation worker (EvaluateSlos.evaluateSlo).
23
+ *
24
+ * Kept free of React so both the SLO pages and the dashboard SLO widget
25
+ * can import it, and so every branch is unit-testable without a DOM.
26
+ */
27
+
28
+ /** The DB default for `ServiceLevelObjective.atRiskThresholdPercentage`. */
29
+ export const DEFAULT_AT_RISK_THRESHOLD_PERCENTAGE: number = 20;
30
+
31
+ /** The DB default for `ServiceLevelObjective.windowDays`. */
32
+ export const DEFAULT_ROLLING_WINDOW_DAYS: number = 30;
33
+
34
+ export enum SloBudgetTier {
35
+ /** The worker has not evaluated this SLO yet — assert nothing. */
36
+ Unknown = "Unknown",
37
+ Healthy = "Healthy",
38
+ AtRisk = "AtRisk",
39
+ Exhausted = "Exhausted",
40
+ }
41
+
42
+ type ToFiniteNumberFunction = (
43
+ value: number | undefined | null,
44
+ ) => number | null;
45
+
46
+ const toFiniteNumber: ToFiniteNumberFunction = (
47
+ value: number | undefined | null,
48
+ ): number | null => {
49
+ if (typeof value !== "number" || !isFinite(value)) {
50
+ return null;
51
+ }
52
+
53
+ return value;
54
+ };
55
+
56
+ /**
57
+ * Tier from the SIGNED remaining-budget percentage, using the SLO's own
58
+ * at-risk threshold rather than a hardcoded 20.
59
+ *
60
+ * Boundaries match SloUtil.computeSloStatus (`<= 0` is exhausted, `<=
61
+ * threshold` is at risk) so the colour never contradicts the status pill
62
+ * the worker computed. The hysteresis SloUtil applies is deliberately NOT
63
+ * reproduced here: this function colours a number the user is looking at,
64
+ * and hysteresis depends on the previous status, which is not part of
65
+ * "what does this number mean".
66
+ */
67
+ export type GetSloBudgetTierFunction = (data: {
68
+ errorBudgetRemainingPercentage: number | undefined | null;
69
+ atRiskThresholdPercentage?: number | undefined | null;
70
+ }) => SloBudgetTier;
71
+
72
+ export const getSloBudgetTier: GetSloBudgetTierFunction = (data: {
73
+ errorBudgetRemainingPercentage: number | undefined | null;
74
+ atRiskThresholdPercentage?: number | undefined | null;
75
+ }): SloBudgetTier => {
76
+ const remaining: number | null = toFiniteNumber(
77
+ data.errorBudgetRemainingPercentage,
78
+ );
79
+
80
+ if (remaining === null) {
81
+ return SloBudgetTier.Unknown;
82
+ }
83
+
84
+ if (remaining <= 0) {
85
+ return SloBudgetTier.Exhausted;
86
+ }
87
+
88
+ const threshold: number =
89
+ toFiniteNumber(data.atRiskThresholdPercentage) ??
90
+ DEFAULT_AT_RISK_THRESHOLD_PERCENTAGE;
91
+
92
+ if (remaining <= threshold) {
93
+ return SloBudgetTier.AtRisk;
94
+ }
95
+
96
+ return SloBudgetTier.Healthy;
97
+ };
98
+
99
+ export enum SloNoticeType {
100
+ Info = "Info",
101
+ Warning = "Warning",
102
+ Danger = "Danger",
103
+ }
104
+
105
+ export interface SloNotice {
106
+ type: SloNoticeType;
107
+ title: string;
108
+ /** One sentence of "here is what to do about it". */
109
+ body: string;
110
+ }
111
+
112
+ /**
113
+ * The fields a notice can be derived from. Declared structurally rather
114
+ * than importing the database model so this stays dependency-free and
115
+ * tests can pass plain objects.
116
+ */
117
+ export interface SloNoticeData {
118
+ isEnabled?: boolean | undefined | null;
119
+ sloStatus?: SloStatus | undefined | null;
120
+ sliType?: SliType | undefined | null;
121
+ /** Number of monitors currently attached. */
122
+ monitorCount?: number | undefined | null;
123
+ /** Of the attached monitors, how many have active monitoring disabled. */
124
+ disabledMonitorCount?: number | undefined | null;
125
+ targetPercentage?: number | undefined | null;
126
+ lastEvaluatedAt?: Date | string | undefined | null;
127
+ }
128
+
129
+ /**
130
+ * The single most important thing to tell the user about this SLO right
131
+ * now, or null when the SLO is measuring normally.
132
+ *
133
+ * Ordered by how completely each condition stops the SLO from producing
134
+ * numbers, so the banner always names the root cause rather than a
135
+ * downstream symptom: a disabled SLO is not evaluated at all, so saying
136
+ * "no monitors attached" would send the user to fix the wrong thing.
137
+ *
138
+ * Every branch mirrors a guard in the evaluation worker
139
+ * (App/FeatureSet/Workers/Jobs/Slo/EvaluateSlos.ts):
140
+ * isEnabled=false -> the SLO is skipped entirely by getDueSlos()
141
+ * sliType != Uptime -> Misconfigured (Metric SLIs are not built yet)
142
+ * monitorCount == 0 -> Misconfigured
143
+ * target outside (0,100) -> Misconfigured
144
+ * all monitors disabled -> Paused
145
+ */
146
+ export type GetSloNoticeFunction = (data: SloNoticeData) => SloNotice | null;
147
+
148
+ export const getSloNotice: GetSloNoticeFunction = (
149
+ data: SloNoticeData,
150
+ ): SloNotice | null => {
151
+ if (data.isEnabled === false) {
152
+ return {
153
+ type: SloNoticeType.Info,
154
+ title: "This SLO is disabled",
155
+ body: "It is not being evaluated and its burn rate rules will not fire alerts. Turn Enabled back on in SLO Details to resume measuring.",
156
+ };
157
+ }
158
+
159
+ const monitorCount: number = toFiniteNumber(data.monitorCount) ?? 0;
160
+
161
+ if (data.sloStatus === SloStatus.Misconfigured) {
162
+ if (data.sliType && data.sliType !== SliType.MonitorUptime) {
163
+ return {
164
+ type: SloNoticeType.Warning,
165
+ title: "This SLO cannot be evaluated",
166
+ body: `Only "${SliType.MonitorUptime}" SLIs are supported today. Change the SLI type to Monitor Uptime and attach the monitors this objective measures.`,
167
+ };
168
+ }
169
+
170
+ if (monitorCount === 0) {
171
+ return {
172
+ type: SloNoticeType.Warning,
173
+ title: "No monitors attached",
174
+ body: "An SLO measures uptime from its monitors, so it cannot be evaluated without at least one. Add monitors in SLO Details below.",
175
+ };
176
+ }
177
+
178
+ const target: number | null = toFiniteNumber(data.targetPercentage);
179
+
180
+ if (target === null || target <= 0 || target >= 100) {
181
+ return {
182
+ type: SloNoticeType.Warning,
183
+ title: "The target is out of range",
184
+ body: "A target must be greater than 0 and less than 100 — a 100% target leaves no error budget to track. Set a target such as 99.9 in SLO Details below.",
185
+ };
186
+ }
187
+
188
+ return {
189
+ type: SloNoticeType.Warning,
190
+ title: "This SLO cannot be evaluated",
191
+ body: "Check that it has monitors attached and a target below 100%, then wait a few minutes for the next evaluation.",
192
+ };
193
+ }
194
+
195
+ if (data.sloStatus === SloStatus.Paused) {
196
+ return {
197
+ type: SloNoticeType.Info,
198
+ title: "Measurement is paused",
199
+ body: "Every monitor attached to this SLO has active monitoring disabled — by hand, by a manual incident, or by a scheduled maintenance event — so there is no signal to measure. The SLO resumes automatically when a monitor starts reporting again.",
200
+ };
201
+ }
202
+
203
+ if (!data.lastEvaluatedAt) {
204
+ return {
205
+ type: SloNoticeType.Info,
206
+ title: "Not evaluated yet",
207
+ body: "OneUptime evaluates SLOs every few minutes. The first numbers will appear shortly after this SLO is created.",
208
+ };
209
+ }
210
+
211
+ return null;
212
+ };
213
+
214
+ /**
215
+ * True when a rolling-window SLO is younger than its own window, so the
216
+ * budget it is measuring against is smaller than the full-window budget.
217
+ *
218
+ * The SLI denominator is clamped forward to the earliest monitor event
219
+ * (SloUtil.computeAnyDownSli), which means `errorBudgetTotalSeconds` for a
220
+ * young SLO is proportionally short — a 30-day SLO with a week of data
221
+ * carries a 7-day budget. Both the SLOs Overview and the Error Budgets
222
+ * doc promise the dashboard flags this as "window not yet full"; this is
223
+ * the detection behind that flag.
224
+ *
225
+ * Two cases are excluded on purpose:
226
+ *
227
+ * - Calendar-month windows: their budget is fixed at the FULL period from
228
+ * day one (never prorated), so the concept does not apply.
229
+ *
230
+ * - Monitor Seconds Average: its denominator is the SUM of monitored
231
+ * seconds ACROSS monitors (SloUtil.computeMonitorSecondsAverageSli), so
232
+ * `errorBudgetTotalSeconds` scales with the monitor count and cannot be
233
+ * compared against a single-window budget. A five-monitor SLO with a
234
+ * week of data would carry 35 monitor-days against a 30-day yardstick
235
+ * and read as mature. Rather than guess at a monitor count the caller
236
+ * may not have loaded, say nothing — a missing hint beats a wrong one.
237
+ *
238
+ * The 1% tolerance absorbs the gap between "now" and the worker's last
239
+ * evaluation — without it, a fully mature SLO would flicker the banner on
240
+ * every evaluation cycle.
241
+ */
242
+ const WINDOW_FULL_TOLERANCE_FRACTION: number = 0.99;
243
+
244
+ export type IsRollingWindowNotYetFullFunction = (data: {
245
+ windowType?: SloWindowType | undefined | null;
246
+ windowDays?: number | undefined | null;
247
+ targetPercentage?: number | undefined | null;
248
+ errorBudgetTotalSeconds?: number | undefined | null;
249
+ multiMonitorMode?: SloMultiMonitorMode | undefined | null;
250
+ }) => boolean;
251
+
252
+ export const isRollingWindowNotYetFull: IsRollingWindowNotYetFullFunction =
253
+ (data: {
254
+ windowType?: SloWindowType | undefined | null;
255
+ windowDays?: number | undefined | null;
256
+ targetPercentage?: number | undefined | null;
257
+ errorBudgetTotalSeconds?: number | undefined | null;
258
+ multiMonitorMode?: SloMultiMonitorMode | undefined | null;
259
+ }): boolean => {
260
+ if (data.windowType === SloWindowType.CalendarMonth) {
261
+ return false;
262
+ }
263
+
264
+ if (data.multiMonitorMode === SloMultiMonitorMode.MonitorSecondsAverage) {
265
+ return false;
266
+ }
267
+
268
+ const budgetTotalSeconds: number | null = toFiniteNumber(
269
+ data.errorBudgetTotalSeconds,
270
+ );
271
+ const target: number | null = toFiniteNumber(data.targetPercentage);
272
+
273
+ /*
274
+ * Not evaluated yet, or a target that would make the full-window budget
275
+ * meaningless: the "not evaluated" notice already covers the first case,
276
+ * and the Misconfigured notice covers the second.
277
+ */
278
+ if (
279
+ budgetTotalSeconds === null ||
280
+ budgetTotalSeconds <= 0 ||
281
+ target === null ||
282
+ target <= 0 ||
283
+ target >= 100
284
+ ) {
285
+ return false;
286
+ }
287
+
288
+ const windowDays: number =
289
+ toFiniteNumber(data.windowDays) ?? DEFAULT_ROLLING_WINDOW_DAYS;
290
+
291
+ if (windowDays <= 0) {
292
+ return false;
293
+ }
294
+
295
+ const fullWindowBudgetSeconds: number =
296
+ (1 - target / 100) * windowDays * 24 * 60 * 60;
297
+
298
+ return (
299
+ budgetTotalSeconds <
300
+ fullWindowBudgetSeconds * WINDOW_FULL_TOLERANCE_FRACTION
301
+ );
302
+ };
@@ -40,8 +40,11 @@ import Permission from "../../Types/Permission";
40
40
  * dashboard charts do) or dedupe explicitly via
41
41
  * `argMax(value, version) ... GROUP BY <identity>`.
42
42
  *
43
- * `metricName` values: "sli.percent", "error.budget.remaining.percent",
44
- * "burn.rate", "good.count", "total.count".
43
+ * `metricName` values written today: "sli.percent",
44
+ * "error.budget.remaining.percent" and "burn.rate" — the three gauges the
45
+ * evaluation worker persists per run. "good.count" / "total.count" are
46
+ * reserved for event-based (Metric) SLIs, which the worker does not
47
+ * evaluate yet; nothing writes or reads them.
45
48
  */
46
49
  export default class SloHistory extends AnalyticsBaseModel {
47
50
  constructor() {
@@ -81,7 +84,7 @@ export default class SloHistory extends AnalyticsBaseModel {
81
84
  key: "metricName",
82
85
  isLowCardinality: true,
83
86
  title: "Metric Name",
84
- description: "Which SLO series this row belongs to: sli.percent, error.budget.remaining.percent, burn.rate, good.count, or total.count.",
87
+ description: "Which SLO series this row belongs to: sli.percent, error.budget.remaining.percent, or burn.rate.",
85
88
  required: true,
86
89
  type: TableColumnType.Text,
87
90
  accessControl: {
@@ -1 +1 @@
1
- {"version":3,"file":"SloHistory.js","sourceRoot":"","sources":["../../../../Models/AnalyticsModels/SloHistory.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"SloHistory.js","sourceRoot":"","sources":["../../../../Models/AnalyticsModels/SloHistory.ts"],"names":[],"mappings":"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"}