diff --git a/app-config.yaml b/app-config.yaml index bc25b7d71e..bec048ce33 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -16,7 +16,7 @@ backend: credentials: true csp: connect-src: ["'self'", 'http:', 'https:'] - # workingDirectory: /tmp # Use this to configure a working direcotry for the scaffolder, defaults to the OS temp-dir + # workingDirectory: /tmp # Use this to configure a working directory for the scaffolder, defaults to the OS temp-dir # See README.md in the proxy-backend plugin for information on the configuration format proxy: diff --git a/packages/catalog-client/.eslintrc.js b/packages/catalog-client/.eslintrc.js new file mode 100644 index 0000000000..13573efa9c --- /dev/null +++ b/packages/catalog-client/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: [require.resolve('@backstage/cli/config/eslint')], +}; diff --git a/packages/catalog-client/README.md b/packages/catalog-client/README.md new file mode 100644 index 0000000000..574e23cf62 --- /dev/null +++ b/packages/catalog-client/README.md @@ -0,0 +1,17 @@ +# Catalog Client + +Contains a frontend and backend compatible client for communicating with the +Backstage Catalog. + +Backend code may import and use this package directly. + +However, frontend code will not want to import this package directly - use the +`@backstage/plugin-catalog` package instead, which re-exports all of the types +and classes from this package. Thereby, you will also gain access to its +`catalogApiRef`. + +## Links + +- [Default frontend part of the catalog](https://github.com/spotify/backstage/tree/master/plugins/catalog) +- [Default backend part of the catalog](https://github.com/spotify/backstage/tree/master/plugins/catalog-backend) +- [The Backstage homepage](https://backstage.io) diff --git a/packages/catalog-client/package.json b/packages/catalog-client/package.json new file mode 100644 index 0000000000..c4aa632ca9 --- /dev/null +++ b/packages/catalog-client/package.json @@ -0,0 +1,35 @@ +{ + "name": "@backstage/catalog-client", + "version": "0.2.0", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "private": false, + "publishConfig": { + "access": "public", + "main": "dist/index.cjs.js", + "module": "dist/index.esm.js", + "types": "dist/index.d.ts" + }, + "scripts": { + "build": "backstage-cli build", + "lint": "backstage-cli lint", + "test": "backstage-cli test", + "prepack": "backstage-cli prepack", + "postpack": "backstage-cli postpack", + "clean": "backstage-cli clean" + }, + "dependencies": { + "@backstage/catalog-model": "^0.2.0", + "@backstage/config": "^0.1.1", + "cross-fetch": "^3.0.6" + }, + "devDependencies": { + "@backstage/cli": "^0.2.0", + "@types/jest": "^26.0.7", + "msw": "^0.21.2" + }, + "files": [ + "dist" + ] +} diff --git a/plugins/catalog/src/api/CatalogClient.test.ts b/packages/catalog-client/src/CatalogClient.test.ts similarity index 87% rename from plugins/catalog/src/api/CatalogClient.test.ts rename to packages/catalog-client/src/CatalogClient.test.ts index 927e8dd52c..65625c11b8 100644 --- a/plugins/catalog/src/api/CatalogClient.test.ts +++ b/packages/catalog-client/src/CatalogClient.test.ts @@ -18,17 +18,22 @@ import { rest } from 'msw'; import { setupServer } from 'msw/node'; import { CatalogClient } from './CatalogClient'; import { Entity } from '@backstage/catalog-model'; -import { UrlPatternDiscovery } from '@backstage/core'; -import { msw } from '@backstage/test-utils'; +import { DiscoveryApi } from './types'; const server = setupServer(); const mockBaseUrl = 'http://backstage:9191/i-am-a-mock-base'; -const discoveryApi = UrlPatternDiscovery.compile(mockBaseUrl); +const discoveryApi: DiscoveryApi = { + async getBaseUrl(_pluginId) { + return mockBaseUrl; + }, +}; describe('CatalogClient', () => { - let client = new CatalogClient({ discoveryApi }); + let client: CatalogClient; - msw.setupDefaultHandlers(server); + beforeAll(() => server.listen({ onUnhandledRequest: 'error' })); + afterAll(() => server.close()); + afterEach(() => server.resetHandlers()); beforeEach(() => { client = new CatalogClient({ discoveryApi }); diff --git a/plugins/catalog/src/api/CatalogClient.ts b/packages/catalog-client/src/CatalogClient.ts similarity index 96% rename from plugins/catalog/src/api/CatalogClient.ts rename to packages/catalog-client/src/CatalogClient.ts index 85fd44cf01..e39759a88d 100644 --- a/plugins/catalog/src/api/CatalogClient.ts +++ b/packages/catalog-client/src/CatalogClient.ts @@ -16,16 +16,17 @@ import { Entity, + EntityName, Location, LOCATION_ANNOTATION, } from '@backstage/catalog-model'; +import fetch from 'cross-fetch'; import { AddLocationRequest, AddLocationResponse, CatalogApi, - EntityCompoundName, + DiscoveryApi, } from './types'; -import { DiscoveryApi } from '@backstage/core'; export class CatalogClient implements CatalogApi { private readonly discoveryApi: DiscoveryApi; @@ -85,9 +86,7 @@ export class CatalogClient implements CatalogApi { return await this.getRequired(path); } - async getEntityByName( - compoundName: EntityCompoundName, - ): Promise { + async getEntityByName(compoundName: EntityName): Promise { const { kind, namespace = 'default', name } = compoundName; return this.getOptional(`/entities/by-name/${kind}/${namespace}/${name}`); } diff --git a/packages/catalog-client/src/index.ts b/packages/catalog-client/src/index.ts new file mode 100644 index 0000000000..9bd13f9b7c --- /dev/null +++ b/packages/catalog-client/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 { CatalogClient } from './CatalogClient'; +export type { CatalogApi } from './types'; diff --git a/packages/catalog-client/src/setupTests.ts b/packages/catalog-client/src/setupTests.ts new file mode 100644 index 0000000000..ba33cf996b --- /dev/null +++ b/packages/catalog-client/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. + */ + +export {}; diff --git a/plugins/catalog/src/api/types.ts b/packages/catalog-client/src/types.ts similarity index 70% rename from plugins/catalog/src/api/types.ts rename to packages/catalog-client/src/types.ts index 06030e6ac5..575b8d6ca2 100644 --- a/plugins/catalog/src/api/types.ts +++ b/packages/catalog-client/src/types.ts @@ -14,26 +14,11 @@ * limitations under the License. */ -import { createApiRef } from '@backstage/core'; -import { Entity, Location } from '@backstage/catalog-model'; - -export const catalogApiRef = createApiRef({ - id: 'plugin.catalog.service', - description: - 'Used by the Catalog plugin to make requests to accompanying backend', -}); - -export type EntityCompoundName = { - kind: string; - namespace?: string; - name: string; -}; +import { Entity, EntityName, Location } from '@backstage/catalog-model'; export interface CatalogApi { getLocationById(id: String): Promise; - getEntityByName( - compoundName: EntityCompoundName, - ): Promise; + getEntityByName(name: EntityName): Promise; getEntities(filter?: Record): Promise; addLocation(location: AddLocationRequest): Promise; getLocationByEntity(entity: Entity): Promise; @@ -50,3 +35,10 @@ export type AddLocationResponse = { location: Location; entities: Entity[]; }; + +/** + * This is a copy of the core DiscoveryApi, to avoid importing core. + */ +export type DiscoveryApi = { + getBaseUrl(pluginId: string): Promise; +}; diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index 66f7a681c5..6d52c4722f 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -21,6 +21,7 @@ "clean": "backstage-cli clean" }, "dependencies": { + "@backstage/catalog-client": "^0.2.0", "@backstage/catalog-model": "^0.2.0", "@backstage/core": "^0.2.0", "@backstage/plugin-scaffolder": "^0.2.0", diff --git a/plugins/catalog/src/components/CatalogFilter/AllServicesCount.tsx b/plugins/catalog/src/components/CatalogFilter/AllServicesCount.tsx index b4d90c249a..e78cedbe2d 100644 --- a/plugins/catalog/src/components/CatalogFilter/AllServicesCount.tsx +++ b/plugins/catalog/src/components/CatalogFilter/AllServicesCount.tsx @@ -14,13 +14,13 @@ * limitations under the License. */ -import React, { FC } from 'react'; import { useApi } from '@backstage/core'; -import { catalogApiRef } from '../../api/types'; -import { useAsync } from 'react-use'; import { CircularProgress, useTheme } from '@material-ui/core'; +import React from 'react'; +import { useAsync } from 'react-use'; +import { catalogApiRef } from '../../plugin'; -export const AllServicesCount: FC<{}> = () => { +export const AllServicesCount = () => { const theme = useTheme(); const catalogApi = useApi(catalogApiRef); const { value, loading } = useAsync(() => catalogApi.getEntities()); diff --git a/plugins/catalog/src/components/CatalogFilter/CatalogFilter.test.tsx b/plugins/catalog/src/components/CatalogFilter/CatalogFilter.test.tsx index bc23d61d9c..a4ede7c2f2 100644 --- a/plugins/catalog/src/components/CatalogFilter/CatalogFilter.test.tsx +++ b/plugins/catalog/src/components/CatalogFilter/CatalogFilter.test.tsx @@ -14,6 +14,7 @@ * limitations under the License. */ +import { CatalogApi } from '@backstage/catalog-client'; import { Entity } from '@backstage/catalog-model'; import { ApiProvider, @@ -25,8 +26,8 @@ import { import { MockStorageApi, wrapInTestApp } from '@backstage/test-utils'; import { fireEvent, render, waitFor } from '@testing-library/react'; import React from 'react'; -import { CatalogApi, catalogApiRef } from '../../api/types'; import { EntityFilterGroupsProvider } from '../../filter'; +import { catalogApiRef } from '../../plugin'; import { ButtonGroup, CatalogFilter } from './CatalogFilter'; describe('Catalog Filter', () => { diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx index 5752e58215..0d645f0f35 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx @@ -14,21 +14,21 @@ * limitations under the License. */ +import { CatalogApi } from '@backstage/catalog-client'; import { Entity } from '@backstage/catalog-model'; import { ApiProvider, ApiRegistry, IdentityApi, identityApiRef, - storageApiRef, ProfileInfo, + storageApiRef, } from '@backstage/core'; import { MockStorageApi, wrapInTestApp } from '@backstage/test-utils'; import { fireEvent, render } from '@testing-library/react'; import React from 'react'; -import { catalogApiRef } from '../..'; -import { CatalogApi } from '../../api/types'; import { EntityFilterGroupsProvider } from '../../filter'; +import { catalogApiRef } from '../../plugin'; import { CatalogPage } from './CatalogPage'; describe('CatalogPage', () => { diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx index 1a9a723f16..e73a789380 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx @@ -29,9 +29,9 @@ import SettingsIcon from '@material-ui/icons/Settings'; import StarIcon from '@material-ui/icons/Star'; import React, { useCallback, useMemo, useState } from 'react'; import { Link as RouterLink } from 'react-router-dom'; -import { catalogApiRef } from '../../api/types'; import { EntityFilterGroupsProvider, useFilteredEntities } from '../../filter'; import { useStarredEntities } from '../../hooks/useStarredEntities'; +import { catalogApiRef } from '../../plugin'; import { ButtonGroup, CatalogFilter } from '../CatalogFilter/CatalogFilter'; import { CatalogTable } from '../CatalogTable/CatalogTable'; import { ResultsFilter } from '../ResultsFilter/ResultsFilter'; diff --git a/plugins/catalog/src/components/ResultsFilter/ResultsFilter.test.tsx b/plugins/catalog/src/components/ResultsFilter/ResultsFilter.test.tsx index 6f294c54ff..39837d2ca1 100644 --- a/plugins/catalog/src/components/ResultsFilter/ResultsFilter.test.tsx +++ b/plugins/catalog/src/components/ResultsFilter/ResultsFilter.test.tsx @@ -14,6 +14,7 @@ * limitations under the License. */ +import { CatalogApi } from '@backstage/catalog-client'; import { Entity } from '@backstage/catalog-model'; import { ApiProvider, @@ -25,8 +26,8 @@ import { import { MockStorageApi, wrapInTestApp } from '@backstage/test-utils'; import { render } from '@testing-library/react'; import React from 'react'; -import { CatalogApi, catalogApiRef } from '../../api/types'; import { EntityFilterGroupsProvider } from '../../filter'; +import { catalogApiRef } from '../../plugin'; import { ResultsFilter } from './ResultsFilter'; describe('Results Filter', () => { diff --git a/plugins/catalog/src/components/UnregisterEntityDialog/UnregisterEntityDialog.tsx b/plugins/catalog/src/components/UnregisterEntityDialog/UnregisterEntityDialog.tsx index d860ba1ea4..218cc45e78 100644 --- a/plugins/catalog/src/components/UnregisterEntityDialog/UnregisterEntityDialog.tsx +++ b/plugins/catalog/src/components/UnregisterEntityDialog/UnregisterEntityDialog.tsx @@ -15,7 +15,7 @@ */ import { Entity, LOCATION_ANNOTATION } from '@backstage/catalog-model'; -import { Progress, useApi, alertApiRef } from '@backstage/core'; +import { alertApiRef, Progress, useApi } from '@backstage/core'; import { Button, Dialog, @@ -31,7 +31,7 @@ import Alert from '@material-ui/lab/Alert'; import React, { FC } from 'react'; import { useAsync } from 'react-use'; import { AsyncState } from 'react-use/lib/useAsync'; -import { catalogApiRef } from '../../api/types'; +import { catalogApiRef } from '../../plugin'; type Props = { open: boolean; diff --git a/plugins/catalog/src/filter/EntityFilterGroupsProvider.tsx b/plugins/catalog/src/filter/EntityFilterGroupsProvider.tsx index ceecc17953..36cc0c56df 100644 --- a/plugins/catalog/src/filter/EntityFilterGroupsProvider.tsx +++ b/plugins/catalog/src/filter/EntityFilterGroupsProvider.tsx @@ -18,7 +18,7 @@ import { Entity } from '@backstage/catalog-model'; import { useApi } from '@backstage/core'; import React, { useCallback, useEffect, useRef, useState } from 'react'; import { useAsyncFn } from 'react-use'; -import { catalogApiRef } from '../api/types'; +import { catalogApiRef } from '../plugin'; import { filterGroupsContext, FilterGroupsContext } from './context'; import { EntityFilterFn, diff --git a/plugins/catalog/src/filter/useEntityFilterGroup.test.tsx b/plugins/catalog/src/filter/useEntityFilterGroup.test.tsx index 3d11ffe7a9..6b573e3721 100644 --- a/plugins/catalog/src/filter/useEntityFilterGroup.test.tsx +++ b/plugins/catalog/src/filter/useEntityFilterGroup.test.tsx @@ -15,13 +15,13 @@ */ import { ApiProvider, ApiRegistry, storageApiRef } from '@backstage/core'; +import { MockStorageApi } from '@backstage/test-utils'; import { act, renderHook } from '@testing-library/react-hooks'; import React from 'react'; -import { catalogApiRef } from '../api/types'; +import { catalogApiRef } from '../plugin'; import { EntityFilterGroupsProvider } from './EntityFilterGroupsProvider'; -import { FilterGroupStatesReady, FilterGroup } from './types'; +import { FilterGroup, FilterGroupStatesReady } from './types'; import { useEntityFilterGroup } from './useEntityFilterGroup'; -import { MockStorageApi } from '@backstage/test-utils'; describe('useEntityFilterGroup', () => { let catalogApi: jest.Mocked; diff --git a/plugins/catalog/src/hooks/useEntity.ts b/plugins/catalog/src/hooks/useEntity.ts index 1c5b94b8c1..29ca63904d 100644 --- a/plugins/catalog/src/hooks/useEntity.ts +++ b/plugins/catalog/src/hooks/useEntity.ts @@ -18,8 +18,8 @@ import { errorApiRef, useApi } from '@backstage/core'; import { createContext, useContext, useEffect } from 'react'; import { useNavigate } from 'react-router'; import { useAsync } from 'react-use'; -import { catalogApiRef } from '../api/types'; import { useEntityCompoundName } from '../components/useEntityCompoundName'; +import { catalogApiRef } from '../plugin'; type EntityLoadingStatus = { entity?: Entity; diff --git a/plugins/catalog/src/index.ts b/plugins/catalog/src/index.ts index 762f4924c0..606ad9028d 100644 --- a/plugins/catalog/src/index.ts +++ b/plugins/catalog/src/index.ts @@ -14,12 +14,11 @@ * limitations under the License. */ -export { plugin } from './plugin'; -export * from './api/CatalogClient'; -export * from './api/types'; -export * from './routes'; -export { useEntityCompoundName } from './components/useEntityCompoundName'; -export { Router } from './components/Router'; -export { useEntity, EntityContext } from './hooks/useEntity'; +export * from '@backstage/catalog-client'; export { AboutCard } from './components/AboutCard'; export { EntityPageLayout } from './components/EntityPageLayout'; +export { Router } from './components/Router'; +export { useEntityCompoundName } from './components/useEntityCompoundName'; +export { EntityContext, useEntity } from './hooks/useEntity'; +export { catalogApiRef, plugin } from './plugin'; +export * from './routes'; diff --git a/plugins/catalog/src/plugin.ts b/plugins/catalog/src/plugin.ts index dfe12fd500..5f737ec12d 100644 --- a/plugins/catalog/src/plugin.ts +++ b/plugins/catalog/src/plugin.ts @@ -14,13 +14,19 @@ * limitations under the License. */ +import { CatalogApi, CatalogClient } from '@backstage/catalog-client'; import { - createPlugin, createApiFactory, + createApiRef, + createPlugin, discoveryApiRef, } from '@backstage/core'; -import { catalogApiRef } from './api/types'; -import { CatalogClient } from './api/CatalogClient'; + +export const catalogApiRef = createApiRef({ + id: 'plugin.catalog.service', + description: + 'Used by the Catalog plugin to make requests to accompanying backend', +}); export const plugin = createPlugin({ id: 'catalog', diff --git a/plugins/techdocs/src/reader/components/TechDocsHome.test.tsx b/plugins/techdocs/src/reader/components/TechDocsHome.test.tsx index 7754f227fc..d5d0e19e08 100644 --- a/plugins/techdocs/src/reader/components/TechDocsHome.test.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsHome.test.tsx @@ -13,14 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { TechDocsHome } from './TechDocsHome'; -import React from 'react'; -import { render } from '@testing-library/react'; -import { wrapInTestApp } from '@backstage/test-utils'; -import { ApiRegistry, ApiProvider } from '@backstage/core-api'; -import { catalogApiRef, CatalogApi } from '@backstage/plugin-catalog'; import { Entity } from '@backstage/catalog-model'; +import { ApiProvider, ApiRegistry } from '@backstage/core-api'; +import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog'; +import { wrapInTestApp } from '@backstage/test-utils'; +import { render } from '@testing-library/react'; +import React from 'react'; +import { TechDocsHome } from './TechDocsHome'; describe('TechDocs Home', () => { const catalogApi: Partial = {