@gitkraken/core-gitlens 0.9.3 → 0.9.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/CHANGELOG.md +34 -1
  2. package/dist/git/models/issue.d.ts +25 -2
  3. package/dist/git/models/issue.js +3 -1
  4. package/dist/git/models/issue.js.map +1 -1
  5. package/dist/git/models/pullRequest.d.ts +55 -5
  6. package/dist/git/models/pullRequest.js +5 -1
  7. package/dist/git/models/pullRequest.js.map +1 -1
  8. package/dist/git/utils/issue.utils.d.ts +4 -0
  9. package/dist/git/utils/issue.utils.js +4 -0
  10. package/dist/git/utils/issue.utils.js.map +1 -1
  11. package/dist/git/utils/pullRequest.utils.d.ts +4 -0
  12. package/dist/git/utils/pullRequest.utils.js +8 -0
  13. package/dist/git/utils/pullRequest.utils.js.map +1 -1
  14. package/dist/plus/git-github/api/github.d.ts +64 -4
  15. package/dist/plus/git-github/api/github.js +232 -15
  16. package/dist/plus/git-github/api/github.js.map +1 -1
  17. package/dist/plus/git-github/models.d.ts +48 -6
  18. package/dist/plus/git-github/models.js +11 -10
  19. package/dist/plus/git-github/models.js.map +1 -1
  20. package/dist/plus/integrations/fieldPresence.d.ts +75 -0
  21. package/dist/plus/integrations/fieldPresence.js +381 -0
  22. package/dist/plus/integrations/fieldPresence.js.map +1 -0
  23. package/dist/plus/integrations/index.d.ts +6 -4
  24. package/dist/plus/integrations/index.js +2 -1
  25. package/dist/plus/integrations/index.js.map +1 -1
  26. package/dist/plus/integrations/integrationService.d.ts +13 -4
  27. package/dist/plus/integrations/integrationService.js.map +1 -1
  28. package/dist/plus/integrations/manager.d.ts +62 -7
  29. package/dist/plus/integrations/models/gitHostIntegration.d.ts +103 -9
  30. package/dist/plus/integrations/models/gitHostIntegration.js +116 -5
  31. package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -1
  32. package/dist/plus/integrations/models/integration.d.ts +91 -4
  33. package/dist/plus/integrations/models/integration.js +56 -7
  34. package/dist/plus/integrations/models/integration.js.map +1 -1
  35. package/dist/plus/integrations/models/issueReads.d.ts +14 -0
  36. package/dist/plus/integrations/models/issuesIntegration.d.ts +44 -3
  37. package/dist/plus/integrations/models/issuesIntegration.js +51 -1
  38. package/dist/plus/integrations/models/issuesIntegration.js.map +1 -1
  39. package/dist/plus/integrations/providers/azure/azure.js +3 -3
  40. package/dist/plus/integrations/providers/azure/azure.js.map +1 -1
  41. package/dist/plus/integrations/providers/azure/models.d.ts +36 -4
  42. package/dist/plus/integrations/providers/azure/models.js +118 -8
  43. package/dist/plus/integrations/providers/azure/models.js.map +1 -1
  44. package/dist/plus/integrations/providers/azureDevOps.d.ts +102 -5
  45. package/dist/plus/integrations/providers/azureDevOps.js +264 -45
  46. package/dist/plus/integrations/providers/azureDevOps.js.map +1 -1
  47. package/dist/plus/integrations/providers/azureDevOpsServer.d.ts +30 -6
  48. package/dist/plus/integrations/providers/azureDevOpsServer.js +82 -21
  49. package/dist/plus/integrations/providers/azureDevOpsServer.js.map +1 -1
  50. package/dist/plus/integrations/providers/bitbucket/bitbucket.d.ts +21 -1
  51. package/dist/plus/integrations/providers/bitbucket/bitbucket.js +74 -10
  52. package/dist/plus/integrations/providers/bitbucket/bitbucket.js.map +1 -1
  53. package/dist/plus/integrations/providers/bitbucket/models.d.ts +45 -8
  54. package/dist/plus/integrations/providers/bitbucket/models.js +32 -9
  55. package/dist/plus/integrations/providers/bitbucket/models.js.map +1 -1
  56. package/dist/plus/integrations/providers/bitbucket-server/models.d.ts +2 -6
  57. package/dist/plus/integrations/providers/bitbucket-server/models.js +1 -3
  58. package/dist/plus/integrations/providers/bitbucket-server/models.js.map +1 -1
  59. package/dist/plus/integrations/providers/bitbucket-server/pullRequestSearch.d.ts +3 -1
  60. package/dist/plus/integrations/providers/bitbucket-server/pullRequestSearch.js +4 -1
  61. package/dist/plus/integrations/providers/bitbucket-server/pullRequestSearch.js.map +1 -1
  62. package/dist/plus/integrations/providers/bitbucket-server.d.ts +2 -2
  63. package/dist/plus/integrations/providers/bitbucket-server.js +7 -5
  64. package/dist/plus/integrations/providers/bitbucket-server.js.map +1 -1
  65. package/dist/plus/integrations/providers/bitbucket.d.ts +22 -2
  66. package/dist/plus/integrations/providers/bitbucket.js +29 -6
  67. package/dist/plus/integrations/providers/bitbucket.js.map +1 -1
  68. package/dist/plus/integrations/providers/github.d.ts +40 -3
  69. package/dist/plus/integrations/providers/github.js +133 -11
  70. package/dist/plus/integrations/providers/github.js.map +1 -1
  71. package/dist/plus/integrations/providers/gitlab/gitlab.d.ts +34 -1
  72. package/dist/plus/integrations/providers/gitlab/gitlab.js +118 -4
  73. package/dist/plus/integrations/providers/gitlab/gitlab.js.map +1 -1
  74. package/dist/plus/integrations/providers/gitlab/models.d.ts +70 -5
  75. package/dist/plus/integrations/providers/gitlab/models.js +106 -13
  76. package/dist/plus/integrations/providers/gitlab/models.js.map +1 -1
  77. package/dist/plus/integrations/providers/gitlab.d.ts +31 -2
  78. package/dist/plus/integrations/providers/gitlab.js +48 -8
  79. package/dist/plus/integrations/providers/gitlab.js.map +1 -1
  80. package/dist/plus/integrations/providers/jira-server.d.ts +3 -2
  81. package/dist/plus/integrations/providers/jira-server.js +18 -9
  82. package/dist/plus/integrations/providers/jira-server.js.map +1 -1
  83. package/dist/plus/integrations/providers/jira.d.ts +26 -2
  84. package/dist/plus/integrations/providers/jira.js +120 -9
  85. package/dist/plus/integrations/providers/jira.js.map +1 -1
  86. package/dist/plus/integrations/providers/jiraIssueByKey.d.ts +79 -0
  87. package/dist/plus/integrations/providers/jiraIssueByKey.js +58 -7
  88. package/dist/plus/integrations/providers/jiraIssueByKey.js.map +1 -1
  89. package/dist/plus/integrations/providers/linear.d.ts +19 -1
  90. package/dist/plus/integrations/providers/linear.js +105 -7
  91. package/dist/plus/integrations/providers/linear.js.map +1 -1
  92. package/dist/plus/integrations/providers/linearIssuesEtag.d.ts +42 -0
  93. package/dist/plus/integrations/providers/linearIssuesEtag.js +94 -0
  94. package/dist/plus/integrations/providers/linearIssuesEtag.js.map +1 -0
  95. package/dist/plus/integrations/providers/models.d.ts +70 -8
  96. package/dist/plus/integrations/providers/models.js +59 -21
  97. package/dist/plus/integrations/providers/models.js.map +1 -1
  98. package/dist/plus/integrations/providers/providerErrors.js +8 -1
  99. package/dist/plus/integrations/providers/providerErrors.js.map +1 -1
  100. package/dist/plus/integrations/providers/providersApi.d.ts +70 -4
  101. package/dist/plus/integrations/providers/providersApi.js +148 -4
  102. package/dist/plus/integrations/providers/providersApi.js.map +1 -1
  103. package/dist/plus/integrations/providers/pullRequestReviews.d.ts +13 -0
  104. package/dist/plus/integrations/providers/pullRequestReviews.js +30 -6
  105. package/dist/plus/integrations/providers/pullRequestReviews.js.map +1 -1
  106. package/dist/plus/integrations/providers/trello.js +11 -7
  107. package/dist/plus/integrations/providers/trello.js.map +1 -1
  108. package/dist/plus/integrations/providers/utils/providerPaging.d.ts +11 -0
  109. package/dist/plus/integrations/providers/utils/providerPaging.js +47 -1
  110. package/dist/plus/integrations/providers/utils/providerPaging.js.map +1 -1
  111. package/dist/plus/integrations/providers/utils.d.ts +1 -1
  112. package/dist/plus/integrations/reads/batchEtags.d.ts +69 -0
  113. package/dist/plus/integrations/reads/batchEtags.js +114 -0
  114. package/dist/plus/integrations/reads/batchEtags.js.map +1 -0
  115. package/dist/plus/integrations/reads/counts.js +2 -2
  116. package/dist/plus/integrations/reads/counts.js.map +1 -1
  117. package/dist/plus/integrations/reads/currentAccount.js +2 -2
  118. package/dist/plus/integrations/reads/currentAccount.js.map +1 -1
  119. package/dist/plus/integrations/reads/cursors.d.ts +8 -0
  120. package/dist/plus/integrations/reads/cursors.js +5 -3
  121. package/dist/plus/integrations/reads/cursors.js.map +1 -1
  122. package/dist/plus/integrations/reads/drains.d.ts +13 -4
  123. package/dist/plus/integrations/reads/drains.js +29 -5
  124. package/dist/plus/integrations/reads/drains.js.map +1 -1
  125. package/dist/plus/integrations/reads/etag.d.ts +26 -0
  126. package/dist/plus/integrations/reads/etag.js +108 -0
  127. package/dist/plus/integrations/reads/etag.js.map +1 -0
  128. package/dist/plus/integrations/reads/filters.d.ts +8 -0
  129. package/dist/plus/integrations/reads/filters.js +2 -1
  130. package/dist/plus/integrations/reads/filters.js.map +1 -1
  131. package/dist/plus/integrations/reads/hierarchy.js +3 -3
  132. package/dist/plus/integrations/reads/hierarchy.js.map +1 -1
  133. package/dist/plus/integrations/reads/issueBatch.d.ts +47 -16
  134. package/dist/plus/integrations/reads/issueBatch.js +55 -87
  135. package/dist/plus/integrations/reads/issueBatch.js.map +1 -1
  136. package/dist/plus/integrations/reads/issueTracker.d.ts +10 -2
  137. package/dist/plus/integrations/reads/issueTracker.js +23 -6
  138. package/dist/plus/integrations/reads/issueTracker.js.map +1 -1
  139. package/dist/plus/integrations/reads/issues.d.ts +1 -1
  140. package/dist/plus/integrations/reads/issues.js +4 -4
  141. package/dist/plus/integrations/reads/issues.js.map +1 -1
  142. package/dist/plus/integrations/reads/pullRequestBatch.d.ts +44 -6
  143. package/dist/plus/integrations/reads/pullRequestBatch.js +31 -34
  144. package/dist/plus/integrations/reads/pullRequestBatch.js.map +1 -1
  145. package/dist/plus/integrations/reads/pullRequestBranches.d.ts +5 -2
  146. package/dist/plus/integrations/reads/pullRequestBranches.js +4 -3
  147. package/dist/plus/integrations/reads/pullRequestBranches.js.map +1 -1
  148. package/dist/plus/integrations/reads/pullRequests.js +8 -4
  149. package/dist/plus/integrations/reads/pullRequests.js.map +1 -1
  150. package/dist/plus/integrations/reads/sweeps.js +8 -4
  151. package/dist/plus/integrations/reads/sweeps.js.map +1 -1
  152. package/dist/plus/integrations/reads/warnings.d.ts +23 -8
  153. package/dist/plus/integrations/reads/warnings.js +43 -19
  154. package/dist/plus/integrations/reads/warnings.js.map +1 -1
  155. package/dist/plus/integrations/results.d.ts +12 -4
  156. package/dist/plus/integrations/results.js +2 -0
  157. package/dist/plus/integrations/results.js.map +1 -1
  158. package/docs/integrations.md +389 -23
  159. package/docs/kepler-read-api-parity.md +16 -2
  160. package/package.json +2 -2
