@andypai/agent-kanban 0.6.4 → 0.6.5
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 +11 -3
- package/src/__tests__/index.test.ts +27 -1
- package/src/__tests__/jira-cache.test.ts +68 -0
- package/src/__tests__/jira-provider-read.test.ts +130 -0
- package/src/__tests__/jira-wiring.test.ts +41 -0
- package/src/__tests__/linear-provider-comment.test.ts +81 -0
- package/src/__tests__/linear-provider-sync.test.ts +107 -0
- package/src/__tests__/linear-state-resolution.test.ts +60 -0
- package/src/__tests__/local-provider-core.test.ts +245 -0
- package/src/__tests__/postgres-jira-provider.test.ts +55 -0
- package/src/__tests__/postgres-linear-provider.test.ts +90 -0
- package/src/__tests__/postgres-local-provider.test.ts +26 -0
- package/src/__tests__/provider-sync-core.test.ts +150 -0
- package/src/__tests__/provider-team-info.test.ts +37 -0
- package/src/__tests__/ui-board-slice.test.ts +14 -0
- package/src/__tests__/use-cases.test.ts +21 -42
- package/src/api.ts +78 -132
- package/src/column-roles.ts +3 -2
- package/src/index.ts +135 -92
- package/src/mcp/core.ts +7 -8
- package/src/provider-runtime.ts +12 -56
- package/src/providers/cache-task-mappers.ts +123 -0
- package/src/providers/factory.ts +103 -0
- package/src/providers/index.ts +4 -25
- package/src/providers/jira-cache.ts +41 -99
- package/src/providers/jira-core.ts +78 -80
- package/src/providers/linear-cache.ts +42 -69
- package/src/providers/linear-core.ts +41 -55
- package/src/providers/local-core.ts +226 -0
- package/src/providers/local.ts +6 -182
- package/src/providers/postgres-batch.ts +10 -0
- package/src/providers/postgres-jira-cache.ts +255 -213
- package/src/providers/postgres-jira.ts +2 -20
- package/src/providers/postgres-linear-cache.ts +279 -183
- package/src/providers/postgres-linear.ts +2 -20
- package/src/providers/postgres-local.ts +22 -47
- package/src/providers/sqlite-local-store.ts +121 -0
- package/src/providers/sync-core.ts +91 -0
- package/src/providers/team-info.ts +24 -0
- package/src/providers/warn-once.ts +6 -0
- package/src/use-cases.ts +7 -131
- package/src/webhook-events.ts +29 -1
- package/ui/dist/assets/index-DcZH7fI3.js +40 -0
- package/ui/dist/index.html +1 -1
- package/ui/dist/assets/index-Cigv8a9S.js +0 -40
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import type { Task } from '../types'
|
|
2
|
+
|
|
3
|
+
export interface JiraTaskRow {
|
|
4
|
+
id: string
|
|
5
|
+
key: string
|
|
6
|
+
summary: string
|
|
7
|
+
description_text: string
|
|
8
|
+
status_id: string
|
|
9
|
+
priority_name: string
|
|
10
|
+
issue_type_name: string
|
|
11
|
+
assignee_account_id: string | null
|
|
12
|
+
assignee_name: string
|
|
13
|
+
labels: string
|
|
14
|
+
comment_count: number
|
|
15
|
+
project_key: string
|
|
16
|
+
url: string | null
|
|
17
|
+
created_at: string
|
|
18
|
+
updated_at: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface LinearTaskRow {
|
|
22
|
+
id: string
|
|
23
|
+
identifier: string
|
|
24
|
+
title: string
|
|
25
|
+
description: string
|
|
26
|
+
state_id: string
|
|
27
|
+
state_position: number
|
|
28
|
+
priority: number
|
|
29
|
+
assignee_name: string
|
|
30
|
+
project_name: string
|
|
31
|
+
labels: string
|
|
32
|
+
comment_count: number
|
|
33
|
+
url: string | null
|
|
34
|
+
created_at: string
|
|
35
|
+
updated_at: string
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function parseTaskLabels(raw: string): string[] {
|
|
39
|
+
try {
|
|
40
|
+
const parsed: unknown = JSON.parse(raw)
|
|
41
|
+
return Array.isArray(parsed)
|
|
42
|
+
? parsed.filter((value): value is string => typeof value === 'string')
|
|
43
|
+
: []
|
|
44
|
+
} catch {
|
|
45
|
+
return []
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function mapJiraPriorityNameToCanonical(name: string): Task['priority'] {
|
|
50
|
+
switch (name.trim().toLowerCase()) {
|
|
51
|
+
case 'highest':
|
|
52
|
+
return 'urgent'
|
|
53
|
+
case 'high':
|
|
54
|
+
return 'high'
|
|
55
|
+
case 'medium':
|
|
56
|
+
return 'medium'
|
|
57
|
+
default:
|
|
58
|
+
return 'low'
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function mapLinearPriority(priority: number): Task['priority'] {
|
|
63
|
+
switch (priority) {
|
|
64
|
+
case 1:
|
|
65
|
+
return 'urgent'
|
|
66
|
+
case 2:
|
|
67
|
+
return 'high'
|
|
68
|
+
case 3:
|
|
69
|
+
return 'medium'
|
|
70
|
+
case 0:
|
|
71
|
+
case 4:
|
|
72
|
+
default:
|
|
73
|
+
return 'low'
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function jiraTaskFromRow(row: JiraTaskRow): Task {
|
|
78
|
+
return {
|
|
79
|
+
id: `jira:${row.id}`,
|
|
80
|
+
providerId: row.id,
|
|
81
|
+
externalRef: row.key,
|
|
82
|
+
url: row.url,
|
|
83
|
+
title: row.summary,
|
|
84
|
+
description: row.description_text,
|
|
85
|
+
column_id: row.status_id,
|
|
86
|
+
position: 0,
|
|
87
|
+
priority: mapJiraPriorityNameToCanonical(row.priority_name),
|
|
88
|
+
assignee: row.assignee_name,
|
|
89
|
+
assignees: row.assignee_name ? [row.assignee_name] : [],
|
|
90
|
+
labels: parseTaskLabels(row.labels),
|
|
91
|
+
comment_count: row.comment_count,
|
|
92
|
+
project: row.project_key,
|
|
93
|
+
metadata: '{}',
|
|
94
|
+
created_at: row.created_at,
|
|
95
|
+
updated_at: row.updated_at,
|
|
96
|
+
version: row.updated_at,
|
|
97
|
+
source_updated_at: row.updated_at,
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function linearTaskFromRow(row: LinearTaskRow): Task {
|
|
102
|
+
return {
|
|
103
|
+
id: `linear:${row.id}`,
|
|
104
|
+
providerId: row.id,
|
|
105
|
+
externalRef: row.identifier,
|
|
106
|
+
url: row.url,
|
|
107
|
+
title: row.title,
|
|
108
|
+
description: row.description,
|
|
109
|
+
column_id: row.state_id,
|
|
110
|
+
position: row.state_position,
|
|
111
|
+
priority: mapLinearPriority(row.priority),
|
|
112
|
+
assignee: row.assignee_name,
|
|
113
|
+
assignees: row.assignee_name ? [row.assignee_name] : [],
|
|
114
|
+
labels: parseTaskLabels(row.labels),
|
|
115
|
+
comment_count: row.comment_count,
|
|
116
|
+
project: row.project_name,
|
|
117
|
+
metadata: '{}',
|
|
118
|
+
created_at: row.created_at,
|
|
119
|
+
updated_at: row.updated_at,
|
|
120
|
+
version: row.updated_at,
|
|
121
|
+
source_updated_at: row.updated_at,
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type { Database } from 'bun:sqlite'
|
|
2
|
+
import type { Sql } from 'postgres'
|
|
3
|
+
|
|
4
|
+
import { initSchema, seedDefaultColumns } from '../db'
|
|
5
|
+
import type { ProviderCapabilities } from '../types'
|
|
6
|
+
import type { TrackerConfig } from '../tracker-config'
|
|
7
|
+
import {
|
|
8
|
+
JIRA_CAPABILITIES,
|
|
9
|
+
LINEAR_CAPABILITIES,
|
|
10
|
+
LOCAL_CAPABILITIES,
|
|
11
|
+
POSTGRES_LOCAL_CAPABILITIES,
|
|
12
|
+
} from './capabilities'
|
|
13
|
+
import { JiraProvider, type JiraProviderConfig } from './jira'
|
|
14
|
+
import { LinearProvider } from './linear'
|
|
15
|
+
import { LocalProvider } from './local'
|
|
16
|
+
import { PostgresJiraProvider } from './postgres-jira'
|
|
17
|
+
import { PostgresLinearProvider } from './postgres-linear'
|
|
18
|
+
import { PostgresLocalProvider } from './postgres-local'
|
|
19
|
+
import type { KanbanProvider } from './types'
|
|
20
|
+
|
|
21
|
+
export interface InitializableKanbanProvider extends KanbanProvider {
|
|
22
|
+
initialize(): Promise<void>
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface ProviderBundle<TProvider extends KanbanProvider = KanbanProvider> {
|
|
26
|
+
provider: TProvider
|
|
27
|
+
capabilities: ProviderCapabilities
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface SqliteProviderOptions {
|
|
31
|
+
dbPath: string
|
|
32
|
+
seedLocalColumns?: boolean
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function jiraProviderConfig(
|
|
36
|
+
config: Extract<TrackerConfig, { provider: 'jira' }>,
|
|
37
|
+
): JiraProviderConfig {
|
|
38
|
+
return {
|
|
39
|
+
baseUrl: config.baseUrl,
|
|
40
|
+
email: config.email,
|
|
41
|
+
apiToken: config.apiToken,
|
|
42
|
+
projectKey: config.projectKey,
|
|
43
|
+
...(config.boardId !== undefined ? { boardId: config.boardId } : {}),
|
|
44
|
+
defaultIssueType: config.defaultIssueType ?? 'Task',
|
|
45
|
+
pollingSyncIntervalMs: config.syncIntervalMs,
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function createSqliteProvider(
|
|
50
|
+
db: Database,
|
|
51
|
+
config: TrackerConfig,
|
|
52
|
+
options: SqliteProviderOptions,
|
|
53
|
+
): ProviderBundle {
|
|
54
|
+
switch (config.provider) {
|
|
55
|
+
case 'linear':
|
|
56
|
+
return {
|
|
57
|
+
provider: new LinearProvider(db, config.teamId, config.apiKey, config.syncIntervalMs),
|
|
58
|
+
capabilities: LINEAR_CAPABILITIES,
|
|
59
|
+
}
|
|
60
|
+
case 'jira':
|
|
61
|
+
return {
|
|
62
|
+
provider: new JiraProvider(db, jiraProviderConfig(config)),
|
|
63
|
+
capabilities: JIRA_CAPABILITIES,
|
|
64
|
+
}
|
|
65
|
+
case 'local':
|
|
66
|
+
initSchema(db)
|
|
67
|
+
if (options.seedLocalColumns !== false) {
|
|
68
|
+
seedDefaultColumns(db, config.defaultColumns)
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
provider: new LocalProvider(db, options.dbPath),
|
|
72
|
+
capabilities: LOCAL_CAPABILITIES,
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function createPostgresProvider(
|
|
78
|
+
sql: Sql,
|
|
79
|
+
config: TrackerConfig,
|
|
80
|
+
): ProviderBundle<InitializableKanbanProvider> {
|
|
81
|
+
switch (config.provider) {
|
|
82
|
+
case 'linear':
|
|
83
|
+
return {
|
|
84
|
+
provider: new PostgresLinearProvider(
|
|
85
|
+
sql,
|
|
86
|
+
config.teamId,
|
|
87
|
+
config.apiKey,
|
|
88
|
+
config.syncIntervalMs,
|
|
89
|
+
),
|
|
90
|
+
capabilities: LINEAR_CAPABILITIES,
|
|
91
|
+
}
|
|
92
|
+
case 'jira':
|
|
93
|
+
return {
|
|
94
|
+
provider: new PostgresJiraProvider(sql, jiraProviderConfig(config)),
|
|
95
|
+
capabilities: JIRA_CAPABILITIES,
|
|
96
|
+
}
|
|
97
|
+
case 'local':
|
|
98
|
+
return {
|
|
99
|
+
provider: new PostgresLocalProvider(sql, config),
|
|
100
|
+
capabilities: POSTGRES_LOCAL_CAPABILITIES,
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
package/src/providers/index.ts
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import type { Database } from 'bun:sqlite'
|
|
2
|
-
import { getDbPath
|
|
3
|
-
import { JiraProvider } from './jira'
|
|
4
|
-
import { LinearProvider } from './linear'
|
|
5
|
-
import { LocalProvider } from './local'
|
|
2
|
+
import { getDbPath } from '../db'
|
|
6
3
|
import type { TrackerConfig } from '../tracker-config'
|
|
7
4
|
import type { KanbanProvider } from './types'
|
|
5
|
+
import { createSqliteProvider } from './factory'
|
|
8
6
|
|
|
9
7
|
export function createProvider(
|
|
10
8
|
db: Database,
|
|
@@ -12,25 +10,6 @@ export function createProvider(
|
|
|
12
10
|
dbPath = getDbPath(),
|
|
13
11
|
options: { seedLocalColumns?: boolean } = {},
|
|
14
12
|
): KanbanProvider {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
if (config.provider === 'jira') {
|
|
20
|
-
return new JiraProvider(db, {
|
|
21
|
-
baseUrl: config.baseUrl,
|
|
22
|
-
email: config.email,
|
|
23
|
-
apiToken: config.apiToken,
|
|
24
|
-
projectKey: config.projectKey,
|
|
25
|
-
...(config.boardId !== undefined ? { boardId: config.boardId } : {}),
|
|
26
|
-
defaultIssueType: config.defaultIssueType ?? 'Task',
|
|
27
|
-
pollingSyncIntervalMs: config.syncIntervalMs,
|
|
28
|
-
})
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
initSchema(db)
|
|
32
|
-
if (options.seedLocalColumns !== false) {
|
|
33
|
-
seedDefaultColumns(db, config.defaultColumns)
|
|
34
|
-
}
|
|
35
|
-
return new LocalProvider(db, dbPath)
|
|
13
|
+
return createSqliteProvider(db, config, { dbPath, seedLocalColumns: options.seedLocalColumns })
|
|
14
|
+
.provider
|
|
36
15
|
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import type { Database } from 'bun:sqlite'
|
|
2
|
+
import { normalizeColumnName } from '../column-roles'
|
|
2
3
|
import { ErrorCode, KanbanError } from '../errors'
|
|
3
4
|
import type { BoardView, ProviderTeamInfo, Task } from '../types'
|
|
5
|
+
import { jiraTaskFromRow, type JiraTaskRow } from './cache-task-mappers'
|
|
6
|
+
import { parseProviderTeamInfo } from './team-info'
|
|
4
7
|
|
|
5
8
|
// Column ids are prefixed to avoid collisions across sources:
|
|
6
9
|
// - board-sourced columns: 'board:<boardId>:<columnName>' with an index suffix
|
|
@@ -70,46 +73,51 @@ export function jiraBoardColumnRows(
|
|
|
70
73
|
})
|
|
71
74
|
}
|
|
72
75
|
|
|
76
|
+
function ambiguousColumnError(input: string, matches: JiraColumnRow[]): KanbanError {
|
|
77
|
+
return new KanbanError(
|
|
78
|
+
ErrorCode.COLUMN_NOT_FOUND,
|
|
79
|
+
`Jira column name '${input}' is ambiguous; use one of these column ids: ${matches
|
|
80
|
+
.map((column) => column.id)
|
|
81
|
+
.join(', ')}`,
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
73
85
|
// Resolves a user-supplied column reference to a cached column id, trying
|
|
74
|
-
// (1) exact id, (2) case-insensitive name, (3) raw status id containment
|
|
75
|
-
//
|
|
76
|
-
//
|
|
86
|
+
// (1) exact id, (2) case-insensitive name, (3) raw status id containment,
|
|
87
|
+
// (4) separator-insensitive name ('Todo' → 'To Do'). Exact passes (id, name,
|
|
88
|
+
// status id) run before the fuzzy normalized pass so a precise reference is
|
|
89
|
+
// never overridden by a normalized-name collision. Name lookups that match
|
|
90
|
+
// multiple distinct columns (possible when Jira returns duplicate board column
|
|
91
|
+
// names, or when two columns collapse to the same normalized token) are
|
|
92
|
+
// rejected as ambiguous so the caller picks an id.
|
|
77
93
|
export function resolveJiraColumnId(columns: JiraColumnRow[], input: string): string {
|
|
78
94
|
const byId = columns.find((column) => column.id === input)
|
|
79
95
|
if (byId) return byId.id
|
|
80
96
|
const lower = input.toLowerCase()
|
|
81
97
|
const byName = columns.filter((column) => column.name.toLowerCase() === lower)
|
|
82
98
|
if (byName.length === 1) return byName[0]!.id
|
|
83
|
-
if (byName.length > 1)
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
.map((column) => column.id)
|
|
88
|
-
.join(', ')}`,
|
|
89
|
-
)
|
|
90
|
-
}
|
|
99
|
+
if (byName.length > 1) throw ambiguousColumnError(input, byName)
|
|
100
|
+
// Exact status-id containment takes precedence over the fuzzy name pass: a raw
|
|
101
|
+
// status id is an unambiguous reference and must not be shadowed by a column
|
|
102
|
+
// whose name happens to normalize to the same token.
|
|
91
103
|
const byStatus = columns.find((column) => decodeColumnStatusIds(column).includes(input))
|
|
92
104
|
if (byStatus) return byStatus.id
|
|
105
|
+
// Status-fallback columns are named after Jira statuses ('To Do', 'In
|
|
106
|
+
// Progress'), but dispatch triggers pass collapsed strings like 'Todo'. Match
|
|
107
|
+
// separator-insensitively so the trigger column resolves instead of throwing
|
|
108
|
+
// COLUMN_NOT_FOUND (which silently strands every ticket in that column). Skip a
|
|
109
|
+
// token that collapses to empty so punctuation-only input never matches a
|
|
110
|
+
// separator-only column name.
|
|
111
|
+
const normalized = normalizeColumnName(input)
|
|
112
|
+
if (normalized !== '') {
|
|
113
|
+
const byNormalized = columns.filter((column) => normalizeColumnName(column.name) === normalized)
|
|
114
|
+
if (byNormalized.length === 1) return byNormalized[0]!.id
|
|
115
|
+
if (byNormalized.length > 1) throw ambiguousColumnError(input, byNormalized)
|
|
116
|
+
}
|
|
93
117
|
throw new KanbanError(ErrorCode.COLUMN_NOT_FOUND, `No Jira column matching '${input}'`)
|
|
94
118
|
}
|
|
95
119
|
|
|
96
|
-
|
|
97
|
-
id: string
|
|
98
|
-
key: string
|
|
99
|
-
summary: string
|
|
100
|
-
description_text: string
|
|
101
|
-
status_id: string
|
|
102
|
-
priority_name: string
|
|
103
|
-
issue_type_name: string
|
|
104
|
-
assignee_account_id: string | null
|
|
105
|
-
assignee_name: string
|
|
106
|
-
labels: string
|
|
107
|
-
comment_count: number
|
|
108
|
-
project_key: string
|
|
109
|
-
url: string | null
|
|
110
|
-
created_at: string
|
|
111
|
-
updated_at: string
|
|
112
|
-
}
|
|
120
|
+
type JiraIssueRow = JiraTaskRow
|
|
113
121
|
|
|
114
122
|
export function initJiraCacheSchema(db: Database): void {
|
|
115
123
|
db.run(`
|
|
@@ -252,27 +260,7 @@ export function saveTeamInfo(db: Database, team: ProviderTeamInfo | null): void
|
|
|
252
260
|
}
|
|
253
261
|
|
|
254
262
|
export function loadTeamInfo(db: Database): ProviderTeamInfo | null {
|
|
255
|
-
|
|
256
|
-
if (raw === null) return null
|
|
257
|
-
try {
|
|
258
|
-
const parsed = JSON.parse(raw) as unknown
|
|
259
|
-
if (
|
|
260
|
-
parsed &&
|
|
261
|
-
typeof parsed === 'object' &&
|
|
262
|
-
'id' in parsed &&
|
|
263
|
-
'key' in parsed &&
|
|
264
|
-
'name' in parsed &&
|
|
265
|
-
typeof (parsed as { id: unknown }).id === 'string' &&
|
|
266
|
-
typeof (parsed as { key: unknown }).key === 'string' &&
|
|
267
|
-
typeof (parsed as { name: unknown }).name === 'string'
|
|
268
|
-
) {
|
|
269
|
-
const t = parsed as { id: string; key: string; name: string }
|
|
270
|
-
return { id: t.id, key: t.key, name: t.name }
|
|
271
|
-
}
|
|
272
|
-
return null
|
|
273
|
-
} catch {
|
|
274
|
-
return null
|
|
275
|
-
}
|
|
263
|
+
return parseProviderTeamInfo(getMeta(db, 'team'))
|
|
276
264
|
}
|
|
277
265
|
|
|
278
266
|
export function loadJiraSyncMeta(db: Database): JiraSyncMeta {
|
|
@@ -522,52 +510,6 @@ export function getCachedColumns(db: Database): JiraColumnRow[] {
|
|
|
522
510
|
return db.query('SELECT * FROM jira_columns ORDER BY position, name').all() as JiraColumnRow[]
|
|
523
511
|
}
|
|
524
512
|
|
|
525
|
-
function mapPriorityNameToCanonical(name: string): Task['priority'] {
|
|
526
|
-
switch (name.trim().toLowerCase()) {
|
|
527
|
-
case 'highest':
|
|
528
|
-
return 'urgent'
|
|
529
|
-
case 'high':
|
|
530
|
-
return 'high'
|
|
531
|
-
case 'medium':
|
|
532
|
-
return 'medium'
|
|
533
|
-
default:
|
|
534
|
-
return 'low'
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
function parseLabels(raw: string): string[] {
|
|
539
|
-
try {
|
|
540
|
-
const parsed: unknown = JSON.parse(raw)
|
|
541
|
-
return Array.isArray(parsed) ? parsed.filter((v): v is string => typeof v === 'string') : []
|
|
542
|
-
} catch {
|
|
543
|
-
return []
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
function taskFromRow(row: JiraIssueRow): Task {
|
|
548
|
-
return {
|
|
549
|
-
id: `jira:${row.id}`,
|
|
550
|
-
providerId: row.id,
|
|
551
|
-
externalRef: row.key,
|
|
552
|
-
url: row.url,
|
|
553
|
-
title: row.summary,
|
|
554
|
-
description: row.description_text,
|
|
555
|
-
column_id: row.status_id,
|
|
556
|
-
position: 0,
|
|
557
|
-
priority: mapPriorityNameToCanonical(row.priority_name),
|
|
558
|
-
assignee: row.assignee_name,
|
|
559
|
-
assignees: row.assignee_name ? [row.assignee_name] : [],
|
|
560
|
-
labels: parseLabels(row.labels),
|
|
561
|
-
comment_count: row.comment_count,
|
|
562
|
-
project: row.project_key,
|
|
563
|
-
metadata: '{}',
|
|
564
|
-
created_at: row.created_at,
|
|
565
|
-
updated_at: row.updated_at,
|
|
566
|
-
version: row.updated_at,
|
|
567
|
-
source_updated_at: row.updated_at,
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
|
|
571
513
|
function selectIssuesByStatusIds(db: Database, statusIds: string[]): JiraIssueRow[] {
|
|
572
514
|
if (statusIds.length === 0) return []
|
|
573
515
|
const placeholders = statusIds.map((_, i) => `$s${i}`).join(', ')
|
|
@@ -589,7 +531,7 @@ export function getCachedBoard(db: Database): BoardView {
|
|
|
589
531
|
return {
|
|
590
532
|
columns: columns.map((column) => {
|
|
591
533
|
const statusIds = decodeColumnStatusIds(column)
|
|
592
|
-
const tasks = selectIssuesByStatusIds(db, statusIds).map(
|
|
534
|
+
const tasks = selectIssuesByStatusIds(db, statusIds).map(jiraTaskFromRow)
|
|
593
535
|
return {
|
|
594
536
|
id: column.id,
|
|
595
537
|
name: column.name,
|
|
@@ -612,7 +554,7 @@ export function getCachedTask(db: Database, lookup: string): Task | null {
|
|
|
612
554
|
LIMIT 1`,
|
|
613
555
|
)
|
|
614
556
|
.get({ $lookup: normalized }) as JiraIssueRow | null
|
|
615
|
-
return row ?
|
|
557
|
+
return row ? jiraTaskFromRow(row) : null
|
|
616
558
|
}
|
|
617
559
|
|
|
618
560
|
export function getCachedTasks(db: Database, params?: { columnId?: string }): Task[] {
|
|
@@ -622,13 +564,13 @@ export function getCachedTasks(db: Database, params?: { columnId?: string }): Ta
|
|
|
622
564
|
.get({ $id: params.columnId }) as Pick<JiraColumnRow, 'status_ids'> | null
|
|
623
565
|
if (!columnRow) return []
|
|
624
566
|
const statusIds = decodeColumnStatusIds(columnRow)
|
|
625
|
-
return selectIssuesByStatusIds(db, statusIds).map(
|
|
567
|
+
return selectIssuesByStatusIds(db, statusIds).map(jiraTaskFromRow)
|
|
626
568
|
}
|
|
627
569
|
return (
|
|
628
570
|
db
|
|
629
571
|
.query('SELECT * FROM jira_issues ORDER BY updated_at DESC, summary ASC')
|
|
630
572
|
.all() as JiraIssueRow[]
|
|
631
|
-
).map(
|
|
573
|
+
).map(jiraTaskFromRow)
|
|
632
574
|
}
|
|
633
575
|
|
|
634
576
|
export function getCachedConfig(db: Database): JiraCacheConfig {
|