terrier-engine 4.63.6 → 4.64.3
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/queries/columns.ts +63 -11
- package/data-dive/queries/dates.ts +41 -14
- package/data-dive/queries/filters.ts +112 -15
- package/data-dive/queries/query-editor.ts +1 -1
- package/data-dive/queries/tables.ts +37 -15
- package/data-dive/queries/validation.ts +14 -2
- package/package.json +1 -1
- package/terrier/glyps.ts +2 -2
|
@@ -57,6 +57,8 @@ function functionType(fun: Function | undefined): 'aggregate' | 'time' | undefin
|
|
|
57
57
|
*/
|
|
58
58
|
export type ColumnRef = {
|
|
59
59
|
name: string
|
|
60
|
+
ref_type?: 'named' | 'raw'
|
|
61
|
+
raw?: string
|
|
60
62
|
alias?: string
|
|
61
63
|
grouped?: boolean
|
|
62
64
|
function?: AggFunction | DateFunction
|
|
@@ -114,6 +116,7 @@ export type ColumnsEditorState = {
|
|
|
114
116
|
|
|
115
117
|
const saveKey = Messages.untypedKey()
|
|
116
118
|
const addKey = Messages.untypedKey()
|
|
119
|
+
const addRawKey = Messages.untypedKey()
|
|
117
120
|
const addSingleKey = Messages.typedKey<{ name: string }>()
|
|
118
121
|
const removeKey = Messages.typedKey<{ id: string }>()
|
|
119
122
|
const valueChangedKey = Messages.untypedKey()
|
|
@@ -158,6 +161,12 @@ export class ColumnsEditorModal extends ModalPart<ColumnsEditorState> {
|
|
|
158
161
|
click: { key: addKey }
|
|
159
162
|
}, 'secondary')
|
|
160
163
|
|
|
164
|
+
this.addAction({
|
|
165
|
+
title: 'Add Raw Select',
|
|
166
|
+
icon: 'glyp-code_details',
|
|
167
|
+
click: { key: addRawKey }
|
|
168
|
+
}, 'secondary')
|
|
169
|
+
|
|
161
170
|
this.onClick(saveKey, _ => {
|
|
162
171
|
this.save()
|
|
163
172
|
})
|
|
@@ -174,6 +183,10 @@ export class ColumnsEditorModal extends ModalPart<ColumnsEditorState> {
|
|
|
174
183
|
this.toggleDropdown(SelectColumnsDropdown, { editor: this as ColumnsEditorModal }, m.event.target)
|
|
175
184
|
})
|
|
176
185
|
|
|
186
|
+
this.onClick(addRawKey, _ => {
|
|
187
|
+
this.addRawColumn()
|
|
188
|
+
})
|
|
189
|
+
|
|
177
190
|
this.onChange(valueChangedKey, m => {
|
|
178
191
|
log.info(`Column value changed`, m)
|
|
179
192
|
this.validate().then()
|
|
@@ -187,6 +200,17 @@ export class ColumnsEditorModal extends ModalPart<ColumnsEditorState> {
|
|
|
187
200
|
this.dirty()
|
|
188
201
|
}
|
|
189
202
|
|
|
203
|
+
addRawColumn() {
|
|
204
|
+
log.info(`Add raw column`)
|
|
205
|
+
const colRef: ColumnRef = {
|
|
206
|
+
name: "",
|
|
207
|
+
ref_type: 'raw'
|
|
208
|
+
}
|
|
209
|
+
this.addEditor(colRef)
|
|
210
|
+
this.validate().then()
|
|
211
|
+
this.dirty()
|
|
212
|
+
}
|
|
213
|
+
|
|
190
214
|
addEditor(col: ColumnRef) {
|
|
191
215
|
this.columnCount += 1
|
|
192
216
|
const state = { schema: this.state.schema, columnsEditor: this, id: `column-${this.columnCount}`, column: col }
|
|
@@ -333,7 +357,7 @@ class ColumnEditor extends TerrierPart<ColumnState> {
|
|
|
333
357
|
schema!: SchemaDef
|
|
334
358
|
modelDef!: ModelDef
|
|
335
359
|
columnRef!: ColumnRef
|
|
336
|
-
columnDef
|
|
360
|
+
columnDef?: ColumnDef
|
|
337
361
|
fields!: TerrierFormFields<ColumnRef>
|
|
338
362
|
|
|
339
363
|
functionOptions!: SelectOptions
|
|
@@ -346,7 +370,7 @@ class ColumnEditor extends TerrierPart<ColumnState> {
|
|
|
346
370
|
this.fields = new TerrierFormFields(this, this.columnRef)
|
|
347
371
|
|
|
348
372
|
let funcs = Array.from<string>(AggFunctions)
|
|
349
|
-
if (this.columnDef.type == 'date' || this.columnDef.type.includes('time')) {
|
|
373
|
+
if (this.columnDef && (this.columnDef.type == 'date' || this.columnDef.type.includes('time'))) {
|
|
350
374
|
funcs = funcs.concat(Array.from(DateFunctions))
|
|
351
375
|
}
|
|
352
376
|
this.functionOptions = Forms.titleizeOptions(funcs, '')
|
|
@@ -357,6 +381,29 @@ class ColumnEditor extends TerrierPart<ColumnState> {
|
|
|
357
381
|
}
|
|
358
382
|
|
|
359
383
|
render(parent: PartTag) {
|
|
384
|
+
if (this.columnDef) {
|
|
385
|
+
this.renderColumnFields(parent)
|
|
386
|
+
}
|
|
387
|
+
else {
|
|
388
|
+
this.renderRawFields(parent)
|
|
389
|
+
}
|
|
390
|
+
parent.div('.actions', actions => {
|
|
391
|
+
actions.a(a => {
|
|
392
|
+
a.i('.glyp-close')
|
|
393
|
+
}).emitClick(removeKey, { id: this.state.id })
|
|
394
|
+
})
|
|
395
|
+
if (this.columnRef.errors?.length) {
|
|
396
|
+
for (const error of this.columnRef.errors) {
|
|
397
|
+
parent.div('.error.tt-bubble.alert').text(error.message)
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Render the fields for an actual column reference.
|
|
404
|
+
* @param parent
|
|
405
|
+
*/
|
|
406
|
+
renderColumnFields(parent: PartTag) {
|
|
360
407
|
parent.div('.name', col => {
|
|
361
408
|
col.div('.tt-readonly-field', { text: this.columnRef.name })
|
|
362
409
|
})
|
|
@@ -372,16 +419,21 @@ class ColumnEditor extends TerrierPart<ColumnState> {
|
|
|
372
419
|
this.fields.checkbox(col, "grouped")
|
|
373
420
|
.emitChange(valueChangedKey)
|
|
374
421
|
})
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Render the fields for a raw select statement.
|
|
426
|
+
* @param parent
|
|
427
|
+
*/
|
|
428
|
+
renderRawFields(parent: PartTag) {
|
|
429
|
+
parent.div(".name", (col) => {
|
|
430
|
+
this.fields.textInput(col, "name", { placeholder: "Select name" })
|
|
431
|
+
.emitChange(valueChangedKey)
|
|
432
|
+
})
|
|
433
|
+
parent.div('.raw', col => {
|
|
434
|
+
this.fields.textArea(col, "raw", { placeholder: "Raw SQL" })
|
|
435
|
+
.emitChange(valueChangedKey)
|
|
379
436
|
})
|
|
380
|
-
if (this.columnRef.errors?.length) {
|
|
381
|
-
for (const error of this.columnRef.errors) {
|
|
382
|
-
parent.div('.error.tt-bubble.alert').text(error.message)
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
437
|
}
|
|
386
438
|
|
|
387
439
|
async serialize() {
|
|
@@ -44,12 +44,23 @@ const virtualPeriods = ['day', 'week', 'month', 'year'] as const
|
|
|
44
44
|
|
|
45
45
|
export type VirtualDatePeriod = typeof virtualPeriods[number]
|
|
46
46
|
|
|
47
|
+
const virtualDirections = ['before', 'inside', 'after'] as const
|
|
48
|
+
|
|
49
|
+
export type VirtualDateDirection = typeof virtualDirections[number]
|
|
50
|
+
|
|
51
|
+
const virtualDirectionDescriptions: Record<VirtualDateDirection, string> = {
|
|
52
|
+
'before': "Filter out dates after the start of the range",
|
|
53
|
+
'inside': "Only include dates inside the range",
|
|
54
|
+
'after': "Filter out dates before the end of the range"
|
|
55
|
+
}
|
|
56
|
+
|
|
47
57
|
/**
|
|
48
58
|
* A date range that's relative to the current date.
|
|
49
59
|
*/
|
|
50
60
|
export type VirtualDateRange = {
|
|
51
61
|
period: VirtualDatePeriod
|
|
52
62
|
relative: number
|
|
63
|
+
direction?: VirtualDateDirection
|
|
53
64
|
}
|
|
54
65
|
|
|
55
66
|
export type DateRange = LiteralDateRange | VirtualDateRange
|
|
@@ -91,31 +102,45 @@ function rangeDisplay(range: DateRange): string {
|
|
|
91
102
|
const max = dayjs(range.max).subtract(1, 'day').format(displayFormat)
|
|
92
103
|
return `${display(range.min)} - ${max}`
|
|
93
104
|
} else if ('period' in range) { // virtual range
|
|
105
|
+
let rel = ''
|
|
94
106
|
// special day names
|
|
95
107
|
if (range.period == 'day') {
|
|
96
108
|
switch (range.relative) {
|
|
97
109
|
case 0:
|
|
98
|
-
|
|
110
|
+
rel = 'Today'
|
|
111
|
+
break
|
|
99
112
|
case -1:
|
|
100
|
-
|
|
113
|
+
rel = "Yesterday"
|
|
114
|
+
break
|
|
101
115
|
case 1:
|
|
102
|
-
|
|
116
|
+
rel = 'Tomorrow'
|
|
117
|
+
break
|
|
103
118
|
}
|
|
104
119
|
}
|
|
105
|
-
if (range.relative == 0) {
|
|
106
|
-
|
|
120
|
+
else if (range.relative == 0) {
|
|
121
|
+
rel = `This ${range.period}`
|
|
122
|
+
}
|
|
123
|
+
else if (range.relative == -1) {
|
|
124
|
+
rel = `Last ${range.period}`
|
|
107
125
|
}
|
|
108
|
-
if (range.relative ==
|
|
109
|
-
|
|
126
|
+
else if (range.relative == 1) {
|
|
127
|
+
rel = `Next ${range.period}`
|
|
110
128
|
}
|
|
111
|
-
|
|
112
|
-
|
|
129
|
+
else {
|
|
130
|
+
const plural = inflection.pluralize(range.period)
|
|
131
|
+
if (range.relative < 0) {
|
|
132
|
+
rel = `${range.relative * -1} ${plural} ago`
|
|
133
|
+
} else {
|
|
134
|
+
rel = `${range.relative} ${plural} from now`
|
|
135
|
+
}
|
|
113
136
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
137
|
+
switch (range.direction) {
|
|
138
|
+
case 'before':
|
|
139
|
+
return `Before ${rel}`
|
|
140
|
+
case 'after':
|
|
141
|
+
return `After ${rel}`
|
|
142
|
+
default:
|
|
143
|
+
return rel
|
|
119
144
|
}
|
|
120
145
|
} else {
|
|
121
146
|
return "Unknown Date Range"
|
|
@@ -279,6 +304,8 @@ const Dates = {
|
|
|
279
304
|
rangeDisplay,
|
|
280
305
|
materializeVirtualRange,
|
|
281
306
|
virtualPeriods,
|
|
307
|
+
virtualDirections,
|
|
308
|
+
virtualDirectionDescriptions,
|
|
282
309
|
parsePeriod,
|
|
283
310
|
serializePeriod
|
|
284
311
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {PartTag} from "tuff-core/parts"
|
|
2
|
-
import Dates, {DateLiteral, VirtualDatePeriod, VirtualDateRange} from "./dates"
|
|
2
|
+
import Dates, { DateLiteral, VirtualDateDirection, VirtualDatePeriod, VirtualDateRange } from "./dates"
|
|
3
3
|
import {ColumnDef, ModelDef, SchemaDef} from "../../terrier/schema"
|
|
4
4
|
import { Query } from "./queries"
|
|
5
5
|
import {TableRef, TableView} from "./tables"
|
|
@@ -25,7 +25,7 @@ const log = new Logger("Filters")
|
|
|
25
25
|
|
|
26
26
|
type BaseFilter = {
|
|
27
27
|
id: string
|
|
28
|
-
filter_type:
|
|
28
|
+
filter_type: 'direct' | 'inclusion' | 'date_range' | 'or' | 'raw'
|
|
29
29
|
column: string
|
|
30
30
|
editable?: 'optional' | 'required'
|
|
31
31
|
edit_label?: string
|
|
@@ -36,7 +36,7 @@ export type DirectOperator = typeof directOperators[number]
|
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
38
|
* Computes the operator options for the given column type.
|
|
39
|
-
* @param
|
|
39
|
+
* @param colDef
|
|
40
40
|
*/
|
|
41
41
|
function operatorOptions(colDef?: ColumnDef): SelectOptions {
|
|
42
42
|
const type = colDef?.type || 'text'
|
|
@@ -73,6 +73,11 @@ export type DirectFilter = BaseFilter & {
|
|
|
73
73
|
column_type?: 'text' | 'number' | 'cents'
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
export type RawFilter = BaseFilter & {
|
|
77
|
+
filter_type: 'raw'
|
|
78
|
+
raw?: string
|
|
79
|
+
}
|
|
80
|
+
|
|
76
81
|
// type DirectColumnType = DirectFilter['column_type']
|
|
77
82
|
|
|
78
83
|
export type DateRangeFilter = BaseFilter & {
|
|
@@ -92,7 +97,7 @@ export type OrFilter = BaseFilter & {
|
|
|
92
97
|
where: Filter[]
|
|
93
98
|
}
|
|
94
99
|
|
|
95
|
-
export type Filter = DirectFilter | DateRangeFilter | InclusionFilter | OrFilter
|
|
100
|
+
export type Filter = DirectFilter | DateRangeFilter | InclusionFilter | OrFilter | RawFilter
|
|
96
101
|
|
|
97
102
|
// export type FilterType = Filter['filter_type']
|
|
98
103
|
|
|
@@ -156,7 +161,7 @@ function renderStatic(parent: PartTag, filter: Filter) {
|
|
|
156
161
|
break
|
|
157
162
|
}
|
|
158
163
|
}
|
|
159
|
-
|
|
164
|
+
return
|
|
160
165
|
case 'date_range':
|
|
161
166
|
parent.div('.column').text(filter.column)
|
|
162
167
|
parent.div('.value').text(Dates.rangeDisplay(filter.range))
|
|
@@ -166,8 +171,8 @@ function renderStatic(parent: PartTag, filter: Filter) {
|
|
|
166
171
|
parent.div('.operator').text("in")
|
|
167
172
|
parent.div('.value').text(filter.in.join(' | '))
|
|
168
173
|
return
|
|
169
|
-
|
|
170
|
-
parent.div('.
|
|
174
|
+
case 'raw':
|
|
175
|
+
parent.div('.raw').text(filter.raw || '??')
|
|
171
176
|
}
|
|
172
177
|
}
|
|
173
178
|
|
|
@@ -183,6 +188,7 @@ export type FiltersEditorState = {
|
|
|
183
188
|
|
|
184
189
|
const saveKey = Messages.untypedKey()
|
|
185
190
|
const addKey = Messages.untypedKey()
|
|
191
|
+
const addRawKey = Messages.untypedKey()
|
|
186
192
|
const removeKey = Messages.typedKey<{ id: string }>()
|
|
187
193
|
|
|
188
194
|
export class FiltersEditorModal extends ModalPart<FiltersEditorState> {
|
|
@@ -232,6 +238,12 @@ export class FiltersEditorModal extends ModalPart<FiltersEditorState> {
|
|
|
232
238
|
click: {key: addKey}
|
|
233
239
|
}, 'secondary')
|
|
234
240
|
|
|
241
|
+
this.addAction({
|
|
242
|
+
title: 'Add Raw Filter',
|
|
243
|
+
icon: 'glyp-code_details',
|
|
244
|
+
click: { key: addRawKey }
|
|
245
|
+
}, 'secondary')
|
|
246
|
+
|
|
235
247
|
this.onClick(saveKey, _ => {
|
|
236
248
|
this.save()
|
|
237
249
|
})
|
|
@@ -243,6 +255,10 @@ export class FiltersEditorModal extends ModalPart<FiltersEditorState> {
|
|
|
243
255
|
this.onClick(addKey, m => {
|
|
244
256
|
this.showAddFilterDropdown(m.event.target! as HTMLElement)
|
|
245
257
|
})
|
|
258
|
+
|
|
259
|
+
this.onClick(addRawKey, _ => {
|
|
260
|
+
this.addRawFilter()
|
|
261
|
+
})
|
|
246
262
|
}
|
|
247
263
|
|
|
248
264
|
renderContent(parent: PartTag) {
|
|
@@ -266,6 +282,16 @@ export class FiltersEditorModal extends ModalPart<FiltersEditorState> {
|
|
|
266
282
|
}
|
|
267
283
|
}
|
|
268
284
|
|
|
285
|
+
addRawFilter() {
|
|
286
|
+
this.addState({
|
|
287
|
+
id: Ids.makeUuid(),
|
|
288
|
+
column: '',
|
|
289
|
+
filter_type: 'raw',
|
|
290
|
+
raw: ''
|
|
291
|
+
})
|
|
292
|
+
this.updateFilterEditors()
|
|
293
|
+
}
|
|
294
|
+
|
|
269
295
|
showAddFilterDropdown(target: HTMLElement | null) {
|
|
270
296
|
const onSelected = (filter: Filter) => {
|
|
271
297
|
log.info(`Adding ${filter.filter_type} filter`, filter)
|
|
@@ -329,6 +355,9 @@ class FilterEditorContainer extends TerrierPart<EditorState> {
|
|
|
329
355
|
case 'date_range':
|
|
330
356
|
this.fields = new DateRangeFilterEditor(this, filter as DateRangeFilter)
|
|
331
357
|
break
|
|
358
|
+
case 'raw':
|
|
359
|
+
this.fields = new RawFilterEditor(this, filter as RawFilter)
|
|
360
|
+
break
|
|
332
361
|
}
|
|
333
362
|
}
|
|
334
363
|
|
|
@@ -398,6 +427,7 @@ class DirectFilterEditor extends FilterFields<DirectFilter> {
|
|
|
398
427
|
|
|
399
428
|
numericChangeKey = Messages.untypedKey()
|
|
400
429
|
operatorChangeKey = Messages.untypedKey()
|
|
430
|
+
textChangeKey = Messages.untypedKey()
|
|
401
431
|
|
|
402
432
|
constructor(container: FilterEditorContainer, filter: DirectFilter) {
|
|
403
433
|
super(container, filter)
|
|
@@ -417,6 +447,11 @@ class DirectFilterEditor extends FilterFields<DirectFilter> {
|
|
|
417
447
|
this.part.dirty()
|
|
418
448
|
})
|
|
419
449
|
|
|
450
|
+
this.part.onChange(this.textChangeKey, m => {
|
|
451
|
+
log.info(`Text changed`, m)
|
|
452
|
+
this.part.dirty()
|
|
453
|
+
})
|
|
454
|
+
|
|
420
455
|
// for numeric types, we use a number input and translate the
|
|
421
456
|
// value back to the string value field whenever it changes
|
|
422
457
|
this.part.onChange(this.numericChangeKey, m => {
|
|
@@ -439,6 +474,9 @@ class DirectFilterEditor extends FilterFields<DirectFilter> {
|
|
|
439
474
|
this.select(col, 'operator', opts)
|
|
440
475
|
.emitChange(this.operatorChangeKey)
|
|
441
476
|
})
|
|
477
|
+
|
|
478
|
+
let error: string = ''
|
|
479
|
+
|
|
442
480
|
parent.div('.filter', col => {
|
|
443
481
|
if (operatorNeedsArgument(this.data.operator)) {
|
|
444
482
|
switch (this.data.column_type) {
|
|
@@ -455,10 +493,17 @@ class DirectFilterEditor extends FilterFields<DirectFilter> {
|
|
|
455
493
|
break
|
|
456
494
|
default:
|
|
457
495
|
this.textInput(col, 'value', { placeholder: "Value" })
|
|
496
|
+
.emitChange(this.textChangeKey)
|
|
497
|
+
if (!this.data.value?.length) {
|
|
498
|
+
error = "Must specify a value"
|
|
499
|
+
}
|
|
458
500
|
}
|
|
459
501
|
}
|
|
460
502
|
})
|
|
461
503
|
this.renderActions(parent)
|
|
504
|
+
if (error.length) {
|
|
505
|
+
parent.div('.error.tt-bubble.alert').text(error)
|
|
506
|
+
}
|
|
462
507
|
}
|
|
463
508
|
|
|
464
509
|
async serialize() {
|
|
@@ -539,6 +584,7 @@ class InclusionFilterEditor extends FilterFields<InclusionFilter> {
|
|
|
539
584
|
|
|
540
585
|
const dateRangeRelativeChangedKey = Messages.untypedKey()
|
|
541
586
|
const dateRangePeriodChangedKey = Messages.typedKey<{period: string}>()
|
|
587
|
+
const dateRangeDirectionChangedKey = Messages.typedKey<{direction: VirtualDateDirection}>()
|
|
542
588
|
const dateRangePreselectKey = Messages.typedKey<VirtualDateRange>()
|
|
543
589
|
|
|
544
590
|
class DateRangeFilterEditor extends FilterFields<DateRangeFilter> {
|
|
@@ -569,6 +615,12 @@ class DateRangeFilterEditor extends FilterFields<DateRangeFilter> {
|
|
|
569
615
|
this.part.dirty()
|
|
570
616
|
})
|
|
571
617
|
|
|
618
|
+
this.part.onChange(dateRangeDirectionChangedKey, m => {
|
|
619
|
+
log.info(`Date range direction ${m.data.direction} changed to ${m.value}`)
|
|
620
|
+
this.range.direction = m.data.direction as VirtualDateDirection
|
|
621
|
+
this.part.dirty()
|
|
622
|
+
})
|
|
623
|
+
|
|
572
624
|
this.part.onClick(dateRangePreselectKey, m => {
|
|
573
625
|
this.range = m.data
|
|
574
626
|
this.data.range = this.range
|
|
@@ -619,9 +671,57 @@ class DateRangeFilterEditor extends FilterFields<DateRangeFilter> {
|
|
|
619
671
|
})
|
|
620
672
|
}
|
|
621
673
|
})
|
|
674
|
+
|
|
675
|
+
// direction
|
|
676
|
+
this.range.direction ||= 'inside'
|
|
677
|
+
cell.div('.tt-flex.gap.wrap.align-center', row => {
|
|
678
|
+
for (const direction of Dates.virtualDirections) {
|
|
679
|
+
row.label('.caption-size', label => {
|
|
680
|
+
label.input({
|
|
681
|
+
type: 'radio',
|
|
682
|
+
name: `${this.id}-direction`,
|
|
683
|
+
value: direction,
|
|
684
|
+
checked: this.range.direction == direction
|
|
685
|
+
})
|
|
686
|
+
.emitChange(dateRangeDirectionChangedKey, { direction })
|
|
687
|
+
label.span().text(inflection.titleize(direction))
|
|
688
|
+
}).data({tooltip: Dates.virtualDirectionDescriptions[direction]})
|
|
689
|
+
}
|
|
690
|
+
})
|
|
691
|
+
})
|
|
692
|
+
|
|
693
|
+
this.renderActions(parent)
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
|
|
699
|
+
////////////////////////////////////////////////////////////////////////////////
|
|
700
|
+
// Raw Filter Editor
|
|
701
|
+
////////////////////////////////////////////////////////////////////////////////
|
|
702
|
+
|
|
703
|
+
class RawFilterEditor extends FilterFields<RawFilter> {
|
|
704
|
+
|
|
705
|
+
valueChangeKey = Messages.untypedKey()
|
|
706
|
+
|
|
707
|
+
constructor(container: FilterEditorContainer, filter: RawFilter) {
|
|
708
|
+
super(container, filter)
|
|
709
|
+
|
|
710
|
+
this.part.onChange(this.valueChangeKey, m => {
|
|
711
|
+
log.info("Raw value changed", m)
|
|
712
|
+
this.part.dirty()
|
|
622
713
|
})
|
|
714
|
+
}
|
|
623
715
|
|
|
716
|
+
render(parent: PartTag): void {
|
|
717
|
+
parent.div('.raw', col => {
|
|
718
|
+
this.textArea(col, 'raw', {placeholder: "Raw SQL"})
|
|
719
|
+
.emitChange(this.valueChangeKey)
|
|
720
|
+
})
|
|
624
721
|
this.renderActions(parent)
|
|
722
|
+
if (!this.data.raw?.length) {
|
|
723
|
+
parent.div('.error.tt-bubble.alert').text("Specify some raw SQL")
|
|
724
|
+
}
|
|
625
725
|
}
|
|
626
726
|
|
|
627
727
|
}
|
|
@@ -663,7 +763,7 @@ class AddFilterDropdown extends Dropdown<{modelDef: ModelDef, callback: AddFilte
|
|
|
663
763
|
return this.state.callback({id, filter_type: 'inclusion', column, in: vals})
|
|
664
764
|
case 'date':
|
|
665
765
|
case 'datetime':
|
|
666
|
-
return this.state.callback({id, filter_type: 'date_range', column, range: {period: 'year', relative: 0}})
|
|
766
|
+
return this.state.callback({id, filter_type: 'date_range', column, range: {period: 'year', relative: 0, direction: 'inside'}})
|
|
667
767
|
default: // direct
|
|
668
768
|
const colType = colDef.type == 'number' || colDef.type == 'cents' ? colDef.type : 'text'
|
|
669
769
|
const defaultValue = colType == 'text' ? '' : '0'
|
|
@@ -722,6 +822,7 @@ export type FilterInput = Filter & {
|
|
|
722
822
|
/**
|
|
723
823
|
* Computes a string used to identify filters that are "the same".
|
|
724
824
|
* @param schema
|
|
825
|
+
* @param query
|
|
725
826
|
* @param table
|
|
726
827
|
* @param filter
|
|
727
828
|
*/
|
|
@@ -776,8 +877,6 @@ function populateRawInputData(filters: FilterInput[]): Record<string,string> {
|
|
|
776
877
|
data[`${filter.id}-${value}`] = 'true'
|
|
777
878
|
}
|
|
778
879
|
break
|
|
779
|
-
default:
|
|
780
|
-
log.warn(`Don't know how to get ${filter.filter_type} raw value`, filter)
|
|
781
880
|
}
|
|
782
881
|
}
|
|
783
882
|
return data
|
|
@@ -799,7 +898,7 @@ function serializeRawInputData(filters: FilterInput[], data: Record<string, stri
|
|
|
799
898
|
}
|
|
800
899
|
const period = Dates.serializePeriod(range)
|
|
801
900
|
filter.input_value = period
|
|
802
|
-
|
|
901
|
+
return
|
|
803
902
|
case 'direct':
|
|
804
903
|
switch (filter.column_type) {
|
|
805
904
|
case 'cents':
|
|
@@ -809,7 +908,7 @@ function serializeRawInputData(filters: FilterInput[], data: Record<string, stri
|
|
|
809
908
|
default:
|
|
810
909
|
filter.input_value = data[filter.id]
|
|
811
910
|
}
|
|
812
|
-
|
|
911
|
+
return
|
|
813
912
|
case 'inclusion':
|
|
814
913
|
const values: string[] = []
|
|
815
914
|
for (const value of filter.possible_values || []) {
|
|
@@ -818,9 +917,7 @@ function serializeRawInputData(filters: FilterInput[], data: Record<string, stri
|
|
|
818
917
|
}
|
|
819
918
|
}
|
|
820
919
|
filter.input_value = values.join(',')
|
|
821
|
-
|
|
822
|
-
default:
|
|
823
|
-
log.warn(`Don't know how to serialize ${filter.filter_type} raw value`, filter)
|
|
920
|
+
return
|
|
824
921
|
}
|
|
825
922
|
}
|
|
826
923
|
}
|
|
@@ -282,7 +282,7 @@ export default class QueryEditor extends ContentPart<QueryEditorState> {
|
|
|
282
282
|
this.updateSettings(m.data)
|
|
283
283
|
})
|
|
284
284
|
|
|
285
|
-
this.sqlPart = this.tabs.upsertTab({ key: 'sql', title: 'SQL', icon: 'glyp-
|
|
285
|
+
this.sqlPart = this.tabs.upsertTab({ key: 'sql', title: 'SQL', icon: 'glyp-code_details' },
|
|
286
286
|
SqlPart, { editor: this, query })
|
|
287
287
|
|
|
288
288
|
this.previewPart = this.tabs.upsertTab({ key: 'preview', title: 'Preview', icon: 'glyp-table', classes: ['no-padding'], click: { key: this.updatePreviewKey } },
|
|
@@ -12,7 +12,7 @@ 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
|
|
|
@@ -36,7 +36,7 @@ export type JoinedTableRef = TableRef & {
|
|
|
36
36
|
|
|
37
37
|
|
|
38
38
|
////////////////////////////////////////////////////////////////////////////////
|
|
39
|
-
//
|
|
39
|
+
// Utilities
|
|
40
40
|
////////////////////////////////////////////////////////////////////////////////
|
|
41
41
|
|
|
42
42
|
const updatedKey = Messages.typedKey<TableRef>()
|
|
@@ -170,7 +170,21 @@ export class TableView<T extends TableRef> extends ContentPart<{ schema: SchemaD
|
|
|
170
170
|
this.dirty()
|
|
171
171
|
}
|
|
172
172
|
}
|
|
173
|
-
|
|
173
|
+
// only show the editor modal if it's an optional association,
|
|
174
|
+
// since the join type doesn't matter for required associations
|
|
175
|
+
log.info(`modelDef, belongsTo`, this.modelDef, belongsTo)
|
|
176
|
+
if (belongsTo.optional) {
|
|
177
|
+
this.app.showModal(JoinedTableEditorModal, {
|
|
178
|
+
table,
|
|
179
|
+
belongsTo,
|
|
180
|
+
callback,
|
|
181
|
+
parentTable: this.state.table as TableRef
|
|
182
|
+
})
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
log.info(`Skipping joined tabled editor for ${belongsTo.name} since it's required`)
|
|
186
|
+
callback(table)
|
|
187
|
+
}
|
|
174
188
|
}
|
|
175
189
|
})
|
|
176
190
|
}
|
|
@@ -389,6 +403,7 @@ type JoinedTableEditorState = {
|
|
|
389
403
|
class JoinedTableEditorForm extends TerrierFormPart<JoinedTableRef> {
|
|
390
404
|
|
|
391
405
|
parentTable!: TableRef
|
|
406
|
+
belongsTo!: BelongsToDef
|
|
392
407
|
|
|
393
408
|
render(parent: PartTag) {
|
|
394
409
|
const name = inflection.titleize(this.state.belongs_to)
|
|
@@ -400,20 +415,26 @@ class JoinedTableEditorForm extends TerrierFormPart<JoinedTableRef> {
|
|
|
400
415
|
h4.div().text("Join Type")
|
|
401
416
|
})
|
|
402
417
|
box.div('.tt-flex.gap.padded', row => {
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
418
|
+
// only allow them to change the join type if the association is optional
|
|
419
|
+
if (this.belongsTo.optional) {
|
|
420
|
+
row.div('.stretch', col => {
|
|
421
|
+
col.label('.body-size', label => {
|
|
422
|
+
label.i('.glyp-join_inner')
|
|
423
|
+
this.radio(label, 'join_type', 'inner')
|
|
424
|
+
label.div().text(`<strong>Inner</strong>: <em>${parentName}</em> is only included if there's an associated <em>${name}</em>`)
|
|
425
|
+
})
|
|
408
426
|
})
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
427
|
+
row.div('.stretch', col => {
|
|
428
|
+
col.label('.body-size', label => {
|
|
429
|
+
label.i('.glyp-join_left')
|
|
430
|
+
this.radio(label, 'join_type', 'left')
|
|
431
|
+
label.div().text(`<strong>Left</strong>: <em>${parentName}</em> is included even if there's no associated <em>${name}</em>`)
|
|
432
|
+
})
|
|
415
433
|
})
|
|
416
|
-
}
|
|
434
|
+
}
|
|
435
|
+
else {
|
|
436
|
+
row.div('.text-center').text("This is a required association, so there's no difference between an inner and left join.")
|
|
437
|
+
}
|
|
417
438
|
})
|
|
418
439
|
})
|
|
419
440
|
}
|
|
@@ -432,6 +453,7 @@ class JoinedTableEditorModal extends ModalPart<JoinedTableEditorState> {
|
|
|
432
453
|
async init() {
|
|
433
454
|
this.form = this.makePart(JoinedTableEditorForm, this.state.table)
|
|
434
455
|
this.form.parentTable = this.state.parentTable
|
|
456
|
+
this.form.belongsTo = this.state.belongsTo
|
|
435
457
|
|
|
436
458
|
this.setIcon('glyp-join')
|
|
437
459
|
this.setTitle(`Join ${this.state.parentTable.model} <i class='glyp-belongs_to'></i> ${Schema.belongsToDisplay(this.state.belongsTo)}`)
|
|
@@ -11,7 +11,7 @@ export type QueryClientValidation = {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
////////////////////////////////////////////////////////////////////////////////
|
|
14
|
-
//
|
|
14
|
+
// Query Validation
|
|
15
15
|
////////////////////////////////////////////////////////////////////////////////
|
|
16
16
|
|
|
17
17
|
/**
|
|
@@ -53,6 +53,16 @@ function validateQuery(query: Query): QueryClientValidation {
|
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
// check raw
|
|
57
|
+
if (column.ref_type == 'raw') {
|
|
58
|
+
if (!column.name?.length) {
|
|
59
|
+
addColumnError(column, "Raw selects must be named")
|
|
60
|
+
}
|
|
61
|
+
if (!column.raw?.length) {
|
|
62
|
+
addColumnError(column, "Missing raw SQL")
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
56
66
|
// each select name should only be used once
|
|
57
67
|
const selectName = Columns.computeSelectName(table, column)
|
|
58
68
|
if (usedNames.has(selectName)) {
|
|
@@ -66,7 +76,9 @@ function validateQuery(query: Query): QueryClientValidation {
|
|
|
66
76
|
// are either grouped, have an aggregate function, or are on a grouped table
|
|
67
77
|
const columns = Queries.columns(query)
|
|
68
78
|
.filter(({ table, column }) =>
|
|
69
|
-
!column.grouped && Columns.functionType(column.function) != 'aggregate' &&
|
|
79
|
+
!column.grouped && Columns.functionType(column.function) != 'aggregate' &&
|
|
80
|
+
!groupedTables.has(table) &&
|
|
81
|
+
'raw' != column.ref_type)
|
|
70
82
|
for (const { column } of columns) {
|
|
71
83
|
addColumnError(column, `<strong>${column.name}</strong> must be grouped or have an aggregate function`)
|
|
72
84
|
}
|
package/package.json
CHANGED
package/terrier/glyps.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
// This file was automatically generated by glyps:compile on 12/
|
|
1
|
+
// This file was automatically generated by glyps:compile on 12/18/25 4:24 PM, DO NOT MANUALLY EDIT!
|
|
2
2
|
|
|
3
3
|
import Strings from "tuff-core/strings"
|
|
4
4
|
|
|
5
|
-
const names = ['glyp-abacus', 'glyp-account', 'glyp-accounts_payable', 'glyp-accounts_receivable', 'glyp-action_log', 'glyp-activate', 'glyp-activate_user', 'glyp-active', 'glyp-activity_high', 'glyp-activity_low', 'glyp-activity_medium', 'glyp-activity_none', 'glyp-address1', 'glyp-adjustment', 'glyp-admin', 'glyp-administration', 'glyp-affinity', 'glyp-agency', 'glyp-air_freshener', 'glyp-alert', 'glyp-align_horizontal', 'glyp-align_vertical', 'glyp-anchor', 'glyp-android_phone', 'glyp-android_tablet', 'glyp-annotation', 'glyp-announcement', 'glyp-anthropic', 'glyp-app_update', 'glyp-append_selection', 'glyp-appointment', 'glyp-appointment_setup', 'glyp-archive', 'glyp-arrow_down', 'glyp-arrow_left', 'glyp-arrow_right', 'glyp-arrow_transfer', 'glyp-arrow_up', 'glyp-ascending', 'glyp-assign', 'glyp-assign_all', 'glyp-attachment', 'glyp-audit', 'glyp-audit_analyzer', 'glyp-audits', 'glyp-autopay', 'glyp-autopay_bank_account', 'glyp-autopay_card', 'glyp-autopay_day', 'glyp-availability', 'glyp-avoca', 'glyp-azuga', 'glyp-background_batch', 'glyp-background_job', 'glyp-background_style', 'glyp-bait_for_demolition', 'glyp-bar_chart', 'glyp-barcode', 'glyp-bat_exclusion', 'glyp-bed_bug_fumigation', 'glyp-begin_service', 'glyp-belongs_to', 'glyp-billing', 'glyp-billing_office', 'glyp-billing_terms', 'glyp-billto', 'glyp-bioremediation', 'glyp-bird_exclusion', 'glyp-black_light', 'glyp-bookmark', 'glyp-branch', 'glyp-branch_bats', 'glyp-branch_birds', 'glyp-branch_general', 'glyp-branch_irrigation', 'glyp-branch_landscape', 'glyp-branch_multi_housing', 'glyp-branch_office', 'glyp-branch_orders', 'glyp-branch_residential', 'glyp-branch_sales', 'glyp-branch_snow', 'glyp-branch_termites', 'glyp-branch_weeds', 'glyp-branch_wildlife', 'glyp-build', 'glyp-business', 'glyp-business_cert', 'glyp-business_cert_type', 'glyp-calculate', 'glyp-calendar', 'glyp-calendar_branch', 'glyp-calendar_map', 'glyp-calendar_rolling', 'glyp-calendar_share', 'glyp-calendar_snap', 'glyp-calendar_style', 'glyp-california', 'glyp-call', 'glyp-callback', 'glyp-camera', 'glyp-canada', 'glyp-cancellation', 'glyp-cancelled', 'glyp-card_american_express', 'glyp-card_discover', 'glyp-card_mastercard', 'glyp-card_visa', 'glyp-catalog', 'glyp-category', 'glyp-caught', 'glyp-cellulose_debris', 'glyp-cert', 'glyp-cert_timeline', 'glyp-cert_type', 'glyp-certificate', 'glyp-certified_summary', 'glyp-certs', 'glyp-ceu', 'glyp-check_all', 'glyp-check_in', 'glyp-check_in_service', 'glyp-checked', 'glyp-checked_check_all', 'glyp-checked_empty', 'glyp-checkmark', 'glyp-checkmark_filled', 'glyp-checkout', 'glyp-chemical', 'glyp-chemical_authorization', 'glyp-chemical_backpack_sprayer', 'glyp-chemical_biological_controls', 'glyp-chemical_disinfectant', 'glyp-chemical_fertilizer', 'glyp-chemical_flying_insect_bait', 'glyp-chemical_fumigants', 'glyp-chemical_insect_aerosol', 'glyp-chemical_insect_bait', 'glyp-chemical_insect_dust', 'glyp-chemical_insect_granules', 'glyp-chemical_insect_spray', 'glyp-chemical_label', 'glyp-chemical_mole_bait', 'glyp-chemical_pest_bird_control', 'glyp-chemical_pheromone_products', 'glyp-chemical_power_sprayer', 'glyp-chemical_rodent_attractant', 'glyp-chemical_rodenticide_block', 'glyp-chemical_rodenticide_non_block', 'glyp-chemical_sds', 'glyp-chemical_tree_products', 'glyp-chemical_weed_control', 'glyp-chemical_wildlife_repellents', 'glyp-chemical_wildlife_toxicants', 'glyp-chevron_down', 'glyp-chevron_left', 'glyp-chevron_right', 'glyp-chevron_up', 'glyp-circle', 'glyp-city', 'glyp-click', 'glyp-client_actions', 'glyp-client_pest_sightings', 'glyp-client_required', 'glyp-clipboard', 'glyp-close', 'glyp-close_empty', 'glyp-cloud', 'glyp-cloudflare_turnstile', 'glyp-clyp_environment', 'glyp-clyp_settings', 'glyp-clypboard', 'glyp-clypedia', 'glyp-clypmart', 'glyp-code', 'glyp-code_details', 'glyp-collections', 'glyp-color', 'glyp-columns', 'glyp-comment', 'glyp-comment_filled', 'glyp-comments', 'glyp-commission_origin', 'glyp-commissions', 'glyp-compact', 'glyp-company_setup', 'glyp-compass', 'glyp-complete', 'glyp-complete_certificate', 'glyp-complete_enrollment', 'glyp-compressed', 'glyp-condition_appliance_machinery', 'glyp-condition_customer', 'glyp-condition_device', 'glyp-condition_door_window', 'glyp-condition_dumpster', 'glyp-condition_entry_point', 'glyp-condition_evidence', 'glyp-condition_exclusion', 'glyp-condition_exterior', 'glyp-condition_interior', 'glyp-condition_plumbing', 'glyp-condition_prep_storage', 'glyp-condition_rodent_evidence', 'glyp-condition_rodent_exclusion', 'glyp-condition_roof_ceiling', 'glyp-condition_structure', 'glyp-condition_supplies', 'glyp-condition_termites', 'glyp-condition_ventilation', 'glyp-condition_wall_floor', 'glyp-condition_wildlife', 'glyp-conditions', 'glyp-consolidate', 'glyp-construction', 'glyp-continuing_ed', 'glyp-contract', 'glyp-contract_cancellation', 'glyp-contract_overview', 'glyp-contractor', 'glyp-conversation', 'glyp-copesan', 'glyp-copy', 'glyp-county', 'glyp-credit_memo', 'glyp-credit_non_revenue', 'glyp-credit_production', 'glyp-credit_prospect', 'glyp-credit_sales_bonus', 'glyp-crew', 'glyp-cumulative', 'glyp-curriculum', 'glyp-cursor', 'glyp-custom_form', 'glyp-customer', 'glyp-customer_service', 'glyp-damage', 'glyp-dashboard', 'glyp-data_dive', 'glyp-data_dive_query', 'glyp-data_dives', 'glyp-database', 'glyp-deactivate_user', 'glyp-dead_animal_removal', 'glyp-default', 'glyp-delete', 'glyp-demo_mode', 'glyp-descending', 'glyp-design', 'glyp-desktop', 'glyp-detail_report', 'glyp-developer', 'glyp-device', 'glyp-device_sync', 'glyp-dialpad', 'glyp-differential', 'glyp-disable', 'glyp-disabled_filled', 'glyp-distribute_horizontal', 'glyp-distribute_vertical', 'glyp-division', 'glyp-document', 'glyp-documentation', 'glyp-documents', 'glyp-download', 'glyp-driving', 'glyp-duplicate', 'glyp-duplicate_location', 'glyp-duration', 'glyp-earth_to_wood', 'glyp-edit', 'glyp-email', 'glyp-employment', 'glyp-entertainment_public_venues', 'glyp-equipment_bear_trap', 'glyp-equipment_flying_insect_bait_station', 'glyp-equipment_flying_insect_light_trap', 'glyp-equipment_insect_monitor', 'glyp-equipment_lethal_animal_trap', 'glyp-equipment_live_animal_trap', 'glyp-equipment_live_rodent_trap', 'glyp-equipment_maintenance', 'glyp-equipment_map_viewer', 'glyp-equipment_maps', 'glyp-equipment_mosquito_trap', 'glyp-equipment_other', 'glyp-equipment_pheromone_trap', 'glyp-equipment_pick_up', 'glyp-equipment_rodent_bait', 'glyp-equipment_rodent_trap', 'glyp-equipment_termite_bait_station', 'glyp-equipment_termite_monitor', 'glyp-escape', 'glyp-ews', 'glyp-exam', 'glyp-exams', 'glyp-exclusion', 'glyp-existing_location', 'glyp-expand', 'glyp-expired', 'glyp-expiring', 'glyp-exterior', 'glyp-extras', 'glyp-facebook', 'glyp-family', 'glyp-farm', 'glyp-farm_grain_seed', 'glyp-fast_and_frequent', 'glyp-favorite', 'glyp-features', 'glyp-feedback', 'glyp-field_guide', 'glyp-field_training', 'glyp-file_blank', 'glyp-file_html', 'glyp-file_image', 'glyp-file_pdf', 'glyp-file_printable', 'glyp-file_spreadsheet', 'glyp-file_text', 'glyp-filter', 'glyp-finance', 'glyp-finding', 'glyp-fogging', 'glyp-folder', 'glyp-followup', 'glyp-food_bev_pharma', 'glyp-formula', 'glyp-fuel', 'glyp-fumigation', 'glyp-function', 'glyp-function_aggregate', 'glyp-function_time', 'glyp-gain_loss', 'glyp-generate_invoices', 'glyp-generate_orders', 'glyp-geo', 'glyp-geo_heat_map', 'glyp-geoboard', 'glyp-google', 'glyp-google_calendar', 'glyp-government', 'glyp-grain_seed', 'glyp-grid', 'glyp-grouped', 'glyp-grouping_combine', 'glyp-grouping_separate', 'glyp-guide', 'glyp-has_many', 'glyp-health_care', 'glyp-heat_map', 'glyp-heat_treatment', 'glyp-help', 'glyp-hidden', 'glyp-hint', 'glyp-home', 'glyp-honeybee_removal', 'glyp-housing', 'glyp-icon', 'glyp-identifier', 'glyp-image', 'glyp-images', 'glyp-import', 'glyp-import_certs', 'glyp-import_data', 'glyp-import_users', 'glyp-in_progress', 'glyp-inbox', 'glyp-incomplete_certificate', 'glyp-industrial', 'glyp-info', 'glyp-information_technology', 'glyp-inspect_map', 'glyp-inspections', 'glyp-insulation', 'glyp-integrations', 'glyp-interior', 'glyp-invoice', 'glyp-invoice_charge', 'glyp-invoice_credit', 'glyp-invoice_paid', 'glyp-invoice_review', 'glyp-ipad', 'glyp-iphone', 'glyp-irrigation_install', 'glyp-irrigation_maintenance', 'glyp-items', 'glyp-job', 'glyp-job_plan', 'glyp-job_plan_history', 'glyp-job_plan_run', 'glyp-job_plan_scheduled', 'glyp-job_plan_single_click', 'glyp-job_plan_step_by_step', 'glyp-join', 'glyp-join_inner', 'glyp-join_left', 'glyp-jump', 'glyp-justice', 'glyp-k9_inspection', 'glyp-key', 'glyp-label_bottom', 'glyp-label_left', 'glyp-label_right', 'glyp-label_top', 'glyp-labor', 'glyp-landscape', 'glyp-landscape_maintenance', 'glyp-laptop', 'glyp-lead', 'glyp-lead_listing', 'glyp-lead_source', 'glyp-leads_report', 'glyp-learning_hub', 'glyp-ledger', 'glyp-legal', 'glyp-license', 'glyp-lifetime', 'glyp-limit', 'glyp-line_cap_arrow', 'glyp-line_cap_bar', 'glyp-line_cap_circle', 'glyp-line_cap_none', 'glyp-line_caps', 'glyp-line_style', 'glyp-link', 'glyp-list', 'glyp-location', 'glyp-location_charge', 'glyp-location_credit', 'glyp-location_document', 'glyp-location_documents', 'glyp-location_import', 'glyp-location_integration', 'glyp-location_kind', 'glyp-location_message', 'glyp-location_origin', 'glyp-location_seed', 'glyp-location_sentiment', 'glyp-location_tags', 'glyp-locations', 'glyp-locked', 'glyp-lodging', 'glyp-log_in', 'glyp-log_out', 'glyp-log_report', 'glyp-logbook', 'glyp-logbook_documents', 'glyp-lot_number', 'glyp-lycensed_simulator', 'glyp-manager', 'glyp-manual', 'glyp-map', 'glyp-markdown', 'glyp-market', 'glyp-materials', 'glyp-mattress_encasement', 'glyp-merge', 'glyp-message_billing', 'glyp-message_collections', 'glyp-message_complaint', 'glyp-message_misc', 'glyp-message_technician', 'glyp-messages', 'glyp-minus_outline', 'glyp-misc', 'glyp-miscellaneous', 'glyp-missing', 'glyp-moisture', 'glyp-moodle', 'glyp-move_order', 'glyp-mowing', 'glyp-multi_housing', 'glyp-multi_tech_order', 'glyp-mute', 'glyp-navicon', 'glyp-neighborhood', 'glyp-new_location', 'glyp-new_ticket', 'glyp-no_charge', 'glyp-no_service', 'glyp-note', 'glyp-note_access', 'glyp-note_billing', 'glyp-note_collection', 'glyp-note_complaint', 'glyp-note_directions', 'glyp-note_focused', 'glyp-note_office', 'glyp-note_preservice', 'glyp-note_sales', 'glyp-note_service', 'glyp-notification', 'glyp-nuke', 'glyp-number', 'glyp-occupant', 'glyp-office', 'glyp-office_government', 'glyp-office_training', 'glyp-offline_mode', 'glyp-on_demand', 'glyp-on_demand_order', 'glyp-online_mode', 'glyp-open', 'glyp-open_invoice', 'glyp-openai', 'glyp-operations', 'glyp-options', 'glyp-order_actions', 'glyp-order_approved', 'glyp-order_batch', 'glyp-order_editor', 'glyp-order_pending', 'glyp-order_series', 'glyp-order_unapproved', 'glyp-org', 'glyp-org_feature', 'glyp-org_settings', 'glyp-org_structure', 'glyp-org_unit', 'glyp-org_unit_settings', 'glyp-orgs', 'glyp-origin', 'glyp-origin_client', 'glyp-origin_tech', 'glyp-outbox', 'glyp-outlier', 'glyp-outlook', 'glyp-overlap', 'glyp-password', 'glyp-past_due', 'glyp-pause', 'glyp-paused', 'glyp-pay_brackets', 'glyp-payment', 'glyp-payment_ach', 'glyp-payment_application_apply', 'glyp-payment_application_correction', 'glyp-payment_application_initial', 'glyp-payment_application_refund', 'glyp-payment_application_transfer', 'glyp-payment_application_unapply', 'glyp-payment_application_unapply_all', 'glyp-payment_applications', 'glyp-payment_batch', 'glyp-payment_cash', 'glyp-payment_check', 'glyp-payment_coupon', 'glyp-payment_credit', 'glyp-payment_declined', 'glyp-payment_discount', 'glyp-payment_eft', 'glyp-payment_finance_charge', 'glyp-payment_method', 'glyp-payments', 'glyp-payroll', 'glyp-pdf', 'glyp-pending', 'glyp-periodic', 'glyp-periodic_assessment', 'glyp-permission', 'glyp-pest', 'glyp-pest_ants', 'glyp-pest_bats', 'glyp-pest_bed_bugs', 'glyp-pest_birds', 'glyp-pest_crawling_insects', 'glyp-pest_dermestids', 'glyp-pest_fall_invaders', 'glyp-pest_flying_insects', 'glyp-pest_moles', 'glyp-pest_mosquitoes', 'glyp-pest_nuisance_animals', 'glyp-pest_occasional_invaders', 'glyp-pest_ornamental', 'glyp-pest_ornamental_diseases', 'glyp-pest_roaches', 'glyp-pest_rodents', 'glyp-pest_scorpions', 'glyp-pest_snakes', 'glyp-pest_spiders', 'glyp-pest_stinging_insects', 'glyp-pest_stored_product_pests', 'glyp-pest_ticks_and_fleas', 'glyp-pest_viruses_and_bacteria', 'glyp-pest_weeds', 'glyp-pest_wood_destroyers', 'glyp-phone', 'glyp-pick_date', 'glyp-pick_list', 'glyp-platform_android', 'glyp-platform_ios', 'glyp-platform_macos', 'glyp-platform_windows', 'glyp-play', 'glyp-plus', 'glyp-plus_filled', 'glyp-plus_outline', 'glyp-portal', 'glyp-price_increase', 'glyp-price_increase_alt', 'glyp-pricing', 'glyp-pricing_table', 'glyp-print', 'glyp-privacy', 'glyp-product_sale', 'glyp-production', 'glyp-professional_consultation', 'glyp-profile', 'glyp-program', 'glyp-program_bundle', 'glyp-program_elements', 'glyp-program_initiation', 'glyp-program_order', 'glyp-program_review', 'glyp-promo', 'glyp-proposal', 'glyp-proposal_bundle', 'glyp-protection', 'glyp-purchase_order', 'glyp-quality', 'glyp-query', 'glyp-radio_checked', 'glyp-radio_empty', 'glyp-reassign', 'glyp-receipt', 'glyp-recent', 'glyp-reciprocity', 'glyp-recommendation', 'glyp-record_details', 'glyp-recurring_revenue', 'glyp-redo', 'glyp-refresh', 'glyp-refund', 'glyp-region', 'glyp-released', 'glyp-remote_camera', 'glyp-remote_photo', 'glyp-remove', 'glyp-renew', 'glyp-renewal', 'glyp-report', 'glyp-required_input', 'glyp-reschedule', 'glyp-restaurant_bar', 'glyp-revenue', 'glyp-review', 'glyp-rfid', 'glyp-ride_along', 'glyp-ring_central', 'glyp-rodent_exclusion', 'glyp-role_admin', 'glyp-role_customer', 'glyp-role_super', 'glyp-role_tech', 'glyp-roster', 'glyp-route_change', 'glyp-route_optimization', 'glyp-routific', 'glyp-rows', 'glyp-ruby', 'glyp-ruler', 'glyp-sales', 'glyp-sales_contest', 'glyp-sales_pipeline', 'glyp-sales_tax', 'glyp-sales_tax_exemption', 'glyp-schedule_lock', 'glyp-schedule_lock_afternoon', 'glyp-schedule_lock_day', 'glyp-schedule_lock_exact', 'glyp-schedule_lock_four_hour', 'glyp-schedule_lock_morning', 'glyp-schedule_lock_none', 'glyp-schedule_lock_two_day', 'glyp-schedule_lock_two_hour', 'glyp-schedule_lock_week', 'glyp-schedule_tyoe_every', 'glyp-schedule_type_every', 'glyp-schedule_type_none', 'glyp-schedule_type_on_demand', 'glyp-schedule_type_schedule', 'glyp-scheduled', 'glyp-scheduling', 'glyp-scheduling_comment', 'glyp-school_church', 'glyp-scope', 'glyp-scoreboard', 'glyp-scrape', 'glyp-script', 'glyp-search', 'glyp-seeding', 'glyp-segment', 'glyp-sellify', 'glyp-send', 'glyp-sent', 'glyp-sentiment', 'glyp-sentricon', 'glyp-service_agreement', 'glyp-service_digest', 'glyp-service_form', 'glyp-service_form_new', 'glyp-service_form_settings', 'glyp-service_form_template', 'glyp-service_miscellaneous', 'glyp-service_reminder', 'glyp-service_report', 'glyp-service_request', 'glyp-services', 'glyp-settings', 'glyp-setup', 'glyp-signature', 'glyp-simulator', 'glyp-site_map', 'glyp-site_map_annotation', 'glyp-site_map_chemical', 'glyp-site_map_edit_details', 'glyp-site_map_edit_geo', 'glyp-site_map_equipment', 'glyp-site_map_exterior', 'glyp-site_map_foundation', 'glyp-site_map_icon', 'glyp-site_map_interior', 'glyp-site_map_label', 'glyp-site_map_perimeter', 'glyp-site_map_settings_left', 'glyp-site_map_settings_right', 'glyp-site_map_template', 'glyp-skill', 'glyp-skip', 'glyp-slope', 'glyp-smart_feature', 'glyp-smart_note', 'glyp-smart_traps', 'glyp-sms', 'glyp-snow_removal', 'glyp-sort', 'glyp-special', 'glyp-split', 'glyp-spp_scorecard', 'glyp-square_edge', 'glyp-start_sheet', 'glyp-start_time', 'glyp-state_ak', 'glyp-state_al', 'glyp-state_ar', 'glyp-state_az', 'glyp-state_ca', 'glyp-state_co', 'glyp-state_ct', 'glyp-state_dc', 'glyp-state_de', 'glyp-state_fl', 'glyp-state_ga', 'glyp-state_hi', 'glyp-state_ia', 'glyp-state_id', 'glyp-state_il', 'glyp-state_in', 'glyp-state_ks', 'glyp-state_ky', 'glyp-state_la', 'glyp-state_ma', 'glyp-state_mb', 'glyp-state_md', 'glyp-state_me', 'glyp-state_mi', 'glyp-state_mn', 'glyp-state_mo', 'glyp-state_ms', 'glyp-state_mt', 'glyp-state_nc', 'glyp-state_nd', 'glyp-state_ne', 'glyp-state_nh', 'glyp-state_nj', 'glyp-state_nm', 'glyp-state_nv', 'glyp-state_ny', 'glyp-state_oh', 'glyp-state_ok', 'glyp-state_or', 'glyp-state_pa', 'glyp-state_ri', 'glyp-state_sc', 'glyp-state_sd', 'glyp-state_tn', 'glyp-state_tx', 'glyp-state_ut', 'glyp-state_va', 'glyp-state_vt', 'glyp-state_wa', 'glyp-state_wi', 'glyp-state_wv', 'glyp-state_wy', 'glyp-statement', 'glyp-states', 'glyp-steps', 'glyp-sticky', 'glyp-stop_time', 'glyp-store_material', 'glyp-store_order', 'glyp-store_order_back_order', 'glyp-store_order_cancelled', 'glyp-store_order_complete', 'glyp-store_order_pending', 'glyp-store_order_pending_return', 'glyp-store_order_pre_order', 'glyp-store_order_returned', 'glyp-stores', 'glyp-styling', 'glyp-subscribe', 'glyp-subscription', 'glyp-supplemental', 'glyp-support', 'glyp-sync', 'glyp-sync_down', 'glyp-sync_up', 'glyp-table', 'glyp-tablet', 'glyp-tablets', 'glyp-tactacam', 'glyp-tag', 'glyp-tag_manager', 'glyp-tags', 'glyp-task_runs', 'glyp-taxability', 'glyp-tech_cert', 'glyp-tech_cert_type', 'glyp-tech_pest_sightings', 'glyp-technician', 'glyp-technician_approach', 'glyp-template', 'glyp-termite_fumigation', 'glyp-terrier', 'glyp-terrier_hub', 'glyp-terrier_sync', 'glyp-territory', 'glyp-text', 'glyp-thermometer', 'glyp-third_party', 'glyp-third_party_billing', 'glyp-threshold', 'glyp-ticket', 'glyp-ticket_type', 'glyp-tickets', 'glyp-tidy', 'glyp-time', 'glyp-time_entry', 'glyp-timeline', 'glyp-timer', 'glyp-toolbox', 'glyp-tqi', 'glyp-tracker', 'glyp-tracking', 'glyp-training_course', 'glyp-treatment_split', 'glyp-trends', 'glyp-trial', 'glyp-trip_charge', 'glyp-ui_type', 'glyp-unarchive', 'glyp-unassign', 'glyp-unassign_all', 'glyp-unchecked', 'glyp-undo', 'glyp-unfavorite', 'glyp-unit_sweep', 'glyp-unit_tag', 'glyp-university', 'glyp-unlocked', 'glyp-unmute', 'glyp-unscheduled', 'glyp-update_check_positions', 'glyp-upload', 'glyp-user', 'glyp-user_credit', 'glyp-user_document', 'glyp-user_documents', 'glyp-user_tags', 'glyp-user_task', 'glyp-user_task_setup', 'glyp-users', 'glyp-utility', 'glyp-vacation', 'glyp-vacuum', 'glyp-variant', 'glyp-vendor', 'glyp-versions', 'glyp-video', 'glyp-visible', 'glyp-warehouse', 'glyp-wdi', 'glyp-wdi_az', 'glyp-wdi_ca', 'glyp-wdi_npma', 'glyp-wdi_nv', 'glyp-wdo', 'glyp-web_dusting', 'glyp-website', 'glyp-wildlife', 'glyp-wildlife_exclusion', 'glyp-winter_check', 'glyp-wizard', 'glyp-work', 'glyp-work_class', 'glyp-work_code', 'glyp-work_order', 'glyp-work_production', 'glyp-work_progress', 'glyp-work_yield', 'glyp-year', 'glyp-yield', 'glyp-zillow', 'glyp-zip_code', 'glyp-zone_adjacent', 'glyp-zone_check', 'glyp-zone_production', 'glyp-zone_remote', 'glyp-zoom', 'glyp-zoom_fit'] as const
|
|
5
|
+
const names = ['glyp-abacus', 'glyp-account', 'glyp-accounts_payable', 'glyp-accounts_receivable', 'glyp-action_log', 'glyp-activate', 'glyp-activate_user', 'glyp-active', 'glyp-activity_high', 'glyp-activity_low', 'glyp-activity_medium', 'glyp-activity_none', 'glyp-address1', 'glyp-adjustment', 'glyp-admin', 'glyp-administration', 'glyp-affinity', 'glyp-agency', 'glyp-air_freshener', 'glyp-alert', 'glyp-align_horizontal', 'glyp-align_vertical', 'glyp-anchor', 'glyp-android_phone', 'glyp-android_tablet', 'glyp-annotation', 'glyp-announcement', 'glyp-anthropic', 'glyp-app_update', 'glyp-append_selection', 'glyp-appointment', 'glyp-appointment_setup', 'glyp-archive', 'glyp-arrow_down', 'glyp-arrow_left', 'glyp-arrow_right', 'glyp-arrow_transfer', 'glyp-arrow_up', 'glyp-ascending', 'glyp-assign', 'glyp-assign_all', 'glyp-attachment', 'glyp-audit', 'glyp-audit_analyzer', 'glyp-audits', 'glyp-autopay', 'glyp-autopay_bank_account', 'glyp-autopay_card', 'glyp-autopay_day', 'glyp-availability', 'glyp-avoca', 'glyp-azuga', 'glyp-background_batch', 'glyp-background_job', 'glyp-background_style', 'glyp-bait_for_demolition', 'glyp-bar_chart', 'glyp-barcode', 'glyp-bat_exclusion', 'glyp-bed_bug_fumigation', 'glyp-begin_service', 'glyp-belongs_to', 'glyp-billing', 'glyp-billing_office', 'glyp-billing_terms', 'glyp-billto', 'glyp-bioremediation', 'glyp-bird_exclusion', 'glyp-black_light', 'glyp-bookmark', 'glyp-branch', 'glyp-branch_bats', 'glyp-branch_birds', 'glyp-branch_general', 'glyp-branch_irrigation', 'glyp-branch_landscape', 'glyp-branch_multi_housing', 'glyp-branch_office', 'glyp-branch_orders', 'glyp-branch_residential', 'glyp-branch_sales', 'glyp-branch_snow', 'glyp-branch_termites', 'glyp-branch_weeds', 'glyp-branch_wildlife', 'glyp-build', 'glyp-business', 'glyp-business_cert', 'glyp-business_cert_type', 'glyp-calculate', 'glyp-calendar', 'glyp-calendar_branch', 'glyp-calendar_map', 'glyp-calendar_rolling', 'glyp-calendar_share', 'glyp-calendar_snap', 'glyp-calendar_style', 'glyp-california', 'glyp-call', 'glyp-callback', 'glyp-camera', 'glyp-canada', 'glyp-cancellation', 'glyp-cancelled', 'glyp-card_american_express', 'glyp-card_discover', 'glyp-card_mastercard', 'glyp-card_visa', 'glyp-catalog', 'glyp-category', 'glyp-caught', 'glyp-cellulose_debris', 'glyp-cert', 'glyp-cert_timeline', 'glyp-cert_type', 'glyp-certificate', 'glyp-certified_summary', 'glyp-certs', 'glyp-ceu', 'glyp-check_all', 'glyp-check_in', 'glyp-check_in_service', 'glyp-checked', 'glyp-checked_check_all', 'glyp-checked_empty', 'glyp-checkmark', 'glyp-checkmark_filled', 'glyp-checkout', 'glyp-chemical', 'glyp-chemical_authorization', 'glyp-chemical_backpack_sprayer', 'glyp-chemical_biological_controls', 'glyp-chemical_disinfectant', 'glyp-chemical_fertilizer', 'glyp-chemical_flying_insect_bait', 'glyp-chemical_fumigants', 'glyp-chemical_insect_aerosol', 'glyp-chemical_insect_bait', 'glyp-chemical_insect_dust', 'glyp-chemical_insect_granules', 'glyp-chemical_insect_spray', 'glyp-chemical_label', 'glyp-chemical_mole_bait', 'glyp-chemical_pest_bird_control', 'glyp-chemical_pheromone_products', 'glyp-chemical_power_sprayer', 'glyp-chemical_rodent_attractant', 'glyp-chemical_rodenticide_block', 'glyp-chemical_rodenticide_non_block', 'glyp-chemical_sds', 'glyp-chemical_tree_products', 'glyp-chemical_weed_control', 'glyp-chemical_wildlife_repellents', 'glyp-chemical_wildlife_toxicants', 'glyp-chevron_down', 'glyp-chevron_left', 'glyp-chevron_right', 'glyp-chevron_up', 'glyp-circle', 'glyp-city', 'glyp-click', 'glyp-client_actions', 'glyp-client_pest_sightings', 'glyp-client_required', 'glyp-clipboard', 'glyp-close', 'glyp-close_empty', 'glyp-cloud', 'glyp-cloudflare_turnstile', 'glyp-clyp_environment', 'glyp-clyp_settings', 'glyp-clypboard', 'glyp-clypedia', 'glyp-clypmart', 'glyp-code', 'glyp-code_details', 'glyp-collections', 'glyp-color', 'glyp-columns', 'glyp-comment', 'glyp-comment_filled', 'glyp-comments', 'glyp-commission_origin', 'glyp-commissions', 'glyp-compact', 'glyp-company', 'glyp-company_setup', 'glyp-compass', 'glyp-complete', 'glyp-complete_certificate', 'glyp-complete_enrollment', 'glyp-compressed', 'glyp-condition_appliance_machinery', 'glyp-condition_customer', 'glyp-condition_device', 'glyp-condition_door_window', 'glyp-condition_dumpster', 'glyp-condition_entry_point', 'glyp-condition_evidence', 'glyp-condition_exclusion', 'glyp-condition_exterior', 'glyp-condition_interior', 'glyp-condition_plumbing', 'glyp-condition_prep_storage', 'glyp-condition_rodent_evidence', 'glyp-condition_rodent_exclusion', 'glyp-condition_roof_ceiling', 'glyp-condition_structure', 'glyp-condition_supplies', 'glyp-condition_termites', 'glyp-condition_ventilation', 'glyp-condition_wall_floor', 'glyp-condition_wildlife', 'glyp-conditions', 'glyp-consolidate', 'glyp-construction', 'glyp-continuing_ed', 'glyp-contract', 'glyp-contract_cancellation', 'glyp-contract_overview', 'glyp-contractor', 'glyp-conversation', 'glyp-copesan', 'glyp-copy', 'glyp-county', 'glyp-credit_memo', 'glyp-credit_non_revenue', 'glyp-credit_production', 'glyp-credit_prospect', 'glyp-credit_sales_bonus', 'glyp-crew', 'glyp-cumulative', 'glyp-curriculum', 'glyp-cursor', 'glyp-custom_form', 'glyp-customer', 'glyp-customer_service', 'glyp-damage', 'glyp-dashboard', 'glyp-data_dive', 'glyp-data_dive_query', 'glyp-data_dives', 'glyp-database', 'glyp-deactivate_user', 'glyp-dead_animal_removal', 'glyp-default', 'glyp-delete', 'glyp-demo_mode', 'glyp-descending', 'glyp-design', 'glyp-desktop', 'glyp-detail_report', 'glyp-developer', 'glyp-device', 'glyp-device_sync', 'glyp-dialpad', 'glyp-differential', 'glyp-disable', 'glyp-disabled_filled', 'glyp-distribute_horizontal', 'glyp-distribute_vertical', 'glyp-division', 'glyp-document', 'glyp-documentation', 'glyp-documents', 'glyp-download', 'glyp-driving', 'glyp-duplicate', 'glyp-duplicate_location', 'glyp-duration', 'glyp-earth_to_wood', 'glyp-edit', 'glyp-email', 'glyp-employment', 'glyp-entertainment_public_venues', 'glyp-equipment_bear_trap', 'glyp-equipment_flying_insect_bait_station', 'glyp-equipment_flying_insect_light_trap', 'glyp-equipment_insect_monitor', 'glyp-equipment_lethal_animal_trap', 'glyp-equipment_live_animal_trap', 'glyp-equipment_live_rodent_trap', 'glyp-equipment_maintenance', 'glyp-equipment_map_viewer', 'glyp-equipment_maps', 'glyp-equipment_mosquito_trap', 'glyp-equipment_other', 'glyp-equipment_pheromone_trap', 'glyp-equipment_pick_up', 'glyp-equipment_rodent_bait', 'glyp-equipment_rodent_trap', 'glyp-equipment_termite_bait_station', 'glyp-equipment_termite_monitor', 'glyp-escape', 'glyp-ews', 'glyp-exam', 'glyp-exams', 'glyp-exclusion', 'glyp-existing_location', 'glyp-expand', 'glyp-expired', 'glyp-expiring', 'glyp-exterior', 'glyp-extras', 'glyp-facebook', 'glyp-family', 'glyp-farm', 'glyp-farm_grain_seed', 'glyp-fast_and_frequent', 'glyp-favorite', 'glyp-features', 'glyp-feedback', 'glyp-field_guide', 'glyp-field_training', 'glyp-file_blank', 'glyp-file_html', 'glyp-file_image', 'glyp-file_pdf', 'glyp-file_printable', 'glyp-file_spreadsheet', 'glyp-file_text', 'glyp-filter', 'glyp-finance', 'glyp-finding', 'glyp-fogging', 'glyp-folder', 'glyp-followup', 'glyp-food_bev_pharma', 'glyp-formula', 'glyp-fuel', 'glyp-fumigation', 'glyp-function', 'glyp-function_aggregate', 'glyp-function_time', 'glyp-gain_loss', 'glyp-generate_invoices', 'glyp-generate_orders', 'glyp-geo', 'glyp-geo_heat_map', 'glyp-geoboard', 'glyp-google', 'glyp-google_calendar', 'glyp-government', 'glyp-grain_seed', 'glyp-grid', 'glyp-grouped', 'glyp-grouping_combine', 'glyp-grouping_separate', 'glyp-guide', 'glyp-has_many', 'glyp-health_care', 'glyp-heat_map', 'glyp-heat_treatment', 'glyp-help', 'glyp-hidden', 'glyp-hint', 'glyp-home', 'glyp-honeybee_removal', 'glyp-housing', 'glyp-icon', 'glyp-identifier', 'glyp-image', 'glyp-images', 'glyp-import', 'glyp-import_certs', 'glyp-import_data', 'glyp-import_users', 'glyp-in_progress', 'glyp-inbox', 'glyp-incomplete_certificate', 'glyp-industrial', 'glyp-info', 'glyp-information_technology', 'glyp-inspect_map', 'glyp-inspections', 'glyp-insulation', 'glyp-integrations', 'glyp-interior', 'glyp-invoice', 'glyp-invoice_charge', 'glyp-invoice_credit', 'glyp-invoice_paid', 'glyp-invoice_review', 'glyp-ipad', 'glyp-iphone', 'glyp-irrigation_install', 'glyp-irrigation_maintenance', 'glyp-items', 'glyp-job', 'glyp-job_plan', 'glyp-job_plan_history', 'glyp-job_plan_run', 'glyp-job_plan_scheduled', 'glyp-job_plan_single_click', 'glyp-job_plan_step_by_step', 'glyp-join', 'glyp-join_inner', 'glyp-join_left', 'glyp-jump', 'glyp-justice', 'glyp-k9_inspection', 'glyp-key', 'glyp-label_bottom', 'glyp-label_left', 'glyp-label_right', 'glyp-label_top', 'glyp-labor', 'glyp-landscape', 'glyp-landscape_maintenance', 'glyp-laptop', 'glyp-lead', 'glyp-lead_listing', 'glyp-lead_source', 'glyp-leads_report', 'glyp-learning_hub', 'glyp-ledger', 'glyp-legal', 'glyp-license', 'glyp-lifetime', 'glyp-limit', 'glyp-line_cap_arrow', 'glyp-line_cap_bar', 'glyp-line_cap_circle', 'glyp-line_cap_none', 'glyp-line_caps', 'glyp-line_style', 'glyp-link', 'glyp-list', 'glyp-location', 'glyp-location_charge', 'glyp-location_credit', 'glyp-location_document', 'glyp-location_documents', 'glyp-location_import', 'glyp-location_integration', 'glyp-location_kind', 'glyp-location_message', 'glyp-location_origin', 'glyp-location_seed', 'glyp-location_sentiment', 'glyp-location_tags', 'glyp-locations', 'glyp-locked', 'glyp-lodging', 'glyp-log_in', 'glyp-log_out', 'glyp-log_report', 'glyp-logbook', 'glyp-logbook_documents', 'glyp-lot_number', 'glyp-lycensed_simulator', 'glyp-manager', 'glyp-manual', 'glyp-map', 'glyp-markdown', 'glyp-market', 'glyp-materials', 'glyp-mattress_encasement', 'glyp-merge', 'glyp-message_billing', 'glyp-message_collections', 'glyp-message_complaint', 'glyp-message_misc', 'glyp-message_technician', 'glyp-messages', 'glyp-minus_outline', 'glyp-misc', 'glyp-miscellaneous', 'glyp-missing', 'glyp-moisture', 'glyp-moodle', 'glyp-move_order', 'glyp-mowing', 'glyp-multi_housing', 'glyp-multi_tech_order', 'glyp-mute', 'glyp-navicon', 'glyp-neighborhood', 'glyp-new_location', 'glyp-new_ticket', 'glyp-no_charge', 'glyp-no_service', 'glyp-note', 'glyp-note_access', 'glyp-note_billing', 'glyp-note_collection', 'glyp-note_complaint', 'glyp-note_directions', 'glyp-note_focused', 'glyp-note_office', 'glyp-note_preservice', 'glyp-note_sales', 'glyp-note_service', 'glyp-notification', 'glyp-nuke', 'glyp-number', 'glyp-occupant', 'glyp-office', 'glyp-office_government', 'glyp-office_training', 'glyp-offline_mode', 'glyp-on_demand', 'glyp-on_demand_order', 'glyp-online_mode', 'glyp-open', 'glyp-open_invoice', 'glyp-openai', 'glyp-operations', 'glyp-options', 'glyp-order_actions', 'glyp-order_approved', 'glyp-order_batch', 'glyp-order_editor', 'glyp-order_pending', 'glyp-order_series', 'glyp-order_unapproved', 'glyp-org', 'glyp-org_feature', 'glyp-org_settings', 'glyp-org_structure', 'glyp-org_unit', 'glyp-org_unit_settings', 'glyp-orgs', 'glyp-origin', 'glyp-origin_client', 'glyp-origin_tech', 'glyp-outbox', 'glyp-outlier', 'glyp-outlook', 'glyp-overlap', 'glyp-password', 'glyp-past_due', 'glyp-pause', 'glyp-paused', 'glyp-pay_brackets', 'glyp-payment', 'glyp-payment_ach', 'glyp-payment_application_apply', 'glyp-payment_application_correction', 'glyp-payment_application_initial', 'glyp-payment_application_refund', 'glyp-payment_application_transfer', 'glyp-payment_application_unapply', 'glyp-payment_application_unapply_all', 'glyp-payment_applications', 'glyp-payment_batch', 'glyp-payment_cash', 'glyp-payment_check', 'glyp-payment_coupon', 'glyp-payment_credit', 'glyp-payment_declined', 'glyp-payment_discount', 'glyp-payment_eft', 'glyp-payment_finance_charge', 'glyp-payment_method', 'glyp-payments', 'glyp-payroll', 'glyp-pdf', 'glyp-pending', 'glyp-periodic', 'glyp-periodic_assessment', 'glyp-permission', 'glyp-pest', 'glyp-pest_ants', 'glyp-pest_bats', 'glyp-pest_bed_bugs', 'glyp-pest_birds', 'glyp-pest_crawling_insects', 'glyp-pest_dermestids', 'glyp-pest_fall_invaders', 'glyp-pest_flying_insects', 'glyp-pest_moles', 'glyp-pest_mosquitoes', 'glyp-pest_nuisance_animals', 'glyp-pest_occasional_invaders', 'glyp-pest_ornamental', 'glyp-pest_ornamental_diseases', 'glyp-pest_roaches', 'glyp-pest_rodents', 'glyp-pest_scorpions', 'glyp-pest_snakes', 'glyp-pest_spiders', 'glyp-pest_stinging_insects', 'glyp-pest_stored_product_pests', 'glyp-pest_ticks_and_fleas', 'glyp-pest_viruses_and_bacteria', 'glyp-pest_weeds', 'glyp-pest_wood_destroyers', 'glyp-phone', 'glyp-pick_date', 'glyp-pick_list', 'glyp-platform_android', 'glyp-platform_ios', 'glyp-platform_macos', 'glyp-platform_windows', 'glyp-play', 'glyp-plus', 'glyp-plus_filled', 'glyp-plus_outline', 'glyp-portal', 'glyp-price_increase', 'glyp-price_increase_alt', 'glyp-pricing', 'glyp-pricing_table', 'glyp-print', 'glyp-privacy', 'glyp-product_sale', 'glyp-production', 'glyp-professional_consultation', 'glyp-profile', 'glyp-program', 'glyp-program_bundle', 'glyp-program_elements', 'glyp-program_initiation', 'glyp-program_order', 'glyp-program_review', 'glyp-promo', 'glyp-proposal', 'glyp-proposal_bundle', 'glyp-protection', 'glyp-purchase_order', 'glyp-quality', 'glyp-query', 'glyp-radio_checked', 'glyp-radio_empty', 'glyp-reassign', 'glyp-receipt', 'glyp-recent', 'glyp-reciprocity', 'glyp-recommendation', 'glyp-record_details', 'glyp-recurring_revenue', 'glyp-redo', 'glyp-refresh', 'glyp-refund', 'glyp-region', 'glyp-released', 'glyp-remote_camera', 'glyp-remote_photo', 'glyp-remove', 'glyp-renew', 'glyp-renewal', 'glyp-report', 'glyp-required_input', 'glyp-reschedule', 'glyp-restaurant_bar', 'glyp-revenue', 'glyp-review', 'glyp-rfid', 'glyp-ride_along', 'glyp-ring_central', 'glyp-rodent_exclusion', 'glyp-role_admin', 'glyp-role_customer', 'glyp-role_super', 'glyp-role_tech', 'glyp-roster', 'glyp-route_change', 'glyp-route_optimization', 'glyp-routific', 'glyp-rows', 'glyp-ruby', 'glyp-ruler', 'glyp-sales', 'glyp-sales_contest', 'glyp-sales_pipeline', 'glyp-sales_tax', 'glyp-sales_tax_exemption', 'glyp-schedule_lock', 'glyp-schedule_lock_afternoon', 'glyp-schedule_lock_day', 'glyp-schedule_lock_exact', 'glyp-schedule_lock_four_hour', 'glyp-schedule_lock_morning', 'glyp-schedule_lock_none', 'glyp-schedule_lock_two_day', 'glyp-schedule_lock_two_hour', 'glyp-schedule_lock_week', 'glyp-schedule_tyoe_every', 'glyp-schedule_type_every', 'glyp-schedule_type_none', 'glyp-schedule_type_on_demand', 'glyp-schedule_type_schedule', 'glyp-scheduled', 'glyp-scheduling', 'glyp-scheduling_comment', 'glyp-school_church', 'glyp-scope', 'glyp-scoreboard', 'glyp-scrape', 'glyp-script', 'glyp-search', 'glyp-seeding', 'glyp-segment', 'glyp-sellify', 'glyp-send', 'glyp-sent', 'glyp-sentiment', 'glyp-sentricon', 'glyp-service_agreement', 'glyp-service_digest', 'glyp-service_form', 'glyp-service_form_new', 'glyp-service_form_settings', 'glyp-service_form_template', 'glyp-service_miscellaneous', 'glyp-service_reminder', 'glyp-service_report', 'glyp-service_request', 'glyp-services', 'glyp-settings', 'glyp-setup', 'glyp-signature', 'glyp-simulator', 'glyp-site_map', 'glyp-site_map_annotation', 'glyp-site_map_chemical', 'glyp-site_map_edit_details', 'glyp-site_map_edit_geo', 'glyp-site_map_equipment', 'glyp-site_map_exterior', 'glyp-site_map_foundation', 'glyp-site_map_icon', 'glyp-site_map_interior', 'glyp-site_map_label', 'glyp-site_map_perimeter', 'glyp-site_map_settings_left', 'glyp-site_map_settings_right', 'glyp-site_map_template', 'glyp-skill', 'glyp-skip', 'glyp-slope', 'glyp-smart_feature', 'glyp-smart_note', 'glyp-smart_traps', 'glyp-sms', 'glyp-snow_removal', 'glyp-sort', 'glyp-special', 'glyp-split', 'glyp-spp_scorecard', 'glyp-square_edge', 'glyp-start_sheet', 'glyp-start_time', 'glyp-state_ak', 'glyp-state_al', 'glyp-state_ar', 'glyp-state_az', 'glyp-state_ca', 'glyp-state_co', 'glyp-state_ct', 'glyp-state_dc', 'glyp-state_de', 'glyp-state_fl', 'glyp-state_ga', 'glyp-state_hi', 'glyp-state_ia', 'glyp-state_id', 'glyp-state_il', 'glyp-state_in', 'glyp-state_ks', 'glyp-state_ky', 'glyp-state_la', 'glyp-state_ma', 'glyp-state_mb', 'glyp-state_md', 'glyp-state_me', 'glyp-state_mi', 'glyp-state_mn', 'glyp-state_mo', 'glyp-state_ms', 'glyp-state_mt', 'glyp-state_nc', 'glyp-state_nd', 'glyp-state_ne', 'glyp-state_nh', 'glyp-state_nj', 'glyp-state_nm', 'glyp-state_nv', 'glyp-state_ny', 'glyp-state_oh', 'glyp-state_ok', 'glyp-state_or', 'glyp-state_pa', 'glyp-state_ri', 'glyp-state_sc', 'glyp-state_sd', 'glyp-state_tn', 'glyp-state_tx', 'glyp-state_ut', 'glyp-state_va', 'glyp-state_vt', 'glyp-state_wa', 'glyp-state_wi', 'glyp-state_wv', 'glyp-state_wy', 'glyp-statement', 'glyp-states', 'glyp-steps', 'glyp-sticky', 'glyp-stop_time', 'glyp-store_material', 'glyp-store_order', 'glyp-store_order_back_order', 'glyp-store_order_cancelled', 'glyp-store_order_complete', 'glyp-store_order_pending', 'glyp-store_order_pending_return', 'glyp-store_order_pre_order', 'glyp-store_order_returned', 'glyp-stores', 'glyp-styling', 'glyp-subscribe', 'glyp-subscription', 'glyp-supplemental', 'glyp-support', 'glyp-sync', 'glyp-sync_down', 'glyp-sync_up', 'glyp-table', 'glyp-tablet', 'glyp-tablets', 'glyp-tactacam', 'glyp-tag', 'glyp-tag_manager', 'glyp-tags', 'glyp-task_runs', 'glyp-taxability', 'glyp-tech_cert', 'glyp-tech_cert_type', 'glyp-tech_pest_sightings', 'glyp-technician', 'glyp-technician_approach', 'glyp-template', 'glyp-termite_fumigation', 'glyp-terrier', 'glyp-terrier_hub', 'glyp-terrier_sync', 'glyp-territory', 'glyp-text', 'glyp-thermometer', 'glyp-third_party', 'glyp-third_party_billing', 'glyp-threshold', 'glyp-ticket', 'glyp-ticket_type', 'glyp-tickets', 'glyp-tidy', 'glyp-time', 'glyp-time_entry', 'glyp-timeline', 'glyp-timer', 'glyp-toolbox', 'glyp-tqi', 'glyp-tracker', 'glyp-tracking', 'glyp-training_course', 'glyp-treatment_split', 'glyp-trends', 'glyp-trial', 'glyp-trip_charge', 'glyp-ui_type', 'glyp-unarchive', 'glyp-unassign', 'glyp-unassign_all', 'glyp-unchecked', 'glyp-undo', 'glyp-unfavorite', 'glyp-unit_sweep', 'glyp-unit_tag', 'glyp-university', 'glyp-unlocked', 'glyp-unmute', 'glyp-unscheduled', 'glyp-update_check_positions', 'glyp-upload', 'glyp-user', 'glyp-user_credit', 'glyp-user_document', 'glyp-user_documents', 'glyp-user_tags', 'glyp-user_task', 'glyp-user_task_setup', 'glyp-users', 'glyp-utility', 'glyp-vacation', 'glyp-vacuum', 'glyp-variant', 'glyp-vendor', 'glyp-versions', 'glyp-video', 'glyp-visible', 'glyp-warehouse', 'glyp-wdi', 'glyp-wdi_az', 'glyp-wdi_ca', 'glyp-wdi_npma', 'glyp-wdi_nv', 'glyp-wdo', 'glyp-web_dusting', 'glyp-website', 'glyp-wildlife', 'glyp-wildlife_exclusion', 'glyp-winter_check', 'glyp-wizard', 'glyp-work', 'glyp-work_class', 'glyp-work_code', 'glyp-work_order', 'glyp-work_production', 'glyp-work_progress', 'glyp-work_yield', 'glyp-year', 'glyp-yield', 'glyp-zillow', 'glyp-zip_code', 'glyp-zone_adjacent', 'glyp-zone_check', 'glyp-zone_production', 'glyp-zone_remote', 'glyp-zoom', 'glyp-zoom_fit'] as const
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Format a glyp name so that it's suitable to show to the user.
|