From 9a03faa997f9131f7d28f8d1b05e171a01ee65ce Mon Sep 17 00:00:00 2001 From: Elliot Greenwood Date: Tue, 23 Feb 2021 23:42:53 +0000 Subject: [PATCH 01/66] Add namespace to the component links in the techdocs header Signed-off-by: Elliot Greenwood --- .../components/TechDocsPageHeader.test.tsx | 139 ++++++++++++------ .../reader/components/TechDocsPageHeader.tsx | 11 +- 2 files changed, 97 insertions(+), 53 deletions(-) diff --git a/plugins/techdocs/src/reader/components/TechDocsPageHeader.test.tsx b/plugins/techdocs/src/reader/components/TechDocsPageHeader.test.tsx index a2369dcfc1..8128366392 100644 --- a/plugins/techdocs/src/reader/components/TechDocsPageHeader.test.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsPageHeader.test.tsx @@ -15,44 +15,49 @@ */ import React from 'react'; import { TechDocsPageHeader } from './TechDocsPageHeader'; -import { render, act } from '@testing-library/react'; -import { wrapInTestApp } from '@backstage/test-utils'; +import { act } from '@testing-library/react'; +import { renderInTestApp } from '@backstage/test-utils'; +import { entityRouteRef } from '@backstage/plugin-catalog-react'; describe('', () => { it('should render a techdocs page header', async () => { await act(async () => { - const rendered = render( - wrapInTestApp( - , - ), + }, + }} + />, + { + mountedRoutes: { + '/catalog/:namespace/:kind/:name/*': entityRouteRef, + }, + }, ); + expect(rendered.container.innerHTML).toContain('header'); expect(rendered.getAllByText('test-site-name')).toHaveLength(2); expect(rendered.getByText('test-site-desc')).toBeDefined(); @@ -61,27 +66,65 @@ describe('', () => { it('should render a techdocs page header even if metadata is missing', async () => { await act(async () => { - const rendered = render( - wrapInTestApp( - , - ), + const rendered = await renderInTestApp( + , + { + mountedRoutes: { + '/catalog/:namespace/:kind/:name/*': entityRouteRef, + }, + }, ); expect(rendered.container.innerHTML).toContain('header'); }); }); + + it('should render a link back to the component page', async () => { + await act(async () => { + const rendered = await renderInTestApp( + , + { + mountedRoutes: { + '/catalog/:namespace/:kind/:name/*': entityRouteRef, + }, + }, + ); + + expect(rendered.container.innerHTML).toContain( + '/catalog/test-namespace/test/test-name', + ); + }); + }); }); diff --git a/plugins/techdocs/src/reader/components/TechDocsPageHeader.tsx b/plugins/techdocs/src/reader/components/TechDocsPageHeader.tsx index c994b52422..7b0c2e64cc 100644 --- a/plugins/techdocs/src/reader/components/TechDocsPageHeader.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsPageHeader.tsx @@ -18,8 +18,9 @@ import React from 'react'; import { AsyncState } from 'react-use/lib/useAsync'; import CodeIcon from '@material-ui/icons/Code'; import { EntityName } from '@backstage/catalog-model'; -import { Header, HeaderLabel, Link } from '@backstage/core'; +import { Header, HeaderLabel, Link, useRouteRef } from '@backstage/core'; import { TechDocsMetadata } from '../../types'; +import { entityRouteRef } from '@backstage/plugin-catalog-react'; type TechDocsPageHeaderProps = { entityId: EntityName; @@ -41,7 +42,7 @@ export const TechDocsPageHeader = ({ const { value: techdocsMetadataValues } = techdocsMetadata; const { value: entityMetadataValues } = entityMetadata; - const { kind, name } = entityId; + const { name } = entityId; const { site_name: siteName, site_description: siteDescription } = techdocsMetadataValues || {}; @@ -51,14 +52,14 @@ export const TechDocsPageHeader = ({ spec: { owner, lifecycle }, } = entityMetadataValues || { spec: {} }; - const componentLink = `/catalog/${kind}/${name}`; + const componentLink = useRouteRef(entityRouteRef); const labels = ( <> + {name} } @@ -92,7 +93,7 @@ export const TechDocsPageHeader = ({ siteDescription && siteDescription !== 'None' ? siteDescription : '' } type={name} - typeLink={componentLink} + typeLink={componentLink(entityId)} > {labels} From 04b2ecd23cea85ef6a989bd1f2a2b33e7bc41daf Mon Sep 17 00:00:00 2001 From: Elliot Greenwood Date: Tue, 23 Feb 2021 23:55:48 +0000 Subject: [PATCH 02/66] Add link to owner from the techdocs header Signed-off-by: Elliot Greenwood --- .../reader/components/TechDocsPageHeader.tsx | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/plugins/techdocs/src/reader/components/TechDocsPageHeader.tsx b/plugins/techdocs/src/reader/components/TechDocsPageHeader.tsx index 7b0c2e64cc..2038c74d72 100644 --- a/plugins/techdocs/src/reader/components/TechDocsPageHeader.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsPageHeader.tsx @@ -17,10 +17,10 @@ import React from 'react'; import { AsyncState } from 'react-use/lib/useAsync'; import CodeIcon from '@material-ui/icons/Code'; -import { EntityName } from '@backstage/catalog-model'; +import { EntityName, parseEntityName } from '@backstage/catalog-model'; import { Header, HeaderLabel, Link, useRouteRef } from '@backstage/core'; import { TechDocsMetadata } from '../../types'; -import { entityRouteRef } from '@backstage/plugin-catalog-react'; +import { EntityRefLink, entityRouteRef } from '@backstage/plugin-catalog-react'; type TechDocsPageHeaderProps = { entityId: EntityName; @@ -54,6 +54,11 @@ export const TechDocsPageHeader = ({ const componentLink = useRouteRef(entityRouteRef); + let ownerEntity; + if (owner) { + ownerEntity = parseEntityName(owner, { defaultKind: 'group' }); + } + const labels = ( <> } /> - {owner ? : null} + {owner ? ( + + ) : ( + owner + ) + } + /> + ) : null} {lifecycle ? : null} {locationMetadata && locationMetadata.type !== 'dir' && From 868e4cdf2c8350653cb8a102d5a28b669f8a3097 Mon Sep 17 00:00:00 2001 From: Elliot Greenwood Date: Tue, 23 Feb 2021 23:58:33 +0000 Subject: [PATCH 03/66] Add changeset Signed-off-by: Elliot Greenwood --- .changeset/techdocs-curvy-geckos-rhyme.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/techdocs-curvy-geckos-rhyme.md diff --git a/.changeset/techdocs-curvy-geckos-rhyme.md b/.changeset/techdocs-curvy-geckos-rhyme.md new file mode 100644 index 0000000000..a0a53fc127 --- /dev/null +++ b/.changeset/techdocs-curvy-geckos-rhyme.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +- Adds a link to the owner entity +- Corrects the link to the component which includes the namespace From 83883a2ea314dccc8bcabdc1bd2c2fdaac9c7390 Mon Sep 17 00:00:00 2001 From: Elliot Greenwood Date: Tue, 2 Mar 2021 16:47:29 +0000 Subject: [PATCH 04/66] Update plugins/techdocs/src/reader/components/TechDocsPageHeader.tsx Co-authored-by: Adam Harvey Signed-off-by: Elliot Greenwood --- plugins/techdocs/src/reader/components/TechDocsPageHeader.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/techdocs/src/reader/components/TechDocsPageHeader.tsx b/plugins/techdocs/src/reader/components/TechDocsPageHeader.tsx index 2038c74d72..bfab2f8515 100644 --- a/plugins/techdocs/src/reader/components/TechDocsPageHeader.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsPageHeader.tsx @@ -71,7 +71,7 @@ export const TechDocsPageHeader = ({ /> {owner ? ( Date: Wed, 24 Feb 2021 12:00:29 +0300 Subject: [PATCH 05/66] swift publisher MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mert Can Bilgiç --- .../src/stages/publish/openStackSwift.ts | 15 ++++- .../src/stages/publish/publish.ts | 6 ++ .../src/stages/publish/types.ts | 3 +- plugins/techdocs-backend/config.d.ts | 2 +- plugins/techdocs/config.d.ts | 56 +++++++++++++++++++ 5 files changed, 77 insertions(+), 5 deletions(-) diff --git a/packages/techdocs-common/src/stages/publish/openStackSwift.ts b/packages/techdocs-common/src/stages/publish/openStackSwift.ts index 3331033f66..b174122789 100644 --- a/packages/techdocs-common/src/stages/publish/openStackSwift.ts +++ b/packages/techdocs-common/src/stages/publish/openStackSwift.ts @@ -103,16 +103,19 @@ export class OpenStackSwiftPublish implements PublisherBase { this.logger = logger; } + public myName: string = 'hey'; + /** - * Upload all the files from the generated `directory` to the S3 bucket. + * Upload all the files from the generated `directory` to the OpenStack Swift container. * Directory structure used in the bucket is - entityNamespace/entityKind/entityName/index.html */ async publish({ entity, directory }: PublishRequest): Promise { + console.log(entity, directory, 'Publish hey'); try { - // Note: S3 manages creation of parent directories if they do not exist. + // Note: OpenStack Swift manages creation of parent directories if they do not exist. // So collecting path of only the files is good enough. const allFilesToUpload = await getFileTreeRecursively(directory); - + console.log(allFilesToUpload, entity, 'hey'); const limiter = createLimiter(10); const uploadPromises: Array> = []; for (const filePath of allFilesToUpload) { @@ -169,6 +172,8 @@ export class OpenStackSwiftPublish implements PublisherBase { async fetchTechDocsMetadata( entityName: EntityName, ): Promise { + console.log(entityName, 'fetchTechDocsMetadata hey'); + try { return await new Promise(async (resolve, reject) => { const entityRootDir = `${entityName.namespace}/${entityName.kind}/${entityName.name}`; @@ -208,6 +213,8 @@ export class OpenStackSwiftPublish implements PublisherBase { return async (req, res) => { // Trim the leading forward slash // filePath example - /default/Component/documented-component/index.html + console.log('docsRouter hey'); + const filePath = req.path.replace(/^\//, ''); // Files with different extensions (CSS, HTML) need to be served with different headers @@ -241,6 +248,8 @@ export class OpenStackSwiftPublish implements PublisherBase { */ async hasDocsBeenGenerated(entity: Entity): Promise { try { + console.log(entity, 'hasDocsBeenGenerated hey'); + const entityRootDir = `${entity.metadata.namespace}/${entity.kind}/${entity.metadata.name}`; this.storageClient.getFile( this.containerName, diff --git a/packages/techdocs-common/src/stages/publish/publish.ts b/packages/techdocs-common/src/stages/publish/publish.ts index a4e33a2d9c..f70a5f7626 100644 --- a/packages/techdocs-common/src/stages/publish/publish.ts +++ b/packages/techdocs-common/src/stages/publish/publish.ts @@ -22,6 +22,7 @@ import { LocalPublish } from './local'; import { GoogleGCSPublish } from './googleStorage'; import { AwsS3Publish } from './awsS3'; import { AzureBlobStoragePublish } from './azureBlobStorage'; +import { OpenStackSwiftPublish } from './openStackSwift'; type factoryOptions = { logger: Logger; @@ -53,6 +54,11 @@ export class Publisher { 'Creating Azure Blob Storage Container publisher for TechDocs', ); return AzureBlobStoragePublish.fromConfig(config, logger); + case 'openStackSwift': + logger.info( + 'Creating OpenStack Swift Container publisher for TechDocs', + ); + return OpenStackSwiftPublish.fromConfig(config, logger); case 'local': logger.info('Creating Local publisher for TechDocs'); return new LocalPublish(config, logger, discovery); diff --git a/packages/techdocs-common/src/stages/publish/types.ts b/packages/techdocs-common/src/stages/publish/types.ts index 5e953deb81..f0ecb3cb2b 100644 --- a/packages/techdocs-common/src/stages/publish/types.ts +++ b/packages/techdocs-common/src/stages/publish/types.ts @@ -23,7 +23,8 @@ export type PublisherType = | 'local' | 'googleGcs' | 'awsS3' - | 'azureBlobStorage'; + | 'azureBlobStorage' + | 'openStackSwift'; export type PublishRequest = { entity: Entity; diff --git a/plugins/techdocs-backend/config.d.ts b/plugins/techdocs-backend/config.d.ts index 5a323f244a..c704014b14 100644 --- a/plugins/techdocs-backend/config.d.ts +++ b/plugins/techdocs-backend/config.d.ts @@ -33,7 +33,7 @@ export interface Config { * Techdocs publisher information */ publisher: { - type: 'local' | 'googleGcs' | 'awsS3'; + type: 'local' | 'googleGcs' | 'awsS3' | 'openStackSwift'; }; /** diff --git a/plugins/techdocs/config.d.ts b/plugins/techdocs/config.d.ts index 1c49833d68..c4fe4a47db 100644 --- a/plugins/techdocs/config.d.ts +++ b/plugins/techdocs/config.d.ts @@ -85,6 +85,62 @@ export interface Config { region?: string; }; } + | { + type: 'openStackSwift'; + + /** + * Required when 'type' is set to awsS3 + */ + openStackSwift?: { + /** + * (Optional) Credentials used to access a storage bucket. + * If not set, environment variables or aws config file will be used to authenticate. + * @see https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/loading-node-credentials-environment.html + * @see https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/loading-node-credentials-shared.html + * @visibility secret + */ + /** + * (Required) Cloud Storage Container Name + * @visibility backend + */ + containerName: string; + /** + * (Required) Root user name + * @visibility backend + */ + username: string; + /** + * (Required) Root user password + * @visibility backend + */ + password: string; // required + /** + * (Required) Auth url sometimes OpenStack uses different port check your OpenStack apis. + * @visibility backend + */ + authUrl: string; + /** + * (Required) Auth version + * @visibility backend + */ + keystoneAuthVersion: string; + /** + * (Required) Domaind Id + * @visibility backend + */ + domainId: string; + /** + * (Required) Domaind Name + * @visibility backend + */ + domainName: 'Default'; + /** + * (Required) Region + * @visibility backend + */ + region: 'earth'; + }; + } | { type: 'azureBlobStorage'; From 0d3f211a24418d6e7d75f41b60e9a1ca43aa92bd Mon Sep 17 00:00:00 2001 From: erdoganoksuz Date: Wed, 24 Feb 2021 12:10:29 +0300 Subject: [PATCH 06/66] console MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mert Can Bilgiç --- .../techdocs-common/src/stages/publish/openStackSwift.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/packages/techdocs-common/src/stages/publish/openStackSwift.ts b/packages/techdocs-common/src/stages/publish/openStackSwift.ts index b174122789..49d7aaee23 100644 --- a/packages/techdocs-common/src/stages/publish/openStackSwift.ts +++ b/packages/techdocs-common/src/stages/publish/openStackSwift.ts @@ -110,12 +110,10 @@ export class OpenStackSwiftPublish implements PublisherBase { * Directory structure used in the bucket is - entityNamespace/entityKind/entityName/index.html */ async publish({ entity, directory }: PublishRequest): Promise { - console.log(entity, directory, 'Publish hey'); try { // Note: OpenStack Swift manages creation of parent directories if they do not exist. // So collecting path of only the files is good enough. const allFilesToUpload = await getFileTreeRecursively(directory); - console.log(allFilesToUpload, entity, 'hey'); const limiter = createLimiter(10); const uploadPromises: Array> = []; for (const filePath of allFilesToUpload) { @@ -172,8 +170,6 @@ export class OpenStackSwiftPublish implements PublisherBase { async fetchTechDocsMetadata( entityName: EntityName, ): Promise { - console.log(entityName, 'fetchTechDocsMetadata hey'); - try { return await new Promise(async (resolve, reject) => { const entityRootDir = `${entityName.namespace}/${entityName.kind}/${entityName.name}`; @@ -213,7 +209,6 @@ export class OpenStackSwiftPublish implements PublisherBase { return async (req, res) => { // Trim the leading forward slash // filePath example - /default/Component/documented-component/index.html - console.log('docsRouter hey'); const filePath = req.path.replace(/^\//, ''); @@ -248,8 +243,6 @@ export class OpenStackSwiftPublish implements PublisherBase { */ async hasDocsBeenGenerated(entity: Entity): Promise { try { - console.log(entity, 'hasDocsBeenGenerated hey'); - const entityRootDir = `${entity.metadata.namespace}/${entity.kind}/${entity.metadata.name}`; this.storageClient.getFile( this.containerName, From a42972e8f3213d17e177e3cc40f8f1b2a4e9dcf1 Mon Sep 17 00:00:00 2001 From: erdoganoksuz Date: Wed, 24 Feb 2021 12:14:14 +0300 Subject: [PATCH 07/66] remove prop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mert Can Bilgiç --- packages/techdocs-common/src/stages/publish/openStackSwift.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/techdocs-common/src/stages/publish/openStackSwift.ts b/packages/techdocs-common/src/stages/publish/openStackSwift.ts index 49d7aaee23..066e1e56d2 100644 --- a/packages/techdocs-common/src/stages/publish/openStackSwift.ts +++ b/packages/techdocs-common/src/stages/publish/openStackSwift.ts @@ -103,8 +103,6 @@ export class OpenStackSwiftPublish implements PublisherBase { this.logger = logger; } - public myName: string = 'hey'; - /** * Upload all the files from the generated `directory` to the OpenStack Swift container. * Directory structure used in the bucket is - entityNamespace/entityKind/entityName/index.html From ed3afb80bc7ee04b66e86e3913479c8bdbbbb6bc Mon Sep 17 00:00:00 2001 From: erdoganoksuz Date: Wed, 24 Feb 2021 17:15:37 +0300 Subject: [PATCH 08/66] tests setup added MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mert Can Bilgiç --- .../techdocs-common/src/stages/publish/openStackSwift.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/techdocs-common/src/stages/publish/openStackSwift.ts b/packages/techdocs-common/src/stages/publish/openStackSwift.ts index 066e1e56d2..30314e1109 100644 --- a/packages/techdocs-common/src/stages/publish/openStackSwift.ts +++ b/packages/techdocs-common/src/stages/publish/openStackSwift.ts @@ -15,7 +15,7 @@ */ import { Entity, EntityName } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; -import pkgcloud from 'pkgcloud'; +import { storage } from 'pkgcloud'; import express from 'express'; import fs from 'fs-extra'; import JSON5 from 'json5'; @@ -57,7 +57,7 @@ export class OpenStackSwiftPublish implements PublisherBase { 'techdocs.publisher.openStackSwift', ); - const storageClient = pkgcloud.storage.createClient({ + const storageClient = storage.createClient({ provider: 'openstack', username: openStackSwiftConfig.getString('username'), password: openStackSwiftConfig.getString('password'), @@ -86,7 +86,6 @@ export class OpenStackSwiftPublish implements PublisherBase { ); logger.error(`from OpenStack client library: ${err.message}`); - throw new Error(); } }); @@ -94,7 +93,7 @@ export class OpenStackSwiftPublish implements PublisherBase { } constructor( - private readonly storageClient: pkgcloud.storage.Client, + private readonly storageClient: storage.Client, private readonly containerName: string, private readonly logger: Logger, ) { From e36ef00fd0ee961e386e224362bbf1ae70040602 Mon Sep 17 00:00:00 2001 From: erdoganoksuz Date: Thu, 25 Feb 2021 11:35:39 +0300 Subject: [PATCH 09/66] router case added and hasDocsBeenGenerated fixed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mert Can Bilgiç --- .../src/stages/publish/openStackSwift.ts | 28 +++++++++++-------- .../techdocs-backend/src/service/router.ts | 1 + 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/packages/techdocs-common/src/stages/publish/openStackSwift.ts b/packages/techdocs-common/src/stages/publish/openStackSwift.ts index 30314e1109..15d8b20332 100644 --- a/packages/techdocs-common/src/stages/publish/openStackSwift.ts +++ b/packages/techdocs-common/src/stages/publish/openStackSwift.ts @@ -107,6 +107,7 @@ export class OpenStackSwiftPublish implements PublisherBase { * Directory structure used in the bucket is - entityNamespace/entityKind/entityName/index.html */ async publish({ entity, directory }: PublishRequest): Promise { + this.logger.info(`Publish Called hey`); try { // Note: OpenStack Swift manages creation of parent directories if they do not exist. // So collecting path of only the files is good enough. @@ -167,6 +168,7 @@ export class OpenStackSwiftPublish implements PublisherBase { async fetchTechDocsMetadata( entityName: EntityName, ): Promise { + this.logger.info(`fetchTechDocsMetadata Called hey`); try { return await new Promise(async (resolve, reject) => { const entityRootDir = `${entityName.namespace}/${entityName.kind}/${entityName.name}`; @@ -204,6 +206,7 @@ export class OpenStackSwiftPublish implements PublisherBase { */ docsRouter(): express.Handler { return async (req, res) => { + this.logger.info(`docsRouter Called hey`); // Trim the leading forward slash // filePath example - /default/Component/documented-component/index.html @@ -240,18 +243,21 @@ export class OpenStackSwiftPublish implements PublisherBase { */ async hasDocsBeenGenerated(entity: Entity): Promise { try { + this.logger.info(`hasDocsBeenGenerated Called hey`); const entityRootDir = `${entity.metadata.namespace}/${entity.kind}/${entity.metadata.name}`; - this.storageClient.getFile( - this.containerName, - `${entityRootDir}/index.html`, - (err: any, file: any) => { - if (!err && file) { - return Promise.resolve(true); - } - return Promise.resolve(false); - }, - ); - return Promise.resolve(true); + + return new Promise(res => { + this.storageClient.getFile( + this.containerName, + `${entityRootDir}/index.html`, + (err: any, file: any) => { + console.log(file); + if (!err && file) { + res(true); + } else res(false); + }, + ); + }); } catch (e) { return Promise.resolve(false); } diff --git a/plugins/techdocs-backend/src/service/router.ts b/plugins/techdocs-backend/src/service/router.ts index 7f6ff60fdd..b331d2170c 100644 --- a/plugins/techdocs-backend/src/service/router.ts +++ b/plugins/techdocs-backend/src/service/router.ts @@ -155,6 +155,7 @@ export async function createRouter({ break; case 'awsS3': case 'azureBlobStorage': + case 'openStackSwift': case 'googleGcs': // This block should be valid for all external storage implementations. So no need to duplicate in future, // add the publisher type in the list here. From 75ec0e2358fec86f08ef95b8b9ee716be5d50c47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Can=20Bilgi=C3=A7?= Date: Thu, 25 Feb 2021 10:48:25 +0300 Subject: [PATCH 10/66] mocked openstackswift publisher and created mocks first test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mert Can Bilgiç --- .../techdocs-common/__mocks__/pkgcloud.ts | 94 +++++++++++++++++++ .../src/stages/publish/publish.test.ts | 25 +++++ 2 files changed, 119 insertions(+) create mode 100644 packages/techdocs-common/__mocks__/pkgcloud.ts diff --git a/packages/techdocs-common/__mocks__/pkgcloud.ts b/packages/techdocs-common/__mocks__/pkgcloud.ts new file mode 100644 index 0000000000..3ea3c251e0 --- /dev/null +++ b/packages/techdocs-common/__mocks__/pkgcloud.ts @@ -0,0 +1,94 @@ +/* + * 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 { OpenstackProviderOptions } from 'pkgcloud'; +import fs from 'fs-extra'; +import os from 'os'; +import path from 'path'; + +const rootDir = os.platform() === 'win32' ? 'C:\\rootDir' : '/rootDir'; + +const checkFileExists = async (Key: string): Promise => { + // Key will always have / as file separator irrespective of OS since S3 expects /. + // Normalize Key to OS specific path before checking if file exists. + const relativeFilePath = Key.split(path.posix.sep).join(path.sep); + const filePath = path.join(rootDir, Key); + + try { + await fs.access(filePath, fs.constants.F_OK); + return true; + } catch (err) { + return false; + } +}; + +class PkgCloudStorageClient { + getFile( + containerName: string, + file: string, + callback: (err: string, file: string) => any, + ) { + checkFileExists(file).then(res => { + if (!res) { + callback('File does not exist', undefined); + throw new Error('File does not exist'); + } else { + callback(undefined, 'success'); + } + }); + } + + getContainer( + containerName: string, + callback: (err: string, container: string) => any, + ) { + if (containerName !== 'mock') { + callback("Container doesn't exist", undefined); + throw new Error('Container does not exist'); + } else { + callback(undefined, 'success'); + } + } + + upload({ containerName, remote }: { containerName: string; remote: string }) { + checkFileExists(remote).then(res => { + if (!res) { + return new Error("File doesn't exists"); + } + return fs.createWriteStream(`${containerName}/${remote}`); + }); + } + + download({ + containerName, + remote, + }: { + containerName: string; + remote: string; + }) { + checkFileExists(remote).then(res => { + if (!res) { + return new Error("File doesn't exists"); + } + return fs.createReadStream(remote); + }); + } +} + +export class storage { + static createClient(params: OpenstackProviderOptions) { + return new PkgCloudStorageClient(); + } +} diff --git a/packages/techdocs-common/src/stages/publish/publish.test.ts b/packages/techdocs-common/src/stages/publish/publish.test.ts index d1faf82b78..f9bd0f3934 100644 --- a/packages/techdocs-common/src/stages/publish/publish.test.ts +++ b/packages/techdocs-common/src/stages/publish/publish.test.ts @@ -23,6 +23,7 @@ import { LocalPublish } from './local'; import { GoogleGCSPublish } from './googleStorage'; import { AwsS3Publish } from './awsS3'; import { AzureBlobStoragePublish } from './azureBlobStorage'; +import { OpenStackSwiftPublish } from './openStackSwift'; const logger = getVoidLogger(); const discovery: jest.Mocked = { @@ -161,4 +162,28 @@ describe('Publisher', () => { }); expect(publisher).toBeInstanceOf(AzureBlobStoragePublish); }); + + it('should create Open Stack Swift publisher from config', async () => { + const mockConfig = new ConfigReader({ + techdocs: { + requestUrl: 'http://localhost:7000', + publisher: { + type: 'openStackSwift', + openStackSwift: { + username: 'mockuser', + password: 'verystrongpass', + authUrl: 'mockauthurl', + region: 'mockregion', + containerName: 'mock', + }, + }, + }, + }); + + const publisher = await Publisher.fromConfig(mockConfig, { + logger, + discovery, + }); + expect(publisher).toBeInstanceOf(OpenStackSwiftPublish); + }); }); From 32fac33d8d8e46b8dc90837f9e75bd589edcf88c Mon Sep 17 00:00:00 2001 From: gmzsenturk Date: Thu, 25 Feb 2021 15:29:23 +0300 Subject: [PATCH 11/66] OpenStackSwift tests added to project MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mert Can Bilgiç --- .../techdocs-common/__mocks__/pkgcloud.ts | 41 +-- packages/techdocs-common/package.json | 1 + .../src/stages/publish/openStackSwift.test.ts | 248 ++++++++++++++++++ .../src/stages/publish/openStackSwift.ts | 8 +- yarn.lock | 181 +++++++++++-- 5 files changed, 424 insertions(+), 55 deletions(-) create mode 100644 packages/techdocs-common/src/stages/publish/openStackSwift.test.ts diff --git a/packages/techdocs-common/__mocks__/pkgcloud.ts b/packages/techdocs-common/__mocks__/pkgcloud.ts index 3ea3c251e0..bf729a7bd2 100644 --- a/packages/techdocs-common/__mocks__/pkgcloud.ts +++ b/packages/techdocs-common/__mocks__/pkgcloud.ts @@ -13,21 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { OpenstackProviderOptions } from 'pkgcloud'; import fs from 'fs-extra'; import os from 'os'; import path from 'path'; +import { ObjectWritableMock, BufferReadableMock } from 'stream-mock'; const rootDir = os.platform() === 'win32' ? 'C:\\rootDir' : '/rootDir'; const checkFileExists = async (Key: string): Promise => { // Key will always have / as file separator irrespective of OS since S3 expects /. // Normalize Key to OS specific path before checking if file exists. - const relativeFilePath = Key.split(path.posix.sep).join(path.sep); const filePath = path.join(rootDir, Key); try { - await fs.access(filePath, fs.constants.F_OK); + fs.accessSync(filePath, fs.constants.F_OK); return true; } catch (err) { return false; @@ -38,11 +37,11 @@ class PkgCloudStorageClient { getFile( containerName: string, file: string, - callback: (err: string, file: string) => any, + callback: (err: any, file: string) => any, ) { checkFileExists(file).then(res => { if (!res) { - callback('File does not exist', undefined); + callback('File does not exist', file); throw new Error('File does not exist'); } else { callback(undefined, 'success'); @@ -55,40 +54,28 @@ class PkgCloudStorageClient { callback: (err: string, container: string) => any, ) { if (containerName !== 'mock') { - callback("Container doesn't exist", undefined); + callback("Container doesn't exist", containerName); throw new Error('Container does not exist'); } else { - callback(undefined, 'success'); + callback('Container does not exist', 'success'); } } - upload({ containerName, remote }: { containerName: string; remote: string }) { - checkFileExists(remote).then(res => { - if (!res) { - return new Error("File doesn't exists"); - } - return fs.createWriteStream(`${containerName}/${remote}`); - }); + upload() { + return new ObjectWritableMock(); } - download({ - containerName, - remote, - }: { - containerName: string; - remote: string; - }) { - checkFileExists(remote).then(res => { - if (!res) { - return new Error("File doesn't exists"); - } - return fs.createReadStream(remote); + download() { + const stringify = JSON.stringify({ + "site_description": 'site_content', + "site_name": "backstage" }); + return new BufferReadableMock([stringify]); } } export class storage { - static createClient(params: OpenstackProviderOptions) { + static createClient() { return new PkgCloudStorageClient(); } } diff --git a/packages/techdocs-common/package.json b/packages/techdocs-common/package.json index a543addfa9..2598de1dce 100644 --- a/packages/techdocs-common/package.json +++ b/packages/techdocs-common/package.json @@ -58,6 +58,7 @@ "p-limit": "^3.1.0", "pkgcloud": "^2.2.0", "recursive-readdir": "^2.2.2", + "stream-mock": "^2.0.5", "winston": "^3.2.1" }, "devDependencies": { diff --git a/packages/techdocs-common/src/stages/publish/openStackSwift.test.ts b/packages/techdocs-common/src/stages/publish/openStackSwift.test.ts new file mode 100644 index 0000000000..fab0bf3a12 --- /dev/null +++ b/packages/techdocs-common/src/stages/publish/openStackSwift.test.ts @@ -0,0 +1,248 @@ +/* + * 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, + EntityName, + ENTITY_DEFAULT_NAMESPACE, +} from '@backstage/catalog-model'; +import { ConfigReader } from '@backstage/config'; +import mockFs from 'mock-fs'; +import os from 'os'; +import path from 'path'; +import * as winston from 'winston'; +import { OpenStackSwiftPublish } from './openStackSwift'; +import { PublisherBase, TechDocsMetadata } from './types'; + +// NOTE: /packages/techdocs-common/__mocks__ is being used to mock pkgcloud client library + +const createMockEntity = (annotations = {}): Entity => { + return { + apiVersion: 'version', + kind: 'TestKind', + metadata: { + name: 'test-component-name', + namespace: 'test-namespace', + annotations: { + ...annotations, + }, + }, + }; +}; + +const createMockEntityName = (): EntityName => ({ + kind: 'TestKind', + name: 'test-component-name', + namespace: 'test-namespace', +}); + +const rootDir = os.platform() === 'win32' ? 'C:\\rootDir' : '/rootDir'; + +const getEntityRootDir = (entity: Entity) => { + const { + kind, + metadata: { namespace, name }, + } = entity; + + return path.join(rootDir, namespace || ENTITY_DEFAULT_NAMESPACE, kind, name); +}; + +const logger = winston.createLogger(); +jest.spyOn(logger, 'info').mockReturnValue(logger); +jest.spyOn(logger, 'error').mockReturnValue(logger); + +let publisher: PublisherBase; + +beforeEach(() => { + mockFs.restore(); + const mockConfig = new ConfigReader({ + techdocs: { + requestUrl: 'http://localhost:7000', + publisher: { + type: 'openStackSwift', + openStackSwift: { + username: 'mockuser', + password: 'verystrongpass', + authUrl: 'mockauthurl', + region: 'mockregion', + containerName: 'mock', + }, + }, + }, + }); + + publisher = OpenStackSwiftPublish.fromConfig(mockConfig, logger); +}); + +describe('OpenStackSwiftPublish', () => { + describe('publish', () => { + beforeEach(() => { + const entity = createMockEntity(); + const entityRootDir = getEntityRootDir(entity); + + mockFs({ + [entityRootDir]: { + 'index.html': '', + '404.html': '', + assets: { + 'main.css': '', + }, + }, + }); + }); + + afterEach(() => { + mockFs.restore(); + }); + + it('should publish a directory', async () => { + const entity = createMockEntity(); + const entityRootDir = getEntityRootDir(entity); + + setTimeout(async () => { + expect( + await publisher.publish({ + entity, + directory: entityRootDir, + }), + ).toBeUndefined() + }, 5000); + }); + + it('should fail to publish a directory', async () => { + expect.assertions(3); + const wrongPathToGeneratedDirectory = path.join( + rootDir, + 'wrong', + 'path', + 'to', + 'generatedDirectory', + ); + + const entity = createMockEntity(); + await expect( + publisher.publish({ + entity, + directory: wrongPathToGeneratedDirectory, + }), + ).rejects.toThrowError(); + + await publisher + .publish({ + entity, + directory: wrongPathToGeneratedDirectory, + }) + .catch(error => { + expect(error.message).toEqual( + // Can not do exact error message match due to mockFs adding unexpected characters in the path when throwing the error + // Issue reported https://github.com/tschaub/mock-fs/issues/118 + expect.stringContaining( + `Unable to upload file(s) to OpenStack Swift. Error: Failed to read template directory: ENOENT, no such file or directory`, + ), + ); + expect(error.message).toEqual( + expect.stringContaining(wrongPathToGeneratedDirectory), + ); + }); + mockFs.restore(); + }); + }); + + describe('hasDocsBeenGenerated', () => { + it('should return true if docs has been generated', async () => { + const entity = createMockEntity(); + const entityRootDir = getEntityRootDir(entity); + + mockFs({ + [entityRootDir]: { + 'index.html': 'file-content', + }, + }); + + expect(await publisher.hasDocsBeenGenerated(entity)).toBe(true); + mockFs.restore(); + }); + + it('should return false if docs has not been generated', async () => { + const entity = createMockEntity(); + + expect(await publisher.hasDocsBeenGenerated(entity)).toBe(false); + }); + }); + + describe('fetchTechDocsMetadata', () => { + it('should return tech docs metadata', async () => { + const entityNameMock = createMockEntityName(); + const entity = createMockEntity(); + const entityRootDir = getEntityRootDir(entity); + + mockFs({ + [entityRootDir]: { + 'techdocs_metadata.json': + '{"site_name": "backstage", "site_description": "site_content"}', + }, + }); + + const expectedMetadata: TechDocsMetadata = { + site_name: 'backstage', + site_description: 'site_content', + }; + expect( + await publisher.fetchTechDocsMetadata(entityNameMock), + ).toStrictEqual(expectedMetadata); + mockFs.restore(); + }); + + it('should return tech docs metadata when json encoded with single quotes', async () => { + const entityNameMock = createMockEntityName(); + const entity = createMockEntity(); + const entityRootDir = getEntityRootDir(entity); + + mockFs({ + [entityRootDir]: { + 'techdocs_metadata.json': `{'site_name': 'backstage', 'site_description': 'site_content'}`, + }, + }); + + const expectedMetadata: TechDocsMetadata = { + site_name: 'backstage', + site_description: 'site_content', + }; + expect( + await publisher.fetchTechDocsMetadata(entityNameMock), + ).toStrictEqual(expectedMetadata); + mockFs.restore(); + }); + + it('should return an error if the techdocs_metadata.json file is not present', async () => { + const entityNameMock = createMockEntityName(); + const entity = createMockEntity(); + const entityRootDir = getEntityRootDir(entity); + + await publisher + .fetchTechDocsMetadata(entityNameMock) + .catch(error => + expect(error).toEqual( + new Error( + `TechDocs metadata fetch failed, The file ${path.join( + entityRootDir, + 'techdocs_metadata.json', + )} does not exist !`, + ), + ), + ); + }); + }); +}); diff --git a/packages/techdocs-common/src/stages/publish/openStackSwift.ts b/packages/techdocs-common/src/stages/publish/openStackSwift.ts index 15d8b20332..60c8349dee 100644 --- a/packages/techdocs-common/src/stages/publish/openStackSwift.ts +++ b/packages/techdocs-common/src/stages/publish/openStackSwift.ts @@ -107,7 +107,6 @@ export class OpenStackSwiftPublish implements PublisherBase { * Directory structure used in the bucket is - entityNamespace/entityKind/entityName/index.html */ async publish({ entity, directory }: PublishRequest): Promise { - this.logger.info(`Publish Called hey`); try { // Note: OpenStack Swift manages creation of parent directories if they do not exist. // So collecting path of only the files is good enough. @@ -139,8 +138,7 @@ export class OpenStackSwiftPublish implements PublisherBase { }; // Rate limit the concurrent execution of file uploads to batches of 10 (per publish) - const uploadFile = limiter( - () => + const uploadFile = limiter(() => new Promise((res, rej) => { const writeStream = this.storageClient.upload(params); @@ -168,7 +166,6 @@ export class OpenStackSwiftPublish implements PublisherBase { async fetchTechDocsMetadata( entityName: EntityName, ): Promise { - this.logger.info(`fetchTechDocsMetadata Called hey`); try { return await new Promise(async (resolve, reject) => { const entityRootDir = `${entityName.namespace}/${entityName.kind}/${entityName.name}`; @@ -206,7 +203,6 @@ export class OpenStackSwiftPublish implements PublisherBase { */ docsRouter(): express.Handler { return async (req, res) => { - this.logger.info(`docsRouter Called hey`); // Trim the leading forward slash // filePath example - /default/Component/documented-component/index.html @@ -243,7 +239,6 @@ export class OpenStackSwiftPublish implements PublisherBase { */ async hasDocsBeenGenerated(entity: Entity): Promise { try { - this.logger.info(`hasDocsBeenGenerated Called hey`); const entityRootDir = `${entity.metadata.namespace}/${entity.kind}/${entity.metadata.name}`; return new Promise(res => { @@ -251,7 +246,6 @@ export class OpenStackSwiftPublish implements PublisherBase { this.containerName, `${entityRootDir}/index.html`, (err: any, file: any) => { - console.log(file); if (!err && file) { res(true); } else res(false); diff --git a/yarn.lock b/yarn.lock index f941b3ee7c..5b7893c852 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1816,35 +1816,78 @@ to-fast-properties "^2.0.0" "@backstage/catalog-model@^0.2.0": - version "0.7.2" + version "0.2.0" + resolved "https://registry.npmjs.org/@backstage/catalog-model/-/catalog-model-0.2.0.tgz#e3fe2a4ddeb6a9b6ec480c80cb2b9c39cb245576" + integrity sha512-Y1ocdRpBlxK/VrJQjHlQd0bgADECd1B2NRjwd8ss46ibT5hwLvMOfD80+Fa7oPLu0ktJrH4lq0pNIIJIml48zA== dependencies: - "@backstage/config" "^0.1.3" + "@backstage/config" "^0.1.1" "@types/json-schema" "^7.0.5" "@types/yup" "^0.29.8" - ajv "^7.0.3" json-schema "^0.2.5" lodash "^4.17.15" uuid "^8.0.0" yup "^0.29.3" "@backstage/catalog-model@^0.3.0": - version "0.7.2" + version "0.3.1" + resolved "https://registry.npmjs.org/@backstage/catalog-model/-/catalog-model-0.3.1.tgz#45d08e2f333c9c566b2bf2629fd707fe989bb404" + integrity sha512-9XhV7c4rmVW+Yzj2PiwTQ7DsegWGB3C4ELsDRExuEVZONdqNcC02cyJtrt3fT5F31ZS3tHkB9bMUymFOBLqUSA== dependencies: - "@backstage/config" "^0.1.3" + "@backstage/config" "^0.1.1" "@types/json-schema" "^7.0.5" "@types/yup" "^0.29.8" - ajv "^7.0.3" json-schema "^0.2.5" lodash "^4.17.15" uuid "^8.0.0" yup "^0.29.3" "@backstage/core@^0.3.0": - version "0.6.3" + version "0.3.2" + resolved "https://registry.npmjs.org/@backstage/core/-/core-0.3.2.tgz#a8209126d5076cf4a8b9bd632fe4e5e2edb62916" + integrity sha512-i5d+Wh8js4qEWoAsPY5L7HVSWpumr1OhfF2dUCGYdyW6AMqVJPca6+n6zp1Rg2CO+J9norp44XAVVCbyhtUpig== dependencies: - "@backstage/config" "^0.1.3" - "@backstage/core-api" "^0.2.11" - "@backstage/theme" "^0.2.3" + "@backstage/config" "^0.1.1" + "@backstage/core-api" "^0.2.1" + "@backstage/theme" "^0.2.1" + "@material-ui/core" "^4.11.0" + "@material-ui/icons" "^4.9.1" + "@material-ui/lab" "4.0.0-alpha.45" + "@types/dagre" "^0.7.44" + "@types/react" "^16.9" + "@types/react-sparklines" "^1.7.0" + classnames "^2.2.6" + clsx "^1.1.0" + d3-selection "^2.0.0" + d3-shape "^2.0.0" + d3-zoom "^2.0.0" + dagre "^0.8.5" + immer "^7.0.9" + lodash "^4.17.15" + material-table "^1.69.1" + prop-types "^15.7.2" + qs "^6.9.4" + rc-progress "^3.0.0" + react "^16.12.0" + react-dom "^16.12.0" + react-helmet "6.1.0" + react-hook-form "^6.6.0" + react-markdown "^5.0.2" + react-router "6.0.0-beta.0" + react-router-dom "6.0.0-beta.0" + react-sparklines "^1.7.0" + react-syntax-highlighter "^13.5.1" + react-use "^15.3.3" + remark-gfm "^1.0.0" + zen-observable "^0.8.15" + +"@backstage/core@^0.5.0": + version "0.5.0" + resolved "https://registry.npmjs.org/@backstage/core/-/core-0.5.0.tgz#6ff384adc595c18c7db60b9b2d23ebbb9086ed36" + integrity sha512-lCxgKBavUlLYZjZmRF8A7koP4NUhK/tbdf9SaEod0miZg6JTaDoAm3dmHPyqrMBHgoRRCDTxRIxNhj/8vY87oA== + dependencies: + "@backstage/config" "^0.1.2" + "@backstage/core-api" "^0.2.8" + "@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" @@ -1877,20 +1920,53 @@ remark-gfm "^1.0.0" zen-observable "^0.8.15" -"@backstage/plugin-catalog@^0.2.1": - version "0.4.0" +"@backstage/plugin-catalog-react@^0.0.2": + version "0.0.2" + resolved "https://registry.npmjs.org/@backstage/plugin-catalog-react/-/plugin-catalog-react-0.0.2.tgz#e50da2dac9fab3a0d5973f8d1083ee2c368e5e52" + integrity sha512-O6aujFPRaEFTk4XlwOoswbnoHIOqMtj6ycUj6R1mNKOM4plUgGDKKhO3be69FHMJEMbiSvVe6AW+1kXaK+1LqA== + dependencies: + "@backstage/catalog-client" "^0.3.5" + "@backstage/catalog-model" "^0.7.1" + "@backstage/core" "^0.6.0" + "@material-ui/core" "^4.11.0" + "@types/react" "^16.9" + react "^16.13.1" + react-router "6.0.0-beta.0" + react-router-dom "6.0.0-beta.0" + react-use "^15.3.3" + +"@backstage/plugin-catalog-react@^0.0.4": + version "0.0.4" + resolved "https://registry.npmjs.org/@backstage/plugin-catalog-react/-/plugin-catalog-react-0.0.4.tgz#a4c8ba90cf48106ac6af2e03afa6338010a1299b" + integrity sha512-1fAqULJvLyE+3SeZ2yxDJnJ3SbUFv2Im55d3KbMgRaSog1chSJJoO3jbIwIRQIBXjRmCXrZbf56qwwWwxj6OjA== dependencies: "@backstage/catalog-client" "^0.3.6" - "@backstage/catalog-model" "^0.7.2" - "@backstage/core" "^0.6.3" - "@backstage/plugin-catalog-react" "^0.1.0" - "@backstage/theme" "^0.2.3" + "@backstage/catalog-model" "^0.7.1" + "@backstage/core" "^0.6.2" + "@material-ui/core" "^4.11.0" + "@types/react" "^16.9" + react "^16.13.1" + react-router "6.0.0-beta.0" + react-router-dom "6.0.0-beta.0" + react-use "^15.3.3" + +"@backstage/plugin-catalog@^0.2.1": + version "0.2.14" + resolved "https://registry.npmjs.org/@backstage/plugin-catalog/-/plugin-catalog-0.2.14.tgz#50a4176a55ffa543a426ec78cbc9deaecdbcf2b7" + integrity sha512-lDmNcC+m1zbbzYATUp5yIZ5PUp+YyBc1KKu3CCgqjLWSbJ1aJrU1N4g59euel1l2+qSW+lH76Kkp6ZYpZbSO9A== + dependencies: + "@backstage/catalog-client" "^0.3.5" + "@backstage/catalog-model" "^0.7.0" + "@backstage/core" "^0.5.0" + "@backstage/plugin-scaffolder" "^0.4.1" + "@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" "@types/react" "^16.9" classnames "^2.2.6" git-url-parse "^11.4.4" + moment "^2.26.0" react "^16.13.1" react-dom "^16.13.1" react-helmet "6.1.0" @@ -1900,12 +1976,15 @@ swr "^0.3.0" "@backstage/plugin-catalog@^0.3.1": - version "0.4.0" + version "0.3.2" + resolved "https://registry.npmjs.org/@backstage/plugin-catalog/-/plugin-catalog-0.3.2.tgz#06945f10fd678efdade3f2795590c12433568fa0" + integrity sha512-iHLxPHRN9nYIXwOEAQ06m+PagsFb6Nb/XjJSebCAnSrAxPPITvBCfxc2H1GbyyMdC7KAr1ozORB/FFkNsCaQJg== dependencies: "@backstage/catalog-client" "^0.3.6" - "@backstage/catalog-model" "^0.7.2" - "@backstage/core" "^0.6.3" - "@backstage/plugin-catalog-react" "^0.1.0" + "@backstage/catalog-model" "^0.7.1" + "@backstage/core" "^0.6.2" + "@backstage/plugin-catalog-react" "^0.0.4" + "@backstage/plugin-scaffolder" "^0.5.1" "@backstage/theme" "^0.2.3" "@material-ui/core" "^4.11.0" "@material-ui/icons" "^4.9.1" @@ -1946,6 +2025,56 @@ react-use "^15.3.3" swr "^0.3.0" +"@backstage/plugin-scaffolder@^0.4.1": + version "0.4.2" + resolved "https://registry.npmjs.org/@backstage/plugin-scaffolder/-/plugin-scaffolder-0.4.2.tgz#58159227997f7e248ce52535bc32f19fcd0990dc" + integrity sha512-YuyHM587Rqg6KufxfFqQdI7dsZniBM/11Aj8Q0m5ZszOpCuNmDDkR1VX8MKHTBJ709mnLAqRgArdla7FOrOAXQ== + dependencies: + "@backstage/catalog-model" "^0.7.1" + "@backstage/core" "^0.6.0" + "@backstage/plugin-catalog-react" "^0.0.2" + "@backstage/theme" "^0.2.3" + "@material-ui/core" "^4.11.0" + "@material-ui/icons" "^4.9.1" + "@material-ui/lab" "4.0.0-alpha.45" + "@rjsf/core" "^2.4.0" + "@rjsf/material-ui" "^2.4.0" + classnames "^2.2.6" + git-url-parse "^11.4.4" + moment "^2.26.0" + react "^16.13.1" + react-dom "^16.13.1" + react-lazylog "^4.5.2" + react-router "6.0.0-beta.0" + react-router-dom "6.0.0-beta.0" + react-use "^15.3.3" + swr "^0.3.0" + +"@backstage/plugin-scaffolder@^0.5.1": + version "0.5.1" + resolved "https://registry.npmjs.org/@backstage/plugin-scaffolder/-/plugin-scaffolder-0.5.1.tgz#9d36f6b01991ddd9f9f2996068f3f31c766db8db" + integrity sha512-EG+iUc107bneVBPQpFKGp2jD9Y9+x50g/gY6TBN1je8TkgyluoxMj7wKv9e+d4TeGzXwK/LW/suajG9Zo0TJGQ== + dependencies: + "@backstage/catalog-model" "^0.7.1" + "@backstage/core" "^0.6.2" + "@backstage/plugin-catalog-react" "^0.0.4" + "@backstage/theme" "^0.2.3" + "@material-ui/core" "^4.11.0" + "@material-ui/icons" "^4.9.1" + "@material-ui/lab" "4.0.0-alpha.45" + "@rjsf/core" "^2.4.0" + "@rjsf/material-ui" "^2.4.0" + classnames "^2.2.6" + git-url-parse "^11.4.4" + moment "^2.26.0" + react "^16.13.1" + react-dom "^16.13.1" + react-lazylog "^4.5.2" + react-router "6.0.0-beta.0" + react-router-dom "6.0.0-beta.0" + react-use "^15.3.3" + swr "^0.3.0" + "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" @@ -15076,6 +15205,11 @@ immer@1.10.0: resolved "https://registry.npmjs.org/immer/-/immer-1.10.0.tgz#bad67605ba9c810275d91e1c2a47d4582e98286d" integrity sha512-O3sR1/opvCDGLEVcvrGTMtLac8GJ5IwZC4puPrLuRj3l7ICKvkmA0vGuU9OW8mV9WIBRnaxp5GJh9IEAaNOoYg== +immer@^7.0.9: + version "7.0.15" + resolved "https://registry.npmjs.org/immer/-/immer-7.0.15.tgz#dc3bc6db87401659d2e737c67a21b227c484a4ad" + integrity sha512-yM7jo9+hvYgvdCQdqvhCNRRio0SCXc8xDPzA25SvKWa7b1WVPjLwQs1VYU5JPXjcJPTqAa5NP5dqpORGYBQ2AA== + immer@^8.0.1: version "8.0.1" resolved "https://registry.npmjs.org/immer/-/immer-8.0.1.tgz#9c73db683e2b3975c424fb0572af5889877ae656" @@ -18630,7 +18764,7 @@ modify-values@^1.0.0: resolved "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022" integrity sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw== -moment@^2.19.3, moment@^2.25.3, moment@^2.27.0: +moment@^2.19.3, moment@^2.25.3, moment@^2.26.0, moment@^2.27.0: version "2.29.1" resolved "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3" integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ== @@ -24025,6 +24159,11 @@ stream-http@^2.7.2: to-arraybuffer "^1.0.0" xtend "^4.0.0" +stream-mock@^2.0.5: + version "2.0.5" + resolved "https://registry.npmjs.org/stream-mock/-/stream-mock-2.0.5.tgz#c99d24bd6dbb0eaa57cf6ffefdb064150747826e" + integrity sha512-dx9skT8QYjwLsal+MhGHr4UtgS49brw851C/oTixmhCi4Ip+/qnZmhV1qOcznYYAED6gYKmKea+jjza4/wjpSg== + stream-shift@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" From cebda81000ec35d680d1c6d0a8f98eb6e4ff9a8c Mon Sep 17 00:00:00 2001 From: erdoganoksuz Date: Thu, 25 Feb 2021 17:24:49 +0300 Subject: [PATCH 12/66] documentation added MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mert Can Bilgiç --- docs/features/techdocs/using-cloud-storage.md | 91 +++++++++ .../techdocs-common/__mocks__/pkgcloud.ts | 44 ++++- packages/techdocs-common/package.json | 3 +- .../src/stages/publish/openStackSwift.test.ts | 14 +- .../src/stages/publish/openStackSwift.ts | 24 +-- plugins/techdocs-backend/package.json | 2 +- yarn.lock | 184 +++--------------- 7 files changed, 171 insertions(+), 191 deletions(-) diff --git a/docs/features/techdocs/using-cloud-storage.md b/docs/features/techdocs/using-cloud-storage.md index cd4b058644..66e6e15811 100644 --- a/docs/features/techdocs/using-cloud-storage.md +++ b/docs/features/techdocs/using-cloud-storage.md @@ -309,3 +309,94 @@ and read the static generated documentation files. When you start the backend of the app, you should be able to see `techdocs info Successfully connected to the Azure Blob Storage container` in the logs. + +## Configuring OpenStack Swift Container with TechDocs + +Follow the +[official OpenStack Api documentation](https://docs.openstack.org/api-ref/identity/v3/) +for the latest instructions on the following steps involving Azure Blob Storage. + +**1. Set `techdocs.publisher.type` config in your `app-config.yaml`** + +Set `techdocs.publisher.type` to `'openStackSwift'`. + +```yaml +techdocs: + publisher: + type: 'openStackSwift' +``` + +**2. Create an Azure Blob Storage Container** + +Create a dedicated container for TechDocs sites. +[Refer to the official documentation](https://docs.openstack.org/mitaka/user-guide/dashboard_manage_containers.html). + +TechDocs will publish documentation to this container and will fetch files from +here to serve documentation in Backstage. Note that the container names are +globally unique. + +Set the config `techdocs.publisher.openStackSwift.containerName` in your +`app-config.yaml` to the name of the container you just created. + +```yaml +techdocs: + publisher: + type: 'openStackSwift' + openStackSwift: + containerName: 'name-of-techdocs-storage-container' +``` + +**3a. (Recommended) Authentication using environment variable** + +Set the config `techdocs.publisher.openStackSwift.accountName` in +your `app-config.yaml` to the your account name. + +The storage blob client will automatically use the environment variable +`AZURE_TENANT_ID`, `AZURE_CLIENT_ID`, `AZURE_CLIENT_SECRET` to authenticate with +Azure Blob Storage. +[Steps to create the service where the variables can be retrieved from](https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal). + +https://docs.microsoft.com/en-us/azure/storage/common/storage-auth-aad for more +details. + +```yaml +techdocs: + publisher: + type: 'azureBlobStorage' + azureBlobStorage: + containerName: 'name-of-techdocs-storage-bucket' + credentials: + accountName: + $env: TECHDOCS_AZURE_BLOB_STORAGE_ACCOUNT_NAME +``` + +**3b. Authentication using app-config.yaml** + +If you do not prefer (3a) and optionally like to use a service account, you can +follow these steps. + +To get credentials, access the Azure Portal and go to "Settings > Access Keys", +and get your Storage account name and Primary Key. +https://docs.microsoft.com/en-us/rest/api/storageservices/authorize-with-shared-key +for more details. + +```yaml +techdocs: + publisher: + type: 'azureBlobStorage' + azureBlobStorage: + containerName: 'name-of-techdocs-storage-bucket' + credentials: + accountName: + $env: TECHDOCS_AZURE_BLOB_STORAGE_ACCOUNT_NAME + accountKey: + $env: TECHDOCS_AZURE_BLOB_STORAGE_ACCOUNT_KEY +``` + +**4. That's it!** + +Your Backstage app is now ready to use Azure Blob Storage for TechDocs, to store +and read the static generated documentation files. When you start the backend of +the app, you should be able to see +`techdocs info Successfully connected to the Azure Blob Storage container` in +the logs. diff --git a/packages/techdocs-common/__mocks__/pkgcloud.ts b/packages/techdocs-common/__mocks__/pkgcloud.ts index bf729a7bd2..4316630928 100644 --- a/packages/techdocs-common/__mocks__/pkgcloud.ts +++ b/packages/techdocs-common/__mocks__/pkgcloud.ts @@ -16,7 +16,7 @@ import fs from 'fs-extra'; import os from 'os'; import path from 'path'; -import { ObjectWritableMock, BufferReadableMock } from 'stream-mock'; +import { EventEmitter } from 'events'; const rootDir = os.platform() === 'win32' ? 'C:\\rootDir' : '/rootDir'; @@ -61,16 +61,44 @@ class PkgCloudStorageClient { } } - upload() { - return new ObjectWritableMock(); + upload({ remote }: { remote: string }) { + const filePath = path.join(rootDir, remote); + + const emitter = new EventEmitter(); + + process.nextTick(() => { + if (fs.existsSync(filePath)) { + emitter.emit('success'); + (emitter as any).end = () => true; + } else { + emitter.emit( + 'error', + new Error(`The file ${filePath} does not exist !`), + ); + } + }); + + return emitter; } - download() { - const stringify = JSON.stringify({ - "site_description": 'site_content', - "site_name": "backstage" + download({ remote }: { remote: string }) { + const filePath = path.join(rootDir, remote); + + const emitter = new EventEmitter(); + + process.nextTick(() => { + if (fs.existsSync(filePath)) { + emitter.emit('data', Buffer.from(fs.readFileSync(filePath))); + emitter.emit('end'); + } else { + emitter.emit( + 'error', + new Error(`The file ${filePath} does not exist !`), + ); + } }); - return new BufferReadableMock([stringify]); + + return emitter; } } diff --git a/packages/techdocs-common/package.json b/packages/techdocs-common/package.json index 2598de1dce..62d7b73f73 100644 --- a/packages/techdocs-common/package.json +++ b/packages/techdocs-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/techdocs-common", "description": "Common functionalities for TechDocs, to be shared between techdocs-backend plugin and techdocs-cli", - "version": "0.4.2", + "version": "0.5.0", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -58,7 +58,6 @@ "p-limit": "^3.1.0", "pkgcloud": "^2.2.0", "recursive-readdir": "^2.2.2", - "stream-mock": "^2.0.5", "winston": "^3.2.1" }, "devDependencies": { diff --git a/packages/techdocs-common/src/stages/publish/openStackSwift.test.ts b/packages/techdocs-common/src/stages/publish/openStackSwift.test.ts index fab0bf3a12..30ec8a59b9 100644 --- a/packages/techdocs-common/src/stages/publish/openStackSwift.test.ts +++ b/packages/techdocs-common/src/stages/publish/openStackSwift.test.ts @@ -111,14 +111,12 @@ describe('OpenStackSwiftPublish', () => { const entity = createMockEntity(); const entityRootDir = getEntityRootDir(entity); - setTimeout(async () => { - expect( - await publisher.publish({ - entity, - directory: entityRootDir, - }), - ).toBeUndefined() - }, 5000); + expect( + await publisher.publish({ + entity, + directory: entityRootDir, + }), + ).toBeUndefined() }); it('should fail to publish a directory', async () => { diff --git a/packages/techdocs-common/src/stages/publish/openStackSwift.ts b/packages/techdocs-common/src/stages/publish/openStackSwift.ts index 60c8349dee..4c4c15d9c4 100644 --- a/packages/techdocs-common/src/stages/publish/openStackSwift.ts +++ b/packages/techdocs-common/src/stages/publish/openStackSwift.ts @@ -49,7 +49,7 @@ export class OpenStackSwiftPublish implements PublisherBase { } catch (error) { throw new Error( "Since techdocs.publisher.type is set to 'openStackSwift' in your app config, " + - 'techdocs.publisher.openStackSwift.containerName is required.', + 'techdocs.publisher.openStackSwift.containerName is required.', ); } @@ -59,8 +59,8 @@ export class OpenStackSwiftPublish implements PublisherBase { const storageClient = storage.createClient({ provider: 'openstack', - username: openStackSwiftConfig.getString('username'), - password: openStackSwiftConfig.getString('password'), + username: openStackSwiftConfig.getString('credentials.username'), + password: openStackSwiftConfig.getString('credentials.password'), authUrl: openStackSwiftConfig.getString('authUrl'), keystoneAuthVersion: openStackSwiftConfig.getOptionalString('keystoneAuthVersion') || 'v3', @@ -80,9 +80,9 @@ export class OpenStackSwiftPublish implements PublisherBase { } else { logger.error( `Could not retrieve metadata about the OpenStack Swift container ${containerName}. ` + - 'Make sure the container exists. Also make sure that authentication is setup either by ' + - 'explicitly defining credentials and region in techdocs.publisher.openStackSwift in app config or ' + - 'by using environment variables. Refer to https://backstage.io/docs/features/techdocs/using-cloud-storage', + 'Make sure the container exists. Also make sure that authentication is setup either by ' + + 'explicitly defining credentials and region in techdocs.publisher.openStackSwift in app config or ' + + 'by using environment variables. Refer to https://backstage.io/docs/features/techdocs/using-cloud-storage', ); logger.error(`from OpenStack client library: ${err.message}`); @@ -139,15 +139,15 @@ export class OpenStackSwiftPublish implements PublisherBase { // Rate limit the concurrent execution of file uploads to batches of 10 (per publish) const uploadFile = limiter(() => - new Promise((res, rej) => { - const writeStream = this.storageClient.upload(params); + new Promise((res, rej) => { + const writeStream = this.storageClient.upload(params); - writeStream.on('error', rej); + writeStream.on('error', rej); - writeStream.on('success', res); + writeStream.on('success', res); - readStream.pipe(writeStream); - }), + readStream.pipe(writeStream); + }), ); uploadPromises.push(uploadFile); } diff --git a/plugins/techdocs-backend/package.json b/plugins/techdocs-backend/package.json index 03bea5233d..0de3393097 100644 --- a/plugins/techdocs-backend/package.json +++ b/plugins/techdocs-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-backend", - "version": "0.6.2", + "version": "0.7.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/yarn.lock b/yarn.lock index 5b7893c852..0f2cbbb7d4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1816,85 +1816,44 @@ to-fast-properties "^2.0.0" "@backstage/catalog-model@^0.2.0": - version "0.2.0" - resolved "https://registry.npmjs.org/@backstage/catalog-model/-/catalog-model-0.2.0.tgz#e3fe2a4ddeb6a9b6ec480c80cb2b9c39cb245576" - integrity sha512-Y1ocdRpBlxK/VrJQjHlQd0bgADECd1B2NRjwd8ss46ibT5hwLvMOfD80+Fa7oPLu0ktJrH4lq0pNIIJIml48zA== + version "0.7.2" dependencies: - "@backstage/config" "^0.1.1" + "@backstage/config" "^0.1.3" "@types/json-schema" "^7.0.5" "@types/yup" "^0.29.8" + ajv "^7.0.3" json-schema "^0.2.5" lodash "^4.17.15" uuid "^8.0.0" yup "^0.29.3" "@backstage/catalog-model@^0.3.0": - version "0.3.1" - resolved "https://registry.npmjs.org/@backstage/catalog-model/-/catalog-model-0.3.1.tgz#45d08e2f333c9c566b2bf2629fd707fe989bb404" - integrity sha512-9XhV7c4rmVW+Yzj2PiwTQ7DsegWGB3C4ELsDRExuEVZONdqNcC02cyJtrt3fT5F31ZS3tHkB9bMUymFOBLqUSA== + version "0.7.2" dependencies: - "@backstage/config" "^0.1.1" + "@backstage/config" "^0.1.3" "@types/json-schema" "^7.0.5" "@types/yup" "^0.29.8" + ajv "^7.0.3" json-schema "^0.2.5" lodash "^4.17.15" uuid "^8.0.0" yup "^0.29.3" "@backstage/core@^0.3.0": - version "0.3.2" - resolved "https://registry.npmjs.org/@backstage/core/-/core-0.3.2.tgz#a8209126d5076cf4a8b9bd632fe4e5e2edb62916" - integrity sha512-i5d+Wh8js4qEWoAsPY5L7HVSWpumr1OhfF2dUCGYdyW6AMqVJPca6+n6zp1Rg2CO+J9norp44XAVVCbyhtUpig== + version "0.6.3" dependencies: - "@backstage/config" "^0.1.1" - "@backstage/core-api" "^0.2.1" - "@backstage/theme" "^0.2.1" - "@material-ui/core" "^4.11.0" - "@material-ui/icons" "^4.9.1" - "@material-ui/lab" "4.0.0-alpha.45" - "@types/dagre" "^0.7.44" - "@types/react" "^16.9" - "@types/react-sparklines" "^1.7.0" - classnames "^2.2.6" - clsx "^1.1.0" - d3-selection "^2.0.0" - d3-shape "^2.0.0" - d3-zoom "^2.0.0" - dagre "^0.8.5" - immer "^7.0.9" - lodash "^4.17.15" - material-table "^1.69.1" - prop-types "^15.7.2" - qs "^6.9.4" - rc-progress "^3.0.0" - react "^16.12.0" - react-dom "^16.12.0" - react-helmet "6.1.0" - react-hook-form "^6.6.0" - react-markdown "^5.0.2" - react-router "6.0.0-beta.0" - react-router-dom "6.0.0-beta.0" - react-sparklines "^1.7.0" - react-syntax-highlighter "^13.5.1" - react-use "^15.3.3" - remark-gfm "^1.0.0" - zen-observable "^0.8.15" - -"@backstage/core@^0.5.0": - version "0.5.0" - resolved "https://registry.npmjs.org/@backstage/core/-/core-0.5.0.tgz#6ff384adc595c18c7db60b9b2d23ebbb9086ed36" - integrity sha512-lCxgKBavUlLYZjZmRF8A7koP4NUhK/tbdf9SaEod0miZg6JTaDoAm3dmHPyqrMBHgoRRCDTxRIxNhj/8vY87oA== - dependencies: - "@backstage/config" "^0.1.2" - "@backstage/core-api" "^0.2.8" - "@backstage/theme" "^0.2.2" + "@backstage/config" "^0.1.3" + "@backstage/core-api" "^0.2.11" + "@backstage/theme" "^0.2.3" "@material-ui/core" "^4.11.0" "@material-ui/icons" "^4.9.1" "@material-ui/lab" "4.0.0-alpha.45" + "@testing-library/react-hooks" "^3.4.2" "@types/dagre" "^0.7.44" "@types/prop-types" "^15.7.3" "@types/react" "^16.9" "@types/react-sparklines" "^1.7.0" + "@types/react-text-truncate" "^0.14.0" classnames "^2.2.6" clsx "^1.1.0" d3-selection "^2.0.0" @@ -1916,57 +1875,25 @@ react-router-dom "6.0.0-beta.0" react-sparklines "^1.7.0" react-syntax-highlighter "^13.5.1" + react-text-truncate "^0.16.0" react-use "^15.3.3" remark-gfm "^1.0.0" zen-observable "^0.8.15" -"@backstage/plugin-catalog-react@^0.0.2": - version "0.0.2" - resolved "https://registry.npmjs.org/@backstage/plugin-catalog-react/-/plugin-catalog-react-0.0.2.tgz#e50da2dac9fab3a0d5973f8d1083ee2c368e5e52" - integrity sha512-O6aujFPRaEFTk4XlwOoswbnoHIOqMtj6ycUj6R1mNKOM4plUgGDKKhO3be69FHMJEMbiSvVe6AW+1kXaK+1LqA== - dependencies: - "@backstage/catalog-client" "^0.3.5" - "@backstage/catalog-model" "^0.7.1" - "@backstage/core" "^0.6.0" - "@material-ui/core" "^4.11.0" - "@types/react" "^16.9" - react "^16.13.1" - react-router "6.0.0-beta.0" - react-router-dom "6.0.0-beta.0" - react-use "^15.3.3" - -"@backstage/plugin-catalog-react@^0.0.4": - version "0.0.4" - resolved "https://registry.npmjs.org/@backstage/plugin-catalog-react/-/plugin-catalog-react-0.0.4.tgz#a4c8ba90cf48106ac6af2e03afa6338010a1299b" - integrity sha512-1fAqULJvLyE+3SeZ2yxDJnJ3SbUFv2Im55d3KbMgRaSog1chSJJoO3jbIwIRQIBXjRmCXrZbf56qwwWwxj6OjA== +"@backstage/plugin-catalog@^0.2.1": + version "0.4.0" dependencies: "@backstage/catalog-client" "^0.3.6" - "@backstage/catalog-model" "^0.7.1" - "@backstage/core" "^0.6.2" - "@material-ui/core" "^4.11.0" - "@types/react" "^16.9" - react "^16.13.1" - react-router "6.0.0-beta.0" - react-router-dom "6.0.0-beta.0" - react-use "^15.3.3" - -"@backstage/plugin-catalog@^0.2.1": - version "0.2.14" - resolved "https://registry.npmjs.org/@backstage/plugin-catalog/-/plugin-catalog-0.2.14.tgz#50a4176a55ffa543a426ec78cbc9deaecdbcf2b7" - integrity sha512-lDmNcC+m1zbbzYATUp5yIZ5PUp+YyBc1KKu3CCgqjLWSbJ1aJrU1N4g59euel1l2+qSW+lH76Kkp6ZYpZbSO9A== - dependencies: - "@backstage/catalog-client" "^0.3.5" - "@backstage/catalog-model" "^0.7.0" - "@backstage/core" "^0.5.0" - "@backstage/plugin-scaffolder" "^0.4.1" - "@backstage/theme" "^0.2.2" + "@backstage/catalog-model" "^0.7.2" + "@backstage/core" "^0.6.3" + "@backstage/plugin-catalog-react" "^0.1.0" + "@backstage/theme" "^0.2.3" "@material-ui/core" "^4.11.0" "@material-ui/icons" "^4.9.1" "@material-ui/lab" "4.0.0-alpha.45" "@types/react" "^16.9" classnames "^2.2.6" git-url-parse "^11.4.4" - moment "^2.26.0" react "^16.13.1" react-dom "^16.13.1" react-helmet "6.1.0" @@ -1976,15 +1903,12 @@ swr "^0.3.0" "@backstage/plugin-catalog@^0.3.1": - version "0.3.2" - resolved "https://registry.npmjs.org/@backstage/plugin-catalog/-/plugin-catalog-0.3.2.tgz#06945f10fd678efdade3f2795590c12433568fa0" - integrity sha512-iHLxPHRN9nYIXwOEAQ06m+PagsFb6Nb/XjJSebCAnSrAxPPITvBCfxc2H1GbyyMdC7KAr1ozORB/FFkNsCaQJg== + version "0.4.0" dependencies: "@backstage/catalog-client" "^0.3.6" - "@backstage/catalog-model" "^0.7.1" - "@backstage/core" "^0.6.2" - "@backstage/plugin-catalog-react" "^0.0.4" - "@backstage/plugin-scaffolder" "^0.5.1" + "@backstage/catalog-model" "^0.7.2" + "@backstage/core" "^0.6.3" + "@backstage/plugin-catalog-react" "^0.1.0" "@backstage/theme" "^0.2.3" "@material-ui/core" "^4.11.0" "@material-ui/icons" "^4.9.1" @@ -2025,56 +1949,6 @@ react-use "^15.3.3" swr "^0.3.0" -"@backstage/plugin-scaffolder@^0.4.1": - version "0.4.2" - resolved "https://registry.npmjs.org/@backstage/plugin-scaffolder/-/plugin-scaffolder-0.4.2.tgz#58159227997f7e248ce52535bc32f19fcd0990dc" - integrity sha512-YuyHM587Rqg6KufxfFqQdI7dsZniBM/11Aj8Q0m5ZszOpCuNmDDkR1VX8MKHTBJ709mnLAqRgArdla7FOrOAXQ== - dependencies: - "@backstage/catalog-model" "^0.7.1" - "@backstage/core" "^0.6.0" - "@backstage/plugin-catalog-react" "^0.0.2" - "@backstage/theme" "^0.2.3" - "@material-ui/core" "^4.11.0" - "@material-ui/icons" "^4.9.1" - "@material-ui/lab" "4.0.0-alpha.45" - "@rjsf/core" "^2.4.0" - "@rjsf/material-ui" "^2.4.0" - classnames "^2.2.6" - git-url-parse "^11.4.4" - moment "^2.26.0" - react "^16.13.1" - react-dom "^16.13.1" - react-lazylog "^4.5.2" - react-router "6.0.0-beta.0" - react-router-dom "6.0.0-beta.0" - react-use "^15.3.3" - swr "^0.3.0" - -"@backstage/plugin-scaffolder@^0.5.1": - version "0.5.1" - resolved "https://registry.npmjs.org/@backstage/plugin-scaffolder/-/plugin-scaffolder-0.5.1.tgz#9d36f6b01991ddd9f9f2996068f3f31c766db8db" - integrity sha512-EG+iUc107bneVBPQpFKGp2jD9Y9+x50g/gY6TBN1je8TkgyluoxMj7wKv9e+d4TeGzXwK/LW/suajG9Zo0TJGQ== - dependencies: - "@backstage/catalog-model" "^0.7.1" - "@backstage/core" "^0.6.2" - "@backstage/plugin-catalog-react" "^0.0.4" - "@backstage/theme" "^0.2.3" - "@material-ui/core" "^4.11.0" - "@material-ui/icons" "^4.9.1" - "@material-ui/lab" "4.0.0-alpha.45" - "@rjsf/core" "^2.4.0" - "@rjsf/material-ui" "^2.4.0" - classnames "^2.2.6" - git-url-parse "^11.4.4" - moment "^2.26.0" - react "^16.13.1" - react-dom "^16.13.1" - react-lazylog "^4.5.2" - react-router "6.0.0-beta.0" - react-router-dom "6.0.0-beta.0" - react-use "^15.3.3" - swr "^0.3.0" - "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" @@ -15205,11 +15079,6 @@ immer@1.10.0: resolved "https://registry.npmjs.org/immer/-/immer-1.10.0.tgz#bad67605ba9c810275d91e1c2a47d4582e98286d" integrity sha512-O3sR1/opvCDGLEVcvrGTMtLac8GJ5IwZC4puPrLuRj3l7ICKvkmA0vGuU9OW8mV9WIBRnaxp5GJh9IEAaNOoYg== -immer@^7.0.9: - version "7.0.15" - resolved "https://registry.npmjs.org/immer/-/immer-7.0.15.tgz#dc3bc6db87401659d2e737c67a21b227c484a4ad" - integrity sha512-yM7jo9+hvYgvdCQdqvhCNRRio0SCXc8xDPzA25SvKWa7b1WVPjLwQs1VYU5JPXjcJPTqAa5NP5dqpORGYBQ2AA== - immer@^8.0.1: version "8.0.1" resolved "https://registry.npmjs.org/immer/-/immer-8.0.1.tgz#9c73db683e2b3975c424fb0572af5889877ae656" @@ -18764,7 +18633,7 @@ modify-values@^1.0.0: resolved "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022" integrity sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw== -moment@^2.19.3, moment@^2.25.3, moment@^2.26.0, moment@^2.27.0: +moment@^2.19.3, moment@^2.25.3, moment@^2.27.0: version "2.29.1" resolved "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3" integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ== @@ -24159,11 +24028,6 @@ stream-http@^2.7.2: to-arraybuffer "^1.0.0" xtend "^4.0.0" -stream-mock@^2.0.5: - version "2.0.5" - resolved "https://registry.npmjs.org/stream-mock/-/stream-mock-2.0.5.tgz#c99d24bd6dbb0eaa57cf6ffefdb064150747826e" - integrity sha512-dx9skT8QYjwLsal+MhGHr4UtgS49brw851C/oTixmhCi4Ip+/qnZmhV1qOcznYYAED6gYKmKea+jjza4/wjpSg== - stream-shift@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" From fc470a310ea2d439ea95d8cdbe2c9f5e92a29139 Mon Sep 17 00:00:00 2001 From: gmzsenturk Date: Thu, 25 Feb 2021 17:50:20 +0300 Subject: [PATCH 13/66] Updated cloud storage documentation for open stack MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mert Can Bilgiç --- docs/features/techdocs/using-cloud-storage.md | 69 +++++++------------ 1 file changed, 25 insertions(+), 44 deletions(-) diff --git a/docs/features/techdocs/using-cloud-storage.md b/docs/features/techdocs/using-cloud-storage.md index 66e6e15811..e160c6f5f5 100644 --- a/docs/features/techdocs/using-cloud-storage.md +++ b/docs/features/techdocs/using-cloud-storage.md @@ -281,11 +281,9 @@ techdocs: **3b. Authentication using app-config.yaml** -If you do not prefer (3a) and optionally like to use a service account, you can -follow these steps. +Set the config `techdocs.publisher.azureBlobStorage.credentials.accountName` in +your `app-config.yaml` to the your account name. -To get credentials, access the Azure Portal and go to "Settings > Access Keys", -and get your Storage account name and Primary Key. https://docs.microsoft.com/en-us/rest/api/storageservices/authorize-with-shared-key for more details. @@ -314,7 +312,7 @@ the logs. Follow the [official OpenStack Api documentation](https://docs.openstack.org/api-ref/identity/v3/) -for the latest instructions on the following steps involving Azure Blob Storage. +for the latest instructions on the following steps involving Open Stack Storage. **1. Set `techdocs.publisher.type` config in your `app-config.yaml`** @@ -326,7 +324,7 @@ techdocs: type: 'openStackSwift' ``` -**2. Create an Azure Blob Storage Container** +**2. Create an OpenStack Swift Storage Container** Create a dedicated container for TechDocs sites. [Refer to the official documentation](https://docs.openstack.org/mitaka/user-guide/dashboard_manage_containers.html). @@ -346,57 +344,40 @@ techdocs: containerName: 'name-of-techdocs-storage-container' ``` -**3a. (Recommended) Authentication using environment variable** +**3. Authentication using app-config.yaml** -Set the config `techdocs.publisher.openStackSwift.accountName` in -your `app-config.yaml` to the your account name. +Set the configs in your `app-config.yaml` to the your container name. -The storage blob client will automatically use the environment variable -`AZURE_TENANT_ID`, `AZURE_CLIENT_ID`, `AZURE_CLIENT_SECRET` to authenticate with -Azure Blob Storage. -[Steps to create the service where the variables can be retrieved from](https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal). - -https://docs.microsoft.com/en-us/azure/storage/common/storage-auth-aad for more -details. - -```yaml -techdocs: - publisher: - type: 'azureBlobStorage' - azureBlobStorage: - containerName: 'name-of-techdocs-storage-bucket' - credentials: - accountName: - $env: TECHDOCS_AZURE_BLOB_STORAGE_ACCOUNT_NAME -``` - -**3b. Authentication using app-config.yaml** - -If you do not prefer (3a) and optionally like to use a service account, you can -follow these steps. - -To get credentials, access the Azure Portal and go to "Settings > Access Keys", -and get your Storage account name and Primary Key. -https://docs.microsoft.com/en-us/rest/api/storageservices/authorize-with-shared-key +https://docs.openstack.org/api-ref/identity/v3/?expanded=password-authentication-with-unscoped-authorization-detail#password-authentication-with-unscoped-authorization for more details. ```yaml techdocs: publisher: - type: 'azureBlobStorage' - azureBlobStorage: + type: 'openStackSwift' + openStackSwift: containerName: 'name-of-techdocs-storage-bucket' credentials: - accountName: - $env: TECHDOCS_AZURE_BLOB_STORAGE_ACCOUNT_NAME - accountKey: - $env: TECHDOCS_AZURE_BLOB_STORAGE_ACCOUNT_KEY + userName: + $env: OPENSTACK_SWIFT_STORAGE_USERNAME + password: + $env: OPENSTACK_SWIFT_STORAGE_PASSWORD + authUrl: + $env: OPENSTACK_SWIFT_STORAGE_AUTH_URL + keystoneAuthVersion: + $env: OPENSTACK_SWIFT_STORAGE_AUTH_VERSION + domainId: + $env: OPENSTACK_SWIFT_STORAGE_DOMAIN_ID + domainName: + $env: OPENSTACK_SWIFT_STORAGE_DOMAIN_NAME + region: + $env: OPENSTACK_SWIFT_STORAGE_REGION ``` **4. That's it!** -Your Backstage app is now ready to use Azure Blob Storage for TechDocs, to store +Your Backstage app is now ready to use OpenStack Swift Storage for TechDocs, to store and read the static generated documentation files. When you start the backend of the app, you should be able to see -`techdocs info Successfully connected to the Azure Blob Storage container` in +`techdocs info Successfully connected to the OpenStack Swift Storage container` in the logs. From 54e711826fb441ee1a7b861f1fec4371e2640562 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Can=20Bilgi=C3=A7?= Date: Thu, 25 Feb 2021 17:54:55 +0300 Subject: [PATCH 14/66] capp config type has changed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mert Can Bilgiç --- app-config.yaml | 2 +- .../src/stages/publish/openStackSwift.test.ts | 8 ++-- .../src/stages/publish/publish.test.ts | 6 ++- plugins/techdocs/config.d.ts | 41 ++++++++++--------- 4 files changed, 31 insertions(+), 26 deletions(-) diff --git a/app-config.yaml b/app-config.yaml index f68f5a30a2..be0f405de5 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -94,7 +94,7 @@ techdocs: generators: techdocs: 'docker' # Alternatives - 'local' publisher: - type: 'local' # Alternatives - 'googleGcs' or 'awsS3' or 'azureBlobStorage'. Read documentation for using alternatives. + type: 'local' # Alternatives - 'googleGcs' or 'awsS3' or 'azureBlobStorage' or 'openStackSwift' Read documentation for using alternatives. sentry: organization: my-company diff --git a/packages/techdocs-common/src/stages/publish/openStackSwift.test.ts b/packages/techdocs-common/src/stages/publish/openStackSwift.test.ts index 30ec8a59b9..b831012acf 100644 --- a/packages/techdocs-common/src/stages/publish/openStackSwift.test.ts +++ b/packages/techdocs-common/src/stages/publish/openStackSwift.test.ts @@ -73,8 +73,10 @@ beforeEach(() => { publisher: { type: 'openStackSwift', openStackSwift: { - username: 'mockuser', - password: 'verystrongpass', + credentials: { + username: 'mockuser', + password: 'verystrongpass', + }, authUrl: 'mockauthurl', region: 'mockregion', containerName: 'mock', @@ -116,7 +118,7 @@ describe('OpenStackSwiftPublish', () => { entity, directory: entityRootDir, }), - ).toBeUndefined() + ).toBeUndefined(); }); it('should fail to publish a directory', async () => { diff --git a/packages/techdocs-common/src/stages/publish/publish.test.ts b/packages/techdocs-common/src/stages/publish/publish.test.ts index f9bd0f3934..948b92aaa6 100644 --- a/packages/techdocs-common/src/stages/publish/publish.test.ts +++ b/packages/techdocs-common/src/stages/publish/publish.test.ts @@ -170,8 +170,10 @@ describe('Publisher', () => { publisher: { type: 'openStackSwift', openStackSwift: { - username: 'mockuser', - password: 'verystrongpass', + credentials: { + username: 'mockuser', + password: 'verystrongpass', + }, authUrl: 'mockauthurl', region: 'mockregion', containerName: 'mock', diff --git a/plugins/techdocs/config.d.ts b/plugins/techdocs/config.d.ts index c4fe4a47db..5e12f7dbc7 100644 --- a/plugins/techdocs/config.d.ts +++ b/plugins/techdocs/config.d.ts @@ -89,56 +89,57 @@ export interface Config { type: 'openStackSwift'; /** - * Required when 'type' is set to awsS3 + * Required when 'type' is set to openStackSwift */ openStackSwift?: { /** - * (Optional) Credentials used to access a storage bucket. - * If not set, environment variables or aws config file will be used to authenticate. - * @see https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/loading-node-credentials-environment.html - * @see https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/loading-node-credentials-shared.html + * (Required) Credentials used to access a storage bucket. + * @see https://docs.openstack.org/api-ref/identity/v3/?expanded=password-authentication-with-unscoped-authorization-detail#password-authentication-with-unscoped-authorization * @visibility secret */ + credentials: { + /** + * (Required) Root user name + * @visibility backend + */ + username: string; + /** + * (Required) Root user password + * @visibility backend + */ + password: string; // required + }; /** * (Required) Cloud Storage Container Name * @visibility backend */ containerName: string; - /** - * (Required) Root user name - * @visibility backend - */ - username: string; - /** - * (Required) Root user password - * @visibility backend - */ - password: string; // required /** * (Required) Auth url sometimes OpenStack uses different port check your OpenStack apis. * @visibility backend */ authUrl: string; /** - * (Required) Auth version + * (Optional) Auth version + * If not set, 'v2.0' will be used. * @visibility backend */ keystoneAuthVersion: string; /** - * (Required) Domaind Id + * (Required) Domain Id * @visibility backend */ domainId: string; /** - * (Required) Domaind Name + * (Required) Domain Name * @visibility backend */ - domainName: 'Default'; + domainName: string; /** * (Required) Region * @visibility backend */ - region: 'earth'; + region: string; }; } | { From e0bccb16a22f327224c910470acceefe6b9746fd Mon Sep 17 00:00:00 2001 From: erdoganoksuz Date: Fri, 26 Feb 2021 10:39:08 +0300 Subject: [PATCH 15/66] versions for changeset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mert Can Bilgiç --- packages/techdocs-common/package.json | 2 +- plugins/techdocs-backend/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/techdocs-common/package.json b/packages/techdocs-common/package.json index 62d7b73f73..a543addfa9 100644 --- a/packages/techdocs-common/package.json +++ b/packages/techdocs-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/techdocs-common", "description": "Common functionalities for TechDocs, to be shared between techdocs-backend plugin and techdocs-cli", - "version": "0.5.0", + "version": "0.4.2", "main": "src/index.ts", "types": "src/index.ts", "private": false, diff --git a/plugins/techdocs-backend/package.json b/plugins/techdocs-backend/package.json index 0de3393097..03bea5233d 100644 --- a/plugins/techdocs-backend/package.json +++ b/plugins/techdocs-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-backend", - "version": "0.7.0", + "version": "0.6.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", From 15fb8e2176ac26bd9b371857299c2a5d75b9799c Mon Sep 17 00:00:00 2001 From: erdoganoksuz Date: Fri, 26 Feb 2021 10:41:43 +0300 Subject: [PATCH 16/66] changeset added MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mert Can Bilgiç --- .changeset/odd-mirrors-smell.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/odd-mirrors-smell.md diff --git a/.changeset/odd-mirrors-smell.md b/.changeset/odd-mirrors-smell.md new file mode 100644 index 0000000000..7b6e0285ef --- /dev/null +++ b/.changeset/odd-mirrors-smell.md @@ -0,0 +1,7 @@ +--- +'@backstage/techdocs-common': minor +'@backstage/plugin-techdocs': minor +'@backstage/plugin-techdocs-backend': minor +--- + +OpenStack Swift Publisher added to TechDocs From 2ebfe29d8a04faa06cb95f312bbce1d9daa6825f Mon Sep 17 00:00:00 2001 From: gmzsenturk Date: Fri, 26 Feb 2021 10:55:16 +0300 Subject: [PATCH 17/66] Cloud storage documentation edited MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mert Can Bilgiç --- docs/features/techdocs/using-cloud-storage.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/features/techdocs/using-cloud-storage.md b/docs/features/techdocs/using-cloud-storage.md index e160c6f5f5..6fefca70c0 100644 --- a/docs/features/techdocs/using-cloud-storage.md +++ b/docs/features/techdocs/using-cloud-storage.md @@ -281,8 +281,11 @@ techdocs: **3b. Authentication using app-config.yaml** -Set the config `techdocs.publisher.azureBlobStorage.credentials.accountName` in -your `app-config.yaml` to the your account name. +If you do not prefer (3a) and optionally like to use a service account, you can Set the config `techdocs.publisher.azureBlobStorage.credentials.accountName` in +follow these steps. your `app-config.yaml` to the your account name. + +To get credentials, access the Azure Portal and go to "Settings > Access Keys", +and get your Storage account name and Primary Key. https://docs.microsoft.com/en-us/rest/api/storageservices/authorize-with-shared-key for more details. From a10174b56d82a0552a809bccab4512ec293b01cb Mon Sep 17 00:00:00 2001 From: gmzsenturk Date: Fri, 26 Feb 2021 11:18:41 +0300 Subject: [PATCH 18/66] Documentations edited MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mert Can Bilgiç --- app-config.yaml | 2 +- docs/features/techdocs/using-cloud-storage.md | 17 +++++++------- .../techdocs-common/__mocks__/pkgcloud.ts | 2 +- .../src/stages/publish/openStackSwift.ts | 23 ++++++++++--------- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/app-config.yaml b/app-config.yaml index be0f405de5..f963296bb8 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -94,7 +94,7 @@ techdocs: generators: techdocs: 'docker' # Alternatives - 'local' publisher: - type: 'local' # Alternatives - 'googleGcs' or 'awsS3' or 'azureBlobStorage' or 'openStackSwift' Read documentation for using alternatives. + type: 'local' # Alternatives - 'googleGcs' or 'awsS3' or 'azureBlobStorage' or 'openStackSwift'. Read documentation for using alternatives. sentry: organization: my-company diff --git a/docs/features/techdocs/using-cloud-storage.md b/docs/features/techdocs/using-cloud-storage.md index 6fefca70c0..8b8ff04573 100644 --- a/docs/features/techdocs/using-cloud-storage.md +++ b/docs/features/techdocs/using-cloud-storage.md @@ -281,12 +281,11 @@ techdocs: **3b. Authentication using app-config.yaml** -If you do not prefer (3a) and optionally like to use a service account, you can Set the config `techdocs.publisher.azureBlobStorage.credentials.accountName` in -follow these steps. your `app-config.yaml` to the your account name. +If you do not prefer (3a) and optionally like to use a service account, you can +follow these steps. To get credentials, access the Azure Portal and go to "Settings > Access Keys", and get your Storage account name and Primary Key. - https://docs.microsoft.com/en-us/rest/api/storageservices/authorize-with-shared-key for more details. @@ -373,14 +372,14 @@ techdocs: $env: OPENSTACK_SWIFT_STORAGE_DOMAIN_ID domainName: $env: OPENSTACK_SWIFT_STORAGE_DOMAIN_NAME - region: + region: $env: OPENSTACK_SWIFT_STORAGE_REGION ``` **4. That's it!** -Your Backstage app is now ready to use OpenStack Swift Storage for TechDocs, to store -and read the static generated documentation files. When you start the backend of -the app, you should be able to see -`techdocs info Successfully connected to the OpenStack Swift Storage container` in -the logs. +Your Backstage app is now ready to use OpenStack Swift Storage for TechDocs, to +store and read the static generated documentation files. When you start the +backend of the app, you should be able to see +`techdocs info Successfully connected to the OpenStack Swift Storage container` +in the logs. diff --git a/packages/techdocs-common/__mocks__/pkgcloud.ts b/packages/techdocs-common/__mocks__/pkgcloud.ts index 4316630928..6b250092b8 100644 --- a/packages/techdocs-common/__mocks__/pkgcloud.ts +++ b/packages/techdocs-common/__mocks__/pkgcloud.ts @@ -21,7 +21,7 @@ import { EventEmitter } from 'events'; const rootDir = os.platform() === 'win32' ? 'C:\\rootDir' : '/rootDir'; const checkFileExists = async (Key: string): Promise => { - // Key will always have / as file separator irrespective of OS since S3 expects /. + // Key will always have / as file separator irrespective of OS since cloud providers expects /. // Normalize Key to OS specific path before checking if file exists. const filePath = path.join(rootDir, Key); diff --git a/packages/techdocs-common/src/stages/publish/openStackSwift.ts b/packages/techdocs-common/src/stages/publish/openStackSwift.ts index 4c4c15d9c4..3987a36e17 100644 --- a/packages/techdocs-common/src/stages/publish/openStackSwift.ts +++ b/packages/techdocs-common/src/stages/publish/openStackSwift.ts @@ -49,7 +49,7 @@ export class OpenStackSwiftPublish implements PublisherBase { } catch (error) { throw new Error( "Since techdocs.publisher.type is set to 'openStackSwift' in your app config, " + - 'techdocs.publisher.openStackSwift.containerName is required.', + 'techdocs.publisher.openStackSwift.containerName is required.', ); } @@ -80,9 +80,9 @@ export class OpenStackSwiftPublish implements PublisherBase { } else { logger.error( `Could not retrieve metadata about the OpenStack Swift container ${containerName}. ` + - 'Make sure the container exists. Also make sure that authentication is setup either by ' + - 'explicitly defining credentials and region in techdocs.publisher.openStackSwift in app config or ' + - 'by using environment variables. Refer to https://backstage.io/docs/features/techdocs/using-cloud-storage', + 'Make sure the container exists. Also make sure that authentication is setup either by ' + + 'explicitly defining credentials and region in techdocs.publisher.openStackSwift in app config or ' + + 'by using environment variables. Refer to https://backstage.io/docs/features/techdocs/using-cloud-storage', ); logger.error(`from OpenStack client library: ${err.message}`); @@ -138,16 +138,17 @@ export class OpenStackSwiftPublish implements PublisherBase { }; // Rate limit the concurrent execution of file uploads to batches of 10 (per publish) - const uploadFile = limiter(() => - new Promise((res, rej) => { - const writeStream = this.storageClient.upload(params); + const uploadFile = limiter( + () => + new Promise((res, rej) => { + const writeStream = this.storageClient.upload(params); - writeStream.on('error', rej); + writeStream.on('error', rej); - writeStream.on('success', res); + writeStream.on('success', res); - readStream.pipe(writeStream); - }), + readStream.pipe(writeStream); + }), ); uploadPromises.push(uploadFile); } From 37586e0fc9b0e55c692d6d52a29f52afef71117b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Can=20Bilgi=C3=A7?= Date: Fri, 26 Feb 2021 15:26:28 +0300 Subject: [PATCH 19/66] runned prettier MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mert Can Bilgiç --- docs/features/techdocs/using-cloud-storage.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/features/techdocs/using-cloud-storage.md b/docs/features/techdocs/using-cloud-storage.md index 8b8ff04573..e6dcd077cd 100644 --- a/docs/features/techdocs/using-cloud-storage.md +++ b/docs/features/techdocs/using-cloud-storage.md @@ -257,7 +257,8 @@ techdocs: **3a. (Recommended) Authentication using environment variable** -Set the config `techdocs.publisher.azureBlobStorage.credentials.accountName` in +If you do not prefer (3a) and optionally like to use a service account, you can +set the config `techdocs.publisher.azureBlobStorage.credentials.accountName` in your `app-config.yaml` to the your account name. The storage blob client will automatically use the environment variable From aa095e469fa5f054c7c5757053edbb2cdf4364cb Mon Sep 17 00:00:00 2001 From: erdoganoksuz Date: Mon, 1 Mar 2021 11:20:17 +0300 Subject: [PATCH 20/66] pull request feedbacks fix. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mert Can Bilgiç --- .changeset/nine-comics-smash.md | 7 +++++++ .changeset/odd-mirrors-smell.md | 7 ------- docs/features/techdocs/README.md | 1 + .../src/stages/publish/openStackSwift.ts | 11 +++++++---- plugins/techdocs/config.d.ts | 4 ++-- 5 files changed, 17 insertions(+), 13 deletions(-) create mode 100644 .changeset/nine-comics-smash.md delete mode 100644 .changeset/odd-mirrors-smell.md diff --git a/.changeset/nine-comics-smash.md b/.changeset/nine-comics-smash.md new file mode 100644 index 0000000000..147944930d --- /dev/null +++ b/.changeset/nine-comics-smash.md @@ -0,0 +1,7 @@ +--- +'@backstage/techdocs-common': patch +'@backstage/plugin-techdocs-backend': patch +'@backstage/plugin-techdocs': patch +--- + +OpenStack Swift publisher added for tech-docs. diff --git a/.changeset/odd-mirrors-smell.md b/.changeset/odd-mirrors-smell.md deleted file mode 100644 index 7b6e0285ef..0000000000 --- a/.changeset/odd-mirrors-smell.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/techdocs-common': minor -'@backstage/plugin-techdocs': minor -'@backstage/plugin-techdocs-backend': minor ---- - -OpenStack Swift Publisher added to TechDocs diff --git a/docs/features/techdocs/README.md b/docs/features/techdocs/README.md index 2fa48f57b0..7025364f78 100644 --- a/docs/features/techdocs/README.md +++ b/docs/features/techdocs/README.md @@ -54,6 +54,7 @@ providers are used. | Google Cloud Storage (GCS) | Yes ✅ | | Amazon Web Services (AWS) S3 | Yes ✅ | | Azure Blob Storage | Yes ✅ | +| OpenStack Swift | Yes ✅ | [Reach out to us](#feedback) if you want to request more platforms. diff --git a/packages/techdocs-common/src/stages/publish/openStackSwift.ts b/packages/techdocs-common/src/stages/publish/openStackSwift.ts index 3987a36e17..0e60bc5796 100644 --- a/packages/techdocs-common/src/stages/publish/openStackSwift.ts +++ b/packages/techdocs-common/src/stages/publish/openStackSwift.ts @@ -130,8 +130,6 @@ export class OpenStackSwiftPublish implements PublisherBase { const entityRootDir = `${entity.metadata.namespace}/${entity.kind}/${entity.metadata.name}`; const destination = `${entityRootDir}/${relativeFilePathPosix}`; // Swift container file relative path - const readStream = fs.createReadStream(filePath, 'utf8'); - const params = { container: this.containerName, remote: destination, @@ -141,6 +139,8 @@ export class OpenStackSwiftPublish implements PublisherBase { const uploadFile = limiter( () => new Promise((res, rej) => { + const readStream = fs.createReadStream(filePath, 'utf8'); + const writeStream = this.storageClient.upload(params); writeStream.on('error', rej); @@ -246,10 +246,13 @@ export class OpenStackSwiftPublish implements PublisherBase { this.storageClient.getFile( this.containerName, `${entityRootDir}/index.html`, - (err: any, file: any) => { + (err, file) => { if (!err && file) { res(true); - } else res(false); + } else { + res(false); + this.logger.warn(err.message); + } }, ); }); diff --git a/plugins/techdocs/config.d.ts b/plugins/techdocs/config.d.ts index 5e12f7dbc7..54d2b8fb69 100644 --- a/plugins/techdocs/config.d.ts +++ b/plugins/techdocs/config.d.ts @@ -100,12 +100,12 @@ export interface Config { credentials: { /** * (Required) Root user name - * @visibility backend + * @visibility secret */ username: string; /** * (Required) Root user password - * @visibility backend + * @visibility secret */ password: string; // required }; From 7bf88b4f311e0c6d1dc7ac234a02f77ce056433f Mon Sep 17 00:00:00 2001 From: erdoganoksuz Date: Tue, 2 Mar 2021 11:53:28 +0300 Subject: [PATCH 21/66] error name and documentation consistency. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mert Can Bilgiç --- docs/features/techdocs/using-cloud-storage.md | 4 ++-- packages/techdocs-common/__mocks__/pkgcloud.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/features/techdocs/using-cloud-storage.md b/docs/features/techdocs/using-cloud-storage.md index e6dcd077cd..fd8f320859 100644 --- a/docs/features/techdocs/using-cloud-storage.md +++ b/docs/features/techdocs/using-cloud-storage.md @@ -315,7 +315,7 @@ the logs. Follow the [official OpenStack Api documentation](https://docs.openstack.org/api-ref/identity/v3/) -for the latest instructions on the following steps involving Open Stack Storage. +for the latest instructions on the following steps involving OpenStack Storage. **1. Set `techdocs.publisher.type` config in your `app-config.yaml`** @@ -349,7 +349,7 @@ techdocs: **3. Authentication using app-config.yaml** -Set the configs in your `app-config.yaml` to the your container name. +Set the configs in your `app-config.yaml` to point to your container name. https://docs.openstack.org/api-ref/identity/v3/?expanded=password-authentication-with-unscoped-authorization-detail#password-authentication-with-unscoped-authorization for more details. diff --git a/packages/techdocs-common/__mocks__/pkgcloud.ts b/packages/techdocs-common/__mocks__/pkgcloud.ts index 6b250092b8..5d9f81cda0 100644 --- a/packages/techdocs-common/__mocks__/pkgcloud.ts +++ b/packages/techdocs-common/__mocks__/pkgcloud.ts @@ -54,7 +54,7 @@ class PkgCloudStorageClient { callback: (err: string, container: string) => any, ) { if (containerName !== 'mock') { - callback("Container doesn't exist", containerName); + callback('Container does not exist', containerName); throw new Error('Container does not exist'); } else { callback('Container does not exist', 'success'); From ffdae9be17bda7d66a85598c6be0b8a9ea2e28f6 Mon Sep 17 00:00:00 2001 From: Tim Hansen Date: Wed, 3 Mar 2021 15:38:50 -0700 Subject: [PATCH 22/66] Update documentation, remove CircleCI from default app Signed-off-by: Tim Hansen --- .../configure-app-with-plugins.md | 11 +++-- docs/getting-started/deployment-docker.md | 42 +++++++------------ .../default-app/packages/app/package.json.hbs | 1 - .../app/src/components/catalog/EntityPage.tsx | 11 ++--- .../default-app/packages/app/src/plugins.ts | 1 - 5 files changed, 26 insertions(+), 40 deletions(-) diff --git a/docs/getting-started/configure-app-with-plugins.md b/docs/getting-started/configure-app-with-plugins.md index 64b9e772d1..af7fdbb4cb 100644 --- a/docs/getting-started/configure-app-with-plugins.md +++ b/docs/getting-started/configure-app-with-plugins.md @@ -4,17 +4,22 @@ title: Configuring App with plugins description: Documentation on How Configuring App with plugins --- +Backstage plugins customize the app for your needs. There is a +[plugin marketplace](https://backstage.io/plugins) with plugins for many common +infrastructure needs - CI/CD, monitoring, auditing, and more. + ## Adding existing plugins to your app -The following steps assume that you have created a new Backstage app and want to -add an existing plugin to it. We are using the +The following steps assume that you have +[created a Backstage app](./create-an-app.md) and want to add an existing plugin +to it. We are using the [CircleCI](https://github.com/backstage/backstage/blob/master/plugins/circleci/README.md) plugin in this example. 1. Add the plugin's npm package to the repo: ```bash -yarn add @backstage/plugin-circleci +yarn workspace app add @backstage/plugin-circleci ``` 2. Add the plugin itself: diff --git a/docs/getting-started/deployment-docker.md b/docs/getting-started/deployment-docker.md index 69158f9104..37a48f5457 100644 --- a/docs/getting-started/deployment-docker.md +++ b/docs/getting-started/deployment-docker.md @@ -27,9 +27,9 @@ The required steps in the host build are to install dependencies with `yarn install`, generate type definitions using `yarn tsc`, and build all packages with `yarn build`. -> NOTE: Using `yarn build` to build packages and bundle the backend assumes that -> you have migrated to using `backstage-cli backend:bundle` as your build script -> in the backend package. +> NOTE: If you created your app prior to 2021-02-18, follow the +> [migration step](https://github.com/backstage/backstage/releases/tag/release-2021-02-18) +> to move from `backend:build` to `backend:bundle`. In a CI workflow it might look something like this: @@ -43,22 +43,10 @@ yarn tsc yarn build ``` -Once the host build is complete, we are ready to build our image. We use the -following `Dockerfile`, which is also included when creating a new app with -`@backstage/create-app`: +Once the host build is complete, we are ready to build our image. The following +`Dockerfile` is included when creating a new app with `@backstage/create-app`: ```Dockerfile -# This dockerfile builds an image for the backend package. -# It should be executed with the root of the repo as docker context. -# -# Before building this image, be sure to have run the following commands in the repo root: -# -# yarn install -# yarn tsc -# yarn build -# -# Once the commands have been run, you can build the image using `yarn build-image` - FROM node:14-buster-slim WORKDIR /app @@ -78,15 +66,15 @@ CMD ["node", "packages/backend", "--config", "app-config.yaml"] For more details on how the `backend:bundle` command and the `skeleton.tar.gz` file works, see the -[`backend:bundle` command docs](../cli/commands.md#backendbundle) +[`backend:bundle` command docs](../cli/commands.md#backendbundle). -The `Dockerfile` is typically placed at `packages/backend/Dockerfile`, but needs -to be executed with the root of the repo as the build context, in order to get -access to the root `yarn.lock` and `package.json`, along with any other files -that might be needed, such as `.npmrc`. +The `Dockerfile` is located at `packages/backend/Dockerfile`, but needs to be +executed with the root of the repo as the build context, in order to get access +to the root `yarn.lock` and `package.json`, along with any other files that +might be needed, such as `.npmrc`. -In order to speed up the build we can significantly reduce the build context -size using the following `.dockerignore` in the root of the repo: +The `@backstage/create-app` command adds the following `.dockerignore` in the +root of the repo to speed up the build by reducing build context size: ```text .git @@ -96,9 +84,9 @@ packages plugins ``` -With the project build and the `.dockerignore` and `Dockerfile` in place, we are -now ready to build the final image. Assuming we're at the root of the repo, we -execute the build like this: +With the project built and the `.dockerignore` and `Dockerfile` in place, we are +now ready to build the final image. From the root of the repo, execute the +build: ```bash docker image build . -f packages/backend/Dockerfile --tag backstage diff --git a/packages/create-app/templates/default-app/packages/app/package.json.hbs b/packages/create-app/templates/default-app/packages/app/package.json.hbs index c7561fe091..d42e19eb1f 100644 --- a/packages/create-app/templates/default-app/packages/app/package.json.hbs +++ b/packages/create-app/templates/default-app/packages/app/package.json.hbs @@ -15,7 +15,6 @@ "@backstage/plugin-scaffolder": "^{{version '@backstage/plugin-scaffolder'}}", "@backstage/plugin-techdocs": "^{{version '@backstage/plugin-techdocs'}}", "@backstage/catalog-model": "^{{version '@backstage/catalog-model'}}", - "@backstage/plugin-circleci": "^{{version '@backstage/plugin-circleci'}}", "@backstage/plugin-tech-radar": "^{{version '@backstage/plugin-tech-radar'}}", "@backstage/plugin-github-actions": "^{{version '@backstage/plugin-github-actions'}}", "@backstage/plugin-user-settings": "^{{version '@backstage/plugin-user-settings'}}", diff --git a/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx b/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx index a3a3715ab0..de6ab2b6b5 100644 --- a/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx +++ b/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx @@ -17,9 +17,9 @@ import { ApiEntity, Entity } from '@backstage/catalog-model'; import { WarningPanel } from '@backstage/core'; import { ApiDefinitionCard, - ConsumedApisCard, - ConsumingComponentsCard, - ProvidedApisCard, + ConsumedApisCard, + ConsumingComponentsCard, + ProvidedApisCard, ProvidingComponentsCard } from '@backstage/plugin-api-docs'; import { @@ -28,9 +28,6 @@ import { import { useEntity } from '@backstage/plugin-catalog-react'; -import { - isPluginApplicableToEntity as isCircleCIAvailable, Router as CircleCIRouter -} from '@backstage/plugin-circleci'; import { isPluginApplicableToEntity as isGitHubActionsAvailable, Router as GitHubActionsRouter } from '@backstage/plugin-github-actions'; @@ -45,8 +42,6 @@ const CICDSwitcher = ({ entity }: { entity: Entity }) => { switch (true) { case isGitHubActionsAvailable(entity): return ; - case isCircleCIAvailable(entity): - return ; default: return ( diff --git a/packages/create-app/templates/default-app/packages/app/src/plugins.ts b/packages/create-app/templates/default-app/packages/app/src/plugins.ts index 28b42d5be2..df53885723 100644 --- a/packages/create-app/templates/default-app/packages/app/src/plugins.ts +++ b/packages/create-app/templates/default-app/packages/app/src/plugins.ts @@ -1,7 +1,6 @@ export { plugin as ApiDocs } from '@backstage/plugin-api-docs'; export { plugin as CatalogPlugin } from '@backstage/plugin-catalog'; export { plugin as CatalogImport } from '@backstage/plugin-catalog-import'; -export { plugin as Circleci } from '@backstage/plugin-circleci'; export { plugin as GithubActions } from '@backstage/plugin-github-actions'; export { plugin as ScaffolderPlugin } from '@backstage/plugin-scaffolder'; export { plugin as TechDocsPlugin } from '@backstage/plugin-techdocs'; From f3ba1fc4639ce484ecbaaa925f434611baec9cfb Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Tue, 23 Feb 2021 10:20:16 +0100 Subject: [PATCH 23/66] Add TaskWorker test Signed-off-by: Johan Haals --- .../src/scaffolder/tasks/TaskWorker.test.ts | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts new file mode 100644 index 0000000000..221b6dc625 --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts @@ -0,0 +1,112 @@ +/* + * Copyright 2021 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 { + getVoidLogger, + SingleConnectionDatabaseManager, +} from '@backstage/backend-common'; +import { TaskWorker } from './TaskWorker'; +import os from 'os'; +import { ActionContext, TemplateActionRegistry } from './TemplateConverter'; +import { ConfigReader, JsonObject } from '@backstage/config'; +import { StorageTaskBroker } from './StorageTaskBroker'; +import { DatabaseTaskStore } from './DatabaseTaskStore'; + +async function createStore(): Promise { + const manager = SingleConnectionDatabaseManager.fromConfig( + new ConfigReader({ + backend: { + database: { + client: 'sqlite3', + connection: ':memory:', + }, + }, + }), + ).forPlugin('scaffolder'); + return await DatabaseTaskStore.create(await manager.getClient()); +} + +describe('TaskWorker', () => { + let storage: DatabaseTaskStore; + + beforeAll(async () => { + storage = await createStore(); + }); + + const logger = getVoidLogger(); + const actionRegistry = new TemplateActionRegistry(); + actionRegistry.register({ + id: 'test-action', + handler: async (ctx: ActionContext) => { + ctx.output('testOutput', 'winning'); + }, + }); + + it('should fail when action does not exist', async () => { + const broker = new StorageTaskBroker(storage, logger); + const taskWorker = new TaskWorker({ + logger, + workingDirectory: os.tmpdir(), + actionRegistry, + taskBroker: broker, + }); + const { taskId } = await broker.dispatch({ + steps: [{ id: 'test', name: 'test', action: 'not-found-action' }], + output: { + result: '{{ steps.test.output.testOutput }}', + }, + }); + const task = await broker.claim(); + await taskWorker.runOneTask(task); + const { events } = await storage.listEvents({ taskId }); + const event = events.find(e => e.type === 'completion'); + + if (!event) { + throw new Error('Expected event'); + } + + expect((event.body?.error as JsonObject)?.message).toBe( + "Template action with ID 'not-found-action' is not registered.", + ); + }); + + it('should template output', async () => { + const broker = new StorageTaskBroker(storage, logger); + const taskWorker = new TaskWorker({ + logger, + workingDirectory: os.tmpdir(), + actionRegistry, + taskBroker: broker, + }); + + const { taskId } = await broker.dispatch({ + steps: [{ id: 'test', name: 'test', action: 'test-action' }], + output: { + result: '{{ steps.test.output.testOutput }}', + }, + }); + + const task = await broker.claim(); + await taskWorker.runOneTask(task); + + const { events } = await storage.listEvents({ taskId }); + const event = events.find(e => e.type === 'completion'); + if (!event) { + throw new Error('Expected event'); + } + expect((event.body?.output as JsonObject).result).toBe('winning'); + }); +}); From 561fd1645bc43e39590ff91ff2e8ad8c341c7aa2 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Thu, 25 Feb 2021 09:21:37 +0100 Subject: [PATCH 24/66] chore: fix expect Signed-off-by: Johan Haals --- .../src/scaffolder/tasks/TaskWorker.test.ts | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts index 221b6dc625..a75338778a 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts @@ -74,11 +74,7 @@ describe('TaskWorker', () => { const { events } = await storage.listEvents({ taskId }); const event = events.find(e => e.type === 'completion'); - if (!event) { - throw new Error('Expected event'); - } - - expect((event.body?.error as JsonObject)?.message).toBe( + expect((event?.body?.error as JsonObject)?.message).toBe( "Template action with ID 'not-found-action' is not registered.", ); }); @@ -104,9 +100,6 @@ describe('TaskWorker', () => { const { events } = await storage.listEvents({ taskId }); const event = events.find(e => e.type === 'completion'); - if (!event) { - throw new Error('Expected event'); - } - expect((event.body?.output as JsonObject).result).toBe('winning'); + expect((event?.body?.output as JsonObject).result).toBe('winning'); }); }); From 53a41207139891866d55f95b8ff42f5c118deb9a Mon Sep 17 00:00:00 2001 From: Tim Hansen Date: Thu, 4 Mar 2021 07:57:18 -0700 Subject: [PATCH 25/66] Remove peer dependency Signed-off-by: Tim Hansen --- packages/create-app/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/create-app/package.json b/packages/create-app/package.json index 05615cc78d..06c3430d1b 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -56,7 +56,6 @@ "@backstage/plugin-catalog": "*", "@backstage/plugin-catalog-backend": "*", "@backstage/plugin-catalog-import": "*", - "@backstage/plugin-circleci": "*", "@backstage/plugin-explore": "*", "@backstage/plugin-github-actions": "*", "@backstage/plugin-lighthouse": "*", From 2089de76be4f24cfb95e187e1dcd35de53e12462 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Thu, 4 Mar 2021 11:40:13 +0100 Subject: [PATCH 26/66] ItemCard: deprecate and replace with composable pieces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/slow-jobs-live.md | 6 + .changeset/strong-wasps-watch.md | 7 + .../src/layout/Breadcrumbs/Breadcrumbs.tsx | 4 +- .../src/layout/ItemCard/ItemCard.stories.tsx | 161 ++++++++++-------- .../core/src/layout/ItemCard/ItemCard.tsx | 119 +++++++------ .../src/layout/ItemCard/ItemCardGrid.test.tsx | 50 ++++++ .../core/src/layout/ItemCard/ItemCardGrid.tsx | 62 +++++++ .../layout/ItemCard/ItemCardHeader.test.tsx | 60 +++++++ .../src/layout/ItemCard/ItemCardHeader.tsx | 85 +++++++++ packages/core/src/layout/ItemCard/index.ts | 4 + packages/theme/src/baseTheme.ts | 19 +++ .../src/components/DomainCard/DomainCard.tsx | 56 ++++-- .../DomainCard/DomainCardGrid.test.tsx | 59 ------- .../components/DomainCard/DomainCardGrid.tsx | 33 ---- .../src/components/DomainCard/index.ts | 1 - .../DomainExplorerContent.tsx | 72 +++++--- .../ScaffolderPage/ScaffolderPage.tsx | 19 +-- .../components/TemplateCard/TemplateCard.tsx | 48 +++--- .../reader/components/TechDocsHome.test.tsx | 24 ++- .../src/reader/components/TechDocsHome.tsx | 48 +++--- 20 files changed, 593 insertions(+), 344 deletions(-) create mode 100644 .changeset/slow-jobs-live.md create mode 100644 .changeset/strong-wasps-watch.md create mode 100644 packages/core/src/layout/ItemCard/ItemCardGrid.test.tsx create mode 100644 packages/core/src/layout/ItemCard/ItemCardGrid.tsx create mode 100644 packages/core/src/layout/ItemCard/ItemCardHeader.test.tsx create mode 100644 packages/core/src/layout/ItemCard/ItemCardHeader.tsx delete mode 100644 plugins/explore/src/components/DomainCard/DomainCardGrid.test.tsx delete mode 100644 plugins/explore/src/components/DomainCard/DomainCardGrid.tsx diff --git a/.changeset/slow-jobs-live.md b/.changeset/slow-jobs-live.md new file mode 100644 index 0000000000..dd640a54f3 --- /dev/null +++ b/.changeset/slow-jobs-live.md @@ -0,0 +1,6 @@ +--- +'@backstage/core': patch +'@backstage/theme': patch +--- + +Deprecated `ItemCard`. Added `ItemCardGrid` and `ItemCardHeader` instead, that can be used to compose functionality around regular Material-UI `Card` components instead. diff --git a/.changeset/strong-wasps-watch.md b/.changeset/strong-wasps-watch.md new file mode 100644 index 0000000000..44923d5335 --- /dev/null +++ b/.changeset/strong-wasps-watch.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-explore': patch +'@backstage/plugin-scaffolder': patch +'@backstage/plugin-techdocs': patch +--- + +Make use of the new core `ItemCardGrid` and `ItemCardHeader` instead of the deprecated `ItemCard`. diff --git a/packages/core/src/layout/Breadcrumbs/Breadcrumbs.tsx b/packages/core/src/layout/Breadcrumbs/Breadcrumbs.tsx index d8cf88497f..a64b462377 100644 --- a/packages/core/src/layout/Breadcrumbs/Breadcrumbs.tsx +++ b/packages/core/src/layout/Breadcrumbs/Breadcrumbs.tsx @@ -87,8 +87,8 @@ export const Breadcrumbs = ({ children, ...props }: Props) => { }} > - {expandablePages.map(pageLink => ( - + {expandablePages.map((pageLink, index) => ( + {pageLink} ))} diff --git a/packages/core/src/layout/ItemCard/ItemCard.stories.tsx b/packages/core/src/layout/ItemCard/ItemCard.stories.tsx index eea78562e8..56d2328454 100644 --- a/packages/core/src/layout/ItemCard/ItemCard.stories.tsx +++ b/packages/core/src/layout/ItemCard/ItemCard.stories.tsx @@ -13,86 +13,101 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Grid } from '@material-ui/core'; + +import { + Card, + CardActions, + CardContent, + CardMedia, + makeStyles, + Typography, +} from '@material-ui/core'; import React from 'react'; import { MemoryRouter } from 'react-router'; -import { ItemCard } from '.'; +import { Button } from '../../components'; +import { ItemCardGrid } from './ItemCardGrid'; +import { ItemCardHeader } from './ItemCardHeader'; export default { - title: 'Layout/Item Card', - component: ItemCard, + title: 'Layout/Item Cards', }; +const text = + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'; + +const useStyles = makeStyles({ + grid: { + gridTemplateColumns: 'repeat(auto-fill, 12em)', + }, + header: { + color: 'black', + backgroundImage: 'linear-gradient(to bottom right, red, yellow)', + }, +}); + export const Default = () => ( - - - {}} - /> - - - {}} - /> - - -); - -export const Tags = () => ( - - - - - - - - - - - -); - -export const Link = () => ( - - - - - - - - + + The most basic setup is to place a bunch of cards into a large grid, + leaving styling to the defaults. Try to resize the window to see how they + rearrange themselves to fit the viewport. + + + {[...Array(10).keys()].map(index => ( + + + + + + {text + .split(' ') + .slice(0, 5 + Math.floor(Math.random() * 30)) + .join(' ')} + + + + + + ))} + ); + +export const Styling = () => { + const classes = useStyles(); + return ( + + + Both the grid and the header can be styled, using the{' '} + classes property. This lets + you for example tweak the column sizes and the background of the header. + + + {[...Array(10).keys()].map(index => ( + + + + + + {text + .split(' ') + .slice(0, 5 + Math.floor(Math.random() * 30)) + .join(' ')} + + + + + + ))} + + + ); +}; diff --git a/packages/core/src/layout/ItemCard/ItemCard.tsx b/packages/core/src/layout/ItemCard/ItemCard.tsx index 9e16def5d3..8c26e6a9f1 100644 --- a/packages/core/src/layout/ItemCard/ItemCard.tsx +++ b/packages/core/src/layout/ItemCard/ItemCard.tsx @@ -13,33 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Button, Card, Chip, makeStyles, Typography } from '@material-ui/core'; -import clsx from 'clsx'; +import { + Box, + Card, + CardActions, + CardContent, + CardMedia, + Chip, +} from '@material-ui/core'; import React, { ReactNode } from 'react'; -import { Link } from '../../components'; - -const useStyles = makeStyles(theme => ({ - header: { - color: theme.palette.common.white, - padding: theme.spacing(2, 2, 6), - backgroundImage: 'linear-gradient(-137deg, #4BB8A5 0%, #187656 100%)', - }, - content: { - padding: theme.spacing(2), - }, - description: { - height: 175, - overflow: 'hidden', - textOverflow: 'ellipsis', - }, - withTags: { - height: 'calc(175px - 32px - 8px)', - }, - footer: { - display: 'flex', - flexDirection: 'row-reverse', - }, -})); +import { Button } from '../../components'; +import { ItemCardHeader } from './ItemCardHeader'; type ItemCardProps = { description?: string; @@ -53,6 +37,31 @@ type ItemCardProps = { href?: string; }; +/** + * This card type has been deprecated. Instead use plain MUI Card and helpers + * where appropriate. + * + * + * + * + * + * @deprecated Use plain MUI and composable helpers instead. + * @see https://material-ui.com/components/cards/ + */ export const ItemCard = ({ description, tags, @@ -63,43 +72,33 @@ export const ItemCard = ({ onClick, href, }: ItemCardProps) => { - const classes = useStyles(); - return ( -
- {(subtitle || type) && ( - {subtitle ?? type} + + + + + {tags?.length ? ( + + {tags.map((tag, i) => ( + + ))} + + ) : null} + {description} + + + {!href && ( + )} - {title} -
-
- {tags?.map((tag, i) => ( - - ))} - 0 && classes.withTags, - )} - > - {description} - -
- {!href && ( - - )} - {href && ( - - )} -
-
+ {href && ( + + )} +
); }; diff --git a/packages/core/src/layout/ItemCard/ItemCardGrid.test.tsx b/packages/core/src/layout/ItemCard/ItemCardGrid.test.tsx new file mode 100644 index 0000000000..51ef8c544f --- /dev/null +++ b/packages/core/src/layout/ItemCard/ItemCardGrid.test.tsx @@ -0,0 +1,50 @@ +/* + * 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 { renderInTestApp } from '@backstage/test-utils'; +import { Card } from '@material-ui/core'; +import { screen } from '@testing-library/react'; +import React from 'react'; +import { ItemCardGrid } from './ItemCardGrid'; + +describe('', () => { + it('renders default without exploding', async () => { + await renderInTestApp( + + Hello! + , + ); + expect(screen.getByRole('grid')).toBeInTheDocument(); + expect(screen.getByText('Hello!')).toBeInTheDocument(); + }); + + it('renders custom styles', async () => { + await renderInTestApp( + <> + + Hello! + + + Goodbye! + + , + ); + expect(screen.getAllByRole('grid')[0]).toHaveStyle({ + gridTemplateColumns: 'repeat(auto-fill, minmax(22em, 1fr))', + }); + expect(screen.getAllByRole('grid')[1]).toHaveClass('my-css-class'); + }); +}); diff --git a/packages/core/src/layout/ItemCard/ItemCardGrid.tsx b/packages/core/src/layout/ItemCard/ItemCardGrid.tsx new file mode 100644 index 0000000000..551c3c67d5 --- /dev/null +++ b/packages/core/src/layout/ItemCard/ItemCardGrid.tsx @@ -0,0 +1,62 @@ +/* + * Copyright 2021 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 { createStyles, makeStyles, Theme, WithStyles } from '@material-ui/core'; +import React from 'react'; + +const styles = (theme: Theme) => + createStyles({ + root: { + display: 'grid', + gridTemplateColumns: 'repeat(auto-fill, minmax(22em, 1fr))', + gridAutoRows: '1fr', + gridGap: theme.spacing(2), + }, + }); + +const useStyles = makeStyles(styles); + +export type ItemCardGridProps = Partial> & { + /** + * The Card items of the grid. + */ + children?: React.ReactNode; +}; + +/** + * A default grid to use when arranging "item cards" - cards that let users + * select among several options. + * + * The immediate children are expected to be MUI Card components. + * + * Styles for the grid can be overridden using the `classes` prop, e.g.: + * + * + * + * + * + * This can be useful for e.g. overriding gridTemplateColumns to adapt the + * minimum size of the cells to fit the content better. + */ +export const ItemCardGrid = (props: ItemCardGridProps) => { + const { children, ...otherProps } = props; + const classes = useStyles(otherProps); + return ( +
+ {children} +
+ ); +}; diff --git a/packages/core/src/layout/ItemCard/ItemCardHeader.test.tsx b/packages/core/src/layout/ItemCard/ItemCardHeader.test.tsx new file mode 100644 index 0000000000..ec9af6436e --- /dev/null +++ b/packages/core/src/layout/ItemCard/ItemCardHeader.test.tsx @@ -0,0 +1,60 @@ +/* + * 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 { renderInTestApp } from '@backstage/test-utils'; +import { Card, CardMedia } from '@material-ui/core'; +import { screen } from '@testing-library/react'; +import React from 'react'; +import { ItemCardHeader } from './ItemCardHeader'; + +describe('', () => { + it('renders default without exploding', async () => { + await renderInTestApp( + + + + + , + ); + expect(screen.getByText('My Title')).toBeInTheDocument(); + expect(screen.getByText('My Subtitle')).toBeInTheDocument(); + }); + + it('renders custom children', async () => { + await renderInTestApp( + + + My Custom Text + + , + ); + expect(screen.getByText('My Title')).toBeInTheDocument(); + expect(screen.getByText('My Custom Text')).toBeInTheDocument(); + }); + + it('renders custom styles', async () => { + await renderInTestApp( + + + + My Custom Text + + + , + ); + expect(screen.getByText('My Custom Text')).toHaveClass('my-css-class'); + }); +}); diff --git a/packages/core/src/layout/ItemCard/ItemCardHeader.tsx b/packages/core/src/layout/ItemCard/ItemCardHeader.tsx new file mode 100644 index 0000000000..17c4a19e58 --- /dev/null +++ b/packages/core/src/layout/ItemCard/ItemCardHeader.tsx @@ -0,0 +1,85 @@ +/* + * Copyright 2021 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 { + createStyles, + makeStyles, + Theme, + Typography, + WithStyles, +} from '@material-ui/core'; +import React from 'react'; + +const styles = (theme: Theme) => + createStyles({ + root: { + color: theme.palette.common.white, + padding: theme.spacing(2, 2, 3), + backgroundImage: 'linear-gradient(-137deg, #4BB8A5 0%, #187656 100%)', + backgroundPosition: 0, + backgroundSize: 'inherit', + }, + }); + +const useStyles = makeStyles(styles); + +export type ItemCardHeaderProps = Partial> & { + /** + * A large title to show in the header, providing the main heading. + * + * Use this if you want to have the default styling and placement of a title. + */ + title?: React.ReactNode; + /** + * A slightly smaller title to show in the header, providing additional + * details. + * + * Use this if you want to have the default styling and placement of a + * subtitle. + */ + subtitle?: React.ReactNode; + /** + * Custom children to draw in the header. + * + * If the title and/or subtitle were specified, the children are drawn below + * those. + */ + children?: React.ReactNode; +}; + +/** + * A simple card header, rendering a default look for "item cards" - cards that + * are arranged in a grid for users to select among several options. + * + * This component expects to be placed within a MUI . + * + * Styles for the header can be overridden using the `classes` prop, e.g.: + * + * + * + * + */ +export const ItemCardHeader = (props: ItemCardHeaderProps) => { + const { title, subtitle, children } = props; + const classes = useStyles(props); + return ( +
+ {subtitle && {subtitle}} + {title && {title}} + {children} +
+ ); +}; diff --git a/packages/core/src/layout/ItemCard/index.ts b/packages/core/src/layout/ItemCard/index.ts index b38dd2acc2..da2c1dd546 100644 --- a/packages/core/src/layout/ItemCard/index.ts +++ b/packages/core/src/layout/ItemCard/index.ts @@ -15,3 +15,7 @@ */ export { ItemCard } from './ItemCard'; +export { ItemCardGrid } from './ItemCardGrid'; +export type { ItemCardGridProps } from './ItemCardGrid'; +export { ItemCardHeader } from './ItemCardHeader'; +export type { ItemCardHeaderProps } from './ItemCardHeader'; diff --git a/packages/theme/src/baseTheme.ts b/packages/theme/src/baseTheme.ts index 37b8e3b47f..e7bb7e58b6 100644 --- a/packages/theme/src/baseTheme.ts +++ b/packages/theme/src/baseTheme.ts @@ -234,12 +234,31 @@ export function createThemeOverrides(theme: BackstageTheme): Overrides { margin: `0 ${theme.spacing(0.5)}px 0 -${theme.spacing(0.5)}px`, }, }, + MuiCard: { + root: { + // When cards have a forced size, such as when they are arranged in a + // CSS grid, the content needs to flex such that the actions (buttons + // etc) end up at the bottom of the card instead of just below the body + // contents. + display: 'flex', + flexDirection: 'column', + }, + }, MuiCardHeader: { root: { // Reduce padding between header and content paddingBottom: 0, }, }, + MuiCardContent: { + root: { + // When cards have a forced size, such as when they are arranged in a + // CSS grid, the content needs to flex such that the actions (buttons + // etc) end up at the bottom of the card instead of just below the body + // contents. + flexGrow: 1, + }, + }, MuiCardActions: { root: { // We default to putting the card actions at the end diff --git a/plugins/explore/src/components/DomainCard/DomainCard.tsx b/plugins/explore/src/components/DomainCard/DomainCard.tsx index 67bee13696..ee06fd39fd 100644 --- a/plugins/explore/src/components/DomainCard/DomainCard.tsx +++ b/plugins/explore/src/components/DomainCard/DomainCard.tsx @@ -14,12 +14,20 @@ * limitations under the License. */ import { DomainEntity, RELATION_OWNED_BY } from '@backstage/catalog-model'; -import { ItemCard, useRouteRef } from '@backstage/core'; +import { Button, ItemCardHeader, useRouteRef } from '@backstage/core'; import { EntityRefLinks, entityRouteParams, getEntityRelations, } from '@backstage/plugin-catalog-react'; +import { + Box, + Card, + CardActions, + CardContent, + CardMedia, + Chip, +} from '@material-ui/core'; import React from 'react'; import { catalogEntityRouteRef } from '../../routes'; @@ -28,23 +36,39 @@ type DomainCardProps = { }; export const DomainCard = ({ entity }: DomainCardProps) => { - const ownedByRelations = getEntityRelations(entity, RELATION_OWNED_BY); const catalogEntityRoute = useRouteRef(catalogEntityRouteRef); - return ( - - } - label="Explore" - href={catalogEntityRoute(entityRouteParams(entity))} + const ownedByRelations = getEntityRelations(entity, RELATION_OWNED_BY); + const url = catalogEntityRoute(entityRouteParams(entity)); + + const owner = ( + ); + + return ( + + + + + + {entity.metadata.tags?.length ? ( + + {entity.metadata.tags.map(tag => ( + + ))} + + ) : null} + {entity.metadata.description} + + + + + + ); }; diff --git a/plugins/explore/src/components/DomainCard/DomainCardGrid.test.tsx b/plugins/explore/src/components/DomainCard/DomainCardGrid.test.tsx deleted file mode 100644 index ee06a50427..0000000000 --- a/plugins/explore/src/components/DomainCard/DomainCardGrid.test.tsx +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { DomainEntity } from '@backstage/catalog-model'; -import { renderInTestApp } from '@backstage/test-utils'; -import React from 'react'; -import { catalogEntityRouteRef } from '../../routes'; -import { DomainCardGrid } from './DomainCardGrid'; - -describe('', () => { - it('renders a grid of domain cards', async () => { - const entities: DomainEntity[] = [ - { - apiVersion: 'backstage.io/v1alpha1', - kind: 'Domain', - metadata: { - name: 'playback', - }, - spec: { - owner: 'guest', - }, - }, - { - apiVersion: 'backstage.io/v1alpha1', - kind: 'Domain', - metadata: { - name: 'artists', - }, - spec: { - owner: 'guest', - }, - }, - ]; - const { getByText } = await renderInTestApp( - , - { - mountedRoutes: { - '/catalog/:namespace/:kind/:name': catalogEntityRouteRef, - }, - }, - ); - - expect(getByText('artists')).toBeInTheDocument(); - expect(getByText('playback')).toBeInTheDocument(); - }); -}); diff --git a/plugins/explore/src/components/DomainCard/DomainCardGrid.tsx b/plugins/explore/src/components/DomainCard/DomainCardGrid.tsx deleted file mode 100644 index b4a13aa39f..0000000000 --- a/plugins/explore/src/components/DomainCard/DomainCardGrid.tsx +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2021 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 { DomainEntity } from '@backstage/catalog-model'; -import { Grid } from '@material-ui/core'; -import React from 'react'; -import { DomainCard } from '.'; - -type DomainCardGridProps = { - entities: DomainEntity[]; -}; - -export const DomainCardGrid = ({ entities }: DomainCardGridProps) => ( - - {entities.map((e, i) => ( - - - - ))} - -); diff --git a/plugins/explore/src/components/DomainCard/index.ts b/plugins/explore/src/components/DomainCard/index.ts index 6acf10800a..3511ff023d 100644 --- a/plugins/explore/src/components/DomainCard/index.ts +++ b/plugins/explore/src/components/DomainCard/index.ts @@ -14,4 +14,3 @@ * limitations under the License. */ export { DomainCard } from './DomainCard'; -export { DomainCardGrid } from './DomainCardGrid'; diff --git a/plugins/explore/src/components/DomainExplorerContent/DomainExplorerContent.tsx b/plugins/explore/src/components/DomainExplorerContent/DomainExplorerContent.tsx index 182adf70ea..a12811e486 100644 --- a/plugins/explore/src/components/DomainExplorerContent/DomainExplorerContent.tsx +++ b/plugins/explore/src/components/DomainExplorerContent/DomainExplorerContent.tsx @@ -18,6 +18,7 @@ import { Content, ContentHeader, EmptyState, + ItemCardGrid, Progress, SupportButton, useApi, @@ -27,47 +28,64 @@ import { catalogApiRef } from '@backstage/plugin-catalog-react'; import { Button } from '@material-ui/core'; import React from 'react'; import { useAsync } from 'react-use'; -import { DomainCardGrid } from '../DomainCard'; +import { DomainCard } from '../DomainCard'; -export const DomainExplorerContent = () => { +const Body = () => { const catalogApi = useApi(catalogApiRef); const { value: entities, loading, error } = useAsync(async () => { const response = await catalogApi.getEntities({ filter: { kind: 'domain' }, }); - return response.items as DomainEntity[]; }, [catalogApi]); + if (loading) { + return ; + } + + if (error) { + return ( + + {error.message} + + ); + } + + if (!entities?.length) { + return ( + + Read more + + } + /> + ); + } + + return ( + + {entities.map((entity, index) => ( + + ))} + + ); +}; + +export const DomainExplorerContent = () => { return ( Discover the domains in your ecosystem. - - {loading && } - {error && ( - - {error.message} - - )} - {!loading && !error && (!entities || entities.length === 0) && ( - - Read more - - } - /> - )} - {!loading && entities && } + ); }; diff --git a/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx b/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx index ebce35a91f..8e8ba15ddc 100644 --- a/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx +++ b/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx @@ -14,14 +14,13 @@ * limitations under the License. */ -import React, { useEffect, useMemo, useState } from 'react'; -import { Link as RouterLink } from 'react-router-dom'; import { EntityMeta, TemplateEntityV1alpha1 } from '@backstage/catalog-model'; import { configApiRef, Content, ContentHeader, Header, + ItemCardGrid, Lifecycle, Page, Progress, @@ -30,14 +29,16 @@ import { WarningPanel, } from '@backstage/core'; import { useStarredEntities } from '@backstage/plugin-catalog-react'; -import { Box, Button, Link, makeStyles, Typography } from '@material-ui/core'; +import { Button, Link, makeStyles, Typography } from '@material-ui/core'; import StarIcon from '@material-ui/icons/Star'; +import React, { useEffect, useMemo, useState } from 'react'; +import { Link as RouterLink } from 'react-router-dom'; import { EntityFilterGroupsProvider, useFilteredEntities } from '../../filter'; -import { TemplateCard, TemplateCardProps } from '../TemplateCard'; import { ResultsFilter } from '../ResultsFilter/ResultsFilter'; import { ScaffolderFilter } from '../ScaffolderFilter'; import { ButtonGroup } from '../ScaffolderFilter/ScaffolderFilter'; import SearchToolbar from '../SearchToolbar/SearchToolbar'; +import { TemplateCard, TemplateCardProps } from '../TemplateCard'; const useStyles = makeStyles(theme => ({ contentWrapper: { @@ -46,12 +47,6 @@ const useStyles = makeStyles(theme => ({ gridTemplateColumns: '250px 1fr', gridColumnGap: theme.spacing(2), }, - templateGrid: { - display: 'grid', - gridTemplateColumns: 'repeat(auto-fill, minmax(22em, 1fr))', - gridAutoRows: '1fr', - gridGap: theme.spacing(2), - }, })); const getTemplateCardProps = ( @@ -183,13 +178,13 @@ export const ScaffolderPageContents = () => { )} - + {matchingEntities && matchingEntities?.length > 0 && matchingEntities.map(template => ( ))} - +
diff --git a/plugins/scaffolder/src/components/TemplateCard/TemplateCard.tsx b/plugins/scaffolder/src/components/TemplateCard/TemplateCard.tsx index 26d5c79cc7..25f89f20ab 100644 --- a/plugins/scaffolder/src/components/TemplateCard/TemplateCard.tsx +++ b/plugins/scaffolder/src/components/TemplateCard/TemplateCard.tsx @@ -13,29 +13,25 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Button, useRouteRef } from '@backstage/core'; +import { Button, ItemCardHeader, useRouteRef } from '@backstage/core'; import { BackstageTheme, pageTheme } from '@backstage/theme'; import { + Box, Card, CardActions, CardContent, CardMedia, Chip, makeStyles, - Typography, useTheme, } from '@material-ui/core'; import React from 'react'; import { generatePath } from 'react-router'; import { rootRouteRef } from '../../routes'; -const useStyles = makeStyles(theme => ({ - header: { - color: theme.palette.common.white, - padding: theme.spacing(2, 2, 3), - backgroundImage: (props: { backgroundImage: string }) => - props.backgroundImage, - backgroundPosition: 0, +const useStyles = makeStyles({ + title: { + backgroundImage: ({ backgroundImage }: any) => backgroundImage, }, description: { overflow: 'hidden', @@ -44,14 +40,7 @@ const useStyles = makeStyles(theme => ({ '-webkit-line-clamp': 10, '-webkit-box-orient': 'vertical', }, - card: { - display: 'flex', - flexDirection: 'column', - }, - cardContent: { - flexGrow: 1, - }, -})); +}); export type TemplateCardProps = { description: string; @@ -79,18 +68,21 @@ export const TemplateCard = ({ }); return ( - - - {type} - {title} + + + - - {tags?.map(tag => ( - - ))} - - {description} - + + + {tags?.map(tag => ( + + ))} + + {description} + + + ))} + ); From b17819246cb0a75fcf4a7fe663ef5faa872eba25 Mon Sep 17 00:00:00 2001 From: Tim Hansen Date: Thu, 4 Mar 2021 11:58:20 -0700 Subject: [PATCH 27/66] Add note about monorepo Signed-off-by: Tim Hansen --- docs/getting-started/configure-app-with-plugins.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/getting-started/configure-app-with-plugins.md b/docs/getting-started/configure-app-with-plugins.md index af7fdbb4cb..cf7806a5d2 100644 --- a/docs/getting-started/configure-app-with-plugins.md +++ b/docs/getting-started/configure-app-with-plugins.md @@ -22,7 +22,12 @@ plugin in this example. yarn workspace app add @backstage/plugin-circleci ``` -2. Add the plugin itself: +Note the plugin is added to the `app` package, rather than the root +package.json. Backstage Apps are set up as monorepos with +[yarn workspaces](https://classic.yarnpkg.com/en/docs/workspaces/). Since +CircleCI is a frontend UI plugin, it goes in `app` rather than `backend`. + +2. Add the plugin itself to the App: ```js // packages/app/src/plugins.ts From b876bc6d9afdf9047d629c7e6815d2da83ef0054 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 5 Mar 2021 04:33:20 +0000 Subject: [PATCH 28/66] chore(deps-dev): bump @storybook/addons from 6.1.20 to 6.1.21 Bumps [@storybook/addons](https://github.com/storybookjs/storybook/tree/HEAD/lib/addons) from 6.1.20 to 6.1.21. - [Release notes](https://github.com/storybookjs/storybook/releases) - [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md) - [Commits](https://github.com/storybookjs/storybook/commits/v6.1.21/lib/addons) Signed-off-by: dependabot[bot] --- yarn.lock | 96 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 95 insertions(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index 24cd8d5b51..c258de0c0c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4713,7 +4713,7 @@ global "^4.3.2" regenerator-runtime "^0.13.7" -"@storybook/addons@6.1.20", "@storybook/addons@^6.1.11": +"@storybook/addons@6.1.20": version "6.1.20" resolved "https://registry.npmjs.org/@storybook/addons/-/addons-6.1.20.tgz#da01dabd6692919b719fcb30519d53ea80887097" integrity sha512-kIhXYgF+ARNpYxO3qhz8yThDvKpaq+HDst8odPU9sCNEI66PSH6hrILhTmnffNnqdtY3LnKkU9rGVfZn+3TOTA== @@ -4728,6 +4728,21 @@ global "^4.3.2" regenerator-runtime "^0.13.7" +"@storybook/addons@^6.1.11": + version "6.1.21" + resolved "https://registry.npmjs.org/@storybook/addons/-/addons-6.1.21.tgz#94bb66fc51d1dfee80d0fe84f5b83c10045651b5" + integrity sha512-xo5TGu9EZVCqgh3D1veVnfuGzyKDWWsvOMo18phVqRxj21G3/+hScVyfIYwNTv7Ys5/Ahp9JxJUMXL3V3ny+tw== + dependencies: + "@storybook/api" "6.1.21" + "@storybook/channels" "6.1.21" + "@storybook/client-logger" "6.1.21" + "@storybook/core-events" "6.1.21" + "@storybook/router" "6.1.21" + "@storybook/theming" "6.1.21" + core-js "^3.0.1" + global "^4.3.2" + regenerator-runtime "^0.13.7" + "@storybook/api@6.1.11": version "6.1.11" resolved "https://registry.npmjs.org/@storybook/api/-/api-6.1.11.tgz#1e0b798203df823ac21184386258cf8b5f17f440" @@ -4828,6 +4843,31 @@ ts-dedent "^2.0.0" util-deprecate "^1.0.2" +"@storybook/api@6.1.21": + version "6.1.21" + resolved "https://registry.npmjs.org/@storybook/api/-/api-6.1.21.tgz#be753ca8d3602efe4a11783c81c689463bee0825" + integrity sha512-QjZk70VSXMw/wPPoWdMp5Bl9VmkfmGhIz8PALrFLLEZHjzptpfZE2qkGEEJHG0NAksFUv6NxGki2/632dzR7Ug== + dependencies: + "@reach/router" "^1.3.3" + "@storybook/channels" "6.1.21" + "@storybook/client-logger" "6.1.21" + "@storybook/core-events" "6.1.21" + "@storybook/csf" "0.0.1" + "@storybook/router" "6.1.21" + "@storybook/semver" "^7.3.2" + "@storybook/theming" "6.1.21" + "@types/reach__router" "^1.3.7" + core-js "^3.0.1" + fast-deep-equal "^3.1.1" + global "^4.3.2" + lodash "^4.17.15" + memoizerific "^1.11.3" + regenerator-runtime "^0.13.7" + store2 "^2.7.1" + telejson "^5.0.2" + ts-dedent "^2.0.0" + util-deprecate "^1.0.2" + "@storybook/channel-postmessage@6.1.15": version "6.1.15" resolved "https://registry.npmjs.org/@storybook/channel-postmessage/-/channel-postmessage-6.1.15.tgz#80ea2346d18496f9710dd7f87fd2a9eca46ef36f" @@ -4890,6 +4930,15 @@ ts-dedent "^2.0.0" util-deprecate "^1.0.2" +"@storybook/channels@6.1.21": + version "6.1.21" + resolved "https://registry.npmjs.org/@storybook/channels/-/channels-6.1.21.tgz#adbfae5f4767234c5b17d9578be983584dddead4" + integrity sha512-7WoizMjyHqCyvcWncLexSg9FLPIErWAZL4NvluEthwsHSO2sDybn9mh1pzsFHdYMuTP6ml06Zt9ayWMtIveHDg== + dependencies: + core-js "^3.0.1" + ts-dedent "^2.0.0" + util-deprecate "^1.0.2" + "@storybook/client-api@6.1.15": version "6.1.15" resolved "https://registry.npmjs.org/@storybook/client-api/-/client-api-6.1.15.tgz#8f8ead111459b94621571bdb2276f8a0aace17b1" @@ -4970,6 +5019,14 @@ core-js "^3.0.1" global "^4.3.2" +"@storybook/client-logger@6.1.21": + version "6.1.21" + resolved "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-6.1.21.tgz#fe7d9e645ddb4eb9dc18fdacea24b4baf11bc6c9" + integrity sha512-QJV+gnVM2fQ4M7lSkRLCXkOw/RU+aEtUefo9TAnXxPHK3UGG+DyvLmha6fHGaz9GAcFxyWtgqCyVOhMe03Q35g== + dependencies: + core-js "^3.0.1" + global "^4.3.2" + "@storybook/components@6.1.15": version "6.1.15" resolved "https://registry.npmjs.org/@storybook/components/-/components-6.1.15.tgz#b4a2af23ee6b9cba4c255191eae3d3463e29bfb7" @@ -5077,6 +5134,13 @@ dependencies: core-js "^3.0.1" +"@storybook/core-events@6.1.21": + version "6.1.21" + resolved "https://registry.npmjs.org/@storybook/core-events/-/core-events-6.1.21.tgz#11f537f78f8c73ba5e627b57b282a279793a3511" + integrity sha512-KWqnh1C7M1pT//WfQb3AD60yTR8jL48AfaeLGto2gO9VK7VVgj/EGsrXZP/GTL90ygyExbbBI5gkr7EBTu/HYw== + dependencies: + core-js "^3.0.1" + "@storybook/core@6.1.15": version "6.1.15" resolved "https://registry.npmjs.org/@storybook/core/-/core-6.1.15.tgz#7ff8c314d3857497bf2e26c69a1fa93ef37301aa" @@ -5277,6 +5341,18 @@ memoizerific "^1.11.3" qs "^6.6.0" +"@storybook/router@6.1.21": + version "6.1.21" + resolved "https://registry.npmjs.org/@storybook/router/-/router-6.1.21.tgz#0a822fa9cc67589a082f7a10fff15c8413f17706" + integrity sha512-m75WvUhoCBWDVekICAdbkidji/w5hCjHo+M8L13UghpwXWEnyr4/QqvkOb/PcSC8aZzxeMqSCpRQ1o6LWULneg== + dependencies: + "@reach/router" "^1.3.3" + "@types/reach__router" "^1.3.7" + core-js "^3.0.1" + global "^4.3.2" + memoizerific "^1.11.3" + qs "^6.6.0" + "@storybook/semver@^7.3.2": version "7.3.2" resolved "https://registry.npmjs.org/@storybook/semver/-/semver-7.3.2.tgz#f3b9c44a1c9a0b933c04e66d0048fcf2fa10dac0" @@ -5374,6 +5450,24 @@ resolve-from "^5.0.0" ts-dedent "^2.0.0" +"@storybook/theming@6.1.21": + version "6.1.21" + resolved "https://registry.npmjs.org/@storybook/theming/-/theming-6.1.21.tgz#b8e612e5a39b77f7e63a5f9ea322ed62adb0d5b0" + integrity sha512-yq7+/mpdljRdSRJYw/In/9tnDGXIUDe//mhyMftFfrB2mq6zi1yAZpowCerWhiDE2ipGkrfzIYx/Sn7bcaXgqg== + dependencies: + "@emotion/core" "^10.1.1" + "@emotion/is-prop-valid" "^0.8.6" + "@emotion/styled" "^10.0.23" + "@storybook/client-logger" "6.1.21" + core-js "^3.0.1" + deep-object-diff "^1.1.0" + emotion-theming "^10.0.19" + global "^4.3.2" + memoizerific "^1.11.3" + polished "^3.4.4" + resolve-from "^5.0.0" + ts-dedent "^2.0.0" + "@storybook/ui@6.1.15": version "6.1.15" resolved "https://registry.npmjs.org/@storybook/ui/-/ui-6.1.15.tgz#a0f6c49fcf81cf172cd2de4c8dba2be1296891f6" From d5d94b545537b8bb595d60101c039337052bd63a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 5 Mar 2021 04:36:10 +0000 Subject: [PATCH 29/66] chore(deps): bump apollo-server from 2.16.1 to 2.21.0 Bumps [apollo-server](https://github.com/apollographql/apollo-server/tree/HEAD/packages/apollo-server) from 2.16.1 to 2.21.0. - [Release notes](https://github.com/apollographql/apollo-server/releases) - [Changelog](https://github.com/apollographql/apollo-server/blob/main/CHANGELOG.md) - [Commits](https://github.com/apollographql/apollo-server/commits/apollo-server@2.21.0/packages/apollo-server) Signed-off-by: dependabot[bot] --- yarn.lock | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/yarn.lock b/yarn.lock index 24cd8d5b51..64546d2eee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7633,7 +7633,7 @@ apollo-server-caching@^0.5.3: dependencies: lru-cache "^6.0.0" -apollo-server-core@^2.16.1, apollo-server-core@^2.21.0: +apollo-server-core@^2.21.0: version "2.21.0" resolved "https://registry.npmjs.org/apollo-server-core/-/apollo-server-core-2.21.0.tgz#12ee11aee61fa124f11b1d73cae2e068112a3a53" integrity sha512-GtIiq2F0dVDLzzIuO5+dK/pGq/sGxYlKCqAuQQqzYg0fvZ7fukyluXtcTe0tMI+FJZjU0j0WnKgiLsboCoAlPQ== @@ -7677,7 +7677,7 @@ apollo-server-errors@^2.4.2: resolved "https://registry.npmjs.org/apollo-server-errors/-/apollo-server-errors-2.4.2.tgz#1128738a1d14da989f58420896d70524784eabe5" integrity sha512-FeGxW3Batn6sUtX3OVVUm7o56EgjxDlmgpTLNyWcLb0j6P8mw9oLNyAm3B+deHA4KNdNHO5BmHS2g1SJYjqPCQ== -apollo-server-express@^2.16.1: +apollo-server-express@^2.16.1, apollo-server-express@^2.21.0: version "2.21.0" resolved "https://registry.npmjs.org/apollo-server-express/-/apollo-server-express-2.21.0.tgz#29bd4ec728e1992da240c5956c3ce6d95c1d252e" integrity sha512-zbOSNGuxUjlOFZnRrbMpga3pKDEroitF4NAqoVxgBivx7v2hGsE7rljct3PucTx2cMN90AyYe3cU4oA8jBxZIQ== @@ -7717,15 +7717,16 @@ apollo-server-types@^0.6.3: apollo-server-env "^3.0.0" apollo-server@^2.16.1: - version "2.16.1" - resolved "https://registry.npmjs.org/apollo-server/-/apollo-server-2.16.1.tgz#edc319606eb29f73132239bdc005dd88ac40a142" - integrity sha512-oy9NVRzGwlpQ+W1DwLKRH+KASmodSYpvYIRY5DMAZtGqNmT2zOCpbIZVjBt23SuPB5NhIhhE4ROzoObRv3zy5w== + version "2.21.0" + resolved "https://registry.npmjs.org/apollo-server/-/apollo-server-2.21.0.tgz#4e62131885b4a8a26bb8b5e77177bd0d4d210852" + integrity sha512-OqngjOSB0MEH6VKGWHcrqt4y39HlhYh9CrMvn4PhadTt53IPYRmBglk5qSRA8xMorGqy60iKrOReqj5YfCjTOg== dependencies: - apollo-server-core "^2.16.1" - apollo-server-express "^2.16.1" + apollo-server-core "^2.21.0" + apollo-server-express "^2.21.0" express "^4.0.0" graphql-subscriptions "^1.0.0" - graphql-tools "^4.0.0" + graphql-tools "^4.0.8" + stoppable "^1.1.0" apollo-tracing@^0.12.2: version "0.12.2" @@ -14125,7 +14126,7 @@ graphql-tools@5.0.0: tslib "^1.11.1" uuid "^7.0.3" -graphql-tools@^4.0.0, graphql-tools@^4.0.8: +graphql-tools@^4.0.8: version "4.0.8" resolved "https://registry.npmjs.org/graphql-tools/-/graphql-tools-4.0.8.tgz#e7fb9f0d43408fb0878ba66b522ce871bafe9d30" integrity sha512-MW+ioleBrwhRjalKjYaLQbr+920pHBgy9vM/n47sswtns8+96sRn5M/G+J1eu7IMeKWiN/9p6tmwCHU7552VJg== From 9f79d9dd1529df77d611e8a4b1bfbfb0439bcdae Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 5 Mar 2021 04:36:31 +0000 Subject: [PATCH 30/66] chore(deps-dev): bump @types/html-webpack-plugin from 3.2.3 to 3.2.4 Bumps [@types/html-webpack-plugin](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/html-webpack-plugin) from 3.2.3 to 3.2.4. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/html-webpack-plugin) Signed-off-by: dependabot[bot] --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 24cd8d5b51..e713355202 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6054,9 +6054,9 @@ "@types/uglify-js" "*" "@types/html-webpack-plugin@*", "@types/html-webpack-plugin@^3.2.2": - version "3.2.3" - resolved "https://registry.npmjs.org/@types/html-webpack-plugin/-/html-webpack-plugin-3.2.3.tgz#865323e30e82560c0ca898dbf9f6f9d1c541cd7f" - integrity sha512-Y7dsVhTn75IaD4lMIY02UP1L8e0ou8KQu8DKPJAegEFKdJR28/8ejayDG8ykfR0DtYCx3dCEHIkdpN8AOB6txQ== + version "3.2.4" + resolved "https://registry.npmjs.org/@types/html-webpack-plugin/-/html-webpack-plugin-3.2.4.tgz#ed770ddfec53ed2aa6b5f4523acca291192235c6" + integrity sha512-WM0s78bfCIXnTlICf+8nWP0IvP+fn4YfiI3uxAX1K1PSRpzs0iysp03j4zR0xTgxSqF67TbOsHs49YXonRAkeQ== dependencies: "@types/html-minifier" "*" "@types/tapable" "*" From 5f85dd34797ea2cb79fdac5f42d87fb03c7a2aca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Fri, 5 Mar 2021 09:39:26 +0100 Subject: [PATCH 31/66] yarn.lock missing changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- yarn.lock | 27 +-------------------------- 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/yarn.lock b/yarn.lock index bf55104a3b..0059ae53c1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1998,31 +1998,6 @@ "@types/react" "^16.9" classnames "^2.2.6" git-url-parse "^11.4.4" - moment "^2.26.0" - react "^16.13.1" - react-dom "^16.13.1" - react-helmet "6.1.0" - react-router "6.0.0-beta.0" - react-router-dom "6.0.0-beta.0" - react-use "^15.3.3" - swr "^0.3.0" - -"@backstage/plugin-scaffolder@^0.4.1": - version "0.4.2" - resolved "https://registry.npmjs.org/@backstage/plugin-scaffolder/-/plugin-scaffolder-0.4.2.tgz#58159227997f7e248ce52535bc32f19fcd0990dc" - integrity sha512-YuyHM587Rqg6KufxfFqQdI7dsZniBM/11Aj8Q0m5ZszOpCuNmDDkR1VX8MKHTBJ709mnLAqRgArdla7FOrOAXQ== - dependencies: - "@backstage/catalog-client" "^0.3.6" - "@backstage/catalog-model" "^0.7.1" - "@backstage/core" "^0.6.2" - "@backstage/plugin-catalog-react" "^0.0.4" - "@backstage/theme" "^0.2.3" - "@material-ui/core" "^4.11.0" - "@material-ui/icons" "^4.9.1" - "@material-ui/lab" "4.0.0-alpha.45" - "@types/react" "^16.9" - classnames "^2.2.6" - git-url-parse "^11.4.4" react "^16.13.1" react-dom "^16.13.1" react-helmet "6.1.0" @@ -21471,7 +21446,7 @@ qs@6.7.0: resolved "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== -qs@^6.5.1, qs@^6.6.0, qs@^6.7.0, qs@^6.9.1, qs@^6.9.4: +qs@^6.5.1, qs@^6.5.2, qs@^6.6.0, qs@^6.7.0, qs@^6.9.1, qs@^6.9.4: version "6.9.6" resolved "https://registry.npmjs.org/qs/-/qs-6.9.6.tgz#26ed3c8243a431b2924aca84cc90471f35d5a0ee" integrity sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ== From 6e435e9a0814fcd41d16c4d28547de3b768212a6 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Fri, 5 Mar 2021 09:48:37 +0100 Subject: [PATCH 32/66] Update tests Signed-off-by: Johan Haals --- .../src/scaffolder/tasks/TaskWorker.test.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts index a75338778a..6facaa21f2 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts @@ -20,10 +20,10 @@ import { } from '@backstage/backend-common'; import { TaskWorker } from './TaskWorker'; import os from 'os'; -import { ActionContext, TemplateActionRegistry } from './TemplateConverter'; import { ConfigReader, JsonObject } from '@backstage/config'; import { StorageTaskBroker } from './StorageTaskBroker'; import { DatabaseTaskStore } from './DatabaseTaskStore'; +import { TemplateActionRegistry } from '../actions'; async function createStore(): Promise { const manager = SingleConnectionDatabaseManager.fromConfig( @@ -50,7 +50,7 @@ describe('TaskWorker', () => { const actionRegistry = new TemplateActionRegistry(); actionRegistry.register({ id: 'test-action', - handler: async (ctx: ActionContext) => { + handler: async ctx => { ctx.output('testOutput', 'winning'); }, }); @@ -68,6 +68,7 @@ describe('TaskWorker', () => { output: { result: '{{ steps.test.output.testOutput }}', }, + values: {}, }); const task = await broker.claim(); await taskWorker.runOneTask(task); @@ -93,6 +94,7 @@ describe('TaskWorker', () => { output: { result: '{{ steps.test.output.testOutput }}', }, + values: {}, }); const task = await broker.claim(); From c20bc8abc454b2ec93cf10e2e16f267cba3b84a7 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Fri, 5 Mar 2021 09:54:27 +0100 Subject: [PATCH 33/66] Don't json parse undefined input Signed-off-by: Johan Haals --- .../src/scaffolder/tasks/TaskWorker.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts index 4d8f652ae0..bd0a418598 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts @@ -98,9 +98,9 @@ export class TaskWorker { throw new Error(`Action '${step.action}' does not exist`); } - const input = JSON.parse( - JSON.stringify(step.input), - (_key, value) => { + const input = + step.input && + JSON.parse(JSON.stringify(step.input), (_key, value) => { if (typeof value === 'string') { return handlebars.compile(value, { noEscape: true, @@ -110,8 +110,7 @@ export class TaskWorker { })(templateCtx); } return value; - }, - ); + }); if (action.schema?.input) { const validateResult = validateJsonSchema(input, action.schema, { From 882ba3c95df7d1c5ff70d5e3b8b39f862149a402 Mon Sep 17 00:00:00 2001 From: erdoganoksuz Date: Fri, 5 Mar 2021 12:06:02 +0300 Subject: [PATCH 34/66] add trendyol to adopter list Signed-off-by: erdoganoksuz --- ADOPTERS.md | 47 ++++++++++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/ADOPTERS.md b/ADOPTERS.md index e1539372ab..9f2dbf3c06 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -1,23 +1,24 @@ -| Organization | Contact | Description of Use | -| --------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | -| [Spotify](https://www.spotify.com) | [@leemills83](https://github.com/leemills83) | Main interface towards all of Spotify's infrastructure and technical documentation. | -| [bol.com](https://www.bol.com) | [@RoyJacobs](https://github.com/RoyJacobs) | Initial work being done to unify platform tooling. | -| [DFDS](https://www.dfds.com) | [@carlsendk](https://github.com/carlsendk) | V2 self-service platform. | -| [Roadie](https://roadie.io) | [@dtuite](https://github.com/dtuite) | Hosted, managed Backstage with easy set-up | -| [Roku](https://www.roku.com) | [@timurista](https://github.com/timurista) | Initial work on Cloud engineering service platform. | -| [SDA SE](https://sda.se) | [@Fox32](https://github.com/Fox32) | Central place for developing and sharing services in our insurance ecosystem. | -| [H-E-B](https://www.heb.com) | [@german-j-rodriguez](https://github.com/german-j-rodriguez) | Initial work on Engineering Portal service platform. | -| [American Airlines](https://www.aa.com) | [@paulpach](https://github.com/paulpach) | Central place for developers to develop and maintain applications | -| [Kiwi.com](https://kiwi.com) | [@aexvir](https://github.com/aexvir) | Replacing the frontend of [The Zoo](https://github.com/kiwicom/the-zoo), their service registry. | -| [Voi](https://www.voiscooters.com/) | [@K-Phoen](https://github.com/K-Phoen) | Developer portal, main gateway to our infrastructure, documentation and internal tooling. | -| [Talkdesk](https://www.talkdesk.com) | [@jaime-talkdesk](https://github.com/jaime-talkdesk) | Initial work for Engineering Portal and Self Provisioning to R&D | -| [Wealthsimple](https://www.wealthsimple.com) | [@andrewthauer](https://github.com/andrewthauer) | Developer portal, service catalog, documentation and tooling | -| [Grab](https://www.grab.com) | [@althafh](https://github.com/althafh) | Initial work as a unified interface for all of Grab's internal tooling | -| [Telenor Sweden](https://www.telenor.se) | [@O5ten](https://github.com/O5ten) | Building a developer portal for scaffolding projects towards our unified build environment and microservice stacks | -| [Fiverr](https://www.fiverr.com) | [@nirga](https://github.com/nirga) | Unifying separate tools that developers are using today (i.e. monitoring, dead letter queues management, etc.) into a single platform. | -| [Zalando SE](https://www.zalando.de) | [@leviferreira](https://github.com/leviferreira) | Building V2 of the Internal Development Portal. | -| [LegalZoom](https://legalzoom.com) | [@backjo](https://github.com/backjo) | Developer portal - hub for all engineering projects and metadata. | -| [Expedia Group](https://www.expediagroup.com) | [Mike Turner](mailto:miturner@expediagroup.com), [Sneha Kumar](mailto:snkumar@expediagroup.com), [@guillermomanzo](https://github.com/guillermomanzo) | EG Common Developer Toolkit | -| [Paddle.com](https://paddle.com) | [Ioannis Georgoulas](https://github.com/geototti21) | Developer portal (Tech Docs, Service Catalog, Internal Tooling), we use vanilla Backstage FE and custom BE implementation in Go | -| [Acast.com](https://acast.com) | [Olle Lundberg](https://github.com/lndbrg) | Developer portal with tech docs, service catalog and a bunch of other internal tooling | -| [Lunar](https://lunar.app) | [Jacob Valdemar](https://github.com/JacobValdemar) | Internal developer portal for service overview and insights, API documentation, technical guides, onboarding guides and RFC's. | +| Organization | Contact | Description of Use | +| --------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | +| [Spotify](https://www.spotify.com) | [@leemills83](https://github.com/leemills83) | Main interface towards all of Spotify's infrastructure and technical documentation. | +| [bol.com](https://www.bol.com) | [@RoyJacobs](https://github.com/RoyJacobs) | Initial work being done to unify platform tooling. | +| [DFDS](https://www.dfds.com) | [@carlsendk](https://github.com/carlsendk) | V2 self-service platform. | +| [Roadie](https://roadie.io) | [@dtuite](https://github.com/dtuite) | Hosted, managed Backstage with easy set-up | +| [Roku](https://www.roku.com) | [@timurista](https://github.com/timurista) | Initial work on Cloud engineering service platform. | +| [SDA SE](https://sda.se) | [@Fox32](https://github.com/Fox32) | Central place for developing and sharing services in our insurance ecosystem. | +| [H-E-B](https://www.heb.com) | [@german-j-rodriguez](https://github.com/german-j-rodriguez) | Initial work on Engineering Portal service platform. | +| [American Airlines](https://www.aa.com) | [@paulpach](https://github.com/paulpach) | Central place for developers to develop and maintain applications | +| [Kiwi.com](https://kiwi.com) | [@aexvir](https://github.com/aexvir) | Replacing the frontend of [The Zoo](https://github.com/kiwicom/the-zoo), their service registry. | +| [Voi](https://www.voiscooters.com/) | [@K-Phoen](https://github.com/K-Phoen) | Developer portal, main gateway to our infrastructure, documentation and internal tooling. | +| [Talkdesk](https://www.talkdesk.com) | [@jaime-talkdesk](https://github.com/jaime-talkdesk) | Initial work for Engineering Portal and Self Provisioning to R&D | +| [Wealthsimple](https://www.wealthsimple.com) | [@andrewthauer](https://github.com/andrewthauer) | Developer portal, service catalog, documentation and tooling | +| [Grab](https://www.grab.com) | [@althafh](https://github.com/althafh) | Initial work as a unified interface for all of Grab's internal tooling | +| [Telenor Sweden](https://www.telenor.se) | [@O5ten](https://github.com/O5ten) | Building a developer portal for scaffolding projects towards our unified build environment and microservice stacks | +| [Fiverr](https://www.fiverr.com) | [@nirga](https://github.com/nirga) | Unifying separate tools that developers are using today (i.e. monitoring, dead letter queues management, etc.) into a single platform. | +| [Zalando SE](https://www.zalando.de) | [@leviferreira](https://github.com/leviferreira) | Building V2 of the Internal Development Portal. | +| [LegalZoom](https://legalzoom.com) | [@backjo](https://github.com/backjo) | Developer portal - hub for all engineering projects and metadata. | +| [Expedia Group](https://www.expediagroup.com) | [Mike Turner](mailto:miturner@expediagroup.com), [Sneha Kumar](mailto:snkumar@expediagroup.com), [@guillermomanzo](https://github.com/guillermomanzo) | EG Common Developer Toolkit | +| [Paddle.com](https://paddle.com) | [Ioannis Georgoulas](https://github.com/geototti21) | Developer portal (Tech Docs, Service Catalog, Internal Tooling), we use vanilla Backstage FE and custom BE implementation in Go | +| [Acast.com](https://acast.com) | [Olle Lundberg](https://github.com/lndbrg) | Developer portal with tech docs, service catalog and a bunch of other internal tooling | +| [Lunar](https://lunar.app) | [Jacob Valdemar](https://github.com/JacobValdemar) | Internal developer portal for service overview and insights, API documentation, technical guides, onboarding guides and RFC's. | +| [Trendyol](https://trendyol.com) | [Erdogan Oksuz](https://github.com/erdoganoksuz) | The Developer Portal has been called `Pandora`. Provides an overview of Trendyol tech ecosystem. TechDocs, Catalog, Custom Plugins and Theme. | From be7b7ce31a9229498b41778ff7fb091783efd06b Mon Sep 17 00:00:00 2001 From: erdoganoksuz Date: Fri, 5 Mar 2021 12:09:56 +0300 Subject: [PATCH 35/66] vocab Signed-off-by: erdoganoksuz --- .github/styles/vocab.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/styles/vocab.txt b/.github/styles/vocab.txt index e60d9b9528..84f0fb8d50 100644 --- a/.github/styles/vocab.txt +++ b/.github/styles/vocab.txt @@ -19,6 +19,7 @@ Docusaurus Dominik Ek Env +Erdogan Expedia Figma Firekube @@ -86,6 +87,7 @@ Templaters Thauer Tolerations Tuite +Trendyol Voi WWW Wealthsimple From c532c16828d3994fb05d063e2d2c941da543c961 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Fri, 5 Mar 2021 10:14:14 +0100 Subject: [PATCH 36/66] Add changeset Signed-off-by: Johan Haals --- .changeset/many-eyes-rest.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/many-eyes-rest.md diff --git a/.changeset/many-eyes-rest.md b/.changeset/many-eyes-rest.md new file mode 100644 index 0000000000..d7a5a806b2 --- /dev/null +++ b/.changeset/many-eyes-rest.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +--- + +Fixes task failures caused by undefined step input From c3eb4917fb9de6ba9919cdf726ade4de3db7f6c3 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Fri, 5 Mar 2021 10:28:13 +0100 Subject: [PATCH 37/66] Add input template test Signed-off-by: Johan Haals --- .../src/scaffolder/tasks/TaskWorker.test.ts | 64 ++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts index 6facaa21f2..b598b91036 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts @@ -23,7 +23,7 @@ import os from 'os'; import { ConfigReader, JsonObject } from '@backstage/config'; import { StorageTaskBroker } from './StorageTaskBroker'; import { DatabaseTaskStore } from './DatabaseTaskStore'; -import { TemplateActionRegistry } from '../actions'; +import { createTemplateAction, TemplateActionRegistry } from '../actions'; async function createStore(): Promise { const manager = SingleConnectionDatabaseManager.fromConfig( @@ -104,4 +104,66 @@ describe('TaskWorker', () => { const event = events.find(e => e.type === 'completion'); expect((event?.body?.output as JsonObject).result).toBe('winning'); }); + + it('should template input', async () => { + const inputAction = createTemplateAction<{ + name: string; + }>({ + id: 'test-input', + schema: { + input: { + type: 'object', + required: ['name'], + properties: { + name: { + title: 'name', + description: 'Enter name', + type: 'string', + }, + }, + }, + }, + async handler(ctx) { + if (ctx.input.name !== 'winning') { + throw new Error( + `expected name to be "winning" got ${ctx.input.name}`, + ); + } + }, + }); + actionRegistry.register(inputAction); + + const broker = new StorageTaskBroker(storage, logger); + const taskWorker = new TaskWorker({ + logger, + workingDirectory: os.tmpdir(), + actionRegistry, + taskBroker: broker, + }); + + const { taskId } = await broker.dispatch({ + steps: [ + { id: 'test', name: 'test', action: 'test-action' }, + { + id: 'test-input', + name: 'test-input', + action: 'test-input', + input: { + name: '{{ steps.test.output.testOutput }}', + }, + }, + ], + output: { + result: '{{ steps.test.output.testOutput }}', + }, + values: {}, + }); + + const task = await broker.claim(); + await taskWorker.runOneTask(task); + + const { events } = await storage.listEvents({ taskId }); + const event = events.find(e => e.type === 'completion'); + expect((event?.body?.output as JsonObject).result).toBe('winning'); + }); }); From 610ec91fed2595be3845c859aea978365b3ed59f Mon Sep 17 00:00:00 2001 From: erdoganoksuz Date: Fri, 5 Mar 2021 14:04:47 +0300 Subject: [PATCH 38/66] Oksuz added to vocab Signed-off-by: erdoganoksuz --- .github/styles/vocab.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/styles/vocab.txt b/.github/styles/vocab.txt index 84f0fb8d50..fcc5d63d20 100644 --- a/.github/styles/vocab.txt +++ b/.github/styles/vocab.txt @@ -52,6 +52,7 @@ Namespaces Niklas OAuth Okta +Oksuz Oldsberg Olle Onboarding From c9b5c1ecacad179932230114d93ab95046c43ba9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Fri, 5 Mar 2021 15:49:48 +0100 Subject: [PATCH 39/66] Standardize the tool cards in explore some more MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/smart-waves-dream.md | 6 ++ plugins/explore-react/src/tools/api.ts | 1 - .../src/components/ToolCard/ToolCard.tsx | 41 ++++---------- .../src/components/ToolCard/ToolCardGrid.tsx | 46 ---------------- .../explore/src/components/ToolCard/index.ts | 1 - .../ToolExplorerContent.tsx | 55 +++++++++++++------ 6 files changed, 54 insertions(+), 96 deletions(-) create mode 100644 .changeset/smart-waves-dream.md delete mode 100644 plugins/explore/src/components/ToolCard/ToolCardGrid.tsx diff --git a/.changeset/smart-waves-dream.md b/.changeset/smart-waves-dream.md new file mode 100644 index 0000000000..648493f2df --- /dev/null +++ b/.changeset/smart-waves-dream.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-explore': patch +'@backstage/plugin-explore-react': patch +--- + +Standardize the tool cards in explore some more diff --git a/plugins/explore-react/src/tools/api.ts b/plugins/explore-react/src/tools/api.ts index 5d96033546..b7c39b4d50 100644 --- a/plugins/explore-react/src/tools/api.ts +++ b/plugins/explore-react/src/tools/api.ts @@ -28,7 +28,6 @@ export type ExploreTool = { image: string; tags?: string[]; lifecycle?: string; - newsTag?: string; }; export interface ExploreToolsConfig { diff --git a/plugins/explore/src/components/ToolCard/ToolCard.tsx b/plugins/explore/src/components/ToolCard/ToolCard.tsx index c6d812b098..60f467ca80 100644 --- a/plugins/explore/src/components/ToolCard/ToolCard.tsx +++ b/plugins/explore/src/components/ToolCard/ToolCard.tsx @@ -17,6 +17,7 @@ import { ExploreTool } from '@backstage/plugin-explore-react'; import { BackstageTheme } from '@backstage/theme'; import { + Box, Button, Card, CardActions, @@ -32,14 +33,6 @@ import React from 'react'; // TODO: Align styling between Domain and ToolCard const useStyles = makeStyles(theme => ({ - card: { - display: 'flex', - flexDirection: 'column', - }, - cardActions: { - flexGrow: 1, - alignItems: 'flex-end', - }, media: { height: 128, }, @@ -59,13 +52,6 @@ const useStyles = makeStyles(theme => ({ beta: { backgroundColor: theme.palette.status.warning, }, - domains: { - position: 'relative', - top: theme.spacing(2), - }, - spaceBetween: { - justifyContent: 'space-between', - }, })); type Props = { @@ -76,10 +62,10 @@ type Props = { export const ToolCard = ({ card, objectFit }: Props) => { const classes = useStyles(); - const { title, description, url, image, lifecycle, newsTag, tags } = card; + const { title, description, url, image, lifecycle, tags } = card; return ( - + { })} /> - + {title}{' '} {lifecycle && lifecycle.toLowerCase() !== 'ga' && ( { /> )} - - {description || 'Description missing'} - + {description || 'Description missing'} {tags && ( -
+ {tags.map((item, idx) => ( - + ))} -
+ )}
- - diff --git a/plugins/explore/src/components/ToolCard/ToolCardGrid.tsx b/plugins/explore/src/components/ToolCard/ToolCardGrid.tsx deleted file mode 100644 index 5b6fbd1834..0000000000 --- a/plugins/explore/src/components/ToolCard/ToolCardGrid.tsx +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { ExploreTool } from '@backstage/plugin-explore-react'; -import { BackstageTheme } from '@backstage/theme'; -import { makeStyles } from '@material-ui/core'; -import React from 'react'; -import { ToolCard } from './ToolCard'; - -const useStyles = makeStyles(theme => ({ - container: { - display: 'grid', - gridTemplateColumns: 'repeat(auto-fill, 296px)', - gridGap: theme.spacing(3), - marginBottom: theme.spacing(6), - }, -})); - -type ToolCardGridProps = { - tools: ExploreTool[]; -}; - -export const ToolCardGrid = ({ tools }: ToolCardGridProps) => { - const classes = useStyles(); - - return ( -
- {tools.map((card: ExploreTool, ix: any) => ( - - ))} -
- ); -}; diff --git a/plugins/explore/src/components/ToolCard/index.ts b/plugins/explore/src/components/ToolCard/index.ts index 84599aa163..805d822a05 100644 --- a/plugins/explore/src/components/ToolCard/index.ts +++ b/plugins/explore/src/components/ToolCard/index.ts @@ -14,4 +14,3 @@ * limitations under the License. */ export { ToolCard } from './ToolCard'; -export { ToolCardGrid } from './ToolCardGrid'; diff --git a/plugins/explore/src/components/ToolExplorerContent/ToolExplorerContent.tsx b/plugins/explore/src/components/ToolExplorerContent/ToolExplorerContent.tsx index 8b8b3f0a61..e00606eeb2 100644 --- a/plugins/explore/src/components/ToolExplorerContent/ToolExplorerContent.tsx +++ b/plugins/explore/src/components/ToolExplorerContent/ToolExplorerContent.tsx @@ -17,36 +17,55 @@ import { Content, ContentHeader, EmptyState, + ItemCardGrid, Progress, SupportButton, useApi, + WarningPanel, } from '@backstage/core'; import { exploreToolsConfigRef } from '@backstage/plugin-explore-react'; import React from 'react'; import { useAsync } from 'react-use'; -import { ToolCardGrid } from '../ToolCard'; +import { ToolCard } from '../ToolCard'; -export const ToolExplorerContent = () => { +const Body = () => { const exploreToolsConfigApi = useApi(exploreToolsConfigRef); - const { value: tools, loading } = useAsync(async () => { + const { value: tools, loading, error } = useAsync(async () => { return await exploreToolsConfigApi.getTools(); }, [exploreToolsConfigApi]); - return ( - - - Discover the tools in your ecosystem. - + if (loading) { + return ; + } - {loading && } - {!loading && (!tools || tools.length === 0) && ( - - )} - {!loading && tools && } - + if (error) { + return ; + } + + if (!tools?.length) { + return ( + + ); + } + + return ( + + {tools.map((tool, index) => ( + + ))} + ); }; + +export const ToolExplorerContent = () => ( + + + Discover the tools in your ecosystem. + + + +); From 9f7dc10fbe7fae2d2aa774091a4dabfc232a933e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Fri, 5 Mar 2021 15:13:52 +0100 Subject: [PATCH 40/66] Show a Not Found message when navigating to a nonexistent entity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/rotten-beds-itch.md | 5 ++++ .../EntityPageLayout/EntityPageLayout.tsx | 30 +++++++++++++++++-- 2 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 .changeset/rotten-beds-itch.md diff --git a/.changeset/rotten-beds-itch.md b/.changeset/rotten-beds-itch.md new file mode 100644 index 0000000000..c046e71572 --- /dev/null +++ b/.changeset/rotten-beds-itch.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog': patch +--- + +Show a Not Found message when navigating to a nonexistent entity diff --git a/plugins/catalog/src/components/EntityPageLayout/EntityPageLayout.tsx b/plugins/catalog/src/components/EntityPageLayout/EntityPageLayout.tsx index 34378d5496..6f9899ff82 100644 --- a/plugins/catalog/src/components/EntityPageLayout/EntityPageLayout.tsx +++ b/plugins/catalog/src/components/EntityPageLayout/EntityPageLayout.tsx @@ -18,7 +18,15 @@ import { ENTITY_DEFAULT_NAMESPACE, RELATION_OWNED_BY, } from '@backstage/catalog-model'; -import { Content, Header, HeaderLabel, Page, Progress } from '@backstage/core'; +import { + Content, + Header, + HeaderLabel, + Link, + Page, + Progress, + WarningPanel, +} from '@backstage/core'; import { EntityContext, EntityRefLinks, @@ -125,7 +133,11 @@ export const EntityPageLayout = ({ children }: PropsWithChildren<{}>) => { )} - {loading && } + {loading && ( + + + + )} {entity && {children}} @@ -134,6 +146,19 @@ export const EntityPageLayout = ({ children }: PropsWithChildren<{}>) => { {error.toString()} )} + + {!loading && !error && !entity && ( + + + There is no {kind} with the requested{' '} + + kind, namespace, and name + + . + + + )} + ) => { ); }; + EntityPageLayout.Content = Tabbed.Content; From 8f72318fec4f78a4279adc7d766d4da1ab58f516 Mon Sep 17 00:00:00 2001 From: ebarrios Date: Fri, 5 Mar 2021 16:54:13 +0100 Subject: [PATCH 41/66] Added lag column to the kafka plugin Signed-off-by: ebarrios --- .changeset/curvy-poems-cough.md | 5 +++++ .../ConsumerGroupOffsets/ConsumerGroupOffsets.tsx | 11 +++++++++++ 2 files changed, 16 insertions(+) create mode 100644 .changeset/curvy-poems-cough.md diff --git a/.changeset/curvy-poems-cough.md b/.changeset/curvy-poems-cough.md new file mode 100644 index 0000000000..6013404490 --- /dev/null +++ b/.changeset/curvy-poems-cough.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-kafka': patch +--- + +Added lag column in the plugin main table diff --git a/plugins/kafka/src/components/ConsumerGroupOffsets/ConsumerGroupOffsets.tsx b/plugins/kafka/src/components/ConsumerGroupOffsets/ConsumerGroupOffsets.tsx index 7928393526..3d458194d6 100644 --- a/plugins/kafka/src/components/ConsumerGroupOffsets/ConsumerGroupOffsets.tsx +++ b/plugins/kafka/src/components/ConsumerGroupOffsets/ConsumerGroupOffsets.tsx @@ -57,6 +57,17 @@ const generatedColumns: TableColumn[] = [ return <>{row.groupOffset ?? ''}; }, }, + { + title: 'Lag', + field: 'lag', + render: (row: Partial) => { + let lag = undefined; + if (row.topicOffset && row.groupOffset) { + lag = +topicOffset - +groupOffset; + } + return <>{lag ?? ''}; + }, + }, ]; type Props = { From e15f7a6464657325d9134f7a436bcad394dbf670 Mon Sep 17 00:00:00 2001 From: ebarrios Date: Fri, 5 Mar 2021 17:02:50 +0100 Subject: [PATCH 42/66] Added missing row to variables Signed-off-by: ebarrios --- .../components/ConsumerGroupOffsets/ConsumerGroupOffsets.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/kafka/src/components/ConsumerGroupOffsets/ConsumerGroupOffsets.tsx b/plugins/kafka/src/components/ConsumerGroupOffsets/ConsumerGroupOffsets.tsx index 3d458194d6..90a99be015 100644 --- a/plugins/kafka/src/components/ConsumerGroupOffsets/ConsumerGroupOffsets.tsx +++ b/plugins/kafka/src/components/ConsumerGroupOffsets/ConsumerGroupOffsets.tsx @@ -63,7 +63,7 @@ const generatedColumns: TableColumn[] = [ render: (row: Partial) => { let lag = undefined; if (row.topicOffset && row.groupOffset) { - lag = +topicOffset - +groupOffset; + lag = +row.topicOffset - +row.groupOffset; } return <>{lag ?? ''}; }, From 0b42fff22287d88d17e5d8d93a20eb8fbb71d5de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Fri, 5 Mar 2021 13:56:25 +0100 Subject: [PATCH 43/66] add parseLocationReference/stringifyLocationReference MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/honest-steaks-beam.md | 5 ++ .changeset/little-crabs-burn.md | 9 ++ packages/catalog-client/src/CatalogClient.ts | 5 +- .../src/location/helpers.test.ts | 70 ++++++++++++++++ .../catalog-model/src/location/helpers.ts | 82 +++++++++++++++++++ packages/catalog-model/src/location/index.ts | 13 +-- packages/techdocs-common/src/helpers.test.ts | 2 +- packages/techdocs-common/src/helpers.ts | 24 ++---- .../src/ingestion/HigherOrderOperations.ts | 34 +++++--- .../AnnotateLocationEntityProcessor.ts | 9 +- .../processors/CodeOwnersProcessor.ts | 14 +++- plugins/catalog/src/data/utils.ts | 11 +-- .../src/scaffolder/stages/helpers.test.ts | 22 ++--- .../src/scaffolder/stages/helpers.ts | 30 ++----- .../scaffolder-backend/src/service/helpers.ts | 22 +++-- 15 files changed, 259 insertions(+), 93 deletions(-) create mode 100644 .changeset/honest-steaks-beam.md create mode 100644 .changeset/little-crabs-burn.md create mode 100644 packages/catalog-model/src/location/helpers.test.ts create mode 100644 packages/catalog-model/src/location/helpers.ts diff --git a/.changeset/honest-steaks-beam.md b/.changeset/honest-steaks-beam.md new file mode 100644 index 0000000000..71c3193c99 --- /dev/null +++ b/.changeset/honest-steaks-beam.md @@ -0,0 +1,5 @@ +--- +'@backstage/catalog-model': patch +--- + +Add parseLocationReference, stringifyLocationReference diff --git a/.changeset/little-crabs-burn.md b/.changeset/little-crabs-burn.md new file mode 100644 index 0000000000..2bc3fe9e96 --- /dev/null +++ b/.changeset/little-crabs-burn.md @@ -0,0 +1,9 @@ +--- +'@backstage/catalog-client': patch +'@backstage/plugin-catalog-backend': patch +'@backstage/plugin-catalog': patch +'@backstage/plugin-scaffolder-backend': patch +'@backstage/techdocs-common': patch +--- + +Make use of parseLocationReference/stringifyLocationReference diff --git a/packages/catalog-client/src/CatalogClient.ts b/packages/catalog-client/src/CatalogClient.ts index 04206edcdb..a1af0240f6 100644 --- a/packages/catalog-client/src/CatalogClient.ts +++ b/packages/catalog-client/src/CatalogClient.ts @@ -19,15 +19,16 @@ import { EntityName, Location, LOCATION_ANNOTATION, + stringifyLocationReference, } from '@backstage/catalog-model'; import fetch from 'cross-fetch'; import { AddLocationRequest, AddLocationResponse, - CatalogRequestOptions, CatalogApi, CatalogEntitiesRequest, CatalogListResponse, + CatalogRequestOptions, DiscoveryApi, } from './types'; @@ -135,7 +136,7 @@ export class CatalogClient implements CatalogApi { ); return all .map(r => r.data) - .find(l => locationCompound === `${l.type}:${l.target}`); + .find(l => locationCompound === stringifyLocationReference(l)); } async removeEntityByUid( diff --git a/packages/catalog-model/src/location/helpers.test.ts b/packages/catalog-model/src/location/helpers.test.ts new file mode 100644 index 0000000000..888c79b328 --- /dev/null +++ b/packages/catalog-model/src/location/helpers.test.ts @@ -0,0 +1,70 @@ +/* + * Copyright 2021 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 { parseLocationReference, stringifyLocationReference } from './helpers'; + +describe('parseLocationReference', () => { + it('works for the simple case', () => { + expect(parseLocationReference('url:https://www.google.com')).toEqual({ + type: 'url', + target: 'https://www.google.com', + }); + }); + + it('rejects faulty inputs', () => { + expect(() => parseLocationReference(7 as any)).toThrow( + "Unable to parse location reference '7', unexpected argument number", + ); + expect(() => parseLocationReference('')).toThrow( + "Unable to parse location reference '', expected ':', e.g. 'url:https://host/path'", + ); + expect(() => parseLocationReference('hello')).toThrow( + "Unable to parse location reference 'hello', expected ':', e.g. 'url:https://host/path'", + ); + expect(() => parseLocationReference(':hello')).toThrow( + "Unable to parse location reference ':hello', expected ':', e.g. 'url:https://host/path'", + ); + expect(() => parseLocationReference('hello:')).toThrow( + "Unable to parse location reference 'hello:', expected ':', e.g. 'url:https://host/path'", + ); + expect(() => parseLocationReference('http://blah')).toThrow( + "Invalid location reference 'http://blah', please prefix it with 'url:', e.g. 'url:http://blah'", + ); + expect(() => parseLocationReference('https://bleh')).toThrow( + "Invalid location reference 'https://bleh', please prefix it with 'url:', e.g. 'url:https://bleh'", + ); + }); +}); + +describe('stringifyLocationReference', () => { + it('works for the simple case', () => { + expect( + stringifyLocationReference({ + type: 'url', + target: 'https://www.google.com', + }), + ).toEqual('url:https://www.google.com'); + }); + + it('rejects faulty inputs', () => { + expect(() => + stringifyLocationReference({ type: '', target: 'hello' }), + ).toThrow('Unable to stringify location reference, empty type'); + expect(() => + stringifyLocationReference({ type: 'hello', target: '' }), + ).toThrow('Unable to stringify location reference, empty target'); + }); +}); diff --git a/packages/catalog-model/src/location/helpers.ts b/packages/catalog-model/src/location/helpers.ts new file mode 100644 index 0000000000..fb1be5abad --- /dev/null +++ b/packages/catalog-model/src/location/helpers.ts @@ -0,0 +1,82 @@ +/* + * Copyright 2021 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. + */ + +/** + * Parses a string form location reference. + * + * Note that the return type is not `LocationSpec`, because we do not want to + * conflate the string form with the additional properties of that type. + * + * @param ref A string-form location reference, e.g. 'url:https://host' + * @returns A location reference, e.g. { type: 'url', target: 'https://host' } + */ +export function parseLocationReference( + ref: string, +): { type: string; target: string } { + if (typeof ref !== 'string') { + throw new TypeError( + `Unable to parse location reference '${ref}', unexpected argument ${typeof ref}`, + ); + } + + const splitIndex = ref.indexOf(':'); + if (splitIndex < 0) { + throw new TypeError( + `Unable to parse location reference '${ref}', expected ':', e.g. 'url:https://host/path'`, + ); + } + + const type = ref.substr(0, splitIndex).trim(); + const target = ref.substr(splitIndex + 1).trim(); + + if (!type || !target) { + throw new TypeError( + `Unable to parse location reference '${ref}', expected ':', e.g. 'url:https://host/path'`, + ); + } + + if (type === 'http' || type === 'https') { + throw new TypeError( + `Invalid location reference '${ref}', please prefix it with 'url:', e.g. 'url:${ref}'`, + ); + } + + return { type, target }; +} + +/** + * Turns a location reference into its string form. + * + * Note that the input type is not `LocationSpec`, because we do not want to + * conflate the string form with the additional properties of that type. + * + * @param ref A location reference, e.g. { type: 'url', target: 'https://host' } + * @returns A string-form location reference, e.g. 'url:https://host' + */ +export function stringifyLocationReference(ref: { + type: string; + target: string; +}): string { + const { type, target } = ref; + + if (!type) { + throw new TypeError(`Unable to stringify location reference, empty type`); + } else if (!target) { + throw new TypeError(`Unable to stringify location reference, empty target`); + } + + return `${type}:${target}`; +} diff --git a/packages/catalog-model/src/location/index.ts b/packages/catalog-model/src/location/index.ts index 6cfb074613..fddc8bde37 100644 --- a/packages/catalog-model/src/location/index.ts +++ b/packages/catalog-model/src/location/index.ts @@ -14,14 +14,15 @@ * limitations under the License. */ -export type { Location, LocationSpec } from './types'; -export { - locationSchema, - locationSpecSchema, - analyzeLocationSchema, -} from './validation'; export { LOCATION_ANNOTATION, ORIGIN_LOCATION_ANNOTATION, SOURCE_LOCATION_ANNOTATION, } from './annotation'; +export { parseLocationReference, stringifyLocationReference } from './helpers'; +export type { Location, LocationSpec } from './types'; +export { + analyzeLocationSchema, + locationSchema, + locationSpecSchema, +} from './validation'; diff --git a/packages/techdocs-common/src/helpers.test.ts b/packages/techdocs-common/src/helpers.test.ts index 6520f0f8e4..c31b1266fa 100644 --- a/packages/techdocs-common/src/helpers.test.ts +++ b/packages/techdocs-common/src/helpers.test.ts @@ -105,7 +105,7 @@ describe('parseReferenceAnnotation', () => { 'backstage.io/techdocs-ref', mockEntityWithBadAnnotation, ); - }).toThrow(/Failure to parse/); + }).toThrow(/Unable to parse/); }); }); diff --git a/packages/techdocs-common/src/helpers.ts b/packages/techdocs-common/src/helpers.ts index 16f2876efb..856624c568 100644 --- a/packages/techdocs-common/src/helpers.ts +++ b/packages/techdocs-common/src/helpers.ts @@ -15,7 +15,7 @@ */ import { Git, InputError, UrlReader } from '@backstage/backend-common'; -import { Entity } from '@backstage/catalog-model'; +import { Entity, parseLocationReference } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; import fs from 'fs-extra'; import parseGitUrl from 'git-url-parse'; @@ -36,28 +36,15 @@ export const parseReferenceAnnotation = ( entity: Entity, ): ParsedLocationAnnotation => { const annotation = entity.metadata.annotations?.[annotationName]; - if (!annotation) { throw new InputError( `No location annotation provided in entity: ${entity.metadata.name}`, ); } - // split on the first colon for the protocol and the rest after the first split - // is the location. - const [type, target] = annotation.split(/:(.+)/) as [ - RemoteProtocol?, - string?, - ]; - - if (!type || !target) { - throw new InputError( - `Failure to parse either protocol or location for entity: ${entity.metadata.name}`, - ); - } - + const { type, target } = parseLocationReference(annotation); return { - type, + type: type as RemoteProtocol, target, }; }; @@ -77,8 +64,9 @@ export const getLocationForEntity = ( case 'url': return { type, target }; case 'dir': - if (path.isAbsolute(target)) return { type, target }; - + if (path.isAbsolute(target)) { + return { type, target }; + } return parseReferenceAnnotation( 'backstage.io/managed-by-location', entity, diff --git a/plugins/catalog-backend/src/ingestion/HigherOrderOperations.ts b/plugins/catalog-backend/src/ingestion/HigherOrderOperations.ts index 467c0eb2e8..9d167603d0 100644 --- a/plugins/catalog-backend/src/ingestion/HigherOrderOperations.ts +++ b/plugins/catalog-backend/src/ingestion/HigherOrderOperations.ts @@ -14,7 +14,11 @@ * limitations under the License. */ -import { Location, LocationSpec } from '@backstage/catalog-model'; +import { + Location, + LocationSpec, + stringifyLocationReference, +} from '@backstage/catalog-model'; import { v4 as uuidv4 } from 'uuid'; import { Logger } from 'winston'; import { EntitiesCatalog, LocationsCatalog } from '../catalog'; @@ -127,14 +131,18 @@ export class HigherOrderOperations implements HigherOrderOperation { for (const { data: location } of locations) { logger.info( - `Locations Refresh: Refreshing location ${location.type}:${location.target}`, + `Locations Refresh: Refreshing location ${stringifyLocationReference( + location, + )}`, ); try { await this.refreshSingleLocation(location, logger); await this.locationsCatalog.logUpdateSuccess(location.id, undefined); } catch (e) { logger.warn( - `Locations Refresh: Failed to refresh location ${location.type}:${location.target}, ${e.stack}`, + `Locations Refresh: Failed to refresh location ${stringifyLocationReference( + location, + )}, ${e.stack}`, ); await this.locationsCatalog.logUpdateFailure(location.id, e); } @@ -162,14 +170,18 @@ export class HigherOrderOperations implements HigherOrderOperation { for (const item of readerOutput.errors) { logger.warn( - `Failed item in location ${item.location.type}:${item.location.target}, ${item.error.stack}`, + `Failed item in location ${stringifyLocationReference( + item.location, + )}, ${item.error.stack}`, ); } logger.info( - `Read ${readerOutput.entities.length} entities from location ${ - location.type - }:${location.target} in ${durationText(startTimestamp)}`, + `Read ${ + readerOutput.entities.length + } entities from location ${stringifyLocationReference( + location, + )} in ${durationText(startTimestamp)}`, ); startTimestamp = process.hrtime(); @@ -198,9 +210,11 @@ export class HigherOrderOperations implements HigherOrderOperation { ); logger.info( - `Wrote ${readerOutput.entities.length} entities from location ${ - location.type - }:${location.target} in ${durationText(startTimestamp)}`, + `Wrote ${ + readerOutput.entities.length + } entities from location ${stringifyLocationReference( + location, + )} in ${durationText(startTimestamp)}`, ); } } diff --git a/plugins/catalog-backend/src/ingestion/processors/AnnotateLocationEntityProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/AnnotateLocationEntityProcessor.ts index b40378226a..1d941e0040 100644 --- a/plugins/catalog-backend/src/ingestion/processors/AnnotateLocationEntityProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/AnnotateLocationEntityProcessor.ts @@ -16,9 +16,10 @@ import { Entity, - LOCATION_ANNOTATION, LocationSpec, + LOCATION_ANNOTATION, ORIGIN_LOCATION_ANNOTATION, + stringifyLocationReference, } from '@backstage/catalog-model'; import lodash from 'lodash'; import { CatalogProcessor, CatalogProcessorEmit } from './types'; @@ -34,8 +35,10 @@ export class AnnotateLocationEntityProcessor implements CatalogProcessor { { metadata: { annotations: { - [LOCATION_ANNOTATION]: `${location.type}:${location.target}`, - [ORIGIN_LOCATION_ANNOTATION]: `${originLocation.type}:${originLocation.target}`, + [LOCATION_ANNOTATION]: stringifyLocationReference(location), + [ORIGIN_LOCATION_ANNOTATION]: stringifyLocationReference( + originLocation, + ), }, }, }, diff --git a/plugins/catalog-backend/src/ingestion/processors/CodeOwnersProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/CodeOwnersProcessor.ts index e1d7e22ccf..8a3951428e 100644 --- a/plugins/catalog-backend/src/ingestion/processors/CodeOwnersProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/CodeOwnersProcessor.ts @@ -15,7 +15,11 @@ */ import { NotFoundError, UrlReader } from '@backstage/backend-common'; -import { Entity, LocationSpec } from '@backstage/catalog-model'; +import { + Entity, + LocationSpec, + stringifyLocationReference, +} from '@backstage/catalog-model'; import * as codeowners from 'codeowners-utils'; import { CodeOwnersEntry } from 'codeowners-utils'; // NOTE: This can be removed when ES2021 is implemented @@ -108,11 +112,15 @@ export async function findRawCodeOwners( ); if (hardError) { options.logger.warn( - `Failed to read codeowners for location ${location.type}:${location.target}, ${hardError}`, + `Failed to read codeowners for location ${stringifyLocationReference( + location, + )}, ${hardError}`, ); } else { options.logger.debug( - `Failed to find codeowners for location ${location.type}:${location.target}`, + `Failed to find codeowners for location ${stringifyLocationReference( + location, + )}`, ); } return undefined; diff --git a/plugins/catalog/src/data/utils.ts b/plugins/catalog/src/data/utils.ts index ec63d94754..60b9144599 100644 --- a/plugins/catalog/src/data/utils.ts +++ b/plugins/catalog/src/data/utils.ts @@ -18,6 +18,7 @@ import { EntityMeta, LocationSpec, LOCATION_ANNOTATION, + parseLocationReference, } from '@backstage/catalog-model'; export function findLocationForEntityMeta( @@ -36,13 +37,9 @@ export function findLocationForEntityMeta( } export function parseLocation(reference: string): LocationSpec | undefined { - const separatorIndex = reference.indexOf(':'); - if (separatorIndex === -1) { + try { + return parseLocationReference(reference); + } catch { return undefined; } - - return { - type: reference.substring(0, separatorIndex), - target: reference.substring(separatorIndex + 1), - }; } diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/helpers.test.ts b/plugins/scaffolder-backend/src/scaffolder/stages/helpers.test.ts index 5c809d229e..fc36eb64e5 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/helpers.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/helpers.test.ts @@ -13,11 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { parseLocationAnnotation, joinGitUrlPath } from './helpers'; import { - TemplateEntityV1alpha1, LOCATION_ANNOTATION, + TemplateEntityV1alpha1, } from '@backstage/catalog-model'; +import { joinGitUrlPath, parseLocationAnnotation } from './helpers'; describe('Helpers', () => { describe('parseLocationAnnotation', () => { @@ -30,7 +30,7 @@ describe('Helpers', () => { name: 'graphql-starter', title: 'GraphQL Service', description: - 'A GraphQL starter template for backstage to get you up and running\nthe best pracices with GraphQL\n', + 'A GraphQL starter template for backstage to get you up and running\nthe best practices with GraphQL\n', uid: '9cf16bad-16e0-4213-b314-c4eec773c50b', etag: 'ZTkxMjUxMjUtYWY3Yi00MjU2LWFkYWMtZTZjNjU5ZjJhOWM2', generation: 1, @@ -78,7 +78,7 @@ describe('Helpers', () => { name: 'graphql-starter', title: 'GraphQL Service', description: - 'A GraphQL starter template for backstage to get you up and running\nthe best pracices with GraphQL\n', + 'A GraphQL starter template for backstage to get you up and running\nthe best practices with GraphQL\n', uid: '9cf16bad-16e0-4213-b314-c4eec773c50b', etag: 'ZTkxMjUxMjUtYWY3Yi00MjU2LWFkYWMtZTZjNjU5ZjJhOWM2', generation: 1, @@ -108,11 +108,13 @@ describe('Helpers', () => { expect(() => parseLocationAnnotation(mockEntity)).toThrow( expect.objectContaining({ - name: 'InputError', - message: `Failure to parse either protocol or location for entity: ${mockEntity.metadata.name}`, + name: 'TypeError', + message: + "Unable to parse location reference ':https://github.com/o/r/blob/master/template.yaml', expected ':', e.g. 'url:https://host/path'", }), ); }); + it('should throw an error when the location part is not set in the location annotation', () => { const mockEntity: TemplateEntityV1alpha1 = { apiVersion: 'backstage.io/v1alpha1', @@ -124,7 +126,7 @@ describe('Helpers', () => { name: 'graphql-starter', title: 'GraphQL Service', description: - 'A GraphQL starter template for backstage to get you up and running\nthe best pracices with GraphQL\n', + 'A GraphQL starter template for backstage to get you up and running\nthe best practices with GraphQL\n', uid: '9cf16bad-16e0-4213-b314-c4eec773c50b', etag: 'ZTkxMjUxMjUtYWY3Yi00MjU2LWFkYWMtZTZjNjU5ZjJhOWM2', generation: 1, @@ -154,8 +156,8 @@ describe('Helpers', () => { expect(() => parseLocationAnnotation(mockEntity)).toThrow( expect.objectContaining({ - name: 'InputError', - message: `Failure to parse either protocol or location for entity: ${mockEntity.metadata.name}`, + name: 'TypeError', + message: `Unable to parse location reference 'github:', expected ':', e.g. 'url:https://host/path'`, }), ); }); @@ -216,7 +218,7 @@ describe('Helpers', () => { name: 'graphql-starter', title: 'GraphQL Service', description: - 'A GraphQL starter template for backstage to get you up and running\nthe best pracices with GraphQL\n', + 'A GraphQL starter template for backstage to get you up and running\nthe best practices with GraphQL\n', uid: '9cf16bad-16e0-4213-b314-c4eec773c50b', etag: 'ZTkxMjUxMjUtYWY3Yi00MjU2LWFkYWMtZTZjNjU5ZjJhOWM2', generation: 1, diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/helpers.ts b/plugins/scaffolder-backend/src/scaffolder/stages/helpers.ts index 784e72ca87..924b9f5811 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/helpers.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/helpers.ts @@ -14,12 +14,13 @@ * limitations under the License. */ -import { posix as posixPath } from 'path'; -import { - TemplateEntityV1alpha1, - LOCATION_ANNOTATION, -} from '@backstage/catalog-model'; import { InputError } from '@backstage/backend-common'; +import { + LOCATION_ANNOTATION, + parseLocationReference, + TemplateEntityV1alpha1, +} from '@backstage/catalog-model'; +import { posix as posixPath } from 'path'; export type ParsedLocationAnnotation = { protocol: 'file' | 'url'; @@ -30,29 +31,16 @@ export const parseLocationAnnotation = ( entity: TemplateEntityV1alpha1, ): ParsedLocationAnnotation => { const annotation = entity.metadata.annotations?.[LOCATION_ANNOTATION]; - if (!annotation) { throw new InputError( `No location annotation provided in entity: ${entity.metadata.name}`, ); } - // split on the first colon for the protocol and the rest after the first split - // is the location. - const [protocol, location] = annotation.split(/:(.+)/) as [ - ('file' | 'url')?, - string?, - ]; - - if (!protocol || !location) { - throw new InputError( - `Failure to parse either protocol or location for entity: ${entity.metadata.name}`, - ); - } - + const { type, target } = parseLocationReference(annotation); return { - protocol, - location, + protocol: type as 'file' | 'url', + location: target, }; }; diff --git a/plugins/scaffolder-backend/src/service/helpers.ts b/plugins/scaffolder-backend/src/service/helpers.ts index 905d85c35d..cc0e2f3aaf 100644 --- a/plugins/scaffolder-backend/src/service/helpers.ts +++ b/plugins/scaffolder-backend/src/service/helpers.ts @@ -14,15 +14,16 @@ * limitations under the License. */ -import os from 'os'; -import fs from 'fs-extra'; -import { Logger } from 'winston'; -import { Config } from '@backstage/config'; import { Entity, LOCATION_ANNOTATION, + parseLocationReference, SOURCE_LOCATION_ANNOTATION, } from '@backstage/catalog-model'; +import { Config } from '@backstage/config'; +import fs from 'fs-extra'; +import os from 'os'; +import { Logger } from 'winston'; export async function getWorkingDirectory( config: Config, @@ -64,16 +65,13 @@ export function getEntityBaseUrl(entity: Entity): string | undefined { return undefined; } - const [type, url] = location.split(/:(.+)/); - if (!url) { - return undefined; + const { type, target } = parseLocationReference(location); + if (type === 'url') { + return target; + } else if (type === 'file') { + return `file://${target}`; } - if (type === 'url') { - return url; - } else if (type === 'file') { - return `file://${url}`; - } // Only url and file location are handled, as we otherwise don't know if // what the url is pointing to makes sense to use as a baseUrl return undefined; From 14d6f407a5583f38ba68a0edac2659451e8eda3d Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Sat, 6 Mar 2021 21:48:54 +0100 Subject: [PATCH 44/66] chore: document useful git commands to avoid any DCO trouble Signed-off-by: Himanshu Mishra --- .github/PULL_REQUEST_TEMPLATE.md | 1 + .github/styles/vocab.txt | 1 + CONTRIBUTING.md | 6 ++++++ README.md | 2 +- 4 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index d68af23491..402617b66d 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -11,3 +11,4 @@ - [ ] Added or updated documentation - [ ] Tests for new functionality and regression tests for bug fixes - [ ] Screenshots attached (for UI changes) +- [ ] All your commits have a `Signed-off-by` line in the message. ([more info](https://github.com/backstage/backstage/blob/master/CONTRIBUTING.md#developer-certificate-of-origin)) diff --git a/.github/styles/vocab.txt b/.github/styles/vocab.txt index fcc5d63d20..3593bd7bad 100644 --- a/.github/styles/vocab.txt +++ b/.github/styles/vocab.txt @@ -65,6 +65,7 @@ Protobuf Proxying Raghunandan Readme +rebase Recharts Redash Repo diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 56404b2c81..1b7433888c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -96,6 +96,12 @@ Awesome commit message Signed-off-by: Jane Smith jane.smith@example.com ``` +- In case you forgot to add it to the most recent commit, use `git commit --amend --signoff` +- In case you forgot to add it to the last N commits in your branch, use `git rebase --signoff HEAD~N` and replace N with the number of new commits you created in your branch. +- If you have a very deep branch with a lot of commits, run `git rebase -i --signoff $(git merge-base -a master HEAD)`, double check to make sense of the commits (keep all lines as `pick`) and save and close the editor. This should bulk sign all the commits in your PR. Do be careful though. If you have a complex flow with a lot of branching and re-merging of work branches and stuff, merge-base may not be the right solution for you. + +Note: If you have already pushed you branch to a remote, you might have to force push: `git push -f` after the rebase. + ## Creating Changesets We use [changesets](https://github.com/atlassian/changesets) to help us prepare releases. They help us make sure that every package affected by a change gets a proper version number and an entry in its `CHANGELOG.md`. To make the process of generating releases easy, it helps when contributors include changesets with their pull requests. diff --git a/README.md b/README.md index d01be3f54c..6dd490e43b 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ Check out [the documentation](https://backstage.io/docs/getting-started) on how ## Community - [Discord chatroom](https://discord.gg/MUpMjP2) - Get support or discuss the project -- [Good First Issues](https://github.com/backstage/backstage/contribute) - Start here if you want to contribute +- [Contributing to Backstage](https://github.com/backstage/backstage/blob/master/CONTRIBUTING.md) - Start here if you want to contribute - [RFCs](https://github.com/backstage/backstage/labels/rfc) - Help shape the technical direction - [FAQ](https://backstage.io/docs/FAQ) - Frequently Asked Questions - [Code of Conduct](CODE_OF_CONDUCT.md) - This is how we roll From 6aa109828d05a9b5dce7ab3a0436ee8c1c1f1410 Mon Sep 17 00:00:00 2001 From: blam Date: Sun, 7 Mar 2021 17:56:07 +0100 Subject: [PATCH 45/66] docs: updating the example action to be valid typescript :) Signed-off-by: blam --- docs/features/software-templates/writing-custom-actions.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/features/software-templates/writing-custom-actions.md b/docs/features/software-templates/writing-custom-actions.md index 36d51decb5..2a48a014d6 100644 --- a/docs/features/software-templates/writing-custom-actions.md +++ b/docs/features/software-templates/writing-custom-actions.md @@ -19,7 +19,7 @@ passed as `input` to the function. In `packages/backend/src/actions/custom.ts` we can create a new action. ```ts -import { createTemplateAction } from '../../createTemplateAction'; +import { createTemplateAction } from '@backstage/plugin-scaffolder-backend'; import fs from 'fs-extra'; export const createNewFileAction = () => { @@ -35,7 +35,7 @@ export const createNewFileAction = () => { title: 'Contents', description: 'The contents of the file', }, - contents: { + filename: { type: 'string', title: 'Filename', description: 'The filename of the file that will be created', @@ -46,7 +46,7 @@ export const createNewFileAction = () => { async handler(ctx) { await fs.outputFile( `${ctx.workspacePath}/${ctx.input.filename}`, - ctx.input.content, + ctx.input.contents, ); }, }); From f69d670054432ba48bc3f130df8b51cb31b21eac Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 6 Mar 2021 16:50:49 +0100 Subject: [PATCH 46/66] package.json: sync prettier version with microsite Signed-off-by: Patrik Oldsberg --- package.json | 2 +- yarn.lock | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 527304b4f3..3a1ed1ed8d 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "husky": "^4.2.3", "lerna": "^4.0.0", "lint-staged": "^10.1.0", - "prettier": "^2.0.5", + "prettier": "^2.2.1", "recursive-readdir": "^2.2.2", "shx": "^0.3.2" }, diff --git a/yarn.lock b/yarn.lock index bdeea0c688..2a291d8208 100644 --- a/yarn.lock +++ b/yarn.lock @@ -21233,7 +21233,12 @@ prettier@^1.18.2, prettier@^1.19.1: resolved "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb" integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew== -prettier@^2.0.5, prettier@~2.0.5: +prettier@^2.2.1: + version "2.2.1" + resolved "https://registry.npmjs.org/prettier/-/prettier-2.2.1.tgz#795a1a78dd52f073da0cd42b21f9c91381923ff5" + integrity sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q== + +prettier@~2.0.5: version "2.0.5" resolved "https://registry.npmjs.org/prettier/-/prettier-2.0.5.tgz#d6d56282455243f2f92cc1716692c08aa31522d4" integrity sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg== From 0e068db972f4dcc84e85853b4156b30ae9909542 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 7 Mar 2021 23:37:48 +0100 Subject: [PATCH 47/66] run prettier Signed-off-by: Patrik Oldsberg --- packages/app/public/index.html | 2 +- packages/core-api/src/routing/RouteRef.ts | 11 +++++---- packages/core/src/layout/Sidebar/Intro.tsx | 7 +++--- .../PreparePullRequestForm.tsx | 10 +++++--- .../components/CatalogPage/CatalogPage.tsx | 7 +++--- .../WorkflowRunDetails/useWorkflowRunJobs.ts | 4 +--- .../cost-insights/src/testUtils/providers.tsx | 24 +++++-------------- .../src/kubernetes-auth-provider/types.ts | 13 +++++----- plugins/lighthouse/src/api.ts | 7 +++--- .../Group/MembersList/MembersListCard.tsx | 15 ++++++------ .../proxy-backend/src/service/router.test.ts | 18 +++++++------- 11 files changed, 57 insertions(+), 61 deletions(-) diff --git a/packages/app/public/index.html b/packages/app/public/index.html index 77e5c01e19..9a665141c0 100644 --- a/packages/app/public/index.html +++ b/packages/app/public/index.html @@ -66,7 +66,7 @@ <% } %> - +