@remogram/provider-gitlab-api 0.1.0-beta.3 → 0.1.0-beta.4

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.
Files changed (2) hide show
  1. package/index.js +167 -26
  2. package/package.json +2 -2
package/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  fetchJson,
3
+ fetchJsonWithMeta,
3
4
  sanitizeField,
4
5
  sanitizeUrl,
5
6
  assertGitRef,
@@ -9,19 +10,33 @@ import {
9
10
  gitAheadBehind,
10
11
  refsInventory,
11
12
  crInventory,
12
- mergeBlockersFromFacts,
13
+ buildMergePlanBodyFromFacts,
13
14
  ERROR_CODES,
14
15
  forgeError,
15
16
  forgeIngestCapabilityFacts,
16
17
  checkPaginationCapabilityFacts,
18
+ openPullListCapabilityFacts,
17
19
  DEFAULT_CHECK_STATUS_PAGE_SIZE,
18
20
  MAX_CHECK_STATUS_PAGES,
21
+ DEFAULT_OPEN_PULL_LIST_PAGE_SIZE,
19
22
  paginateCheckStatusPages,
20
23
  paginateOffsetListPages,
21
24
  fetchWithIngestPageBackoff,
22
25
  fetchPageWithIngestBackoff,
23
26
  withPerPageParam,
24
27
  apiProviderCommands,
28
+ normalizeCrInventorySort,
29
+ DEFAULT_CR_INVENTORY_SLICE_SORT,
30
+ parseTotalCountHeader,
31
+ isCrInventoryFastPathEligible,
32
+ validateFastPathPageLength,
33
+ isNumberSortFastPathEligible,
34
+ isNumberSortFullCollectRequired,
35
+ resolveListTruncatedWithTrustedTotal,
36
+ orderOpenPullNumbers,
37
+ buildOpenPullListMeta,
38
+ gitlabOpenPullSortQuery,
39
+ appendSortQuery,
25
40
  } from '@remogram/core';
26
41
 
27
42
  const PUBLIC_GITLAB_HOST = 'gitlab.com';
@@ -129,6 +144,16 @@ export async function gitlabFetch(config, parsed, path, options = {}) {
129
144
  });
130
145
  }
131
146
 
147
+ export async function gitlabFetchWithMeta(config, parsed, path, options = {}) {
148
+ const base = apiBase(config, parsed);
149
+ const token = requireToken();
150
+ const url = `${base}${path}`;
151
+ return fetchJsonWithMeta(url, {
152
+ ...options,
153
+ headers: { ...authHeaders(token), ...(options.headers || {}) },
154
+ });
155
+ }
156
+
132
157
  const MAX_CHECK_PAGES = MAX_CHECK_STATUS_PAGES;
133
158
  const GITLAB_PAGE_SIZE = 100;
134
159
 
@@ -162,6 +187,10 @@ export function providerCapabilities() {
162
187
  pageSizeParam: 'per_page',
163
188
  sourceCount: check_sources.length,
164
189
  }),
190
+ ...openPullListCapabilityFacts({
191
+ totalCountSource: 'response_header',
192
+ totalCountHeader: 'X-Total',
193
+ }),
165
194
  };
166
195
  }
167
196
 