@@ -1,5 +1,7 @@
1
+ import { chunk } from "../../../utils/array.js";
1
2
  import { CancellationError } from "../../../utils/cancellation.js";
2
3
  import { mapSettledBounded } from "../../../utils/promise.js";
4
+ import { PromiseCache } from "../../../utils/promiseCache.js";
3
5
  import "../../../git/models/pullRequest.js";
4
6
  import "../constants.js";
5
7
  import { toCollectionScopeFailure } from "../collectionMetadata.js";
@@ -8,8 +10,11 @@ import "../providerFilters.js";
8
10
  import { collectProviderPagedResult, flatSettledResultsOrThrow, mergeCollectionMetadata, resolveBranchPullRequests, selectBranchPullRequests } from "./utils/providerPaging.js";
9
11
  import { decodePathSegment } from "../utils/domain.utils.js";
10
12
  import { fromProviderIssue, fromProviderPullRequest, getProviderPullRequestIdentity, providerPullRequestMatchesSearch, providersMetadata, toIssueShape, toProviderPullRequestStates } from "./models.js";
13
+ import { toAzurePullRequestEtagFields, toAzureWorkItemEtagFields } from "./azure/models.js";
11
14
  import { GitHostIntegration } from "../models/gitHostIntegration.js";
12
15
  import { getAzurePullRequestIdentityFromMaybeUrl } from "./azure/azure.utils.js";
