@kernhq/module-tracker 0.1.3 → 0.2.1
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/dist/contract/models.d.ts +1185 -136
- package/dist/contract/models.d.ts.map +1 -1
- package/dist/contract/models.js +131 -29
- package/dist/contract/models.js.map +1 -1
- package/dist/contract/router.d.ts +1098 -356
- package/dist/contract/router.d.ts.map +1 -1
- package/dist/contract/router.js +6 -25
- package/dist/contract/router.js.map +1 -1
- package/dist/kql/fields.d.ts.map +1 -1
- package/dist/kql/fields.js +4 -1
- package/dist/kql/fields.js.map +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +39 -6
- package/dist/server/index.js.map +1 -1
- package/dist/server/kql/compile.d.ts.map +1 -1
- package/dist/server/kql/compile.js +9 -1
- package/dist/server/kql/compile.js.map +1 -1
- package/dist/server/router.d.ts +1104 -384
- package/dist/server/router.d.ts.map +1 -1
- package/dist/server/router.js +3 -15
- package/dist/server/router.js.map +1 -1
- package/dist/server/schema.d.ts +1 -136
- package/dist/server/schema.d.ts.map +1 -1
- package/dist/server/schema.js +3 -13
- package/dist/server/schema.js.map +1 -1
- package/dist/server/services/config.d.ts +1 -11
- package/dist/server/services/config.d.ts.map +1 -1
- package/dist/server/services/config.js +28 -64
- package/dist/server/services/config.js.map +1 -1
- package/dist/server/services/db.d.ts +2 -4
- package/dist/server/services/db.d.ts.map +1 -1
- package/dist/server/services/db.js +4 -12
- package/dist/server/services/db.js.map +1 -1
- package/dist/server/services/imports.d.ts.map +1 -1
- package/dist/server/services/imports.js +50 -4
- package/dist/server/services/imports.js.map +1 -1
- package/dist/server/services/index.d.ts +2 -0
- package/dist/server/services/index.d.ts.map +1 -1
- package/dist/server/services/index.js +3 -0
- package/dist/server/services/index.js.map +1 -1
- package/dist/server/services/issues.d.ts +7 -18
- package/dist/server/services/issues.d.ts.map +1 -1
- package/dist/server/services/issues.js +34 -18
- package/dist/server/services/issues.js.map +1 -1
- package/dist/server/services/layout.d.ts +25 -0
- package/dist/server/services/layout.d.ts.map +1 -0
- package/dist/server/services/layout.js +97 -0
- package/dist/server/services/layout.js.map +1 -0
- package/dist/server/services/projects.d.ts.map +1 -1
- package/dist/server/services/projects.js +0 -1
- package/dist/server/services/projects.js.map +1 -1
- package/dist/server/services/transitions.d.ts.map +1 -1
- package/dist/server/services/transitions.js +36 -0
- package/dist/server/services/transitions.js.map +1 -1
- package/dist/server/services/values.d.ts +37 -0
- package/dist/server/services/values.d.ts.map +1 -0
- package/dist/server/services/values.js +172 -0
- package/dist/server/services/values.js.map +1 -0
- package/migrations/0002_field_keys_unique.sql +35 -0
- package/migrations/0003_drop_field_schemes.sql +9 -0
- package/migrations/meta/_journal.json +14 -0
- package/package.json +1 -1
- package/src/client/types.ts +6 -0
- package/src/contract/models.ts +157 -33
- package/src/contract/router.ts +6 -27
- package/src/kql/fields.ts +5 -1
package/src/contract/models.ts
CHANGED
|
@@ -124,7 +124,6 @@ export const Project = z.object({
|
|
|
124
124
|
defaultAssignee: z.enum(['unassigned', 'lead']),
|
|
125
125
|
workflowSchemeId: Id.nullable(),
|
|
126
126
|
typeSchemeId: Id.nullable(),
|
|
127
|
-
fieldSchemeId: Id.nullable(),
|
|
128
127
|
settings: ProjectSettings,
|
|
129
128
|
/** public intake form token (null = intake disabled) */
|
|
130
129
|
intakeToken: z.string().nullable(),
|
|
@@ -141,6 +140,14 @@ export const Project = z.object({
|
|
|
141
140
|
})
|
|
142
141
|
export type Project = z.infer<typeof Project>
|
|
143
142
|
|
|
143
|
+
/**
|
|
144
|
+
* The built-in project templates. `software`, `support`, `marketing` and `simple` are the four
|
|
145
|
+
* team shapes the tracker ships with; `kanban` and `blank` are kept because existing projects were
|
|
146
|
+
* created from them.
|
|
147
|
+
*/
|
|
148
|
+
export const ProjectTemplateId = z.enum(['software', 'support', 'marketing', 'simple', 'kanban', 'blank'])
|
|
149
|
+
export type ProjectTemplateId = z.infer<typeof ProjectTemplateId>
|
|
150
|
+
|
|
144
151
|
export const CreateProject = z.object({
|
|
145
152
|
key: ProjectKey,
|
|
146
153
|
name: z.string().min(1).max(120),
|
|
@@ -150,8 +157,8 @@ export const CreateProject = z.object({
|
|
|
150
157
|
leadId: UserId.optional(),
|
|
151
158
|
visibility: ProjectVisibility.default('workspace'),
|
|
152
159
|
defaultAssignee: z.enum(['unassigned', 'lead']).default('unassigned'),
|
|
153
|
-
/** seed types/workflow from a built-in template */
|
|
154
|
-
template:
|
|
160
|
+
/** seed types/workflow/fields from a built-in template */
|
|
161
|
+
template: ProjectTemplateId.default('software'),
|
|
155
162
|
/** or from a saved project template (overrides `template`) */
|
|
156
163
|
templateId: Id.optional(),
|
|
157
164
|
settings: ProjectSettings.partial().optional(),
|
|
@@ -169,7 +176,6 @@ export const UpdateProject = z.object({
|
|
|
169
176
|
defaultAssignee: z.enum(['unassigned', 'lead']).optional(),
|
|
170
177
|
workflowSchemeId: Id.nullable().optional(),
|
|
171
178
|
typeSchemeId: Id.nullable().optional(),
|
|
172
|
-
fieldSchemeId: Id.nullable().optional(),
|
|
173
179
|
settings: ProjectSettings.partial().optional(),
|
|
174
180
|
})
|
|
175
181
|
export type UpdateProject = z.infer<typeof UpdateProject>
|
|
@@ -186,21 +192,6 @@ export const ProjectMember = z.object({
|
|
|
186
192
|
})
|
|
187
193
|
export type ProjectMember = z.infer<typeof ProjectMember>
|
|
188
194
|
|
|
189
|
-
/** Reusable project blueprint (types, workflow, fields, labels, sample views). */
|
|
190
|
-
export const ProjectTemplate = z.object({
|
|
191
|
-
id: Id,
|
|
192
|
-
workspaceId: WorkspaceId.nullable(),
|
|
193
|
-
key: MachineKey,
|
|
194
|
-
name: z.string().min(1).max(120),
|
|
195
|
-
description: z.string().max(1000).nullable(),
|
|
196
|
-
icon: z.string().max(64).nullable(),
|
|
197
|
-
/** a full ProjectTemplateBody JSON */
|
|
198
|
-
body: z.record(z.string(), z.unknown()),
|
|
199
|
-
builtin: z.boolean(),
|
|
200
|
-
createdAt: Timestamp,
|
|
201
|
-
})
|
|
202
|
-
export type ProjectTemplate = z.infer<typeof ProjectTemplate>
|
|
203
|
-
|
|
204
195
|
// =====================================================================================
|
|
205
196
|
// work item types & hierarchy
|
|
206
197
|
// =====================================================================================
|
|
@@ -209,16 +200,66 @@ export type ProjectTemplate = z.infer<typeof ProjectTemplate>
|
|
|
209
200
|
export const HierarchyLevel = z.number().int().min(-1).max(2)
|
|
210
201
|
export type HierarchyLevel = z.infer<typeof HierarchyLevel>
|
|
211
202
|
|
|
203
|
+
/**
|
|
204
|
+
* The system fields a work item type may lay out, in their default order.
|
|
205
|
+
*
|
|
206
|
+
* `pinned` fields are always visible: an issue without a title, a status or a type is not an
|
|
207
|
+
* issue. The settings editor does not offer the control, and the resolver ignores a stored
|
|
208
|
+
* instruction that tries to hide one.
|
|
209
|
+
*/
|
|
210
|
+
export const SYSTEM_LAYOUT_FIELDS = [
|
|
211
|
+
{ id: 'title', section: 'main', pinned: true },
|
|
212
|
+
{ id: 'description', section: 'main', pinned: false },
|
|
213
|
+
{ id: 'status', section: 'sidebar', pinned: true },
|
|
214
|
+
{ id: 'type', section: 'sidebar', pinned: true },
|
|
215
|
+
{ id: 'assignees', section: 'sidebar', pinned: false },
|
|
216
|
+
{ id: 'priority', section: 'sidebar', pinned: false },
|
|
217
|
+
{ id: 'labels', section: 'sidebar', pinned: false },
|
|
218
|
+
{ id: 'components', section: 'sidebar', pinned: false },
|
|
219
|
+
{ id: 'versions', section: 'sidebar', pinned: false },
|
|
220
|
+
{ id: 'estimate', section: 'sidebar', pinned: false },
|
|
221
|
+
{ id: 'startDate', section: 'sidebar', pinned: false },
|
|
222
|
+
{ id: 'dueDate', section: 'sidebar', pinned: false },
|
|
223
|
+
{ id: 'cycle', section: 'sidebar', pinned: false },
|
|
224
|
+
{ id: 'milestone', section: 'sidebar', pinned: false },
|
|
225
|
+
{ id: 'parent', section: 'sidebar', pinned: false },
|
|
226
|
+
{ id: 'reporter', section: 'sidebar', pinned: false },
|
|
227
|
+
] as const satisfies ReadonlyArray<{
|
|
228
|
+
id: string
|
|
229
|
+
section: 'main' | 'sidebar'
|
|
230
|
+
pinned: boolean
|
|
231
|
+
}>
|
|
232
|
+
|
|
233
|
+
export const SystemFieldId = z.enum(SYSTEM_LAYOUT_FIELDS.map((f) => f.id) as [string, ...string[]])
|
|
234
|
+
export type SystemFieldId = (typeof SYSTEM_LAYOUT_FIELDS)[number]['id']
|
|
235
|
+
|
|
236
|
+
/** System field ids that may never be hidden, whatever a stored layout says. */
|
|
237
|
+
export const PINNED_FIELD_IDS: readonly string[] = SYSTEM_LAYOUT_FIELDS.filter((f) => f.pinned).map(
|
|
238
|
+
(f) => f.id,
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* A layout entry's `fieldId` is a *namespaced* name: a system field id (`priority`, `dueDate`) or
|
|
243
|
+
* `cf.<key>` for a custom field. That is the same string KQL, `ViewDisplay.columns` and workflow
|
|
244
|
+
* post-functions already use, so one resolver serves all four.
|
|
245
|
+
*/
|
|
246
|
+
export const LayoutFieldId = z
|
|
247
|
+
.string()
|
|
248
|
+
.min(1)
|
|
249
|
+
.max(80)
|
|
250
|
+
.regex(/^(?:[a-zA-Z][a-zA-Z0-9_]*|cf\.[a-z][a-z0-9_]*)$/, 'Expected a system field id or `cf.<key>`')
|
|
251
|
+
export type LayoutFieldId = z.infer<typeof LayoutFieldId>
|
|
252
|
+
|
|
212
253
|
export const FieldLayoutItem = z.object({
|
|
213
|
-
/** system field
|
|
214
|
-
fieldId:
|
|
254
|
+
/** system field id (`priority`, `dueDate`…) or `cf.<key>` for a custom field */
|
|
255
|
+
fieldId: LayoutFieldId,
|
|
215
256
|
section: z.enum(['main', 'sidebar', 'hidden']).default('sidebar'),
|
|
257
|
+
/** required on this type, over and above the field definition's own `required` */
|
|
216
258
|
required: z.boolean().default(false),
|
|
217
259
|
hidden: z.boolean().default(false),
|
|
218
260
|
order: z.number().int().default(0),
|
|
219
261
|
})
|
|
220
262
|
export type FieldLayoutItem = z.infer<typeof FieldLayoutItem>
|
|
221
|
-
|
|
222
263
|
export const WorkItemType = z.object({
|
|
223
264
|
id: Id,
|
|
224
265
|
workspaceId: WorkspaceId,
|
|
@@ -377,15 +418,37 @@ export const UpsertFieldDef = z.object({
|
|
|
377
418
|
})
|
|
378
419
|
export type UpsertFieldDef = z.infer<typeof UpsertFieldDef>
|
|
379
420
|
|
|
380
|
-
/**
|
|
381
|
-
export const
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
421
|
+
/** One field as an interface should render it, system and custom fields alike. */
|
|
422
|
+
export const ResolvedField = z.object({
|
|
423
|
+
fieldId: LayoutFieldId,
|
|
424
|
+
/** `system` fields are rendered by a built-in component, `custom` ones by their field type */
|
|
425
|
+
kind: z.enum(['system', 'custom']),
|
|
426
|
+
label: z.string().min(1).max(120),
|
|
427
|
+
section: z.enum(['main', 'sidebar']),
|
|
428
|
+
order: z.number().int(),
|
|
429
|
+
required: z.boolean(),
|
|
430
|
+
pinned: z.boolean(),
|
|
431
|
+
showInCards: z.boolean(),
|
|
432
|
+
/** present when `kind` is `custom` — everything needed to render and validate the value */
|
|
433
|
+
field: FieldDef.nullable(),
|
|
434
|
+
})
|
|
435
|
+
export type ResolvedField = z.infer<typeof ResolvedField>
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* The fields of one work item type in one project, already ordered and merged.
|
|
439
|
+
*
|
|
440
|
+
* An **empty stored layout means the default layout** — everything visible. A field the stored
|
|
441
|
+
* layout does not name appends to `sidebar` rather than disappearing, so a newly created field
|
|
442
|
+
* shows up instead of looking broken.
|
|
443
|
+
*/
|
|
444
|
+
export const ResolvedLayout = z.object({
|
|
445
|
+
typeId: Id,
|
|
446
|
+
projectId: Id.nullable(),
|
|
447
|
+
main: z.array(ResolvedField),
|
|
448
|
+
sidebar: z.array(ResolvedField),
|
|
449
|
+
hidden: z.array(ResolvedField),
|
|
387
450
|
})
|
|
388
|
-
export type
|
|
451
|
+
export type ResolvedLayout = z.infer<typeof ResolvedLayout>
|
|
389
452
|
|
|
390
453
|
// =====================================================================================
|
|
391
454
|
// workflows
|
|
@@ -1053,6 +1116,13 @@ export const GroupBy = z.enum([
|
|
|
1053
1116
|
])
|
|
1054
1117
|
export type GroupBy = z.infer<typeof GroupBy>
|
|
1055
1118
|
|
|
1119
|
+
/**
|
|
1120
|
+
* A group key that may also name a custom field. `GroupBy` stays as it was so nothing that accepts
|
|
1121
|
+
* only the built-in keys has to change; views and `issues.query` accept this wider one.
|
|
1122
|
+
*/
|
|
1123
|
+
export const GroupByValue = z.union([GroupBy, z.string().regex(/^cf\.[a-z][a-z0-9_]*$/)])
|
|
1124
|
+
export type GroupByValue = z.infer<typeof GroupByValue>
|
|
1125
|
+
|
|
1056
1126
|
export const OrderBy = z.object({
|
|
1057
1127
|
/** KQL field name (`priority`, `updated`, `rank`, `cf.severity`…) */
|
|
1058
1128
|
field: z.string().min(1),
|
|
@@ -1071,7 +1141,7 @@ export const BoardColumn = z.object({
|
|
|
1071
1141
|
export type BoardColumn = z.infer<typeof BoardColumn>
|
|
1072
1142
|
|
|
1073
1143
|
export const ViewDisplay = z.object({
|
|
1074
|
-
groupBy:
|
|
1144
|
+
groupBy: GroupByValue.default('none'),
|
|
1075
1145
|
subGroupBy: GroupBy.optional(),
|
|
1076
1146
|
orderBy: z.array(OrderBy).default([{ field: 'rank', dir: 'asc' }]),
|
|
1077
1147
|
/** visible columns (list/spreadsheet): system field names or `cf.<key>` */
|
|
@@ -1084,8 +1154,13 @@ export const ViewDisplay = z.object({
|
|
|
1084
1154
|
/** board columns (null → one column per status of the project's workflows) */
|
|
1085
1155
|
boardColumns: z.array(BoardColumn).nullable().default(null),
|
|
1086
1156
|
wipLimits: z.record(z.string(), z.number().int().positive()).default({}),
|
|
1087
|
-
/** calendar: which date field positions issues */
|
|
1088
|
-
calendarField: z
|
|
1157
|
+
/** calendar: which date field positions issues — a system date field or `cf.<key>` */
|
|
1158
|
+
calendarField: z
|
|
1159
|
+
.union([
|
|
1160
|
+
z.enum(['dueDate', 'startDate', 'createdAt', 'updatedAt', 'resolvedAt']),
|
|
1161
|
+
z.string().regex(/^cf\.[a-z][a-z0-9_]*$/),
|
|
1162
|
+
])
|
|
1163
|
+
.default('dueDate'),
|
|
1089
1164
|
/** timeline: show dependency arrows */
|
|
1090
1165
|
showDependencies: z.boolean().default(true),
|
|
1091
1166
|
density: z.enum(['compact', 'comfortable']).default('comfortable'),
|
|
@@ -1420,3 +1495,52 @@ export const IssueApproval = z.object({
|
|
|
1420
1495
|
updatedAt: Timestamp,
|
|
1421
1496
|
})
|
|
1422
1497
|
export type IssueApproval = z.infer<typeof IssueApproval>
|
|
1498
|
+
|
|
1499
|
+
// =====================================================================================
|
|
1500
|
+
// project templates
|
|
1501
|
+
// =====================================================================================
|
|
1502
|
+
|
|
1503
|
+
/**
|
|
1504
|
+
* Everything a template seeds into a new project. This is the *only* description of a template's
|
|
1505
|
+
* contents: the built-in four are values of this type in code, and a template saved from an
|
|
1506
|
+
* existing project snapshots into the same shape, so one applier serves both.
|
|
1507
|
+
*
|
|
1508
|
+
* Ids inside a body are template-local. Workflows are named by index, types name their workflow by
|
|
1509
|
+
* that index, and layouts name fields by `cf.<key>` — nothing here refers to a database id, which
|
|
1510
|
+
* is what lets a body created in one workspace apply in another.
|
|
1511
|
+
*/
|
|
1512
|
+
export const ProjectTemplateBody = z.object({
|
|
1513
|
+
version: z.literal(1).default(1),
|
|
1514
|
+
settings: ProjectSettings.partial().optional(),
|
|
1515
|
+
workflows: z
|
|
1516
|
+
.array(z.object({ name: z.string().min(1).max(120), definition: WorkflowDefinition }))
|
|
1517
|
+
.default([]),
|
|
1518
|
+
fields: z.array(UpsertFieldDef).default([]),
|
|
1519
|
+
types: z
|
|
1520
|
+
.array(
|
|
1521
|
+
UpsertWorkItemType.omit({ workflowId: true }).extend({
|
|
1522
|
+
/** index into `workflows`; null → the project's default workflow */
|
|
1523
|
+
workflowIndex: z.number().int().nonnegative().nullable().default(null),
|
|
1524
|
+
}),
|
|
1525
|
+
)
|
|
1526
|
+
.default([]),
|
|
1527
|
+
labels: z
|
|
1528
|
+
.array(z.object({ name: z.string().min(1).max(60), color: Color.nullable().default(null) }))
|
|
1529
|
+
.default([]),
|
|
1530
|
+
views: z.array(UpsertView).default([]),
|
|
1531
|
+
})
|
|
1532
|
+
export type ProjectTemplateBody = z.infer<typeof ProjectTemplateBody>
|
|
1533
|
+
|
|
1534
|
+
/** Reusable project blueprint (types, workflow, fields, labels, sample views). */
|
|
1535
|
+
export const ProjectTemplate = z.object({
|
|
1536
|
+
id: Id,
|
|
1537
|
+
workspaceId: WorkspaceId.nullable(),
|
|
1538
|
+
key: MachineKey,
|
|
1539
|
+
name: z.string().min(1).max(120),
|
|
1540
|
+
description: z.string().max(1000).nullable(),
|
|
1541
|
+
icon: z.string().max(64).nullable(),
|
|
1542
|
+
body: ProjectTemplateBody,
|
|
1543
|
+
builtin: z.boolean(),
|
|
1544
|
+
createdAt: Timestamp,
|
|
1545
|
+
})
|
|
1546
|
+
export type ProjectTemplate = z.infer<typeof ProjectTemplate>
|
package/src/contract/router.ts
CHANGED
|
@@ -16,7 +16,6 @@ import {
|
|
|
16
16
|
Cycle,
|
|
17
17
|
DateOnly,
|
|
18
18
|
FieldDef,
|
|
19
|
-
FieldScheme,
|
|
20
19
|
HierarchyRules,
|
|
21
20
|
ImportJob,
|
|
22
21
|
ImportSource,
|
|
@@ -42,6 +41,7 @@ import {
|
|
|
42
41
|
RecurringIssue,
|
|
43
42
|
RelationType,
|
|
44
43
|
RelationView,
|
|
44
|
+
ResolvedLayout,
|
|
45
45
|
RichDoc,
|
|
46
46
|
StatusHistoryEntry,
|
|
47
47
|
StatusInfo,
|
|
@@ -173,6 +173,11 @@ export const trackerContract = {
|
|
|
173
173
|
.route({ method: 'POST', path: '/types/{id}/archive', ...t('types') })
|
|
174
174
|
.input(ws.extend({ id: Id, archived: z.boolean().default(true) }))
|
|
175
175
|
.output(WorkItemType),
|
|
176
|
+
/** the fields of one type in one project, ordered and merged — what a form should render */
|
|
177
|
+
layout: baseContract
|
|
178
|
+
.route({ method: 'GET', path: '/types/{id}/layout', ...t('types') })
|
|
179
|
+
.input(ws.extend({ id: Id, projectId: Id.nullable().optional() }))
|
|
180
|
+
.output(ResolvedLayout),
|
|
176
181
|
hierarchyRules: baseContract
|
|
177
182
|
.route({ method: 'GET', path: '/types/hierarchy-rules', ...t('types') })
|
|
178
183
|
.input(ws)
|
|
@@ -238,32 +243,6 @@ export const trackerContract = {
|
|
|
238
243
|
.route({ method: 'DELETE', path: '/fields/{id}', ...t('fields') })
|
|
239
244
|
.input(ws.extend({ id: Id }))
|
|
240
245
|
.output(Ok),
|
|
241
|
-
schemes: {
|
|
242
|
-
list: baseContract
|
|
243
|
-
.route({ method: 'GET', path: '/field-schemes', ...t('fields') })
|
|
244
|
-
.input(ws)
|
|
245
|
-
.output(z.array(FieldScheme)),
|
|
246
|
-
create: baseContract
|
|
247
|
-
.route({ method: 'POST', path: '/field-schemes', ...t('fields') })
|
|
248
|
-
.input(ws.extend({ name: z.string().min(1).max(120), fieldIds: z.array(Id) }))
|
|
249
|
-
.output(FieldScheme),
|
|
250
|
-
update: baseContract
|
|
251
|
-
.route({ method: 'PATCH', path: '/field-schemes/{id}', ...t('fields') })
|
|
252
|
-
.input(
|
|
253
|
-
ws.extend({
|
|
254
|
-
id: Id,
|
|
255
|
-
patch: z.object({
|
|
256
|
-
name: z.string().min(1).max(120).optional(),
|
|
257
|
-
fieldIds: z.array(Id).optional(),
|
|
258
|
-
}),
|
|
259
|
-
}),
|
|
260
|
-
)
|
|
261
|
-
.output(FieldScheme),
|
|
262
|
-
delete: baseContract
|
|
263
|
-
.route({ method: 'DELETE', path: '/field-schemes/{id}', ...t('fields') })
|
|
264
|
-
.input(ws.extend({ id: Id }))
|
|
265
|
-
.output(Ok),
|
|
266
|
-
},
|
|
267
246
|
},
|
|
268
247
|
|
|
269
248
|
// ------------------------------------------------------------------ workflows
|
package/src/kql/fields.ts
CHANGED
|
@@ -143,7 +143,11 @@ export function customKqlField(key: string, fieldType: FieldType, label: string)
|
|
|
143
143
|
name: `cf.${key}`,
|
|
144
144
|
kind,
|
|
145
145
|
label,
|
|
146
|
-
array:
|
|
146
|
+
array:
|
|
147
|
+
fieldType === 'multiselect' ||
|
|
148
|
+
fieldType === 'multiuser' ||
|
|
149
|
+
fieldType === 'label' ||
|
|
150
|
+
fieldType === 'relation',
|
|
147
151
|
sortable: kind === 'number' || kind === 'date' || kind === 'datetime' || kind === 'text',
|
|
148
152
|
custom: { key, fieldType },
|
|
149
153
|
}
|