From e44925723e264ae8e90c4bff15c46cdac1d95952 Mon Sep 17 00:00:00 2001 From: Parth Shandilya Date: Tue, 19 Jan 2021 23:46:48 +0100 Subject: [PATCH 01/16] TechDocs: Make requestUrl and storageUrl optional configs by using discovery APIs closes #3715 Co-authored-by: Himanshu Mishra --- .changeset/gentle-scissors-compare.md | 7 ++ docs/features/techdocs/configuration.md | 4 +- .../src/stages/publish/local.ts | 17 +++-- plugins/techdocs-backend/config.d.ts | 2 +- .../techdocs-backend/src/service/router.ts | 10 ++- plugins/techdocs/config.d.ts | 4 +- plugins/techdocs/dev/api.ts | 35 ++++++++-- plugins/techdocs/dev/index.tsx | 8 ++- plugins/techdocs/package.json | 1 + plugins/techdocs/src/api.test.ts | 15 ++-- plugins/techdocs/src/api.ts | 69 +++++++++++++++---- plugins/techdocs/src/plugin.ts | 16 +++-- .../techdocs/src/reader/components/Reader.tsx | 2 +- .../reader/components/TechDocsPage.test.tsx | 2 +- .../reader/transformers/addBaseUrl.test.ts | 2 +- .../src/reader/transformers/addBaseUrl.ts | 4 +- .../reader/transformers/onCssReady.test.ts | 5 +- .../src/reader/transformers/onCssReady.ts | 6 +- 18 files changed, 154 insertions(+), 55 deletions(-) create mode 100644 .changeset/gentle-scissors-compare.md diff --git a/.changeset/gentle-scissors-compare.md b/.changeset/gentle-scissors-compare.md new file mode 100644 index 0000000000..bb615987b2 --- /dev/null +++ b/.changeset/gentle-scissors-compare.md @@ -0,0 +1,7 @@ +--- +'@backstage/techdocs-common': patch +'@backstage/plugin-techdocs': patch +'@backstage/plugin-techdocs-backend': patch +--- + +`techdocs.requestUrl` and `techdocs.storageUrl` are now optional configs and the discovery API will be used to get the URL where techdocs plugin is hosted. diff --git a/docs/features/techdocs/configuration.md b/docs/features/techdocs/configuration.md index bb44050dcf..b9413227af 100644 --- a/docs/features/techdocs/configuration.md +++ b/docs/features/techdocs/configuration.md @@ -13,12 +13,12 @@ configuration options for TechDocs. # File: app-config.yaml techdocs: - # TechDocs makes API calls to techdocs-backend using this URL. e.g. get docs of an entity, get metadata, etc. + # TechDocs makes API calls to techdocs-backend using this URL. e.g. get docs of an entity, get metadata, etc. (Optional) requestUrl: http://localhost:7000/api/techdocs # Just another route in techdocs-backend where TechDocs requests the static files from. This URL uses an HTTP middleware - # to serve files from either a local directory or an External storage provider. + # to serve files from either a local directory or an External storage provider. (Optional) storageUrl: http://localhost:7000/api/techdocs/static/docs diff --git a/packages/techdocs-common/src/stages/publish/local.ts b/packages/techdocs-common/src/stages/publish/local.ts index d07472e17d..b8ccf7df8a 100644 --- a/packages/techdocs-common/src/stages/publish/local.ts +++ b/packages/techdocs-common/src/stages/publish/local.ts @@ -23,6 +23,7 @@ import { Entity, EntityName } from '@backstage/catalog-model'; import { resolvePackagePath, PluginEndpointDiscovery, + SingleHostDiscovery, } from '@backstage/backend-common'; import { Config } from '@backstage/config'; import { @@ -109,9 +110,9 @@ export class LocalPublish implements PublisherBase { fetchTechDocsMetadata(entityName: EntityName): Promise { return new Promise((resolve, reject) => { - this.discovery.getBaseUrl('techdocs').then(techdocsApiUrl => { + this.discovery.getBaseUrl('techdocs').then(async techdocsApiUrl => { const storageUrl = new URL( - new URL(this.config.getString('techdocs.storageUrl')).pathname, + new URL(await this.getStorageUrl()).pathname, techdocsApiUrl, ).toString(); @@ -141,12 +142,20 @@ export class LocalPublish implements PublisherBase { return express.static(staticDocsDir); } + async getStorageUrl() { + const discoveryApi = SingleHostDiscovery.fromConfig(this.config); + return ( + this.config.getOptionalString('techdocs.storageUrl') ?? + (await discoveryApi.getBaseUrl('techdocs')) + ); + } + async hasDocsBeenGenerated(entity: Entity): Promise { const namespace = entity.metadata.namespace ?? 'default'; return new Promise(resolve => { - this.discovery.getBaseUrl('techdocs').then(techdocsApiUrl => { + this.discovery.getBaseUrl('techdocs').then(async techdocsApiUrl => { const storageUrl = new URL( - new URL(this.config.getString('techdocs.storageUrl')).pathname, + new URL(await this.getStorageUrl()).pathname, techdocsApiUrl, ).toString(); diff --git a/plugins/techdocs-backend/config.d.ts b/plugins/techdocs-backend/config.d.ts index b51d86138a..1e4298688e 100644 --- a/plugins/techdocs-backend/config.d.ts +++ b/plugins/techdocs-backend/config.d.ts @@ -24,7 +24,7 @@ export interface Config { * attr: 'storageUrl' - accepts a string value * e.g. storageUrl: http://localhost:7000/api/techdocs/static/docs */ - storageUrl: string; + storageUrl?: string; /** * documentation building process depends on the builder attr * attr: 'builder' - accepts a string value diff --git a/plugins/techdocs-backend/src/service/router.ts b/plugins/techdocs-backend/src/service/router.ts index dc5e6a4911..7d232e9ea1 100644 --- a/plugins/techdocs-backend/src/service/router.ts +++ b/plugins/techdocs-backend/src/service/router.ts @@ -26,7 +26,10 @@ import { PublisherBase, getLocationForEntity, } from '@backstage/techdocs-common'; -import { PluginEndpointDiscovery } from '@backstage/backend-common'; +import { + PluginEndpointDiscovery, + SingleHostDiscovery, +} from '@backstage/backend-common'; import { Entity } from '@backstage/catalog-model'; import { getEntityNameFromUrlPath } from './helpers'; import { DocsBuilder } from '../DocsBuilder'; @@ -102,7 +105,10 @@ export async function createRouter({ router.get('/docs/:namespace/:kind/:name/*', async (req, res) => { const { kind, namespace, name } = req.params; - const storageUrl = config.getString('techdocs.storageUrl'); + const discoveryApi = SingleHostDiscovery.fromConfig(config); + const storageUrl = + config.getOptionalString('techdocs.storageUrl') ?? + `${await discoveryApi.getBaseUrl('techdocs')}/static/docs`; const catalogUrl = await discovery.getBaseUrl('catalog'); const triple = [kind, namespace, name].map(encodeURIComponent).join('/'); diff --git a/plugins/techdocs/config.d.ts b/plugins/techdocs/config.d.ts index 0af3ab4290..ef4e026911 100644 --- a/plugins/techdocs/config.d.ts +++ b/plugins/techdocs/config.d.ts @@ -22,12 +22,12 @@ export interface Config { * e.g. requestUrl: http://localhost:7000/api/techdocs * @visibility frontend */ - requestUrl: string; + requestUrl?: string; /** * attr: 'storageUrl' - accepts a string value * e.g. storageUrl: http://localhost:7000/api/techdocs/static/docs */ - storageUrl: string; + storageUrl?: string; /** * documentation building process depends on the builder attr * attr: 'builder' - accepts a string value diff --git a/plugins/techdocs/dev/api.ts b/plugins/techdocs/dev/api.ts index ae2b8c58ae..7d0679f6f6 100644 --- a/plugins/techdocs/dev/api.ts +++ b/plugins/techdocs/dev/api.ts @@ -13,20 +13,38 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { DiscoveryApi } from '@backstage/core'; +import { Config } from '@backstage/config'; import { EntityName } from '@backstage/catalog-model'; import { TechDocsStorage } from '../src/api'; export class TechDocsDevStorageApi implements TechDocsStorage { - public apiOrigin: string; + public configApi: Config; + public discoveryApi: DiscoveryApi; - constructor({ apiOrigin }: { apiOrigin: string }) { - this.apiOrigin = apiOrigin; + constructor({ + configApi, + discoveryApi, + }: { + configApi: Config; + discoveryApi: DiscoveryApi; + }) { + this.configApi = configApi; + this.discoveryApi = discoveryApi; + } + + async getApiOrigin() { + return ( + this.configApi.getOptionalString('techdocs.requestUrl') ?? + (await this.discoveryApi.getBaseUrl('techdocs')) + ); } async getEntityDocs(entityId: EntityName, path: string) { const { name } = entityId; - const url = `${this.apiOrigin}/${name}/${path}`; + const apiOrigin = await this.getApiOrigin(); + const url = `${apiOrigin}/${name}/${path}`; const request = await fetch( `${url.endsWith('/') ? url : `${url}/`}index.html`, @@ -39,8 +57,13 @@ export class TechDocsDevStorageApi implements TechDocsStorage { return request.text(); } - getBaseUrl(oldBaseUrl: string, entityId: EntityName, path: string): string { + async getBaseUrl( + oldBaseUrl: string, + entityId: EntityName, + path: string, + ): Promise { const { name } = entityId; - return new URL(oldBaseUrl, `${this.apiOrigin}/${name}/${path}`).toString(); + const apiOrigin = await this.getApiOrigin(); + return new URL(oldBaseUrl, `${apiOrigin}/${name}/${path}`).toString(); } } diff --git a/plugins/techdocs/dev/index.tsx b/plugins/techdocs/dev/index.tsx index 12974ef8b7..42fa9a9ec4 100644 --- a/plugins/techdocs/dev/index.tsx +++ b/plugins/techdocs/dev/index.tsx @@ -14,6 +14,7 @@ * limitations under the License. */ +import { configApiRef, discoveryApiRef } from '@backstage/core'; import { createDevApp } from '@backstage/dev-utils'; import { plugin } from '../src/plugin'; import { TechDocsDevStorageApi } from './api'; @@ -22,10 +23,11 @@ import { techdocsStorageApiRef } from '../src'; createDevApp() .registerApi({ api: techdocsStorageApiRef, - deps: {}, - factory: () => + deps: { configApi: configApiRef, discoveryApi: discoveryApiRef }, + factory: ({ configApi, discoveryApi }) => new TechDocsDevStorageApi({ - apiOrigin: 'http://localhost:3000/api', + configApi, + discoveryApi, }), }) .registerPlugin(plugin) diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index 90c8fd3840..20e294c661 100644 --- a/plugins/techdocs/package.json +++ b/plugins/techdocs/package.json @@ -31,6 +31,7 @@ "clean": "backstage-cli clean" }, "dependencies": { + "@backstage/config": "^0.1.2", "@backstage/catalog-model": "^0.7.0", "@backstage/core": "^0.5.0", "@backstage/plugin-catalog-react": "^0.0.1", diff --git a/plugins/techdocs/src/api.test.ts b/plugins/techdocs/src/api.test.ts index 132976e280..ebffa2469e 100644 --- a/plugins/techdocs/src/api.test.ts +++ b/plugins/techdocs/src/api.test.ts @@ -13,10 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { useApi, configApiRef, UrlPatternDiscovery } from '@backstage/core'; import { TechDocsStorageApi } from './api'; -const DOC_STORAGE_URL = 'https://example-storage.com'; - const mockEntity = { kind: 'Component', namespace: 'default', @@ -24,19 +23,23 @@ const mockEntity = { }; describe('TechDocsStorageApi', () => { + const mockBaseUrl = 'http://backstage:9191/api/techdocs'; + const configApi = useApi(configApiRef); + const discoveryApi = UrlPatternDiscovery.compile(mockBaseUrl); + it('should return correct base url based on defined storage', () => { - const storageApi = new TechDocsStorageApi({ apiOrigin: DOC_STORAGE_URL }); + const storageApi = new TechDocsStorageApi({ configApi, discoveryApi }); expect(storageApi.getBaseUrl('test.js', mockEntity, '')).toEqual( - `${DOC_STORAGE_URL}/docs/${mockEntity.namespace}/${mockEntity.kind}/${mockEntity.name}/test.js`, + `${mockBaseUrl}/docs/${mockEntity.namespace}/${mockEntity.kind}/${mockEntity.name}/test.js`, ); }); it('should return base url with correct entity structure', () => { - const storageApi = new TechDocsStorageApi({ apiOrigin: DOC_STORAGE_URL }); + const storageApi = new TechDocsStorageApi({ configApi, discoveryApi }); expect(storageApi.getBaseUrl('test/', mockEntity, '')).toEqual( - `${DOC_STORAGE_URL}/docs/${mockEntity.namespace}/${mockEntity.kind}/${mockEntity.name}/test/`, + `${mockBaseUrl}/docs/${mockEntity.namespace}/${mockEntity.kind}/${mockEntity.name}/test/`, ); }); }); diff --git a/plugins/techdocs/src/api.ts b/plugins/techdocs/src/api.ts index 3a950aeeb3..1ca0e40592 100644 --- a/plugins/techdocs/src/api.ts +++ b/plugins/techdocs/src/api.ts @@ -14,7 +14,8 @@ * limitations under the License. */ -import { createApiRef } from '@backstage/core'; +import { createApiRef, DiscoveryApi } from '@backstage/core'; +import { Config } from '@backstage/config'; import { EntityName } from '@backstage/catalog-model'; import { TechDocsMetadata } from './types'; @@ -30,7 +31,11 @@ export const techdocsApiRef = createApiRef({ export interface TechDocsStorage { getEntityDocs(entityId: EntityName, path: string): Promise; - getBaseUrl(oldBaseUrl: string, entityId: EntityName, path: string): string; + getBaseUrl( + oldBaseUrl: string, + entityId: EntityName, + path: string, + ): Promise; } export interface TechDocs { @@ -44,10 +49,25 @@ export interface TechDocs { * @property {string} apiOrigin Set to techdocs.requestUrl as the URL for techdocs-backend API */ export class TechDocsApi implements TechDocs { - public apiOrigin: string; + public configApi: Config; + public discoveryApi: DiscoveryApi; - constructor({ apiOrigin }: { apiOrigin: string }) { - this.apiOrigin = apiOrigin; + constructor({ + configApi, + discoveryApi, + }: { + configApi: Config; + discoveryApi: DiscoveryApi; + }) { + this.configApi = configApi; + this.discoveryApi = discoveryApi; + } + + async getApiOrigin() { + return ( + this.configApi.getOptionalString('techdocs.requestUrl') ?? + (await this.discoveryApi.getBaseUrl('techdocs')) + ); } /** @@ -62,7 +82,8 @@ export class TechDocsApi implements TechDocs { async getTechDocsMetadata(entityId: EntityName) { const { kind, namespace, name } = entityId; - const requestUrl = `${this.apiOrigin}/metadata/techdocs/${namespace}/${kind}/${name}`; + const apiOrigin = await this.getApiOrigin(); + const requestUrl = `${apiOrigin}/metadata/techdocs/${namespace}/${kind}/${name}`; const request = await fetch(`${requestUrl}`); const res = await request.json(); @@ -81,7 +102,8 @@ export class TechDocsApi implements TechDocs { async getEntityMetadata(entityId: EntityName) { const { kind, namespace, name } = entityId; - const requestUrl = `${this.apiOrigin}/metadata/entity/${namespace}/${kind}/${name}`; + const apiOrigin = await this.getApiOrigin(); + const requestUrl = `${apiOrigin}/metadata/entity/${namespace}/${kind}/${name}`; const request = await fetch(`${requestUrl}`); const res = await request.json(); @@ -96,10 +118,25 @@ export class TechDocsApi implements TechDocs { * @property {string} apiOrigin Set to techdocs.requestUrl as the URL for techdocs-backend API */ export class TechDocsStorageApi implements TechDocsStorage { - public apiOrigin: string; + public configApi: Config; + public discoveryApi: DiscoveryApi; - constructor({ apiOrigin }: { apiOrigin: string }) { - this.apiOrigin = apiOrigin; + constructor({ + configApi, + discoveryApi, + }: { + configApi: Config; + discoveryApi: DiscoveryApi; + }) { + this.configApi = configApi; + this.discoveryApi = discoveryApi; + } + + async getApiOrigin() { + return ( + this.configApi.getOptionalString('techdocs.requestUrl') ?? + (await this.discoveryApi.getBaseUrl('techdocs')) + ); } /** @@ -113,7 +150,8 @@ export class TechDocsStorageApi implements TechDocsStorage { async getEntityDocs(entityId: EntityName, path: string) { const { kind, namespace, name } = entityId; - const url = `${this.apiOrigin}/docs/${namespace}/${kind}/${name}/${path}`; + const apiOrigin = await this.getApiOrigin(); + const url = `${apiOrigin}/docs/${namespace}/${kind}/${name}/${path}`; const request = await fetch( `${url.endsWith('/') ? url : `${url}/`}index.html`, @@ -132,12 +170,17 @@ export class TechDocsStorageApi implements TechDocsStorage { return request.text(); } - getBaseUrl(oldBaseUrl: string, entityId: EntityName, path: string): string { + async getBaseUrl( + oldBaseUrl: string, + entityId: EntityName, + path: string, + ): Promise { const { kind, namespace, name } = entityId; + const apiOrigin = await this.getApiOrigin(); return new URL( oldBaseUrl, - `${this.apiOrigin}/docs/${namespace}/${kind}/${name}/${path}`, + `${apiOrigin}/docs/${namespace}/${kind}/${name}/${path}`, ).toString(); } } diff --git a/plugins/techdocs/src/plugin.ts b/plugins/techdocs/src/plugin.ts index 576ba0a292..982b749f2c 100644 --- a/plugins/techdocs/src/plugin.ts +++ b/plugins/techdocs/src/plugin.ts @@ -34,6 +34,7 @@ import { createRouteRef, createApiFactory, configApiRef, + discoveryApiRef, } from '@backstage/core'; import { techdocsStorageApiRef, @@ -57,24 +58,25 @@ export const rootCatalogDocsRouteRef = createRouteRef({ title: 'Docs', }); -// TODO: Use discovery API for frontend to get URL for techdocs-backend instead of requestUrl export const plugin = createPlugin({ id: 'techdocs', apis: [ createApiFactory({ api: techdocsStorageApiRef, - deps: { configApi: configApiRef }, - factory: ({ configApi }) => + deps: { configApi: configApiRef, discoveryApi: discoveryApiRef }, + factory: ({ configApi, discoveryApi }) => new TechDocsStorageApi({ - apiOrigin: configApi.getString('techdocs.requestUrl'), + configApi, + discoveryApi, }), }), createApiFactory({ api: techdocsApiRef, - deps: { configApi: configApiRef }, - factory: ({ configApi }) => + deps: { configApi: configApiRef, discoveryApi: discoveryApiRef }, + factory: ({ configApi, discoveryApi }) => new TechDocsApi({ - apiOrigin: configApi.getString('techdocs.requestUrl'), + configApi, + discoveryApi, }), }), ], diff --git a/plugins/techdocs/src/reader/components/Reader.tsx b/plugins/techdocs/src/reader/components/Reader.tsx index 1264e2b6eb..50990911a1 100644 --- a/plugins/techdocs/src/reader/components/Reader.tsx +++ b/plugins/techdocs/src/reader/components/Reader.tsx @@ -129,7 +129,7 @@ export const Reader = ({ entityId, onReady }: Props) => { }, }), onCssReady({ - docStorageUrl: techdocsStorageApi.apiOrigin, + docStorageUrl: techdocsStorageApi.getApiOrigin(), onLoading: (dom: Element) => { (dom as HTMLElement).style.setProperty('opacity', '0'); }, diff --git a/plugins/techdocs/src/reader/components/TechDocsPage.test.tsx b/plugins/techdocs/src/reader/components/TechDocsPage.test.tsx index bc84f792a7..da383dd90c 100644 --- a/plugins/techdocs/src/reader/components/TechDocsPage.test.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsPage.test.tsx @@ -56,7 +56,7 @@ describe('', () => { }; const techdocsStorageApi: Partial = { getEntityDocs: (): Promise => Promise.resolve('String'), - getBaseUrl: (): string => '', + getBaseUrl: (): Promise => Promise.resolve('String'), }; const apiRegistry = ApiRegistry.from([ diff --git a/plugins/techdocs/src/reader/transformers/addBaseUrl.test.ts b/plugins/techdocs/src/reader/transformers/addBaseUrl.test.ts index 728c39826d..f915396fa3 100644 --- a/plugins/techdocs/src/reader/transformers/addBaseUrl.test.ts +++ b/plugins/techdocs/src/reader/transformers/addBaseUrl.test.ts @@ -21,7 +21,7 @@ import { TechDocsStorage } from '../../api'; const DOC_STORAGE_URL = 'https://example-host.storage.googleapis.com'; const techdocsStorageApi: TechDocsStorage = { - getBaseUrl: jest.fn(() => DOC_STORAGE_URL), + getBaseUrl: () => new Promise(resolve => resolve(DOC_STORAGE_URL)), getEntityDocs: () => new Promise(resolve => resolve('yes!')), }; diff --git a/plugins/techdocs/src/reader/transformers/addBaseUrl.ts b/plugins/techdocs/src/reader/transformers/addBaseUrl.ts index 8d9d5a7294..2872ad36cc 100644 --- a/plugins/techdocs/src/reader/transformers/addBaseUrl.ts +++ b/plugins/techdocs/src/reader/transformers/addBaseUrl.ts @@ -35,12 +35,12 @@ export const addBaseUrl = ({ ): void => { Array.from(list) .filter(elem => !!elem.getAttribute(attributeName)) - .forEach((elem: T) => { + .forEach(async (elem: T) => { const elemAttribute = elem.getAttribute(attributeName); if (!elemAttribute) return; elem.setAttribute( attributeName, - techdocsStorageApi.getBaseUrl(elemAttribute, entityId, path), + await techdocsStorageApi.getBaseUrl(elemAttribute, entityId, path), ); }); }; diff --git a/plugins/techdocs/src/reader/transformers/onCssReady.test.ts b/plugins/techdocs/src/reader/transformers/onCssReady.test.ts index 67ae38c56b..1fbc59e213 100644 --- a/plugins/techdocs/src/reader/transformers/onCssReady.test.ts +++ b/plugins/techdocs/src/reader/transformers/onCssReady.test.ts @@ -22,8 +22,9 @@ import { } from '../../test-utils'; import { onCssReady } from '../transformers'; -const docStorageUrl: string = - 'https://techdocs-mock-sites.storage.googleapis.com'; +const docStorageUrl: Promise = Promise.resolve( + 'https://techdocs-mock-sites.storage.googleapis.com', +); const fixture = ` diff --git a/plugins/techdocs/src/reader/transformers/onCssReady.ts b/plugins/techdocs/src/reader/transformers/onCssReady.ts index dd50879459..50dbe52cbd 100644 --- a/plugins/techdocs/src/reader/transformers/onCssReady.ts +++ b/plugins/techdocs/src/reader/transformers/onCssReady.ts @@ -17,7 +17,7 @@ import type { Transformer } from './index'; type OnCssReadyOptions = { - docStorageUrl: string; + docStorageUrl: Promise; onLoading: (dom: Element) => void; onLoaded: (dom: Element) => void; }; @@ -30,7 +30,9 @@ export const onCssReady = ({ return dom => { const cssPages = Array.from( dom.querySelectorAll('head > link[rel="stylesheet"]'), - ).filter(elem => elem.getAttribute('href')?.startsWith(docStorageUrl)); + ).filter(async elem => + elem.getAttribute('href')?.startsWith(await docStorageUrl), + ); let count = cssPages.length; From 38469b66bf22d08af3a7a177563ef8fa208daab0 Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Sun, 24 Jan 2021 13:01:11 +0100 Subject: [PATCH 02/16] techdocs: fix tests by reusing the instantiated discovery --- packages/techdocs-common/src/stages/publish/local.ts | 4 +--- plugins/techdocs-backend/src/service/router.ts | 8 ++------ 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/packages/techdocs-common/src/stages/publish/local.ts b/packages/techdocs-common/src/stages/publish/local.ts index b8ccf7df8a..76489ce01a 100644 --- a/packages/techdocs-common/src/stages/publish/local.ts +++ b/packages/techdocs-common/src/stages/publish/local.ts @@ -23,7 +23,6 @@ import { Entity, EntityName } from '@backstage/catalog-model'; import { resolvePackagePath, PluginEndpointDiscovery, - SingleHostDiscovery, } from '@backstage/backend-common'; import { Config } from '@backstage/config'; import { @@ -143,10 +142,9 @@ export class LocalPublish implements PublisherBase { } async getStorageUrl() { - const discoveryApi = SingleHostDiscovery.fromConfig(this.config); return ( this.config.getOptionalString('techdocs.storageUrl') ?? - (await discoveryApi.getBaseUrl('techdocs')) + (await this.discovery.getBaseUrl('techdocs')) ); } diff --git a/plugins/techdocs-backend/src/service/router.ts b/plugins/techdocs-backend/src/service/router.ts index 7d232e9ea1..fe7b52ab52 100644 --- a/plugins/techdocs-backend/src/service/router.ts +++ b/plugins/techdocs-backend/src/service/router.ts @@ -26,10 +26,7 @@ import { PublisherBase, getLocationForEntity, } from '@backstage/techdocs-common'; -import { - PluginEndpointDiscovery, - SingleHostDiscovery, -} from '@backstage/backend-common'; +import { PluginEndpointDiscovery } from '@backstage/backend-common'; import { Entity } from '@backstage/catalog-model'; import { getEntityNameFromUrlPath } from './helpers'; import { DocsBuilder } from '../DocsBuilder'; @@ -105,10 +102,9 @@ export async function createRouter({ router.get('/docs/:namespace/:kind/:name/*', async (req, res) => { const { kind, namespace, name } = req.params; - const discoveryApi = SingleHostDiscovery.fromConfig(config); const storageUrl = config.getOptionalString('techdocs.storageUrl') ?? - `${await discoveryApi.getBaseUrl('techdocs')}/static/docs`; + `${await discovery.getBaseUrl('techdocs')}/static/docs`; const catalogUrl = await discovery.getBaseUrl('catalog'); const triple = [kind, namespace, name].map(encodeURIComponent).join('/'); From 3927e79c291f289eb85ddbe3dc3cfb9b02716d1a Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Sun, 24 Jan 2021 14:08:16 +0100 Subject: [PATCH 03/16] techdocs: local publisher should directly access file system --- .../src/stages/publish/local.ts | 96 +++++++------------ 1 file changed, 36 insertions(+), 60 deletions(-) diff --git a/packages/techdocs-common/src/stages/publish/local.ts b/packages/techdocs-common/src/stages/publish/local.ts index 76489ce01a..e4d235609e 100644 --- a/packages/techdocs-common/src/stages/publish/local.ts +++ b/packages/techdocs-common/src/stages/publish/local.ts @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import fetch from 'cross-fetch'; import express from 'express'; import fs from 'fs-extra'; import path from 'path'; @@ -52,17 +51,13 @@ try { * called "static" at the root of techdocs-backend plugin. */ export class LocalPublish implements PublisherBase { - private readonly config: Config; - private readonly logger: Logger; - private readonly discovery: PluginEndpointDiscovery; - // TODO: Use a static fromConfig method to create a LocalPublish instance, similar to aws/gcs publishers. // Move the logic of setting staticDocsDir based on config over to fromConfig, // and set the value as a class parameter. constructor( - config: Config, - logger: Logger, - discovery: PluginEndpointDiscovery, + private readonly config: Config, + private readonly logger: Logger, + private readonly discovery: PluginEndpointDiscovery, ) { this.config = config; this.logger = logger; @@ -107,67 +102,48 @@ export class LocalPublish implements PublisherBase { }); } - fetchTechDocsMetadata(entityName: EntityName): Promise { - return new Promise((resolve, reject) => { - this.discovery.getBaseUrl('techdocs').then(async techdocsApiUrl => { - const storageUrl = new URL( - new URL(await this.getStorageUrl()).pathname, - techdocsApiUrl, - ).toString(); + async fetchTechDocsMetadata( + entityName: EntityName, + ): Promise { + const metadataPath = path.join( + staticDocsDir, + entityName.namespace, + entityName.kind, + entityName.name, + 'techdocs_metadata.json', + ); - const entityRootDir = `${entityName.namespace}/${entityName.kind}/${entityName.name}`; - const metadataURL = `${storageUrl}/${entityRootDir}/techdocs_metadata.json`; - fetch(metadataURL) - .then(response => - response - .json() - .then(techdocsMetadata => resolve(techdocsMetadata)) - .catch(err => { - reject( - `Unable to parse metadata JSON for ${entityRootDir}. Error: ${err}`, - ); - }), - ) - .catch(err => { - reject( - `Unable to fetch metadata for ${entityRootDir}. Error ${err}`, - ); - }); - }); - }); + try { + return await fs.readJson(metadataPath); + } catch (err) { + this.logger.error( + `Unable to read techdocs_metadata.json at ${metadataPath}. Error: ${err}`, + ); + throw new Error(err.message); + } } docsRouter(): express.Handler { return express.static(staticDocsDir); } - async getStorageUrl() { - return ( - this.config.getOptionalString('techdocs.storageUrl') ?? - (await this.discovery.getBaseUrl('techdocs')) - ); - } - async hasDocsBeenGenerated(entity: Entity): Promise { const namespace = entity.metadata.namespace ?? 'default'; - return new Promise(resolve => { - this.discovery.getBaseUrl('techdocs').then(async techdocsApiUrl => { - const storageUrl = new URL( - new URL(await this.getStorageUrl()).pathname, - techdocsApiUrl, - ).toString(); - const entityRootDir = `${namespace}/${entity.kind}/${entity.metadata.name}`; - const indexHtmlUrl = `${storageUrl}/${entityRootDir}/index.html`; - // Check if the file exists - fs.access(indexHtmlUrl, fs.constants.F_OK, err => { - if (err) { - resolve(false); - } else { - resolve(true); - } - }); - }); - }); + const indexHtmlPath = path.join( + staticDocsDir, + namespace, + entity.kind, + entity.metadata.name, + 'index.html', + ); + + // Check if the file exists + try { + fs.access(indexHtmlPath, fs.constants.F_OK); + return true; + } catch (err) { + return false; + } } } From 0ed9d03c215f0c865cd0966a10cfa0ed26f71d58 Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Sun, 24 Jan 2021 14:09:09 +0100 Subject: [PATCH 04/16] config: Remove techdocs.requestUrl and storageUrl from default app-config.yaml --- app-config.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/app-config.yaml b/app-config.yaml index 763dd0c258..5240a050a8 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -74,8 +74,6 @@ organization: # Reference documentation http://backstage.io/docs/features/techdocs/configuration techdocs: - requestUrl: http://localhost:7000/api/techdocs - storageUrl: http://localhost:7000/api/techdocs/static/docs builder: 'local' # Alternatives - 'external' generators: techdocs: 'docker' # Alternatives - 'local' From 5d94132bf923b82f7d367af94bf8b827bc6ae5d3 Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Thu, 28 Jan 2021 18:39:39 +0100 Subject: [PATCH 05/16] TechDocs: Suppress ts error of config not being used --- packages/techdocs-common/src/stages/publish/local.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/techdocs-common/src/stages/publish/local.ts b/packages/techdocs-common/src/stages/publish/local.ts index e4d235609e..b4b30c1bf1 100644 --- a/packages/techdocs-common/src/stages/publish/local.ts +++ b/packages/techdocs-common/src/stages/publish/local.ts @@ -55,6 +55,7 @@ export class LocalPublish implements PublisherBase { // Move the logic of setting staticDocsDir based on config over to fromConfig, // and set the value as a class parameter. constructor( + // @ts-ignore private readonly config: Config, private readonly logger: Logger, private readonly discovery: PluginEndpointDiscovery, From 29e269d9207a8e641afe06b5cfaa7c03bc661e2d Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Thu, 28 Jan 2021 18:51:18 +0100 Subject: [PATCH 06/16] TechDocs: Remove traces of requestUrl/storageUrl from docs --- docs/features/techdocs/configuration.md | 20 +++++++++++--------- docs/features/techdocs/getting-started.md | 21 +++------------------ 2 files changed, 14 insertions(+), 27 deletions(-) diff --git a/docs/features/techdocs/configuration.md b/docs/features/techdocs/configuration.md index b9413227af..20e436d8a4 100644 --- a/docs/features/techdocs/configuration.md +++ b/docs/features/techdocs/configuration.md @@ -13,15 +13,6 @@ configuration options for TechDocs. # File: app-config.yaml techdocs: - # TechDocs makes API calls to techdocs-backend using this URL. e.g. get docs of an entity, get metadata, etc. (Optional) - - requestUrl: http://localhost:7000/api/techdocs - - # Just another route in techdocs-backend where TechDocs requests the static files from. This URL uses an HTTP middleware - # to serve files from either a local directory or an External storage provider. (Optional) - - storageUrl: http://localhost:7000/api/techdocs/static/docs - # generators.techdocs can have two values: 'docker' or 'local'. This is to determine how to run the generator - whether to # spin up the techdocs-container docker image or to run mkdocs locally (assuming all the dependencies are taken care of). # You want to change this to 'local' if you are running Backstage using your own custom Docker setup and want to avoid running @@ -101,4 +92,15 @@ techdocs: # https://docs.microsoft.com/en-us/azure/storage/common/storage-auth?toc=/azure/storage/blobs/toc.json accountKey: $env: TECHDOCS_AZURE_BLOB_STORAGE_ACCOUNT_KEY + + # (Optional and Legacy) TechDocs makes API calls to techdocs-backend using this URL. e.g. get docs of an entity, get metadata, etc. + # You don't have to specify this anymore. + + requestUrl: http://localhost:7000/api/techdocs + + # (Optional and Legacy) Just another route in techdocs-backend where TechDocs requests the static files from. This URL uses an HTTP middleware + # to serve files from either a local directory or an External storage provider. + # You don't have to specify this anymore. + + storageUrl: http://localhost:7000/api/techdocs/static/docs ``` diff --git a/docs/features/techdocs/getting-started.md b/docs/features/techdocs/getting-started.md index 5147e17aaa..e0b323857d 100644 --- a/docs/features/techdocs/getting-started.md +++ b/docs/features/techdocs/getting-started.md @@ -150,26 +150,10 @@ app. Now let us tweak some configurations to suit your needs. **See [TechDocs Configuration Options](configuration.md) for complete configuration reference.** -### Setting TechDocs URLs - -```yaml -techdocs: - storageUrl: http://localhost:7000/api/techdocs/static/docs - requestUrl: http://localhost:7000/api/techdocs/ -``` - -`requestUrl` is used by TechDocs frontend plugin to discover `techdocs-backend` -endpoints, and the `storageUrl` is another endpoint in `techdocs-backend` which -acts as a middleware between TechDocs and the storage (where the static -generated docs site are stored). These default values should mostly work for -you. These options will soon be optional to set. - ### Should TechDocs Backend generate docs? ```yaml techdocs: - storageUrl: http://localhost:7000/api/techdocs/static/docs - requestUrl: http://localhost:7000/api/techdocs/ builder: 'local' ``` @@ -196,8 +180,6 @@ out Backstage for the first time. At a later time, review ```yaml techdocs: - storageUrl: http://localhost:7000/api/techdocs/static/docs - requestUrl: http://localhost:7000/api/techdocs/ builder: 'local' publisher: type: 'local' @@ -219,6 +201,9 @@ no config is provided. ```yaml techdocs: + builder: 'local' + publisher: + type: 'local' generators: techdocs: local ``` From ae893e99f9b9131c4ed3121a09f9e37a0f0d1c0f Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Thu, 28 Jan 2021 19:58:26 +0100 Subject: [PATCH 07/16] TechDocs: Update tests to reflect requestUrl change --- .../src/stages/publish/local.test.ts | 41 ++++--------------- 1 file changed, 9 insertions(+), 32 deletions(-) diff --git a/packages/techdocs-common/src/stages/publish/local.test.ts b/packages/techdocs-common/src/stages/publish/local.test.ts index efd56285ef..63adfad09a 100644 --- a/packages/techdocs-common/src/stages/publish/local.test.ts +++ b/packages/techdocs-common/src/stages/publish/local.test.ts @@ -24,23 +24,6 @@ import { import { ConfigReader } from '@backstage/config'; import { LocalPublish } from './local'; -jest.mock('fs-extra', () => { - const fsOriginal = jest.requireActual('fs-extra'); - return { - ...fsOriginal, - access: jest.fn().mockImplementation((path, checkType, callback) => { - if ( - path.includes('http://localhost:7000/static') && - checkType === fs.constants.F_OK - ) { - callback(); - } else { - callback(new Error()); - } - }), - }; -}); - const createMockEntity = (annotations = {}) => { return { apiVersion: 'version', @@ -59,40 +42,34 @@ const logger = getVoidLogger(); describe('local publisher', () => { it('should publish generated documentation dir', async () => { const testDiscovery: jest.Mocked = { - getBaseUrl: jest.fn().mockResolvedValue('http://localhost:7000'), + getBaseUrl: jest + .fn() + .mockResolvedValue('http://localhost:7000/api/techdocs'), getExternalBaseUrl: jest.fn(), }; - const mockConfig = new ConfigReader({ - techdocs: { - requestUrl: 'http://localhost:7000', - storageUrl: 'http://localhost:7000/static/docs', - }, - }); + const mockConfig = new ConfigReader({}); const publisher = new LocalPublish(mockConfig, logger, testDiscovery); const mockEntity = createMockEntity(); const tempDir = fs.mkdtempSync(`${__dirname}/test-component-folder-`); expect(tempDir).toBeTruthy(); - fs.closeSync(fs.openSync(path.join(tempDir, '/mock-file'), 'w')); + fs.closeSync(fs.openSync(path.join(tempDir, '/index.html'), 'w')); await publisher.publish({ entity: mockEntity, directory: tempDir }); - const publishDir = path.resolve( - __dirname, - `../../../../../plugins/techdocs-backend/static/docs/${mockEntity.metadata.name}`, - ); + fs.removeSync(tempDir); + const resultDir = path.resolve( __dirname, `../../../../../plugins/techdocs-backend/static/docs/default/${mockEntity.kind}/${mockEntity.metadata.name}`, ); expect(fs.existsSync(resultDir)).toBeTruthy(); - expect(fs.existsSync(path.join(resultDir, '/mock-file'))).toBeTruthy(); + expect(fs.existsSync(path.join(resultDir, '/index.html'))).toBeTruthy(); expect(await publisher.hasDocsBeenGenerated(mockEntity)).toBe(true); - fs.removeSync(publishDir); - fs.removeSync(tempDir); + fs.removeSync(resultDir); }); }); From 4ddf8d5e2763c70f8db23629dcd9b3d0fb5e189a Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Thu, 28 Jan 2021 20:17:58 +0100 Subject: [PATCH 08/16] TechDocs: Fix frontend tests due to async apiOrigin function --- plugins/techdocs/src/reader/components/TechDocsPage.test.tsx | 1 + plugins/techdocs/src/reader/transformers/addBaseUrl.test.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/techdocs/src/reader/components/TechDocsPage.test.tsx b/plugins/techdocs/src/reader/components/TechDocsPage.test.tsx index da383dd90c..4b578bef38 100644 --- a/plugins/techdocs/src/reader/components/TechDocsPage.test.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsPage.test.tsx @@ -57,6 +57,7 @@ describe('', () => { const techdocsStorageApi: Partial = { getEntityDocs: (): Promise => Promise.resolve('String'), getBaseUrl: (): Promise => Promise.resolve('String'), + getApiOrigin: (): Promise => Promise.resolve('String'), }; const apiRegistry = ApiRegistry.from([ diff --git a/plugins/techdocs/src/reader/transformers/addBaseUrl.test.ts b/plugins/techdocs/src/reader/transformers/addBaseUrl.test.ts index f915396fa3..10c7191b63 100644 --- a/plugins/techdocs/src/reader/transformers/addBaseUrl.test.ts +++ b/plugins/techdocs/src/reader/transformers/addBaseUrl.test.ts @@ -21,7 +21,7 @@ import { TechDocsStorage } from '../../api'; const DOC_STORAGE_URL = 'https://example-host.storage.googleapis.com'; const techdocsStorageApi: TechDocsStorage = { - getBaseUrl: () => new Promise(resolve => resolve(DOC_STORAGE_URL)), + getBaseUrl: jest.fn(() => Promise.resolve(DOC_STORAGE_URL)), getEntityDocs: () => new Promise(resolve => resolve('yes!')), }; From 1df7721a68b8b1ef4fd675166bb061a3f3c08a91 Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Thu, 28 Jan 2021 20:45:49 +0100 Subject: [PATCH 09/16] TechDocs: Fix test with invalid hook call error --- plugins/techdocs/src/api.test.ts | 21 +++++++++++++++------ plugins/techdocs/src/api.ts | 1 + 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/plugins/techdocs/src/api.test.ts b/plugins/techdocs/src/api.test.ts index ebffa2469e..734650d7c8 100644 --- a/plugins/techdocs/src/api.test.ts +++ b/plugins/techdocs/src/api.test.ts @@ -13,7 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { useApi, configApiRef, UrlPatternDiscovery } from '@backstage/core'; +import { Config } from '@backstage/config'; +import { UrlPatternDiscovery } from '@backstage/core'; import { TechDocsStorageApi } from './api'; const mockEntity = { @@ -24,21 +25,29 @@ const mockEntity = { describe('TechDocsStorageApi', () => { const mockBaseUrl = 'http://backstage:9191/api/techdocs'; - const configApi = useApi(configApiRef); + const configApi = { + getOptionalString: () => 'http://backstage:9191/api/techdocs', + } as Partial; const discoveryApi = UrlPatternDiscovery.compile(mockBaseUrl); - it('should return correct base url based on defined storage', () => { + it('should return correct base url based on defined storage', async () => { + // @ts-ignore Partial not assignable to Config. const storageApi = new TechDocsStorageApi({ configApi, discoveryApi }); - expect(storageApi.getBaseUrl('test.js', mockEntity, '')).toEqual( + await expect( + storageApi.getBaseUrl('test.js', mockEntity, ''), + ).resolves.toEqual( `${mockBaseUrl}/docs/${mockEntity.namespace}/${mockEntity.kind}/${mockEntity.name}/test.js`, ); }); - it('should return base url with correct entity structure', () => { + it('should return base url with correct entity structure', async () => { + // @ts-ignore Partial not assignable to Config. const storageApi = new TechDocsStorageApi({ configApi, discoveryApi }); - expect(storageApi.getBaseUrl('test/', mockEntity, '')).toEqual( + await expect( + storageApi.getBaseUrl('test/', mockEntity, ''), + ).resolves.toEqual( `${mockBaseUrl}/docs/${mockEntity.namespace}/${mockEntity.kind}/${mockEntity.name}/test/`, ); }); diff --git a/plugins/techdocs/src/api.ts b/plugins/techdocs/src/api.ts index 1ca0e40592..a8b1f1e34f 100644 --- a/plugins/techdocs/src/api.ts +++ b/plugins/techdocs/src/api.ts @@ -178,6 +178,7 @@ export class TechDocsStorageApi implements TechDocsStorage { const { kind, namespace, name } = entityId; const apiOrigin = await this.getApiOrigin(); + return new URL( oldBaseUrl, `${apiOrigin}/docs/${namespace}/${kind}/${name}/${path}`, From 436ca3f625bfd79615ab34e3166e6d0f5f9c322c Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Thu, 28 Jan 2021 20:59:43 +0100 Subject: [PATCH 10/16] create-app: Remove TechDocs requestUrl and storageUrl configs from app-config.yaml --- .changeset/curly-ghosts-laugh.md | 5 +++++ .../create-app/templates/default-app/app-config.yaml.hbs | 9 ++++----- 2 files changed, 9 insertions(+), 5 deletions(-) create mode 100644 .changeset/curly-ghosts-laugh.md diff --git a/.changeset/curly-ghosts-laugh.md b/.changeset/curly-ghosts-laugh.md new file mode 100644 index 0000000000..8c55e93cfc --- /dev/null +++ b/.changeset/curly-ghosts-laugh.md @@ -0,0 +1,5 @@ +--- +'@backstage/create-app': patch +--- + +Remove techdocs.requestUrl and techdocs.storageUrl from app-config.yaml diff --git a/packages/create-app/templates/default-app/app-config.yaml.hbs b/packages/create-app/templates/default-app/app-config.yaml.hbs index d253102f71..495b30dd6f 100644 --- a/packages/create-app/templates/default-app/app-config.yaml.hbs +++ b/packages/create-app/templates/default-app/app-config.yaml.hbs @@ -56,14 +56,13 @@ proxy: target: 'https://example.com' changeOrigin: true +# Reference documentation http://backstage.io/docs/features/techdocs/configuration techdocs: - requestUrl: http://localhost:7000/api/techdocs - storageUrl: http://localhost:7000/api/techdocs/static/docs - builder: 'local' + builder: 'local' # Alternatives - 'external' generators: - techdocs: 'docker' + techdocs: 'docker' # Alternatives - 'local' publisher: - type: 'local' + type: 'local' # Alternatives - 'googleGcs' or 'awsS3'. Read documentation for using alternatives. auth: # see https://backstage.io/docs/tutorials/quickstart-app-auth to know more about enabling auth providers From a5628df4006b506883a04f4d2335764ae518aa77 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 2 Feb 2021 19:06:24 +0100 Subject: [PATCH 11/16] lighthouse: migrate to new composability API --- .changeset/fifty-dolls-doubt.md | 5 +++ plugins/lighthouse/dev/index.tsx | 4 +-- plugins/lighthouse/src/Router.tsx | 23 +++++------- .../components/AuditList/AuditListTable.tsx | 7 +--- .../src/components/AuditList/index.tsx | 3 +- .../src/components/AuditView/index.tsx | 10 ++---- plugins/lighthouse/src/index.ts | 15 ++++++-- plugins/lighthouse/src/plugin.test.ts | 4 +-- plugins/lighthouse/src/plugin.ts | 35 ++++++++++++++++++- 9 files changed, 68 insertions(+), 38 deletions(-) create mode 100644 .changeset/fifty-dolls-doubt.md diff --git a/.changeset/fifty-dolls-doubt.md b/.changeset/fifty-dolls-doubt.md new file mode 100644 index 0000000000..1abd7a16b9 --- /dev/null +++ b/.changeset/fifty-dolls-doubt.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-lighthouse': patch +--- + +Migrate to new composability API, exporting the plugin instance as `lighthousePlugin`, the top-level page as `LighthousePage`, the entity card as `EntityLastLighthouseAuditCard`, and the entity content as `EntityLighthouseContent`. diff --git a/plugins/lighthouse/dev/index.tsx b/plugins/lighthouse/dev/index.tsx index bf761965f1..1ea54e5871 100644 --- a/plugins/lighthouse/dev/index.tsx +++ b/plugins/lighthouse/dev/index.tsx @@ -15,11 +15,11 @@ */ import { createDevApp } from '@backstage/dev-utils'; -import { plugin } from '../src/plugin'; +import { lighthousePlugin } from '../src/plugin'; import { lighthouseApiRef, LighthouseRestApi } from '../src'; createDevApp() - .registerPlugin(plugin) + .registerPlugin(lighthousePlugin) .registerApi({ api: lighthouseApiRef, deps: {}, diff --git a/plugins/lighthouse/src/Router.tsx b/plugins/lighthouse/src/Router.tsx index d264f3bc1b..0634a0aaa0 100644 --- a/plugins/lighthouse/src/Router.tsx +++ b/plugins/lighthouse/src/Router.tsx @@ -16,7 +16,6 @@ import React from 'react'; import { Route, Routes } from 'react-router-dom'; -import { createAuditRouteRef, rootRouteRef, viewAuditRouteRef } from './plugin'; import AuditList from './components/AuditList'; import AuditView, { AuditViewContent } from './components/AuditView'; import CreateAudit, { CreateAuditContent } from './components/CreateAudit'; @@ -25,32 +24,26 @@ import { LIGHTHOUSE_WEBSITE_URL_ANNOTATION } from '../constants'; import { AuditListForEntity } from './components/AuditList/AuditListForEntity'; import { MissingAnnotationEmptyState } from '@backstage/core'; -export const isPluginApplicableToEntity = (entity: Entity) => +export const isLighthouseAvailable = (entity: Entity) => Boolean(entity.metadata.annotations?.[LIGHTHOUSE_WEBSITE_URL_ANNOTATION]); export const Router = () => ( - } /> - } /> - } /> + } /> + } /> + } /> ); export const EmbeddedRouter = ({ entity }: { entity: Entity }) => - !isPluginApplicableToEntity(entity) ? ( + !isLighthouseAvailable(entity) ? ( ) : ( - } /> - } - /> - } - /> + } /> + } /> + } /> ); diff --git a/plugins/lighthouse/src/components/AuditList/AuditListTable.tsx b/plugins/lighthouse/src/components/AuditList/AuditListTable.tsx index 0fb66a317b..8a59869266 100644 --- a/plugins/lighthouse/src/components/AuditList/AuditListTable.tsx +++ b/plugins/lighthouse/src/components/AuditList/AuditListTable.tsx @@ -25,7 +25,6 @@ import { } from '../../utils'; import { Link, generatePath } from 'react-router-dom'; import AuditStatusIcon from '../AuditStatusIcon'; -import { viewAuditRouteRef } from '../../plugin'; const columns: TableColumn[] = [ { @@ -99,11 +98,7 @@ export const AuditListTable = ({ items }: { items: Website[] }) => { return { websiteUrl: ( - + {website.url} ), diff --git a/plugins/lighthouse/src/components/AuditList/index.tsx b/plugins/lighthouse/src/components/AuditList/index.tsx index b022482da4..ce0fcdb46e 100644 --- a/plugins/lighthouse/src/components/AuditList/index.tsx +++ b/plugins/lighthouse/src/components/AuditList/index.tsx @@ -35,7 +35,6 @@ import { useQuery } from '../../utils'; import LighthouseSupportButton from '../SupportButton'; import LighthouseIntro, { LIGHTHOUSE_INTRO_LOCAL_STORAGE } from '../Intro'; import AuditListTable from './AuditListTable'; -import { createAuditRouteRef } from '../../plugin'; export const LIMIT = 10; @@ -110,7 +109,7 @@ const AuditList = () => { diff --git a/plugins/lighthouse/src/components/AuditView/index.tsx b/plugins/lighthouse/src/components/AuditView/index.tsx index 2a99339a84..2be284183c 100644 --- a/plugins/lighthouse/src/components/AuditView/index.tsx +++ b/plugins/lighthouse/src/components/AuditView/index.tsx @@ -47,7 +47,6 @@ import { lighthouseApiRef, Website, Audit } from '../../api'; import AuditStatusIcon from '../AuditStatusIcon'; import LighthouseSupportButton from '../SupportButton'; import { formatTime } from '../../utils'; -import { viewAuditRouteRef, createAuditRouteRef } from '../../plugin'; const useStyles = makeStyles({ contentGrid: { @@ -78,12 +77,7 @@ const AuditLinkList = ({ audits = [], selectedId }: AuditLinkListProps) => ( button component={Link} replace - to={resolvePath( - generatePath(viewAuditRouteRef.path, { - id: audit.id, - }), - '../../', - )} + to={resolvePath(generatePath('audit/:id', { id: audit.id }), '../../')} > @@ -163,7 +157,7 @@ export const AuditViewContent = () => { ); } - let createAuditButtonUrl = createAuditRouteRef.path; + let createAuditButtonUrl = 'create-audit'; if (value?.url) { createAuditButtonUrl += `?url=${encodeURIComponent(value.url)}`; } diff --git a/plugins/lighthouse/src/index.ts b/plugins/lighthouse/src/index.ts index 64fe2f8cc0..bf51bbbf35 100644 --- a/plugins/lighthouse/src/index.ts +++ b/plugins/lighthouse/src/index.ts @@ -14,7 +14,18 @@ * limitations under the License. */ -export { plugin } from './plugin'; -export { Router, isPluginApplicableToEntity, EmbeddedRouter } from './Router'; +export { + lighthousePlugin, + lighthousePlugin as plugin, + LighthousePage, + EntityLighthouseContent, + EntityLastLighthouseAuditCard, +} from './plugin'; +export { + Router, + isLighthouseAvailable as isPluginApplicableToEntity, + isLighthouseAvailable, + EmbeddedRouter, +} from './Router'; export * from './api'; export * from './components/Cards'; diff --git a/plugins/lighthouse/src/plugin.test.ts b/plugins/lighthouse/src/plugin.test.ts index 70b1844ec2..642f438e03 100644 --- a/plugins/lighthouse/src/plugin.test.ts +++ b/plugins/lighthouse/src/plugin.test.ts @@ -14,10 +14,10 @@ * limitations under the License. */ -import { plugin } from './plugin'; +import { lighthousePlugin } from './plugin'; describe('lighthouse', () => { it('should export plugin', () => { - expect(plugin).toBeDefined(); + expect(lighthousePlugin).toBeDefined(); }); }); diff --git a/plugins/lighthouse/src/plugin.ts b/plugins/lighthouse/src/plugin.ts index e9055b4db7..c2a8521da7 100644 --- a/plugins/lighthouse/src/plugin.ts +++ b/plugins/lighthouse/src/plugin.ts @@ -19,6 +19,8 @@ import { createRouteRef, createApiFactory, configApiRef, + createRoutableExtension, + createComponentExtension, } from '@backstage/core'; import { lighthouseApiRef, LighthouseRestApi } from './api'; @@ -37,7 +39,11 @@ export const createAuditRouteRef = createRouteRef({ title: 'Create Lighthouse Audit', }); -export const plugin = createPlugin({ +export const entityContentRouteRef = createRouteRef({ + title: 'Lighthouse Entity Content', +}); + +export const lighthousePlugin = createPlugin({ id: 'lighthouse', apis: [ createApiFactory({ @@ -46,4 +52,31 @@ export const plugin = createPlugin({ factory: ({ configApi }) => LighthouseRestApi.fromConfig(configApi), }), ], + routes: { + root: createAuditRouteRef, + entityContent: entityContentRouteRef, + }, }); + +export const LighthousePage = lighthousePlugin.provide( + createRoutableExtension({ + component: () => import('./Router').then(m => m.Router), + mountPoint: rootRouteRef, + }), +); + +export const EntityLighthouseContent = lighthousePlugin.provide( + createRoutableExtension({ + component: () => import('./Router').then(m => m.EmbeddedRouter), + mountPoint: entityContentRouteRef, + }), +); + +export const EntityLastLighthouseAuditCard = lighthousePlugin.provide( + createComponentExtension({ + component: { + lazy: () => + import('./components/Cards').then(m => m.LastLighthouseAuditCard), + }, + }), +); From b712841d61556a670efd3d7c73989ee1c8d5deda Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 2 Feb 2021 19:44:05 +0100 Subject: [PATCH 12/16] catalog-import: migrated to new composability API --- .changeset/fast-dots-camp.md | 5 +++++ plugins/catalog-import/dev/index.tsx | 4 ++-- plugins/catalog-import/src/index.ts | 6 +++++- plugins/catalog-import/src/plugin.test.ts | 4 ++-- plugins/catalog-import/src/plugin.ts | 13 ++++++++++++- 5 files changed, 26 insertions(+), 6 deletions(-) create mode 100644 .changeset/fast-dots-camp.md diff --git a/.changeset/fast-dots-camp.md b/.changeset/fast-dots-camp.md new file mode 100644 index 0000000000..7a1f510f47 --- /dev/null +++ b/.changeset/fast-dots-camp.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-import': patch +--- + +Migrated to new composability API, exporting the plugin instance as `catalogImportPlugin`, and the page as `CatalogImportPage`. diff --git a/plugins/catalog-import/dev/index.tsx b/plugins/catalog-import/dev/index.tsx index 812a5585d4..c9b46a7ed4 100644 --- a/plugins/catalog-import/dev/index.tsx +++ b/plugins/catalog-import/dev/index.tsx @@ -15,6 +15,6 @@ */ import { createDevApp } from '@backstage/dev-utils'; -import { plugin } from '../src/plugin'; +import { catalogImportPlugin } from '../src/plugin'; -createDevApp().registerPlugin(plugin).render(); +createDevApp().registerPlugin(catalogImportPlugin).render(); diff --git a/plugins/catalog-import/src/index.ts b/plugins/catalog-import/src/index.ts index dd8ca2bbb4..7b4fc17121 100644 --- a/plugins/catalog-import/src/index.ts +++ b/plugins/catalog-import/src/index.ts @@ -14,6 +14,10 @@ * limitations under the License. */ -export { plugin } from './plugin'; +export { + catalogImportPlugin, + catalogImportPlugin as plugin, + CatalogImportPage, +} from './plugin'; export { Router } from './components/Router'; export * from './api'; diff --git a/plugins/catalog-import/src/plugin.test.ts b/plugins/catalog-import/src/plugin.test.ts index 2f5964946c..7ebe194b36 100644 --- a/plugins/catalog-import/src/plugin.test.ts +++ b/plugins/catalog-import/src/plugin.test.ts @@ -14,10 +14,10 @@ * limitations under the License. */ -import { plugin } from './plugin'; +import { catalogImportPlugin } from './plugin'; describe('catalog-import', () => { it('should export plugin', () => { - expect(plugin).toBeDefined(); + expect(catalogImportPlugin).toBeDefined(); }); }); diff --git a/plugins/catalog-import/src/plugin.ts b/plugins/catalog-import/src/plugin.ts index 829be22045..42210d0ed1 100644 --- a/plugins/catalog-import/src/plugin.ts +++ b/plugins/catalog-import/src/plugin.ts @@ -21,6 +21,7 @@ import { discoveryApiRef, githubAuthApiRef, configApiRef, + createRoutableExtension, } from '@backstage/core'; import { catalogImportApiRef } from './api/CatalogImportApi'; import { CatalogImportClient } from './api/CatalogImportClient'; @@ -30,7 +31,7 @@ export const rootRouteRef = createRouteRef({ title: 'catalog-import', }); -export const plugin = createPlugin({ +export const catalogImportPlugin = createPlugin({ id: 'catalog-import', apis: [ createApiFactory({ @@ -44,4 +45,14 @@ export const plugin = createPlugin({ new CatalogImportClient({ discoveryApi, githubAuthApi, configApi }), }), ], + routes: { + importPage: rootRouteRef, + }, }); + +export const CatalogImportPage = catalogImportPlugin.provide( + createRoutableExtension({ + component: () => import('./components/Router').then(m => m.Router), + mountPoint: rootRouteRef, + }), +); From 0269f4fd9143a216d7bcaacc7b62f0022ec6e1d4 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 2 Feb 2021 20:22:07 +0100 Subject: [PATCH 13/16] org: migrate to new composability API --- .changeset/metal-insects-compete.md | 5 +++++ plugins/org/dev/index.tsx | 4 ++-- plugins/org/src/index.ts | 9 +++++++- plugins/org/src/plugin.test.ts | 4 ++-- plugins/org/src/plugin.ts | 33 +++++++++++++++++++++++++++-- 5 files changed, 48 insertions(+), 7 deletions(-) create mode 100644 .changeset/metal-insects-compete.md diff --git a/.changeset/metal-insects-compete.md b/.changeset/metal-insects-compete.md new file mode 100644 index 0000000000..ead779d6a9 --- /dev/null +++ b/.changeset/metal-insects-compete.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-org': patch +--- + +Migrate to new composability API, exporting the plugin instance as `orgPlugin`, and the entity cards as `EntityGroupProfileCard`, `EntityMembersListCard`, `EntityOwnershipCard`, and `EntityUserProfileCard`. diff --git a/plugins/org/dev/index.tsx b/plugins/org/dev/index.tsx index 264d6f801f..01951eaf03 100644 --- a/plugins/org/dev/index.tsx +++ b/plugins/org/dev/index.tsx @@ -14,6 +14,6 @@ * limitations under the License. */ import { createDevApp } from '@backstage/dev-utils'; -import { plugin } from '../src/plugin'; +import { orgPlugin } from '../src/plugin'; -createDevApp().registerPlugin(plugin).render(); +createDevApp().registerPlugin(orgPlugin).render(); diff --git a/plugins/org/src/index.ts b/plugins/org/src/index.ts index 77ad7f9266..36c5f94ee5 100644 --- a/plugins/org/src/index.ts +++ b/plugins/org/src/index.ts @@ -13,5 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { plugin } from './plugin'; +export { + orgPlugin, + orgPlugin as plugin, + EntityGroupProfileCard, + EntityMembersListCard, + EntityOwnershipCard, + EntityUserProfileCard, +} from './plugin'; export * from './components'; diff --git a/plugins/org/src/plugin.test.ts b/plugins/org/src/plugin.test.ts index d77cfd7ae8..e488422441 100644 --- a/plugins/org/src/plugin.test.ts +++ b/plugins/org/src/plugin.test.ts @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { plugin } from './plugin'; +import { orgPlugin } from './plugin'; describe('groups', () => { it('should export plugin', () => { - expect(plugin).toBeDefined(); + expect(orgPlugin).toBeDefined(); }); }); diff --git a/plugins/org/src/plugin.ts b/plugins/org/src/plugin.ts index 39c3502fb5..195e88ea41 100644 --- a/plugins/org/src/plugin.ts +++ b/plugins/org/src/plugin.ts @@ -13,8 +13,37 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { createPlugin } from '@backstage/core'; +import { createComponentExtension, createPlugin } from '@backstage/core'; -export const plugin = createPlugin({ +export const orgPlugin = createPlugin({ id: 'org', }); + +export const EntityGroupProfileCard = orgPlugin.provide( + createComponentExtension({ + component: { + lazy: () => import('./components').then(m => m.GroupProfileCard), + }, + }), +); +export const EntityMembersListCard = orgPlugin.provide( + createComponentExtension({ + component: { + lazy: () => import('./components').then(m => m.MembersListCard), + }, + }), +); +export const EntityOwnershipCard = orgPlugin.provide( + createComponentExtension({ + component: { + lazy: () => import('./components').then(m => m.OwnershipCard), + }, + }), +); +export const EntityUserProfileCard = orgPlugin.provide( + createComponentExtension({ + component: { + lazy: () => import('./components').then(m => m.UserProfileCard), + }, + }), +); From a06f9974f563ca706d4d20dc8ac7ccdd0ad61930 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 2 Feb 2021 22:49:12 +0100 Subject: [PATCH 14/16] user-settings: restore route ref path to fix sidebar item --- plugins/user-settings/src/plugin.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/user-settings/src/plugin.ts b/plugins/user-settings/src/plugin.ts index 5353c12f10..36d2254c11 100644 --- a/plugins/user-settings/src/plugin.ts +++ b/plugins/user-settings/src/plugin.ts @@ -21,6 +21,7 @@ import { } from '@backstage/core'; export const settingsRouteRef = createRouteRef({ + path: '/settings', title: 'Settings', }); From 93a53ea093b809d1e8ac4d721e79ff41017fa198 Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Tue, 2 Feb 2021 23:16:09 +0100 Subject: [PATCH 15/16] TechDocs: Use @deprecated for techdocs config urls --- plugins/techdocs-backend/config.d.ts | 15 ++++++++------- plugins/techdocs/config.d.ts | 24 +++++++++++++----------- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/plugins/techdocs-backend/config.d.ts b/plugins/techdocs-backend/config.d.ts index 1e4298688e..7ec1162d03 100644 --- a/plugins/techdocs-backend/config.d.ts +++ b/plugins/techdocs-backend/config.d.ts @@ -14,17 +14,12 @@ * limitations under the License. */ /** - * techdocs schema below is an abstract of what's used within techdocs-backend and for its visibility - * to view the complete techdoc schema please refer: plugins/techdocs/config.d.ts + * TechDocs schema below is an abstract of what's used within techdocs-backend and for its visibility + * to view the complete TechDocs schema please refer: plugins/techdocs/config.d.ts * */ export interface Config { /** Configuration options for the techdocs-backend plugin */ techdocs: { - /** - * attr: 'storageUrl' - accepts a string value - * e.g. storageUrl: http://localhost:7000/api/techdocs/static/docs - */ - storageUrl?: string; /** * documentation building process depends on the builder attr * attr: 'builder' - accepts a string value @@ -45,5 +40,11 @@ export interface Config { */ type: 'local' | 'googleGcs' | 'awsS3'; }; + /** + * attr: 'storageUrl' - accepts a string value + * e.g. storageUrl: http://localhost:7000/api/techdocs/static/docs + * @deprecated + */ + storageUrl?: string; }; } diff --git a/plugins/techdocs/config.d.ts b/plugins/techdocs/config.d.ts index ef4e026911..1104d75237 100644 --- a/plugins/techdocs/config.d.ts +++ b/plugins/techdocs/config.d.ts @@ -17,17 +17,6 @@ export interface Config { /** Configuration options for the techdocs plugin */ techdocs: { - /** - * attr: 'requestUrl' - accepts a string value - * e.g. requestUrl: http://localhost:7000/api/techdocs - * @visibility frontend - */ - requestUrl?: string; - /** - * attr: 'storageUrl' - accepts a string value - * e.g. storageUrl: http://localhost:7000/api/techdocs/static/docs - */ - storageUrl?: string; /** * documentation building process depends on the builder attr * attr: 'builder' - accepts a string value @@ -184,5 +173,18 @@ export interface Config { credentials?: string; }; }; + /** + * attr: 'requestUrl' - accepts a string value + * e.g. requestUrl: http://localhost:7000/api/techdocs + * @visibility frontend + * @deprecated + */ + requestUrl?: string; + /** + * attr: 'storageUrl' - accepts a string value + * e.g. storageUrl: http://localhost:7000/api/techdocs/static/docs + * @deprecated + */ + storageUrl?: string; }; } From 1ca98d7df87d956f2ce054b81bb7a069ac953bce Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Tue, 2 Feb 2021 23:28:39 +0100 Subject: [PATCH 16/16] TechDocs: Use mockFs for tests instead of real filesystem --- .../src/stages/publish/local.test.ts | 32 ++++++++----------- .../src/stages/publish/local.ts | 14 ++++---- 2 files changed, 20 insertions(+), 26 deletions(-) diff --git a/packages/techdocs-common/src/stages/publish/local.test.ts b/packages/techdocs-common/src/stages/publish/local.test.ts index 63adfad09a..cd1a17ab5f 100644 --- a/packages/techdocs-common/src/stages/publish/local.test.ts +++ b/packages/techdocs-common/src/stages/publish/local.test.ts @@ -13,15 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -/* eslint-disable no-restricted-syntax */ -import fs from 'fs-extra'; -import path from 'path'; import { getVoidLogger, PluginEndpointDiscovery, } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; +import mockFs from 'mock-fs'; +import * as os from 'os'; import { LocalPublish } from './local'; const createMockEntity = (annotations = {}) => { @@ -39,8 +37,17 @@ const createMockEntity = (annotations = {}) => { const logger = getVoidLogger(); +const tmpDir = + os.platform() === 'win32' ? 'C:\\tmp\\generatedDir' : '/tmp/generatedDir'; + describe('local publisher', () => { it('should publish generated documentation dir', async () => { + mockFs({ + [tmpDir]: { + 'index.html': '', + }, + }); + const testDiscovery: jest.Mocked = { getBaseUrl: jest .fn() @@ -52,24 +59,11 @@ describe('local publisher', () => { const publisher = new LocalPublish(mockConfig, logger, testDiscovery); const mockEntity = createMockEntity(); - const tempDir = fs.mkdtempSync(`${__dirname}/test-component-folder-`); - expect(tempDir).toBeTruthy(); - fs.closeSync(fs.openSync(path.join(tempDir, '/index.html'), 'w')); - await publisher.publish({ entity: mockEntity, directory: tempDir }); - - fs.removeSync(tempDir); - - const resultDir = path.resolve( - __dirname, - `../../../../../plugins/techdocs-backend/static/docs/default/${mockEntity.kind}/${mockEntity.metadata.name}`, - ); - - expect(fs.existsSync(resultDir)).toBeTruthy(); - expect(fs.existsSync(path.join(resultDir, '/index.html'))).toBeTruthy(); + await publisher.publish({ entity: mockEntity, directory: tmpDir }); expect(await publisher.hasDocsBeenGenerated(mockEntity)).toBe(true); - fs.removeSync(resultDir); + mockFs.restore(); }); }); diff --git a/packages/techdocs-common/src/stages/publish/local.ts b/packages/techdocs-common/src/stages/publish/local.ts index b4b30c1bf1..e349a2119a 100644 --- a/packages/techdocs-common/src/stages/publish/local.ts +++ b/packages/techdocs-common/src/stages/publish/local.ts @@ -13,17 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { + PluginEndpointDiscovery, + resolvePackagePath, +} from '@backstage/backend-common'; +import { Entity, EntityName } from '@backstage/catalog-model'; +import { Config } from '@backstage/config'; import express from 'express'; import fs from 'fs-extra'; -import path from 'path'; import os from 'os'; +import path from 'path'; import { Logger } from 'winston'; -import { Entity, EntityName } from '@backstage/catalog-model'; -import { - resolvePackagePath, - PluginEndpointDiscovery, -} from '@backstage/backend-common'; -import { Config } from '@backstage/config'; import { PublisherBase, PublishRequest,