@oneuptime/common 12.0.34 → 13.0.0

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 (259) hide show
  1. package/Models/DatabaseModels/GlobalConfig.ts +12 -12
  2. package/Models/DatabaseModels/InventoryItem.ts +44 -1
  3. package/Models/DatabaseModels/NetworkDevice.ts +126 -0
  4. package/Server/EnvironmentConfig.ts +47 -16
  5. package/Server/Infrastructure/Postgres/SchemaMigrations/1791700000000-AddMacAddressToNetworkDevice.ts +25 -0
  6. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
  7. package/Server/Infrastructure/Redis.ts +8 -8
  8. package/Server/Infrastructure/Status.ts +10 -4
  9. package/Server/Services/DatabaseService.ts +65 -13
  10. package/Server/Services/InventoryItemService.ts +31 -1
  11. package/Server/Services/MonitorTemplateService.ts +140 -19
  12. package/Server/Services/NetworkDeviceAutoImportRuleEngineService.ts +349 -41
  13. package/Server/Services/NetworkDeviceDiscoveryScanService.ts +87 -2
  14. package/Server/Services/NetworkDeviceService.ts +69 -0
  15. package/Server/Services/UserNotificationRuleService.ts +76 -25
  16. package/Server/Types/Database/Permissions/ReadPermission.ts +45 -5
  17. package/Server/Types/Database/QueryUtil.ts +56 -0
  18. package/Server/Types/Markdown.ts +93 -1
  19. package/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.ts +82 -17
  20. package/Server/Utils/EmailRollup/EmailRollupFlushRunner.ts +10 -1
  21. package/Server/Utils/EmailRollup/EmailRollupRenderer.ts +2 -2
  22. package/Server/Utils/Monitor/Criteria/CompareCriteria.ts +82 -35
  23. package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +31 -5
  24. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +202 -37
  25. package/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.ts +82 -9
  26. package/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.ts +16 -7
  27. package/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.ts +29 -17
  28. package/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.ts +107 -48
  29. package/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.ts +158 -0
  30. package/Server/Utils/Monitor/NetworkInventoryUtil.ts +27 -0
  31. package/Server/Utils/Telemetry/EntityRegistry.ts +11 -1
  32. package/Server/Utils/Telemetry/TelemetryEntity.ts +83 -9
  33. package/Tests/App/Dashboard/AddNeighborToMonitoringModal.test.tsx +32 -0
  34. package/Tests/App/Dashboard/AdoptedDeviceDrawer.test.tsx +374 -0
  35. package/Tests/App/Dashboard/DashboardLabelVariables.test.tsx +580 -0
  36. package/Tests/App/Dashboard/DashboardMonitorLabelVariable.test.tsx +438 -0
  37. package/Tests/App/Dashboard/DashboardVariableAllContract.test.tsx +5 -2
  38. package/Tests/App/Dashboard/DeviceAttachmentCard.test.tsx +273 -0
  39. package/Tests/App/Dashboard/DiscoveryReviewInventoryRefresh.test.tsx +505 -0
  40. package/Tests/App/Dashboard/EntityDetailPanel.test.tsx +163 -0
  41. package/Tests/App/Dashboard/InfrastructureExplorer.test.tsx +401 -0
  42. package/Tests/App/Dashboard/InfrastructureGraph.test.tsx +232 -0
  43. package/Tests/App/Dashboard/InventoryItems.test.tsx +201 -0
  44. package/Tests/App/Dashboard/InventoryTypeAndStatusFacets.test.tsx +798 -0
  45. package/Tests/App/Dashboard/NetworkTopologyLiveView.test.tsx +320 -0
  46. package/Tests/App/Dashboard/NetworkTopologyToolbar.test.tsx +275 -0
  47. package/Tests/App/Dashboard/OnCallCalendarFeedEmptyState.test.tsx +975 -0
  48. package/Tests/App/Dashboard/OnCallCalendarFeedPlanGate.test.tsx +63 -0
  49. package/Tests/App/Dashboard/ServiceMapGraph.test.tsx +626 -0
  50. package/Tests/App/Dashboard/TopologyDataLoading.test.tsx +348 -0
  51. package/Tests/App/Dashboard/TopologyPageNavigation.test.tsx +331 -0
  52. package/Tests/App/Dashboard/UserSettingsEmailPreferences.test.tsx +603 -0
  53. package/Tests/App/Dashboard/UserSettingsNotificationSettings.test.tsx +324 -0
  54. package/Tests/App/PublicDashboard/DashboardVariableSelector.test.tsx +13 -4
  55. package/Tests/App/StatusPage/StatusPageOidcOrigin.test.tsx +223 -0
  56. package/Tests/Models/InventoryItemStatus.test.ts +147 -0
  57. package/Tests/Models/NetworkDeviceMacAddressColumn.test.ts +294 -0
  58. package/Tests/Server/API/DashboardPublicResourceListAPI.test.ts +129 -0
  59. package/Tests/Server/EnvironmentConfigValkey.test.ts +227 -0
  60. package/Tests/Server/Infrastructure/Postgres/AddMacAddressToNetworkDeviceMigration.test.ts +365 -0
  61. package/Tests/Server/Infrastructure/Postgres/CustomFieldValueMappingMigration.test.ts +62 -23
  62. package/Tests/Server/Infrastructure/ValkeyDeploymentConfig.test.ts +265 -0
  63. package/Tests/Server/Middleware/HttpMetricsMiddleware.test.ts +382 -0
  64. package/Tests/Server/Middleware/SCIMAuthorization.test.ts +525 -0
  65. package/Tests/Server/Services/DatabaseServiceAggregateBy.test.ts +291 -56
  66. package/Tests/Server/Services/DatabaseServiceSortTiebreaker.test.ts +151 -0
  67. package/Tests/Server/Services/InventoryItemManualCreate.test.ts +105 -0
  68. package/Tests/Server/Services/InventoryItemPromotionGate.test.ts +178 -0
  69. package/Tests/Server/Services/MonitorTemplateServiceCustomFieldSync.test.ts +502 -0
  70. package/Tests/Server/Services/NetworkDeviceAutoImportRuleEngineService.test.ts +1133 -81
  71. package/Tests/Server/Services/NetworkDeviceDiscoveryScanRegistration.test.ts +520 -0
  72. package/Tests/Server/Services/NetworkDeviceMacAddressNormalization.test.ts +572 -0
  73. package/Tests/Server/Types/Database/InventoryStatusPostgres.test.ts +368 -0
  74. package/Tests/Server/Types/Database/Permissions/ReadBlockPermission.test.ts +261 -14
  75. package/Tests/Server/Types/Database/QueryUtilIncludesAnyOfGroups.test.ts +169 -0
  76. package/Tests/Server/Types/Database/QueryUtilIncludesAnyOfGroupsPostgres.test.ts +297 -0
  77. package/Tests/Server/Types/Markdown.test.ts +194 -1
  78. package/Tests/Server/Types/Workflow/Components/IncomingWebhookUtils.test.ts +279 -0
  79. package/Tests/Server/Types/Workflow/Components/TextToJson.test.ts +212 -0
  80. package/Tests/Server/Utils/CustomField/CustomFieldMappingRegistry.test.ts +436 -0
  81. package/Tests/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.test.ts +214 -0
  82. package/Tests/Server/Utils/EmailRollup/EmailRollupFlushRunnerBehaviour.test.ts +1 -1
  83. package/Tests/Server/Utils/EmailRollup/EmailRollupRenderer.test.ts +10 -3
  84. package/Tests/Server/Utils/EventLoop.test.ts +359 -0
  85. package/Tests/Server/Utils/JsonWebToken.test.ts +30 -0
  86. package/Tests/Server/Utils/Monitor/Criteria/CompareCriteria.test.ts +242 -0
  87. package/Tests/Server/Utils/Monitor/Criteria/CompareCriteriaAggregation.test.ts +13 -7
  88. package/Tests/Server/Utils/Monitor/Criteria/DatabaseMonitorCriteria.test.ts +29 -0
  89. package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.test.ts +30 -6
  90. package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteriaAnomalyUnits.test.ts +246 -0
  91. package/Tests/Server/Utils/Monitor/MonitorCriteriaEmailEvalLogAgreement.test.ts +280 -0
  92. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluator.test.ts +224 -10
  93. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorMetricUnits.test.ts +570 -0
  94. package/Tests/Server/Utils/Monitor/MonitorCriteriaExpectationBuilderUnits.test.ts +30 -5
  95. package/Tests/Server/Utils/Monitor/MonitorCriteriaMessageBuilderUnits.test.ts +68 -21
  96. package/Tests/Server/Utils/Monitor/MonitorCriteriaObservationBuilderUnits.test.ts +36 -19
  97. package/Tests/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.test.ts +573 -0
  98. package/Tests/Server/Utils/Monitor/NetworkInventoryUtil.test.ts +317 -0
  99. package/Tests/Server/Utils/Telemetry/ContextSpanProcessor.test.ts +281 -0
  100. package/Tests/Server/Utils/Telemetry/InventoryDuplicateIdentity.test.ts +316 -0
  101. package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +111 -8
  102. package/Tests/Types/Database/IncludesAnyOfGroups.test.ts +106 -0
  103. package/Tests/Types/JSONFunctions.test.ts +40 -0
  104. package/Tests/Types/NetworkAutomation/AutoImportRunMerge.test.ts +235 -0
  105. package/Tests/Types/NetworkAutomation/RuleRunResult.test.ts +66 -0
  106. package/Tests/Types/NetworkAutomation/RuleRunResultDescribe.test.ts +309 -1
  107. package/Tests/Types/Telemetry/InventoryLiveness.test.ts +99 -0
  108. package/Tests/UI/Components/CustomFields/CustomFieldsDetail.test.tsx +160 -0
  109. package/Tests/UI/ReactRouterSingletonBuild.test.ts +133 -0
  110. package/Tests/UI/Rum/ReplayHeader.test.tsx +10 -2
  111. package/Tests/UI/Rum/ReplayPlayerChrome.test.tsx +514 -0
  112. package/Tests/UI/Rum/ReplayTimeFormat.test.ts +94 -0
  113. package/Tests/UI/Rum/ReplayUi.test.tsx +559 -0
  114. package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +5 -0
  115. package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +1 -0
  116. package/Tests/Utils/Dashboard/Components/DashboardMonitorListComponent.test.ts +11 -1
  117. package/Tests/Utils/Dashboard/LabelVariable.test.ts +257 -0
  118. package/Tests/Utils/Dashboard/VariableUrlState.test.ts +144 -14
  119. package/Tests/Utils/MetricUnitUtil.test.ts +81 -0
  120. package/Tests/Utils/Monitor/DeviceMacLearningUtil.test.ts +497 -0
  121. package/Tests/Utils/Monitor/MetricValueFormatter.test.ts +546 -0
  122. package/Tests/Utils/Monitor/MonitorTemplateCustomFieldUtil.test.ts +218 -0
  123. package/Tests/Utils/Monitor/NetworkTopologyEndpointAdoption.test.ts +1755 -0
  124. package/Tests/Utils/NetworkAutomation/AutoImportRunChain.test.ts +320 -0
  125. package/Types/BaseDatabase/IncludesAnyOfGroups.ts +77 -0
  126. package/Types/Dashboard/DashboardComponents/ComponentArgument.ts +1 -0
  127. package/Types/Dashboard/DashboardComponents/DashboardMonitorListComponent.ts +1 -0
  128. package/Types/Dashboard/DashboardVariable.ts +8 -0
  129. package/Types/JSON.ts +4 -0
  130. package/Types/JSONFunctions.ts +16 -0
  131. package/Types/Monitor/SnmpMonitor/NetworkTopology.ts +13 -0
  132. package/Types/NetworkAutomation/RuleRunResult.ts +402 -13
  133. package/Types/SerializableObjectDictionary.ts +4 -0
  134. package/Types/Telemetry/InventoryLiveness.ts +56 -0
  135. package/UI/Components/CSVFileUpload/CSVFileUpload.tsx +9 -2
  136. package/UI/Components/CustomFields/CustomFieldsDetail.tsx +46 -21
  137. package/UI/Components/Dashboard/DashboardVariableControl.tsx +285 -0
  138. package/UI/esbuild-config.js +6 -0
  139. package/Utils/Dashboard/Components/DashboardMonitorListComponent.ts +12 -1
  140. package/Utils/Dashboard/LabelVariable.ts +152 -0
  141. package/Utils/Dashboard/VariableUrlState.ts +27 -5
  142. package/Utils/MetricUnitUtil.ts +34 -0
  143. package/Utils/Monitor/DeviceMacLearningUtil.ts +177 -0
  144. package/Utils/Monitor/MetricValueFormatter.ts +342 -0
  145. package/Utils/Monitor/MonitorTemplateCustomFieldUtil.ts +124 -0
  146. package/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.ts +7 -2
  147. package/Utils/Monitor/NetworkTopologyUtil.ts +693 -0
  148. package/Utils/NetworkAutomation/AutoImportRunChain.ts +138 -0
  149. package/build/dist/Models/DatabaseModels/GlobalConfig.js +12 -12
  150. package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
  151. package/build/dist/Models/DatabaseModels/InventoryItem.js +34 -1
  152. package/build/dist/Models/DatabaseModels/InventoryItem.js.map +1 -1
  153. package/build/dist/Models/DatabaseModels/NetworkDevice.js +128 -0
  154. package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
  155. package/build/dist/Server/EnvironmentConfig.js +44 -12
  156. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  157. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791700000000-AddMacAddressToNetworkDevice.js +14 -0
  158. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791700000000-AddMacAddressToNetworkDevice.js.map +1 -0
  159. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
  160. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  161. package/build/dist/Server/Infrastructure/Redis.js +8 -8
  162. package/build/dist/Server/Infrastructure/Redis.js.map +1 -1
  163. package/build/dist/Server/Infrastructure/Status.js +10 -4
  164. package/build/dist/Server/Infrastructure/Status.js.map +1 -1
  165. package/build/dist/Server/Services/DatabaseService.js +58 -13
  166. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  167. package/build/dist/Server/Services/InventoryItemService.js +28 -1
  168. package/build/dist/Server/Services/InventoryItemService.js.map +1 -1
  169. package/build/dist/Server/Services/MonitorTemplateService.js +116 -17
  170. package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
  171. package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js +270 -37
  172. package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js.map +1 -1
  173. package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js +65 -0
  174. package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js.map +1 -1
  175. package/build/dist/Server/Services/NetworkDeviceService.js +54 -0
  176. package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
  177. package/build/dist/Server/Services/UserNotificationRuleService.js +60 -25
  178. package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
  179. package/build/dist/Server/Types/Database/Permissions/ReadPermission.js +29 -4
  180. package/build/dist/Server/Types/Database/Permissions/ReadPermission.js.map +1 -1
  181. package/build/dist/Server/Types/Database/QueryUtil.js +36 -0
  182. package/build/dist/Server/Types/Database/QueryUtil.js.map +1 -1
  183. package/build/dist/Server/Types/Markdown.js +75 -0
  184. package/build/dist/Server/Types/Markdown.js.map +1 -1
  185. package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js +53 -18
  186. package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js.map +1 -1
  187. package/build/dist/Server/Utils/EmailRollup/EmailRollupFlushRunner.js +10 -1
  188. package/build/dist/Server/Utils/EmailRollup/EmailRollupFlushRunner.js.map +1 -1
  189. package/build/dist/Server/Utils/EmailRollup/EmailRollupRenderer.js +2 -2
  190. package/build/dist/Server/Utils/EmailRollup/EmailRollupRenderer.js.map +1 -1
  191. package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js +58 -35
  192. package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js.map +1 -1
  193. package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +29 -5
  194. package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js.map +1 -1
  195. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +166 -35
  196. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  197. package/build/dist/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.js +58 -9
  198. package/build/dist/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.js.map +1 -1
  199. package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.js +9 -3
  200. package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.js.map +1 -1
  201. package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.js +26 -12
  202. package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.js.map +1 -1
  203. package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js +74 -33
  204. package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js.map +1 -1
  205. package/build/dist/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.js +114 -0
  206. package/build/dist/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.js.map +1 -0
  207. package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js +25 -0
  208. package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js.map +1 -1
  209. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +9 -1
  210. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
  211. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js +70 -13
  212. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js.map +1 -1
  213. package/build/dist/Types/BaseDatabase/IncludesAnyOfGroups.js +54 -0
  214. package/build/dist/Types/BaseDatabase/IncludesAnyOfGroups.js.map +1 -0
  215. package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js +1 -0
  216. package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js.map +1 -1
  217. package/build/dist/Types/Dashboard/DashboardVariable.js +1 -0
  218. package/build/dist/Types/Dashboard/DashboardVariable.js.map +1 -1
  219. package/build/dist/Types/JSON.js +1 -0
  220. package/build/dist/Types/JSON.js.map +1 -1
  221. package/build/dist/Types/JSONFunctions.js +13 -0
  222. package/build/dist/Types/JSONFunctions.js.map +1 -1
  223. package/build/dist/Types/NetworkAutomation/RuleRunResult.js +291 -6
  224. package/build/dist/Types/NetworkAutomation/RuleRunResult.js.map +1 -1
  225. package/build/dist/Types/SerializableObjectDictionary.js +4 -0
  226. package/build/dist/Types/SerializableObjectDictionary.js.map +1 -1
  227. package/build/dist/Types/Telemetry/InventoryLiveness.js +37 -0
  228. package/build/dist/Types/Telemetry/InventoryLiveness.js.map +1 -0
  229. package/build/dist/UI/Components/CSVFileUpload/CSVFileUpload.js +7 -0
  230. package/build/dist/UI/Components/CSVFileUpload/CSVFileUpload.js.map +1 -1
  231. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +9 -2
  232. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
  233. package/build/dist/UI/Components/Dashboard/DashboardVariableControl.js +137 -0
  234. package/build/dist/UI/Components/Dashboard/DashboardVariableControl.js.map +1 -0
  235. package/build/dist/Utils/Dashboard/Components/DashboardMonitorListComponent.js +9 -1
  236. package/build/dist/Utils/Dashboard/Components/DashboardMonitorListComponent.js.map +1 -1
  237. package/build/dist/Utils/Dashboard/LabelVariable.js +96 -0
  238. package/build/dist/Utils/Dashboard/LabelVariable.js.map +1 -0
  239. package/build/dist/Utils/Dashboard/VariableUrlState.js +19 -4
  240. package/build/dist/Utils/Dashboard/VariableUrlState.js.map +1 -1
  241. package/build/dist/Utils/MetricUnitUtil.js +26 -0
  242. package/build/dist/Utils/MetricUnitUtil.js.map +1 -1
  243. package/build/dist/Utils/Monitor/DeviceMacLearningUtil.js +112 -0
  244. package/build/dist/Utils/Monitor/DeviceMacLearningUtil.js.map +1 -0
  245. package/build/dist/Utils/Monitor/MetricValueFormatter.js +276 -0
  246. package/build/dist/Utils/Monitor/MetricValueFormatter.js.map +1 -0
  247. package/build/dist/Utils/Monitor/MonitorTemplateCustomFieldUtil.js +101 -0
  248. package/build/dist/Utils/Monitor/MonitorTemplateCustomFieldUtil.js.map +1 -0
  249. package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js +7 -2
  250. package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js.map +1 -1
  251. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +484 -0
  252. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
  253. package/build/dist/Utils/NetworkAutomation/AutoImportRunChain.js +59 -0
  254. package/build/dist/Utils/NetworkAutomation/AutoImportRunChain.js.map +1 -0
  255. package/jest.config.json +2 -0
  256. package/package.json +1 -1
  257. package/test-setup.sh +9 -9
  258. package/tsconfig.json +51 -11
  259. package/Tests/App/Dashboard/NotificationEmailPreferences.test.tsx +0 -399
