@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
@@ -113,12 +113,52 @@ export interface AutoImportRuleRunResult {
113
113
  monitorsSkippedUnsupportedHost: number;
114
114
  // Monitor creates that failed after the device was available.
115
115
  monitorsFailed: number;
116
+ /*
117
+ * Why devices could not be created — the actual server-side messages,
118
+ * deduplicated and capped. "0 imported, check the server logs" is not an
119
+ * answer an operator of a self-hosted install can act on, and it is not one
120
+ * support can act on either: every distinct failure mode used to render the
121
+ * identical sentence. See OneUptime/oneuptime#3643.
122
+ */
123
+ deviceFailureReasons: Array<string>;
124
+ // Why active Network Device monitors could not be created. Same contract.
125
+ monitorFailureReasons: Array<string>;
126
+ /*
127
+ * True when the run stopped ATTEMPTING monitor creates because the failure
128
+ * it hit is systemic rather than per host — the rule's Monitor Template
129
+ * cannot be resolved at all, or the identical error came back for every
130
+ * device it tried. The remaining devices are still counted into
131
+ * monitorsFailed (that many monitors really are missing); they are simply
132
+ * not retried, because they would fail identically.
133
+ */
134
+ monitorProvisioningHalted: boolean;
116
135
  /*
117
136
  * True when the run stopped at the device-create or monitor-create cap
118
137
  * with work left over. Running again continues from idempotent inventory
119
138
  * and provisioning keys.
120
139
  */
121
140
  isTruncated: boolean;
141
+ /*
142
+ * Matched hosts a cap left un-imported: they have no device yet and this
143
+ * run did not create one. A capped run keeps EVALUATING after it stops
144
+ * writing precisely so it can answer "how much is left" — reporting
145
+ * "imported 500" with no remainder is what makes a truncated run read as
146
+ * silently losing the rest of the estate (OneUptime issue #3642).
147
+ */
148
+ hostsPendingImport: number;
149
+ /*
150
+ * (device, template) pairs a cap left unprovisioned: the device is there,
151
+ * nothing monitors it yet, and this run's monitor budget was spent. The
152
+ * counterpart of hostsPendingImport for the monitor half of the work,
153
+ * which has its OWN cap and so can be the only thing left over.
154
+ */
155
+ monitorsPendingCreation: number;
156
+ /*
157
+ * True when a capped manual run stopped OPENING further scans. The two
158
+ * pending counters above then cover only the scans it actually read, so
159
+ * the remainder they report is a floor rather than a total.
160
+ */
161
+ hasUnevaluatedScans: boolean;
122
162
  /*
123
163
  * True when the project has more completed scans than one manual run
124
164
  * reads (the newest MAX_SCANS_PER_AUTO_IMPORT_RULE_RUN). Distinct from
@@ -139,6 +179,38 @@ export interface AutoImportRuleRunResult {
139
179
  // How many addresses matchedIpAddressSample carries at most.
140
180
  export const MAX_MATCHED_IP_SAMPLE: number = 50;
141
181
 
182
+ /*
183
+ * How many DISTINCT failure reasons a run reports. A run that fails the same
184
+ * way 500 times has one reason; a run that fails three different ways has
185
+ * three worth reading. Beyond that the dialog stops being a summary.
186
+ */
187
+ export const MAX_RUN_FAILURE_REASONS: number = 3;
188
+
189
+ /*
190
+ * How much of one reason survives. Server exception messages are written for
191
+ * humans and are short, but a driver error can carry an entire statement, and
192
+ * that belongs in the logs rather than in a modal.
193
+ */
194
+ export const MAX_RUN_FAILURE_REASON_LENGTH: number = 300;
195
+
196
+ /*
197
+ * How many capped server passes ONE press of "Run Rule" chains together.
198
+ *
199
+ * A real run is bounded server-side (see the engine's per-run device and
200
+ * monitor caps) so a rule that meets a /16 cannot become one unbounded API
201
+ * request. That bound used to be the operator's problem: a 909-device estate
202
+ * imported 500 and stopped, and finishing it meant noticing the cap line and
203
+ * pressing the button again — which is exactly what OneUptime issue #3642
204
+ * reported as "the rest are simply missing". The button now drives the passes
205
+ * itself, so one press finishes an estate of any ordinary size, while each
206
+ * individual request stays as small as it always was.
207
+ *
208
+ * Still bounded, because "one press" must not mean "an hour of chained
209
+ * requests": past this many passes the run reports what is left and the
210
+ * operator presses again, deliberately.
211
+ */
212
+ export const MAX_AUTO_IMPORT_RUN_PASSES: number = 20;
213
+
142
214
  /*
143
215
  * A count off the wire. An absent or non-numeric field reads as zero rather
144
216
  * than as NaN: a summary line that says "NaN devices" is worse than one that
@@ -158,12 +230,129 @@ function hosts(count: number): string {
158
230
  return `${count} ${count === 1 ? "host" : "hosts"}`;
159
231
  }
160
232
 
233
+ /*
234
+ * The denominator of "matched N out of M". It carries its own noun because
235
+ * hosts() already supplies one: composing them produced "matched 501 hosts out
236
+ * of the 501 hosts discovered hosts it looked at", which is the sentence in the
237
+ * screenshot on issue #3643.
238
+ */
239
+ function discoveredHosts(count: number): string {
240
+ return `${count} discovered ${count === 1 ? "host" : "hosts"}`;
241
+ }
242
+
161
243
  function activeMonitors(count: number): string {
162
244
  return `${count} active Network Device ${
163
245
  count === 1 ? "monitor" : "monitors"
164
246
  }`;
165
247
  }
166
248
 
249
+ /*
250
+ * Reasons off the wire. Anything that is not a non-empty string is dropped
251
+ * rather than rendered as "undefined", and the list is capped the same way the
252
+ * server caps it so a hand-rolled API client cannot make the dialog unbounded.
253
+ */
254
+ function readReasons(json: JSONObject, key: string): Array<string> {
255
+ const value: unknown = json[key];
256
+
257
+ if (!Array.isArray(value)) {
258
+ return [];
259
+ }
260
+
261
+ return (value as Array<unknown>)
262
+ .filter((entry: unknown): boolean => {
263
+ return typeof entry === "string" && entry.trim().length > 0;
264
+ })
265
+ .map((entry: unknown): string => {
266
+ return (entry as string)
267
+ .trim()
268
+ .substring(0, MAX_RUN_FAILURE_REASON_LENGTH);
269
+ })
270
+ .slice(0, MAX_RUN_FAILURE_REASONS);
271
+ }
272
+
273
+ // What already ends a sentence, so becauseOf does not add a second full stop.
274
+ const TERMINAL_PUNCTUATION: Array<string> = [".", "!", "?"];
275
+
276
+ /*
277
+ * The half-sentence that turns "N could not be created" into something the
278
+ * operator can act on. With no reason recorded the old pointer at the server
279
+ * logs is still the honest answer; with reasons, they ARE the answer.
280
+ */
281
+ function becauseOf(reasons: Array<string>): string {
282
+ if (reasons.length === 0) {
283
+ return " Check the server logs for the reason.";
284
+ }
285
+
286
+ const rendered: Array<string> = reasons.map((reason: string): string => {
287
+ return TERMINAL_PUNCTUATION.includes(reason.slice(-1))
288
+ ? reason
289
+ : `${reason}.`;
290
+ });
291
+
292
+ if (rendered.length === 1) {
293
+ return ` Reason: ${rendered[0]}`;
294
+ }
295
+
296
+ return ` Reasons: ${rendered.join(" ")}`;
297
+ }
298
+
299
+ /*
300
+ * Every pass's reasons as one deduplicated, capped list — see
301
+ * mergeAutoImportRunPasses.
302
+ */
303
+ function mergeReasons(lists: Array<Array<string>>): Array<string> {
304
+ const seen: Set<string> = new Set<string>();
305
+
306
+ for (const list of lists) {
307
+ for (const reason of list) {
308
+ if (seen.size >= MAX_RUN_FAILURE_REASONS) {
309
+ return Array.from(seen);
310
+ }
311
+
312
+ seen.add(reason);
313
+ }
314
+ }
315
+
316
+ return Array.from(seen);
317
+ }
318
+
319
+ /*
320
+ * What a capped run left behind, as a phrase — "409 hosts still to import",
321
+ * "409 active Network Device monitors still to create", or both.
322
+ *
323
+ * The two halves of the work have separate caps, so either can be the only
324
+ * thing left over: an estate that is fully imported but unmonitored hits the
325
+ * monitor cap with nothing at all pending on the device side.
326
+ *
327
+ * Empty when the run stopped without anything measurable left — the caller
328
+ * falls back to the unquantified sentence rather than printing "with —".
329
+ */
330
+ function describeRemainingWork(result: AutoImportRuleRunResult): string {
331
+ const parts: Array<string> = [];
332
+
333
+ if (result.hostsPendingImport > 0) {
334
+ parts.push(`${hosts(result.hostsPendingImport)} still to import`);
335
+ }
336
+
337
+ if (result.monitorsPendingCreation > 0) {
338
+ parts.push(
339
+ `${activeMonitors(result.monitorsPendingCreation)} still to create`,
340
+ );
341
+ }
342
+
343
+ if (parts.length === 0) {
344
+ return "";
345
+ }
346
+
347
+ /*
348
+ * A capped run stops OPENING scans once it stops writing, so when scans
349
+ * were left unread the remainder it counted is a floor, not a total.
350
+ */
351
+ return `${result.hasUnevaluatedScans ? "at least " : ""}${parts.join(
352
+ " and ",
353
+ )}`;
354
+ }
355
+
167
356
  export class RuleRunResultUtil {
168
357
  /*
169
358
  * The sentences the auto-import report shows. A run that imported nothing is
@@ -199,22 +388,32 @@ export class RuleRunResultUtil {
199
388
  lines.push(
200
389
  `This rule would import nothing. It matched ${hosts(
201
390
  result.hostsMatched,
202
- )} out of the ${hosts(
391
+ )} out of the ${discoveredHosts(
203
392
  result.hostsEvaluated,
204
- )} discovered hosts it looked at.`,
393
+ )} it looked at.`,
205
394
  );
206
395
  }
207
396
  } else if (result.devicesCreated > 0) {
208
397
  lines.push(
209
398
  `Imported ${hosts(result.devicesCreated)} as network devices.`,
210
399
  );
400
+ } else if (result.monitorsCreated > 0) {
401
+ /*
402
+ * A backfill run over an estate that is ALREADY imported creates no
403
+ * devices at all, and the flat "No devices were imported" read as a
404
+ * failure right above "Created 500 active Network Device monitors".
405
+ * Name what did not happen precisely instead.
406
+ */
407
+ lines.push(
408
+ `No new network devices were imported. This rule matched ${hosts(
409
+ result.hostsMatched,
410
+ )} out of the ${discoveredHosts(result.hostsEvaluated)} it looked at.`,
411
+ );
211
412
  } else {
212
413
  lines.push(
213
414
  `No devices were imported. This rule matched ${hosts(
214
415
  result.hostsMatched,
215
- )} out of the ${hosts(
216
- result.hostsEvaluated,
217
- )} discovered hosts it looked at.`,
416
+ )} out of the ${discoveredHosts(result.hostsEvaluated)} it looked at.`,
218
417
  );
219
418
  }
220
419
 
@@ -262,7 +461,20 @@ export class RuleRunResultUtil {
262
461
  lines.push(
263
462
  `${activeMonitors(
264
463
  result.monitorsFailed,
265
- )} could not be created. Their network devices remain imported; check the server logs for the reason.`,
464
+ )} could not be created. Their network devices remain imported.${becauseOf(
465
+ result.monitorFailureReasons,
466
+ )}`,
467
+ );
468
+ }
469
+
470
+ /*
471
+ * Said once, after the reason, because it changes what the operator should
472
+ * do next: nothing here is waiting for another run. The remaining devices
473
+ * were counted, not attempted — see monitorProvisioningHalted.
474
+ */
475
+ if (result.monitorProvisioningHalted) {
476
+ lines.push(
477
+ "Monitor provisioning stopped for the rest of this run: that failure applies to every remaining device, not just one, so the rest were not attempted. Fix it and run this rule again.",
266
478
  );
267
479
  }
