@oneuptime/common 11.4.0 → 11.4.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 (178) hide show
  1. package/Server/API/AIAgentTaskAPI.ts +10 -24
  2. package/Server/API/TelemetryAPI.ts +4 -3
  3. package/Server/Services/AIAgentTaskService.ts +62 -0
  4. package/Server/Services/AnalyticsDatabaseService.ts +27 -0
  5. package/Server/Services/LogAggregationService.ts +29 -11
  6. package/Server/Services/MetricService.ts +603 -13
  7. package/Server/Utils/AI/Sentinel/IncidentInvestigationRunner.ts +1 -1
  8. package/Server/Utils/AI/Sentinel/README.md +10 -0
  9. package/Server/Utils/Monitor/Criteria/APIRequestCriteria.ts +44 -0
  10. package/Server/Utils/Monitor/MonitorMetricUtil.ts +108 -2
  11. package/Tests/Server/Services/AIAgentTaskServiceClaim.test.ts +146 -0
  12. package/Tests/Server/Services/LogAggregationService.test.ts +22 -0
  13. package/Tests/Server/Services/MetricServiceAggregate.test.ts +194 -0
  14. package/Tests/Types/AI/AIAgentTaskStatus.test.ts +86 -0
  15. package/Tests/Types/BaseDatabase/AggregationIntervalUtil.test.ts +127 -0
  16. package/Tests/Types/BaseDatabase/DatabaseCommonInteractionPropsUtil.test.ts +152 -0
  17. package/Tests/Types/Trace/TraceRecordingRuleDefinition.test.ts +308 -0
  18. package/Tests/UI/Components/Dropdown.test.tsx +17 -0
  19. package/Tests/UI/Components/EntityDropdown.test.tsx +130 -0
  20. package/Tests/UI/Components/Modal.test.tsx +144 -2
  21. package/Tests/UI/Components/TelemetrySearchBar.test.tsx +90 -0
  22. package/Tests/UI/Utils/Theme.test.ts +161 -0
  23. package/Types/BaseDatabase/AggregateBy.ts +12 -0
  24. package/Types/Dashboard/DashboardComponentType.ts +1 -0
  25. package/Types/Dashboard/DashboardComponents/ComponentArgument.ts +1 -0
  26. package/Types/Dashboard/DashboardComponents/DashboardLogChartComponent.ts +19 -0
  27. package/Types/Dashboard/DashboardTemplates.ts +9 -1
  28. package/Types/Monitor/CriteriaFilter.ts +4 -0
  29. package/Types/Monitor/HttpPhaseTimings.ts +16 -0
  30. package/Types/Monitor/MonitorMetricType.ts +17 -0
  31. package/Types/Monitor/PingMonitor/PingMonitorResponse.ts +20 -0
  32. package/Types/Probe/ProbeMonitorResponse.ts +14 -0
  33. package/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.tsx +8 -5
  34. package/UI/Components/Charts/ChartLibrary/BarChart/BarChart.tsx +4 -1
  35. package/UI/Components/Charts/ChartLibrary/LineChart/LineChart.tsx +6 -3
  36. package/UI/Components/CodeBlock/CodeBlock.tsx +2 -1
  37. package/UI/Components/CodeEditor/CodeEditor.tsx +3 -0
  38. package/UI/Components/Divider/Divider.tsx +1 -1
  39. package/UI/Components/Dropdown/Dropdown.tsx +13 -4
  40. package/UI/Components/EntityDropdown/EntityDropdown.tsx +110 -2
  41. package/UI/Components/ErrorBoundary.tsx +2 -1
  42. package/UI/Components/EventItem/EventItem.tsx +2 -2
  43. package/UI/Components/GanttChart/Bar/Index.tsx +1 -1
  44. package/UI/Components/Graphs/UptimeBarTooltip.tsx +30 -23
  45. package/UI/Components/Header/Header.tsx +7 -0
  46. package/UI/Components/Header/HeaderIconDropdownButton.tsx +2 -0
  47. package/UI/Components/Label/Label.tsx +2 -2
  48. package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +22 -10
  49. package/UI/Components/LogsViewer/components/LogsHistogram.tsx +9 -3
  50. package/UI/Components/Markdown.tsx/MarkdownEditor.tsx +8 -8
  51. package/UI/Components/Markdown.tsx/MarkdownViewer.tsx +44 -20
  52. package/UI/Components/Modal/ConfirmModal.tsx +1 -1
  53. package/UI/Components/Modal/Modal.tsx +213 -121
  54. package/UI/Components/Modal/ModalFooter.tsx +33 -22
  55. package/UI/Components/ModelTable/TableView.tsx +1 -1
  56. package/UI/Components/MonitorGraphs/UptimeBarDayModal.tsx +20 -12
  57. package/UI/Components/Pill/Pill.tsx +21 -13
  58. package/UI/Components/StatusBubble/StatusBubble.tsx +3 -3
  59. package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +7 -1
  60. package/UI/Components/TelemetryViewer/components/TelemetryHistogram.tsx +9 -3
  61. package/UI/Components/TelemetryViewer/components/TelemetrySearchBar.tsx +62 -4
  62. package/UI/Components/Workflow/Component.tsx +53 -33
  63. package/UI/Components/Workflow/ComponentPortViewer.tsx +6 -6
  64. package/UI/Components/Workflow/ComponentReturnValueViewer.tsx +8 -8
  65. package/UI/Components/Workflow/ComponentsModal.tsx +16 -9
  66. package/UI/Components/Workflow/Workflow.tsx +13 -11
  67. package/UI/Utils/Theme.ts +140 -0
  68. package/Utils/Dashboard/Components/DashboardLogChartComponent.ts +104 -0
  69. package/Utils/Dashboard/Components/Index.ts +7 -0
  70. package/Utils/Monitor/MonitorMetricType.ts +70 -2
  71. package/build/dist/Server/API/AIAgentTaskAPI.js +8 -23
  72. package/build/dist/Server/API/AIAgentTaskAPI.js.map +1 -1
  73. package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
  74. package/build/dist/Server/Services/AIAgentTaskService.js +59 -0
  75. package/build/dist/Server/Services/AIAgentTaskService.js.map +1 -1
  76. package/build/dist/Server/Services/AnalyticsDatabaseService.js +19 -0
  77. package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
  78. package/build/dist/Server/Services/LogAggregationService.js +21 -7
  79. package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
  80. package/build/dist/Server/Services/MetricService.js +449 -7
  81. package/build/dist/Server/Services/MetricService.js.map +1 -1
  82. package/build/dist/Server/Utils/AI/Sentinel/IncidentInvestigationRunner.js +1 -1
  83. package/build/dist/Server/Utils/Monitor/Criteria/APIRequestCriteria.js +29 -0
  84. package/build/dist/Server/Utils/Monitor/Criteria/APIRequestCriteria.js.map +1 -1
  85. package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js +86 -2
  86. package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js.map +1 -1
  87. package/build/dist/Types/Dashboard/DashboardComponentType.js +1 -0
  88. package/build/dist/Types/Dashboard/DashboardComponentType.js.map +1 -1
  89. package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js +1 -0
  90. package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js.map +1 -1
  91. package/build/dist/Types/Dashboard/DashboardComponents/DashboardLogChartComponent.js +2 -0
  92. package/build/dist/Types/Dashboard/DashboardComponents/DashboardLogChartComponent.js.map +1 -0
  93. package/build/dist/Types/Dashboard/DashboardTemplates.js +9 -1
  94. package/build/dist/Types/Dashboard/DashboardTemplates.js.map +1 -1
  95. package/build/dist/Types/Monitor/CriteriaFilter.js +4 -0
  96. package/build/dist/Types/Monitor/CriteriaFilter.js.map +1 -1
  97. package/build/dist/Types/Monitor/HttpPhaseTimings.js +2 -0
  98. package/build/dist/Types/Monitor/HttpPhaseTimings.js.map +1 -0
  99. package/build/dist/Types/Monitor/MonitorMetricType.js +15 -0
  100. package/build/dist/Types/Monitor/MonitorMetricType.js.map +1 -1
  101. package/build/dist/Types/Monitor/PingMonitor/PingMonitorResponse.js +2 -0
  102. package/build/dist/Types/Monitor/PingMonitor/PingMonitorResponse.js.map +1 -0
  103. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js +7 -4
  104. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js.map +1 -1
  105. package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js +4 -1
  106. package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js.map +1 -1
  107. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js +6 -3
  108. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js.map +1 -1
  109. package/build/dist/UI/Components/CodeBlock/CodeBlock.js +1 -1
  110. package/build/dist/UI/Components/CodeBlock/CodeBlock.js.map +1 -1
  111. package/build/dist/UI/Components/CodeEditor/CodeEditor.js +3 -1
  112. package/build/dist/UI/Components/CodeEditor/CodeEditor.js.map +1 -1
  113. package/build/dist/UI/Components/Divider/Divider.js +1 -1
  114. package/build/dist/UI/Components/Divider/Divider.js.map +1 -1
  115. package/build/dist/UI/Components/Dropdown/Dropdown.js +10 -4
  116. package/build/dist/UI/Components/Dropdown/Dropdown.js.map +1 -1
  117. package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js +76 -3
  118. package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js.map +1 -1
  119. package/build/dist/UI/Components/ErrorBoundary.js +2 -1
  120. package/build/dist/UI/Components/ErrorBoundary.js.map +1 -1
  121. package/build/dist/UI/Components/EventItem/EventItem.js +2 -2
  122. package/build/dist/UI/Components/EventItem/EventItem.js.map +1 -1
  123. package/build/dist/UI/Components/GanttChart/Bar/Index.js +1 -1
  124. package/build/dist/UI/Components/GanttChart/Bar/Index.js.map +1 -1
  125. package/build/dist/UI/Components/Graphs/UptimeBarTooltip.js +29 -23
  126. package/build/dist/UI/Components/Graphs/UptimeBarTooltip.js.map +1 -1
  127. package/build/dist/UI/Components/Header/Header.js +1 -0
  128. package/build/dist/UI/Components/Header/Header.js.map +1 -1
  129. package/build/dist/UI/Components/Header/HeaderIconDropdownButton.js +1 -1
  130. package/build/dist/UI/Components/Header/HeaderIconDropdownButton.js.map +1 -1
  131. package/build/dist/UI/Components/Label/Label.js +2 -2
  132. package/build/dist/UI/Components/Label/Label.js.map +1 -1
  133. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +20 -8
  134. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
  135. package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js +8 -2
  136. package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js.map +1 -1
  137. package/build/dist/UI/Components/Markdown.tsx/MarkdownEditor.js +8 -8
  138. package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js +40 -20
  139. package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js.map +1 -1
  140. package/build/dist/UI/Components/Modal/ConfirmModal.js +1 -1
  141. package/build/dist/UI/Components/Modal/ConfirmModal.js.map +1 -1
  142. package/build/dist/UI/Components/Modal/Modal.js +109 -51
  143. package/build/dist/UI/Components/Modal/Modal.js.map +1 -1
  144. package/build/dist/UI/Components/Modal/ModalFooter.js +13 -10
  145. package/build/dist/UI/Components/Modal/ModalFooter.js.map +1 -1
  146. package/build/dist/UI/Components/ModelTable/TableView.js +1 -1
  147. package/build/dist/UI/Components/ModelTable/TableView.js.map +1 -1
  148. package/build/dist/UI/Components/MonitorGraphs/UptimeBarDayModal.js +17 -12
  149. package/build/dist/UI/Components/MonitorGraphs/UptimeBarDayModal.js.map +1 -1
  150. package/build/dist/UI/Components/Pill/Pill.js +12 -13
  151. package/build/dist/UI/Components/Pill/Pill.js.map +1 -1
  152. package/build/dist/UI/Components/StatusBubble/StatusBubble.js +3 -3
  153. package/build/dist/UI/Components/StatusBubble/StatusBubble.js.map +1 -1
  154. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +1 -1
  155. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
  156. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js +8 -2
  157. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js.map +1 -1
  158. package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchBar.js +36 -6
  159. package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchBar.js.map +1 -1
  160. package/build/dist/UI/Components/Workflow/Component.js +43 -33
  161. package/build/dist/UI/Components/Workflow/Component.js.map +1 -1
  162. package/build/dist/UI/Components/Workflow/ComponentPortViewer.js +6 -6
  163. package/build/dist/UI/Components/Workflow/ComponentPortViewer.js.map +1 -1
  164. package/build/dist/UI/Components/Workflow/ComponentReturnValueViewer.js +8 -8
  165. package/build/dist/UI/Components/Workflow/ComponentReturnValueViewer.js.map +1 -1
  166. package/build/dist/UI/Components/Workflow/ComponentsModal.js +15 -9
  167. package/build/dist/UI/Components/Workflow/ComponentsModal.js.map +1 -1
  168. package/build/dist/UI/Components/Workflow/Workflow.js +13 -11
  169. package/build/dist/UI/Components/Workflow/Workflow.js.map +1 -1
  170. package/build/dist/UI/Utils/Theme.js +106 -0
  171. package/build/dist/UI/Utils/Theme.js.map +1 -0
  172. package/build/dist/Utils/Dashboard/Components/DashboardLogChartComponent.js +92 -0
  173. package/build/dist/Utils/Dashboard/Components/DashboardLogChartComponent.js.map +1 -0
  174. package/build/dist/Utils/Dashboard/Components/Index.js +4 -0
  175. package/build/dist/Utils/Dashboard/Components/Index.js.map +1 -1
  176. package/build/dist/Utils/Monitor/MonitorMetricType.js +70 -3
  177. package/build/dist/Utils/Monitor/MonitorMetricType.js.map +1 -1
  178. package/package.json +1 -1
