terrier-engine 4.14.2 → 4.16.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.
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import Schema, {SchemaDef} from "../../terrier/schema"
|
|
2
2
|
import {PartTag} from "tuff-core/parts"
|
|
3
3
|
import Dives from "./dives"
|
|
4
|
-
import {Query, QueryModelPicker} from "../queries/queries"
|
|
4
|
+
import Queries, {Query, QueryModelPicker} from "../queries/queries"
|
|
5
5
|
import QueryEditor from "../queries/query-editor"
|
|
6
6
|
import {Logger} from "tuff-core/logging"
|
|
7
7
|
import QueryForm from "../queries/query-form"
|
|
@@ -17,6 +17,8 @@ import {DiveRunModal} from "./dive-runs"
|
|
|
17
17
|
import Nav from "tuff-core/nav"
|
|
18
18
|
import Messages from "tuff-core/messages"
|
|
19
19
|
import Arrays from "tuff-core/arrays"
|
|
20
|
+
import {FormFields} from "tuff-core/forms";
|
|
21
|
+
import Fragments from "../../terrier/fragments";
|
|
20
22
|
|
|
21
23
|
const log = new Logger("DiveEditor")
|
|
22
24
|
|
|
@@ -36,6 +38,7 @@ export default class DiveEditor extends ContentPart<DiveEditorState> {
|
|
|
36
38
|
tabs!: TabContainerPart
|
|
37
39
|
|
|
38
40
|
newQueryKey = Messages.untypedKey()
|
|
41
|
+
duplicateQueryKey = Messages.untypedKey()
|
|
39
42
|
|
|
40
43
|
static readonly diveChangedKey = Messages.untypedKey()
|
|
41
44
|
|
|
@@ -54,15 +57,22 @@ export default class DiveEditor extends ContentPart<DiveEditorState> {
|
|
|
54
57
|
tooltip: "Each query represents a separate tab in the resulting spreadsheet",
|
|
55
58
|
click: {key: this.newQueryKey}
|
|
56
59
|
})
|
|
60
|
+
this.tabs.addAfterAction({
|
|
61
|
+
icon: 'glyp-copy',
|
|
62
|
+
classes: ['duplicate-query'],
|
|
63
|
+
tooltip: "Duplicate this query",
|
|
64
|
+
click: {key: this.duplicateQueryKey}
|
|
65
|
+
})
|
|
57
66
|
this.tabs.addAfterAction({
|
|
58
67
|
icon: 'glyp-plus_outline',
|
|
59
68
|
classes: ['new-query'],
|
|
69
|
+
tooltip: "Add a new query to this Dive",
|
|
60
70
|
click: {key: this.newQueryKey}
|
|
61
71
|
})
|
|
62
72
|
|
|
63
73
|
this.queries = this.state.dive.query_data?.queries || []
|
|
64
74
|
for (const query of this.queries) {
|
|
65
|
-
this.
|
|
75
|
+
this.addQueryTab(query)
|
|
66
76
|
}
|
|
67
77
|
|
|
68
78
|
this.listenMessage(QueryForm.settingsChangedKey, m => {
|
|
@@ -75,6 +85,26 @@ export default class DiveEditor extends ContentPart<DiveEditorState> {
|
|
|
75
85
|
this.app.showModal(NewQueryModal, {editor: this as DiveEditor, schema: this.state.schema})
|
|
76
86
|
})
|
|
77
87
|
|
|
88
|
+
this.onClick(this.duplicateQueryKey, _ => {
|
|
89
|
+
const id = this.tabs.currentTagKey
|
|
90
|
+
if (id?.length) {
|
|
91
|
+
const query = this.queries.find(q => q.id == id)
|
|
92
|
+
if (query) {
|
|
93
|
+
this.app.showModal(DuplicateQueryModal, {
|
|
94
|
+
editor: this as DiveEditor,
|
|
95
|
+
schema: this.state.schema,
|
|
96
|
+
query
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
this.app.alertToast(`No query with id ${id}`, 'glyp-alert')
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
this.app.alertToast("No query shown", 'glyp-alert')
|
|
105
|
+
}
|
|
106
|
+
})
|
|
107
|
+
|
|
78
108
|
this.onClick(DiveEditor.deleteQueryKey, m => {
|
|
79
109
|
this.app.confirm({title: "Delete Query", icon: 'glyp-delete', body: "Are you sure you want to delete this query?"}, () => {
|
|
80
110
|
this.deleteQuery(m.data.id)
|
|
@@ -88,14 +118,14 @@ export default class DiveEditor extends ContentPart<DiveEditorState> {
|
|
|
88
118
|
*/
|
|
89
119
|
addQuery(query: Query) {
|
|
90
120
|
this.queries.push(query)
|
|
91
|
-
this.
|
|
121
|
+
this.addQueryTab(query)
|
|
92
122
|
}
|
|
93
123
|
|
|
94
124
|
/**
|
|
95
125
|
* Adds a tab for an existing query.
|
|
96
126
|
* @param query
|
|
97
127
|
*/
|
|
98
|
-
private
|
|
128
|
+
private addQueryTab(query: Query) {
|
|
99
129
|
const state = {...this.state, query}
|
|
100
130
|
this.tabs.upsertTab({key: query.id, title: query.name}, QueryEditor, state)
|
|
101
131
|
}
|
|
@@ -126,6 +156,10 @@ export default class DiveEditor extends ContentPart<DiveEditorState> {
|
|
|
126
156
|
}
|
|
127
157
|
|
|
128
158
|
|
|
159
|
+
////////////////////////////////////////////////////////////////////////////////
|
|
160
|
+
// Editor Page
|
|
161
|
+
////////////////////////////////////////////////////////////////////////////////
|
|
162
|
+
|
|
129
163
|
export class DiveEditorPage extends PagePart<{id: string}> {
|
|
130
164
|
|
|
131
165
|
editor!: DiveEditor
|
|
@@ -304,4 +338,56 @@ class NewQueryModal extends ModalPart<NewQueryState> {
|
|
|
304
338
|
this.pop()
|
|
305
339
|
}
|
|
306
340
|
|
|
307
|
-
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
////////////////////////////////////////////////////////////////////////////////
|
|
345
|
+
// Duplicate Query Modal
|
|
346
|
+
////////////////////////////////////////////////////////////////////////////////
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
type DuplicateQueryState = {
|
|
350
|
+
editor: DiveEditor
|
|
351
|
+
schema: SchemaDef
|
|
352
|
+
query: Query
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
class DuplicateQueryModal extends ModalPart<DuplicateQueryState> {
|
|
356
|
+
|
|
357
|
+
dupKey = Messages.untypedKey()
|
|
358
|
+
fields!: FormFields<Query>
|
|
359
|
+
|
|
360
|
+
async init() {
|
|
361
|
+
this.setIcon('glyp-data_dive_query')
|
|
362
|
+
this.setTitle("Duplicate Query")
|
|
363
|
+
|
|
364
|
+
const newQuery = {...this.state.query, name: `${this.state.query.name} Copy`}
|
|
365
|
+
this.fields = new FormFields<Query>(this, newQuery)
|
|
366
|
+
|
|
367
|
+
this.addAction({
|
|
368
|
+
title: "Duplicate",
|
|
369
|
+
icon: 'glyp-checkmark',
|
|
370
|
+
click: {key: this.dupKey}
|
|
371
|
+
})
|
|
372
|
+
|
|
373
|
+
this.onClick(this.dupKey, async _ => {
|
|
374
|
+
await this.save()
|
|
375
|
+
})
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
async save() {
|
|
379
|
+
const newName = this.fields.data.name
|
|
380
|
+
const query = {...Queries.duplicate(this.state.query), name: newName}
|
|
381
|
+
this.state.editor.addQuery(query)
|
|
382
|
+
this.state.editor.tabs.showTab(query.id)
|
|
383
|
+
this.pop()
|
|
384
|
+
this.app.successToast("Duplicated Query", 'glyp-copy')
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
renderContent(parent: PartTag): void {
|
|
388
|
+
parent.div(".tt-flex.column.padded.gap", col => {
|
|
389
|
+
Fragments.simpleHeading(col, this.theme, "Name")
|
|
390
|
+
this.fields.textInput(col, 'name')
|
|
391
|
+
})
|
|
392
|
+
}
|
|
393
|
+
}
|
|
@@ -12,6 +12,8 @@ import Messages from "tuff-core/messages"
|
|
|
12
12
|
import Strings from "tuff-core/strings"
|
|
13
13
|
import Arrays from "tuff-core/arrays"
|
|
14
14
|
import {ColumnRef} from "./columns"
|
|
15
|
+
import Ids from "../../terrier/ids";
|
|
16
|
+
import Objects from "tuff-core/objects";
|
|
15
17
|
|
|
16
18
|
const log = new Logger("Queries")
|
|
17
19
|
|
|
@@ -85,6 +87,17 @@ function eachColumn(query: Query, fn: ColumnFunction) {
|
|
|
85
87
|
eachColumnForTable(query.from, fn)
|
|
86
88
|
}
|
|
87
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Duplicates a query, including all of the nested data structures.
|
|
92
|
+
* @param query the query to duplicate
|
|
93
|
+
* @return a completely new query
|
|
94
|
+
*/
|
|
95
|
+
function duplicate(query: Query): Query {
|
|
96
|
+
const newQuery = Objects.deepCopy(query)
|
|
97
|
+
newQuery.id = Ids.makeUuid()
|
|
98
|
+
return newQuery
|
|
99
|
+
}
|
|
100
|
+
|
|
88
101
|
|
|
89
102
|
////////////////////////////////////////////////////////////////////////////////
|
|
90
103
|
// Server-Side Validation
|
|
@@ -319,6 +332,7 @@ export class QueryModelPicker extends TerrierPart<QueryModelPickerState> {
|
|
|
319
332
|
const Queries = {
|
|
320
333
|
eachColumn,
|
|
321
334
|
eachTable,
|
|
335
|
+
duplicate,
|
|
322
336
|
validate,
|
|
323
337
|
preview,
|
|
324
338
|
renderPreview
|
package/package.json
CHANGED
package/terrier/tabs.ts
CHANGED
|
@@ -116,6 +116,13 @@ export class TabContainerPart extends TerrierPart<TabContainerState> {
|
|
|
116
116
|
this.dirty()
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
+
/**
|
|
120
|
+
* Gets the current tab key.
|
|
121
|
+
*/
|
|
122
|
+
get currentTagKey(): string | undefined {
|
|
123
|
+
return this.state.currentTab || Object.keys(this.tabs)[0]
|
|
124
|
+
}
|
|
125
|
+
|
|
119
126
|
|
|
120
127
|
_beforeActions: Action[] = []
|
|
121
128
|
|