@gitkraken/core-gitlens 0.5.106 → 0.5.108
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 +20 -1
- package/dist/plus/git-github/api/github.d.ts +9 -0
- package/dist/plus/git-github/api/github.d.ts.map +1 -1
- package/dist/plus/git-github/api/github.js +18 -1
- package/dist/plus/git-github/api/github.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.map +1 -1
- package/dist/plus/integrations/integrationService.js +16 -1
- package/dist/plus/integrations/integrationService.js.map +1 -1
- package/dist/plus/integrations/manager.d.ts +75 -0
- package/dist/plus/integrations/manager.d.ts.map +1 -1
- package/dist/plus/integrations/models/integration.d.ts.map +1 -1
- package/dist/plus/integrations/models/integration.js +7 -1
- package/dist/plus/integrations/models/integration.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/package.json +1 -1
- package/src/plus/git-github/api/github.ts +18 -1
- package/src/plus/integrations/index.ts +1 -0
- package/src/plus/integrations/integrationService.ts +23 -4
- package/src/plus/integrations/manager.ts +76 -0
- package/src/plus/integrations/models/integration.ts +7 -1
- package/src/plus/integrations/reads/sweeps.ts +150 -76
|
@@ -6,65 +6,129 @@ import { isGitHostIntegration, isIssuesHostIntegrationId } from '../utils/integr
|
|
|
6
6
|
import { drainPullRequests, getCurrentAccountId, resolvePullRequestSweepTargets } from './drains.js';
|
|
7
7
|
import { resolveAccountWidePullRequestFilters, resolvePullRequestFilters } from './filters.js';
|
|
8
8
|
import { gitHostOnlySurfaceWarning, noConnectionWarning, unsupportedAccountWidePullRequestFiltersWarning, unsupportedFiltersWarning, } from './warnings.js';
|
|
9
|
+
/**
|
|
10
|
+
* Drain ONE sweep target. Extracted from the fan-out so the fan-out callback has a single exit: every
|
|
11
|
+
* per-target observation (see `onTargetSettled`) is then reported in one place instead of at each of this
|
|
12
|
+
* function's several early returns, where a missed branch would silently drop a provider's attribution.
|
|
13
|
+
*
|
|
14
|
+
* `undefined` means the target resolved to no reachable connection and is deliberately not attributed in the
|
|
15
|
+
* aggregate result.
|
|
16
|
+
*/
|
|
17
|
+
async function sweepTarget(ctx, options, target, attributeUnavailableProviders) {
|
|
18
|
+
const { providerId: id, connectionId, domain: requestedDomain } = target;
|
|
19
|
+
const repos = options?.repos ?? [];
|
|
20
|
+
const maxPages = options?.maxPages ?? 100;
|
|
21
|
+
/** A target that never reached a drain: the provider itself failed, so its slice is empty and attributed. */
|
|
22
|
+
const rejectedTarget = (warnings) => ({
|
|
23
|
+
items: [],
|
|
24
|
+
warnings: warnings,
|
|
25
|
+
fetchFailed: true,
|
|
26
|
+
truncated: false,
|
|
27
|
+
providerId: id,
|
|
28
|
+
failedProvider: true,
|
|
29
|
+
});
|
|
30
|
+
if (isIssuesHostIntegrationId(id)) {
|
|
31
|
+
return rejectedTarget([gitHostOnlySurfaceWarning(id, requestedDomain, connectionId, 'pull request sweeps')]);
|
|
32
|
+
}
|
|
33
|
+
const integration = await ctx.getIntegrationForRead(id, connectionId, requestedDomain);
|
|
34
|
+
if (integration == null) {
|
|
35
|
+
// A requested connection that can't be resolved is a broken connection — surface it as a
|
|
36
|
+
// warning + fetchFailed rather than dropping the provider's slice silently.
|
|
37
|
+
const early = ctx.earlyReturnConnectionWarnings(id, connectionId, requestedDomain);
|
|
38
|
+
if (early.warnings.length === 0 && !attributeUnavailableProviders)
|
|
39
|
+
return undefined;
|
|
40
|
+
return rejectedTarget(early.warnings.length !== 0 ? early.warnings : [noConnectionWarning(id, requestedDomain, connectionId)]);
|
|
41
|
+
}
|
|
42
|
+
if (!isGitHostIntegration(integration)) {
|
|
43
|
+
return rejectedTarget([gitHostOnlySurfaceWarning(id, requestedDomain, connectionId, 'pull request sweeps')]);
|
|
44
|
+
}
|
|
45
|
+
await ctx.forceRefreshIfRequested(integration, options?.forceSync, connectionId);
|
|
46
|
+
const domain = ctx.domainForRead(integration, id, connectionId, requestedDomain);
|
|
47
|
+
const accountWide = repos.length === 0;
|
|
48
|
+
const requestedFilters = target.filters ?? options?.filters;
|
|
49
|
+
const resolved = accountWide
|
|
50
|
+
? resolveAccountWidePullRequestFilters(id, requestedFilters)
|
|
51
|
+
: resolvePullRequestFilters(id, requestedFilters);
|
|
52
|
+
if (resolved.unsupported) {
|
|
53
|
+
return rejectedTarget([
|
|
54
|
+
accountWide
|
|
55
|
+
? unsupportedAccountWidePullRequestFiltersWarning(id, domain, connectionId, requestedFilters ?? [])
|
|
56
|
+
: unsupportedFiltersWarning(id, domain, connectionId),
|
|
57
|
+
]);
|
|
58
|
+
}
|
|
59
|
+
const drain = await drainPullRequests(integration, id, domain, repos, options?.states, resolved.filters, accountWide ? (options?.includeReviewRequested ?? false) : false, connectionId, maxPages, attributeUnavailableProviders);
|
|
60
|
+
const currentAccountId = drain.items.some(pr => pr.author != null)
|
|
61
|
+
? await getCurrentAccountId(integration, connectionId)
|
|
62
|
+
: undefined;
|
|
63
|
+
// Normalize the raw provider-apis PRs to the GitLens-owned shape here, where the per-provider
|
|
64
|
+
// `integration` (the mapper's provider reference) is in scope; the aggregation below only sees drains.
|
|
65
|
+
return {
|
|
66
|
+
...drain,
|
|
67
|
+
items: drain.items.map(pr => fromProviderPullRequest(pr, integration, { currentAccountId: currentAccountId })),
|
|
68
|
+
providerId: id,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* How a target ended, as the consumer buckets it. `failedProvider` outranks `fetchFailed` because a target
|
|
73
|
+
* whose provider failed produced no slice to be partial about, and `undefined` is the target that resolved to
|
|
74
|
+
* no reachable connection at all.
|
|
75
|
+
*/
|
|
76
|
+
function sliceOutcome(slice) {
|
|
77
|
+
if (slice == null)
|
|
78
|
+
return 'skipped';
|
|
79
|
+
if (slice.failedProvider)
|
|
80
|
+
return 'failed-provider';
|
|
81
|
+
if (slice.fetchFailed)
|
|
82
|
+
return 'fetch-failed';
|
|
83
|
+
return 'ok';
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Per-target reporting for a sweep's `onTargetSettled`, opened once per sweep: this stamps the fan-out start,
|
|
87
|
+
* the returned function stamps one target's start, and the function IT returns is the only thing that can
|
|
88
|
+
* report that target. The timestamps therefore never cross a boundary, so there is no pair of interchangeable
|
|
89
|
+
* numbers for a call site to transpose, and the whole reporting concern is one value the fan-out either has or
|
|
90
|
+
* does not. That is also what keeps the option free when it is omitted: with no observer there is nothing to
|
|
91
|
+
* open, so no clock is read and no reporting object exists — do not "simplify" that into an unconditional
|
|
92
|
+
* open, because a host whose perf gate is off (the default) is entitled to pay nothing for it.
|
|
93
|
+
*
|
|
94
|
+
* The try/catch is what makes the observer observation-only: called from the fan-out's success path, a throwing
|
|
95
|
+
* callback would otherwise propagate out of the `mapBounded` task and reject the entire sweep — corrupting the
|
|
96
|
+
* read, not just the metric. Swallowed silently; the consumer owns its own aggregation.
|
|
97
|
+
*
|
|
98
|
+
* The domain is resolved here rather than carried out of {@link sweepTarget}, so every target reports it by the
|
|
99
|
+
* same rule no matter how far it got. `resolveDomainForRead` needs no integration instance, which is what makes
|
|
100
|
+
* that possible: a target rejected by the first guard resolves the same host a fully drained one does.
|
|
101
|
+
*/
|
|
102
|
+
function startSweepReporting(ctx, observe) {
|
|
103
|
+
const fanOutStartedAt = performance.now();
|
|
104
|
+
return function beginTarget(target) {
|
|
105
|
+
const startedAt = performance.now();
|
|
106
|
+
return function reportSettled(slice) {
|
|
107
|
+
try {
|
|
108
|
+
observe({
|
|
109
|
+
providerId: target.providerId,
|
|
110
|
+
domain: ctx.resolveDomainForRead(target.providerId, target.connectionId, target.domain),
|
|
111
|
+
connectionId: target.connectionId,
|
|
112
|
+
count: slice?.items.length ?? 0,
|
|
113
|
+
durationMs: performance.now() - startedAt,
|
|
114
|
+
queueWaitMs: startedAt - fanOutStartedAt,
|
|
115
|
+
outcome: sliceOutcome(slice),
|
|
116
|
+
truncated: slice?.truncated ?? false,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
catch { }
|
|
120
|
+
};
|
|
121
|
+
};
|
|
122
|
+
}
|
|
9
123
|
export async function sweepPullRequests(ctx, options) {
|
|
10
124
|
const { targets, attributeUnavailableProviders } = resolvePullRequestSweepTargets(options);
|
|
11
|
-
const
|
|
12
|
-
const
|
|
125
|
+
const observe = options?.onTargetSettled;
|
|
126
|
+
const beginTarget = observe != null ? startSweepReporting(ctx, observe) : undefined;
|
|
13
127
|
const results = await mapBounded(targets, providerFanOutConcurrency, async (target) => {
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
warnings: warnings,
|
|
19
|
-
fetchFailed: true,
|
|
20
|
-
truncated: false,
|
|
21
|
-
providerId: id,
|
|
22
|
-
failedProvider: true,
|
|
23
|
-
});
|
|
24
|
-
if (isIssuesHostIntegrationId(id)) {
|
|
25
|
-
return rejectedTarget([
|
|
26
|
-
gitHostOnlySurfaceWarning(id, requestedDomain, connectionId, 'pull request sweeps'),
|
|
27
|
-
]);
|
|
28
|
-
}
|
|
29
|
-
const integration = await ctx.getIntegrationForRead(id, connectionId, requestedDomain);
|
|
30
|
-
if (integration == null) {
|
|
31
|
-
// A requested connection that can't be resolved is a broken connection — surface it as a
|
|
32
|
-
// warning + fetchFailed rather than dropping the provider's slice silently.
|
|
33
|
-
const early = ctx.earlyReturnConnectionWarnings(id, connectionId, requestedDomain);
|
|
34
|
-
if (early.warnings.length === 0 && !attributeUnavailableProviders)
|
|
35
|
-
return undefined;
|
|
36
|
-
return rejectedTarget(early.warnings.length !== 0 ? early.warnings : [noConnectionWarning(id, requestedDomain, connectionId)]);
|
|
37
|
-
}
|
|
38
|
-
if (!isGitHostIntegration(integration)) {
|
|
39
|
-
return rejectedTarget([
|
|
40
|
-
gitHostOnlySurfaceWarning(id, requestedDomain, connectionId, 'pull request sweeps'),
|
|
41
|
-
]);
|
|
42
|
-
}
|
|
43
|
-
await ctx.forceRefreshIfRequested(integration, options?.forceSync, connectionId);
|
|
44
|
-
const domain = ctx.domainForRead(integration, id, connectionId, requestedDomain);
|
|
45
|
-
const accountWide = repos.length === 0;
|
|
46
|
-
const requestedFilters = target.filters ?? options?.filters;
|
|
47
|
-
const resolved = accountWide
|
|
48
|
-
? resolveAccountWidePullRequestFilters(id, requestedFilters)
|
|
49
|
-
: resolvePullRequestFilters(id, requestedFilters);
|
|
50
|
-
if (resolved.unsupported) {
|
|
51
|
-
return rejectedTarget([
|
|
52
|
-
accountWide
|
|
53
|
-
? unsupportedAccountWidePullRequestFiltersWarning(id, domain, connectionId, requestedFilters ?? [])
|
|
54
|
-
: unsupportedFiltersWarning(id, domain, connectionId),
|
|
55
|
-
]);
|
|
56
|
-
}
|
|
57
|
-
const drain = await drainPullRequests(integration, id, domain, repos, options?.states, resolved.filters, accountWide ? (options?.includeReviewRequested ?? false) : false, connectionId, maxPages, attributeUnavailableProviders);
|
|
58
|
-
const currentAccountId = drain.items.some(pr => pr.author != null)
|
|
59
|
-
? await getCurrentAccountId(integration, connectionId)
|
|
60
|
-
: undefined;
|
|
61
|
-
// Normalize the raw provider-apis PRs to the GitLens-owned shape here, where the per-provider
|
|
62
|
-
// `integration` (the mapper's provider reference) is in scope; the aggregation below only sees drains.
|
|
63
|
-
return {
|
|
64
|
-
...drain,
|
|
65
|
-
items: drain.items.map(pr => fromProviderPullRequest(pr, integration, { currentAccountId: currentAccountId })),
|
|
66
|
-
providerId: id,
|
|
67
|
-
};
|
|
128
|
+
const reportSettled = beginTarget?.(target);
|
|
129
|
+
const slice = await sweepTarget(ctx, options, target, attributeUnavailableProviders);
|
|
130
|
+
reportSettled?.(slice);
|
|
131
|
+
return slice;
|
|
68
132
|
});
|
|
69
133
|
const items = [];
|
|
70
134
|
const warnings = [];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sweeps.js","sourceRoot":"","sources":["../../../../src/plus/integrations/reads/sweeps.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAEvD,OAAO,EAAE,yBAAyB,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"sweeps.js","sourceRoot":"","sources":["../../../../src/plus/integrations/reads/sweeps.ts"],"names":[],"mappings":"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"}
|
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.108",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "GitKraken",
|
|
@@ -3510,6 +3510,15 @@ export class GitHubApi {
|
|
|
3510
3510
|
return `https://avatars.githubusercontent.com/u/e?email=${encodeURIComponent(email)}&s=${avatarSize}`;
|
|
3511
3511
|
}
|
|
3512
3512
|
|
|
3513
|
+
/**
|
|
3514
|
+
* One page of the current user's pull requests, filtered by state and optionally by an explicit
|
|
3515
|
+
* relationship qualifier. Backs the PR sweeps, which drain it page by page.
|
|
3516
|
+
*
|
|
3517
|
+
* Ordering is part of the contract, not an option: always `sort:updated` (most recently updated first),
|
|
3518
|
+
* matching {@link searchPullRequestsPage}. A caller that stops before `hasMore` clears — every sweep with a
|
|
3519
|
+
* page budget — therefore retains a well-defined recency window instead of an arbitrary slice of GitHub's
|
|
3520
|
+
* relevance ranking.
|
|
3521
|
+
*/
|
|
3513
3522
|
@trace({ args: (provider, token) => ({ provider: provider.name, token: `<token:${token.microHash}>` }) })
|
|
3514
3523
|
async searchMyPullRequestsPage(
|
|
3515
3524
|
provider: Provider,
|
|
@@ -3608,12 +3617,20 @@ export class GitHubApi {
|
|
|
3608
3617
|
options?.includeDefaultInvolvement === false
|
|
3609
3618
|
? 'is:pr archived:false'
|
|
3610
3619
|
: 'is:pr involves:@me archived:false';
|
|
3620
|
+
// Ordering is part of the contract, not an option — same as `searchPullRequestsPage`. Without it
|
|
3621
|
+
// GitHub answers in `best-match` (relevance) order, so any result set the caller stops short of
|
|
3622
|
+
// draining is an arbitrary sample rather than "the N most recent": which rows land inside a page
|
|
3623
|
+
// budget can then shift with GitHub's ranking even when nothing changed upstream. Consumers that
|
|
3624
|
+
// cap the walk depend on this to make their window deterministic and time-bounded.
|
|
3611
3625
|
const rsp = await this.graphql<SearchResult>(
|
|
3612
3626
|
provider,
|
|
3613
3627
|
token,
|
|
3614
3628
|
query,
|
|
3615
3629
|
{
|
|
3616
|
-
search: [stateQualifier, relationshipQualifier, search
|
|
3630
|
+
search: [stateQualifier, relationshipQualifier, search, 'sort:updated']
|
|
3631
|
+
.filter(Boolean)
|
|
3632
|
+
.join(' ')
|
|
3633
|
+
.trim(),
|
|
3617
3634
|
cursor: options?.cursor,
|
|
3618
3635
|
baseUrl: options?.baseUrl,
|
|
3619
3636
|
avatarSize: options?.avatarSize,
|
|
@@ -1574,12 +1574,31 @@ export class IntegrationService implements Disposable, RepositoryResolutionConte
|
|
|
1574
1574
|
}
|
|
1575
1575
|
}
|
|
1576
1576
|
|
|
1577
|
+
// Concurrent, not serial: each `syncCloudConnection` is a cloud round trip (a forced sync deletes the
|
|
1578
|
+
// stored session and refetches it), so a serial loop pays the SUM of every provider's latency on a
|
|
1579
|
+
// path that gates EVERY provider read. `reconcileCloudConnections` below is already `Promise.all`.
|
|
1580
|
+
//
|
|
1581
|
+
// Safe because every shared mutable path is already serialized, which is why this can be a plain
|
|
1582
|
+
// fan-out rather than needing per-provider grouping:
|
|
1583
|
+
// - `ensureProvider` is `@gate()`d on `providerId`, so the two integrations a multi-host
|
|
1584
|
+
// self-managed id yields (one per domain, see `getSupportedCloudIntegrations`) share one
|
|
1585
|
+
// in-flight construction instead of racing to `providers.set`.
|
|
1586
|
+
// - `ensureSession` is `@gate()`d per integration instance.
|
|
1587
|
+
// - `addOrUpdateConfigured`/`removeConfigured` mutate `configured` in a synchronous critical
|
|
1588
|
+
// section (no `await` between read and `set`), and `storeConfigured` has its own write queue.
|
|
1589
|
+
// - Secrets and the `connected:` workspace flags are keyed by integration id + domain.
|
|
1590
|
+
const integrations: Integration[] = [];
|
|
1577
1591
|
for await (const integration of this.getSupportedCloudIntegrations(domainsById)) {
|
|
1578
|
-
|
|
1579
|
-
this.getCloudConnectionState(integration, connectedIntegrations, domainsById),
|
|
1580
|
-
forceConnect,
|
|
1581
|
-
);
|
|
1592
|
+
integrations.push(integration);
|
|
1582
1593
|
}
|
|
1594
|
+
await Promise.all(
|
|
1595
|
+
integrations.map(integration =>
|
|
1596
|
+
integration.syncCloudConnection(
|
|
1597
|
+
this.getCloudConnectionState(integration, connectedIntegrations, domainsById),
|
|
1598
|
+
forceConnect,
|
|
1599
|
+
),
|
|
1600
|
+
),
|
|
1601
|
+
);
|
|
1583
1602
|
|
|
1584
1603
|
// Persist every account when the backend advertises per-connection identity (multi-account). This
|
|
1585
1604
|
// is a strict no-op for backends that return a single, id-less connection per provider.
|
|
@@ -59,6 +59,63 @@ export interface ProviderSweepTarget {
|
|
|
59
59
|
filters?: PullRequestFilter[];
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
/**
|
|
63
|
+
* One target's contribution to a pull-request sweep, reported as it settles.
|
|
64
|
+
*
|
|
65
|
+
* A sweep makes a single call and distributes its targets internally, so its aggregate result carries no
|
|
66
|
+
* per-target timing at all and only derivable per-target counts. This is the boundary that exposes them, for a
|
|
67
|
+
* host attributing a slow or failing sweep to the provider responsible.
|
|
68
|
+
*
|
|
69
|
+
* `outcome` is reported as one value rather than the underlying booleans because a consumer buckets by it, and
|
|
70
|
+
* because "the whole target is unusable" and "the target returned a slice with a gap" are different facts:
|
|
71
|
+
* a `failed-provider` target contributes nothing, a `fetch-failed` one contributes `count` rows that are
|
|
72
|
+
* incomplete. `skipped` is a target that resolved to no reachable connection and is deliberately not
|
|
73
|
+
* attributed in the aggregate result — reported anyway so a consumer counting targets never loses one.
|
|
74
|
+
*
|
|
75
|
+
* A target that produced no slice at all reports `count: 0` and `truncated: false` — always for `skipped`, and
|
|
76
|
+
* for `failed-provider` as `drainPullRequests` reports it today. Read those fields rather than deriving them
|
|
77
|
+
* from `outcome`: they are the target's own values, not constants the outcome guarantees.
|
|
78
|
+
*/
|
|
79
|
+
export interface ProviderSweepTargetEvent {
|
|
80
|
+
providerId: IntegrationIds;
|
|
81
|
+
/**
|
|
82
|
+
* The self-managed host this target selects, resolved from its configured connection, its explicit domain,
|
|
83
|
+
* or the provider's primary configured host — the same rule the read itself uses, and the same one whether
|
|
84
|
+
* the target drained fully or was rejected by the first guard.
|
|
85
|
+
*
|
|
86
|
+
* Always `undefined` for a cloud provider: it has a single host, so there is nothing to disambiguate. Group
|
|
87
|
+
* by `providerId` and treat this as a label, not part of the key.
|
|
88
|
+
*/
|
|
89
|
+
domain: string | undefined;
|
|
90
|
+
connectionId: string | undefined;
|
|
91
|
+
/**
|
|
92
|
+
* Rows this target contributed to the aggregate result. The sweep concatenates target slices without a
|
|
93
|
+
* cross-target pass — duplicates are collapsed per target, across its own pages — so these sum to exactly
|
|
94
|
+
* `items.length`, and a sum that disagrees means a target went unreported.
|
|
95
|
+
*/
|
|
96
|
+
count: number;
|
|
97
|
+
/**
|
|
98
|
+
* Wall time from this target's worker picking it up to its slice being ready.
|
|
99
|
+
*
|
|
100
|
+
* Targets in the same sweep run concurrently, so these intervals OVERLAP and are not additive: summing them
|
|
101
|
+
* across a sweep exceeds the sweep's own duration. Compare them against each other, not against a total.
|
|
102
|
+
*/
|
|
103
|
+
durationMs: number;
|
|
104
|
+
/**
|
|
105
|
+
* Wall time between the fan-out starting and this target's worker picking the target up.
|
|
106
|
+
*
|
|
107
|
+
* Structurally 0 only while the target count fits the fan-out's concurrency limit, which a selection of a
|
|
108
|
+
* few providers does and the default sweep does NOT: with no `targets`/`providerIds` it opens one target per
|
|
109
|
+
* supported git host, more than the limit, so the last ones genuinely wait. A non-zero value there is the
|
|
110
|
+
* normal case, not an anomaly — it is the cost of the bound, and only worth acting on if it rivals
|
|
111
|
+
* `durationMs`.
|
|
112
|
+
*/
|
|
113
|
+
queueWaitMs: number;
|
|
114
|
+
outcome: 'ok' | 'failed-provider' | 'fetch-failed' | 'skipped';
|
|
115
|
+
/** Whether this target left pages unread. */
|
|
116
|
+
truncated: boolean;
|
|
117
|
+
}
|
|
118
|
+
|
|
62
119
|
type ProviderSweepSelection =
|
|
63
120
|
| {
|
|
64
121
|
targets: readonly ProviderSweepTarget[];
|
|
@@ -105,6 +162,25 @@ type PullRequestSweepCommonOptions = {
|
|
|
105
162
|
includeReviewRequested?: boolean;
|
|
106
163
|
forceSync?: boolean;
|
|
107
164
|
maxPages?: number;
|
|
165
|
+
/**
|
|
166
|
+
* Fired once per target as it settles, for host-side per-provider attribution.
|
|
167
|
+
*
|
|
168
|
+
* Observation only: it cannot influence the sweep, and a SYNCHRONOUS throw is swallowed rather than allowed
|
|
169
|
+
* to turn a successful target into a failed one. Return nothing and do no async work: the `void` return type
|
|
170
|
+
* admits an `async` callback, but nothing awaits it, so a rejection from one escapes that guarantee as an
|
|
171
|
+
* unhandled rejection. It is also invoked synchronously in the middle of the fan-out — do not re-enter the
|
|
172
|
+
* manager from it.
|
|
173
|
+
*
|
|
174
|
+
* Omitting it costs nothing at all, not even a clock read, so a host that only measures behind a gate can
|
|
175
|
+
* leave the gate off without paying for the option.
|
|
176
|
+
*
|
|
177
|
+
* It reports how a target SETTLED, not every way one can end, and **delivery does not stop when the sweep
|
|
178
|
+
* fails**: if a target's read throws instead of reporting failure through its slice, the sweep rejects with
|
|
179
|
+
* that error, but its sibling targets are already in flight and are not cancelled, so their events still
|
|
180
|
+
* arrive — after the returned promise has rejected. Key the accumulator to the call rather than closing it
|
|
181
|
+
* on rejection, or a late event lands in whatever bucket is current by then.
|
|
182
|
+
*/
|
|
183
|
+
onTargetSettled?: (event: ProviderSweepTargetEvent) => void;
|
|
108
184
|
};
|
|
109
185
|
|
|
110
186
|
export type PullRequestSweepOptions = PullRequestSweepCommonOptions & ProviderSweepSelection;
|
|
@@ -269,7 +269,13 @@ export abstract class IntegrationBase<
|
|
|
269
269
|
// id, so an unscoped clear would sign the user out of unrelated hosts. deleteAllSessions derives an
|
|
270
270
|
// undefined domain for cloud providers, so they still clear every account as intended.
|
|
271
271
|
const authProvider = await this.authenticationService.get(this.authProvider.id);
|
|
272
|
-
|
|
272
|
+
// Awaited, not fire-and-forget: a caller that awaits `disconnect()` has to be able to rely on the
|
|
273
|
+
// secrets and descriptors actually being gone when it resumes. Left floating, the only thing that
|
|
274
|
+
// ever made this land in time was incidental scheduling slack — `syncCloudIntegrations` used to
|
|
275
|
+
// await each provider in turn, so a later iteration's suspension let the previous provider's delete
|
|
276
|
+
// finish. Syncing providers concurrently removes that slack and the clear was observably still
|
|
277
|
+
// pending when the sync returned.
|
|
278
|
+
await authProvider.deleteAllSessions(this.authProviderDescriptor);
|
|
273
279
|
}
|
|
274
280
|
|
|
275
281
|
this.resetRequestExceptionCount('all');
|