@oneuptime/common 12.0.9 → 12.0.11

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 (262) hide show
  1. package/Models/DatabaseModels/NetworkDevice.ts +44 -0
  2. package/Models/DatabaseModels/NetworkDeviceLink.ts +116 -0
  3. package/Models/DatabaseModels/RunnerJob.ts +17 -2
  4. package/Server/API/LlmProviderAPI.ts +8 -0
  5. package/Server/Infrastructure/Postgres/SchemaMigrations/1787400000000-AddDeviceRoleAndDeclaredLinkParent.ts +52 -0
  6. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
  7. package/Server/Services/NetworkDeviceLabelRuleEngineService.ts +240 -0
  8. package/Server/Services/NetworkDeviceLinkService.ts +338 -2
  9. package/Server/Services/NetworkDeviceService.ts +265 -12
  10. package/Server/Services/RunnerJobService.ts +24 -1
  11. package/Server/Types/Billing/MeteredPlan/AllMeteredPlans.ts +19 -5
  12. package/Server/Types/Database/JSONColumnQuery.ts +42 -4
  13. package/Server/Utils/LLM/LLMService.ts +108 -19
  14. package/Server/Utils/Monitor/MonitorAlert.ts +22 -115
  15. package/Server/Utils/Monitor/MonitorIncident.ts +7 -337
  16. package/Server/Utils/Monitor/MonitorMaintenanceSuppression.ts +27 -2
  17. package/Server/Utils/Monitor/SeriesResourceLabels.ts +32 -0
  18. package/Server/Utils/Monitor/SeriesResourceLinker.ts +469 -0
  19. package/Server/Utils/SSRFProtection.ts +5 -4
  20. package/Server/Utils/Telemetry/InventoryEntityRegistry.ts +186 -38
  21. package/Tests/App/AdminDashboard/AdminUsersBulkAddToProject.test.tsx +955 -0
  22. package/Tests/App/AdminDashboard/BulkAddUsersToProject.test.ts +778 -0
  23. package/Tests/App/AdminDashboard/BulkAddUsersToProjectModal.test.tsx +1051 -0
  24. package/Tests/App/Dashboard/AdminNotificationRulesPage.test.tsx +420 -112
  25. package/Tests/App/Dashboard/OnCallReadinessSurfaces.test.tsx +81 -6
  26. package/Tests/App/Dashboard/PayAsYouGoConsentGate.test.tsx +543 -0
  27. package/Tests/App/Dashboard/PayAsYouGoFreePlan.test.ts +125 -0
  28. package/Tests/App/Dashboard/PayAsYouGoNotices.test.tsx +612 -0
  29. package/Tests/App/Dashboard/SloWidgetChartRendering.test.tsx +536 -0
  30. package/Tests/App/Dashboard/UserElementEmail.test.tsx +447 -0
  31. package/Tests/App/Dashboard/UserEmailCallSites.test.tsx +284 -0
  32. package/Tests/Models/AnalyticsModels/CommonModel.test.ts +312 -0
  33. package/Tests/Server/Infrastructure/Postgres/DeviceRoleAndDeclaredLinkParentMigration.test.ts +250 -0
  34. package/Tests/Server/Infrastructure/Postgres/EpisodeMemberNotifyIndexesMigration.test.ts +46 -18
  35. package/Tests/Server/Services/NetworkDeviceLabelRuleRun.test.ts +618 -0
  36. package/Tests/Server/Services/NetworkDeviceLinkParentValidation.test.ts +1043 -0
  37. package/Tests/Server/Services/NetworkSiteAssignmentRuleRun.test.ts +706 -0
  38. package/Tests/Server/Services/RunnerJobEnqueue.test.ts +412 -0
  39. package/Tests/Server/Types/Billing/PayAsYouGoMeteredPricing.test.ts +125 -0
  40. package/Tests/Server/Types/Database/JSONColumnQuery.test.ts +508 -0
  41. package/Tests/Server/Utils/AI/LLMServiceRequestPolicy.test.ts +2 -2
  42. package/Tests/Server/Utils/AI/LLMServiceRetryPolicy.test.ts +450 -0
  43. package/Tests/Server/Utils/Monitor/MonitorAlertResourceLinking.test.ts +556 -0
  44. package/Tests/Server/Utils/Monitor/MonitorIncidentResourceLinking.test.ts +547 -0
  45. package/Tests/Server/Utils/Monitor/MonitorMaintenanceSuppression.test.ts +30 -0
  46. package/Tests/Server/Utils/Monitor/SeriesResourceLinker.test.ts +699 -0
  47. package/Tests/Server/Utils/SeriesResourceLabels.test.ts +55 -0
  48. package/Tests/Server/Utils/Telemetry/InventoryEntityRegistry.test.ts +668 -0
  49. package/Tests/Types/API/URLMalformedTolerance.test.ts +301 -0
  50. package/Tests/Types/API/URLTelScheme.test.ts +261 -0
  51. package/Tests/Types/API/URLUnsupportedScheme.test.ts +148 -0
  52. package/Tests/Types/Billing/PayAsYouGoPricing.test.ts +64 -0
  53. package/Tests/Types/CustomField/CustomFieldTypeCorrespondence.test.ts +107 -0
  54. package/Tests/Types/Monitor/MonitorType.test.ts +62 -0
  55. package/Tests/Types/NetworkAutomation/RuleRunResult.test.ts +184 -0
  56. package/Tests/Types/Runbook/RunbookStepType.test.ts +124 -0
  57. package/Tests/Types/Workflow/BaseModelComponents.test.ts +17 -0
  58. package/Tests/UI/Components/Button.test.tsx +279 -0
  59. package/Tests/UI/Components/Charts/ChartXAxisDataKey.test.tsx +256 -0
  60. package/Tests/UI/Components/Charts/DataPointUtil.test.ts +323 -0
  61. package/Tests/UI/Components/DictionaryValuePlaceholderAndSuggestions.test.tsx +595 -0
  62. package/Tests/UI/Components/Icon.test.tsx +57 -0
  63. package/Tests/UI/Components/List.test.tsx +20 -3
  64. package/Tests/UI/Components/LogsSavedViewsDropdown.test.tsx +304 -0
  65. package/Tests/UI/Components/LogsViewerClearSavedView.test.tsx +179 -0
  66. package/Tests/UI/Components/ModelTable/CustomFieldColumns.test.tsx +349 -1
  67. package/Tests/UI/Components/Pagination.test.tsx +721 -146
  68. package/Tests/UI/Components/PaginationIntegration.test.tsx +386 -0
  69. package/Tests/UI/Components/PaginationUtil.test.ts +417 -0
  70. package/Tests/UI/Components/TelemetrySavedViewsDropdown.test.tsx +343 -0
  71. package/Tests/UI/Components/Workflow/ColumnEditor/ColumnControl.test.ts +289 -0
  72. package/Tests/UI/Components/Workflow/ColumnEditor/ColumnOperators.test.ts +271 -0
  73. package/Tests/UI/Components/Workflow/ColumnEditor/ColumnRow.test.ts +119 -0
  74. package/Tests/UI/Components/Workflow/ColumnEditor/ColumnRowSerialization.test.ts +968 -0
  75. package/Tests/UI/Components/Workflow/ModelColumnEditor.render.test.tsx +766 -0
  76. package/Tests/UI/Utils/RunNetworkRule.test.ts +320 -0
  77. package/Tests/Utils/APIRetryPolicy.test.ts +823 -0
  78. package/Tests/Utils/Monitor/NetworkDeviceRoleUtil.test.ts +502 -0
  79. package/Tests/Utils/Monitor/NetworkTopologyDeclaredHierarchy.test.ts +759 -0
  80. package/Tests/Utils/NetworkAutomation/RuleRunSummary.test.ts +286 -0
  81. package/Tests/Utils/Schema/BaseSchema.test.ts +651 -0
  82. package/Types/API/Protocol.ts +9 -0
  83. package/Types/API/URL.ts +286 -20
  84. package/Types/Billing/PayAsYouGoPricing.ts +47 -0
  85. package/Types/CustomField/CustomFieldType.ts +28 -0
  86. package/Types/Monitor/MonitorType.ts +11 -0
  87. package/Types/Monitor/SnmpMonitor/NetworkTopology.ts +13 -0
  88. package/Types/NetworkAutomation/RuleRunResult.ts +121 -0
  89. package/Types/Runbook/RunbookStepType.ts +23 -0
  90. package/UI/Components/AutocompleteTextInput/AutocompleteTextInput.tsx +7 -0
  91. package/UI/Components/Button/Button.tsx +14 -22
  92. package/UI/Components/Charts/Area/AreaChart.tsx +4 -2
  93. package/UI/Components/Charts/Bar/BarChart.tsx +4 -2
  94. package/UI/Components/Charts/ChartLibrary/Types/ChartDataPoint.ts +13 -0
  95. package/UI/Components/Charts/Line/LineChart.tsx +4 -2
  96. package/UI/Components/Charts/Types/XAxis/XAxis.ts +6 -0
  97. package/UI/Components/Charts/Utils/DataPoint.ts +15 -12
  98. package/UI/Components/Dictionary/Dictionary.tsx +4 -39
  99. package/UI/Components/Icon/Icon.tsx +1 -1
  100. package/UI/Components/LogsViewer/LogsViewer.tsx +5 -1
  101. package/UI/Components/LogsViewer/components/LogsFacetSidebar.tsx +8 -0
  102. package/UI/Components/LogsViewer/components/LogsPagination.tsx +29 -91
  103. package/UI/Components/LogsViewer/components/LogsViewerToolbar.tsx +2 -0
  104. package/UI/Components/LogsViewer/components/SavedViewsDropdown.tsx +66 -1
  105. package/UI/Components/ModelTable/CustomFieldColumns.tsx +55 -0
  106. package/UI/Components/Pagination/Pagination.tsx +373 -328
  107. package/UI/Components/Pagination/PaginationUtil.ts +263 -0
  108. package/UI/Components/Pagination/Screenshots/README.md +15 -0
  109. package/UI/Components/Pagination/Screenshots/pagination-before-after.png +0 -0
  110. package/UI/Components/Pagination/Screenshots/pagination-go-to-page-modal.png +0 -0
  111. package/UI/Components/Pagination/Screenshots/pagination-long-list.png +0 -0
  112. package/UI/Components/Pagination/Screenshots/pagination-mobile.png +0 -0
  113. package/UI/Components/Pagination/Screenshots/pagination-telemetry.png +0 -0
  114. package/UI/Components/TelemetryViewer/components/SavedViewsDropdown.tsx +66 -1
  115. package/UI/Components/TelemetryViewer/components/TelemetryPagination.tsx +38 -92
  116. package/UI/Components/TextArea/TextArea.tsx +3 -0
  117. package/UI/Components/Workflow/ArgumentsForm.tsx +18 -1
  118. package/UI/Components/Workflow/ColumnEditor/AddColumnDropdown.tsx +177 -0
  119. package/UI/Components/Workflow/ColumnEditor/ColumnConditionRow.tsx +243 -0
  120. package/UI/Components/Workflow/ColumnEditor/ColumnControl.ts +255 -0
  121. package/UI/Components/Workflow/ColumnEditor/ColumnFieldRow.tsx +178 -0
  122. package/UI/Components/Workflow/ColumnEditor/ColumnOperators.ts +206 -0
  123. package/UI/Components/Workflow/ColumnEditor/ColumnRow.ts +157 -0
  124. package/UI/Components/Workflow/ColumnEditor/ColumnRowSerialization.ts +517 -0
  125. package/UI/Components/Workflow/ColumnEditor/ColumnValueInput.tsx +482 -0
  126. package/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.tsx +131 -0
  127. package/UI/Components/Workflow/ColumnEditor/ModelRecordForm.tsx +163 -0
  128. package/UI/Components/Workflow/ModelColumnEditor.tsx +284 -100
  129. package/UI/Utils/NetworkAutomation/RunNetworkRule.ts +122 -0
  130. package/Utils/API.ts +243 -36
  131. package/Utils/Monitor/NetworkDeviceRoleUtil.ts +45 -0
  132. package/Utils/Monitor/NetworkTopologyUtil.ts +42 -2
  133. package/Utils/NetworkAutomation/RuleRunSummary.ts +147 -0
  134. package/build/dist/Models/DatabaseModels/NetworkDevice.js +45 -0
  135. package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
  136. package/build/dist/Models/DatabaseModels/NetworkDeviceLink.js +115 -0
  137. package/build/dist/Models/DatabaseModels/NetworkDeviceLink.js.map +1 -1
  138. package/build/dist/Models/DatabaseModels/RunnerJob.js +17 -2
  139. package/build/dist/Models/DatabaseModels/RunnerJob.js.map +1 -1
  140. package/build/dist/Server/API/LlmProviderAPI.js +9 -1
  141. package/build/dist/Server/API/LlmProviderAPI.js.map +1 -1
  142. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787400000000-AddDeviceRoleAndDeclaredLinkParent.js +33 -0
  143. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787400000000-AddDeviceRoleAndDeclaredLinkParent.js.map +1 -0
  144. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
  145. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  146. package/build/dist/Server/Services/NetworkDeviceLabelRuleEngineService.js +202 -0
  147. package/build/dist/Server/Services/NetworkDeviceLabelRuleEngineService.js.map +1 -1
  148. package/build/dist/Server/Services/NetworkDeviceLinkService.js +253 -1
  149. package/build/dist/Server/Services/NetworkDeviceLinkService.js.map +1 -1
  150. package/build/dist/Server/Services/NetworkDeviceService.js +223 -12
  151. package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
  152. package/build/dist/Server/Services/RunnerJobService.js +20 -2
  153. package/build/dist/Server/Services/RunnerJobService.js.map +1 -1
  154. package/build/dist/Server/Types/Billing/MeteredPlan/AllMeteredPlans.js +12 -5
  155. package/build/dist/Server/Types/Billing/MeteredPlan/AllMeteredPlans.js.map +1 -1
  156. package/build/dist/Server/Types/Database/JSONColumnQuery.js +40 -4
  157. package/build/dist/Server/Types/Database/JSONColumnQuery.js.map +1 -1
  158. package/build/dist/Server/Utils/LLM/LLMService.js +78 -22
  159. package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -1
  160. package/build/dist/Server/Utils/Monitor/MonitorAlert.js +22 -98
  161. package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
  162. package/build/dist/Server/Utils/Monitor/MonitorIncident.js +7 -254
  163. package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
  164. package/build/dist/Server/Utils/Monitor/MonitorMaintenanceSuppression.js +14 -2
  165. package/build/dist/Server/Utils/Monitor/MonitorMaintenanceSuppression.js.map +1 -1
  166. package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js +27 -0
  167. package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js.map +1 -1
  168. package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js +285 -0
  169. package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js.map +1 -0
  170. package/build/dist/Server/Utils/SSRFProtection.js +5 -4
  171. package/build/dist/Server/Utils/SSRFProtection.js.map +1 -1
  172. package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js +128 -35
  173. package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js.map +1 -1
  174. package/build/dist/Types/API/Protocol.js +9 -0
  175. package/build/dist/Types/API/Protocol.js.map +1 -1
  176. package/build/dist/Types/API/URL.js +241 -18
  177. package/build/dist/Types/API/URL.js.map +1 -1
  178. package/build/dist/Types/Billing/PayAsYouGoPricing.js +41 -0
  179. package/build/dist/Types/Billing/PayAsYouGoPricing.js.map +1 -0
  180. package/build/dist/Types/CustomField/CustomFieldType.js +26 -0
  181. package/build/dist/Types/CustomField/CustomFieldType.js.map +1 -1
  182. package/build/dist/Types/Monitor/MonitorType.js +10 -0
  183. package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
  184. package/build/dist/Types/NetworkAutomation/RuleRunResult.js +39 -0
  185. package/build/dist/Types/NetworkAutomation/RuleRunResult.js.map +1 -0
  186. package/build/dist/Types/Runbook/RunbookStepType.js +21 -0
  187. package/build/dist/Types/Runbook/RunbookStepType.js.map +1 -1
  188. package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js +1 -1
  189. package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js.map +1 -1
  190. package/build/dist/UI/Components/Button/Button.js +10 -16
  191. package/build/dist/UI/Components/Button/Button.js.map +1 -1
  192. package/build/dist/UI/Components/Charts/Area/AreaChart.js +2 -1
  193. package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -1
  194. package/build/dist/UI/Components/Charts/Bar/BarChart.js +2 -1
  195. package/build/dist/UI/Components/Charts/Bar/BarChart.js.map +1 -1
  196. package/build/dist/UI/Components/Charts/ChartLibrary/Types/ChartDataPoint.js +12 -0
  197. package/build/dist/UI/Components/Charts/ChartLibrary/Types/ChartDataPoint.js.map +1 -1
  198. package/build/dist/UI/Components/Charts/Line/LineChart.js +2 -1
  199. package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
  200. package/build/dist/UI/Components/Charts/Utils/DataPoint.js +17 -10
  201. package/build/dist/UI/Components/Charts/Utils/DataPoint.js.map +1 -1
  202. package/build/dist/UI/Components/Dictionary/Dictionary.js +4 -18
  203. package/build/dist/UI/Components/Dictionary/Dictionary.js.map +1 -1
  204. package/build/dist/UI/Components/Icon/Icon.js +1 -1
  205. package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
  206. package/build/dist/UI/Components/LogsViewer/LogsViewer.js +3 -3
  207. package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
  208. package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js +5 -1
  209. package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js.map +1 -1
  210. package/build/dist/UI/Components/LogsViewer/components/LogsPagination.js +14 -44
  211. package/build/dist/UI/Components/LogsViewer/components/LogsPagination.js.map +1 -1
  212. package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js +1 -1
  213. package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js.map +1 -1
  214. package/build/dist/UI/Components/LogsViewer/components/SavedViewsDropdown.js +20 -2
  215. package/build/dist/UI/Components/LogsViewer/components/SavedViewsDropdown.js.map +1 -1
  216. package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js +42 -0
  217. package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js.map +1 -1
  218. package/build/dist/UI/Components/Pagination/Pagination.js +174 -199
  219. package/build/dist/UI/Components/Pagination/Pagination.js.map +1 -1
  220. package/build/dist/UI/Components/Pagination/PaginationUtil.js +160 -0
  221. package/build/dist/UI/Components/Pagination/PaginationUtil.js.map +1 -0
  222. package/build/dist/UI/Components/TelemetryViewer/components/SavedViewsDropdown.js +20 -2
  223. package/build/dist/UI/Components/TelemetryViewer/components/SavedViewsDropdown.js.map +1 -1
  224. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryPagination.js +23 -47
  225. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryPagination.js.map +1 -1
  226. package/build/dist/UI/Components/TextArea/TextArea.js +1 -1
  227. package/build/dist/UI/Components/TextArea/TextArea.js.map +1 -1
  228. package/build/dist/UI/Components/Workflow/ArgumentsForm.js +14 -2
  229. package/build/dist/UI/Components/Workflow/ArgumentsForm.js.map +1 -1
  230. package/build/dist/UI/Components/Workflow/ColumnEditor/AddColumnDropdown.js +86 -0
  231. package/build/dist/UI/Components/Workflow/ColumnEditor/AddColumnDropdown.js.map +1 -0
  232. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnConditionRow.js +119 -0
  233. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnConditionRow.js.map +1 -0
  234. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnControl.js +206 -0
  235. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnControl.js.map +1 -0
  236. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnFieldRow.js +57 -0
  237. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnFieldRow.js.map +1 -0
  238. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnOperators.js +150 -0
  239. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnOperators.js.map +1 -0
  240. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnRow.js +67 -0
  241. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnRow.js.map +1 -0
  242. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnRowSerialization.js +309 -0
  243. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnRowSerialization.js.map +1 -0
  244. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnValueInput.js +204 -0
  245. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnValueInput.js.map +1 -0
  246. package/build/dist/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.js +66 -0
  247. package/build/dist/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.js.map +1 -0
  248. package/build/dist/UI/Components/Workflow/ColumnEditor/ModelRecordForm.js +83 -0
  249. package/build/dist/UI/Components/Workflow/ColumnEditor/ModelRecordForm.js.map +1 -0
  250. package/build/dist/UI/Components/Workflow/ModelColumnEditor.js +187 -68
  251. package/build/dist/UI/Components/Workflow/ModelColumnEditor.js.map +1 -1
  252. package/build/dist/UI/Utils/NetworkAutomation/RunNetworkRule.js +75 -0
  253. package/build/dist/UI/Utils/NetworkAutomation/RunNetworkRule.js.map +1 -0
  254. package/build/dist/Utils/API.js +175 -34
  255. package/build/dist/Utils/API.js.map +1 -1
  256. package/build/dist/Utils/Monitor/NetworkDeviceRoleUtil.js +37 -0
  257. package/build/dist/Utils/Monitor/NetworkDeviceRoleUtil.js.map +1 -1
  258. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +25 -3
  259. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
  260. package/build/dist/Utils/NetworkAutomation/RuleRunSummary.js +78 -0
  261. package/build/dist/Utils/NetworkAutomation/RuleRunSummary.js.map +1 -0
  262. package/package.json +1 -1
