Skip to content
Snippets Groups Projects
Unverified Commit daaf74de authored by Johannes Feichtner's avatar Johannes Feichtner Committed by GitHub
Browse files

feat(bitbucket-server): add paginate http option (#34114)

parent 69ef5219
No related branches found
Tags 39.175.0
No related merge requests found
import * as httpMock from '../../../test/http-mock'; import * as httpMock from '../../../test/http-mock';
import * as hostRules from '../host-rules'; import * as hostRules from '../host-rules';
import { range } from '../range';
import { BitbucketServerHttp, setBaseUrl } from './bitbucket-server'; import { BitbucketServerHttp, setBaseUrl } from './bitbucket-server';
const baseUrl = 'https://git.example.com'; const baseUrl = 'https://git.example.com';
...@@ -27,4 +28,87 @@ describe('util/http/bitbucket-server', () => { ...@@ -27,4 +28,87 @@ describe('util/http/bitbucket-server', () => {
const res = await api.postJson('some-url'); const res = await api.postJson('some-url');
expect(res.body).toEqual(body); expect(res.body).toEqual(body);
}); });
it('invalid path', async () => {
setBaseUrl('some-in|valid-host');
const res = api.getJsonUnchecked('/some-url');
await expect(res).rejects.toThrow(
'Bitbucket Server: cannot parse path /some-url',
);
});
it('pagination: uses default limit if not configured', async () => {
const valuesPageOne = [...range(1, 100)];
const valuesPageTwo = [...range(101, 200)];
const valuesPageThree = [...range(201, 210)];
httpMock
.scope(baseUrl)
.get('/some-url?foo=bar&limit=100')
.reply(200, {
values: valuesPageOne,
size: 100,
isLastPage: false,
start: 0,
limit: 100,
nextPageStart: 100,
})
.get('/some-url?foo=bar&limit=100&start=100')
.reply(200, {
values: valuesPageTwo,
size: 100,
isLastPage: false,
start: 100,
limit: 100,
nextPageStart: 200,
})
.get('/some-url?foo=bar&limit=100&start=200')
.reply(200, {
values: valuesPageThree,
size: 10,
isLastPage: true,
start: 200,
limit: 100,
});
const res = await api.getJsonUnchecked('/some-url?foo=bar', {
paginate: true,
});
expect(res.body).toEqual([
...valuesPageOne,
...valuesPageTwo,
...valuesPageThree,
]);
});
it('pagination: uses configured limit', async () => {
const valuesPageOne = [...range(1, 50)];
const valuesPageTwo = [...range(51, 90)];
httpMock
.scope(baseUrl)
.get('/some-url?foo=bar&limit=50')
.reply(200, {
values: valuesPageOne,
size: 50,
isLastPage: false,
start: 0,
limit: 50,
nextPageStart: 50,
})
.get('/some-url?foo=bar&limit=50&start=50')
.reply(200, {
values: valuesPageTwo,
size: 40,
isLastPage: true,
start: 50,
limit: 50,
});
const res = await api.getJsonUnchecked('/some-url?foo=bar', {
paginate: true,
limit: 50,
});
expect(res.body).toEqual([...valuesPageOne, ...valuesPageTwo]);
});
}); });
import { resolveBaseUrl } from '../url'; import is from '@sindresorhus/is';
import { logger } from '../../logger';
import { parseUrl, resolveBaseUrl } from '../url';
import type { HttpOptions, HttpResponse, InternalHttpOptions } from './types'; import type { HttpOptions, HttpResponse, InternalHttpOptions } from './types';
import { Http } from '.'; import { Http } from '.';
const MAX_LIMIT = 100;
let baseUrl: string; let baseUrl: string;
export const setBaseUrl = (url: string): void => { export const setBaseUrl = (url: string): void => {
baseUrl = url; baseUrl = url;
}; };
export class BitbucketServerHttp extends Http { export interface BitbucketServerHttpOptions extends HttpOptions {
paginate?: boolean;
limit?: number;
}
interface PagedResult<T = unknown> {
nextPageStart?: number;
values: T[];
}
export class BitbucketServerHttp extends Http<BitbucketServerHttpOptions> {
constructor(options?: HttpOptions) { constructor(options?: HttpOptions) {
super('bitbucket-server', options); super('bitbucket-server', options);
} }
protected override request<T>( protected override async request<T>(
path: string, path: string,
options?: InternalHttpOptions, options?: InternalHttpOptions & BitbucketServerHttpOptions,
): Promise<HttpResponse<T>> { ): Promise<HttpResponse<T>> {
const url = resolveBaseUrl(baseUrl, path); const opts = { baseUrl, ...options };
const opts = {
baseUrl,
...options,
};
opts.headers = { opts.headers = {
...opts.headers, ...opts.headers,
'X-Atlassian-Token': 'no-check', 'X-Atlassian-Token': 'no-check',
}; };
return super.request<T>(url, opts);
const resolvedUrl = parseUrl(resolveBaseUrl(baseUrl, path));
if (!resolvedUrl) {
logger.error({ path }, 'Bitbucket Server: cannot parse path');
throw new Error(`Bitbucket Server: cannot parse path ${path}`);
}
if (opts.paginate) {
const limit = opts.limit ?? MAX_LIMIT;
resolvedUrl.searchParams.set('limit', limit.toString());
}
const result = await super.request<T | PagedResult<T>>(
resolvedUrl.toString(),
opts,
);
if (opts.paginate && isPagedResult(result.body)) {
const collectedValues = [...result.body.values];
let nextPageStart = result.body.nextPageStart;
while (nextPageStart) {
resolvedUrl.searchParams.set('start', nextPageStart.toString());
const nextResult = await super.request<PagedResult<T>>(
resolvedUrl.toString(),
opts,
);
collectedValues.push(...nextResult.body.values);
nextPageStart = nextResult.body.nextPageStart;
}
return { ...result, body: collectedValues as T };
}
return result as HttpResponse<T>;
} }
} }
function isPagedResult(obj: unknown): obj is PagedResult {
return is.nonEmptyObject(obj) && is.array(obj.values);
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment