@oneuptime/common 10.2.0 → 10.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/Models/DatabaseModels/Index.ts +2 -0
  2. package/Models/DatabaseModels/ProjectOidc.ts +705 -0
  3. package/Server/API/ProjectOIDC.ts +73 -0
  4. package/Server/Infrastructure/Postgres/SchemaMigrations/1778506655291-AddProjectOIDC.ts +79 -0
  5. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
  6. package/Server/Services/AlertLabelRuleEngineService.ts +16 -0
  7. package/Server/Services/IncidentLabelRuleEngineService.ts +16 -0
  8. package/Server/Services/Index.ts +2 -0
  9. package/Server/Services/OnCallDutyPolicyScheduleService.ts +139 -26
  10. package/Server/Services/ProjectOidcService.ts +10 -0
  11. package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +23 -0
  12. package/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.ts +98 -3
  13. package/Tests/Utils/MetricUnitUtil.test.ts +38 -1
  14. package/Types/Monitor/MetricMonitor/MetricMonitorResponse.ts +8 -0
  15. package/Types/OnCallDutyPolicy/UserOverrideUtil.ts +155 -0
  16. package/Types/Permission.ts +42 -0
  17. package/UI/Components/Calendar/Calendar.css +257 -0
  18. package/UI/Components/Calendar/Calendar.tsx +22 -11
  19. package/Utils/MetricUnitUtil.ts +24 -0
  20. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  21. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  22. package/build/dist/Models/DatabaseModels/ProjectOidc.js +727 -0
  23. package/build/dist/Models/DatabaseModels/ProjectOidc.js.map +1 -0
  24. package/build/dist/Server/API/ProjectOIDC.js +45 -0
  25. package/build/dist/Server/API/ProjectOIDC.js.map +1 -0
  26. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1778506655291-AddProjectOIDC.js +34 -0
  27. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1778506655291-AddProjectOIDC.js.map +1 -0
  28. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
  29. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  30. package/build/dist/Server/Services/AlertLabelRuleEngineService.js +16 -0
  31. package/build/dist/Server/Services/AlertLabelRuleEngineService.js.map +1 -1
  32. package/build/dist/Server/Services/IncidentLabelRuleEngineService.js +16 -0
  33. package/build/dist/Server/Services/IncidentLabelRuleEngineService.js.map +1 -1
  34. package/build/dist/Server/Services/Index.js +2 -0
  35. package/build/dist/Server/Services/Index.js.map +1 -1
  36. package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js +106 -17
  37. package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js.map +1 -1
  38. package/build/dist/Server/Services/ProjectOidcService.js +9 -0
  39. package/build/dist/Server/Services/ProjectOidcService.js.map +1 -0
  40. package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +25 -8
  41. package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js.map +1 -1
  42. package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js +71 -1
  43. package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js.map +1 -1
  44. package/build/dist/Tests/Utils/MetricUnitUtil.test.js +29 -1
  45. package/build/dist/Tests/Utils/MetricUnitUtil.test.js.map +1 -1
  46. package/build/dist/Types/OnCallDutyPolicy/UserOverrideUtil.js +86 -0
  47. package/build/dist/Types/OnCallDutyPolicy/UserOverrideUtil.js.map +1 -0
  48. package/build/dist/Types/Permission.js +40 -0
  49. package/build/dist/Types/Permission.js.map +1 -1
  50. package/build/dist/UI/Components/Calendar/Calendar.js +12 -10
  51. package/build/dist/UI/Components/Calendar/Calendar.js.map +1 -1
  52. package/build/dist/Utils/MetricUnitUtil.js +22 -0
  53. package/build/dist/Utils/MetricUnitUtil.js.map +1 -1
  54. package/package.json +1 -1