16
+ import { discoveryCacheTtl } from "./utils/discoveryCache.js";
17
+ import { GitPullRequestState } from "@gitkraken/provider-apis";
13
18
  //#region ../plus/integrations/src/providers/azureDevOps.ts
14
19
  function getAzureRepositoryIdentity(repo) {
15
20
  const match = /^([^/]+)\/_git\/([^/]+)$/i.exec(repo.name);
@@ -49,6 +54,42 @@ function uniqueAzureNames(names) {
49
54
  return unique;
50
55
  }
51
56
  /**
57
+ * Flags the reviews of `pr` that name one of the current user's groups (see `PullRequestReviewer.isMyGroup`), which a
58
+ * reviewer read finds through that group rather than through the user.
59
+ */
60
+ function markMyGroupReviews(pr, groupIds) {
61
+ if (pr.reviews == null || !pr.reviews.some((r) => groupIds.has(r.reviewer.id))) return pr;
62
+ return {
63
+ ...pr,
64
+ reviews: pr.reviews.map((r) => groupIds.has(r.reviewer.id) ? {
65
+ ...r,
66
+ isMyGroup: true
67
+ } : r)
68
+ };
69
+ }
70
+ /**
71
+ * The reads a reviewer relationship over `states` (omitted: open) takes for `userId`, whose groups are `groupIds`:
72
+ *
73
+ * - Open pull requests: all of them, kept when one names the user, or names one of the user's groups and the user
74
+ * didn't write it. Azure's reviewer filter only matches the identity it names, never the groups that identity is in,
75
+ * and a group asked to review the user's own pull request isn't asking the user.
76
+ * - Closed and merged ones: Azure's own filter for the user. They are every pull request a project ever had, too many
77
+ * to read whole on every read, so a group's request is only followed while the pull request is open.
78
+ */
79
+ function toAzureReviewerReads(userId, groupIds, states) {
80
+ const reads = [];
81
+ if (states == null || states.includes(GitPullRequestState.Open)) reads.push({
82
+ filter: { states: [GitPullRequestState.Open] },
83
+ keep: (pr) => pr.reviews?.some((r) => r.reviewer.id === userId || groupIds.has(r.reviewer.id) && pr.author?.id !== userId) ?? false
84
+ });
85
+ const closed = states?.filter((s) => s !== GitPullRequestState.Open) ?? [];
86
+ if (closed.length > 0) reads.push({ filter: {
87
+ reviewerId: userId,
88
+ states: closed
89
+ } });
90
+ return reads;
91
+ }
92
+ /**
52
93
  * Matches an org/project descriptor against a caller-supplied name, mirroring the facade's own
53
94
  * key/id/name comparison so `listIssuesPage({ org, project })` narrows on the same identifiers a consumer
54
95
  * already got back from `listOrgs`/`listProjects`.
@@ -93,6 +134,44 @@ var AzureDevOpsIntegrationBase = class extends GitHostIntegration {
93
134
  return (await this.getProviderCurrentAccount(session))?.id;
94
135
  }
95
136
  /**
137
+ * The current user's groups per credential, collection and user, kept for {@link discoveryCacheTtl} like the other
138
+ * discovered sets: membership changes rarely, and a sweep that reads every few minutes would otherwise pay for it
139
+ * every time. A failed read isn't kept, and a re-sync drops them all (see {@link invalidateDiscoveryCaches}).
140
+ */
141
+ _reviewerGroupIds = new PromiseCache({
142
+ capacity: 50,
143
+ createTTL: discoveryCacheTtl
144
+ });
145
+ /**
146
+ * Every group the current user (`userId`, as {@link getFilterUserId} resolved it) is a member of in `collection`:
147
+ * teams and security groups, in any project, directly or through other groups. Two requests per collection,
148
+ * however many groups, and none while the last read is kept.
149
+ */
150
+ getReviewerGroupIds(session, collection, userId) {
151
+ return this._reviewerGroupIds.getOrCreate(JSON.stringify([
152
+ this.discoveryKey(session),
153
+ collection.toLowerCase(),
154
+ userId
155
+ ]), async () => {
156
+ const api = await this.getProvidersApi();
157
+ const { tokenWithInfo } = this.getApiOptions(session);
158
+ const ids = await api.getAzureGroupIdsForUser(tokenWithInfo, collection, userId, this.getCollectionApiOptions(session, collection));
159
+ return new Set(ids);
160
+ });
161
+ }
162
+ invalidateDiscoveryCaches() {
163
+ super.invalidateDiscoveryCaches();
164
+ this._reviewerGroupIds.clear();
165
+ }
166
+ /**
167
+ * For one read, the current account as each organization's pull requests identify it, given the account itself
168
+ * (`undefined` when it couldn't be read). Azure DevOps Services gives a person one id, so that is `account`; the
169
+ * server override resolves each collection's own, once per read.
170
+ */
171
+ getOrganizationViewers(_session, account) {
172
+ return () => Promise.resolve(account);
173
+ }
174
+ /**
96
175
  * What the discovery caches (account, organizations, projects, and their stored copies) are keyed by. Azure DevOps
97
176
  * Services has a single address, so the credential alone identifies what it discovers; the server override adds
98
177
  * the installation address.
@@ -434,7 +513,7 @@ var AzureDevOpsIntegrationBase = class extends GitHostIntegration {
434
513
  project: c.project
435
514
  }, c.number, this.getCollectionApiOptions(session, c.owner));
436
515
  if (issue == null) return void 0;
437
- const shape = toIssueShape(issue, this);
516
+ const shape = toIssueShape(issue, this, { projection: "batch" });
438
517
  if (shape == null) throw new Error(`Azure DevOps returned work item ${c.number} without a URL or change date`);
439
518
  return shape;
440
519
  });
@@ -447,6 +526,7 @@ var AzureDevOpsIntegrationBase = class extends GitHostIntegration {
447
526
  async getProviderPullRequestsBatch(session, coordinates, options, _cancellation) {
448
527
  const api = await this.getProvidersApi();
449
528
  const { tokenWithInfo } = this.getApiOptions(session);
529
+ const viewerFor = this.getOrganizationViewers(session, options?.currentAccount);
450
530
  return mapSettledBounded(coordinates, 6, async (c) => {
451
531
  const pr = await api.getPullRequestForRepo(tokenWithInfo, {
452
532
  namespace: c.owner,
@@ -456,7 +536,127 @@ var AzureDevOpsIntegrationBase = class extends GitHostIntegration {
456
536
  ...this.getCollectionApiOptions(session, c.owner),
457
537
  includeRemoteInfo: true
458
538
  });
459
- return pr != null ? fromProviderPullRequest(pr, this, { currentAccount: options?.currentAccount }) : void 0;
539
+ if (pr == null) return void 0;
540
+ return fromProviderPullRequest(pr, this, {
541
+ currentAccount: await viewerFor(c.owner),
542
+ projection: "batch"
543
+ });
544
+ });
545
+ }
546
+ /**
547
+ * The cheap check behind the batch issue read's etags: one request per organization, project and
548
+ * {@link azureWorkItemsEtagFieldsMaxIds} distinct ids, where {@link getProviderIssuesBatch} sends one per target,
549
+ * asking only for each work item's change state. A request that throws rejects only its own targets' slots, with
550
+ * its error classified as the full read's would be.
551
+ *
552
+ * Never proves an absence. Azure silently leaves out every id it can't return, whether missing, hidden or failing,
553
+ * so such a target's slot is rejected and the full read, whose not-found does prove an absence, decides. So is a
554
+ * work item in another project, which the full read fails. When no target was answered and nothing failed, the
555
+ * check declines instead, so a batch of only such ids costs a full read, not a failure.
556
+ *
557
+ * A work item has no reactions, and its full row no count, so `'reactions'` reads nothing and costs nothing.
558
+ */
559
+ async getProviderIssuesEtagFields(session, coordinates, _options, _cancellation) {
560
+ const slots = new Array(coordinates.length);
561
+ let answered = false;
562
+ let failed = false;
563
+ const byProject = /* @__PURE__ */ new Map();
564
+ for (const [index, c] of coordinates.entries()) {
565
+ if (c.project == null) {
566
+ slots[index] = {
567
+ status: "rejected",
568
+ reason: /* @__PURE__ */ new Error(`Azure DevOps needs a project to read work item ${c.number}`)
569
+ };
570
+ failed = true;
571
+ continue;
572
+ }
573
+ const key = JSON.stringify([c.owner, c.project]);
574
+ let group = byProject.get(key);
575
+ if (group == null) {
576
+ group = {
577
+ owner: c.owner,
578
+ project: c.project,
579
+ ids: /* @__PURE__ */ new Map()
580
+ };
581
+ byProject.set(key, group);
582
+ }
583
+ let indices = group.ids.get(c.number);
584
+ if (indices == null) {
585
+ indices = [];
586
+ group.ids.set(c.number, indices);
587
+ }
588
+ indices.push(index);
589
+ }
590
+ const requests = [...byProject.values()].flatMap((group) => chunk([...group.ids], 200).map((entries) => ({
591
+ ...group,
592
+ entries
593
+ })));
594
+ if (!requests.length) return slots;
595
+ const api = await this.getProvidersApi();
596
+ const { tokenWithInfo } = this.getApiOptions(session);
597
+ const answers = await mapSettledBounded(requests, 6, (r) => api.getAzureWorkItemsEtagFields(tokenWithInfo, {
598
+ namespace: r.owner,
599
+ project: r.project
600
+ }, r.entries.map(([id]) => id), this.getCollectionApiOptions(session, r.owner)));
601
+ for (const [i, answer] of answers.entries()) {
602
+ const { project, entries } = requests[i];
603
+ if (answer.status === "rejected") {
604
+ failed = true;
605
+ for (const [, indices] of entries) for (const index of indices) slots[index] = answer;
606
+ continue;
607
+ }
608
+ const workItems = /* @__PURE__ */ new Map();
609
+ for (const workItem of answer.value) workItems.set(workItem.id, workItem);
610
+ for (const [id, indices] of entries) {
611
+ const workItem = workItems.get(id);
612
+ let slot;
613
+ if (workItem == null) slot = {
614
+ status: "rejected",
615
+ reason: /* @__PURE__ */ new Error(`Azure DevOps did not return work item ${id}`)
616
+ };
617
+ else try {
618
+ slot = {
619
+ status: "fulfilled",
620
+ value: toAzureWorkItemEtagFields(workItem, project)
621
+ };
622
+ answered = true;
623
+ } catch (ex) {
624
+ slot = {
625
+ status: "rejected",
626
+ reason: ex
627
+ };
628
+ failed = true;
629
+ }
630
+ for (const index of indices) slots[index] = slot;
631
+ }
632
+ }
633
+ return answered || failed ? slots : void 0;
634
+ }
635
+ /**
636
+ * The cheap check behind the batch pull request read's etags: still one request per target, as Azure DevOps has
637
+ * no read of several pull requests by id, but only the one {@link getProviderPullRequestsBatch} sends first —
638
+ * not the repository read provider-apis adds to it for clone URLs, once per pull request. Settled per target,
639
+ * with absence proven by the same not-found rule as the full read.
640
+ *
641
+ * Azure DevOps reports no update time, so a full row's `updatedDate` is its close time, else its creation time,
642
+ * and moves only when it closes. Its etag therefore adds a `revision` of the fields that change without it —
643
+ * title, description, target branch, and every reviewer and their vote — read off the same response at no extra
644
+ * cost. It sees a pull request's state, draft flag, head commit and those fields, and, when included, its
645
+ * mergeability and its required reviewers' review decision; never a comment or a label, which its full row
646
+ * doesn't carry either. A full row carries no check rollup, so the `checks` include adds nothing and costs nothing.
647
+ */
648
+ async getProviderPullRequestsEtagFields(session, coordinates, options, _cancellation) {
649
+ const api = await this.getProvidersApi();
650
+ const { tokenWithInfo } = this.getApiOptions(session);
651
+ const etagIncludes = options.etagIncludes ?? [];
652
+ return mapSettledBounded(coordinates, 6, async (c) => {
653
+ if (c.project == null) throw new Error(`Azure DevOps needs a project to read pull request ${c.number}`);
654
+ const pr = await api.getAzurePullRequest(tokenWithInfo, {
655
+ namespace: c.owner,
656
+ project: c.project,
657
+ name: c.repo
658
+ }, c.number, this.getCollectionApiOptions(session, c.owner));
659
+ return pr != null ? toAzurePullRequestEtagFields(pr, etagIncludes) : void 0;
460
660
  });
461
661
  }
462
662
  /**
@@ -518,42 +718,13 @@ var AzureDevOpsIntegrationBase = class extends GitHostIntegration {
518
718
  return (await this.authenticationService.apis.azure)?.getRepositoryMetadata(this, toTokenWithInfo(this.id, session), repo.owner, repo.name, { baseUrl: getAzureRepositoryApiBaseUrl(this.apiBaseUrlFor(session), repo) }, cancellation);
519
719
  }
520
720
  async searchProviderMyPullRequests(session, repos, _cancellation, options) {
521
- const api = await this.getProvidersApi();
522
721
  if (repos != null) return;
523
- const states = toProviderPullRequestStates(options?.state);
524
- if ((await this.getProviderCurrentAccount(session))?.id == null) return void 0;
525
- const orgs = await this.getProviderResourcesForUser(session);
526
- if (orgs == null || orgs.length === 0) return void 0;
527
- const projects = await this.getProviderProjectsForResources(session, orgs);
528
- if (projects.values.length === 0) return void 0;
529
- const repoDescriptors = [...(await this.getRepoDescriptorsForProjects(session, projects.values) ?? /* @__PURE__ */ new Map()).values()].filter((r) => r != null).flat();
530
- const { tokenWithInfo } = this.getApiOptions(session);
531
- const assignedPrs = [];
532
- const authoredPrs = [];
533
- for (const org of uniqueAzureNames(projects.values.map((p) => p.resourceName))) {
534
- const userId = await this.getFilterUserId(session, org);
535
- if (userId == null) continue;
536
- const projectInputs = projects.values.filter((p) => sameAzureName(p.resourceName, org)).map((p) => ({
537
- namespace: p.resourceName,
538
- project: p.name
539
- }));
540
- const orgOptions = this.getCollectionApiOptions(session, org);
541
- const reads = [[{ assigneeLogins: [userId] }, assignedPrs], [{ authorLogin: userId }, authoredPrs]];
542
- for (const [filter, into] of reads) {
543
- const result = await api.getPullRequestsForAzureProjects(tokenWithInfo, projectInputs, {
544
- ...orgOptions,
545
- ...filter,
546
- states
547
- });
548
- into.push(...result.values.map((pr) => this.fromAzureProviderPullRequest(pr, repoDescriptors, projects.values)));
549
- }
550
- }
551
- const prsByIdentity = /* @__PURE__ */ new Map();
552
- for (const pr of [...authoredPrs, ...assignedPrs]) {
553
- const identity = `${pr.repository.owner}/${pr.repository.id || pr.repository.repo}#${pr.id}`;
554
- if (!prsByIdentity.has(identity)) prsByIdentity.set(identity, pr);
555
- }
556
- return [...prsByIdentity.values()];
722
+ const result = await this.getProviderMyPullRequestsForUser(session, { state: options?.state != null ? [options.state] : void 0 });
723
+ if (result == null) return void 0;
724
+ const projects = (await this.getProviderProjectsForResources(session, await this.getProviderResourcesForUser(session) ?? [])).values;
725
+ if (projects.length === 0) return void 0;
726
+ const repoDescriptors = [...(await this.getRepoDescriptorsForProjects(session, projects) ?? /* @__PURE__ */ new Map()).values()].filter((r) => r != null).flat();
727
+ return result.values.map((pr) => this.fromAzureProviderPullRequest(pr, repoDescriptors, projects, "search"));
557
728
  }
