@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.
Files changed (36) hide show
  1. package/CHANGELOG.md +16 -1
  2. package/dist/plus/integrations/index.d.ts +3 -3
  3. package/dist/plus/integrations/index.js.map +1 -1
  4. package/dist/plus/integrations/integrationService.d.ts +10 -2
  5. package/dist/plus/integrations/integrationService.js +9 -1
  6. package/dist/plus/integrations/integrationService.js.map +1 -1
  7. package/dist/plus/integrations/manager.d.ts +25 -1
  8. package/dist/plus/integrations/models/issueReads.d.ts +5 -0
  9. package/dist/plus/integrations/providers/linear.d.ts +39 -6
  10. package/dist/plus/integrations/providers/linear.js +129 -15
  11. package/dist/plus/integrations/providers/linear.js.map +1 -1
  12. package/dist/plus/integrations/providers/models.d.ts +4 -1
  13. package/dist/plus/integrations/providers/models.js.map +1 -1
  14. package/dist/plus/integrations/providers/providersApi.d.ts +9 -1
  15. package/dist/plus/integrations/providers/providersApi.js +8 -0
  16. package/dist/plus/integrations/providers/providersApi.js.map +1 -1
  17. package/dist/plus/integrations/reads/cursors.d.ts +8 -0
  18. package/dist/plus/integrations/reads/cursors.js +14 -4
  19. package/dist/plus/integrations/reads/cursors.js.map +1 -1
  20. package/dist/plus/integrations/reads/hierarchy.d.ts +8 -2
  21. package/dist/plus/integrations/reads/hierarchy.js +42 -2
  22. package/dist/plus/integrations/reads/hierarchy.js.map +1 -1
  23. package/dist/plus/integrations/reads/issueTracker.d.ts +14 -1
  24. package/dist/plus/integrations/reads/issueTracker.js +46 -5
  25. package/dist/plus/integrations/reads/issueTracker.js.map +1 -1
  26. package/dist/plus/integrations/reads/selectionConfirmation.d.ts +5 -3
  27. package/dist/plus/integrations/reads/selectionConfirmation.js.map +1 -1
  28. package/dist/plus/integrations/reads/warnings.js +1 -1
  29. package/dist/plus/integrations/reads/warnings.js.map +1 -1
  30. package/dist/plus/integrations/results.d.ts +18 -2
  31. package/dist/plus/integrations/results.js.map +1 -1
  32. package/dist/plus/integrations/utils/integration.utils.d.ts +3 -0
  33. package/dist/plus/integrations/utils/integration.utils.js +5 -1
  34. package/dist/plus/integrations/utils/integration.utils.js.map +1 -1
  35. package/docs/integrations.md +41 -22
  36. 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 as
68
- * assignee, so a team with none of the user's issues costs no request of its own. Linear's filter is a GraphQL
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 first-page failure throws, so the caller sees a hard error rather than an empty success.
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 { mergeCollectionMetadata } from "./utils/providerPaging.js";
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
- ...viewerId != null ? { assignees: [viewerId] } : {},
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 as
243
- * assignee, so a team with none of the user's issues costs no request of its own. Linear's filter is a GraphQL
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
- if (options.user == null) return void 0;
257
- const viewerId = await this.getViewerId(session, options);
258
- if (viewerId == null) return void 0;
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
- assignees: [viewerId],
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 first-page failure throws, so the caller sees a hard error rather than an empty success.
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