@kernhq/module-tracker 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +16 -3
- package/src/client/api-instance.ts +34 -0
- package/src/client/components/BoardCard.svelte +182 -0
- package/src/client/components/BoardView.svelte +248 -0
- package/src/client/components/CommentComposer.svelte +219 -0
- package/src/client/components/CommentThread.svelte +389 -0
- package/src/client/components/CustomField.svelte +333 -0
- package/src/client/components/DueDate.svelte +44 -0
- package/src/client/components/FilterMenu.svelte +91 -0
- package/src/client/components/GroupHeader.svelte +137 -0
- package/src/client/components/HomeLinks.svelte +38 -0
- package/src/client/components/IssueApprovals.svelte +217 -0
- package/src/client/components/IssueConnections.svelte +424 -0
- package/src/client/components/IssueDetailPanel.svelte +1337 -0
- package/src/client/components/IssueInline.svelte +75 -0
- package/src/client/components/IssueListView.svelte +153 -0
- package/src/client/components/IssuePicker.svelte +180 -0
- package/src/client/components/IssueRow.svelte +171 -0
- package/src/client/components/IssueTime.svelte +306 -0
- package/src/client/components/KqlInput.svelte +295 -0
- package/src/client/components/NewIssueDialog.svelte +210 -0
- package/src/client/components/NewProjectDialog.svelte +235 -0
- package/src/client/components/PriorityGlyph.svelte +52 -0
- package/src/client/components/SaveViewDialog.svelte +153 -0
- package/src/client/components/SidebarProjects.svelte +263 -0
- package/src/client/components/SidebarViews.svelte +336 -0
- package/src/client/components/StatusIcon.svelte +50 -0
- package/src/client/components/TrackerControls.svelte +109 -0
- package/src/client/components/TrackerSidebar.svelte +96 -0
- package/src/client/context.svelte.ts +105 -0
- package/src/client/core-api.ts +35 -0
- package/src/client/csv.test.ts +50 -0
- package/src/client/csv.ts +60 -0
- package/src/client/filters.ts +94 -0
- package/src/client/i18n.ts +3260 -0
- package/src/client/index.ts +12 -0
- package/src/client/labels.ts +200 -0
- package/src/client/mock.ts +2729 -0
- package/src/client/module.ts +491 -0
- package/src/client/nav.test.ts +59 -0
- package/src/client/nav.ts +84 -0
- package/src/client/pages/IntakePage.svelte +287 -0
- package/src/client/pages/IssuesPage.svelte +701 -0
- package/src/client/pages/ProjectPage.svelte +29 -0
- package/src/client/pages/ReportsPage.svelte +365 -0
- package/src/client/permissions.ts +33 -0
- package/src/client/planning/PlanningSections.svelte +259 -0
- package/src/client/project/ComponentsPage.svelte +423 -0
- package/src/client/project/CyclesPage.svelte +488 -0
- package/src/client/project/MilestonesPage.svelte +342 -0
- package/src/client/project/PlanCard.svelte +191 -0
- package/src/client/project/ProjectShell.svelte +93 -0
- package/src/client/project/TemplatesPage.svelte +321 -0
- package/src/client/project/context.svelte.ts +58 -0
- package/src/client/query.ts +50 -0
- package/src/client/rank.test.ts +78 -0
- package/src/client/rank.ts +13 -4
- package/src/client/recurrence.test.ts +37 -0
- package/src/client/recurrence.ts +89 -0
- package/src/client/richtext.ts +155 -0
- package/src/client/settings/CycleList.svelte +422 -0
- package/src/client/settings/FieldEditor.svelte +324 -0
- package/src/client/settings/FieldsSettings.svelte +273 -0
- package/src/client/settings/ImportSettings.svelte +410 -0
- package/src/client/settings/LayoutEditor.svelte +263 -0
- package/src/client/settings/PlanningList.svelte +249 -0
- package/src/client/settings/PlanningSettings.svelte +179 -0
- package/src/client/settings/ProjectsSettings.svelte +462 -0
- package/src/client/settings/RepeatingSettings.svelte +293 -0
- package/src/client/settings/TypeEditor.svelte +214 -0
- package/src/client/settings/TypesSettings.svelte +384 -0
- package/src/client/settings/WorkflowsSettings.svelte +645 -0
- package/src/client/settings/mutations.ts +19 -0
- package/src/client/time.test.ts +39 -0
- package/src/client/time.ts +34 -0
- package/src/client/tracker.test.ts +399 -0
- package/src/client/views.ts +27 -0
- package/src/client/widgets/AssignedCountWidget.svelte +14 -0
- package/src/client/widgets/CountWidget.svelte +56 -0
- package/src/client/widgets/CycleWidget.svelte +85 -0
- package/src/client/widgets/DueSoonCountWidget.svelte +14 -0
- package/src/client/widgets/IssuesWidget.svelte +222 -0
- package/src/client/widgets/ThroughputWidget.svelte +68 -0
- package/src/client/widgets/TimerWidget.svelte +116 -0
- package/src/client/widgets/VelocityWidget.svelte +55 -0
package/src/client/index.ts
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
export { createTrackerClient, type TrackerApi } from './api.js'
|
|
15
|
+
export { __setTrackerApi, getTrackerApi } from './api-instance.js'
|
|
15
16
|
export {
|
|
16
17
|
type DueTone,
|
|
17
18
|
daysUntil,
|
|
@@ -33,6 +34,7 @@ export {
|
|
|
33
34
|
groupOrder,
|
|
34
35
|
type IssueGroup,
|
|
35
36
|
} from './group.js'
|
|
37
|
+
export { type TrackerMessageKey, t, trackerMessageBundles } from './i18n.js'
|
|
36
38
|
export {
|
|
37
39
|
customKqlField,
|
|
38
40
|
type KqlComparison,
|
|
@@ -49,6 +51,16 @@ export {
|
|
|
49
51
|
type Span,
|
|
50
52
|
SYSTEM_FIELDS,
|
|
51
53
|
} from './kql.js'
|
|
54
|
+
export { trackerClientModule, trackerClientModule as default } from './module.js'
|
|
55
|
+
/**
|
|
56
|
+
* The public intake form, exported as a component.
|
|
57
|
+
*
|
|
58
|
+
* It is the one tracker screen that is *not* inside a workspace: somebody outside the organisation
|
|
59
|
+
* opens `/request/<token>` with no session at all, so the shell mounts it as an app route rather
|
|
60
|
+
* than through the module router, and needs the component by name.
|
|
61
|
+
*/
|
|
62
|
+
export { default as IntakePage } from './pages/IntakePage.svelte'
|
|
63
|
+
export { canTracker, TRACKER_PERMISSIONS, type TrackerPermission } from './permissions.js'
|
|
52
64
|
export {
|
|
53
65
|
byRank,
|
|
54
66
|
initialRank,
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import type { TrackerCatalogue } from './context.svelte.js'
|
|
2
|
+
import type { Preset } from './filters.js'
|
|
3
|
+
import { t } from './i18n.js'
|
|
4
|
+
import type { EstimateUnit, GroupBy, GroupKey, Priority, Project, StatusCategory } from './index.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* What the tracker's own vocabulary is called on screen.
|
|
8
|
+
*
|
|
9
|
+
* Group keys, priorities and presets are ids and enums in the data; every place they are shown to a
|
|
10
|
+
* person goes through here, so the interface has one translation of each and Persian, German and
|
|
11
|
+
* Arabic get them all.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** What a group heading draws next to its name. */
|
|
15
|
+
export type GroupBadge =
|
|
16
|
+
| { kind: 'status'; id: string; category: StatusCategory }
|
|
17
|
+
| { kind: 'priority'; priority: Priority }
|
|
18
|
+
| { kind: 'person'; id: string; name: string; avatarUrl: string | null }
|
|
19
|
+
| { kind: 'colour'; color: string | null }
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The built-in groupings the toolbar offers, in order (DESIGN.md 2.5).
|
|
23
|
+
*
|
|
24
|
+
* The last four are how a project is planned rather than how work is triaged, which is also how the
|
|
25
|
+
* sidebar reaches them — a project's "Components" row is this list grouped by component, so the two
|
|
26
|
+
* cannot offer different things.
|
|
27
|
+
*/
|
|
28
|
+
export const GROUP_CYCLE: GroupBy[] = [
|
|
29
|
+
'status',
|
|
30
|
+
'priority',
|
|
31
|
+
'assignee',
|
|
32
|
+
'project',
|
|
33
|
+
'component',
|
|
34
|
+
'milestone',
|
|
35
|
+
'cycle',
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
export function groupByLabel(groupBy: GroupBy): string {
|
|
39
|
+
switch (groupBy) {
|
|
40
|
+
case 'status':
|
|
41
|
+
return t('group_status')
|
|
42
|
+
case 'priority':
|
|
43
|
+
return t('group_priority')
|
|
44
|
+
case 'assignee':
|
|
45
|
+
return t('group_assignee')
|
|
46
|
+
case 'label':
|
|
47
|
+
return t('group_label')
|
|
48
|
+
case 'project':
|
|
49
|
+
return t('group_project')
|
|
50
|
+
case 'cycle':
|
|
51
|
+
return t('group_cycle')
|
|
52
|
+
case 'component':
|
|
53
|
+
return t('group_component')
|
|
54
|
+
case 'milestone':
|
|
55
|
+
return t('group_milestone')
|
|
56
|
+
case 'type':
|
|
57
|
+
return t('group_type')
|
|
58
|
+
default:
|
|
59
|
+
return t('group_none')
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* An estimate, in the unit its project keeps.
|
|
65
|
+
*
|
|
66
|
+
* A project decides whether it estimates in points or in hours (`settings.estimation`), and every
|
|
67
|
+
* issue carries the unit it was raised under — so a row asks the issue rather than assuming. The
|
|
68
|
+
* whole interface said "pts" regardless, which made an hours project read as story points.
|
|
69
|
+
*/
|
|
70
|
+
export function estimateLabel(value: number, unit: EstimateUnit): string {
|
|
71
|
+
return unit === 'hours' ? t('hours', { count: value }) : t('points', { count: value })
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** What a project estimates in; `none` means it does not, and nothing should offer to. */
|
|
75
|
+
export const estimateUnitOf = (project: Project | null | undefined): EstimateUnit =>
|
|
76
|
+
project?.settings.estimation ?? 'points'
|
|
77
|
+
|
|
78
|
+
export function priorityLabel(priority: Priority): string {
|
|
79
|
+
switch (priority) {
|
|
80
|
+
case 'urgent':
|
|
81
|
+
return t('priority_urgent')
|
|
82
|
+
case 'high':
|
|
83
|
+
return t('priority_high')
|
|
84
|
+
case 'medium':
|
|
85
|
+
return t('priority_medium')
|
|
86
|
+
case 'low':
|
|
87
|
+
return t('priority_low')
|
|
88
|
+
default:
|
|
89
|
+
return t('priority_none')
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* The heading for one group: what to call it, and the badge that goes in front of it.
|
|
95
|
+
*
|
|
96
|
+
* `null` is the group for issues that have no value for the field, and it reads differently per
|
|
97
|
+
* field — "Unassigned" for people, "No cycle" for planning — which is why this is one place rather
|
|
98
|
+
* than a fallback string sprinkled through the components.
|
|
99
|
+
*/
|
|
100
|
+
export function describeGroup(
|
|
101
|
+
key: GroupKey,
|
|
102
|
+
/** a built-in group key or `cf.<key>` */
|
|
103
|
+
groupBy: string,
|
|
104
|
+
cat: TrackerCatalogue,
|
|
105
|
+
): { label: string; badge?: GroupBadge } {
|
|
106
|
+
if (groupBy === 'none') return { label: t('all_issues') }
|
|
107
|
+
|
|
108
|
+
// A custom field's value is stored as an option id, so the heading has to ask the field what it
|
|
109
|
+
// is called — otherwise a column reads `opt_7f3a` instead of `Sev 1`.
|
|
110
|
+
const custom = /^cf\.(.+)$/.exec(groupBy)
|
|
111
|
+
if (custom) {
|
|
112
|
+
if (key === null) return { label: t('group_none') }
|
|
113
|
+
return { label: cat.customValueLabel(custom[1]!, key) }
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (key === null) {
|
|
117
|
+
switch (groupBy) {
|
|
118
|
+
case 'assignee':
|
|
119
|
+
return { label: t('unassigned') }
|
|
120
|
+
case 'label':
|
|
121
|
+
return { label: t('no_label') }
|
|
122
|
+
case 'cycle':
|
|
123
|
+
return { label: t('no_cycle') }
|
|
124
|
+
case 'milestone':
|
|
125
|
+
return { label: t('no_milestone') }
|
|
126
|
+
case 'component':
|
|
127
|
+
return { label: t('no_component') }
|
|
128
|
+
default:
|
|
129
|
+
return { label: t('group_none') }
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
switch (groupBy as GroupBy) {
|
|
134
|
+
case 'status': {
|
|
135
|
+
const status = cat.status(key)
|
|
136
|
+
return {
|
|
137
|
+
label: status?.name ?? key,
|
|
138
|
+
badge: { kind: 'status', id: key, category: status?.category ?? 'todo' },
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
case 'statusCategory':
|
|
142
|
+
return { label: key, badge: { kind: 'status', id: key, category: key as StatusCategory } }
|
|
143
|
+
case 'priority':
|
|
144
|
+
return {
|
|
145
|
+
label: priorityLabel(key as Priority),
|
|
146
|
+
badge: { kind: 'priority', priority: key as Priority },
|
|
147
|
+
}
|
|
148
|
+
case 'assignee': {
|
|
149
|
+
const person = cat.person(key)
|
|
150
|
+
return {
|
|
151
|
+
label: person?.name ?? t('unassigned'),
|
|
152
|
+
badge: { kind: 'person', id: key, name: person?.name ?? '', avatarUrl: person?.avatarUrl ?? null },
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
case 'label': {
|
|
156
|
+
const label = cat.label(key)
|
|
157
|
+
return { label: label?.name ?? key, badge: { kind: 'colour', color: label?.color ?? null } }
|
|
158
|
+
}
|
|
159
|
+
case 'project': {
|
|
160
|
+
const project = cat.project(key)
|
|
161
|
+
return { label: project?.name ?? key, badge: { kind: 'colour', color: project?.color ?? null } }
|
|
162
|
+
}
|
|
163
|
+
case 'cycle': {
|
|
164
|
+
const cycle = cat.cycle(key)
|
|
165
|
+
return { label: cycle?.name ?? key }
|
|
166
|
+
}
|
|
167
|
+
case 'milestone': {
|
|
168
|
+
const milestone = cat.milestone(key)
|
|
169
|
+
return { label: milestone?.name ?? key }
|
|
170
|
+
}
|
|
171
|
+
case 'component': {
|
|
172
|
+
// without this a components board reads `01920000-…` where a name belongs
|
|
173
|
+
const component = cat.component(key)
|
|
174
|
+
return { label: component?.name ?? key }
|
|
175
|
+
}
|
|
176
|
+
case 'type': {
|
|
177
|
+
const type = cat.type(key)
|
|
178
|
+
return { label: type?.name ?? key }
|
|
179
|
+
}
|
|
180
|
+
default:
|
|
181
|
+
return { label: key }
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function presetLabel(preset: Preset): string {
|
|
186
|
+
switch (preset) {
|
|
187
|
+
case 'assigned':
|
|
188
|
+
return t('preset_assigned')
|
|
189
|
+
case 'active':
|
|
190
|
+
return t('preset_active')
|
|
191
|
+
case 'backlog':
|
|
192
|
+
return t('preset_backlog')
|
|
193
|
+
case 'created':
|
|
194
|
+
return t('preset_created')
|
|
195
|
+
case 'subscribed':
|
|
196
|
+
return t('preset_subscribed')
|
|
197
|
+
default:
|
|
198
|
+
return t('preset_all')
|
|
199
|
+
}
|
|
200
|
+
}
|