@gitkraken/core-gitlens 0.5.108 → 0.5.109

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 (137) hide show
  1. package/CHANGELOG.md +17 -1
  2. package/dist/git/models/issue.d.ts +70 -0
  3. package/dist/git/models/issue.d.ts.map +1 -1
  4. package/dist/git/models/issue.js +17 -0
  5. package/dist/git/models/issue.js.map +1 -1
  6. package/dist/git/utils/issue.utils.d.ts +41 -1
  7. package/dist/git/utils/issue.utils.d.ts.map +1 -1
  8. package/dist/git/utils/issue.utils.js +80 -0
  9. package/dist/git/utils/issue.utils.js.map +1 -1
  10. package/dist/plus/git-github/api/github.d.ts +33 -6
  11. package/dist/plus/git-github/api/github.d.ts.map +1 -1
  12. package/dist/plus/git-github/api/github.js +137 -15
  13. package/dist/plus/git-github/api/github.js.map +1 -1
  14. package/dist/plus/git-github/api/issueSearchQuery.d.ts +49 -5
  15. package/dist/plus/git-github/api/issueSearchQuery.d.ts.map +1 -1
  16. package/dist/plus/git-github/api/issueSearchQuery.js +64 -6
  17. package/dist/plus/git-github/api/issueSearchQuery.js.map +1 -1
  18. package/dist/plus/integrations/collectionMetadata.d.ts +18 -0
  19. package/dist/plus/integrations/collectionMetadata.d.ts.map +1 -1
  20. package/dist/plus/integrations/collectionMetadata.js +24 -0
  21. package/dist/plus/integrations/collectionMetadata.js.map +1 -1
  22. package/dist/plus/integrations/integrationService.d.ts +15 -9
  23. package/dist/plus/integrations/integrationService.d.ts.map +1 -1
  24. package/dist/plus/integrations/integrationService.js.map +1 -1
  25. package/dist/plus/integrations/manager.d.ts +40 -26
  26. package/dist/plus/integrations/manager.d.ts.map +1 -1
  27. package/dist/plus/integrations/models/gitHostIntegration.d.ts +6 -31
  28. package/dist/plus/integrations/models/gitHostIntegration.d.ts.map +1 -1
  29. package/dist/plus/integrations/models/gitHostIntegration.js +11 -1
  30. package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -1
  31. package/dist/plus/integrations/models/issueReads.d.ts +63 -1
  32. package/dist/plus/integrations/models/issueReads.d.ts.map +1 -1
  33. package/dist/plus/integrations/models/issuesIntegration.d.ts +7 -21
  34. package/dist/plus/integrations/models/issuesIntegration.d.ts.map +1 -1
  35. package/dist/plus/integrations/models/issuesIntegration.js.map +1 -1
  36. package/dist/plus/integrations/providerFilters.d.ts +1 -1
  37. package/dist/plus/integrations/providerFilters.d.ts.map +1 -1
  38. package/dist/plus/integrations/providers/azureDevOps.d.ts.map +1 -1
  39. package/dist/plus/integrations/providers/azureDevOps.js +1 -0
  40. package/dist/plus/integrations/providers/azureDevOps.js.map +1 -1
  41. package/dist/plus/integrations/providers/github.d.ts.map +1 -1
  42. package/dist/plus/integrations/providers/github.js +1 -0
  43. package/dist/plus/integrations/providers/github.js.map +1 -1
  44. package/dist/plus/integrations/providers/gitlab.d.ts.map +1 -1
  45. package/dist/plus/integrations/providers/gitlab.js +1 -0
  46. package/dist/plus/integrations/providers/gitlab.js.map +1 -1
  47. package/dist/plus/integrations/providers/issueSorts.d.ts +36 -0
  48. package/dist/plus/integrations/providers/issueSorts.d.ts.map +1 -0
  49. package/dist/plus/integrations/providers/issueSorts.js +76 -0
  50. package/dist/plus/integrations/providers/issueSorts.js.map +1 -0
  51. package/dist/plus/integrations/providers/jira.d.ts +3 -9
  52. package/dist/plus/integrations/providers/jira.d.ts.map +1 -1
  53. package/dist/plus/integrations/providers/jira.js +5 -1
  54. package/dist/plus/integrations/providers/jira.js.map +1 -1
  55. package/dist/plus/integrations/providers/linear.d.ts +24 -9
  56. package/dist/plus/integrations/providers/linear.d.ts.map +1 -1
  57. package/dist/plus/integrations/providers/linear.js +63 -5
  58. package/dist/plus/integrations/providers/linear.js.map +1 -1
  59. package/dist/plus/integrations/providers/models.d.ts +63 -7
  60. package/dist/plus/integrations/providers/models.d.ts.map +1 -1
  61. package/dist/plus/integrations/providers/models.js +55 -0
  62. package/dist/plus/integrations/providers/models.js.map +1 -1
  63. package/dist/plus/integrations/providers/providersApi.d.ts +18 -3
  64. package/dist/plus/integrations/providers/providersApi.d.ts.map +1 -1
  65. package/dist/plus/integrations/providers/providersApi.js +24 -7
  66. package/dist/plus/integrations/providers/providersApi.js.map +1 -1
  67. package/dist/plus/integrations/providers/trello.d.ts +3 -9
  68. package/dist/plus/integrations/providers/trello.d.ts.map +1 -1
  69. package/dist/plus/integrations/providers/trello.js +1 -0
  70. package/dist/plus/integrations/providers/trello.js.map +1 -1
  71. package/dist/plus/integrations/providers/utils/providerPaging.d.ts.map +1 -1
  72. package/dist/plus/integrations/providers/utils/providerPaging.js +5 -1
  73. package/dist/plus/integrations/providers/utils/providerPaging.js.map +1 -1
  74. package/dist/plus/integrations/reads/broaden.d.ts +6 -0
  75. package/dist/plus/integrations/reads/broaden.d.ts.map +1 -1
  76. package/dist/plus/integrations/reads/broaden.js.map +1 -1
  77. package/dist/plus/integrations/reads/counts.d.ts +8 -1
  78. package/dist/plus/integrations/reads/counts.d.ts.map +1 -1
  79. package/dist/plus/integrations/reads/counts.js.map +1 -1
  80. package/dist/plus/integrations/reads/filters.d.ts +59 -9
  81. package/dist/plus/integrations/reads/filters.d.ts.map +1 -1
  82. package/dist/plus/integrations/reads/filters.js +17 -0
  83. package/dist/plus/integrations/reads/filters.js.map +1 -1
  84. package/dist/plus/integrations/reads/issueTracker.d.ts +18 -1
  85. package/dist/plus/integrations/reads/issueTracker.d.ts.map +1 -1
  86. package/dist/plus/integrations/reads/issueTracker.js +40 -3
  87. package/dist/plus/integrations/reads/issueTracker.js.map +1 -1
  88. package/dist/plus/integrations/reads/issues.d.ts +27 -3
  89. package/dist/plus/integrations/reads/issues.d.ts.map +1 -1
  90. package/dist/plus/integrations/reads/issues.js +223 -123
  91. package/dist/plus/integrations/reads/issues.js.map +1 -1
  92. package/dist/plus/integrations/reads/ordering.d.ts +101 -0
  93. package/dist/plus/integrations/reads/ordering.d.ts.map +1 -0
  94. package/dist/plus/integrations/reads/ordering.js +48 -0
  95. package/dist/plus/integrations/reads/ordering.js.map +1 -0
  96. package/dist/plus/integrations/reads/searchIssues.d.ts +7 -2
  97. package/dist/plus/integrations/reads/searchIssues.d.ts.map +1 -1
  98. package/dist/plus/integrations/reads/searchIssues.js +9 -3
  99. package/dist/plus/integrations/reads/searchIssues.js.map +1 -1
  100. package/dist/plus/integrations/reads/warnings.d.ts +34 -2
  101. package/dist/plus/integrations/reads/warnings.d.ts.map +1 -1
  102. package/dist/plus/integrations/reads/warnings.js +47 -2
  103. package/dist/plus/integrations/reads/warnings.js.map +1 -1
  104. package/dist/plus/integrations/results.d.ts +10 -0
  105. package/dist/plus/integrations/results.d.ts.map +1 -1
  106. package/dist/plus/integrations/results.js.map +1 -1
  107. package/package.json +2 -2
  108. package/src/git/models/issue.ts +73 -0
  109. package/src/git/utils/issue.utils.ts +91 -1
  110. package/src/plus/git-github/api/github.ts +180 -20
  111. package/src/plus/git-github/api/issueSearchQuery.ts +67 -7
  112. package/src/plus/integrations/collectionMetadata.ts +24 -0
  113. package/src/plus/integrations/integrationService.ts +15 -10
  114. package/src/plus/integrations/manager.ts +40 -31
  115. package/src/plus/integrations/models/gitHostIntegration.ts +18 -31
  116. package/src/plus/integrations/models/issueReads.ts +65 -1
  117. package/src/plus/integrations/models/issuesIntegration.ts +7 -6
  118. package/src/plus/integrations/providerFilters.ts +2 -0
  119. package/src/plus/integrations/providers/azureDevOps.ts +1 -0
  120. package/src/plus/integrations/providers/github.ts +1 -0
  121. package/src/plus/integrations/providers/gitlab.ts +1 -0
  122. package/src/plus/integrations/providers/issueSorts.ts +89 -0
  123. package/src/plus/integrations/providers/jira.ts +9 -3
  124. package/src/plus/integrations/providers/linear.ts +77 -8
  125. package/src/plus/integrations/providers/models.ts +122 -5
  126. package/src/plus/integrations/providers/providersApi.ts +51 -13
  127. package/src/plus/integrations/providers/trello.ts +4 -3
  128. package/src/plus/integrations/providers/utils/providerPaging.ts +6 -1
  129. package/src/plus/integrations/reads/broaden.ts +6 -0
  130. package/src/plus/integrations/reads/counts.ts +8 -1
  131. package/src/plus/integrations/reads/filters.ts +70 -8
  132. package/src/plus/integrations/reads/issueTracker.ts +78 -4
  133. package/src/plus/integrations/reads/issues.ts +386 -194
  134. package/src/plus/integrations/reads/ordering.ts +124 -0
  135. package/src/plus/integrations/reads/searchIssues.ts +15 -3
  136. package/src/plus/integrations/reads/warnings.ts +72 -1
  137. package/src/plus/integrations/results.ts +10 -0
