@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
@@ -41,26 +41,22 @@ import type {
41
41
  SetPullRequestInput,
42
42
  Trello,
43
43
  } from '@gitkraken/provider-apis';
44
- import entityIdentifiersModule from '@gitkraken/provider-apis/entity-identifiers';
45
- import providerUtilsModule from '@gitkraken/provider-apis/provider-utils';
44
+ import { EntityIdentifierUtils } from '@gitkraken/provider-apis/entity-identifiers';
45
+ import { GitProviderUtils } from '@gitkraken/provider-apis/provider-utils';
46
46
  import { githubSearchResultLimit } from '../../git-github/api/config.js';
47
47
  import type { Account as UserAccount } from '../../../git/models/author.js';
48
- import type { IssueMember, IssueProject, IssueShape, IssueStateFilter } from '../../../git/models/issue.js';
48
+ import type { IssueProject, IssueShape, IssueStateFilter } from '../../../git/models/issue.js';
49
49
  import { Issue, RepositoryAccessLevel } from '../../../git/models/issue.js';
50
50
  import type {
51
- PullRequestMember,
52
51
  PullRequestRef,
53
52
  PullRequestRefs,
54
53
  PullRequestRepositoryIdentityDescriptor,
55
- PullRequestReviewer,
56
54
  PullRequestState,
57
55
  PullRequestStateFilter,
58
56
  } from '../../../git/models/pullRequest.js';
59
57
  import {
60
58
  PullRequest,
61
59
  PullRequestMergeableState,
62
- PullRequestReviewDecision,
63
- PullRequestReviewState,
64
60
  PullRequestStatusCheckRollupState,
65
61
  } from '../../../git/models/pullRequest.js';
66
62
  import type { Provider, ProviderReference } from '../../../git/models/remoteProvider.js';
@@ -75,7 +71,12 @@ import {
75
71
  IssuesCloudHostIntegrationId,
76
72
  } from '../constants.js';
77
73
  import type { Integration, IntegrationType } from '../models/integration.js';
78
- import type { IssueSearchCapabilities, IssueSorting, PullRequestSearchCapabilities } from '../providerFilters.js';
74
+ import type {
75
+ IssueSearchCapabilities,
76
+ IssueSorting,
77
+ PullRequestSearchCapabilities,
78
+ PullRequestSorting,
79
+ } from '../providerFilters.js';
79
80
  import { IssueFilter, PullRequestFilter } from '../providerFilters.js';
80
81
 
81
82
  export { IssueFilter, PullRequestFilter } from '../providerFilters.js';
@@ -87,10 +88,13 @@ export type {
87
88
  IssueSorting,
88
89
  PullRequestSearchCapabilities,
89
90
  PullRequestSearchCriteria,
91
+ PullRequestSortField,
92
+ PullRequestSorting,
90
93
  } from '../providerFilters.js';
91
94
  import type { ProviderRepositoryShape } from '../results.js';
92
95
 
93
96
  export type { ProviderOrganization, ProviderRepositoryShape } from '../results.js';
97
+ import { fromProviderAccount, toProviderAccount } from './accounts.js';
94
98
  import {
95
99
  azureAccountWideIssueSorts,
96
100
  azureIssueSorts,
@@ -102,6 +106,15 @@ import {
102
106
  linearIssueSorts,
103
107
  trelloIssueSorts,
104
108
  } from './issueSorts.js';
109
+ import type { ProviderPullRequestReview, ProviderPullRequestReviews } from './pullRequestReviews.js';
110
+ import {
111
+ fromPullRequestReviewDecision,
112
+ providerPullRequestReviewStateDismissed,
113
+ toCompletedReviews,
114
+ toProviderReviewDecision,
115
+ toProviderReviews,
116
+ toReviewRequests,
117
+ } from './pullRequestReviews.js';
105
118
  import { getEntityIdentifierInput } from './utils.js';
106
119
 
107
120
  type GitBuildStatusState = GitBuildStatusStateType;
@@ -110,9 +123,6 @@ type GitPullRequestMergeableState = GitPullRequestMergeableStateType;
110
123
  type GitPullRequestReviewState = GitPullRequestReviewStateType;
111
124
  type GitPullRequestState = GitPullRequestStateType;
112
125
 
113
- const { EntityIdentifierUtils } = entityIdentifiersModule;
114
- const { GitProviderUtils } = providerUtilsModule;
115
-
116
126
  // Local runtime copies of the `@gitkraken/provider-apis` string enums. Duplicated (not imported as
117
127
  // values) because the SDK ships as CJS whose named enum exports can't be statically imported as ESM
118
128
  // values from the bundled `dist`. Exported so the enum-parity test can deep-equal them against the
@@ -191,7 +201,7 @@ export function isRepoIdsInput(input: unknown): input is (string | number)[] {
191
201
  );
192
202
  }
