diff --git a/.changeset/gentle-bears-fry.md b/.changeset/gentle-bears-fry.md new file mode 100644 index 0000000000..43966311c7 --- /dev/null +++ b/.changeset/gentle-bears-fry.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-sonarqube': patch +--- + +Collect all available metric types if the number exceeds the default page size of 100. diff --git a/plugins/sonarqube/package.json b/plugins/sonarqube/package.json index ba6c173a59..440ede4d54 100644 --- a/plugins/sonarqube/package.json +++ b/plugins/sonarqube/package.json @@ -41,6 +41,7 @@ "@material-ui/lab": "4.0.0-alpha.45", "@material-ui/styles": "^4.10.0", "cross-fetch": "^3.0.6", + "qs": "^6.9.4", "rc-progress": "^3.0.0", "react": "^16.13.1", "react-dom": "^16.13.1", diff --git a/plugins/sonarqube/src/api/SonarQubeClient.test.ts b/plugins/sonarqube/src/api/SonarQubeClient.test.ts index e5cbc2ccdf..eb3dc8a8b9 100644 --- a/plugins/sonarqube/src/api/SonarQubeClient.test.ts +++ b/plugins/sonarqube/src/api/SonarQubeClient.test.ts @@ -45,10 +45,25 @@ describe('SonarQubeClient', () => { ], ) => { server.use( - rest.get(`${mockBaseUrl}/sonarqube/metrics/search`, (_, res, ctx) => { + rest.get(`${mockBaseUrl}/sonarqube/metrics/search`, (req, res, ctx) => { + expect(req.url.searchParams.get('ps')).toBe('500'); + + // emulate paging to check if everything is requested + if (req.url.searchParams.get('p') === '1') { + return res( + ctx.json({ + metrics: metricKeys.slice(0, 5).map(k => ({ key: k })), + total: 2, + }), + ); + } + + // make sure this is only called twice + expect(req.url.searchParams.get('p')).toBe('2'); return res( ctx.json({ - metrics: metricKeys.map(k => ({ key: k })), + metrics: metricKeys.slice(5).map(k => ({ key: k })), + total: 2, }), ); }), diff --git a/plugins/sonarqube/src/api/SonarQubeClient.ts b/plugins/sonarqube/src/api/SonarQubeClient.ts index 35035b33c5..20292d94e3 100644 --- a/plugins/sonarqube/src/api/SonarQubeClient.ts +++ b/plugins/sonarqube/src/api/SonarQubeClient.ts @@ -16,6 +16,7 @@ import { DiscoveryApi } from '@backstage/core'; import fetch from 'cross-fetch'; +import qs from 'qs'; import { FindingSummary, Metrics, SonarQubeApi } from './SonarQubeApi'; import { ComponentWrapper, MeasuresWrapper } from './types'; @@ -34,9 +35,13 @@ export class SonarQubeClient implements SonarQubeApi { this.baseUrl = baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`; } - private async callApi(path: string): Promise { + private async callApi( + path: string, + query?: { [key in string]: any }, + ): Promise { + const queryString = query ? `?${qs.stringify(query)}` : ''; const apiUrl = `${await this.discoveryApi.getBaseUrl('proxy')}/sonarqube`; - const response = await fetch(`${apiUrl}/${path}`); + const response = await fetch(`${apiUrl}/${path}${queryString}`); if (response.status === 200) { return (await response.json()) as T; } @@ -44,10 +49,23 @@ export class SonarQubeClient implements SonarQubeApi { } private async getSupportedMetrics(): Promise { - const result = await this.callApi<{ metrics: Array<{ key: string }> }>( - 'metrics/search', - ); - return result?.metrics?.map(m => m.key) ?? []; + const metrics: string[] = []; + let pageSize: number | undefined; + let nextPage: number = 1; + + do { + const result = await this.callApi<{ + metrics: Array<{ key: string }>; + total: number; + }>('metrics/search', { ps: 500, p: nextPage }); + + metrics.push(...(result?.metrics?.map(m => m.key) ?? [])); + + pageSize = result?.total; + nextPage++; + } while (pageSize && nextPage <= pageSize); + + return metrics; } async getFindingSummary( @@ -57,9 +75,9 @@ export class SonarQubeClient implements SonarQubeApi { return undefined; } - const component = await this.callApi( - `components/show?component=${componentKey}`, - ); + const component = await this.callApi('components/show', { + component: componentKey, + }); if (!component) { return undefined; } @@ -84,11 +102,10 @@ export class SonarQubeClient implements SonarQubeApi { supportedMetrics.includes(m), ); - const measures = await this.callApi( - `measures/search?projectKeys=${componentKey}&metricKeys=${metricKeys.join( - ',', - )}`, - ); + const measures = await this.callApi('measures/search', { + projectKeys: componentKey, + metricKeys: metricKeys.join(','), + }); if (!measures) { return undefined; }