@@ -75,7 +75,7 @@ import {
75
75
  IssuesCloudHostIntegrationId,
76
76
  } from '../constants.js';
77
77
  import type { Integration, IntegrationType } from '../models/integration.js';
78
- import type { IssueSearchCapabilities, PullRequestSearchCapabilities } from '../providerFilters.js';
78
+ import type { IssueSearchCapabilities, IssueSorting, PullRequestSearchCapabilities } from '../providerFilters.js';
79
79
  import { IssueFilter, PullRequestFilter } from '../providerFilters.js';
80
80
 
81
81
  export { IssueFilter, PullRequestFilter } from '../providerFilters.js';
@@ -83,12 +83,25 @@ export type {
83
83
  IssueSearchCapabilities,
84
84
  IssueSearchCriteria,
85
85
  IssueSearchRelationship,
86
+ IssueSortField,
87
+ IssueSorting,
86
88
  PullRequestSearchCapabilities,
87
89
  PullRequestSearchCriteria,
88
90
  } from '../providerFilters.js';
89
91
  import type { ProviderRepositoryShape } from '../results.js';
90
92
 
91
93
  export type { ProviderOrganization, ProviderRepositoryShape } from '../results.js';
94
+ import {
95
+ azureAccountWideIssueSorts,
96
+ azureIssueSorts,
97
+ githubAccountWideIssueSorts,
98
+ githubIssueSorts,
99
+ gitlabAccountWideIssueSorts,
100
+ gitlabIssueSorts,
101
+ jiraIssueSorts,
102
+ linearIssueSorts,
103
+ trelloIssueSorts,
104
+ } from './issueSorts.js';
92
105
  import { getEntityIdentifierInput } from './utils.js';