@@ -49,7 +49,19 @@ describe("MetricUnitUtil", () => {
49
49
  test("returns percent family for '%'", () => {
50
50
  const options: Array<{ value: string; label: string }> =
51
51
  MetricUnitUtil.getCompatibleUnits("%");
52
- expect(options).toEqual([{ value: "%", label: "Percent (%)" }]);
52
+ expect(options).toEqual([
53
+ { value: "%", label: "Percent (%)" },
54
+ { value: "1", label: "Fraction (0-1)" },
55
+ ]);
56
+ });
57
+
58
+ test("returns percent family for UCUM '1' (dimensionless / ratio)", () => {
59
+ const options: Array<{ value: string; label: string }> =
60
+ MetricUnitUtil.getCompatibleUnits("1");
61
+ expect(options).toEqual([
62
+ { value: "%", label: "Percent (%)" },
63
+ { value: "1", label: "Fraction (0-1)" },
64
+ ]);
53
65
  });
54
66
 
55
67
  test("returns raw unit as sole option for unknown unit", () => {
@@ -69,6 +81,10 @@ describe("MetricUnitUtil", () => {
69
81
  expect(MetricUnitUtil.getCanonicalUnitValue("percent")).toBe("%");
70
82
  });
71
83
 
84
+ test("returns '%' for UCUM dimensionless '1' so the threshold UI doesn't default to a literal '1'", () => {
85
+ expect(MetricUnitUtil.getCanonicalUnitValue("1")).toBe("%");
86
+ });
87
+
72
88
  test("passes through unknown units", () => {
73
89
  expect(MetricUnitUtil.getCanonicalUnitValue("widgets")).toBe("widgets");
74
90
  });
@@ -197,6 +213,26 @@ describe("MetricUnitUtil", () => {
197
213
  }),
198
214
  ).toBe(2e9);
199
215
  });
216
+
217
+ test("converts '%' to the fraction unit '1' by dividing by 100", () => {
218
+ expect(
219
+ MetricUnitUtil.convertToMetricUnit({
220
+ value: 50,
221
+ fromUnit: "%",
222
+ metricUnit: "1",
223
+ }),
224
+ ).toBe(0.5);
225
+ });
226
+
227
+ test("converts the fraction unit '1' to '%' by multiplying by 100", () => {
228
+ expect(
229
+ MetricUnitUtil.convertToMetricUnit({
230
+ value: 0.05,
231
+ fromUnit: "1",
232
+ metricUnit: "%",
233
+ }),
234
+ ).toBe(5);
235
+ });
200
236
  });
201
237
 