268
480
 
@@ -280,9 +492,9 @@ export class RuleRunResultUtil {
280
492
 
281
493
  if (result.devicesFailed > 0) {
282
494
  lines.push(
283
- `${hosts(
284
- result.devicesFailed,
285
- )} could not be imported. Check the server logs for the reason.`,
495
+ `${hosts(result.devicesFailed)} could not be imported.${becauseOf(
496
+ result.deviceFailureReasons,
497
+ )}`,
286
498
  );
287
499
  }
288
500
 
@@ -291,14 +503,43 @@ export class RuleRunResultUtil {
291
503
  * re-running skips what is already imported and continues. The scan cap
292
504
  * does NOT: a re-run re-reads the same newest scans, so promising "run
293
505
  * again to continue" there would send the operator in a circle.
506
+ *
507
+ * And a run that reached the cap having created NOTHING resumes nowhere
508
+ * either: every attempt failed, so the identical run would fail again.
509
+ * Telling that operator to "run again to continue" is the advice issue
510
+ * #3643 was given, and following it changes nothing. The automatic sweep
511
+ * has always understood this — it refuses to re-queue a zero-progress
512
+ * truncated pass — so say the same thing here.
294
513
  */
295
514
  if (result.isTruncated && !result.isDryRun) {
296
- lines.push(
297
- "Stopped at the run cap run again to continue; already-imported hosts are skipped.",
298
- );
515
+ const madeProgress: boolean =
516
+ result.devicesCreated > 0 || result.monitorsCreated > 0;
517
+ const remaining: string = describeRemainingWork(result);
518
+
519
+ if (!madeProgress) {
520
+ /*
521
+ * The remainder is still worth naming here — it is the size of the
522
+ * problem to fix — but the advice is the opposite of "run again".
523
+ */
524
+ lines.push(
525
+ remaining
526
+ ? `Stopped at the run cap without creating anything, with ${remaining}. Running again as-is would repeat the same failures — fix the failures reported above first.`
527
+ : "Stopped at the run cap without creating anything, so running again as-is would repeat the same failures — fix the failures reported above first.",
528
+ );
529
+ } else {
530
+ lines.push(
531
+ remaining
532
+ ? `Stopped at the run cap with ${remaining}. Run again to continue; already-imported hosts are skipped.`
533
+ : "Stopped at the run cap — run again to continue; already-imported hosts are skipped.",
534
+ );
535
+ }
299
536
  } else if (result.isTruncated) {
537
+ const remaining: string = describeRemainingWork(result);
538
+
300
539
  lines.push(
301
- "Stopped counting at the run cap — a real run is bounded by the same device-import and active-monitor creation limits.",
540
+ remaining
541
+ ? `Stopped counting at the run cap with ${remaining} — a real run is bounded by the same device-import and active-monitor creation limits.`
542
+ : "Stopped counting at the run cap — a real run is bounded by the same device-import and active-monitor creation limits.",
302
543
  );
303
544
  }
304
545
 
@@ -311,6 +552,148 @@ export class RuleRunResultUtil {
311
552
  return lines.join(" ");
312
553
  }
313
554
 
555
+ /*
556
+ * The line shown WHILE a "Run Rule" press is still chaining passes.
557
+ *
558
+ * A capped run over a large estate is now several requests long, and an
559
+ * unlabelled spinner sitting there for two minutes reads as a hang — which
560
+ * is the same "is it doing anything?" the silent cap created in the first
561
+ * place. This says what has landed so far; the full report replaces it when
562
+ * the chain ends.
563
+ */
564
+ public static describeAutoImportProgress(
565
+ result: AutoImportRuleRunResult,
566
+ ): string {
567
+ const done: Array<string> = [];
568
+
569
+ if (result.devicesCreated > 0) {
570
+ done.push(`${hosts(result.devicesCreated)} imported`);
571
+ }
572
+
573
+ if (result.monitorsCreated > 0) {
574
+ done.push(`${activeMonitors(result.monitorsCreated)} created`);
575
+ }
576
+
577
+ if (done.length === 0) {
578
+ return "Still importing…";
579
+ }
580
+
581
+ return `${done.join(
582
+ ", ",
583
+ )} so far. This rule matched more than one run's cap, so the import is continuing — leave this open.`;
584
+ }
585
+
586
+ /*
587
+ * The one report for a "Run Rule" press that took several server passes.
588
+ *
589
+ * Each pass is capped server-side and re-reads the SAME scans, so the
590
+ * counters split into three kinds and only one of them may be summed:
591
+ *
592
+ * - State of the estate as the run found it (evaluated / matched /
593
+ * excluded / already-registered / already-monitored) comes from the
594
+ * FIRST pass. Summing would multiply the estate by the pass count, and
595
+ * taking the last pass would report the run's own creates back as
596
+ * "already registered" — "imported 909 hosts" followed by "909 hosts
597
+ * already had network devices" is a self-contradiction.
598
+ * - Work actually done (creates and failures) is SUMMED: every pass does
599
+ * a different 500.
600
+ * - What is still left (the pending counters and isTruncated) comes from
601
+ * the LAST pass, which is the only one that still speaks for now.
602
+ *
603
+ * hasMoreScans is a property of the project, identical in every pass; it is
604
+ * taken from the last for the same reason as the pending counters.
605
+ */
606
+ public static mergeAutoImportRunPasses(
607
+ passes: Array<AutoImportRuleRunResult>,
608
+ ): AutoImportRuleRunResult {
609
+ const first: AutoImportRuleRunResult | undefined = passes[0];
610
+ const last: AutoImportRuleRunResult | undefined = passes[passes.length - 1];
611
+
612
+ if (!first || !last) {
613
+ throw new Error(
614
+ "mergeAutoImportRunPasses needs at least one run result to merge.",
615
+ );
616
+ }
617
+
618
+ if (passes.length === 1) {
619
+ return first;
620
+ }
621
+
622
+ const sum: (pick: (pass: AutoImportRuleRunResult) => number) => number = (
623
+ pick: (pass: AutoImportRuleRunResult) => number,
624
+ ): number => {
625
+ return passes.reduce(
626
+ (total: number, pass: AutoImportRuleRunResult): number => {
627
+ return total + pick(pass);
628
+ },
629
+ 0,
630
+ );
631
+ };
632
+
633
+ return {
634
+ hostsEvaluated: first.hostsEvaluated,
635
+ hostsMatched: first.hostsMatched,
636
+ hostsExcluded: first.hostsExcluded,
637
+ hostsSkippedAlreadyRegistered: first.hostsSkippedAlreadyRegistered,
638
+ monitorsSkippedAlreadyExisting: first.monitorsSkippedAlreadyExisting,
639
+ monitorsSkippedUnsupportedHost: first.monitorsSkippedUnsupportedHost,
640
+ devicesCreated: sum((pass: AutoImportRuleRunResult): number => {
641
+ return pass.devicesCreated;
642
+ }),
643
+ devicesFailed: sum((pass: AutoImportRuleRunResult): number => {
644
+ return pass.devicesFailed;
645
+ }),
646
+ monitorsWouldCreate: sum((pass: AutoImportRuleRunResult): number => {
647
+ return pass.monitorsWouldCreate;
648
+ }),
649
+ monitorsCreated: sum((pass: AutoImportRuleRunResult): number => {
650
+ return pass.monitorsCreated;
651
+ }),
652
+ monitorsFailed: sum((pass: AutoImportRuleRunResult): number => {
653
+ return pass.monitorsFailed;
654
+ }),
655
+ /*
656
+ * Failure reasons are a SET across the chain, not a per-pass list: the
657
+ * same fault usually reappears in every pass, and a distinct fault in
658
+ * pass three is exactly what the operator has to see. Deduplicated in
659
+ * order and capped the same way one pass is, so the dialog stays a
660
+ * summary however many passes ran.
661
+ */
662
+ deviceFailureReasons: mergeReasons(
663
+ passes.map((pass: AutoImportRuleRunResult): Array<string> => {
664
+ return pass.deviceFailureReasons;
665
+ }),
666
+ ),
667
+ monitorFailureReasons: mergeReasons(
668
+ passes.map((pass: AutoImportRuleRunResult): Array<string> => {
669
+ return pass.monitorFailureReasons;
670
+ }),
671
+ ),
672
+ /*
673
+ * Halting is a property of the pass that halted, and the chain stops
674
+ * chaining after one — so any pass having halted is the answer, not
675
+ * just the last.
676
+ */
677
+ monitorProvisioningHalted: passes.some(
678
+ (pass: AutoImportRuleRunResult): boolean => {
679
+ return pass.monitorProvisioningHalted;
680
+ },
681
+ ),
682
+ isTruncated: last.isTruncated,
683
+ hostsPendingImport: last.hostsPendingImport,
684
+ monitorsPendingCreation: last.monitorsPendingCreation,
685
+ hasUnevaluatedScans: last.hasUnevaluatedScans,
686
+ hasMoreScans: last.hasMoreScans,
687
+ isDryRun: first.isDryRun,
688
+ /*
689
+ * The first pass's sample is the one worth keeping: it was drawn
690
+ * before this run changed anything, so it answers "which hosts is this
691
+ * rule claiming" rather than "which hosts were left over by pass 19".
692
+ */
693
+ matchedIpAddressSample: first.matchedIpAddressSample,
694
+ };
695
+ }
696
+
314
697
  public static parseSiteAssignmentRuleRunResult(
315
698
  json: JSONObject | undefined | null,
316
699
  ): SiteAssignmentRuleRunResult {
@@ -385,7 +768,13 @@ export class RuleRunResultUtil {
385
768
  "monitorsSkippedUnsupportedHost",
386
769
  ),
387
770
  monitorsFailed: readCount(source, "monitorsFailed"),
771
+ deviceFailureReasons: readReasons(source, "deviceFailureReasons"),
772
+ monitorFailureReasons: readReasons(source, "monitorFailureReasons"),
773
+ monitorProvisioningHalted: source["monitorProvisioningHalted"] === true,
388
774
  isTruncated: source["isTruncated"] === true,
775
+ hostsPendingImport: readCount(source, "hostsPendingImport"),
776
+ monitorsPendingCreation: readCount(source, "monitorsPendingCreation"),
777
+ hasUnevaluatedScans: source["hasUnevaluatedScans"] === true,
389
778
  hasMoreScans: source["hasMoreScans"] === true,
390
779
  isDryRun: source["isDryRun"] === true,
391
780
  matchedIpAddressSample: sample as Array<string>,
@@ -8,6 +8,7 @@ import GreaterThanOrEqual from "./BaseDatabase/GreaterThanOrEqual";
8
8
  import InBetween from "./BaseDatabase/InBetween";
9
9
  import Includes from "./BaseDatabase/Includes";
10
10
  import IncludesAll from "./BaseDatabase/IncludesAll";
11
+ import IncludesAnyOfGroups from "./BaseDatabase/IncludesAnyOfGroups";
11
12
  import IncludesNone from "./BaseDatabase/IncludesNone";
12
13
  import StartsWith from "./BaseDatabase/StartsWith";
13
14
  import EndsWith from "./BaseDatabase/EndsWith";
@@ -155,6 +156,9 @@ const SerializableObjectDictionary: Dictionary<any> = {
155
156
  get [ObjectType.IncludesAll](): any {
156
157
  return IncludesAll;
157
158
  },
159
+ get [ObjectType.IncludesAnyOfGroups](): any {
160
+ return IncludesAnyOfGroups;
161
+ },
158
162
  get [ObjectType.IncludesNone](): any {
159
163
  return IncludesNone;
160
164
  },
@@ -0,0 +1,56 @@
1
+ import EntitySource from "./EntitySource";
2
+
3
+ export enum InventoryLiveness {
4
+ Live = "live",
5
+ Recent = "recent",
6
+ Stale = "stale",
7
+ Never = "never",
8
+ NotTracked = "not-tracked",
9
+ }
10
+
11
+ export const INVENTORY_LIVE_WINDOW_MINUTES: number = 30;
12
+ export const INVENTORY_STALE_AFTER_MINUTES: number = 24 * 60;
13
+
14
+ export interface InventoryLivenessInput {
15
+ source?: string | undefined;
16
+ lastSeenAt?: Date | string | undefined | null;
17
+ now: Date;
18
+ }
19
+
20
+ /**
21
+ * A last-seen timestamp is a heartbeat only for discovered items. Mirrored
22
+ * and manual items retain their creation timestamp, so aging those would
23
+ * incorrectly classify healthy infrastructure as stale.
24
+ */
25
+ export const getInventoryLivenessState: (
26
+ input: InventoryLivenessInput,
27
+ ) => InventoryLiveness = (input: InventoryLivenessInput): InventoryLiveness => {
28
+ if (input.source !== EntitySource.Discovered) {
29
+ return InventoryLiveness.NotTracked;
30
+ }
31
+
32
+ if (!input.lastSeenAt) {
33
+ return InventoryLiveness.Never;
34
+ }
35
+
36
+ const lastSeenAt: Date = new Date(input.lastSeenAt);
37
+
38
+ if (Number.isNaN(lastSeenAt.getTime())) {
39
+ return InventoryLiveness.Never;
40
+ }
41
+
42
+ const minutesSinceLastSeen: number = Math.max(
43
+ 0,
44
+ Math.floor((input.now.getTime() - lastSeenAt.getTime()) / (60 * 1000)),
45
+ );
46
+
47
+ if (minutesSinceLastSeen <= INVENTORY_LIVE_WINDOW_MINUTES) {
48
+ return InventoryLiveness.Live;
49
+ }
50
+
51
+ if (minutesSinceLastSeen <= INVENTORY_STALE_AFTER_MINUTES) {
52
+ return InventoryLiveness.Recent;
53
+ }
54
+
55
+ return InventoryLiveness.Stale;
56
+ };
@@ -93,7 +93,14 @@ const CSVFileUpload: FunctionComponent<ComponentProps> = (
93
93
  const [error, setError] = useState<string>("");
94
94
  const [parsedRows, setParsedRows] = useState<Array<CSVRow>>([]);
95
95
  const [fileName, setFileName] = useState<string>("");
96
- const fileInputRef: React.RefObject<HTMLInputElement | null> =
96
+ /*
97
+ * The element type goes in T on its own: RefObject<T> already declares
98
+ * `current` as `T | null`, and RefObject<HTMLInputElement | null> is a
99
+ * structurally identical but non-assignable type, rejected on the `ref`
100
+ * prop below because RefObject is covariant in T. That mismatch is what
101
+ * used to need a cast there.
102
+ */
103
+ const fileInputRef: React.RefObject<HTMLInputElement> =
97
104
  useRef<HTMLInputElement>(null);
98
105
 
99
106
  const downloadTemplate: DownloadTemplateFunction = (): void => {
@@ -270,7 +277,7 @@ const CSVFileUpload: FunctionComponent<ComponentProps> = (
270
277
  </div>
271
278
  <p className="text-xs text-gray-500">CSV files only</p>
272
279
  <input
273
- ref={fileInputRef as React.RefObject<HTMLInputElement>}
280
+ ref={fileInputRef}
274
281
  type="file"
275
282
  accept=".csv"
276
283
  className="sr-only"