@@ -0,0 +1,257 @@
1
+ import Includes from "../../../Types/BaseDatabase/Includes";
2
+ import IncludesAnyOfGroups from "../../../Types/BaseDatabase/IncludesAnyOfGroups";
3
+ import DashboardVariable, {
4
+ DashboardVariableType,
5
+ } from "../../../Types/Dashboard/DashboardVariable";
6
+ import JSONFunctions from "../../../Types/JSONFunctions";
7
+ import DashboardLabelVariable from "../../../Utils/Dashboard/LabelVariable";
8
+
9
+ const UNIT_660: string = "06600000-0000-4000-8000-000000000000";
10
+ const UNIT_661: string = "06610000-0000-4000-8000-000000000000";
11
+ const NETWORK: string = "00000000-0000-4000-8000-000000000001";
12
+
13
+ function variable(
14
+ overrides: Partial<DashboardVariable> = {},
15
+ ): DashboardVariable {
16
+ return {
17
+ id: "unit",
18
+ name: "UNIT",
19
+ type: DashboardVariableType.ProjectLabel,
20
+ labelOptions: [
21
+ { label: "0660", value: UNIT_660 },
22
+ { label: "0661", value: UNIT_661 },
23
+ ],
24
+ ...overrides,
25
+ };
26
+ }
27
+
28
+ function filter(
29
+ overrides: Partial<DashboardVariable> = {},
30
+ labelIds?: Array<string>,
31
+ ): ReturnType<typeof DashboardLabelVariable.getFilter> {
32
+ return DashboardLabelVariable.getFilter({
33
+ labelVariableId: "unit",
34
+ labelIds,
35
+ variables: [variable(overrides)],
36
+ });
37
+ }
38
+
39
+ describe("project label dashboard variable filters", () => {
40
+ test("keeps existing widgets with fixed labels unchanged", () => {
41
+ expect(DashboardLabelVariable.getFilter({ labelIds: [NETWORK] })).toEqual(
42
+ new Includes([NETWORK]),
43
+ );
44
+ expect(DashboardLabelVariable.getFilter({})).toBeUndefined();
45
+ });
46
+
47
+ test("a title or an unbound variable does not change the query", () => {
48
+ expect(
49
+ DashboardLabelVariable.getFilter({
50
+ variables: [variable({ selectedValue: UNIT_660 })],
51
+ }),
52
+ ).toBeUndefined();
53
+ });
54
+
55
+ test.each([UNIT_660, UNIT_661])(
56
+ "filters by selected label ID %s",
57
+ (id: string) => {
58
+ expect(filter({ selectedValue: id })).toEqual(new Includes([id]));
59
+ },
60
+ );
61
+
62
+ test("uses an absent selection's default", () => {
63
+ expect(filter({ defaultValue: UNIT_660 })).toEqual(
64
+ new Includes([UNIT_660]),
65
+ );
66
+ });
67
+
68
+ test("All overrides a default and retains the fixed label condition", () => {
69
+ expect(
70
+ filter({ defaultValue: UNIT_660, selectedValue: "" }, [NETWORK]),
71
+ ).toEqual(new Includes([NETWORK]));
72
+ expect(filter({ selectedValue: "" })).toBeUndefined();
73
+ });
74
+
75
+ test("multi-select is OR within units and AND with fixed labels", () => {
76
+ expect(
77
+ filter({ isMultiSelect: true, selectedValues: [UNIT_660, UNIT_661] }, [
78
+ NETWORK,
79
+ ]),
80
+ ).toEqual(new IncludesAnyOfGroups([[NETWORK], [UNIT_660, UNIT_661]]));
81
+ });
82
+
83
+ test("empty multi-select ignores a stale scalar and default", () => {
84
+ expect(
85
+ filter(
86
+ {
87
+ isMultiSelect: true,
88
+ selectedValues: [],
89
+ selectedValue: UNIT_660,
90
+ defaultValue: UNIT_661,
91
+ },
92
+ [NETWORK],
93
+ ),
94
+ ).toEqual(new Includes([NETWORK]));
95
+ });
96
+
97
+ test("does not mutate saved configuration and deduplicates repeated picks", () => {
98
+ const saved: DashboardVariable = variable({
99
+ isMultiSelect: true,
100
+ selectedValues: [UNIT_660, UNIT_660],
101
+ });
102
+ const before: string = JSON.stringify(saved);
103
+ const fixed: Array<string> = [NETWORK, NETWORK];
104
+ expect(
105
+ DashboardLabelVariable.getFilter({
106
+ labelIds: fixed,
107
+ labelVariableId: saved.id,
108
+ variables: [saved],
109
+ }),
110
+ ).toEqual(new IncludesAnyOfGroups([[NETWORK], [UNIT_660]]));
111
+ expect(JSON.stringify(saved)).toBe(before);
112
+ expect(fixed).toEqual([NETWORK, NETWORK]);
113
+ });
114
+
115
+ test("binding survives a variable rename and display name changes", () => {
116
+ expect(
117
+ filter({
118
+ name: "BRANCH",
119
+ selectedValue: UNIT_660,
120
+ labelOptions: [{ label: "Renamed unit", value: UNIT_660 }],
121
+ }),
122
+ ).toEqual(new Includes([UNIT_660]));
123
+ });
124
+
125
+ test("missing, duplicate, or incompatible variable definitions are errors", () => {
126
+ for (const variables of [
127
+ [],
128
+ [variable(), variable()],
129
+ [variable({ type: DashboardVariableType.CustomList })],
130
+ ]) {
131
+ expect(() => {
132
+ return DashboardLabelVariable.getFilter({
133
+ labelVariableId: "unit",
134
+ variables,
135
+ });
136
+ }).toThrow(/label variable/);
137
+ }
138
+ });
139
+
140
+ test.each(["0660", NETWORK])(
141
+ "rejects selections not in the configured options: %s",
142
+ (selectedValue: string) => {
143
+ expect(() => {
144
+ return filter({ selectedValue });
145
+ }).toThrow(/offered by this dashboard/);
146
+ },
147
+ );
148
+
149
+ test("an invalid default does not silently fall back to all monitors", () => {
150
+ expect(() => {
151
+ return filter({ defaultValue: NETWORK });
152
+ }).toThrow(/offered by this dashboard/);
153
+ });
154
+
155
+ test("validates every multi-selection instead of dropping invalid values", () => {
156
+ expect(() => {
157
+ return filter({
158
+ isMultiSelect: true,
159
+ selectedValues: [UNIT_660, NETWORK],
160
+ });
161
+ }).toThrow(/offered by this dashboard/);
162
+ });
163
+
164
+ test("bounds multi-selection size", () => {
165
+ expect(() => {
166
+ return filter({
167
+ isMultiSelect: true,
168
+ selectedValues: Array(101).fill(UNIT_660) as Array<string>,
169
+ });
170
+ }).toThrow(/100 labels/);
171
+ });
172
+
173
+ test.each([
174
+ undefined,
175
+ [],
176
+ [{ label: "0660", value: "0660" }],
177
+ [{ label: "", value: UNIT_660 }],
178
+ [{ label: " ", value: UNIT_660 }],
179
+ [{ label: "x".repeat(1025), value: UNIT_660 }],
180
+ [
181
+ { label: "0660", value: UNIT_660 },
182
+ { label: "Duplicate", value: UNIT_660 },
183
+ ],
184
+ Array(1001).fill({ label: "0660", value: UNIT_660 }),
185
+ ])(
186
+ "rejects malformed label choices %#",
187
+ (labelOptions: DashboardVariable["labelOptions"]) => {
188
+ expect(() => {
189
+ return filter({ labelOptions });
190
+ }).toThrow();
191
+ },
192
+ );
193
+
194
+ test("filter operators survive the API serialization round trip", () => {
195
+ const original: IncludesAnyOfGroups = filter({ selectedValue: UNIT_660 }, [
196
+ NETWORK,
197
+ ]) as IncludesAnyOfGroups;
198
+ expect(
199
+ JSONFunctions.deserializeValue(JSONFunctions.serializeValue(original)),
200
+ ).toEqual(original);
201
+ });
202
+ });
203
+
204
+ describe("dashboard title display interpolation", () => {
205
+ test.each([
206
+ [{ selectedValue: UNIT_660 }, "0660 NETWORK"],
207
+ [{ defaultValue: UNIT_661 }, "0661 NETWORK"],
208
+ [{ selectedValue: "", defaultValue: UNIT_660 }, "All NETWORK"],
209
+ [
210
+ { isMultiSelect: true, selectedValues: [UNIT_660, UNIT_661] },
211
+ "0660, 0661 NETWORK",
212
+ ],
213
+ [
214
+ { isMultiSelect: true, selectedValues: [], defaultValue: UNIT_660 },
215
+ "All NETWORK",
216
+ ],
217
+ [{ selectedValue: NETWORK }, "Unavailable label NETWORK"],
218
+ ])(
219
+ "renders a label selection using display names %#",
220
+ (overrides: Partial<DashboardVariable>, expected: string) => {
221
+ expect(
222
+ DashboardLabelVariable.interpolateTitle("{{UNIT}} NETWORK", [
223
+ variable(overrides),
224
+ ]),
225
+ ).toBe(expected);
226
+ },
227
+ );
228
+
229
+ test("preserves unknown references and ordinary titles", () => {
230
+ expect(
231
+ DashboardLabelVariable.interpolateTitle("{{missing}} NETWORK", [
232
+ variable(),
233
+ ]),
234
+ ).toBe("{{missing}} NETWORK");
235
+ expect(DashboardLabelVariable.interpolateTitle("Monitors", [])).toBe(
236
+ "Monitors",
237
+ );
238
+ expect(
239
+ DashboardLabelVariable.interpolateTitle(undefined, []),
240
+ ).toBeUndefined();
241
+ });
242
+
243
+ test("supports multiple references and text values without interpreting markup", () => {
244
+ const text: DashboardVariable = {
245
+ id: "env",
246
+ name: "ENV",
247
+ type: DashboardVariableType.TextInput,
248
+ selectedValue: "<b>prod</b>",
249
+ };
250
+ expect(
251
+ DashboardLabelVariable.interpolateTitle("{{UNIT}} / {{ENV}} / {{UNIT}}", [
252
+ variable({ selectedValue: UNIT_660 }),
253
+ text,
254
+ ]),
255
+ ).toBe("0660 / <b>prod</b> / 0660");
256
+ });
257
+ });
@@ -100,7 +100,10 @@ describe("DashboardVariableUrlState", () => {
100
100
  });
