terrier-engine 4.4.0 → 4.4.3

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 (313) hide show
  1. package/data-dive/dd-app.ts +45 -0
  2. package/data-dive/dd-db.ts +12 -0
  3. package/data-dive/dd-routes.ts +10 -0
  4. package/data-dive/dd-session.ts +45 -0
  5. package/data-dive/dd-user.ts +12 -0
  6. package/data-dive/dives/dive-editor.ts +244 -0
  7. package/data-dive/dives/dive-list.ts +164 -0
  8. package/data-dive/dives/dive-runs.ts +272 -0
  9. package/data-dive/dives/dive-settings.ts +227 -0
  10. package/data-dive/dives/dives.ts +81 -0
  11. package/data-dive/dives/group-editor.ts +139 -0
  12. package/data-dive/gen/models.ts +192 -0
  13. package/data-dive/queries/columns.ts +355 -0
  14. package/data-dive/queries/dates.ts +286 -0
  15. package/data-dive/queries/filters.ts +723 -0
  16. package/data-dive/queries/queries.ts +263 -0
  17. package/data-dive/queries/query-editor.ts +232 -0
  18. package/data-dive/queries/query-form.ts +51 -0
  19. package/data-dive/queries/tables.ts +395 -0
  20. package/data-dive/queries/test/dates.test.ts +178 -0
  21. package/package.json +1 -1
  22. package/{parts → terrier/parts}/panel-part.ts +5 -1
  23. package/tsconfig.json +21 -0
  24. /package/{README.md → terrier/README.md} +0 -0
  25. /package/{api.ts → terrier/api.ts} +0 -0
  26. /package/{app.ts → terrier/app.ts} +0 -0
  27. /package/{attachments.ts → terrier/attachments.ts} +0 -0
  28. /package/{db-client.ts → terrier/db-client.ts} +0 -0
  29. /package/{dropdowns.ts → terrier/dropdowns.ts} +0 -0
  30. /package/{format.ts → terrier/format.ts} +0 -0
  31. /package/{forms.ts → terrier/forms.ts} +0 -0
  32. /package/{fragments.ts → terrier/fragments.ts} +0 -0
  33. /package/{gen → terrier/gen}/hub-icons.ts +0 -0
  34. /package/{glyps.ts → terrier/glyps.ts} +0 -0
  35. /package/{ids.ts → terrier/ids.ts} +0 -0
  36. /package/{images → terrier/images}/icons/active.svg +0 -0
  37. /package/{images → terrier/images}/icons/admin.svg +0 -0
  38. /package/{images → terrier/images}/icons/archive.svg +0 -0
  39. /package/{images → terrier/images}/icons/arrow_down.svg +0 -0
  40. /package/{images → terrier/images}/icons/arrow_left.svg +0 -0
  41. /package/{images → terrier/images}/icons/arrow_right.svg +0 -0
  42. /package/{images → terrier/images}/icons/arrow_up.svg +0 -0
  43. /package/{images → terrier/images}/icons/assign.svg +0 -0
  44. /package/{images → terrier/images}/icons/attachment.svg +0 -0
  45. /package/{images → terrier/images}/icons/back.svg +0 -0
  46. /package/{images → terrier/images}/icons/badge.svg +0 -0
  47. /package/{images → terrier/images}/icons/board.svg +0 -0
  48. /package/{images → terrier/images}/icons/branch.svg +0 -0
  49. /package/{images → terrier/images}/icons/bug.svg +0 -0
  50. /package/{images → terrier/images}/icons/calculator.svg +0 -0
  51. /package/{images → terrier/images}/icons/checkmark.svg +0 -0
  52. /package/{images → terrier/images}/icons/close.svg +0 -0
  53. /package/{images → terrier/images}/icons/clypboard.svg +0 -0
  54. /package/{images → terrier/images}/icons/comment.svg +0 -0
  55. /package/{images → terrier/images}/icons/complete.svg +0 -0
  56. /package/{images → terrier/images}/icons/dashboard.svg +0 -0
  57. /package/{images → terrier/images}/icons/data_pull.svg +0 -0
  58. /package/{images → terrier/images}/icons/data_update.svg +0 -0
  59. /package/{images → terrier/images}/icons/database.svg +0 -0
  60. /package/{images → terrier/images}/icons/day.svg +0 -0
  61. /package/{images → terrier/images}/icons/delete.svg +0 -0
  62. /package/{images → terrier/images}/icons/documentation.svg +0 -0
  63. /package/{images → terrier/images}/icons/edit.svg +0 -0
  64. /package/{images → terrier/images}/icons/feature.svg +0 -0
  65. /package/{images → terrier/images}/icons/flex.svg +0 -0
  66. /package/{images → terrier/images}/icons/forward.svg +0 -0
  67. /package/{images → terrier/images}/icons/github.svg +0 -0
  68. /package/{images → terrier/images}/icons/history.svg +0 -0
  69. /package/{images → terrier/images}/icons/home.svg +0 -0
  70. /package/{images → terrier/images}/icons/image.svg +0 -0
  71. /package/{images → terrier/images}/icons/inbox.svg +0 -0
  72. /package/{images → terrier/images}/icons/info.svg +0 -0
  73. /package/{images → terrier/images}/icons/issue.svg +0 -0
  74. /package/{images → terrier/images}/icons/lane.svg +0 -0
  75. /package/{images → terrier/images}/icons/lane_asap.svg +0 -0
  76. /package/{images → terrier/images}/icons/lane_days.svg +0 -0
  77. /package/{images → terrier/images}/icons/lane_hours.svg +0 -0
  78. /package/{images → terrier/images}/icons/lane_weeks.svg +0 -0
  79. /package/{images → terrier/images}/icons/lanes_board.svg +0 -0
  80. /package/{images → terrier/images}/icons/level_complete.svg +0 -0
  81. /package/{images → terrier/images}/icons/level_highway.svg +0 -0
  82. /package/{images → terrier/images}/icons/level_on_ramp.svg +0 -0
  83. /package/{images → terrier/images}/icons/level_parking.svg +0 -0
  84. /package/{images → terrier/images}/icons/minus.svg +0 -0
  85. /package/{images → terrier/images}/icons/night.svg +0 -0
  86. /package/{images → terrier/images}/icons/origin.svg +0 -0
  87. /package/{images → terrier/images}/icons/pending.svg +0 -0
  88. /package/{images → terrier/images}/icons/plus.svg +0 -0
  89. /package/{images → terrier/images}/icons/post.svg +0 -0
  90. /package/{images → terrier/images}/icons/pr_closed.svg +0 -0
  91. /package/{images → terrier/images}/icons/pr_merged.svg +0 -0
  92. /package/{images → terrier/images}/icons/pr_open.svg +0 -0
  93. /package/{images → terrier/images}/icons/prioritized.svg +0 -0
  94. /package/{images → terrier/images}/icons/project.svg +0 -0
  95. /package/{images → terrier/images}/icons/question.svg +0 -0
  96. /package/{images → terrier/images}/icons/reaction.svg +0 -0
  97. /package/{images → terrier/images}/icons/recent.svg +0 -0
  98. /package/{images → terrier/images}/icons/refresh.svg +0 -0
  99. /package/{images → terrier/images}/icons/request.svg +0 -0
  100. /package/{images → terrier/images}/icons/settings.svg +0 -0
  101. /package/{images → terrier/images}/icons/status.svg +0 -0
  102. /package/{images → terrier/images}/icons/step_deploy.svg +0 -0
  103. /package/{images → terrier/images}/icons/step_develop.svg +0 -0
  104. /package/{images → terrier/images}/icons/step_investigate.svg +0 -0
  105. /package/{images → terrier/images}/icons/step_review.svg +0 -0
  106. /package/{images → terrier/images}/icons/step_test.svg +0 -0
  107. /package/{images → terrier/images}/icons/steps.svg +0 -0
  108. /package/{images → terrier/images}/icons/steps_board.svg +0 -0
  109. /package/{images → terrier/images}/icons/subscribe.svg +0 -0
  110. /package/{images → terrier/images}/icons/support.svg +0 -0
  111. /package/{images → terrier/images}/icons/terrier.svg +0 -0
  112. /package/{images → terrier/images}/icons/thumbs_up.svg +0 -0
  113. /package/{images → terrier/images}/icons/type.svg +0 -0
  114. /package/{images → terrier/images}/icons/unprioritized.svg +0 -0
  115. /package/{images → terrier/images}/icons/upload.svg +0 -0
  116. /package/{images → terrier/images}/icons/user.svg +0 -0
  117. /package/{images → terrier/images}/icons/users.svg +0 -0
  118. /package/{images → terrier/images}/optimized/icon-active.svg +0 -0
  119. /package/{images → terrier/images}/optimized/icon-admin.svg +0 -0
  120. /package/{images → terrier/images}/optimized/icon-archive.svg +0 -0
  121. /package/{images → terrier/images}/optimized/icon-arrow_down.svg +0 -0
  122. /package/{images → terrier/images}/optimized/icon-arrow_left.svg +0 -0
  123. /package/{images → terrier/images}/optimized/icon-arrow_right.svg +0 -0
  124. /package/{images → terrier/images}/optimized/icon-arrow_up.svg +0 -0
  125. /package/{images → terrier/images}/optimized/icon-assign.svg +0 -0
  126. /package/{images → terrier/images}/optimized/icon-attachment.svg +0 -0
  127. /package/{images → terrier/images}/optimized/icon-back.svg +0 -0
  128. /package/{images → terrier/images}/optimized/icon-badge.svg +0 -0
  129. /package/{images → terrier/images}/optimized/icon-board.svg +0 -0
  130. /package/{images → terrier/images}/optimized/icon-branch.svg +0 -0
  131. /package/{images → terrier/images}/optimized/icon-bug.svg +0 -0
  132. /package/{images → terrier/images}/optimized/icon-calculator.svg +0 -0
  133. /package/{images → terrier/images}/optimized/icon-checkmark.svg +0 -0
  134. /package/{images → terrier/images}/optimized/icon-close.svg +0 -0
  135. /package/{images → terrier/images}/optimized/icon-clypboard.svg +0 -0
  136. /package/{images → terrier/images}/optimized/icon-comment.svg +0 -0
  137. /package/{images → terrier/images}/optimized/icon-complete.svg +0 -0
  138. /package/{images → terrier/images}/optimized/icon-dashboard.svg +0 -0
  139. /package/{images → terrier/images}/optimized/icon-data_pull.svg +0 -0
  140. /package/{images → terrier/images}/optimized/icon-data_update.svg +0 -0
  141. /package/{images → terrier/images}/optimized/icon-database.svg +0 -0
  142. /package/{images → terrier/images}/optimized/icon-day.svg +0 -0
  143. /package/{images → terrier/images}/optimized/icon-delete.svg +0 -0
  144. /package/{images → terrier/images}/optimized/icon-documentation.svg +0 -0
  145. /package/{images → terrier/images}/optimized/icon-edit.svg +0 -0
  146. /package/{images → terrier/images}/optimized/icon-feature.svg +0 -0
  147. /package/{images → terrier/images}/optimized/icon-flex.svg +0 -0
  148. /package/{images → terrier/images}/optimized/icon-forward.svg +0 -0
  149. /package/{images → terrier/images}/optimized/icon-github.svg +0 -0
  150. /package/{images → terrier/images}/optimized/icon-history.svg +0 -0
  151. /package/{images → terrier/images}/optimized/icon-home.svg +0 -0
  152. /package/{images → terrier/images}/optimized/icon-image.svg +0 -0
  153. /package/{images → terrier/images}/optimized/icon-inbox.svg +0 -0
  154. /package/{images → terrier/images}/optimized/icon-info.svg +0 -0
  155. /package/{images → terrier/images}/optimized/icon-issue.svg +0 -0
  156. /package/{images → terrier/images}/optimized/icon-lane.svg +0 -0
  157. /package/{images → terrier/images}/optimized/icon-lane_asap.svg +0 -0
  158. /package/{images → terrier/images}/optimized/icon-lane_days.svg +0 -0
  159. /package/{images → terrier/images}/optimized/icon-lane_hours.svg +0 -0
  160. /package/{images → terrier/images}/optimized/icon-lane_weeks.svg +0 -0
  161. /package/{images → terrier/images}/optimized/icon-lanes_board.svg +0 -0
  162. /package/{images → terrier/images}/optimized/icon-level_complete.svg +0 -0
  163. /package/{images → terrier/images}/optimized/icon-level_highway.svg +0 -0
  164. /package/{images → terrier/images}/optimized/icon-level_on_ramp.svg +0 -0
  165. /package/{images → terrier/images}/optimized/icon-level_parking.svg +0 -0
  166. /package/{images → terrier/images}/optimized/icon-minus.svg +0 -0
  167. /package/{images → terrier/images}/optimized/icon-night.svg +0 -0
  168. /package/{images → terrier/images}/optimized/icon-origin.svg +0 -0
  169. /package/{images → terrier/images}/optimized/icon-pending.svg +0 -0
  170. /package/{images → terrier/images}/optimized/icon-plus.svg +0 -0
  171. /package/{images → terrier/images}/optimized/icon-post.svg +0 -0
  172. /package/{images → terrier/images}/optimized/icon-pr_closed.svg +0 -0
  173. /package/{images → terrier/images}/optimized/icon-pr_merged.svg +0 -0
  174. /package/{images → terrier/images}/optimized/icon-pr_open.svg +0 -0
  175. /package/{images → terrier/images}/optimized/icon-prioritized.svg +0 -0
  176. /package/{images → terrier/images}/optimized/icon-project.svg +0 -0
  177. /package/{images → terrier/images}/optimized/icon-question.svg +0 -0
  178. /package/{images → terrier/images}/optimized/icon-reaction.svg +0 -0
  179. /package/{images → terrier/images}/optimized/icon-recent.svg +0 -0
  180. /package/{images → terrier/images}/optimized/icon-refresh.svg +0 -0
  181. /package/{images → terrier/images}/optimized/icon-request.svg +0 -0
  182. /package/{images → terrier/images}/optimized/icon-settings.svg +0 -0
  183. /package/{images → terrier/images}/optimized/icon-status.svg +0 -0
  184. /package/{images → terrier/images}/optimized/icon-step_deploy.svg +0 -0
  185. /package/{images → terrier/images}/optimized/icon-step_develop.svg +0 -0
  186. /package/{images → terrier/images}/optimized/icon-step_investigate.svg +0 -0
  187. /package/{images → terrier/images}/optimized/icon-step_review.svg +0 -0
  188. /package/{images → terrier/images}/optimized/icon-step_test.svg +0 -0
  189. /package/{images → terrier/images}/optimized/icon-steps.svg +0 -0
  190. /package/{images → terrier/images}/optimized/icon-steps_board.svg +0 -0
  191. /package/{images → terrier/images}/optimized/icon-subscribe.svg +0 -0
  192. /package/{images → terrier/images}/optimized/icon-support.svg +0 -0
  193. /package/{images → terrier/images}/optimized/icon-terrier.svg +0 -0
  194. /package/{images → terrier/images}/optimized/icon-thumbs_up.svg +0 -0
  195. /package/{images → terrier/images}/optimized/icon-type.svg +0 -0
  196. /package/{images → terrier/images}/optimized/icon-unprioritized.svg +0 -0
  197. /package/{images → terrier/images}/optimized/icon-upload.svg +0 -0
  198. /package/{images → terrier/images}/optimized/icon-user.svg +0 -0
  199. /package/{images → terrier/images}/optimized/icon-users.svg +0 -0
  200. /package/{images → terrier/images}/optimized/terrier-hub-favicon.svg +0 -0
  201. /package/{images → terrier/images}/optimized/terrier-hub-icon-dark.svg +0 -0
  202. /package/{images → terrier/images}/optimized/terrier-hub-icon-light.svg +0 -0
  203. /package/{images → terrier/images}/optimized/terrier-hub-loader.svg +0 -0
  204. /package/{images → terrier/images}/optimized/terrier-hub-logo-dark.svg +0 -0
  205. /package/{images → terrier/images}/optimized/terrier-hub-logo-light.svg +0 -0
  206. /package/{images → terrier/images}/raw/icon-active.svg +0 -0
  207. /package/{images → terrier/images}/raw/icon-admin.svg +0 -0
  208. /package/{images → terrier/images}/raw/icon-archive.svg +0 -0
  209. /package/{images → terrier/images}/raw/icon-arrow_down.svg +0 -0
  210. /package/{images → terrier/images}/raw/icon-arrow_left.svg +0 -0
  211. /package/{images → terrier/images}/raw/icon-arrow_right.svg +0 -0
  212. /package/{images → terrier/images}/raw/icon-arrow_up.svg +0 -0
  213. /package/{images → terrier/images}/raw/icon-assign.svg +0 -0
  214. /package/{images → terrier/images}/raw/icon-attachment.svg +0 -0
  215. /package/{images → terrier/images}/raw/icon-back.svg +0 -0
  216. /package/{images → terrier/images}/raw/icon-badge.svg +0 -0
  217. /package/{images → terrier/images}/raw/icon-board.svg +0 -0
  218. /package/{images → terrier/images}/raw/icon-branch.svg +0 -0
  219. /package/{images → terrier/images}/raw/icon-bug.svg +0 -0
  220. /package/{images → terrier/images}/raw/icon-calculator.svg +0 -0
  221. /package/{images → terrier/images}/raw/icon-checkmark.svg +0 -0
  222. /package/{images → terrier/images}/raw/icon-close.svg +0 -0
  223. /package/{images → terrier/images}/raw/icon-clypboard.svg +0 -0
  224. /package/{images → terrier/images}/raw/icon-comment.svg +0 -0
  225. /package/{images → terrier/images}/raw/icon-complete.svg +0 -0
  226. /package/{images → terrier/images}/raw/icon-dashboard.svg +0 -0
  227. /package/{images → terrier/images}/raw/icon-data_pull.svg +0 -0
  228. /package/{images → terrier/images}/raw/icon-data_update.svg +0 -0
  229. /package/{images → terrier/images}/raw/icon-database.svg +0 -0
  230. /package/{images → terrier/images}/raw/icon-day.svg +0 -0
  231. /package/{images → terrier/images}/raw/icon-delete.svg +0 -0
  232. /package/{images → terrier/images}/raw/icon-documentation.svg +0 -0
  233. /package/{images → terrier/images}/raw/icon-edit.svg +0 -0
  234. /package/{images → terrier/images}/raw/icon-feature.svg +0 -0
  235. /package/{images → terrier/images}/raw/icon-flex.svg +0 -0
  236. /package/{images → terrier/images}/raw/icon-forward.svg +0 -0
  237. /package/{images → terrier/images}/raw/icon-github.svg +0 -0
  238. /package/{images → terrier/images}/raw/icon-history.svg +0 -0
  239. /package/{images → terrier/images}/raw/icon-home.svg +0 -0
  240. /package/{images → terrier/images}/raw/icon-image.svg +0 -0
  241. /package/{images → terrier/images}/raw/icon-inbox.svg +0 -0
  242. /package/{images → terrier/images}/raw/icon-info.svg +0 -0
  243. /package/{images → terrier/images}/raw/icon-issue.svg +0 -0
  244. /package/{images → terrier/images}/raw/icon-lane.svg +0 -0
  245. /package/{images → terrier/images}/raw/icon-lane_asap.svg +0 -0
  246. /package/{images → terrier/images}/raw/icon-lane_days.svg +0 -0
  247. /package/{images → terrier/images}/raw/icon-lane_hours.svg +0 -0
  248. /package/{images → terrier/images}/raw/icon-lane_weeks.svg +0 -0
  249. /package/{images → terrier/images}/raw/icon-lanes_board.svg +0 -0
  250. /package/{images → terrier/images}/raw/icon-level_complete.svg +0 -0
  251. /package/{images → terrier/images}/raw/icon-level_highway.svg +0 -0
  252. /package/{images → terrier/images}/raw/icon-level_on_ramp.svg +0 -0
  253. /package/{images → terrier/images}/raw/icon-level_parking.svg +0 -0
  254. /package/{images → terrier/images}/raw/icon-minus.svg +0 -0
  255. /package/{images → terrier/images}/raw/icon-night.svg +0 -0
  256. /package/{images → terrier/images}/raw/icon-origin.svg +0 -0
  257. /package/{images → terrier/images}/raw/icon-pending.svg +0 -0
  258. /package/{images → terrier/images}/raw/icon-plus.svg +0 -0
  259. /package/{images → terrier/images}/raw/icon-post.svg +0 -0
  260. /package/{images → terrier/images}/raw/icon-pr_closed.svg +0 -0
  261. /package/{images → terrier/images}/raw/icon-pr_merged.svg +0 -0
  262. /package/{images → terrier/images}/raw/icon-pr_open.svg +0 -0
  263. /package/{images → terrier/images}/raw/icon-prioritized.svg +0 -0
  264. /package/{images → terrier/images}/raw/icon-project.svg +0 -0
  265. /package/{images → terrier/images}/raw/icon-question.svg +0 -0
  266. /package/{images → terrier/images}/raw/icon-reaction.svg +0 -0
  267. /package/{images → terrier/images}/raw/icon-recent.svg +0 -0
  268. /package/{images → terrier/images}/raw/icon-refresh.svg +0 -0
  269. /package/{images → terrier/images}/raw/icon-request.svg +0 -0
  270. /package/{images → terrier/images}/raw/icon-settings.svg +0 -0
  271. /package/{images → terrier/images}/raw/icon-status.svg +0 -0
  272. /package/{images → terrier/images}/raw/icon-step_deploy.svg +0 -0
  273. /package/{images → terrier/images}/raw/icon-step_develop.svg +0 -0
  274. /package/{images → terrier/images}/raw/icon-step_investigate.svg +0 -0
  275. /package/{images → terrier/images}/raw/icon-step_review.svg +0 -0
  276. /package/{images → terrier/images}/raw/icon-step_test.svg +0 -0
  277. /package/{images → terrier/images}/raw/icon-steps.svg +0 -0
  278. /package/{images → terrier/images}/raw/icon-steps_board.svg +0 -0
  279. /package/{images → terrier/images}/raw/icon-subscribe.svg +0 -0
  280. /package/{images → terrier/images}/raw/icon-support.svg +0 -0
  281. /package/{images → terrier/images}/raw/icon-terrier.svg +0 -0
  282. /package/{images → terrier/images}/raw/icon-thumbs_up.svg +0 -0
  283. /package/{images → terrier/images}/raw/icon-type.svg +0 -0
  284. /package/{images → terrier/images}/raw/icon-unprioritized.svg +0 -0
  285. /package/{images → terrier/images}/raw/icon-upload.svg +0 -0
  286. /package/{images → terrier/images}/raw/icon-user.svg +0 -0
  287. /package/{images → terrier/images}/raw/icon-users.svg +0 -0
  288. /package/{images → terrier/images}/raw/terrier-hub-favicon-alert.png +0 -0
  289. /package/{images → terrier/images}/raw/terrier-hub-favicon-dark.png +0 -0
  290. /package/{images → terrier/images}/raw/terrier-hub-favicon.png +0 -0
  291. /package/{images → terrier/images}/raw/terrier-hub-favicon.svg +0 -0
  292. /package/{images → terrier/images}/raw/terrier-hub-icon-dark.svg +0 -0
  293. /package/{images → terrier/images}/raw/terrier-hub-icon-light.png +0 -0
  294. /package/{images → terrier/images}/raw/terrier-hub-icon-light.svg +0 -0
  295. /package/{images → terrier/images}/raw/terrier-hub-loader.svg +0 -0
  296. /package/{images → terrier/images}/raw/terrier-hub-logo-dark.svg +0 -0
  297. /package/{images → terrier/images}/raw/terrier-hub-logo-light.png +0 -0
  298. /package/{images → terrier/images}/raw/terrier-hub-logo-light.svg +0 -0
  299. /package/{lightbox.ts → terrier/lightbox.ts} +0 -0
  300. /package/{loading.ts → terrier/loading.ts} +0 -0
  301. /package/{modals.ts → terrier/modals.ts} +0 -0
  302. /package/{overlays.ts → terrier/overlays.ts} +0 -0
  303. /package/{parts → terrier/parts}/content-part.ts +0 -0
  304. /package/{parts → terrier/parts}/not-found-page.ts +0 -0
  305. /package/{parts → terrier/parts}/page-part.ts +0 -0
  306. /package/{parts → terrier/parts}/terrier-form-part.ts +0 -0
  307. /package/{parts → terrier/parts}/terrier-part.ts +0 -0
  308. /package/{schema.ts → terrier/schema.ts} +0 -0
  309. /package/{sheets.ts → terrier/sheets.ts} +0 -0
  310. /package/{tabs.ts → terrier/tabs.ts} +0 -0
  311. /package/{theme.ts → terrier/theme.ts} +0 -0
  312. /package/{toasts.ts → terrier/toasts.ts} +0 -0
  313. /package/{tooltips.ts → terrier/tooltips.ts} +0 -0
