@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,177 @@
1
+ /*
2
+ * The "add a field" picker.
3
+ *
4
+ * This is the part that answers the complaint directly: the columns are already
5
+ * known, so they are offered by name and description rather than typed from
6
+ * memory into a box labelled "Key". Required columns are grouped first, because
7
+ * on a create those are the ones that decide whether the step works at all.
8
+ */
9
+
10
+ import IconProp from "../../../../Types/Icon/IconProp";
11
+ import Button, { ButtonSize, ButtonStyleType } from "../../Button/Button";
12
+ import Dropdown, {
13
+ DropdownOption,
14
+ DropdownOptionGroup,
15
+ DropdownValue,
16
+ } from "../../Dropdown/Dropdown";
17
+ import Icon from "../../Icon/Icon";
18
+ import Input from "../../Input/Input";
19
+ import { ModelSchemaColumn } from "../ModelSchema";
20
+ import { columnTypeLabel } from "./ColumnControl";
21
+ import React, { FunctionComponent, ReactElement, useState } from "react";
22
+
23
+ export interface ComponentProps {
24
+ /** Already filtered to what may be offered, and to what is not on screen. */
25
+ columns: Array<ModelSchemaColumn>;
26
+ /** Column ids a create must be given a value for, listed first. */
27
+ requiredColumnIds: Array<string>;
28
+ placeholder: string;
29
+ /**
30
+ * True when there is nothing to offer from the schema - it failed to load, or
31
+ * every column is already on screen - and the builder must still be able to
32
+ * name one. The runner accepts column names this endpoint does not describe.
33
+ */
34
+ allowCustomColumn: boolean;
35
+ onAdd: (columnId: string) => void;
36
+ dataTestId?: string | undefined;
37
+ }
38
+
39
+ type ToOptionFunction = (column: ModelSchemaColumn) => DropdownOption;
40
+
41
+ const toOption: ToOptionFunction = (
42
+ column: ModelSchemaColumn,
43
+ ): DropdownOption => {
44
+ return {
45
+ value: column.id,
46
+ label: `${column.title} · ${column.id}`,
47
+ /*
48
+ * The model's own description, shown as the option's second line. It is the
49
+ * sentence that was previously nowhere on this screen, and it is the whole
50
+ * reason the picker beats a text box.
51
+ */
52
+ description: column.description || columnTypeLabel(column),
53
+ };
54
+ };
55
+
56
+ const AddColumnDropdown: FunctionComponent<ComponentProps> = (
57
+ props: ComponentProps,
58
+ ): ReactElement => {
59
+ const [customColumnId, setCustomColumnId] = useState<string>("");
60
+ const [isNamingCustomColumn, setIsNamingCustomColumn] =
61
+ useState<boolean>(false);
62
+ /*
63
+ * Bumped after every pick to remount the dropdown. It keeps its selection in
64
+ * state of its own and only re-reads props.value when that prop changes, so
65
+ * without this the picker would go on displaying the field it just added -
66
+ * as though it were a filter rather than an action.
67
+ */
68
+ const [pickerNonce, setPickerNonce] = useState<number>(0);
69
+
70
+ type AddCustomColumnFunction = () => void;
71
+
72
+ const addCustomColumn: AddCustomColumnFunction = (): void => {
73
+ const columnId: string = customColumnId.trim();
74
+
75
+ if (columnId === "") {
76
+ return;
77
+ }
78
+
79
+ props.onAdd(columnId);
80
+ setCustomColumnId("");
81
+ setIsNamingCustomColumn(false);
82
+ };
83
+
84
+ const hasOptions: boolean = props.columns.length > 0;
85
+
86
+ if (isNamingCustomColumn || !hasOptions) {
87
+ return (
88
+ <div className="flex items-start gap-2">
89
+ <div className="w-64">
90
+ <Input
91
+ value={customColumnId}
92
+ placeholder="Column name"
93
+ outerDivClassName="relative w-full"
94
+ dataTestId={`${props.dataTestId || "model-column-add"}-custom`}
95
+ onChange={setCustomColumnId}
96
+ onEnterPress={addCustomColumn}
97
+ />
98
+ </div>
99
+ <Button
100
+ title="Add"
101
+ buttonSize={ButtonSize.Small}
102
+ buttonStyle={ButtonStyleType.OUTLINE}
103
+ onClick={addCustomColumn}
104
+ />
105
+ {hasOptions && (
106
+ <Button
107
+ title="Cancel"
108
+ buttonSize={ButtonSize.Small}
109
+ buttonStyle={ButtonStyleType.SECONDARY_LINK}
110
+ onClick={() => {
111
+ setIsNamingCustomColumn(false);
112
+ setCustomColumnId("");
113
+ }}
114
+ />
115
+ )}
116
+ </div>
117
+ );
118
+ }
119
+
120
+ const requiredColumns: Array<ModelSchemaColumn> = props.columns.filter(
121
+ (column: ModelSchemaColumn) => {
122
+ return props.requiredColumnIds.includes(column.id);
123
+ },
124
+ );
125
+
126
+ const otherColumns: Array<ModelSchemaColumn> = props.columns.filter(
127
+ (column: ModelSchemaColumn) => {
128
+ return !props.requiredColumnIds.includes(column.id);
129
+ },
130
+ );
131
+
132
+ const options: Array<DropdownOption | DropdownOptionGroup> =
133
+ requiredColumns.length > 0
134
+ ? [
135
+ { label: "Required", options: requiredColumns.map(toOption) },
136
+ { label: "All fields", options: otherColumns.map(toOption) },
137
+ ]
138
+ : props.columns.map(toOption);
139
+
140
+ return (
141
+ <div className="flex items-center gap-3">
142
+ <div className="w-72">
143
+ <Dropdown
144
+ key={pickerNonce}
145
+ options={options}
146
+ placeholder={props.placeholder}
147
+ className="relative w-full overflow-visible rounded-md"
148
+ isMultiSelect={false}
149
+ dataTestId={props.dataTestId || "model-column-add"}
150
+ ariaLabel={props.placeholder}
151
+ onChange={(value: DropdownValue | Array<DropdownValue> | null) => {
152
+ if (value === null || Array.isArray(value)) {
153
+ return;
154
+ }
155
+
156
+ setPickerNonce(pickerNonce + 1);
157
+ props.onAdd(String(value));
158
+ }}
159
+ />
160
+ </div>
161
+ {props.allowCustomColumn && (
162
+ <button
163
+ type="button"
164
+ className="inline-flex items-center gap-1 text-xs text-gray-500 hover:text-gray-700"
165
+ onClick={() => {
166
+ setIsNamingCustomColumn(true);
167
+ }}
168
+ >
169
+ <Icon icon={IconProp.Add} className="h-3 w-3" />
170
+ Add a column by name
171
+ </button>
172
+ )}
173
+ </div>
174
+ );
175
+ };
176
+
177
+ export default AddColumnDropdown;
@@ -0,0 +1,243 @@
1
+ /*
2
+ * One condition of a query: column, comparison, value.
3
+ *
4
+ * A condition genuinely picks its subject, so this row keeps a column picker -
5
+ * but a picker of real columns, not a text box. The comparison list is filtered
6
+ * by the column's type, so a Boolean column no longer offers "starts with" and
7
+ * an ID column no longer offers "greater than".
8
+ */
9
+
10
+ import IconProp from "../../../../Types/Icon/IconProp";
11
+ import Button, { ButtonStyleType } from "../../Button/Button";
12
+ import {
13
+ DictionaryFilterOperator,
14
+ DictionaryFilterOperatorOption,
15
+ getOperatorOption,
16
+ } from "../../Dictionary/DictionaryFilterOperator";
17
+ import Dropdown, {
18
+ DropdownOption,
19
+ DropdownValue,
20
+ } from "../../Dropdown/Dropdown";
21
+ import AutocompleteTextInput from "../../AutocompleteTextInput/AutocompleteTextInput";
22
+ import { ModelSchemaColumn } from "../ModelSchema";
23
+ import { columnTypeLabel, controlForColumn } from "./ColumnControl";
24
+ import { operatorLabelFor, operatorsForControl } from "./ColumnOperators";
25
+ import {
26
+ ColumnValueMode,
27
+ ModelColumnControl,
28
+ ModelColumnRow,
29
+ changeColumnRow,
30
+ } from "./ColumnRow";
31
+ import { rowIssue } from "./ColumnRowSerialization";
32
+ import ColumnValueInput from "./ColumnValueInput";
33
+ import React, { FunctionComponent, ReactElement } from "react";
34
+
35
+ export interface ComponentProps {
36
+ row: ModelColumnRow;
37
+ column: ModelSchemaColumn | undefined;
38
+ /** Every column the model describes, for this row's own picker. */
39
+ columns: Array<ModelSchemaColumn>;
40
+ suggestions?: Array<string> | undefined;
41
+ /** Focused on mount, so a condition added from the picker is ready to type into. */
42
+ autoFocus?: boolean | undefined;
43
+ onChange: (row: ModelColumnRow) => void;
44
+ onRemove: () => void;
45
+ }
46
+
47
+ const ColumnConditionRow: FunctionComponent<ComponentProps> = (
48
+ props: ComponentProps,
49
+ ): ReactElement => {
50
+ const control: ModelColumnControl = controlForColumn(props.column);
51
+ const operatorOption: DictionaryFilterOperatorOption = getOperatorOption(
52
+ props.row.operator,
53
+ );
54
+ const issue: string | null = rowIssue(props.row, props.column);
55
+ const isUnknownColumn: boolean = !props.column && props.row.columnId !== "";
56
+
57
+ /*
58
+ * The title alone is the label, with the column name carried in the option's
59
+ * second line and repeated as a chip under the picker once it is chosen.
60
+ * Putting both in the label - "Created At · createdAt" - is what a select
61
+ * ellipsizes first, and the name is exactly the half that gets cut.
62
+ */
63
+ const columnOptions: Array<DropdownOption> = props.columns.map(
64
+ (column: ModelSchemaColumn) => {
65
+ return {
66
+ value: column.id,
67
+ label: column.title,
68
+ description: `${column.id} · ${
69
+ column.description || columnTypeLabel(column)
70
+ }`,
71
+ };
72
+ },
73
+ );
74
+
75
+ /*
76
+ * Whatever this condition already filters on is always in the list, even when
77
+ * the picker would not offer it: a name the endpoint does not describe at all,
78
+ * and equally a column it describes but does not offer - a relation, the
79
+ * project column, a JSON blob. Keyed on "is it in the options" rather than on
80
+ * "is it unknown", because the second misses the whole middle case and leaves
81
+ * the row showing an empty picker over a condition that is really there.
82
+ */
83
+ if (
84
+ props.row.columnId !== "" &&
85
+ !columnOptions.some((option: DropdownOption) => {
86
+ return option.value === props.row.columnId;
87
+ })
88
+ ) {
89
+ columnOptions.unshift({
90
+ value: props.row.columnId,
91
+ label: props.column?.title || props.row.columnId,
92
+ description: isUnknownColumn
93
+ ? "Not a column on this model"
94
+ : `${props.row.columnId} · can't be picked from the list`,
95
+ });
96
+ }
97
+
98
+ const operatorOptions: Array<DropdownOption> = operatorsForControl(
99
+ control,
100
+ props.row.operator,
101
+ ).map((operator: DictionaryFilterOperator) => {
102
+ return {
103
+ value: operator,
104
+ label: operatorLabelFor(operator, control),
105
+ };
106
+ });
107
+
108
+ /*
109
+ * Proportional column widths rather than fixed ones. A picker showing
110
+ * "Created At · createdAt" and a comparison reading "is on or before" both
111
+ * outgrow any rem figure that also leaves room for the value, and the settings
112
+ * panel is a different width on every screen. ModelQueryBuilder's header row
113
+ * repeats this template and has to stay in step with it.
114
+ */
115
+ const rowGridClass: string =
116
+ "group grid grid-cols-1 items-start gap-2 px-3 py-2.5 transition-colors hover:bg-gray-50/60 sm:grid-cols-[minmax(0,1fr)_minmax(0,0.85fr)_minmax(0,1.3fr)_auto]";
117
+
118
+ return (
119
+ <div className={rowGridClass} data-testid="model-column-row">
120
+ <div className="min-w-0">
121
+ {props.columns.length === 0 ? (
122
+ <AutocompleteTextInput
123
+ value={props.row.columnId}
124
+ placeholder="Column name"
125
+ outerDivClassName="relative w-full"
126
+ disableSpellCheck={true}
127
+ onChange={(value: string) => {
128
+ props.onChange(changeColumnRow(props.row, { columnId: value }));
129
+ }}
130
+ />
131
+ ) : (
132
+ <Dropdown
133
+ options={columnOptions}
134
+ placeholder="Column"
135
+ className="relative w-full overflow-visible rounded-md"
136
+ isMultiSelect={false}
137
+ ariaLabel="Column"
138
+ dataTestId={`model-column-picker-${props.row.columnId}`}
139
+ value={columnOptions.find((option: DropdownOption) => {
140
+ return option.value === props.row.columnId;
141
+ })}
142
+ onChange={(value: DropdownValue | Array<DropdownValue> | null) => {
143
+ /*
144
+ * The dropdown is always clearable and hands back null. The row
145
+ * stays - a condition being retargeted is not a condition being
146
+ * deleted - and an empty column is dropped on serialization.
147
+ */
148
+ props.onChange(
149
+ changeColumnRow(props.row, {
150
+ columnId:
151
+ value === null || Array.isArray(value) ? "" : String(value),
152
+ }),
153
+ );
154
+ }}
155
+ />
156
+ )}
157
+ {isUnknownColumn ? (
158
+ <span className="mt-1 inline-block rounded bg-amber-50 px-1.5 py-0.5 text-[11px] text-amber-700">
159
+ Not a column on this model
160
+ </span>
161
+ ) : (
162
+ props.column && (
163
+ <span className="mt-1 inline-block truncate rounded bg-gray-100 px-1.5 py-0.5 font-mono text-[11px] text-gray-500">
164
+ {props.row.columnId}
165
+ </span>
166
+ )
167
+ )}
168
+ </div>
169
+
170
+ <Dropdown
171
+ options={operatorOptions}
172
+ className="relative w-full overflow-visible rounded-md"
173
+ isMultiSelect={false}
174
+ ariaLabel="Comparison"
175
+ dataTestId={`model-column-operator-${props.row.columnId}`}
176
+ value={operatorOptions.find((option: DropdownOption) => {
177
+ return option.value === props.row.operator;
178
+ })}
179
+ onChange={(value: DropdownValue | Array<DropdownValue> | null) => {
180
+ if (value === null || Array.isArray(value)) {
181
+ return;
182
+ }
183
+
184
+ const nextOperator: DictionaryFilterOperator =
185
+ value as DictionaryFilterOperator;
186
+ const nextOption: DictionaryFilterOperatorOption =
187
+ getOperatorOption(nextOperator);
188
+
189
+ /*
190
+ * The value is cleared when the shape of the input changes, so a
191
+ * scalar is never shipped where a list is expected, or the other way
192
+ * round.
193
+ */
194
+ props.onChange(
195
+ changeColumnRow(props.row, {
196
+ operator: nextOperator,
197
+ text: nextOption.hidesValueInput ? "" : props.row.text,
198
+ values: nextOption.expectsMultiValue ? props.row.values : [],
199
+ }),
200
+ );
201
+ }}
202
+ />
203
+
204
+ <div className="min-w-0">
205
+ <ColumnValueInput
206
+ control={control}
207
+ valueMode={props.row.valueMode}
208
+ text={props.row.text}
209
+ values={props.row.values}
210
+ operatorOption={operatorOption}
211
+ placeholder={props.column?.example || props.column?.placeholder}
212
+ suggestions={props.suggestions}
213
+ autoFocus={props.autoFocus}
214
+ dataTestId={`model-column-value-${props.row.columnId}`}
215
+ onChange={(change: Partial<ModelColumnRow>) => {
216
+ props.onChange(changeColumnRow(props.row, change));
217
+ }}
218
+ />
219
+
220
+ {props.row.valueMode === ColumnValueMode.Raw && (
221
+ <p className="mt-1 text-[11px] text-amber-600">
222
+ Kept exactly as it was saved.
223
+ </p>
224
+ )}
225
+
226
+ {issue && <p className="mt-1 text-[11px] text-red-500">{issue}</p>}
227
+ </div>
228
+
229
+ <div className="flex items-center pt-1">
230
+ <Button
231
+ buttonStyle={ButtonStyleType.ICON}
232
+ icon={IconProp.Trash}
233
+ title="Remove"
234
+ dataTestId={`model-column-remove-${props.row.columnId}`}
235
+ className="opacity-0 transition-opacity focus-visible:opacity-100 group-hover:opacity-100"
236
+ onClick={props.onRemove}
237
+ />
238
+ </div>
239
+ </div>
240
+ );
241
+ };
242
+
243
+ export default ColumnConditionRow;
@@ -0,0 +1,255 @@
1
+ /*
2
+ * Which control a column gets, decided from the model's own schema.
3
+ *
4
+ * This is the answer to "you already know the schema, why are you asking me?".
5
+ * The editor used to show a Type dropdown next to every row — Text, Number,
6
+ * Boolean — even though /model-schema/:tableName had already said that `color`
7
+ * is a Color and `name` is a Name. Nothing good could come of that dropdown:
8
+ * the builder could only agree with the schema or be wrong.
9
+ */
10
+
11
+ import TableColumnType from "../../../../Types/Database/TableColumnType";
12
+ import { ModelSchemaColumn } from "../ModelSchema";
13
+ import { ModelColumnControl } from "./ColumnRow";
14
+
15
+ /*
16
+ * Spelled with the enum, never with string literals. The wire carries the
17
+ * enum's *values* and several of them differ from the member name that reads
18
+ * like the obvious literal: ShortText is "Text", ObjectID is "Object ID",
19
+ * LongURL is "URL", PositiveNumber is "Positive Number" — and JavaScript is
20
+ * "JavaSCript", capital S and all. ModelSchema.ts:160 documents the same trap;
21
+ * a hand-written list here would classify the two most-declared column types
22
+ * as unsupported and quietly send every one of them to a plain text box.
23
+ */
24
+ const TEXT_COLUMN_TYPES: Array<string> = [
25
+ TableColumnType.ShortText,
26
+ TableColumnType.Slug,
27
+ TableColumnType.Name,
28
+ TableColumnType.Email,
29
+ TableColumnType.Phone,
30
+ TableColumnType.Domain,
31
+ TableColumnType.IP,
32
+ TableColumnType.Version,
33
+ TableColumnType.LongURL,
34
+ TableColumnType.ShortURL,
35
+ TableColumnType.HashedString,
36
+ TableColumnType.Password,
37
+ TableColumnType.OTP,
38
+ TableColumnType.MonitorType,
39
+ TableColumnType.WorkflowStatus,
40
+ TableColumnType.CustomFieldType,
41
+ TableColumnType.Permission,
42
+ ];
43
+
44
+ const LONG_TEXT_COLUMN_TYPES: Array<string> = [
45
+ TableColumnType.LongText,
46
+ TableColumnType.VeryLongText,
47
+ TableColumnType.Description,
48
+ TableColumnType.Markdown,
49
+ TableColumnType.HTML,
50
+ TableColumnType.CSS,
51
+ TableColumnType.JavaScript,
52
+ ];
53
+
54
+ const NUMBER_COLUMN_TYPES: Array<string> = [
55
+ TableColumnType.Number,
56
+ TableColumnType.SmallNumber,
57
+ TableColumnType.BigNumber,
58
+ TableColumnType.PositiveNumber,
59
+ TableColumnType.SmallPositiveNumber,
60
+ TableColumnType.BigPositiveNumber,
61
+ TableColumnType.Port,
62
+ ];
63
+
64
+ /*
65
+ * Everything a row cannot hold. Entity/EntityArray are relations - pointing a
66
+ * record at a related row means naming an existing record, which is what the
67
+ * scalar foreign-key column next to it (currentIncidentStateId, and every other
68
+ * "... ID" column) is for, and that column is offered normally.
69
+ */
70
+ const UNSUPPORTED_COLUMN_TYPES: Array<string> = [
71
+ TableColumnType.Entity,
72
+ TableColumnType.EntityArray,
73
+ TableColumnType.JSON,
74
+ TableColumnType.Array,
75
+ TableColumnType.Buffer,
76
+ TableColumnType.File,
77
+ TableColumnType.MonitorSteps,
78
+ ];
79
+
80
+ export type ControlForColumnFunction = (
81
+ column: ModelSchemaColumn | undefined,
82
+ ) => ModelColumnControl;
83
+
84
+ /**
85
+ * The control for a column, or Text when the schema has nothing to say.
86
+ *
87
+ * An unknown column — one the endpoint did not describe, or a key typed before
88
+ * the schema loaded — gets Text rather than Unsupported, because the runner
89
+ * accepts column names this endpoint does not surface and locking those rows
90
+ * would be worse than letting them be typed.
91
+ */
92
+ export const controlForColumn: ControlForColumnFunction = (
93
+ column: ModelSchemaColumn | undefined,
94
+ ): ModelColumnControl => {
95
+ if (!column) {
96
+ return ModelColumnControl.Text;
97
+ }
98
+
99
+ if (column.isRelation) {
100
+ return ModelColumnControl.Unsupported;
101
+ }
102
+
103
+ if (UNSUPPORTED_COLUMN_TYPES.includes(column.type)) {
104
+ return ModelColumnControl.Unsupported;
105
+ }
106
+
107
+ if (column.type === TableColumnType.Boolean) {
108
+ return ModelColumnControl.Boolean;
109
+ }
110
+
111
+ if (column.type === TableColumnType.Date) {
112
+ return ModelColumnControl.Date;
113
+ }
114
+
115
+ if (column.type === TableColumnType.ObjectID) {
116
+ return ModelColumnControl.ObjectId;
117
+ }
118
+
119
+ if (column.type === TableColumnType.Color) {
120
+ return ModelColumnControl.Color;
121
+ }
122
+
123
+ if (NUMBER_COLUMN_TYPES.includes(column.type)) {
124
+ return ModelColumnControl.Number;
125
+ }
126
+
127
+ if (LONG_TEXT_COLUMN_TYPES.includes(column.type)) {
128
+ return ModelColumnControl.LongText;
129
+ }
130
+
131
+ if (TEXT_COLUMN_TYPES.includes(column.type)) {
132
+ return ModelColumnControl.Text;
133
+ }
134
+
135
+ return ModelColumnControl.Text;
136
+ };
137
+
138
+ export type ColumnTypeLabelFunction = (
139
+ column: ModelSchemaColumn | undefined,
140
+ ) => string;
141
+
142
+ /**
143
+ * The one-word type shown under a field's name, so the builder can see what
144
+ * kind of value is expected without opening the model's documentation.
145
+ */
146
+ export const columnTypeLabel: ColumnTypeLabelFunction = (
147
+ column: ModelSchemaColumn | undefined,
148
+ ): string => {
149
+ if (!column) {
150
+ return "Unknown";
151
+ }
152
+
153
+ if (column.isRelation) {
154
+ return "Relation";
155
+ }
156
+
157
+ switch (controlForColumn(column)) {
158
+ case ModelColumnControl.Number:
159
+ return "Number";
160
+ case ModelColumnControl.Boolean:
161
+ return "True or false";
162
+ case ModelColumnControl.Date:
163
+ return "Date and time";
164
+ case ModelColumnControl.ObjectId:
165
+ return "ID";
166
+ case ModelColumnControl.Color:
167
+ return "Color";
168
+ case ModelColumnControl.LongText:
169
+ return "Long text";
170
+ case ModelColumnControl.Unsupported:
171
+ /*
172
+ * The raw column type is more use than the word "unsupported" here: it is
173
+ * what the builder will have to write by hand in the JSON editor.
174
+ */
175
+ return column.type;
176
+ default:
177
+ return "Text";
178
+ }
179
+ };
180
+
181
+ export type LiteralFitsControlFunction = (
182
+ control: ModelColumnControl,
183
+ value: unknown,
184
+ ) => boolean;
185
+
186
+ /**
187
+ * Can this stored value be shown in the typed control without changing it?
188
+ *
189
+ * A `false` here is not an error — it sends the row to ColumnValueMode.Raw,
190
+ * where it is shown as text and re-emitted exactly as it was read. That is what
191
+ * stops opening a workflow and saving it again from rewriting `{"port":"8080"}`
192
+ * into `{"port":8080}` behind the builder's back.
193
+ */
194
+ export const literalFitsControl: LiteralFitsControlFunction = (
195
+ control: ModelColumnControl,
196
+ value: unknown,
197
+ ): boolean => {
198
+ /*
199
+ * An empty control holds "" and nothing else. undefined and "" are what an
200
+ * untouched row is, so they fit; null does not, because an empty control
201
+ * cannot say "null" - it emits "", which a record drops entirely. A stored
202
+ * null therefore keeps its own value rather than being flattened into a
203
+ * missing field.
204
+ */
205
+ if (value === undefined || value === "") {
206
+ return true;
207
+ }
208
+
209
+ if (value === null) {
210
+ return false;
211
+ }
212
+
213
+ switch (control) {
214
+ case ModelColumnControl.Number:
215
+ return typeof value === "number" && !isNaN(value);
216
+ case ModelColumnControl.Boolean:
217
+ return typeof value === "boolean";
218
+ case ModelColumnControl.Date:
219
+ /*
220
+ * Dates travel as ISO strings. Anything else - a number of milliseconds,
221
+ * an operator wrapper - is shown raw rather than reinterpreted.
222
+ */
223
+ return typeof value === "string" && !isNaN(Date.parse(value));
224
+ default:
225
+ return typeof value === "string";
226
+ }
227
+ };
228
+
229
+ export type IsOfferableColumnFunction = (column: ModelSchemaColumn) => boolean;
230
+
231
+ /**
232
+ * Should this column appear in the "add a field" picker?
233
+ *
234
+ * Note this only gates what is *offered*. A column already stored on the
235
+ * argument always gets its row, tenant column and all, because hiding a value
236
+ * that is really there is how an editor loses someone's work.
237
+ */
238
+ export const isOfferableColumn: IsOfferableColumnFunction = (
239
+ column: ModelSchemaColumn,
240
+ ): boolean => {
241
+ if (column.isRelation) {
242
+ return false;
243
+ }
244
+
245
+ /*
246
+ * The workflow runner stamps the project column itself
247
+ * (ModelArguments.applyTenantColumn) and overwrites whatever was typed, so
248
+ * offering it would be offering a field that cannot work.
249
+ */
250
+ if (column.isTenantColumn) {
251
+ return false;
252
+ }
253
+
254
+ return controlForColumn(column) !== ModelColumnControl.Unsupported;
255
+ };