From ab88f2a25565aae6c103695a929679d9bc7f7d7b Mon Sep 17 00:00:00 2001 From: Dominik Henneke Date: Thu, 10 Dec 2020 16:06:23 +0100 Subject: [PATCH] Add the fossa plugin --- plugins/fossa/.eslintrc.js | 3 + plugins/fossa/README.md | 79 ++++++++ plugins/fossa/config.d.ts | 25 +++ plugins/fossa/dev/index.tsx | 133 +++++++++++++ plugins/fossa/docs/fossa-card.png | Bin 0 -> 61617 bytes plugins/fossa/package.json | 62 ++++++ plugins/fossa/src/api/FossaApi.ts | 34 ++++ plugins/fossa/src/api/FossaClient.test.ts | 138 ++++++++++++++ plugins/fossa/src/api/FossaClient.ts | 74 ++++++++ plugins/fossa/src/api/index.ts | 19 ++ .../src/components/FossaCard/FossaCard.tsx | 177 ++++++++++++++++++ .../fossa/src/components/FossaCard/index.ts | 17 ++ plugins/fossa/src/components/index.ts | 17 ++ .../fossa/src/components/useProjectName.ts | 23 +++ plugins/fossa/src/index.ts | 18 ++ plugins/fossa/src/plugin.test.ts | 23 +++ plugins/fossa/src/plugin.ts | 38 ++++ plugins/fossa/src/setupTests.ts | 17 ++ 18 files changed, 897 insertions(+) create mode 100644 plugins/fossa/.eslintrc.js create mode 100644 plugins/fossa/README.md create mode 100644 plugins/fossa/config.d.ts create mode 100644 plugins/fossa/dev/index.tsx create mode 100644 plugins/fossa/docs/fossa-card.png create mode 100644 plugins/fossa/package.json create mode 100644 plugins/fossa/src/api/FossaApi.ts create mode 100644 plugins/fossa/src/api/FossaClient.test.ts create mode 100644 plugins/fossa/src/api/FossaClient.ts create mode 100644 plugins/fossa/src/api/index.ts create mode 100644 plugins/fossa/src/components/FossaCard/FossaCard.tsx create mode 100644 plugins/fossa/src/components/FossaCard/index.ts create mode 100644 plugins/fossa/src/components/index.ts create mode 100644 plugins/fossa/src/components/useProjectName.ts create mode 100644 plugins/fossa/src/index.ts create mode 100644 plugins/fossa/src/plugin.test.ts create mode 100644 plugins/fossa/src/plugin.ts create mode 100644 plugins/fossa/src/setupTests.ts diff --git a/plugins/fossa/.eslintrc.js b/plugins/fossa/.eslintrc.js new file mode 100644 index 0000000000..13573efa9c --- /dev/null +++ b/plugins/fossa/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: [require.resolve('@backstage/cli/config/eslint')], +}; diff --git a/plugins/fossa/README.md b/plugins/fossa/README.md new file mode 100644 index 0000000000..489aef90ab --- /dev/null +++ b/plugins/fossa/README.md @@ -0,0 +1,79 @@ +# FOSSA Plugin + +The FOSSA Plugin displays code statistics from [FOSSA](https://fossa.com/). + +![FOSSA Card](./docs/fossa-card.png) + +## Getting Started + +1. Install the FOSSA Plugin: + +```bash +# packages/app + +yarn add @backstage/plugin-fossa +``` + +2. Add plugin to the app: + +```js +// packages/app/src/plugins.ts + +export { plugin as Fossa } from '@backstage/plugin-fossa'; +``` + +3. Add the `FossaCard` to the EntityPage: + +```jsx +// packages/app/src/components/catalog/EntityPage.tsx + +import { FossaCard } from '@backstage/plugin-fossa'; + +const OverviewContent = ({ entity }: { entity: Entity }) => ( + + // ... + + + + // ... + +); +``` + +4. Add the proxy config: + +```yaml +# app-config.yaml + +proxy: + '/fossa': + target: https://app.fossa.io/api + allowedMethods: ['GET'] + headers: + Authorization: + # Content: 'token ' + $env: FOSSA_AUTH_HEADER + +# if you have a fossa organization, configure your id here +fossa: + organizationId: +``` + +5. Get an api-token and provide `FOSSA_AUTH_HEADER` as env variable (https://app.fossa.com/account/settings/integrations/api_tokens) + +6. Add the `fossa.io/project-name` annotation to your catalog-info.yaml file: + +```yaml +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage + description: | + Backstage is an open-source developer portal that puts the developer experience first. + annotations: + fossa.io/project-name: YOUR_PROJECT_NAME +spec: + type: library + owner: CNCF + lifecycle: experimental +``` diff --git a/plugins/fossa/config.d.ts b/plugins/fossa/config.d.ts new file mode 100644 index 0000000000..744a1cb52d --- /dev/null +++ b/plugins/fossa/config.d.ts @@ -0,0 +1,25 @@ +/* + * 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. + */ + +export interface Config { + fossa?: { + /** + * The organization id in fossa. + * @visibility frontend + */ + organizationId: string; + }; +} diff --git a/plugins/fossa/dev/index.tsx b/plugins/fossa/dev/index.tsx new file mode 100644 index 0000000000..7846324b25 --- /dev/null +++ b/plugins/fossa/dev/index.tsx @@ -0,0 +1,133 @@ +/* + * 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 { createDevApp } from '@backstage/dev-utils'; +import { + Content, + createPlugin, + createRouteRef, + Header, + Page, +} from '@backstage/core'; +import React from 'react'; +import { Grid } from '@material-ui/core'; +import { FossaApi, fossaApiRef } from '../src/api'; +import { FossaCard } from '../src'; +import { Entity } from '@backstage/catalog-model'; +import { FOSSA_PROJECT_NAME_ANNOTATION } from '../src/components/useProjectName'; + +createDevApp() + .registerApi({ + api: fossaApiRef, + deps: {}, + factory: () => + ({ + getFindingSummary: async projectTitle => { + switch (projectTitle) { + case 'error': + throw new Error('Error!'); + + case 'never': + return new Promise(() => {}); + + case 'zero-deps': + return { + timestamp: new Date().toISOString(), + issueCount: 0, + dependencyCount: 0, + projectDefaultBranch: 'master', + projectUrl: `/#${projectTitle}`, + }; + + case 'issues': + return { + timestamp: new Date().toISOString(), + issueCount: 5, + dependencyCount: 100, + projectDefaultBranch: 'develop', + projectUrl: `/#${projectTitle}`, + }; + + case 'no-issues': + return { + timestamp: new Date().toISOString(), + issueCount: 0, + dependencyCount: 150, + projectDefaultBranch: 'feat/fossa', + projectUrl: `/#${projectTitle}`, + }; + + default: + return undefined; + } + }, + } as FossaApi), + }) + .registerPlugin( + createPlugin({ + id: 'fossa-demo', + register({ router }) { + const entity = (name?: string) => + ({ + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + annotations: { + [FOSSA_PROJECT_NAME_ANNOTATION]: name, + }, + name: name, + }, + } as Entity); + + const ExamplePage = () => ( + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + ); + + router.addRoute( + createRouteRef({ path: '/', title: 'Fossa' }), + ExamplePage, + ); + }, + }), + ) + .render(); diff --git a/plugins/fossa/docs/fossa-card.png b/plugins/fossa/docs/fossa-card.png new file mode 100644 index 0000000000000000000000000000000000000000..a3b7f59294e54d46eb3e9e0c3225869568bd3e28 GIT binary patch literal 61617 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 mL~hPLhW;;eX*>Y{ literal 0 HcmV?d00001 diff --git a/plugins/fossa/package.json b/plugins/fossa/package.json new file mode 100644 index 0000000000..035df53b93 --- /dev/null +++ b/plugins/fossa/package.json @@ -0,0 +1,62 @@ +{ + "name": "@backstage/plugin-fossa", + "version": "0.1.0", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "private": false, + "publishConfig": { + "access": "public", + "main": "dist/index.esm.js", + "types": "dist/index.d.ts" + }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/fossa" + }, + "keywords": [ + "backstage", + "fossa" + ], + "scripts": { + "build": "backstage-cli plugin:build", + "start": "backstage-cli plugin:serve", + "lint": "backstage-cli lint", + "test": "backstage-cli test", + "diff": "backstage-cli plugin:diff", + "prepack": "backstage-cli prepack", + "postpack": "backstage-cli postpack", + "clean": "backstage-cli clean" + }, + "dependencies": { + "@backstage/catalog-model": "^0.5.0", + "@backstage/core": "^0.4.0", + "@backstage/theme": "^0.2.2", + "@material-ui/core": "^4.11.0", + "@material-ui/icons": "^4.9.1", + "@material-ui/lab": "4.0.0-alpha.45", + "cross-fetch": "^3.0.6", + "react": "^16.13.1", + "react-dom": "^16.13.1", + "react-use": "^15.3.3" + }, + "devDependencies": { + "@backstage/cli": "^0.4.1", + "@backstage/dev-utils": "^0.1.6", + "@backstage/test-utils": "^0.1.5", + "@testing-library/jest-dom": "^5.10.1", + "@testing-library/react": "^10.4.1", + "@testing-library/user-event": "^12.0.7", + "@types/jest": "^26.0.7", + "@types/node": "^12.0.0", + "cross-fetch": "^3.0.6", + "msw": "^0.21.2" + }, + "files": [ + "dist", + "config.d.ts" + ], + "configSchema": "config.d.ts" +} diff --git a/plugins/fossa/src/api/FossaApi.ts b/plugins/fossa/src/api/FossaApi.ts new file mode 100644 index 0000000000..c44d66661c --- /dev/null +++ b/plugins/fossa/src/api/FossaApi.ts @@ -0,0 +1,34 @@ +/* + * 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 { createApiRef } from '@backstage/core'; + +export interface FindingSummary { + timestamp: string; + issueCount: number; + dependencyCount: number; + projectDefaultBranch: string; + projectUrl: string; +} + +export const fossaApiRef = createApiRef({ + id: 'plugin.fossa.service', + description: 'Used by the Fossa plugin to make requests', +}); + +export type FossaApi = { + getFindingSummary(projectTitle?: string): Promise; +}; diff --git a/plugins/fossa/src/api/FossaClient.test.ts b/plugins/fossa/src/api/FossaClient.test.ts new file mode 100644 index 0000000000..8028ade57a --- /dev/null +++ b/plugins/fossa/src/api/FossaClient.test.ts @@ -0,0 +1,138 @@ +/* + * 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 { UrlPatternDiscovery } from '@backstage/core'; +import { msw } from '@backstage/test-utils'; +import { rest } from 'msw'; +import { setupServer } from 'msw/node'; +import { FindingSummary, FossaApi, FossaClient } from './index'; + +const server = setupServer(); + +describe('FossaClient', () => { + msw.setupDefaultHandlers(server); + + const mockBaseUrl = 'http://backstage:9191/api/proxy'; + const discoveryApi = UrlPatternDiscovery.compile(mockBaseUrl); + let client: FossaApi; + + beforeEach(() => { + client = new FossaClient({ discoveryApi, organizationId: '8736' }); + }); + + it('should report finding summary', async () => { + server.use( + rest.get(`${mockBaseUrl}/fossa/projects`, (req, res, ctx) => { + expect(req.url.searchParams.toString()).toBe( + 'count=1&title=our-service&organizationId=8736', + ); + return res( + ctx.json([ + { + locator: 'custom+8736/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.getFindingSummary('our-service'); + + expect(summary).toEqual({ + timestamp: '2020-01-01T00:00:00Z', + issueCount: 5, + dependencyCount: 160, + projectDefaultBranch: 'develop', + projectUrl: 'https://app.fossa.com/projects/custom%2B8736%2Four-service', + } as FindingSummary); + }); + + it('should report finding summary without licenseing_issue_count', async () => { + server.use( + rest.get(`${mockBaseUrl}/fossa/projects`, (req, res, ctx) => { + expect(req.url.searchParams.toString()).toBe( + 'count=1&title=our-service&organizationId=8736', + ); + return res( + ctx.json([ + { + locator: 'custom+8736/our-service', + default_branch: 'refs/master', + revisions: [ + { + updatedAt: '2020-01-01T00:00:00Z', + dependency_count: 160, + unresolved_issue_count: 100, + }, + ], + }, + ]), + ); + }), + ); + + const summary = await client.getFindingSummary('our-service'); + + expect(summary).toEqual({ + timestamp: '2020-01-01T00:00:00Z', + issueCount: 100, + dependencyCount: 160, + projectDefaultBranch: 'refs/master', + projectUrl: 'https://app.fossa.com/projects/custom%2B8736%2Four-service', + } as FindingSummary); + }); + + it('should skip organizationId', async () => { + client = new FossaClient({ discoveryApi }); + + server.use( + rest.get(`${mockBaseUrl}/fossa/projects`, (req, res, ctx) => { + expect(req.url.searchParams.toString()).toBe( + 'count=1&title=our-service', + ); + return res(ctx.status(404)); + }), + ); + + const summary = await client.getFindingSummary('our-service'); + + expect(summary).toBeUndefined(); + }); + + it('should handle 404 status', async () => { + server.use( + rest.get(`${mockBaseUrl}/fossa/projects`, (req, res, ctx) => { + expect(req.url.searchParams.toString()).toBe( + 'count=1&title=our-service&organizationId=8736', + ); + return res(ctx.status(404)); + }), + ); + + const summary = await client.getFindingSummary('our-service'); + + expect(summary).toBeUndefined(); + }); +}); diff --git a/plugins/fossa/src/api/FossaClient.ts b/plugins/fossa/src/api/FossaClient.ts new file mode 100644 index 0000000000..7bea10194b --- /dev/null +++ b/plugins/fossa/src/api/FossaClient.ts @@ -0,0 +1,74 @@ +/* + * 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 { DiscoveryApi } from '@backstage/core'; +import fetch from 'cross-fetch'; +import { FindingSummary, FossaApi } from './FossaApi'; + +export class FossaClient implements FossaApi { + discoveryApi: DiscoveryApi; + organizationId?: string; + + constructor({ + discoveryApi, + organizationId, + }: { + discoveryApi: DiscoveryApi; + organizationId?: string; + }) { + this.discoveryApi = discoveryApi; + this.organizationId = organizationId; + } + + private async callApi(path: string): Promise { + const apiUrl = `${await this.discoveryApi.getBaseUrl('proxy')}/fossa`; + const response = await fetch(`${apiUrl}/${path}`); + if (response.status === 200) { + return await response.json(); + } + return undefined; + } + + async getFindingSummary( + projectTitle?: string, + ): Promise { + if (!projectTitle) { + return undefined; + } + + const project = await this.callApi( + `projects?count=1&title=${projectTitle}${ + this.organizationId ? `&organizationId=${this.organizationId}` : '' + }`, + ); + if (!project) { + return undefined; + } + + const revision = project[0].revisions[0]; + return { + timestamp: revision.updatedAt, + issueCount: + revision.unresolved_licensing_issue_count || + revision.unresolved_issue_count, + dependencyCount: revision.dependency_count, + projectDefaultBranch: project[0].default_branch, + projectUrl: `https://app.fossa.com/projects/${encodeURIComponent( + project[0].locator, + )}`, + }; + } +} diff --git a/plugins/fossa/src/api/index.ts b/plugins/fossa/src/api/index.ts new file mode 100644 index 0000000000..f31c4bd1d5 --- /dev/null +++ b/plugins/fossa/src/api/index.ts @@ -0,0 +1,19 @@ +/* + * 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. + */ + +export type { FossaApi, FindingSummary } from './FossaApi'; +export { fossaApiRef } from './FossaApi'; +export { FossaClient } from './FossaClient'; diff --git a/plugins/fossa/src/components/FossaCard/FossaCard.tsx b/plugins/fossa/src/components/FossaCard/FossaCard.tsx new file mode 100644 index 0000000000..b730be9343 --- /dev/null +++ b/plugins/fossa/src/components/FossaCard/FossaCard.tsx @@ -0,0 +1,177 @@ +/* + * 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 React from 'react'; +import { + EmptyState, + InfoCard, + MissingAnnotationEmptyState, + Progress, + useApi, +} from '@backstage/core'; +import { useAsync } from 'react-use'; +import { Entity } from '@backstage/catalog-model'; +import { fossaApiRef } from '../../api'; +import { makeStyles } from '@material-ui/core/styles'; +import { + FOSSA_PROJECT_NAME_ANNOTATION, + useProjectName, +} from '../useProjectName'; +import { Grid, Tooltip } from '@material-ui/core'; + +const useStyles = makeStyles(theme => ({ + numberError: { + fontSize: '5rem', + textAlign: 'center', + fontWeight: theme.typography.fontWeightMedium, + margin: theme.spacing(2, 0), + color: theme.palette.error.main, + }, + numberSuccess: { + fontSize: '5rem', + textAlign: 'center', + fontWeight: theme.typography.fontWeightMedium, + margin: theme.spacing(2, 0), + color: theme.palette.success.main, + }, + description: { + fontSize: '1rem', + textAlign: 'center', + fontWeight: theme.typography.fontWeightMedium, + color: theme.palette.text.secondary, + }, + disabled: { + backgroundColor: theme.palette.background.default, + }, + lastAnalyzed: { + color: theme.palette.text.secondary, + textAlign: 'center', + }, + branch: { + textDecoration: 'underline dotted', + }, +})); + +export const FossaCard = ({ + entity, + variant = 'gridItem', +}: { + entity: Entity; + variant?: string; +}) => { + const fossaApi = useApi(fossaApiRef); + + const projectTitle = useProjectName(entity); + + const { value, loading } = useAsync( + async () => fossaApi.getFindingSummary(projectTitle), + [fossaApi, projectTitle], + ); + + const deepLink = value + ? { + title: 'View more', + link: value.projectUrl, + } + : undefined; + + const classes = useStyles(); + + return ( + <> + + {loading && } + + {!loading && !projectTitle && ( + + )} + + {!loading && projectTitle && !value && ( + + )} + + {value && ( + + +

