@gitkraken/core-gitlens 0.5.107 → 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.
- package/CHANGELOG.md +24 -1
- package/dist/git/models/issue.d.ts +70 -0
- package/dist/git/models/issue.d.ts.map +1 -1
- package/dist/git/models/issue.js +17 -0
- package/dist/git/models/issue.js.map +1 -1
- package/dist/git/utils/issue.utils.d.ts +41 -1
- package/dist/git/utils/issue.utils.d.ts.map +1 -1
- package/dist/git/utils/issue.utils.js +80 -0
- package/dist/git/utils/issue.utils.js.map +1 -1
- package/dist/plus/git-github/api/github.d.ts +33 -6
- package/dist/plus/git-github/api/github.d.ts.map +1 -1
- package/dist/plus/git-github/api/github.js +137 -15
- package/dist/plus/git-github/api/github.js.map +1 -1
- package/dist/plus/git-github/api/issueSearchQuery.d.ts +49 -5
- package/dist/plus/git-github/api/issueSearchQuery.d.ts.map +1 -1
- package/dist/plus/git-github/api/issueSearchQuery.js +64 -6
- package/dist/plus/git-github/api/issueSearchQuery.js.map +1 -1
- package/dist/plus/integrations/collectionMetadata.d.ts +18 -0
- package/dist/plus/integrations/collectionMetadata.d.ts.map +1 -1
- package/dist/plus/integrations/collectionMetadata.js +24 -0
- package/dist/plus/integrations/collectionMetadata.js.map +1 -1
- package/dist/plus/integrations/index.d.ts +1 -1
- package/dist/plus/integrations/index.d.ts.map +1 -1
- package/dist/plus/integrations/index.js.map +1 -1
- package/dist/plus/integrations/integrationService.d.ts +15 -9
- package/dist/plus/integrations/integrationService.d.ts.map +1 -1
- package/dist/plus/integrations/integrationService.js.map +1 -1
- package/dist/plus/integrations/manager.d.ts +115 -26
- package/dist/plus/integrations/manager.d.ts.map +1 -1
- package/dist/plus/integrations/models/gitHostIntegration.d.ts +6 -31
- package/dist/plus/integrations/models/gitHostIntegration.d.ts.map +1 -1
- package/dist/plus/integrations/models/gitHostIntegration.js +11 -1
- package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -1
- package/dist/plus/integrations/models/issueReads.d.ts +63 -1
- package/dist/plus/integrations/models/issueReads.d.ts.map +1 -1
- package/dist/plus/integrations/models/issuesIntegration.d.ts +7 -21
- package/dist/plus/integrations/models/issuesIntegration.d.ts.map +1 -1
- package/dist/plus/integrations/models/issuesIntegration.js.map +1 -1
- package/dist/plus/integrations/providerFilters.d.ts +1 -1
- package/dist/plus/integrations/providerFilters.d.ts.map +1 -1
- package/dist/plus/integrations/providers/azureDevOps.d.ts.map +1 -1
- package/dist/plus/integrations/providers/azureDevOps.js +1 -0
- package/dist/plus/integrations/providers/azureDevOps.js.map +1 -1
- package/dist/plus/integrations/providers/github.d.ts.map +1 -1
- package/dist/plus/integrations/providers/github.js +1 -0
- package/dist/plus/integrations/providers/github.js.map +1 -1
- package/dist/plus/integrations/providers/gitlab.d.ts.map +1 -1
- package/dist/plus/integrations/providers/gitlab.js +1 -0
- package/dist/plus/integrations/providers/gitlab.js.map +1 -1
- package/dist/plus/integrations/providers/issueSorts.d.ts +36 -0
- package/dist/plus/integrations/providers/issueSorts.d.ts.map +1 -0
- package/dist/plus/integrations/providers/issueSorts.js +76 -0
- package/dist/plus/integrations/providers/issueSorts.js.map +1 -0
- package/dist/plus/integrations/providers/jira.d.ts +3 -9
- package/dist/plus/integrations/providers/jira.d.ts.map +1 -1
- package/dist/plus/integrations/providers/jira.js +5 -1
- package/dist/plus/integrations/providers/jira.js.map +1 -1
- package/dist/plus/integrations/providers/linear.d.ts +24 -9
- package/dist/plus/integrations/providers/linear.d.ts.map +1 -1
- package/dist/plus/integrations/providers/linear.js +63 -5
- package/dist/plus/integrations/providers/linear.js.map +1 -1
- package/dist/plus/integrations/providers/models.d.ts +63 -7
- package/dist/plus/integrations/providers/models.d.ts.map +1 -1
- package/dist/plus/integrations/providers/models.js +55 -0
- package/dist/plus/integrations/providers/models.js.map +1 -1
- package/dist/plus/integrations/providers/providersApi.d.ts +18 -3
- package/dist/plus/integrations/providers/providersApi.d.ts.map +1 -1
- package/dist/plus/integrations/providers/providersApi.js +24 -7
- package/dist/plus/integrations/providers/providersApi.js.map +1 -1
- package/dist/plus/integrations/providers/trello.d.ts +3 -9
- package/dist/plus/integrations/providers/trello.d.ts.map +1 -1
- package/dist/plus/integrations/providers/trello.js +1 -0
- package/dist/plus/integrations/providers/trello.js.map +1 -1
- package/dist/plus/integrations/providers/utils/providerPaging.d.ts.map +1 -1
- package/dist/plus/integrations/providers/utils/providerPaging.js +5 -1
- package/dist/plus/integrations/providers/utils/providerPaging.js.map +1 -1
- package/dist/plus/integrations/reads/broaden.d.ts +6 -0
- package/dist/plus/integrations/reads/broaden.d.ts.map +1 -1
- package/dist/plus/integrations/reads/broaden.js.map +1 -1
- package/dist/plus/integrations/reads/counts.d.ts +8 -1
- package/dist/plus/integrations/reads/counts.d.ts.map +1 -1
- package/dist/plus/integrations/reads/counts.js.map +1 -1
- package/dist/plus/integrations/reads/filters.d.ts +59 -9
- package/dist/plus/integrations/reads/filters.d.ts.map +1 -1
- package/dist/plus/integrations/reads/filters.js +17 -0
- package/dist/plus/integrations/reads/filters.js.map +1 -1
- package/dist/plus/integrations/reads/issueTracker.d.ts +18 -1
- package/dist/plus/integrations/reads/issueTracker.d.ts.map +1 -1
- package/dist/plus/integrations/reads/issueTracker.js +40 -3
- package/dist/plus/integrations/reads/issueTracker.js.map +1 -1
- package/dist/plus/integrations/reads/issues.d.ts +27 -3
- package/dist/plus/integrations/reads/issues.d.ts.map +1 -1
- package/dist/plus/integrations/reads/issues.js +223 -123
- package/dist/plus/integrations/reads/issues.js.map +1 -1
- package/dist/plus/integrations/reads/ordering.d.ts +101 -0
- package/dist/plus/integrations/reads/ordering.d.ts.map +1 -0
- package/dist/plus/integrations/reads/ordering.js +48 -0
- package/dist/plus/integrations/reads/ordering.js.map +1 -0
- package/dist/plus/integrations/reads/searchIssues.d.ts +7 -2
- package/dist/plus/integrations/reads/searchIssues.d.ts.map +1 -1
- package/dist/plus/integrations/reads/searchIssues.js +9 -3
- package/dist/plus/integrations/reads/searchIssues.js.map +1 -1
- package/dist/plus/integrations/reads/sweeps.d.ts.map +1 -1
- package/dist/plus/integrations/reads/sweeps.js +120 -56
- package/dist/plus/integrations/reads/sweeps.js.map +1 -1
- package/dist/plus/integrations/reads/warnings.d.ts +34 -2
- package/dist/plus/integrations/reads/warnings.d.ts.map +1 -1
- package/dist/plus/integrations/reads/warnings.js +47 -2
- package/dist/plus/integrations/reads/warnings.js.map +1 -1
- package/dist/plus/integrations/results.d.ts +10 -0
- package/dist/plus/integrations/results.d.ts.map +1 -1
- package/dist/plus/integrations/results.js.map +1 -1
- package/package.json +2 -2
- package/src/git/models/issue.ts +73 -0
- package/src/git/utils/issue.utils.ts +91 -1
- package/src/plus/git-github/api/github.ts +180 -20
- package/src/plus/git-github/api/issueSearchQuery.ts +67 -7
- package/src/plus/integrations/collectionMetadata.ts +24 -0
- package/src/plus/integrations/index.ts +1 -0
- package/src/plus/integrations/integrationService.ts +15 -10
- package/src/plus/integrations/manager.ts +116 -31
- package/src/plus/integrations/models/gitHostIntegration.ts +18 -31
- package/src/plus/integrations/models/issueReads.ts +65 -1
- package/src/plus/integrations/models/issuesIntegration.ts +7 -6
- package/src/plus/integrations/providerFilters.ts +2 -0
- package/src/plus/integrations/providers/azureDevOps.ts +1 -0
- package/src/plus/integrations/providers/github.ts +1 -0
- package/src/plus/integrations/providers/gitlab.ts +1 -0
- package/src/plus/integrations/providers/issueSorts.ts +89 -0
- package/src/plus/integrations/providers/jira.ts +9 -3
- package/src/plus/integrations/providers/linear.ts +77 -8
- package/src/plus/integrations/providers/models.ts +122 -5
- package/src/plus/integrations/providers/providersApi.ts +51 -13
- package/src/plus/integrations/providers/trello.ts +4 -3
- package/src/plus/integrations/providers/utils/providerPaging.ts +6 -1
- package/src/plus/integrations/reads/broaden.ts +6 -0
- package/src/plus/integrations/reads/counts.ts +8 -1
- package/src/plus/integrations/reads/filters.ts +70 -8
- package/src/plus/integrations/reads/issueTracker.ts +78 -4
- package/src/plus/integrations/reads/issues.ts +386 -194
- package/src/plus/integrations/reads/ordering.ts +124 -0
- package/src/plus/integrations/reads/searchIssues.ts +15 -3
- package/src/plus/integrations/reads/sweeps.ts +150 -76
- package/src/plus/integrations/reads/warnings.ts +72 -1
- package/src/plus/integrations/results.ts +10 -0
|
@@ -11,7 +11,8 @@ import {
|
|
|
11
11
|
} from '../../../git/errors.js';
|
|
12
12
|
import type { Account, UnidentifiedAuthor } from '../../../git/models/author.js';
|
|
13
13
|
import type { DefaultBranch } from '../../../git/models/defaultBranch.js';
|
|
14
|
-
import type { Issue, IssueSearchCriteria, IssueShape } from '../../../git/models/issue.js';
|
|
14
|
+
import type { Issue, IssueSearchCriteria, IssueShape, IssueSorting } from '../../../git/models/issue.js';
|
|
15
|
+
import { defaultIssueSort } from '../../../git/models/issue.js';
|
|
15
16
|
import type { IssueOrPullRequest } from '../../../git/models/issueOrPullRequest.js';
|
|
16
17
|
import type {
|
|
17
18
|
PullRequest,
|
|
@@ -27,6 +28,7 @@ import type { GitRevisionRange } from '../../../git/models/revision.js';
|
|
|
27
28
|
import type { GitUser } from '../../../git/models/user.js';
|
|
28
29
|
import type { RepositoryVisibility } from '../../../git/providers/types.js';
|
|
29
30
|
import { getGitHubNoReplyAddressParts } from '../../../git/remotes/github.js';
|
|
31
|
+
import { effectiveIssueSort, getIssueComparator } from '../../../git/utils/issue.utils.js';
|
|
30
32
|
import {
|
|
31
33
|
createRevisionRange,
|
|
32
34
|
getRevisionRangeParts,
|
|
@@ -77,11 +79,21 @@ import {
|
|
|
77
79
|
gitHubIssueSearchRelationships,
|
|
78
80
|
toGitHubIssueSearchQualifiers,
|
|
79
81
|
toGitHubIssueSearchScopeQualifiers,
|
|
82
|
+
toGitHubIssueSortQualifier,
|
|
80
83
|
} from './issueSearchQuery.js';
|
|
81
84
|
import { toGitHubPullRequestSearchFacets } from './pullRequestSearchQuery.js';
|
|
82
85
|
import type { GitHubTokenInfo } from './token.js';
|
|
83
86
|
|
|
84
87
|
const emptyPagedResult: PagedResult<any> = Object.freeze({ values: [] });
|
|
88
|
+
/**
|
|
89
|
+
* What an issue-search cursor records when the caller asked for no ordering at all.
|
|
90
|
+
*
|
|
91
|
+
* A sentinel rather than an omitted field, because omitted already means something else and more important: a
|
|
92
|
+
* cursor persisted before ordering existed. Distinguishing the two is what lets an old cursor keep resuming while
|
|
93
|
+
* a genuine change from unordered to ordered is still refused. Not an `IssueSorting`, so it can never collide
|
|
94
|
+
* with one.
|
|
95
|
+
*/
|
|
96
|
+
const unsortedCursorSort = 'unsorted' as const;
|
|
85
97
|
const emptyBlameResult: GitHubBlame = Object.freeze({ ranges: [] });
|
|
86
98
|
|
|
87
99
|
// Transient gateway/network failures (e.g. an upstream `502 Bad Gateway`) are worth a few quick
|
|
@@ -3672,6 +3684,16 @@ export class GitHubApi {
|
|
|
3672
3684
|
return (await this.searchMyPullRequestsPage(provider, token, options, cancellation)).values;
|
|
3673
3685
|
}
|
|
3674
3686
|
|
|
3687
|
+
/**
|
|
3688
|
+
* The current user's issues: authored ∪ assigned ∪ mentioned, each its own aliased search behind one composite
|
|
3689
|
+
* cursor. Bound to `@me` by construction, unlike {@link searchIssuesPage}.
|
|
3690
|
+
*
|
|
3691
|
+
* Ordering is OPT-IN here, and that asymmetry with {@link searchIssuesPage} is deliberate: this read has never
|
|
3692
|
+
* requested a sort, so GitHub has always answered it in relevance order. Emitting a default would change which
|
|
3693
|
+
* issues its already-shipped consumers see, so an omitted `sort` still emits no `sort:` qualifier at all and
|
|
3694
|
+
* keeps today's result. Pass one to get a defined order — which is also what makes a page budget meaningful,
|
|
3695
|
+
* since relevance ranking can shift under an unchanged upstream.
|
|
3696
|
+
*/
|
|
3675
3697
|
@trace({ args: (provider, token) => ({ provider: provider.name, token: `<token:${token.microHash}>` }) })
|
|
3676
3698
|
async searchMyIssues(
|
|
3677
3699
|
provider: Provider,
|
|
@@ -3685,6 +3707,8 @@ export class GitHubApi {
|
|
|
3685
3707
|
includeBody?: boolean;
|
|
3686
3708
|
includeAllAssignees?: boolean;
|
|
3687
3709
|
cursor?: string;
|
|
3710
|
+
/** Requested order. Omitted leaves GitHub's relevance order, which is what this read has always served. */
|
|
3711
|
+
sort?: IssueSorting;
|
|
3688
3712
|
/**
|
|
3689
3713
|
* Which of the three "my issues" searches to run. Omitted runs all three (GitHub's own definition of
|
|
3690
3714
|
* "mine": authored ∪ assigned ∪ mentioned). Supplied, only the `true` ones run — so a caller wanting
|
|
@@ -3710,7 +3734,10 @@ export class GitHubApi {
|
|
|
3710
3734
|
search += `${repo}${options.repos.join(repo)}`;
|
|
3711
3735
|
}
|
|
3712
3736
|
|
|
3713
|
-
|
|
3737
|
+
// A requested sort goes through the same table `searchIssuesPage` uses, so the two GitHub issue reads can't
|
|
3738
|
+
// diverge the first time a key is added. Omitted appends nothing — see this method's contract above.
|
|
3739
|
+
const sortQualifier = toGitHubIssueSortQualifier(options?.sort);
|
|
3740
|
+
const baseFilters = ['type:issue is:open archived:false', sortQualifier].filter(Boolean).join(' ');
|
|
3714
3741
|
// `includeAllAssignees` broadens the assigned category from "assigned to me" to "assigned to anyone"
|
|
3715
3742
|
// (`assignee:*` is GitHub's has-any-assignee qualifier). Authored/mentioned stay bound to `@me` — they're
|
|
3716
3743
|
// user-relative by definition, so an all-assignees read still only surfaces the current user's authored
|
|
@@ -3748,7 +3775,24 @@ export class GitHubApi {
|
|
|
3748
3775
|
searches.push({ alias: 'authored', query: `${search} ${baseFilters} author:@me`.trim() });
|
|
3749
3776
|
}
|
|
3750
3777
|
|
|
3751
|
-
|
|
3778
|
+
// Field by field, like `searchIssuesPage`: `options` also carries `repos`/`includeAllAssignees`/`categories`,
|
|
3779
|
+
// already folded into `searches[].query` above and undeclared by the callee.
|
|
3780
|
+
return this.searchIssuesByAlias(
|
|
3781
|
+
provider,
|
|
3782
|
+
token,
|
|
3783
|
+
searches,
|
|
3784
|
+
{
|
|
3785
|
+
baseUrl: options?.baseUrl,
|
|
3786
|
+
avatarSize: options?.avatarSize,
|
|
3787
|
+
includeBody: options?.includeBody,
|
|
3788
|
+
cursor: options?.cursor,
|
|
3789
|
+
sort: options?.sort,
|
|
3790
|
+
// This read emitted no `sort:` qualifier at all before ordering existed, so a cursor with no
|
|
3791
|
+
// recorded key came out of a relevance-ordered walk.
|
|
3792
|
+
legacySort: unsortedCursorSort,
|
|
3793
|
+
},
|
|
3794
|
+
cancellation,
|
|
3795
|
+
);
|
|
3752
3796
|
}
|
|
3753
3797
|
|
|
3754
3798
|
/**
|
|
@@ -3756,9 +3800,15 @@ export class GitHubApi {
|
|
|
3756
3800
|
* relationship to the current user. The issue counterpart of {@link searchMyPullRequestsPage}, and distinct
|
|
3757
3801
|
* from {@link searchMyIssues}, which is permanently bound to `@me`.
|
|
3758
3802
|
*
|
|
3759
|
-
* Ordering is
|
|
3760
|
-
*
|
|
3761
|
-
* order
|
|
3803
|
+
* Ordering is `criteria.sort`, defaulting to most-recently-updated-first — the order this read served before
|
|
3804
|
+
* ordering was an option, so an omitted `sort` emits the identical query. What is NOT optional is that SOME
|
|
3805
|
+
* order is always requested: without one GitHub answers in relevance order, and at the result ceiling that
|
|
3806
|
+
* makes which rows are reachable a function of GitHub's ranking rather than of the request. A key GitHub can't
|
|
3807
|
+
* express (`closed`, `priority`, …) is refused by the facade before the request, not silently downgraded.
|
|
3808
|
+
*
|
|
3809
|
+
* With more than one relationship the page is a UNION of several searches, each ordered by the provider; the
|
|
3810
|
+
* merged page is re-sorted here so the whole page honors the requested key. Across pages the order is still
|
|
3811
|
+
* per-alias — see {@link searchIssuesByAlias}.
|
|
3762
3812
|
*
|
|
3763
3813
|
* Each requested relationship becomes its own aliased search, unioned and deduped by url; with none, a single
|
|
3764
3814
|
* search runs over the scope alone. `criteria.text` and the other free-form values are sanitized so user input
|
|
@@ -3780,9 +3830,12 @@ export class GitHubApi {
|
|
|
3780
3830
|
},
|
|
3781
3831
|
cancellation?: AbortSignal,
|
|
3782
3832
|
): Promise<AliasedIssueSearchResult | undefined> {
|
|
3833
|
+
// Resolved once: the emitted qualifier, the merged page's comparator and the cursor's fingerprint must all
|
|
3834
|
+
// be the same key, which is what `effectiveIssueSort` exists to guarantee.
|
|
3835
|
+
const sort = effectiveIssueSort(options?.criteria?.sort);
|
|
3783
3836
|
const base = [
|
|
3784
3837
|
...toGitHubIssueSearchScopeQualifiers(options?.org, options?.repos),
|
|
3785
|
-
...toGitHubIssueSearchQualifiers(options?.criteria),
|
|
3838
|
+
...toGitHubIssueSearchQualifiers(options?.criteria, sort),
|
|
3786
3839
|
].join(' ');
|
|
3787
3840
|
|
|
3788
3841
|
// One aliased search per relationship, OR-ed by union. They can't be one query: GitHub AND-s qualifiers,
|
|
@@ -3796,7 +3849,27 @@ export class GitHubApi {
|
|
|
3796
3849
|
}))
|
|
3797
3850
|
: [{ alias: 'matched', query: base }];
|
|
3798
3851
|
|
|
3799
|
-
|
|
3852
|
+
// Forwarded field by field rather than spread: `options` also carries `repos`/`org`/`criteria`, which are
|
|
3853
|
+
// already baked into `searches[].query` above and which the callee declares nothing about. `sort` is the
|
|
3854
|
+
// EFFECTIVE key, since the merged page and the cursor's fingerprint must both use the one the query used.
|
|
3855
|
+
return this.searchIssuesByAlias(
|
|
3856
|
+
provider,
|
|
3857
|
+
token,
|
|
3858
|
+
searches,
|
|
3859
|
+
{
|
|
3860
|
+
baseUrl: options?.baseUrl,
|
|
3861
|
+
avatarSize: options?.avatarSize,
|
|
3862
|
+
includeBody: options?.includeBody,
|
|
3863
|
+
cursor: options?.cursor,
|
|
3864
|
+
pageSize: options?.pageSize,
|
|
3865
|
+
sort: sort,
|
|
3866
|
+
// This read has always emitted `sort:updated`, which is `defaultIssueSort` — so a cursor with no
|
|
3867
|
+
// recorded key came out of a walk under exactly that key, and only a caller asking for a
|
|
3868
|
+
// different one has to restart.
|
|
3869
|
+
legacySort: defaultIssueSort,
|
|
3870
|
+
},
|
|
3871
|
+
cancellation,
|
|
3872
|
+
);
|
|
3800
3873
|
}
|
|
3801
3874
|
|
|
3802
3875
|
/**
|
|
@@ -3823,9 +3896,12 @@ export class GitHubApi {
|
|
|
3823
3896
|
if (scopes.length === 0) return [];
|
|
3824
3897
|
|
|
3825
3898
|
const queries = scopes.map(s => {
|
|
3899
|
+
// The same resolved key the search would use, so the count previews the query it previews. Ordering
|
|
3900
|
+
// cannot change a total, but emitting a DIFFERENT qualifier string than the search does would break the
|
|
3901
|
+
// parity this probe is for.
|
|
3826
3902
|
const qualifiers = [
|
|
3827
3903
|
...toGitHubIssueSearchScopeQualifiers(s.org, s.repos),
|
|
3828
|
-
...toGitHubIssueSearchQualifiers(s.criteria),
|
|
3904
|
+
...toGitHubIssueSearchQualifiers(s.criteria, effectiveIssueSort(s.criteria?.sort)),
|
|
3829
3905
|
];
|
|
3830
3906
|
|
|
3831
3907
|
// A relationship set is OR-ed across searches, which a single count can't express — the facade splits
|
|
@@ -3886,14 +3962,31 @@ export class GitHubApi {
|
|
|
3886
3962
|
* {@link searchMyIssues} is one configuration of it (its three `@me` categories), and its alias names are
|
|
3887
3963
|
* that read's published cursor keys.
|
|
3888
3964
|
*
|
|
3889
|
-
* `searches` must have unique aliases, each a valid GraphQL name that is
|
|
3890
|
-
* the composite cursor keys aliases at its top level, alongside those
|
|
3965
|
+
* `searches` must have unique aliases, each a valid GraphQL name that is none of `page`, `truncated` or
|
|
3966
|
+
* `sort` — the composite cursor keys aliases at its top level, alongside those three reserved fields.
|
|
3967
|
+
*
|
|
3968
|
+
* `sort` is the order the caller asked for, which this does two things with. Each alias comes back ordered by
|
|
3969
|
+
* it (the qualifier is already in `searches[].query`), but the UNION of several aliases is not, so the merged
|
|
3970
|
+
* page is re-sorted here; and the key is recorded in the cursor, so a continuation that changed it THROWS
|
|
3971
|
+
* rather than serving a sequence with gaps and repeats. Omitted means the caller asked for no order at all
|
|
3972
|
+
* ({@link searchMyIssues}'s default), which re-sorts nothing and pins nothing.
|
|
3973
|
+
*
|
|
3974
|
+
* `legacySort` is the order the calling read produced BEFORE this field existed, and is what a cursor with no
|
|
3975
|
+
* recorded key is compared against — such a cursor is not of unknown order, it is of that read's old one.
|
|
3891
3976
|
*/
|
|
3892
3977
|
private async searchIssuesByAlias(
|
|
3893
3978
|
provider: Provider,
|
|
3894
3979
|
token: GitHubTokenInfo,
|
|
3895
3980
|
searches: readonly AliasedIssueSearch[],
|
|
3896
|
-
options
|
|
3981
|
+
options: {
|
|
3982
|
+
baseUrl?: string;
|
|
3983
|
+
avatarSize?: number;
|
|
3984
|
+
includeBody?: boolean;
|
|
3985
|
+
cursor?: string;
|
|
3986
|
+
pageSize?: number;
|
|
3987
|
+
sort?: IssueSorting;
|
|
3988
|
+
legacySort: IssueSorting | typeof unsortedCursorSort;
|
|
3989
|
+
},
|
|
3897
3990
|
cancellation?: AbortSignal,
|
|
3898
3991
|
): Promise<AliasedIssueSearchResult | undefined> {
|
|
3899
3992
|
const scope = getScopedLogger();
|
|
@@ -3914,6 +4007,16 @@ export class GitHubApi {
|
|
|
3914
4007
|
interface SearchCursor {
|
|
3915
4008
|
page?: number;
|
|
3916
4009
|
truncated?: boolean;
|
|
4010
|
+
/**
|
|
4011
|
+
* The order this cursor's pages were produced under: an `IssueSorting`, or `unsortedCursorSort` when
|
|
4012
|
+
* the caller asked for none. Written as a value rather than left absent in the no-order case
|
|
4013
|
+
* specifically so that ABSENT keeps meaning "cursor from before ordering existed", which is accepted
|
|
4014
|
+
* and sealed instead of refused — a consumer's persisted cursor has to keep working across this change.
|
|
4015
|
+
*
|
|
4016
|
+
* Those three cases are the whole domain, so it is typed as them rather than as `string`: a foreign
|
|
4017
|
+
* cursor carrying something else is caught by the mismatch check either way.
|
|
4018
|
+
*/
|
|
4019
|
+
sort?: IssueSorting | typeof unsortedCursorSort;
|
|
3917
4020
|
[alias: string]: string | number | boolean | null | undefined;
|
|
3918
4021
|
}
|
|
3919
4022
|
|
|
@@ -3922,19 +4025,54 @@ export class GitHubApi {
|
|
|
3922
4025
|
// by a cursor string that reads back as page 1, restarting the walk with no error and no truncation flag.
|
|
3923
4026
|
// Cheap to check, and it fails at the one call that introduced the collision rather than in a consumer's
|
|
3924
4027
|
// persisted cursor.
|
|
3925
|
-
const reserved = searches.filter(s => s.alias === 'page' || s.alias === 'truncated');
|
|
4028
|
+
const reserved = searches.filter(s => s.alias === 'page' || s.alias === 'truncated' || s.alias === 'sort');
|
|
3926
4029
|
if (reserved.length > 0) {
|
|
3927
4030
|
throw new Error(
|
|
3928
4031
|
`Issue search alias(es) ${reserved.map(s => `'${s.alias}'`).join(', ')} collide with the composite cursor's reserved keys`,
|
|
3929
4032
|
);
|
|
3930
4033
|
}
|
|
3931
4034
|
|
|
4035
|
+
// A key GitHub cannot express emits no `sort:` qualifier, so each alias would come back in RELEVANCE order
|
|
4036
|
+
// while the union below is sorted by that key and the cursor sealed under it: an arbitrary subset,
|
|
4037
|
+
// presented as ordered, resumable only into more of the same. Refused here rather than downgraded, which
|
|
4038
|
+
// is the rule the whole feature is built on. Unreachable through the facade — no `supportedIssueSorts`
|
|
4039
|
+
// table declares a key without a qualifier — so this guards the direct callers of the two public reads,
|
|
4040
|
+
// where `title` is expressible enough to have a comparator and not enough to be a GitHub search qualifier.
|
|
4041
|
+
if (options.sort != null && toGitHubIssueSortQualifier(options.sort) == null) {
|
|
4042
|
+
throw new Error(`GitHub cannot order an issue search by '${options.sort}'`);
|
|
4043
|
+
}
|
|
4044
|
+
|
|
3932
4045
|
let cursor: SearchCursor | undefined;
|
|
3933
4046
|
if (options?.cursor != null) {
|
|
3934
4047
|
try {
|
|
3935
4048
|
cursor = JSON.parse(options.cursor) as SearchCursor;
|
|
3936
4049
|
} catch {}
|
|
3937
4050
|
}
|
|
4051
|
+
// The order this request is being made under, as the cursor records it.
|
|
4052
|
+
const requestedSort = options.sort ?? unsortedCursorSort;
|
|
4053
|
+
// A cursor produced under a DIFFERENT order can't be resumed: every alias would continue from a position in
|
|
4054
|
+
// a differently-ordered result set, so the continuation re-emits rows already seen and skips rows never
|
|
4055
|
+
// seen. REFUSED rather than silently restarted from page 1, because a restart cannot be reported honestly
|
|
4056
|
+
// from here: this read is cursor-only, so `resolveCurrentPage` has no page of its own to trust and echoes
|
|
4057
|
+
// the `page` the caller supplied alongside the cursor — page 1's rows would be published as page N, which
|
|
4058
|
+
// is the very confusion the fingerprint exists to prevent. Refusing surfaces a warning + `fetchFailed`, and
|
|
4059
|
+
// the remedy ("drop the cursor") is the caller's to apply.
|
|
4060
|
+
//
|
|
4061
|
+
// A cursor with NO recorded sort predates this field — which is not the same as being of unknown order.
|
|
4062
|
+
// Each read produced exactly one order before ordering was an option (`sort:updated` for the filtered
|
|
4063
|
+
// search, relevance for `searchMyIssues`), so an absent key reads as THAT one, `legacySort`. Compared
|
|
4064
|
+
// rather than waved through: the facade now resolves an omitted key to `defaultIssueSort`, so the
|
|
4065
|
+
// account-wide read's query gained a `sort:updated` qualifier it did not have, and resuming a
|
|
4066
|
+
// relevance-ordered cursor inside it advances each alias through a re-ordered result set — the gaps and
|
|
4067
|
+
// repeats this check exists to prevent, arriving through the very case meant to keep working. A cursor
|
|
4068
|
+
// whose implied key matches the request still resumes, and is sealed with the current one.
|
|
4069
|
+
const cursorSort = cursor?.sort ?? options.legacySort;
|
|
4070
|
+
if (cursor != null && cursorSort !== requestedSort) {
|
|
4071
|
+
throw new Error(
|
|
4072
|
+
`Issue search cursor was produced under sort '${cursorSort}' but '${requestedSort}' was requested; restart the read without a cursor`,
|
|
4073
|
+
);
|
|
4074
|
+
}
|
|
4075
|
+
|
|
3938
4076
|
const page = Math.max(1, Math.trunc(cursor?.page ?? 1));
|
|
3939
4077
|
// A slot is a continuation string, `null` (exhausted), or absent. Anything else came from a malformed or
|
|
3940
4078
|
// foreign cursor, and is read as absent rather than threaded back into the request as a continuation.
|
|
@@ -4023,16 +4161,38 @@ export class GitHubApi {
|
|
|
4023
4161
|
|
|
4024
4162
|
// Dedupe by `url`, not `IssueShape.id`: for some providers `id` is a per-repository number, so an
|
|
4025
4163
|
// id-keyed map would collapse distinct issues across repositories.
|
|
4026
|
-
const
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4164
|
+
const deduped = [
|
|
4165
|
+
...uniqueBy(
|
|
4166
|
+
issues,
|
|
4167
|
+
r => r.url,
|
|
4168
|
+
(original, _current) => original,
|
|
4169
|
+
),
|
|
4170
|
+
];
|
|
4171
|
+
|
|
4172
|
+
// Each alias arrived ordered by the server; their concatenation is not, so the merged page is ordered
|
|
4173
|
+
// here. AFTER the dedupe, not before, and that ordering is load-bearing: the alias order is also the
|
|
4174
|
+
// dedupe's precedence (an issue both assigned to and authored by the user surfaces as the assigned one,
|
|
4175
|
+
// per `searchMyIssues`), and sorting first would hand `uniqueBy` a different first occurrence and
|
|
4176
|
+
// silently change which copy wins. The pull-request path sorts BEFORE its dedupe because its facets
|
|
4177
|
+
// carry no such precedence — the difference is deliberate, not an inconsistency to tidy up.
|
|
4178
|
+
//
|
|
4179
|
+
// A comparator is always available for a key GitHub declares (`created`/`updated`/`comments`/
|
|
4180
|
+
// `reactions` are all on `IssueShape`), so `undefined` here means the capability table has outrun this
|
|
4181
|
+
// read; leave the provider's per-alias order rather than inventing one.
|
|
4182
|
+
//
|
|
4183
|
+
// Counted over `active`, not `searches`: continuations exhaust aliases one at a time, so a later page of
|
|
4184
|
+
// a three-category walk can come from ONE surviving search — already ordered by the server. Re-sorting
|
|
4185
|
+
// it could only reproduce that order, while hiding a provider that ignored the qualifier.
|
|
4186
|
+
const comparator = options?.sort != null ? getIssueComparator(options.sort) : undefined;
|
|
4187
|
+
if (comparator != null && active.length > 1) {
|
|
4188
|
+
deduped.sort(comparator);
|
|
4189
|
+
}
|
|
4031
4190
|
|
|
4032
4191
|
// Every alias gets a slot, so an inactive one keeps its `null` and stays out of the next request. A
|
|
4033
4192
|
// missing slot would be read as "never requested", which for a `searches` set that still lists it
|
|
4034
4193
|
// would restart it from its first page.
|
|
4035
|
-
|
|
4194
|
+
// The order is pinned on the way out too, so the next round can refuse a changed key (see above).
|
|
4195
|
+
const next: SearchCursor = { page: page + 1, sort: requestedSort };
|
|
4036
4196
|
let hasMore = false;
|
|
4037
4197
|
let continuationMissing = false;
|
|
4038
4198
|
let maxIssueCount = 0;
|
|
@@ -4057,7 +4217,7 @@ export class GitHubApi {
|
|
|
4057
4217
|
cursor?.truncated === true || maxIssueCount > githubSearchResultLimit || continuationMissing;
|
|
4058
4218
|
next.truncated = truncated || undefined;
|
|
4059
4219
|
return {
|
|
4060
|
-
values:
|
|
4220
|
+
values: deduped,
|
|
4061
4221
|
cursor: hasMore ? JSON.stringify(next) : undefined,
|
|
4062
4222
|
hasMore: hasMore,
|
|
4063
4223
|
page: page,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { IssueSearchCriteria, IssueSearchRelationship } from '../../../git/models/issue.js';
|
|
1
|
+
import type { IssueSearchCriteria, IssueSearchRelationship, IssueSorting } from '../../../git/models/issue.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Translation of the provider-neutral issue-search criteria into GitHub search qualifiers, and the sanitizing
|
|
@@ -70,6 +70,56 @@ export const gitHubIssueSearchRelationships: Record<IssueSearchRelationship, { q
|
|
|
70
70
|
unassigned: { qualifier: 'no:assignee', alias: 'unassigned' },
|
|
71
71
|
};
|
|
72
72
|
|
|
73
|
+
/**
|
|
74
|
+
* How each orderable key becomes a GitHub `sort:` qualifier.
|
|
75
|
+
*
|
|
76
|
+
* A literal table rather than a derived transform, for the same reason {@link gitHubIssueSearchRelationships} is
|
|
77
|
+
* one: being a `Record` over the union means adding a sort field without deciding its qualifier FAILS THE BUILD,
|
|
78
|
+
* instead of quietly emitting a search with no ordering constraint at all — which is the one failure mode the
|
|
79
|
+
* ordering contract exists to prevent, and the one a caller cannot detect from the result.
|
|
80
|
+
*
|
|
81
|
+
* `updated:desc` maps to the bare `sort:updated`, not `sort:updated-desc`. The two are the same query to GitHub,
|
|
82
|
+
* but the bare form is what this read has always emitted, so keeping it makes the default byte-identical to
|
|
83
|
+
* today's query — and the exact-query tests and recorded fixtures stay valid. Do not "normalize" it.
|
|
84
|
+
*
|
|
85
|
+
* `reactions` maps to the per-emoji `sort:reactions-+1`, NOT the bare `sort:reactions`. GitHub's bare form orders
|
|
86
|
+
* by the total across every reaction type, but the only reaction count on an `IssueShape` is `thumbsUpCount`,
|
|
87
|
+
* which this client fills from `reactions(content: THUMBS_UP)`. Ordering by the total would rank the page by a
|
|
88
|
+
* number the caller cannot see — and would disagree with `getIssueComparator`, which re-sorts a merged page from
|
|
89
|
+
* `thumbsUpCount`, so the same request would order one way with a single relationship and another way with two.
|
|
90
|
+
* `@gitkraken/provider-apis` maps it the same way for the same reason.
|
|
91
|
+
*
|
|
92
|
+
* Absent keys are absent on purpose: `closed` is NOT here even though `IssueShape.closedDate` exists, because
|
|
93
|
+
* GitHub's search cannot order by close date and a client-side sort over a page cut off at the result ceiling is
|
|
94
|
+
* exactly the lie the contract avoids. `sorts` in `githubIssueSearchCapabilities` is the same set, and a parity
|
|
95
|
+
* test pins the two together.
|
|
96
|
+
*/
|
|
97
|
+
export const gitHubIssueSortQualifiers: Partial<Record<IssueSorting, string>> = {
|
|
98
|
+
'created:asc': 'sort:created-asc',
|
|
99
|
+
'created:desc': 'sort:created-desc',
|
|
100
|
+
'updated:asc': 'sort:updated-asc',
|
|
101
|
+
'updated:desc': 'sort:updated',
|
|
102
|
+
'comments:asc': 'sort:comments-asc',
|
|
103
|
+
'comments:desc': 'sort:comments-desc',
|
|
104
|
+
'reactions:asc': 'sort:reactions-+1-asc',
|
|
105
|
+
'reactions:desc': 'sort:reactions-+1-desc',
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* The `sort:` qualifier for a key, or `undefined` when there is no key or GitHub can't express it.
|
|
110
|
+
*
|
|
111
|
+
* Applies no default of its own: the two callers differ on whether they HAVE one — the filtered search always
|
|
112
|
+
* orders, and `searchMyIssues` has never requested an order at all — so the default belongs at the call site that
|
|
113
|
+
* has one rather than behind a guard at the one that doesn't.
|
|
114
|
+
*
|
|
115
|
+
* `undefined` for an inexpressible key rather than a fallback to something orderable: every caller validated
|
|
116
|
+
* against the declared capability first, so reaching that case means the capability table has outrun this one, and
|
|
117
|
+
* a silently unordered search would change which rows land inside the result ceiling.
|
|
118
|
+
*/
|
|
119
|
+
export function toGitHubIssueSortQualifier(sort: IssueSorting | undefined): string | undefined {
|
|
120
|
+
return sort != null ? gitHubIssueSortQualifiers[sort] : undefined;
|
|
121
|
+
}
|
|
122
|
+
|
|
73
123
|
/**
|
|
74
124
|
* The scope half of an issue search query: `org:` plus one `repo:` per repository.
|
|
75
125
|
*
|
|
@@ -106,11 +156,18 @@ export function toGitHubIssueSearchScopeQualifiers(
|
|
|
106
156
|
* becomes its own aliased search, since GitHub AND-s qualifiers and relationships are OR-ed) and excluding the
|
|
107
157
|
* repository/org scope (which {@link toGitHubIssueSearchScopeQualifiers} owns).
|
|
108
158
|
*
|
|
109
|
-
* `sort
|
|
110
|
-
*
|
|
111
|
-
*
|
|
159
|
+
* A `sort:` qualifier is ALWAYS emitted. Never none: the provider serves at most a bounded window of matches, and
|
|
160
|
+
* without an explicit order GitHub answers in relevance order, so which rows land inside that window would shift
|
|
161
|
+
* with GitHub's ranking even when nothing changed upstream. A key GitHub can't express is refused before this by
|
|
162
|
+
* the facade, so it cannot arrive here.
|
|
163
|
+
*
|
|
164
|
+
* `sort` is passed in rather than read off `criteria` so that the qualifier this emits and the key the caller
|
|
165
|
+
* records elsewhere cannot disagree — see `effectiveIssueSort`.
|
|
166
|
+
*
|
|
167
|
+
* Free-form values go through the sanitizers above, and one emptied by sanitizing is dropped rather than emitted
|
|
168
|
+
* as an empty qualifier.
|
|
112
169
|
*/
|
|
113
|
-
export function toGitHubIssueSearchQualifiers(criteria: IssueSearchCriteria | undefined): string[] {
|
|
170
|
+
export function toGitHubIssueSearchQualifiers(criteria: IssueSearchCriteria | undefined, sort: IssueSorting): string[] {
|
|
114
171
|
const qualifiers = ['type:issue'];
|
|
115
172
|
|
|
116
173
|
switch (criteria?.state) {
|
|
@@ -158,8 +215,11 @@ export function toGitHubIssueSearchQualifiers(criteria: IssueSearchCriteria | un
|
|
|
158
215
|
}
|
|
159
216
|
}
|
|
160
217
|
|
|
161
|
-
//
|
|
162
|
-
|
|
218
|
+
// Last, where it has always been: the exact-query tests pin the whole qualifier string, not a set.
|
|
219
|
+
const sortQualifier = toGitHubIssueSortQualifier(sort);
|
|
220
|
+
if (sortQualifier != null) {
|
|
221
|
+
qualifiers.push(sortQualifier);
|
|
222
|
+
}
|
|
163
223
|
|
|
164
224
|
return qualifiers;
|
|
165
225
|
}
|
|
@@ -4,12 +4,36 @@ import type {
|
|
|
4
4
|
CollectionScope,
|
|
5
5
|
CollectionScopeFailure,
|
|
6
6
|
} from '@gitkraken/provider-apis';
|
|
7
|
+
// Matched by its `code` discriminator rather than by `instanceof`: the SDK ships one bundle per entry point, so
|
|
8
|
+
// the class reached through the root is not the same object as the one reached through `/providers`.
|
|
9
|
+
import { isInvalidRequestError, isUnsupportedSortError } from '@gitkraken/provider-apis';
|
|
7
10
|
import { AuthenticationError, RequestNotFoundError, RequestRateLimitError } from '../../git/errors.js';
|
|
8
11
|
import type { IntegrationIds } from './constants.js';
|
|
9
12
|
import { isRateLimitResponse } from './errors.js';
|
|
10
13
|
import type { ProviderWarning, ProviderWarningOmission } from './results.js';
|
|
11
14
|
import { appendDedupedWarning } from './results.js';
|
|
12
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Re-throws an error that is a fact about the CALL rather than about one scope of a fan-out.
|
|
18
|
+
*
|
|
19
|
+
* Every provider fan-out catches per-scope rejections and records them as {@link CollectionScopeFailure}s, which is
|
|
20
|
+
* right for auth, rate limits and a missing project: those really did happen to one scope, and the siblings that
|
|
21
|
+
* succeeded must survive. A caller-contract error is the opposite — it is identical for every scope and was decided
|
|
22
|
+
* before any request went out — so degrading it would hand back N indistinguishable failures and an empty `partial`
|
|
23
|
+
* page, describing an invalid call as an incomplete read.
|
|
24
|
+
*
|
|
25
|
+
* Today that means the SDK's `UnsupportedSortError` and `InvalidRequestError` — the two codes it lists in its own
|
|
26
|
+
* `CALLER_CONTRACT_ERROR_CODES`, both documented as "never degraded into a `CollectionScopeFailure` or an
|
|
27
|
+
* omission". The second covers arguments rejected before the first request goes out: a project key list longer
|
|
28
|
+
* than one Jira search can carry (or an empty one), a non-integer `pageSize`, an unknown current-user filter, or a
|
|
29
|
+
* Jira Server read with no `baseUrl` to address. Called from every fan-out rather than spelled out in each, so a
|
|
30
|
+
* fan-out added later inherits the rule instead of silently omitting it — which is exactly how Azure's
|
|
31
|
+
* account-wide drain came to degrade it while its three siblings did not.
|
|
32
|
+
*/
|
|
33
|
+
export function throwIfCallerContractError(ex: unknown): void {
|
|
34
|
+
if (isUnsupportedSortError(ex) || isInvalidRequestError(ex)) throw ex;
|
|
35
|
+
}
|
|
36
|
+
|
|
13
37
|
/**
|
|
14
38
|
* Maps a caught GitLens request error to the SDK collection failure vocabulary used inside provider fan-outs.
|
|
15
39
|
*/
|
|
@@ -67,12 +67,11 @@ import { createApiClients } from './providers/apiClients.js';
|
|
|
67
67
|
import type { GitHubApi } from './providers/github/github.js';
|
|
68
68
|
import type {
|
|
69
69
|
IssueFilter,
|
|
70
|
-
|
|
70
|
+
IssueSorting,
|
|
71
71
|
ProviderOrganization,
|
|
72
72
|
ProviderReposInput,
|
|
73
73
|
ProviderRepositoryShape,
|
|
74
74
|
PullRequestFilter,
|
|
75
|
-
PullRequestSearchCapabilities,
|
|
76
75
|
} from './providers/models.js';
|
|
77
76
|
import { providersMetadata } from './providers/models.js';
|
|
78
77
|
import type { ProvidersApi } from './providers/providersApi.js';
|
|
@@ -80,6 +79,7 @@ import { broadenIssues } from './reads/broaden.js';
|
|
|
80
79
|
import type { RepositoryResolutionContext } from './reads/context.js';
|
|
81
80
|
import type { IssueCountResult, IssueCountScope } from './reads/counts.js';
|
|
82
81
|
import { countIssues } from './reads/counts.js';
|
|
82
|
+
import type { SupportedFilters } from './reads/filters.js';
|
|
83
83
|
import { getSupportedFilters } from './reads/filters.js';
|
|
84
84
|
import { listOrgs, listProjects, listRepos } from './reads/hierarchy.js';
|
|
85
85
|
import { listIssuesPage } from './reads/issues.js';
|
|
@@ -537,14 +537,7 @@ export class IntegrationService implements Disposable, RepositoryResolutionConte
|
|
|
537
537
|
* stays a method here even though it needs no instance state; see {@link getSupportedFilters} for the
|
|
538
538
|
* capability table itself and why a consumer should intersect against it before reading.
|
|
539
539
|
*/
|
|
540
|
-
getSupportedFilters(providerId: IntegrationIds): {
|
|
541
|
-
pullRequests: PullRequestFilter[];
|
|
542
|
-
pullRequestsAccountWide: PullRequestFilter[];
|
|
543
|
-
pullRequestSearch: PullRequestSearchCapabilities;
|
|
544
|
-
issues: IssueFilter[];
|
|
545
|
-
issuesAccountWide: IssueFilter[];
|
|
546
|
-
issueSearch: IssueSearchCapabilities;
|
|
547
|
-
} {
|
|
540
|
+
getSupportedFilters(providerId: IntegrationIds): SupportedFilters {
|
|
548
541
|
return getSupportedFilters(providerId);
|
|
549
542
|
}
|
|
550
543
|
|
|
@@ -1092,6 +1085,13 @@ export class IntegrationService implements Disposable, RepositoryResolutionConte
|
|
|
1092
1085
|
filters?: IssueFilter[];
|
|
1093
1086
|
/** Broadens the read to every assignee. Contradicts `filters`; passing both is refused. */
|
|
1094
1087
|
includeAllAssignees?: boolean;
|
|
1088
|
+
/**
|
|
1089
|
+
* How to order the page, as `field:direction`. Omitted orders most-recently-updated-first wherever the
|
|
1090
|
+
* provider can express it. Validated against `getSupportedFilters().issueSorts` on the repo-scoped path and
|
|
1091
|
+
* `.issueSortsAccountWide` on the account-wide one, and refused rather than downgraded; a key no normalized
|
|
1092
|
+
* issue carries is additionally refused for a page spanning several repositories/projects, which is a merge.
|
|
1093
|
+
*/
|
|
1094
|
+
sort?: IssueSorting;
|
|
1095
1095
|
page?: number;
|
|
1096
1096
|
cursor?: string;
|
|
1097
1097
|
itemsPerPage?: number;
|
|
@@ -1174,6 +1174,11 @@ export class IntegrationService implements Disposable, RepositoryResolutionConte
|
|
|
1174
1174
|
project?: string;
|
|
1175
1175
|
filters?: IssueFilter[];
|
|
1176
1176
|
includeAllAssignees?: boolean;
|
|
1177
|
+
/**
|
|
1178
|
+
* How to order the issues, as `field:direction`. Validated against `getSupportedFilters().issueSorts`, which
|
|
1179
|
+
* is where a tracker reports; a key no normalized issue carries is refused for a multi-project page.
|
|
1180
|
+
*/
|
|
1181
|
+
sort?: IssueSorting;
|
|
1177
1182
|
forceSync?: boolean;
|
|
1178
1183
|
page?: number;
|
|
1179
1184
|
cursor?: string;
|