@gitkraken/core-gitlens 0.5.101 → 0.5.102
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 +18 -1
- package/dist/plus/integrations/collectionMetadata.d.ts +1 -9
- package/dist/plus/integrations/collectionMetadata.d.ts.map +1 -1
- package/dist/plus/integrations/collectionMetadata.js +43 -14
- 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/providers/utils/providerPaging.d.ts.map +1 -1
- package/dist/plus/integrations/providers/utils/providerPaging.js +2 -1
- package/dist/plus/integrations/providers/utils/providerPaging.js.map +1 -1
- package/dist/plus/integrations/reads/drains.d.ts.map +1 -1
- package/dist/plus/integrations/reads/drains.js +59 -20
- package/dist/plus/integrations/reads/drains.js.map +1 -1
- package/dist/plus/integrations/reads/issueTracker.d.ts.map +1 -1
- package/dist/plus/integrations/reads/issueTracker.js +8 -2
- package/dist/plus/integrations/reads/issueTracker.js.map +1 -1
- package/dist/plus/integrations/reads/issues.d.ts.map +1 -1
- package/dist/plus/integrations/reads/issues.js +13 -3
- package/dist/plus/integrations/reads/issues.js.map +1 -1
- package/dist/plus/integrations/reads/pullRequests.d.ts.map +1 -1
- package/dist/plus/integrations/reads/pullRequests.js +7 -1
- package/dist/plus/integrations/reads/pullRequests.js.map +1 -1
- package/dist/plus/integrations/reads/warnings.d.ts +37 -4
- package/dist/plus/integrations/reads/warnings.d.ts.map +1 -1
- package/dist/plus/integrations/reads/warnings.js +50 -5
- package/dist/plus/integrations/reads/warnings.js.map +1 -1
- package/dist/plus/integrations/results.d.ts +115 -1
- package/dist/plus/integrations/results.d.ts.map +1 -1
- package/dist/plus/integrations/results.js +78 -3
- package/dist/plus/integrations/results.js.map +1 -1
- package/docs/integrations.md +81 -7
- package/package.json +1 -1
- package/src/plus/integrations/collectionMetadata.ts +45 -18
- package/src/plus/integrations/index.ts +4 -0
- package/src/plus/integrations/providers/utils/providerPaging.ts +2 -1
- package/src/plus/integrations/reads/drains.ts +84 -20
- package/src/plus/integrations/reads/issueTracker.ts +8 -2
- package/src/plus/integrations/reads/issues.ts +21 -4
- package/src/plus/integrations/reads/pullRequests.ts +15 -1
- package/src/plus/integrations/reads/warnings.ts +78 -10
- package/src/plus/integrations/results.ts +173 -4
|
@@ -48,11 +48,86 @@ export function toProviderWarning(providerId, domain, connectionId, ex) {
|
|
|
48
48
|
isAuth: kind === 'auth',
|
|
49
49
|
};
|
|
50
50
|
}
|
|
51
|
-
/**
|
|
51
|
+
/**
|
|
52
|
+
* A scope's identity, as the stable string that keys it.
|
|
53
|
+
*
|
|
54
|
+
* The single definition of what "the same scope" means, shared by the failure and omission dedup keys in
|
|
55
|
+
* `providerPaging.ts` and by {@link providerWarningKey} below, so a scope gaining a field is one edit rather
|
|
56
|
+
* than three. `providerId` is included: the same repository ID under two providers is two scopes.
|
|
57
|
+
*
|
|
58
|
+
* The parameter is structural rather than the SDK's `CollectionScope` so this module keeps naming no
|
|
59
|
+
* `@gitkraken/provider-apis` types (see the export block in `index.ts`) while still serving its SDK-facing
|
|
60
|
+
* callers, which pass that type in unchanged.
|
|
61
|
+
*/
|
|
62
|
+
export function collectionScopeKey(scope) {
|
|
63
|
+
return [scope?.providerId ?? '', scope?.resourceId ?? '', scope?.projectId ?? '', scope?.repositoryId ?? ''].join(' ');
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* The omission's contribution to a warning's identity — empty when there is none, so a warning without an
|
|
67
|
+
* omission keeps deduping exactly as it did before the field existed.
|
|
68
|
+
*
|
|
69
|
+
* Two omissions of different kinds do produce different `message` values today, so message alone would still
|
|
70
|
+
* separate them. That is incidental: the premise of `omission` is that consumers must not depend on prose
|
|
71
|
+
* carrying the distinguishing fact, and this key must not either.
|
|
72
|
+
*/
|
|
73
|
+
function providerWarningOmissionKey(omission) {
|
|
74
|
+
if (omission == null)
|
|
75
|
+
return '';
|
|
76
|
+
return [
|
|
77
|
+
omission.kind,
|
|
78
|
+
omission.recovery,
|
|
79
|
+
omission.limit ?? '',
|
|
80
|
+
omission.totalCount ?? '',
|
|
81
|
+
collectionScopeKey(omission.scope),
|
|
82
|
+
].join(' ');
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Strips the omission from every warning in `warnings` when the read as a whole failed.
|
|
86
|
+
*
|
|
87
|
+
* An omission asserts the request SUCCEEDED, and a drain only learns it failed AFTER it may have emitted one:
|
|
88
|
+
* an early page can report its own truncation and then a later page can die. Deciding per warning, at the
|
|
89
|
+
* moment each is built, cannot see that future — so the aggregate is reconciled once, here, where
|
|
90
|
+
* `fetchFailed` is final. Call it at the point a read returns its `fetchFailed`.
|
|
91
|
+
*
|
|
92
|
+
* Re-dedupes as it goes: the omission is part of a warning's identity, so two warnings that differed only
|
|
93
|
+
* there become identical once it is gone, and the array's contract is that no two entries are equal.
|
|
94
|
+
*
|
|
95
|
+
* Mutates in place: the warning array is the one being returned, and callers accumulate into it across pages.
|
|
96
|
+
*/
|
|
97
|
+
export function reconcileOmissionsWithFailure(warnings, fetchFailed) {
|
|
98
|
+
if (!fetchFailed || !warnings.some(w => w.omission != null))
|
|
99
|
+
return;
|
|
100
|
+
const reconciled = [];
|
|
101
|
+
for (const warning of warnings) {
|
|
102
|
+
if (warning.omission == null) {
|
|
103
|
+
appendDedupedWarning(reconciled, warning);
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
const { omission: _omission, ...rest } = warning;
|
|
107
|
+
appendDedupedWarning(reconciled, rest);
|
|
108
|
+
}
|
|
109
|
+
warnings.splice(0, warnings.length, ...reconciled);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* A stable key for deduplicating warnings accumulated across drained pages / fan-out scopes.
|
|
113
|
+
*
|
|
114
|
+
* `message` stays LAST. It is the only free-form segment — provider prose, spaces and all — so anything
|
|
115
|
+
* appended after it could be impersonated by a message that happens to end in the same text.
|
|
116
|
+
*/
|
|
52
117
|
function providerWarningKey(warning) {
|
|
53
|
-
return [
|
|
118
|
+
return [
|
|
119
|
+
warning.providerId,
|
|
120
|
+
warning.connectionId ?? '',
|
|
121
|
+
warning.domain ?? '',
|
|
122
|
+
warning.kind,
|
|
123
|
+
providerWarningOmissionKey(warning.omission),
|
|
124
|
+
warning.message,
|
|
125
|
+
].join(' ');
|
|
54
126
|
}
|
|
55
|
-
/**
|
|
127
|
+
/**
|
|
128
|
+
* Appends `warning` to `into` only when an equal warning (by provider/connection/domain/kind/message, plus the
|
|
129
|
+
* structured omission when one is present) is absent.
|
|
130
|
+
*/
|
|
56
131
|
export function appendDedupedWarning(into, warning) {
|
|
57
132
|
const key = providerWarningKey(warning);
|
|
58
133
|
if (into.some(existing => providerWarningKey(existing) === key))
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"results.js","sourceRoot":"","sources":["../../../src/plus/integrations/results.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"results.js","sourceRoot":"","sources":["../../../src/plus/integrations/results.ts"],"names":[],"mappings":"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"}
|
package/docs/integrations.md
CHANGED
|
@@ -177,13 +177,13 @@ result** instead of rejecting the call. One provider's expired token never blank
|
|
|
177
177
|
`ProviderWarning.kind` (also exported as `ProviderWarningKind`) carries the classifications the facade can
|
|
178
178
|
prove from structured errors:
|
|
179
179
|
|
|
180
|
-
| `kind` | Meaning
|
|
181
|
-
| --------------- |
|
|
182
|
-
| `auth` | Token rejected (401/403 that isn't a throttle).
|
|
183
|
-
| `rate-limit` | Throttled (429, or a 403 whose body says so).
|
|
184
|
-
| `not-found` | 404/410/422 on the requested scope.
|
|
185
|
-
| `no-connection` | The requested `connectionId`/`domain` doesn't resolve.
|
|
186
|
-
| `other` | Catch-all: unsupported input, truncation, upstream/network failure, or an unclassified error. | Preserve the warning and use the result flags; do not assume it is benign or non-retryable. |
|
|
180
|
+
| `kind` | Meaning | Reasonable response |
|
|
181
|
+
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
|
|
182
|
+
| `auth` | Token rejected (401/403 that isn't a throttle). | Prompt to reconnect that connection. |
|
|
183
|
+
| `rate-limit` | Throttled (429, or a 403 whose body says so). | Back off and retry; keep the last snapshot. |
|
|
184
|
+
| `not-found` | 404/410/422 on the requested scope. | Drop that scope; don't reconnect. |
|
|
185
|
+
| `no-connection` | The requested `connectionId`/`domain` doesn't resolve. | Re-resolve the target or re-authenticate. |
|
|
186
|
+
| `other` | Catch-all: unsupported input, truncation, upstream/network failure, or an unclassified error. Read `omission` before treating one as a failure. | Preserve the warning and use the result flags; do not assume it is benign or non-retryable. |
|
|
187
187
|
|
|
188
188
|
`isAuth` is a convenience mirror of `kind === 'auth'`. **Collapsing `kind` into that boolean loses the
|
|
189
189
|
rate-limit and not-found distinctions**, which then have to be re-derived from raw provider prose.
|
|
@@ -191,6 +191,70 @@ Conversely, `other` is intentionally not a complete failure taxonomy. Treat `mes
|
|
|
191
191
|
text rather than a stable protocol; use `fetchFailed`, `page.truncated`, and `page.allPages` for completeness
|
|
192
192
|
and keep unknown failures conservative.
|
|
193
193
|
|
|
194
|
+
### `omission` — succeeded, but withheld results
|
|
195
|
+
|
|
196
|
+
`other` covers two facts with **opposite remedies**: a request that failed, and a request that succeeded while
|
|
197
|
+
part of the answer was withheld. `ProviderWarning.omission` is set only for the second, so a consumer can act
|
|
198
|
+
on it without parsing `message`:
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
if (warning.omission != null) {
|
|
202
|
+
// The read SUCCEEDED — message it as incompleteness, not failure.
|
|
203
|
+
// Whether anything would fetch the rest is a separate question; see `recovery` below.
|
|
204
|
+
if (warning.omission.recovery !== 'none') offerLoadMore(warning.omission);
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
`kind` stays `'other'` for these on purpose: it is the discriminant derived from a caught exception's type, and
|
|
209
|
+
adding a member would silently change what `'other'` means for every existing build.
|
|
210
|
+
|
|
211
|
+
**Its absence proves nothing.** It is never set on a failure — an exception or a structured scope failure —
|
|
212
|
+
where it would be a lie. But it is also absent whenever incompleteness was reported without naming what was
|
|
213
|
+
left out, so treat a bare `kind: 'other'` warning as unclassified rather than as a proven failure.
|
|
214
|
+
|
|
215
|
+
The line that matters is whether the request **succeeded**, not whether a tail was left unread. A drain that
|
|
216
|
+
stopped on its own accounting succeeded and is capped, so it carries the omission; a drain that was interrupted
|
|
217
|
+
mid-read left an unread tail too, but a retry may complete it — that one carries no omission and sets
|
|
218
|
+
`fetchFailed`.
|
|
219
|
+
|
|
220
|
+
`kind` says **why** results are missing:
|
|
221
|
+
|
|
222
|
+
| `omission.kind` | What happened |
|
|
223
|
+
| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
|
|
224
|
+
| `provider-limit` | The provider refuses to serve past a cap (GitHub search's 1,000, Trello's `cards_limit`). |
|
|
225
|
+
| `recovery-budget` | The internal partitioned recovery stopped before visiting every partition. |
|
|
226
|
+
| `pagination-incomplete` | Pages were left unread: an undrained sub-scope, a page budget, or a provider that advertised another page without a usable cursor. |
|
|
227
|
+
|
|
228
|
+
#### `recovery` — what, if anything, would fetch the rest
|
|
229
|
+
|
|
230
|
+
**`kind` does not answer that**, and `pagination-incomplete` is why: a drain that stopped at a page budget and
|
|
231
|
+
a provider that gave no usable cursor are the same kind, but only the first can be fetched. Gate a "load more"
|
|
232
|
+
affordance on `recovery`, never on `kind`:
|
|
233
|
+
|
|
234
|
+
| `omission.recovery` | Means | What a consumer does |
|
|
235
|
+
| ------------------- | -------------------------------------------------------------- | -------------------------------------------------------------------------- |
|
|
236
|
+
| `none` | Nothing you can call returns the missing items. | Say the results are capped. Do not offer to fetch more. |
|
|
237
|
+
| `page-budget` | Re-run the same read with a higher `maxPages` (sweep options). | Offer it — but note it re-reads from the start, so make it user-initiated. |
|
|
238
|
+
|
|
239
|
+
`recovery` is **required** — unlike `limit`, `totalCount` and `scope`, it is never absent. An absent value
|
|
240
|
+
would be indistinguishable from `none` while actually meaning "this producer didn't say", which is the
|
|
241
|
+
ambiguity `omission` exists to remove.
|
|
242
|
+
|
|
243
|
+
It is also **conservative**: it names only what a producer can prove, so `none` means "not known to be
|
|
244
|
+
recoverable", not "proven unrecoverable". Today only a sweep that spent its own page budget reports
|
|
245
|
+
`page-budget`; everything else — every provider cap, every exhausted internal budget, and every omission
|
|
246
|
+
derived from SDK metadata — is `none`. A `scope` does not change that. It attributes where results were
|
|
247
|
+
withheld, and the SDK reports the same scoped shape both for a scope it merely sampled and for one whose
|
|
248
|
+
cursor stalled, so re-reading it is not something this layer can promise.
|
|
249
|
+
|
|
250
|
+
`limit`, `totalCount` and `scope` are forwarded only when reported; **most omissions carry none of the three**,
|
|
251
|
+
so render correctly without them. Two traps: `totalCount` is `number | undefined` and never `null` (the SDK's
|
|
252
|
+
`null` is normalized to absent at the boundary), and `limit` on `recovery-budget` is a **request** budget — do
|
|
253
|
+
not show it to a user as a number of results.
|
|
254
|
+
|
|
255
|
+
Warnings dedup on their structure, `omission` included, so two omissions that differ only in kind, recovery or
|
|
256
|
+
scope stay two warnings even if their messages ever converge.
|
|
257
|
+
|
|
194
258
|
| Flag | Says |
|
|
195
259
|
| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
196
260
|
| `fetchFailed` | `items` is incomplete because a scope failed, or because a flat hierarchy read was truncated. Distinguishes this from a genuine empty result. |
|
|
@@ -199,6 +263,16 @@ and keep unknown failures conservative.
|
|
|
199
263
|
| `failedProviderIds` | Sweeps/broadens: providers whose requested scopes produced no usable result. |
|
|
200
264
|
| `incompleteProviderIds` | Sweeps/broadens: providers with a usable result plus a failed, partial, or truncated sibling scope. |
|
|
201
265
|
|
|
266
|
+
An omission pairs with `page.truncated: true` — results are missing — and typically with `fetchFailed: false`,
|
|
267
|
+
since nothing failed. But the flags are **per result** and `omission` is **per warning**, so the two can differ
|
|
268
|
+
on a fan-out: a sweep where provider A was capped and provider B failed outright reports `fetchFailed: true`
|
|
269
|
+
while A's omission stays true for A. Read `omission` on the warning that carries it — its `providerId`,
|
|
270
|
+
`domain` and `connectionId` say who it is about — rather than inferring it from the aggregate.
|
|
271
|
+
|
|
272
|
+
What is guaranteed is the narrower thing: a warning never claims its own read succeeded when it didn't. A drain
|
|
273
|
+
that dies mid-read publishes its unread tail with no omission, even if an earlier page had already reported
|
|
274
|
+
one.
|
|
275
|
+
|
|
202
276
|
`resolveRepository` reports through `resolution.status` instead: `resolved` · `not-found` · `unauthorized` ·
|
|
203
277
|
`unsupported-provider` · `invalid-remote-url` · `host-mismatch` · `undetermined`. A `resolved` identity
|
|
204
278
|
carries the provider's **canonical** owner/name plus `renamed: true` when the local remote is stale.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gitkraken/core-gitlens",
|
|
3
3
|
"description": "GitLens core — shared Git / AI / GitHub primitives for internal GitKraken consumption",
|
|
4
|
-
"version": "0.5.
|
|
4
|
+
"version": "0.5.102",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "GitKraken",
|
|
@@ -7,7 +7,7 @@ import type {
|
|
|
7
7
|
import { AuthenticationError, RequestNotFoundError, RequestRateLimitError } from '../../git/errors.js';
|
|
8
8
|
import type { IntegrationIds } from './constants.js';
|
|
9
9
|
import { isRateLimitResponse } from './errors.js';
|
|
10
|
-
import type { ProviderWarning } from './results.js';
|
|
10
|
+
import type { ProviderWarning, ProviderWarningOmission } from './results.js';
|
|
11
11
|
import { appendDedupedWarning } from './results.js';
|
|
12
12
|
|
|
13
13
|
/**
|
|
@@ -73,19 +73,6 @@ function toCollectionFailureWarningKind(failure: CollectionScopeFailure): Provid
|
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
/**
|
|
77
|
-
* A scope's identity, as the stable string that keys it.
|
|
78
|
-
*
|
|
79
|
-
* The dedup keys in `providerPaging.ts` build on this, so failures and omissions can never disagree about
|
|
80
|
-
* what "the same scope" means. `providerId` is included: the same repository ID under two providers is two
|
|
81
|
-
* scopes.
|
|
82
|
-
*/
|
|
83
|
-
export function collectionScopeKey(scope: CollectionScope | undefined): string {
|
|
84
|
-
return [scope?.providerId ?? '', scope?.resourceId ?? '', scope?.projectId ?? '', scope?.repositoryId ?? ''].join(
|
|
85
|
-
' ',
|
|
86
|
-
);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
76
|
/** ` (resource r, project p, repository o/n)` for the scope IDs present; empty when the scope names none. */
|
|
90
77
|
function collectionScopeText(scope: CollectionScope | undefined): string {
|
|
91
78
|
const parts: string[] = [];
|
|
@@ -110,9 +97,10 @@ function collectionFailureMessage(failure: CollectionScopeFailure): string {
|
|
|
110
97
|
/**
|
|
111
98
|
* Explains one omission in the consumer's terms — what was left out and, where the SDK reports it, how much.
|
|
112
99
|
*
|
|
113
|
-
* An omission is a completeness fact, never a failure: the read succeeded and the provider (or the SDK's own
|
|
114
|
-
* recovery budget) is what withheld results
|
|
115
|
-
*
|
|
100
|
+
* An omission is a completeness fact, never a failure: the read succeeded, and the provider (or the SDK's own
|
|
101
|
+
* recovery budget) is what withheld results. That is why these never contribute to `fetchFailed`. Whether a
|
|
102
|
+
* retry would recover anything is a separate question this layer cannot answer — see the `recovery` note on
|
|
103
|
+
* {@link toProviderWarningOmission}.
|
|
116
104
|
*/
|
|
117
105
|
function collectionOmissionMessage(omission: CollectionOmission): string {
|
|
118
106
|
const scopeText = collectionScopeText(omission.scope);
|
|
@@ -142,6 +130,39 @@ function collectionOmissionMessage(omission: CollectionOmission): string {
|
|
|
142
130
|
}
|
|
143
131
|
}
|
|
144
132
|
|
|
133
|
+
/**
|
|
134
|
+
* Forwards an SDK omission as the structured signal consumers read instead of parsing {@link
|
|
135
|
+
* collectionOmissionMessage}'s prose.
|
|
136
|
+
*
|
|
137
|
+
* `results.ts` re-spells `CollectionOmissionKind` rather than importing it, so the published warning surface
|
|
138
|
+
* carries no `@gitkraken/provider-apis` types (see the export block in `index.ts`). The return type is what
|
|
139
|
+
* keeps the two unions honest: an SDK bump that adds a member fails to compile here, alongside
|
|
140
|
+
* `collectionOmissionMessage`'s `satisfies never`.
|
|
141
|
+
*
|
|
142
|
+
* `scope` is copied because the SDK's own object is retained and re-merged across drained pages
|
|
143
|
+
* (`providerPaging.ts`), and this one crosses the package boundary.
|
|
144
|
+
*
|
|
145
|
+
* `recovery` is always `'none'`, deliberately, and a scoped `pagination-incomplete` is why it looks wrong: the
|
|
146
|
+
* SDK emits that one shape from situations with opposite remedies and says so itself — "Either the read
|
|
147
|
+
* deliberately took one page per scope, or the provider advertised another page it gave no way to reach." Its
|
|
148
|
+
* `collectAcrossScopes` producers (Azure, Bitbucket, Bitbucket Server) are the first, its `drainAcrossScopes`
|
|
149
|
+
* producers (GitLab, Jira) the second, and those report an omission only when a cursor STALLED — so re-reading
|
|
150
|
+
* that scope stalls at the identical page. Nothing in `CollectionOmission` separates the two, so claiming
|
|
151
|
+
* recoverability would ship the dead-end button `recovery` exists to prevent. Making the recoverable case
|
|
152
|
+
* claimable needs provider-apis to say whether a scope was drained or merely sampled: a fact to forward, not
|
|
153
|
+
* to guess here.
|
|
154
|
+
*/
|
|
155
|
+
function toProviderWarningOmission(omission: CollectionOmission): ProviderWarningOmission {
|
|
156
|
+
return {
|
|
157
|
+
kind: omission.kind,
|
|
158
|
+
recovery: 'none',
|
|
159
|
+
...(omission.limit != null ? { limit: omission.limit } : {}),
|
|
160
|
+
// The SDK types this `number | null | undefined`; normalize to one absence for consumers.
|
|
161
|
+
...(omission.totalCount != null ? { totalCount: omission.totalCount } : {}),
|
|
162
|
+
...(omission.scope != null ? { scope: { ...omission.scope } } : {}),
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
145
166
|
/**
|
|
146
167
|
* Whether SDK metadata describes a read that may be missing results.
|
|
147
168
|
*
|
|
@@ -184,7 +205,8 @@ export function assessCollectionMetadata(
|
|
|
184
205
|
|
|
185
206
|
// Omissions explain WHY a read is incomplete when nothing failed — a provider cap, an exhausted recovery
|
|
186
207
|
// budget, an undrained scope. They classify as `other`, never `auth`, and are deliberately excluded from
|
|
187
|
-
// `fetchFailed` below: the request succeeded
|
|
208
|
+
// `fetchFailed` below: the request itself succeeded. `omission` carries that same fact structurally, so a
|
|
209
|
+
// consumer can act on it without parsing the message.
|
|
188
210
|
const omissions = metadata.omissions ?? [];
|
|
189
211
|
for (const omission of omissions) {
|
|
190
212
|
appendDedupedWarning(warnings, {
|
|
@@ -194,12 +216,17 @@ export function assessCollectionMetadata(
|
|
|
194
216
|
message: collectionOmissionMessage(omission),
|
|
195
217
|
kind: 'other',
|
|
196
218
|
isAuth: false,
|
|
219
|
+
omission: toProviderWarningOmission(omission),
|
|
197
220
|
});
|
|
198
221
|
}
|
|
199
222
|
|
|
200
223
|
const incomplete = isIncompleteCollection(metadata);
|
|
201
224
|
// Only fall back to the generic message when nothing more specific was reported; an omission already
|
|
202
225
|
// explains the incompleteness in the consumer's terms, so adding this on top would be noise.
|
|
226
|
+
//
|
|
227
|
+
// This one deliberately carries no `omission`: it fires precisely when the SDK reported incompleteness
|
|
228
|
+
// WITHOUT saying what was left out, so there is no structured fact to forward and synthesizing one would
|
|
229
|
+
// assert a specificity this layer does not have.
|
|
203
230
|
if (incomplete && failures.length === 0 && omissions.length === 0) {
|
|
204
231
|
appendDedupedWarning(warnings, {
|
|
205
232
|
providerId: providerId,
|
|
@@ -202,6 +202,10 @@ export type {
|
|
|
202
202
|
ProviderSweepResult,
|
|
203
203
|
ProviderWarning,
|
|
204
204
|
ProviderWarningKind,
|
|
205
|
+
ProviderWarningOmission,
|
|
206
|
+
ProviderWarningOmissionKind,
|
|
207
|
+
ProviderWarningOmissionRecovery,
|
|
208
|
+
ProviderWarningOmissionScope,
|
|
205
209
|
ProviderOrganization,
|
|
206
210
|
ProviderRepositoryShape,
|
|
207
211
|
RepositoryIdentity,
|
|
@@ -6,7 +6,8 @@ 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 {
|
|
9
|
+
import { toCollectionScopeFailure } from '../../collectionMetadata.js';
|
|
10
|
+
import { collectionScopeKey } from '../../results.js';
|
|
10
11
|
import type { ProviderApiPagedResult, ProviderHierarchyResult } from '../models.js';
|
|
11
12
|
|
|
12
13
|
/**
|
|
@@ -9,7 +9,7 @@ import type { PullRequestFilter } from '../providerFilters.js';
|
|
|
9
9
|
import type { ProviderPullRequest, ProviderReposInput, ProviderRepository } from '../providers/models.js';
|
|
10
10
|
import { getProviderPullRequestIdentity } from '../providers/models.js';
|
|
11
11
|
import type { ProviderWarning } from '../results.js';
|
|
12
|
-
import { appendDedupedWarning, toProviderWarning } from '../results.js';
|
|
12
|
+
import { appendDedupedWarning, reconcileOmissionsWithFailure, toProviderWarning } from '../results.js';
|
|
13
13
|
import { isIssuesHostIntegrationId } from '../utils/integration.utils.js';
|
|
14
14
|
import { noConnectionWarning, truncationWarning } from './warnings.js';
|
|
15
15
|
|
|
@@ -90,10 +90,18 @@ export async function drainPullRequests(
|
|
|
90
90
|
// this through the terminal returns instead of resetting it to false at the last page.
|
|
91
91
|
let fetchFailed = false;
|
|
92
92
|
let truncated = false;
|
|
93
|
+
// A page the provider capped or couldn't vouch for. Decides the CAUSE the terminal warning reports: a cap
|
|
94
|
+
// outranks a budget stop, because raising a budget cannot un-cap a page.
|
|
95
|
+
let providerTruncated = false;
|
|
96
|
+
// The subset of that which no other warning already explains — decides whether this drain raises one of
|
|
97
|
+
// its own on an otherwise clean exit.
|
|
98
|
+
let unexplainedTruncation = false;
|
|
93
99
|
|
|
94
100
|
// With no repos this is an account-wide "my PRs" sweep. The repo-scoped core rejects an empty `repos`
|
|
95
101
|
// input, so read the provider-native account-wide core instead.
|
|
96
102
|
const accountWide = repos.length === 0;
|
|
103
|
+
/** Every cursor already followed, so a provider that cycles them can't keep the drain walking in circles. */
|
|
104
|
+
const seenCursors = new Set<string>();
|
|
97
105
|
|
|
98
106
|
for (;;) {
|
|
99
107
|
page++;
|
|
@@ -130,10 +138,23 @@ export async function drainPullRequests(
|
|
|
130
138
|
appendDedupedWarning(warnings, noConnectionWarning(id, domain, connectionId));
|
|
131
139
|
}
|
|
132
140
|
// `warning` set → a hard read failure (incomplete items); otherwise not connected / no session.
|
|
141
|
+
const failed = fetchFailed || warning != null || unavailable;
|
|
142
|
+
// A cap seen on an earlier page still left results out, so it is still worth saying — but as part
|
|
143
|
+
// of a read that failed, never as an omission. (`failed` is always true here when anything was
|
|
144
|
+
// latched: reaching this exit past page 1 means a later page was lost.)
|
|
145
|
+
if (unexplainedTruncation) {
|
|
146
|
+
appendDedupedWarning(
|
|
147
|
+
warnings,
|
|
148
|
+
truncationWarning(id, domain, connectionId, 'Pull request', failed ? 'interrupted' : 'exhausted'),
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
// An earlier page may already have emitted an omission before this one died; it asserts the read
|
|
152
|
+
// succeeded, which is no longer true.
|
|
153
|
+
reconcileOmissionsWithFailure(warnings, failed);
|
|
133
154
|
return {
|
|
134
155
|
items: items,
|
|
135
156
|
warnings: warnings,
|
|
136
|
-
fetchFailed:
|
|
157
|
+
fetchFailed: failed,
|
|
137
158
|
truncated: truncated || sessionLostAfterProgress,
|
|
138
159
|
// Only a top-level first-page rejection means the provider itself failed. A later-page or
|
|
139
160
|
// per-scope failure still yielded a usable provider slice and stays represented separately.
|
|
@@ -171,14 +192,31 @@ export async function drainPullRequests(
|
|
|
171
192
|
value.paging?.truncated === true ||
|
|
172
193
|
assessment.truncated;
|
|
173
194
|
truncated = truncated || pageTruncated;
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
195
|
+
// A page the provider itself capped, or one whose completeness it couldn't confirm. Latched from ANY
|
|
196
|
+
// source, SDK metadata included: no budget of ours un-caps a page, so a later `maxPages` hit must not
|
|
197
|
+
// claim raising it would help.
|
|
198
|
+
providerTruncated = providerTruncated || pageTruncated;
|
|
199
|
+
// Whether this drain owes its OWN warning for that is a separate question — `mergeAssessmentInto` has
|
|
200
|
+
// already appended one when the fact came from SDK metadata, and repeating it would be noise.
|
|
201
|
+
unexplainedTruncation = unexplainedTruncation || (pageTruncated && !assessment.truncated);
|
|
177
202
|
|
|
178
203
|
if (!(value.paging?.more ?? false)) {
|
|
179
204
|
// A read that can't confirm completeness (single-page provider reads with no `hasNextPage`)
|
|
180
205
|
// sets `paging.truncated`; propagate it (and any top-level `truncated` and SDK incompleteness)
|
|
181
206
|
// so the sweep doesn't claim an all-pages result.
|
|
207
|
+
if (unexplainedTruncation) {
|
|
208
|
+
appendDedupedWarning(
|
|
209
|
+
warnings,
|
|
210
|
+
truncationWarning(
|
|
211
|
+
id,
|
|
212
|
+
domain,
|
|
213
|
+
connectionId,
|
|
214
|
+
'Pull request',
|
|
215
|
+
fetchFailed ? 'interrupted' : 'exhausted',
|
|
216
|
+
),
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
reconcileOmissionsWithFailure(warnings, fetchFailed);
|
|
182
220
|
return {
|
|
183
221
|
items: items,
|
|
184
222
|
warnings: warnings,
|
|
@@ -187,21 +225,34 @@ export async function drainPullRequests(
|
|
|
187
225
|
failedProvider: false,
|
|
188
226
|
};
|
|
189
227
|
}
|
|
190
|
-
if (page >= maxPages) {
|
|
191
|
-
appendDedupedWarning(warnings, truncationWarning(id, domain, connectionId, 'Pull request'));
|
|
192
|
-
return {
|
|
193
|
-
items: items,
|
|
194
|
-
warnings: warnings,
|
|
195
|
-
fetchFailed: fetchFailed,
|
|
196
|
-
truncated: true,
|
|
197
|
-
failedProvider: false,
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
228
|
|
|
229
|
+
// Resolve the continuation BEFORE deciding why the drain stops. `page-budget` claims the missing items
|
|
230
|
+
// are reachable, which is only true with a usable cursor in hand — and a provider can report another
|
|
231
|
+
// page while handing back none (Bitbucket Server does, when it omits `nextPageStart`). Checking the
|
|
232
|
+
// budget first would label that unreachable tail as merely unfetched, so raising `maxPages` would
|
|
233
|
+
// return the identical set.
|
|
201
234
|
const nextCursor = value.paging?.cursor;
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
235
|
+
// A cursor already used isn't a continuation either — following it refetches a page we have, and a
|
|
236
|
+
// provider that cycles (A→B→A) would otherwise burn the whole budget and then be reported as merely
|
|
237
|
+
// out of budget. Tracked as a SET rather than compared one-back, matching the SDK's own `followCursors`:
|
|
238
|
+
// `drainToRequestedPage` and `collectProviderPagedResult` compare only the previous cursor, which a
|
|
239
|
+
// cycle slips past. That is tolerable there and not here, because only this drain reports `page-budget`.
|
|
240
|
+
const continuable = nextCursor != null && nextCursor !== '{}' && !seenCursors.has(nextCursor);
|
|
241
|
+
if (!continuable || page >= maxPages) {
|
|
242
|
+
// `providerTruncated` outranks the budget: a page the provider capped stays capped however many
|
|
243
|
+
// pages we are allowed to read, so promising `page-budget` on top of it would be a load-more that
|
|
244
|
+
// cannot deliver the capped part.
|
|
245
|
+
appendDedupedWarning(
|
|
246
|
+
warnings,
|
|
247
|
+
truncationWarning(
|
|
248
|
+
id,
|
|
249
|
+
domain,
|
|
250
|
+
connectionId,
|
|
251
|
+
'Pull request',
|
|
252
|
+
fetchFailed ? 'interrupted' : continuable && !providerTruncated ? 'page-budget' : 'exhausted',
|
|
253
|
+
),
|
|
254
|
+
);
|
|
255
|
+
reconcileOmissionsWithFailure(warnings, fetchFailed);
|
|
205
256
|
return {
|
|
206
257
|
items: items,
|
|
207
258
|
warnings: warnings,
|
|
@@ -211,6 +262,7 @@ export async function drainPullRequests(
|
|
|
211
262
|
};
|
|
212
263
|
}
|
|
213
264
|
|
|
265
|
+
seenCursors.add(nextCursor);
|
|
214
266
|
cursor = nextCursor;
|
|
215
267
|
}
|
|
216
268
|
}
|
|
@@ -262,12 +314,20 @@ export async function drainRepositories(
|
|
|
262
314
|
if (value == null) {
|
|
263
315
|
const interruptedAfterProgress = page > 1;
|
|
264
316
|
if (interruptedAfterProgress && warning == null) {
|
|
265
|
-
|
|
317
|
+
// Not a backstop: the read was cut short mid-drain, which is why it also sets `fetchFailed`
|
|
318
|
+
// below. A retry may complete it, so this must not claim the succeeded-but-capped omission.
|
|
319
|
+
appendDedupedWarning(
|
|
320
|
+
warnings,
|
|
321
|
+
truncationWarning(id, domain, connectionId, 'Repository', 'interrupted'),
|
|
322
|
+
);
|
|
266
323
|
}
|
|
324
|
+
const failed = fetchFailed || warning != null || interruptedAfterProgress;
|
|
325
|
+
// An SDK omission from an earlier page asserts the read succeeded; this one didn't.
|
|
326
|
+
reconcileOmissionsWithFailure(warnings, failed);
|
|
267
327
|
return {
|
|
268
328
|
repos: repos,
|
|
269
329
|
warnings: warnings,
|
|
270
|
-
fetchFailed:
|
|
330
|
+
fetchFailed: failed,
|
|
271
331
|
truncated: truncated || interruptedAfterProgress,
|
|
272
332
|
};
|
|
273
333
|
}
|
|
@@ -277,6 +337,7 @@ export async function drainRepositories(
|
|
|
277
337
|
fetchFailed = fetchFailed || assessment.fetchFailed;
|
|
278
338
|
truncated = truncated || value.truncated === true || value.paging?.truncated === true || assessment.truncated;
|
|
279
339
|
if (!(value.paging?.more ?? false)) {
|
|
340
|
+
reconcileOmissionsWithFailure(warnings, fetchFailed);
|
|
280
341
|
return {
|
|
281
342
|
repos: repos,
|
|
282
343
|
warnings: warnings,
|
|
@@ -284,13 +345,16 @@ export async function drainRepositories(
|
|
|
284
345
|
truncated: truncated,
|
|
285
346
|
};
|
|
286
347
|
}
|
|
348
|
+
|
|
287
349
|
if (page >= maxPages) {
|
|
350
|
+
reconcileOmissionsWithFailure(warnings, fetchFailed);
|
|
288
351
|
return { repos: repos, warnings: warnings, fetchFailed: fetchFailed, truncated: true };
|
|
289
352
|
}
|
|
290
353
|
|
|
291
354
|
const nextCursor = value.paging?.cursor;
|
|
292
355
|
if (nextCursor == null || nextCursor === '{}') {
|
|
293
356
|
// Provider says there is more but didn't return a usable cursor; stop rather than refetch the same page.
|
|
357
|
+
reconcileOmissionsWithFailure(warnings, fetchFailed);
|
|
294
358
|
return { repos: repos, warnings: warnings, fetchFailed: fetchFailed, truncated: true };
|
|
295
359
|
}
|
|
296
360
|
|
|
@@ -9,12 +9,13 @@ import { isIssuesIntegration } from '../models/issuesIntegration.js';
|
|
|
9
9
|
import { IssueFilter, providersMetadata } from '../providers/models.js';
|
|
10
10
|
import { mergeCollectionMetadata, parsePageCursor } from '../providers/utils/providerPaging.js';
|
|
11
11
|
import type { ProviderPagedResult, ProviderWarning } from '../results.js';
|
|
12
|
+
import { reconcileOmissionsWithFailure } from '../results.js';
|
|
12
13
|
import { isIssuesHostIntegrationId } from '../utils/integration.utils.js';
|
|
13
14
|
import type { ProviderReadContext } from './context.js';
|
|
14
15
|
import { parseIssueTrackerPageCursor, toIssueTrackerPageCursor } from './cursors.js';
|
|
15
16
|
import { runCaptured } from './drains.js';
|
|
16
17
|
import { projectKey, resourceIdForProject, resourceLabel, resourceMatchesOrg } from './hierarchy.utils.js';
|
|
17
|
-
import { issueTrackerOnlySurfaceWarning, otherWarning } from './warnings.js';
|
|
18
|
+
import { incompleteReadWarning, issueTrackerOnlySurfaceWarning, otherWarning } from './warnings.js';
|
|
18
19
|
|
|
19
20
|
export async function listIssueTrackerIssuesPage(
|
|
20
21
|
ctx: ProviderReadContext,
|
|
@@ -431,14 +432,19 @@ export async function listIssueTrackerIssuesPage(
|
|
|
431
432
|
// the caller sees the truncation, but only when no warning already explains it (avoid duplicate noise).
|
|
432
433
|
if (projectTruncated && warnings.length === 0) {
|
|
433
434
|
warnings.push(
|
|
434
|
-
|
|
435
|
+
incompleteReadWarning(
|
|
435
436
|
options.providerId,
|
|
436
437
|
domain,
|
|
437
438
|
options.connectionId,
|
|
438
439
|
'Some issues were omitted; the provider returned an incomplete result.',
|
|
440
|
+
// `exhausted`, not `page-budget`: the per-project drain's backstop is an internal constant
|
|
441
|
+
// (`maxPagesPerRequest`), not an option this read exposes, so no caller can raise it.
|
|
442
|
+
fetchFailed ? 'interrupted' : 'exhausted',
|
|
439
443
|
),
|
|
440
444
|
);
|
|
441
445
|
}
|
|
446
|
+
// A metadata omission from an earlier project asserts the read succeeded; a later one may since have failed.
|
|
447
|
+
reconcileOmissionsWithFailure(warnings, fetchFailed);
|
|
442
448
|
|
|
443
449
|
const retryPages = retryWindowPages();
|
|
444
450
|
const cursor = toIssueTrackerPageCursor({
|
|
@@ -6,7 +6,7 @@ import type { IssueFilter, ProviderReposInput } from '../providers/models.js';
|
|
|
6
6
|
import { PagingMode, providersMetadata } from '../providers/models.js';
|
|
7
7
|
import { mergeCollectionMetadata } from '../providers/utils/providerPaging.js';
|
|
8
8
|
import type { ProviderPagedResult, ProviderWarning } from '../results.js';
|
|
9
|
-
import { appendDedupedWarning } from '../results.js';
|
|
9
|
+
import { appendDedupedWarning, reconcileOmissionsWithFailure } from '../results.js';
|
|
10
10
|
import {
|
|
11
11
|
isGitHostIntegration,
|
|
12
12
|
isIssuesHostIntegrationId,
|
|
@@ -269,14 +269,19 @@ export async function listIssuesPage(
|
|
|
269
269
|
const truncated = continuation.truncated || assessment.truncated;
|
|
270
270
|
if (truncated && warnings.length === 0) {
|
|
271
271
|
warnings.push(
|
|
272
|
-
|
|
272
|
+
truncationWarning(
|
|
273
273
|
options.providerId,
|
|
274
274
|
domain,
|
|
275
275
|
options.connectionId,
|
|
276
|
-
|
|
276
|
+
'Account-wide issue search',
|
|
277
|
+
// `exhausted`: this composite read exposes no budget the caller can raise, so nothing it
|
|
278
|
+
// could call would return the withheld items.
|
|
279
|
+
assessment.fetchFailed || pageFetchFailed ? 'interrupted' : 'exhausted',
|
|
277
280
|
),
|
|
278
281
|
);
|
|
279
282
|
}
|
|
283
|
+
// A metadata omission from an earlier page asserts the read succeeded; a later page may since have failed.
|
|
284
|
+
reconcileOmissionsWithFailure(warnings, assessment.fetchFailed || pageFetchFailed);
|
|
280
285
|
return {
|
|
281
286
|
items: items,
|
|
282
287
|
warnings: warnings,
|
|
@@ -385,8 +390,20 @@ export async function listIssuesPage(
|
|
|
385
390
|
// page isn't published as complete. Metadata incompleteness is an independent source of the same signal.
|
|
386
391
|
const truncated = continuation.truncated || assessment.truncated;
|
|
387
392
|
if (truncated && warnings.length === 0) {
|
|
388
|
-
warnings.push(
|
|
393
|
+
warnings.push(
|
|
394
|
+
truncationWarning(
|
|
395
|
+
options.providerId,
|
|
396
|
+
domain,
|
|
397
|
+
options.connectionId,
|
|
398
|
+
'Issue',
|
|
399
|
+
// `exhausted`, never `page-budget`: a paged read has no budget the caller can raise, and
|
|
400
|
+
// ordinary continuation is already expressed by `hasMore`/`cursor`.
|
|
401
|
+
assessment.fetchFailed || pageFetchFailed ? 'interrupted' : 'exhausted',
|
|
402
|
+
),
|
|
403
|
+
);
|
|
389
404
|
}
|
|
405
|
+
// A metadata omission from an earlier page asserts the read succeeded; a later page may since have failed.
|
|
406
|
+
reconcileOmissionsWithFailure(warnings, assessment.fetchFailed || pageFetchFailed);
|
|
390
407
|
return {
|
|
391
408
|
items: items,
|
|
392
409
|
warnings: warnings,
|