@@ -314,45 +343,157 @@ export async function prChecks(ctx, opts) {
314
343
  export async function mergePlan(ctx, opts) {
315
344
  const view = await prView(ctx, opts);
316
345
  const checks = await prChecks(ctx, { number: view.pr_number });
317
- const blockers = mergeBlockersFromFacts(view, checks);
346
+ return buildMergePlanBodyFromFacts(ctx, view, checks, opts);
347
+ }
348
+
349
+ const GITLAB_OPEN_PULL_COMPLIANT_MAX =
350
+ DEFAULT_OPEN_PULL_LIST_PAGE_SIZE * MAX_CHECK_STATUS_PAGES;
351
+
352
+ async function probeGitlabOpenPullPageOne(ctx, retainMax, sliceSort) {
353
+ const maxTrusted = GITLAB_OPEN_PULL_COMPLIANT_MAX * 2;
354
+ let path = `${projectApiPath(ctx.config, 'merge_requests')}?state=opened`;
355
+ path = appendSortQuery(path, gitlabOpenPullSortQuery(sliceSort));
356
+ const separator = path.includes('?') ? '&' : '?';
357
+ const requestLimit = Math.min(retainMax, GITLAB_PAGE_SIZE);
358
+ try {
359
+ const { body, headers } = await gitlabFetchWithMeta(
360
+ ctx.config,
361
+ ctx.parsed,
362
+ `${path}${separator}per_page=${requestLimit}&page=1`,
363
+ );
364
+ if (!Array.isArray(body)) return null;
365
+ const totalCount = parseTotalCountHeader(headers, 'X-Total', { maxTrusted });
366
+ if (totalCount == null) return null;
367
+ const listTruncated = totalCount > GITLAB_OPEN_PULL_COMPLIANT_MAX;
368
+ return { body, totalCount, listTruncated, requestLimit };
369
+ } catch {
370
+ return null;
371
+ }
372
+ }
373
+
374
+ function buildGitlabOpenPullMetaFromPage(body, retainMax, sliceSort, totalCount, listTruncated) {
375
+ let numbers = orderOpenPullNumbers(body, (mr) => mr?.iid, sliceSort);
376
+ if (numbers.length > retainMax) numbers = numbers.slice(0, retainMax);
377
+ return buildOpenPullListMeta({
378
+ totalCount,
379
+ numbers,
380
+ listTruncated,
381
+ sliceSort,
382
+ });
383
+ }
384
+
385
+ function gitlabProbePaginationOpts(probe, extra = {}) {
386
+ const { body, totalCount, requestLimit } = probe;
318
387
  return {
319
- pr_number: view.pr_number,
320
- mergeability: view.mergeability,
321
- checks_conclusion: checks.check_conclusion,
322
- blockers,
388
+ trustedTotalCount: totalCount,
389
+ seededFirstPage: { items: body, usedLimit: requestLimit },
390
+ ...extra,
323
391
  };
324
392
  }
325
393
 
326
- export async function listOpenPullsWithMeta(ctx, opts = {}) {
327
- apiBase(ctx.config, ctx.parsed);
328
- requireToken();
394
+ async function paginateGitlabOpenPullList(ctx, opts, sliceSort, paginationOpts = {}) {
395
+ const {
396
+ trustedTotalCount = null,
397
+ numberSortFullCollect = false,
398
+ seededFirstPage = null,
399
+ startPage = 1,
400
+ maxPages = MAX_CHECK_STATUS_PAGES,
401
+ suppressFinalPageProbe = false,
402
+ } = paginationOpts;
329
403
  const listLimit =
330
404
  opts.limit != null && Number.isInteger(Number(opts.limit)) && Number(opts.limit) > 0
331
405
  ? Number(opts.limit)
332
406
  : null;
333
- const path = `${projectApiPath(ctx.config, 'merge_requests')}?state=opened`;
407
+ const retainMax =
408
+ listLimit == null &&
409
+ opts.retain_max != null &&
410
+ Number.isInteger(Number(opts.retain_max)) &&
411
+ Number(opts.retain_max) > 0
412
+ ? Number(opts.retain_max)
413
+ : null;
414
+ let path = `${projectApiPath(ctx.config, 'merge_requests')}?state=opened`;
415
+ path = appendSortQuery(path, gitlabOpenPullSortQuery(sliceSort));
334
416
  const separator = path.includes('?') ? '&' : '?';
335
- const { items: all, list_truncated: listTruncated } = await paginateOffsetListPages({
417
+ const effectiveRetainMax = numberSortFullCollect ? null : retainMax;
418
+ const {
419
+ items: all,
420
+ list_truncated: listTruncated,
421
+ entry_count: entryCount,
422
+ walked_count: walkedCount,
423
+ } = await paginateOffsetListPages({
336
424
  pageSize: GITLAB_PAGE_SIZE,
337
425
  listLimit,
426
+ retainMax: effectiveRetainMax,
427
+ trustedEntryCount: trustedTotalCount,
428
+ seededFirstPage,
429
+ startPage,
430
+ maxPages,
431
+ suppressFinalPageProbe,
338
432
  ...(listLimit != null ? { maxPagesTruncatesWithLimit: true } : {}),
339
433
  fetchPage: async ({ page, limit }) => {
340
- const body = await gitlabFetch(
341
- ctx.config,
342
- ctx.parsed,
343
- `${path}${separator}per_page=${limit}&page=${page}`,
344
- );
345
- return Array.isArray(body) ? body : [];
346
- },
347
- });
348
- let numbers = all
349
- .map((mr) => mr.iid)
350
- .filter((number) => Number.isInteger(number))
351
- .sort((a, b) => a - b);
352
- if (listLimit != null && numbers.length > listLimit) {
353
- numbers = numbers.slice(0, listLimit);
434
+ const body = await gitlabFetch(
435
+ ctx.config,
436
+ ctx.parsed,
437
+ `${path}${separator}per_page=${limit}&page=${page}`,
438
+ );
439
+ return Array.isArray(body) ? body : [];
440
+ },
441
+ });
442
+ let numbers = orderOpenPullNumbers(all, (mr) => mr?.iid, sliceSort);
443
+ const outputCap = listLimit ?? retainMax;
444
+ if (outputCap != null && numbers.length > outputCap) {
445
+ numbers = numbers.slice(0, outputCap);
446
+ }
447
+ return {
448
+ numbers,
449
+ list_truncated: resolveListTruncatedWithTrustedTotal({
450
+ listTruncated,
451
+ trustedTotalCount,
452
+ walkedCount,
453
+ fullCollect: numberSortFullCollect,
454
+ }),
455
+ ...(entryCount != null ? { entry_count: entryCount } : {}),
456
+ slice_sort: sliceSort,
457
+ };
458
+ }
459
+
460
+ export async function listOpenPullsWithMeta(ctx, opts = {}) {
461
+ apiBase(ctx.config, ctx.parsed);
462
+ requireToken();
463
+ const sliceSort = normalizeCrInventorySort(opts.sort ?? DEFAULT_CR_INVENTORY_SLICE_SORT);
464
+ if (!isCrInventoryFastPathEligible(opts)) {
465
+ return paginateGitlabOpenPullList(ctx, opts, sliceSort);
354
466
  }
355
- return { numbers, list_truncated: listTruncated };
467
+
468
+ const retainMax = Number(opts.retain_max);
469
+ const probe = await probeGitlabOpenPullPageOne(ctx, retainMax, sliceSort);
470
+ if (!probe) {
471
+ return paginateGitlabOpenPullList(ctx, opts, sliceSort);
472
+ }
473
+
474
+ const { body, totalCount, listTruncated, requestLimit } = probe;
475
+
476
+ if (listTruncated) {
477
+ if (body.length === 0) {
478
+ return paginateGitlabOpenPullList(ctx, opts, sliceSort, gitlabProbePaginationOpts(probe));
479
+ }
480
+ return buildGitlabOpenPullMetaFromPage(body, retainMax, sliceSort, totalCount, true);
481
+ }
482
+
483
+ if (
484
+ isNumberSortFastPathEligible(totalCount, retainMax, sliceSort) &&
485
+ validateFastPathPageLength(totalCount, requestLimit, body.length)
486
+ ) {
487
+ return buildGitlabOpenPullMetaFromPage(body, retainMax, sliceSort, totalCount, false);
488
+ }
489
+
490
+ const numberSortFullCollect = isNumberSortFullCollectRequired(totalCount, retainMax, sliceSort);
491
+ return paginateGitlabOpenPullList(
492
+ ctx,
493
+ opts,
494
+ sliceSort,
495
+ gitlabProbePaginationOpts(probe, { numberSortFullCollect }),
496
+ );
356
497
  }
357
498
 
358
499
  export async function listOpenPulls(ctx, opts = {}) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remogram/provider-gitlab-api",
3
- "version": "0.1.0-beta.3",
3
+ "version": "0.1.0-beta.4",
4
4
  "description": "GitLab API provider for remogram",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -22,6 +22,6 @@
22
22
  "node": ">=20"
23
23
  },
24
24
  "dependencies": {
25
- "@remogram/core": "0.1.0-beta.3"
25
+ "@remogram/core": "0.1.0-beta.4"
26
26
  }
27
27
  }