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/src/api/SonarQubeClient.test.ts b/plugins/sonarqube/src/api/SonarQubeClient.test.ts index e5cbc2ccdf..3d488aa9a5 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: metricKeys.length, + }), + ); + } + + // 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: metricKeys.length, }), ); }), diff --git a/plugins/sonarqube/src/api/SonarQubeClient.ts b/plugins/sonarqube/src/api/SonarQubeClient.ts index 35035b33c5..4f149d8d1f 100644 --- a/plugins/sonarqube/src/api/SonarQubeClient.ts +++ b/plugins/sonarqube/src/api/SonarQubeClient.ts @@ -34,9 +34,14 @@ 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 apiUrl = `${await this.discoveryApi.getBaseUrl('proxy')}/sonarqube`; - const response = await fetch(`${apiUrl}/${path}`); + const response = await fetch( + `${apiUrl}/${path}?${new URLSearchParams(query).toString()}`, + ); if (response.status === 200) { return (await response.json()) as T; } @@ -44,10 +49,24 @@ 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 nextPage: number = 1; + + for (;;) { + 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) ?? [])); + + if (result && metrics.length < result.total) { + nextPage++; + continue; + } + + return metrics; + } } async getFindingSummary( @@ -57,9 +76,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 +103,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; }