@cat-factory/integrations 0.28.1 → 0.30.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/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/modules/accountSettings/AccountSettingsService.d.ts +2 -1
- package/dist/modules/accountSettings/AccountSettingsService.d.ts.map +1 -1
- package/dist/modules/accountSettings/AccountSettingsService.js +4 -2
- package/dist/modules/accountSettings/AccountSettingsService.js.map +1 -1
- package/dist/modules/documents/LinearDocumentProvider.js +3 -3
- package/dist/modules/documents/LinearDocumentProvider.js.map +1 -1
- package/dist/modules/environments/EnvironmentConnectionService.d.ts +6 -4
- package/dist/modules/environments/EnvironmentConnectionService.d.ts.map +1 -1
- package/dist/modules/environments/EnvironmentConnectionService.js +28 -1
- package/dist/modules/environments/EnvironmentConnectionService.js.map +1 -1
- package/dist/modules/shared/linear.client.d.ts +11 -0
- package/dist/modules/shared/linear.client.d.ts.map +1 -1
- package/dist/modules/shared/linear.client.js +15 -0
- package/dist/modules/shared/linear.client.js.map +1 -1
- package/dist/modules/tasks/GitHubIssuesProvider.d.ts +1 -0
- package/dist/modules/tasks/GitHubIssuesProvider.d.ts.map +1 -1
- package/dist/modules/tasks/JiraProvider.d.ts +1 -0
- package/dist/modules/tasks/JiraProvider.d.ts.map +1 -1
- package/dist/modules/tasks/LinearTaskProvider.d.ts +21 -0
- package/dist/modules/tasks/LinearTaskProvider.d.ts.map +1 -1
- package/dist/modules/tasks/LinearTaskProvider.js +106 -11
- package/dist/modules/tasks/LinearTaskProvider.js.map +1 -1
- package/dist/modules/tasks/TaskConnectionService.d.ts +37 -0
- package/dist/modules/tasks/TaskConnectionService.d.ts.map +1 -1
- package/dist/modules/tasks/TaskConnectionService.js +54 -2
- package/dist/modules/tasks/TaskConnectionService.js.map +1 -1
- package/dist/modules/tasks/linear.logic.d.ts +67 -12
- package/dist/modules/tasks/linear.logic.d.ts.map +1 -1
- package/dist/modules/tasks/linear.logic.js +83 -15
- package/dist/modules/tasks/linear.logic.js.map +1 -1
- package/dist/modules/tracker/TicketTrackerService.d.ts +3 -2
- package/dist/modules/tracker/TicketTrackerService.d.ts.map +1 -1
- package/dist/modules/tracker/TicketTrackerService.js +3 -3
- package/dist/modules/tracker/TicketTrackerService.js.map +1 -1
- package/dist/modules/writeback/IssueWritebackService.d.ts.map +1 -1
- package/dist/modules/writeback/IssueWritebackService.js +3 -3
- package/dist/modules/writeback/IssueWritebackService.js.map +1 -1
- package/package.json +3 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ValidationError, } from '@cat-factory/kernel';
|
|
2
|
-
import { LinearApiError, LinearGraphqlClient } from '../shared/linear.client.js';
|
|
3
|
-
import { LINEAR_ISSUE_QUERY, LINEAR_SEARCH_ISSUES_QUERY, LINEAR_TASK_DESCRIPTOR, LINEAR_VIEWER_QUERY, mapLinearIssue, mapLinearSearchResults, parseLinearRef, } from './linear.logic.js';
|
|
2
|
+
import { LinearApiError, LinearGraphqlClient, linearAuthFromCredentials, } from '../shared/linear.client.js';
|
|
3
|
+
import { LINEAR_ISSUE_CHILDREN_QUERY, LINEAR_ISSUE_COMMENTS_QUERY, LINEAR_ISSUE_QUERY, LINEAR_PAGE_CAP, LINEAR_SEARCH_ISSUES_QUERY, LINEAR_TASK_DESCRIPTOR, LINEAR_TEAMS_QUERY, LINEAR_VIEWER_QUERY, linearIssueSearchHit, mapLinearChildIds, mapLinearComments, mapLinearIssue, mapLinearSearchResults, mapLinearTeams, parseLinearRef, } from './linear.logic.js';
|
|
4
4
|
// LinearTaskProvider: the task-source provider for Linear. It authenticates with a
|
|
5
5
|
// personal API key against Linear's single GraphQL endpoint (via the shared
|
|
6
6
|
// host-pinned, redirect-safe client) and maps an issue onto the structured
|
|
@@ -15,26 +15,121 @@ export class LinearTaskProvider {
|
|
|
15
15
|
kind = 'linear';
|
|
16
16
|
descriptor = LINEAR_TASK_DESCRIPTOR;
|
|
17
17
|
normalizeConnection(input) {
|
|
18
|
+
// The OAuth connect flow writes a `{ token }` record directly (it never calls
|
|
19
|
+
// this), so the manual connect form only ever supplies an API key — but accept
|
|
20
|
+
// either so a token bag isn't rejected if it ever reaches here.
|
|
21
|
+
const token = input.token?.trim();
|
|
18
22
|
const apiKey = input.apiKey?.trim();
|
|
19
|
-
if (!apiKey) {
|
|
23
|
+
if (!token && !apiKey) {
|
|
20
24
|
throw new ValidationError('Linear requires a personal API key');
|
|
21
25
|
}
|
|
22
|
-
return {
|
|
26
|
+
return {
|
|
27
|
+
credentials: token ? { token } : { apiKey: apiKey },
|
|
28
|
+
label: 'Linear workspace',
|
|
29
|
+
};
|
|
23
30
|
}
|
|
24
31
|
parseRef(input) {
|
|
25
32
|
return parseLinearRef(input);
|
|
26
33
|
}
|
|
27
34
|
async fetchTask(credentials, externalId) {
|
|
28
|
-
const client = new LinearGraphqlClient(
|
|
35
|
+
const client = new LinearGraphqlClient(linearAuthFromCredentials(credentials));
|
|
29
36
|
const data = await client.query(LINEAR_ISSUE_QUERY, {
|
|
30
37
|
id: externalId,
|
|
31
38
|
});
|
|
32
|
-
|
|
39
|
+
const content = mapLinearIssue(data);
|
|
40
|
+
return this.paginate(client, externalId, content, data);
|
|
33
41
|
}
|
|
34
42
|
async search(credentials, query) {
|
|
35
|
-
const client = new LinearGraphqlClient(
|
|
43
|
+
const client = new LinearGraphqlClient(linearAuthFromCredentials(credentials));
|
|
44
|
+
const out = [];
|
|
45
|
+
const seen = new Set();
|
|
46
|
+
// Exact match first: a pasted issue identifier / URL resolves to one issue, which
|
|
47
|
+
// the picker offers as the top option ("point at it, don't search"). Best-effort —
|
|
48
|
+
// a miss (no such issue) falls through to the free-text search below.
|
|
49
|
+
const exactId = parseLinearRef(query);
|
|
50
|
+
if (exactId) {
|
|
51
|
+
const hit = await this.fetchExact(client, exactId).catch(() => null);
|
|
52
|
+
if (hit) {
|
|
53
|
+
seen.add(hit.externalId);
|
|
54
|
+
out.push(hit);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
36
57
|
const data = await client.query(LINEAR_SEARCH_ISSUES_QUERY, { term: query });
|
|
37
|
-
|
|
58
|
+
for (const hit of mapLinearSearchResults(data)) {
|
|
59
|
+
if (seen.has(hit.externalId))
|
|
60
|
+
continue;
|
|
61
|
+
seen.add(hit.externalId);
|
|
62
|
+
out.push(hit);
|
|
63
|
+
}
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
66
|
+
/** Fetch one issue by identifier and project it as a lean search hit (for the exact-match path). */
|
|
67
|
+
async fetchExact(client, externalId) {
|
|
68
|
+
const data = await client.query(LINEAR_ISSUE_QUERY, {
|
|
69
|
+
id: externalId,
|
|
70
|
+
});
|
|
71
|
+
return data.issue?.identifier ? linearIssueSearchHit(mapLinearIssue(data)) : null;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Linear's GraphQL connections (children, comments) return only the first page by
|
|
75
|
+
* default, so an epic with many sub-issues or a long comment thread is silently
|
|
76
|
+
* truncated. When the initial issue query reports more pages, walk the cursors to
|
|
77
|
+
* gather the rest (bounded by {@link LINEAR_PAGE_CAP} per connection). Best-effort —
|
|
78
|
+
* a failed page returns what was gathered so far, mirroring {@link JiraProvider}'s
|
|
79
|
+
* epic-children walk.
|
|
80
|
+
*/
|
|
81
|
+
async paginate(client, externalId, content, first) {
|
|
82
|
+
const childExternalIds = [...(content.childExternalIds ?? [])];
|
|
83
|
+
const comments = [...content.comments];
|
|
84
|
+
let childPage = first.issue?.children?.pageInfo ?? null;
|
|
85
|
+
for (let page = 0; childPage?.hasNextPage && childPage.endCursor && page < LINEAR_PAGE_CAP; page++) {
|
|
86
|
+
const data = await client
|
|
87
|
+
.query(LINEAR_ISSUE_CHILDREN_QUERY, {
|
|
88
|
+
id: externalId,
|
|
89
|
+
after: childPage.endCursor,
|
|
90
|
+
})
|
|
91
|
+
.catch(() => null);
|
|
92
|
+
if (!data)
|
|
93
|
+
break;
|
|
94
|
+
const conn = data.issue?.children;
|
|
95
|
+
for (const id of mapLinearChildIds(conn))
|
|
96
|
+
childExternalIds.push(id);
|
|
97
|
+
childPage = conn?.pageInfo ?? null;
|
|
98
|
+
}
|
|
99
|
+
let commentPage = first.issue?.comments?.pageInfo ?? null;
|
|
100
|
+
for (let page = 0; commentPage?.hasNextPage && commentPage.endCursor && page < LINEAR_PAGE_CAP; page++) {
|
|
101
|
+
const data = await client
|
|
102
|
+
.query(LINEAR_ISSUE_COMMENTS_QUERY, {
|
|
103
|
+
id: externalId,
|
|
104
|
+
after: commentPage.endCursor,
|
|
105
|
+
})
|
|
106
|
+
.catch(() => null);
|
|
107
|
+
if (!data)
|
|
108
|
+
break;
|
|
109
|
+
const conn = data.issue?.comments;
|
|
110
|
+
for (const c of mapLinearComments(conn))
|
|
111
|
+
comments.push(c);
|
|
112
|
+
commentPage = conn?.pageInfo ?? null;
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
...content,
|
|
116
|
+
comments,
|
|
117
|
+
childExternalIds,
|
|
118
|
+
isEpic: childExternalIds.length > 0,
|
|
119
|
+
type: childExternalIds.length > 0 ? 'Epic' : content.type,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* List the connection's Linear teams (id + name + key), so the ticket-filing UI can
|
|
124
|
+
* offer a team picker instead of asking the user to paste a raw team UUID. Linear-
|
|
125
|
+
* specific (no other task source has teams), so it is NOT on the generic
|
|
126
|
+
* {@link TaskSourceProvider} port — {@link TaskConnectionService} narrows to this
|
|
127
|
+
* class to reach it.
|
|
128
|
+
*/
|
|
129
|
+
async listTeams(credentials) {
|
|
130
|
+
const client = new LinearGraphqlClient(linearAuthFromCredentials(credentials));
|
|
131
|
+
const data = await client.query(LINEAR_TEAMS_QUERY);
|
|
132
|
+
return mapLinearTeams(data);
|
|
38
133
|
}
|
|
39
134
|
/**
|
|
40
135
|
* Live setup check: read `viewer` (the cheapest authenticated query) with the
|
|
@@ -42,8 +137,8 @@ export class LinearTaskProvider {
|
|
|
42
137
|
* a thrown fetch (DNS/network) ⇒ unreachable. Resolves (never rejects), per the port.
|
|
43
138
|
*/
|
|
44
139
|
async diagnose(input) {
|
|
45
|
-
const
|
|
46
|
-
if (!apiKey) {
|
|
140
|
+
const credentials = input.credentials;
|
|
141
|
+
if (!credentials?.apiKey && !credentials?.token) {
|
|
47
142
|
return {
|
|
48
143
|
source: 'linear',
|
|
49
144
|
ok: false,
|
|
@@ -52,7 +147,7 @@ export class LinearTaskProvider {
|
|
|
52
147
|
};
|
|
53
148
|
}
|
|
54
149
|
try {
|
|
55
|
-
const client = new LinearGraphqlClient(
|
|
150
|
+
const client = new LinearGraphqlClient(linearAuthFromCredentials(credentials));
|
|
56
151
|
const data = await client.query(LINEAR_VIEWER_QUERY);
|
|
57
152
|
return {
|
|
58
153
|
source: 'linear',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LinearTaskProvider.js","sourceRoot":"","sources":["../../../src/modules/tasks/LinearTaskProvider.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,GAOhB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,
|
|
1
|
+
{"version":3,"file":"LinearTaskProvider.js","sourceRoot":"","sources":["../../../src/modules/tasks/LinearTaskProvider.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,6 +5,8 @@ import type { GitHubInstallationRepository } from '@cat-factory/kernel';
|
|
|
5
5
|
import type { TaskSourceRegistry } from '@cat-factory/kernel';
|
|
6
6
|
import type { TaskConnection, TaskSourceDiagnostic, TaskSourceKind, TaskSourceState } from '@cat-factory/kernel';
|
|
7
7
|
import type { WorkspaceRepository } from '@cat-factory/kernel';
|
|
8
|
+
import type { LinearOAuthSecret } from '@cat-factory/contracts';
|
|
9
|
+
import type { LinearTeam } from './linear.logic.js';
|
|
8
10
|
export interface TaskConnectionServiceDependencies {
|
|
9
11
|
taskConnectionRepository: TaskConnectionRepository;
|
|
10
12
|
/** Per-workspace on/off toggle for each source (absent row ⇒ enabled). */
|
|
@@ -19,6 +21,14 @@ export interface TaskConnectionServiceDependencies {
|
|
|
19
21
|
* provider is even registered — is reported unavailable.
|
|
20
22
|
*/
|
|
21
23
|
installations?: GitHubInstallationRepository;
|
|
24
|
+
/**
|
|
25
|
+
* Resolves the account's Linear OAuth app credentials (the "Connect with Linear"
|
|
26
|
+
* flow), keyed by the account-scope key, or undefined when the account hasn't
|
|
27
|
+
* registered one. Backed by the per-account deployment settings (sealed in the DB,
|
|
28
|
+
* set in the UI) — NOT env — mirroring the Slack OAuth model. Absent ⇒ OAuth
|
|
29
|
+
* onboarding isn't offered (the manual personal-API-key path still works).
|
|
30
|
+
*/
|
|
31
|
+
resolveLinearOAuth?: (accountKey: string) => Promise<LinearOAuthSecret | undefined>;
|
|
22
32
|
}
|
|
23
33
|
export declare class TaskConnectionService {
|
|
24
34
|
private readonly deps;
|
|
@@ -57,6 +67,33 @@ export declare class TaskConnectionService {
|
|
|
57
67
|
private requireProvider;
|
|
58
68
|
/** Connect (or re-connect) a workspace to a task source. */
|
|
59
69
|
connect(workspaceId: string, source: TaskSourceKind, credentials: Record<string, string>): Promise<TaskConnection>;
|
|
70
|
+
/**
|
|
71
|
+
* Complete the Linear OAuth flow: persist the exchanged access token as the
|
|
72
|
+
* workspace's Linear connection (a `{ token }` credential bag, used as a `Bearer`
|
|
73
|
+
* token by the shared client). The token exchange itself happens in the server's
|
|
74
|
+
* OAuth callback (which holds the OAuth client + secret); this only stores the
|
|
75
|
+
* result, so the integrations package stays free of OAuth config.
|
|
76
|
+
*/
|
|
77
|
+
connectLinearViaOAuth(workspaceId: string, token: string): Promise<TaskConnection>;
|
|
78
|
+
/**
|
|
79
|
+
* List the workspace's Linear teams, for the ticket-filing team picker. Linear-
|
|
80
|
+
* specific, so it duck-types the provider's `listTeams` rather than widening the
|
|
81
|
+
* generic port (and `instanceof` is unreliable once the class is bundled across
|
|
82
|
+
* module boundaries). Throws when Linear isn't connected; returns [] if the wired
|
|
83
|
+
* provider can't list teams.
|
|
84
|
+
*/
|
|
85
|
+
listLinearTeams(workspaceId: string): Promise<LinearTeam[]>;
|
|
86
|
+
/**
|
|
87
|
+
* Resolve the account's Linear OAuth app credentials for a workspace (the per-account
|
|
88
|
+
* deployment setting), or undefined when none is configured. Keyed by the account-scope
|
|
89
|
+
* key (the workspace's account id, else the workspace id) — mirroring the Slack model — so
|
|
90
|
+
* an org registers ONE Linear OAuth app shared by its workspaces.
|
|
91
|
+
*/
|
|
92
|
+
resolveLinearOAuthConfig(workspaceId: string): Promise<LinearOAuthSecret | undefined>;
|
|
93
|
+
/** The per-account scope key for a workspace (account id, else the workspace id). */
|
|
94
|
+
private resolveAccountKey;
|
|
95
|
+
/** Build + upsert a connection record (shared by the manual connect + OAuth paths). */
|
|
96
|
+
private store;
|
|
60
97
|
/** The workspace's current connection for a source, or null if not connected. */
|
|
61
98
|
getConnection(workspaceId: string, source: TaskSourceKind): Promise<TaskConnection | null>;
|
|
62
99
|
/** Every live connection the workspace holds, across sources. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TaskConnectionService.d.ts","sourceRoot":"","sources":["../../../src/modules/tasks/TaskConnectionService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAA;AAChD,OAAO,KAAK,EAAE,oBAAoB,EAAE,wBAAwB,EAAE,MAAM,qBAAqB,CAAA;AACzF,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,qBAAqB,CAAA;AACvE,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,qBAAqB,CAAA;AACvE,OAAO,KAAK,EAAuC,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AAClG,OAAO,KAAK,EACV,cAAc,EACd,oBAAoB,EACpB,cAAc,EACd,eAAe,EAChB,MAAM,qBAAqB,CAAA;AAG5B,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"TaskConnectionService.d.ts","sourceRoot":"","sources":["../../../src/modules/tasks/TaskConnectionService.ts"],"names":[],"mappings":"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"}
|
|
@@ -19,6 +19,10 @@ function isCredentialless(provider) {
|
|
|
19
19
|
function ridesGitHubApp(provider) {
|
|
20
20
|
return provider.kind === 'github' && isCredentialless(provider);
|
|
21
21
|
}
|
|
22
|
+
/** Duck-type the optional `listTeams` capability (bundling-safe, unlike `instanceof`). */
|
|
23
|
+
function hasListTeams(provider) {
|
|
24
|
+
return typeof provider.listTeams === 'function';
|
|
25
|
+
}
|
|
22
26
|
function toConnection(record) {
|
|
23
27
|
return {
|
|
24
28
|
source: record.source,
|
|
@@ -161,12 +165,60 @@ export class TaskConnectionService {
|
|
|
161
165
|
throw new ValidationError(`The ${source} source has no connection to configure; it uses the workspace's installed GitHub App. Enable or disable it instead.`);
|
|
162
166
|
}
|
|
163
167
|
const normalized = provider.normalizeConnection(credentials);
|
|
168
|
+
return this.store(workspaceId, source, normalized.credentials, normalized.label);
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Complete the Linear OAuth flow: persist the exchanged access token as the
|
|
172
|
+
* workspace's Linear connection (a `{ token }` credential bag, used as a `Bearer`
|
|
173
|
+
* token by the shared client). The token exchange itself happens in the server's
|
|
174
|
+
* OAuth callback (which holds the OAuth client + secret); this only stores the
|
|
175
|
+
* result, so the integrations package stays free of OAuth config.
|
|
176
|
+
*/
|
|
177
|
+
async connectLinearViaOAuth(workspaceId, token) {
|
|
178
|
+
await requireWorkspace(this.deps.workspaceRepository, workspaceId);
|
|
179
|
+
this.requireProvider('linear');
|
|
180
|
+
return this.store(workspaceId, 'linear', { token }, 'Linear workspace');
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* List the workspace's Linear teams, for the ticket-filing team picker. Linear-
|
|
184
|
+
* specific, so it duck-types the provider's `listTeams` rather than widening the
|
|
185
|
+
* generic port (and `instanceof` is unreliable once the class is bundled across
|
|
186
|
+
* module boundaries). Throws when Linear isn't connected; returns [] if the wired
|
|
187
|
+
* provider can't list teams.
|
|
188
|
+
*/
|
|
189
|
+
async listLinearTeams(workspaceId) {
|
|
190
|
+
const provider = this.requireProvider('linear');
|
|
191
|
+
const connection = await this.deps.taskConnectionRepository.getByWorkspace(workspaceId, 'linear');
|
|
192
|
+
if (!connection)
|
|
193
|
+
throw new ConflictError(`Workspace '${workspaceId}' is not connected to linear`);
|
|
194
|
+
if (!hasListTeams(provider))
|
|
195
|
+
return [];
|
|
196
|
+
return provider.listTeams(connection.credentials);
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Resolve the account's Linear OAuth app credentials for a workspace (the per-account
|
|
200
|
+
* deployment setting), or undefined when none is configured. Keyed by the account-scope
|
|
201
|
+
* key (the workspace's account id, else the workspace id) — mirroring the Slack model — so
|
|
202
|
+
* an org registers ONE Linear OAuth app shared by its workspaces.
|
|
203
|
+
*/
|
|
204
|
+
async resolveLinearOAuthConfig(workspaceId) {
|
|
205
|
+
if (!this.deps.resolveLinearOAuth)
|
|
206
|
+
return undefined;
|
|
207
|
+
return this.deps.resolveLinearOAuth(await this.resolveAccountKey(workspaceId));
|
|
208
|
+
}
|
|
209
|
+
/** The per-account scope key for a workspace (account id, else the workspace id). */
|
|
210
|
+
async resolveAccountKey(workspaceId) {
|
|
211
|
+
await requireWorkspace(this.deps.workspaceRepository, workspaceId);
|
|
212
|
+
return (await this.deps.workspaceRepository.accountOf(workspaceId)) ?? workspaceId;
|
|
213
|
+
}
|
|
214
|
+
/** Build + upsert a connection record (shared by the manual connect + OAuth paths). */
|
|
215
|
+
async store(workspaceId, source, credentials, label) {
|
|
164
216
|
const existing = await this.deps.taskConnectionRepository.getByWorkspace(workspaceId, source);
|
|
165
217
|
const record = {
|
|
166
218
|
workspaceId,
|
|
167
219
|
source,
|
|
168
|
-
credentials
|
|
169
|
-
label
|
|
220
|
+
credentials,
|
|
221
|
+
label,
|
|
170
222
|
createdAt: existing?.createdAt ?? this.deps.clock.now(),
|
|
171
223
|
deletedAt: null,
|
|
172
224
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TaskConnectionService.js","sourceRoot":"","sources":["../../../src/modules/tasks/TaskConnectionService.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AACpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"TaskConnectionService.js","sourceRoot":"","sources":["../../../src/modules/tasks/TaskConnectionService.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,16 +1,47 @@
|
|
|
1
|
-
import type { TaskContent, TaskDependencyLink, TaskSearchResult, TaskSourceDescriptor } from '@cat-factory/kernel';
|
|
1
|
+
import type { TaskComment, TaskContent, TaskDependencyLink, TaskSearchResult, TaskSourceDescriptor } from '@cat-factory/kernel';
|
|
2
2
|
/** What the connect UI renders, and which credentials the provider needs. */
|
|
3
3
|
export declare const LINEAR_TASK_DESCRIPTOR: TaskSourceDescriptor;
|
|
4
|
+
/**
|
|
5
|
+
* How many extra connection pages to walk per issue (children / comments), bounding
|
|
6
|
+
* the per-import fan-out. Mirrors {@link JiraProvider}'s `CHILD_PAGE_CAP` — an epic
|
|
7
|
+
* with up to ~2100 children/comments imports fully; beyond that it is truncated.
|
|
8
|
+
*/
|
|
9
|
+
export declare const LINEAR_PAGE_CAP = 20;
|
|
4
10
|
/**
|
|
5
11
|
* Fetch a single issue with everything the structured {@link TaskContent} needs.
|
|
6
12
|
* Linear's `issue(id:)` resolves the human identifier (`ENG-123`) as well as the
|
|
7
|
-
* UUID, so the stored external id is a valid argument.
|
|
13
|
+
* UUID, so the stored external id is a valid argument. The `children` and `comments`
|
|
14
|
+
* connections request the max page plus a `pageInfo` cursor so the provider can walk
|
|
15
|
+
* the rest (Linear returns only the first page by default — see {@link LinearTaskProvider}).
|
|
8
16
|
*/
|
|
9
|
-
export declare const LINEAR_ISSUE_QUERY = "query Issue($id: String!) {\n issue(id: $id) {\n identifier\n title\n description\n url\n priorityLabel\n state { name type }\n assignee { name }\n labels { nodes { name } }\n parent { identifier }\n children { nodes { identifier } }\n comments { nodes { user { name } createdAt body } }\n relations { nodes { type relatedIssue { identifier } } }\n inverseRelations { nodes { type issue { identifier } } }\n }\n}";
|
|
17
|
+
export declare const LINEAR_ISSUE_QUERY = "query Issue($id: String!) {\n issue(id: $id) {\n identifier\n title\n description\n url\n priorityLabel\n state { name type }\n assignee { name }\n labels(first: 100) { nodes { name } }\n parent { identifier }\n children(first: 100) { nodes { identifier } pageInfo { hasNextPage endCursor } }\n comments(first: 100) { nodes { user { name } createdAt body } pageInfo { hasNextPage endCursor } }\n relations(first: 100) { nodes { type relatedIssue { identifier } } }\n inverseRelations(first: 100) { nodes { type issue { identifier } } }\n }\n}";
|
|
18
|
+
/** One more page of an issue's child identifiers (the children-pagination walk). */
|
|
19
|
+
export declare const LINEAR_ISSUE_CHILDREN_QUERY = "query IssueChildren($id: String!, $after: String!) {\n issue(id: $id) {\n children(first: 100, after: $after) {\n nodes { identifier }\n pageInfo { hasNextPage endCursor }\n }\n }\n}";
|
|
20
|
+
/** One more page of an issue's comments (the comments-pagination walk). */
|
|
21
|
+
export declare const LINEAR_ISSUE_COMMENTS_QUERY = "query IssueComments($id: String!, $after: String!) {\n issue(id: $id) {\n comments(first: 100, after: $after) {\n nodes { user { name } createdAt body }\n pageInfo { hasNextPage endCursor }\n }\n }\n}";
|
|
10
22
|
/** Free-text issue search (used to populate the import picker). */
|
|
11
23
|
export declare const LINEAR_SEARCH_ISSUES_QUERY = "query SearchIssues($term: String!) {\n searchIssues(term: $term, first: 20) {\n nodes { identifier title url state { name } }\n }\n}";
|
|
24
|
+
/** List the connection's teams (for the ticket-filing team picker). */
|
|
25
|
+
export declare const LINEAR_TEAMS_QUERY = "query Teams { teams(first: 250) { nodes { id name key } } }";
|
|
12
26
|
/** Cheapest authenticated read, for the live "check setup" probe. */
|
|
13
27
|
export declare const LINEAR_VIEWER_QUERY = "query Viewer { viewer { name } }";
|
|
28
|
+
/** A connection's cursor info, present on the paginated children/comments connections. */
|
|
29
|
+
export interface LinearPageInfo {
|
|
30
|
+
hasNextPage?: boolean;
|
|
31
|
+
endCursor?: string | null;
|
|
32
|
+
}
|
|
33
|
+
/** One page of an issue's child identifiers. */
|
|
34
|
+
export interface LinearChildrenPage {
|
|
35
|
+
nodes?: {
|
|
36
|
+
identifier?: string;
|
|
37
|
+
}[];
|
|
38
|
+
pageInfo?: LinearPageInfo | null;
|
|
39
|
+
}
|
|
40
|
+
/** One page of an issue's comments. */
|
|
41
|
+
export interface LinearCommentsPage {
|
|
42
|
+
nodes?: LinearCommentNode[];
|
|
43
|
+
pageInfo?: LinearPageInfo | null;
|
|
44
|
+
}
|
|
14
45
|
interface LinearIssueNode {
|
|
15
46
|
identifier?: string;
|
|
16
47
|
title?: string;
|
|
@@ -32,14 +63,8 @@ interface LinearIssueNode {
|
|
|
32
63
|
parent?: {
|
|
33
64
|
identifier?: string;
|
|
34
65
|
} | null;
|
|
35
|
-
children?:
|
|
36
|
-
|
|
37
|
-
identifier?: string;
|
|
38
|
-
}[];
|
|
39
|
-
};
|
|
40
|
-
comments?: {
|
|
41
|
-
nodes?: LinearCommentNode[];
|
|
42
|
-
};
|
|
66
|
+
children?: LinearChildrenPage;
|
|
67
|
+
comments?: LinearCommentsPage;
|
|
43
68
|
relations?: {
|
|
44
69
|
nodes?: {
|
|
45
70
|
type?: string;
|
|
@@ -64,6 +89,10 @@ interface LinearCommentNode {
|
|
|
64
89
|
createdAt?: string;
|
|
65
90
|
body?: string | null;
|
|
66
91
|
}
|
|
92
|
+
/** Map one page of a `children` connection onto child identifiers (drops blanks). */
|
|
93
|
+
export declare function mapLinearChildIds(page: LinearChildrenPage | null | undefined): string[];
|
|
94
|
+
/** Map one page of a `comments` connection onto normalized {@link TaskComment}s. */
|
|
95
|
+
export declare function mapLinearComments(page: LinearCommentsPage | null | undefined): TaskComment[];
|
|
67
96
|
/**
|
|
68
97
|
* Resolve a Linear issue identifier from raw user input: a bare identifier
|
|
69
98
|
* (`ENG-123`), or a `linear.app/.../issue/ENG-123` URL. The identifier is
|
|
@@ -83,7 +112,11 @@ export declare function parseLinearRef(input: string): string | null;
|
|
|
83
112
|
* malformed entries are dropped, and duplicates are de-duped.
|
|
84
113
|
*/
|
|
85
114
|
export declare function mapLinearRelations(issue: LinearIssueNode): TaskDependencyLink[];
|
|
86
|
-
/**
|
|
115
|
+
/**
|
|
116
|
+
* Map an `issue` GraphQL payload onto the structured {@link TaskContent}. Only the
|
|
117
|
+
* FIRST page of the `children`/`comments` connections is reflected here; the provider
|
|
118
|
+
* walks any further pages (see {@link LinearTaskProvider}) and appends them.
|
|
119
|
+
*/
|
|
87
120
|
export declare function mapLinearIssue(data: {
|
|
88
121
|
issue?: LinearIssueNode | null;
|
|
89
122
|
}): TaskContent;
|
|
@@ -101,5 +134,27 @@ export declare function mapLinearSearchResults(data: {
|
|
|
101
134
|
nodes?: LinearSearchNode[];
|
|
102
135
|
};
|
|
103
136
|
}): TaskSearchResult[];
|
|
137
|
+
/**
|
|
138
|
+
* Project a fully-fetched {@link TaskContent} onto a lean {@link TaskSearchResult}, so the
|
|
139
|
+
* exact-match path (a pasted identifier / URL resolved by {@link parseLinearRef}) can
|
|
140
|
+
* surface the issue as a search hit alongside the free-text results.
|
|
141
|
+
*/
|
|
142
|
+
export declare function linearIssueSearchHit(content: TaskContent): TaskSearchResult;
|
|
143
|
+
/** A Linear team, as offered in the ticket-filing team picker. */
|
|
144
|
+
export interface LinearTeam {
|
|
145
|
+
id: string;
|
|
146
|
+
name: string;
|
|
147
|
+
key: string;
|
|
148
|
+
}
|
|
149
|
+
/** Map a `teams` payload onto the picker shape (drops id-less nodes). */
|
|
150
|
+
export declare function mapLinearTeams(data: {
|
|
151
|
+
teams?: {
|
|
152
|
+
nodes?: {
|
|
153
|
+
id?: string;
|
|
154
|
+
name?: string;
|
|
155
|
+
key?: string;
|
|
156
|
+
}[];
|
|
157
|
+
};
|
|
158
|
+
}): LinearTeam[];
|
|
104
159
|
export {};
|
|
105
160
|
//# sourceMappingURL=linear.logic.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"linear.logic.d.ts","sourceRoot":"","sources":["../../../src/modules/tasks/linear.logic.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"linear.logic.d.ts","sourceRoot":"","sources":["../../../src/modules/tasks/linear.logic.ts"],"names":[],"mappings":"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"}
|
|
@@ -20,12 +20,25 @@ export const LINEAR_TASK_DESCRIPTOR = {
|
|
|
20
20
|
refLabel: 'Issue identifier or URL',
|
|
21
21
|
refPlaceholder: 'ENG-123 or https://linear.app/acme/issue/ENG-123',
|
|
22
22
|
searchable: true,
|
|
23
|
+
// Offers the "Connect with Linear" OAuth button (when the deployment configured a
|
|
24
|
+
// Linear OAuth app); the personal-API-key field above stays as the manual fallback.
|
|
25
|
+
oauth: true,
|
|
23
26
|
};
|
|
24
27
|
// ---- GraphQL operations ----------------------------------------------------
|
|
28
|
+
/** Largest page Linear allows on a connection, and the page we always request. */
|
|
29
|
+
const PAGE_SIZE = 100;
|
|
30
|
+
/**
|
|
31
|
+
* How many extra connection pages to walk per issue (children / comments), bounding
|
|
32
|
+
* the per-import fan-out. Mirrors {@link JiraProvider}'s `CHILD_PAGE_CAP` — an epic
|
|
33
|
+
* with up to ~2100 children/comments imports fully; beyond that it is truncated.
|
|
34
|
+
*/
|
|
35
|
+
export const LINEAR_PAGE_CAP = 20;
|
|
25
36
|
/**
|
|
26
37
|
* Fetch a single issue with everything the structured {@link TaskContent} needs.
|
|
27
38
|
* Linear's `issue(id:)` resolves the human identifier (`ENG-123`) as well as the
|
|
28
|
-
* UUID, so the stored external id is a valid argument.
|
|
39
|
+
* UUID, so the stored external id is a valid argument. The `children` and `comments`
|
|
40
|
+
* connections request the max page plus a `pageInfo` cursor so the provider can walk
|
|
41
|
+
* the rest (Linear returns only the first page by default — see {@link LinearTaskProvider}).
|
|
29
42
|
*/
|
|
30
43
|
export const LINEAR_ISSUE_QUERY = `query Issue($id: String!) {
|
|
31
44
|
issue(id: $id) {
|
|
@@ -36,12 +49,30 @@ export const LINEAR_ISSUE_QUERY = `query Issue($id: String!) {
|
|
|
36
49
|
priorityLabel
|
|
37
50
|
state { name type }
|
|
38
51
|
assignee { name }
|
|
39
|
-
labels { nodes { name } }
|
|
52
|
+
labels(first: ${PAGE_SIZE}) { nodes { name } }
|
|
40
53
|
parent { identifier }
|
|
41
|
-
children { nodes { identifier } }
|
|
42
|
-
comments { nodes { user { name } createdAt body } }
|
|
43
|
-
relations { nodes { type relatedIssue { identifier } } }
|
|
44
|
-
inverseRelations { nodes { type issue { identifier } } }
|
|
54
|
+
children(first: ${PAGE_SIZE}) { nodes { identifier } pageInfo { hasNextPage endCursor } }
|
|
55
|
+
comments(first: ${PAGE_SIZE}) { nodes { user { name } createdAt body } pageInfo { hasNextPage endCursor } }
|
|
56
|
+
relations(first: ${PAGE_SIZE}) { nodes { type relatedIssue { identifier } } }
|
|
57
|
+
inverseRelations(first: ${PAGE_SIZE}) { nodes { type issue { identifier } } }
|
|
58
|
+
}
|
|
59
|
+
}`;
|
|
60
|
+
/** One more page of an issue's child identifiers (the children-pagination walk). */
|
|
61
|
+
export const LINEAR_ISSUE_CHILDREN_QUERY = `query IssueChildren($id: String!, $after: String!) {
|
|
62
|
+
issue(id: $id) {
|
|
63
|
+
children(first: ${PAGE_SIZE}, after: $after) {
|
|
64
|
+
nodes { identifier }
|
|
65
|
+
pageInfo { hasNextPage endCursor }
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}`;
|
|
69
|
+
/** One more page of an issue's comments (the comments-pagination walk). */
|
|
70
|
+
export const LINEAR_ISSUE_COMMENTS_QUERY = `query IssueComments($id: String!, $after: String!) {
|
|
71
|
+
issue(id: $id) {
|
|
72
|
+
comments(first: ${PAGE_SIZE}, after: $after) {
|
|
73
|
+
nodes { user { name } createdAt body }
|
|
74
|
+
pageInfo { hasNextPage endCursor }
|
|
75
|
+
}
|
|
45
76
|
}
|
|
46
77
|
}`;
|
|
47
78
|
/** Free-text issue search (used to populate the import picker). */
|
|
@@ -50,8 +81,22 @@ export const LINEAR_SEARCH_ISSUES_QUERY = `query SearchIssues($term: String!) {
|
|
|
50
81
|
nodes { identifier title url state { name } }
|
|
51
82
|
}
|
|
52
83
|
}`;
|
|
84
|
+
/** List the connection's teams (for the ticket-filing team picker). */
|
|
85
|
+
export const LINEAR_TEAMS_QUERY = `query Teams { teams(first: 250) { nodes { id name key } } }`;
|
|
53
86
|
/** Cheapest authenticated read, for the live "check setup" probe. */
|
|
54
87
|
export const LINEAR_VIEWER_QUERY = `query Viewer { viewer { name } }`;
|
|
88
|
+
/** Map one page of a `children` connection onto child identifiers (drops blanks). */
|
|
89
|
+
export function mapLinearChildIds(page) {
|
|
90
|
+
return (page?.nodes ?? []).map((c) => c.identifier).filter((id) => !!id);
|
|
91
|
+
}
|
|
92
|
+
/** Map one page of a `comments` connection onto normalized {@link TaskComment}s. */
|
|
93
|
+
export function mapLinearComments(page) {
|
|
94
|
+
return (page?.nodes ?? []).map((c) => ({
|
|
95
|
+
author: c.user?.name ?? '',
|
|
96
|
+
createdAt: c.createdAt ?? '',
|
|
97
|
+
body: normalizeMarkdown(c.body),
|
|
98
|
+
}));
|
|
99
|
+
}
|
|
55
100
|
/**
|
|
56
101
|
* Resolve a Linear issue identifier from raw user input: a bare identifier
|
|
57
102
|
* (`ENG-123`), or a `linear.app/.../issue/ENG-123` URL. The identifier is
|
|
@@ -123,20 +168,18 @@ export function mapLinearRelations(issue) {
|
|
|
123
168
|
}
|
|
124
169
|
return out;
|
|
125
170
|
}
|
|
126
|
-
/**
|
|
171
|
+
/**
|
|
172
|
+
* Map an `issue` GraphQL payload onto the structured {@link TaskContent}. Only the
|
|
173
|
+
* FIRST page of the `children`/`comments` connections is reflected here; the provider
|
|
174
|
+
* walks any further pages (see {@link LinearTaskProvider}) and appends them.
|
|
175
|
+
*/
|
|
127
176
|
export function mapLinearIssue(data) {
|
|
128
177
|
const issue = data.issue;
|
|
129
178
|
if (!issue?.identifier)
|
|
130
179
|
throw new Error('Linear returned no issue for the requested identifier');
|
|
131
|
-
const childExternalIds = (issue.children
|
|
132
|
-
.map((c) => c.identifier)
|
|
133
|
-
.filter((id) => !!id);
|
|
180
|
+
const childExternalIds = mapLinearChildIds(issue.children);
|
|
134
181
|
const isEpic = childExternalIds.length > 0;
|
|
135
|
-
const comments = (issue.comments
|
|
136
|
-
author: c.user?.name ?? '',
|
|
137
|
-
createdAt: c.createdAt ?? '',
|
|
138
|
-
body: normalizeMarkdown(c.body),
|
|
139
|
-
}));
|
|
182
|
+
const comments = mapLinearComments(issue.comments);
|
|
140
183
|
return {
|
|
141
184
|
externalId: issue.identifier,
|
|
142
185
|
url: issue.url ?? `https://linear.app/issue/${issue.identifier}`,
|
|
@@ -173,4 +216,29 @@ export function mapLinearSearchResults(data) {
|
|
|
173
216
|
}
|
|
174
217
|
return out;
|
|
175
218
|
}
|
|
219
|
+
/**
|
|
220
|
+
* Project a fully-fetched {@link TaskContent} onto a lean {@link TaskSearchResult}, so the
|
|
221
|
+
* exact-match path (a pasted identifier / URL resolved by {@link parseLinearRef}) can
|
|
222
|
+
* surface the issue as a search hit alongside the free-text results.
|
|
223
|
+
*/
|
|
224
|
+
export function linearIssueSearchHit(content) {
|
|
225
|
+
return {
|
|
226
|
+
source: 'linear',
|
|
227
|
+
externalId: content.externalId,
|
|
228
|
+
title: content.title,
|
|
229
|
+
url: content.url,
|
|
230
|
+
status: content.status,
|
|
231
|
+
excerpt: '',
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
/** Map a `teams` payload onto the picker shape (drops id-less nodes). */
|
|
235
|
+
export function mapLinearTeams(data) {
|
|
236
|
+
const out = [];
|
|
237
|
+
for (const node of data.teams?.nodes ?? []) {
|
|
238
|
+
if (!node.id)
|
|
239
|
+
continue;
|
|
240
|
+
out.push({ id: node.id, name: node.name ?? node.id, key: node.key ?? '' });
|
|
241
|
+
}
|
|
242
|
+
return out;
|
|
243
|
+
}
|
|
176
244
|
//# sourceMappingURL=linear.logic.js.map
|