@gitkraken/core-gitlens 0.5.109 → 0.5.110

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 (105) hide show
  1. package/CHANGELOG.md +33 -1
  2. package/dist/git/models/pullRequest.d.ts +81 -0
  3. package/dist/git/models/pullRequest.d.ts.map +1 -1
  4. package/dist/git/models/pullRequest.js +15 -0
  5. package/dist/git/models/pullRequest.js.map +1 -1
  6. package/dist/git/utils/pullRequest.utils.d.ts +11 -1
  7. package/dist/git/utils/pullRequest.utils.d.ts.map +1 -1
  8. package/dist/git/utils/pullRequest.utils.js +40 -0
  9. package/dist/git/utils/pullRequest.utils.js.map +1 -1
  10. package/dist/plus/git-github/api/github.d.ts +37 -4
  11. package/dist/plus/git-github/api/github.d.ts.map +1 -1
  12. package/dist/plus/git-github/api/github.js +147 -16
  13. package/dist/plus/git-github/api/github.js.map +1 -1
  14. package/dist/plus/git-github/api/pullRequestSearchQuery.d.ts +27 -1
  15. package/dist/plus/git-github/api/pullRequestSearchQuery.d.ts.map +1 -1
  16. package/dist/plus/git-github/api/pullRequestSearchQuery.js +73 -6
  17. package/dist/plus/git-github/api/pullRequestSearchQuery.js.map +1 -1
  18. package/dist/plus/git-github/models.d.ts +17 -5
  19. package/dist/plus/git-github/models.d.ts.map +1 -1
  20. package/dist/plus/git-github/models.js +14 -1
  21. package/dist/plus/git-github/models.js.map +1 -1
  22. package/dist/plus/integrations/integrationService.d.ts +16 -1
  23. package/dist/plus/integrations/integrationService.d.ts.map +1 -1
  24. package/dist/plus/integrations/integrationService.js +9 -1
  25. package/dist/plus/integrations/integrationService.js.map +1 -1
  26. package/dist/plus/integrations/manager.d.ts +45 -5
  27. package/dist/plus/integrations/manager.d.ts.map +1 -1
  28. package/dist/plus/integrations/models/gitHostIntegration.d.ts +12 -0
  29. package/dist/plus/integrations/models/gitHostIntegration.d.ts.map +1 -1
  30. package/dist/plus/integrations/models/gitHostIntegration.js +18 -0
  31. package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -1
  32. package/dist/plus/integrations/models/integration.d.ts +1 -1
  33. package/dist/plus/integrations/models/integration.d.ts.map +1 -1
  34. package/dist/plus/integrations/models/integration.js.map +1 -1
  35. package/dist/plus/integrations/providerFilters.d.ts +1 -1
  36. package/dist/plus/integrations/providerFilters.d.ts.map +1 -1
  37. package/dist/plus/integrations/providers/accounts.d.ts +6 -0
  38. package/dist/plus/integrations/providers/accounts.d.ts.map +1 -0
  39. package/dist/plus/integrations/providers/accounts.js +30 -0
  40. package/dist/plus/integrations/providers/accounts.js.map +1 -0
  41. package/dist/plus/integrations/providers/github.d.ts +9 -0
  42. package/dist/plus/integrations/providers/github.d.ts.map +1 -1
  43. package/dist/plus/integrations/providers/github.js +114 -135
  44. package/dist/plus/integrations/providers/github.js.map +1 -1
  45. package/dist/plus/integrations/providers/gitlab.d.ts +9 -0
  46. package/dist/plus/integrations/providers/gitlab.d.ts.map +1 -1
  47. package/dist/plus/integrations/providers/gitlab.js +23 -4
  48. package/dist/plus/integrations/providers/gitlab.js.map +1 -1
  49. package/dist/plus/integrations/providers/models.d.ts +14 -32
  50. package/dist/plus/integrations/providers/models.d.ts.map +1 -1
  51. package/dist/plus/integrations/providers/models.js +36 -105
  52. package/dist/plus/integrations/providers/models.js.map +1 -1
  53. package/dist/plus/integrations/providers/pullRequestReviews.d.ts +50 -0
  54. package/dist/plus/integrations/providers/pullRequestReviews.d.ts.map +1 -0
  55. package/dist/plus/integrations/providers/pullRequestReviews.js +99 -0
  56. package/dist/plus/integrations/providers/pullRequestReviews.js.map +1 -0
  57. package/dist/plus/integrations/reads/counts.d.ts +55 -0
  58. package/dist/plus/integrations/reads/counts.d.ts.map +1 -1
  59. package/dist/plus/integrations/reads/counts.js +146 -2
  60. package/dist/plus/integrations/reads/counts.js.map +1 -1
  61. package/dist/plus/integrations/reads/drains.d.ts +7 -1
  62. package/dist/plus/integrations/reads/drains.d.ts.map +1 -1
  63. package/dist/plus/integrations/reads/drains.js +13 -2
  64. package/dist/plus/integrations/reads/drains.js.map +1 -1
  65. package/dist/plus/integrations/reads/filters.d.ts.map +1 -1
  66. package/dist/plus/integrations/reads/filters.js +22 -0
  67. package/dist/plus/integrations/reads/filters.js.map +1 -1
  68. package/dist/plus/integrations/reads/searchPullRequests.d.ts +4 -3
  69. package/dist/plus/integrations/reads/searchPullRequests.d.ts.map +1 -1
  70. package/dist/plus/integrations/reads/searchPullRequests.js +6 -4
  71. package/dist/plus/integrations/reads/searchPullRequests.js.map +1 -1
  72. package/dist/plus/integrations/reads/sweeps.js +1 -1
  73. package/dist/plus/integrations/reads/sweeps.js.map +1 -1
  74. package/dist/plus/integrations/reads/warnings.d.ts +2 -2
  75. package/dist/plus/integrations/reads/warnings.d.ts.map +1 -1
  76. package/dist/plus/integrations/reads/warnings.js +11 -2
  77. package/dist/plus/integrations/reads/warnings.js.map +1 -1
  78. package/dist/plus/integrations/results.d.ts +6 -1
  79. package/dist/plus/integrations/results.d.ts.map +1 -1
  80. package/dist/plus/integrations/results.js.map +1 -1
  81. package/docs/integrations.md +76 -10
  82. package/docs/kepler-read-api-parity.md +9 -0
  83. package/package.json +1 -1
  84. package/src/git/models/pullRequest.ts +84 -0
  85. package/src/git/utils/pullRequest.utils.ts +50 -0
  86. package/src/plus/git-github/api/github.ts +176 -16
  87. package/src/plus/git-github/api/pullRequestSearchQuery.ts +81 -7
  88. package/src/plus/git-github/models.ts +29 -5
  89. package/src/plus/integrations/integrationService.ts +25 -2
  90. package/src/plus/integrations/manager.ts +50 -5
  91. package/src/plus/integrations/models/gitHostIntegration.ts +29 -0
  92. package/src/plus/integrations/models/integration.ts +1 -0
  93. package/src/plus/integrations/providerFilters.ts +6 -1
  94. package/src/plus/integrations/providers/accounts.ts +34 -0
  95. package/src/plus/integrations/providers/github.ts +133 -144
  96. package/src/plus/integrations/providers/gitlab.ts +28 -10
  97. package/src/plus/integrations/providers/models.ts +64 -124
  98. package/src/plus/integrations/providers/pullRequestReviews.ts +122 -0
  99. package/src/plus/integrations/reads/counts.ts +273 -2
  100. package/src/plus/integrations/reads/drains.ts +12 -2
  101. package/src/plus/integrations/reads/filters.ts +22 -0
  102. package/src/plus/integrations/reads/searchPullRequests.ts +12 -4
  103. package/src/plus/integrations/reads/sweeps.ts +1 -1
  104. package/src/plus/integrations/reads/warnings.ts +12 -1
  105. package/src/plus/integrations/results.ts +6 -1