193
203
 
194
- export type ProviderPullRequest = GitPullRequest;
204
+ export type ProviderPullRequest = Omit<GitPullRequest, 'reviews'> & { reviews: ProviderPullRequestReviews };
195
205
  export type ProviderRepository = GitRepository;
196
206
  export type ProviderIssue = ProviderApiIssue;
197
207
  export type ProviderEnterpriseOptions = EnterpriseOptions;
@@ -859,19 +869,36 @@ const githubIssueSearchCapabilities: IssueSearchCapabilities = {
859
869
  sorts: githubIssueSorts,
860
870
  };
861
871
 
872
+ /**
873
+ * The ordering vocabulary of GitHub's filtered PR search: `created` and `updated`, both directions.
874
+ *
875
+ * Hand-declared rather than derived from `@gitkraken/provider-apis` (which has no PR-sort surface), and narrower
876
+ * than the issue set on purpose: the merged relationship × state facets can only be re-ordered by a field a
877
+ * `PullRequestShape` carries (see `getPullRequestComparator`), and GitHub PRs have no priority and no relevance
878
+ * that orders stably under the result ceiling. A parity test pins this to `gitHubPullRequestSortQualifiers`' keys
879
+ * so the capability can't outrun the emitter.
880
+ */
881
+ const githubPullRequestSorts: PullRequestSorting[] = ['updated:desc', 'updated:asc', 'created:desc', 'created:asc'];
882
+
862
883
  /** GitHub and GHE use the same filtered pull-request search syntax and result ceiling. */
863
884
  const githubPullRequestSearchCapabilities: PullRequestSearchCapabilities = {
864
885
  relationships: [
865
886
  PullRequestFilter.Author,
866
887
  PullRequestFilter.Assignee,
867
888
  PullRequestFilter.ReviewRequested,
889
+ PullRequestFilter.Reviewed,
868
890
  PullRequestFilter.Mention,
869
891
  ],
870
892
  states: ['open', 'closed', 'merged', 'all'],
871
893
  text: true,
894
+ updatedAfter: true,
895
+ createdAfter: true,
872
896
  includeArchived: true,
897
+ draft: true,
873
898
  repositoryScope: true,
874
899
  organizationScope: true,
900
+ // `sort:updated`, `sort:updated-asc`, `sort:created-desc`, `sort:created-asc` — see `githubPullRequestSorts`.
901
+ sorts: githubPullRequestSorts,
875
902
  };
876
903
 
