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.
@@ -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!: 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
- parent.div('.actions', actions => {
376
- actions.a(a => {
377
- a.i('.glyp-close')
378
- }).emitClick(removeKey, { id: this.state.id })
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
- return 'Today'
110
+ rel = 'Today'
111
+ break
99
112
  case -1:
100
- return 'Yesterday'
113
+ rel = "Yesterday"
114
+ break
101
115
  case 1:
102
- return 'Tomorrow'
116
+ rel = 'Tomorrow'
117
+ break
103
118
  }
104
119
  }
105
- if (range.relative == 0) {
106
- return `This ${range.period}`
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 == -1) {
109
- return `Last ${range.period}`
126
+ else if (range.relative == 1) {
127
+ rel = `Next ${range.period}`
110
128
  }
111
- if (range.relative == 1) {
112
- return `Next ${range.period}`
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
- const plural = inflection.pluralize(range.period)
115
- if (range.relative < 0) {
116
- return `${range.relative*-1} ${plural} ago`
117
- } else {
118
- return `${range.relative} ${plural} from now`
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: string
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 type
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
- break
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
- default:
170
- parent.div('.empty').text(`${filter.filter_type} filter`)
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
- break
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
- break
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
- break
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-code' },
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
- // Keys
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
- this.app.showModal(JoinedTableEditorModal, { table, belongsTo, callback, parentTable: this.state.table as TableRef })
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
- row.div('.stretch', col => {
404
- col.label('.body-size', label => {
405
- label.i('.glyp-join_inner')
406
- this.radio(label, 'join_type', 'inner')
407
- label.div().text(`<strong>Inner</strong>: <em>${parentName}</em> is only included if there's an associated <em>${name}</em>`)
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
- row.div('.stretch', col => {
411
- col.label('.body-size', label => {
412
- label.i('.glyp-join_left')
413
- this.radio(label, 'join_type', 'left')
414
- label.div().text(`<strong>Left</strong>: <em>${parentName}</em> is included even if there's no associated <em>${name}</em>`)
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
- // Client-Side Validation
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' && !groupedTables.has(table))
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
@@ -4,7 +4,7 @@
4
4
  "files": [
5
5
  "*"
6
6
  ],
7
- "version": "4.63.6",
7
+ "version": "4.64.3",
8
8
  "repository": {
9
9
  "type": "git",
10
10
  "url": "https://github.com/Terrier-Tech/terrier-engine"
package/terrier/glyps.ts CHANGED
@@ -1,8 +1,8 @@
1
- // This file was automatically generated by glyps:compile on 12/02/25 2:53 PM, DO NOT MANUALLY EDIT!
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.