0 || value.dependencyCount === 0 + ? classes.numberError + : classes.numberSuccess + } + > + {value.issueCount} +

+ {value.dependencyCount > 0 && ( +

Number of issues

+ )} + {value.dependencyCount === 0 && ( +

+ No Dependencies. +
+ Please check your FOSSA project settings. +

+ )} +
+ + + Last analyzed on{' '} + {new Date(value.timestamp).toLocaleString('en-US', { + timeZone: 'UTC', + day: 'numeric', + month: 'short', + year: 'numeric', + hour: '2-digit', + minute: '2-digit', + hour12: false, + })} + + + Based on {value.dependencyCount} Dependencies on branch{' '} + + + {value.projectDefaultBranch} + + + . + +
+ )} +
+ + ); +}; diff --git a/plugins/fossa/src/components/FossaCard/index.ts b/plugins/fossa/src/components/FossaCard/index.ts new file mode 100644 index 0000000000..5f84660bf0 --- /dev/null +++ b/plugins/fossa/src/components/FossaCard/index.ts @@ -0,0 +1,17 @@ +/* + * 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. + */ + +export { FossaCard } from './FossaCard'; diff --git a/plugins/fossa/src/components/index.ts b/plugins/fossa/src/components/index.ts new file mode 100644 index 0000000000..a49c71603b --- /dev/null +++ b/plugins/fossa/src/components/index.ts @@ -0,0 +1,17 @@ +/* + * 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. + */ + +export * from './FossaCard'; diff --git a/plugins/fossa/src/components/useProjectName.ts b/plugins/fossa/src/components/useProjectName.ts new file mode 100644 index 0000000000..fde07ca5c6 --- /dev/null +++ b/plugins/fossa/src/components/useProjectName.ts @@ -0,0 +1,23 @@ +/* + * 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'; + +export const FOSSA_PROJECT_NAME_ANNOTATION = 'fossa.io/project-name'; + +export const useProjectName = (entity: Entity) => { + return entity?.metadata.annotations?.[FOSSA_PROJECT_NAME_ANNOTATION] ?? ''; +}; diff --git a/plugins/fossa/src/index.ts b/plugins/fossa/src/index.ts new file mode 100644 index 0000000000..c35c534122 --- /dev/null +++ b/plugins/fossa/src/index.ts @@ -0,0 +1,18 @@ +/* + * 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. + */ + +export { plugin } from './plugin'; +export * from './components'; diff --git a/plugins/fossa/src/plugin.test.ts b/plugins/fossa/src/plugin.test.ts new file mode 100644 index 0000000000..4f9b00a02b --- /dev/null +++ b/plugins/fossa/src/plugin.test.ts @@ -0,0 +1,23 @@ +/* + * 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 { plugin } from './plugin'; + +describe('fossa', () => { + it('should export plugin', () => { + expect(plugin).toBeDefined(); + }); +}); diff --git a/plugins/fossa/src/plugin.ts b/plugins/fossa/src/plugin.ts new file mode 100644 index 0000000000..d1fe621018 --- /dev/null +++ b/plugins/fossa/src/plugin.ts @@ -0,0 +1,38 @@ +/* + * 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 { + configApiRef, + createApiFactory, + createPlugin, + discoveryApiRef, +} from '@backstage/core'; +import { fossaApiRef, FossaClient } from './api'; + +export const plugin = createPlugin({ + id: 'fossa', + apis: [ + createApiFactory({ + api: fossaApiRef, + deps: { configApi: configApiRef, discoveryApi: discoveryApiRef }, + factory: ({ configApi, discoveryApi }) => + new FossaClient({ + discoveryApi, + organizationId: configApi.getOptionalString('fossa.organizationId'), + }), + }), + ], +}); diff --git a/plugins/fossa/src/setupTests.ts b/plugins/fossa/src/setupTests.ts new file mode 100644 index 0000000000..825bcd4115 --- /dev/null +++ b/plugins/fossa/src/setupTests.ts @@ -0,0 +1,17 @@ +/* + * 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 '@testing-library/jest-dom';