202
238
  describe("hasCompatibleUnitFamily", () => {
@@ -204,6 +240,7 @@ describe("MetricUnitUtil", () => {
204
240
  expect(MetricUnitUtil.hasCompatibleUnitFamily("bytes")).toBe(true);
205
241
  expect(MetricUnitUtil.hasCompatibleUnitFamily("ms")).toBe(true);
206
242
  expect(MetricUnitUtil.hasCompatibleUnitFamily("%")).toBe(true);
243
+ expect(MetricUnitUtil.hasCompatibleUnitFamily("1")).toBe(true);
207
244
  expect(MetricUnitUtil.hasCompatibleUnitFamily("GB")).toBe(true);
208
245
  });
209
246
 
@@ -41,4 +41,12 @@ export default interface MetricMonitorResponse {
41
41
  * `metricResult` as before.
42
42
  */
43
43
  seriesBreakdown?: Array<MetricSeriesResult> | undefined;
44
+ /**
45
+ * Native units (UCUM / OTel) per referenced metric name, lowercased.
46
+ * Loaded once when the monitor data is fetched. The criteria
47
+ * evaluator falls back to this when the query alias has no explicit
48
+ * `legendUnit` so threshold unit conversion (e.g. % vs the
49
+ * dimensionless "1" used by ratio metrics) still works.
50
+ */
51
+ nativeUnitsByMetricName?: Dictionary<string> | undefined;
44
52
  }
@@ -0,0 +1,155 @@
1
+ import CalendarEvent from "../Calendar/CalendarEvent";
2
+ import OneUptimeDate from "../Date";
3
+
4
+ export interface UserOverrideRecord {
5
+ overrideUserId: string;
6
+ routeAlertsToUserId: string;
7
+ startsAt: Date;
8
+ endsAt: Date;
9
+ // null/undefined means global override (applies to all on-call duty policies)
10
+ onCallDutyPolicyId?: string | null | undefined;
11
+ }
12
+
13
+ export interface OverrideEventMeta {
14
+ isOverride: true;
15
+ originalUserId: string;
16
+ overrideUserId: string;
17
+ overrideStartsAt: Date;
18
+ overrideEndsAt: Date;
19
+ }
20
+
21
+ /*
22
+ * CalendarEvent extends JSONObject so it accepts string-indexed metadata.
23
+ * We attach the override info under a known key so downstream consumers can
24
+ * detect and render the substitution distinctly.
25
+ */
26
+ export const OVERRIDE_META_KEY: string = "_override";
27
+
28
+ export default class UserOverrideUtil {
29
+ /**
30
+ * Returns true when this override should be applied on top of the schedule
31
+ * events. Global overrides always apply; policy-scoped overrides apply to
32
+ * any schedule, since a schedule's calendar shows coverage information for
33
+ * every user who could potentially be paged through it.
34
+ */
35
+ public static isOverrideApplicable(_override: UserOverrideRecord): boolean {
36
+ return true;
37
+ }
38
+
39
+ public static applyOverridesToEvents(data: {
40
+ events: Array<CalendarEvent>;
41
+ overrides: Array<UserOverrideRecord>;
42
+ }): Array<CalendarEvent> {
43
+ const applicable: Array<UserOverrideRecord> = data.overrides.filter(
44
+ UserOverrideUtil.isOverrideApplicable,
45
+ );
46
+
47
+ if (applicable.length === 0) {
48
+ return data.events;
49
+ }
50
+
51
+ let working: Array<CalendarEvent> = data.events;
52
+
53
+ for (const override of applicable) {
54
+ const next: Array<CalendarEvent> = [];
55
+ for (const event of working) {
56
+ next.push(...UserOverrideUtil.splitEventByOverride(event, override));
57
+ }
58
+ working = next;
59
+ }
60
+
61
+ return UserOverrideUtil.reassignEventIds(working);
62
+ }
63
+
64
+ private static splitEventByOverride(
65
+ event: CalendarEvent,
66
+ override: UserOverrideRecord,
67
+ ): Array<CalendarEvent> {
68
+ if (event.title !== override.overrideUserId) {
69
+ return [event];
70
+ }
71
+
72
+ // Override window doesn't overlap event window at all.
73
+ if (
74
+ OneUptimeDate.isAfter(override.startsAt, event.end) ||
75
+ OneUptimeDate.isSame(override.startsAt, event.end) ||
76
+ OneUptimeDate.isBefore(override.endsAt, event.start) ||
77
+ OneUptimeDate.isSame(override.endsAt, event.start)
78
+ ) {
79
+ return [event];
80
+ }
81
+
82
+ const overrideStart: Date = OneUptimeDate.isAfter(
83
+ override.startsAt,
84
+ event.start,
85
+ )
86
+ ? override.startsAt
87
+ : event.start;
88
+
89
+ const overrideEnd: Date = OneUptimeDate.isBefore(override.endsAt, event.end)
90
+ ? override.endsAt
91
+ : event.end;
92
+
93
+ const segments: Array<CalendarEvent> = [];
94
+
95
+ // Segment before the override window — original user remains on call.
96
+ if (OneUptimeDate.isBefore(event.start, overrideStart)) {
97
+ segments.push({
98
+ ...event,
99
+ end: overrideStart,
100
+ });
101
+ }
102
+
103
+ // Override window — substitute user takes over.
104
+ const meta: OverrideEventMeta = {
105
+ isOverride: true,
106
+ originalUserId: override.overrideUserId,
107
+ overrideUserId: override.routeAlertsToUserId,
108
+ overrideStartsAt: override.startsAt,
109
+ overrideEndsAt: override.endsAt,
110
+ };
111
+
112
+ segments.push({
113
+ ...event,
114
+ start: overrideStart,
115
+ end: overrideEnd,
116
+ title: override.routeAlertsToUserId,
117
+ [OVERRIDE_META_KEY]: meta as unknown as never,
118
+ });
119
+
120
+ // Segment after the override window — original user resumes.
121
+ if (OneUptimeDate.isAfter(event.end, overrideEnd)) {
122
+ segments.push({
123
+ ...event,
124
+ start: overrideEnd,
125
+ });
126
+ }
127
+
128
+ return segments;
129
+ }
130
+
131
+ private static reassignEventIds(
132
+ events: Array<CalendarEvent>,
133
+ ): Array<CalendarEvent> {
134
+ let id: number = 1;
135
+ return events.map((event: CalendarEvent) => {
136
+ return { ...event, id: id++ };
137
+ });
138
+ }
139
+
140
+ public static getOverrideMeta(
141
+ event: CalendarEvent,
142
+ ): OverrideEventMeta | null {
143
+ const meta: unknown = (event as unknown as Record<string, unknown>)[
144
+ OVERRIDE_META_KEY
145
+ ];
146
+ if (
147
+ meta &&
148
+ typeof meta === "object" &&
149
+ (meta as { isOverride?: boolean }).isOverride === true
150
+ ) {
151
+ return meta as OverrideEventMeta;
152
+ }
153
+ return null;
154
+ }
155
+ }
@@ -527,6 +527,11 @@ enum Permission {
527
527
  EditProjectSSO = "EditProjectSSO",
528
528
  ReadProjectSSO = "ReadProjectSSO",
529
529
 
530
+ CreateProjectOIDC = "CreateProjectOIDC",
531
+ DeleteProjectOIDC = "DeleteProjectOIDC",
532
+ EditProjectOIDC = "EditProjectOIDC",
533
+ ReadProjectOIDC = "ReadProjectOIDC",
534
+
530
535
  CreateStatusPageSSO = "CreateStatusPageSSO",
531
536
  DeleteStatusPageSSO = "DeleteStatusPageSSO",
532
537
  EditStatusPageSSO = "EditStatusPageSSO",
@@ -2949,6 +2954,43 @@ export class PermissionHelper {
2949
2954
  group: PermissionGroup.Settings,
2950
2955
  },
2951
2956
 
2957
+ {
2958
+ permission: Permission.CreateProjectOIDC,
2959
+ title: "Create Project OIDC",
2960
+ description: "This permission can create Project OIDC in this project.",
2961
+ isAssignableToTenant: true,
2962
+ isAccessControlPermission: false,
2963
+ isRolePermission: false,
2964
+ group: PermissionGroup.Settings,
2965
+ },
2966
+ {
2967
+ permission: Permission.DeleteProjectOIDC,
2968
+ title: "Delete Project OIDC",
2969
+ description: "This permission can delete Project OIDC in this project.",
2970
+ isAssignableToTenant: true,
2971
+ isAccessControlPermission: false,
2972
+ isRolePermission: false,
2973
+ group: PermissionGroup.Settings,
2974
+ },
2975
+ {
2976
+ permission: Permission.EditProjectOIDC,
2977
+ title: "Edit Project OIDC",
2978
+ description: "This permission can edit Project OIDC in this project.",
2979
+ isAssignableToTenant: true,
2980
+ isAccessControlPermission: false,
2981
+ isRolePermission: false,
2982
+ group: PermissionGroup.Settings,
2983
+ },
2984
+ {
2985
+ permission: Permission.ReadProjectOIDC,
2986
+ title: "Read Project OIDC",
2987
+ description: "This permission can read Project OIDC in this project.",
2988
+ isAssignableToTenant: true,
2989
+ isAccessControlPermission: false,
2990
+ isRolePermission: false,
2991
+ group: PermissionGroup.Settings,
2992
+ },
2993
+
2952
2994
  {
2953
2995
  permission: Permission.CreateStatusPageSSO,
2954
2996
  title: "Create Status Page SSO",
@@ -0,0 +1,257 @@
1
+ /* OneUptime Calendar – modern, minimal overrides for react-big-calendar.
2
+ Scoped via the .oneuptime-calendar wrapper so we don't leak styles. */
3
+
4
+ .oneuptime-calendar .rbc-calendar {
5
+ font-family: inherit;
6
+ color: #111827; /* gray-900 */
7
+ }
8
+
9
+ /* ---------- Toolbar ---------- */
10
+ .oneuptime-calendar .rbc-toolbar {
11
+ display: flex;
12
+ flex-wrap: wrap;
13
+ align-items: center;
14
+ justify-content: space-between;
15
+ gap: 0.75rem;
16
+ margin-bottom: 1rem;
17
+ padding: 0;
18
+ font-size: 0.875rem;
19
+ }
20
+
21
+ .oneuptime-calendar .rbc-toolbar .rbc-toolbar-label {
22
+ flex: 1 1 auto;
23
+ text-align: center;
24
+ font-size: 1rem;
25
+ font-weight: 600;
26
+ color: #111827; /* gray-900 */
27
+ letter-spacing: -0.01em;
28
+ }
29
+
30
+ .oneuptime-calendar .rbc-toolbar .rbc-btn-group {
31
+ display: inline-flex;
32
+ border: 1px solid #e5e7eb; /* gray-200 */
33
+ background-color: #ffffff;
34
+ border-radius: 0.5rem;
35
+ box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.04);
36
+ overflow: hidden;
37
+ }
38
+
39
+ .oneuptime-calendar .rbc-toolbar .rbc-btn-group button {
40
+ border: none;
41
+ background-color: transparent;
42
+ color: #4b5563; /* gray-600 */
43
+ padding: 0.4rem 0.85rem;
44
+ font-size: 0.8125rem;
45
+ font-weight: 500;
46
+ line-height: 1.25rem;
47
+ cursor: pointer;
48
+ transition:
49
+ background-color 120ms ease,
50
+ color 120ms ease;
51
+ }
52
+
53
+ .oneuptime-calendar .rbc-toolbar .rbc-btn-group button + button {
54
+ border-left: 1px solid #e5e7eb;
55
+ }
56
+
57
+ .oneuptime-calendar .rbc-toolbar .rbc-btn-group button:hover,
58
+ .oneuptime-calendar .rbc-toolbar .rbc-btn-group button:focus {
59
+ background-color: #f9fafb; /* gray-50 */
60
+ color: #111827;
61
+ outline: none;
62
+ box-shadow: none;
63
+ }
64
+
65
+ .oneuptime-calendar .rbc-toolbar .rbc-btn-group button.rbc-active,
66
+ .oneuptime-calendar .rbc-toolbar .rbc-btn-group button.rbc-active:hover,
67
+ .oneuptime-calendar .rbc-toolbar .rbc-btn-group button.rbc-active:focus {
68
+ background-color: #4f46e5; /* indigo-600 */
69
+ color: #ffffff;
70
+ box-shadow: none;
71
+ }
72
+
73
+ /* ---------- Calendar surface ---------- */
74
+ .oneuptime-calendar .rbc-month-view,
75
+ .oneuptime-calendar .rbc-time-view,
76
+ .oneuptime-calendar .rbc-agenda-view {
77
+ border: 1px solid #e5e7eb; /* gray-200 */
78
+ border-radius: 0.75rem;
79
+ background-color: #ffffff;
80
+ overflow: hidden;
81
+ }
82
+
83
+ .oneuptime-calendar .rbc-month-view {
84
+ background-color: #ffffff;
85
+ }
86
+
87
+ /* ---------- Headers ---------- */
88
+ .oneuptime-calendar .rbc-header,
89
+ .oneuptime-calendar .rbc-time-header-content > .rbc-row > .rbc-header,
90
+ .oneuptime-calendar .rbc-time-header .rbc-header {
91
+ padding: 0.625rem 0.5rem;
92
+ font-size: 0.75rem;
93
+ font-weight: 600;
94
+ color: #6b7280; /* gray-500 */
95
+ text-transform: uppercase;
96
+ letter-spacing: 0.04em;
97
+ background-color: #f9fafb; /* gray-50 */
98
+ border-bottom: 1px solid #e5e7eb;
99
+ }
100
+
101
+ .oneuptime-calendar .rbc-header + .rbc-header,
102
+ .oneuptime-calendar .rbc-time-header-content .rbc-header + .rbc-header {
103
+ border-left: 1px solid #f3f4f6; /* gray-100 */
104
+ }
105
+
106
+ .oneuptime-calendar .rbc-time-header.rbc-overflowing {
107
+ border-right: none;
108
+ }
109
+
110
+ .oneuptime-calendar .rbc-time-header-content {
111
+ border-left: 1px solid #e5e7eb;
112
+ }
113
+
114
+ /* ---------- Month grid ---------- */
115
+ .oneuptime-calendar .rbc-month-row + .rbc-month-row {
116
+ border-top: 1px solid #f3f4f6; /* gray-100 */
117
+ }
118
+
119
+ .oneuptime-calendar .rbc-day-bg + .rbc-day-bg {
120
+ border-left: 1px solid #f3f4f6;
121
+ }
122
+
123
+ .oneuptime-calendar .rbc-date-cell {
124
+ padding: 0.35rem 0.5rem;
125
+ text-align: right;
126
+ font-size: 0.8125rem;
127
+ font-weight: 500;
128
+ color: #374151; /* gray-700 */
129
+ }
130
+
131
+ .oneuptime-calendar .rbc-date-cell.rbc-now {
132
+ color: #4f46e5; /* indigo-600 */
133
+ font-weight: 600;
134
+ }
135
+
136
+ .oneuptime-calendar .rbc-off-range {
137
+ color: #d1d5db; /* gray-300 */
138
+ }
139
+
140
+ .oneuptime-calendar .rbc-off-range-bg {
141
+ background-color: #fafafa;
142
+ }
143
+
144
+ .oneuptime-calendar .rbc-today {
145
+ background-color: #eef2ff; /* indigo-50 */
146
+ }
147
+
148
+ /* ---------- Time view (week/day) ---------- */
149
+ .oneuptime-calendar .rbc-time-view .rbc-time-gutter,
150
+ .oneuptime-calendar .rbc-time-view .rbc-time-content > * + * > * {
151
+ border-color: #f3f4f6;
152
+ }
153
+
154
+ .oneuptime-calendar .rbc-time-view .rbc-time-gutter .rbc-timeslot-group {
155
+ font-size: 0.75rem;
156
+ color: #6b7280;
157
+ }
158
+
159
+ .oneuptime-calendar .rbc-timeslot-group {
160
+ border-bottom: 1px solid #f3f4f6;
161
+ }
162
+
163
+ .oneuptime-calendar .rbc-time-content {
164
+ border-top: 1px solid #e5e7eb;
165
+ }
166
+
167
+ .oneuptime-calendar .rbc-time-content > * + * > * {
168
+ border-left: 1px solid #f3f4f6;
169
+ }
170
+
171
+ .oneuptime-calendar .rbc-current-time-indicator {
172
+ background-color: #ef4444; /* red-500 */
173
+ height: 2px;
174
+ }
175
+
176
+ .oneuptime-calendar .rbc-time-slot {
177
+ color: #9ca3af; /* gray-400 */
178
+ }
179
+
180
+ .oneuptime-calendar .rbc-label {
181
+ padding: 0 0.5rem;
182
+ font-size: 0.75rem;
183
+ color: #6b7280;
184
+ }
185
+
186
+ /* ---------- Events ---------- */
187
+ .oneuptime-calendar .rbc-event,
188
+ .oneuptime-calendar .rbc-day-slot .rbc-background-event {
189
+ border: none;
190
+ border-radius: 0.375rem;
191
+ padding: 0.2rem 0.5rem;
192
+ font-size: 0.75rem;
193
+ font-weight: 500;
194
+ box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.08);
195
+ transition:
196
+ transform 120ms ease,
197
+ box-shadow 120ms ease,
198
+ filter 120ms ease;
199
+ }
200
+
201
+ .oneuptime-calendar .rbc-event:hover,
202
+ .oneuptime-calendar .rbc-day-slot .rbc-background-event:hover {
203
+ filter: brightness(0.97);
204
+ box-shadow: 0 4px 10px -2px rgb(0 0 0 / 0.12);
205
+ }
206
+
207
+ .oneuptime-calendar .rbc-event:focus,
208
+ .oneuptime-calendar .rbc-event.rbc-selected {
209
+ outline: 2px solid #4f46e5; /* indigo-600 */
210
+ outline-offset: 1px;
211
+ }
212
+
213
+ .oneuptime-calendar .rbc-event-label {
214
+ font-size: 0.6875rem;
215
+ opacity: 0.85;
216
+ }
217
+
218
+ .oneuptime-calendar .rbc-show-more {
219
+ color: #4f46e5;
220
+ font-weight: 500;
221
+ background-color: transparent;
222
+ padding: 0.125rem 0.375rem;
223
+ }
224
+
225
+ /* ---------- Agenda view ---------- */
226
+ .oneuptime-calendar .rbc-agenda-view table.rbc-agenda-table {
227
+ border: none;
228
+ }
229
+
230
+ .oneuptime-calendar .rbc-agenda-view table.rbc-agenda-table thead > tr > th {
231
+ background-color: #f9fafb;
232
+ color: #6b7280;
233
+ font-size: 0.75rem;
234
+ font-weight: 600;
235
+ text-transform: uppercase;
236
+ letter-spacing: 0.04em;
237
+ padding: 0.625rem 0.75rem;
238
+ border-bottom: 1px solid #e5e7eb;
239
+ }
240
+
241
+ .oneuptime-calendar .rbc-agenda-view table.rbc-agenda-table tbody > tr > td {
242
+ padding: 0.625rem 0.75rem;
243
+ font-size: 0.8125rem;
244
+ color: #374151;
245
+ border-top: 1px solid #f3f4f6;
246
+ }
247
+
248
+ .oneuptime-calendar .rbc-agenda-view table.rbc-agenda-table tbody > tr + tr {
249
+ border-top: none;
250
+ }
251
+
252
+ .oneuptime-calendar .rbc-agenda-empty {
253
+ padding: 1.5rem;
254
+ text-align: center;
255
+ color: #9ca3af;
256
+ font-size: 0.875rem;
257
+ }
@@ -10,8 +10,10 @@ import {
10
10
  DateLocalizer,
11
11
  EventPropGetter,
12
12
  momentLocalizer,
13
+ View,
13
14
  } from "react-big-calendar";
14
15
  import "react-big-calendar/lib/css/react-big-calendar.css";
16
+ import "./Calendar.css";
15
17
 
16
18
  const localizer: DateLocalizer = momentLocalizer(moment);
17
19
 
@@ -29,6 +31,8 @@ export enum DefaultCalendarView {
29
31
  Agenda = "agenda",
30
32
  }
31
33
 
34
+ const CALENDAR_VIEWS: View[] = ["month", "week", "day", "agenda"];
35
+
32
36
  const CalendarElement: FunctionComponent<ComponentProps> = (
33
37
  props: ComponentProps,
34
38
  ): ReactElement => {
@@ -43,33 +47,40 @@ const CalendarElement: FunctionComponent<ComponentProps> = (
43
47
  ): { className?: string | undefined; style?: React.CSSProperties } => {
44
48
  const backgroundColor: string =
45
49
  event.color?.toString() || Blue500.toString();
50
+
51
+ const computedTextColor: string =
52
+ event.textColor?.toString() ||
53
+ (Color.shouldUseDarkText(new Color(backgroundColor))
54
+ ? "#111827"
55
+ : "#ffffff");
56
+
46
57
  const style: React.CSSProperties = {
47
- backgroundColor: backgroundColor,
48
- borderRadius: "0px",
49
- opacity: 0.8,
50
- color:
51
- event.textColor?.toString() ||
52
- Color.shouldUseDarkText(new Color(backgroundColor))
53
- ? "#000000"
54
- : "#ffffff",
58
+ backgroundColor,
59
+ color: computedTextColor,
60
+ borderRadius: "0.375rem",
55
61
  border: "0px",
56
62
  display: "block",
57
63
  };
58
64
 
59
65
  return {
60
- style: style,
66
+ style,
61
67
  };
62
68
  };
63
69
 
64
70
  return (
65
- <div id={props.id} className="mt-5 h-[42rem]">
71
+ <div
72
+ id={props.id}
73
+ className="oneuptime-calendar mt-5 h-[42rem] rounded-xl bg-white"
74
+ >
66
75
  <Calendar
67
76
  defaultDate={defaultDate}
68
77
  events={props.events}
69
78
  localizer={localizer}
70
79
  showMultiDayTimes
71
- defaultView={props.defaultCalendarView || "day"}
80
+ views={CALENDAR_VIEWS}
81
+ defaultView={props.defaultCalendarView || "week"}
72
82
  eventPropGetter={eventStyleGetter}
83
+ popup
73
84
  onRangeChange={(range: Date[] | { start: Date; end: Date }) => {
74
85
  if (Array.isArray(range)) {
75
86
  return props.onRangeChange({
@@ -113,6 +113,20 @@ const percentUnits: Array<UnitDefinition> = [
113
113
  aliases: ["%", "percent"],
114
114
  toCanonical: 1,
115
115
  },
116
+ /*
117
+ * UCUM "1" is OTel's dimensionless marker for ratio metrics — values in
118
+ * [0, 1] that represent a fraction (e.g. system.filesystem.utilization).
119
+ * Treating it as part of the percent family lets the threshold UI offer
120
+ * both "%" and "Fraction (0-1)" as input units. One unit of "1" equals
121
+ * 100% in the canonical, so a fraction threshold of 0.5 converts to a
122
+ * stored canonical value of 50.
123
+ */
124
+ {
125
+ value: "1",
126
+ label: "Fraction (0-1)",
127
+ aliases: ["1"],
128
+ toCanonical: 100,
129
+ },
116
130
  ];
117
131
 
118
132
  const bitUnits: Array<UnitDefinition> = [
@@ -206,6 +220,11 @@ export default class MetricUnitUtil {
206
220
  * is what the threshold dropdown should default to when nothing is
207
221
  * selected yet. Returns the input unit unchanged if the family is
208
222
  * unknown.
223
+ *
224
+ * Special case for fraction metrics (unit "1"): users think in percent,
225
+ * not 0-1 fractions, so the dropdown defaults to "%" rather than the
226
+ * raw "1" — which used to render an unlabelled "1" next to the value
227
+ * input and read as a typo.
209
228
  */
210
229
  public static getCanonicalUnitValue(
211
230
  metricUnit: string | undefined,
@@ -223,6 +242,11 @@ export default class MetricUnitUtil {
223
242
  metricUnit,
224
243
  family,
225
244
  );
245
+
246
+ if (family === percentUnits && def?.value === "1") {
247
+ return "%";
248
+ }
249
+
226
250
  return def?.value || metricUnit;
227
251
  }
228
252
 
@@ -118,6 +118,7 @@ import ProjectUserProfile from "./ProjectUserProfile";
118
118
  import ProjectSmtpConfig from "./ProjectSmtpConfig";
119
119
  //SSO
120
120
  import ProjectSSO from "./ProjectSso";
121
+ import ProjectOIDC from "./ProjectOidc";
121
122
  import PromoCode from "./PromoCode";
122
123
  import EnterpriseLicense from "./EnterpriseLicense";
123
124
  import OpenSourceDeployment from "./OpenSourceDeployment";
@@ -405,6 +406,7 @@ const AllModelTypes = [
405
406
  WorkflowVariables,
406
407
  WorkflowLog,
407
408
  ProjectSSO,
409
+ ProjectOIDC,
408
410
  StatusPageSSO,
409
411
  StatusPageSCIM,
410
412
  MonitorProbe,