terrier-engine 4.52.6 → 4.52.7

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.
@@ -1,16 +1,16 @@
1
- import {MarkerStyle, PlotTrace, TraceType, YAxisName} from "tuff-plot/trace"
1
+ import { MarkerStyle, PlotTrace, TraceType, YAxisName } from "tuff-plot/trace"
2
2
  import { PartTag } from "tuff-core/parts"
3
- import {ModalPart} from "../../terrier/modals"
3
+ import { ModalPart } from "../../terrier/modals"
4
4
  import Ids from "../../terrier/ids"
5
- import {DivePlotEditorState} from "./dive-plot-editor"
6
- import {TerrierFormFields} from "../../terrier/forms"
7
- import {UnpersistedDdDivePlot} from "../gen/models"
8
- import {SelectOptions} from "tuff-core/forms"
9
- import Queries, {Query, QueryResult} from "../queries/queries"
10
- import {Logger} from "tuff-core/logging"
5
+ import { DivePlotEditorState } from "./dive-plot-editor"
6
+ import { TerrierFormFields } from "../../terrier/forms"
7
+ import { UnpersistedDdDivePlot } from "../gen/models"
8
+ import { SelectOptions } from "tuff-core/forms"
9
+ import Queries, { Query, QueryResult } from "../queries/queries"
10
+ import { Logger } from "tuff-core/logging"
11
11
  import Columns from "../queries/columns"
12
12
  import Messages from "tuff-core/messages"
13
- import DivePlotStyles, {DivePlotTraceStyle, TraceStyleFields} from "./dive-plot-styles"
13
+ import DivePlotStyles, { DivePlotTraceStyle, TraceStyleFields } from "./dive-plot-styles"
14
14
  import TerrierPart from "../../terrier/parts/terrier-part"
15
15
 
16
16
  const log = new Logger("DivePlotTraces")
@@ -86,7 +86,7 @@ export class DivePlotTraceEditor extends ModalPart<DivePlotTraceEditorState> {
86
86
 
87
87
  this.queries = this.state.dive.query_data?.queries || []
88
88
  this.queryOptions = this.queries.map(query => {
89
- return {value: query.id, title: query.name}
89
+ return { value: query.id, title: query.name }
90
90
  }) || []
91
91
 
92
92
  this.trace.query_id ||= this.queries.at(0)?.id || ''
@@ -100,13 +100,13 @@ export class DivePlotTraceEditor extends ModalPart<DivePlotTraceEditorState> {
100
100
  this.addAction({
101
101
  title: "Save",
102
102
  icon: "glyp-checkmark",
103
- click: {key: this.saveKey}
103
+ click: { key: this.saveKey }
104
104
  })
105
105
 
106
106
  this.addAction({
107
107
  title: "Delete",
108
108
  icon: "glyp-delete",
109
- click: {key: this.deleteKey},
109
+ click: { key: this.deleteKey },
110
110
  classes: ['alert']
111
111
  }, 'secondary')
112
112
 
@@ -133,9 +133,9 @@ export class DivePlotTraceEditor extends ModalPart<DivePlotTraceEditorState> {
133
133
  this.axisOptions = []
134
134
  if (query) {
135
135
  log.info(`Computing axis options for query`, query)
136
- Queries.eachColumn(query, (table, column) => {
136
+ for (const { table, column } of Queries.columns(query)) {
137
137
  this.axisOptions.push(Columns.computeSelectName(table, column))
138
- })
138
+ }
139
139
  }
140
140
  else {
141
141
  log.warn(`No query with id ${queryId}`)
@@ -180,7 +180,7 @@ export class DivePlotTraceEditor extends ModalPart<DivePlotTraceEditorState> {
180
180
 
181
181
  async save() {
182
182
  const data = await this.fields.serialize()
183
- this.trace = {...this.trace, ...data}
183
+ this.trace = { ...this.trace, ...data }
184
184
  this.trace.style = await this.styleFields.serialize()
185
185
  log.info("Saving plot trace", this.trace)
186
186
  this.state.onSave(this.trace)
@@ -224,9 +224,9 @@ export class DivePlotTraceRow extends TerrierPart<DivePlotTraceRowState> {
224
224
  // style
225
225
  content.div('.style', stylePreview => {
226
226
  DivePlotStyles.renderPreview(stylePreview, style, this.state.index)
227
- }).data({tooltip: `${style.colorName} ${style.strokeWidthName} ${style.strokeDasharrayName}`})
227
+ }).data({ tooltip: `${style.colorName} ${style.strokeWidthName} ${style.strokeDasharrayName}` })
228
228
  })
229
- }).emitClick(editKey, {id: trace.id})
229
+ }).emitClick(editKey, { id: trace.id })
230
230
  }
231
231
 
232
232
  }
@@ -1,11 +1,11 @@
1
1
  import Arrays from "tuff-core/arrays"
2
2
  import { PartTag } from "tuff-core/parts"
3
- import {ModalPart} from "../../terrier/modals"
3
+ import { ModalPart } from "../../terrier/modals"
4
4
  import Columns from "./columns"
5
- import Queries, {Query} from "./queries"
5
+ import Queries, { Query } from "./queries"
6
6
  import Messages from "tuff-core/messages"
7
7
  import SortablePlugin from "tuff-sortable/sortable-plugin"
8
- import {Logger} from "tuff-core/logging"
8
+ import { Logger } from "tuff-core/logging"
9
9
 
10
10
  const log = new Logger("ColumnOrderModal")
11
11
 
