@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
|
@@ -8,13 +8,9 @@ import type { Event } from '../../utils/event.js';
|
|
|
8
8
|
import type { ConfiguredIntegrationsChangeEvent } from './authentication/configuredIntegrationService.js';
|
|
9
9
|
import type { ConfiguredIntegrationDescriptor } from './authentication/models.js';
|
|
10
10
|
import type { IntegrationIds } from './constants.js';
|
|
11
|
-
import type {
|
|
12
|
-
IssueFilter,
|
|
13
|
-
IssueSearchCapabilities,
|
|
14
|
-
PullRequestFilter,
|
|
15
|
-
PullRequestSearchCapabilities,
|
|
16
|
-
} from './providerFilters.js';
|
|
11
|
+
import type { IssueFilter, IssueSorting, PullRequestFilter } from './providerFilters.js';
|
|
17
12
|
import type { IssueCountResult, IssueCountScope } from './reads/counts.js';
|
|
13
|
+
import type { SupportedFilters } from './reads/filters.js';
|
|
18
14
|
import type {
|
|
19
15
|
ConnectionStateChangeEvent,
|
|
20
16
|
ProviderBroadenResult,
|
|
@@ -59,6 +55,63 @@ export interface ProviderSweepTarget {
|
|
|
59
55
|
filters?: PullRequestFilter[];
|
|
60
56
|
}
|
|
61
57
|
|
|
58
|
+
/**
|
|
59
|
+
* One target's contribution to a pull-request sweep, reported as it settles.
|
|
60
|
+
*
|
|
61
|
+
* A sweep makes a single call and distributes its targets internally, so its aggregate result carries no
|
|
62
|
+
* per-target timing at all and only derivable per-target counts. This is the boundary that exposes them, for a
|
|
63
|
+
* host attributing a slow or failing sweep to the provider responsible.
|
|
64
|
+
*
|
|
65
|
+
* `outcome` is reported as one value rather than the underlying booleans because a consumer buckets by it, and
|
|
66
|
+
* because "the whole target is unusable" and "the target returned a slice with a gap" are different facts:
|
|
67
|
+
* a `failed-provider` target contributes nothing, a `fetch-failed` one contributes `count` rows that are
|
|
68
|
+
* incomplete. `skipped` is a target that resolved to no reachable connection and is deliberately not
|
|
69
|
+
* attributed in the aggregate result — reported anyway so a consumer counting targets never loses one.
|
|
70
|
+
*
|
|
71
|
+
* A target that produced no slice at all reports `count: 0` and `truncated: false` — always for `skipped`, and
|
|
72
|
+
* for `failed-provider` as `drainPullRequests` reports it today. Read those fields rather than deriving them
|
|
73
|
+
* from `outcome`: they are the target's own values, not constants the outcome guarantees.
|
|
74
|
+
*/
|
|
75
|
+
export interface ProviderSweepTargetEvent {
|
|
76
|
+
providerId: IntegrationIds;
|
|
77
|
+
/**
|
|
78
|
+
* The self-managed host this target selects, resolved from its configured connection, its explicit domain,
|
|
79
|
+
* or the provider's primary configured host — the same rule the read itself uses, and the same one whether
|
|
80
|
+
* the target drained fully or was rejected by the first guard.
|
|
81
|
+
*
|
|
82
|
+
* Always `undefined` for a cloud provider: it has a single host, so there is nothing to disambiguate. Group
|
|
83
|
+
* by `providerId` and treat this as a label, not part of the key.
|
|
84
|
+
*/
|
|
85
|
+
domain: string | undefined;
|
|
86
|
+
connectionId: string | undefined;
|
|
87
|
+
/**
|
|
88
|
+
* Rows this target contributed to the aggregate result. The sweep concatenates target slices without a
|
|
89
|
+
* cross-target pass — duplicates are collapsed per target, across its own pages — so these sum to exactly
|
|
90
|
+
* `items.length`, and a sum that disagrees means a target went unreported.
|
|
91
|
+
*/
|
|
92
|
+
count: number;
|
|
93
|
+
/**
|
|
94
|
+
* Wall time from this target's worker picking it up to its slice being ready.
|
|
95
|
+
*
|
|
96
|
+
* Targets in the same sweep run concurrently, so these intervals OVERLAP and are not additive: summing them
|
|
97
|
+
* across a sweep exceeds the sweep's own duration. Compare them against each other, not against a total.
|
|
98
|
+
*/
|
|
99
|
+
durationMs: number;
|
|
100
|
+
/**
|
|
101
|
+
* Wall time between the fan-out starting and this target's worker picking the target up.
|
|
102
|
+
*
|
|
103
|
+
* Structurally 0 only while the target count fits the fan-out's concurrency limit, which a selection of a
|
|
104
|
+
* few providers does and the default sweep does NOT: with no `targets`/`providerIds` it opens one target per
|
|
105
|
+
* supported git host, more than the limit, so the last ones genuinely wait. A non-zero value there is the
|
|
106
|
+
* normal case, not an anomaly — it is the cost of the bound, and only worth acting on if it rivals
|
|
107
|
+
* `durationMs`.
|
|
108
|
+
*/
|
|
109
|
+
queueWaitMs: number;
|
|
110
|
+
outcome: 'ok' | 'failed-provider' | 'fetch-failed' | 'skipped';
|
|
111
|
+
/** Whether this target left pages unread. */
|
|
112
|
+
truncated: boolean;
|
|
113
|
+
}
|
|
114
|
+
|
|
62
115
|
type ProviderSweepSelection =
|
|
63
116
|
| {
|
|
64
117
|
targets: readonly ProviderSweepTarget[];
|
|
@@ -105,6 +158,25 @@ type PullRequestSweepCommonOptions = {
|
|
|
105
158
|
includeReviewRequested?: boolean;
|
|
106
159
|
forceSync?: boolean;
|
|
107
160
|
maxPages?: number;
|
|
161
|
+
/**
|
|
162
|
+
* Fired once per target as it settles, for host-side per-provider attribution.
|
|
163
|
+
*
|
|
164
|
+
* Observation only: it cannot influence the sweep, and a SYNCHRONOUS throw is swallowed rather than allowed
|
|
165
|
+
* to turn a successful target into a failed one. Return nothing and do no async work: the `void` return type
|
|
166
|
+
* admits an `async` callback, but nothing awaits it, so a rejection from one escapes that guarantee as an
|
|
167
|
+
* unhandled rejection. It is also invoked synchronously in the middle of the fan-out — do not re-enter the
|
|
168
|
+
* manager from it.
|
|
169
|
+
*
|
|
170
|
+
* Omitting it costs nothing at all, not even a clock read, so a host that only measures behind a gate can
|
|
171
|
+
* leave the gate off without paying for the option.
|
|
172
|
+
*
|
|
173
|
+
* It reports how a target SETTLED, not every way one can end, and **delivery does not stop when the sweep
|
|
174
|
+
* fails**: if a target's read throws instead of reporting failure through its slice, the sweep rejects with
|
|
175
|
+
* that error, but its sibling targets are already in flight and are not cancelled, so their events still
|
|
176
|
+
* arrive — after the returned promise has rejected. Key the accumulator to the call rather than closing it
|
|
177
|
+
* on rejection, or a late event lands in whatever bucket is current by then.
|
|
178
|
+
*/
|
|
179
|
+
onTargetSettled?: (event: ProviderSweepTargetEvent) => void;
|
|
108
180
|
};
|
|
109
181
|
|
|
110
182
|
export type PullRequestSweepOptions = PullRequestSweepCommonOptions & ProviderSweepSelection;
|
|
@@ -205,24 +277,7 @@ export interface IntegrationManager {
|
|
|
205
277
|
* This is a capability table, not a recommendation: a consumer matching another tool's behavior may pass fewer
|
|
206
278
|
* filters than are listed, or none where the underlying read is already scoped.
|
|
207
279
|
*/
|
|
208
|
-
getSupportedFilters(providerId: IntegrationIds):
|
|
209
|
-
pullRequests: PullRequestFilter[];
|
|
210
|
-
/** Optional for structural compatibility; missing means no account-wide narrowing filters are supported. */
|
|
211
|
-
pullRequestsAccountWide?: PullRequestFilter[];
|
|
212
|
-
/**
|
|
213
|
-
* Criteria and scopes {@link searchPullRequestsPage} can express. Always present; an empty relationship list
|
|
214
|
-
* means the provider exposes no filtered pull-request search.
|
|
215
|
-
*/
|
|
216
|
-
pullRequestSearch: PullRequestSearchCapabilities;
|
|
217
|
-
issues: IssueFilter[];
|
|
218
|
-
issuesAccountWide: IssueFilter[];
|
|
219
|
-
/**
|
|
220
|
-
* What {@link searchIssuesPage} (and {@link countIssues}, over the same criteria) can express for this
|
|
221
|
-
* provider. Always present: a provider with no filtered issue search reports an empty `relationships` and
|
|
222
|
-
* all-false flags, which is the signal to hide that surface rather than to hide individual chips.
|
|
223
|
-
*/
|
|
224
|
-
issueSearch: IssueSearchCapabilities;
|
|
225
|
-
};
|
|
280
|
+
getSupportedFilters(providerId: IntegrationIds): SupportedFilters;
|
|
226
281
|
/** Forces an authoritative cloud connection refresh. Rejects if the backend connection list cannot be read. */
|
|
227
282
|
refreshConnections(): Promise<void>;
|
|
228
283
|
/** Rejects unless `connectionId` is a configured cloud connection for `id`. */
|
|
@@ -351,6 +406,21 @@ export interface IntegrationManager {
|
|
|
351
406
|
filters?: IssueFilter[];
|
|
352
407
|
/** Broadens to every assignee. On account-wide reads it contradicts `filters`, so passing both is refused. */
|
|
353
408
|
includeAllAssignees?: boolean;
|
|
409
|
+
/**
|
|
410
|
+
* How to order the page, as `field:direction` (e.g. `updated:desc`, `created:asc`). Omitted orders
|
|
411
|
+
* most-recently-updated-first wherever the provider can express it — this facade's default, not the
|
|
412
|
+
* provider's, which differ from each other.
|
|
413
|
+
*
|
|
414
|
+
* Validated against `getSupportedFilters().issueSorts` when `repos` is supplied and
|
|
415
|
+
* `.issueSortsAccountWide` when it isn't, and refused (warning + `fetchFailed`) rather than downgraded: at
|
|
416
|
+
* the provider's reachable window another order is another subset, so a silently different one is
|
|
417
|
+
* indistinguishable from the one asked for. Keep it fixed across a pagination.
|
|
418
|
+
*
|
|
419
|
+
* With SEVERAL repositories/projects the page is a merge of one query each, so only a key a normalized
|
|
420
|
+
* issue carries can be honored — `priority`, `dueDate` and `resolved` are refused there even where the
|
|
421
|
+
* provider supports them on a single scope. The order applies within a page; across pages it is per scope.
|
|
422
|
+
*/
|
|
423
|
+
sort?: IssueSorting;
|
|
354
424
|
/**
|
|
355
425
|
* Requested 1-based page. Without a `cursor` this may cost O(page) upstream requests on cursor-backed
|
|
356
426
|
* reads such as repo-scoped GitHub/GHE; aggregate single-page account-wide reads remain O(1).
|
|
@@ -378,16 +448,21 @@ export interface IntegrationManager {
|
|
|
378
448
|
* - **Scope is mandatory.** Pass `repos`, `org`, or a user relationship (`authored`/`assigned`/`mentioned`).
|
|
379
449
|
* `any-assignee`/`unassigned` do NOT scope anything — either alone matches every such issue on the host —
|
|
380
450
|
* so a call carrying only those is refused (warning + `fetchFailed`).
|
|
381
|
-
* - **Ordering is
|
|
382
|
-
*
|
|
451
|
+
* - **Ordering is `criteria.sort`**, most-recently-updated-first when omitted. It is validated like every other
|
|
452
|
+
* criterion (against `getSupportedFilters().issueSearch.sorts`) and refused rather than downgraded, and SOME
|
|
453
|
+
* order is always requested — a "show the N most recent" policy at the provider's result ceiling is only
|
|
454
|
+
* correct under a guaranteed one. Keep it fixed for the life of a pagination: a cursor is bound to the order
|
|
455
|
+
* that produced it, and threading it under a different key is REFUSED (warning + `fetchFailed`) rather than
|
|
456
|
+
* resumed into a differently-ordered set. To change the order, drop the cursor and read from the first page.
|
|
383
457
|
* - **At the result ceiling the read SUCCEEDS.** It reports an omission carrying `totalCount` (how many
|
|
384
|
-
* matched)
|
|
385
|
-
* matched, showing the 1.000 most recent"
|
|
386
|
-
*
|
|
458
|
+
* matched), `limit` (how many are reachable) and `sort` (the order that window was selected under) with
|
|
459
|
+
* `recovery: 'none'`, so a consumer can say "19.240 matched, showing the 1.000 most recent" — naming the
|
|
460
|
+
* order, since which 1.000 are reachable depends on it — and know not to offer a "load more". It never falls
|
|
461
|
+
* back to a per-repository recovery walk.
|
|
387
462
|
*
|
|
388
463
|
* Check `getSupportedFilters().issueSearch` first: a provider with no filtered issue search reports empty
|
|
389
|
-
* relationships (and this read refuses), and a criterion it can't express refuses the whole read
|
|
390
|
-
* serving a wider result than asked for.
|
|
464
|
+
* relationships (and this read refuses), and a criterion or sort key it can't express refuses the whole read
|
|
465
|
+
* rather than serving a wider or differently-ordered result than asked for.
|
|
391
466
|
*/
|
|
392
467
|
searchIssuesPage(options: {
|
|
393
468
|
providerId: IntegrationIds;
|
|
@@ -450,6 +525,16 @@ export interface IntegrationManager {
|
|
|
450
525
|
project?: string;
|
|
451
526
|
filters?: IssueFilter[];
|
|
452
527
|
includeAllAssignees?: boolean;
|
|
528
|
+
/**
|
|
529
|
+
* How to order the issues, as `field:direction`. Omitted orders most-recently-updated-first where the
|
|
530
|
+
* tracker can express it; validated against `getSupportedFilters().issueSorts` and refused rather than
|
|
531
|
+
* downgraded. A page spanning several projects is a merge, so a key no normalized issue carries
|
|
532
|
+
* (`priority`, `dueDate`, `resolved`) is refused there.
|
|
533
|
+
*
|
|
534
|
+
* Safe to change between pages, unlike the git-host reads: this cursor windows PROJECTS and drains each in
|
|
535
|
+
* full, so which projects a round covers doesn't depend on how their issues are ordered.
|
|
536
|
+
*/
|
|
537
|
+
sort?: IssueSorting;
|
|
453
538
|
forceSync?: boolean;
|
|
454
539
|
page?: number;
|
|
455
540
|
/**
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { CollectionMetadata, CollectionScopeFailure } from '@gitkraken/provider-apis';
|
|
2
2
|
import type { Account, UnidentifiedAuthor } from '../../../git/models/author.js';
|
|
3
3
|
import type { DefaultBranch } from '../../../git/models/defaultBranch.js';
|
|
4
|
-
import type { IssueSearchCriteria, IssueShape
|
|
4
|
+
import type { IssueSearchCriteria, IssueShape } from '../../../git/models/issue.js';
|
|
5
5
|
import type { IssueOrPullRequestState as PullRequestState } from '../../../git/models/issueOrPullRequest.js';
|
|
6
6
|
import type {
|
|
7
7
|
PullRequest,
|
|
@@ -20,7 +20,7 @@ import { getScopedLogger } from '../../../utils/logger.scoped.js';
|
|
|
20
20
|
import type { PagedResult } from '../../../utils/paging.js';
|
|
21
21
|
import type { ProviderAuthenticationSession } from '../authentication/models.js';
|
|
22
22
|
import { toTokenWithInfo } from '../authentication/models.js';
|
|
23
|
-
import { toCollectionScopeFailure } from '../collectionMetadata.js';
|
|
23
|
+
import { throwIfCallerContractError, toCollectionScopeFailure } from '../collectionMetadata.js';
|
|
24
24
|
import type { IntegrationIds } from '../constants.js';
|
|
25
25
|
import { GitCloudHostIntegrationId, GitSelfManagedHostIntegrationId } from '../constants.js';
|
|
26
26
|
import { toError } from '../errors.js';
|
|
@@ -56,6 +56,7 @@ import type {
|
|
|
56
56
|
ProviderPullRequestSearchPage,
|
|
57
57
|
} from './integration.js';
|
|
58
58
|
import { IntegrationBase } from './integration.js';
|
|
59
|
+
import type { MyIssuesForReposOptions } from './issueReads.js';
|
|
59
60
|
|
|
60
61
|
function isAzureDevOpsProvider(
|
|
61
62
|
providerId: IntegrationIds,
|
|
@@ -673,17 +674,7 @@ export abstract class GitHostIntegration<
|
|
|
673
674
|
|
|
674
675
|
async getMyIssuesForRepos(
|
|
675
676
|
reposOrRepoIds: ProviderReposInput,
|
|
676
|
-
options?:
|
|
677
|
-
filters?: IssueFilter[];
|
|
678
|
-
cursor?: string;
|
|
679
|
-
customUrl?: string;
|
|
680
|
-
page?: number;
|
|
681
|
-
pageSize?: number;
|
|
682
|
-
/** When true, don't constrain to the current user's assigned issues even if the Assignee filter is set. */
|
|
683
|
-
includeAllAssignees?: boolean;
|
|
684
|
-
/** Issue states to include; when omitted the provider returns its default (open only). */
|
|
685
|
-
state?: IssueStateFilter;
|
|
686
|
-
},
|
|
677
|
+
options?: MyIssuesForReposOptions,
|
|
687
678
|
connectionId?: string,
|
|
688
679
|
): Promise<PagedResult<ProviderIssue> | undefined> {
|
|
689
680
|
return (await this.getMyIssuesForReposResult(reposOrRepoIds, options, connectionId))?.value;
|
|
@@ -697,15 +688,7 @@ export abstract class GitHostIntegration<
|
|
|
697
688
|
*/
|
|
698
689
|
async getMyIssuesForReposResult(
|
|
699
690
|
reposOrRepoIds: ProviderReposInput,
|
|
700
|
-
options?:
|
|
701
|
-
filters?: IssueFilter[];
|
|
702
|
-
cursor?: string;
|
|
703
|
-
customUrl?: string;
|
|
704
|
-
page?: number;
|
|
705
|
-
pageSize?: number;
|
|
706
|
-
includeAllAssignees?: boolean;
|
|
707
|
-
state?: IssueStateFilter;
|
|
708
|
-
},
|
|
691
|
+
options?: MyIssuesForReposOptions,
|
|
709
692
|
connectionId?: string,
|
|
710
693
|
): Promise<IntegrationResult<(PagedResult<ProviderIssue> & { metadata?: CollectionMetadata }) | undefined>> {
|
|
711
694
|
const scope = getScopedLogger();
|
|
@@ -828,6 +811,7 @@ export abstract class GitHostIntegration<
|
|
|
828
811
|
page: projectInput.cursor == null ? options?.page : undefined,
|
|
829
812
|
pageSize: options?.pageSize,
|
|
830
813
|
states: states,
|
|
814
|
+
sort: options?.sort,
|
|
831
815
|
},
|
|
832
816
|
);
|
|
833
817
|
return { projectInput: projectInput, results: results };
|
|
@@ -838,6 +822,10 @@ export abstract class GitHostIntegration<
|
|
|
838
822
|
const outcome = settled[i];
|
|
839
823
|
const projectInput = projectInputs[i];
|
|
840
824
|
if (outcome.status !== 'fulfilled') {
|
|
825
|
+
// Errors that really are per-scope (auth, rate limit, a missing project) degrade below; one
|
|
826
|
+
// that is a fact about the call is re-thrown instead — see `throwIfCallerContractError`.
|
|
827
|
+
throwIfCallerContractError(outcome.reason);
|
|
828
|
+
|
|
841
829
|
truncated = true;
|
|
842
830
|
const failure = toCollectionScopeFailure(
|
|
843
831
|
{
|
|
@@ -963,6 +951,7 @@ export abstract class GitHostIntegration<
|
|
|
963
951
|
page: repoInput.cursor == null ? options?.page : undefined,
|
|
964
952
|
pageSize: options?.pageSize,
|
|
965
953
|
states: states,
|
|
954
|
+
sort: options?.sort,
|
|
966
955
|
},
|
|
967
956
|
);
|
|
968
957
|
return { repoInput: repoInput, results: results };
|
|
@@ -973,6 +962,11 @@ export abstract class GitHostIntegration<
|
|
|
973
962
|
const outcome = settled[i];
|
|
974
963
|
const repoInput = repoInputs[i];
|
|
975
964
|
if (outcome.status !== 'fulfilled') {
|
|
965
|
+
// See the project fan-out above. Reachable in practice even though the facade validates the
|
|
966
|
+
// key first — a self-managed GitLab can reject an `IssueSort` member the SDK declares, which
|
|
967
|
+
// is only discoverable from the response.
|
|
968
|
+
throwIfCallerContractError(outcome.reason);
|
|
969
|
+
|
|
976
970
|
truncated = true;
|
|
977
971
|
const failure = toCollectionScopeFailure(
|
|
978
972
|
{
|
|
@@ -1030,6 +1024,7 @@ export abstract class GitHostIntegration<
|
|
|
1030
1024
|
page: options?.page,
|
|
1031
1025
|
pageSize: options?.pageSize,
|
|
1032
1026
|
states: states,
|
|
1027
|
+
sort: options?.sort,
|
|
1033
1028
|
});
|
|
1034
1029
|
return { value: result, duration: performance.now() - start };
|
|
1035
1030
|
} catch (ex) {
|
|
@@ -1050,15 +1045,7 @@ export abstract class GitHostIntegration<
|
|
|
1050
1045
|
*/
|
|
1051
1046
|
async getMyIssuesForReposAsShapesResult(
|
|
1052
1047
|
reposOrRepoIds: ProviderReposInput,
|
|
1053
|
-
options?:
|
|
1054
|
-
filters?: IssueFilter[];
|
|
1055
|
-
cursor?: string;
|
|
1056
|
-
customUrl?: string;
|
|
1057
|
-
page?: number;
|
|
1058
|
-
pageSize?: number;
|
|
1059
|
-
includeAllAssignees?: boolean;
|
|
1060
|
-
state?: IssueStateFilter;
|
|
1061
|
-
},
|
|
1048
|
+
options?: MyIssuesForReposOptions,
|
|
1062
1049
|
connectionId?: string,
|
|
1063
1050
|
): Promise<IntegrationResult<(PagedResult<IssueShape> & { metadata?: CollectionMetadata }) | undefined>> {
|
|
1064
1051
|
const result = await this.getMyIssuesForReposResult(reposOrRepoIds, options, connectionId);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { CollectionMetadata } from '@gitkraken/provider-apis';
|
|
2
|
-
import type { IssueShape } from '../../../git/models/issue.js';
|
|
2
|
+
import type { IssueShape, IssueSorting, IssueStateFilter } from '../../../git/models/issue.js';
|
|
3
3
|
import type { IssueFilter } from '../providerFilters.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -42,6 +42,58 @@ export type ProviderIssueSearchPage = {
|
|
|
42
42
|
totalCount?: number;
|
|
43
43
|
};
|
|
44
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Options for an issue tracker's PROJECT-scoped read (`IssuesIntegration.getIssuesForProject*`), which is the
|
|
47
|
+
* only issue surface a tracker has — its issues live under resource -> project, so there is no account-wide read
|
|
48
|
+
* to pair this with.
|
|
49
|
+
*
|
|
50
|
+
* Named rather than repeated inline at each of the six declarations this shape appears in (the two public
|
|
51
|
+
* `Result` methods, the two protected provider cores, and their Jira/Linear/Trello overrides), so a field added
|
|
52
|
+
* here can't reach five of them and be forgotten in the sixth.
|
|
53
|
+
*/
|
|
54
|
+
export type IssuesForProjectOptions = {
|
|
55
|
+
/** The account handle to scope to, resolved per resource by the caller. Omitted reads every assignee. */
|
|
56
|
+
user?: string;
|
|
57
|
+
/** Validated by the caller against `ProviderMetadata.supportedIssueFilters`; unsupported refuses the read. */
|
|
58
|
+
filters?: IssueFilter[];
|
|
59
|
+
/**
|
|
60
|
+
* How the provider should order the project's issues, as `field:direction`.
|
|
61
|
+
*
|
|
62
|
+
* Validated against `ProviderMetadata.supportedIssueSorts` — a tracker reports there, not under the
|
|
63
|
+
* account-wide table. Note the caller reads MANY projects and merges them, so it additionally refuses a key
|
|
64
|
+
* no normalized issue carries; this option orders one project's query.
|
|
65
|
+
*/
|
|
66
|
+
sort?: IssueSorting;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Options for the REPO-scoped issue read (`GitHostIntegration.getMyIssuesForRepos*`), the git-host counterpart of
|
|
71
|
+
* {@link IssuesForProjectOptions}.
|
|
72
|
+
*
|
|
73
|
+
* Named for the same reason that one is: the shape appears at three declarations — the public method, its
|
|
74
|
+
* result-returning core, and the shapes-returning core that maps it — and was previously spelled out inline at
|
|
75
|
+
* each, so `sort` had to be added three times and two of the copies grew a `See {@link getMyIssuesForRepos}`
|
|
76
|
+
* breadcrumb to admit they were the same contract. One name means a field added here reaches all three.
|
|
77
|
+
*/
|
|
78
|
+
export type MyIssuesForReposOptions = {
|
|
79
|
+
/** Validated by the caller against `ProviderMetadata.supportedIssueFilters`; unsupported refuses the read. */
|
|
80
|
+
filters?: IssueFilter[];
|
|
81
|
+
cursor?: string;
|
|
82
|
+
customUrl?: string;
|
|
83
|
+
page?: number;
|
|
84
|
+
pageSize?: number;
|
|
85
|
+
/** When true, don't constrain to the current user's assigned issues even if the Assignee filter is set. */
|
|
86
|
+
includeAllAssignees?: boolean;
|
|
87
|
+
/** Issue states to include; when omitted the provider returns its default (open only). */
|
|
88
|
+
state?: IssueStateFilter;
|
|
89
|
+
/**
|
|
90
|
+
* How the provider should order the read. Forwarded to every query this read issues, so the ORDER WITHIN each
|
|
91
|
+
* scope is the provider's; the union across scopes is not ordered here — the facade (`listIssuesPage`) owns
|
|
92
|
+
* that, because it is also the layer that can refuse a key no merge can honor.
|
|
93
|
+
*/
|
|
94
|
+
sort?: IssueSorting;
|
|
95
|
+
};
|
|
96
|
+
|
|
45
97
|
/**
|
|
46
98
|
* Options for the account-wide issue read. `includeAllAssignees` drops the "assigned to me" scoping so the
|
|
47
99
|
* read broadens to issues assigned to anyone (the account-wide equivalent of the repo-scoped
|
|
@@ -70,4 +122,16 @@ export type SearchMyIssuesOptions = {
|
|
|
70
122
|
*/
|
|
71
123
|
org?: string;
|
|
72
124
|
project?: string;
|
|
125
|
+
/**
|
|
126
|
+
* How the provider should order the read, as `field:direction`.
|
|
127
|
+
*
|
|
128
|
+
* Validated by the facade against `ProviderMetadata.supportedAccountWideIssueSorts` — a DIFFERENT table from
|
|
129
|
+
* the repo-scoped one, because for GitLab the two reads are different APIs with different sort vocabularies —
|
|
130
|
+
* and refused rather than downgraded when the provider can't express it.
|
|
131
|
+
*
|
|
132
|
+
* Supplied on every facade read, never omitted: an omitted sort leaves each provider's own default, which is
|
|
133
|
+
* exactly the cross-provider incoherence this layer exists to remove. A provider that reaches this with
|
|
134
|
+
* `undefined` was called directly, not through the facade, and keeps whatever order it had.
|
|
135
|
+
*/
|
|
136
|
+
sort?: IssueSorting;
|
|
73
137
|
};
|
|
@@ -8,9 +8,10 @@ import { getScopedLogger } from '../../../utils/logger.scoped.js';
|
|
|
8
8
|
import type { ProviderAuthenticationSession } from '../authentication/models.js';
|
|
9
9
|
import type { IntegrationIds } from '../constants.js';
|
|
10
10
|
import { toError } from '../errors.js';
|
|
11
|
-
import type {
|
|
11
|
+
import type { ProviderApiCollectionResult } from '../providers/models.js';
|
|
12
12
|
import type { Integration, IntegrationResult, IntegrationType } from './integration.js';
|
|
13
13
|
import { IntegrationBase } from './integration.js';
|
|
14
|
+
import type { IssuesForProjectOptions } from './issueReads.js';
|
|
14
15
|
|
|
15
16
|
export function isIssuesIntegration(integration: Integration): integration is IssuesIntegration {
|
|
16
17
|
return integration.type === 'issues';
|
|
@@ -135,7 +136,7 @@ export abstract class IssuesIntegration<
|
|
|
135
136
|
@trace()
|
|
136
137
|
async getIssuesForProject(
|
|
137
138
|
project: T,
|
|
138
|
-
options?:
|
|
139
|
+
options?: IssuesForProjectOptions,
|
|
139
140
|
connectionId?: string,
|
|
140
141
|
): Promise<IssueShape[] | undefined> {
|
|
141
142
|
return (await this.getIssuesForProjectResult(project, options, connectionId))?.value;
|
|
@@ -148,7 +149,7 @@ export abstract class IssuesIntegration<
|
|
|
148
149
|
*/
|
|
149
150
|
async getIssuesForProjectResult(
|
|
150
151
|
project: T,
|
|
151
|
-
options?:
|
|
152
|
+
options?: IssuesForProjectOptions,
|
|
152
153
|
connectionId?: string,
|
|
153
154
|
): Promise<IntegrationResult<IssueShape[] | undefined>> {
|
|
154
155
|
const scope = getScopedLogger();
|
|
@@ -174,7 +175,7 @@ export abstract class IssuesIntegration<
|
|
|
174
175
|
*/
|
|
175
176
|
async getIssuesForProjectWithTruncationResult(
|
|
176
177
|
project: T,
|
|
177
|
-
options?:
|
|
178
|
+
options?: IssuesForProjectOptions,
|
|
178
179
|
connectionId?: string,
|
|
179
180
|
): Promise<
|
|
180
181
|
IntegrationResult<{ values: IssueShape[]; truncated: boolean; metadata?: CollectionMetadata } | undefined>
|
|
@@ -196,7 +197,7 @@ export abstract class IssuesIntegration<
|
|
|
196
197
|
protected abstract getProviderIssuesForProject(
|
|
197
198
|
session: ProviderAuthenticationSession,
|
|
198
199
|
project: T,
|
|
199
|
-
options?:
|
|
200
|
+
options?: IssuesForProjectOptions,
|
|
200
201
|
): Promise<IssueShape[] | undefined>;
|
|
201
202
|
|
|
202
203
|
/**
|
|
@@ -208,7 +209,7 @@ export abstract class IssuesIntegration<
|
|
|
208
209
|
protected async getProviderIssuesForProjectWithTruncation(
|
|
209
210
|
session: ProviderAuthenticationSession,
|
|
210
211
|
project: T,
|
|
211
|
-
options?:
|
|
212
|
+
options?: IssuesForProjectOptions,
|
|
212
213
|
): Promise<{ values: IssueShape[]; truncated: boolean; metadata?: CollectionMetadata } | undefined> {
|
|
213
214
|
const values = await this.getProviderIssuesForProject(session, project, options);
|
|
214
215
|
if (values == null) return undefined;
|
|
@@ -618,6 +618,7 @@ abstract class GitHubIntegrationBase<ID extends GitHubIntegrationIds> extends Gi
|
|
|
618
618
|
includeBody: true,
|
|
619
619
|
includeAllAssignees: options?.includeAllAssignees,
|
|
620
620
|
cursor: options?.cursor,
|
|
621
|
+
sort: options?.sort,
|
|
621
622
|
categories: options?.filters?.length
|
|
622
623
|
? {
|
|
623
624
|
authored: options.filters.includes(IssueFilter.Author),
|
|
@@ -668,6 +668,7 @@ abstract class GitLabIntegrationBase<ID extends GitLabIntegrationIds> extends Gi
|
|
|
668
668
|
isPAT: this.isEnterprise,
|
|
669
669
|
baseUrl: baseUrl,
|
|
670
670
|
cursor: cursor,
|
|
671
|
+
sort: options?.sort,
|
|
671
672
|
});
|
|
672
673
|
if (cancellation?.aborted) throw new CancellationError();
|
|
673
674
|
return page;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { SUPPORTED_ISSUE_SORTS } from '@gitkraken/provider-apis';
|
|
2
|
+
import { getIssueComparator } from '../../../git/utils/issue.utils.js';
|
|
3
|
+
import type { IssueSorting } from '../providerFilters.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The per-provider ordering vocabulary of the issue reads: which neutral `field:direction` keys each provider
|
|
7
|
+
* surface can express SERVER-SIDE.
|
|
8
|
+
*
|
|
9
|
+
* DERIVED from `@gitkraken/provider-apis`, not transcribed from it. The SDK is where a neutral key becomes a
|
|
10
|
+
* provider query, so it is the only place that can say what a provider accepts; a copy kept here would be a
|
|
11
|
+
* second hand-maintained table on a second release cadence, and the symptom of drift is not a failed build but a
|
|
12
|
+
* read this facade promised and the SDK then rejects at runtime — which is exactly what happened while these were
|
|
13
|
+
* transcribed, on the one key (`reactions`) whose meaning is narrower than its name.
|
|
14
|
+
*
|
|
15
|
+
* Why the asymmetries between surfaces exist (GitHub cannot order by close date, GitLab's two reads are different
|
|
16
|
+
* APIs with different vocabularies, Azure has no `dueDate` outside the Agile process, Trello has only its
|
|
17
|
+
* `sort:edited` modifier, Jira has votes rather than reactions) is documented on the SDK's own maps. This module
|
|
18
|
+
* adds exactly one rule of its own — {@link mergeableSorts} — and everything else is a rename from the SDK's
|
|
19
|
+
* surface names to the two fields `ProviderMetadata` publishes.
|
|
20
|
+
*
|
|
21
|
+
* A caveat for anything building ONE ordering control across providers: a neutral key does not always mean the
|
|
22
|
+
* same thing. `priority:desc` is most-important-first on Jira and Linear, and least-important-first on Azure
|
|
23
|
+
* DevOps, whose WIQL orders the raw numeric column where 1 is the highest priority — so no single direction
|
|
24
|
+
* means "highest priority first" everywhere, and a unified control has to pick the direction per provider.
|
|
25
|
+
* `title` likewise orders by each backing database's collation, never JavaScript's.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/** One SDK read surface, widened to the union this package uses (structurally the same `field:direction` type). */
|
|
29
|
+
const surface = (name: keyof typeof SUPPORTED_ISSUE_SORTS): IssueSorting[] => [...SUPPORTED_ISSUE_SORTS[name]];
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The subset a MERGED page can honor: the keys a normalized issue carries.
|
|
33
|
+
*
|
|
34
|
+
* Applied to the account-wide surfaces because every account-wide read is a union of several provider queries
|
|
35
|
+
* merged in this facade — GitHub's three `@me` searches, GitLab's one REST call per relationship, Azure's
|
|
36
|
+
* (project × relationship) drains — and the caller has no scope count to reduce, so it ALWAYS merges. GitLab's
|
|
37
|
+
* REST endpoint really does order by `priority` and `dueDate`, and Azure's WIQL by `resolved` and `priority`;
|
|
38
|
+
* none of them survive a merge, so the surface cannot honor them and must not advertise them.
|
|
39
|
+
*
|
|
40
|
+
* Expressed as the rule rather than as a hand-removed list, so a key the SDK adds later is classified by the same
|
|
41
|
+
* predicate that will decide at read time whether the merge can order by it, instead of by whoever last edited a
|
|
42
|
+
* literal here. The repo-scoped surfaces are NOT filtered: those merge only when the caller passes several
|
|
43
|
+
* scopes, which is a property of the call rather than of the provider (see `mergesProviderQueries`).
|
|
44
|
+
*/
|
|
45
|
+
const mergeableSorts = (sorts: readonly IssueSorting[]): IssueSorting[] =>
|
|
46
|
+
sorts.filter(sort => getIssueComparator(sort) != null);
|
|
47
|
+
|
|
48
|
+
/** GitHub and GHE: one `search` channel serves all three issue reads, so all three order the same way. */
|
|
49
|
+
export const githubIssueSorts: IssueSorting[] = surface('github');
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The same `search` channel, narrowed to what the three `@me` searches can order once this facade has merged them.
|
|
53
|
+
*
|
|
54
|
+
* Identical to {@link githubIssueSorts} today — every qualifier the SDK's GitHub surface carries is a field an
|
|
55
|
+
* `IssueShape` models — so this exists for the drift, not for a difference: `readAccountWideIssuesPage`
|
|
56
|
+
* deliberately runs no `unmergeable` check, on the stated grounds that an account-wide table only lists keys a
|
|
57
|
+
* merge can honor. Left as the raw surface, one qualifier added upstream (a `closed` or `interactions` sort) would
|
|
58
|
+
* make that assumption false for GitHub alone and publish three concatenated alias runs under it, unwarned.
|
|
59
|
+
*/
|
|
60
|
+
export const githubAccountWideIssueSorts: IssueSorting[] = mergeableSorts(surface('github'));
|
|
61
|
+
|
|
62
|
+
/** GitLab's repository-scoped read, which goes through GraphQL `IssueSort`. */
|
|
63
|
+
export const gitlabIssueSorts: IssueSorting[] = surface('gitlabRepository');
|
|
64
|
+
|
|
65
|
+
/** GitLab's account-wide read: the REST `order_by`/`sort` pair, narrowed to what its merge can order. */
|
|
66
|
+
export const gitlabAccountWideIssueSorts: IssueSorting[] = mergeableSorts(surface('gitlabAccountWide'));
|
|
67
|
+
|
|
68
|
+
/** Azure DevOps, as WIQL `ORDER BY` columns. */
|
|
69
|
+
export const azureIssueSorts: IssueSorting[] = surface('azureDevOps');
|
|
70
|
+
|
|
71
|
+
/** The same WIQL, narrowed to what the per-project fan-out can order once it has merged. */
|
|
72
|
+
export const azureAccountWideIssueSorts: IssueSorting[] = mergeableSorts(surface('azureDevOps'));
|
|
73
|
+
|
|
74
|
+
/** Jira Cloud and Server, as JQL `ORDER BY` fields — one JQL builder, so one surface. */
|
|
75
|
+
export const jiraIssueSorts: IssueSorting[] = surface('jira');
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Linear's one root `issues` query, which both of its reads share and which takes the same `sort` argument in
|
|
79
|
+
* either direction — so this is one surface, not a repo-scoped and an account-wide pair.
|
|
80
|
+
*
|
|
81
|
+
* NOT filtered by {@link mergeableSorts} even though the account-wide read is "my issues": that read is a
|
|
82
|
+
* single server-ordered query (one `or` filter over the four relationships), so nothing is merged in this
|
|
83
|
+
* facade and there is no comparator to satisfy. Filtering it would drop `priority` and `dueDate` — keys the
|
|
84
|
+
* server really does order by — for a merge that does not happen.
|
|
85
|
+
*/
|
|
86
|
+
export const linearIssueSorts: IssueSorting[] = surface('linear');
|
|
87
|
+
|
|
88
|
+
/** Trello's search modifiers: `sort:edited` / `sort:-edited`, and nothing else usable for issues. */
|
|
89
|
+
export const trelloIssueSorts: IssueSorting[] = surface('trello');
|
|
@@ -8,8 +8,9 @@ import { Logger } from '../../../utils/logger.js';
|
|
|
8
8
|
import type { IntegrationAuthenticationProviderDescriptor } from '../authentication/integrationAuthenticationProvider.js';
|
|
9
9
|
import type { ProviderAuthenticationSession } from '../authentication/models.js';
|
|
10
10
|
import { toTokenWithInfo } from '../authentication/models.js';
|
|
11
|
-
import { toCollectionScopeFailure } from '../collectionMetadata.js';
|
|
11
|
+
import { throwIfCallerContractError, toCollectionScopeFailure } from '../collectionMetadata.js';
|
|
12
12
|
import { IssuesCloudHostIntegrationId } from '../constants.js';
|
|
13
|
+
import type { IssuesForProjectOptions } from '../models/issueReads.js';
|
|
13
14
|
import { IssuesIntegration } from '../models/issuesIntegration.js';
|
|
14
15
|
import type { ProviderApiCollectionResult, ProviderIssue } from './models.js';
|
|
15
16
|
import { IssueFilter, providersMetadata, toAccount, toIssueShape } from './models.js';
|
|
@@ -234,7 +235,7 @@ export class JiraIntegration extends IssuesIntegration<IssuesCloudHostIntegratio
|
|
|
234
235
|
protected override async getProviderIssuesForProject(
|
|
235
236
|
session: ProviderAuthenticationSession,
|
|
236
237
|
project: JiraProjectDescriptor,
|
|
237
|
-
options?:
|
|
238
|
+
options?: IssuesForProjectOptions,
|
|
238
239
|
): Promise<IssueShape[] | undefined> {
|
|
239
240
|
return (await this.getProviderIssuesForProjectWithTruncation(session, project, options))?.values;
|
|
240
241
|
}
|
|
@@ -242,7 +243,7 @@ export class JiraIntegration extends IssuesIntegration<IssuesCloudHostIntegratio
|
|
|
242
243
|
protected override async getProviderIssuesForProjectWithTruncation(
|
|
243
244
|
session: ProviderAuthenticationSession,
|
|
244
245
|
project: JiraProjectDescriptor,
|
|
245
|
-
options?:
|
|
246
|
+
options?: IssuesForProjectOptions,
|
|
246
247
|
): Promise<{ values: IssueShape[]; truncated: boolean; metadata?: CollectionMetadata } | undefined> {
|
|
247
248
|
const tokenWithInfo = toTokenWithInfo(this.id, session);
|
|
248
249
|
|
|
@@ -273,6 +274,7 @@ export class JiraIntegration extends IssuesIntegration<IssuesCloudHostIntegratio
|
|
|
273
274
|
result = await api.getIssuesForProjectPaged(tokenWithInfo, project.name, project.resourceId, {
|
|
274
275
|
...scope,
|
|
275
276
|
cursor: cursor,
|
|
277
|
+
sort: options?.sort,
|
|
276
278
|
});
|
|
277
279
|
} catch (ex) {
|
|
278
280
|
// A page failure after the first page leaves the already-drained prefix intact; record the
|
|
@@ -390,6 +392,10 @@ export class JiraIntegration extends IssuesIntegration<IssuesCloudHostIntegratio
|
|
|
390
392
|
// incomplete: keep the sibling results but record a structured failure so the facade can warn on
|
|
391
393
|
// the specific filter (auth/rate-limit) instead of just a generic truncation flag.
|
|
392
394
|
if (outcome.status !== 'fulfilled') {
|
|
395
|
+
// Identical for every filter branch, so degrading it would report one failure per branch for a
|
|
396
|
+
// single invalid call — see `throwIfCallerContractError`.
|
|
397
|
+
throwIfCallerContractError(outcome.reason);
|
|
398
|
+
|
|
393
399
|
truncated = true;
|
|
394
400
|
const failure = toCollectionScopeFailure(
|
|
395
401
|
{ providerId: this.id, resourceId: project.resourceId, projectId: project.name },
|