terrier-engine 4.5.3 → 4.6.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.
@@ -16,6 +16,7 @@ import inflection from "inflection"
16
16
  import Dates, {DateLiteral, DatePeriodPickerPart, DatePeriodPickerState, LiteralDateRange} from "../queries/dates"
17
17
  import dayjs from "dayjs"
18
18
  import {ProgressBarPart} from "../../terrier/progress";
19
+ import {LogListPart} from "../../terrier/logging";
19
20
 
20
21
  const log = new Logger("DiveRuns")
21
22
 
@@ -26,6 +27,10 @@ type RunQueryResult = {
26
27
  message?: string
27
28
  }
28
29
 
30
+ type InitRunResult = {
31
+ total_steps: number
32
+ }
33
+
29
34
  const statusIcons: Record<RunQueryResult['status'], IconName> = {
30
35
  pending: 'glyp-pending',
31
36
  success: 'glyp-complete',
@@ -48,6 +53,7 @@ export class DiveRunModal extends ModalPart<{dive: DdDive }> {
48
53
  fileOutput?: RunFileOutput
49
54
  queryResults: Record<string, RunQueryResult> = {}
50
55
  progressBar!: ProgressBarPart
56
+ logList!: LogListPart
51
57
 
52
58
  startKey = messages.untypedKey()
53
59
  pickDateKey = messages.typedKey<{ input_key: string }>()
@@ -58,9 +64,8 @@ export class DiveRunModal extends ModalPart<{dive: DdDive }> {
58
64
 
59
65
  this.schema = await Schema.get()
60
66
 
61
- // progress is # queries +1 for creating and run and +1 for the output
62
- const total = (this.state.dive.query_data?.queries?.length || 0) + 2
63
- this.progressBar = this.makePart(ProgressBarPart, {total})
67
+ this.progressBar = this.makePart(ProgressBarPart, {total: 10})
68
+ this.logList = this.makePart(LogListPart, {})
64
69
 
65
70
  // initialize the inputs
66
71
  this.filters = Dives.computeFilterInputs(this.schema, this.state.dive)
@@ -123,6 +128,7 @@ export class DiveRunModal extends ModalPart<{dive: DdDive }> {
123
128
  const res = await Db().upsert('dd_dive_run', newRun)
124
129
  if (res.status == 'success' && res.record) {
125
130
  this.beginStreaming(res.record)
131
+ this.logList.info(`Created dive run`)
126
132
  } else {
127
133
  this.alertToast(`Error creating dive run: ${res.message}`)
128
134
  }
@@ -133,6 +139,11 @@ export class DiveRunModal extends ModalPart<{dive: DdDive }> {
133
139
  beginStreaming(run: DdDiveRun) {
134
140
  this.run = run
135
141
  Api.stream(`/data_dive/stream_run/${this.run.id}`)
142
+ .on<InitRunResult>('init_run', res => {
143
+ this.progressBar.setTotal(res.total_steps)
144
+ log.info(`Total steps for run: ${res.total_steps}`)
145
+ this.dirty()
146
+ })
136
147
  .on<RunQueryResult>('query_result', res => {
137
148
  this.queryResults[res.id] = res
138
149
  this.progressBar.increment()
@@ -144,6 +155,12 @@ export class DiveRunModal extends ModalPart<{dive: DdDive }> {
144
155
  this.progressBar.complete('success')
145
156
  this.dirty()
146
157
  })
158
+ .onLog(evt => {
159
+ this.progressBar.increment()
160
+ log.log(evt.level, evt.message)
161
+ this.logList.push(evt)
162
+ this.dirty()
163
+ })
147
164
  .onError(evt => {
148
165
  this.error = evt
149
166
  this.stopActionLoading()
@@ -160,6 +177,8 @@ export class DiveRunModal extends ModalPart<{dive: DdDive }> {
160
177
  renderContent(parent: PartTag): void {
161
178
  parent.div('.tt-flex.padded.gap.column', col => {
162
179
  col.part(this.progressBar)
180
+
181
+ // inputs and outputs row
163
182
  col.div('.tt-flex.collapsible.gap.tt-form', row => {
164
183
  // inputs
165
184
  row.div('.tt-flex.column.shrink.dd-dive-run-inputs', col => {
@@ -197,6 +216,9 @@ export class DiveRunModal extends ModalPart<{dive: DdDive }> {
197
216
  }
198
217
  })
199
218
  })
219
+
220
+ // log
221
+ col.part(this.logList)
200
222
  })
201
223
  }
202
224
 
@@ -50,24 +50,19 @@ async function validate(query: Query): Promise<QueryValidation> {
50
50
  }
51
51
 
52
52
 
53
- ////////////////////////////////////////////////////////////////////////////////
54
- // Inputs
55
- ////////////////////////////////////////////////////////////////////////////////
56
-
57
-
58
-
59
53
  ////////////////////////////////////////////////////////////////////////////////
60
54
  // Preview
61
55
  ////////////////////////////////////////////////////////////////////////////////
62
56
 
63
57
  export type QueryResultRow = Record<string, any>
64
58
 
65
- const ColumnTypes = ['string', 'integer', 'float', 'date', 'time', 'dollars'] as const
59
+ const ColumnTypes = ['string', 'integer', 'float', 'date', 'datetime', 'cents', 'dollars'] as const
66
60
 
67
61
  export type ColumnType = typeof ColumnTypes[number]
68
62
 
69
63
  export type QueryResultColumn = {
70
- name: string
64
+ select_name: string
65
+ column_name: string
71
66
  type: ColumnType
72
67
  }
73
68
 
@@ -102,15 +97,19 @@ function renderCell(td: TableCellTag, col: QueryResultColumn, val: any): any {
102
97
  case 'date':
103
98
  const date = dayjs(val.toString())
104
99
  return td.div('.date').text(date.format(DateFormat))
105
- case 'time':
100
+ case 'datetime':
106
101
  const time = dayjs(val.toString())
107
102
  td.div('.date').text(time.format(DateFormat))
108
103
  return td.div('.time').text(time.format(TimeFormat))
109
104
  case 'dollars':
110
105
  const dollars = parseFloat(val)
111
106
  return td.div('.dollars').text(`\$${dollars}`)
107
+ case 'cents':
108
+ const cents = parseInt(val)
109
+ const d = (cents/100.0).toFixed(2)
110
+ return td.div('.dollars').text(`\$${d}`)
112
111
  case 'string':
113
- if (col.name.endsWith('id')) {
112
+ if (col.select_name.endsWith('id')) {
114
113
  const id = val.toString()
115
114
  td.a('.id')
116
115
  .data({tooltip: id})
@@ -134,7 +133,7 @@ function renderTable(parent: PartTag, rows: QueryResultRow[], columns: QueryResu
134
133
  thead.tr(tr => {
135
134
  for (const col of columns) {
136
135
  tr.th(col.type, th => {
137
- th.a().text(col.name)
136
+ th.a().text(col.select_name)
138
137
  })
139
138
  }
140
139
  })
@@ -146,7 +145,7 @@ function renderTable(parent: PartTag, rows: QueryResultRow[], columns: QueryResu
146
145
  tbody.tr(tr => {
147
146
  for (const col of columns) {
148
147
  tr.td(td => {
149
- const val = row[col.name]
148
+ const val = row[col.select_name]
150
149
  if (val) {
151
150
  renderCell(td, col, val)
152
151
  }
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "files": [
5
5
  "*"
6
6
  ],
7
- "version": "4.5.3",
7
+ "version": "4.6.2",
8
8
  "repository": {
9
9
  "type": "git",
10
10
  "url": "https://github.com/Terrier-Tech/terrier-engine"
package/terrier/api.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { Logger } from "tuff-core/logging"
2
2
  import { QueryParams } from "tuff-core/urls"
3
+ import {LogEntry} from "./logging"
3
4
 
4
5
  const log = new Logger('Api')
5
6
  log.level = 'debug'
@@ -113,17 +114,6 @@ async function post<ResponseType>(url: string, body: Record<string, unknown> | F
113
114
  // Event Streams
114
115
  ////////////////////////////////////////////////////////////////////////////////
115
116
 
116
- type LogLevel = 'success' | 'info' | 'warn' | 'debug'
117
-
118
- /**
119
- * Type of log events from a streaming response.
120
- */
121
- export type LogEvent = {
122
- level: LogLevel
123
- prefix?: string
124
- message: string
125
- }
126
-
127
117
  /**
128
118
  * Type of error events from a streaming response.
129
119
  */
@@ -188,8 +178,8 @@ export class Streamer {
188
178
  * Listen specifically for log events.
189
179
  * @param listener
190
180
  */
191
- onLog(listener: (event: LogEvent) => any) {
192
- return this.on<LogEvent>('_log', listener)
181
+ onLog(listener: (event: LogEntry) => any) {
182
+ return this.on<LogEntry>('_log', listener)
193
183
  }
194
184
 
195
185
  /**
@@ -77,7 +77,7 @@ export abstract class Dropdown<TState> extends TerrierPart<TState> {
77
77
  }
78
78
 
79
79
  update(_elem: HTMLElement) {
80
- const content = _elem.querySelector('.tt-dropdown-content')
80
+ const content: HTMLElement | null = _elem.querySelector('.tt-dropdown-content')
81
81
  if (content) {
82
82
  if (this.anchorTarget) {
83
83
  log.info(`Anchoring dropdown`, content, this.anchorTarget)
@@ -0,0 +1,75 @@
1
+ import TerrierPart from "./parts/terrier-part"
2
+ import {NoState, PartTag} from "tuff-core/parts"
3
+
4
+ ////////////////////////////////////////////////////////////////////////////////
5
+ // Types
6
+ ////////////////////////////////////////////////////////////////////////////////
7
+
8
+ export type LogLevel = 'success' | 'info' | 'warn' | 'error' | 'debug'
9
+
10
+ /**
11
+ * Type of log events from a streaming response.
12
+ */
13
+ export type LogEntry = {
14
+ level: LogLevel
15
+ prefix?: string
16
+ message: string
17
+ }
18
+
19
+
20
+ ////////////////////////////////////////////////////////////////////////////////
21
+ // List Part
22
+ ////////////////////////////////////////////////////////////////////////////////
23
+
24
+ export class LogListPart extends TerrierPart<NoState> {
25
+
26
+ entries: LogEntry[] = []
27
+
28
+ async init() {
29
+ }
30
+
31
+ render(parent: PartTag): any {
32
+ parent.div('.tt-log-entries', entries => {
33
+ for (const entry of this.entries) {
34
+ this.renderEntry(entries, entry)
35
+ }
36
+ })
37
+ }
38
+
39
+ /**
40
+ * Pushes an entry to the list and renders it.
41
+ * @todo make this more performant for large lists!
42
+ * @param entry
43
+ */
44
+ push(entry: LogEntry) {
45
+ this.entries.push(entry)
46
+ this.dirty()
47
+ }
48
+
49
+ success(message: string, prefix: string | undefined) {
50
+ this.push({message, prefix, level: "success"})
51
+ }
52
+
53
+ info(message: string, prefix?: string) {
54
+ this.push({message, prefix, level: "info"})
55
+ }
56
+
57
+ warn(message: string, prefix?: string) {
58
+ this.push({message, prefix, level: "warn"})
59
+ }
60
+
61
+ error(message: string, prefix?: string) {
62
+ this.push({message, prefix, level: "error"})
63
+ }
64
+
65
+ debug(message: string, prefix?: string) {
66
+ this.push({message, prefix, level: "debug"})
67
+ }
68
+
69
+ renderEntry(parent: PartTag, entry: LogEntry) {
70
+ parent.div(`.log-entry.${entry.level}`, row => {
71
+ row.div('.message').text(entry.message)
72
+ })
73
+ }
74
+
75
+ }
@@ -164,15 +164,24 @@ type AnchorResult = {
164
164
  valid: boolean
165
165
  }
166
166
 
167
- function clampHorizontal(result: AnchorResult, size: Size, container: Size) {
167
+ function clampAnchorResult(result: AnchorResult, size: Size, container: Size) {
168
168
  if (container.width < size.width) {
169
169
  result.width = container.width
170
170
  }
171
171
  const rightOffset = container.width - (result.width ?? size.width) // don't hang off the right side of the viewport
172
- result.left = Math.max(0, Math.min(result.left, rightOffset)) // don't hang off the left side of the viewport
172
+ result.left = Math.max(0, Math.min(result.left, rightOffset))
173
173
  if (result.top < 0 || result.top+size.height > container.height) {
174
174
  result.valid = false
175
175
  }
176
+
177
+ if (container.height < size.height) {
178
+ result.height = container.height
179
+ }
180
+ const bottomOffset = container.height - (result.height ?? size.height) // don't hang off the bottom of the viewport
181
+ result.top = Math.max(0, Math.min(result.top, bottomOffset))
182
+ if (result.left < 0 || result.left + size.width > container.width) {
183
+ result.valid = false
184
+ }
176
185
  }
177
186
 
178
187
  function anchorBoxBottom(size: Size, anchor: Box, container: Size): AnchorResult {
@@ -181,7 +190,7 @@ function anchorBoxBottom(size: Size, anchor: Box, container: Size): AnchorResult
181
190
  left: anchor.x + anchor.width / 2 - size.width / 2,
182
191
  valid: true
183
192
  }
184
- clampHorizontal(result, size, container)
193
+ clampAnchorResult(result, size, container)
185
194
  return result
186
195
  }
187
196
 
@@ -191,28 +200,17 @@ function anchorBoxTop(size: Size, anchor: Box, container: Size): AnchorResult {
191
200
  left: anchor.x + anchor.width / 2 - size.width / 2,
192
201
  valid: true
193
202
  }
194
- clampHorizontal(result, size, container)
203
+ clampAnchorResult(result, size, container)
195
204
  return result
196
205
  }
197
206
 
198
- function clampVertical(result: AnchorResult, size: Size, container: Size) {
199
- if (container.height < size.height) {
200
- result.height = container.height
201
- }
202
- const bottomOffset = container.height - (result.height ?? size.height) // don't hang off the bottom of the viewport
203
- result.top = Math.max(0, Math.min(result.top, bottomOffset)) // don't hang off the top of the viewport
204
- if (result.left < 0 || result.left + size.width > container.width) {
205
- result.valid = false
206
- }
207
- }
208
-
209
207
  function anchorBoxLeft(size: Size, anchor: Box, container: Size): AnchorResult {
210
208
  const result = {
211
209
  top: anchor.y + anchor.height/2 - size.height / 2,
212
210
  left: anchor.x - size.width,
213
211
  valid: true
214
212
  }
215
- clampVertical(result, size, container)
213
+ clampAnchorResult(result, size, container)
216
214
  return result
217
215
  }
218
216
 
@@ -222,7 +220,7 @@ function anchorBoxRight(size: Size, anchor: Box, container: Size): AnchorResult
222
220
  left: anchor.x + anchor.width,
223
221
  valid: true
224
222
  }
225
- clampVertical(result, size, container)
223
+ clampAnchorResult(result, size, container)
226
224
  return result
227
225
  }
228
226
 
@@ -14,6 +14,16 @@ export class ProgressBarPart extends TerrierPart<ProgressState> {
14
14
  progress = 0
15
15
  color: ColorName = 'primary'
16
16
 
17
+ /**
18
+ * Set the total number of steps.
19
+ * @param total
20
+ */
21
+ setTotal(total: number) {
22
+ this.state.total = total
23
+ this.progress = Math.min(this.progress, this.state.total)
24
+ this.dirty()
25
+ }
26
+
17
27
  /**
18
28
  * Sets the progress and (optionally) color, then dirties the part.
19
29
  * @param progress