terrier-engine 4.41.2 → 4.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -416,6 +416,7 @@ class DuplicateQueryModal extends ModalPart<DuplicateQueryState> {
416
416
  this.state.editor.queryTabs.showTab(query.id)
417
417
  this.pop()
418
418
  this.app.successToast("Duplicated Query", 'glyp-copy')
419
+ this.emitMessage(DiveEditor.diveChangedKey, {})
419
420
  }
420
421
 
421
422
  renderContent(parent: PartTag): void {
@@ -1,3 +1,4 @@
1
+ import Arrays from "tuff-core/arrays"
1
2
  import {ModalPart} from "../../terrier/modals"
2
3
  import {PartTag} from "tuff-core/parts"
3
4
  import {DdDive, DdDiveRun, UnpersistedDdDiveRun} from "../gen/models"
@@ -191,8 +192,12 @@ export class DiveRunModal extends ModalPart<{dive: DdDive }> {
191
192
  col.div('.tt-flex.collapsible.gap.tt-form', row => {
192
193
  // inputs
193
194
  row.div('.tt-flex.column.dd-dive-run-inputs', col => {
194
- for (const filter of this.filters) {
195
- this.renderInput(col, filter)
195
+ const filtersByQuery = Arrays.groupBy(this.filters, 'query_name')
196
+ for (const queryName of Object.keys(filtersByQuery).sort()) {
197
+ col.h3(".glyp-data_dive_query").text(queryName)
198
+ for (const filter of filtersByQuery[queryName]) {
199
+ this.renderInput(col, filter)
200
+ }
196
201
  }
197
202
  })
198
203
 
@@ -61,7 +61,7 @@ async function list(): Promise<DiveListResult> {
61
61
  function computeFilterInputs(schema: SchemaDef, dive: DdDive): FilterInput[] {
62
62
  const filters: Record<string, FilterInput> = {}
63
63
  for (const query of dive.query_data?.queries || []) {
64
- Tables.computeFilterInputs(schema, query.from, filters)
64
+ Tables.computeFilterInputs(schema, query, query.from, filters)
65
65
  }
66
66
  return Object.values(filters)
67
67
  }
@@ -1,6 +1,7 @@
1
1
  import {PartTag} from "tuff-core/parts"
2
2
  import Dates, {DateLiteral, VirtualDatePeriod, VirtualDateRange} from "./dates"
3
3
  import {ColumnDef, ModelDef, SchemaDef} from "../../terrier/schema"
4
+ import { Query } from "./queries"
4
5
  import {TableRef, TableView} from "./tables"
5
6
  import {Logger} from "tuff-core/logging"
6
7
  import * as inflection from "inflection"
@@ -683,6 +684,7 @@ class AddFilterDropdown extends Dropdown<{modelDef: ModelDef, callback: AddFilte
683
684
  ////////////////////////////////////////////////////////////////////////////////
684
685
 
685
686
  export type FilterInput = Filter & {
687
+ query_name: string
686
688
  input_name: string
687
689
  input_value: string
688
690
  possible_values?: string[]
@@ -694,12 +696,12 @@ export type FilterInput = Filter & {
694
696
  * @param table
695
697
  * @param filter
696
698
  */
697
- function toInput(schema: SchemaDef, table: TableRef, filter: Filter): FilterInput {
699
+ function toInput(schema: SchemaDef, query: Query, table: TableRef, filter: Filter): FilterInput {
698
700
  if (!filter.id?.length) {
699
701
  filter.id = Ids.makeRandom(8)
700
702
  }
701
703
  const name = `${table.model}.${filter.column}`
702
- const filterInput: FilterInput = {...filter, input_name: name, input_value: ''}
704
+ const filterInput: FilterInput = {...filter, query_name: query.name, input_name: name, input_value: ''}
703
705
  switch (filter.filter_type) {
704
706
  case 'inclusion':
705
707
  const modelDef = schema.models[table.model]
@@ -1,3 +1,4 @@
1
+ import { Filter } from "./filters"
1
2
  import {TableRef} from "./tables"
2
3
  import api, {ApiResponse} from "../../terrier/api"
3
4
  import {PartTag} from "tuff-core/parts"
@@ -63,7 +64,7 @@ function eachTable(query: Query, fn: TableFunction) {
63
64
  eachChildTable(query.from, fn)
64
65
  }
65
66
 
66
- type ColumnFunction = (table: TableRef, col: ColumnRef) => any
67
+ export type ColumnFunction = (table: TableRef, col: ColumnRef) => any
67
68
 
68
69
  function eachColumnForTable(table: TableRef, fn: ColumnFunction) {
69
70
  if (table.columns) {
@@ -87,14 +88,44 @@ function eachColumn(query: Query, fn: ColumnFunction) {
87
88
  eachColumnForTable(query.from, fn)
88
89
  }
89
90
 
91
+ export type FilterFunction = (table: TableRef, filter: Filter) => any
92
+
93
+ function eachFilterForTable(table: TableRef, fn: FilterFunction) {
94
+ if (table.filters) {
95
+ for (const filter of table.filters) {
96
+ fn(table, filter)
97
+ }
98
+ }
99
+ if (table.joins) {
100
+ for (const joinedTable of Object.values(table.joins)) {
101
+ eachFilterForTable(joinedTable, fn)
102
+ }
103
+ }
104
+ }
105
+
90
106
  /**
91
- * Duplicates a query, including all of the nested data structures.
107
+ * Recursively iterates over all filters in the query and executes the given function for each.
108
+ * @param query
109
+ * @param fn a function to evaluate on each filter
110
+ */
111
+ function eachFilter(query: Query, fn: FilterFunction) {
112
+ eachFilterForTable(query.from, fn)
113
+ }
114
+
115
+ /**
116
+ * Duplicates a query, including all the nested data structures.
92
117
  * @param query the query to duplicate
93
118
  * @return a completely new query
94
119
  */
95
120
  function duplicate(query: Query): Query {
96
121
  const newQuery = Objects.deepCopy(query)
97
122
  newQuery.id = Ids.makeUuid()
123
+
124
+ // filters need new IDs, otherwise they won't be able to be set differently than the original query's filters
125
+ eachFilter(newQuery, (_, filter) => {
126
+ filter.id = Ids.makeUuid()
127
+ })
128
+
98
129
  return newQuery
99
130
  }
100
131
 
@@ -332,6 +363,7 @@ export class QueryModelPicker extends TerrierPart<QueryModelPickerState> {
332
363
  const Queries = {
333
364
  eachColumn,
334
365
  eachTable,
366
+ eachFilter,
335
367
  duplicate,
336
368
  validate,
337
369
  preview,
@@ -1,4 +1,5 @@
1
1
  import {ModalPart} from "../../terrier/modals"
2
+ import Columns from "./columns"
2
3
  import Queries, {OrderBy, Query} from "./queries"
3
4
  import Messages from "tuff-core/messages"
4
5
  import {PartTag} from "tuff-core/parts"
@@ -54,15 +55,19 @@ export default class RowOrderModal extends ModalPart<RowOrderState> {
54
55
 
55
56
  // collect the column options
56
57
  const query = this.state.query
57
- Queries.eachColumn(query, (_, col) => {
58
- this.columnOptions.push({title: col.name, value: col.name})
58
+ const existingColumns = new Set<string>()
59
+ Queries.eachColumn(query, (table, col) => {
60
+ const name = Columns.computeSelectName(table, col)
61
+ existingColumns.add(name)
62
+ this.columnOptions.push({title: name, value: name})
59
63
  })
60
64
 
61
- // initialize the order=bys from the query, if present
65
+ // initialize the order-bys from the query, if present
62
66
  if (query.order_by?.length) {
63
- this.orderBys = query.order_by
67
+ // filter out columns that are no longer present in the query
68
+ this.orderBys = query.order_by.filter(ob => existingColumns.has(ob.column))
64
69
  }
65
- else {
70
+ if (!query.order_by?.length) { // this isn't an else because the previous clause might've resulted in no columns
66
71
  // start with something by default
67
72
  this.addClause()
68
73
  }
@@ -47,22 +47,19 @@ const updatedKey = Messages.typedKey<TableRef>()
47
47
  ////////////////////////////////////////////////////////////////////////////////
48
48
 
49
49
  /**
50
- * Recursively collect s all of the filters for this and all joined tables.
51
- * Only keep one (the last one traversed) per table/column combination.
52
- * This means that some filters may clobber others, but I think it will yield
53
- * the desired result most of the time.
50
+ * Recursively collects all the filters for this and all joined tables.
54
51
  * @param schema
55
52
  * @param table
56
53
  * @param filters
57
54
  */
58
- function computeFilterInputs(schema: SchemaDef, table: TableRef, filters: Record<string, FilterInput>) {
55
+ function computeFilterInputs(schema: SchemaDef, query: Query, table: TableRef, filters: Record<string, FilterInput>) {
59
56
  for (const f of table.filters || []) {
60
- const fi = Filters.toInput(schema, table, f)
57
+ const fi = Filters.toInput(schema, query, table, f)
61
58
  filters[fi.id] = fi
62
59
  }
63
60
  if (table.joins) {
64
61
  for (const j of Object.values(table.joins)) {
65
- computeFilterInputs(schema, j, filters)
62
+ computeFilterInputs(schema, query, j, filters)
66
63
  }
67
64
  }
68
65
  }
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "files": [
5
5
  "*"
6
6
  ],
7
- "version": "4.41.2",
7
+ "version": "4.44.0",
8
8
  "repository": {
9
9
  "type": "git",
10
10
  "url": "https://github.com/Terrier-Tech/terrier-engine"
@@ -20,18 +20,19 @@
20
20
  "inflection": "^2.0.1",
21
21
  "mime-types": "^2.1.35",
22
22
  "tuff-core": "latest",
23
- "tuff-plot": "^0.3.0",
23
+ "tuff-plot": "latest",
24
24
  "tuff-sortable": "latest",
25
25
  "turbolinks": "^5.2.0"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@types/mime-types": "^2.1.4",
29
- "@types/node": "^18.16.3",
29
+ "@types/node": "^22.9.0",
30
30
  "glyphs2font": "^1.2.4",
31
31
  "prettier": "^2.8.8",
32
32
  "svgo": "^3.0.2",
33
- "vite": "^5.4.8",
33
+ "typescript": "^5.6.3",
34
+ "vite": "^5.4.11",
34
35
  "vite-plugin-ruby": "^5.1.0",
35
- "vitest": "^0.31.1"
36
+ "vitest": "^2.1.5"
36
37
  }
37
38
  }
package/terrier/app.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import {Logger} from "tuff-core/logging"
2
- import {Part, PartConstructor, PartParent} from "tuff-core/parts"
2
+ import {Part, PartConstructor} from "tuff-core/parts"
3
3
  import TerrierPart from "./parts/terrier-part"
4
4
  import Tooltips from "./tooltips"
5
5
  import Lightbox from "./lightbox"
@@ -48,8 +48,8 @@ export abstract class TerrierApp<TState> extends TerrierPart<TState> {
48
48
  /// Overlays
49
49
 
50
50
  addOverlay<OverlayType extends Part<StateType>, StateType extends {}>(
51
- constructor: { new(p: PartParent, id: string, state: StateType): OverlayType; },
52
- state: StateType,
51
+ constructor: PartConstructor<OverlayType, StateType>,
52
+ state: NoInfer<StateType>,
53
53
  type: OverlayLayerType
54
54
  ) {
55
55
  return this.overlayPart.pushLayer(constructor, state, type)
@@ -85,7 +85,7 @@ export abstract class TerrierApp<TState> extends TerrierPart<TState> {
85
85
 
86
86
  showModal<ModalType extends ModalPart<StateType>, StateType>(
87
87
  constructor: PartConstructor<ModalType, StateType>,
88
- state: StateType
88
+ state: NoInfer<StateType>
89
89
  ): ModalType {
90
90
  const modalStack = this.overlayPart.getOrCreateLayer(ModalStackPart, {}, 'modal')
91
91
  const modal = modalStack.pushModal(constructor, state)
@@ -70,14 +70,14 @@ class LightboxPart extends Part<LightboxState> {
70
70
  }
71
71
 
72
72
  update(_elem: HTMLElement) {
73
- setTimeout(_ => {
73
+ setTimeout(() => {
74
74
  _elem.classList.add('active') // start css fade in
75
75
  }, 10)
76
76
  }
77
77
 
78
78
  close() {
79
79
  this.element?.classList.remove('active')
80
- setTimeout(_ => {
80
+ setTimeout(() => {
81
81
  this.state.app.removeOverlay(this.state)
82
82
  }, 500)
83
83
  }
package/terrier/modals.ts CHANGED
@@ -147,7 +147,10 @@ export class ModalStackPart extends TerrierPart<{}> {
147
147
  * @param constructor the modal class
148
148
  * @param state the modal's state
149
149
  */
150
- pushModal<StateType>(constructor: PartConstructor<ModalPart<StateType>, StateType>, state: StateType): ModalPart<StateType> {
150
+ pushModal<ModalType extends ModalPart<StateType>, StateType>(
151
+ constructor: PartConstructor<ModalType, StateType>,
152
+ state: NoInfer<StateType>
153
+ ): ModalPart<StateType> {
151
154
  log.info(`Making modal`, constructor.name)
152
155
  const modal = this.makePart(constructor, state)
153
156
  this.modals.push(modal)
@@ -33,7 +33,7 @@ export class OverlayPart extends Part<NoState> {
33
33
  */
34
34
  pushLayer<PartType extends Part<StateType>, StateType extends {}>(
35
35
  constructor: PartConstructor<PartType, StateType>,
36
- state: StateType,
36
+ state: NoInfer<StateType>,
37
37
  type: OverlayLayerType
38
38
  ): PartType {
39
39
  this.layerStates.push({partClass: constructor, partState: state, type})
@@ -50,7 +50,7 @@ export class OverlayPart extends Part<NoState> {
50
50
  */
51
51
  getOrCreateLayer<PartType extends Part<StateType>, StateType extends {}>(
52
52
  constructor: PartConstructor<PartType, StateType>,
53
- state: StateType,
53
+ state: NoInfer<StateType>,
54
54
  type: OverlayLayerType
55
55
  ): PartType {
56
56
  const layers = this.getCollectionParts('layers')
@@ -1,4 +1,4 @@
1
- import {Part, PartParent, PartTag} from "tuff-core/parts"
1
+ import { Part, PartConstructor, PartTag } from "tuff-core/parts"
2
2
  import {TerrierApp} from "../app"
3
3
  import Loading from "../loading"
4
4
  import Theme, {IconName} from "../theme"
@@ -171,8 +171,8 @@ export default abstract class TerrierPart<TState> extends Part<TState> {
171
171
  * @param target the target element around which to show the dropdown
172
172
  */
173
173
  makeDropdown<DropdownType extends Dropdown<DropdownStateType>, DropdownStateType extends {}>(
174
- constructor: { new(p: PartParent, id: string, state: DropdownStateType): DropdownType; },
175
- state: DropdownStateType,
174
+ constructor: PartConstructor<DropdownType, DropdownStateType>,
175
+ state: NoInfer<DropdownStateType>,
176
176
  target: EventTarget | null) {
177
177
  const dropdown = this.app.addOverlay(constructor, state, 'dropdown')
178
178
  dropdown.parentPart = this
@@ -193,8 +193,8 @@ export default abstract class TerrierPart<TState> extends Part<TState> {
193
193
  * @param target the target element around which to show the dropdown
194
194
  */
195
195
  toggleDropdown<DropdownType extends Dropdown<DropdownStateType>, DropdownStateType extends {}>(
196
- constructor: { new(p: PartParent, id: string, state: DropdownStateType): DropdownType; },
197
- state: DropdownStateType,
196
+ constructor: PartConstructor<DropdownType, DropdownStateType>,
197
+ state: NoInfer<DropdownStateType>,
198
198
  target: EventTarget | null) {
199
199
  if (target && target instanceof HTMLElement && target == this.app.lastDropdownTarget) {
200
200
  this.clearDropdowns()
package/tsconfig.json CHANGED
@@ -8,7 +8,7 @@
8
8
  "ESNext",
9
9
  "DOM"
10
10
  ],
11
- "moduleResolution" : "Node",
11
+ "moduleResolution" : "bundler",
12
12
  "strict" : true,
13
13
  "sourceMap" : true,
14
14
  "resolveJsonModule" : true,