@@ -26,7 +26,7 @@ export default class ColumnOrderModal extends ModalPart<ColumnOrderState> {
26
26
  this.addAction({
27
27
  title: "Apply",
28
28
  icon: "glyp-checkmark",
29
- click: {key: this.submitKey}
29
+ click: { key: this.submitKey }
30
30
  })
31
31
 
32
32
  this.onClick(this.submitKey, _ => {
@@ -36,25 +36,7 @@ export default class ColumnOrderModal extends ModalPart<ColumnOrderState> {
36
36
 
37
37
  // initialize the columns from the query, if present
38
38
  const query = this.state.query
39
- const initialColumns = new Set<string>() // keep track of the initial columns
40
- if (query.columns?.length) {
41
- this.columns = query.columns
42
- this.columns.forEach(c => {
43
- initialColumns.add(c)
44
- })
45
- }
46
-
47
- // ensure that all columns in the query are represented, regardless of whether they're stored
48
- Queries.eachColumn(query, (table, col) => {
49
- const name = Columns.computeSelectName(table, col)
50
- if (!this.columns.includes(name)) {
51
- this.columns.push(name)
52
- initialColumns.delete(name)
53
- }
54
- })
55
-
56
- // remove any of the initial columns that aren't in the query anymore
57
- Arrays.deleteIf(this.columns, (c) => initialColumns.has(c))
39
+ if (query.columns?.length) this.columns = Array.from(query.columns)
58
40
 
59
41
  // make the list sortable
60
42
  this.makePlugin(SortablePlugin, {
@@ -72,7 +54,7 @@ export default class ColumnOrderModal extends ModalPart<ColumnOrderState> {
72
54
  container.p().text("Drag and drop the columns to change their order:")
73
55
  container.div(".dive-column-sort-zone", zone => {
74
56
  for (const col of this.columns) {
75
- zone.div(".column").data({column: col}).text(col)
57
+ zone.div(".column").data({ column: col }).text(col)
76
58
  }
77
59
  })
78
60
  })
@@ -1,18 +1,18 @@
1
- import {PartTag} from "tuff-core/parts"
2
- import {ColumnDef, ModelDef, SchemaDef} from "../../terrier/schema"
3
- import {TableRef, TableView} from "./tables"
4
- import {Logger} from "tuff-core/logging"
5
- import Forms, {FormFields, SelectOptions} from "tuff-core/forms"
1
+ import { PartTag } from "tuff-core/parts"
2
+ import { ColumnDef, ModelDef, SchemaDef } from "../../terrier/schema"
3
+ import { TableRef, TableView } from "./tables"
4
+ import { Logger } from "tuff-core/logging"
5
+ import Forms, { FormFields, SelectOptions } from "tuff-core/forms"
6
6
  import Objects from "tuff-core/objects"
7
- import {ModalPart} from "../../terrier/modals"
8
- import {Dropdown} from "../../terrier/dropdowns"
7
+ import { ModalPart } from "../../terrier/modals"
8
+ import { Dropdown } from "../../terrier/dropdowns"
9
9
  import DiveEditor from "../dives/dive-editor"
10
10
  import Messages from "tuff-core/messages"
11
11
  import Arrays from "tuff-core/arrays"
12
12
  import Dom from "tuff-core/dom"
13
- import Validation, {ColumnValidationError} from "./validation"
14
- import Queries, {Query} from "./queries"
15
- import {TerrierFormFields} from "../../terrier/forms"
13
+ import Validation, { ColumnValidationError } from "./validation"
14
+ import Queries, { Query } from "./queries"
15
+ import { TerrierFormFields } from "../../terrier/forms"
16
16
  import TerrierPart from "../../terrier/parts/terrier-part"
17
17
 
18
18
  const log = new Logger("Columns")
@@ -115,7 +115,7 @@ export type ColumnsEditorState = {
115
115
  const saveKey = Messages.untypedKey()
116
116
  const addKey = Messages.untypedKey()
117
117
  const addSingleKey = Messages.typedKey<{ name: string }>()
118
- const removeKey = Messages.typedKey<{id: string}>()
118
+ const removeKey = Messages.typedKey<{ id: string }>()
119
119
  const valueChangedKey = Messages.untypedKey()
120
120
 
121
121
  /**
@@ -131,11 +131,11 @@ export class ColumnsEditorModal extends ModalPart<ColumnsEditorState> {
131
131
  tableFields!: FormFields<TableRef>
132
132
 
133
133
 
134
- async init () {
134
+ async init() {
135
135
  this.table = this.state.tableView.table
136
136
  this.modelDef = this.state.tableView.modelDef
137
137
 
138
- this.tableFields = new FormFields(this, {...this.table})
138
+ this.tableFields = new FormFields(this, { ...this.table })
139
139
 
140
140
  // initialize the columns states
141
141
  const columns: ColumnRef[] = this.table.columns || []
@@ -149,13 +149,13 @@ export class ColumnsEditorModal extends ModalPart<ColumnsEditorState> {
149
149
  this.addAction({
150
150
  title: 'Apply',
151
151
  icon: 'glyp-checkmark',
152
- click: {key: saveKey}
152
+ click: { key: saveKey }
153
153
  }, 'primary')
154
154
 
155
155
  this.addAction({
156
156
  title: 'Add Columns',
157
157
  icon: 'glyp-plus',
158
- click: {key: addKey}
158
+ click: { key: addKey }
159
159
  }, 'secondary')
160
160
 
161
161
  this.onClick(saveKey, _ => {
@@ -171,7 +171,7 @@ export class ColumnsEditorModal extends ModalPart<ColumnsEditorState> {
171
171
  })
172
172
 
173
173
  this.onClick(addKey, m => {
174
- this.toggleDropdown(SelectColumnsDropdown, {editor: this as ColumnsEditorModal}, m.event.target)
174
+ this.toggleDropdown(SelectColumnsDropdown, { editor: this as ColumnsEditorModal }, m.event.target)
175
175
  })
176
176
 
177
177
  this.onChange(valueChangedKey, m => {
@@ -189,7 +189,7 @@ export class ColumnsEditorModal extends ModalPart<ColumnsEditorState> {
189
189
 
190
190
  addEditor(col: ColumnRef) {
191
191
  this.columnCount += 1
192
- const state = {schema: this.state.schema, columnsEditor: this, id: `column-${this.columnCount}`, column: col}
192
+ const state = { schema: this.state.schema, columnsEditor: this, id: `column-${this.columnCount}`, column: col }
193
193
  this.columnEditors[state.id] = this.makePart(ColumnEditor, state)
194
194
  }
195
195
 
@@ -227,10 +227,10 @@ export class ColumnsEditorModal extends ModalPart<ColumnsEditorState> {
227
227
  // the table of column editors
228
228
  parent.div('.dd-columns-editor-table', table => {
229
229
  table.div('.dd-editor-header', header => {
230
- header.div('.name').label({text: "Name"})
231
- header.div('.alias').label({text: "Alias"})
232
- header.div('.function').label({text: "Function"})
233
- header.div('.group-by').label({text: "Group By?"})
230
+ header.div('.name').label({ text: "Name" })
231
+ header.div('.alias').label({ text: "Alias" })
232
+ header.div('.function').label({ text: "Function" })
233
+ header.div('.group-by').label({ text: "Group By?" })
234
234
  })
235
235
  table.div('dd-editor-row-container', container => {
236
236
  for (const id of Object.keys(this.columnEditors)) {
@@ -261,7 +261,7 @@ export class ColumnsEditorModal extends ModalPart<ColumnsEditorState> {
261
261
  tr.td('.description').text(colDef.metadata?.description || '')
262
262
  tr.td().a('.add-column.tt-button.secondary.circle.compact.inline', a => {
263
263
  a.i('.glyp-plus')
264
- }).emitClick(addSingleKey, {name: colDef.name})
264
+ }).emitClick(addSingleKey, { name: colDef.name })
265
265
  })
266
266
  }
267
267
  })
@@ -288,12 +288,12 @@ export class ColumnsEditorModal extends ModalPart<ColumnsEditorState> {
288
288
  // make a deep copy of the query and update this table's columns and settings
289
289
  this.table._id = this.id // we need this to identify the table after the deep copy
290
290
  const query = Objects.deepCopy(this.state.query)
291
- Queries.eachTable(query, table => {
292
- if (table._id == this.id) {
293
- table.columns = columns
294
- table.prefix = tableData.prefix
295
- }
296
- })
291
+ const tables = Queries.tables(query).
292
+ filter(table => table._id == this.id)
293
+ for (const table of tables) {
294
+ table.columns = columns
295
+ table.prefix = tableData.prefix
296
+ }
297
297
 
298
298
  // validate the temporary query
299
299
  log.info(`Validating temporary query with column changes`, query)
@@ -358,10 +358,10 @@ class ColumnEditor extends TerrierPart<ColumnState> {
358
358
 
359
359
  render(parent: PartTag) {
360
360
  parent.div('.name', col => {
361
- col.div('.tt-readonly-field', {text: this.columnRef.name})
361
+ col.div('.tt-readonly-field', { text: this.columnRef.name })
362
362
  })
363
363
  parent.div('.alias', col => {
364
- this.fields.textInput(col, "alias", {placeholder: "Alias"})
364
+ this.fields.textInput(col, "alias", { placeholder: "Alias" })
365
365
  .emitChange(valueChangedKey)
366
366
  })
367
367
  parent.div('.function', col => {
@@ -375,7 +375,7 @@ class ColumnEditor extends TerrierPart<ColumnState> {
375
375
  parent.div('.actions', actions => {
376
376
  actions.a(a => {
377
377
  a.i('.glyp-close')
378
- }).emitClick(removeKey, {id: this.state.id})
378
+ }).emitClick(removeKey, { id: this.state.id })
379
379
  })
380
380
  if (this.columnRef.errors?.length) {
381
381
  for (const error of this.columnRef.errors) {
@@ -387,7 +387,7 @@ class ColumnEditor extends TerrierPart<ColumnState> {
387
387
  async serialize() {
388
388
  return await this.fields.serialize()
389
389
  }
390
-
390
+
391
391
  }
392
392
 
393
393
 
@@ -406,7 +406,7 @@ type SelectableColumn = {
406
406
  /**
407
407
  * Shows a dropdown that allows the user to select one or more columns from the given model.
408
408
  */
409
- class SelectColumnsDropdown extends Dropdown<{editor: ColumnsEditorModal}> {
409
+ class SelectColumnsDropdown extends Dropdown<{ editor: ColumnsEditorModal }> {
410
410
 
411
411
  addAllKey = Messages.untypedKey()
412
412
  addKey = Messages.typedKey<ColumnRef>()
@@ -431,9 +431,11 @@ class SelectColumnsDropdown extends Dropdown<{editor: ColumnsEditorModal}> {
431
431
  const description = colDef.metadata?.description || ''
432
432
  return {
433
433
  def: colDef,
434
- ref: {name: colDef.name},
434
+ ref: { name: colDef.name },
435
435
  included,
436
- sortOrder, description}
436
+ sortOrder,
437
+ description
438
+ }
437
439
  })
438
440
  this.columns = Arrays.sortBy(this.columns, 'sortOrder')
439
441
 
@@ -527,7 +529,7 @@ class SelectColumnsDropdown extends Dropdown<{editor: ColumnsEditorModal}> {
527
529
 
528
530
  parent.a('.primary', a => {
529
531
  a.i('.glyp-check_all')
530
- a.span({text: "Add All"})
532
+ a.span({ text: "Add All" })
531
533
  }).emitClick(this.addAllKey)
532
534
  }
533
535
 
@@ -1,18 +1,18 @@
1
1
  import { Filter } from "./filters"
2
- import {TableRef} from "./tables"
3
- import api, {ApiResponse} from "../../terrier/api"
4
- import {PartTag} from "tuff-core/parts"
5
- import {TableCellTag} from "tuff-core/html"
2
+ import { TableRef } from "./tables"
3
+ import api, { ApiResponse } from "../../terrier/api"
4
+ import { PartTag } from "tuff-core/parts"
5
+ import { TableCellTag } from "tuff-core/html"
6
6
  import dayjs from "dayjs"
7
7
  import QueryEditor from "./query-editor"
8
8
  import TerrierPart from "../../terrier/parts/terrier-part"
9
- import Schema, {ModelDef, SchemaDef} from "../../terrier/schema"
10
- import {Logger} from "tuff-core/logging"
9
+ import Schema, { ModelDef, SchemaDef } from "../../terrier/schema"
10
+ import { Logger } from "tuff-core/logging"
11
11
  import * as inflection from "inflection"
12
12
  import Messages from "tuff-core/messages"
13
13
  import Strings from "tuff-core/strings"
14
14
  import Arrays from "tuff-core/arrays"
15
- import {ColumnRef} from "./columns"
15
+ import { ColumnRef } from "./columns"
16
16
  import Ids from "../../terrier/ids";
17
17
  import Objects from "tuff-core/objects";
18
18
 
@@ -42,74 +42,46 @@ export type Query = {
42
42
  // Utilities
43
43
  ////////////////////////////////////////////////////////////////////////////////
44
44
 
45
- type TableFunction = (table: TableRef) => any
45
+ function* childTables(table: TableRef): Generator<TableRef, void, void> {
46
+ if (!table.joins) return
46
47
 
47
- function eachChildTable(table: TableRef, fn: TableFunction) {
48
- if (!table.joins) {
49
- return
50
- }
51
48
  for (const joinedTable of Object.values(table.joins)) {
52
- fn(joinedTable)
53
- eachChildTable(joinedTable, fn)
49
+ yield joinedTable
50
+ yield* childTables(joinedTable)
54
51
  }
55
52
  }
56
53
 
57
- /**
58
- * Recursively iterates through each table reference in the query and evaluates the function.
59
- * @param query
60
- * @param fn
61
- */
62
- function eachTable(query: Query, fn: TableFunction) {
63
- fn(query.from)
64
- eachChildTable(query.from, fn)
54
+ function* tables(query: Query): Generator<TableRef, void, void> {
55
+ yield query.from
56
+ yield* childTables(query.from)
65
57
  }
66
58
 
67
- export type ColumnFunction = (table: TableRef, col: ColumnRef) => any
59
+ function* tableColumns(table: TableRef): Generator<{ table: TableRef, column: ColumnRef }, void, void> {
60
+ if (table.columns)
61
+ for (const column of table.columns)
62
+ yield { table, column }
68
63
 
69
- function eachColumnForTable(table: TableRef, fn: ColumnFunction) {
70
- if (table.columns) {
71
- for (const col of table.columns) {
72
- fn(table, col)
73
- }
74
- }
75
- if (table.joins) {
76
- for (const joinedTable of Object.values(table.joins)) {
77
- eachColumnForTable(joinedTable, fn)
78
- }
79
- }
64
+ if (table.joins)
65
+ for (const joinedTable of Object.values(table.joins))
66
+ yield* tableColumns(joinedTable)
80
67
  }
81
68
 
82
- /**
83
- * Recursively iterates over all columns in a query.
84
- * @param query
85
- * @param fn a function to evaluate for each column in the query
86
- */
87
- function eachColumn(query: Query, fn: ColumnFunction) {
88
- eachColumnForTable(query.from, fn)
69
+ function columns(query: Query) {
70
+ return tableColumns(query.from)
89
71
  }
90
72
 
91
- export type FilterFunction = (table: TableRef, filter: Filter) => any
73
+ function* tableFilters(table: TableRef): Generator<{ table: TableRef, filter: Filter }, void, void> {
74
+ if (table.filters)
75
+ for (const filter of table.filters)
76
+ yield ({ table, filter })
92
77
 
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
- }
78
+ if (table.joins)
79
+ for (const joinedTable of Object.values(table.joins))
80
+ yield* tableFilters(joinedTable)
104
81
  }
105
82
 
106
- /**
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)
83
+ function filters(query: Query) {
84
+ return tableFilters(query.from)
113
85
  }
114
86
 
115
87
  /**
@@ -122,9 +94,8 @@ function duplicate(query: Query): Query {
122
94
  newQuery.id = Ids.makeUuid()
123
95
 
124
96
  // 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
- })
97
+ filters(query)
98
+ .forEach(({ filter }) => filter.id = Ids.makeUuid())
128
99
 
129
100
  return newQuery
130
101
  }
@@ -151,7 +122,7 @@ export type QueryServerValidation = ApiResponse & {
151
122
  * @param query
152
123
  */
153
124
  async function validate(query: Query): Promise<QueryServerValidation> {
154
- return await api.post<QueryServerValidation>("/data_dive/validate_query.json", {query})
125
+ return await api.post<QueryServerValidation>("/data_dive/validate_query.json", { query })
155
126
  }
156
127
 
157
128
 
@@ -184,7 +155,7 @@ export type QueryResult = ApiResponse & {
184
155
  * @param query
185
156
  */
186
157
  async function preview(query: Query): Promise<QueryResult> {
187
- return await api.post<QueryResult>("/data_dive/preview_query.json", {query})
158
+ return await api.post<QueryResult>("/data_dive/preview_query.json", { query })
188
159
  }
189
160
 
190
161
 
@@ -211,15 +182,15 @@ function renderCell(td: TableCellTag, col: QueryResultColumn, val: any): any {
211
182
  return td.div('.dollars').text(`\$${dollars}`)
212
183
  case 'cents':
213
184
  const cents = parseInt(val)
214
- const d = (cents/100.0).toFixed(2)
185
+ const d = (cents / 100.0).toFixed(2)
215
186
  return td.div('.dollars').text(`\$${d}`)
216
187
  case 'string':
217
188
  if (col.select_name.endsWith('id')) {
218
189
  const id = val.toString()
219
190
  td.a('.id')
220
- .data({tooltip: id})
221
- .text(`...${id.substring(id.length-6)}`)
222
- .emitClick(QueryEditor.copyToClipboardKey, {value: id})
191
+ .data({ tooltip: id })
192
+ .text(`...${id.substring(id.length - 6)}`)
193
+ .emitClick(QueryEditor.copyToClipboardKey, { value: id })
223
194
  }
224
195
  else {
225
196
  td.text(val.toString())
@@ -307,8 +278,8 @@ export class QueryModelPicker extends TerrierPart<QueryModelPickerState> {
307
278
 
308
279
  renderModelOption(parent: PartTag, model: ModelDef) {
309
280
  parent.label('.model-option', label => {
310
- label.input({type: 'radio', name: `new-query-model-${this.id}`, value: model.name})
311
- .emitChange(this.pickedKey, {model: model.name})
281
+ label.input({ type: 'radio', name: `new-query-model-${this.id}`, value: model.name })
282
+ .emitChange(this.pickedKey, { model: model.name })
312
283
  label.div(col => {
313
284
  const name = inflection.pluralize(Strings.titleize(model.name))
314
285
  col.div('.name').text(name)
@@ -364,10 +335,16 @@ const Queries = {
364
335
  eachColumn,
365
336
  eachTable,
366
337
  eachFilter,
338
+ childTables,
339
+ tables,
340
+ tableColumns,
341
+ columns,
342
+ tableFilters,
343
+ filters,
367
344
  duplicate,
368
345
  validate,
369
346
  preview,
370
347
  renderPreview
371
348
  }
372
349
 
373
- export default Queries
350
+ export default Queries
@@ -1,17 +1,18 @@
1
- import {PartTag} from "tuff-core/parts"
2
- import Queries, {Query, QueryResult, QueryServerValidation} from "./queries"
3
- import Tables, {FromTableView} from "./tables"
4
- import {Logger} from "tuff-core/logging"
5
- import QueryForm, {QuerySettings, QuerySettingsColumns} from "./query-form"
6
- import DiveEditor, {DiveEditorState} from "../dives/dive-editor"
1
+ import { PartTag } from "tuff-core/parts"
2
+ import Queries, { Query, QueryResult, QueryServerValidation } from "./queries"
3
+ import Tables, { FromTableView } from "./tables"
4
+ import { Logger } from "tuff-core/logging"
5
+ import QueryForm, { QuerySettings, QuerySettingsColumns } from "./query-form"
6
+ import DiveEditor, { DiveEditorState } from "../dives/dive-editor"
7
7
  import Objects from "tuff-core/objects"
8
8
  import Html from "tuff-core/html"
9
9
  import ContentPart from "../../terrier/parts/content-part"
10
- import {TabContainerPart} from "../../terrier/tabs"
10
+ import { TabContainerPart } from "../../terrier/tabs"
11
11
  import Messages from "tuff-core/messages"
12
- import Validation, {QueryClientValidation} from "./validation"
12
+ import Validation, { QueryClientValidation } from "./validation"
13
13
  import ColumnOrderModal from "./column-order-modal"
14
14
  import RowOrderModal from "./row-order-modal"
15
+ import Columns from "./columns"
15
16
 
16
17
  const log = new Logger("QueryEditor")
17
18
 
@@ -32,7 +33,7 @@ class SettingsPart extends ContentPart<SubEditorState> {
32
33
  form!: QueryForm
33
34
 
34
35
  async init() {
35
- this.form = this.makePart(QueryForm, {query: Objects.slice(this.state.query, ...QuerySettingsColumns)})
36
+ this.form = this.makePart(QueryForm, { query: Objects.slice(this.state.query, ...QuerySettingsColumns) })
36
37
  }
37
38
 
38
39
 
@@ -48,8 +49,8 @@ class SettingsPart extends ContentPart<SubEditorState> {
48
49
  })
49
50
  row.a('.alert.tt-flex', a => {
50
51
  a.i('.glyp-delete')
51
- a.span({text: "Delete"})
52
- }).emitClick(DiveEditor.deleteQueryKey, {id: this.state.query.id})
52
+ a.span({ text: "Delete" })
53
+ }).emitClick(DiveEditor.deleteQueryKey, { id: this.state.query.id })
53
54
  })
54
55
  }
55
56
 
@@ -68,9 +69,11 @@ class SortingPart extends ContentPart<SubEditorState> {
68
69
  async init() {
69
70
  this.onClick(this.sortColumnsKey, _ => {
70
71
  log.info("Sorting columns")
72
+ this.state.query.columns = Array.from(Queries.tableColumns(this.state.query.from)).
73
+ map(({ table, column }) => Columns.computeSelectName(table, column))
71
74
  this.app.showModal(ColumnOrderModal, {
72
75
  query: this.state.query,
73
- onSorted: (newColumns) => {
76
+ onSorted: (newColumns) => {
74
77
  this.state.query.columns = newColumns
75
78
  this.state.editor.dirty()
76
79
  this.emitMessage(DiveEditor.diveChangedKey, {})
@@ -82,7 +85,7 @@ class SortingPart extends ContentPart<SubEditorState> {
82
85
  log.info("Sorting rows")
83
86
  this.app.showModal(RowOrderModal, {
84
87
  query: this.state.query,
85
- onSorted: (newOrderBys) => {
88
+ onSorted: (newOrderBys) => {
86
89
  log.info(`New row sort order`, newOrderBys)
87
90
  this.state.query.order_by = newOrderBys
88
91
  this.state.editor.dirty()
@@ -170,7 +173,7 @@ class SqlPart extends ContentPart<SubEditorState> {
170
173
  })
171
174
  }
172
175
  else {
173
- row.div({text: 'SQL Goes Here'})
176
+ row.div({ text: 'SQL Goes Here' })
174
177
  }
175
178
  })
176
179
  }
@@ -266,12 +269,12 @@ export default class QueryEditor extends ContentPart<QueryEditorState> {
266
269
 
267
270
  log.info("Initializing query editor", query)
268
271
 
269
- this.tabs = this.makePart(TabContainerPart, {side: 'left'})
270
- this.settingsPart = this.tabs.upsertTab({key: 'settings', title: 'Settings', icon: 'glyp-settings'},
271
- SettingsPart, {editor: this, query})
272
+ this.tabs = this.makePart(TabContainerPart, { side: 'left' })
273
+ this.settingsPart = this.tabs.upsertTab({ key: 'settings', title: 'Settings', icon: 'glyp-settings' },
274
+ SettingsPart, { editor: this, query })
272
275
 
273
- this.sortingPart = this.tabs.upsertTab({key: 'sorting', title: 'Sorting', icon: 'glyp-sort'},
274
- SortingPart, {editor: this, query})
276
+ this.sortingPart = this.tabs.upsertTab({ key: 'sorting', title: 'Sorting', icon: 'glyp-sort' },
277
+ SortingPart, { editor: this, query })
275
278
 
276
279
 
277
280
  this.listenMessage(QueryForm.settingsChangedKey, m => {
@@ -279,13 +282,13 @@ export default class QueryEditor extends ContentPart<QueryEditorState> {
279
282
  this.updateSettings(m.data)
280
283
  })
281
284
 
282
- this.sqlPart = this.tabs.upsertTab({key: 'sql', title: 'SQL', icon: 'glyp-code'},
283
- SqlPart, {editor: this, query})
285
+ this.sqlPart = this.tabs.upsertTab({ key: 'sql', title: 'SQL', icon: 'glyp-code' },
286
+ SqlPart, { editor: this, query })
284
287
 
285
- this.previewPart = this.tabs.upsertTab({key: 'preview', title: 'Preview', icon: 'glyp-table', classes: ['no-padding'], click: {key: this.updatePreviewKey}},
286
- PreviewPart, {editor: this, query})
288
+ this.previewPart = this.tabs.upsertTab({ key: 'preview', title: 'Preview', icon: 'glyp-table', classes: ['no-padding'], click: { key: this.updatePreviewKey } },
289
+ PreviewPart, { editor: this, query })
287
290
 
288
- this.tableEditor = this.makePart(FromTableView, {schema: this.state.schema, queryEditor: this, table: this.state.query.from})
291
+ this.tableEditor = this.makePart(FromTableView, { schema: this.state.schema, queryEditor: this, table: this.state.query.from })
289
292
 
290
293
  this.listenMessage(Tables.updatedKey, m => {
291
294
  log.info(`Table ${m.data.model} updated`, m.data)
@@ -300,7 +303,7 @@ export default class QueryEditor extends ContentPart<QueryEditorState> {
300
303
  this.onClick(QueryEditor.copyToClipboardKey, async m => {
301
304
  log.info(`Copy value to clipboard: ${m.data.value}`)
302
305
  await navigator.clipboard.writeText(m.data.value)
303
- this.showToast(`Copied '${m.data.value}' to clipboard`, {color: 'primary'})
306
+ this.showToast(`Copied '${m.data.value}' to clipboard`, { color: 'primary' })
304
307
  })
305
308
 
306
309
  this.validate().then()
@@ -341,4 +344,4 @@ export default class QueryEditor extends ContentPart<QueryEditorState> {
341
344
  }
342
345
 
343
346
  static readonly copyToClipboardKey = Messages.typedKey<{ value: string }>()
344
- }
347
+ }
@@ -1,10 +1,10 @@
1
- import {ModalPart} from "../../terrier/modals"
1
+ import { ModalPart } from "../../terrier/modals"
2
2
  import Columns from "./columns"
3
- import Queries, {OrderBy, Query} from "./queries"
3
+ import Queries, { OrderBy, Query } from "./queries"
4
4
  import Messages from "tuff-core/messages"
5
- import {PartTag} from "tuff-core/parts"
6
- import {optionsForSelect, SelectOption} from "tuff-core/forms"
7
- import {Logger} from "tuff-core/logging"
5
+ import { PartTag } from "tuff-core/parts"
6
+ import { optionsForSelect, SelectOption } from "tuff-core/forms"
7
+ import { Logger } from "tuff-core/logging"
8
8
  import Forms from "../../terrier/forms"
9
9
  import SortablePlugin from "tuff-sortable/sortable-plugin";
10
10
 
@@ -22,7 +22,7 @@ export default class RowOrderModal extends ModalPart<RowOrderState> {
22
22
  changedKey = Messages.untypedKey()
23
23
  orderBys: OrderBy[] = []
24
24
  columnOptions: SelectOption[] = []
25
- removeClauseKey = Messages.typedKey<{index: number}>()
25
+ removeClauseKey = Messages.typedKey<{ index: number }>()
26
26
 
27
27
  async init() {
28
28
  this.setTitle("Row Order")
@@ -31,7 +31,7 @@ export default class RowOrderModal extends ModalPart<RowOrderState> {
31
31
  this.addAction({
32
32
  title: "Apply",
33
33
  icon: "glyp-checkmark",
34
- click: {key: this.submitKey}
34
+ click: { key: this.submitKey }
35
35
  })
36
36
 
37
37
  this.onClick(this.submitKey, _ => {
@@ -42,7 +42,7 @@ export default class RowOrderModal extends ModalPart<RowOrderState> {
42
42
  this.addAction({
43
43
  title: "New Clause",
44
44
  icon: "glyp-plus",
45
- click: {key: this.newClauseKey}
45
+ click: { key: this.newClauseKey }
46
46
  }, 'secondary')
47
47
 
48
48
  this.onClick(this.newClauseKey, _ => {
@@ -56,11 +56,12 @@ export default class RowOrderModal extends ModalPart<RowOrderState> {
56
56
  // collect the column options
57
57
  const query = this.state.query
58
58
  const existingColumns = new Set<string>()
59
- Queries.eachColumn(query, (table, col) => {
60
- const name = Columns.computeSelectName(table, col)
59
+ for (const { table, column } of Queries.columns(query)) {
60
+ const name = Columns.computeSelectName(table, column)
61
61
  existingColumns.add(name)
62
- this.columnOptions.push({title: name, value: name})
63
- })
62
+ this.columnOptions.push({ title: name, value: name })
63
+
64
+ }
64
65
 
65
66
  // initialize the order-bys from the query, if present
66
67
  if (query.order_by?.length) {
@@ -88,7 +89,7 @@ export default class RowOrderModal extends ModalPart<RowOrderState> {
88
89
  }
89
90
 
90
91
  addClause() {
91
- this.orderBys.push({column: this.columnOptions[0]?.value || '', dir: 'asc'})
92
+ this.orderBys.push({ column: this.columnOptions[0]?.value || '', dir: 'asc' })
92
93
  log.info(`Added a line, orderBys is now ${this.orderBys.length} long`, this.orderBys)
93
94
  this.dirty()
94
95
  }
@@ -107,7 +108,7 @@ export default class RowOrderModal extends ModalPart<RowOrderState> {
107
108
  this.element.querySelectorAll<HTMLElement>(".order-by").forEach(line => {
108
109
  const column = line.querySelector<HTMLSelectElement>("select.column")?.value!!
109
110
  const dir = Forms.getRadioValue(line, "input.dir") || "asc"
110
- this.orderBys.push({column, dir})
111
+ this.orderBys.push({ column, dir })
111
112
  })
112
113
  log.info("Serialized", this.orderBys)
113
114
  }
@@ -119,9 +120,9 @@ export default class RowOrderModal extends ModalPart<RowOrderState> {
119
120
  container.div(".dive-row-sort-zone", zone => {
120
121
  let index = 0 // for making unique radio names
121
122
  for (const orderBy of this.orderBys) {
122
- zone.div(".order-by", {data: {index: index.toString()}}, line => {
123
+ zone.div(".order-by", { data: { index: index.toString() } }, line => {
123
124
  line.a(".drag.glyp-navicon")
124
- .data({tooltip: "Re-order this clause"})
125
+ .data({ tooltip: "Re-order this clause" })
125
126
  line.select('.column', colSelect => {
126
127
  optionsForSelect(colSelect, this.columnOptions, orderBy.column)
127
128
  }).emitChange(this.changedKey)
@@ -144,8 +145,8 @@ export default class RowOrderModal extends ModalPart<RowOrderState> {
144
145
  label.span().text("descending")
145
146
  })
146
147
  line.a(".remove.glyp-close")
147
- .data({tooltip: "Remove this clause"})
148
- .emitClick(this.removeClauseKey, {index})
148
+ .data({ tooltip: "Remove this clause" })
149
+ .emitClick(this.removeClauseKey, { index })
149
150
  })
150
151
  index += 1
151
152
  }
@@ -1,18 +1,18 @@
1
- import {PartTag} from "tuff-core/parts"
2
- import Schema, {BelongsToDef, ModelDef, SchemaDef} from "../../terrier/schema"
1
+ import { PartTag } from "tuff-core/parts"
2
+ import Schema, { BelongsToDef, ModelDef, SchemaDef } from "../../terrier/schema"
3
3
  import * as inflection from "inflection"
4
- import Filters, {Filter, FilterInput, FiltersEditorModal} from "./filters"
5
- import Columns, {ColumnRef, ColumnsEditorModal} from "./columns"
6
- import {Logger} from "tuff-core/logging"
4
+ import Filters, { Filter, FilterInput, FiltersEditorModal } from "./filters"
5
+ import Columns, { ColumnRef, ColumnsEditorModal } from "./columns"
6
+ import { Logger } from "tuff-core/logging"
7
7
  import ContentPart from "../../terrier/parts/content-part"
8
- import {ActionsDropdown} from "../../terrier/dropdowns"
9
- import {ModalPart} from "../../terrier/modals"
8
+ import { ActionsDropdown } from "../../terrier/dropdowns"
9
+ import { ModalPart } from "../../terrier/modals"
10
10
  import TerrierFormPart from "../../terrier/parts/terrier-form-part"
11
11
  import DiveEditor from "../dives/dive-editor"
12
12
  import Messages from "tuff-core/messages"
13
13
  import Arrays from "tuff-core/arrays"
14
14
  import QueryEditor from "./query-editor"
15
- import {Query} from "./queries";
15
+ import { Query } from "./queries";
16
16
 
17
17
  const log = new Logger("Tables")
18
18
 
@@ -85,7 +85,7 @@ export class TableView<T extends TableRef> extends ContentPart<{ schema: SchemaD
85
85
  editColumnsKey = Messages.untypedKey()
86
86
  editFiltersKey = Messages.untypedKey()
87
87
  newJoinedKey = Messages.untypedKey()
88
- createJoinedKey = Messages.typedKey<{name: string}>()
88
+ createJoinedKey = Messages.typedKey<{ name: string }>()
89
89
 
90
90
  async init() {
91
91
  this.schema = this.state.schema
@@ -102,12 +102,12 @@ export class TableView<T extends TableRef> extends ContentPart<{ schema: SchemaD
102
102
 
103
103
  this.onClick(this.editColumnsKey, _ => {
104
104
  log.info(`Edit ${this.displayName} Columns`)
105
- this.app.showModal(ColumnsEditorModal, {schema: this.schema, query: this.query, tableView: this as TableView<TableRef>})
105
+ this.app.showModal(ColumnsEditorModal, { schema: this.schema, query: this.query, tableView: this as TableView<TableRef> })
106
106
  })
107
107
 
108
108
  this.onClick(this.editFiltersKey, _ => {
109
109
  log.info(`Edit ${this.displayName} Filters`)
110
- this.app.showModal(FiltersEditorModal, {schema: this.schema, tableView: this as TableView<TableRef>})
110
+ this.app.showModal(FiltersEditorModal, { schema: this.schema, tableView: this as TableView<TableRef> })
111
111
  })
112
112
 
113
113
  // show the new join dropdown
@@ -127,19 +127,19 @@ export class TableView<T extends TableRef> extends ContentPart<{ schema: SchemaD
127
127
  const common = model.metadata?.visibility == 'common' ? '0' : '1'
128
128
  return `${common}${bt.name}`
129
129
  })
130
- .map(bt => {
131
- const model = this.schema.models[bt.model]
132
- const isCommon = model.metadata?.visibility == 'common'
133
- // put a border between the common and uncommon
134
- const classes = showingCommon && !isCommon ? ['border-top'] : []
135
- showingCommon = isCommon
136
- return {
137
- title: Schema.belongsToDisplay(bt),
138
- subtitle: model.metadata?.description,
139
- classes,
140
- click: {key: this.createJoinedKey, data: {name: bt.name}}
141
- }
142
- })
130
+ .map(bt => {
131
+ const model = this.schema.models[bt.model]
132
+ const isCommon = model.metadata?.visibility == 'common'
133
+ // put a border between the common and uncommon
134
+ const classes = showingCommon && !isCommon ? ['border-top'] : []
135
+ showingCommon = isCommon
136
+ return {
137
+ title: Schema.belongsToDisplay(bt),
138
+ subtitle: model.metadata?.description,
139
+ classes,
140
+ click: { key: this.createJoinedKey, data: { name: bt.name } }
141
+ }
142
+ })
143
143
 
144
144
 
145
145
  // don't show the dropdown if there are no more belongs-tos left
@@ -147,7 +147,7 @@ export class TableView<T extends TableRef> extends ContentPart<{ schema: SchemaD
147
147
  this.toggleDropdown(ActionsDropdown, actions, m.event.target)
148
148
  }
149
149
  else {
150
- this.showToast(`No more possible joins for ${this.displayName}`, {color: 'pending'})
150
+ this.showToast(`No more possible joins for ${this.displayName}`, { color: 'pending' })
151
151
  }
152
152
  })
153
153
 
@@ -170,13 +170,13 @@ export class TableView<T extends TableRef> extends ContentPart<{ schema: SchemaD
170
170
  this.dirty()
171
171
  }
172
172
  }
173
- this.app.showModal(JoinedTableEditorModal, {table, belongsTo, callback, parentTable: this.state.table as TableRef})
173
+ this.app.showModal(JoinedTableEditorModal, { table, belongsTo, callback, parentTable: this.state.table as TableRef })
174
174
  }
175
175
  })
176
176
  }
177
177
 
178
178
  addJoinedPart(joinedTable: JoinedTableRef) {
179
- const state = {schema: this.schema, queryEditor: this.state.queryEditor, table: joinedTable}
179
+ const state = { schema: this.schema, queryEditor: this.state.queryEditor, table: joinedTable }
180
180
  const part = this.makePart(JoinedTableView, state)
181
181
  part.parentView = this
182
182
  this.joinParts[joinedTable.belongs_to] = part
@@ -241,8 +241,8 @@ export class TableView<T extends TableRef> extends ContentPart<{ schema: SchemaD
241
241
  panel.a('.dd-hint.joins.arrow-top.glyp-hint', hint => {
242
242
  hint.div('.title').text("Join More Tables")
243
243
  })
244
- .emitClick(this.newJoinedKey)
245
- .data({tooltip: "Include data from other tables that are related to this one"})
244
+ .emitClick(this.newJoinedKey)
245
+ .data({ tooltip: "Include data from other tables that are related to this one" })
246
246
  }
247
247
  })
248
248
 
@@ -252,7 +252,7 @@ export class TableView<T extends TableRef> extends ContentPart<{ schema: SchemaD
252
252
  parent.section(section => {
253
253
  section.div('.title', title => {
254
254
  title.i(".glyp-columns")
255
- title.span({text: "Columns"})
255
+ title.span({ text: "Columns" })
256
256
  if (this.table.prefix?.length) {
257
257
  title.span('.prefix').text(`${this.table.prefix}*`)
258
258
  }
@@ -285,7 +285,7 @@ export class TableView<T extends TableRef> extends ContentPart<{ schema: SchemaD
285
285
  parent.section('.filters', section => {
286
286
  section.div('.title', title => {
287
287
  title.i(".glyp-filter")
288
- title.span({text: "Filters"})
288
+ title.span({ text: "Filters" })
289
289
  })
290
290
  if (this.table.filters?.length) {
291
291
  for (const filter of this.table.filters) {
@@ -439,7 +439,7 @@ class JoinedTableEditorModal extends ModalPart<JoinedTableEditorState> {
439
439
  this.addAction({
440
440
  title: "Apply",
441
441
  icon: 'glyp-checkmark',
442
- click: {key: this.applyKey}
442
+ click: { key: this.applyKey }
443
443
  }, 'primary')
444
444
 
445
445
  this.onClick(this.applyKey, async _ => {
@@ -455,7 +455,7 @@ class JoinedTableEditorModal extends ModalPart<JoinedTableEditorState> {
455
455
  this.addAction({
456
456
  title: "Delete",
457
457
  icon: "glyp-delete",
458
- click: {key: this.deleteKey},
458
+ click: { key: this.deleteKey },
459
459
  classes: ['alert']
460
460
  }, 'secondary')
461
461
 
@@ -483,4 +483,4 @@ const Tables = {
483
483
  computeFilterInputs
484
484
  }
485
485
 
486
- export default Tables
486
+ export default Tables
@@ -1,6 +1,6 @@
1
- import Queries, {Query} from "./queries"
2
- import Columns, {ColumnRef} from "./columns"
3
- import {TableRef} from "./tables"
1
+ import Queries, { Query } from "./queries"
2
+ import Columns, { ColumnRef } from "./columns"
3
+ import { TableRef } from "./tables"
4
4
 
5
5
  export type ColumnValidationError = {
6
6
  message: string
@@ -24,7 +24,7 @@ function validateQuery(query: Query): QueryClientValidation {
24
24
  }
25
25
 
26
26
  function addColumnError(col: ColumnRef, message: string) {
27
- const error = {message}
27
+ const error = { message }
28
28
  col.errors ||= []
29
29
  col.errors.push(error)
30
30
  validation.columns.push(error)
@@ -35,40 +35,41 @@ function validateQuery(query: Query): QueryClientValidation {
35
35
  const aggCols: ColumnRef[] = [] // keep track of columns with an aggregate function
36
36
  let isGrouped = false
37
37
  const groupedTables: Set<TableRef> = new Set()
38
- Queries.eachColumn(query, (table, col) => {
38
+ for (const { table, column } of Queries.columns(query)) {
39
39
  // clear the errors
40
- col.errors = undefined
40
+ column.errors = undefined
41
41
 
42
42
  // determine if there's an aggregate function
43
- if (col.function?.length && Columns.functionType(col.function) == 'aggregate') {
44
- aggCols.push(col)
43
+ if (column.function?.length && Columns.functionType(column.function) == 'aggregate') {
44
+ aggCols.push(column)
45
45
  }
46
46
 
47
47
  // determine if there's a _group by_ in the query
48
- if (col.grouped) {
48
+ if (column.grouped) {
49
49
  isGrouped = true
50
- if (col.name == 'id') {
50
+ if (column.name == 'id') {
51
51
  // ungrouped columns on this table are okay
52
52
  groupedTables.add(table)
53
53
  }
54
54
  }
55
55
 
56
56
  // each select name should only be used once
57
- const selectName = Columns.computeSelectName(table, col)
57
+ const selectName = Columns.computeSelectName(table, column)
58
58
  if (usedNames.has(selectName)) {
59
- addColumnError(col, `<strong>${selectName}</strong> has already been selected for a different column`)
59
+ addColumnError(column, `<strong>${selectName}</strong> has already been selected for a different column`)
60
60
  }
61
61
  usedNames.add(selectName)
62
- })
62
+ }
63
63
 
64
64
  if (isGrouped) {
65
65
  // if the query is grouped, ensure that all other column refs
66
66
  // are either grouped, have an aggregate function, or are on a grouped table
67
- Queries.eachColumn(query, (table, col) => {
68
- if (!col.grouped && Columns.functionType(col.function) != 'aggregate' && !groupedTables.has(table)) {
69
- addColumnError(col, `<strong>${col.name}</strong> must be grouped or have an aggregate function`)
70
- }
71
- })
67
+ const columns = Queries.columns(query)
68
+ .filter(({ table, column }) =>
69
+ !column.grouped && Columns.functionType(column.function) != 'aggregate' && !groupedTables.has(table))
70
+ for (const { column } of columns) {
71
+ addColumnError(column, `<strong>${column.name}</strong> must be grouped or have an aggregate function`)
72
+ }
72
73
  }
73
74
  else if (aggCols.length) {
74
75
  // if the query isn't grouped, aggregate functions are an error
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "files": [
5
5
  "*"
6
6
  ],
7
- "version": "4.52.6",
7
+ "version": "4.52.7",
8
8
  "repository": {
9
9
  "type": "git",
10
10
  "url": "https://github.com/Terrier-Tech/terrier-engine"
package/terrier/modals.ts CHANGED
@@ -1,6 +1,6 @@
1
- import {Logger} from "tuff-core/logging"
1
+ import { Logger } from "tuff-core/logging"
2
2
  import TerrierPart from "./parts/terrier-part"
3
- import {PartConstructor, PartTag} from "tuff-core/parts"
3
+ import { PartConstructor, PartTag } from "tuff-core/parts"
4
4
  import ContentPart from "./parts/content-part"
5
5
  import Messages from "tuff-core/messages"
6
6
 
@@ -27,13 +27,13 @@ export abstract class ModalPart<TState> extends ContentPart<TState> {
27
27
  this.emitMessage(modalPopKey, {}, { scope: 'bubble' })
28
28
  }
29
29
 
30
- render(parent: PartTag) {
30
+ render(parent: PartTag) {
31
31
  parent.div('.modal-header', header => {
32
32
  if (this._icon) {
33
33
  this.theme.renderIcon(header, this._icon, 'secondary')
34
34
  }
35
- header.h2({text: this._title || 'Call setTitle()'})
36
- this.theme.renderActions(header, this.getActions('tertiary'), {defaultClass: 'secondary'})
35
+ header.h2({ text: this._title || 'Call setTitle()' })
36
+ this.theme.renderActions(header, this.getActions('tertiary'), { defaultClass: 'secondary' })
37
37
  header.a('.close-modal', closeButton => {
38
38
  this.theme.renderCloseIcon(closeButton)
39
39
  }).emitClick(modalPopKey)
@@ -165,7 +165,7 @@ export class ModalStackPart extends TerrierPart<{}> {
165
165
  if (this.displayClass == 'show') {
166
166
  classes.push(this.displayClass)
167
167
  }
168
- parent.div('.tt-modal-stack', {classes}, stack => {
168
+ parent.div('.tt-modal-stack', { classes }, stack => {
169
169
  stack.div('.modal-container', container => {
170
170
  this.eachChild(part => {
171
171
  container.part(part)