diff --git a/plugins/fossa/dev/index.tsx b/plugins/fossa/dev/index.tsx index 9625b24dc2..1c2e96807f 100644 --- a/plugins/fossa/dev/index.tsx +++ b/plugins/fossa/dev/index.tsx @@ -27,7 +27,7 @@ import React from 'react'; import { EntityFossaCard, fossaPlugin } from '../src'; import { FindingSummary, FossaApi, fossaApiRef } from '../src/api'; import { FossaPage } from '../src/components/FossaPage'; -import { FOSSA_PROJECT_NAME_ANNOTATION } from '../src/components/useProjectName'; +import { FOSSA_PROJECT_NAME_ANNOTATION } from '../src/components/getProjectName'; const entity = (name?: string) => ({ diff --git a/plugins/fossa/src/api/FossaClient.test.ts b/plugins/fossa/src/api/FossaClient.test.ts index 24bcad526c..29e5b50fcd 100644 --- a/plugins/fossa/src/api/FossaClient.test.ts +++ b/plugins/fossa/src/api/FossaClient.test.ts @@ -287,6 +287,70 @@ describe('FossaClient', () => { ); }); + it('should handle multiple pages', async () => { + server.use( + rest.get(`${mockBaseUrl}/fossa/projects`, (req, res, ctx) => { + const page = req.url.searchParams.get('page'); + + if (page === '0') { + return res( + ctx.json( + [...Array(1000)].map(() => ({ + locator: 'custom+8736/our-service', + title: 'our-service-2', + default_branch: 'develop', + revisions: [ + { + updatedAt: '2020-01-01T00:00:00Z', + dependency_count: 160, + unresolved_licensing_issue_count: 5, + unresolved_issue_count: 100, + }, + ], + })), + ), + ); + } + + return res( + ctx.json([ + { + locator: 'custom+8736/our-service', + title: 'our-service', + default_branch: 'develop', + revisions: [ + { + updatedAt: '2020-01-01T00:00:00Z', + dependency_count: 160, + unresolved_licensing_issue_count: 5, + unresolved_issue_count: 100, + }, + ], + }, + ]), + ); + }), + ); + + const summary = await client.getFindingSummaries(['our-service']); + + expect(summary).toEqual( + new Map([ + [ + 'our-service', + { + timestamp: '2020-01-01T00:00:00Z', + issueCount: 5, + dependencyCount: 160, + projectDefaultBranch: 'develop', + projectUrl: + 'https://app.fossa.com/projects/custom%2B8736%2Four-service', + }, + ], + ]), + ); + }); + it('should handle empty result', async () => { server.use( rest.get(`${mockBaseUrl}/fossa/projects`, (_req, res, ctx) => { diff --git a/plugins/fossa/src/api/FossaClient.ts b/plugins/fossa/src/api/FossaClient.ts index fb5eb80345..e8f3ecb7cd 100644 --- a/plugins/fossa/src/api/FossaClient.ts +++ b/plugins/fossa/src/api/FossaClient.ts @@ -55,7 +55,7 @@ export class FossaClient implements FossaApi { private async callApi( path: string, query: Record, - ): Promise { + ): Promise { const apiUrl = `${await this.discoveryApi.getBaseUrl('proxy')}/fossa`; const idToken = await this.identityApi.getIdToken(); const response = await fetch( @@ -75,30 +75,23 @@ export class FossaClient implements FossaApi { async *getProject( projectTitles: Set, ): AsyncIterable<{ title: string; summary: FindingSummary }> { - let nextPage = 0; const pageSize = 1000; - for (;;) { - const projects = await this.limit( - page => - this.callApi('projects', { - count: pageSize, - page, - sort: 'title+', - ...(this.organizationId && { - organizationId: this.organizationId, - }), - ...(projectTitles.size === 1 && { - title: projectTitles.values().next().value, - }), + for (let page = 0; ; page++) { + const projects = await this.limit(() => + this.callApi('projects', { + count: pageSize, + page, + sort: 'title+', + ...(this.organizationId && { + organizationId: this.organizationId, }), - nextPage++, + ...(projectTitles.size === 1 && { + title: projectTitles.values().next().value, + }), + }), ); - if (!projects) { - break; - } - for (const project of projects) { if (projectTitles.has(project.title) && project.revisions.length > 0) { const revision = project.revisions[0]; diff --git a/plugins/fossa/src/components/FossaCard/FossaCard.tsx b/plugins/fossa/src/components/FossaCard/FossaCard.tsx index 9d2db263c8..034ba57dfc 100644 --- a/plugins/fossa/src/components/FossaCard/FossaCard.tsx +++ b/plugins/fossa/src/components/FossaCard/FossaCard.tsx @@ -32,8 +32,8 @@ import { useAsync } from 'react-use'; import { fossaApiRef } from '../../api'; import { FOSSA_PROJECT_NAME_ANNOTATION, - useProjectName, -} from '../useProjectName'; + getProjectName, +} from '../getProjectName'; const useStyles = makeStyles(theme => ({ numberError: { @@ -102,7 +102,7 @@ export const FossaCard = ({ variant }: { variant?: InfoCardVariants }) => { const { entity } = useEntity(); const fossaApi = useApi(fossaApiRef); - const projectTitle = useProjectName(entity); + const projectTitle = getProjectName(entity); const { value, loading, error } = useAsync( async () => projectTitle ? fossaApi.getFindingSummary(projectTitle) : undefined, diff --git a/plugins/fossa/src/components/FossaPage/FossaPage.tsx b/plugins/fossa/src/components/FossaPage/FossaPage.tsx index 05e1379378..cfc6e8af97 100644 --- a/plugins/fossa/src/components/FossaPage/FossaPage.tsx +++ b/plugins/fossa/src/components/FossaPage/FossaPage.tsx @@ -45,7 +45,7 @@ import * as React from 'react'; import { useMemo } from 'react'; import { useAsync } from 'react-use'; import { FindingSummary, fossaApiRef } from '../../api'; -import { useProjectNames } from '../useProjectNames'; +import { getProjectName } from '../getProjectName'; type FossaRow = { entity: Entity; @@ -63,7 +63,9 @@ const columns: TableColumn[] = [ title: 'Name', field: 'resolved.name', highlight: true, - render: ({ entity }) => , + render: ({ entity }) => ( + + ), }, { title: 'Owner', @@ -170,7 +172,10 @@ export const FossaPage = () => { }); // get the project names of all entities. the idx of both lists match. - const projectNames = useProjectNames(entities?.items); + const projectNames = useMemo( + () => entities?.items?.map(getProjectName) ?? [], + [entities?.items], + ); // get the summary list const { value: summaries, loading: summariesLoading } = useAsync( diff --git a/plugins/fossa/src/components/useProjectName.test.ts b/plugins/fossa/src/components/getProjectName.test.ts similarity index 56% rename from plugins/fossa/src/components/useProjectName.test.ts rename to plugins/fossa/src/components/getProjectName.test.ts index e85b1f427c..461cb18ed7 100644 --- a/plugins/fossa/src/components/useProjectName.test.ts +++ b/plugins/fossa/src/components/getProjectName.test.ts @@ -16,23 +16,37 @@ import { Entity } from '@backstage/catalog-model'; import { renderHook } from '@testing-library/react-hooks'; -import { useProjectName } from './useProjectName'; - -describe('useProjectName', () => { - const entity: Entity = { - apiVersion: 'v1', - kind: 'Component', - metadata: { - name: 'test', - annotations: { - 'fossa.io/project-name': 'test', - }, - }, - }; +import { getProjectName } from './getProjectName'; +describe('getProjectName', () => { it('should extract the fossa project name', async () => { - const { result } = renderHook(() => useProjectName(entity)); + const entity: Entity = { + apiVersion: 'v1', + kind: 'Component', + metadata: { + name: 'test', + annotations: { + 'fossa.io/project-name': 'test', + }, + }, + }; + + const { result } = renderHook(() => getProjectName(entity)); expect(result.current).toBe('test'); }); + + it('should return undefined', async () => { + const entity: Entity = { + apiVersion: 'v1', + kind: 'Component', + metadata: { + name: 'test', + }, + }; + + const { result } = renderHook(() => getProjectName(entity)); + + expect(result.current).toBeUndefined(); + }); }); diff --git a/plugins/fossa/src/components/useProjectName.ts b/plugins/fossa/src/components/getProjectName.ts similarity index 92% rename from plugins/fossa/src/components/useProjectName.ts rename to plugins/fossa/src/components/getProjectName.ts index 88379ca3fa..3f117dbbda 100644 --- a/plugins/fossa/src/components/useProjectName.ts +++ b/plugins/fossa/src/components/getProjectName.ts @@ -18,7 +18,7 @@ import { Entity } from '@backstage/catalog-model'; export const FOSSA_PROJECT_NAME_ANNOTATION = 'fossa.io/project-name'; -export const useProjectName = (entity: Entity): string | undefined => { +export const getProjectName = (entity: Entity): string | undefined => { return ( entity?.metadata.annotations?.[FOSSA_PROJECT_NAME_ANNOTATION] ?? undefined ); diff --git a/plugins/fossa/src/components/useProjectNames.test.ts b/plugins/fossa/src/components/useProjectNames.test.ts deleted file mode 100644 index ae17fa93d8..0000000000 --- a/plugins/fossa/src/components/useProjectNames.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { Entity } from '@backstage/catalog-model'; -import { renderHook } from '@testing-library/react-hooks'; -import { useProjectNames } from './useProjectNames'; - -describe('useProjectNames', () => { - const entity0: Entity = { - apiVersion: 'v1', - kind: 'Component', - metadata: { - name: 'test', - annotations: { - 'fossa.io/project-name': 'test', - }, - }, - }; - const entity1: Entity = { - apiVersion: 'v1', - kind: 'Component', - metadata: { - name: 'test', - }, - }; - - it('should extract the fossa project name', async () => { - const { result } = renderHook(() => useProjectNames([entity0, entity1])); - - expect(result.current).toEqual(['test', undefined]); - }); -}); diff --git a/plugins/fossa/src/components/useProjectNames.ts b/plugins/fossa/src/components/useProjectNames.ts deleted file mode 100644 index c0e11a1e87..0000000000 --- a/plugins/fossa/src/components/useProjectNames.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { Entity } from '@backstage/catalog-model'; -import { useMemo } from 'react'; -import { FOSSA_PROJECT_NAME_ANNOTATION } from './useProjectName'; - -export const useProjectNames = ( - entities: Entity[] | undefined, -): Array => { - return useMemo( - () => - entities?.map( - entity => - entity.metadata.annotations?.[FOSSA_PROJECT_NAME_ANNOTATION] ?? - undefined, - ) ?? [], - [entities], - ); -};