@@ -9,9 +9,10 @@ import { request } from '@octokit/request';
9
9
  import { RequestError } from '@octokit/request-error';
10
10
  import { AuthenticationError, AuthenticationErrorReason, RequestClientError, RequestNotFoundError, RequestRateLimitError, } from '../../../git/errors.js';
11
11
  import { defaultIssueSort } from '../../../git/models/issue.js';
12
- import { PullRequestMergeMethod } from '../../../git/models/pullRequest.js';
12
+ import { defaultPullRequestSort, PullRequestMergeMethod } from '../../../git/models/pullRequest.js';
13
13
  import { getGitHubNoReplyAddressParts } from '../../../git/remotes/github.js';
14
14
  import { effectiveIssueSort, getIssueComparator } from '../../../git/utils/issue.utils.js';
15
+ import { getPullRequestComparator } from '../../../git/utils/pullRequest.utils.js';
15
16
  import { createRevisionRange, getRevisionRangeParts, isRevisionRange, isSha, } from '../../../git/utils/revision.utils.js';
16
17
  import { chunk } from '../../../utils/array.js';
17
18
  import { base64 } from '../../../utils/base64.js';
@@ -51,10 +52,17 @@ const requestRetryMaxDelay = 2000; // ms
51
52
  const accountResolveBatchSize = 25;