93
106
 
94
107
  type GitBuildStatusState = GitBuildStatusStateType;
@@ -156,6 +169,28 @@ type EnrichableItem = any;
156
169
  export type ProviderAccount = Account;
157
170
  export type ProviderReposInput = (string | number)[] | GetRepoInput[];
158
171
  export type ProviderRepoInput = GetRepoInput;
172
+
173
+ /**
174
+ * Which half of {@link ProviderReposInput} a caller supplied: repository IDS rather than descriptors.
175
+ *
176
+ * The union has no discriminant, so the answer is the element shape — and several layers need it for different
177
+ * reasons (`ProvidersApi` to pick `repoIds` vs `repos` in the SDK input, `GitHostIntegration` to refuse an
178
+ * id-based read a provider can't serve, `listIssuesPage` to know GitLab's id form takes the SDK's own merging
179
+ * aggregate). One predicate rather than one per layer, because each copy is free to disagree about the shape,
180
+ * and the symptom of disagreement is a read routed to the wrong provider query.
181
+ *
182
+ * Takes `unknown` rather than {@link ProviderReposInput} because `ProvidersApi.isRepoIdsInput` forwards here and
183
+ * is public with an unvalidated parameter, so the array check has to stay part of the rule rather than be assumed
184
+ * from the parameter type.
185
+ */
186
+ export function isRepoIdsInput(input: unknown): input is (string | number)[] {
187
+ return (
188
+ input != null &&
189
+ Array.isArray(input) &&
190
+ input.every(repo => typeof repo === 'string' || typeof repo === 'number')
191
+ );
192
+ }
193
+
159
194
  export type ProviderPullRequest = GitPullRequest;
160
195
  export type ProviderRepository = GitRepository;
161
196
  export type ProviderIssue = ProviderApiIssue;
@@ -331,6 +366,20 @@ export interface GetIssuesOptions {
331
366
  page?: number;
332
367
  // Items to request per page (numbered-page providers, plus GitHub's maxPageSize).
333
368
  pageSize?: number;
369
+ /**
370
+ * How the provider should order the result, as `field:direction`.
371
+ *
372
+ * Passed through to the SDK verbatim — the translation to each provider's own vocabulary lives THERE, next to
373
+ * the capability map it validates against, so that the table this facade publishes and the table that does the
374
+ * translating can't drift apart across two repositories. A key the provider can't express raises the SDK's
375
+ * `UnsupportedSortError`, which is why the facade validates against `supportedIssueSorts` first: reaching that
376
+ * error means this table has outrun the SDK's.
377
+ *
378
+ * Always supplied by the facade's reads, never omitted. Omitting it would delegate the order to each provider's
379
+ * own default, which is the incoherence across providers the SDK deliberately preserves and this layer
380
+ * deliberately does not.
381
+ */
382
+ sort?: IssueSorting;
334
383
  }
335
384
 