558
729
  async getProviderMyPullRequestsForUser(session, options) {
559
730
  const api = await this.getProvidersApi();
@@ -582,8 +753,8 @@ var AzureDevOpsIntegrationBase = class extends GitHostIntegration {
582
753
  for (let i = 0; i < maxPagesPerProject; i++) try {
583
754
  const result = await api.getPullRequestsForAzureProject(tokenWithInfo, project, {
584
755
  ...this.getCollectionApiOptions(session, project.namespace),
585
- ...filter,
586
756
  states,
757
+ ...filter,
587
758
  page
588
759
  });
589
760
  if (result == null) {
@@ -625,6 +796,8 @@ var AzureDevOpsIntegrationBase = class extends GitHostIntegration {
625
796
  const wantReviewed = requested == null || requested.has("assignee") || requested.has("review-requested");
626
797
  const userIds = /* @__PURE__ */ new Map();
627
798
  for (const org of uniqueAzureNames(projects.values.map((p) => p.resourceName))) userIds.set(org.toLowerCase(), await this.getFilterUserId(session, org));
799
+ const groups = wantReviewed ? await this.getReviewerGroupsByOrganization(session, projects.values, userIds, failures) : void 0;
800
+ if (groups?.incomplete) truncated = true;
628
801
  const outcomes = await Promise.all(projects.values.flatMap((p) => {
629
802
  const project = {
630
803
  namespace: p.resourceName,
@@ -643,7 +816,13 @@ var AzureDevOpsIntegrationBase = class extends GitHostIntegration {
643
816
  }
644
817
  const drains = [];
645
818
  if (wantAuthored) drains.push(drainProject(project, scope, { authorLogin: userId }));
646
- if (wantReviewed) drains.push(drainProject(project, scope, { reviewerId: userId }));
819
+ if (wantReviewed) {
820
+ const groupIds = groups?.byOrganization.get(p.resourceName.toLowerCase()) ?? /* @__PURE__ */ new Set();
821
+ for (const { filter, keep } of toAzureReviewerReads(userId, groupIds, states)) drains.push(drainProject(project, scope, filter).then((o) => keep != null ? {
822
+ ...o,
823
+ prs: o.prs.filter(keep)
824
+ } : o));
825
+ }
647
826
  return drains;
648
827
  }));
649
828
  const prsByIdentity = /* @__PURE__ */ new Map();
@@ -658,8 +837,9 @@ var AzureDevOpsIntegrationBase = class extends GitHostIntegration {
658
837
  completeness: "partial",
659
838
  failures
660
839
  } : void 0, projects.metadata);
840
+ const groupIds = groups?.groupIds ?? /* @__PURE__ */ new Set();
661
841
  return {
662
- values: [...prsByIdentity.values()],
842
+ values: Array.from(prsByIdentity.values(), (pr) => markMyGroupReviews(pr, groupIds)),
663
843
  paging: {
664
844
  cursor: "{}",
665
845
  more: false,
@@ -668,6 +848,38 @@ var AzureDevOpsIntegrationBase = class extends GitHostIntegration {
668
848
  metadata
669
849
  };
670
850
  }
851
+ /**
852
+ * Per organization (keyed by its lowercased name), every group the user is in (see {@link getReviewerGroupIds}),
853
+ * and their union. An organization whose groups can't be read gets none and is recorded in `failures`, with the
854
+ * read reported `incomplete`: what only its groups review is missing, while what names the user is still read.
855
+ * Organizations whose user could not be resolved are left out; the read already reports them.
856
+ */
857
+ async getReviewerGroupsByOrganization(session, projects, userIds, failures) {
858
+ const byOrganization = /* @__PURE__ */ new Map();
859
+ const groupIds = /* @__PURE__ */ new Set();
860
+ let incomplete = false;
861
+ await Promise.all(uniqueAzureNames(projects.map((p) => p.resourceName)).map(async (org) => {
862
+ const userId = userIds.get(org.toLowerCase());
863
+ if (userId == null) return;
864
+ try {
865
+ const ids = await this.getReviewerGroupIds(session, org, userId);
866
+ byOrganization.set(org.toLowerCase(), ids);
867
+ for (const id of ids) groupIds.add(id);
868
+ } catch (ex) {
869
+ const resourceId = projects.find((p) => sameAzureName(p.resourceName, org)).resourceId;
870
+ failures.push(toCollectionScopeFailure({
871
+ providerId: this.id,
872
+ resourceId
873
+ }, ex));
874
+ incomplete = true;
875
+ }
876
+ }));
877
+ return {
878
+ byOrganization,
879
+ groupIds,
880
+ incomplete
881
+ };
882
+ }
671
883
  async searchProviderPullRequests(session, searchQuery, repos, cancellation, options) {
672
884
  if (cancellation?.aborted) throw new CancellationError();
673
885
  const orgs = await this.getProviderResourcesForUser(session);
@@ -719,7 +931,7 @@ var AzureDevOpsIntegrationBase = class extends GitHostIntegration {
719
931
  return values;
720
932
  }));
721
933
  if (cancellation?.aborted) throw new CancellationError();
722
- return [...new Map(providerPullRequests.map((pr) => [pr.url ?? `${pr.repository.id}:${pr.id}`, pr])).values()].filter((pr) => providerPullRequestMatchesSearch(pr, searchQuery)).map((pr) => this.fromAzureProviderPullRequest(pr, repoDescriptors, projects));
934
+ return [...new Map(providerPullRequests.map((pr) => [pr.url ?? `${pr.repository.id}:${pr.id}`, pr])).values()].filter((pr) => providerPullRequestMatchesSearch(pr, searchQuery)).map((pr) => this.fromAzureProviderPullRequest(pr, repoDescriptors, projects, "text-search"));
723
935
  }
724
936
  async searchProviderMyIssues(session, repos) {
725
937
  return (await this.searchProviderMyIssuesWithTruncation(session, repos))?.values;
@@ -779,7 +991,10 @@ var AzureDevOpsIntegrationBase = class extends GitHostIntegration {
779
991
  projectId: p.name
780
992
  });
781
993
  return {
782
- issues: result.values.map((i) => fromProviderIssue(i, this, { project: p })),
994
+ issues: result.values.map((i) => fromProviderIssue(i, this, {
995
+ project: p,
996
+ projection: "account"
997
+ })),
783
998
  projectKey: `${p.resourceId}:${p.id}`,
784
999
  truncated: result.truncated ?? false,
785
1000
  metadata: result.metadata
@@ -887,8 +1102,9 @@ var AzureDevOpsIntegrationBase = class extends GitHostIntegration {
887
1102
  this._organizations = void 0;
888
1103
  this._projects = void 0;
889
1104
  this._accounts = void 0;
1105
+ this._reviewerGroupIds.clear();
890
1106
  }
891
- fromAzureProviderPullRequest(azurePullRequest, repoDescriptors, projectDescriptors) {
1107
+ fromAzureProviderPullRequest(azurePullRequest, repoDescriptors, projectDescriptors, projection) {
892
1108
  const baseRepoDescriptor = repoDescriptors.find((r) => r.id === azurePullRequest.repository.id);
893
1109
  const headRepoDescriptor = azurePullRequest.headRepository != null ? repoDescriptors.find((r) => r.id === azurePullRequest.headRepository.id) : void 0;
894
1110
  let project;
@@ -909,7 +1125,10 @@ var AzureDevOpsIntegrationBase = class extends GitHostIntegration {
909
1125
  }
910
1126
  };
911
1127
  if (baseRepoDescriptor?.projectName != null) project = projectDescriptors.find((p) => p.resourceName === baseRepoDescriptor.resourceName && p.name === baseRepoDescriptor.projectName);
912
- return fromProviderPullRequest(azurePullRequest, this, { project });
1128
+ return fromProviderPullRequest(azurePullRequest, this, {
1129
+ project,
1130
+ projection
1131
+ });
913
1132
  }
914
1133
  };
915
1134
  const cloudMetadata = providersMetadata["azureDevOps"];
@@ -930,6 +1149,6 @@ var AzureDevOpsIntegration = class extends AzureDevOpsIntegrationBase {
930
1149
  }
931
1150
  };
932
1151
  //#endregion
933
- export { AzureDevOpsIntegration, AzureDevOpsIntegrationBase, getAzureRepositoryApiBaseUrl, getAzureRepositoryIdentity, sameAzureName, uniqueAzureNames };
1152
+ export { AzureDevOpsIntegration, AzureDevOpsIntegrationBase, getAzureRepositoryApiBaseUrl, getAzureRepositoryIdentity, markMyGroupReviews, sameAzureName, toAzureReviewerReads, uniqueAzureNames };
934
1153
 
935
1154
  //# sourceMappingURL=azureDevOps.js.map