@@ -0,0 +1,127 @@
1
+ import AggregationInterval from "../../../Types/BaseDatabase/AggregationInterval";
2
+ import AggregationIntervalUtil from "../../../Types/BaseDatabase/AggregationIntervalUtil";
3
+
4
+ describe("AggregationIntervalUtil", () => {
5
+ const SECOND: number = 1000;
6
+ const HOUR: number = SECOND * 60 * 60;
7
+ const DAY: number = HOUR * 24;
8
+
9
+ const windowOf: (durationMs: number) => { startDate: Date; endDate: Date } = (
10
+ durationMs: number,
11
+ ): { startDate: Date; endDate: Date } => {
12
+ const startDate: Date = new Date(Date.UTC(2020, 0, 1, 0, 0, 0));
13
+ const endDate: Date = new Date(startDate.getTime() + durationMs);
14
+ return { startDate, endDate };
15
+ };
16
+
17
+ describe("getAggregationIntervalForWindow", () => {
18
+ const cases: Array<[string, number, AggregationInterval]> = [
19
+ ["2 hours -> Minute", 2 * HOUR, AggregationInterval.Minute],
20
+ [
21
+ "exactly 3 hours -> Minute (inclusive bound)",
22
+ 3 * HOUR,
23
+ AggregationInterval.Minute,
24
+ ],
25
+ ["3 hours + 1ms -> Hour", 3 * HOUR + 1, AggregationInterval.Hour],
26
+ ["1 day -> Hour", DAY, AggregationInterval.Hour],
27
+ [
28
+ "exactly 7 days -> Hour (inclusive bound)",
29
+ 7 * DAY,
30
+ AggregationInterval.Hour,
31
+ ],
32
+ ["7 days + 1ms -> Day", 7 * DAY + 1, AggregationInterval.Day],
33
+ ["14 days -> Day", 14 * DAY, AggregationInterval.Day],
34
+ [
35
+ "exactly 6 weeks -> Day (inclusive bound)",
36
+ 42 * DAY,
37
+ AggregationInterval.Day,
38
+ ],
39
+ ["6 weeks + 1ms -> Week", 42 * DAY + 1, AggregationInterval.Week],
40
+ ["60 days -> Week", 60 * DAY, AggregationInterval.Week],
41
+ ["200 days -> Month", 200 * DAY, AggregationInterval.Month],
42
+ ["8 years -> Year", 8 * 365 * DAY, AggregationInterval.Year],
43
+ ];
44
+
45
+ test.each(cases)(
46
+ "%s",
47
+ (_label: string, durationMs: number, expected: AggregationInterval) => {
48
+ expect(
49
+ AggregationIntervalUtil.getAggregationIntervalForWindow(
50
+ windowOf(durationMs),
51
+ ),
52
+ ).toBe(expected);
53
+ },
54
+ );
55
+
56
+ test("returns the widest interval as the window grows", () => {
57
+ const order: Array<AggregationInterval> = [
58
+ AggregationInterval.Minute,
59
+ AggregationInterval.Hour,
60
+ AggregationInterval.Day,
61
+ AggregationInterval.Week,
62
+ AggregationInterval.Month,
63
+ AggregationInterval.Year,
64
+ ];
65
+ const durations: Array<number> = [
66
+ HOUR,
67
+ 2 * DAY,
68
+ 30 * DAY,
69
+ 90 * DAY,
70
+ 365 * DAY,
71
+ 10 * 365 * DAY,
72
+ ];
73
+
74
+ const results: Array<AggregationInterval> = durations.map(
75
+ (d: number): AggregationInterval => {
76
+ return AggregationIntervalUtil.getAggregationIntervalForWindow(
77
+ windowOf(d),
78
+ );
79
+ },
80
+ );
81
+
82
+ // Each larger window is at least as wide an interval as the previous.
83
+ for (let i: number = 1; i < results.length; i++) {
84
+ expect(order.indexOf(results[i]!)).toBeGreaterThanOrEqual(
85
+ order.indexOf(results[i - 1]!),
86
+ );
87
+ }
88
+ });
89
+ });
90
+
91
+ describe("getAggregationIntervalMs", () => {
92
+ test.each([
93
+ [AggregationInterval.Minute, 1000 * 60],
94
+ [AggregationInterval.Hour, 1000 * 60 * 60],
95
+ [AggregationInterval.Day, 1000 * 60 * 60 * 24],
96
+ [AggregationInterval.Week, 1000 * 60 * 60 * 24 * 7],
97
+ [AggregationInterval.Month, 1000 * 60 * 60 * 24 * 30],
98
+ [AggregationInterval.Year, 1000 * 60 * 60 * 24 * 365],
99
+ ])(
100
+ "maps %s to its nominal width",
101
+ (interval: AggregationInterval, expectedMs: number) => {
102
+ expect(AggregationIntervalUtil.getAggregationIntervalMs(interval)).toBe(
103
+ expectedMs,
104
+ );
105
+ },
106
+ );
107
+
108
+ test("intervals are strictly increasing in width", () => {
109
+ const ordered: Array<AggregationInterval> = [
110
+ AggregationInterval.Minute,
111
+ AggregationInterval.Hour,
112
+ AggregationInterval.Day,
113
+ AggregationInterval.Week,
114
+ AggregationInterval.Month,
115
+ AggregationInterval.Year,
116
+ ];
117
+
118
+ for (let i: number = 1; i < ordered.length; i++) {
119
+ expect(
120
+ AggregationIntervalUtil.getAggregationIntervalMs(ordered[i]!),
121
+ ).toBeGreaterThan(
122
+ AggregationIntervalUtil.getAggregationIntervalMs(ordered[i - 1]!),
123
+ );
124
+ }
125
+ });
126
+ });
127
+ });
@@ -0,0 +1,152 @@
1
+ import DatabaseCommonInteractionProps from "../../../Types/BaseDatabase/DatabaseCommonInteractionProps";
2
+ import DatabaseCommonInteractionPropsUtil, {
3
+ PermissionType,
4
+ } from "../../../Types/BaseDatabase/DatabaseCommonInteractionPropsUtil";
5
+ import ObjectID from "../../../Types/ObjectID";
6
+ import Permission, {
7
+ UserPermission,
8
+ UserTenantAccessPermission,
9
+ } from "../../../Types/Permission";
10
+
11
+ describe("DatabaseCommonInteractionPropsUtil.getUserPermissions", () => {
12
+ const makePerm: (
13
+ permission: Permission,
14
+ isBlockPermission: boolean,
15
+ ) => UserPermission = (
16
+ permission: Permission,
17
+ isBlockPermission: boolean,
18
+ ): UserPermission => {
19
+ return {
20
+ _type: "UserPermission",
21
+ permission,
22
+ labelIds: [],
23
+ isBlockPermission,
24
+ };
25
+ };
26
+
27
+ const permissionsOf: (result: Array<UserPermission>) => Array<Permission> = (
28
+ result: Array<UserPermission>,
29
+ ): Array<Permission> => {
30
+ return result.map((p: UserPermission): Permission => {
31
+ return p.permission;
32
+ });
33
+ };
34
+
35
+ describe("global permissions", () => {
36
+ test("always grants Public for an anonymous Allow request", () => {
37
+ const props: DatabaseCommonInteractionProps = {};
38
+
39
+ const perms: Array<Permission> = permissionsOf(
40
+ DatabaseCommonInteractionPropsUtil.getUserPermissions(
41
+ props,
42
+ PermissionType.Allow,
43
+ ),
44
+ );
45
+
46
+ expect(perms).toContain(Permission.Public);
47
+ });
48
+
49
+ test("adds CurrentUser when a user is logged in", () => {
50
+ const props: DatabaseCommonInteractionProps = {
51
+ userId: ObjectID.generate(),
52
+ };
53
+
54
+ const perms: Array<Permission> = permissionsOf(
55
+ DatabaseCommonInteractionPropsUtil.getUserPermissions(
56
+ props,
57
+ PermissionType.Allow,
58
+ ),
59
+ );
60
+
61
+ expect(perms).toContain(Permission.Public);
62
+ expect(perms).toContain(Permission.CurrentUser);
63
+ });
64
+
65
+ test("preserves existing global permissions and appends Public", () => {
66
+ const props: DatabaseCommonInteractionProps = {
67
+ userGlobalAccessPermission: {
68
+ _type: "UserGlobalAccessPermission",
69
+ projectIds: [],
70
+ globalPermissions: [Permission.ProjectOwner],
71
+ },
72
+ };
73
+
74
+ const perms: Array<Permission> = permissionsOf(
75
+ DatabaseCommonInteractionPropsUtil.getUserPermissions(
76
+ props,
77
+ PermissionType.Allow,
78
+ ),
79
+ );
80
+
81
+ expect(perms).toContain(Permission.ProjectOwner);
82
+ expect(perms).toContain(Permission.Public);
83
+ });
84
+
85
+ test("does not include global permissions for a Block request", () => {
86
+ const props: DatabaseCommonInteractionProps = {};
87
+
88
+ expect(
89
+ DatabaseCommonInteractionPropsUtil.getUserPermissions(
90
+ props,
91
+ PermissionType.Block,
92
+ ),
93
+ ).toEqual([]);
94
+ });
95
+ });
96
+
97
+ describe("tenant permissions", () => {
98
+ const buildTenantProps: () => {
99
+ props: DatabaseCommonInteractionProps;
100
+ tenantId: ObjectID;
101
+ } = (): {
102
+ props: DatabaseCommonInteractionProps;
103
+ tenantId: ObjectID;
104
+ } => {
105
+ const tenantId: ObjectID = ObjectID.generate();
106
+ const tenantPermission: UserTenantAccessPermission = {
107
+ _type: "UserTenantAccessPermission",
108
+ projectId: tenantId,
109
+ permissions: [
110
+ makePerm(Permission.ProjectMember, false), // allow row
111
+ makePerm(Permission.ProjectOwner, true), // block row
112
+ ],
113
+ };
114
+
115
+ return {
116
+ tenantId,
117
+ props: {
118
+ tenantId,
119
+ userTenantAccessPermission: {
120
+ [tenantId.toString()]: tenantPermission,
121
+ },
122
+ },
123
+ };
124
+ };
125
+
126
+ test("Allow includes the tenant's non-block permissions (plus global)", () => {
127
+ const perms: Array<Permission> = permissionsOf(
128
+ DatabaseCommonInteractionPropsUtil.getUserPermissions(
129
+ buildTenantProps().props,
130
+ PermissionType.Allow,
131
+ ),
132
+ );
133
+
134
+ expect(perms).toContain(Permission.ProjectMember); // allow tenant row
135
+ expect(perms).toContain(Permission.Public); // global
136
+ expect(perms).not.toContain(Permission.ProjectOwner); // block row excluded
137
+ });
138
+
139
+ test("Block includes only the tenant's block permissions", () => {
140
+ const perms: Array<Permission> = permissionsOf(
141
+ DatabaseCommonInteractionPropsUtil.getUserPermissions(
142
+ buildTenantProps().props,
143
+ PermissionType.Block,
144
+ ),
145
+ );
146
+
147
+ expect(perms).toContain(Permission.ProjectOwner); // block tenant row
148
+ expect(perms).not.toContain(Permission.ProjectMember); // allow row excluded
149
+ expect(perms).not.toContain(Permission.Public); // no global for Block
150
+ });
151
+ });
152
+ });
@@ -0,0 +1,308 @@
1
+ import TraceAggregationType from "../../../Types/Trace/TraceAggregationType";
2
+ import TraceRecordingRuleDefinition, {
3
+ TraceRecordingRuleDefinitionUtil,
4
+ TraceRecordingRuleSource,
5
+ TraceRecordingRuleAttributeFilter,
6
+ TRACE_RECORDING_RULE_MAX_SOURCES,
7
+ TRACE_RECORDING_RULE_MAX_EXPRESSION_LENGTH,
8
+ } from "../../../Types/Trace/TraceRecordingRuleDefinition";
9
+
10
+ describe("TraceRecordingRuleDefinitionUtil", () => {
11
+ const makeSource: (
12
+ overrides?: Partial<TraceRecordingRuleSource>,
13
+ ) => TraceRecordingRuleSource = (
14
+ overrides: Partial<TraceRecordingRuleSource> = {},
15
+ ): TraceRecordingRuleSource => {
16
+ return {
17
+ alias: "A",
18
+ aggregationType: TraceAggregationType.Count,
19
+ ...overrides,
20
+ };
21
+ };
22
+
23
+ describe("getAggregationOptions", () => {
24
+ test("every option has a valid value, label and description", () => {
25
+ const options: Array<{
26
+ value: TraceAggregationType;
27
+ label: string;
28
+ description: string;
29
+ }> = TraceRecordingRuleDefinitionUtil.getAggregationOptions();
30
+
31
+ expect(options.length).toBeGreaterThan(0);
32
+ for (const option of options) {
33
+ expect(option.label.length).toBeGreaterThan(0);
34
+ expect(option.description.length).toBeGreaterThan(0);
35
+ expect(Object.values(TraceAggregationType)).toContain(option.value);
36
+ }
37
+ });
38
+ });
39
+
40
+ describe("getSpanKindOptions", () => {
41
+ test("includes an 'Any' option with an empty value", () => {
42
+ const options: Array<{ value: string; label: string }> =
43
+ TraceRecordingRuleDefinitionUtil.getSpanKindOptions();
44
+
45
+ expect(
46
+ options.some((o: { value: string }): boolean => {
47
+ return o.value === "";
48
+ }),
49
+ ).toBe(true);
50
+ expect(
51
+ options.some((o: { value: string }): boolean => {
52
+ return o.value === "SPAN_KIND_SERVER";
53
+ }),
54
+ ).toBe(true);
55
+ });
56
+ });
57
+
58
+ describe("getSourceAttributeFilters", () => {
59
+ test("returns the multi-filter entries, trimmed", () => {
60
+ const filters: Array<TraceRecordingRuleAttributeFilter> =
61
+ TraceRecordingRuleDefinitionUtil.getSourceAttributeFilters(
62
+ makeSource({
63
+ filterAttributes: [{ key: " http.route ", value: " /api " }],
64
+ }),
65
+ );
66
+
67
+ expect(filters).toEqual([{ key: "http.route", value: "/api" }]);
68
+ });
69
+
70
+ test("drops entries with a missing key or value, and deduplicates by key", () => {
71
+ const filters: Array<TraceRecordingRuleAttributeFilter> =
72
+ TraceRecordingRuleDefinitionUtil.getSourceAttributeFilters(
73
+ makeSource({
74
+ filterAttributes: [
75
+ { key: "host", value: "a" },
76
+ { key: "host", value: "b" }, // duplicate key — dropped
77
+ { key: "empty", value: "" }, // no value — dropped
78
+ ],
79
+ }),
80
+ );
81
+
82
+ expect(filters).toEqual([{ key: "host", value: "a" }]);
83
+ });
84
+
85
+ test("merges the legacy single filter when its key is not already present", () => {
86
+ const filters: Array<TraceRecordingRuleAttributeFilter> =
87
+ TraceRecordingRuleDefinitionUtil.getSourceAttributeFilters(
88
+ makeSource({
89
+ filterAttributes: [{ key: "host", value: "a" }],
90
+ filterAttributeKey: "env",
91
+ filterAttributeValue: "prod",
92
+ }),
93
+ );
94
+
95
+ expect(filters).toEqual([
96
+ { key: "host", value: "a" },
97
+ { key: "env", value: "prod" },
98
+ ]);
99
+ });
100
+
101
+ test("array entry wins over the legacy filter for the same key", () => {
102
+ const filters: Array<TraceRecordingRuleAttributeFilter> =
103
+ TraceRecordingRuleDefinitionUtil.getSourceAttributeFilters(
104
+ makeSource({
105
+ filterAttributes: [{ key: "env", value: "staging" }],
106
+ filterAttributeKey: "env",
107
+ filterAttributeValue: "prod",
108
+ }),
109
+ );
110
+
111
+ expect(filters).toEqual([{ key: "env", value: "staging" }]);
112
+ });
113
+ });
114
+
115
+ describe("getNextAlias", () => {
116
+ test("returns A when empty and the first free letter otherwise", () => {
117
+ expect(TraceRecordingRuleDefinitionUtil.getNextAlias(undefined)).toBe(
118
+ "A",
119
+ );
120
+ expect(
121
+ TraceRecordingRuleDefinitionUtil.getNextAlias([
122
+ makeSource({ alias: "A" }),
123
+ makeSource({ alias: "C" }),
124
+ ]),
125
+ ).toBe("B");
126
+ });
127
+ });
128
+
129
+ describe("getEmptyDefinition", () => {
130
+ test("returns a single Count source aliased A that validates", () => {
131
+ const def: TraceRecordingRuleDefinition =
132
+ TraceRecordingRuleDefinitionUtil.getEmptyDefinition();
133
+
134
+ expect(def.sources.length).toBe(1);
135
+ expect(def.sources[0]!.alias).toBe("A");
136
+ expect(def.sources[0]!.aggregationType).toBe(TraceAggregationType.Count);
137
+ expect(def.expression).toBe("A");
138
+ expect(
139
+ TraceRecordingRuleDefinitionUtil.getValidationError(def),
140
+ ).toBeNull();
141
+ });
142
+ });
143
+
144
+ describe("getValidationError", () => {
145
+ const validDefinition: () => TraceRecordingRuleDefinition =
146
+ (): TraceRecordingRuleDefinition => {
147
+ return {
148
+ sources: [makeSource({ alias: "A" }), makeSource({ alias: "B" })],
149
+ expression: "A / B * 100",
150
+ };
151
+ };
152
+
153
+ test("accepts a valid definition", () => {
154
+ expect(
155
+ TraceRecordingRuleDefinitionUtil.getValidationError(validDefinition()),
156
+ ).toBeNull();
157
+ });
158
+
159
+ test("requires a definition", () => {
160
+ expect(
161
+ TraceRecordingRuleDefinitionUtil.getValidationError(undefined),
162
+ ).toBe("Definition is required.");
163
+ });
164
+
165
+ test("requires at least one source", () => {
166
+ expect(
167
+ TraceRecordingRuleDefinitionUtil.getValidationError({
168
+ sources: [],
169
+ expression: "A",
170
+ }),
171
+ ).toBe("Add at least one source.");
172
+ });
173
+
174
+ test("rejects more than the maximum number of sources", () => {
175
+ const sources: Array<TraceRecordingRuleSource> = Array.from(
176
+ { length: TRACE_RECORDING_RULE_MAX_SOURCES + 1 },
177
+ (_unused: unknown, i: number) => {
178
+ return makeSource({ alias: "ABCDE"[i] as string });
179
+ },
180
+ );
181
+
182
+ expect(
183
+ TraceRecordingRuleDefinitionUtil.getValidationError({
184
+ sources,
185
+ expression: "A",
186
+ }),
187
+ ).toContain(`at most ${TRACE_RECORDING_RULE_MAX_SOURCES}`);
188
+ });
189
+
190
+ test("rejects an invalid alias", () => {
191
+ expect(
192
+ TraceRecordingRuleDefinitionUtil.getValidationError({
193
+ sources: [makeSource({ alias: "1" })],
194
+ expression: "A",
195
+ }),
196
+ ).toContain("single uppercase letter");
197
+ });
198
+
199
+ test("rejects duplicate aliases", () => {
200
+ expect(
201
+ TraceRecordingRuleDefinitionUtil.getValidationError({
202
+ sources: [makeSource({ alias: "A" }), makeSource({ alias: "A" })],
203
+ expression: "A",
204
+ }),
205
+ ).toContain("Duplicate alias");
206
+ });
207
+
208
+ test("requires an aggregation type", () => {
209
+ expect(
210
+ TraceRecordingRuleDefinitionUtil.getValidationError({
211
+ sources: [
212
+ {
213
+ alias: "A",
214
+ } as unknown as TraceRecordingRuleSource,
215
+ ],
216
+ expression: "A",
217
+ }),
218
+ ).toContain("Aggregation type is required");
219
+ });
220
+
221
+ test("requires both a legacy filter key and value, or neither", () => {
222
+ expect(
223
+ TraceRecordingRuleDefinitionUtil.getValidationError({
224
+ sources: [makeSource({ alias: "A", filterAttributeValue: "prod" })],
225
+ expression: "A",
226
+ }),
227
+ ).toContain("needs both a key and a value");
228
+ });
229
+
230
+ test("rejects a non-array filterAttributes", () => {
231
+ expect(
232
+ TraceRecordingRuleDefinitionUtil.getValidationError({
233
+ sources: [
234
+ makeSource({
235
+ alias: "A",
236
+ filterAttributes:
237
+ "nope" as unknown as Array<TraceRecordingRuleAttributeFilter>,
238
+ }),
239
+ ],
240
+ expression: "A",
241
+ }),
242
+ ).toContain("Invalid attribute filters");
243
+ });
244
+
245
+ test("rejects a half-specified filter row", () => {
246
+ expect(
247
+ TraceRecordingRuleDefinitionUtil.getValidationError({
248
+ sources: [
249
+ makeSource({
250
+ alias: "A",
251
+ filterAttributes: [{ key: "host", value: "" }],
252
+ }),
253
+ ],
254
+ expression: "A",
255
+ }),
256
+ ).toContain("remove the row");
257
+ });
258
+
259
+ test("requires an expression", () => {
260
+ expect(
261
+ TraceRecordingRuleDefinitionUtil.getValidationError({
262
+ sources: [makeSource({ alias: "A" })],
263
+ expression: " ",
264
+ }),
265
+ ).toBe("Expression is required.");
266
+ });
267
+
268
+ test("rejects an over-length expression", () => {
269
+ expect(
270
+ TraceRecordingRuleDefinitionUtil.getValidationError({
271
+ sources: [makeSource({ alias: "A" })],
272
+ expression: "A".repeat(
273
+ TRACE_RECORDING_RULE_MAX_EXPRESSION_LENGTH + 1,
274
+ ),
275
+ }),
276
+ ).toContain(
277
+ `${TRACE_RECORDING_RULE_MAX_EXPRESSION_LENGTH} characters or fewer`,
278
+ );
279
+ });
280
+
281
+ test("rejects an expression referencing an undefined alias", () => {
282
+ expect(
283
+ TraceRecordingRuleDefinitionUtil.getValidationError({
284
+ sources: [makeSource({ alias: "A" })],
285
+ expression: "A + B",
286
+ }),
287
+ ).toContain("references alias 'B'");
288
+ });
289
+
290
+ test("rejects an expression that references no alias", () => {
291
+ expect(
292
+ TraceRecordingRuleDefinitionUtil.getValidationError({
293
+ sources: [makeSource({ alias: "A" })],
294
+ expression: "5 + 10",
295
+ }),
296
+ ).toContain("must reference at least one source alias");
297
+ });
298
+
299
+ test("rejects an expression with characters outside the DSL grammar", () => {
300
+ expect(
301
+ TraceRecordingRuleDefinitionUtil.getValidationError({
302
+ sources: [makeSource({ alias: "A" }), makeSource({ alias: "B" })],
303
+ expression: "A & B",
304
+ }),
305
+ ).toContain("may only contain");
306
+ });
307
+ });
308
+ });
@@ -55,6 +55,23 @@ describe("Dropdown", () => {
55
55
  expect(await screen.findByText("2")).toBeInTheDocument();
56
56
  });
57
57
 
58
+ test("renders the menu portal above modal surfaces", async () => {
59
+ const { getByRole } = render(
60
+ <Dropdown onChange={() => {}} options={options} />,
61
+ );
62
+ const dropdown: HTMLElement = getByRole("combobox");
63
+
64
+ fireEvent.keyDown(dropdown, { key: "ArrowDown", code: "ArrowDown" });
65
+ await screen.findByText("1");
66
+
67
+ const menuPortal: HTMLElement | null = document.querySelector<HTMLElement>(
68
+ ".ou-select__menu-portal",
69
+ );
70
+
71
+ expect(menuPortal).toBeInTheDocument();
72
+ expect(menuPortal).toHaveStyle({ zIndex: "60" });
73
+ });
74
+
58
75
  test("renders placeholder", async () => {
59
76
  const { getByText } = render(
60
77
  <Dropdown