336
385
  export interface GetIssuesForRepoInput extends GetIssuesOptions {
@@ -492,6 +541,8 @@ export type GetIssuesForCurrentUserInput = PagingInput & {
492
541
  assigneeUsername?: string;
493
542
  authorUsername?: string;
494
543
  pageSize?: number;
544
+ /** See {@link GetIssuesOptions.sort}. */
545
+ sort?: IssueSorting;
495
546
  };
496
547
 
497
548
  export type GetIssuesForCurrentUserFn = (
@@ -624,10 +675,10 @@ export type GetBitbucketServerPullRequestsForCurrentUserFn = (
624
675
  data: GitPullRequest[];
625
676
  }>;
626
677
  export type GetIssuesForProjectFn = Jira['getIssuesForProject'];
627
- export type GetIssuesForResourceForCurrentUserFn = (
628
- input: { resourceId: string },
629
- options?: EnterpriseOptions,
630
- ) => Promise<{ data: ProviderIssue[] }>;
678
+ // Derived from the client method rather than hand-declared, as its project-scoped sibling above already is: the
679
+ // hand-written shape named only `resourceId`, so every other field the SDK accepts (the cursor, the sort, the
680
+ // transitions switch) was invisible to the type system and had to be smuggled through `getPagedResult`'s `any`.
681
+ export type GetIssuesForResourceForCurrentUserFn = Jira['getIssuesForResourceForCurrentUser'];
631
682
 
632
683
  // Trello reads (issues-capable provider). The Trello client is keyed by an `appKey` (the Trello app key from
633
684
  // the cloud token exchange) alongside the OAuth token, so these mirror the client method shapes directly.
@@ -729,6 +780,36 @@ export interface ProviderMetadata {
729
780
  * axes; it omits `Mention` because that REST read has no first-class mention filter.
730
781
  */
731
782
  supportedAccountWideIssueFilters?: IssueFilter[];
783
+ /**
784
+ * Sort keys the REPO-scoped issue read can express server-side — and, for an issue tracker (Jira/Linear/Trello),
785
+ * its project-scoped read, which is the only issue surface a tracker has.
786
+ *
787
+ * Absent/empty means the read can't be ordered at all, so a request carrying `sort` is refused rather than
788
+ * served in whatever order the provider felt like. Present, it is a promise: every key here reaches the
789
+ * provider query.
790
+ *
791
+ * A key listed here is expressible on ONE provider query, which is what this read is when it is given ONE
792
+ * scope. Given several (GitLab across repositories, Azure across projects, a tracker across projects) it merges
793
+ * their results here and can only honor a key a normalized issue carries, so it additionally refuses
794
+ * `priority`/`dueDate`/`resolved` — see `getIssueComparator`. That depends on the caller's scope count rather
795
+ * than on the provider, which is why it is a runtime refusal and not a fourth table: encoding it here would
796
+ * under-report the single-scope read, which really does support those keys.
797
+ */
798
+ supportedIssueSorts?: IssueSorting[];
799
+ /**
800
+ * Sort keys the ACCOUNT-WIDE issue read can express server-side, which is NOT the same set as
801
+ * {@link ProviderMetadata.supportedIssueSorts} — for GitLab the two reads are different APIs (GraphQL vs REST)
802
+ * with genuinely different vocabularies, not one narrowed twice.
803
+ *
804
+ * Absent means the account-wide read can't be ordered (or doesn't exist). An issue tracker leaves this empty
805
+ * and reports under `supportedIssueSorts`, so reading a tracker's capability from here under-reports it.
806
+ *
807
+ * Unlike {@link ProviderMetadata.supportedIssueSorts} this needs no companion runtime refusal, because every
808
+ * provider's account-wide read is a union of several queries with no scope count for a caller to reduce: it
809
+ * ALWAYS merges. So the keys a merge can't order by are simply absent here — the table states what the read can
810
+ * honor, which is what makes intersecting against it sufficient.
811
+ */
812
+ supportedAccountWideIssueSorts?: IssueSorting[];
732
813
  /**
733
814
  * What the provider's FILTERED issue search (`searchIssuesPage`, and the `countIssues` probe over the same
734
815
  * criteria) can express server-side. A third, wider surface than either filter set above: it is not bound to
@@ -774,6 +855,8 @@ const githubIssueSearchCapabilities: IssueSearchCapabilities = {
774
855
  withoutLinkedPullRequest: true,
775
856
  // `is:open` / `is:closed`, or neither for all states.
776
857
  states: true,
858
+ // `sort:created-desc`, `sort:updated`, `sort:comments-asc`, … — see `githubIssueSorts`.
859
+ sorts: githubIssueSorts,
777
860
  };
778
861
 
779
862
  /** GitHub and GHE use the same filtered pull-request search syntax and result ceiling. */
@@ -820,6 +903,10 @@ export const providersMetadata: ProvidersMetadata = {
820
903
  // The account-wide read is three independent searches (`author:@me`, `assignee:@me`, `mentions:@me`) behind
821
904
  // one composite cursor, so any subset of them is expressible.
822
905
  supportedAccountWideIssueFilters: [IssueFilter.Author, IssueFilter.Assignee, IssueFilter.Mention],
906
+ // One `search` field serves all three issue reads (repo-scoped, account-wide, filtered search), so the same
907
+ // qualifiers are expressible on each — the account-wide one narrowed to what its three-alias merge can order.
908
+ supportedIssueSorts: githubIssueSorts,
909
+ supportedAccountWideIssueSorts: githubAccountWideIssueSorts,
823
910
  supportedIssueSearch: githubIssueSearchCapabilities,
824
911
  issueSearchResultLimit: githubSearchResultLimit,
825
912
  scopes: ['repo', 'read:user', 'user:email'],
@@ -852,6 +939,10 @@ export const providersMetadata: ProvidersMetadata = {
852
939
  // The account-wide read is three independent searches (`author:@me`, `assignee:@me`, `mentions:@me`) behind
853
940
  // one composite cursor, so any subset of them is expressible.
854
941
  supportedAccountWideIssueFilters: [IssueFilter.Author, IssueFilter.Assignee, IssueFilter.Mention],
942
+ // One `search` field serves all three issue reads (repo-scoped, account-wide, filtered search), so the same
943
+ // qualifiers are expressible on each — the account-wide one narrowed to what its three-alias merge can order.
944
+ supportedIssueSorts: githubIssueSorts,
945
+ supportedAccountWideIssueSorts: githubAccountWideIssueSorts,
855
946
  supportedIssueSearch: githubIssueSearchCapabilities,
856
947
  issueSearchResultLimit: githubSearchResultLimit,
857
948
  scopes: ['repo', 'read:user', 'user:email'],
@@ -881,6 +972,10 @@ export const providersMetadata: ProvidersMetadata = {
881
972
  // unioned, so either axis is expressible on its own. Mention is absent because GitLab's REST issue read has
882
973
  // no first-class mention filter to narrow with.
883
974
  supportedAccountWideIssueFilters: [IssueFilter.Assignee, IssueFilter.Author],
975
+ // Two different surfaces, not one narrowed twice: the repository-scoped read is GraphQL and the
976
+ // account-wide one is REST, and REST has neither `title` nor `closed_at`.
977
+ supportedIssueSorts: gitlabIssueSorts,
978
+ supportedAccountWideIssueSorts: gitlabAccountWideIssueSorts,
884
979
  scopes: ['api', 'read_user', 'read_repository'],
885
980
  },
886
981
  [GitSelfManagedHostIntegrationId.CloudGitLabSelfHosted]: {
@@ -908,6 +1003,10 @@ export const providersMetadata: ProvidersMetadata = {
908
1003
  // unioned, so either axis is expressible on its own. Mention is absent because GitLab's REST issue read has
909
1004
  // no first-class mention filter to narrow with.
910
1005
  supportedAccountWideIssueFilters: [IssueFilter.Assignee, IssueFilter.Author],
1006
+ // Two different surfaces, not one narrowed twice: the repository-scoped read is GraphQL and the
1007
+ // account-wide one is REST, and REST has neither `title` nor `closed_at`.
1008
+ supportedIssueSorts: gitlabIssueSorts,
1009
+ supportedAccountWideIssueSorts: gitlabAccountWideIssueSorts,
911
1010
  scopes: ['api', 'read_user', 'read_repository'],
912
1011
  },
913
1012
  [GitCloudHostIntegrationId.Bitbucket]: {
@@ -956,6 +1055,11 @@ export const providersMetadata: ProvidersMetadata = {
956
1055
  // The account-wide read drains one (project × assignee) and one (project × author) query per project, so
957
1056
  // either axis is expressible on its own. There is no mention query to narrow to.
958
1057
  supportedAccountWideIssueFilters: [IssueFilter.Author, IssueFilter.Assignee],
1058
+ // Both reads emit the same WIQL, but the account-wide one drains every (project x relationship) query and
1059
+ // merges the results, so it can only honor keys a normalized issue carries — `resolved` and `priority` are
1060
+ // dropped from that surface for the same reason they are dropped from GitLab's.
1061
+ supportedIssueSorts: azureIssueSorts,
1062
+ supportedAccountWideIssueSorts: azureAccountWideIssueSorts,
959
1063
  scopes: ['vso.code', 'vso.identity', 'vso.project', 'vso.profile', 'vso.work'],
960
1064
  },
961
1065
  [GitSelfManagedHostIntegrationId.AzureDevOpsServer]: {
@@ -982,6 +1086,11 @@ export const providersMetadata: ProvidersMetadata = {
982
1086
  // The account-wide read drains one (project × assignee) and one (project × author) query per project, so
983
1087
  // either axis is expressible on its own. There is no mention query to narrow to.
984
1088
  supportedAccountWideIssueFilters: [IssueFilter.Author, IssueFilter.Assignee],
1089
+ // Both reads emit the same WIQL, but the account-wide one drains every (project x relationship) query and
1090
+ // merges the results, so it can only honor keys a normalized issue carries — `resolved` and `priority` are
1091
+ // dropped from that surface for the same reason they are dropped from GitLab's.
1092
+ supportedIssueSorts: azureIssueSorts,
1093
+ supportedAccountWideIssueSorts: azureAccountWideIssueSorts,
985
1094
  scopes: ['vso.code', 'vso.identity', 'vso.project', 'vso.profile', 'vso.work'],
986
1095
  },
987
1096
  [IssuesCloudHostIntegrationId.Jira]: {
@@ -1027,6 +1136,9 @@ export const providersMetadata: ProvidersMetadata = {
1027
1136
  'read:project-version:jira',
1028
1137
  ],
1029
1138
  supportedIssueFilters: [IssueFilter.Author, IssueFilter.Assignee, IssueFilter.Mention],
1139
+ // A tracker's issues live under resource -> project, so it has no account-wide surface to declare: its
1140
+ // capability is reported under `issues`, which is what `listIssueTrackerIssuesPage` validates against.
1141
+ supportedIssueSorts: jiraIssueSorts,
1030
1142
  },
1031
1143
  [IssuesCloudHostIntegrationId.Linear]: {
1032
1144
  domain: 'linear.app',
@@ -1037,6 +1149,9 @@ export const providersMetadata: ProvidersMetadata = {
1037
1149
  scopes: [],
1038
1150
  // Linear scopes "my issues" client-side by the viewer's assignee id; author/mention aren't supported.
1039
1151
  supportedIssueFilters: [IssueFilter.Assignee],
1152
+ // Both directions, and one table for both reads: they are the same root `issues` query taking the same
1153
+ // `sort` argument, so there is no account-wide narrowing to declare.
1154
+ supportedIssueSorts: linearIssueSorts,
1040
1155
  },
1041
1156
  [IssuesCloudHostIntegrationId.Trello]: {
1042
1157
  domain: 'trello.com',
@@ -1047,6 +1162,8 @@ export const providersMetadata: ProvidersMetadata = {
1047
1162
  scopes: [],
1048
1163
  // Trello cards are filtered by the assignee (member) only; author/mention have no Trello equivalent.
1049
1164
  supportedIssueFilters: [IssueFilter.Assignee],
1165
+ // `sort:edited` / `sort:-edited`. Trello's other search sorts (`created`, `due`) have no card-order effect.
1166
+ supportedIssueSorts: trelloIssueSorts,
1050
1167
  },
1051
1168
  };
1052
1169
 
@@ -39,6 +39,7 @@ import type {
39
39
  GetPullRequestsOptions,
40
40
  GetReposOptions,
41
41
  IssueFilter,
42
+ IssueSorting,
42
43
  PageInfo,
43
44
  PagingInput,
44
45
  PagingMode,
@@ -67,7 +68,7 @@ import type {
67
68
  Providers,
68
69
  PullRequestFilter,
69
70
  } from './models.js';
70
- import { providersMetadata } from './models.js';
71
+ import { isRepoIdsInput, providersMetadata } from './models.js';
71
72
  import {
72
73
  collectProviderPagedResult,
73
74
  mergeCollectionMetadata,
@@ -132,6 +133,14 @@ function isGraphQLRepoNotFoundError(ex: unknown): boolean {
132
133
 
133
134
  const trelloBaseUrl = 'https://api.trello.com';
134
135
 
136
+ /**
137
+ * Jira computes an issue's available transitions per issue, from the workflow and the reader's permissions, so a
138
+ * list read at a page size of 100 pays for 100 of those in both server work and response payload. Nothing in this
139
+ * package reads `statusTransitions` off a list — only the singular `getIssue` path exposes them, and it always
140
+ * expands them — so every list read opts out. Revisit the day a list row offers a status change straight off it.
141
+ */
142
+ const jiraListIncludeTransitions = false;
143
+
135
144
  type TrelloMemberResponse = {
136
145
  id: string;
137
146
  username?: string | null;
@@ -479,12 +488,9 @@ export class ProvidersApi {
479
488
  );
480
489
  }
481
490
 
482
- isRepoIdsInput(input: any): input is (string | number)[] {
483
- return (
484
- input != null &&
485
- Array.isArray(input) &&
486
- input.every((id: any) => typeof id === 'string' || typeof id === 'number')
487
- );
491
+ /** See {@link isRepoIdsInput}, which owns the rule; kept as a method because callers reach it through the api. */
492
+ isRepoIdsInput(input: unknown): input is (string | number)[] {
493
+ return isRepoIdsInput(input);
488
494
  }
489
495
 
490
496
  private async getProviderToken<T extends IntegrationIds>(
@@ -868,7 +874,10 @@ export class ProvidersApi {
868
874
  async getLinearIssues(
869
875
  tokenOptInfo: TokenWithInfo<IssuesCloudHostIntegrationId.Linear>,
870
876
  input: { teams?: string[]; projects?: string[]; labels?: string[] },
871
- options?: PagingInput,
877
+ options?: PagingInput & {
878
+ /** See {@link GetIssuesOptions.sort}. Linear expresses `created`/`updated`, descending only. */
879
+ sort?: IssueSorting;
880
+ },
872
881
  ): Promise<PagedResult<ProviderIssue>> {
873
882
  const { provider, tokenWithInfo } = await this.ensureProviderTokenAndFunction(
874
883
  tokenOptInfo,
@@ -1565,6 +1574,8 @@ export class ProvidersApi {
1565
1574
  */
1566
1575
  authorUsername?: string;
1567
1576
  pageSize?: number;
1577
+ /** See {@link GetIssuesOptions.sort}. Forwarded to the provider fn; ordering is translated in the SDK. */
1578
+ sort?: IssueSorting;
1568
1579
  isPAT?: boolean;
1569
1580
  baseUrl?: string;
1570
1581
  },
@@ -1580,6 +1591,7 @@ export class ProvidersApi {
1580
1591
  authorUsername: options?.authorUsername,
1581
1592
  page: options?.page,
1582
1593
  pageSize: options?.pageSize,
1594
+ sort: options?.sort,
1583
1595
  },
1584
1596
  provider.getIssuesForCurrentUserFn,
1585
1597
  tokenWithInfo,
@@ -1626,7 +1638,12 @@ export class ProvidersApi {
1626
1638
 
1627
1639
  try {
1628
1640
  const result = await provider.getIssuesForProjectFn?.(
1629
- { projectKey: project, resourceId: resourceId, ...options },
1641
+ {
1642
+ projectKey: project,
1643
+ resourceId: resourceId,
1644
+ ...options,
1645
+ includeTransitions: jiraListIncludeTransitions,
1646
+ },
1630
1647
  { token: token },
1631
1648
  );
1632
1649
 
@@ -1655,7 +1672,12 @@ export class ProvidersApi {
1655
1672
 
1656
1673
  try {
1657
1674
  const result = await provider.getIssuesForProjectFn?.(
1658
- { projectKey: project, resourceId: resourceId, ...options },
1675
+ {
1676
+ projectKey: project,
1677
+ resourceId: resourceId,
1678
+ ...options,
1679
+ includeTransitions: jiraListIncludeTransitions,
1680
+ },
1659
1681
  { token: token },
1660
1682
  );
1661
1683
  if (result == null) return undefined;
@@ -1760,7 +1782,12 @@ export class ProvidersApi {
1760
1782
  tokenOptInfo: TokenWithInfo,
1761
1783
  appKey: string,
1762
1784
  boardId: string,
1763
- options?: { assigneeLogins?: string[]; trelloBoardListsById?: Record<string, { name: string }> },
1785
+ options?: {
1786
+ assigneeLogins?: string[];
1787
+ trelloBoardListsById?: Record<string, { name: string }>;
1788
+ /** See {@link GetIssuesOptions.sort}. Trello expresses only `updated`, in either direction. */
1789
+ sort?: IssueSorting;
1790
+ },
1764
1791
  ): Promise<ProviderApiCollectionResult<ProviderIssue>> {
1765
1792
  const { provider, tokenWithInfo } = await this.ensureProviderTokenAndFunction(
1766
1793
  tokenOptInfo,
@@ -1782,7 +1809,18 @@ export class ProvidersApi {
1782
1809
  async getIssuesForResourceForCurrentUser(
1783
1810
  tokenOptInfo: TokenWithInfo,
1784
1811
  resourceId: string,
1785
- options?: { cursor?: string; isPAT?: boolean; baseUrl?: string },
1812
+ options?: {
1813
+ cursor?: string;
1814
+ /**
1815
+ * See {@link GetIssuesOptions.sort}. Forwarded for the same reason as every sibling issue read, even
1816
+ * though nothing reaches this one with a sort today: `IssuesIntegration.searchProviderMyIssues` is the
1817
+ * only caller and takes no options. A wrapper that accepted the SDK's input and silently dropped one
1818
+ * field of it would give whoever adds that option no ordering and no error.
1819
+ */
1820
+ sort?: IssueSorting;
1821
+ isPAT?: boolean;
1822
+ baseUrl?: string;
1823
+ },
1786
1824
  ): Promise<PagedResult<ProviderIssue>> {
1787
1825
  const { provider, tokenWithInfo } = await this.ensureProviderTokenAndFunction(
1788
1826
  tokenOptInfo,
@@ -1790,7 +1828,7 @@ export class ProvidersApi {
1790
1828
  );
1791
1829
 
1792
1830
  return this.getPagedResult<ProviderIssue>(
1793
- { resourceId: resourceId },
1831
+ { resourceId: resourceId, sort: options?.sort, includeTransitions: jiraListIncludeTransitions },
1794
1832
  provider.getIssuesForResourceForCurrentUserFn,
1795
1833
  tokenWithInfo,
1796
1834
  options?.cursor,
@@ -9,8 +9,8 @@ import type { ProviderAuthenticationSession } from '../authentication/models.js'
9
9
  import { toTokenWithInfo } from '../authentication/models.js';
10
10
  import { IssuesCloudHostIntegrationId } from '../constants.js';
11
11
  import { IntegrationReadUnavailableError } from '../errors.js';
12
+ import type { IssuesForProjectOptions } from '../models/issueReads.js';
12
13
  import { IssuesIntegration } from '../models/issuesIntegration.js';
13
- import type { IssueFilter } from './models.js';
14
14
  import { fromProviderIssue, providersMetadata, toIssueShape } from './models.js';
15
15
 
16
16
  const metadata = providersMetadata[IssuesCloudHostIntegrationId.Trello];
@@ -95,7 +95,7 @@ export class TrelloIntegration extends IssuesIntegration<IssuesCloudHostIntegrat
95
95
  protected override async getProviderIssuesForProject(
96
96
  session: ProviderAuthenticationSession,
97
97
  project: ResourceDescriptor,
98
- options?: { user?: string; filters?: IssueFilter[] },
98
+ options?: IssuesForProjectOptions,
99
99
  ): Promise<IssueShape[] | undefined> {
100
100
  return (await this.getProviderIssuesForProjectWithTruncation(session, project, options))?.values;
101
101
  }
@@ -103,7 +103,7 @@ export class TrelloIntegration extends IssuesIntegration<IssuesCloudHostIntegrat
103
103
  protected override async getProviderIssuesForProjectWithTruncation(
104
104
  session: ProviderAuthenticationSession,
105
105
  project: ResourceDescriptor,
106
- options?: { user?: string; filters?: IssueFilter[] },
106
+ options?: IssuesForProjectOptions,
107
107
  ): Promise<{ values: IssueShape[]; truncated: boolean; metadata?: CollectionMetadata } | undefined> {
108
108
  // A non-issue descriptor genuinely has nothing to read (empty), but a missing app key is a broken read.
109
109
  if (!isIssueResourceDescriptor(project)) return undefined;
@@ -129,6 +129,7 @@ export class TrelloIntegration extends IssuesIntegration<IssuesCloudHostIntegrat
129
129
  const result = await api.getTrelloIssuesForBoard(tokenWithInfo, appKey, project.id, {
130
130
  assigneeLogins: options?.user != null ? [options.user] : undefined,
131
131
  trelloBoardListsById: trelloBoardListsById,
132
+ sort: options?.sort,
132
133
  });
133
134
 
134
135
  const values = result.values.flatMap(issue => {
@@ -6,7 +6,7 @@ import type {
6
6
  } from '@gitkraken/provider-apis';
7
7
  import { isCancellationError } from '../../../../utils/cancellation.js';
8
8
  import { uniqueBy } from '../../../../utils/iterable.js';
9
- import { toCollectionScopeFailure } from '../../collectionMetadata.js';
9
+ import { throwIfCallerContractError, toCollectionScopeFailure } from '../../collectionMetadata.js';
10
10
  import { collectionScopeKey } from '../../results.js';
11
11
  import type { ProviderApiPagedResult, ProviderHierarchyResult } from '../models.js';
12
12
 
@@ -144,6 +144,11 @@ export async function collectProviderPagedResult<T>(
144
144
  } catch (ex) {
145
145
  if (isCancellationError(ex)) throw ex;
146
146
 
147
+ // A caller-contract error is not a fact about this scope, so it is never recorded as one — see
148
+ // `throwIfCallerContractError`. Checked alongside cancellation because both are errors that a
149
+ // per-scope failure would misdescribe.
150
+ throwIfCallerContractError(ex);
151
+
147
152
  // When the caller supplied a scope, preserve the items already fetched from that scope and record the
148
153
  // failure in collection metadata rather than re-throwing and discarding the prefix. Callers without a
149
154
  // scope keep the legacy throw behavior.
@@ -33,6 +33,12 @@ import { gitHostOnlySurfaceWarning, issuesUnsupportedWarning, noConnectionWarnin
33
33
  * `includeAllAssignees: true`, which resolves to no assignee constraint at all, so unassigned issues ARE
34
34
  * included. The equivalent is an OMITTED `relationships`, not `['any-assignee']`: `assignee:*` means "has some
35
35
  * assignee" and would silently exclude every unassigned issue, which is the opposite of broadening.
36
+ *
37
+ * Takes no `sort`, unlike `listIssuesPage` and `searchIssuesPage`. One logical page here spans several orgs, each
38
+ * at its own provider position in a cursor bundle, so honoring an order across them would need a k-way merge with
39
+ * a buffer per org rather than a sort of what arrived — the page is a slice of several independent walks, not a
40
+ * union of one round's results. `searchIssuesPage({ repos, criteria: { sort } })` answers the ordered version of
41
+ * this question for a caller that knows its repositories, and is already the recommended migration above.
36
42
  */
37
43
 
38
44
  export interface BroadenIssuesOptions {
@@ -44,7 +44,14 @@ export interface IssueCountScope {
44
44
  /** Repositories to count over. Combines with `org`, exactly as in `searchIssuesPage`. */
45
45
  repos?: ProviderReposInput;
46
46
  org?: string;
47
- /** Same criteria model as `searchIssuesPage`, validated against the same capability table. */
47
+ /**
48
+ * Same criteria model as `searchIssuesPage`, validated against the same capability table.
49
+ *
50
+ * `criteria.sort` does not affect the count — a total is invariant under ordering — but it is still VALIDATED,
51
+ * and an unsupported key refuses this scope exactly as it would refuse the read. That is the point of the
52
+ * count: it previews the constraints the read will apply, so one that accepted what the read refuses would
53
+ * promise a fetch that can't happen, which is worse than no preview at all.
54
+ */
48
55
  criteria?: IssueSearchCriteria;
49
56
  }
50
57
 
@@ -4,6 +4,7 @@ import type {
4
4
  IssueSearchCapabilities,
5
5
  IssueSearchCriteria,
6
6
  IssueSearchRelationship,
7
+ IssueSorting,
7
8
  PullRequestFilter,
8
9
  PullRequestSearchCapabilities,
9
10
  PullRequestSearchCriteria,
@@ -237,6 +238,12 @@ export function resolveIssueSearchCriteria(
237
238
  if (criteria.state != null && criteria.state !== 'open' && !supported.states) {
238
239
  unsupported.push('state');
239
240
  }
241
+ // Folded into the same `unsupported-criteria` rejection rather than given its own reason, so a caller asking
242
+ // for one inexpressible filter and one inexpressible sort learns about both at once instead of fixing them one
243
+ // refusal at a time. The `sorts` list is `supported`'s own, so this needs no branch on which surface it is.
244
+ if (criteria.sort != null && !supported.sorts.includes(criteria.sort)) {
245
+ unsupported.push(`sort:${criteria.sort}`);
246
+ }
240
247
 
241
248
  if (unsupported.length > 0) return { rejection: { reason: 'unsupported-criteria', criteria: unsupported } };
242
249
 
@@ -264,6 +271,7 @@ const unsupportedIssueSearchCapabilities: IssueSearchCapabilities = {
264
271
  createdAfter: false,
265
272
  withoutLinkedPullRequest: false,
266
273
  states: false,
274
+ sorts: [],
267
275
  };
268
276
 
269
277
  /** What a provider with no filtered pull-request search reports. */
@@ -313,6 +321,57 @@ export function resolveIssueSearchScope(
313
321
  return { rejection: 'unscoped' };
314
322
  }
315
323
 
324
+ /**
325
+ * What {@link getSupportedFilters} reports: every vocabulary a provider's reads can express, so a consumer can
326
+ * narrow to it BEFORE issuing a read. See that function for the capability table itself and why intersecting
327
+ * against this is what keeps a filtered read from being refused.
328
+ *
329
+ * Named rather than written inline, because the same shape is the declared return type of THREE declarations that
330
+ * all describe one value — this function, `IntegrationService.getSupportedFilters`, which only forwards to it, and
331
+ * `IntegrationManager.getSupportedFilters`, which publishes it. Spelled out three times, a member added here
332
+ * reached one of them and left the others a compile error away from the truth; that is exactly how the two sort
333
+ * members below came to be written out three times.
334
+ */
335
+ export type SupportedFilters = {
336
+ pullRequests: PullRequestFilter[];
337
+ pullRequestsAccountWide: PullRequestFilter[];
338
+ /**
339
+ * Criteria and scopes the filtered pull-request search can express. Always present; an empty relationship list
340
+ * means the provider exposes no filtered pull-request search.
341
+ */
342
+ pullRequestSearch: PullRequestSearchCapabilities;
343
+ /**
344
+ * Filters the repo-scoped issue read accepts — and, for an issue tracker (Jira/Linear/Trello), its
345
+ * project-scoped read, that being its only issue surface. A tracker therefore reports here and leaves
346
+ * `issuesAccountWide` empty, so intersecting a tracker against that field would read "cannot filter" for a
347
+ * provider that filters fine.
348
+ */
349
+ issues: IssueFilter[];
350
+ issuesAccountWide: IssueFilter[];
351
+ /**
352
+ * What the filtered issue search (and the count probe over the same criteria) can express. Always present: a
353
+ * provider with no filtered issue search reports an empty `relationships` and all-false flags, which is the
354
+ * signal to hide that surface rather than to hide individual chips.
355
+ */
356
+ issueSearch: IssueSearchCapabilities;
357
+ /**
358
+ * Sort keys the repo-scoped issue read can express — and, for an issue tracker, its project-scoped read. Empty
359
+ * means the read can't be ordered, so pass no `sort`.
360
+ *
361
+ * A key here is expressible on ONE provider query. A page spanning several repositories or projects is a merge,
362
+ * and a merge can only order by what a normalized issue carries, so `priority`/`dueDate`/`resolved` are refused
363
+ * there even where they are listed. Read one scope at a time to use them.
364
+ */
365
+ issueSorts: IssueSorting[];
366
+ /**
367
+ * Sort keys the account-wide issue read can express — a different vocabulary, not a subset: for GitLab the two
368
+ * reads are different APIs. Needs no mergeability caveat, because every account-wide read is a union of several
369
+ * queries, so only keys a merge can honor are listed at all. Empty for an issue tracker, which reports under
370
+ * `issueSorts`.
371
+ */
372
+ issueSortsAccountWide: IssueSorting[];
373
+ };
374
+
316
375
  /**
317
376
  * The filters `listPullRequestsPage`/`listIssuesPage` (and the sweeps) accept for a provider, so a caller can
318
377
  * narrow to what the provider can express BEFORE issuing the read.
@@ -345,19 +404,19 @@ export function resolveIssueSearchScope(
345
404
  * `pullRequestSearch` describes the separate filtered PR search. Its table declares each criteria vocabulary
346
405
  * plus repository/organization scope support; an empty relationship list means the search itself is absent.
347
406
  *
407
+ * `issueSorts` / `issueSortsAccountWide` / `issueSearch.sorts` are the ORDERING vocabulary of those same three
408
+ * issue reads, split the same way and for the same reason (GitLab's repo-scoped read is GraphQL and its
409
+ * account-wide read is REST, with genuinely different sort vocabularies). Empty means the read can't be ordered,
410
+ * so pass no `sort`; a key not listed refuses the whole read exactly like an inexpressible filter. Note a key
411
+ * listed here is expressible on ONE provider query: a read that fans out across projects can only honor a key
412
+ * derivable from a normalized issue, so it refuses `priority`/`dueDate`/`resolved` on top of this table.
413
+ *
348
414
  * Note this is a CAPABILITY table — "what the provider can express" — not a recommendation. A consumer
349
415
  * matching another tool's behavior may deliberately pass fewer filters than are listed here (or none, where an
350
416
  * already-scoped read would only be narrowed by them). Intersecting against this table is what keeps a
351
417
  * filtered read from being refused; it isn't a directive to use every filter in it.
352
418
  */
353
- export function getSupportedFilters(providerId: IntegrationIds): {
354
- pullRequests: PullRequestFilter[];
355
- pullRequestsAccountWide: PullRequestFilter[];
356
- pullRequestSearch: PullRequestSearchCapabilities;
357
- issues: IssueFilter[];
358
- issuesAccountWide: IssueFilter[];
359
- issueSearch: IssueSearchCapabilities;
360
- } {
419
+ export function getSupportedFilters(providerId: IntegrationIds): SupportedFilters {
361
420
  const metadata = providersMetadata[providerId];
362
421
  const issueSearch = metadata?.supportedIssueSearch;
363
422
  const pullRequestSearch = metadata?.supportedPullRequestSearch;
@@ -385,6 +444,9 @@ export function getSupportedFilters(providerId: IntegrationIds): {
385
444
  ...issueSearch,
386
445
  // Copied, so mutating the result can't corrupt the metadata table.
387
446
  relationships: [...(issueSearch?.relationships ?? [])],
447
+ sorts: [...(issueSearch?.sorts ?? [])],
388
448
  },
449
+ issueSorts: [...(metadata?.supportedIssueSorts ?? [])],
450
+ issueSortsAccountWide: [...(metadata?.supportedAccountWideIssueSorts ?? [])],
389
451
  };
390
452
  }