101
101
 
102
102
  test("applies multi-selected values from the url", () => {
103
- const variable: DashboardVariable = makeVariable({ name: "region" });
103
+ const variable: DashboardVariable = makeVariable({
104
+ name: "region",
105
+ isMultiSelect: true,
106
+ });
104
107
  const result: Array<DashboardVariable> =
105
108
  DashboardVariableUrlState.applyUrlToVariables([variable], {
106
109
  region: { selectedValues: ["a", "b"] },
@@ -108,6 +111,55 @@ describe("DashboardVariableUrlState", () => {
108
111
  expect(result[0]?.selectedValues).toEqual(["a", "b"]);
109
112
  });
110
113
 
114
+ test("restores one choice into a multi-select and clears a stale scalar", () => {
115
+ const variable: DashboardVariable = makeVariable({
116
+ name: "UNIT",
117
+ type: DashboardVariableType.ProjectLabel,
118
+ isMultiSelect: true,
119
+ selectedValue: "old",
120
+ selectedValues: ["old"],
121
+ });
122
+ const result: Array<DashboardVariable> =
123
+ DashboardVariableUrlState.applyUrlToVariables([variable], {
124
+ UNIT: { selectedValue: "label-0660" },
125
+ });
126
+ expect(result[0]?.selectedValues).toEqual(["label-0660"]);
127
+ expect(result[0]?.selectedValue).toBeUndefined();
128
+ expect(variable.selectedValues).toEqual(["old"]);
129
+ });
130
+
131
+ test("normalizes an explicit All to an empty multi-select", () => {
132
+ const result: Array<DashboardVariable> =
133
+ DashboardVariableUrlState.applyUrlToVariables(
134
+ [
135
+ makeVariable({
136
+ name: "UNIT",
137
+ isMultiSelect: true,
138
+ selectedValues: ["old"],
139
+ }),
140
+ ],
141
+ { UNIT: { selectedValue: "" } },
142
+ );
143
+ expect(result[0]?.selectedValues).toEqual([]);
144
+ expect(result[0]?.selectedValue).toBeUndefined();
145
+ });
146
+
147
+ test("normalizes a comma-containing scalar using the single-select definition", () => {
148
+ const result: Array<DashboardVariable> =
149
+ DashboardVariableUrlState.applyUrlToVariables(
150
+ [
151
+ makeVariable({
152
+ name: "text",
153
+ type: DashboardVariableType.TextInput,
154
+ selectedValues: ["stale"],
155
+ }),
156
+ ],
157
+ DashboardVariableUrlState.parseFromSearch("?var-text=a,b"),
158
+ );
159
+ expect(result[0]?.selectedValue).toBe("a,b");
160
+ expect(result[0]?.selectedValues).toBeUndefined();
161
+ });
162
+
111
163
  test("does not mutate the original variable object", () => {
112
164
  const variable: DashboardVariable = makeVariable({
113
165
  name: "cluster",
@@ -135,7 +187,7 @@ describe("DashboardVariableUrlState", () => {
135
187
  test("a shared url reproduces the selection on the variables", () => {
136
188
  const variables: Array<DashboardVariable> = [
137
189
  makeVariable({ name: "cluster" }),
138
- makeVariable({ name: "region" }),
190
+ makeVariable({ name: "region", isMultiSelect: true }),
139
191
  ];
140
192
 
141
193
  const fromUrl: Record<string, VariableUrlSnapshot> =
@@ -149,14 +201,7 @@ describe("DashboardVariableUrlState", () => {
149
201
  expect(applied[1]?.selectedValues).toEqual(["us", "eu"]);
150
202
  });
151
203
 
152
- /*
153
- * "All" is the absence of a var- param — writeToBrowserUrl emits
154
- * nothing for an empty selection, so a reload cannot tell "cleared"
155
- * from "never touched" and must treat both as All. That only holds
156
- * because resolveValue ignores defaultValue for a multi-select; if it
157
- * fell back, a viewer who cleared the popover would silently get the
158
- * author's Default back on every refresh and on every shared link.
159
- */
204
+ // Explicit All must override any selection persisted in the saved config.
160
205
  test("a cleared multi-select survives a url round trip as All", () => {
161
206
  window.history.replaceState({}, "", "/dashboard");
162
207
  DashboardVariableUrlState.writeToBrowserUrl([
@@ -168,7 +213,7 @@ describe("DashboardVariableUrlState", () => {
168
213
  }),
169
214
  ]);
170
215
 
171
- expect(window.location.search).toBe("");
216
+ expect(window.location.search).toBe("?var-region=");
172
217
 
173
218
  const applied: Array<DashboardVariable> =
174
219
  DashboardVariableUrlState.applyUrlToVariables(
@@ -177,18 +222,60 @@ describe("DashboardVariableUrlState", () => {
177
222
  name: "region",
178
223
  isMultiSelect: true,
179
224
  defaultValue: "us",
225
+ selectedValues: ["eu"],
180
226
  }),
181
227
  ],
182
228
  DashboardVariableUrlState.parseFromSearch(window.location.search),
183
229
  );
184
230
 
185
- expect(applied[0]?.selectedValues).toBeUndefined();
231
+ expect(applied[0]?.selectedValues).toEqual([]);
186
232
  expect(
187
233
  DashboardVariableInterpolation.resolveValue(
188
234
  applied[0] as DashboardVariable,
189
235
  ),
190
236
  ).toBeUndefined();
191
237
  });
238
+
239
+ test("a single project label selection survives a multi-select URL round trip", () => {
240
+ window.history.replaceState({}, "", "/dashboard");
241
+ const variable: DashboardVariable = makeVariable({
242
+ name: "UNIT",
243
+ type: DashboardVariableType.ProjectLabel,
244
+ isMultiSelect: true,
245
+ selectedValues: ["0660"],
246
+ });
247
+ DashboardVariableUrlState.writeToBrowserUrl([variable]);
248
+ expect(window.location.search).toBe("?var-UNIT=0660");
249
+ const [restored]: Array<DashboardVariable> =
250
+ DashboardVariableUrlState.applyUrlToVariables(
251
+ [{ ...variable, selectedValues: undefined }],
252
+ DashboardVariableUrlState.parseFromSearch(window.location.search),
253
+ );
254
+ expect(DashboardVariableInterpolation.resolveValue(restored!)).toEqual({
255
+ multi: ["0660"],
256
+ });
257
+ });
258
+
259
+ test("explicit All survives a single-select URL round trip despite a stored default", () => {
260
+ window.history.replaceState({}, "", "/dashboard");
261
+ const variable: DashboardVariable = makeVariable({
262
+ name: "UNIT",
263
+ type: DashboardVariableType.ProjectLabel,
264
+ defaultValue: "0660",
265
+ selectedValue: "",
266
+ });
267
+ DashboardVariableUrlState.writeToBrowserUrl([variable]);
268
+ expect(window.location.search).toBe("?var-UNIT=");
269
+ const [restored]: Array<DashboardVariable> =
270
+ DashboardVariableUrlState.applyUrlToVariables(
271
+ [{ ...variable, selectedValue: undefined }],
272
+ DashboardVariableUrlState.parseFromSearch(window.location.search),
273
+ );
274
+ expect(restored?.selectedValue).toBe("");
275
+ expect(
276
+ DashboardVariableInterpolation.resolveValue(restored!),
277
+ ).toBeUndefined();
278
+ });
192
279
  });
193
280
 
194
281
  describe("writeToBrowserUrl", () => {
@@ -209,7 +296,11 @@ describe("DashboardVariableUrlState", () => {
209
296
 
210
297
  test("writes multi-selected values joined by commas", () => {
211
298
  DashboardVariableUrlState.writeToBrowserUrl([
212
- makeVariable({ name: "region", selectedValues: ["us", "eu"] }),
299
+ makeVariable({
300
+ name: "region",
301
+ isMultiSelect: true,
302
+ selectedValues: ["us", "eu"],
303
+ }),
213
304
  ]);
214
305
  expect(
215
306
  DashboardVariableUrlState.parseFromSearch(window.location.search)[
@@ -239,6 +330,41 @@ describe("DashboardVariableUrlState", () => {
239
330
  expect(window.location.search).toBe("");
240
331
  });
241
332
 
333
+ test("keeps explicit All distinct from an untouched default", () => {
334
+ DashboardVariableUrlState.writeToBrowserUrl([
335
+ makeVariable({
336
+ name: "cleared",
337
+ selectedValue: "",
338
+ defaultValue: "prod",
339
+ }),
340
+ makeVariable({ name: "untouched", defaultValue: "prod" }),
341
+ makeVariable({
342
+ name: "clearedMulti",
343
+ isMultiSelect: true,
344
+ selectedValues: [],
345
+ }),
346
+ makeVariable({ name: "untouchedMulti", isMultiSelect: true }),
347
+ ]);
348
+ expect(window.location.search).toBe("?var-cleared=&var-clearedMulti=");
349
+ });
350
+
351
+ test("writes only the selection field used by the saved selection mode", () => {
352
+ DashboardVariableUrlState.writeToBrowserUrl([
353
+ makeVariable({
354
+ name: "single",
355
+ selectedValue: "prod",
356
+ selectedValues: ["stale"],
357
+ }),
358
+ makeVariable({
359
+ name: "multi",
360
+ isMultiSelect: true,
361
+ selectedValue: "stale",
362
+ selectedValues: [],
363
+ }),
364
+ ]);
365
+ expect(window.location.search).toBe("?var-single=prod&var-multi=");
366
+ });
367
+
242
368
  test("does not push a new history entry (uses replaceState)", () => {
243
369
  const lengthBefore: number = window.history.length;
244
370
  DashboardVariableUrlState.writeToBrowserUrl([
@@ -250,7 +376,11 @@ describe("DashboardVariableUrlState", () => {
250
376
  test("write -> parse round trip preserves the selection", () => {
251
377
  DashboardVariableUrlState.writeToBrowserUrl([
252
378
  makeVariable({ name: "cluster", selectedValue: "prod" }),
253
- makeVariable({ name: "region", selectedValues: ["us", "eu"] }),
379
+ makeVariable({
380
+ name: "region",
381
+ isMultiSelect: true,
382
+ selectedValues: ["us", "eu"],
383
+ }),
254
384
  ]);
255
385
  const parsed: Record<string, VariableUrlSnapshot> =
256
386
  DashboardVariableUrlState.parseFromSearch(window.location.search);
@@ -235,6 +235,87 @@ describe("MetricUnitUtil", () => {
235
235
  });
236
236
  });
237
237
 
238
+ describe("getFamilyBaseUnit", () => {
239
+ test("returns the family's canonical base for every member", () => {
240
+ for (const unit of [
241
+ "B",
242
+ "b",
243
+ "By",
244
+ "bytes",
245
+ "KB",
246
+ "kby",
247
+ "MB",
248
+ "GB",
249
+ "TB",
250
+ "PB",
251
+ ]) {
252
+ expect(MetricUnitUtil.getFamilyBaseUnit(unit)).toBe("B");
253
+ }
254
+
255
+ for (const unit of [
256
+ "ns",
257
+ "µs",
258
+ "us",
259
+ "ms",
260
+ "s",
261
+ "sec",
262
+ "seconds",
263
+ "min",
264
+ "hours",
265
+ "d",
266
+ "days",
267
+ ]) {
268
+ expect(MetricUnitUtil.getFamilyBaseUnit(unit)).toBe("sec");
269
+ }
270
+
271
+ for (const unit of ["%", "percent", "1"]) {
272
+ expect(MetricUnitUtil.getFamilyBaseUnit(unit)).toBe("%");
273
+ }
274
+
275
+ for (const unit of ["bit", "bits", "kbit", "mbit", "gbit"]) {
276
+ expect(MetricUnitUtil.getFamilyBaseUnit(unit)).toBe("bit");
277
+ }
278
+ });
279
+
280
+ test("is case- and whitespace-insensitive", () => {
281
+ expect(MetricUnitUtil.getFamilyBaseUnit(" GB ")).toBe("B");
282
+ expect(MetricUnitUtil.getFamilyBaseUnit("MS")).toBe("sec");
283
+ });
284
+
285
+ test("returns null for an unknown or empty unit", () => {
286
+ expect(MetricUnitUtil.getFamilyBaseUnit(undefined)).toBeNull();
287
+ expect(MetricUnitUtil.getFamilyBaseUnit("")).toBeNull();
288
+ expect(MetricUnitUtil.getFamilyBaseUnit(" ")).toBeNull();
289
+ expect(MetricUnitUtil.getFamilyBaseUnit("cores")).toBeNull();
290
+ expect(MetricUnitUtil.getFamilyBaseUnit("widgets")).toBeNull();
291
+ // Binary prefixes are deliberately not in any family here.
292
+ expect(MetricUnitUtil.getFamilyBaseUnit("KiBy")).toBeNull();
293
+ });
294
+
295
+ /*
296
+ * The base is the unit every other member converts into with the
297
+ * family's own converter — this is the contract MetricValueFormatter
298
+ * relies on when it restates "2500 GB" as bytes before scaling.
299
+ */
300
+ test("the base it names round-trips through convertToMetricUnit", () => {
301
+ expect(
302
+ MetricUnitUtil.convertToMetricUnit({
303
+ value: 2500,
304
+ fromUnit: "GB",
305
+ metricUnit: MetricUnitUtil.getFamilyBaseUnit("GB") as string,
306
+ }),
307
+ ).toBe(2.5e12);
308
+
309
+ expect(
310
+ MetricUnitUtil.convertToMetricUnit({
311
+ value: 36,
312
+ fromUnit: "hours",
313
+ metricUnit: MetricUnitUtil.getFamilyBaseUnit("hours") as string,
314
+ }),
315
+ ).toBe(129600);
316
+ });
317
+ });
318
+
238
319
  describe("hasCompatibleUnitFamily", () => {
239
320
  test("true for known families", () => {
240
321
  expect(MetricUnitUtil.hasCompatibleUnitFamily("bytes")).toBe(true);