@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.
@@ -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 maxPages = options?.maxPages ?? 100;
12
- const repos = options?.repos ?? [];
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 { providerId: id, connectionId, domain: requestedDomain } = target;
15
- /** A target that never reached a drain: the provider itself failed, so its slice is empty and attributed. */
16
- const rejectedTarget = (warnings) => ({
17
- items: [],
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":"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"}
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.106",
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].filter(Boolean).join(' ').trim(),
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,
@@ -113,6 +113,7 @@ export type {
113
113
  ProviderRepositoriesInput,
114
114
  ProviderRepositoryInput,
115
115
  ProviderSweepTarget,
116
+ ProviderSweepTargetEvent,
116
117
  PullRequestSweepOptions,
117
118
  } from './manager.js';
118
119
 
@@ -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
- await integration.syncCloudConnection(
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
- void authProvider.deleteAllSessions(this.authProviderDescriptor);
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');