Skip to content

Commit

Permalink
feat(bitbucket): add paginate http option (#22135)
Browse files Browse the repository at this point in the history
  • Loading branch information
setchy committed May 13, 2023
1 parent 2aae511 commit 976a5a1
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 7 deletions.
30 changes: 30 additions & 0 deletions lib/util/http/bitbucket.spec.ts
Expand Up @@ -55,4 +55,34 @@ describe('util/http/bitbucket', () => {
statusCode: 200,
});
});

it('paginates', async () => {
httpMock
.scope(baseUrl)
.get('/some-url')
.reply(200, {
values: ['a'],
page: '1',
next: `${baseUrl}/some-url?page=2`,
})
.get('/some-url?page=2')
.reply(200, {
values: ['b', 'c'],
page: '2',
next: `${baseUrl}/some-url?page=3`,
})
.get('/some-url?page=3')
.reply(200, {
values: ['d'],
page: '3',
});
const res = await api.getJson('some-url', { paginate: true });
expect(res.body).toEqual({
page: '1',
pagelen: 4,
size: 4,
values: ['a', 'b', 'c', 'd'],
next: undefined,
});
});
});
80 changes: 73 additions & 7 deletions lib/util/http/bitbucket.ts
@@ -1,4 +1,7 @@
import type { HttpOptions, HttpResponse, InternalHttpOptions } from './types';
import is from '@sindresorhus/is';
import type { PagedResult } from '../../modules/platform/bitbucket/types';
import { parseUrl, resolveBaseUrl } from '../url';
import type { HttpOptions, HttpResponse } from './types';
import { Http } from '.';

let baseUrl = 'https://api.bitbucket.org/';
Expand All @@ -7,16 +10,79 @@ export const setBaseUrl = (url: string): void => {
baseUrl = url;
};

export class BitbucketHttp extends Http {
constructor(type = 'bitbucket', options?: HttpOptions) {
export interface BitbucketHttpOptions extends HttpOptions {
paginate?: boolean;
}

export class BitbucketHttp extends Http<BitbucketHttpOptions> {
constructor(type = 'bitbucket', options?: BitbucketHttpOptions) {
super(type, options);
}

protected override request<T>(
url: string | URL,
options?: InternalHttpOptions
protected override async request<T>(
path: string,
options?: BitbucketHttpOptions
): Promise<HttpResponse<T>> {
const opts = { baseUrl, ...options };
return super.request<T>(url, opts);

const result = await super.request<T>(path, opts);

if (opts.paginate && isPagedResult(result.body)) {
const resultBody = result.body as PagedResult<T>;

let nextPage = getPageFromURL(resultBody.next);

while (is.nonEmptyString(nextPage)) {
const nextPath = getNextPagePath(path, nextPage);

// istanbul ignore if
if (is.nullOrUndefined(nextPath)) {
break;
}

const nextResult = await super.request<PagedResult<T>>(
nextPath,
options
);

resultBody.values.push(...nextResult.body.values);

nextPage = getPageFromURL(nextResult.body?.next);
}

// Override other page-related attributes
resultBody.pagelen = resultBody.values.length;
resultBody.size = resultBody.values.length;
resultBody.next = undefined;
}

return result;
}
}

function getPageFromURL(url: string | undefined): string | null {
const resolvedURL = parseUrl(url);

if (is.nullOrUndefined(resolvedURL)) {
return null;
}

return resolvedURL.searchParams.get('page');
}

function getNextPagePath(path: string, nextPage: string): string | null {
const resolvedURL = parseUrl(resolveBaseUrl(baseUrl, path));

// istanbul ignore if
if (is.nullOrUndefined(resolvedURL)) {
return null;
}

resolvedURL.searchParams.set('page', nextPage);

return resolvedURL.toString();
}

function isPagedResult(obj: any): obj is PagedResult {
return is.nonEmptyObject(obj) && Array.isArray(obj.values);
}

0 comments on commit 976a5a1

Please sign in to comment.