@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
|
@@ -2,7 +2,12 @@ import type { PullRequestShape } from '../../../git/models/pullRequest.js';
|
|
|
2
2
|
import { mapBounded } from '../../../utils/promise.js';
|
|
3
3
|
import type { IntegrationIds } from '../constants.js';
|
|
4
4
|
import { providerFanOutConcurrency } from '../constants.js';
|
|
5
|
-
import type {
|
|
5
|
+
import type {
|
|
6
|
+
ClosedPullRequestSweepOptions,
|
|
7
|
+
ProviderSweepTarget,
|
|
8
|
+
ProviderSweepTargetEvent,
|
|
9
|
+
PullRequestSweepOptions,
|
|
10
|
+
} from '../manager.js';
|
|
6
11
|
import { fromProviderPullRequest } from '../providers/models.js';
|
|
7
12
|
import type { ProviderSweepResult, ProviderWarning } from '../results.js';
|
|
8
13
|
import { appendDedupedWarning } from '../results.js';
|
|
@@ -37,89 +42,158 @@ interface SweepSlice {
|
|
|
37
42
|
failedProvider: boolean;
|
|
38
43
|
}
|
|
39
44
|
|
|
40
|
-
|
|
45
|
+
/**
|
|
46
|
+
* Drain ONE sweep target. Extracted from the fan-out so the fan-out callback has a single exit: every
|
|
47
|
+
* per-target observation (see `onTargetSettled`) is then reported in one place instead of at each of this
|
|
48
|
+
* function's several early returns, where a missed branch would silently drop a provider's attribution.
|
|
49
|
+
*
|
|
50
|
+
* `undefined` means the target resolved to no reachable connection and is deliberately not attributed in the
|
|
51
|
+
* aggregate result.
|
|
52
|
+
*/
|
|
53
|
+
async function sweepTarget(
|
|
41
54
|
ctx: ProviderReadContext,
|
|
42
|
-
options
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
55
|
+
options: PullRequestSweepOptions | undefined,
|
|
56
|
+
target: ProviderSweepTarget,
|
|
57
|
+
attributeUnavailableProviders: boolean,
|
|
58
|
+
): Promise<SweepSlice | undefined> {
|
|
59
|
+
const { providerId: id, connectionId, domain: requestedDomain } = target;
|
|
46
60
|
const repos = options?.repos ?? [];
|
|
61
|
+
const maxPages = options?.maxPages ?? 100;
|
|
62
|
+
/** A target that never reached a drain: the provider itself failed, so its slice is empty and attributed. */
|
|
63
|
+
const rejectedTarget = (warnings: ProviderWarning[]): SweepSlice => ({
|
|
64
|
+
items: [],
|
|
65
|
+
warnings: warnings,
|
|
66
|
+
fetchFailed: true,
|
|
67
|
+
truncated: false,
|
|
68
|
+
providerId: id,
|
|
69
|
+
failedProvider: true,
|
|
70
|
+
});
|
|
47
71
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const rejectedTarget = (warnings: ProviderWarning[]): SweepSlice => ({
|
|
52
|
-
items: [],
|
|
53
|
-
warnings: warnings,
|
|
54
|
-
fetchFailed: true,
|
|
55
|
-
truncated: false,
|
|
56
|
-
providerId: id,
|
|
57
|
-
failedProvider: true,
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
if (isIssuesHostIntegrationId(id)) {
|
|
61
|
-
return rejectedTarget([
|
|
62
|
-
gitHostOnlySurfaceWarning(id, requestedDomain, connectionId, 'pull request sweeps'),
|
|
63
|
-
]);
|
|
64
|
-
}
|
|
72
|
+
if (isIssuesHostIntegrationId(id)) {
|
|
73
|
+
return rejectedTarget([gitHostOnlySurfaceWarning(id, requestedDomain, connectionId, 'pull request sweeps')]);
|
|
74
|
+
}
|
|
65
75
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
76
|
+
const integration = await ctx.getIntegrationForRead(id, connectionId, requestedDomain);
|
|
77
|
+
if (integration == null) {
|
|
78
|
+
// A requested connection that can't be resolved is a broken connection — surface it as a
|
|
79
|
+
// warning + fetchFailed rather than dropping the provider's slice silently.
|
|
80
|
+
const early = ctx.earlyReturnConnectionWarnings(id, connectionId, requestedDomain);
|
|
81
|
+
if (early.warnings.length === 0 && !attributeUnavailableProviders) return undefined;
|
|
72
82
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
]);
|
|
81
|
-
}
|
|
83
|
+
return rejectedTarget(
|
|
84
|
+
early.warnings.length !== 0 ? early.warnings : [noConnectionWarning(id, requestedDomain, connectionId)],
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
if (!isGitHostIntegration(integration)) {
|
|
88
|
+
return rejectedTarget([gitHostOnlySurfaceWarning(id, requestedDomain, connectionId, 'pull request sweeps')]);
|
|
89
|
+
}
|
|
82
90
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const domain = ctx.domainForRead(integration, id, connectionId, requestedDomain);
|
|
86
|
-
const accountWide = repos.length === 0;
|
|
87
|
-
const requestedFilters = target.filters ?? options?.filters;
|
|
88
|
-
const resolved = accountWide
|
|
89
|
-
? resolveAccountWidePullRequestFilters(id, requestedFilters)
|
|
90
|
-
: resolvePullRequestFilters(id, requestedFilters);
|
|
91
|
-
if (resolved.unsupported) {
|
|
92
|
-
return rejectedTarget([
|
|
93
|
-
accountWide
|
|
94
|
-
? unsupportedAccountWidePullRequestFiltersWarning(id, domain, connectionId, requestedFilters ?? [])
|
|
95
|
-
: unsupportedFiltersWarning(id, domain, connectionId),
|
|
96
|
-
]);
|
|
97
|
-
}
|
|
91
|
+
await ctx.forceRefreshIfRequested(integration, options?.forceSync, connectionId);
|
|
98
92
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
);
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
93
|
+
const domain = ctx.domainForRead(integration, id, connectionId, requestedDomain);
|
|
94
|
+
const accountWide = repos.length === 0;
|
|
95
|
+
const requestedFilters = target.filters ?? options?.filters;
|
|
96
|
+
const resolved = accountWide
|
|
97
|
+
? resolveAccountWidePullRequestFilters(id, requestedFilters)
|
|
98
|
+
: resolvePullRequestFilters(id, requestedFilters);
|
|
99
|
+
if (resolved.unsupported) {
|
|
100
|
+
return rejectedTarget([
|
|
101
|
+
accountWide
|
|
102
|
+
? unsupportedAccountWidePullRequestFiltersWarning(id, domain, connectionId, requestedFilters ?? [])
|
|
103
|
+
: unsupportedFiltersWarning(id, domain, connectionId),
|
|
104
|
+
]);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const drain = await drainPullRequests(
|
|
108
|
+
integration,
|
|
109
|
+
id,
|
|
110
|
+
domain,
|
|
111
|
+
repos,
|
|
112
|
+
options?.states,
|
|
113
|
+
resolved.filters,
|
|
114
|
+
accountWide ? (options?.includeReviewRequested ?? false) : false,
|
|
115
|
+
connectionId,
|
|
116
|
+
maxPages,
|
|
117
|
+
attributeUnavailableProviders,
|
|
118
|
+
);
|
|
119
|
+
const currentAccountId = drain.items.some(pr => pr.author != null)
|
|
120
|
+
? await getCurrentAccountId(integration, connectionId)
|
|
121
|
+
: undefined;
|
|
122
|
+
// Normalize the raw provider-apis PRs to the GitLens-owned shape here, where the per-provider
|
|
123
|
+
// `integration` (the mapper's provider reference) is in scope; the aggregation below only sees drains.
|
|
124
|
+
return {
|
|
125
|
+
...drain,
|
|
126
|
+
items: drain.items.map(pr => fromProviderPullRequest(pr, integration, { currentAccountId: currentAccountId })),
|
|
127
|
+
providerId: id,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* How a target ended, as the consumer buckets it. `failedProvider` outranks `fetchFailed` because a target
|
|
133
|
+
* whose provider failed produced no slice to be partial about, and `undefined` is the target that resolved to
|
|
134
|
+
* no reachable connection at all.
|
|
135
|
+
*/
|
|
136
|
+
function sliceOutcome(slice: SweepSlice | undefined): ProviderSweepTargetEvent['outcome'] {
|
|
137
|
+
if (slice == null) return 'skipped';
|
|
138
|
+
if (slice.failedProvider) return 'failed-provider';
|
|
139
|
+
if (slice.fetchFailed) return 'fetch-failed';
|
|
140
|
+
return 'ok';
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Per-target reporting for a sweep's `onTargetSettled`, opened once per sweep: this stamps the fan-out start,
|
|
145
|
+
* the returned function stamps one target's start, and the function IT returns is the only thing that can
|
|
146
|
+
* report that target. The timestamps therefore never cross a boundary, so there is no pair of interchangeable
|
|
147
|
+
* numbers for a call site to transpose, and the whole reporting concern is one value the fan-out either has or
|
|
148
|
+
* does not. That is also what keeps the option free when it is omitted: with no observer there is nothing to
|
|
149
|
+
* open, so no clock is read and no reporting object exists — do not "simplify" that into an unconditional
|
|
150
|
+
* open, because a host whose perf gate is off (the default) is entitled to pay nothing for it.
|
|
151
|
+
*
|
|
152
|
+
* The try/catch is what makes the observer observation-only: called from the fan-out's success path, a throwing
|
|
153
|
+
* callback would otherwise propagate out of the `mapBounded` task and reject the entire sweep — corrupting the
|
|
154
|
+
* read, not just the metric. Swallowed silently; the consumer owns its own aggregation.
|
|
155
|
+
*
|
|
156
|
+
* The domain is resolved here rather than carried out of {@link sweepTarget}, so every target reports it by the
|
|
157
|
+
* same rule no matter how far it got. `resolveDomainForRead` needs no integration instance, which is what makes
|
|
158
|
+
* that possible: a target rejected by the first guard resolves the same host a fully drained one does.
|
|
159
|
+
*/
|
|
160
|
+
function startSweepReporting(ctx: ProviderReadContext, observe: (event: ProviderSweepTargetEvent) => void) {
|
|
161
|
+
const fanOutStartedAt = performance.now();
|
|
162
|
+
|
|
163
|
+
return function beginTarget(target: ProviderSweepTarget) {
|
|
164
|
+
const startedAt = performance.now();
|
|
165
|
+
|
|
166
|
+
return function reportSettled(slice: SweepSlice | undefined) {
|
|
167
|
+
try {
|
|
168
|
+
observe({
|
|
169
|
+
providerId: target.providerId,
|
|
170
|
+
domain: ctx.resolveDomainForRead(target.providerId, target.connectionId, target.domain),
|
|
171
|
+
connectionId: target.connectionId,
|
|
172
|
+
count: slice?.items.length ?? 0,
|
|
173
|
+
durationMs: performance.now() - startedAt,
|
|
174
|
+
queueWaitMs: startedAt - fanOutStartedAt,
|
|
175
|
+
outcome: sliceOutcome(slice),
|
|
176
|
+
truncated: slice?.truncated ?? false,
|
|
177
|
+
});
|
|
178
|
+
} catch {}
|
|
122
179
|
};
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export async function sweepPullRequests(
|
|
184
|
+
ctx: ProviderReadContext,
|
|
185
|
+
options?: PullRequestSweepOptions,
|
|
186
|
+
): Promise<ProviderSweepResult<PullRequestShape>> {
|
|
187
|
+
const { targets, attributeUnavailableProviders } = resolvePullRequestSweepTargets(options);
|
|
188
|
+
|
|
189
|
+
const observe = options?.onTargetSettled;
|
|
190
|
+
const beginTarget = observe != null ? startSweepReporting(ctx, observe) : undefined;
|
|
191
|
+
|
|
192
|
+
const results = await mapBounded(targets, providerFanOutConcurrency, async target => {
|
|
193
|
+
const reportSettled = beginTarget?.(target);
|
|
194
|
+
const slice = await sweepTarget(ctx, options, target, attributeUnavailableProviders);
|
|
195
|
+
reportSettled?.(slice);
|
|
196
|
+
return slice;
|
|
123
197
|
});
|
|
124
198
|
|
|
125
199
|
const items: PullRequestShape[] = [];
|