52
53
  // Pull-request search selects the full PR fragment (reviews, requests, refs, commits, etc.),
53
54
  // which makes GitHub reject broad 100-node searches with `Resource limits for this query
54
- // exceeded` on large repositories. Thirty keeps the default within that GraphQL cost budget
55
- // while callers that know their scope is cheap can still opt into the supported 100-node max.
55
+ // exceeded` on large repositories. Thirty keeps the default within that GraphQL cost budget;
56
+ // the 100-node maximum the connection accepts is for the lite shape, whose per-node cost is a
57
+ // fraction of it. The budget is per DOCUMENT, and one document holds every relationship × state
58
+ // facet, so the worst case scales with the facet count (five relationships × four states = 20).
56
59
  const defaultPullRequestSearchPageSize = 30;
57
60
  const maxPullRequestSearchPageSize = 100;
61
+ // Pages `searchMyPullRequests` drains for a caller that wants a whole list rather than a page.
62
+ // Four reduced pages cover more than the single 100-node page this read served before it paged,
63
+ // so the smaller page costs coverage nowhere; it bounds an unbounded drain on a read whose
64
+ // consumers (Launchpad, the Graph pull-request panel) block on it.
65
+ const maxMyPullRequestSearchPages = 4;
58
66
  function isRetryableTransientError(ex) {
59
67
  // An aborted request is rethrown as the original `AbortError` (not a `RequestError`), so it is
60
68
  // excluded here. octokit maps a fetch/network failure to a `RequestError` with status 500 and
@@ -153,6 +161,24 @@ repository {
153
161
  viewerPermission
154
162
  }
155
163
  `;
164
+ /**
165
+ * One review's fields, shared by `latestReviews` and `viewerLatestReview`. The two selections must stay
166
+ * identical: `fromGitHubPullRequest` merges the viewer's review into the capped `latestReviews` window and
167
+ * dedups by `id`, so a field present in only one of them would produce rows that differ by where they came
168
+ * from rather than by what they are.
169
+ */
170
+ const gqlPullRequestReviewFragment = `
171
+ id
172
+ author {
173
+ login
174
+ avatarUrl(size: $avatarSize)
175
+ url
176
+ }
177
+ state
178
+ commit {
179
+ oid
180
+ }
181
+ `;
156
182
  const gqlPullRequestFragment = `
157
183
  ${gqlPullRequestLiteFragment}
158
184
  additions
@@ -172,14 +198,12 @@ mergedBy {
172
198
  reviewDecision
173
199
  latestReviews(first: 25) {
174
200
  nodes {
175
- author {
176
- login
177
- avatarUrl(size: $avatarSize)
178
- url
179
- }
180
- state
201
+ ${gqlPullRequestReviewFragment}
181
202
  }
182
203
  }
204
+ viewerLatestReview {
205
+ ${gqlPullRequestReviewFragment}
206
+ }
183
207
  reviewRequests(first: 25) {
184
208
  nodes {
185
209
  asCodeOwner
@@ -2161,14 +2185,21 @@ export class GitHubApi {
2161
2185
  * One page of the current user's pull requests, filtered by state and optionally by an explicit
2162
2186
  * relationship qualifier. Backs the PR sweeps, which drain it page by page.
2163
2187
  *
2164
- * Ordering is part of the contract, not an option: always `sort:updated` (most recently updated first),
2165
- * matching {@link searchPullRequestsPage}. A caller that stops before `hasMore` clears every sweep with a
2188
+ * Ordering is part of the contract, not an option: always `sort:updated` (most recently updated first), which is
2189
+ * {@link searchPullRequestsPage}'s DEFAULT rather than its only orderthat read takes a `criteria.sort`, this
2190
+ * one does not, because a sweep's recency window is what its page budget is defined against. A caller that stops
2191
+ * before `hasMore` clears — every sweep with a
2166
2192
  * page budget — therefore retains a well-defined recency window instead of an arbitrary slice of GitHub's
2167
2193
  * relevance ranking.
2168
2194
  */
2169
2195
  async searchMyPullRequestsPage(provider, token, options, cancellation) {
2170
2196
  const scope = getScopedLogger();
2171
- const limit = Math.min(100, this.config.getLaunchpadQueryLimit?.() ?? 100);
2197
+ // The page follows the projection rather than being a separate decision: the full fragment is what GitHub
2198
+ // rejects at 100 nodes (see `defaultPullRequestSearchPageSize`), so every read that selects it pages at the
2199
+ // reduced size, and only the lite shape asks for the maximum. A caller that wants a whole list pages for it
2200
+ // — see `searchMyPullRequests`.
2201
+ const configuredLimit = this.config.getLaunchpadQueryLimit?.() ?? maxPullRequestSearchPageSize;
2202
+ const limit = Math.min(options?.summary === true ? maxPullRequestSearchPageSize : defaultPullRequestSearchPageSize, configuredLimit);
2172
2203
  try {
2173
2204
  const query = `query searchMyPullRequests(
2174
2205
  $search: String!
@@ -2247,8 +2278,36 @@ export class GitHubApi {
2247
2278
  throw this.handleException(ex, provider, scope, options?.silent);
2248
2279
  }
2249
2280
  }
2281
+ /**
2282
+ * The whole list rather than a page: this read has no cursor to hand back (its `IntegrationResult<T[]>` return
2283
+ * has no paging channel at all), so it drains up to {@link maxMyPullRequestSearchPages} pages itself.
2284
+ *
2285
+ * Draining instead of taking one page is what lets the full projection page at the reduced size everywhere
2286
+ * (see the `limit` in {@link searchMyPullRequestsPage}): a single page would otherwise have to ask for 100
2287
+ * nodes of the selection GitHub rejects at that size, purely because this caller cannot resume. Ordering is
2288
+ * `sort:updated`, so the pages compose into a defined window rather than a shifting relevance ranking, and the
2289
+ * drain stops as soon as GitHub reports no next page.
2290
+ */
2250
2291
  async searchMyPullRequests(provider, token, options, cancellation) {
2251
- return (await this.searchMyPullRequestsPage(provider, token, options, cancellation)).values;
2292
+ const values = [];
2293
+ // The searches behind one page can overlap (a PR can be authored by and review-requested from the same
2294
+ // user), and a later page can repeat a row an earlier one already served if the PR was updated mid-drain.
2295
+ const seen = new Set();
2296
+ let cursor;
2297
+ for (let page = 0; page < maxMyPullRequestSearchPages; page++) {
2298
+ const result = await this.searchMyPullRequestsPage(provider, token, { ...options, cursor: cursor }, cancellation);
2299
+ for (const pr of result.values) {
2300
+ if (seen.has(pr.url))
2301
+ continue;
2302
+ seen.add(pr.url);
2303
+ values.push(pr);
2304
+ }
2305
+ // A missing or repeated cursor with `hasMore` would loop on the same page forever.
2306
+ if (!result.hasMore || result.cursor == null || result.cursor === cursor)
2307
+ break;
2308
+ cursor = result.cursor;
2309
+ }
2310
+ return values;
2252
2311
  }
2253
2312
  /**
2254
2313
  * The current user's issues: authored ∪ assigned ∪ mentioned, each its own aliased search behind one composite
@@ -2431,6 +2490,64 @@ export class GitHubApi {
2431
2490
  throw this.handleException(ex, provider, scope);
2432
2491
  }
2433
2492
  }
2493
+ /**
2494
+ * The PR twin of {@link countIssues}: counts pull requests for several scopes in ONE request via aliased
2495
+ * `search` fields selecting only `issueCount` with `first: 0`. Same positional/undefined contract as the issue
2496
+ * count.
2497
+ *
2498
+ * The one difference is states. {@link toGitHubPullRequestSearchFacets} fans a scope's states out into one
2499
+ * `search` each, so this reports the count the SAME way {@link searchPullRequestsPage} reports its total — the
2500
+ * LARGEST facet's `issueCount`, not their sum: the result ceiling applies per search, so the max is what
2501
+ * `exceedsProviderLimit` compares against, and summing would claim a total the read never surfaces. Relationships
2502
+ * are OR-ed and can't be a single count; the facade refuses a multi-relationship scope, so at most one is here.
2503
+ */
2504
+ async countPullRequests(provider, token, scopes, options, cancellation) {
2505
+ const scope = getScopedLogger();
2506
+ if (scopes.length === 0)
2507
+ return [];
2508
+ // Each scope expands to one query string per state facet (all sharing its single relationship, which the
2509
+ // facade guarantees). A scope's count is the MAX across its facets — mirroring searchPullRequestsPage's
2510
+ // totalCount — so the aliases stay grouped by scope and are reduced after the response.
2511
+ const scopeQueries = scopes.map(s => {
2512
+ const scopeQualifiers = toGitHubIssueSearchScopeQualifiers(s.org, s.repos);
2513
+ return toGitHubPullRequestSearchFacets(s.criteria).map(f => [...scopeQualifiers, ...f.qualifiers].join(' '));
2514
+ });
2515
+ // Aliases are positional and generated (`s${scope}f${facet}`): a caller's key is arbitrary text and would
2516
+ // break the document, so results are reduced back to one count per scope by index.
2517
+ const aliased = scopeQueries.flatMap((queries, si) => queries.map((query, fi) => ({ alias: `s${si}f${fi}`, query: query })));
2518
+ const params = aliased.map(a => `$${a.alias}: String!`).join('\n\t\t\t\t');
2519
+ // `first: 0` is what makes this cheap — `issueCount` alone, no nodes over the wire.
2520
+ const fields = aliased
2521
+ .map(a => `${a.alias}: search(query: $${a.alias}, type: ISSUE, first: 0) { issueCount }`)
2522
+ .join('\n\t\t\t\t');
2523
+ const query = `query countPullRequests(
2524
+ ${params}
2525
+ ) {
2526
+ ${fields}
2527
+ }`;
2528
+ const variables = { baseUrl: options?.baseUrl };
2529
+ for (const a of aliased) {
2530
+ variables[a.alias] = a.query;
2531
+ }
2532
+ try {
2533
+ const rsp = await this.graphql(provider, token, query, variables, scope, cancellation);
2534
+ if (rsp == null)
2535
+ return scopes.map(() => undefined);
2536
+ return scopeQueries.map((queries, si) => {
2537
+ let max;
2538
+ for (let fi = 0; fi < queries.length; fi++) {
2539
+ const count = rsp[`s${si}f${fi}`]?.issueCount;
2540
+ if (count != null) {
2541
+ max = max == null ? count : Math.max(max, count);
2542
+ }
2543
+ }
2544
+ return max;
2545
+ });
2546
+ }
2547
+ catch (ex) {
2548
+ throw this.handleException(ex, provider, scope);
2549
+ }
2550
+ }
2434
2551
  /**
2435
2552
  * The aliased-search engine behind every GitHub issue search: one GraphQL request carrying N independently
2436
2553
  * cursored `search` fields, `@include`-gated so an exhausted or unrequested one costs nothing.
@@ -2646,8 +2763,11 @@ export class GitHubApi {
2646
2763
  * every active relationship × state facet, so one HTTP request serves one page even when the logical search is
2647
2764
  * a union. The cursor preserves each facet's continuation plus the positional page.
2648
2765
  *
2649
- * Ordering is always most-recently-updated-first, and user text is sanitized before it reaches the provider
2650
- * query. See {@link toGitHubPullRequestSearchFacets}.
2766
+ * Ordering is `criteria.sort`, defaulting to most-recently-updated-first the order this read served before it
2767
+ * was expressible. It is requested of the provider AND re-applied to the merged page, because the page is a
2768
+ * union of facets and no per-facet server order describes it. The sort is part of the cursor fingerprint, so
2769
+ * changing it invalidates a threaded cursor exactly as changing the text or the scope does. User text is
2770
+ * sanitized before it reaches the provider query. See {@link toGitHubPullRequestSearchFacets}.
2651
2771
  */
2652
2772
  async searchPullRequestsPage(provider, token, options, cancellation) {
2653
2773
  const scope = getScopedLogger();
@@ -2758,7 +2878,15 @@ export class GitHubApi {
2758
2878
  }
2759
2879
  }
2760
2880
  }
2761
- pullRequests.sort((a, b) => b.updatedDate.getTime() - a.updatedDate.getTime());
2881
+ // The PR path re-sorts the merged page rather than trusting the per-facet server order — GitHub's
2882
+ // server-side PR sort has been unreliable (the per-branch path re-sorts for the same reason), and the
2883
+ // union of several facets is unordered regardless. The key was validated against
2884
+ // `githubPullRequestSearchCapabilities.sorts` upstream, so its comparator is always defined for a PR
2885
+ // shape; the guard only guards the unreachable case rather than inventing an order for it.
2886
+ const comparator = getPullRequestComparator(options?.criteria?.sort ?? defaultPullRequestSort);
2887
+ if (comparator != null) {
2888
+ pullRequests.sort(comparator);
2889
+ }
2762
2890
  const values = [
2763
2891
  ...uniqueBy(pullRequests, pr => pr.url, (original, _current) => original),
2764
2892
  ];
@@ -3205,6 +3333,9 @@ __decorate([
3205
3333
  __decorate([
3206
3334
  trace({ args: (provider, token) => ({ provider: provider.name, token: `<token:${token.microHash}>` }) })
3207
3335
  ], GitHubApi.prototype, "countIssues", null);
3336
+ __decorate([
3337
+ trace({ args: (provider, token) => ({ provider: provider.name, token: `<token:${token.microHash}>` }) })
3338
+ ], GitHubApi.prototype, "countPullRequests", null);
3208
3339
  __decorate([
3209
3340
  trace({ args: (provider, token) => ({ provider: provider.name, token: `<token:${token.microHash}>` }) })
3210
3341
  ], GitHubApi.prototype, "searchPullRequestsPage", null);