@gitkraken/core-gitlens 0.22.1 → 0.24.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/CHANGELOG.md +16 -1
- package/dist/plus/integrations/index.d.ts +3 -3
- package/dist/plus/integrations/index.js.map +1 -1
- package/dist/plus/integrations/integrationService.d.ts +10 -2
- package/dist/plus/integrations/integrationService.js +9 -1
- package/dist/plus/integrations/integrationService.js.map +1 -1
- package/dist/plus/integrations/manager.d.ts +25 -1
- package/dist/plus/integrations/models/issueReads.d.ts +5 -0
- package/dist/plus/integrations/providers/linear.d.ts +39 -6
- package/dist/plus/integrations/providers/linear.js +129 -15
- package/dist/plus/integrations/providers/linear.js.map +1 -1
- package/dist/plus/integrations/providers/models.d.ts +4 -1
- package/dist/plus/integrations/providers/models.js.map +1 -1
- package/dist/plus/integrations/providers/providersApi.d.ts +9 -1
- package/dist/plus/integrations/providers/providersApi.js +8 -0
- package/dist/plus/integrations/providers/providersApi.js.map +1 -1
- package/dist/plus/integrations/reads/cursors.d.ts +8 -0
- package/dist/plus/integrations/reads/cursors.js +14 -4
- package/dist/plus/integrations/reads/cursors.js.map +1 -1
- package/dist/plus/integrations/reads/hierarchy.d.ts +8 -2
- package/dist/plus/integrations/reads/hierarchy.js +42 -2
- package/dist/plus/integrations/reads/hierarchy.js.map +1 -1
- package/dist/plus/integrations/reads/issueTracker.d.ts +14 -1
- package/dist/plus/integrations/reads/issueTracker.js +46 -5
- package/dist/plus/integrations/reads/issueTracker.js.map +1 -1
- package/dist/plus/integrations/reads/selectionConfirmation.d.ts +5 -3
- package/dist/plus/integrations/reads/selectionConfirmation.js.map +1 -1
- package/dist/plus/integrations/reads/warnings.js +1 -1
- package/dist/plus/integrations/reads/warnings.js.map +1 -1
- package/dist/plus/integrations/results.d.ts +18 -2
- package/dist/plus/integrations/results.js.map +1 -1
- package/dist/plus/integrations/utils/integration.utils.d.ts +3 -0
- package/dist/plus/integrations/utils/integration.utils.js +5 -1
- package/dist/plus/integrations/utils/integration.utils.js.map +1 -1
- package/docs/integrations.md +41 -22
- package/package.json +2 -2
|
@@ -5,7 +5,7 @@ import { PullRequestFilter, PullRequestSearchCriteria, PullRequestShape, PullReq
|
|
|
5
5
|
import { ConfiguredIntegrationDescriptor } from "./authentication/models.js";
|
|
6
6
|
import { IntegrationIds, IssuesCloudHostIntegrationId, IssuesSelfManagedHostIntegrationId } from "./constants.js";
|
|
7
7
|
import { ConfiguredIntegrationsChangeEvent } from "./authentication/configuredIntegrationService.js";
|
|
8
|
-
import { ConnectionStateChangeEvent, ProviderBroadenResult, ProviderOrganization, ProviderPagedResult, ProviderRepositoryShape, ProviderResult, ProviderSweepResult, ResolveRepositoryResult } from "./results.js";
|
|
8
|
+
import { ConnectionStateChangeEvent, ProviderBroadenResult, ProviderLinearProjectShape, ProviderOrganization, ProviderPagedResult, ProviderRepositoryShape, ProviderResult, ProviderSweepResult, ResolveRepositoryResult } from "./results.js";
|
|
9
9
|
import { IssueFilter } from "./providerFilters.js";
|
|
10
10
|
import { IssueEtagInclude, PullRequestEtagInclude } from "./models/integration.js";
|
|
11
11
|
import { IssueCountResult, IssueCountScope, PullRequestCountResult, PullRequestCountScope } from "./reads/counts.js";
|
|
@@ -223,6 +223,12 @@ export type GetAutolinkReferencesOptions = {
|
|
|
223
223
|
/** Drops discovery and awaits a fresh build; retires builds started before the refresh. */
|
|
224
224
|
forceSync?: boolean;
|
|
225
225
|
};
|
|
226
|
+
/** Linear Project discovery reads one Linear connection: the primary, or the one `connectionId` names. */
|
|
227
|
+
export type ListLinearProjectsOptions = {
|
|
228
|
+
connectionId?: string;
|
|
229
|
+
/** Drops the cached Project list and re-syncs the connection before reading, as the other reads' `forceSync`. */
|
|
230
|
+
forceSync?: boolean;
|
|
231
|
+
};
|
|
226
232
|
/**
|
|
227
233
|
* Public, provider-neutral integration facade. Provider clients and integration model instances remain private
|
|
228
234
|
* implementation details so SDK changes don't expand this contract.
|
|
@@ -298,6 +304,16 @@ export interface IntegrationManager {
|
|
|
298
304
|
* references can contain compiled regular expressions and are not a wire format.
|
|
299
305
|
*/
|
|
300
306
|
getAutolinkReferences(options: GetAutolinkReferencesOptions): Promise<RefSet | undefined>;
|
|
307
|
+
/**
|
|
308
|
+
* The Linear workspace's Projects: Linear's own `Project` entity, which can span several teams. Not what
|
|
309
|
+
* {@link listProjects} returns for Linear, which is its teams (the tier a Linear issue read is windowed by).
|
|
310
|
+
* Pass a Project's `id` to {@link listIssueTrackerIssuesPage}'s `linearProjects` to narrow an issue read to it.
|
|
311
|
+
*
|
|
312
|
+
* Drained across Linear's pages (100 Projects each, up to 2,000) and cached per connection like the team list,
|
|
313
|
+
* for five minutes or until `forceSync`. A list the drain could not finish comes back with what it read, a
|
|
314
|
+
* warning and `fetchFailed`, and is not cached.
|
|
315
|
+
*/
|
|
316
|
+
listLinearProjects(options?: ListLinearProjectsOptions): Promise<ProviderResult<ProviderLinearProjectShape>>;
|
|
301
317
|
listRepos(options: {
|
|
302
318
|
providerId: IntegrationIds;
|
|
303
319
|
org?: string;
|
|
@@ -824,6 +840,14 @@ export interface IntegrationManager {
|
|
|
824
840
|
* `getSupportedFilters().issueStates` and are refused otherwise (warning + `fetchFailed`).
|
|
825
841
|
*/
|
|
826
842
|
state?: IssueStateFilter;
|
|
843
|
+
/**
|
|
844
|
+
* Linear only: narrows the read to issues in any of these Linear Projects, by the `id`
|
|
845
|
+
* {@link listLinearProjects} lists. Combines with `org`/`project` (a team), `includeAllAssignees`, `state`
|
|
846
|
+
* and `sort`: the read stays windowed by team, and each team's issues are those in the Projects. Omitted or
|
|
847
|
+
* empty narrows nothing; any other tracker refuses it (warning + `fetchFailed`). Part of a cursor's
|
|
848
|
+
* fingerprint, so drop the cursor when you change it.
|
|
849
|
+
*/
|
|
850
|
+
linearProjects?: string[];
|
|
827
851
|
forceSync?: boolean;
|
|
828
852
|
page?: number;
|
|
829
853
|
/**
|
|
@@ -99,6 +99,11 @@ export type IssuesForProjectOptions = {
|
|
|
99
99
|
* (Trello) refuses anything but `'open'` rather than serving its open cards as if they were the closed ones.
|
|
100
100
|
*/
|
|
101
101
|
state?: IssueStateFilter;
|
|
102
|
+
/**
|
|
103
|
+
* Linear only: narrows the read to issues in any of these Linear Projects (Linear's `Project` entity, by id),
|
|
104
|
+
* server-side. Validated by the facade, which refuses it for any other tracker; omitted or empty adds nothing.
|
|
105
|
+
*/
|
|
106
|
+
linearProjects?: readonly string[];
|
|
102
107
|
};
|
|
103
108
|
/** One project of a batched tracker read (`IssuesIntegration.getIssuesForProjectsWithTruncationResult`). */
|
|
104
109
|
export type ProjectIssuesRequest<T> = {
|
|
@@ -5,10 +5,10 @@ import { Issue, IssueShape } from "../../../git/models/issue.js";
|
|
|
5
5
|
import { Account } from "../../../git/models/author.js";
|
|
6
6
|
import { ConnectionResource, ProviderAuthenticationSession } from "../authentication/models.js";
|
|
7
7
|
import { IssuesCloudHostIntegrationId } from "../constants.js";
|
|
8
|
-
import { ProviderApiCollectionResult } from "./models.js";
|
|
8
|
+
import { ProviderApiCollectionResult, ProviderHierarchyResult, ProviderLinearProject } from "./models.js";
|
|
9
9
|
import { AccountWideIssuesResult, IssuesForProjectOptions, ProjectIssuesDrain, ProjectIssuesRequest, SearchMyIssuesOptions } from "../models/issueReads.js";
|
|
10
10
|
import { IssuesIntegration } from "../models/issuesIntegration.js";
|
|
11
|
-
import { IssueEtagFields } from "../models/integration.js";
|
|
11
|
+
import { IntegrationResult, IssueEtagFields } from "../models/integration.js";
|
|
12
12
|
import { IntegrationAuthenticationProviderDescriptor } from "../authentication/integrationAuthenticationProvider.js";
|
|
13
13
|
//#region ../plus/integrations/src/providers/linear.d.ts
|
|
14
14
|
export interface LinearTeamDescriptor extends IssueResourceDescriptor {
|
|
@@ -45,6 +45,34 @@ export declare class LinearIntegration extends IssuesIntegration<IssuesCloudHost
|
|
|
45
45
|
private getTeams;
|
|
46
46
|
private getTeamsWithMetadata;
|
|
47
47
|
private readTeams;
|
|
48
|
+
/**
|
|
49
|
+
* The workspace's Linear Projects (Linear's `Project` entity), drained across their pages. Separate from the
|
|
50
|
+
* teams this integration lists as its projects: a Linear Project can span several teams, and selecting one
|
|
51
|
+
* narrows an issue read (`IssuesForProjectOptions.linearProjects`) rather than naming what is read.
|
|
52
|
+
*
|
|
53
|
+
* Cached like the teams, and only when complete: a drain cut short by its backstop or a failed page is returned
|
|
54
|
+
* with its `truncated`/`metadata` so the facade can say so, and read again next time. A first-page failure
|
|
55
|
+
* rejects, which the result wrapper turns into `{ error }`. `fresh` reads past the cache and any read in flight,
|
|
56
|
+
* for a caller that must not take a list from before what it needs to see (a Project created since).
|
|
57
|
+
*/
|
|
58
|
+
getWorkspaceProjectsResult(connectionId?: string, options?: {
|
|
59
|
+
fresh?: boolean;
|
|
60
|
+
}): Promise<IntegrationResult<ProviderHierarchyResult<ProviderLinearProject>>>;
|
|
61
|
+
/** Expires, and is dropped on a re-sync, so a Project created or archived mid-session is picked up. */
|
|
62
|
+
private readonly _workspaceProjects;
|
|
63
|
+
/**
|
|
64
|
+
* In flight by token, with the cache generation it started under: concurrent cold reads share one drain, but a
|
|
65
|
+
* read after the cache was dropped (a `forceSync`) never joins one started before, which could miss what it asks
|
|
66
|
+
* to see.
|
|
67
|
+
*/
|
|
68
|
+
private readonly _workspaceProjectsReads;
|
|
69
|
+
private getWorkspaceProjects;
|
|
70
|
+
/**
|
|
71
|
+
* Drains the Projects cursor up to {@link maxWorkspaceProjectPages}. A first-page failure rejects, so the read
|
|
72
|
+
* fails as the team read does rather than reading as a workspace with no Projects; a later one keeps the prefix
|
|
73
|
+
* and records the failure, as do a stalled cursor and the backstop with pages left.
|
|
74
|
+
*/
|
|
75
|
+
private readWorkspaceProjects;
|
|
48
76
|
protected getProviderResourcesForUser(session: ProviderAuthenticationSession): Promise<ResourceDescriptor[] | undefined>;
|
|
49
77
|
protected getProviderUncachedResourcesForUser(session: ProviderAuthenticationSession): Promise<ResourceDescriptor[] | undefined>;
|
|
50
78
|
protected getProviderProjectsForResources(session: ProviderAuthenticationSession, _resources: ResourceDescriptor[]): Promise<ResourceDescriptor[] | undefined>;
|
|
@@ -64,9 +92,14 @@ export declare class LinearIntegration extends IssuesIntegration<IssuesCloudHost
|
|
|
64
92
|
protected getProviderIssuesForProject(session: ProviderAuthenticationSession, project: ResourceDescriptor, options?: IssuesForProjectOptions): Promise<IssueShape[] | undefined>;
|
|
65
93
|
protected getProviderIssuesForProjectWithTruncation(session: ProviderAuthenticationSession, project: ResourceDescriptor, options?: IssuesForProjectOptions): Promise<ProjectIssuesDrain | undefined>;
|
|
66
94
|
/**
|
|
67
|
-
* Searches every team of a user-scoped read together: one `getIssues` names all of them and the viewer
|
|
68
|
-
*
|
|
69
|
-
* body, not a query string, so there is no key bound to chunk at.
|
|
95
|
+
* Searches every team of a user-scoped read together: one `getIssues` names all of them and the viewer's scope
|
|
96
|
+
* (see {@link viewerScope}), so a team with none of the user's issues costs no request of its own. Linear's filter
|
|
97
|
+
* is a GraphQL body, not a query string, so there is no key bound to chunk at.
|
|
98
|
+
*
|
|
99
|
+
* A read narrowed to Linear Projects is searched together even across every assignee: the Projects already bound
|
|
100
|
+
* it to a few teams' worth of issues, so a team holding none of them costs no request either. An unnarrowed read
|
|
101
|
+
* of every assignee is still read team by team, since a search over all of them would just spend one team's page
|
|
102
|
+
* budget before falling back.
|
|
70
103
|
*/
|
|
71
104
|
protected getProjectIssuesSearches(requests: readonly ProjectIssuesRequest<ResourceDescriptor>[]): number[][] | undefined;
|
|
72
105
|
protected searchProviderProjectIssues(session: ProviderAuthenticationSession, requests: readonly ProjectIssuesRequest<ResourceDescriptor>[]): Promise<IssueShape[][] | undefined>;
|
|
@@ -81,7 +114,7 @@ export declare class LinearIntegration extends IssuesIntegration<IssuesCloudHost
|
|
|
81
114
|
* Follows `getIssues`' cursor up to {@link maxPagesPerRequest}. `truncated` is set when that backstop stopped
|
|
82
115
|
* the drain with more pages still available, when the provider stalled its cursor or flagged its paging as
|
|
83
116
|
* truncated, or when a page after the first failed, which is recorded at `failureScope` while the prefix is
|
|
84
|
-
* kept. A
|
|
117
|
+
* kept. A failure before any issue was kept throws, so the caller sees a hard error rather than an empty success.
|
|
85
118
|
*/
|
|
86
119
|
private drainIssues;
|
|
87
120
|
get id(): IssuesCloudHostIntegrationId.Linear;
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import { chunk } from "../../../utils/array.js";
|
|
2
2
|
import { Logger as _logger } from "../../../utils/logger.js";
|
|
3
|
+
import { getScopedLogger } from "../../../utils/logger.scoped.js";
|
|
3
4
|
import { mapSettledBounded } from "../../../utils/promise.js";
|
|
5
|
+
import { trace } from "../../../utils/decorators/log.js";
|
|
4
6
|
import { PromiseCache } from "../../../utils/promiseCache.js";
|
|
7
|
+
import __decorate from "../../../_virtual/@oxc-project+runtime/helpers/esm/decorate.js";
|
|
5
8
|
import { isIssueResourceDescriptor } from "../../../git/utils/resourceDescriptor.utils.js";
|
|
6
|
-
import { IntegrationReadUnavailableError } from "../errors.js";
|
|
9
|
+
import { IntegrationReadUnavailableError, toError } from "../errors.js";
|
|
7
10
|
import "../constants.js";
|
|
8
11
|
import { toCollectionScopeFailure } from "../collectionMetadata.js";
|
|
9
12
|
import { toTokenWithInfo } from "../authentication/models.js";
|
|
10
|
-
import
|
|
13
|
+
import "../providerFilters.js";
|
|
14
|
+
import { collectProviderPagedResult, mergeCollectionMetadata } from "./utils/providerPaging.js";
|
|
11
15
|
import { fromProviderIssue, providersMetadata, toIssueShape, toProviderIssueStates } from "./models.js";
|
|
16
|
+
import { isReadSessionFailure } from "../models/integration.js";
|
|
12
17
|
import { IssuesIntegration, groupProjectIssuesSearches, splitProjectIssuesSearch } from "../models/issuesIntegration.js";
|
|
13
18
|
import { createIssueAutolinkReferences } from "./utils/issueAutolinks.js";
|
|
14
19
|
import { DiscoveryCache } from "./utils/discoveryCache.js";
|
|
@@ -36,10 +41,26 @@ const maxPagesPerRequest = 10;
|
|
|
36
41
|
*/
|
|
37
42
|
const maxAccountWidePagesPerRequest = 50;
|
|
38
43
|
const linearImplicitTeamsPageSize = 50;
|
|
44
|
+
/** The workspace Projects read's backstop: provider-apis pages them 100 at a time, so 2,000 Projects. */
|
|
45
|
+
const maxWorkspaceProjectPages = 20;
|
|
39
46
|
/** Long enough to span one tracker read, short enough that a profile change shows on the next. */
|
|
40
47
|
const recentAccountTTL = 6e4;
|
|
41
48
|
/** A team key and issue number, as Linear writes an issue's identifier (`ENG-123`). */
|
|
42
49
|
const linearIdentifier = /^([A-Za-z0-9]+)-(\d+)$/;
|
|
50
|
+
/**
|
|
51
|
+
* How a user-scoped team read narrows to the viewer: the issues assigned to them, plus, unless the caller asked for
|
|
52
|
+
* assigned issues by name, the GitHub-synced issues they created. Linear's GitHub Issues sync makes the GitHub author
|
|
53
|
+
* of an issue (one who linked their GitHub account) the creator of its Linear copy but assigns no one GitHub doesn't,
|
|
54
|
+
* so an assignee-only read never returns the copy of a GitHub issue the user filed (gitkraken/kepler#4480). Only
|
|
55
|
+
* synced issues widen, and only those the viewer created on either side: the rest of what the user created in Linear
|
|
56
|
+
* stays out of "my issues", as does a synced issue they merely follow.
|
|
57
|
+
*/
|
|
58
|
+
function viewerScope(viewerId, filters) {
|
|
59
|
+
return filters?.includes("assignee") ? { assignees: [viewerId] } : {
|
|
60
|
+
assignees: [viewerId],
|
|
61
|
+
githubSyncedCreators: [viewerId]
|
|
62
|
+
};
|
|
63
|
+
}
|
|
43
64
|
var LinearIntegration = class extends IssuesIntegration {
|
|
44
65
|
/**
|
|
45
66
|
* Built from the teams, so it follows them: rebuilt when the team list changes or the caches are dropped, and once
|
|
@@ -148,6 +169,84 @@ var LinearIntegration = class extends IssuesIntegration {
|
|
|
148
169
|
};
|
|
149
170
|
return { values: descriptors };
|
|
150
171
|
}
|
|
172
|
+
/**
|
|
173
|
+
* The workspace's Linear Projects (Linear's `Project` entity), drained across their pages. Separate from the
|
|
174
|
+
* teams this integration lists as its projects: a Linear Project can span several teams, and selecting one
|
|
175
|
+
* narrows an issue read (`IssuesForProjectOptions.linearProjects`) rather than naming what is read.
|
|
176
|
+
*
|
|
177
|
+
* Cached like the teams, and only when complete: a drain cut short by its backstop or a failed page is returned
|
|
178
|
+
* with its `truncated`/`metadata` so the facade can say so, and read again next time. A first-page failure
|
|
179
|
+
* rejects, which the result wrapper turns into `{ error }`. `fresh` reads past the cache and any read in flight,
|
|
180
|
+
* for a caller that must not take a list from before what it needs to see (a Project created since).
|
|
181
|
+
*/
|
|
182
|
+
async getWorkspaceProjectsResult(connectionId, options) {
|
|
183
|
+
const scope = getScopedLogger();
|
|
184
|
+
const session = await this.resolveReadSession(connectionId, scope);
|
|
185
|
+
if (session == null || isReadSessionFailure(session)) return session && { error: session.error };
|
|
186
|
+
try {
|
|
187
|
+
const projects = await this.getWorkspaceProjects(session, options?.fresh === true);
|
|
188
|
+
await this.confirmScopedAuthFailures(session, projects.metadata);
|
|
189
|
+
this.resetRequestExceptionCount("getProjectsForResources");
|
|
190
|
+
return { value: projects };
|
|
191
|
+
} catch (ex) {
|
|
192
|
+
this.handleProviderException("getProjectsForResources", ex, { connectionId });
|
|
193
|
+
return { error: toError(ex) };
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/** Expires, and is dropped on a re-sync, so a Project created or archived mid-session is picked up. */
|
|
197
|
+
_workspaceProjects = new DiscoveryCache();
|
|
198
|
+
/**
|
|
199
|
+
* In flight by token, with the cache generation it started under: concurrent cold reads share one drain, but a
|
|
200
|
+
* read after the cache was dropped (a `forceSync`) never joins one started before, which could miss what it asks
|
|
201
|
+
* to see.
|
|
202
|
+
*/
|
|
203
|
+
_workspaceProjectsReads = /* @__PURE__ */ new Map();
|
|
204
|
+
getWorkspaceProjects(session, fresh) {
|
|
205
|
+
const { accessToken } = session;
|
|
206
|
+
const generation = this._workspaceProjects.generation;
|
|
207
|
+
if (!fresh) {
|
|
208
|
+
const cached = this._workspaceProjects.get(accessToken);
|
|
209
|
+
if (cached != null) return Promise.resolve({ values: cached });
|
|
210
|
+
const inFlight = this._workspaceProjectsReads.get(accessToken);
|
|
211
|
+
if (inFlight?.generation === generation) return inFlight.read;
|
|
212
|
+
}
|
|
213
|
+
const entry = {
|
|
214
|
+
generation,
|
|
215
|
+
read: this.readWorkspaceProjects(session)
|
|
216
|
+
};
|
|
217
|
+
entry.read = entry.read.then((result) => {
|
|
218
|
+
if (this._workspaceProjectsReads.get(accessToken) === entry && result.truncated !== true && (result.metadata == null || result.metadata.completeness === "complete")) this._workspaceProjects.set(accessToken, result.values, { generation });
|
|
219
|
+
return result;
|
|
220
|
+
});
|
|
221
|
+
this._workspaceProjectsReads.set(accessToken, entry);
|
|
222
|
+
entry.read.finally(() => {
|
|
223
|
+
if (this._workspaceProjectsReads.get(accessToken) === entry) this._workspaceProjectsReads.delete(accessToken);
|
|
224
|
+
}).catch(() => {});
|
|
225
|
+
return entry.read;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Drains the Projects cursor up to {@link maxWorkspaceProjectPages}. A first-page failure rejects, so the read
|
|
229
|
+
* fails as the team read does rather than reading as a workspace with no Projects; a later one keeps the prefix
|
|
230
|
+
* and records the failure, as do a stalled cursor and the backstop with pages left.
|
|
231
|
+
*/
|
|
232
|
+
async readWorkspaceProjects(session) {
|
|
233
|
+
const api = await this.getProvidersApi();
|
|
234
|
+
const tokenWithInfo = toTokenWithInfo(this.id, session);
|
|
235
|
+
let pagesRead = 0;
|
|
236
|
+
let firstPageError;
|
|
237
|
+
const read = await collectProviderPagedResult(async (cursor) => {
|
|
238
|
+
try {
|
|
239
|
+
const page = await api.getLinearProjects(tokenWithInfo, { cursor });
|
|
240
|
+
pagesRead++;
|
|
241
|
+
return page;
|
|
242
|
+
} catch (ex) {
|
|
243
|
+
if (pagesRead === 0) firstPageError = toError(ex);
|
|
244
|
+
throw ex;
|
|
245
|
+
}
|
|
246
|
+
}, maxWorkspaceProjectPages, { providerId: this.id });
|
|
247
|
+
if (firstPageError != null) throw firstPageError;
|
|
248
|
+
return read;
|
|
249
|
+
}
|
|
151
250
|
async getProviderResourcesForUser(session) {
|
|
152
251
|
const organization = await this.getOrganization(session);
|
|
153
252
|
return organization != null ? [organization] : void 0;
|
|
@@ -174,6 +273,8 @@ var LinearIntegration = class extends IssuesIntegration {
|
|
|
174
273
|
clearDiscoveryCaches() {
|
|
175
274
|
this._organizations = void 0;
|
|
176
275
|
this._teams.clear();
|
|
276
|
+
this._workspaceProjects.clear();
|
|
277
|
+
this._workspaceProjectsReads.clear();
|
|
177
278
|
this._autolinks.clear();
|
|
178
279
|
this._recentAccounts.clear();
|
|
179
280
|
}
|
|
@@ -221,7 +322,8 @@ var LinearIntegration = class extends IssuesIntegration {
|
|
|
221
322
|
}
|
|
222
323
|
const drain = await this.drainIssues(session, {
|
|
223
324
|
teams: [project.id],
|
|
224
|
-
...
|
|
325
|
+
...options?.linearProjects?.length ? { projects: [...options.linearProjects] } : {},
|
|
326
|
+
...viewerId != null ? viewerScope(viewerId, options?.filters) : {},
|
|
225
327
|
states: toProviderIssueStates(options?.state)
|
|
226
328
|
}, options?.sort, {
|
|
227
329
|
providerId: this.id,
|
|
@@ -239,28 +341,38 @@ var LinearIntegration = class extends IssuesIntegration {
|
|
|
239
341
|
};
|
|
240
342
|
}
|
|
241
343
|
/**
|
|
242
|
-
* Searches every team of a user-scoped read together: one `getIssues` names all of them and the viewer
|
|
243
|
-
*
|
|
244
|
-
* body, not a query string, so there is no key bound to chunk at.
|
|
344
|
+
* Searches every team of a user-scoped read together: one `getIssues` names all of them and the viewer's scope
|
|
345
|
+
* (see {@link viewerScope}), so a team with none of the user's issues costs no request of its own. Linear's filter
|
|
346
|
+
* is a GraphQL body, not a query string, so there is no key bound to chunk at.
|
|
347
|
+
*
|
|
348
|
+
* A read narrowed to Linear Projects is searched together even across every assignee: the Projects already bound
|
|
349
|
+
* it to a few teams' worth of issues, so a team holding none of them costs no request either. An unnarrowed read
|
|
350
|
+
* of every assignee is still read team by team, since a search over all of them would just spend one team's page
|
|
351
|
+
* budget before falling back.
|
|
245
352
|
*/
|
|
246
353
|
getProjectIssuesSearches(requests) {
|
|
247
|
-
return groupProjectIssuesSearches(requests, ({ project, options }) => options.user != null && isIssueResourceDescriptor(project) ? [
|
|
248
|
-
options.user,
|
|
354
|
+
return groupProjectIssuesSearches(requests, ({ project, options }) => (options.user != null || options.linearProjects?.length) && isIssueResourceDescriptor(project) ? [
|
|
355
|
+
options.user ?? "",
|
|
249
356
|
options.userId ?? "",
|
|
250
357
|
options.sort ?? "",
|
|
251
|
-
options.state ?? ""
|
|
358
|
+
options.state ?? "",
|
|
359
|
+
options.filters?.join(",") ?? "",
|
|
360
|
+
...options.linearProjects ?? []
|
|
252
361
|
].join("\0") : void 0);
|
|
253
362
|
}
|
|
254
363
|
async searchProviderProjectIssues(session, requests) {
|
|
255
364
|
const { options } = requests[0];
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
365
|
+
let viewerId;
|
|
366
|
+
if (options.user != null) {
|
|
367
|
+
viewerId = await this.getViewerId(session, options);
|
|
368
|
+
if (viewerId == null) return void 0;
|
|
369
|
+
} else if (!options.linearProjects?.length) return;
|
|
259
370
|
const teams = requests.map((request) => request.project).filter(isIssueResourceDescriptor);
|
|
260
371
|
if (teams.length !== requests.length) return void 0;
|
|
261
372
|
const drain = await this.drainIssues(session, {
|
|
262
373
|
teams: teams.map((team) => team.id),
|
|
263
|
-
|
|
374
|
+
...options.linearProjects?.length ? { projects: [...options.linearProjects] } : {},
|
|
375
|
+
...viewerId != null ? viewerScope(viewerId, options.filters) : {},
|
|
264
376
|
states: toProviderIssueStates(options.state)
|
|
265
377
|
}, options.sort, { providerId: this.id });
|
|
266
378
|
if (drain.truncated || drain.metadata != null && drain.metadata.completeness !== "complete") return;
|
|
@@ -285,11 +397,12 @@ var LinearIntegration = class extends IssuesIntegration {
|
|
|
285
397
|
* Follows `getIssues`' cursor up to {@link maxPagesPerRequest}. `truncated` is set when that backstop stopped
|
|
286
398
|
* the drain with more pages still available, when the provider stalled its cursor or flagged its paging as
|
|
287
399
|
* truncated, or when a page after the first failed, which is recorded at `failureScope` while the prefix is
|
|
288
|
-
* kept. A
|
|
400
|
+
* kept. A failure before any issue was kept throws, so the caller sees a hard error rather than an empty success.
|
|
289
401
|
*/
|
|
290
402
|
async drainIssues(session, filter, sort, failureScope) {
|
|
291
403
|
const api = await this.getProvidersApi();
|
|
292
404
|
const assignees = filter.assignees?.length ? filter.assignees : void 0;
|
|
405
|
+
const syncedCreators = filter.githubSyncedCreators?.length ? filter.githubSyncedCreators : void 0;
|
|
293
406
|
let cursor;
|
|
294
407
|
let hasMore;
|
|
295
408
|
let requestCount = 0;
|
|
@@ -319,7 +432,7 @@ var LinearIntegration = class extends IssuesIntegration {
|
|
|
319
432
|
collectionMetadata = mergeCollectionMetadata(collectionMetadata, result.metadata);
|
|
320
433
|
for (const issue of result.values) {
|
|
321
434
|
const shape = toIssueShape(issue, this, { projection: "project" });
|
|
322
|
-
if (shape != null && (assignees == null || shape.assignees?.some((a) => assignees.includes(a.id)))) issues.push(shape);
|
|
435
|
+
if (shape != null && (assignees == null || shape.assignees?.some((a) => assignees.includes(a.id)) || syncedCreators != null && shape.author != null && syncedCreators.includes(shape.author.id) && shape.syncedWith?.length !== 0)) issues.push(shape);
|
|
323
436
|
}
|
|
324
437
|
if (hasMore && (nextCursor == null || nextCursor === cursor)) {
|
|
325
438
|
truncated = true;
|
|
@@ -532,6 +645,7 @@ var LinearIntegration = class extends IssuesIntegration {
|
|
|
532
645
|
return url.substring(0, url.length - lastSegment.length - 1);
|
|
533
646
|
}
|
|
534
647
|
};
|
|
648
|
+
__decorate([trace()], LinearIntegration.prototype, "getWorkspaceProjectsResult", null);
|
|
535
649
|
//#endregion
|
|
536
650
|
export { LinearIntegration };
|
|
537
651
|
|