@@ -0,0 +1,482 @@
1
+ /*
2
+ * One value cell.
3
+ *
4
+ * The control comes from the column's type in the model schema, so nobody is
5
+ * asked whether the thing they are typing is Text, Number or Boolean - the
6
+ * model already said. What is left for the builder to choose is the one thing
7
+ * the schema genuinely cannot know: whether this value is typed in, or read
8
+ * from an earlier step at run time.
9
+ */
10
+
11
+ import Color from "../../../../Types/Color";
12
+ import IconProp from "../../../../Types/Icon/IconProp";
13
+ import AutocompleteTextInput from "../../AutocompleteTextInput/AutocompleteTextInput";
14
+ import { DictionaryFilterOperatorOption } from "../../Dictionary/DictionaryFilterOperator";
15
+ import ColorPicker from "../../Forms/Fields/ColorPicker";
16
+ import Icon from "../../Icon/Icon";
17
+ import Input, { InputType } from "../../Input/Input";
18
+ import TextArea from "../../TextArea/TextArea";
19
+ import { ColumnValueMode, ModelColumnControl } from "./ColumnRow";
20
+ import { containsTemplateExpression } from "./ColumnRowSerialization";
21
+ import React, { FunctionComponent, ReactElement } from "react";
22
+
23
+ export interface ComponentProps {
24
+ control: ModelColumnControl;
25
+ valueMode: ColumnValueMode;
26
+ text: string;
27
+ values: Array<string>;
28
+ /** Absent in record mode, where every row is an equality. */
29
+ operatorOption?: DictionaryFilterOperatorOption | undefined;
30
+ placeholder?: string | undefined;
31
+ /** The other steps' return values and the workflow's variables. */
32
+ suggestions?: Array<string> | undefined;
33
+ /**
34
+ * Every edit reports as one change.
35
+ *
36
+ * Deliberately not three callbacks: two of them fired from a single event
37
+ * both close over the same render's row, so the second silently discards the
38
+ * first - which is what made a raw cell impossible to type into and the
39
+ * "typing {{ turns this into a reference" behaviour dead.
40
+ */
41
+ onChange: (
42
+ change: Partial<{
43
+ text: string;
44
+ values: Array<string>;
45
+ valueMode: ColumnValueMode;
46
+ }>,
47
+ ) => void;
48
+ tabIndex?: number | undefined;
49
+ ariaLabelledby?: string | undefined;
50
+ /** Focused on mount, so picking a field lands the caret in its value box. */
51
+ autoFocus?: boolean | undefined;
52
+ dataTestId?: string | undefined;
53
+ }
54
+
55
+ const INPUT_WRAPPER_CLASS: string = "relative w-full";
56
+
57
+ const MONO_INPUT_CLASS: string =
58
+ "block w-full rounded-md border-0 bg-transparent py-1 pl-0 pr-0 font-mono text-xs text-indigo-900 placeholder-indigo-300 focus:outline-none focus:ring-0";
59
+
60
+ const ColumnValueInput: FunctionComponent<ComponentProps> = (
61
+ props: ComponentProps,
62
+ ): ReactElement => {
63
+ const isReference: boolean = props.valueMode === ColumnValueMode.Reference;
64
+
65
+ type SetTextFunction = (value: string) => void;
66
+
67
+ /*
68
+ * Typing "{{" anywhere in a text control means the builder is reaching for
69
+ * another step's output, so the cell becomes a reference there and then and
70
+ * keeps what has been typed. Without this the autocomplete would never open
71
+ * unless the reference button had been found first.
72
+ */
73
+ const setTextAndDetectReference: SetTextFunction = (value: string): void => {
74
+ if (!isReference && value.includes("{{")) {
75
+ props.onChange({ text: value, valueMode: ColumnValueMode.Reference });
76
+ return;
77
+ }
78
+
79
+ props.onChange({ text: value });
80
+ };
81
+
82
+ /*
83
+ * Spelled "{ }" rather than drawn as an icon. The shared variable glyph is a
84
+ * dashed square that reads as a placeholder or a spinner, and this button has
85
+ * to say what it does at a glance - it is the only route from a typed value to
86
+ * an earlier step's output, which is how most of these payloads are built.
87
+ */
88
+ const referenceButton: ReactElement = (
89
+ <button
90
+ type="button"
91
+ aria-pressed={isReference}
92
+ title={
93
+ isReference
94
+ ? "Type a value instead"
95
+ : "Use a value from an earlier step"
96
+ }
97
+ aria-label={
98
+ isReference
99
+ ? "Type a value instead"
100
+ : "Use a value from an earlier step"
101
+ }
102
+ data-testid={`${props.dataTestId || "model-column"}-reference-toggle`}
103
+ className={`shrink-0 rounded-md border px-1.5 py-1 font-mono text-[11px] leading-none transition-colors focus:outline-none focus:ring-1 focus:ring-indigo-500 ${
104
+ isReference
105
+ ? "border-indigo-200 bg-white text-indigo-500 hover:bg-indigo-50"
106
+ : "border-transparent text-gray-400 hover:border-gray-200 hover:bg-gray-50 hover:text-gray-600"
107
+ }`}
108
+ onClick={() => {
109
+ props.onChange({
110
+ valueMode: isReference
111
+ ? ColumnValueMode.Literal
112
+ : ColumnValueMode.Reference,
113
+ });
114
+ }}
115
+ >
116
+ {isReference ? "abc" : "{ }"}
117
+ </button>
118
+ );
119
+
120
+ // An operator like "is not set" takes no value at all.
121
+ if (props.operatorOption?.hidesValueInput) {
122
+ return (
123
+ <div className="flex h-9 items-center rounded-md border border-dashed border-gray-200 px-3 text-xs text-gray-400">
124
+ No value needed
125
+ </div>
126
+ );
127
+ }
128
+
129
+ if (props.operatorOption?.expectsMultiValue) {
130
+ return (
131
+ <MultiValueInput
132
+ values={props.values}
133
+ placeholder={props.placeholder}
134
+ ariaLabelledby={props.ariaLabelledby}
135
+ dataTestId={props.dataTestId}
136
+ onChange={(values: Array<string>) => {
137
+ props.onChange({ values: values });
138
+ }}
139
+ />
140
+ );
141
+ }
142
+
143
+ if (isReference) {
144
+ return (
145
+ <div className="flex w-full items-center gap-1.5 rounded-md border border-indigo-200 bg-indigo-50/40 px-2 py-1">
146
+ <span className="shrink-0 font-mono text-[11px] text-indigo-400">
147
+ {"{ }"}
148
+ </span>
149
+ <div className="min-w-0 flex-1">
150
+ <AutocompleteTextInput
151
+ value={props.text}
152
+ placeholder="{{local.components.step.returnValues.value}}"
153
+ suggestions={props.suggestions}
154
+ className={MONO_INPUT_CLASS}
155
+ outerDivClassName={INPUT_WRAPPER_CLASS}
156
+ disableSpellCheck={true}
157
+ autoFocus={props.autoFocus}
158
+ ariaLabelledby={props.ariaLabelledby}
159
+ dataTestId={props.dataTestId}
160
+ onChange={(value: string) => {
161
+ props.onChange({ text: value });
162
+ }}
163
+ />
164
+ </div>
165
+ {referenceButton}
166
+ </div>
167
+ );
168
+ }
169
+
170
+ type RenderControlFunction = () => ReactElement;
171
+
172
+ const renderControl: RenderControlFunction = (): ReactElement => {
173
+ /*
174
+ * A value the typed control cannot hold - text where the column wants a
175
+ * number, most often - is edited as text and written back exactly as it
176
+ * was read. Forcing it through the typed control would rewrite it.
177
+ */
178
+ if (props.valueMode === ColumnValueMode.Raw) {
179
+ return (
180
+ <Input
181
+ value={props.text}
182
+ placeholder={props.placeholder}
183
+ outerDivClassName={INPUT_WRAPPER_CLASS}
184
+ ariaLabelledby={props.ariaLabelledby}
185
+ dataTestId={props.dataTestId}
186
+ tabIndex={props.tabIndex}
187
+ autoFocus={props.autoFocus}
188
+ onChange={(value: string) => {
189
+ /*
190
+ * The first keystroke ends raw mode: what is in the box is now what
191
+ * the builder means, so it is written in the column's own type. The
192
+ * mode and the text move together in one change - reported
193
+ * separately, the second report would overwrite the first and the
194
+ * cell could never be typed into at all.
195
+ */
196
+ props.onChange({
197
+ text: value,
198
+ valueMode: value.includes("{{")
199
+ ? ColumnValueMode.Reference
200
+ : ColumnValueMode.Literal,
201
+ });
202
+ }}
203
+ />
204
+ );
205
+ }
206
+
207
+ switch (props.control) {
208
+ case ModelColumnControl.Boolean:
209
+ return (
210
+ <BooleanSegments
211
+ value={props.text}
212
+ ariaLabelledby={props.ariaLabelledby}
213
+ autoFocus={props.autoFocus}
214
+ dataTestId={props.dataTestId}
215
+ onChange={(value: string) => {
216
+ props.onChange({ text: value });
217
+ }}
218
+ />
219
+ );
220
+
221
+ case ModelColumnControl.Number:
222
+ return (
223
+ <Input
224
+ type={InputType.NUMBER}
225
+ value={props.text}
226
+ placeholder={props.placeholder || "0"}
227
+ outerDivClassName={INPUT_WRAPPER_CLASS}
228
+ ariaLabelledby={props.ariaLabelledby}
229
+ dataTestId={props.dataTestId}
230
+ tabIndex={props.tabIndex}
231
+ autoFocus={props.autoFocus}
232
+ onChange={(value: string) => {
233
+ props.onChange({ text: value });
234
+ }}
235
+ />
236
+ );
237
+
238
+ case ModelColumnControl.Date:
239
+ return (
240
+ <Input
241
+ type={InputType.DATETIME_LOCAL}
242
+ value={props.text}
243
+ outerDivClassName={INPUT_WRAPPER_CLASS}
244
+ ariaLabelledby={props.ariaLabelledby}
245
+ dataTestId={props.dataTestId}
246
+ tabIndex={props.tabIndex}
247
+ autoFocus={props.autoFocus}
248
+ onChange={(value: string) => {
249
+ props.onChange({ text: value });
250
+ }}
251
+ />
252
+ );
253
+
254
+ case ModelColumnControl.Color:
255
+ return (
256
+ <ColorPicker
257
+ value={props.text}
258
+ placeholder={props.placeholder || "#000000"}
259
+ ariaLabelledby={props.ariaLabelledby}
260
+ dataTestId={props.dataTestId}
261
+ tabIndex={props.tabIndex}
262
+ onChange={(value: Color | null) => {
263
+ props.onChange({ text: value ? value.toString() : "" });
264
+ }}
265
+ />
266
+ );
267
+
268
+ case ModelColumnControl.LongText:
269
+ return (
270
+ <TextArea
271
+ value={props.text}
272
+ placeholder={props.placeholder}
273
+ className="block w-full rounded-md border border-gray-300 bg-white py-2 px-3 text-sm placeholder-gray-500 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 resize-y min-h-16"
274
+ ariaLabelledby={props.ariaLabelledby}
275
+ dataTestId={props.dataTestId}
276
+ tabIndex={props.tabIndex}
277
+ autoFocus={props.autoFocus}
278
+ onChange={setTextAndDetectReference}
279
+ />
280
+ );
281
+
282
+ default:
283
+ return (
284
+ <AutocompleteTextInput
285
+ value={props.text}
286
+ placeholder={props.placeholder}
287
+ suggestions={props.suggestions}
288
+ outerDivClassName={INPUT_WRAPPER_CLASS}
289
+ className={
290
+ props.control === ModelColumnControl.ObjectId
291
+ ? "block w-full rounded-md border border-gray-300 bg-white py-2 pl-3 pr-3 font-mono text-xs text-gray-900 placeholder-gray-400 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500"
292
+ : undefined
293
+ }
294
+ ariaLabelledby={props.ariaLabelledby}
295
+ autoFocus={props.autoFocus}
296
+ dataTestId={props.dataTestId}
297
+ onChange={setTextAndDetectReference}
298
+ />
299
+ );
300
+ }
301
+ };
302
+
303
+ return (
304
+ <div className="flex w-full items-center gap-1">
305
+ <div className="min-w-0 flex-1">{renderControl()}</div>
306
+ {referenceButton}
307
+ </div>
308
+ );
309
+ };
310
+
311
+ interface BooleanSegmentsProps {
312
+ value: string;
313
+ ariaLabelledby?: string | undefined;
314
+ autoFocus?: boolean | undefined;
315
+ dataTestId?: string | undefined;
316
+ onChange: (value: string) => void;
317
+ }
318
+
319
+ /*
320
+ * Three states, not two. A boolean column with nothing typed into it is not
321
+ * "false" - it is a field the record does not set, and a toggle cannot say
322
+ * that. (The shared Toggle component also fires its onChange twice per click.)
323
+ */
324
+ const BooleanSegments: FunctionComponent<BooleanSegmentsProps> = (
325
+ props: BooleanSegmentsProps,
326
+ ): ReactElement => {
327
+ const segments: Array<{ label: string; value: string }> = [
328
+ { label: "True", value: "true" },
329
+ { label: "False", value: "false" },
330
+ { label: "Not set", value: "" },
331
+ ];
332
+
333
+ return (
334
+ <div
335
+ className="inline-flex rounded-md border border-gray-300 bg-white p-0.5"
336
+ role="group"
337
+ aria-labelledby={props.ariaLabelledby}
338
+ data-testid={props.dataTestId}
339
+ >
340
+ {segments.map((segment: { label: string; value: string }) => {
341
+ const isSelected: boolean = props.value === segment.value;
342
+
343
+ return (
344
+ <button
345
+ key={segment.label}
346
+ type="button"
347
+ autoFocus={props.autoFocus && segment.value === "true"}
348
+ aria-pressed={isSelected}
349
+ className={`rounded px-2.5 py-1 text-xs font-medium transition-colors ${
350
+ isSelected
351
+ ? "bg-indigo-50 text-indigo-700"
352
+ : "text-gray-500 hover:text-gray-700"
353
+ }`}
354
+ onClick={() => {
355
+ props.onChange(segment.value);
356
+ }}
357
+ >
358
+ {segment.label}
359
+ </button>
360
+ );
361
+ })}
362
+ </div>
363
+ );
364
+ };
365
+
366
+ interface MultiValueInputProps {
367
+ values: Array<string>;
368
+ placeholder?: string | undefined;
369
+ ariaLabelledby?: string | undefined;
370
+ dataTestId?: string | undefined;
371
+ onChange: (values: Array<string>) => void;
372
+ }
373
+
374
+ /*
375
+ * "is any of" holds a list. The previous editor rendered a multi-select whose
376
+ * options were only ever the suggestions it had been handed, so a value nobody
377
+ * had suggested could not be entered at all - which for a list of IDs is every
378
+ * value. This is a chip input: type, press Enter or comma, and it is in.
379
+ */
380
+ const MultiValueInput: FunctionComponent<MultiValueInputProps> = (
381
+ props: MultiValueInputProps,
382
+ ): ReactElement => {
383
+ const [draft, setDraft] = React.useState<string>("");
384
+
385
+ type CommitDraftFunction = () => void;
386
+
387
+ const commitDraft: CommitDraftFunction = (): void => {
388
+ const entry: string = draft.trim();
389
+
390
+ if (entry === "" || props.values.includes(entry)) {
391
+ setDraft("");
392
+ return;
393
+ }
394
+
395
+ props.onChange([...props.values, entry]);
396
+ setDraft("");
397
+ };
398
+
399
+ return (
400
+ <div className="w-full rounded-md border border-gray-300 bg-white px-2 py-1.5">
401
+ {props.values.length > 0 && (
402
+ <div className="mb-1 flex flex-wrap gap-1">
403
+ {props.values.map((value: string, index: number) => {
404
+ return (
405
+ <span
406
+ key={`${value}-${index}`}
407
+ className="inline-flex items-center gap-1 rounded bg-gray-100 px-1.5 py-0.5 text-xs text-gray-700"
408
+ >
409
+ {containsTemplateExpression(value) ? (
410
+ <span className="font-mono text-indigo-600">{value}</span>
411
+ ) : (
412
+ value
413
+ )}
414
+ <button
415
+ type="button"
416
+ aria-label={`Remove ${value}`}
417
+ className="text-gray-400 hover:text-gray-600"
418
+ onClick={() => {
419
+ props.onChange(
420
+ props.values.filter((_entry: string, i: number) => {
421
+ return i !== index;
422
+ }),
423
+ );
424
+ }}
425
+ >
426
+ <Icon icon={IconProp.Close} className="h-3 w-3" />
427
+ </button>
428
+ </span>
429
+ );
430
+ })}
431
+ </div>
432
+ )}
433
+ <input
434
+ type="text"
435
+ value={draft}
436
+ aria-labelledby={props.ariaLabelledby}
437
+ data-testid={props.dataTestId}
438
+ placeholder={props.placeholder || "Type a value and press Enter"}
439
+ className="block w-full border-0 p-0 text-sm placeholder-gray-400 focus:outline-none focus:ring-0"
440
+ onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
441
+ const value: string = event.target.value;
442
+
443
+ if (value.endsWith(",")) {
444
+ setDraft(value.slice(0, -1));
445
+ /*
446
+ * setDraft is async, so commit from the typed text rather than from
447
+ * state, which still holds the previous keystroke here.
448
+ */
449
+ const entry: string = value.slice(0, -1).trim();
450
+
451
+ if (entry !== "" && !props.values.includes(entry)) {
452
+ props.onChange([...props.values, entry]);
453
+ }
454
+
455
+ setDraft("");
456
+ return;
457
+ }
458
+
459
+ setDraft(value);
460
+ }}
461
+ onKeyDown={(event: React.KeyboardEvent<HTMLInputElement>) => {
462
+ if (event.key === "Enter") {
463
+ event.preventDefault();
464
+ commitDraft();
465
+ return;
466
+ }
467
+
468
+ if (
469
+ event.key === "Backspace" &&
470
+ draft === "" &&
471
+ props.values.length > 0
472
+ ) {
473
+ props.onChange(props.values.slice(0, -1));
474
+ }
475
+ }}
476
+ onBlur={commitDraft}
477
+ />
478
+ </div>
479
+ );
480
+ };
481
+
482
+ export default ColumnValueInput;
@@ -0,0 +1,131 @@
1
+ /*
2
+ * The query body: the conditions that decide which records a step acts on.
3
+ *
4
+ * Fully controlled, like the record form. One column header for the whole list
5
+ * rather than a Key/Operator/Value label repeated above every row, which is
6
+ * what made the old editor read as a spreadsheet.
7
+ */
8
+
9
+ import IconProp from "../../../../Types/Icon/IconProp";
10
+ import Icon from "../../Icon/Icon";
11
+ import { ModelSchemaColumn, findColumn } from "../ModelSchema";
12
+ import AddColumnDropdown from "./AddColumnDropdown";
13
+ import { isOfferableColumn } from "./ColumnControl";
14
+ import ColumnConditionRow from "./ColumnConditionRow";
15
+ import { ModelColumnRow, makeColumnRow } from "./ColumnRow";
16
+ import React, { FunctionComponent, ReactElement, useState } from "react";
17
+
18
+ export interface ComponentProps {
19
+ rows: Array<ModelColumnRow>;
20
+ columns: Array<ModelSchemaColumn>;
21
+ suggestions?: Array<string> | undefined;
22
+ onChange: (rows: Array<ModelColumnRow>) => void;
23
+ }
24
+
25
+ const ModelQueryBuilder: FunctionComponent<ComponentProps> = (
26
+ props: ComponentProps,
27
+ ): ReactElement => {
28
+ /*
29
+ * The row just added from the picker, so its value control can take focus.
30
+ * Without it the picker remounts to clear its own selection and focus falls
31
+ * to the document body, which for a keyboard user means starting again from
32
+ * the top of the panel.
33
+ */
34
+ const [justAddedKey, setJustAddedKey] = useState<string>("");
35
+
36
+ const offerableColumns: Array<ModelSchemaColumn> = props.columns.filter(
37
+ (column: ModelSchemaColumn) => {
38
+ return isOfferableColumn(column);
39
+ },
40
+ );
41
+
42
+ type ReplaceRowFunction = (index: number, row: ModelColumnRow) => void;
43
+
44
+ const replaceRow: ReplaceRowFunction = (
45
+ index: number,
46
+ row: ModelColumnRow,
47
+ ): void => {
48
+ const next: Array<ModelColumnRow> = [...props.rows];
49
+ next[index] = row;
50
+ props.onChange(next);
51
+ };
52
+
53
+ return (
54
+ <div>
55
+ <div className="overflow-hidden rounded-md border border-gray-200 bg-white">
56
+ {props.rows.length === 0 ? (
57
+ <div className="px-6 py-10 text-center">
58
+ <Icon
59
+ icon={IconProp.Filter}
60
+ className="mx-auto h-6 w-6 text-gray-300"
61
+ />
62
+ <p className="mt-2 text-sm font-medium text-gray-700">
63
+ No conditions yet
64
+ </p>
65
+ <p className="mt-1 text-xs text-gray-500">
66
+ With no conditions this matches every record in the project.
67
+ </p>
68
+ </div>
69
+ ) : (
70
+ <>
71
+ {/* Column widths must stay in step with ColumnConditionRow's grid. */}
72
+ <div className="hidden border-b border-gray-100 bg-gray-50/60 px-3 py-1.5 text-[10px] font-semibold uppercase tracking-[0.08em] text-gray-400 sm:grid sm:grid-cols-[minmax(0,1fr)_minmax(0,0.85fr)_minmax(0,1.3fr)_auto] sm:gap-2">
73
+ <span>Column</span>
74
+ <span>Comparison</span>
75
+ <span>Value</span>
76
+ <span className="w-8" />
77
+ </div>
78
+ <div className="divide-y divide-gray-100">
79
+ {props.rows.map((row: ModelColumnRow, index: number) => {
80
+ return (
81
+ <ColumnConditionRow
82
+ key={row.key}
83
+ row={row}
84
+ column={findColumn(props.columns, row.columnId)}
85
+ columns={offerableColumns}
86
+ suggestions={props.suggestions}
87
+ autoFocus={row.key === justAddedKey}
88
+ onChange={(nextRow: ModelColumnRow) => {
89
+ replaceRow(index, nextRow);
90
+ }}
91
+ onRemove={() => {
92
+ props.onChange(
93
+ props.rows.filter((_row: ModelColumnRow, i: number) => {
94
+ return i !== index;
95
+ }),
96
+ );
97
+ }}
98
+ />
99
+ );
100
+ })}
101
+ </div>
102
+ </>
103
+ )}
104
+
105
+ <div className="border-t border-gray-100 bg-gray-50/40 px-3 py-2.5">
106
+ <AddColumnDropdown
107
+ columns={offerableColumns.filter((column: ModelSchemaColumn) => {
108
+ return !props.rows.some((row: ModelColumnRow) => {
109
+ return row.columnId === column.id;
110
+ });
111
+ })}
112
+ requiredColumnIds={[]}
113
+ placeholder="Add a condition..."
114
+ allowCustomColumn={true}
115
+ dataTestId="model-column-add"
116
+ onAdd={(columnId: string) => {
117
+ const added: ModelColumnRow = makeColumnRow({
118
+ columnId: columnId,
119
+ });
120
+
121
+ setJustAddedKey(added.key);
122
+ props.onChange([...props.rows, added]);
123
+ }}
124
+ />
125
+ </div>
126
+ </div>
127
+ </div>
128
+ );
129
+ };
130
+
131
+ export default ModelQueryBuilder;