877
904
  export const providersMetadata: ProvidersMetadata = {
@@ -894,6 +921,7 @@ export const providersMetadata: ProvidersMetadata = {
894
921
  PullRequestFilter.Author,
895
922
  PullRequestFilter.Assignee,
896
923
  PullRequestFilter.ReviewRequested,
924
+ PullRequestFilter.Reviewed,
897
925
  PullRequestFilter.Mention,
898
926
  ],
899
927
  supportedPullRequestSearch: githubPullRequestSearchCapabilities,
@@ -930,6 +958,7 @@ export const providersMetadata: ProvidersMetadata = {
930
958
  PullRequestFilter.Author,
931
959
  PullRequestFilter.Assignee,
932
960
  PullRequestFilter.ReviewRequested,
961
+ PullRequestFilter.Reviewed,
933
962
  PullRequestFilter.Mention,
934
963
  ],
935
964
  supportedPullRequestSearch: githubPullRequestSearchCapabilities,
@@ -1320,22 +1349,6 @@ export const fromProviderBuildStatusState = {
1320
1349
  [GitBuildStatusState.Warning]: undefined,
1321
1350
  };
1322
1351
 
1323
- export const toProviderPullRequestReviewState = {
1324
- [PullRequestReviewState.Approved]: GitPullRequestReviewState.Approved,
1325
- [PullRequestReviewState.ChangesRequested]: GitPullRequestReviewState.ChangesRequested,
1326
- [PullRequestReviewState.Commented]: GitPullRequestReviewState.Commented,
1327
- [PullRequestReviewState.ReviewRequested]: GitPullRequestReviewState.ReviewRequested,
1328
- [PullRequestReviewState.Dismissed]: null,
1329
- [PullRequestReviewState.Pending]: null,
1330
- };
1331
-
1332
- export const fromProviderPullRequestReviewState = {
1333
- [GitPullRequestReviewState.Approved]: PullRequestReviewState.Approved,
1334
- [GitPullRequestReviewState.ChangesRequested]: PullRequestReviewState.ChangesRequested,
1335
- [GitPullRequestReviewState.Commented]: PullRequestReviewState.Commented,
1336
- [GitPullRequestReviewState.ReviewRequested]: PullRequestReviewState.ReviewRequested,
1337
- };
1338
-
1339
1352
  export const toProviderPullRequestMergeableState = {
1340
1353
  [PullRequestMergeableState.Mergeable]: GitPullRequestMergeableState.Mergeable,
1341
1354
  [PullRequestMergeableState.Conflicting]: GitPullRequestMergeableState.Conflicts,
@@ -1355,68 +1368,6 @@ export const fromProviderPullRequestMergeableState = {
1355
1368
  [GitPullRequestMergeableState.Unstable]: PullRequestMergeableState.Unknown,
1356
1369
  };
1357
1370
 
1358
- export function toProviderReviews(reviewers: PullRequestReviewer[]): ProviderPullRequest['reviews'] {
1359
- return reviewers
1360
- .filter(r => r.state !== PullRequestReviewState.Dismissed && r.state !== PullRequestReviewState.Pending)
1361
- .map(reviewer => ({
1362
- reviewer: toProviderAccount(reviewer.reviewer),
1363
- state: toProviderPullRequestReviewState[reviewer.state] ?? GitPullRequestReviewState.ReviewRequested,
1364
- }));
1365
- }
1366
-
1367
- export function toReviewRequests(reviews: ProviderPullRequest['reviews']): PullRequestReviewer[] | undefined {
1368
- return reviews == null
1369
- ? undefined
1370
- : reviews
1371
- ?.filter(r => r.state === GitPullRequestReviewState.ReviewRequested)
1372
- .map(r => ({
1373
- isCodeOwner: false, // TODO: Find this value, and implement in the shared lib if needed
1374
- reviewer: fromProviderAccount(r.reviewer),
1375
- state: PullRequestReviewState.ReviewRequested,
1376
- }));
1377
- }
1378
-
1379
- export function toCompletedReviews(reviews: ProviderPullRequest['reviews']): PullRequestReviewer[] | undefined {
1380
- return reviews == null
1381
- ? undefined
1382
- : reviews
1383
- ?.filter(r => r.state !== GitPullRequestReviewState.ReviewRequested)
1384
- .map(r => ({
1385
- isCodeOwner: false, // TODO: Find this value, and implement in the shared lib if needed
1386
- reviewer: fromProviderAccount(r.reviewer),
1387
- state: fromProviderPullRequestReviewState[r.state],
1388
- }));
1389
- }
1390
-
1391
- export function toProviderReviewDecision(
1392
- reviewDecision?: PullRequestReviewDecision,
1393
- reviewers?: PullRequestReviewer[],
1394
- ): GitPullRequestReviewState | null {
1395
- switch (reviewDecision) {
1396
- case PullRequestReviewDecision.Approved:
1397
- return GitPullRequestReviewState.Approved;
1398
- case PullRequestReviewDecision.ChangesRequested:
1399
- return GitPullRequestReviewState.ChangesRequested;
1400
- case PullRequestReviewDecision.ReviewRequired:
1401
- return GitPullRequestReviewState.ReviewRequested;
1402
- default: {
1403
- if (reviewers?.some(r => r.state === PullRequestReviewState.ReviewRequested)) {
1404
- return GitPullRequestReviewState.ReviewRequested;
1405
- } else if (reviewers?.some(r => r.state === PullRequestReviewState.Commented)) {
1406
- return GitPullRequestReviewState.Commented;
1407
- }
1408
- return null;
1409
- }
1410
- }
1411
- }
1412
-
1413
- export const fromPullRequestReviewDecision = {
1414
- [GitPullRequestReviewState.Approved]: PullRequestReviewDecision.Approved,
1415
- [GitPullRequestReviewState.ChangesRequested]: PullRequestReviewDecision.ChangesRequested,
1416
- [GitPullRequestReviewState.Commented]: undefined,
1417
- [GitPullRequestReviewState.ReviewRequested]: PullRequestReviewDecision.ReviewRequired,
1418
- };
1419
-
1420
1371
  export function toProviderPullRequestState(state: PullRequestState): GitPullRequestState {
1421
1372
  return state === 'opened'
1422
1373
  ? GitPullRequestState.Open
@@ -1541,7 +1492,11 @@ export function toProviderPullRequest(pr: PullRequest): ProviderPullRequest {
1541
1492
  name: pr.refs.head.branch,
1542
1493
  oid: pr.refs.head.sha,
1543
1494
  },
1544
- reviews: toProviderReviews(prReviews),
1495
+ // `null` when the source carried no review data at all, rather than the empty array a lite read would
1496
+ // otherwise produce: `toCompletedReviews`/`toReviewRequests` map `null` back to `undefined`, which is what
1497
+ // lets a consumer tell "nobody has reviewed this" from "this read never fetched reviews" (see
1498
+ // `PullRequestShape.latestReviews`). An empty array from a full projection stays an empty array.
1499
+ reviews: pr.reviewRequests == null && pr.latestReviews == null ? null : toProviderReviews(prReviews),
1545
1500
  reviewDecision: toProviderReviewDecision(pr.reviewDecision, prReviews),
1546
1501
  repository:
1547
1502
  pr.repository != null
@@ -1740,46 +1695,31 @@ export function fromProviderIssue(
1740
1695
  }
1741
1696
 
1742
1697
  export function toProviderPullRequestWithUniqueId(pr: PullRequest): PullRequestWithUniqueID {
1698
+ const { reviews, ...providerPr } = toProviderPullRequest(pr);
1743
1699
  return {
1744
- ...toProviderPullRequest(pr),
1700
+ ...providerPr,
1701
+ // The SDK boundary: `getActionablePullRequests` categorizes by review state and only knows
1702
+ // provider-apis' own vocabulary, so the locally-added dismissed state is dropped here rather than
1703
+ // handed over as a value it would fall through on. Our own projection keeps it — see
1704
+ // `providerPullRequestReviewStateDismissed`. `commitOid` rides along on the surviving reviews: it is an
1705
+ // extra property the SDK ignores, not a value it could misread.
1706
+ reviews:
1707
+ reviews?.filter(
1708
+ (r): r is ProviderPullRequestReview & { state: GitPullRequestReviewState } =>
1709
+ r.state !== providerPullRequestReviewStateDismissed,
1710
+ ) ?? null,
1745
1711
  uuid: EntityIdentifierUtils.encode(getEntityIdentifierInput(pr)),
1746
1712
  };
1747
1713
  }
1748
1714
 
1749
- export function toProviderAccount(account: PullRequestMember | IssueMember): ProviderAccount {
1750
- return {
1751
- id: account.id ?? null,
1752
- avatarUrl: account.avatarUrl ?? null,
1753
- name: account.name ?? null,
1754
- url: account.url ?? null,
1755
- // TODO: Implement these in our own model
1756
- email: '',
1757
- username: account.name ?? null,
1758
- };
1759
- }
1760
-
1761
- export function fromProviderAccount(account: ProviderAccount | null): PullRequestMember | IssueMember {
1762
- return {
1763
- id: account?.id ?? '',
1764
- // An absent name stays absent. `'unknown'` was a display fallback invented in the provider layer, and a
1765
- // consumer couldn't tell it apart from a member genuinely named that — so it couldn't be undone where a
1766
- // name-shaped placeholder is wrong (rendering an avatar-only chip, or building an AI prompt, where
1767
- // `Assignees: unknown` reads as a real assignee). It also disagreed with {@link toIssueShape}, which
1768
- // collapsed the same absent name to `''` — same facade method, two fallbacks. Both now emit `undefined`.
1769
- name: account?.name ?? undefined,
1770
- avatarUrl: account?.avatarUrl ?? undefined,
1771
- // `url` is optional, so an absent one must be `undefined`, not `''`. `''` passes a `!= null` presence
1772
- // check and renders as a link to nowhere, and it disagreed with {@link toIssueShape} — which already
1773
- // collapses to `undefined` — even though BOTH mappers feed `listIssuesPage` (the repo-scoped path goes
1774
- // through `toIssueShape`, Azure's account-wide path through {@link fromProviderIssue}). Same facade
1775
- // method, two shapes. Matches {@link toProviderRepositoryShape}, which collapses every absent optional.
1776
- url: account?.url ?? undefined,
1777
- };
1778
- }
1779
-
1780
1715
  export type ProviderActionablePullRequest = ActionablePullRequest;
1781
1716
 
1782
- export type EnrichablePullRequest = ProviderPullRequest & {
1717
+ /**
1718
+ * Built on the SDK's `GitPullRequest`, NOT the local {@link ProviderPullRequest} widening: this is handed to
1719
+ * `getActionablePullRequests`, which categorizes by review state and only knows provider-apis' vocabulary.
1720
+ * {@link toProviderPullRequestWithUniqueId} is what narrows `reviews` down to it.
1721
+ */
1722
+ export type EnrichablePullRequest = GitPullRequest & {
1783
1723
  uuid: string;
1784
1724
  type: 'pullrequest';
1785
1725
  provider: ProviderReference;
@@ -0,0 +1,122 @@
1
+ import type { GitPullRequest } from '@gitkraken/provider-apis';
2
+ import { GitPullRequestReviewState } from '@gitkraken/provider-apis';
3
+ import type { PullRequestReviewer } from '../../../git/models/pullRequest.js';
4
+ import { PullRequestReviewDecision, PullRequestReviewState } from '../../../git/models/pullRequest.js';
5
+ import { fromProviderAccount, toProviderAccount } from './accounts.js';
6
+
7
+ /**
8
+ * The GitLens-local extension of provider-apis' review shape, in two parts: a `DISMISSED` state the upstream
9
+ * `GitPullRequestReviewState` has no member for, and the head-commit oid a review was submitted against
10
+ * (optional, so a raw SDK review stays assignable — only the GitHub path populates it).
11
+ *
12
+ * Both exist for the same case: on GitHub the `dismiss stale reviews` rule flips an approval to dismissed on
13
+ * the next push, which is exactly the "the PR moved past my review" situation the oid detects. Dropping such a
14
+ * review instead would hand a consumer a PR out of the `reviewed-by:@me` set with no review row at all,
15
+ * indistinguishable from never having reviewed it.
16
+ */
17
+ export const providerPullRequestReviewStateDismissed = 'DISMISSED' as const;
18
+ export type ProviderPullRequestReview = Omit<NonNullable<GitPullRequest['reviews']>[number], 'state'> & {
19
+ state: GitPullRequestReviewState | typeof providerPullRequestReviewStateDismissed;
20
+ commitOid?: string;
21
+ };
22
+ /**
23
+ * The review list as it travels on {@link ProviderPullRequest}: `null` when the read carried no review data at
24
+ * all, as opposed to an empty array for a pull request nobody has reviewed.
25
+ */
26
+ export type ProviderPullRequestReviews = ProviderPullRequestReview[] | null;
27
+
28
+ export const toProviderPullRequestReviewState = {
29
+ [PullRequestReviewState.Approved]: GitPullRequestReviewState.Approved,
30
+ [PullRequestReviewState.ChangesRequested]: GitPullRequestReviewState.ChangesRequested,
31
+ [PullRequestReviewState.Commented]: GitPullRequestReviewState.Commented,
32
+ [PullRequestReviewState.ReviewRequested]: GitPullRequestReviewState.ReviewRequested,
33
+ [PullRequestReviewState.Dismissed]: providerPullRequestReviewStateDismissed,
34
+ // A review the author started but never submitted. Visible only to that author and carrying no verdict, so
35
+ // it stays unmapped and is dropped by the projection.
36
+ [PullRequestReviewState.Pending]: null,
37
+ };
38
+
39
+ export const fromProviderPullRequestReviewState = {
40
+ [GitPullRequestReviewState.Approved]: PullRequestReviewState.Approved,
41
+ [GitPullRequestReviewState.ChangesRequested]: PullRequestReviewState.ChangesRequested,
42
+ [GitPullRequestReviewState.Commented]: PullRequestReviewState.Commented,
43
+ [GitPullRequestReviewState.ReviewRequested]: PullRequestReviewState.ReviewRequested,
44
+ [providerPullRequestReviewStateDismissed]: PullRequestReviewState.Dismissed,
45
+ };
46
+
47
+ export function toProviderReviews(reviewers: PullRequestReviewer[]): ProviderPullRequestReviews {
48
+ // Only `Pending` maps to null (see `toProviderPullRequestReviewState`), so this drops exactly the reviews
49
+ // that carry no verdict rather than defaulting them to `ReviewRequested` — which would report an unsubmitted
50
+ // draft as a pending request from that reviewer.
51
+ return reviewers
52
+ .filter(r => r.state !== PullRequestReviewState.Pending)
53
+ .map(reviewer => ({
54
+ reviewer: toProviderAccount(reviewer.reviewer),
55
+ state: toProviderPullRequestReviewState[reviewer.state] ?? GitPullRequestReviewState.ReviewRequested,
56
+ commitOid: reviewer.commitOid,
57
+ }));
58
+ }
59
+
60
+ export function toReviewRequests(reviews: ProviderPullRequestReviews): PullRequestReviewer[] | undefined {
61
+ return reviews == null
62
+ ? undefined
63
+ : reviews
64
+ ?.filter(r => r.state === GitPullRequestReviewState.ReviewRequested)
65
+ .map(r => ({
66
+ isCodeOwner: false, // TODO: Find this value, and implement in the shared lib if needed
67
+ reviewer: fromProviderAccount(r.reviewer),
68
+ state: PullRequestReviewState.ReviewRequested,
69
+ }));
70
+ }
71
+
72
+ export function toCompletedReviews(reviews: ProviderPullRequestReviews): PullRequestReviewer[] | undefined {
73
+ return reviews == null
74
+ ? undefined
75
+ : reviews
76
+ ?.filter(
77
+ r =>
78
+ r.state !== GitPullRequestReviewState.ReviewRequested &&
79
+ // provider-apis' own GitHub normalizer maps only its four known states, so a review it has no
80
+ // member for (a real `DISMISSED` one, which its `latestReviews(first: 100)` selection does
81
+ // return) arrives with `state: undefined` — a value its type says cannot happen. Publishing it
82
+ // would put an unswitchable state on `PullRequestShape.latestReviews`, and mapping back would
83
+ // hit `toProviderReviews`' fallback and report a dismissed review as an outstanding request.
84
+ // Our own GitHub path never lands here: it carries `providerPullRequestReviewStateDismissed`.
85
+ fromProviderPullRequestReviewState[r.state] != null,
86
+ )
87
+ .map(r => ({
88
+ isCodeOwner: false, // TODO: Find this value, and implement in the shared lib if needed
89
+ reviewer: fromProviderAccount(r.reviewer),
90
+ state: fromProviderPullRequestReviewState[r.state],
91
+ commitOid: r.commitOid,
92
+ }));
93
+ }
94
+
95
+ export function toProviderReviewDecision(
96
+ reviewDecision?: PullRequestReviewDecision,
97
+ reviewers?: PullRequestReviewer[],
98
+ ): GitPullRequestReviewState | null {
99
+ switch (reviewDecision) {
100
+ case PullRequestReviewDecision.Approved:
101
+ return GitPullRequestReviewState.Approved;
102
+ case PullRequestReviewDecision.ChangesRequested:
103
+ return GitPullRequestReviewState.ChangesRequested;
104
+ case PullRequestReviewDecision.ReviewRequired:
105
+ return GitPullRequestReviewState.ReviewRequested;
106
+ default: {
107
+ if (reviewers?.some(r => r.state === PullRequestReviewState.ReviewRequested)) {
108
+ return GitPullRequestReviewState.ReviewRequested;
109
+ } else if (reviewers?.some(r => r.state === PullRequestReviewState.Commented)) {
110
+ return GitPullRequestReviewState.Commented;
111
+ }
112
+ return null;
113
+ }
114
+ }
115
+ }
116
+
117
+ export const fromPullRequestReviewDecision = {
118
+ [GitPullRequestReviewState.Approved]: PullRequestReviewDecision.Approved,
119
+ [GitPullRequestReviewState.ChangesRequested]: PullRequestReviewDecision.ChangesRequested,
120
+ [GitPullRequestReviewState.Commented]: undefined,
121
+ [GitPullRequestReviewState.ReviewRequested]: PullRequestReviewDecision.ReviewRequired,
122
+ };