From c617b1cf33bd0a3eb1286e1bb54da1e299272632 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Mon, 8 Jun 2020 16:31:29 +0200 Subject: [PATCH 01/19] Unbreak the unregister dialog after catalog changes --- plugins/catalog/src/api/CatalogClient.ts | 29 +++++-- plugins/catalog/src/api/types.ts | 3 +- .../components/CatalogPage/CatalogPage.tsx | 84 +++++++------------ .../components/CatalogTable/CatalogTable.tsx | 15 ++-- .../ComponentContextMenu.tsx | 2 +- .../ComponentRemovalDialog.tsx | 79 ++++++++++++----- plugins/catalog/src/data/component.ts | 3 +- plugins/catalog/src/data/utils.tsx | 31 ++++--- 8 files changed, 141 insertions(+), 105 deletions(-) diff --git a/plugins/catalog/src/api/CatalogClient.ts b/plugins/catalog/src/api/CatalogClient.ts index 2f9ffe302c..9ea59aab48 100644 --- a/plugins/catalog/src/api/CatalogClient.ts +++ b/plugins/catalog/src/api/CatalogClient.ts @@ -45,14 +45,27 @@ export class CatalogClient implements CatalogApi { } return undefined; } - async getEntitiesByLocationId(id: string): Promise { - const response = await fetch( - `${this.apiOrigin}${this.basePath}/entities?${LOCATION_ANNOTATION}=${id}`, - ); - return await response.json(); - } - async getEntities(): Promise { - const response = await fetch(`${this.apiOrigin}${this.basePath}/entities`); + async getEntities( + filter?: Record, + ): Promise { + let url = `${this.apiOrigin}${this.basePath}/entities`; + if (filter) { + url += '?'; + url += Object.entries(filter) + .map( + ([key, value]) => + `${encodeURIComponent(key)}=${encodeURIComponent(value)}`, + ) + .join('&'); + } + const response = await fetch(url); + if (!response.ok) { + const payload = await response.text(); + throw new Error( + `Request failed with ${response.status} ${response.statusText}, ${payload}`, + ); + } + return await response.json(); } async getEntityByName(name: string): Promise { diff --git a/plugins/catalog/src/api/types.ts b/plugins/catalog/src/api/types.ts index 7d6c1d2b47..1cfe5736fb 100644 --- a/plugins/catalog/src/api/types.ts +++ b/plugins/catalog/src/api/types.ts @@ -24,9 +24,8 @@ export const catalogApiRef = createApiRef({ export interface CatalogApi { getLocationById(id: String): Promise; - getEntities(): Promise; + getEntities(filter?: Record): Promise; getEntityByName(name: string): Promise; - getEntitiesByLocationId(id: string): Promise; addLocation(type: string, target: string): Promise; getLocationByEntity(entity: Entity): Promise; } diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx index ac8de59bd9..0a8468efef 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx @@ -14,35 +14,33 @@ * limitations under the License. */ -import React, { FC, useCallback, useState } from 'react'; import { Content, ContentHeader, DismissableBanner, Header, + HeaderTabs, HomepageTimer, - SupportButton, Page, pageTheme, + SupportButton, useApi, - HeaderTabs, } from '@backstage/core'; +import { rootRoute as scaffolderRootRoute } from '@backstage/plugin-scaffolder'; +import { Button, Link, makeStyles, Typography } from '@material-ui/core'; +import GitHub from '@material-ui/icons/GitHub'; +import React, { FC, useCallback, useState } from 'react'; +import { Link as RouterLink } from 'react-router-dom'; import { useAsync } from 'react-use'; -import CatalogTable from '../CatalogTable/CatalogTable'; +import { catalogApiRef } from '../..'; +import { Component } from '../../data/component'; +import { defaultFilter, filterGroups } from '../../data/filters'; +import { entityToComponent, findLocationForEntity } from '../../data/utils'; import { CatalogFilter, CatalogFilterItem, } from '../CatalogFilter/CatalogFilter'; -import { Button, makeStyles, Typography, Link } from '@material-ui/core'; -import { filterGroups, defaultFilter } from '../../data/filters'; -import { Link as RouterLink } from 'react-router-dom'; -import { rootRoute as scaffolderRootRoute } from '@backstage/plugin-scaffolder'; -import GitHub from '@material-ui/icons/GitHub'; -import { - Entity, - Location, - LOCATION_ANNOTATION, -} from '@backstage/catalog-model'; +import CatalogTable from '../CatalogTable/CatalogTable'; const useStyles = makeStyles(theme => ({ contentWrapper: { @@ -57,10 +55,6 @@ const useStyles = makeStyles(theme => ({ }, })); -import { catalogApiRef } from '../..'; -import { entityToComponent, findLocationForEntity } from '../../data/utils'; -import { Component } from '../../data/component'; - const CatalogPage: FC<{}> = () => { const catalogApi = useApi(catalogApiRef); const { value, error, loading } = useAsync(() => catalogApi.getEntities()); @@ -74,26 +68,6 @@ const CatalogPage: FC<{}> = () => { ); const styles = useStyles(); - const { value: locations } = useAsync(async () => { - const getLocationDataForEntities = async (entities: Entity[]) => { - return Promise.all( - entities.map(entity => { - const locationId = entity.metadata.annotations?.[LOCATION_ANNOTATION]; - if (!locationId) return undefined; - - return catalogApi.getLocationById(locationId); - }), - ); - }; - - if (value) { - return getLocationDataForEntities(value).then( - (location): Location[] => - location.filter(loc => !!loc) as Array, - ); - } - return []; - }, [value, catalogApi, catalogApi]); const actions = [ (rowData: Component) => ({ icon: GitHub, @@ -171,24 +145,22 @@ const CatalogPage: FC<{}> = () => { onSelectedChange={onFilterSelected} /> - {locations && ( - { - return { - ...entityToComponent(val), - location: findLocationForEntity(val, locations), - }; - })) || - [] - } - loading={loading} - error={error} - actions={actions} - /> - )} + { + return { + ...entityToComponent(val), + locationSpec: findLocationForEntity(val), + }; + })) || + [] + } + loading={loading} + error={error} + actions={actions} + /> diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx index 7ff521ee67..704fc68eff 100644 --- a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx @@ -13,11 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, { FC } from 'react'; -import { Component } from '../../data/component'; import { InfoCard, Progress, Table, TableColumn } from '@backstage/core'; -import { Typography, Link } from '@material-ui/core'; -import { Link as RouterLink, generatePath } from 'react-router-dom'; +import { Link, Typography } from '@material-ui/core'; +import React, { FC } from 'react'; +import { generatePath, Link as RouterLink } from 'react-router-dom'; +import { Component } from '../../data/component'; import { entityRoute } from '../../routes'; const columns: TableColumn[] = [ @@ -51,6 +51,7 @@ type CatalogTableProps = { error?: any; actions?: any; }; + const CatalogTable: FC = ({ components, loading, @@ -60,16 +61,17 @@ const CatalogTable: FC = ({ }) => { if (loading) { return ; - } - if (error) { + } else if (error) { return ( Error encountered while fetching components. + {error} ); } + return ( = ({ /> ); }; + export default CatalogTable; diff --git a/plugins/catalog/src/components/ComponentContextMenu/ComponentContextMenu.tsx b/plugins/catalog/src/components/ComponentContextMenu/ComponentContextMenu.tsx index 674d979f83..7b3729f841 100644 --- a/plugins/catalog/src/components/ComponentContextMenu/ComponentContextMenu.tsx +++ b/plugins/catalog/src/components/ComponentContextMenu/ComponentContextMenu.tsx @@ -81,7 +81,7 @@ const ComponentContextMenu: FC = ({ - More repository + Move repository diff --git a/plugins/catalog/src/components/ComponentRemovalDialog/ComponentRemovalDialog.tsx b/plugins/catalog/src/components/ComponentRemovalDialog/ComponentRemovalDialog.tsx index 7dde95c8f0..2c9ccc3eea 100644 --- a/plugins/catalog/src/components/ComponentRemovalDialog/ComponentRemovalDialog.tsx +++ b/plugins/catalog/src/components/ComponentRemovalDialog/ComponentRemovalDialog.tsx @@ -13,7 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, { FC } from 'react'; +import { Entity, LOCATION_ANNOTATION } from '@backstage/catalog-model'; +import { Progress, useApi } from '@backstage/core'; import { Button, Dialog, @@ -23,12 +24,15 @@ import { DialogTitle, useMediaQuery, useTheme, + List, + ListItem, + ListItemText, } from '@material-ui/core'; -import { Component } from '../../data/component'; +import React, { FC } from 'react'; import { useAsync } from 'react-use'; -import { useApi } from '@backstage/core'; +import { AsyncState } from 'react-use/lib/useAsync'; import { catalogApiRef } from '../../api/types'; -import { Entity } from '@backstage/catalog-model'; +import { Component } from '../../data/component'; type ComponentRemovalDialogProps = { onConfirm: () => any; @@ -36,44 +40,81 @@ type ComponentRemovalDialogProps = { onClose: () => any; component: Component; }; + +function useColocatedEntities(component: Component): AsyncState { + const catalogApi = useApi(catalogApiRef); + return useAsync(async () => { + const myLocation = component.metadata.annotations?.[LOCATION_ANNOTATION]; + return myLocation + ? await catalogApi.getEntities({ [LOCATION_ANNOTATION]: myLocation }) + : []; + }, [catalogApi, component]); +} + const ComponentRemovalDialog: FC = ({ onConfirm, onCancel, onClose, component, }) => { - const catalogApi = useApi(catalogApiRef); - const { value } = useAsync(async () => { - let colocatedEntities: Array = []; - const locationId = component.location?.id; - if (locationId) { - colocatedEntities = await catalogApi.getEntitiesByLocationId(locationId); - } - return colocatedEntities; - }); + const { value: entities, loading, error } = useColocatedEntities(component); const theme = useTheme(); const fullScreen = useMediaQuery(theme.breakpoints.down('sm')); - const infoMessage = `This action will unregister ${ - value ? value.map(e => e.metadata.name).join(', ') : '' - } from location with target ${component.location?.target}. To undo, - just re-register the component in Backstage.`; + return ( Are you sure you want to unregister this component? - {infoMessage} + {loading ? : null} + {error ? ( + {error.toString()} + ) : null} + {entities ? ( + <> + + This action will unregister the following entities: + + + {entities.map(e => ( + + + + ))} + + + That are located at the following location: + + + + + + + + To undo, just re-register the component in Backstage. + + + ) : null} - ); }; + export default ComponentRemovalDialog; diff --git a/plugins/catalog/src/data/component.ts b/plugins/catalog/src/data/component.ts index 73b349391b..5827c05930 100644 --- a/plugins/catalog/src/data/component.ts +++ b/plugins/catalog/src/data/component.ts @@ -13,12 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { EntityMeta, Location } from '@backstage/catalog-model'; import { ReactNode } from 'react'; -import { Location } from '@backstage/catalog-model'; export type Component = { name: string; kind: string; + metadata: EntityMeta; description: ReactNode; location?: Location; }; diff --git a/plugins/catalog/src/data/utils.tsx b/plugins/catalog/src/data/utils.tsx index 93d28f5ca2..a3dca7b6e1 100644 --- a/plugins/catalog/src/data/utils.tsx +++ b/plugins/catalog/src/data/utils.tsx @@ -19,6 +19,7 @@ import { Entity, Location, LOCATION_ANNOTATION, + LocationSpec, } from '@backstage/catalog-model'; import Edit from '@material-ui/icons/Edit'; import IconButton from '@material-ui/core/IconButton'; @@ -38,13 +39,12 @@ const createEditLink = (location: Location): string => { } }; -export function entityToComponent( - envelope: Entity, - location?: Location, -): Component { +export function entityToComponent(envelope: Entity): Component { + const location = findLocationForEntity(envelope); return { name: envelope.metadata?.name ?? '', kind: envelope.kind ?? 'unknown', + metadata: envelope.metadata, description: ( {envelope.metadata?.annotations?.description ?? 'placeholder'} @@ -57,18 +57,25 @@ export function entityToComponent( ) : null} ), - location, + location: findLocationForEntity(envelope), }; } export function findLocationForEntity( entity: Entity, - locations: Location[], -): Location | undefined { - for (const loc of locations) { - if (loc.id === entity.metadata.annotations?.[LOCATION_ANNOTATION]) { - return loc; - } +): LocationSpec | undefined { + const annotation = entity.metadata.annotations?.[LOCATION_ANNOTATION]; + if (!annotation) { + return undefined; } - return undefined; + + const separatorIndex = annotation.indexOf(':'); + if (separatorIndex === -1) { + return undefined; + } + + return { + type: annotation.substring(0, separatorIndex), + target: annotation.substring(separatorIndex + 1), + }; } From 7a654c6d1920b3910d30d946bc89cb71bdaa8c37 Mon Sep 17 00:00:00 2001 From: Nikita Nek Dudnik Date: Tue, 9 Jun 2020 09:23:33 +0200 Subject: [PATCH 02/19] feat(catalog): implement entities with location removal --- .../catalog/DatabaseEntitiesCatalog.test.ts | 1 + .../src/catalog/DatabaseEntitiesCatalog.ts | 28 +++++++++++++++- .../src/catalog/DatabaseLocationsCatalog.ts | 2 +- .../src/database/CommonDatabase.test.ts | 3 +- .../src/database/CommonDatabase.ts | 23 ++++++++++--- plugins/catalog-backend/src/database/types.ts | 4 ++- plugins/catalog/src/api/CatalogClient.ts | 30 +++++++++++++++++ plugins/catalog/src/api/types.ts | 2 ++ .../CatalogTable/CatalogTable.test.tsx | 29 ++++++++++++++--- .../ComponentMetadataCard.test.tsx | 14 ++++---- .../ComponentMetadataCard.tsx | 10 +++--- .../ComponentPage/ComponentPage.tsx | 28 +++++++--------- .../ComponentRemovalDialog.tsx | 32 +++++++++++++------ plugins/catalog/src/data/component.ts | 4 +-- plugins/catalog/src/data/utils.tsx | 3 +- .../RegisterComponentPage.test.tsx | 1 - 16 files changed, 159 insertions(+), 55 deletions(-) diff --git a/plugins/catalog-backend/src/catalog/DatabaseEntitiesCatalog.test.ts b/plugins/catalog-backend/src/catalog/DatabaseEntitiesCatalog.test.ts index eb9cb2439c..6de0aa90a5 100644 --- a/plugins/catalog-backend/src/catalog/DatabaseEntitiesCatalog.test.ts +++ b/plugins/catalog-backend/src/catalog/DatabaseEntitiesCatalog.test.ts @@ -28,6 +28,7 @@ describe('DatabaseEntitiesCatalog', () => { updateEntity: jest.fn(), entities: jest.fn(), entity: jest.fn(), + entityById: jest.fn(), removeEntity: jest.fn(), addLocation: jest.fn(), removeLocation: jest.fn(), diff --git a/plugins/catalog-backend/src/catalog/DatabaseEntitiesCatalog.ts b/plugins/catalog-backend/src/catalog/DatabaseEntitiesCatalog.ts index 1ec1ebf6e5..9c3f87055b 100644 --- a/plugins/catalog-backend/src/catalog/DatabaseEntitiesCatalog.ts +++ b/plugins/catalog-backend/src/catalog/DatabaseEntitiesCatalog.ts @@ -15,6 +15,9 @@ */ import type { Entity } from '@backstage/catalog-model'; +import { LOCATION_ANNOTATION } from '@backstage/catalog-model'; +import { NotFoundError } from '@backstage/backend-common'; + import type { Database, DbEntityResponse, EntityFilters } from '../database'; import type { EntitiesCatalog } from './types'; @@ -78,7 +81,30 @@ export class DatabaseEntitiesCatalog implements EntitiesCatalog { async removeEntityByUid(uid: string): Promise { return await this.database.transaction(async tx => { - await this.database.removeEntity(tx, uid); + const currentEntity = await this.database.entityById(tx, uid); + if (!currentEntity) { + throw new NotFoundError(`Entity with ID ${uid} was not found`); + } + const colocatedEntities = currentEntity?.entity.metadata.annotations?.[ + LOCATION_ANNOTATION + ] + ? await this.database.entities(tx, [ + { + key: LOCATION_ANNOTATION, + values: [ + currentEntity.entity.metadata.annotations[LOCATION_ANNOTATION], + ], + }, + ]) + : [currentEntity]; + for (const dbResponse of colocatedEntities) { + await this.database.removeEntity(tx, dbResponse?.entity.metadata.uid!); + } + + if (currentEntity?.locationId) { + await this.database.removeLocation(tx, currentEntity?.locationId!); + } + return Promise.resolve(); }); } diff --git a/plugins/catalog-backend/src/catalog/DatabaseLocationsCatalog.ts b/plugins/catalog-backend/src/catalog/DatabaseLocationsCatalog.ts index a1e76679f7..f515829b91 100644 --- a/plugins/catalog-backend/src/catalog/DatabaseLocationsCatalog.ts +++ b/plugins/catalog-backend/src/catalog/DatabaseLocationsCatalog.ts @@ -31,7 +31,7 @@ export class DatabaseLocationsCatalog implements LocationsCatalog { } async removeLocation(id: string): Promise { - await this.database.removeLocation(id); + await this.database.transaction(tx => this.database.removeLocation(tx, id)); } async locations(): Promise { diff --git a/plugins/catalog-backend/src/database/CommonDatabase.test.ts b/plugins/catalog-backend/src/database/CommonDatabase.test.ts index f1723e3733..1802ea5e7d 100644 --- a/plugins/catalog-backend/src/database/CommonDatabase.test.ts +++ b/plugins/catalog-backend/src/database/CommonDatabase.test.ts @@ -105,8 +105,7 @@ describe('CommonDatabase', () => { expect(locations).toEqual([output]); const location = await db.location(locations[0].id); expect(location).toEqual(output); - - await db.removeLocation(locations[0].id); + await db.transaction(tx => db.removeLocation(tx, locations[0].id)); await expect(db.locations()).resolves.toEqual([]); await expect(db.location(locations[0].id)).rejects.toThrow( diff --git a/plugins/catalog-backend/src/database/CommonDatabase.ts b/plugins/catalog-backend/src/database/CommonDatabase.ts index 882995c142..75d1bfb64f 100644 --- a/plugins/catalog-backend/src/database/CommonDatabase.ts +++ b/plugins/catalog-backend/src/database/CommonDatabase.ts @@ -319,6 +319,21 @@ export class CommonDatabase implements Database { return toEntityResponse(rows[0]); } + async entityById( + txOpaque: unknown, + id: string, + ): Promise { + const tx = txOpaque as Knex.Transaction; + + const rows = await tx('entities').where({ id }).select(); + + if (rows.length !== 1) { + return undefined; + } + + return toEntityResponse(rows[0]); + } + async removeEntity(txOpaque: unknown, uid: string): Promise { const tx = txOpaque as Knex.Transaction; @@ -341,10 +356,10 @@ export class CommonDatabase implements Database { }); } - async removeLocation(id: string): Promise { - const result = await this.database('locations') - .where({ id }) - .del(); + async removeLocation(txOpaque: unknown, id: string): Promise { + const tx = txOpaque as Knex.Transaction; + + const result = await tx('locations').where({ id }).del(); if (!result) { throw new NotFoundError(`Found no location with ID ${id}`); diff --git a/plugins/catalog-backend/src/database/types.ts b/plugins/catalog-backend/src/database/types.ts index 17da373ae9..078319d9cb 100644 --- a/plugins/catalog-backend/src/database/types.ts +++ b/plugins/catalog-backend/src/database/types.ts @@ -130,11 +130,13 @@ export type Database = { namespace?: string, ): Promise; + entityById(tx: unknown, id: string): Promise; + removeEntity(tx: unknown, uid: string): Promise; addLocation(location: Location): Promise; - removeLocation(id: string): Promise; + removeLocation(tx: unknown, id: string): Promise; location(id: string): Promise; diff --git a/plugins/catalog/src/api/CatalogClient.ts b/plugins/catalog/src/api/CatalogClient.ts index 9ea59aab48..231c6c45d4 100644 --- a/plugins/catalog/src/api/CatalogClient.ts +++ b/plugins/catalog/src/api/CatalogClient.ts @@ -45,6 +45,36 @@ export class CatalogClient implements CatalogApi { } return undefined; } + async removeEntityByUid(uid: string): Promise { + const response = await fetch( + `${this.apiOrigin}${this.basePath}/entities/by-uid/${uid}`, + { + method: 'DELETE', + }, + ); + if (!response.ok) { + const payload = await response.text(); + throw new Error( + `Request failed with ${response.status} ${response.statusText}, ${payload}`, + ); + } + return undefined; + } + async removeLocationById(id: string): Promise { + const response = await fetch( + `${this.apiOrigin}${this.basePath}/locations/${id}`, + { + method: 'DELETE', + }, + ); + if (!response.ok) { + const payload = await response.text(); + throw new Error( + `Request failed with ${response.status} ${response.statusText}, ${payload}`, + ); + } + return undefined; + } async getEntities( filter?: Record, ): Promise { diff --git a/plugins/catalog/src/api/types.ts b/plugins/catalog/src/api/types.ts index 1cfe5736fb..3076d6950b 100644 --- a/plugins/catalog/src/api/types.ts +++ b/plugins/catalog/src/api/types.ts @@ -24,10 +24,12 @@ export const catalogApiRef = createApiRef({ export interface CatalogApi { getLocationById(id: String): Promise; + removeLocationById(id: String): Promise; getEntities(filter?: Record): Promise; getEntityByName(name: string): Promise; addLocation(type: string, target: string): Promise; getLocationByEntity(entity: Entity): Promise; + removeEntityByUid(uid: string): Promise; } export type AddLocationResponse = { location: Location; entities: Entity[] }; diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.test.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.test.tsx index 029fad5474..f7b2addbd6 100644 --- a/plugins/catalog/src/components/CatalogTable/CatalogTable.test.tsx +++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.test.tsx @@ -20,9 +20,30 @@ import CatalogTable from './CatalogTable'; import { Component } from '../../data/component'; const components: Component[] = [ - { name: 'component1', kind: 'Component', description: 'Placeholder' }, - { name: 'component2', kind: 'Component', description: 'Placeholder' }, - { name: 'component3', kind: 'Component', description: 'Placeholder' }, + { + name: 'component1', + kind: 'Component', + description: 'Placeholder', + metadata: { + name: 'component1', + }, + }, + { + name: 'component2', + kind: 'Component', + description: 'Placeholder', + metadata: { + name: 'component2', + }, + }, + { + name: 'component3', + kind: 'Component', + description: 'Placeholder', + metadata: { + name: 'component3', + }, + }, ]; describe('CatalogTable component', () => { @@ -48,7 +69,7 @@ describe('CatalogTable component', () => { ), ); const errorMessage = await rendered.findByText( - 'Error encountered while fetching components.', + 'Something went wrong here. Please contact #backstage for help.', ); expect(errorMessage).toBeInTheDocument(); }); diff --git a/plugins/catalog/src/components/ComponentMetadataCard/ComponentMetadataCard.test.tsx b/plugins/catalog/src/components/ComponentMetadataCard/ComponentMetadataCard.test.tsx index 62e3d52c29..06b768d55f 100644 --- a/plugins/catalog/src/components/ComponentMetadataCard/ComponentMetadataCard.test.tsx +++ b/plugins/catalog/src/components/ComponentMetadataCard/ComponentMetadataCard.test.tsx @@ -15,24 +15,26 @@ */ import React from 'react'; import ComponentMetadataCard from './ComponentMetadataCard'; -import { Component } from '../../data/component'; import { render } from '@testing-library/react'; +import { Entity } from '../../../../../packages/catalog-model/src/entity/Entity'; describe('ComponentMetadataCard component', () => { it('should display component name if provided', async () => { - const testComponent: Component = { - name: 'test', + const testEntity: Entity = { + apiVersion: '', kind: 'Component', - description: 'Placeholder', + metadata: { + name: 'test', + }, }; const rendered = await render( - , + , ); expect(await rendered.findByText('test')).toBeInTheDocument(); }); it('should display loader when loading is set to true', async () => { const rendered = await render( - , + , ); expect(await rendered.findByRole('progressbar')).toBeInTheDocument(); }); diff --git a/plugins/catalog/src/components/ComponentMetadataCard/ComponentMetadataCard.tsx b/plugins/catalog/src/components/ComponentMetadataCard/ComponentMetadataCard.tsx index 7059709992..8f504545e3 100644 --- a/plugins/catalog/src/components/ComponentMetadataCard/ComponentMetadataCard.tsx +++ b/plugins/catalog/src/components/ComponentMetadataCard/ComponentMetadataCard.tsx @@ -14,16 +14,16 @@ * limitations under the License. */ import React, { FC } from 'react'; -import { Component } from '../../data/component'; import { Progress, InfoCard, StructuredMetadataTable } from '@backstage/core'; +import { Entity } from '@backstage/catalog-model'; type ComponentMetadataCardProps = { loading: boolean; - component: Component | undefined; + entity: Entity | undefined; }; const ComponentMetadataCard: FC = ({ loading, - component, + entity, }) => { if (loading) { return ( @@ -32,12 +32,12 @@ const ComponentMetadataCard: FC = ({ ); } - if (!component) { + if (!entity) { return null; } return ( - + ); }; diff --git a/plugins/catalog/src/components/ComponentPage/ComponentPage.tsx b/plugins/catalog/src/components/ComponentPage/ComponentPage.tsx index f26c21b6ca..b7de5de01e 100644 --- a/plugins/catalog/src/components/ComponentPage/ComponentPage.tsx +++ b/plugins/catalog/src/components/ComponentPage/ComponentPage.tsx @@ -32,8 +32,7 @@ import ComponentRemovalDialog from '../ComponentRemovalDialog/ComponentRemovalDi import { SentryIssuesWidget } from '@backstage/plugin-sentry'; import { Grid } from '@material-ui/core'; import { catalogApiRef } from '../..'; -import { entityToComponent } from '../../data/utils'; -import { Component } from '../../data/component'; +import { Entity } from '@backstage/catalog-model'; const REDIRECT_DELAY = 1000; @@ -53,14 +52,12 @@ const ComponentPage: FC = ({ match, history }) => { const [removingPending, setRemovingPending] = useState(false); const showRemovalDialog = () => setConfirmationDialogOpen(true); const hideRemovalDialog = () => setConfirmationDialogOpen(false); - const componentName = match.params.name; + const entityName = match.params.name; const errorApi = useApi(errorApiRef); const catalogApi = useApi(catalogApiRef); - const { value: component, error, loading } = useAsync(async () => { - const entity = await catalogApi.getEntityByName(match.params.name); - const location = await catalogApi.getLocationByEntity(entity); - return { ...entityToComponent(entity), location }; + const { value: entity, error, loading } = useAsync(async () => { + return await catalogApi.getEntityByName(match.params.name); }); useEffect(() => { @@ -72,17 +69,14 @@ const ComponentPage: FC = ({ match, history }) => { } }, [error, errorApi, history]); - if (componentName === '') { + if (entityName === '') { history.push('/catalog'); return null; } - const removeComponent = async () => { + const cleanUpAfterRemoval = async () => { setConfirmationDialogOpen(false); setRemovingPending(true); - // await componentFactory.removeComponentByName(componentName); - - await catalogApi; history.push('/'); }; @@ -117,16 +111,16 @@ const ComponentPage: FC = ({ match, history }) => { return ( // TODO: Switch theme and type props based on component type (website, library, ...) -
+
- {confirmationDialogOpen && component && ( + {confirmationDialogOpen && entity && ( )} @@ -135,7 +129,7 @@ const ComponentPage: FC = ({ match, history }) => { diff --git a/plugins/catalog/src/components/ComponentRemovalDialog/ComponentRemovalDialog.tsx b/plugins/catalog/src/components/ComponentRemovalDialog/ComponentRemovalDialog.tsx index 2c9ccc3eea..a923382111 100644 --- a/plugins/catalog/src/components/ComponentRemovalDialog/ComponentRemovalDialog.tsx +++ b/plugins/catalog/src/components/ComponentRemovalDialog/ComponentRemovalDialog.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ import { Entity, LOCATION_ANNOTATION } from '@backstage/catalog-model'; -import { Progress, useApi } from '@backstage/core'; +import { Progress, useApi, alertApiRef } from '@backstage/core'; import { Button, Dialog, @@ -32,32 +32,33 @@ import React, { FC } from 'react'; import { useAsync } from 'react-use'; import { AsyncState } from 'react-use/lib/useAsync'; import { catalogApiRef } from '../../api/types'; -import { Component } from '../../data/component'; type ComponentRemovalDialogProps = { onConfirm: () => any; onCancel: () => any; onClose: () => any; - component: Component; + entity: Entity; }; -function useColocatedEntities(component: Component): AsyncState { +function useColocatedEntities(entity: Entity): AsyncState { const catalogApi = useApi(catalogApiRef); return useAsync(async () => { - const myLocation = component.metadata.annotations?.[LOCATION_ANNOTATION]; + const myLocation = entity.metadata.annotations?.[LOCATION_ANNOTATION]; return myLocation ? await catalogApi.getEntities({ [LOCATION_ANNOTATION]: myLocation }) : []; - }, [catalogApi, component]); + }, [catalogApi, entity]); } const ComponentRemovalDialog: FC = ({ onConfirm, onCancel, onClose, - component, + entity, }) => { - const { value: entities, loading, error } = useColocatedEntities(component); + const catalogApi = useApi(catalogApiRef); + const alertApi = useApi(alertApiRef); + const { value: entities, loading, error } = useColocatedEntities(entity); const theme = useTheme(); const fullScreen = useMediaQuery(theme.breakpoints.down('sm')); @@ -107,7 +108,20 @@ const ComponentRemovalDialog: FC = ({
{ it('should call onUnregisterComponent on button click', async () => { await act(async () => { const mockCallback = jest.fn(); - const menu = await render( + const menu = render( , ); const button = await menu.findByTestId('menu-button'); diff --git a/plugins/catalog/src/components/ComponentPage/ComponentPage.test.tsx b/plugins/catalog/src/components/ComponentPage/ComponentPage.test.tsx index f580a84d8a..252f2d4e3f 100644 --- a/plugins/catalog/src/components/ComponentPage/ComponentPage.test.tsx +++ b/plugins/catalog/src/components/ComponentPage/ComponentPage.test.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ import ComponentPage from './ComponentPage'; -import { render } from '@testing-library/react'; +import { render, wait } from '@testing-library/react'; import * as React from 'react'; import { wrapInTestApp } from '@backstage/test-utils'; import { ApiProvider, ApiRegistry, errorApiRef } from '@backstage/core'; @@ -38,7 +38,7 @@ const errorApi = { post: () => {} }; describe('ComponentPage', () => { it('should redirect to component table page when name is not provided', async () => { const props = getTestProps(''); - await render( + render( wrapInTestApp( { , ), ); - expect(props.history.push).toHaveBeenCalledWith('/catalog'); + + await wait(() => + expect(props.history.push).toHaveBeenCalledWith('/catalog'), + ); }); }); diff --git a/plugins/catalog/src/data/filters.ts b/plugins/catalog/src/data/filters.ts index 1b0a2a407c..1fcdfa27a2 100644 --- a/plugins/catalog/src/data/filters.ts +++ b/plugins/catalog/src/data/filters.ts @@ -19,21 +19,26 @@ import { } from '../components/CatalogFilter/CatalogFilter'; import SettingsIcon from '@material-ui/icons/Settings'; import StarIcon from '@material-ui/icons/Star'; +import { StarredCount } from '../components/CatalogFilter/StarredCount'; +import { AllServicesCount } from '../components/CatalogFilter/AllServicesCount'; +import { FilterGroupItem } from '../types'; +import { CatalogApi } from '../..'; +import { Entity } from '@backstage/catalog-model'; export const filterGroups: CatalogFilterGroup[] = [ { name: 'Personal', items: [ { - id: 'owned', + id: FilterGroupItem.OWNED, label: 'Owned', - count: 123, + count: 0, icon: SettingsIcon, }, { - id: 'starred', + id: FilterGroupItem.STARRED, label: 'Starred', - count: 10, + count: StarredCount, icon: StarIcon, }, ], @@ -43,12 +48,34 @@ export const filterGroups: CatalogFilterGroup[] = [ name: 'Company', items: [ { - id: 'all', + id: FilterGroupItem.ALL, label: 'All Services', - count: 123, + count: AllServicesCount, }, ], }, ]; +type ResolverFunction = ({ + catalogApi, + starredEntities, +}: { + catalogApi: CatalogApi; + starredEntities: Set; +}) => Promise; + +export const dataResolvers: Record = { + [FilterGroupItem.OWNED]: async () => [], + [FilterGroupItem.ALL]: async ({ catalogApi }) => { + return catalogApi.getEntities(); + }, + [FilterGroupItem.STARRED]: async ({ catalogApi, starredEntities }) => { + const allEntities = await catalogApi.getEntities(); + + return allEntities.filter(entity => + starredEntities.has(entity.metadata.name), + ); + }, +}; + export const defaultFilter: CatalogFilterItem = filterGroups[0].items[0]; diff --git a/plugins/catalog/src/hooks/useStarredEntites.ts b/plugins/catalog/src/hooks/useStarredEntites.ts new file mode 100644 index 0000000000..631991e9b0 --- /dev/null +++ b/plugins/catalog/src/hooks/useStarredEntites.ts @@ -0,0 +1,43 @@ +/* + * 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 { useState, useEffect } from 'react'; +import { useApi, storageApiRef } from '@backstage/core'; +import { useObservable } from 'react-use'; + +export const useStarredEntities = () => { + const storageApi = useApi(storageApiRef); + const settingsStore = storageApi.forBucket('settings'); + const rawStarredItems = settingsStore.get('starredEntities') ?? []; + + const [starredEntities, setStarredEntities] = useState( + new Set(rawStarredItems), + ); + + const observedItems = useObservable( + settingsStore.observe$('starredEntities'), + ); + + useEffect(() => { + if (observedItems?.newValue) { + const currentValue = observedItems?.newValue ?? []; + setStarredEntities(new Set(currentValue)); + } + }, [observedItems?.newValue]); + + return { + starredEntities, + }; +}; diff --git a/plugins/catalog/src/hooks/useStarredEntities.test.tsx b/plugins/catalog/src/hooks/useStarredEntities.test.tsx new file mode 100644 index 0000000000..49890b7238 --- /dev/null +++ b/plugins/catalog/src/hooks/useStarredEntities.test.tsx @@ -0,0 +1,80 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { renderHook } from '@testing-library/react-hooks'; +import { useStarredEntities } from './useStarredEntites'; +import { + ApiProvider, + ApiRegistry, + storageApiRef, + WebStorage, + StorageApi, +} from '@backstage/core'; +import { MockErrorApi } from '@backstage/test-utils'; + +describe('useStarredEntities', () => { + let mockStorage: StorageApi | undefined; + + const wrapper: React.FC<{}> = ({ children }) => { + return ( + + {children} + + ); + }; + + beforeEach(() => { + mockStorage = new WebStorage('@backstage', new MockErrorApi()).forBucket( + Date.now().toString(), // TODO(blam): need something that changes every test run for now until the MockStorage is implemented + ); + }); + it('should return an empty set for when there is no items in storage', async () => { + const { result } = renderHook(() => useStarredEntities(), { wrapper }); + + expect(result.current.starredEntities.size).toBe(0); + }); + it('should return a set with the current items when there is items in storage', async () => { + const expectedIds = ['i', 'am', 'some', 'test', 'ids']; + const store = mockStorage?.forBucket('settings'); + await store?.set('starredEntities', expectedIds); + + const { result } = renderHook(() => useStarredEntities(), { wrapper }); + + for (const item of expectedIds) { + expect(result.current.starredEntities.has(item)).toBeTruthy(); + } + }); + it('should listen to changes when the storage is set elsewhere', async () => { + const store = mockStorage?.forBucket('settings'); + + const { result, waitForNextUpdate } = renderHook( + () => useStarredEntities(), + { wrapper }, + ); + + expect(result.current.starredEntities.size).toBe(0); + expect(result.current.starredEntities.has('something')).toBeFalsy(); + + // Make this happen after awaiting for the next update so we can + // catch when the hook re-renders with the latest data + setTimeout(() => store?.set('starredEntities', ['something']), 1); + + await waitForNextUpdate(); + + expect(result.current.starredEntities.size).toBe(1); + expect(result.current.starredEntities.has('something')).toBeTruthy(); + }); +}); diff --git a/plugins/catalog/src/types.ts b/plugins/catalog/src/types.ts index 0f91e1ed8e..42f156ecb7 100644 --- a/plugins/catalog/src/types.ts +++ b/plugins/catalog/src/types.ts @@ -157,3 +157,9 @@ export type KindParser = { envelope: DescriptorEnvelope, ): Promise; }; + +export enum FilterGroupItem { + ALL = 'ALL', + STARRED = 'STARRED', + OWNED = 'OWNED', +} diff --git a/yarn.lock b/yarn.lock index 074e666e55..2b7246b230 100644 --- a/yarn.lock +++ b/yarn.lock @@ -891,6 +891,13 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.5.4": + version "7.10.2" + resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.2.tgz#d103f21f2602497d38348a32e008637d506db839" + integrity sha512-6sF3uQw2ivImfVIl62RZ7MXhO2tap69WeWK57vAaimT6AZbE4FbqjdEJIN1UqoD6wI6B+1n9UiagafH1sxjOtg== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/template@^7.3.3", "@babel/template@^7.7.4", "@babel/template@^7.8.3", "@babel/template@^7.8.6": version "7.8.6" resolved "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz#86b22af15f828dfb086474f964dcc3e39c43ce2b" @@ -3279,6 +3286,14 @@ lodash "^4.17.15" redent "^3.0.0" +"@testing-library/react-hooks@^3.3.0": + version "3.3.0" + resolved "https://registry.npmjs.org/@testing-library/react-hooks/-/react-hooks-3.3.0.tgz#dc217bfce8e7c34a99c811d73d23feef957b7c1d" + integrity sha512-rE9geI1+HJ6jqXkzzJ6abREbeud6bLF8OmF+Vyc7gBoPwZAEVBYjbC1up5nNoVfYBhO5HUwdD4u9mTehAUeiyw== + dependencies: + "@babel/runtime" "^7.5.4" + "@types/testing-library__react-hooks" "^3.0.0" + "@testing-library/react@^9.3.2": version "9.5.0" resolved "https://registry.npmjs.org/@testing-library/react/-/react-9.5.0.tgz#71531655a7890b61e77a1b39452fbedf0472ca5e" @@ -3910,6 +3925,13 @@ dependencies: "@types/react" "*" +"@types/react-test-renderer@*": + version "16.9.2" + resolved "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-16.9.2.tgz#e1c408831e8183e5ad748fdece02214a7c2ab6c5" + integrity sha512-4eJr1JFLIAlWhzDkBCkhrOIWOvOxcCAfQh+jiKg7l/nNZcCIL2MHl2dZhogIFKyHzedVWHaVP1Yydq/Ruu4agw== + dependencies: + "@types/react" "*" + "@types/react-textarea-autosize@^4.3.3": version "4.3.5" resolved "https://registry.npmjs.org/@types/react-textarea-autosize/-/react-textarea-autosize-4.3.5.tgz#6c4d2753fa1864c98c0b2b517f67bb1f6e4c46de" @@ -4067,6 +4089,14 @@ dependencies: "@types/jest" "*" +"@types/testing-library__react-hooks@^3.0.0": + version "3.2.0" + resolved "https://registry.npmjs.org/@types/testing-library__react-hooks/-/testing-library__react-hooks-3.2.0.tgz#52f3a109bef06080e3b1e3ae7ea1c014ce859897" + integrity sha512-dE8iMTuR5lzB+MqnxlzORlXzXyCL0EKfzH0w/lau20OpkHD37EaWjZDz0iNG8b71iEtxT4XKGmSKAGVEqk46mw== + dependencies: + "@types/react" "*" + "@types/react-test-renderer" "*" + "@types/testing-library__react@^9.1.2": version "9.1.3" resolved "https://registry.npmjs.org/@types/testing-library__react/-/testing-library__react-9.1.3.tgz#35eca61cc6ea923543796f16034882a1603d7302" @@ -15617,6 +15647,16 @@ react-syntax-highlighter@^12.2.1: prismjs "^1.8.4" refractor "^2.4.1" +react-test-renderer@^16.13.1: + version "16.13.1" + resolved "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.13.1.tgz#de25ea358d9012606de51e012d9742e7f0deabc1" + integrity sha512-Sn2VRyOK2YJJldOqoh8Tn/lWQ+ZiKhyZTPtaO0Q6yNj+QDbmRkVFap6pZPy3YQk8DScRDfyqm/KxKYP9gCMRiQ== + dependencies: + object-assign "^4.1.1" + prop-types "^15.6.2" + react-is "^16.8.6" + scheduler "^0.19.1" + react-textarea-autosize@^7.1.0: version "7.1.2" resolved "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-7.1.2.tgz#70fdb333ef86bcca72717e25e623e90c336e2cda" From d5641a9713790c128ad959213adf89bd6e17cb95 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 9 Jun 2020 14:57:12 +0200 Subject: [PATCH 06/19] Merge pull request #1212 from spotify/rugvip/release Release v0.1.1-alpha.7 --- lerna.json | 2 +- packages/app/package.json | 28 ++++++++++++------------- packages/backend-common/package.json | 4 ++-- packages/backend/package.json | 18 ++++++++-------- packages/catalog-model/package.json | 4 ++-- packages/cli/package.json | 6 +++--- packages/config-loader/package.json | 4 ++-- packages/config/package.json | 2 +- packages/core-api/package.json | 10 ++++----- packages/core/package.json | 12 +++++------ packages/dev-utils/package.json | 10 ++++----- packages/storybook/package.json | 4 ++-- packages/test-utils-core/package.json | 2 +- packages/test-utils/package.json | 10 ++++----- packages/theme/package.json | 4 ++-- plugins/auth-backend/package.json | 12 +++++------ plugins/catalog-backend/package.json | 8 +++---- plugins/catalog/package.json | 18 ++++++++-------- plugins/circleci/package.json | 10 ++++----- plugins/explore/package.json | 12 +++++------ plugins/graphiql/package.json | 12 +++++------ plugins/home-page/package.json | 10 ++++----- plugins/identity-backend/package.json | 6 +++--- plugins/lighthouse/package.json | 12 +++++------ plugins/register-component/package.json | 14 ++++++------- plugins/scaffolder-backend/package.json | 6 +++--- plugins/scaffolder/package.json | 10 ++++----- plugins/sentry-backend/package.json | 6 +++--- plugins/sentry/package.json | 10 ++++----- plugins/tech-radar/package.json | 12 +++++------ plugins/welcome/package.json | 10 ++++----- 31 files changed, 144 insertions(+), 144 deletions(-) diff --git a/lerna.json b/lerna.json index 5be143e0ef..2feea0137f 100644 --- a/lerna.json +++ b/lerna.json @@ -2,5 +2,5 @@ "packages": ["packages/*", "plugins/*"], "npmClient": "yarn", "useWorkspaces": true, - "version": "0.1.1-alpha.6" + "version": "0.1.1-alpha.7" } diff --git a/packages/app/package.json b/packages/app/package.json index 15681d0548..6e314c3fe0 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,21 +1,21 @@ { "name": "example-app", - "version": "0.1.1-alpha.6", + "version": "0.1.1-alpha.7", "private": true, "dependencies": { - "@backstage/cli": "^0.1.1-alpha.6", - "@backstage/core": "^0.1.1-alpha.6", - "@backstage/plugin-catalog": "^0.1.1-alpha.6", - "@backstage/plugin-circleci": "^0.1.1-alpha.6", - "@backstage/plugin-explore": "^0.1.1-alpha.6", - "@backstage/plugin-home-page": "^0.1.1-alpha.6", - "@backstage/plugin-lighthouse": "^0.1.1-alpha.6", - "@backstage/plugin-register-component": "^0.1.1-alpha.6", - "@backstage/plugin-scaffolder": "^0.1.1-alpha.6", - "@backstage/plugin-sentry": "^0.1.1-alpha.6", - "@backstage/plugin-tech-radar": "^0.1.1-alpha.6", - "@backstage/plugin-welcome": "^0.1.1-alpha.6", - "@backstage/theme": "^0.1.1-alpha.6", + "@backstage/cli": "^0.1.1-alpha.7", + "@backstage/core": "^0.1.1-alpha.7", + "@backstage/plugin-catalog": "^0.1.1-alpha.7", + "@backstage/plugin-circleci": "^0.1.1-alpha.7", + "@backstage/plugin-explore": "^0.1.1-alpha.7", + "@backstage/plugin-home-page": "^0.1.1-alpha.7", + "@backstage/plugin-lighthouse": "^0.1.1-alpha.7", + "@backstage/plugin-register-component": "^0.1.1-alpha.7", + "@backstage/plugin-scaffolder": "^0.1.1-alpha.7", + "@backstage/plugin-sentry": "^0.1.1-alpha.7", + "@backstage/plugin-tech-radar": "^0.1.1-alpha.7", + "@backstage/plugin-welcome": "^0.1.1-alpha.7", + "@backstage/theme": "^0.1.1-alpha.7", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "prop-types": "^15.7.2", diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index 847f034550..94dd222c41 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-common", "description": "Common functionality library for Backstage backends", - "version": "0.1.1-alpha.6", + "version": "0.1.1-alpha.7", "main": "dist", "types": "src/index.ts", "private": false, @@ -32,7 +32,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.6", + "@backstage/cli": "^0.1.1-alpha.7", "@types/express": "^4.17.6", "@types/http-errors": "^1.6.3", "@types/morgan": "^1.9.0", diff --git a/packages/backend/package.json b/packages/backend/package.json index 51fe04e51d..65e2976489 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,6 +1,6 @@ { "name": "example-backend", - "version": "0.1.1-alpha.6", + "version": "0.1.1-alpha.7", "main": "dist", "types": "src/index.ts", "private": true, @@ -17,13 +17,13 @@ "migrate:create": "knex migrate:make -x ts" }, "dependencies": { - "@backstage/backend-common": "^0.1.1-alpha.6", - "@backstage/catalog-model": "^0.1.1-alpha.6", - "@backstage/plugin-auth-backend": "^0.1.1-alpha.6", - "@backstage/plugin-catalog-backend": "^0.1.1-alpha.6", - "@backstage/plugin-identity-backend": "^0.1.1-alpha.6", - "@backstage/plugin-scaffolder-backend": "^0.1.1-alpha.6", - "@backstage/plugin-sentry-backend": "^0.1.1-alpha.6", + "@backstage/backend-common": "^0.1.1-alpha.7", + "@backstage/catalog-model": "^0.1.1-alpha.7", + "@backstage/plugin-auth-backend": "^0.1.1-alpha.7", + "@backstage/plugin-catalog-backend": "^0.1.1-alpha.7", + "@backstage/plugin-identity-backend": "^0.1.1-alpha.7", + "@backstage/plugin-scaffolder-backend": "^0.1.1-alpha.7", + "@backstage/plugin-sentry-backend": "^0.1.1-alpha.7", "compression": "^1.7.4", "cors": "^2.8.5", "esm": "^3.2.25", @@ -34,7 +34,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.6", + "@backstage/cli": "^0.1.1-alpha.7", "@types/compression": "^1.7.0", "@types/cors": "^2.8.6", "@types/express": "^4.17.6", diff --git a/packages/catalog-model/package.json b/packages/catalog-model/package.json index 2de2e70e8e..d2a63560e3 100644 --- a/packages/catalog-model/package.json +++ b/packages/catalog-model/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/catalog-model", - "version": "0.1.1-alpha.6", + "version": "0.1.1-alpha.7", "main": "dist/index.cjs.js", "module": "dist/index.esm.js", "main:src": "src/index.ts", @@ -26,7 +26,7 @@ "yup": "^0.28.5" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.6", + "@backstage/cli": "^0.1.1-alpha.7", "@types/jest": "^25.2.2", "@types/lodash": "^4.14.151", "@types/yup": "^0.28.2", diff --git a/packages/cli/package.json b/packages/cli/package.json index 30e56bf353..58b6c3fd84 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/cli", "description": "CLI for developing Backstage plugins and apps", - "version": "0.1.1-alpha.6", + "version": "0.1.1-alpha.7", "private": false, "publishConfig": { "access": "public" @@ -29,8 +29,8 @@ "backstage-cli": "bin/backstage-cli" }, "dependencies": { - "@backstage/config": "^0.1.1-alpha.6", - "@backstage/config-loader": "^0.1.1-alpha.6", + "@backstage/config": "^0.1.1-alpha.7", + "@backstage/config-loader": "^0.1.1-alpha.7", "@hot-loader/react-dom": "^16.13.0", "@lerna/package-graph": "^3.18.5", "@lerna/project": "^3.18.0", diff --git a/packages/config-loader/package.json b/packages/config-loader/package.json index 2641e988d6..7018dc40a8 100644 --- a/packages/config-loader/package.json +++ b/packages/config-loader/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/config-loader", "description": "Config loading functionality used by Backstage backend, and CLI", - "version": "0.1.1-alpha.6", + "version": "0.1.1-alpha.7", "private": false, "publishConfig": { "access": "public", @@ -30,7 +30,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/config": "^0.1.1-alpha.6", + "@backstage/config": "^0.1.1-alpha.7", "fs-extra": "^9.0.0", "yaml": "^1.9.2" }, diff --git a/packages/config/package.json b/packages/config/package.json index 2ff5450a0b..c67264d4c9 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/config", "description": "Config API used by Backstage core, backend, and CLI", - "version": "0.1.1-alpha.6", + "version": "0.1.1-alpha.7", "private": false, "publishConfig": { "access": "public", diff --git a/packages/core-api/package.json b/packages/core-api/package.json index c91a96ab31..f08b03a77e 100644 --- a/packages/core-api/package.json +++ b/packages/core-api/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core-api", "description": "Internal Core API used by Backstage plugins and apps", - "version": "0.1.1-alpha.6", + "version": "0.1.1-alpha.7", "private": false, "publishConfig": { "access": "public", @@ -30,8 +30,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/config": "^0.1.1-alpha.6", - "@backstage/theme": "^0.1.1-alpha.6", + "@backstage/config": "^0.1.1-alpha.7", + "@backstage/theme": "^0.1.1-alpha.7", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@types/react": "^16.9", @@ -42,8 +42,8 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.6", - "@backstage/test-utils-core": "^0.1.1-alpha.6", + "@backstage/cli": "^0.1.1-alpha.7", + "@backstage/test-utils-core": "^0.1.1-alpha.7", "@testing-library/jest-dom": "^5.7.0", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^10.2.4", diff --git a/packages/core/package.json b/packages/core/package.json index 6233f27065..8a476a58d5 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core", "description": "Core API used by Backstage plugins and apps", - "version": "0.1.1-alpha.6", + "version": "0.1.1-alpha.7", "private": false, "publishConfig": { "access": "public", @@ -30,9 +30,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/config": "^0.1.1-alpha.6", - "@backstage/core-api": "0.1.1-alpha.6", - "@backstage/theme": "^0.1.1-alpha.6", + "@backstage/config": "^0.1.1-alpha.7", + "@backstage/core-api": "^0.1.1-alpha.7", + "@backstage/theme": "^0.1.1-alpha.7", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -55,8 +55,8 @@ "react-use": "^14.2.0" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.6", - "@backstage/test-utils": "^0.1.1-alpha.6", + "@backstage/cli": "^0.1.1-alpha.7", + "@backstage/test-utils": "^0.1.1-alpha.7", "@testing-library/jest-dom": "^5.7.0", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^10.2.4", diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index eea0956a8b..01753db4d8 100644 --- a/packages/dev-utils/package.json +++ b/packages/dev-utils/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/dev-utils", "description": "Utilities for developing Backstage plugins.", - "version": "0.1.1-alpha.6", + "version": "0.1.1-alpha.7", "private": false, "publishConfig": { "access": "public", @@ -30,10 +30,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/cli": "^0.1.1-alpha.6", - "@backstage/core": "^0.1.1-alpha.6", - "@backstage/test-utils": "^0.1.1-alpha.6", - "@backstage/theme": "^0.1.1-alpha.6", + "@backstage/cli": "^0.1.1-alpha.7", + "@backstage/core": "^0.1.1-alpha.7", + "@backstage/test-utils": "^0.1.1-alpha.7", + "@backstage/theme": "^0.1.1-alpha.7", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@testing-library/jest-dom": "^5.7.0", diff --git a/packages/storybook/package.json b/packages/storybook/package.json index 883fb1452a..132bf29c72 100644 --- a/packages/storybook/package.json +++ b/packages/storybook/package.json @@ -1,6 +1,6 @@ { "name": "storybook", - "version": "0.1.1-alpha.6", + "version": "0.1.1-alpha.7", "description": "Storybook build for core package", "private": true, "scripts": { @@ -14,7 +14,7 @@ ] }, "dependencies": { - "@backstage/theme": "^0.1.1-alpha.6" + "@backstage/theme": "^0.1.1-alpha.7" }, "devDependencies": { "@storybook/addon-actions": "^5.3.17", diff --git a/packages/test-utils-core/package.json b/packages/test-utils-core/package.json index b1ef87729d..f0b98add94 100644 --- a/packages/test-utils-core/package.json +++ b/packages/test-utils-core/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/test-utils-core", "description": "Utilities to test Backstage core", - "version": "0.1.1-alpha.6", + "version": "0.1.1-alpha.7", "private": false, "publishConfig": { "access": "public", diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index 2a9320ee63..f48fe65c30 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/test-utils", "description": "Utilities to test Backstage plugins and apps.", - "version": "0.1.1-alpha.6", + "version": "0.1.1-alpha.7", "private": false, "publishConfig": { "access": "public", @@ -30,10 +30,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/cli": "^0.1.1-alpha.6", - "@backstage/core-api": "^0.1.1-alpha.6", - "@backstage/test-utils-core": "^0.1.1-alpha.6", - "@backstage/theme": "^0.1.1-alpha.6", + "@backstage/cli": "^0.1.1-alpha.7", + "@backstage/core-api": "^0.1.1-alpha.7", + "@backstage/test-utils-core": "^0.1.1-alpha.7", + "@backstage/theme": "^0.1.1-alpha.7", "@material-ui/core": "^4.9.1", "@testing-library/jest-dom": "^5.7.0", "@testing-library/react": "^9.3.2", diff --git a/packages/theme/package.json b/packages/theme/package.json index 501ef6473e..8b017d4cf9 100644 --- a/packages/theme/package.json +++ b/packages/theme/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/theme", "description": "material-ui theme for use with Backstage.", - "version": "0.1.1-alpha.6", + "version": "0.1.1-alpha.7", "private": false, "publishConfig": { "access": "public", @@ -32,7 +32,7 @@ "@material-ui/core": "^4.9.1" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.6" + "@backstage/cli": "^0.1.1-alpha.7" }, "files": [ "dist/**/*.{js,d.ts}" diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index d662f3eb6c..f45a1cbf7a 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend", - "version": "0.1.1-alpha.6", + "version": "0.1.1-alpha.7", "main": "dist", "types": "src/index.ts", "license": "Apache-2.0", @@ -15,8 +15,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.1.1-alpha.6", + "@backstage/backend-common": "^0.1.1-alpha.7", "@types/cookie-parser": "^1.4.2", + "@types/jwt-decode": "2.2.1", "@types/passport": "^1.0.3", "@types/passport-github2": "^1.2.4", "@types/passport-google-oauth20": "^2.0.3", @@ -28,18 +29,17 @@ "express-promise-router": "^3.0.3", "fs-extra": "^9.0.0", "helmet": "^3.22.0", + "jwt-decode": "2.2.0", "morgan": "^1.10.0", "passport": "^0.4.1", "passport-github2": "^0.1.12", "passport-google-oauth20": "^2.0.0", "passport-saml": "^1.3.3", "winston": "^3.2.1", - "yn": "^4.0.0", - "jwt-decode": "2.2.0", - "@types/jwt-decode": "2.2.1" + "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.6", + "@backstage/cli": "^0.1.1-alpha.7", "@types/body-parser": "^1.19.0", "@types/passport-saml": "^1.1.2", "jest-fetch-mock": "^3.0.3", diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index 253959c61b..7490d1221a 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend", - "version": "0.1.1-alpha.6", + "version": "0.1.1-alpha.7", "main": "dist", "types": "src/index.ts", "license": "Apache-2.0", @@ -16,8 +16,8 @@ "mock-data": "./scripts/mock-data" }, "dependencies": { - "@backstage/backend-common": "^0.1.1-alpha.6", - "@backstage/catalog-model": "^0.1.1-alpha.6", + "@backstage/backend-common": "^0.1.1-alpha.7", + "@backstage/catalog-model": "^0.1.1-alpha.7", "compression": "^1.7.4", "cors": "^2.8.5", "esm": "^3.2.25", @@ -37,7 +37,7 @@ "yup": "^0.28.5" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.6", + "@backstage/cli": "^0.1.1-alpha.7", "@types/lodash": "^4.14.151", "@types/node-fetch": "^2.5.7", "@types/supertest": "^2.0.8", diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index e19869c946..7b8c1ccc70 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog", - "version": "0.1.1-alpha.6", + "version": "0.1.1-alpha.7", "main": "dist/index.esm.js", "main:src": "src/index.ts", "types": "src/index.ts", @@ -22,11 +22,11 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.1.1-alpha.6", - "@backstage/core": "^0.1.1-alpha.6", - "@backstage/plugin-scaffolder": "^0.1.1-alpha.6", - "@backstage/plugin-sentry": "^0.1.1-alpha.6", - "@backstage/theme": "^0.1.1-alpha.6", + "@backstage/catalog-model": "^0.1.1-alpha.7", + "@backstage/core": "^0.1.1-alpha.7", + "@backstage/plugin-scaffolder": "^0.1.1-alpha.7", + "@backstage/plugin-sentry": "^0.1.1-alpha.7", + "@backstage/theme": "^0.1.1-alpha.7", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -37,9 +37,9 @@ "react-use": "^14.2.0" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.6", - "@backstage/dev-utils": "^0.1.1-alpha.6", - "@backstage/test-utils": "^0.1.1-alpha.6", + "@backstage/cli": "^0.1.1-alpha.7", + "@backstage/dev-utils": "^0.1.1-alpha.7", + "@backstage/test-utils": "^0.1.1-alpha.7", "@testing-library/jest-dom": "^5.7.0", "@testing-library/react": "^9.3.2", "@testing-library/react-hooks": "^3.3.0", diff --git a/plugins/circleci/package.json b/plugins/circleci/package.json index d58ea6b406..c1570eaaad 100644 --- a/plugins/circleci/package.json +++ b/plugins/circleci/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-circleci", - "version": "0.1.1-alpha.6", + "version": "0.1.1-alpha.7", "main": "dist/index.esm.js", "main:src": "src/index.ts", "types": "src/index.ts", @@ -31,8 +31,8 @@ "postpack": "backstage-cli postpack" }, "dependencies": { - "@backstage/core": "^0.1.1-alpha.6", - "@backstage/theme": "^0.1.1-alpha.6", + "@backstage/core": "^0.1.1-alpha.7", + "@backstage/theme": "^0.1.1-alpha.7", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -47,8 +47,8 @@ "react-use": "^14.2.0" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.6", - "@backstage/dev-utils": "^0.1.1-alpha.6", + "@backstage/cli": "^0.1.1-alpha.7", + "@backstage/dev-utils": "^0.1.1-alpha.7", "@testing-library/jest-dom": "^5.7.0", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^10.2.4", diff --git a/plugins/explore/package.json b/plugins/explore/package.json index 400d212e69..15df6fae75 100644 --- a/plugins/explore/package.json +++ b/plugins/explore/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-explore", - "version": "0.1.1-alpha.6", + "version": "0.1.1-alpha.7", "main": "dist/index.esm.js", "main:src": "src/index.ts", "types": "src/index.ts", @@ -22,8 +22,8 @@ "start": "backstage-cli plugin:serve" }, "dependencies": { - "@backstage/core": "^0.1.1-alpha.6", - "@backstage/theme": "^0.1.1-alpha.6", + "@backstage/core": "^0.1.1-alpha.7", + "@backstage/theme": "^0.1.1-alpha.7", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -33,9 +33,9 @@ "react-use": "^14.2.0" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.6", - "@backstage/dev-utils": "^0.1.1-alpha.6", - "@backstage/test-utils": "^0.1.1-alpha.6", + "@backstage/cli": "^0.1.1-alpha.7", + "@backstage/dev-utils": "^0.1.1-alpha.7", + "@backstage/test-utils": "^0.1.1-alpha.7", "@testing-library/jest-dom": "^5.7.0", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^10.2.4", diff --git a/plugins/graphiql/package.json b/plugins/graphiql/package.json index d4141aa367..1132ced6da 100644 --- a/plugins/graphiql/package.json +++ b/plugins/graphiql/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-graphiql", "description": "Backstage plugin for browsing GraphQL APIs", - "version": "0.1.1-alpha.6", + "version": "0.1.1-alpha.7", "private": false, "publishConfig": { "access": "public", @@ -32,8 +32,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.1.1-alpha.6", - "@backstage/theme": "^0.1.1-alpha.6", + "@backstage/core": "^0.1.1-alpha.7", + "@backstage/theme": "^0.1.1-alpha.7", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -44,9 +44,9 @@ "react-use": "^14.2.0" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.6", - "@backstage/dev-utils": "^0.1.1-alpha.6", - "@backstage/test-utils": "^0.1.1-alpha.6", + "@backstage/cli": "^0.1.1-alpha.7", + "@backstage/dev-utils": "^0.1.1-alpha.7", + "@backstage/test-utils": "^0.1.1-alpha.7", "@testing-library/jest-dom": "^5.7.0", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^10.2.4", diff --git a/plugins/home-page/package.json b/plugins/home-page/package.json index 54144cefe3..cf4b077dfc 100644 --- a/plugins/home-page/package.json +++ b/plugins/home-page/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-home-page", - "version": "0.1.1-alpha.6", + "version": "0.1.1-alpha.7", "main": "dist/index.esm.js", "main:src": "src/index.ts", "types": "src/index.ts", @@ -22,8 +22,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.1.1-alpha.6", - "@backstage/theme": "^0.1.1-alpha.6", + "@backstage/core": "^0.1.1-alpha.7", + "@backstage/theme": "^0.1.1-alpha.7", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -32,8 +32,8 @@ "react-use": "^14.2.0" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.6", - "@backstage/dev-utils": "^0.1.1-alpha.6", + "@backstage/cli": "^0.1.1-alpha.7", + "@backstage/dev-utils": "^0.1.1-alpha.7", "@testing-library/jest-dom": "^5.7.0", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^10.2.4", diff --git a/plugins/identity-backend/package.json b/plugins/identity-backend/package.json index 30512ee658..922ff8412d 100644 --- a/plugins/identity-backend/package.json +++ b/plugins/identity-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-identity-backend", - "version": "0.1.1-alpha.6", + "version": "0.1.1-alpha.7", "main": "dist", "types": "src/index.ts", "license": "Apache-2.0", @@ -15,7 +15,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.1.1-alpha.6", + "@backstage/backend-common": "^0.1.1-alpha.7", "compression": "^1.7.4", "cors": "^2.8.5", "express": "^4.17.1", @@ -27,7 +27,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.6", + "@backstage/cli": "^0.1.1-alpha.7", "jest-fetch-mock": "^3.0.3", "tsc-watch": "^4.2.3" }, diff --git a/plugins/lighthouse/package.json b/plugins/lighthouse/package.json index fce604b609..eeb6bfa0bd 100644 --- a/plugins/lighthouse/package.json +++ b/plugins/lighthouse/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-lighthouse", - "version": "0.1.1-alpha.6", + "version": "0.1.1-alpha.7", "main": "dist/index.esm.js", "main:src": "src/index.ts", "types": "src/index.ts", @@ -22,8 +22,8 @@ "start": "backstage-cli plugin:serve" }, "dependencies": { - "@backstage/core": "^0.1.1-alpha.6", - "@backstage/theme": "^0.1.1-alpha.6", + "@backstage/core": "^0.1.1-alpha.7", + "@backstage/theme": "^0.1.1-alpha.7", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -34,9 +34,9 @@ "react-use": "^14.2.0" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.6", - "@backstage/dev-utils": "^0.1.1-alpha.6", - "@backstage/test-utils": "^0.1.1-alpha.6", + "@backstage/cli": "^0.1.1-alpha.7", + "@backstage/dev-utils": "^0.1.1-alpha.7", + "@backstage/test-utils": "^0.1.1-alpha.7", "@testing-library/jest-dom": "^5.7.0", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^10.2.4", diff --git a/plugins/register-component/package.json b/plugins/register-component/package.json index 3f14d2f4cb..f1b28e99d2 100644 --- a/plugins/register-component/package.json +++ b/plugins/register-component/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-register-component", - "version": "0.1.1-alpha.6", + "version": "0.1.1-alpha.7", "main": "dist/index.esm.js", "main:src": "src/index.ts", "types": "src/index.ts", @@ -22,10 +22,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.1.1-alpha.6", - "@backstage/catalog-model": "^0.1.1-alpha.6", - "@backstage/plugin-catalog": "^0.1.1-alpha.6", - "@backstage/theme": "^0.1.1-alpha.6", + "@backstage/catalog-model": "^0.1.1-alpha.7", + "@backstage/core": "^0.1.1-alpha.7", + "@backstage/plugin-catalog": "^0.1.1-alpha.7", + "@backstage/theme": "^0.1.1-alpha.7", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -37,8 +37,8 @@ "react-use": "^14.2.0" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.6", - "@backstage/dev-utils": "^0.1.1-alpha.6", + "@backstage/cli": "^0.1.1-alpha.7", + "@backstage/dev-utils": "^0.1.1-alpha.7", "@testing-library/jest-dom": "^5.7.0", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^10.2.4", diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index 244294cd42..2fa5462fcc 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend", - "version": "0.1.1-alpha.6", + "version": "0.1.1-alpha.7", "main": "dist", "types": "src/index.ts", "license": "Apache-2.0", @@ -14,7 +14,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.1.1-alpha.6", + "@backstage/backend-common": "^0.1.1-alpha.7", "compression": "^1.7.4", "cors": "^2.8.5", "dockerode": "^3.2.0", @@ -27,7 +27,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.6", + "@backstage/cli": "^0.1.1-alpha.7", "@types/fs-extra": "^9.0.1", "@types/supertest": "^2.0.8", "supertest": "^4.0.2" diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index b03fc39772..bc54e5f7c5 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder", - "version": "0.1.1-alpha.6", + "version": "0.1.1-alpha.7", "main": "dist/index.esm.js", "main:src": "src/index.ts", "types": "src/index.ts", @@ -22,8 +22,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.1.1-alpha.6", - "@backstage/theme": "^0.1.1-alpha.6", + "@backstage/core": "^0.1.1-alpha.7", + "@backstage/theme": "^0.1.1-alpha.7", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -33,8 +33,8 @@ "react-use": "^14.2.0" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.6", - "@backstage/dev-utils": "^0.1.1-alpha.6", + "@backstage/cli": "^0.1.1-alpha.7", + "@backstage/dev-utils": "^0.1.1-alpha.7", "@testing-library/jest-dom": "^5.7.0", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^10.2.4", diff --git a/plugins/sentry-backend/package.json b/plugins/sentry-backend/package.json index 3d4abb8790..c9d7a585e4 100644 --- a/plugins/sentry-backend/package.json +++ b/plugins/sentry-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-sentry-backend", - "version": "0.1.1-alpha.6", + "version": "0.1.1-alpha.7", "main": "dist", "types": "src/index.ts", "license": "Apache-2.0", @@ -15,7 +15,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.1.1-alpha.6", + "@backstage/backend-common": "^0.1.1-alpha.7", "axios": "^0.19.2", "compression": "^1.7.4", "cors": "^2.8.5", @@ -28,7 +28,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.6", + "@backstage/cli": "^0.1.1-alpha.7", "jest-fetch-mock": "^3.0.3", "tsc-watch": "^4.2.3" }, diff --git a/plugins/sentry/package.json b/plugins/sentry/package.json index f539afa886..cdf2025cb8 100644 --- a/plugins/sentry/package.json +++ b/plugins/sentry/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-sentry", - "version": "0.1.1-alpha.6", + "version": "0.1.1-alpha.7", "main": "dist/index.esm.js", "main:src": "src/index.ts", "types": "src/index.ts", @@ -22,8 +22,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.1.1-alpha.6", - "@backstage/theme": "^0.1.1-alpha.6", + "@backstage/core": "^0.1.1-alpha.7", + "@backstage/theme": "^0.1.1-alpha.7", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -34,8 +34,8 @@ "timeago.js": "^4.0.2" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.6", - "@backstage/dev-utils": "^0.1.1-alpha.6", + "@backstage/cli": "^0.1.1-alpha.7", + "@backstage/dev-utils": "^0.1.1-alpha.7", "@testing-library/jest-dom": "^5.7.0", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^10.2.4", diff --git a/plugins/tech-radar/package.json b/plugins/tech-radar/package.json index 5327ce7cfb..a5befefe2f 100644 --- a/plugins/tech-radar/package.json +++ b/plugins/tech-radar/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-tech-radar", - "version": "0.1.1-alpha.6", + "version": "0.1.1-alpha.7", "main": "dist/index.esm.js", "main:src": "src/index.ts", "types": "src/index.ts", @@ -22,9 +22,9 @@ "start": "backstage-cli plugin:serve" }, "dependencies": { - "@backstage/core": "^0.1.1-alpha.6", - "@backstage/test-utils-core": "^0.1.1-alpha.6", - "@backstage/theme": "^0.1.1-alpha.6", + "@backstage/core": "^0.1.1-alpha.7", + "@backstage/test-utils-core": "^0.1.1-alpha.7", + "@backstage/theme": "^0.1.1-alpha.7", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -37,8 +37,8 @@ "react-use": "^14.2.0" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.6", - "@backstage/dev-utils": "^0.1.1-alpha.6", + "@backstage/cli": "^0.1.1-alpha.7", + "@backstage/dev-utils": "^0.1.1-alpha.7", "@testing-library/jest-dom": "^5.7.0", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^10.2.4", diff --git a/plugins/welcome/package.json b/plugins/welcome/package.json index 6debcc9c80..fd6aa917b4 100644 --- a/plugins/welcome/package.json +++ b/plugins/welcome/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-welcome", - "version": "0.1.1-alpha.6", + "version": "0.1.1-alpha.7", "main": "dist/index.esm.js", "main:src": "src/index.ts", "types": "src/index.ts", @@ -22,8 +22,8 @@ "start": "backstage-cli plugin:serve" }, "dependencies": { - "@backstage/core": "^0.1.1-alpha.6", - "@backstage/theme": "^0.1.1-alpha.6", + "@backstage/core": "^0.1.1-alpha.7", + "@backstage/theme": "^0.1.1-alpha.7", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -33,8 +33,8 @@ "react-use": "^14.2.0" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.6", - "@backstage/dev-utils": "^0.1.1-alpha.6", + "@backstage/cli": "^0.1.1-alpha.7", + "@backstage/dev-utils": "^0.1.1-alpha.7", "@testing-library/jest-dom": "^5.7.0", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^10.2.4", From c3f0759c2d903e6a1ba0f99969f49d9da29578d1 Mon Sep 17 00:00:00 2001 From: Sebastian Qvarfordt Date: Tue, 9 Jun 2020 14:58:19 +0200 Subject: [PATCH 07/19] Merge pull request #1213 from hooloovooo/move-edit-metadata-button Moved edit button in catalog to actions menu --- .../components/CatalogPage/CatalogPage.tsx | 25 +++++++++++++ .../catalog/src/data/{utils.tsx => utils.ts} | 36 ++----------------- 2 files changed, 28 insertions(+), 33 deletions(-) rename plugins/catalog/src/data/{utils.tsx => utils.ts} (57%) diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx index 859449c715..3bd09af0bd 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx @@ -26,9 +26,11 @@ import { SupportButton, useApi, } from '@backstage/core'; +import { LocationSpec } from '@backstage/catalog-model'; import { rootRoute as scaffolderRootRoute } from '@backstage/plugin-scaffolder'; import { Button, makeStyles, Typography, Link } from '@material-ui/core'; import GitHub from '@material-ui/icons/GitHub'; +import Edit from '@material-ui/icons/Edit'; import React, { FC, useCallback, useState } from 'react'; import { Link as RouterLink } from 'react-router-dom'; import { useAsync } from 'react-use'; @@ -89,6 +91,29 @@ const CatalogPage: FC<{}> = () => { hidden: location ? location?.type !== 'github' : true, }; }, + (rowData: Component) => { + const createEditLink = (location: LocationSpec): string => { + switch (location.type) { + case 'github': + return location.target.replace('/blob/', '/edit/'); + default: + return location.target; + } + }; + + const location = findLocationForEntityMeta(rowData.metadata); + + return { + icon: Edit, + tooltip: 'Edit', + iconProps: { size: 'small' }, + onClick: () => { + if (!location) return; + window.open(createEditLink(location), '_blank'); + }, + hidden: location ? location?.type !== 'github' : true, + }; + }, ]; // TODO: replace me with the proper tabs implemntation diff --git a/plugins/catalog/src/data/utils.tsx b/plugins/catalog/src/data/utils.ts similarity index 57% rename from plugins/catalog/src/data/utils.tsx rename to plugins/catalog/src/data/utils.ts index 3e8e0458d6..b731268c41 100644 --- a/plugins/catalog/src/data/utils.tsx +++ b/plugins/catalog/src/data/utils.ts @@ -19,44 +19,14 @@ import { LOCATION_ANNOTATION, EntityMeta, } from '@backstage/catalog-model'; -import IconButton from '@material-ui/core/IconButton'; -import { styled } from '@material-ui/core/styles'; -import Edit from '@material-ui/icons/Edit'; -import React from 'react'; import { Component } from './component'; -const DescriptionWrapper = styled('span')({ - display: 'flex', - alignItems: 'center', -}); - -const createEditLink = (location: LocationSpec): string => { - switch (location.type) { - case 'github': - return location.target.replace('/blob/', '/edit/'); - default: - return location.target; - } -}; - export function entityToComponent(envelope: Entity): Component { - const location = findLocationForEntityMeta(envelope.metadata); return { - name: envelope.metadata?.name ?? '', - kind: envelope.kind ?? 'unknown', + name: envelope.metadata.name, + kind: envelope.kind, metadata: envelope.metadata, - description: ( - - {envelope.metadata?.annotations?.description ?? 'placeholder'} - {location?.target ? ( - - - - - - ) : null} - - ), + description: envelope.metadata.annotations?.description ?? 'placeholder', }; } From 1550535d45be16ef36a24b0c35a8dc1a57960506 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 9 Jun 2020 22:20:18 +0200 Subject: [PATCH 08/19] Merge pull request #1217 from spotify/rugvip/authdocs docs/auth: add overview and implementation docs --- docs/FAQ.md | 283 ++++++++++++------ .../adr002-default-catalog-file-format.md | 145 ++++----- .../adr003-avoid-default-exports.md | 47 ++- .../adr004-module-export-structure.md | 44 ++- .../adr005-catalog-core-entities.md | 51 +++- docs/architecture-terminology.md | 15 +- docs/auth/add-auth-provider.md | 71 +++-- docs/auth/glossary.md | 26 ++ docs/auth/oauth-popup-flow.svg | 56 ++++ docs/auth/oauth.md | 151 ++++++++++ docs/auth/overview.md | 44 +++ docs/create-an-app.md | 19 +- docs/design.md | 114 +++++-- docs/generate-uml.sh | 20 ++ docs/getting-started/Plugin development.md | 18 +- docs/getting-started/README.md | 4 +- docs/getting-started/app-custom-theme.md | 44 ++- .../contributing-to-storybook.md | 13 +- docs/getting-started/create-a-plugin.md | 21 +- .../development-environment.md | 30 +- docs/getting-started/structure-of-a-plugin.md | 33 +- docs/getting-started/utility-apis.md | 138 +++++---- docs/prettier.config.js | 21 ++ docs/publishing.md | 17 +- docs/reference/createPlugin-feature-flags.md | 7 +- docs/reference/createPlugin-router.md | 6 +- 26 files changed, 1074 insertions(+), 364 deletions(-) create mode 100644 docs/auth/glossary.md create mode 100644 docs/auth/oauth-popup-flow.svg create mode 100644 docs/auth/oauth.md create mode 100644 docs/auth/overview.md create mode 100755 docs/generate-uml.sh create mode 100644 docs/prettier.config.js diff --git a/docs/FAQ.md b/docs/FAQ.md index 100e3e19de..5fb131528a 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -4,35 +4,67 @@ ### Can we call Backstage something different? So that it fits our company better? -Yes, Backstage is just a platform for building your own developer portal. We happen to call our internal version Backstage, as well, as a reference to our music roots. You can call your version whatever suits your team, company, or brand. +Yes, Backstage is just a platform for building your own developer portal. We +happen to call our internal version Backstage, as well, as a reference to our +music roots. You can call your version whatever suits your team, company, or +brand. ### Is Backstage a monitoring platform? -No, but it can be! Backstage is designed to be a developer portal for all your infrastructure tooling, services, and documentation. So, it's not a monitoring platform — but that doesn't mean you can't integrate a monitoring tool into Backstage by writing [a plugin](https://github.com/spotify/faq#what-is-a-plugin-in-backstage). +No, but it can be! Backstage is designed to be a developer portal for all your +infrastructure tooling, services, and documentation. So, it's not a monitoring +platform — but that doesn't mean you can't integrate a monitoring tool into +Backstage by writing +[a plugin](https://github.com/spotify/faq#what-is-a-plugin-in-backstage). ### How is Backstage licensed? -Backstage was released as free and open software by Spotify and is licensed under [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0). +Backstage was released as free and open software by Spotify and is licensed +under [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0). ### Why did we open source Backstage? -We hope to see Backstage become the infrastructure standard everywhere. When we saw how much Backstage improved developer experience and productivity internally, we wanted to share those gains. After all, if Backstage can create order in an engineering environment as open and diverse as ours, then we're pretty sure it can create order (and boost productivity) anywhere. To learn more, read our blog post, "[What the heck is Backstage anyway?](https://backstage.io/blog/2020/03/18/what-is-backstage)" +We hope to see Backstage become the infrastructure standard everywhere. When we +saw how much Backstage improved developer experience and productivity +internally, we wanted to share those gains. After all, if Backstage can create +order in an engineering environment as open and diverse as ours, then we're +pretty sure it can create order (and boost productivity) anywhere. To learn +more, read our blog post, +"[What the heck is Backstage anyway?](https://backstage.io/blog/2020/03/18/what-is-backstage)" ### Will Spotify's internal plugins be open sourced, too? -Yes, we've already started releasing open source versions of some of the plugins we use here, and we'll continue to do so. [Plugins](https://github.com/spotify/faq#what-is-a-plugin-in-backstage) are the building blocks of functionality in Backstage. We have over 120 plugins inside Spotify — many of those are specialized for our use, so will remain internal and proprietary to us. But we estimate that about a third of our existing plugins make good open source candidates. (And we'll probably end up writing some brand new ones, too.) -​ +Yes, we've already started releasing open source versions of some of the plugins +we use here, and we'll continue to do so. +[Plugins](https://github.com/spotify/faq#what-is-a-plugin-in-backstage) are the +building blocks of functionality in Backstage. We have over 120 plugins inside +Spotify — many of those are specialized for our use, so will remain internal and +proprietary to us. But we estimate that about a third of our existing plugins +make good open source candidates. (And we'll probably end up writing some brand +new ones, too.) ​ + ### What's the roadmap for Backstage? -​ -We envision three phases, which you can learn about in [our project roadmap](https://github.com/spotify/backstage#project-roadmap). Even though the open source version of Backstage is relatively new compared to our internal version, we have already begun work on various aspects of all three phases. Looking at the [milestones for active issues](https://github.com/spotify/backstage/milestones) will also give you a sense of our progress. + +​ We envision three phases, which you can learn about in +[our project roadmap](https://github.com/spotify/backstage#project-roadmap). +Even though the open source version of Backstage is relatively new compared to +our internal version, we have already begun work on various aspects of all three +phases. Looking at the +[milestones for active issues](https://github.com/spotify/backstage/milestones) +will also give you a sense of our progress. ### My company doesn't have thousands of developers or services. Is Backstage overkill? -Not at all! A core reason to adopt Backstage is to standardize how software is built at your company. It's easier to decide on those standards as a small company, and grows in importance as the company grows. Backstage sets a foundation, and an early investment in your infrastructure becomes even more valuable as you grow. +Not at all! A core reason to adopt Backstage is to standardize how software is +built at your company. It's easier to decide on those standards as a small +company, and grows in importance as the company grows. Backstage sets a +foundation, and an early investment in your infrastructure becomes even more +valuable as you grow. ### Our company has a strong design language system/brand that we want to incorporate. Does Backstage support this? -Yes! The Backstage UI is built using Material-UI. With the theming capabilities of Material-UI, you are able to adapt the interface to your brand guidelines. +Yes! The Backstage UI is built using Material-UI. With the theming capabilities +of Material-UI, you are able to adapt the interface to your brand guidelines. ## Technical FAQ: @@ -40,96 +72,155 @@ Yes! The Backstage UI is built using Material-UI. With the theming capabilities The short answer is that's what we've been using in Backstage internally. -The original decision was based on Google's Material Design being a thorough, well thought out and complete design system, with many mature and powerful libraries implemented in both the system itself and auxiliary components that we knew that we would like to use. +The original decision was based on Google's Material Design being a thorough, +well thought out and complete design system, with many mature and powerful +libraries implemented in both the system itself and auxiliary components that we +knew that we would like to use. + +It strikes a good balance between power, customizability, and ease of use. A +core focus of Backstage is to make plugin developers productive with as few +hurdles as possible. Material-UI lets plugin makers get going easily with both +well-known tech and a large flora of components. ​ -It strikes a good balance between power, customizability, and ease of use. A core focus of Backstage is to make plugin developers productive with as few hurdles as possible. Material-UI lets plugin makers get going easily with both well-known tech and a large flora of components. -​ ### What technology does Backstage use? -​ -The code base is a large-scale React application that uses TypeScript. For [Phase 2](https://github.com/spotify/backstage#project-roadmap), we plan to use Node.js and GraphQL. -​ -### What is the end-to-end user flow? The happy path story. -​ -There are three main user profiles for Backstage: the integrator, the contributor, and the software engineer. -​ -The **integrator** hosts the Backstage app and configures which plugins are available to use in the app. -​ -The **contributor** adds functionality to the app by writing plugins. -​ -The **software engineer** uses the app's functionality and interacts with its plugins. -​ -### What is a "plugin" in Backstage? -​ -Plugins are what provide the feature functionality in Backstage. They are used to integrate different systems into Backstage's frontend, so that the developer gets a consistent UX, no matter what tool or service is being accessed on the other side. -​ -Each plugin is treated as a self-contained web app and can include almost any type of content. Plugins all use a common set of platform APIs and reusable UI components. Plugins can fetch data either from the backend or an API exposed through the proxy. -​ -Learn more about [the different components](https://github.com/spotify/backstage#overview) that make up Backstage. -​ -### Do I have to write plugins in TypeScript? -​ -No, you can use JavaScript if you prefer. -​ -We want to keep the Backstage core APIs in TypeScript, but aren't forcing it on individual plugins. -​ -### How do I find out if a plugin already exists? -​ -Before you write a plugin, [search the plugin issues](https://github.com/spotify/backstage/issues?q=is%3Aissue+label%3Aplugin+) to see if it already exists or is in the works. If no one's thought of it yet, great! Open a new issue as [a plugin suggestion](https://github.com/spotify/backstage/issues/new/choose) and describe what your plugin will do. This will help coordinate our contributors' efforts and avoid duplicating existing functionality. -​ -In the future, we will create [a plugin gallery](https://github.com/spotify/backstage/issues/260) where people can browse and search for all available plugins. -​ -### Which plugin is used the most at Spotify? -​ -By far, our most-used plugin is our TechDocs plugin, which we use for creating technical documentation. Our philosophy at Spotify is to treat "docs like code", where you write documentation using the same workflow as you write your code. This makes it easier to create, find, and update documentation. We hope to release [the open source version](https://github.com/spotify/backstage/issues/687) in the future. (See also: "[Will Spotify's internal plugins be open sourced, too?](https://github.com/spotify/faq#will-spotifys-internal-plugins-be-open-sourced-too)" above) -​ -### Are you planning to have plugins baked into the repo? Or should they be developed in separate repos? -​ -Contributors can add open source plugins to the plugins directory in [this monorepo](https://github.com/spotify/backstage). Integrators can then configure which open source plugins are available to use in their instance of the app. Open source plugins are downloaded as npm packages published in the open source repository. -​ -While we encourage using the open source model, we know there are cases where contributors might want to experiment internally or keep their plugins closed source. Contributors writing closed source plugins should develop them in the plugins directory in their own Backstage repository. Integrators also configure closed source plugins locally from the monorepo. -​ -### Any plans for integrating with other repository managers, such as GitLab or Bitbucket? -​ -We chose GitHub because it is the tool that we are most familiar with, so that will naturally lead to integrations for GitHub being developed at an early stage. -​ -Hosting this project on GitHub does not exclude integrations with alternatives, such as GitLab or Bitbucket. We believe that in time there will be plugins that will provide functionality for these tools as well. Hopefully, contributed by the community! -​ -Also note, implementations of Backstage can be hosted wherever you feel suits your needs best. -​ -### Who maintains Backstage? -​ -Spotify will maintain the open source core, but we envision different parts of the project being maintained by various companies and contributors. We also envision a large, diverse ecosystem of open source plugins, which would be maintained by their original authors/contributors or by the community. -​ -When it comes to [deployment](https://github.com/spotify/backstage/blob/master/DEPLOYMENT.md), the system integrator (typically, the infrastructure team in your organization) maintains Backstage in your own environment. -​ -### Does Spotify provide a managed version of Backstage? -​ -No, this is not a service offering. We build the piece of software, and someone in your infrastructure team is responsible for [deploying](https://github.com/spotify/backstage/blob/master/DEPLOYMENT.md) and maintaining it. -​ -### How secure is Backstage? -​ -We take security seriously. When it comes to packages and code we scan our repositories periodically and update our packages to the latest versions. When it comes to deployment of Backstage within an organisation it depends on the deployment and security setup in your organisation. Reach out to us on [Discord](https://discord.gg/MUpMjP2) if you have specific queries. -Please report sensitive security issues via Spotify's [bug-bounty program](https://hackerone.com/spotify) rather than GitHub. -​ +​ The code base is a large-scale React application that uses TypeScript. For +[Phase 2](https://github.com/spotify/backstage#project-roadmap), we plan to use +Node.js and GraphQL. ​ + +### What is the end-to-end user flow? The happy path story. + +​ There are three main user profiles for Backstage: the integrator, the +contributor, and the software engineer. ​ The **integrator** hosts the Backstage +app and configures which plugins are available to use in the app. ​ The +**contributor** adds functionality to the app by writing plugins. ​ The +**software engineer** uses the app's functionality and interacts with its +plugins. ​ + +### What is a "plugin" in Backstage? + +​ Plugins are what provide the feature functionality in Backstage. They are used +to integrate different systems into Backstage's frontend, so that the developer +gets a consistent UX, no matter what tool or service is being accessed on the +other side. ​ Each plugin is treated as a self-contained web app and can include +almost any type of content. Plugins all use a common set of platform APIs and +reusable UI components. Plugins can fetch data either from the backend or an API +exposed through the proxy. ​ Learn more about +[the different components](https://github.com/spotify/backstage#overview) that +make up Backstage. ​ + +### Do I have to write plugins in TypeScript? + +​ No, you can use JavaScript if you prefer. ​ We want to keep the Backstage core +APIs in TypeScript, but aren't forcing it on individual plugins. ​ + +### How do I find out if a plugin already exists? + +​ Before you write a plugin, +[search the plugin issues](https://github.com/spotify/backstage/issues?q=is%3Aissue+label%3Aplugin+) +to see if it already exists or is in the works. If no one's thought of it yet, +great! Open a new issue as +[a plugin suggestion](https://github.com/spotify/backstage/issues/new/choose) +and describe what your plugin will do. This will help coordinate our +contributors' efforts and avoid duplicating existing functionality. ​ In the +future, we will create +[a plugin gallery](https://github.com/spotify/backstage/issues/260) where people +can browse and search for all available plugins. ​ + +### Which plugin is used the most at Spotify? + +​ By far, our most-used plugin is our TechDocs plugin, which we use for creating +technical documentation. Our philosophy at Spotify is to treat "docs like code", +where you write documentation using the same workflow as you write your code. +This makes it easier to create, find, and update documentation. We hope to +release +[the open source version](https://github.com/spotify/backstage/issues/687) in +the future. (See also: +"[Will Spotify's internal plugins be open sourced, too?](https://github.com/spotify/faq#will-spotifys-internal-plugins-be-open-sourced-too)" +above) ​ + +### Are you planning to have plugins baked into the repo? Or should they be developed in separate repos? + +​ Contributors can add open source plugins to the plugins directory in +[this monorepo](https://github.com/spotify/backstage). Integrators can then +configure which open source plugins are available to use in their instance of +the app. Open source plugins are downloaded as npm packages published in the +open source repository. ​ While we encourage using the open source model, we +know there are cases where contributors might want to experiment internally or +keep their plugins closed source. Contributors writing closed source plugins +should develop them in the plugins directory in their own Backstage repository. +Integrators also configure closed source plugins locally from the monorepo. ​ + +### Any plans for integrating with other repository managers, such as GitLab or Bitbucket? + +​ We chose GitHub because it is the tool that we are most familiar with, so that +will naturally lead to integrations for GitHub being developed at an early +stage. ​ Hosting this project on GitHub does not exclude integrations with +alternatives, such as GitLab or Bitbucket. We believe that in time there will be +plugins that will provide functionality for these tools as well. Hopefully, +contributed by the community! ​ Also note, implementations of Backstage can be +hosted wherever you feel suits your needs best. ​ + +### Who maintains Backstage? + +​ Spotify will maintain the open source core, but we envision different parts of +the project being maintained by various companies and contributors. We also +envision a large, diverse ecosystem of open source plugins, which would be +maintained by their original authors/contributors or by the community. ​ When it +comes to +[deployment](https://github.com/spotify/backstage/blob/master/DEPLOYMENT.md), +the system integrator (typically, the infrastructure team in your organization) +maintains Backstage in your own environment. ​ + +### Does Spotify provide a managed version of Backstage? + +​ No, this is not a service offering. We build the piece of software, and +someone in your infrastructure team is responsible for +[deploying](https://github.com/spotify/backstage/blob/master/DEPLOYMENT.md) and +maintaining it. ​ + +### How secure is Backstage? + +​ We take security seriously. When it comes to packages and code we scan our +repositories periodically and update our packages to the latest versions. When +it comes to deployment of Backstage within an organisation it depends on the +deployment and security setup in your organisation. Reach out to us on +[Discord](https://discord.gg/MUpMjP2) if you have specific queries. + +Please report sensitive security issues via Spotify's +[bug-bounty program](https://hackerone.com/spotify) rather than GitHub. ​ + ### Does Backstage collect any information that is shared with Spotify? -​ -No. Backstage does not collect any telemetry from any third party using the platform. Spotify, and the open source community, does have access to [GitHub Insights](https://github.com/features/insights), which contains information such as contributors, commits, traffic, and dependencies. -​ -Backstage is an open platform, but you are in control of your own data. You control who has access to any data you provide to your version of Backstage and who that data is shared with. -​ + +​ No. Backstage does not collect any telemetry from any third party using the +platform. Spotify, and the open source community, does have access to +[GitHub Insights](https://github.com/features/insights), which contains +information such as contributors, commits, traffic, and dependencies. ​ +Backstage is an open platform, but you are in control of your own data. You +control who has access to any data you provide to your version of Backstage and +who that data is shared with. ​ + ### Can Backstage be used to build something other than a developer portal? -​ -Yes. The core frontend framework could be used for building any large-scale web application where (1) multiple teams are building separate parts of the app, and (2) you want the overall experience to be consistent. -​ -That being said, in [Phase 2](https://github.com/spotify/backstage#project-roadmap) of the project we will add features that are needed for developer portals and systems for managing software ecosystems. Our ambition will be to keep Backstage modular. -​ + +​ Yes. The core frontend framework could be used for building any large-scale +web application where (1) multiple teams are building separate parts of the app, +and (2) you want the overall experience to be consistent. ​ That being said, in +[Phase 2](https://github.com/spotify/backstage#project-roadmap) of the project +we will add features that are needed for developer portals and systems for +managing software ecosystems. Our ambition will be to keep Backstage modular. ​ + ### How can I get involved? -​ -Jump right in! Come help us fix some of the [early bugs and first issues](https://github.com/spotify/backstage/labels/good%20first%20issue) or reach [a new milestone](https://github.com/spotify/backstage/milestones). Or write an open source plugin for Backstage, like this [Lighthouse plugin](https://github.com/spotify/backstage/tree/master/plugins/lighthouse). -​ -See all the ways you can [contribute here](https://github.com/spotify/backstage/blob/master/CONTRIBUTING.md). We'd love to have you as part of the community. -​ + +​ Jump right in! Come help us fix some of the +[early bugs and first issues](https://github.com/spotify/backstage/labels/good%20first%20issue) +or reach [a new milestone](https://github.com/spotify/backstage/milestones). Or +write an open source plugin for Backstage, like this +[Lighthouse plugin](https://github.com/spotify/backstage/tree/master/plugins/lighthouse). +​ See all the ways you can +[contribute here](https://github.com/spotify/backstage/blob/master/CONTRIBUTING.md). +We'd love to have you as part of the community. ​ + ### Can I join the Backstage team? -​ -If you're interested in being part of the Backstage team, reach out to [fossopportunities@spotify.com](mailto:fossopportunities@spotify.com) + +​ If you're interested in being part of the Backstage team, reach out to +[fossopportunities@spotify.com](mailto:fossopportunities@spotify.com) diff --git a/docs/architecture-decisions/adr002-default-catalog-file-format.md b/docs/architecture-decisions/adr002-default-catalog-file-format.md index 23db32cc9c..6cf649aedf 100644 --- a/docs/architecture-decisions/adr002-default-catalog-file-format.md +++ b/docs/architecture-decisions/adr002-default-catalog-file-format.md @@ -6,22 +6,22 @@ ## Background -Backstage comes with a software catalog functionality, that you can use to -track all your software components and more. It can be powered by data from -various sources, and one of them that is included with the package, is a -custom database backed catalog. It has the ability to keep itself updated -automatically based on the contents of little descriptor files in your -version control system of choice. Developers create these files and maintain -them side by side with their code, and the catalog system reacts accordingly. +Backstage comes with a software catalog functionality, that you can use to track +all your software components and more. It can be powered by data from various +sources, and one of them that is included with the package, is a custom database +backed catalog. It has the ability to keep itself updated automatically based on +the contents of little descriptor files in your version control system of +choice. Developers create these files and maintain them side by side with their +code, and the catalog system reacts accordingly. This ADR describes the default format of these descriptor files. ### Inspiration -Internally at Spotify, a home grown software catalog system is used heavily -and forms a core part of Backstage and other important pieces of the -infrastructure. The user experience, learnings and certain pieces of metadata -from that catalog are being carried over to the open source effort. +Internally at Spotify, a home grown software catalog system is used heavily and +forms a core part of Backstage and other important pieces of the infrastructure. +The user experience, learnings and certain pieces of metadata from that catalog +are being carried over to the open source effort. The file format described herein, also draws heavy inspiration from the [kubernetes object format](https://kubernetes.io/docs/concepts/overview/working-with-objects/kubernetes-objects/). @@ -35,9 +35,9 @@ inside Backstage, or by push events from a CI/CD pipelines, or by webhook triggers from the version control system, etc. Each file describes one or more entities in accordance with the -[Backstage System Model](https://github.com/spotify/backstage/issues/390). -All of these entities have a common stucture and nomenclature, and they are -stored in the software catalog from which they then can be queried. +[Backstage System Model](https://github.com/spotify/backstage/issues/390). All +of these entities have a common stucture and nomenclature, and they are stored +in the software catalog from which they then can be queried. Entities have distinct names, and they may reference each other by those names. @@ -70,9 +70,9 @@ spec: ``` The root fields `apiVersion`, `kind`, `metadata`, and `spec` are part of the -_envelope_, defining the overall structure of all kinds of entity. Likewise, -the `name`, `namespace`, `labels`, and `annotations` metadata fields are of -special significance and have reserved purposes and distinct shapes. +_envelope_, defining the overall structure of all kinds of entity. Likewise, the +`name`, `namespace`, `labels`, and `annotations` metadata fields are of special +significance and have reserved purposes and distinct shapes. See below for details about these fields. @@ -88,30 +88,31 @@ first versions of the catalog will focus on the `Component` kind. The `apiVersion`is the version of specification format for that particular entity that this file is written against. The version is used for being able to -evolve the format, and the tuple of `apiVersion` and `kind` should be enough -for a parser to know how to interpret the contents of the rest of the document. +evolve the format, and the tuple of `apiVersion` and `kind` should be enough for +a parser to know how to interpret the contents of the rest of the document. Backstage specific entities have an `apiVersion` that is prefixed with -`backstage.io/`, to distinguish them from other types of object that share -the same type of structure. This may be relevant when co-hosting these +`backstage.io/`, to distinguish them from other types of object that share the +same type of structure. This may be relevant when co-hosting these specifications with e.g. kubernetes object manifests. -Early versions of the catalog will be using beta versions, e.g. `backstage.io/v1beta1`, -to signal that the format may still change. After that, we will be using -`backstage.io/v1` and up. +Early versions of the catalog will be using beta versions, e.g. +`backstage.io/v1beta1`, to signal that the format may still change. After that, +we will be using `backstage.io/v1` and up. ### `metadata` -A structure that contains metadata about the entity, i.e. things that aren't directly -part of the entity specification itself. See below for more details about this structure. +A structure that contains metadata about the entity, i.e. things that aren't +directly part of the entity specification itself. See below for more details +about this structure. ### `spec` The actual specification data that describes the entity. -The precise structure of the `spec` depends on the `apiVersion` and `kind` combination, -and some kinds may not even have a `spec` at all. See further down in this document for -the specification structure of specific kinds. +The precise structure of the `spec` depends on the `apiVersion` and `kind` +combination, and some kinds may not even have a `spec` at all. See further down +in this document for the specification structure of specific kinds. ## Metadata @@ -119,27 +120,31 @@ The `metadata` root field has the following nested structure. ### `name` -The name of the entity. This name is both meant for human eyes to recognize the entity, -and for machines and other components to reference the entity (e.g. in URLs or from -other entity specification files). +The name of the entity. This name is both meant for human eyes to recognize the +entity, and for machines and other components to reference the entity (e.g. in +URLs or from other entity specification files). -Names must be unique per kind, within a given namespace (if specified), at any point in -time. Names may be reused at a later time, after an entity is deleted from the registry. +Names must be unique per kind, within a given namespace (if specified), at any +point in time. Names may be reused at a later time, after an entity is deleted +from the registry. -Names are required to follow a certain format. Entities that do not follow those rules -will not be accepted for registration in the catalog. The ruleset is configurable to fit -your organization's needs, but the default behavior is as follows. +Names are required to follow a certain format. Entities that do not follow those +rules will not be accepted for registration in the catalog. The ruleset is +configurable to fit your organization's needs, but the default behavior is as +follows. - Strings of length at least 1, and at most 63 -- Must consist of sequences of `[a-z0-9A-Z]` possibly separated by one of `[-_.]` +- Must consist of sequences of `[a-z0-9A-Z]` possibly separated by one of + `[-_.]` Example: `visits-tracking-service`, `CircleciBuildsDs_avro_gcs` -In addition to this, names are passed through a normalization function and then compared -to the same normalized form of other entity names and made sure to not collide. This rule -of uniqueness exists to avoid situations where e.g. both `my-component` and `MyComponent` -are registered side by side, which leads to confusion and risk. The normalization function -is also configurable, but the default behavior is as follows. +In addition to this, names are passed through a normalization function and then +compared to the same normalized form of other entity names and made sure to not +collide. This rule of uniqueness exists to avoid situations where e.g. both +`my-component` and `MyComponent` are registered side by side, which leads to +confusion and risk. The normalization function is also configurable, but the +default behavior is as follows. - Strip out all characters outside of the set `[a-zA-Z0-9]` - Convert to lowercase @@ -148,55 +153,59 @@ Example: `CircleciBuildsDs_avro_gcs` -> `circlecibuildsdsavrogcs` ### `namespace` -The `name` of a namespace that the entity belongs to. This field is optional, and currently -has no special semantics apart from bounding the name uniqueness constraint if specified. -It is reserved for future use and may get broader semantic implication. +The `name` of a namespace that the entity belongs to. This field is optional, +and currently has no special semantics apart from bounding the name uniqueness +constraint if specified. It is reserved for future use and may get broader +semantic implication. Namespaces may also be part of the catalog, and are `v1` / `Namespace` entities, i.e. not Backstage specific but the same as in Kubernetes. ### `description` -A human readable description of the entity, to be shown in Backstage. Should be kept short -and informative, suitable to give an overview of the entity's purpose at a glance. More -detailed explanations and documentation should be placed elsewhere. +A human readable description of the entity, to be shown in Backstage. Should be +kept short and informative, suitable to give an overview of the entity's purpose +at a glance. More detailed explanations and documentation should be placed +elsewhere. ### `labels` -Labels are optional key/value pairs of that are attached to the entity, and their use is -identical to [kubernetes object labels](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/). +Labels are optional key/value pairs of that are attached to the entity, and +their use is identical to +[kubernetes object labels](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/). -Their main purpose is for references to other entities, and for information that is -in one way or another classifying for the current entity. They are often used as values -in queries or filters. +Their main purpose is for references to other entities, and for information that +is in one way or another classifying for the current entity. They are often used +as values in queries or filters. Both the key and the value are strings, subject to the following restrictions. -Keys have an optional prefix followed by a slash, and then the name part which is required. -The prefix must be a valid lowercase domain name, at most 253 characters in total. The name -part must be sequences of `[a-zA-Z0-9]` separated by any of `[-_.]`, at most 63 characters -in total. +Keys have an optional prefix followed by a slash, and then the name part which +is required. The prefix must be a valid lowercase domain name, at most 253 +characters in total. The name part must be sequences of `[a-zA-Z0-9]` separated +by any of `[-_.]`, at most 63 characters in total. -The `backstage.io/` prefix is reserved for use by Backstage core components. Some keys such as -`system` also have predefined semantics. +The `backstage.io/` prefix is reserved for use by Backstage core components. +Some keys such as `system` also have predefined semantics. Values are strings that follow the same restrictions as `name` above. ### `annotations` An object with arbitrary non-identifying metadata attached to the entity, -identical in use to [kubernetes object annotations](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/). +identical in use to +[kubernetes object annotations](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/). -Their purpose is mainly, but not limited, to reference into external systems. This could -for example be a reference to the git ref the entity was ingested from, to monitoring -and logging systems, to pagerduty schedules, etc. +Their purpose is mainly, but not limited, to reference into external systems. +This could for example be a reference to the git ref the entity was ingested +from, to monitoring and logging systems, to pagerduty schedules, etc. Both the key and the value are strings, subject to the following restrictions. -Keys have an optional prefix followed by a slash, and then the name part which is required. -The prefix must be a valid lowercase domain name, at most 253 characters in total. The name -part must be sequences of `[a-zA-Z0-9]` separated by any of `[-_.]`, at most 63 characters -in total. +Keys have an optional prefix followed by a slash, and then the name part which +is required. The prefix must be a valid lowercase domain name, at most 253 +characters in total. The name part must be sequences of `[a-zA-Z0-9]` separated +by any of `[-_.]`, at most 63 characters in total. The `backstage.io/` prefix is reserved for use by Backstage core components. diff --git a/docs/architecture-decisions/adr003-avoid-default-exports.md b/docs/architecture-decisions/adr003-avoid-default-exports.md index 20a402e9c2..7becebaa4b 100644 --- a/docs/architecture-decisions/adr003-avoid-default-exports.md +++ b/docs/architecture-decisions/adr003-avoid-default-exports.md @@ -6,30 +6,50 @@ ## Context -When CommonJS was the primary authoring format, the best practice was to export only one thing from a module using the `module.exports = ...` format. This aligned with the [UNIX philosophy](https://en.wikipedia.org/wiki/Unix_philosophy) of "Do one thing well". The module would be consumed (`const localName = require('the-module');`) without having to know the internal structure. +When CommonJS was the primary authoring format, the best practice was to export +only one thing from a module using the `module.exports = ...` format. This +aligned with the +[UNIX philosophy](https://en.wikipedia.org/wiki/Unix_philosophy) of "Do one +thing well". The module would be consumed +(`const localName = require('the-module');`) without having to know the internal +structure. -Now, ESModules are the primary authoring format. They have numerous benefits, such as compile-time verification of exports, and standards-defined semantics. They have a similar mechanism known as "default exports", which allows for a consumer to `import localName from 'the-module';`. This is implicitly the same as `import { default as localName } from 'the-module';`. +Now, ESModules are the primary authoring format. They have numerous benefits, +such as compile-time verification of exports, and standards-defined semantics. +They have a similar mechanism known as "default exports", which allows for a +consumer to `import localName from 'the-module';`. This is implicitly the same +as `import { default as localName } from 'the-module';`. -However, there are numerous reasons to avoid default exports, as documented by others before: +However, there are numerous reasons to avoid default exports, as documented by +others before: - https://humanwhocodes.com/blog/2019/01/stop-using-default-exports-javascript-module/ A summary: -- They add indirection by encouraging a developer to create local names for modules, increasing cognitive load and slowing down code comprehension: `import TheListThing from 'not-a-list-thing';`. -- They thwart tools, such as IDEs, that can automatically rename and refactor code. -- They promote typos and mistakes, as the imported member is completely up to the consuming developer to define. -- They are ugly in CommonJS interop, as the default property must be manually specified by the consumer. This is often hidden by Babel's module interop. -- They break re-exports due to name conflicts, forcing the developer to manually name each. +- They add indirection by encouraging a developer to create local names for + modules, increasing cognitive load and slowing down code comprehension: + `import TheListThing from 'not-a-list-thing';`. +- They thwart tools, such as IDEs, that can automatically rename and refactor + code. +- They promote typos and mistakes, as the imported member is completely up to + the consuming developer to define. +- They are ugly in CommonJS interop, as the default property must be manually + specified by the consumer. This is often hidden by Babel's module interop. +- They break re-exports due to name conflicts, forcing the developer to manually + name each. -Using named exports helps prevent needing to rename symbols, which has myriad benefts. A few are: +Using named exports helps prevent needing to rename symbols, which has myriad +benefts. A few are: - IDE tools like "Find All References" and "Go To Definition" function - Manual codebase searching ("grep", etc) is easier with a unique symbol ## Decision -We will stop using default exports except when absolutely necessary (such as [`React.lazy`](https://reactjs.org/docs/code-splitting.html#reactlazy) modules). A workaround exists for those that would prefer to never use `default`: +We will stop using default exports except when absolutely necessary (such as +[`React.lazy`](https://reactjs.org/docs/code-splitting.html#reactlazy) modules). +A workaround exists for those that would prefer to never use `default`: ```ts const Component = React.lazy(() => @@ -39,6 +59,9 @@ const Component = React.lazy(() => ## Consequences -We will actively work to remove them from our codebases, being as explicit as possible. Have a connected component? `export const ConnectedComponent = connect(Component)`. +We will actively work to remove them from our codebases, being as explicit as +possible. Have a connected component? +`export const ConnectedComponent = connect(Component)`. -We will add tools, such as lint rules, to help migrate away from default exports. +We will add tools, such as lint rules, to help migrate away from default +exports. diff --git a/docs/architecture-decisions/adr004-module-export-structure.md b/docs/architecture-decisions/adr004-module-export-structure.md index 56c675f069..54132773c9 100644 --- a/docs/architecture-decisions/adr004-module-export-structure.md +++ b/docs/architecture-decisions/adr004-module-export-structure.md @@ -6,7 +6,8 @@ ## Context -With a growing number of exports of packages like `@backstage/core`, it is becoming more and more difficult to answer questions such as +With a growing number of exports of packages like `@backstage/core`, it is +becoming more and more difficult to answer questions such as > Is the export in this module also exported by the package? @@ -14,12 +15,19 @@ or > What is exported from this directory? -We currently do not use any pattern for how to structure exports. There is a mix of package-level re-exports deep into the directory tree, shallow re-exports for each directory, exports using `*` and explicit lists of each symbol, etc. -The mix and lack of predictability makes it difficult to reason about the boundaries of a module, and for example knowing whether is is safe to export a symbol in a given file. +We currently do not use any pattern for how to structure exports. There is a mix +of package-level re-exports deep into the directory tree, shallow re-exports for +each directory, exports using `*` and explicit lists of each symbol, etc. The +mix and lack of predictability makes it difficult to reason about the boundaries +of a module, and for example knowing whether is is safe to export a symbol in a +given file. ## Decision -We will make each exported symbol traceable through index files all the way down to the root of the package, `src/index.ts`. Each index file will only re-export from its own immediate directory children, and only index files will have re-exports. This gives a file tree similar to this: +We will make each exported symbol traceable through index files all the way down +to the root of the package, `src/index.ts`. Each index file will only re-export +from its own immediate directory children, and only index files will have +re-exports. This gives a file tree similar to this: ```text index.ts @@ -33,16 +41,25 @@ lib/index.ts /helper.ts ``` -To check whether for example `SubComponentY` is exported from the package, it should be possible to traverse the index files towards the root, starting at the adjacent one. If there is any index file that doesn't export the previous one, the symbol is not publicly exported. For example, if `components/ComponentX/index.ts` exports `SubComponentY`, but `components/index.ts` does not re-export `./ComponentX`, one should be certain that `SubComponentY` is not exported outside the package. This rule would be broken if for example the root `index.ts` re-exports `./components/ComponentX` +To check whether for example `SubComponentY` is exported from the package, it +should be possible to traverse the index files towards the root, starting at the +adjacent one. If there is any index file that doesn't export the previous one, +the symbol is not publicly exported. For example, if +`components/ComponentX/index.ts` exports `SubComponentY`, but +`components/index.ts` does not re-export `./ComponentX`, one should be certain +that `SubComponentY` is not exported outside the package. This rule would be +broken if for example the root `index.ts` re-exports `./components/ComponentX` -In addition, index files that are re-exporting other index files should always use wildcard form, that is: +In addition, index files that are re-exporting other index files should always +use wildcard form, that is: ```ts // in components/index.ts export * from './ComponentX'; ``` -Index files that are re-exporting symbols from non-index files should always enumerate all exports, that is: +Index files that are re-exporting symbols from non-index files should always +enumerate all exports, that is: ```ts // in components/ComponentX/index.ts @@ -50,14 +67,16 @@ export { ComponentX } from './ComponentX'; export type { ComponentXProps } from './ComponentX'; ``` -Internal cross-directory imports are allowed from non-index modules to index modules, for example: +Internal cross-directory imports are allowed from non-index modules to index +modules, for example: ```ts // in components/ComponentX/ComponentX.tsx import { UtilityX } from '../../lib/UtilityX'; ``` -Imports that bypass an index file are discouraged, but may sometimes be necessary, for example: +Imports that bypass an index file are discouraged, but may sometimes be +necessary, for example: ```ts // in components/ComponentX/ComponentX.tsx @@ -66,6 +85,9 @@ import { helperFunc } from '../../lib/UtilityX/helper'; ## Consequences -We will actively work to rework the export structure in our codebase, prioritizing the library packages such as `@backstage/core` and `@backstage/backend-common`. +We will actively work to rework the export structure in our codebase, +prioritizing the library packages such as `@backstage/core` and +`@backstage/backend-common`. -If possible, we will add tools, such as lint rules, to help enforce the export structure. +If possible, we will add tools, such as lint rules, to help enforce the export +structure. diff --git a/docs/architecture-decisions/adr005-catalog-core-entities.md b/docs/architecture-decisions/adr005-catalog-core-entities.md index ca2a66f6b9..578eb72b91 100644 --- a/docs/architecture-decisions/adr005-catalog-core-entities.md +++ b/docs/architecture-decisions/adr005-catalog-core-entities.md @@ -6,7 +6,8 @@ ## Context -We want to standardize on a few core entities that we are tracking in the Backstage catalog. This allows us to build specific plugins around them. +We want to standardize on a few core entities that we are tracking in the +Backstage catalog. This allows us to build specific plugins around them. ## Decision @@ -14,17 +15,26 @@ Backstage should eventually support the following core entities: - **Components** are individual pieces of software - **APIs** are the boundaries between different components -- **Resources** are physical or virtual infrastructure needed to operate a component +- **Resources** are physical or virtual infrastructure needed to operate a + component ![Catalog Core Entities](catalog-core-entities.png) -For now, we'll start by only implementing support for the Component entity in the Backstage catalog. This can later be extended to APIs, Resources and other potentially useful entities. +For now, we'll start by only implementing support for the Component entity in +the Backstage catalog. This can later be extended to APIs, Resources and other +potentially useful entities. ### Component -A component is a piece of software, for example a mobile application feature, web site, backend service or data pipeline (list not exhaustive). A component can be tracked in source control, or use some existing open source or commercial software. It can implement APIs for other components to consume. In turn it might depend on APIs implemented by other components, or resources that are attached to it at runtime. +A component is a piece of software, for example a mobile application feature, +web site, backend service or data pipeline (list not exhaustive). A component +can be tracked in source control, or use some existing open source or commercial +software. It can implement APIs for other components to consume. In turn it +might depend on APIs implemented by other components, or resources that are +attached to it at runtime. -Component entities are typically defined in YAML descriptor files next to the code of the component, and could look like this (actual schema will evolve): +Component entities are typically defined in YAML descriptor files next to the +code of the component, and could look like this (actual schema will evolve): ```yaml apiVersion: backstage.io/v1beta1 @@ -37,11 +47,20 @@ spec: ### API -APIs form an abstraction that allows large software ecosystems to scale. Thus, APIs are a first class citizen in the Backstage model and the primary way to discover existing functionality in the ecosystem. +APIs form an abstraction that allows large software ecosystems to scale. Thus, +APIs are a first class citizen in the Backstage model and the primary way to +discover existing functionality in the ecosystem. -APIs are implemented by components and make their boundaries explicit. They might be defined using an RPC IDL (e.g. in Protobuf, GraphQL or similar), a data schema (e.g. in Avro, TFRecord or similar), or as code interfaces (e.g. framework APIs in Swift, Kotlin, Java, C++, Typescript etc). In any case, APIs exposed by components need to be in a known machine-readable format so we can build further tooling and analysis on top. +APIs are implemented by components and make their boundaries explicit. They +might be defined using an RPC IDL (e.g. in Protobuf, GraphQL or similar), a data +schema (e.g. in Avro, TFRecord or similar), or as code interfaces (e.g. +framework APIs in Swift, Kotlin, Java, C++, Typescript etc). In any case, APIs +exposed by components need to be in a known machine-readable format so we can +build further tooling and analysis on top. -APIs are typically indexed from existing definitions in source control and thus wouldn't need their own descriptor files, but would be stored in the catalog somewhat like this (actual schema will evolve): +APIs are typically indexed from existing definitions in source control and thus +wouldn't need their own descriptor files, but would be stored in the catalog +somewhat like this (actual schema will evolve): ```yaml apiVersion: backstage.io/v1beta1 @@ -54,9 +73,11 @@ spec: service HelloService { rpc SayHello (HelloRequest) returns (HelloResponse); } + message HelloRequest { string greeting = 1; } + message HelloResponse { string reply = 1; } @@ -64,9 +85,16 @@ spec: ### Resource -Resources are the infrastructure your software needs to operate at runtime like Bigtable databases, Pub/Sub topics, S3 buckets or CDNs. Modelling them together with components and APIs will allow us to visualize and create tooling around them in Backstage. +Resources are the infrastructure your software needs to operate at runtime like +Bigtable databases, Pub/Sub topics, S3 buckets or CDNs. Modelling them together +with components and APIs will allow us to visualize and create tooling around +them in Backstage. -Resources are typically indexed from declarative definitions (e.g. Terraform, GCP Config Connector, AWS Cloud Formation) and/or inventories from cloud providers (e.g. GCP Asset Inventory) and thus wouldn't need their own descriptor files, but would be stored in the catalog somewhat like this (actual schema will evolve): +Resources are typically indexed from declarative definitions (e.g. Terraform, +GCP Config Connector, AWS Cloud Formation) and/or inventories from cloud +providers (e.g. GCP Asset Inventory) and thus wouldn't need their own descriptor +files, but would be stored in the catalog somewhat like this (actual schema will +evolve): ```yaml apiVersion: backstage.io/v1beta1 @@ -80,4 +108,5 @@ spec: ## Consequences -We will continue fleshing out support for the Component entity in the Backstage catalog. +We will continue fleshing out support for the Component entity in the Backstage +catalog. diff --git a/docs/architecture-terminology.md b/docs/architecture-terminology.md index 1570607896..836b2cfa99 100644 --- a/docs/architecture-terminology.md +++ b/docs/architecture-terminology.md @@ -1,9 +1,18 @@ # Architecture and Terminology -Backstage is constructed out of three parts. We separate Backstage in this way because we see three groups of contributors that work with Backstage in three different ways. +Backstage is constructed out of three parts. We separate Backstage in this way +because we see three groups of contributors that work with Backstage in three +different ways. - Core - Base functionality built by core devs in the open source project. -- App - The app is an instance of a Backstage app that is deployed and tweaked. The app ties together core functionality with additional plugins. The app is built and maintained by app developers, usually a productivity team within a company. -- Plugins - Additional functionality to make your Backstage app useful for your company. Plugins can be specific to a company or open sourced and reusable. At Spotify we have over 100 plugins built by over 50 different teams. It has been very powerful to get contributions from various infrastructure teams added into a single unified developer experience. +- App - The app is an instance of a Backstage app that is deployed and tweaked. + The app ties together core functionality with additional plugins. The app is + built and maintained by app developers, usually a productivity team within a + company. +- Plugins - Additional functionality to make your Backstage app useful for your + company. Plugins can be specific to a company or open sourced and reusable. At + Spotify we have over 100 plugins built by over 50 different teams. It has been + very powerful to get contributions from various infrastructure teams added + into a single unified developer experience. [Back to Docs](README.md) diff --git a/docs/auth/add-auth-provider.md b/docs/auth/add-auth-provider.md index 4bbbec88bb..33f1d335a0 100644 --- a/docs/auth/add-auth-provider.md +++ b/docs/auth/add-auth-provider.md @@ -2,17 +2,21 @@ ## Passport -We chose [Passport](http://www.passportjs.org/) as our authentication platform due to its comprehensive set of supported authentication [strategies](http://www.passportjs.org/packages/). +We chose [Passport](http://www.passportjs.org/) as our authentication platform +due to its comprehensive set of supported authentication +[strategies](http://www.passportjs.org/packages/). ## How to add a new strategy provider ### Quick guide -[1.](#installing-the-dependencies) Install the passport-js based provider package. +[1.](#installing-the-dependencies) Install the passport-js based provider +package. [2.](#create-implementation) Create a new folder structure for the provider. -[3.](#adding-an-oauth-based-provider) Implement the provider, extending the suitable framework if needed. +[3.](#adding-an-oauth-based-provider) Implement the provider, extending the +suitable framework if needed. [4.](#hook-it-up-to-the-backend) Add the provider to the backend. @@ -26,7 +30,8 @@ yarn add @types/passport-provider-a ### Create implementation -Make a new folder with the name of the provider following the below file structure: +Make a new folder with the name of the provider following the below file +structure: ```bash plugins/auth-backend/src/providers/providerA @@ -34,13 +39,16 @@ plugins/auth-backend/src/providers/providerA └── provider.ts ``` -**`plugins/auth-backend/src/providers/providerA/provider.ts`** defines the provider class which implements a handler for the chosen framework. +**`plugins/auth-backend/src/providers/providerA/provider.ts`** defines the +provider class which implements a handler for the chosen framework. #### Adding an OAuth based provider -If we're adding an `OAuth` based provider we would implement the [OAuthProviderHandlers](#OAuthProviderHandlers) interface. +If we're adding an `OAuth` based provider we would implement the +[OAuthProviderHandlers](#OAuthProviderHandlers) interface. -The provider class takes the provider's configuration as a class parameter. It also imports the `Strategy` from the passport package. +The provider class takes the provider's configuration as a class parameter. It +also imports the `Strategy` from the passport package. ```ts import { Strategy as ProviderAStrategy } from 'passport-provider-a'; @@ -64,9 +72,11 @@ export class ProviderAAuthProvider implements OAuthProviderHandlers { #### Adding an non-OAuth based provider -_**Note**: We have prioritized OAuth-based providers and non-OAuth providers should be considered experimental._ +_**Note**: We have prioritized OAuth-based providers and non-OAuth providers +should be considered experimental._ -An non-`OAuth` based provider could implement [AuthProviderRouteHandlers](#AuthProviderRouteHandlers) instead. +An non-`OAuth` based provider could implement +[AuthProviderRouteHandlers](#AuthProviderRouteHandlers) instead. ```ts export class ProviderAAuthProvider implements AuthProviderRouteHandlers { @@ -90,9 +100,12 @@ export class ProviderAAuthProvider implements AuthProviderRouteHandlers { #### Create method -Each provider exports a create method that creates the provider instance, optionally extending a supported authorization framework. This method exists to allow for flexibility if additional frameworks are supported in the future. +Each provider exports a create method that creates the provider instance, +optionally extending a supported authorization framework. This method exists to +allow for flexibility if additional frameworks are supported in the future. -Implementing OAuth by returning an instance of `OAuthProvider` based of the provider's class: +Implementing OAuth by returning an instance of `OAuthProvider` based of the +provider's class: ```ts export function createProviderAProvider(config: AuthProviderConfig) { @@ -102,7 +115,9 @@ export function createProviderAProvider(config: AuthProviderConfig) { } ``` -Not extending with OAuth, the main difference here is that the create method is returning a instance of the class without adding the OAuth authorization framework to it. +Not extending with OAuth, the main difference here is that the create method is +returning a instance of the class without adding the OAuth authorization +framework to it. ```ts export function createProviderAProvider(config: AuthProviderConfig) { @@ -112,14 +127,22 @@ export function createProviderAProvider(config: AuthProviderConfig) { #### Verify Callback -> Strategies require what is known as a verify callback. The purpose of a verify callback is to find the user that possesses a set of credentials. -> When Passport authenticates a request, it parses the credentials contained in the request. It then invokes the verify callback with those credentials as arguments [...]. If the credentials are valid, the verify callback invokes done to supply Passport with the user that authenticated. +> Strategies require what is known as a verify callback. The purpose of a verify +> callback is to find the user that possesses a set of credentials. When +> Passport authenticates a request, it parses the credentials contained in the +> request. It then invokes the verify callback with those credentials as +> arguments [...]. If the credentials are valid, the verify callback invokes +> done to supply Passport with the user that authenticated. > -> If the credentials are not valid (for example, if the password is incorrect), done should be invoked with false instead of a user to indicate an authentication failure. +> If the credentials are not valid (for example, if the password is incorrect), +> done should be invoked with false instead of a user to indicate an +> authentication failure. > > http://www.passportjs.org/docs/configure/ -**`plugins/auth-backend/src/providers/providerA/index.ts`** is simply re-exporting the create method to be used for hooking the provider up to the backend. +**`plugins/auth-backend/src/providers/providerA/index.ts`** is simply +re-exporting the create method to be used for hooking the provider up to the +backend. ```ts export { createProviderAProvider } from './provider'; @@ -127,7 +150,9 @@ export { createProviderAProvider } from './provider'; ### Hook it up to the backend -**`plugins/auth-backend/src/providers/config.ts`** The provider needs to be configured properly so you need to add it to the list of configured providers, all of which implement [AuthProviderConfig](#AuthProviderConfig): +**`plugins/auth-backend/src/providers/config.ts`** The provider needs to be +configured properly so you need to add it to the list of configured providers, +all of which implement [AuthProviderConfig](#AuthProviderConfig): ```ts export const providers = [ @@ -138,7 +163,10 @@ export const providers = [ }, ``` -**`plugins/auth-backend/src/providers/factories.ts`** When the `auth-backend` starts it sets up routing for all the available providers by calling `createAuthProviderRouter` on each provider. You need to import the create method from the provider and add it to the factory: +**`plugins/auth-backend/src/providers/factories.ts`** When the `auth-backend` +starts it sets up routing for all the available providers by calling +`createAuthProviderRouter` on each provider. You need to import the create +method from the provider and add it to the factory: ```ts import { createProviderAProvider } from './providerA'; @@ -157,11 +185,14 @@ router.post('/auth/providerA/logout'); router.get('/auth/providerA/refresh'); // if supported ``` -As you can see each endpoint is prefixed with both `/auth` and its provider name. +As you can see each endpoint is prefixed with both `/auth` and its provider +name. ### Test the new provider -You can `curl -i localhost:7000/auth/providerA/start` and which should provide a `302` redirect with a `Location` header. Paste the url from that header into a web browser and you should be able to trigger the authorization flow. +You can `curl -i localhost:7000/auth/providerA/start` and which should provide a +`302` redirect with a `Location` header. Paste the url from that header into a +web browser and you should be able to trigger the authorization flow. --- diff --git a/docs/auth/glossary.md b/docs/auth/glossary.md new file mode 100644 index 0000000000..6408587273 --- /dev/null +++ b/docs/auth/glossary.md @@ -0,0 +1,26 @@ +# Glossary + +- **Popup** - A separate browser window opened on top of the previous one. +- **OAuth** - More specifically OAuth 2.0, a standard protocol for + authorization. See [oauth.net/2/](https://oauth.net/2/). +- **OpenID Connect** - A layer on top of OAuth which standardises + authentication. See + [en.wikipedia.org/wiki/OpenID_Connect](https://en.wikipedia.org/wiki/OpenID_Connect). +- **JWT** - JSON Web Token, a popular JSON based token format that is commonly + encrypted and/or signed, see + [en.wikipedia.org/wiki/JSON_Web_Token](https://en.wikipedia.org/wiki/JSON_Web_Token) +- **Scope** - A string that describes a certain type of access that can be + granted to a user using OAuth. +- **Access token** - A token that gives access to perform actions on behalf of a + user. It will commonly have a short expiry time, and be limited to a set of + scopes. Part of the OAuth protocol. +- **ID token** - A JWT used to prove a user's identity, containing for example + the user's email. Part of OpenID Connect. +- **Offline access** - OAuth flow that results in both a refresh and access + token, where the refresh token has a long expiration or never expires, and can + be used to request more access tokens in the future. This lets the user go + "offline" with respect to the token issuer, but still be able to request more + tokens at a later time without further direct interaction for the user. +- **Code grant** - OAuth flow where the client receives an authorization code + that is passed to the backend to be exchanged for an access token and possibly + refresh token. diff --git a/docs/auth/oauth-popup-flow.svg b/docs/auth/oauth-popup-flow.svg new file mode 100644 index 0000000000..4d9e79787a --- /dev/null +++ b/docs/auth/oauth-popup-flow.svg @@ -0,0 +1,56 @@ +OAuth Consent and Refresh FlowBrowserBrowserPopup WindowPopup Windowauth-backend pluginauth-backend pluginConsent ScreenConsent ScreenOAuth ProviderOAuth ProviderComponents on page ask for anaccess token with greaterscope than the existing session.Open popupGET /auth/<provider>/start?scope=some%20scopesRedirect to consent screen withrandom nonce in OAuth state andshort-lived cookie with the same nonce.GET /consent_url?redirect_uri=<redirect_uri>?nonce=<n>where redirect_uri=<app-origin>/auth/<provider>/handler/frameUser consents toaccess the new scope.Redirect to given redirect URL, with authorization codeGET /auth/<provider>/handler/frame?code=<c>&nonce=<n>Request includes the previously set none cookieVerify that the nonce in the cookiematches the nonce in the OAuth stateAuthorization CodeClient IDClient SecretVerify and generate tokensAccess Token(ID Token)(Refresh Token)ScopeExpire TimeSmall HTML page with inlined response payloadStore Refresh Token in HTTP-only cookiepostMessage() with tokens and info or errorClose selfA later point when a refreshis needed. Either because ofa reload or an expiring session.GET /auth/<provider>/tokenRefresh Token cookie includedRefresh TokenClient IDClient SecretAccess Token(ID Token)ScopeExpire TimeTokens and info \ No newline at end of file diff --git a/docs/auth/oauth.md b/docs/auth/oauth.md new file mode 100644 index 0000000000..519cfe1d85 --- /dev/null +++ b/docs/auth/oauth.md @@ -0,0 +1,151 @@ +# OAuth and OpenID Connect + +This section describes how Backstage allows plugins to request OAuth Access +Tokens and OpenID Connect ID Tokens on behalf of the user, to be used for auth +to various third party APIs. + +## Summary + +There are occasions when the user wants to perform actions towards third party +services that require authorization via OAuth. Backstage provides standardized +[Utility APIs](../getting-started/utility-apis.md) such as the +[GoogleAuthApi](../../packages/core-api/src/apis/definitions/auth.ts) for that +use-case. Backstage also includes a set of implementations of these APIs that +integrate with the [auth-backend](../../plugins/auth-backend) plugin to provide +a popup-based OAuth flow. + +## Background + +Access control in OAuth is implemented in terms of scope, which is a list of +permissions given to the app. An OAuth service can issue Access Tokens that are +tied to a certain set of scopes, such as viewing profile information, reading +and/or writing user data in the service. The scope format and handling is +specific to each OAuth provider, and the set of available scopes are typically +found in the documentation describing the auth solution of the provider, for +example +[developers.google.com/identity/protocols/oauth2/scopes](https://developers.google.com/identity/protocols/oauth2/scopes). + +As a part of logging in with an OAuth provider, the user needs to consent to +both the login itself and the set of scopes that the app is requesting to use. +This is done by loading a page provided by the OAuth provider, where a user can +choose an account to log in with, and accept or reject the request. If the user +accepts the login request, a token is issued, and any holder of the token can +use it to make authenticated requests towards the third party service. + +## OAuth in @backstage/core-api and auth-backend + +The default OAuth implementation in Backstage is based on an OAuth server-side +offline access flow, which means that it uses the backend as a helper in order +to trade credentials. A benefit of this type of flow is that it does not require +the use of third party cookies, and is robust on a wide selection of browsers +and privacy browsing plugins, strict security settings, etc. + +The implementation also uses a popup-based flow, where auth requests are handled +in a new popup window that is opened by the app. By using a popup-based flow it +is possible to request authentication at any point in the app, without requiring +a redirect. Because of this there is no need to ask for all scopes upfront, or +interrupt the app with a redirect and forcing plugin authors to take care in +restoring state after a redirect has been make. All in all it makes it much +easier to make authenticated requests inside a plugin. + +## OAuth Flow + +The following describes the OAuth flow implemented by the +[auth-backend](../../plugins/auth-backend) and +[DefaultAuthConnector](../../packages/core-api/src/lib/AuthConnector/DefaultAuthConnector.ts) +in `@backstage/core-api`. + +Component and APIs can request Access or ID Tokens from any available Auth +provider. If there already exists a cached fresh token that covers (at least) +the requested scopes, it will be returned immediately. If the OAuth provider +implements token refreshes, this check will also trigger a token refresh attempt +if no session is a available. + +If new scopes are requested, or the user is not yet logged in with that +provider, a dialog is shown informing the user that they need to log in with the +specified provider. If the user agrees to continue, a separate popup window is +opened that implements the entire consent flow. + +The popup window is pointed to the `/start` endpoint of the auth provider in the +`auth-backend` plugin, which then redirects to the OAuth consent screen of the +provider. The consent screen is controlled by the OAuth provider, and will do +things like prompting the user to log in with an account, and possibly reviewing +the set of requested scopes. If the login request is accepted, the popup window +will be redirected back to the `/handler/frame` endpoint of the auth backend. +The redirect URL will contain a short-term authorization code, which is picked +up by the backend and exchanged for long-term tokens via a call to the OAuth +provider. The Access and possibly ID Token is then handed back to the main +Backstage page via `postMessage`. If the OAuth provider implements offline +refresh, a refresh token will be stored in an HTTP-only cookie scoped to the +specific provider in the `auth-backend` plugin. + +To protect against certain attacks, the above flow also includes a simple nonce +check and a lightweight CSRF protection header. The nonce check is done to +protect against attacks where an attacker tricks a user to log in with an +account of the attacker's choosing in order to gather data. In the first part of +the flow where the popup is directed to the `/start` endpoint, a nonce is +generated and placed in both a cookie and the OAuth state. The nonces received +in the cookie and OAuth state in the redirect handler are then checked, and the +auth attempt will fail if they're not valid. The CSRF protection for the +`/refresh` and `/logout` endpoints is implemented by simply checking for the +presence of a `X-Requested-With` header. + +The target origin of the `postMessage` is also of importance to keep the flow +secure. It is configured to a single value for each auth provider and +environment. Without a single configured origin, any page could open a popup and +request an access token. + +### Sequence Diagram + +The following diagram visualizes the flow described in the previous section. + + + +![](oauth-popup-flow.svg) diff --git a/docs/auth/overview.md b/docs/auth/overview.md new file mode 100644 index 0000000000..2806b2dccf --- /dev/null +++ b/docs/auth/overview.md @@ -0,0 +1,44 @@ +# User Authentication and Authorization in Backstage + +## Summary + +The purpose of the Auth APIs in Backstage are to identify the user, and to +provide a way for plugins to request access to 3rd party services on behalf of +the user (OAuth). This documentation focuses on the implementation of that +solution and how to extend it. For documentation on how to consume the Auth APIs +in a plugin, see [TODO](#TODO). + +### Accessing Third Party Services + +The main pattern for talking to third party services in Backstage is +user-to-server requests, where short-lived OAuth Access Tokens are requested by +plugins to authenticate calls to external services. These calls can be made +either directly to the services or through a backend plugin or service. + +By relying on user-to-server calls we keep the coupling between the frontend and +backend low, and provide a much lower barrier for plugins to make use of third +party services. This is in comparison to for example a session-based system, +where access tokens are stored server-side. Such a solution would require a much +deeper coupling between the auth backend plugin, its session storage, and other +backend plugins or separate services. A goal of Backstage is to make it as easy +as possible to create new plugins, and an auth solution based on user-to-server +OAuth helps in that regard. + +The method with which frontend plugins request access to third party services is +through [Utility APIs](../getting-started/utility-apis.md) for each service +provider. For a full list of providers, see [TODO](#TODO). + +### Identity - TODO + +This documentation currently only covers the OAuth use-case, as identity +management is not settled yet and part of an +[upcoming milestone](https://github.com/spotify/backstage/milestone/12). + +## Further Reading + +More details are provided in dedicated sections of the documentation. + +- [OAuth](./oauth): Description of the generic OAuth flow implemented by the + [auth-backend](../../plugins/auth-backend). +- [Glossary](./glossary): Glossary of some common terms related to the auth + flows. diff --git a/docs/create-an-app.md b/docs/create-an-app.md index 8c8050c2ff..b69ff1508c 100644 --- a/docs/create-an-app.md +++ b/docs/create-an-app.md @@ -1,12 +1,16 @@ # Backstage App -To get set up quickly with your own Backstage project you can create a Backstage App. +To get set up quickly with your own Backstage project you can create a Backstage +App. -A Backstage App is a monorepo setup with `lerna` that includes everything you need to run Backstage in your own environment. +A Backstage App is a monorepo setup with `lerna` that includes everything you +need to run Backstage in your own environment. ## Create an app -To create a Backstage app, you will need to have [NodeJS](https://nodejs.org/en/download/) Active LTS Release installed (currently v12). +To create a Backstage app, you will need to have +[NodeJS](https://nodejs.org/en/download/) Active LTS Release installed +(currently v12). With `npx`: @@ -14,13 +18,15 @@ With `npx`: npx @backstage/cli create-app ``` -This will create a new Backstage App inside the current folder. The name of the app-folder is the name that was provided when prompted. +This will create a new Backstage App inside the current folder. The name of the +app-folder is the name that was provided when prompted.

create app

-Inside that directory, it will generate all the files and folder structure needed for you to run your app. +Inside that directory, it will generate all the files and folder structure +needed for you to run your app. ### Folder structure @@ -69,4 +75,5 @@ cd my-backstage-app yarn start ``` -_When `yarn start` is ready it should open up a browser window displaying your app, if not you can navigate to `http://localhost:3000`._ +_When `yarn start` is ready it should open up a browser window displaying your +app, if not you can navigate to `http://localhost:3000`._ diff --git a/docs/design.md b/docs/design.md index bcbf0bbdc5..e3816bd2e5 100644 --- a/docs/design.md +++ b/docs/design.md @@ -1,70 +1,134 @@ ![header](designheader.png) -Much like Backstage Open Source, this is a *living* document! We'll keep this updated as we evolve our practices! +Much like Backstage Open Source, this is a _living_ document! We'll keep this +updated as we evolve our practices! ## 📚 Our Philosophy ### Iterative -Backstage Open Source is a newly launched endeavor, and we’re excited to scale up our design practices! With that said, we’ll be working closely with you, the community, and iterating and experimenting as we go to see what works best. As a continual work in progress, we aspire to release early and often. Not only that, we are committed to working with developers to create a seamless and easy handoff. If you’re curious to see how we grow and would like to play a role in that growth, check out the issues in this GitHub repo! +Backstage Open Source is a newly launched endeavor, and we’re excited to scale +up our design practices! With that said, we’ll be working closely with you, the +community, and iterating and experimenting as we go to see what works best. As a +continual work in progress, we aspire to release early and often. Not only that, +we are committed to working with developers to create a seamless and easy +handoff. If you’re curious to see how we grow and would like to play a role in +that growth, check out the issues in this GitHub repo! ### Collaborative -The Backstage Design Team is small but mighty, and we truly cherish the amazing opportunity we have to work with the Backstage Open Source community! Have an idea? A component request? Feel free to communicate with us via [Discord](https://discord.gg/EBHEGzX) (*#design* channel). Collaboration trumps individual speed, and we want to work with you to make Backstage work for all of our users. +The Backstage Design Team is small but mighty, and we truly cherish the amazing +opportunity we have to work with the Backstage Open Source community! Have an +idea? A component request? Feel free to communicate with us via +[Discord](https://discord.gg/EBHEGzX) (_#design_ channel). Collaboration trumps +individual speed, and we want to work with you to make Backstage work for all of +our users. ### Transparent -There are a lot of exciting things coming up and we want to keep you in the loop! Keep an eye on our Milestones in GitHub to see where we’re headed. We’ll also be posting updates in the *#design* channel on [Discord](https://discord.gg/EBHEGzX). Not only that, we want to keep you informed on the decisions we’ve made and why we’ve made them. +There are a lot of exciting things coming up and we want to keep you in the +loop! Keep an eye on our Milestones in GitHub to see where we’re headed. We’ll +also be posting updates in the _#design_ channel on +[Discord](https://discord.gg/EBHEGzX). Not only that, we want to keep you +informed on the decisions we’ve made and why we’ve made them. ## 🛠 Our Practice -The chart below details how we work. ***Stay tuned***: We are currently in the process of securing a Figma workspace for Backstage Open Source, and we plan on referencing Figma documents to share specs and prototypes with the community. + +The chart below details how we work. **_Stay tuned_**: We are currently in the +process of securing a Figma workspace for Backstage Open Source, and we plan on +referencing Figma documents to share specs and prototypes with the community. ### Creating a New Design Component -| Step 1 | Step 2 | Step 3 | Step 4 | Step 5 | Step 6 | -|:---|:---|:---|:---|:---|:---| -| Platform design team submits an issue to **spotify/Backstage GitHub** with a potential component. | Backstage community offers feedback or approval on **spotify/Backstage GitHub**. | Platform design team adjusts accordingly (as they see fit) and update the Figma DLS document. | Designed component is added to **spotify/Backstage GitHub** as an issue. | External or internal Backstage open source contributors build the component. | External or internal contributors add the component to the **Backstage Storybook**. 🎉 | +| Step 1 | Step 2 | Step 3 | Step 4 | Step 5 | Step 6 | +| :------------------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------- | :--------------------------------------------------------------------------- | :------------------------------------------------------------------------------------- | +| Platform design team submits an issue to **spotify/Backstage GitHub** with a potential component. | Backstage community offers feedback or approval on **spotify/Backstage GitHub**. | Platform design team adjusts accordingly (as they see fit) and update the Figma DLS document. | Designed component is added to **spotify/Backstage GitHub** as an issue. | External or internal Backstage open source contributors build the component. | External or internal contributors add the component to the **Backstage Storybook**. 🎉 | ### Building for Backstage -| Step 1 | Step 2 | Step 3 | Step 4 | -|:---|:---|:---|:---| -| External or internal contributors use Backstage and come up with an idea of an entity to build for Backstage. |External or internal contributors refer to the Backstage Open Source design system documentation in the Figma DLS document. | External or internal contributors leverage the components and tokens from the Backstage Storybook. | External or internal contributors build their Backstage entity. | -| Step 5 | Step 6 | Step 7 | Step 8 | -|:---|:---|:---|:---| -| External or internal contributors make a pull request for their entity on spotify/Backstage GitHub for review. | Platform designers and devs review the entity and submit feedback or approval on spotify/Backstage GitHub. | External or internal contributors make the changes, pull request is approved and the entity is merged. It’s live on Backstage! 🎉 | If the entity happens to be or include a UX component, it’s added to Backstage Storybook as well. | +| Step 1 | Step 2 | Step 3 | Step 4 | +| :------------------------------------------------------------------------------------------------------------ | :-------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------- | +| External or internal contributors use Backstage and come up with an idea of an entity to build for Backstage. | External or internal contributors refer to the Backstage Open Source design system documentation in the Figma DLS document. | External or internal contributors leverage the components and tokens from the Backstage Storybook. | External or internal contributors build their Backstage entity. | +| Step 5 | Step 6 | Step 7 | Step 8 | +| :------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------ | +| External or internal contributors make a pull request for their entity on spotify/Backstage GitHub for review. | Platform designers and devs review the entity and submit feedback or approval on spotify/Backstage GitHub. | External or internal contributors make the changes, pull request is approved and the entity is merged. It’s live on Backstage! 🎉 | If the entity happens to be or include a UX component, it’s added to Backstage Storybook as well. | -The following diagram shows the relationship between the Backstage Design System and our foundation, which comprises of [Material UI](https://material-ui.com/) that is shaped by user experience and user interface decisions made by our Backstage Design Team. Also note, we encourage you to take the core experience we’ve crafted and add custom theming to better represent your organization! +The following diagram shows the relationship between the Backstage Design System +and our foundation, which comprises of [Material UI](https://material-ui.com/) +that is shaped by user experience and user interface decisions made by our +Backstage Design Team. Also note, we encourage you to take the core experience +we’ve crafted and add custom theming to better represent your organization! ![dls](DLS.png) - ## ✅ Our Priorities + ### Backstage Design System -This is the set of building blocks for Backstage contributors to leverage as they create rad plugins for Backstage! Why reinvent the wheel when you can use components that have already been vetted by our team and the Backstage community? In the spirit of crafting a cohesive and consistent user experience across all of Backstage, we strongly urge all plugin developers to utilize our Storybook as a reference. Our design system is new and evolving, and we’ll be building it up with your help! + +This is the set of building blocks for Backstage contributors to leverage as +they create rad plugins for Backstage! Why reinvent the wheel when you can use +components that have already been vetted by our team and the Backstage +community? In the spirit of crafting a cohesive and consistent user experience +across all of Backstage, we strongly urge all plugin developers to utilize our +Storybook as a reference. Our design system is new and evolving, and we’ll be +building it up with your help! + ### Core Backstage User Experience -This is the universal user experience that is shared amongst all Backstage users. From more concrete aspects like the plugins marketplace to more abstract ones like end-to-end workflows on Backstage, we’ll be working with the community to create a core user experience that best serves you and your organization. + +This is the universal user experience that is shared amongst all Backstage +users. From more concrete aspects like the plugins marketplace to more abstract +ones like end-to-end workflows on Backstage, we’ll be working with the community +to create a core user experience that best serves you and your organization. ## ⭐️ How to Contribute -### Pick up an issue! -In the beginning, most of our issues will be centered around creating universal components for our Backstage Design System and adding them to our Storybook so plugin developers can reference them. We’ll also be creating issues that are focused on building up our core Backstage user experience. We’ll be labeling our issues in GitHub with ‘design’ and/or ‘storybook’ - so feel free to browse and tackle the tasks that interest you. If you have any questions regarding an issue, you can ask them in the comments section of the issue or on [Discord](https://discord.gg/EBHEGzX). We absolutely adore our external contributors and will send you virtual semlas for your contributions! + +### Pick up an issue! + +In the beginning, most of our issues will be centered around creating universal +components for our Backstage Design System and adding them to our Storybook so +plugin developers can reference them. We’ll also be creating issues that are +focused on building up our core Backstage user experience. We’ll be labeling our +issues in GitHub with ‘design’ and/or ‘storybook’ - so feel free to browse and +tackle the tasks that interest you. If you have any questions regarding an +issue, you can ask them in the comments section of the issue or on +[Discord](https://discord.gg/EBHEGzX). We absolutely adore our external +contributors and will send you virtual semlas for your contributions! ### Request a component. -Create an issue (label it design and assign it to katz95) or send us a message on [Discord](https://discord.gg/EBHEGzX) (*#design* channel) with details of what the component is and its relevant use cases. Your request will be reviewed by our design team and you should hear back from us within 1-2 business days. We’ll get back to you and let you know whether your requested component will get picked up by our team as something to be added to our design system. + +Create an issue (label it design and assign it to katz95) or send us a message +on [Discord](https://discord.gg/EBHEGzX) (_#design_ channel) with details of +what the component is and its relevant use cases. Your request will be reviewed +by our design team and you should hear back from us within 1-2 business days. +We’ll get back to you and let you know whether your requested component will get +picked up by our team as something to be added to our design system. ## ✏️ Resources -**[Storybook](http://storybook.backstage.io/)** - where you can view our components. If you’d like to help build up our design system, you can also add components we’ve designed to the Storybook as well. -**[Discord](https://discord.gg/EBHEGzX)** - all design questions should be directed to the *#design* channel. +**[Storybook](http://storybook.backstage.io/)** - where you can view our +components. If you’d like to help build up our design system, you can also add +components we’ve designed to the Storybook as well. + +**[Discord](https://discord.gg/EBHEGzX)** - all design questions should be +directed to the _#design_ channel. **Documentation** + - Patterns (stay tuned) - Figma files/libraries (stay tuned) ## 🔮 Future -### Contributions from designers -Are you a designer at an organisation that’s implementing Backstage? A designer who’s fascinated by the developer productivity problem space? A designer who’s curious about open source design? We’d love for you to contribute. Behind the scenes, we’re setting up a few foundational elements to make sure that contributing to Backstage as a designer is easy. From styling guidelines to UX principles to Figma documents, we’ll make sure you’re equipped to chip in on this project. We’re excited to work with you! In the meantime, we’d love to hear from you on [Discord](https://discord.gg/EBHEGzX). +### Contributions from designers + +Are you a designer at an organisation that’s implementing Backstage? A designer +who’s fascinated by the developer productivity problem space? A designer who’s +curious about open source design? We’d love for you to contribute. Behind the +scenes, we’re setting up a few foundational elements to make sure that +contributing to Backstage as a designer is easy. From styling guidelines to UX +principles to Figma documents, we’ll make sure you’re equipped to chip in on +this project. We’re excited to work with you! In the meantime, we’d love to hear +from you on [Discord](https://discord.gg/EBHEGzX). [Back to Docs](../README.md) diff --git a/docs/generate-uml.sh b/docs/generate-uml.sh new file mode 100755 index 0000000000..55117838ea --- /dev/null +++ b/docs/generate-uml.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +# This script uses plantuml to generate SVG images from inline plantuml descriptions. +# See ./auth/oauth.md for an example. +# +# You need to have plantuml installed (brew install plantuml, or some other method). +# +# Either call directly to generate diagrams for all markdown files in this directory, +# or add a --watch flag to rebuild SVGs on changes. + +cd "$( dirname "${BASH_SOURCE[0]}" )" + +if [[ "$1" == '--watch' ]]; then + npx --no-install nodemon --ext md --exec './generate-uml.sh' +fi + +grep '@startuml' -rl --include '*.md' . | while read -r file ; do + echo "Generating : $file" + plantuml -tsvg "$file" 2> >(grep -v "CoreText note:") +done diff --git a/docs/getting-started/Plugin development.md b/docs/getting-started/Plugin development.md index 0f578151a2..41918b5de2 100644 --- a/docs/getting-started/Plugin development.md +++ b/docs/getting-started/Plugin development.md @@ -2,10 +2,10 @@ Backstage plugins provide features to a Backstage App. -Each plugin is treated as a self-contained web app and can include almost any type of content. -Plugins all use a common set of platform APIs and reusable UI components. -Plugins can fetch data from external sources using the regular browser APIs or by depending on -external modules to do the work. +Each plugin is treated as a self-contained web app and can include almost any +type of content. Plugins all use a common set of platform APIs and reusable UI +components. Plugins can fetch data from external sources using the regular +browser APIs or by depending on external modules to do the work.