@@ -0,0 +1,723 @@
1
+ import {Part, PartTag} from "tuff-core/parts"
2
+ import Dates, {DateLiteral, VirtualDatePeriod, VirtualDateRange} from "./dates"
3
+ import {ColumnDef, ModelDef, SchemaDef} from "../../terrier/schema"
4
+ import {TableRef, TableView} from "./tables"
5
+ import {arrays, messages} from "tuff-core"
6
+ import {Logger} from "tuff-core/logging"
7
+ import Objects from "tuff-core/objects"
8
+ import inflection from "inflection"
9
+ import {ModalPart} from "../../terrier/modals";
10
+ import TerrierFormPart from "../../terrier/parts/terrier-form-part"
11
+ import {Dropdown} from "../../terrier/dropdowns"
12
+ import dayjs from "dayjs";
13
+ import Format from "../../terrier/format";
14
+
15
+ const log = new Logger("Filters")
16
+
17
+ ////////////////////////////////////////////////////////////////////////////////
18
+ // Types
19
+ ////////////////////////////////////////////////////////////////////////////////
20
+
21
+ type BaseFilter = {
22
+ filter_type: string
23
+ column: string
24
+ editable?: 'optional' | 'required'
25
+ edit_label?: string
26
+ }
27
+
28
+ export const DirectOperators = ['eq', 'ne', 'ilike', 'lt', 'gt', 'lte', 'gte'] as const
29
+ export type DirectOperator = typeof DirectOperators[number]
30
+
31
+ export type DirectFilter = BaseFilter & {
32
+ filter_type: 'direct'
33
+ operator: DirectOperator
34
+ value: string
35
+ numeric_value?: number
36
+ column_type?: 'text' | 'number' | 'cents'
37
+ }
38
+
39
+ // type DirectColumnType = DirectFilter['column_type']
40
+
41
+ export type DateRangeFilter = BaseFilter & {
42
+ filter_type: 'date_range'
43
+ range: VirtualDateRange
44
+ }
45
+
46
+ export type InclusionFilter = BaseFilter & {
47
+ filter_type: 'inclusion'
48
+ in: string[]
49
+ }
50
+
51
+ // currently not implemented, but it would be neat
52
+ export type OrFilter = {
53
+ column: 'or'
54
+ filter_type: 'or'
55
+ where: Filter[]
56
+ }
57
+
58
+ export type Filter = DirectFilter | DateRangeFilter | InclusionFilter | OrFilter
59
+
60
+ type FilterType = Filter['filter_type']
61
+
62
+
63
+ ////////////////////////////////////////////////////////////////////////////////
64
+ // Rendering
65
+ ////////////////////////////////////////////////////////////////////////////////
66
+
67
+ function operatorDisplay(op: DirectOperator): string {
68
+ switch (op) {
69
+ case 'eq':
70
+ return '='
71
+ case 'ne':
72
+ return '≠'
73
+ case 'ilike':
74
+ return '~'
75
+ case 'lt':
76
+ return '<'
77
+ case 'lte':
78
+ return '≤'
79
+ case 'gt':
80
+ return '>'
81
+ case 'gte':
82
+ return '≥'
83
+ default:
84
+ return '?'
85
+ }
86
+ }
87
+
88
+
89
+ function renderStatic(parent: PartTag, filter: Filter) {
90
+ switch (filter.filter_type) {
91
+ case 'direct':
92
+ parent.div('.column').text(filter.column)
93
+ parent.div('.operator').text(operatorDisplay(filter.operator))
94
+ switch (filter.column_type) {
95
+ case 'cents':
96
+ parent.div('.value').text(Format.cents(filter.value))
97
+ break
98
+ default:
99
+ parent.div('.value').text(filter.value)
100
+ break
101
+ }
102
+ break
103
+ case 'date_range':
104
+ parent.div('.column').text(filter.column)
105
+ parent.div('.value').text(Dates.rangeDisplay(filter.range))
106
+ return
107
+ case 'inclusion':
108
+ parent.div('.column').text(filter.column)
109
+ parent.div('.operator').text("in")
110
+ parent.div('.value').text(filter.in.join(' | '))
111
+ return
112
+ default:
113
+ parent.div('.empty').text(`${filter.filter_type} filter`)
114
+ }
115
+ }
116
+
117
+
118
+ ////////////////////////////////////////////////////////////////////////////////
119
+ // Editor Modal
120
+ ////////////////////////////////////////////////////////////////////////////////
121
+
122
+ export type FiltersEditorState = {
123
+ schema: SchemaDef
124
+ tableView: TableView<TableRef>
125
+ }
126
+
127
+ const saveKey = messages.untypedKey()
128
+ const addKey = messages.untypedKey()
129
+ const removeKey = messages.typedKey<{ id: string }>()
130
+
131
+ export class FiltersEditorModal extends ModalPart<FiltersEditorState> {
132
+
133
+ modelDef!: ModelDef
134
+ table!: TableRef
135
+ filterStates: FilterState[] = []
136
+ filterCount = 0
137
+
138
+ updateFilterEditors() {
139
+ this.assignCollection('filters', FilterEditorContainer, this.filterStates)
140
+ }
141
+
142
+ addState(filter: Filter) {
143
+ this.filterCount += 1
144
+ this.filterStates.push({
145
+ schema: this.state.schema,
146
+ filtersEditor: this,
147
+ id: `filter-${this.filterCount}`, ...filter
148
+ })
149
+ }
150
+
151
+ async init() {
152
+ this.table = this.state.tableView.table
153
+ this.modelDef = this.state.tableView.modelDef
154
+
155
+ // initialize the filter states
156
+ const filters = this.table.filters || []
157
+ for (const filter of filters) {
158
+ this.addState(filter)
159
+ }
160
+ this.updateFilterEditors()
161
+
162
+ this.setTitle(`Filters for ${this.state.tableView.displayName}`)
163
+ this.setIcon('glyp-filter')
164
+
165
+ this.addAction({
166
+ title: 'Apply',
167
+ icon: 'glyp-checkmark',
168
+ click: {key: saveKey}
169
+ }, 'primary')
170
+
171
+ this.addAction({
172
+ title: 'Add Filter',
173
+ icon: 'glyp-plus',
174
+ click: {key: addKey}
175
+ }, 'secondary')
176
+
177
+ this.onClick(saveKey, _ => {
178
+ this.save()
179
+ })
180
+
181
+ this.onClick(removeKey, m => {
182
+ this.removeFilter(m.data.id)
183
+ })
184
+
185
+ this.onClick(addKey, m => {
186
+ const onSelected = (filter: Filter) => {
187
+ log.info(`Adding ${filter.filter_type} filter`, filter)
188
+ this.addState(filter)
189
+ this.updateFilterEditors()
190
+ }
191
+ this.toggleDropdown(AddFilterDropdown, {modelDef: this.modelDef, callback: onSelected}, m.event.target)
192
+ })
193
+ }
194
+
195
+ renderContent(parent: PartTag) {
196
+ parent.div('.dd-filters-editor-table', table => {
197
+ table.div('.dd-editor-header', header => {
198
+ header.div('.column').label({text: "Column"})
199
+ header.div('.operator').label({text: "Operator"})
200
+ header.div('.filter').label({text: "Filter"})
201
+ })
202
+ this.renderCollection(table, 'filters')
203
+ .class('dd-editor-row-container')
204
+ })
205
+ }
206
+
207
+ removeFilter(id: string) {
208
+ const filter = arrays.find(this.filterStates, f => f.id == id)
209
+ if (filter) {
210
+ log.info(`Removing filter ${id}`, filter)
211
+ this.filterStates = arrays.without(this.filterStates, filter)
212
+ this.updateFilterEditors()
213
+ }
214
+ }
215
+
216
+
217
+ save() {
218
+ const filters = this.filterStates.map(state => {
219
+ return Objects.omit(state, 'schema', 'filtersEditor', 'id') as Filter
220
+ })
221
+ this.state.tableView.updateFilters(filters)
222
+ this.pop()
223
+ }
224
+
225
+ }
226
+
227
+
228
+ ////////////////////////////////////////////////////////////////////////////////
229
+ // Base Editor
230
+ ////////////////////////////////////////////////////////////////////////////////
231
+
232
+ type BaseFilterState<T extends BaseFilter> = T & {
233
+ schema: SchemaDef
234
+ filtersEditor: FiltersEditorModal
235
+ id: string
236
+ }
237
+
238
+ type FilterState = BaseFilterState<Filter>
239
+
240
+ /**
241
+ * Base class for editors for specific filter types.
242
+ */
243
+ abstract class FilterEditor<T extends BaseFilter> extends TerrierFormPart<BaseFilterState<T>> {
244
+
245
+ modelDef!: ModelDef
246
+ columnDef?: ColumnDef
247
+
248
+ async init() {
249
+ this.modelDef = this.state.filtersEditor.modelDef
250
+ this.columnDef = this.modelDef.columns[this.state.column]
251
+ }
252
+
253
+ get parentClasses(): Array<string> {
254
+ return super.parentClasses.concat(['dd-editor-row'])
255
+ }
256
+
257
+ renderActions(row: PartTag) {
258
+ row.div('.actions', actions => {
259
+ actions.a(a => {
260
+ a.i('.glyp-close')
261
+ }).emitClick(removeKey, {id: this.state.id})
262
+ })
263
+ }
264
+ }
265
+
266
+ /**
267
+ * Contains a concrete instance of FilterEditor for the specific type of filter.
268
+ */
269
+ class FilterEditorContainer extends Part<FilterState> {
270
+
271
+ editor?: FilterEditor<any>
272
+
273
+ async init() {
274
+ this.makeEditor(this.state.filter_type)
275
+ }
276
+
277
+ makeEditor(filterType: FilterType) {
278
+ if (this.editor) {
279
+ this.removeChild(this.editor)
280
+ }
281
+ switch (filterType) {
282
+ case 'direct':
283
+ this.editor = this.makePart(DirectFilterEditor, this.state as BaseFilterState<DirectFilter>)
284
+ break
285
+ case 'inclusion':
286
+ this.editor = this.makePart(InclusionFilterEditor, this.state as BaseFilterState<InclusionFilter>)
287
+ break
288
+ case 'date_range':
289
+ this.editor = this.makePart(DateRangeFilterEditor, this.state as BaseFilterState<DateRangeFilter>)
290
+ break
291
+ }
292
+ }
293
+
294
+ /**
295
+ * This needs to be overridden because the state can be changed by the collection API,
296
+ * in which case we need a new editor since it's dependent on the filter type.
297
+ * @param state
298
+ */
299
+ assignState(state: FilterState): boolean {
300
+ const changed = super.assignState(state)
301
+ if (changed) {
302
+ this.makeEditor(this.state.filter_type)
303
+ }
304
+ return changed
305
+ }
306
+
307
+ render(parent: PartTag) {
308
+ if (this.editor) {
309
+ parent.part(this.editor)
310
+ }
311
+ else {
312
+ parent.div('.tt-bubble.alert', {text: `Unknown filter type '${this.state.filter_type}'`})
313
+ }
314
+ }
315
+
316
+ }
317
+
318
+ ////////////////////////////////////////////////////////////////////////////////
319
+ // Direct Editor
320
+ ////////////////////////////////////////////////////////////////////////////////
321
+
322
+ class DirectFilterEditor extends FilterEditor<DirectFilter> {
323
+
324
+ numericChangeKey = messages.untypedKey()
325
+
326
+ async init() {
327
+ await super.init()
328
+
329
+ if (this.columnDef?.type == 'cents') {
330
+ this.state.column_type = 'cents'
331
+ this.state.numeric_value = parseInt(this.state.value) / 100
332
+ }
333
+ else if (this.columnDef?.type == 'number') {
334
+ this.state.column_type = 'number'
335
+ this.state.numeric_value = parseFloat(this.state.value)
336
+ }
337
+
338
+ // for numeric types, we use a number input and translate the
339
+ // value back to the string value field whenever it changes
340
+ this.onChange(this.numericChangeKey, m => {
341
+ log.info(`Direct filter for ${this.columnDef?.name} numeric value changed to ${m.value}`)
342
+ if (this.state.column_type == 'cents') {
343
+ this.state.value = Math.round(parseFloat(m.value)*100).toString()
344
+ }
345
+ else {
346
+ this.state.value = m.value
347
+ }
348
+ })
349
+ }
350
+
351
+ render(parent: PartTag) {
352
+ parent.div('.column', col => {
353
+ col.div('.tt-readonly-field', {text: this.state.column})
354
+ })
355
+ parent.div('.operator', col => {
356
+ const operatorOptions = DirectOperators.map(op => {
357
+ return {title: operatorDisplay(op), value: op}
358
+ })
359
+ this.select(col, 'operator', operatorOptions)
360
+ })
361
+ parent.div('.filter', col => {
362
+ switch (this.state.column_type) {
363
+ case 'cents':
364
+ col.div('.tt-compound-field', field => {
365
+ field.label().text('$')
366
+ this.numberInput(field, 'numeric_value', {placeholder: "Value"})
367
+ .emitChange(this.numericChangeKey)
368
+ })
369
+ break
370
+ case 'number':
371
+ this.numberInput(col, 'numeric_value', {placeholder: "Value"})
372
+ .emitChange(this.numericChangeKey)
373
+ break
374
+ default:
375
+ this.textInput(col, 'value', {placeholder: "Value"})
376
+ }
377
+ })
378
+ this.renderActions(parent)
379
+ }
380
+
381
+ }
382
+
383
+ ////////////////////////////////////////////////////////////////////////////////
384
+ // Inclusion Editor
385
+ ////////////////////////////////////////////////////////////////////////////////
386
+
387
+ const inclusionChangedKey = messages.typedKey<{value: string}>()
388
+
389
+ class InclusionFilterEditor extends FilterEditor<InclusionFilter> {
390
+
391
+ values!: Set<string>
392
+
393
+ async init() {
394
+ await super.init()
395
+
396
+ this.values = new Set(this.state.in || [])
397
+ this.onChange(inclusionChangedKey, m => {
398
+ const val = m.data.value
399
+ const checked = (m.event.target as HTMLInputElement).checked
400
+ log.info(`${val} checkbox changed to ${checked}`)
401
+ if (checked) {
402
+ this.values.add(val)
403
+ }
404
+ else {
405
+ this.values.delete(val)
406
+ }
407
+ this.state.in = Array.from(this.values)
408
+ })
409
+ }
410
+
411
+ render(parent: PartTag) {
412
+ parent.div('.column', col => {
413
+ col.div('.tt-readonly-field', {text: this.state.column})
414
+ })
415
+ parent.div('.operator', col => {
416
+ col.span().text("In")
417
+ })
418
+ parent.div('.filter', col => {
419
+ if (this.columnDef?.possible_values?.length) {
420
+ col.div('.tt-flex.gap.wrap.possible-values.align-center', row => {
421
+ for (const val of this.columnDef!.possible_values!) {
422
+ row.label('.body-size', label => {
423
+ label.input({type: 'checkbox', checked: this.values.has(val)})
424
+ .emitChange(inclusionChangedKey, {value: val})
425
+ label.div().text(val)
426
+ })
427
+ }
428
+ })
429
+ }
430
+ else { // no possible values
431
+ col.input({type: 'text', placeholder: "Values"})
432
+ }
433
+ })
434
+ this.renderActions(parent)
435
+ }
436
+
437
+ }
438
+
439
+
440
+ ////////////////////////////////////////////////////////////////////////////////
441
+ // Date Range Editor
442
+ ////////////////////////////////////////////////////////////////////////////////
443
+
444
+ const dateRangeRelativeChangedKey = messages.untypedKey()
445
+ const dateRangePeriodChangedKey = messages.typedKey<{period: string}>()
446
+ const dateRangePreselectKey = messages.typedKey<VirtualDateRange>()
447
+
448
+ class DateRangeFilterEditor extends FilterEditor<DateRangeFilter> {
449
+
450
+ range!: VirtualDateRange
451
+
452
+ async init() {
453
+ // assume it's a virtual range for the sake of editing it
454
+ if ('period' in this.state.range) {
455
+ this.range = this.state.range
456
+ }
457
+ else {
458
+ // make up a new range
459
+ this.range = {period: 'day', relative: -1}
460
+ this.state.range = this.range
461
+ }
462
+
463
+ this.onChange(dateRangeRelativeChangedKey, m => {
464
+ this.range.relative = parseFloat(m.value)
465
+ this.dirty()
466
+ })
467
+
468
+ this.onChange(dateRangePeriodChangedKey, m => {
469
+ log.info(`Date range period ${m.data.period} changed to ${m.value}`)
470
+ this.range.period = m.data.period as VirtualDatePeriod
471
+ this.dirty()
472
+ })
473
+
474
+ this.onClick(dateRangePreselectKey, m => {
475
+ this.range = m.data
476
+ this.state.range = this.range
477
+ this.dirty()
478
+ })
479
+ }
480
+
481
+ render(parent: PartTag) {
482
+ parent.div('.column', col => {
483
+ col.div('.tt-readonly-field', {text: this.state.column})
484
+ })
485
+ parent.div('.operator', col => {
486
+ col.span().text("Range")
487
+ })
488
+ parent.div('.filter.column', cell => {
489
+
490
+ // the actual inputs
491
+ cell.div('.tt-flex.gap.align-center', row => {
492
+ row.div('.shrink', col => {
493
+ col.input({type: 'number', value: this.range.relative.toString()})
494
+ .data({tooltip: "Positive for the future, zero for current, and negative for the past"})
495
+ .emitChange(dateRangeRelativeChangedKey)
496
+ })
497
+ row.div('.stretch.tt-flex.wrap.gap', col => {
498
+ for (const period of Dates.virtualPeriods) {
499
+ col.label('.caption-size', label => {
500
+ label.input({type: 'radio', name: `${this.id}-period`, value: period, checked: this.range.period==period})
501
+ .emitChange(dateRangePeriodChangedKey, {period})
502
+ label.span().text(inflection.titleize(period))
503
+ })
504
+ }
505
+ })
506
+ })
507
+
508
+ // some common sets
509
+ cell.div('.tt-flex.gap.wrap.align-center', row => {
510
+ for (const period of Dates.virtualPeriods) {
511
+ row.div('.shrink', col => {
512
+ for (let relative = -1; relative < 2; relative++) {
513
+ const classes = ['date-range-preselect']
514
+ if (period == this.range.period && relative == this.range.relative) {
515
+ classes.push('current')
516
+ }
517
+ col.a().class(...classes)
518
+ .text(Dates.rangeDisplay({period, relative}))
519
+ .emitClick(dateRangePreselectKey, {period, relative})
520
+ }
521
+ })
522
+ }
523
+ })
524
+ })
525
+
526
+ this.renderActions(parent)
527
+ }
528
+
529
+ }
530
+
531
+
532
+ ////////////////////////////////////////////////////////////////////////////////
533
+ // Add Filter Dropdown
534
+ ////////////////////////////////////////////////////////////////////////////////
535
+
536
+ type AddFilterCallback = (filter: Filter) => any
537
+
538
+ const columnSelectedKey = messages.typedKey<{column: string}>()
539
+
540
+ class AddFilterDropdown extends Dropdown<{modelDef: ModelDef, callback: AddFilterCallback}> {
541
+ columns!: string[]
542
+
543
+
544
+ get autoClose(): boolean {
545
+ return true
546
+ }
547
+
548
+ async init() {
549
+ await super.init()
550
+
551
+ this.columns = Object.keys(this.state.modelDef.columns).sort()
552
+
553
+ this.onClick(columnSelectedKey, m => {
554
+ const column = m.data.column
555
+ const colDef = this.state.modelDef.columns[column]
556
+ if (colDef) {
557
+ this.clear()
558
+ switch (colDef.type) {
559
+ case 'enum':
560
+ const vals = colDef.possible_values || []
561
+ return this.state.callback({filter_type: 'inclusion', column, in: vals})
562
+ case 'date':
563
+ case 'datetime':
564
+ return this.state.callback({filter_type: 'date_range', column, range: {period: 'year', relative: 0}})
565
+ default: // direct
566
+ const colType = colDef.type == 'number' || colDef.type == 'cents' ? colDef.type : 'text'
567
+ return this.state.callback({filter_type: 'direct', column, column_type: colType, operator: 'eq', value: ''})
568
+ }
569
+ }
570
+ else {
571
+ this.showToast(`Invalid column ${column}`, {color: "alert"})
572
+ }
573
+
574
+ })
575
+ }
576
+
577
+ get parentClasses(): Array<string> {
578
+ return super.parentClasses.concat(['dd-select-columns-dropdown']);
579
+ }
580
+
581
+ renderContent(parent: PartTag) {
582
+ parent.div('.header', header => {
583
+ header.i(".glyp-columns")
584
+ header.span().text("Select a Column")
585
+ })
586
+ for (const column of this.columns) {
587
+ parent.a({text: column})
588
+ .emitClick(columnSelectedKey, {column})
589
+ }
590
+ }
591
+
592
+ }
593
+
594
+
595
+ ////////////////////////////////////////////////////////////////////////////////
596
+ // Inputs
597
+ ////////////////////////////////////////////////////////////////////////////////
598
+
599
+ export type FilterInput = Filter & {
600
+ input_key: string
601
+ input_value: string
602
+ possible_values?: string[]
603
+ }
604
+
605
+ /**
606
+ * Computes a string used to identify filters that are "the same".
607
+ * @param schema
608
+ * @param table
609
+ * @param filter
610
+ */
611
+ function toInput(schema: SchemaDef, table: TableRef, filter: Filter): FilterInput {
612
+ const key = `${table.model}.${filter.column}`
613
+ const filterInput: FilterInput = {input_key: key,...filter, input_value: ''}
614
+ switch (filter.filter_type) {
615
+ case 'inclusion':
616
+ const modelDef = schema.models[table.model]
617
+ const columnDef = modelDef.columns[filter.column]
618
+ filterInput.possible_values = columnDef.possible_values
619
+ filterInput.input_key = `${filterInput.input_key}#in`
620
+ break
621
+ case 'date_range':
622
+ filterInput.input_key = `${filterInput.input_key}#range`
623
+ break
624
+ case 'direct':
625
+ filterInput.input_key = `${filterInput.input_key}#${filter.operator}`
626
+ break
627
+ }
628
+ return filterInput
629
+ }
630
+
631
+ /**
632
+ * Populates a hash of raw field values used to track the value of the filter inputs.
633
+ * @param filters
634
+ * @return an object ready to be used in a `FormFields`
635
+ */
636
+ function populateRawInputData(filters: FilterInput[]): Record<string,string> {
637
+ const data: Record<string, string> = {}
638
+ for (const filter of filters) {
639
+ switch (filter.filter_type) {
640
+ case 'date_range':
641
+ const range = Dates.materializeVirtualRange(filter.range)
642
+ data[`${filter.input_key}-min`] = range.min
643
+ data[`${filter.input_key}-max`] = dayjs(range.max).subtract(1, 'day').format(Dates.literalFormat)
644
+ break
645
+ case 'direct':
646
+ switch (filter.column_type) {
647
+ case 'cents':
648
+ data[filter.input_key] = (parseInt(filter.value)/100).toString()
649
+ break
650
+ default:
651
+ data[filter.input_key] = filter.value
652
+ }
653
+ break
654
+ case 'inclusion':
655
+ for (const value of filter.in) {
656
+ data[`${filter.input_key}-${value}`] = 'true'
657
+ }
658
+ break
659
+ default:
660
+ log.warn(`Don't know how to get ${filter.filter_type} raw value`, filter)
661
+ }
662
+ }
663
+ return data
664
+ }
665
+
666
+ /**
667
+ * Assign the input_value values for all filters based on the raw filter data that was populated with `populateRawInputData`.
668
+ * This is necessary since some filter values need more than one form field to represent (like date range and inclusion).
669
+ * @param filters
670
+ * @param data
671
+ */
672
+ function serializeRawInputData(filters: FilterInput[], data: Record<string, string>) {
673
+ for (const filter of filters) {
674
+ switch (filter.filter_type) {
675
+ case 'date_range':
676
+ const range = {
677
+ min: data[`${filter.input_key}-min`] as DateLiteral,
678
+ max: dayjs(data[`${filter.input_key}-max`]).add(1, 'day').format(Dates.literalFormat) as DateLiteral
679
+ }
680
+ const period = Dates.serializePeriod(range)
681
+ filter.input_value = period
682
+ break
683
+ case 'direct':
684
+ switch (filter.column_type) {
685
+ case 'cents':
686
+ const dollars = data[filter.input_key]
687
+ filter.input_value = Math.round(parseFloat(dollars)*100).toString()
688
+ break
689
+ default:
690
+ filter.input_value = data[filter.input_key]
691
+ }
692
+ break
693
+ case 'inclusion':
694
+ const values: string[] = []
695
+ for (const value of filter.possible_values || []) {
696
+ if (data[`${filter.input_key}-${value}`]) {
697
+ values.push(value)
698
+ }
699
+ }
700
+ filter.input_value = values.join(',')
701
+ break
702
+ default:
703
+ log.warn(`Don't know how to serialize ${filter.filter_type} raw value`, filter)
704
+ }
705
+ }
706
+ }
707
+
708
+
709
+
710
+
711
+ ////////////////////////////////////////////////////////////////////////////////
712
+ // Export
713
+ ////////////////////////////////////////////////////////////////////////////////
714
+
715
+ const Filters = {
716
+ renderStatic,
717
+ toInput,
718
+ operatorDisplay,
719
+ populateRawInputData,
720
+ serializeRawInputData
721
+ }
722
+
723
+ export default Filters