terrier-engine 4.42.0 → 4.44.2
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.
- package/data-dive/dives/dive-editor.ts +1 -0
- package/data-dive/dives/dive-runs.ts +8 -3
- package/data-dive/dives/dives.ts +1 -1
- package/data-dive/queries/filters.ts +4 -2
- package/data-dive/queries/queries.ts +34 -2
- package/data-dive/queries/row-order-modal.ts +10 -5
- package/data-dive/queries/tables.ts +4 -7
- package/package.json +1 -1
|
@@ -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
|
-
|
|
195
|
-
|
|
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
|
|
|
@@ -250,7 +255,7 @@ export class DiveRunModal extends ModalPart<{dive: DdDive }> {
|
|
|
250
255
|
}
|
|
251
256
|
|
|
252
257
|
renderFileOutput(parent: DivTag, fileOutput: RunFileOutput) {
|
|
253
|
-
parent.a('.file-output', {href: fileOutput.url}, row => {
|
|
258
|
+
parent.a('.file-output', { href: fileOutput.url, download: fileOutput.name }, row => {
|
|
254
259
|
row.div('.name.glyp-file_spreadsheet.with-icon').text(fileOutput.name)
|
|
255
260
|
row.div('.details.glyp-download.with-icon').text("Click to download")
|
|
256
261
|
})
|
package/data-dive/dives/dives.ts
CHANGED
|
@@ -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
|
-
*
|
|
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
|
-
|
|
58
|
-
|
|
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
|
|
65
|
+
// initialize the order-bys from the query, if present
|
|
62
66
|
if (query.order_by?.length) {
|
|
63
|
-
|
|
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
|
|
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
|
}
|