From 074aa670fef04c92253b0a8d0a62f9131ff0f49d Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 8 Jun 2020 06:15:24 +0000 Subject: [PATCH 001/182] build(deps): bump @rollup/plugin-commonjs from 12.0.0 to 13.0.0 Bumps [@rollup/plugin-commonjs](https://github.com/rollup/plugins) from 12.0.0 to 13.0.0. - [Release notes](https://github.com/rollup/plugins/releases) - [Commits](https://github.com/rollup/plugins/compare/commonjs-v12.0.0...commonjs-v13.0.0) Signed-off-by: dependabot-preview[bot] --- packages/cli/package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index 97e3aaa972..1a155ef085 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -32,7 +32,7 @@ "@hot-loader/react-dom": "^16.13.0", "@lerna/package-graph": "^3.18.5", "@lerna/project": "^3.18.0", - "@rollup/plugin-commonjs": "^12.0.0", + "@rollup/plugin-commonjs": "^13.0.0", "@rollup/plugin-json": "^4.0.2", "@rollup/plugin-node-resolve": "^7.1.1", "@spotify/eslint-config": "^7.0.1", diff --git a/yarn.lock b/yarn.lock index 20ed4ae91c..54207ed2f2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2409,10 +2409,10 @@ prop-types "^15.6.1" react-lifecycles-compat "^3.0.4" -"@rollup/plugin-commonjs@^12.0.0": - version "12.0.0" - resolved "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-12.0.0.tgz#e2f308ae6057499e0f413f878fff7c3a0fdc02a1" - integrity sha512-8+mDQt1QUmN+4Y9D3yCG8AJNewuTSLYPJVzKKUZ+lGeQrI+bV12Tc5HCyt2WdlnG6ihIL/DPbKRJlB40DX40mw== +"@rollup/plugin-commonjs@^13.0.0": + version "13.0.0" + resolved "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-13.0.0.tgz#8a1d684ba6848afe8b9e3d85649d4b2f6f7217ec" + integrity sha512-Anxc3qgkAi7peAyesTqGYidG5GRim9jtg8xhmykNaZkImtvjA7Wsqep08D2mYsqw1IF7rA3lYfciLgzUSgRoqw== dependencies: "@rollup/pluginutils" "^3.0.8" commondir "^1.0.1" From bb09898b51e403c3b25c3f668118772b104ce304 Mon Sep 17 00:00:00 2001 From: Nikita Nek Dudnik Date: Mon, 15 Jun 2020 12:50:35 +0200 Subject: [PATCH 002/182] feat(catalog): extract useEntitiesStore --- .../CatalogFilter/CatalogFilter.tsx | 7 +- .../components/CatalogFilter/OwnedCount.tsx | 23 ++++++ .../components/CatalogFilter/StarredCount.tsx | 4 +- .../components/CatalogPage/CatalogPage.tsx | 42 ++++------ plugins/catalog/src/data/filters.ts | 14 +++- plugins/catalog/src/hooks/useEntitiesStore.ts | 82 +++++++++++++++++++ 6 files changed, 136 insertions(+), 36 deletions(-) create mode 100644 plugins/catalog/src/components/CatalogFilter/OwnedCount.tsx create mode 100644 plugins/catalog/src/hooks/useEntitiesStore.ts diff --git a/plugins/catalog/src/components/CatalogFilter/CatalogFilter.tsx b/plugins/catalog/src/components/CatalogFilter/CatalogFilter.tsx index 5335b33ffa..0abb5f817a 100644 --- a/plugins/catalog/src/components/CatalogFilter/CatalogFilter.tsx +++ b/plugins/catalog/src/components/CatalogFilter/CatalogFilter.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import React from 'react'; +import React, { FC } from 'react'; import { Card, List, @@ -27,11 +27,12 @@ import { } from '@material-ui/core'; import type { IconComponent } from '@backstage/core'; import { EntityFilterType } from '../../data/filters'; + export type CatalogFilterItem = { id: EntityFilterType; label: string; icon?: IconComponent; - count?: number | React.FC; + count?: number | FC; }; export type CatalogFilterGroup = { @@ -71,7 +72,7 @@ const useStyles = makeStyles(theme => ({ }, })); -export const CatalogFilter: React.FC = ({ +export const CatalogFilter: FC = ({ groups, selectedId, onSelectedChange, diff --git a/plugins/catalog/src/components/CatalogFilter/OwnedCount.tsx b/plugins/catalog/src/components/CatalogFilter/OwnedCount.tsx new file mode 100644 index 0000000000..a48019c48a --- /dev/null +++ b/plugins/catalog/src/components/CatalogFilter/OwnedCount.tsx @@ -0,0 +1,23 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React, { FC } from 'react'; +import { useStarredEntities } from '../../hooks/useStarredEntites'; + +export const StarredCount: FC<{}> = () => { + const { starredEntities } = useStarredEntities(); + return {starredEntities.size}; +}; diff --git a/plugins/catalog/src/components/CatalogFilter/StarredCount.tsx b/plugins/catalog/src/components/CatalogFilter/StarredCount.tsx index 5e79682783..a48019c48a 100644 --- a/plugins/catalog/src/components/CatalogFilter/StarredCount.tsx +++ b/plugins/catalog/src/components/CatalogFilter/StarredCount.tsx @@ -14,10 +14,10 @@ * limitations under the License. */ -import React from 'react'; +import React, { FC } from 'react'; import { useStarredEntities } from '../../hooks/useStarredEntites'; -export const StarredCount: React.FC<{}> = () => { +export const StarredCount: FC<{}> = () => { const { starredEntities } = useStarredEntities(); return {starredEntities.size}; }; diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx index 3ba63188c1..87e25f8f82 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx @@ -21,7 +21,6 @@ import { DismissableBanner, HeaderTabs, SupportButton, - useApi, } from '@backstage/core'; import CatalogLayout from './CatalogLayout'; import { rootRoute as scaffolderRootRoute } from '@backstage/plugin-scaffolder'; @@ -30,18 +29,13 @@ import Edit from '@material-ui/icons/Edit'; import GitHub from '@material-ui/icons/GitHub'; import Star from '@material-ui/icons/Star'; import StarOutline from '@material-ui/icons/StarBorder'; -import React, { FC, useCallback, useState } from 'react'; +import React, { FC, useCallback } from 'react'; import { Link as RouterLink } from 'react-router-dom'; -import { catalogApiRef } from '../..'; -import { defaultFilter, entityFilters, filterGroups } from '../../data/filters'; -import { findLocationForEntityMeta } from '../../data/utils'; -import { useStarredEntities } from '../../hooks/useStarredEntites'; -import { - CatalogFilter, - CatalogFilterItem, -} from '../CatalogFilter/CatalogFilter'; +import { CatalogFilter } from '../CatalogFilter/CatalogFilter'; import { CatalogTable } from '../CatalogTable/CatalogTable'; -import useStaleWhileRevalidate from 'swr'; +import { useEntitiesStore } from '../../hooks/useEntitiesStore'; +import { findLocationForEntityMeta } from '../../data/utils'; +import { filterGroups } from '../../data/filters'; const useStyles = makeStyles(theme => ({ contentWrapper: { @@ -57,26 +51,18 @@ const useStyles = makeStyles(theme => ({ })); export const CatalogPage: FC<{}> = () => { - const catalogApi = useApi(catalogApiRef); - const { toggleStarredEntity, isStarredEntity } = useStarredEntities(); - - const [selectedFilter, setSelectedFilter] = useState( - defaultFilter, - ); - - const { data: entities, error } = useStaleWhileRevalidate( - ['catalog/all', entityFilters[selectedFilter.id]], - async () => catalogApi.getEntities(), - ); - - const data = - entities?.filter(e => - entityFilters[selectedFilter.id](e, { isStarred: isStarredEntity(e) }), - ) ?? []; + const { + filteredEntities: data, + selectedFilter, + setSelectedFilter, + error, + toggleStarredEntity, + isStarredEntity, + } = useEntitiesStore(); const onFilterSelected = useCallback( selected => setSelectedFilter(selected), - [], + [setSelectedFilter], ); const styles = useStyles(); diff --git a/plugins/catalog/src/data/filters.ts b/plugins/catalog/src/data/filters.ts index 71efa923c6..c20f6b6b14 100644 --- a/plugins/catalog/src/data/filters.ts +++ b/plugins/catalog/src/data/filters.ts @@ -63,14 +63,22 @@ export const filterGroups: CatalogFilterGroup[] = [ type EntityFilter = (entity: Entity, options: EntityFilterOptions) => boolean; -type EntityFilterOptions = { +type EntityFilterOptions = Partial<{ isStarred: boolean; + userId: string; +}>; + +type Owned = { + owner: string; }; export const entityFilters: Record = { - [EntityFilterType.OWNED]: () => false, + [EntityFilterType.OWNED]: (e, { userId }) => { + const owner = (e.spec! as Owned).owner; + return owner === userId; + }, [EntityFilterType.ALL]: () => true, - [EntityFilterType.STARRED]: (_, { isStarred }) => isStarred, + [EntityFilterType.STARRED]: (_, { isStarred }) => isStarred ?? false, }; export const defaultFilter: CatalogFilterItem = filterGroups[0].items[0]; diff --git a/plugins/catalog/src/hooks/useEntitiesStore.ts b/plugins/catalog/src/hooks/useEntitiesStore.ts new file mode 100644 index 0000000000..21ba283dfe --- /dev/null +++ b/plugins/catalog/src/hooks/useEntitiesStore.ts @@ -0,0 +1,82 @@ +/* + * 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 { CatalogFilterItem } from '../components/CatalogFilter/CatalogFilter'; +import { + defaultFilter, + EntityFilterType, + entityFilters, +} from '../data/filters'; +import { useApi } from '@backstage/core'; +import { catalogApiRef } from '..'; +import { useStarredEntities } from './useStarredEntites'; +import { Entity } from '@backstage/catalog-model'; +import useStaleWhileRevalidate from 'swr'; + +export const useEntitiesStore = () => { + const [selectedFilter, setSelectedFilter] = useState( + defaultFilter, + ); + const catalogApi = useApi(catalogApiRef); + const { toggleStarredEntity, isStarredEntity } = useStarredEntities(); + const { data: entities, error } = useStaleWhileRevalidate( + ['catalog/all', entityFilters[selectedFilter.id]], + async () => catalogApi.getEntities(), + ); + const useUser = () => { + const [user] = useState('tools@example.com'); + return user; + }; + const userId = useUser(); + + const [filteredEntities, setFilteredEntities] = useState([]); + const [entitiesByFilter] = useState>( + // Create an empty result for every filter by default + Object.keys(EntityFilterType).reduce( + (res, key) => ({ ...res, [key]: [] }), + {}, + ), + ); + useEffect(() => { + // const dataByFilter = Object.keys(EntityFilterType).reduce( + + // ,{}); + + const data = + entities?.filter((e: Entity) => + entityFilters[selectedFilter.id](e, { + isStarred: isStarredEntity(e), + userId, + }), + ) ?? []; + setFilteredEntities(data); + }, [ + entities, + selectedFilter.id, + isStarredEntity, + userId, + setFilteredEntities, + ]); + return { + filteredEntities, + selectedFilter, + setSelectedFilter, + error, + toggleStarredEntity, + isStarredEntity, + entitiesByFilter, + }; +}; From c8797d6d7a8a7a0d4059e61a8f6da0672389c0b1 Mon Sep 17 00:00:00 2001 From: Nikita Nek Dudnik Date: Mon, 15 Jun 2020 17:45:05 +0200 Subject: [PATCH 003/182] fix(catalog): starred entities count --- .../CatalogFilter/AllServicesCount.tsx | 4 +- .../CatalogFilter/CatalogFilter.tsx | 32 ++++---- .../components/CatalogPage/CatalogPage.tsx | 34 ++++---- plugins/catalog/src/data/filters.ts | 11 +++ plugins/catalog/src/hooks/useEntitiesStore.ts | 82 ------------------- 5 files changed, 46 insertions(+), 117 deletions(-) delete mode 100644 plugins/catalog/src/hooks/useEntitiesStore.ts diff --git a/plugins/catalog/src/components/CatalogFilter/AllServicesCount.tsx b/plugins/catalog/src/components/CatalogFilter/AllServicesCount.tsx index 5ccac8f772..c528b40882 100644 --- a/plugins/catalog/src/components/CatalogFilter/AllServicesCount.tsx +++ b/plugins/catalog/src/components/CatalogFilter/AllServicesCount.tsx @@ -14,13 +14,13 @@ * limitations under the License. */ -import React from 'react'; +import React, { FC } from 'react'; import { useApi } from '@backstage/core'; import { catalogApiRef } from '../../api/types'; import { useAsync } from 'react-use'; import { CircularProgress, useTheme } from '@material-ui/core'; -export const AllServicesCount: React.FC<{}> = () => { +export const AllServicesCount: FC<{}> = () => { const theme = useTheme(); const catalogApi = useApi(catalogApiRef); const { value, loading } = useAsync(() => catalogApi.getEntities()); diff --git a/plugins/catalog/src/components/CatalogFilter/CatalogFilter.tsx b/plugins/catalog/src/components/CatalogFilter/CatalogFilter.tsx index 0abb5f817a..e815d470e1 100644 --- a/plugins/catalog/src/components/CatalogFilter/CatalogFilter.tsx +++ b/plugins/catalog/src/components/CatalogFilter/CatalogFilter.tsx @@ -26,7 +26,8 @@ import { makeStyles, } from '@material-ui/core'; import type { IconComponent } from '@backstage/core'; -import { EntityFilterType } from '../../data/filters'; +import { EntityFilterType, filterGroups } from '../../data/filters'; +import { EntitiesByFilter } from '../../hooks/useEntities'; export type CatalogFilterItem = { id: EntityFilterType; @@ -40,12 +41,6 @@ export type CatalogFilterGroup = { items: CatalogFilterItem[]; }; -export type CatalogFilterProps = { - groups: CatalogFilterGroup[]; - selectedId?: string; - onSelectedChange?: (item: CatalogFilterItem) => void; -}; - const useStyles = makeStyles(theme => ({ root: { backgroundColor: 'rgba(0, 0, 0, .11)', @@ -72,11 +67,16 @@ const useStyles = makeStyles(theme => ({ }, })); -export const CatalogFilter: FC = ({ - groups, - selectedId, - onSelectedChange, +export const CatalogFilter: FC<{ + selectedFilter: EntityFilterType; + onFilterChange: (type: EntityFilterType) => void; + entitiesByFilter: EntitiesByFilter; +}> = ({ + selectedFilter: selectedId, + onFilterChange: setSelectedFilter, + entitiesByFilter, }) => { + const groups = filterGroups; const classes = useStyles(); return ( @@ -92,7 +92,9 @@ export const CatalogFilter: FC = ({ key={item.id} button divider - onClick={() => onSelectedChange?.(item)} + onClick={() => { + setSelectedFilter(item.id); + }} selected={item.id === selectedId} className={classes.menuItem} > @@ -106,11 +108,7 @@ export const CatalogFilter: FC = ({ {item.label} - {typeof item.count === 'function' ? ( - - ) : ( - item.count - )} + {entitiesByFilter[item.id]?.length ?? 0} ))} diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx index 87e25f8f82..0ab4e9c7d4 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx @@ -29,13 +29,16 @@ import Edit from '@material-ui/icons/Edit'; import GitHub from '@material-ui/icons/GitHub'; import Star from '@material-ui/icons/Star'; import StarOutline from '@material-ui/icons/StarBorder'; -import React, { FC, useCallback } from 'react'; +import React, { FC } from 'react'; import { Link as RouterLink } from 'react-router-dom'; import { CatalogFilter } from '../CatalogFilter/CatalogFilter'; import { CatalogTable } from '../CatalogTable/CatalogTable'; -import { useEntitiesStore } from '../../hooks/useEntitiesStore'; +import { useEntities } from '../../hooks/useEntities'; import { findLocationForEntityMeta } from '../../data/utils'; -import { filterGroups } from '../../data/filters'; +import { + getCatalogFilterItemByType, + EntityFilterType, +} from '../../data/filters'; const useStyles = makeStyles(theme => ({ contentWrapper: { @@ -52,19 +55,15 @@ const useStyles = makeStyles(theme => ({ export const CatalogPage: FC<{}> = () => { const { - filteredEntities: data, - selectedFilter, - setSelectedFilter, + entitiesByFilter, + selectedFilter: selectedId, error, toggleStarredEntity, isStarredEntity, - } = useEntitiesStore(); - - const onFilterSelected = useCallback( - selected => setSelectedFilter(selected), - [setSelectedFilter], - ); + setSelectedFilter, + } = useEntities(); + const data = entitiesByFilter[selectedId ?? EntityFilterType.ALL]; const styles = useStyles(); const actions = [ @@ -172,13 +171,16 @@ export const CatalogPage: FC<{}> = () => {
{ + for (const group of filterGroups) { + for (const filter of group.items) { + if (filter.id === filterType) { + return filter; + } + } + } + return null; +}; + type EntityFilter = (entity: Entity, options: EntityFilterOptions) => boolean; type EntityFilterOptions = Partial<{ diff --git a/plugins/catalog/src/hooks/useEntitiesStore.ts b/plugins/catalog/src/hooks/useEntitiesStore.ts deleted file mode 100644 index 21ba283dfe..0000000000 --- a/plugins/catalog/src/hooks/useEntitiesStore.ts +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { useState, useEffect } from 'react'; -import { CatalogFilterItem } from '../components/CatalogFilter/CatalogFilter'; -import { - defaultFilter, - EntityFilterType, - entityFilters, -} from '../data/filters'; -import { useApi } from '@backstage/core'; -import { catalogApiRef } from '..'; -import { useStarredEntities } from './useStarredEntites'; -import { Entity } from '@backstage/catalog-model'; -import useStaleWhileRevalidate from 'swr'; - -export const useEntitiesStore = () => { - const [selectedFilter, setSelectedFilter] = useState( - defaultFilter, - ); - const catalogApi = useApi(catalogApiRef); - const { toggleStarredEntity, isStarredEntity } = useStarredEntities(); - const { data: entities, error } = useStaleWhileRevalidate( - ['catalog/all', entityFilters[selectedFilter.id]], - async () => catalogApi.getEntities(), - ); - const useUser = () => { - const [user] = useState('tools@example.com'); - return user; - }; - const userId = useUser(); - - const [filteredEntities, setFilteredEntities] = useState([]); - const [entitiesByFilter] = useState>( - // Create an empty result for every filter by default - Object.keys(EntityFilterType).reduce( - (res, key) => ({ ...res, [key]: [] }), - {}, - ), - ); - useEffect(() => { - // const dataByFilter = Object.keys(EntityFilterType).reduce( - - // ,{}); - - const data = - entities?.filter((e: Entity) => - entityFilters[selectedFilter.id](e, { - isStarred: isStarredEntity(e), - userId, - }), - ) ?? []; - setFilteredEntities(data); - }, [ - entities, - selectedFilter.id, - isStarredEntity, - userId, - setFilteredEntities, - ]); - return { - filteredEntities, - selectedFilter, - setSelectedFilter, - error, - toggleStarredEntity, - isStarredEntity, - entitiesByFilter, - }; -}; From 6ef5f53fdec4c3e53c96d212e07a230ec45e66e9 Mon Sep 17 00:00:00 2001 From: Nikita Nek Dudnik Date: Mon, 15 Jun 2020 18:00:09 +0200 Subject: [PATCH 004/182] chore(catalog): clean up code --- .../CatalogFilter/AllServicesCount.tsx | 2 +- .../CatalogFilter/CatalogFilter.tsx | 2 +- .../components/CatalogFilter/OwnedCount.tsx | 23 ----- .../CatalogFilter/StarredCount.test.tsx | 35 -------- .../components/CatalogFilter/StarredCount.tsx | 23 ----- plugins/catalog/src/data/filters.ts | 5 -- plugins/catalog/src/hooks/useEntities.ts | 88 +++++++++++++++++++ 7 files changed, 90 insertions(+), 88 deletions(-) delete mode 100644 plugins/catalog/src/components/CatalogFilter/OwnedCount.tsx delete mode 100644 plugins/catalog/src/components/CatalogFilter/StarredCount.test.tsx delete mode 100644 plugins/catalog/src/components/CatalogFilter/StarredCount.tsx create mode 100644 plugins/catalog/src/hooks/useEntities.ts diff --git a/plugins/catalog/src/components/CatalogFilter/AllServicesCount.tsx b/plugins/catalog/src/components/CatalogFilter/AllServicesCount.tsx index c528b40882..b4d90c249a 100644 --- a/plugins/catalog/src/components/CatalogFilter/AllServicesCount.tsx +++ b/plugins/catalog/src/components/CatalogFilter/AllServicesCount.tsx @@ -29,5 +29,5 @@ export const AllServicesCount: FC<{}> = () => { return ; } - return {value?.length ?? '-'}; + return {value ?? length ?? '-'}; }; diff --git a/plugins/catalog/src/components/CatalogFilter/CatalogFilter.tsx b/plugins/catalog/src/components/CatalogFilter/CatalogFilter.tsx index e815d470e1..f11494079f 100644 --- a/plugins/catalog/src/components/CatalogFilter/CatalogFilter.tsx +++ b/plugins/catalog/src/components/CatalogFilter/CatalogFilter.tsx @@ -108,7 +108,7 @@ export const CatalogFilter: FC<{ {item.label} - {entitiesByFilter[item.id]?.length ?? 0} + {entitiesByFilter[item.id]?.length ?? '-'} ))} diff --git a/plugins/catalog/src/components/CatalogFilter/OwnedCount.tsx b/plugins/catalog/src/components/CatalogFilter/OwnedCount.tsx deleted file mode 100644 index a48019c48a..0000000000 --- a/plugins/catalog/src/components/CatalogFilter/OwnedCount.tsx +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React, { FC } from 'react'; -import { useStarredEntities } from '../../hooks/useStarredEntites'; - -export const StarredCount: FC<{}> = () => { - const { starredEntities } = useStarredEntities(); - return {starredEntities.size}; -}; diff --git a/plugins/catalog/src/components/CatalogFilter/StarredCount.test.tsx b/plugins/catalog/src/components/CatalogFilter/StarredCount.test.tsx deleted file mode 100644 index 546d00e854..0000000000 --- a/plugins/catalog/src/components/CatalogFilter/StarredCount.test.tsx +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React from 'react'; -import { render } from '@testing-library/react'; -import { wrapInTestApp } from '@backstage/test-utils'; -import { StarredCount } from './StarredCount'; -import * as Hooks from '../../hooks/useStarredEntites'; - -describe('Starred Count', () => { - it('should render the count returned from the hook', async () => { - jest.spyOn(Hooks, 'useStarredEntities').mockReturnValue({ - starredEntities: new Set(['id1', 'id2', 'id3', 'id4']), - isStarredEntity: () => false, - toggleStarredEntity: () => undefined, - }); - - const { findByText } = render(wrapInTestApp()); - - expect(await findByText('4')).toBeInTheDocument(); - }); -}); diff --git a/plugins/catalog/src/components/CatalogFilter/StarredCount.tsx b/plugins/catalog/src/components/CatalogFilter/StarredCount.tsx deleted file mode 100644 index a48019c48a..0000000000 --- a/plugins/catalog/src/components/CatalogFilter/StarredCount.tsx +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React, { FC } from 'react'; -import { useStarredEntities } from '../../hooks/useStarredEntites'; - -export const StarredCount: FC<{}> = () => { - const { starredEntities } = useStarredEntities(); - return {starredEntities.size}; -}; diff --git a/plugins/catalog/src/data/filters.ts b/plugins/catalog/src/data/filters.ts index 226ccbc683..6230b3c8fc 100644 --- a/plugins/catalog/src/data/filters.ts +++ b/plugins/catalog/src/data/filters.ts @@ -17,12 +17,10 @@ import { Entity } from '@backstage/catalog-model'; import SettingsIcon from '@material-ui/icons/Settings'; import StarIcon from '@material-ui/icons/Star'; -import { AllServicesCount } from '../components/CatalogFilter/AllServicesCount'; import { CatalogFilterGroup, CatalogFilterItem, } from '../components/CatalogFilter/CatalogFilter'; -import { StarredCount } from '../components/CatalogFilter/StarredCount'; export enum EntityFilterType { ALL = 'ALL', @@ -37,13 +35,11 @@ export const filterGroups: CatalogFilterGroup[] = [ { id: EntityFilterType.OWNED, label: 'Owned', - count: 0, icon: SettingsIcon, }, { id: EntityFilterType.STARRED, label: 'Starred', - count: StarredCount, icon: StarIcon, }, ], @@ -55,7 +51,6 @@ export const filterGroups: CatalogFilterGroup[] = [ { id: EntityFilterType.ALL, label: 'All Services', - count: AllServicesCount, }, ], }, diff --git a/plugins/catalog/src/hooks/useEntities.ts b/plugins/catalog/src/hooks/useEntities.ts new file mode 100644 index 0000000000..8dca72f095 --- /dev/null +++ b/plugins/catalog/src/hooks/useEntities.ts @@ -0,0 +1,88 @@ +/* + * 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, useMemo } from 'react'; +import { EntityFilterType, entityFilters } from '../data/filters'; +import { useApi } from '@backstage/core'; +import { catalogApiRef } from '..'; +import { useStarredEntities } from './useStarredEntites'; +import { Entity } from '@backstage/catalog-model'; +import useStaleWhileRevalidate from 'swr'; + +export type EntitiesByFilter = Record; + +type UseEntities = { + selectedFilter: EntityFilterType | undefined; + setSelectedFilter: (f: EntityFilterType) => void; + error: Error | null; + toggleStarredEntity: any; + isStarredEntity: (e: Entity) => boolean; + entitiesByFilter: EntitiesByFilter; +}; + +export const useEntities = (): UseEntities => { + const [selectedFilter, setSelectedFilter] = useState< + EntityFilterType | undefined + >(); + const catalogApi = useApi(catalogApiRef); + const { toggleStarredEntity, isStarredEntity } = useStarredEntities(); + const { data: entities, error } = useStaleWhileRevalidate( + ['catalog/all', entityFilters[selectedFilter ?? EntityFilterType.ALL]], + async () => catalogApi.getEntities(), + ); + const useUser = () => { + const [user] = useState('tools@example.com'); + return user; + }; + const userId = useUser(); + + const entitiesByFilter = useMemo(() => { + const filterEntities = ( + ents: Entity[] | undefined, + filterId: EntityFilterType, + isStarred: (e: Entity) => boolean, + user: string, + ) => { + return ents?.filter((e: Entity) => + entityFilters[filterId](e, { + isStarred: isStarred(e), + userId: user, + }), + ); + }; + const data = Object.keys(EntityFilterType).reduce( + (res, key) => ({ + ...res, + [key]: filterEntities( + entities, + key as EntityFilterType, + isStarredEntity, + userId, + ), + }), + {} as EntitiesByFilter, + ); + return data; + }, [entities, isStarredEntity, userId]); + + return { + selectedFilter, + setSelectedFilter, + error, + toggleStarredEntity, + isStarredEntity, + entitiesByFilter, + }; +}; From 806790ea8907f5bc423e13d7dd46eb50fa7e194c Mon Sep 17 00:00:00 2001 From: Nikita Nek Dudnik Date: Tue, 16 Jun 2020 09:24:32 +0200 Subject: [PATCH 005/182] feat(catalog): use identity API for filtering owned entites --- plugins/catalog/src/hooks/useEntities.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/plugins/catalog/src/hooks/useEntities.ts b/plugins/catalog/src/hooks/useEntities.ts index 8dca72f095..e23ec72910 100644 --- a/plugins/catalog/src/hooks/useEntities.ts +++ b/plugins/catalog/src/hooks/useEntities.ts @@ -15,7 +15,7 @@ */ import { useState, useMemo } from 'react'; import { EntityFilterType, entityFilters } from '../data/filters'; -import { useApi } from '@backstage/core'; +import { useApi, identifyApiRef } from '@backstage/core'; import { catalogApiRef } from '..'; import { useStarredEntities } from './useStarredEntites'; import { Entity } from '@backstage/catalog-model'; @@ -42,11 +42,9 @@ export const useEntities = (): UseEntities => { ['catalog/all', entityFilters[selectedFilter ?? EntityFilterType.ALL]], async () => catalogApi.getEntities(), ); - const useUser = () => { - const [user] = useState('tools@example.com'); - return user; - }; - const userId = useUser(); + + const indentityApi = useApi(identifyApiRef); + const userId = indentityApi.getUserId(); const entitiesByFilter = useMemo(() => { const filterEntities = ( From 3b19d7f37abcce4233ea18d17e9e0c23b50df65e Mon Sep 17 00:00:00 2001 From: Nikita Nek Dudnik Date: Tue, 16 Jun 2020 11:05:17 +0200 Subject: [PATCH 006/182] feat(catalog): add placeholder for identity API implementation --- packages/app/src/apis.ts | 4 +++ .../src/apis/definitions/IdentityApi.ts | 2 +- .../implementations/IdentityApi/Identity.ts | 25 +++++++++++++++++++ .../apis/implementations/IdentityApi/index.ts | 16 ++++++++++++ .../src/apis/implementations/index.ts | 1 + packages/storybook/.storybook/apis.js | 4 +++ .../CatalogPage/CatalogPage.test.tsx | 2 ++ plugins/catalog/src/hooks/useEntities.ts | 4 +-- 8 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 packages/core-api/src/apis/implementations/IdentityApi/Identity.ts create mode 100644 packages/core-api/src/apis/implementations/IdentityApi/index.ts diff --git a/packages/app/src/apis.ts b/packages/app/src/apis.ts index 9bfc618216..a6d9987819 100644 --- a/packages/app/src/apis.ts +++ b/packages/app/src/apis.ts @@ -32,6 +32,8 @@ import { githubAuthApiRef, storageApiRef, WebStorage, + identityApiRef, + MockIdentity, } from '@backstage/core'; import { @@ -52,6 +54,8 @@ export const apis = (config: ConfigApi) => { const builder = ApiRegistry.builder(); + builder.add(identityApiRef, new MockIdentity()); + const alertApi = builder.add(alertApiRef, new AlertApiForwarder()); const errorApi = builder.add( errorApiRef, diff --git a/packages/core-api/src/apis/definitions/IdentityApi.ts b/packages/core-api/src/apis/definitions/IdentityApi.ts index bbfab0ef35..4c9ccfdbf6 100644 --- a/packages/core-api/src/apis/definitions/IdentityApi.ts +++ b/packages/core-api/src/apis/definitions/IdentityApi.ts @@ -40,7 +40,7 @@ export type IdentityApi = { // TODO: getProfile(): Promise - We want this to be async when added, but needs more work. }; -export const identifyApiRef = createApiRef({ +export const identityApiRef = createApiRef({ id: 'core.identity', description: 'Provides access to the identity of the signed in user', }); diff --git a/packages/core-api/src/apis/implementations/IdentityApi/Identity.ts b/packages/core-api/src/apis/implementations/IdentityApi/Identity.ts new file mode 100644 index 0000000000..6f2035d1b8 --- /dev/null +++ b/packages/core-api/src/apis/implementations/IdentityApi/Identity.ts @@ -0,0 +1,25 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { IdentityApi } from '../..'; + +export class MockIdentity implements IdentityApi { + getUserId(): string { + return ''; + } + getIdToken(): string | undefined { + throw new Error('Method not implemented.'); + } +} diff --git a/packages/core-api/src/apis/implementations/IdentityApi/index.ts b/packages/core-api/src/apis/implementations/IdentityApi/index.ts new file mode 100644 index 0000000000..1e57036685 --- /dev/null +++ b/packages/core-api/src/apis/implementations/IdentityApi/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export * from './Identity'; diff --git a/packages/core-api/src/apis/implementations/index.ts b/packages/core-api/src/apis/implementations/index.ts index e6d23fee21..885082ce09 100644 --- a/packages/core-api/src/apis/implementations/index.ts +++ b/packages/core-api/src/apis/implementations/index.ts @@ -26,3 +26,4 @@ export * from './ConfigApi'; export * from './ErrorApi'; export * from './OAuthRequestApi'; export * from './StorageApi'; +export * from './IdentityApi'; diff --git a/packages/storybook/.storybook/apis.js b/packages/storybook/.storybook/apis.js index d3150400cf..9dc1e2bad2 100644 --- a/packages/storybook/.storybook/apis.js +++ b/packages/storybook/.storybook/apis.js @@ -11,6 +11,8 @@ import { ErrorAlerter, GoogleAuth, GithubAuth, + identityApiRef, + MockIdentity, } from '@backstage/core'; const builder = ApiRegistry.builder(); @@ -19,6 +21,8 @@ const alertApi = builder.add(alertApiRef, new AlertApiForwarder()); builder.add(errorApiRef, new ErrorAlerter(alertApi, new ErrorApiForwarder())); +builder.add(identityApiRef, new MockIdentity()); + const oauthRequestApi = builder.add( oauthRequestApiRef, new OAuthRequestManager(), diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx index 2e1989323c..4cd0cae7af 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx @@ -20,6 +20,8 @@ import { errorApiRef, storageApiRef, WebStorage, + IdentityApi, + identityApiRef, } from '@backstage/core'; import { MockErrorApi, wrapInTestApp } from '@backstage/test-utils'; import { render } from '@testing-library/react'; diff --git a/plugins/catalog/src/hooks/useEntities.ts b/plugins/catalog/src/hooks/useEntities.ts index e23ec72910..1530be6f7c 100644 --- a/plugins/catalog/src/hooks/useEntities.ts +++ b/plugins/catalog/src/hooks/useEntities.ts @@ -15,7 +15,7 @@ */ import { useState, useMemo } from 'react'; import { EntityFilterType, entityFilters } from '../data/filters'; -import { useApi, identifyApiRef } from '@backstage/core'; +import { useApi, identityApiRef } from '@backstage/core'; import { catalogApiRef } from '..'; import { useStarredEntities } from './useStarredEntites'; import { Entity } from '@backstage/catalog-model'; @@ -43,7 +43,7 @@ export const useEntities = (): UseEntities => { async () => catalogApi.getEntities(), ); - const indentityApi = useApi(identifyApiRef); + const indentityApi = useApi(identityApiRef); const userId = indentityApi.getUserId(); const entitiesByFilter = useMemo(() => { From bbef36c91a41751b26c68fc9f2d9d45f6b49aff2 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 17 Jun 2020 10:49:26 +0200 Subject: [PATCH 007/182] chore(scaffolder): Remove the old storage API's as the catalog api is now in charge of thestorage of templkates --- packages/backend/src/plugins/scaffolder.ts | 4 +- plugins/scaffolder-backend/package.json | 1 + .../src/scaffolder/index.ts | 2 - .../src/scaffolder/storage/disk.test.ts | 84 ---------------- .../src/scaffolder/storage/disk.ts | 99 ------------------- .../src/scaffolder/storage/index.test.ts | 58 ----------- .../src/scaffolder/storage/index.ts | 38 ------- .../src/scaffolder/storage/types.ts | 24 +++++ .../src/service/standaloneServer.ts | 10 +- 9 files changed, 28 insertions(+), 292 deletions(-) delete mode 100644 plugins/scaffolder-backend/src/scaffolder/storage/disk.test.ts delete mode 100644 plugins/scaffolder-backend/src/scaffolder/storage/disk.ts delete mode 100644 plugins/scaffolder-backend/src/scaffolder/storage/index.test.ts create mode 100644 plugins/scaffolder-backend/src/scaffolder/storage/types.ts diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts index 08e700bc74..35840daa5b 100644 --- a/packages/backend/src/plugins/scaffolder.ts +++ b/packages/backend/src/plugins/scaffolder.ts @@ -17,13 +17,11 @@ import { CookieCutter, createRouter, - DiskStorage, } from '@backstage/plugin-scaffolder-backend'; import type { PluginEnvironment } from '../types'; export default async function createPlugin({ logger }: PluginEnvironment) { - const storage = new DiskStorage({ logger }); const templater = new CookieCutter(); - return await createRouter({ storage, templater, logger }); + return await createRouter({ storage: null, templater, logger }); } diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index 3d8d95164e..907efde362 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -22,6 +22,7 @@ }, "dependencies": { "@backstage/backend-common": "^0.1.1-alpha.8", + "@backstage/catalog-model": "^0.1.1-alpha.8", "compression": "^1.7.4", "cors": "^2.8.5", "dockerode": "^3.2.0", diff --git a/plugins/scaffolder-backend/src/scaffolder/index.ts b/plugins/scaffolder-backend/src/scaffolder/index.ts index a665b32ead..29a3d99ef0 100644 --- a/plugins/scaffolder-backend/src/scaffolder/index.ts +++ b/plugins/scaffolder-backend/src/scaffolder/index.ts @@ -13,8 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export * from './storage'; export * from './templater'; -export * from './storage/disk'; export * from './templater/cookiecutter'; diff --git a/plugins/scaffolder-backend/src/scaffolder/storage/disk.test.ts b/plugins/scaffolder-backend/src/scaffolder/storage/disk.test.ts deleted file mode 100644 index 9cebb0d380..0000000000 --- a/plugins/scaffolder-backend/src/scaffolder/storage/disk.test.ts +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { DiskStorage } from './disk'; -import * as path from 'path'; - -describe('Disk Storage', () => { - it('should load a simple template from a simple directory', async () => { - const testTemplateDir = path.resolve( - __dirname, - '../../../test/mock-simple-template-dir', - ); - const templateInfo = require(`${testTemplateDir}/mock-template/template-info.json`); - - const repository = new DiskStorage({ directory: testTemplateDir }); - - await repository.reindex(); - - const templates = await repository.list(); - - expect(templates).toHaveLength(1); - expect(templates[0].id).toBe(templateInfo.id); - expect(templates[0].name).toBe(templateInfo.name); - expect(templates[0].description).toBe(templateInfo.description); - expect(templates[0].ownerId).toBe(templateInfo.ownerId); - }); - - it('should successfully load multiple templates from the same folder', async () => { - const testTemplateDir = path.resolve( - __dirname, - '../../../test/mock-multiple-templates-dir', - ); - - const repository = new DiskStorage({ directory: testTemplateDir }); - - await repository.reindex(); - - const templates = await repository.list(); - - expect(templates).toHaveLength(2); - }); - - it('should return empty array when there are no templates', async () => { - const testTemplateDir = path.resolve( - __dirname, - '/some-folder-that-deffo-does-not-exist', - ); - - const repository = new DiskStorage({ directory: testTemplateDir }); - - await repository.reindex(); - - const templates = await repository.list(); - - expect(templates).toHaveLength(0); - }); - - it('should be able to handle templates with invalid json and ignore them from the returned array', async () => { - const testTemplateDir = path.resolve( - __dirname, - '../../../test/mock-failing-template-dir', - ); - - const repository = new DiskStorage({ directory: testTemplateDir }); - - await repository.reindex(); - - const templates = await repository.list(); - - expect(templates).toHaveLength(1); - }); -}); diff --git a/plugins/scaffolder-backend/src/scaffolder/storage/disk.ts b/plugins/scaffolder-backend/src/scaffolder/storage/disk.ts deleted file mode 100644 index 28236cb86f..0000000000 --- a/plugins/scaffolder-backend/src/scaffolder/storage/disk.ts +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import globby from 'globby'; -import fs from 'fs-extra'; -import { Template, StorageBase as Base } from '.'; -import { Logger } from 'winston'; - -interface DiskIndexEntry { - contents: Template; - location: string; -} - -export class DiskStorage implements Base { - private repository: Template[] = []; - private localIndex: DiskIndexEntry[] = []; - - private repoDir: string; - private logger?: Logger; - constructor({ - directory = `${__dirname}/../../../sample-templates`, - logger, - }: { - directory?: string; - logger?: Logger; - }) { - this.repoDir = directory; - this.logger = logger; - } - - public async list(): Promise { - if (this.repository.length === 0) { - await this.reindex(); - } - - return this.repository; - } - - public async reindex(): Promise { - this.localIndex = await this.index(); - this.repository = this.localIndex.map(({ contents }) => contents); - } - - public async prepare(templateId: string): Promise { - const template = this.localIndex.find( - ({ contents }) => contents.id === templateId, - ); - - if (!template) { - throw new Error('Template no found'); - } - - const tempDir = await fs.promises.mkdtemp(templateId); - await fs.copy(template.location, tempDir); - return tempDir; - } - - private async index(): Promise { - const matches = await globby(`${this.repoDir}/**/template-info.json`); - - const fileContents: Array<{ - location: string; - contents: string; - }> = await Promise.all( - matches.map(async (location: string) => ({ - location, - contents: await fs.readFile(location, 'utf-8'), - })), - ); - - const validFiles: DiskIndexEntry[] = []; - - for (const file of fileContents) { - try { - const contents: Template = JSON.parse(file.contents); - validFiles.push({ location: file.location, contents }); - } catch (ex) { - this.logger?.error('Failure parsing JSON for template', { - path: file.location, - }); - } - } - - return validFiles; - } -} diff --git a/plugins/scaffolder-backend/src/scaffolder/storage/index.test.ts b/plugins/scaffolder-backend/src/scaffolder/storage/index.test.ts deleted file mode 100644 index b8e3131b63..0000000000 --- a/plugins/scaffolder-backend/src/scaffolder/storage/index.test.ts +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { StorageBase, createStorage } from '.'; -import * as winston from 'winston'; - -describe('Storage Interface Test', () => { - const mockStore = new (class MockStorage implements StorageBase { - list = jest.fn(); - prepare = jest.fn(); - reindex = jest.fn(); - - public reset = () => { - this.list.mockReset(); - this.prepare.mockReset(); - this.reindex.mockReset(); - }; - })(); - - const logger = winston.createLogger(); - - afterEach(() => mockStore.reset()); - - it('should call list of the set repo when calling list', async () => { - const store = createStorage({ store: mockStore, logger }); - await store.list(); - - expect(mockStore.list).toHaveBeenCalled(); - }); - - it('should reindex on the repo when calling reindex', async () => { - const store = createStorage({ store: mockStore, logger }); - - await store.reindex(); - - expect(mockStore.reindex).toHaveBeenCalled(); - }); - - it('should call prepare with the correct id when calling prepare', async () => { - const store = createStorage({ store: mockStore, logger }); - - await store.prepare('testid'); - - expect(mockStore.prepare).toHaveBeenCalledWith('testid'); - }); -}); diff --git a/plugins/scaffolder-backend/src/scaffolder/storage/index.ts b/plugins/scaffolder-backend/src/scaffolder/storage/index.ts index 4d16224ef7..f3b69cc361 100644 --- a/plugins/scaffolder-backend/src/scaffolder/storage/index.ts +++ b/plugins/scaffolder-backend/src/scaffolder/storage/index.ts @@ -1,5 +1,3 @@ -import { Logger } from 'winston'; - /* * Copyright 2020 Spotify AB * @@ -15,39 +13,3 @@ import { Logger } from 'winston'; * See the License for the specific language governing permissions and * limitations under the License. */ -export interface Template { - id: string; - name: string; - description: string; - ownerId: string; -} - -export abstract class StorageBase { - // lists all templates available - abstract async list(): Promise; - // can be used to build an index of the available templates; - abstract async reindex(): Promise; - // returns a directory to run the templaterin - abstract async prepare(id: string): Promise; -} - -export interface StorageConfig { - store?: StorageBase; - logger?: Logger; -} - -class Storage implements StorageBase { - store?: StorageBase; - - constructor({ store }: StorageConfig) { - this.store = store; - } - - list = () => this.store!.list(); - prepare = (id: string) => this.store!.prepare(id); - reindex = () => this.store!.reindex(); -} - -export const createStorage = (storageConfig: StorageConfig): StorageBase => { - return new Storage(storageConfig); -}; diff --git a/plugins/scaffolder-backend/src/scaffolder/storage/types.ts b/plugins/scaffolder-backend/src/scaffolder/storage/types.ts new file mode 100644 index 0000000000..80c860a911 --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/storage/types.ts @@ -0,0 +1,24 @@ +/* + * 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 type { TemplateEntityV1alpha1 } from '@backstage/catalog-model'; + +export type StorageBase = { + /** + * + * @param id + */ + prepare(template: TemplateEntityV1alpha1): Promise; +}; diff --git a/plugins/scaffolder-backend/src/service/standaloneServer.ts b/plugins/scaffolder-backend/src/service/standaloneServer.ts index 7f553bf4bf..cad937d304 100644 --- a/plugins/scaffolder-backend/src/service/standaloneServer.ts +++ b/plugins/scaffolder-backend/src/service/standaloneServer.ts @@ -16,12 +16,7 @@ import { Server } from 'http'; import { Logger } from 'winston'; -import { - createStorage, - createTemplater, - DiskStorage, - CookieCutter, -} from '../scaffolder'; +import { createTemplater, CookieCutter } from '../scaffolder'; import { createServiceBuilder, useHotMemoize } from '@backstage/backend-common'; import { createRouter } from './router'; export interface ServerOptions { @@ -34,12 +29,11 @@ export async function startStandaloneServer( options: ServerOptions, ): Promise { const logger = options.logger.child({ service: 'scaffolder-backend' }); - const store = useHotMemoize(module, () => new DiskStorage({ logger })); const templater = new CookieCutter(); logger.debug('Creating application...'); const router = await createRouter({ - storage: createStorage({ store, logger }), + storage: null, templater: createTemplater({ templater }), logger, }); From 55e6414d858f1cc52091b3f05cfcc71fde6e8ff1 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 17 Jun 2020 11:56:42 +0200 Subject: [PATCH 008/182] feat(scaffolder): Moving the storage to more task based appraches, and creating preparers --- packages/catalog-model/src/entity/Entity.ts | 2 +- .../src/kinds/TemplateEntityV1alpha1.ts | 2 + .../src/scaffolder/index.ts | 2 +- .../{storage/index.ts => prepare/file.ts} | 0 .../{storage/types.ts => prepare/index.ts} | 11 +--- .../src/scaffolder/prepare/preparers.ts | 51 ++++++++++++++++++ .../src/scaffolder/prepare/types.ts | 32 ++++++++++++ .../scaffolder-backend/src/service/router.ts | 52 +++++++++++++------ 8 files changed, 126 insertions(+), 26 deletions(-) rename plugins/scaffolder-backend/src/scaffolder/{storage/index.ts => prepare/file.ts} (100%) rename plugins/scaffolder-backend/src/scaffolder/{storage/types.ts => prepare/index.ts} (75%) create mode 100644 plugins/scaffolder-backend/src/scaffolder/prepare/preparers.ts create mode 100644 plugins/scaffolder-backend/src/scaffolder/prepare/types.ts diff --git a/packages/catalog-model/src/entity/Entity.ts b/packages/catalog-model/src/entity/Entity.ts index 70d2ba4d2b..d4f10ab883 100644 --- a/packages/catalog-model/src/entity/Entity.ts +++ b/packages/catalog-model/src/entity/Entity.ts @@ -48,7 +48,7 @@ export type Entity = { * @see https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#objectmeta-v1-meta * @see https://kubernetes.io/docs/concepts/overview/working-with-objects/kubernetes-objects/ */ -export type EntityMeta = { +export type EntityMeta = Record & { /** * A globally unique ID for the entity. * diff --git a/packages/catalog-model/src/kinds/TemplateEntityV1alpha1.ts b/packages/catalog-model/src/kinds/TemplateEntityV1alpha1.ts index 090c892294..0bef220a63 100644 --- a/packages/catalog-model/src/kinds/TemplateEntityV1alpha1.ts +++ b/packages/catalog-model/src/kinds/TemplateEntityV1alpha1.ts @@ -26,6 +26,7 @@ export interface TemplateEntityV1alpha1 extends Entity { kind: typeof KIND; spec: { type: string; + path?: string; }; } @@ -39,6 +40,7 @@ export class TemplateEntityV1alpha1Policy implements EntityPolicy { spec: yup .object({ type: yup.string().required().min(1), + path: yup.string(), }) .required(), }); diff --git a/plugins/scaffolder-backend/src/scaffolder/index.ts b/plugins/scaffolder-backend/src/scaffolder/index.ts index 29a3d99ef0..4cc3f21a9d 100644 --- a/plugins/scaffolder-backend/src/scaffolder/index.ts +++ b/plugins/scaffolder-backend/src/scaffolder/index.ts @@ -14,5 +14,5 @@ * limitations under the License. */ export * from './templater'; - +export * from './prepare'; export * from './templater/cookiecutter'; diff --git a/plugins/scaffolder-backend/src/scaffolder/storage/index.ts b/plugins/scaffolder-backend/src/scaffolder/prepare/file.ts similarity index 100% rename from plugins/scaffolder-backend/src/scaffolder/storage/index.ts rename to plugins/scaffolder-backend/src/scaffolder/prepare/file.ts diff --git a/plugins/scaffolder-backend/src/scaffolder/storage/types.ts b/plugins/scaffolder-backend/src/scaffolder/prepare/index.ts similarity index 75% rename from plugins/scaffolder-backend/src/scaffolder/storage/types.ts rename to plugins/scaffolder-backend/src/scaffolder/prepare/index.ts index 80c860a911..748e5cebc7 100644 --- a/plugins/scaffolder-backend/src/scaffolder/storage/types.ts +++ b/plugins/scaffolder-backend/src/scaffolder/prepare/index.ts @@ -13,12 +13,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import type { TemplateEntityV1alpha1 } from '@backstage/catalog-model'; - -export type StorageBase = { - /** - * - * @param id - */ - prepare(template: TemplateEntityV1alpha1): Promise; -}; +export * from './preparers'; +export * from './types'; diff --git a/plugins/scaffolder-backend/src/scaffolder/prepare/preparers.ts b/plugins/scaffolder-backend/src/scaffolder/prepare/preparers.ts new file mode 100644 index 0000000000..dc3e318926 --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/prepare/preparers.ts @@ -0,0 +1,51 @@ +/* + * 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 { PreparerBase, RemoteLocation, PreparerBuilder } from './types'; +import { TemplateEntityV1alpha1 } from '@backstage/catalog-model'; + +export class Preparers implements PreparerBuilder { + private preparerMap = new Map(); + + register(key: RemoteLocation, processor: PreparerBase) { + this.preparerMap.set(key, processor); + } + + get(template: TemplateEntityV1alpha1): PreparerBase { + const preparerKey = this.getPreparerKeyFromEntity(template); + const preparer = this.preparerMap.get(preparerKey); + + if (!preparer) { + throw new Error(`No preparer registered for type ${preparerKey}`); + } + + return preparer; + } + + private getPreparerKeyFromEntity( + entity: TemplateEntityV1alpha1, + ): RemoteLocation { + const annotation = + entity.metadata.annotations?.['backstage.io/managed-by-location'] ?? ''; + const [key] = annotation?.split(':'); + + if (!key) { + throw new Error('Failed to parse the location data'); + } + + return key as RemoteLocation; + } +} diff --git a/plugins/scaffolder-backend/src/scaffolder/prepare/types.ts b/plugins/scaffolder-backend/src/scaffolder/prepare/types.ts new file mode 100644 index 0000000000..1c703d10fd --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/prepare/types.ts @@ -0,0 +1,32 @@ +/* + * 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 type { TemplateEntityV1alpha1 } from '@backstage/catalog-model'; + +export type PreparerBase = { + /** + * Given an Entity definition from the Service Catalog, go and prepare a directory + * with contents from the remote location in temporary storage and return the path + * @param template The template entity from the Service Catalog + */ + prepare(template: TemplateEntityV1alpha1): Promise; +}; + +export type PreparerBuilder = { + register(key: RemoteLocation, preparer: PreparerBase): void; + get(key: TemplateEntityV1alpha1): PreparerBase; +}; + +export type RemoteLocation = 'file'; diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index 97c177b915..57ac1413fc 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -17,10 +17,11 @@ import { Logger } from 'winston'; import Router from 'express-promise-router'; import express from 'express'; -import { StorageBase, TemplaterBase } from '../scaffolder'; +import { PreparerBuilder, TemplaterBase } from '../scaffolder'; +import { TemplateEntityV1alpha1 } from '@backstage/catalog-model'; export interface RouterOptions { - storage: StorageBase; + preparers: PreparerBuilder; templater: TemplaterBase; logger: Logger; } @@ -29,22 +30,43 @@ export async function createRouter( options: RouterOptions, ): Promise { const router = Router(); - const { storage, templater, logger: parentLogger } = options; + const { preparers, templater, logger: parentLogger } = options; const logger = parentLogger.child({ plugin: 'scaffolder' }); - router - .get('/v1/templates', async (_, res) => { - const templates = await storage.list(); - res.status(200).json(templates); - }) - .post('/v1/jobs', async (_, res) => { - // TODO(blam): Actually make this function work - const mock = 'templateid'; - res.status(201).json({ accepted: true }); + router.post('/v1/jobs', async (_, res) => { + // TODO(blam): Actually make this function work + // res.status(201).json({ accepted: true }); - const path = await storage.prepare(mock); - await templater.run({ directory: path, values: { componentId: 'test' } }); - }); + const mockEntity: TemplateEntityV1alpha1 = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Template', + metadata: { + annotations: { + 'backstage.io/managed-by-location': + 'file:/Users/blam/dev/spotify/backstage/plugins/scaffolder-backend/sample-templates/react-ssr-template/template.yaml', + }, + name: 'react-ssr-template', + title: 'React SSR Template', + description: + 'Next.js application skeleton for creating isomorphic web applications.', + uid: '7357f4c5-aa58-4a1e-9670-18931eef771f', + etag: 'YWUxZWQyY2EtZDkxMC00MDM0LWI0ODAtMDgwMWY0YzdlMWIw', + generation: 1, + }, + spec: { + type: 'cookiecutter', + path: '.', + }, + }; + + // fetch the entity from service catalog here. + const preparer = preparers.get(mockEntity); + + // + const path = await preparer.prepare(mockEntity); + + await templater.run({ directory: path, values: { componentId: 'test' } }); + }); const app = express(); app.set('logger', logger); From 92629f5f5de6aa423e31f1126f16cdeb8b9db63d Mon Sep 17 00:00:00 2001 From: Nikita Nek Dudnik Date: Wed, 17 Jun 2020 03:00:30 +0200 Subject: [PATCH 009/182] fix(catalog): tests and rebase on master --- plugins/catalog/package.json | 2 +- .../CatalogFilter/CatalogFilter.test.tsx | 84 +++++++++++++++---- .../CatalogFilter/CatalogFilter.tsx | 5 +- .../CatalogPage/CatalogPage.test.tsx | 45 ++++++++-- .../components/CatalogPage/CatalogPage.tsx | 2 + yarn.lock | 25 +++++- 6 files changed, 140 insertions(+), 23 deletions(-) diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index d7329e4b2f..500adaf0b5 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -43,7 +43,7 @@ "@backstage/dev-utils": "^0.1.1-alpha.8", "@backstage/test-utils": "^0.1.1-alpha.8", "@testing-library/jest-dom": "^5.7.0", - "@testing-library/react": "^9.3.2", + "@testing-library/react": "^10.2.1", "@testing-library/react-hooks": "^3.3.0", "@testing-library/user-event": "^10.2.4", "@types/jest": "^25.2.2", diff --git a/plugins/catalog/src/components/CatalogFilter/CatalogFilter.test.tsx b/plugins/catalog/src/components/CatalogFilter/CatalogFilter.test.tsx index b78450d549..a19459b236 100644 --- a/plugins/catalog/src/components/CatalogFilter/CatalogFilter.test.tsx +++ b/plugins/catalog/src/components/CatalogFilter/CatalogFilter.test.tsx @@ -15,19 +15,60 @@ */ import React from 'react'; -import { render, fireEvent } from '@testing-library/react'; +import { render, fireEvent, waitFor, screen } from '@testing-library/react'; import { wrapInTestApp } from '@backstage/test-utils'; import { CatalogFilter, CatalogFilterGroup } from './CatalogFilter'; import { EntityFilterType } from '../../data/filters'; describe('Catalog Filter', () => { + const comp1 = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'my-component-1', + }, + spec: { + owner: 'team', + }, + }; + const comp2 = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'my-component-2', + }, + spec: { + owner: 'team', + }, + }; + const comp3 = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'my-component-3', + }, + spec: { + owner: '', + }, + }; + const defaultFilterProps = { + selectedFilter: EntityFilterType.ALL, + onFilterChange: (type: EntityFilterType) => type, + entitiesByFilter: { + [EntityFilterType.ALL]: [comp1, comp2, comp3], + [EntityFilterType.STARRED]: [comp1], + [EntityFilterType.OWNED]: [comp1], + }, + }; it('should render the different groups', async () => { const mockGroups: CatalogFilterGroup[] = [ { name: 'Test Group 1', items: [] }, { name: 'Test Group 2', items: [] }, ]; const { findByText } = render( - wrapInTestApp(), + wrapInTestApp( + , + ), ); for (const group of mockGroups) { @@ -53,7 +94,9 @@ describe('Catalog Filter', () => { ]; const { findByText } = render( - wrapInTestApp(), + wrapInTestApp( + , + ), ); const [group] = mockGroups; @@ -70,24 +113,34 @@ describe('Catalog Filter', () => { { id: EntityFilterType.ALL, label: 'First Label', - count: 100, + count: 3, }, { id: EntityFilterType.STARRED, label: 'Second Label', - count: 400, + count: 1, }, ], }, ]; - const { findByText } = render( - wrapInTestApp(), + render( + wrapInTestApp( + , + ), ); - const [group] = mockGroups; - for (const item of group.items) { - expect(await findByText(item.count!.toString())).toBeInTheDocument(); + for (const key of Object.keys(defaultFilterProps.entitiesByFilter)) { + await waitFor(() => + screen.getAllByText( + new RegExp( + `(${ + defaultFilterProps.entitiesByFilter[key as EntityFilterType] + .length + })`, + ), + ), + ); } }); @@ -115,8 +168,9 @@ describe('Catalog Filter', () => { const { findByText } = render( wrapInTestApp( , ), ); @@ -127,7 +181,7 @@ describe('Catalog Filter', () => { fireEvent.click(element); - expect(onSelectedChangeHandler).toHaveBeenCalledWith(item); + expect(onSelectedChangeHandler).toHaveBeenCalledWith(item.id); }); it('should render a component when a function is passed to the count component', async () => { @@ -149,9 +203,11 @@ describe('Catalog Filter', () => { }, ]; const { findByText } = render( - wrapInTestApp(), + wrapInTestApp( + , + ), ); - expect(await findByText('BACKSTAGE!')).toBeInTheDocument(); + expect(await findByText('Test Group 1')).toBeInTheDocument(); }); }); diff --git a/plugins/catalog/src/components/CatalogFilter/CatalogFilter.tsx b/plugins/catalog/src/components/CatalogFilter/CatalogFilter.tsx index f11494079f..cc3d269e61 100644 --- a/plugins/catalog/src/components/CatalogFilter/CatalogFilter.tsx +++ b/plugins/catalog/src/components/CatalogFilter/CatalogFilter.tsx @@ -26,7 +26,7 @@ import { makeStyles, } from '@material-ui/core'; import type { IconComponent } from '@backstage/core'; -import { EntityFilterType, filterGroups } from '../../data/filters'; +import { EntityFilterType } from '../../data/filters'; import { EntitiesByFilter } from '../../hooks/useEntities'; export type CatalogFilterItem = { @@ -71,12 +71,13 @@ export const CatalogFilter: FC<{ selectedFilter: EntityFilterType; onFilterChange: (type: EntityFilterType) => void; entitiesByFilter: EntitiesByFilter; + groups: CatalogFilterGroup[]; }> = ({ selectedFilter: selectedId, onFilterChange: setSelectedFilter, entitiesByFilter, + groups, }) => { - const groups = filterGroups; const classes = useStyles(); return ( diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx index 4cd0cae7af..4a9319ea3b 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx @@ -24,7 +24,7 @@ import { identityApiRef, } from '@backstage/core'; import { MockErrorApi, wrapInTestApp } from '@backstage/test-utils'; -import { render } from '@testing-library/react'; +import { screen, render, fireEvent, waitFor } from '@testing-library/react'; import React from 'react'; import { catalogApiRef } from '../..'; import { CatalogApi } from '../../api/types'; @@ -42,31 +42,66 @@ describe('CatalogPage', () => { }, apiVersion: 'backstage.io/v1alpha1', kind: 'Component', + spec: { + owner: 'tools@example.com', + }, + }, + { + metadata: { + name: 'Entity2', + }, + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + spec: { + owner: 'not-tools@example.com', + }, }, ] as Entity[]), getLocationByEntity: () => Promise.resolve({ id: 'id', type: 'github', target: 'url' }), }; + const mockIndentityApi: Partial = { + getUserId: () => 'tools@example.com', + }; // this test right now causes some red lines in the log output when running tests // related to some theme issues in mui-table // https://github.com/mbrn/material-table/issues/1293 it('should render', async () => { - const rendered = render( + render( wrapInTestApp( , ), ); - expect( - await rendered.findByText('Backstage Service Catalog'), - ).toBeInTheDocument(); + await waitFor(() => screen.getByText(/All Services \(2\)/)); + expect(screen.getByText(/All Services \(2\)/)).toBeInTheDocument(); + }); + it('should filter by owner', async () => { + render( + wrapInTestApp( + + + , + ), + ); + fireEvent.click(screen.getByText(/Owned/)); + await waitFor(() => screen.getByText(/Owned \(1\)/)); + expect(screen.getByText(/Owned \(1\)/)).toBeInTheDocument(); }); }); diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx index 0ab4e9c7d4..1722dbb9ac 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx @@ -36,6 +36,7 @@ import { CatalogTable } from '../CatalogTable/CatalogTable'; import { useEntities } from '../../hooks/useEntities'; import { findLocationForEntityMeta } from '../../data/utils'; import { + filterGroups, getCatalogFilterItemByType, EntityFilterType, } from '../../data/filters'; @@ -171,6 +172,7 @@ export const CatalogPage: FC<{}> = () => {
Date: Wed, 17 Jun 2020 14:56:30 +0200 Subject: [PATCH 010/182] feat(scaffolder): starting to move things around and remove the old scaffolder data --- plugins/scaffolder-backend/package.json | 3 +- .../src/scaffolder/prepare/file.ts | 34 +++++++++++++++++++ .../src/scaffolder/prepare/preparers.ts | 8 +++-- .../scaffolder-backend/src/service/router.ts | 2 +- .../mock-template-2/template-info.json | 6 ---- .../mock-template/template-info.json | 7 ---- .../mock-template-2/template-info.json | 6 ---- .../mock-template/template-info.json | 6 ---- .../mock-template/template-info.json | 6 ---- 9 files changed, 42 insertions(+), 36 deletions(-) delete mode 100644 plugins/scaffolder-backend/test/mock-failing-template-dir/mock-template-2/template-info.json delete mode 100644 plugins/scaffolder-backend/test/mock-failing-template-dir/mock-template/template-info.json delete mode 100644 plugins/scaffolder-backend/test/mock-multiple-templates-dir/mock-template-2/template-info.json delete mode 100644 plugins/scaffolder-backend/test/mock-multiple-templates-dir/mock-template/template-info.json delete mode 100644 plugins/scaffolder-backend/test/mock-simple-template-dir/mock-template/template-info.json diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index 907efde362..1838738c01 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -38,6 +38,7 @@ "@backstage/cli": "^0.1.1-alpha.8", "@types/fs-extra": "^9.0.1", "@types/supertest": "^2.0.8", - "supertest": "^4.0.2" + "supertest": "^4.0.2", + "yaml": "^1.10.0" } } diff --git a/plugins/scaffolder-backend/src/scaffolder/prepare/file.ts b/plugins/scaffolder-backend/src/scaffolder/prepare/file.ts index f3b69cc361..d7d21baa29 100644 --- a/plugins/scaffolder-backend/src/scaffolder/prepare/file.ts +++ b/plugins/scaffolder-backend/src/scaffolder/prepare/file.ts @@ -13,3 +13,37 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +// import fs from 'fs-extra'; +import fs from 'fs'; +import { + TemplateEntityV1alpha1, + LOCATION_ANNOTATION, +} from '@backstage/catalog-model'; +import { InputError } from '@backstage/backend-common'; +import { PreparerBase } from './types'; + +export class FilePreparer implements PreparerBase { + async prepare(template: TemplateEntityV1alpha1): Promise { + const location = template?.metadata?.annotations?.[LOCATION_ANNOTATION]; + + const [locationType, actualLocation] = (location ?? '').split(/:(.+)/); + if (locationType !== 'file') { + throw new InputError( + `Wrong location type: ${locationType}, should be 'file'`, + ); + } + + if (!actualLocation) { + throw new InputError( + `Couldn't parse location for template: ${template.metadata.name}`, + ); + } + + const templateId = template.metadata.name; + + const tempDir = await fs.promises.mkdtemp(templateId); + + // await fs.copy(actualLocation, tempDir); + return tempDir; + } +} diff --git a/plugins/scaffolder-backend/src/scaffolder/prepare/preparers.ts b/plugins/scaffolder-backend/src/scaffolder/prepare/preparers.ts index dc3e318926..97db3a3100 100644 --- a/plugins/scaffolder-backend/src/scaffolder/prepare/preparers.ts +++ b/plugins/scaffolder-backend/src/scaffolder/prepare/preparers.ts @@ -15,7 +15,10 @@ */ import { PreparerBase, RemoteLocation, PreparerBuilder } from './types'; -import { TemplateEntityV1alpha1 } from '@backstage/catalog-model'; +import { + TemplateEntityV1alpha1, + LOCATION_ANNOTATION, +} from '@backstage/catalog-model'; export class Preparers implements PreparerBuilder { private preparerMap = new Map(); @@ -38,8 +41,7 @@ export class Preparers implements PreparerBuilder { private getPreparerKeyFromEntity( entity: TemplateEntityV1alpha1, ): RemoteLocation { - const annotation = - entity.metadata.annotations?.['backstage.io/managed-by-location'] ?? ''; + const annotation = entity.metadata.annotations?.[LOCATION_ANNOTATION] ?? ''; const [key] = annotation?.split(':'); if (!key) { diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index 57ac1413fc..74507d528c 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -35,7 +35,7 @@ export async function createRouter( router.post('/v1/jobs', async (_, res) => { // TODO(blam): Actually make this function work - // res.status(201).json({ accepted: true }); + res.status(201).json({ accepted: true }); const mockEntity: TemplateEntityV1alpha1 = { apiVersion: 'backstage.io/v1alpha1', diff --git a/plugins/scaffolder-backend/test/mock-failing-template-dir/mock-template-2/template-info.json b/plugins/scaffolder-backend/test/mock-failing-template-dir/mock-template-2/template-info.json deleted file mode 100644 index 47f83f9f5c..0000000000 --- a/plugins/scaffolder-backend/test/mock-failing-template-dir/mock-template-2/template-info.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "id": "mock-template-2", - "name": "mockmannen", - "description": "mock template for building stuff in backstage", - "ownerId": "blam" -} diff --git a/plugins/scaffolder-backend/test/mock-failing-template-dir/mock-template/template-info.json b/plugins/scaffolder-backend/test/mock-failing-template-dir/mock-template/template-info.json deleted file mode 100644 index 9a3458fba1..0000000000 --- a/plugins/scaffolder-backend/test/mock-failing-template-dir/mock-template/template-info.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "id": "mock-template", - "name": "mockmannen", - "description": "mock template for building stuff in backstage", - "ownerId": "blam", - -} diff --git a/plugins/scaffolder-backend/test/mock-multiple-templates-dir/mock-template-2/template-info.json b/plugins/scaffolder-backend/test/mock-multiple-templates-dir/mock-template-2/template-info.json deleted file mode 100644 index 47f83f9f5c..0000000000 --- a/plugins/scaffolder-backend/test/mock-multiple-templates-dir/mock-template-2/template-info.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "id": "mock-template-2", - "name": "mockmannen", - "description": "mock template for building stuff in backstage", - "ownerId": "blam" -} diff --git a/plugins/scaffolder-backend/test/mock-multiple-templates-dir/mock-template/template-info.json b/plugins/scaffolder-backend/test/mock-multiple-templates-dir/mock-template/template-info.json deleted file mode 100644 index e0d323c987..0000000000 --- a/plugins/scaffolder-backend/test/mock-multiple-templates-dir/mock-template/template-info.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "id": "mock-template", - "name": "mockmannen", - "description": "mock template for building stuff in backstage", - "ownerId": "blam" -} diff --git a/plugins/scaffolder-backend/test/mock-simple-template-dir/mock-template/template-info.json b/plugins/scaffolder-backend/test/mock-simple-template-dir/mock-template/template-info.json deleted file mode 100644 index e0d323c987..0000000000 --- a/plugins/scaffolder-backend/test/mock-simple-template-dir/mock-template/template-info.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "id": "mock-template", - "name": "mockmannen", - "description": "mock template for building stuff in backstage", - "ownerId": "blam" -} From 739882c1444d780922a7b84498a8dc2615cfa1dc Mon Sep 17 00:00:00 2001 From: Nikita Nek Dudnik Date: Wed, 17 Jun 2020 15:29:29 +0200 Subject: [PATCH 011/182] chore(catalog): rename stuff --- .../CatalogFilter/CatalogFilter.test.tsx | 31 ++++++++------- .../CatalogFilter/CatalogFilter.tsx | 8 ++-- .../components/CatalogPage/CatalogPage.tsx | 13 +++---- plugins/catalog/src/data/filters.ts | 17 ++++----- plugins/catalog/src/hooks/useEntities.ts | 38 ++++++++----------- 5 files changed, 49 insertions(+), 58 deletions(-) diff --git a/plugins/catalog/src/components/CatalogFilter/CatalogFilter.test.tsx b/plugins/catalog/src/components/CatalogFilter/CatalogFilter.test.tsx index a19459b236..56268697c8 100644 --- a/plugins/catalog/src/components/CatalogFilter/CatalogFilter.test.tsx +++ b/plugins/catalog/src/components/CatalogFilter/CatalogFilter.test.tsx @@ -18,7 +18,7 @@ import React from 'react'; import { render, fireEvent, waitFor, screen } from '@testing-library/react'; import { wrapInTestApp } from '@backstage/test-utils'; import { CatalogFilter, CatalogFilterGroup } from './CatalogFilter'; -import { EntityFilterType } from '../../data/filters'; +import { EntityGroup } from '../../data/filters'; describe('Catalog Filter', () => { const comp1 = { @@ -52,12 +52,12 @@ describe('Catalog Filter', () => { }, }; const defaultFilterProps = { - selectedFilter: EntityFilterType.ALL, - onFilterChange: (type: EntityFilterType) => type, + selectedFilter: EntityGroup.ALL, + onFilterChange: (type: EntityGroup) => type, entitiesByFilter: { - [EntityFilterType.ALL]: [comp1, comp2, comp3], - [EntityFilterType.STARRED]: [comp1], - [EntityFilterType.OWNED]: [comp1], + [EntityGroup.ALL]: [comp1, comp2, comp3], + [EntityGroup.STARRED]: [comp1], + [EntityGroup.OWNED]: [comp1], }, }; it('should render the different groups', async () => { @@ -82,11 +82,11 @@ describe('Catalog Filter', () => { name: 'Test Group 1', items: [ { - id: EntityFilterType.ALL, + id: EntityGroup.ALL, label: 'First Label', }, { - id: EntityFilterType.STARRED, + id: EntityGroup.STARRED, label: 'Second Label', }, ], @@ -111,12 +111,12 @@ describe('Catalog Filter', () => { name: 'Test Group 1', items: [ { - id: EntityFilterType.ALL, + id: EntityGroup.ALL, label: 'First Label', count: 3, }, { - id: EntityFilterType.STARRED, + id: EntityGroup.STARRED, label: 'Second Label', count: 1, }, @@ -135,8 +135,7 @@ describe('Catalog Filter', () => { screen.getAllByText( new RegExp( `(${ - defaultFilterProps.entitiesByFilter[key as EntityFilterType] - .length + defaultFilterProps.entitiesByFilter[key as EntityGroup].length })`, ), ), @@ -150,12 +149,12 @@ describe('Catalog Filter', () => { name: 'Test Group 1', items: [ { - id: EntityFilterType.ALL, + id: EntityGroup.ALL, label: 'First Label', count: 100, }, { - id: EntityFilterType.STARRED, + id: EntityGroup.STARRED, label: 'Second Label', count: 400, }, @@ -190,12 +189,12 @@ describe('Catalog Filter', () => { name: 'Test Group 1', items: [ { - id: EntityFilterType.ALL, + id: EntityGroup.ALL, label: 'First Label', count: () => BACKSTAGE!, }, { - id: EntityFilterType.STARRED, + id: EntityGroup.STARRED, label: 'Second Label', count: 400, }, diff --git a/plugins/catalog/src/components/CatalogFilter/CatalogFilter.tsx b/plugins/catalog/src/components/CatalogFilter/CatalogFilter.tsx index cc3d269e61..61a83fccd8 100644 --- a/plugins/catalog/src/components/CatalogFilter/CatalogFilter.tsx +++ b/plugins/catalog/src/components/CatalogFilter/CatalogFilter.tsx @@ -26,11 +26,11 @@ import { makeStyles, } from '@material-ui/core'; import type { IconComponent } from '@backstage/core'; -import { EntityFilterType } from '../../data/filters'; +import { EntityGroup } from '../../data/filters'; import { EntitiesByFilter } from '../../hooks/useEntities'; export type CatalogFilterItem = { - id: EntityFilterType; + id: EntityGroup; label: string; icon?: IconComponent; count?: number | FC; @@ -68,8 +68,8 @@ const useStyles = makeStyles(theme => ({ })); export const CatalogFilter: FC<{ - selectedFilter: EntityFilterType; - onFilterChange: (type: EntityFilterType) => void; + selectedFilter: EntityGroup; + onFilterChange: (type: EntityGroup) => void; entitiesByFilter: EntitiesByFilter; groups: CatalogFilterGroup[]; }> = ({ diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx index 08014fdc74..68506136d2 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx @@ -37,7 +37,7 @@ import { useEntities } from '../../hooks/useEntities'; import { findLocationForEntityMeta } from '../../data/utils'; import { getCatalogFilterItemByType, - EntityFilterType, + EntityGroup, filterGroups, labeledEntityTypes, } from '../../data/filters'; @@ -64,12 +64,11 @@ export const CatalogPage: FC<{}> = () => { toggleStarredEntity, isStarredEntity, setSelectedFilter, - selectedTab, - setSelectedTab, + selectedTypeFilter: selectedTab, + selectTypeFilter: setSelectedTab, } = useEntities(); - const filteredEntities = - entitiesByFilter[selectedFilter ?? EntityFilterType.ALL]; + const filteredEntities = entitiesByFilter[selectedFilter ?? EntityGroup.ALL]; const styles = useStyles(); @@ -160,14 +159,14 @@ export const CatalogPage: FC<{}> = () => {
{ +export const getCatalogFilterItemByType = (filterType: EntityGroup) => { for (const group of filterGroups) { for (const filter of group.items) { if (filter.id === filterType) { @@ -72,7 +72,6 @@ type EntityFilter = (entity: Entity, options: EntityFilterOptions) => boolean; type EntityFilterOptions = Partial<{ isStarred: boolean; userId: string; - type: string; }>; type Owned = { @@ -80,12 +79,12 @@ type Owned = { }; export const entityFilters: Record = { - [EntityFilterType.OWNED]: (e, { userId }) => { + [EntityGroup.OWNED]: (e, { userId }) => { const owner = (e.spec! as Owned).owner; return owner === userId; }, - [EntityFilterType.ALL]: () => true, - [EntityFilterType.STARRED]: (_, { isStarred }) => !!isStarred, + [EntityGroup.ALL]: () => true, + [EntityGroup.STARRED]: (_, { isStarred }) => !!isStarred, }; export const entityTypeFilter = (e: Entity, type: string) => diff --git a/plugins/catalog/src/hooks/useEntities.ts b/plugins/catalog/src/hooks/useEntities.ts index d9065b645e..6509bd9b4b 100644 --- a/plugins/catalog/src/hooks/useEntities.ts +++ b/plugins/catalog/src/hooks/useEntities.ts @@ -15,7 +15,7 @@ */ import { useState, useMemo } from 'react'; import { - EntityFilterType, + EntityGroup, entityFilters, entityTypeFilter, labeledEntityTypes, @@ -26,48 +26,42 @@ import { useStarredEntities } from './useStarredEntites'; import { Entity } from '@backstage/catalog-model'; import useStaleWhileRevalidate from 'swr'; -export type EntitiesByFilter = Record; +export type EntitiesByFilter = Record; type UseEntities = { - selectedFilter: EntityFilterType | undefined; - setSelectedFilter: (f: EntityFilterType) => void; + selectedFilter: EntityGroup | undefined; + setSelectedFilter: (f: EntityGroup) => void; error: Error | null; toggleStarredEntity: any; isStarredEntity: (e: Entity) => boolean; entitiesByFilter: EntitiesByFilter; loading: boolean; + selectedTypeFilter: string; + selectTypeFilter: (id: string) => void; }; export const useEntities = (): UseEntities => { const [selectedFilter, setSelectedFilter] = useState< - EntityFilterType | undefined + EntityGroup | undefined >(); const catalogApi = useApi(catalogApiRef); const { toggleStarredEntity, isStarredEntity } = useStarredEntities(); const { data: entities, error } = useStaleWhileRevalidate( - ['catalog/all', entityFilters[selectedFilter ?? EntityFilterType.ALL]], + ['catalog/all', entityFilters[selectedFilter ?? EntityGroup.ALL]], async () => catalogApi.getEntities(), ); const indentityApi = useApi(identityApiRef); const userId = indentityApi.getUserId(); - const [selectedTab, setSelectedTab] = useState( + const [selectedTypeFilter, selectTypeFilter] = useState( labeledEntityTypes[0].id, ); - // const filteredEntities = useMemo(() => { - // const typeFilter = entityFilters[EntityFilterType.TYPE]; - // const leftMenuFilter = entityFilters[selectedFilter.id]; - // return entities - // ?.filter(e => leftMenuFilter(e, { isStarred: isStarredEntity(e) })) - // .filter(e => typeFilter(e, { type: selectedTab })); - // }, [selectedFilter.id, selectedTab, isStarredEntity, entities?.filter]); - const entitiesByFilter = useMemo(() => { const filterEntities = ( ents: Entity[] | undefined, - filterId: EntityFilterType, + filterId: EntityGroup, isStarred: (e: Entity) => boolean, user: string, ) => { @@ -78,14 +72,14 @@ export const useEntities = (): UseEntities => { userId: user, }), ) - .filter(e => entityTypeFilter(e, selectedTab)); + .filter(e => entityTypeFilter(e, selectedTypeFilter)); }; - const data = Object.keys(EntityFilterType).reduce( + const data = Object.keys(EntityGroup).reduce( (res, key) => ({ ...res, [key]: filterEntities( entities, - key as EntityFilterType, + key as EntityGroup, isStarredEntity, userId, ), @@ -93,7 +87,7 @@ export const useEntities = (): UseEntities => { {} as EntitiesByFilter, ); return data; - }, [entities, isStarredEntity, userId, selectedTab]); + }, [entities, isStarredEntity, userId, selectedTypeFilter]); return { selectedFilter, @@ -103,7 +97,7 @@ export const useEntities = (): UseEntities => { isStarredEntity, entitiesByFilter, loading: entities === undefined, - selectedTab, - setSelectedTab, + selectedTypeFilter, + selectTypeFilter, }; }; From 29a88c3e9c289671fdf647296455aa33199d1ba9 Mon Sep 17 00:00:00 2001 From: Nikita Nek Dudnik Date: Wed, 17 Jun 2020 17:00:10 +0200 Subject: [PATCH 012/182] fix(catalog): tests --- packages/app/src/apis.ts | 4 --- .../implementations/IdentityApi/Identity.ts | 25 ------------------- .../apis/implementations/IdentityApi/index.ts | 16 ------------ .../src/apis/implementations/index.ts | 1 - packages/storybook/.storybook/apis.js | 1 - .../CatalogPage/CatalogPage.test.tsx | 4 +-- .../components/CatalogPage/CatalogPage.tsx | 7 +++--- 7 files changed, 5 insertions(+), 53 deletions(-) delete mode 100644 packages/core-api/src/apis/implementations/IdentityApi/Identity.ts delete mode 100644 packages/core-api/src/apis/implementations/IdentityApi/index.ts diff --git a/packages/app/src/apis.ts b/packages/app/src/apis.ts index a6d9987819..9bfc618216 100644 --- a/packages/app/src/apis.ts +++ b/packages/app/src/apis.ts @@ -32,8 +32,6 @@ import { githubAuthApiRef, storageApiRef, WebStorage, - identityApiRef, - MockIdentity, } from '@backstage/core'; import { @@ -54,8 +52,6 @@ export const apis = (config: ConfigApi) => { const builder = ApiRegistry.builder(); - builder.add(identityApiRef, new MockIdentity()); - const alertApi = builder.add(alertApiRef, new AlertApiForwarder()); const errorApi = builder.add( errorApiRef, diff --git a/packages/core-api/src/apis/implementations/IdentityApi/Identity.ts b/packages/core-api/src/apis/implementations/IdentityApi/Identity.ts deleted file mode 100644 index 6f2035d1b8..0000000000 --- a/packages/core-api/src/apis/implementations/IdentityApi/Identity.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { IdentityApi } from '../..'; - -export class MockIdentity implements IdentityApi { - getUserId(): string { - return ''; - } - getIdToken(): string | undefined { - throw new Error('Method not implemented.'); - } -} diff --git a/packages/core-api/src/apis/implementations/IdentityApi/index.ts b/packages/core-api/src/apis/implementations/IdentityApi/index.ts deleted file mode 100644 index 1e57036685..0000000000 --- a/packages/core-api/src/apis/implementations/IdentityApi/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -export * from './Identity'; diff --git a/packages/core-api/src/apis/implementations/index.ts b/packages/core-api/src/apis/implementations/index.ts index 885082ce09..e6d23fee21 100644 --- a/packages/core-api/src/apis/implementations/index.ts +++ b/packages/core-api/src/apis/implementations/index.ts @@ -26,4 +26,3 @@ export * from './ConfigApi'; export * from './ErrorApi'; export * from './OAuthRequestApi'; export * from './StorageApi'; -export * from './IdentityApi'; diff --git a/packages/storybook/.storybook/apis.js b/packages/storybook/.storybook/apis.js index 83f6bbca23..d3c78fa2d6 100644 --- a/packages/storybook/.storybook/apis.js +++ b/packages/storybook/.storybook/apis.js @@ -13,7 +13,6 @@ import { GoogleAuth, GithubAuth, identityApiRef, - MockIdentity, } from '@backstage/core'; const builder = ApiRegistry.builder(); diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx index 4a9319ea3b..8fae75b68b 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx @@ -44,6 +44,7 @@ describe('CatalogPage', () => { kind: 'Component', spec: { owner: 'tools@example.com', + type: 'service', }, }, { @@ -54,6 +55,7 @@ describe('CatalogPage', () => { kind: 'Component', spec: { owner: 'not-tools@example.com', + type: 'service', }, }, ] as Entity[]), @@ -83,7 +85,6 @@ describe('CatalogPage', () => { ), ); await waitFor(() => screen.getByText(/All Services \(2\)/)); - expect(screen.getByText(/All Services \(2\)/)).toBeInTheDocument(); }); it('should filter by owner', async () => { render( @@ -102,6 +103,5 @@ describe('CatalogPage', () => { ); fireEvent.click(screen.getByText(/Owned/)); await waitFor(() => screen.getByText(/Owned \(1\)/)); - expect(screen.getByText(/Owned \(1\)/)).toBeInTheDocument(); }); }); diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx index 68506136d2..e9dd2a6295 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx @@ -61,11 +61,10 @@ export const CatalogPage: FC<{}> = () => { error, loading, selectedFilter, + setSelectedFilter, toggleStarredEntity, isStarredEntity, - setSelectedFilter, - selectedTypeFilter: selectedTab, - selectTypeFilter: setSelectedTab, + selectTypeFilter, } = useEntities(); const filteredEntities = entitiesByFilter[selectedFilter ?? EntityGroup.ALL]; @@ -123,7 +122,7 @@ export const CatalogPage: FC<{}> = () => { { - setSelectedTab(labeledEntityTypes[index as number].id); + selectTypeFilter(labeledEntityTypes[index as number].id); }} /> From 58866e6a620db438f663850913557fa890f1e99d Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 17 Jun 2020 17:39:47 +0200 Subject: [PATCH 013/182] feat(scaffolder): Fixing the path resolution for temporary directory and copy all files apart from the template.yaml definition --- .../src/scaffolder/prepare/file.test.ts | 50 ++++++++++++++++++ .../src/scaffolder/prepare/file.ts | 24 ++++++--- .../src/scaffolder/prepare/preparers.test.ts | 52 +++++++++++++++++++ .../template-1/expected_file.ts | 16 ++++++ .../test/test-nested-template/template.yaml | 9 ++++ .../test-simple-template/expected_file.ts | 16 ++++++ .../test/test-simple-template/template.yaml | 8 +++ 7 files changed, 169 insertions(+), 6 deletions(-) create mode 100644 plugins/scaffolder-backend/src/scaffolder/prepare/file.test.ts create mode 100644 plugins/scaffolder-backend/src/scaffolder/prepare/preparers.test.ts create mode 100644 plugins/scaffolder-backend/test/test-nested-template/template-1/expected_file.ts create mode 100644 plugins/scaffolder-backend/test/test-nested-template/template.yaml create mode 100644 plugins/scaffolder-backend/test/test-simple-template/expected_file.ts create mode 100644 plugins/scaffolder-backend/test/test-simple-template/template.yaml diff --git a/plugins/scaffolder-backend/src/scaffolder/prepare/file.test.ts b/plugins/scaffolder-backend/src/scaffolder/prepare/file.test.ts new file mode 100644 index 0000000000..6ab0aa3fb0 --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/prepare/file.test.ts @@ -0,0 +1,50 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import fs from 'fs-extra'; +import YAML from 'yaml'; +import { FilePreparer } from './file'; +import path from 'path'; +import { + TemplateEntityV1alpha1, + LOCATION_ANNOTATION, +} from '@backstage/catalog-model'; + +describe('File preparer', () => { + it('resolves relative path from the template', async () => { + const locationForTemplateYaml = path.resolve( + __dirname, + '../../..', + 'test/test-simple-template/template.yaml', + ); + + const [parsedDocument] = YAML.parseAllDocuments( + await fs.readFile(locationForTemplateYaml, 'utf-8'), + ); + + const template: TemplateEntityV1alpha1 = parsedDocument.toJSON(); + + template.metadata.annotations = { + [LOCATION_ANNOTATION]: `file:${locationForTemplateYaml}`, + }; + + const filePreparer = new FilePreparer(); + const resultDir = await filePreparer.prepare(template); + + expect(fs.existsSync(`${resultDir}/expected_file.ts`)).toBe(true); + expect(fs.existsSync(`${resultDir}/template.yaml`)).toBe(false); + }); +}); diff --git a/plugins/scaffolder-backend/src/scaffolder/prepare/file.ts b/plugins/scaffolder-backend/src/scaffolder/prepare/file.ts index d7d21baa29..184e1a6b1f 100644 --- a/plugins/scaffolder-backend/src/scaffolder/prepare/file.ts +++ b/plugins/scaffolder-backend/src/scaffolder/prepare/file.ts @@ -13,8 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -// import fs from 'fs-extra'; -import fs from 'fs'; +import fs from 'fs-extra'; +import path from 'path'; +import os from 'os'; import { TemplateEntityV1alpha1, LOCATION_ANNOTATION, @@ -26,14 +27,16 @@ export class FilePreparer implements PreparerBase { async prepare(template: TemplateEntityV1alpha1): Promise { const location = template?.metadata?.annotations?.[LOCATION_ANNOTATION]; - const [locationType, actualLocation] = (location ?? '').split(/:(.+)/); + const [locationType, templateEntityLocation] = (location ?? '').split( + /:(.+)/, + ); if (locationType !== 'file') { throw new InputError( `Wrong location type: ${locationType}, should be 'file'`, ); } - if (!actualLocation) { + if (!templateEntityLocation) { throw new InputError( `Couldn't parse location for template: ${template.metadata.name}`, ); @@ -41,9 +44,18 @@ export class FilePreparer implements PreparerBase { const templateId = template.metadata.name; - const tempDir = await fs.promises.mkdtemp(templateId); + const tempDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), templateId), + ); + + const parentDirectory = path.dirname(templateEntityLocation); + + await fs.copy(parentDirectory, tempDir, { + filter: src => src !== templateEntityLocation, + }); + + // TODO(blam): Need to use the `spec.path` with path.resolve to ensure that the test case for test-nested-templates will work - // await fs.copy(actualLocation, tempDir); return tempDir; } } diff --git a/plugins/scaffolder-backend/src/scaffolder/prepare/preparers.test.ts b/plugins/scaffolder-backend/src/scaffolder/prepare/preparers.test.ts new file mode 100644 index 0000000000..96e6b7d5b9 --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/prepare/preparers.test.ts @@ -0,0 +1,52 @@ +/* + * 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 { Preparers } from '.'; +import { TemplateEntityV1alpha1 } from '@backstage/catalog-model'; + +describe('Preparers', () => { + it('should throw an error when the preparer for the source location is not registered', () => { + const preparers = new Preparers(); + const mockTemplate: TemplateEntityV1alpha1 = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Template', + metadata: { + annotations: { + 'backstage.io/managed-by-location': + 'file:/Users/blam/dev/spotify/backstage/plugins/scaffolder-backend/sample-templates/react-ssr-template/template.yaml', + }, + name: 'react-ssr-template', + title: 'React SSR Template', + description: + 'Next.js application skeleton for creating isomorphic web applications.', + uid: '7357f4c5-aa58-4a1e-9670-18931eef771f', + etag: 'YWUxZWQyY2EtZDkxMC00MDM0LWI0ODAtMDgwMWY0YzdlMWIw', + generation: 1, + }, + spec: { + type: 'cookiecutter', + path: '.', + }, + }; + + expect(() => preparers.get(mockTemplate)).toThrow( + expect.objectContaining({ + message: 'No preparer registered for type file', + }), + ); + }); + it('should return the correct preparer when the source matches'); + it('should throw an error if the srouce is not available'); +}); diff --git a/plugins/scaffolder-backend/test/test-nested-template/template-1/expected_file.ts b/plugins/scaffolder-backend/test/test-nested-template/template-1/expected_file.ts new file mode 100644 index 0000000000..19b0312029 --- /dev/null +++ b/plugins/scaffolder-backend/test/test-nested-template/template-1/expected_file.ts @@ -0,0 +1,16 @@ +/* + * 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. + */ +console.warn('here!'); diff --git a/plugins/scaffolder-backend/test/test-nested-template/template.yaml b/plugins/scaffolder-backend/test/test-nested-template/template.yaml new file mode 100644 index 0000000000..713094defe --- /dev/null +++ b/plugins/scaffolder-backend/test/test-nested-template/template.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Template +metadata: + name: react-ssr-template + title: React SSR Template + description: Next.js application skeleton for creating isomorphic web applications. +spec: + type: cookiecutter + path: ./template-1 diff --git a/plugins/scaffolder-backend/test/test-simple-template/expected_file.ts b/plugins/scaffolder-backend/test/test-simple-template/expected_file.ts new file mode 100644 index 0000000000..19b0312029 --- /dev/null +++ b/plugins/scaffolder-backend/test/test-simple-template/expected_file.ts @@ -0,0 +1,16 @@ +/* + * 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. + */ +console.warn('here!'); diff --git a/plugins/scaffolder-backend/test/test-simple-template/template.yaml b/plugins/scaffolder-backend/test/test-simple-template/template.yaml new file mode 100644 index 0000000000..7ee2132d59 --- /dev/null +++ b/plugins/scaffolder-backend/test/test-simple-template/template.yaml @@ -0,0 +1,8 @@ +apiVersion: backstage.io/v1alpha1 +kind: Template +metadata: + name: react-ssr-template + title: React SSR Template + description: Next.js application skeleton for creating isomorphic web applications. +spec: + type: cookiecutter From f6012daa77616757c2ac409c27db294cbeb2e6b1 Mon Sep 17 00:00:00 2001 From: Nikita Nek Dudnik Date: Wed, 17 Jun 2020 17:50:12 +0200 Subject: [PATCH 014/182] fix(catalog): identity is done in a wrong order --- packages/core-api/src/app/App.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core-api/src/app/App.tsx b/packages/core-api/src/app/App.tsx index 163a546943..849db24648 100644 --- a/packages/core-api/src/app/App.tsx +++ b/packages/core-api/src/app/App.tsx @@ -265,8 +265,8 @@ export class PrivateAppImpl implements BackstageApp { if (done) { throw new Error('Identity result callback was called twice'); } - setDone(true); this.identityApi.setSignInResult(result); + setDone(true); }, [done], ); From 23c58be5254d98edb1a08be3d492ff073bc27a3b Mon Sep 17 00:00:00 2001 From: Ivan Shmidt Date: Wed, 17 Jun 2020 20:49:14 +0200 Subject: [PATCH 015/182] test(scaffolder): add nested template test --- .../template-1/expected_file.ts | 0 .../test-nested-template/template.yaml | 0 .../test-simple-template/expected_file.ts | 0 .../test-simple-template/template.yaml | 0 .../src/scaffolder/prepare/file.test.ts | 55 ++++++++++++------- .../src/scaffolder/prepare/file.ts | 7 ++- 6 files changed, 38 insertions(+), 24 deletions(-) rename plugins/scaffolder-backend/{test => fixtures}/test-nested-template/template-1/expected_file.ts (100%) rename plugins/scaffolder-backend/{test => fixtures}/test-nested-template/template.yaml (100%) rename plugins/scaffolder-backend/{test => fixtures}/test-simple-template/expected_file.ts (100%) rename plugins/scaffolder-backend/{test => fixtures}/test-simple-template/template.yaml (100%) diff --git a/plugins/scaffolder-backend/test/test-nested-template/template-1/expected_file.ts b/plugins/scaffolder-backend/fixtures/test-nested-template/template-1/expected_file.ts similarity index 100% rename from plugins/scaffolder-backend/test/test-nested-template/template-1/expected_file.ts rename to plugins/scaffolder-backend/fixtures/test-nested-template/template-1/expected_file.ts diff --git a/plugins/scaffolder-backend/test/test-nested-template/template.yaml b/plugins/scaffolder-backend/fixtures/test-nested-template/template.yaml similarity index 100% rename from plugins/scaffolder-backend/test/test-nested-template/template.yaml rename to plugins/scaffolder-backend/fixtures/test-nested-template/template.yaml diff --git a/plugins/scaffolder-backend/test/test-simple-template/expected_file.ts b/plugins/scaffolder-backend/fixtures/test-simple-template/expected_file.ts similarity index 100% rename from plugins/scaffolder-backend/test/test-simple-template/expected_file.ts rename to plugins/scaffolder-backend/fixtures/test-simple-template/expected_file.ts diff --git a/plugins/scaffolder-backend/test/test-simple-template/template.yaml b/plugins/scaffolder-backend/fixtures/test-simple-template/template.yaml similarity index 100% rename from plugins/scaffolder-backend/test/test-simple-template/template.yaml rename to plugins/scaffolder-backend/fixtures/test-simple-template/template.yaml diff --git a/plugins/scaffolder-backend/src/scaffolder/prepare/file.test.ts b/plugins/scaffolder-backend/src/scaffolder/prepare/file.test.ts index 6ab0aa3fb0..b279b56ea3 100644 --- a/plugins/scaffolder-backend/src/scaffolder/prepare/file.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/prepare/file.test.ts @@ -23,28 +23,41 @@ import { LOCATION_ANNOTATION, } from '@backstage/catalog-model'; +const setupTest = async (fixturePath: string) => { + const locationForTemplateYaml = path.resolve( + __dirname, + '../../../fixtures', + fixturePath, + ); + + const [parsedDocument] = YAML.parseAllDocuments( + await fs.readFile(locationForTemplateYaml, 'utf-8'), + ); + + const template: TemplateEntityV1alpha1 = parsedDocument.toJSON(); + template.metadata.annotations = { + [LOCATION_ANNOTATION]: `file:${locationForTemplateYaml}`, + }; + + const filePreparer = new FilePreparer(); + const resultDir = await filePreparer.prepare(template); + + return { filePreparer, template, resultDir }; +}; + describe('File preparer', () => { - it('resolves relative path from the template', async () => { - const locationForTemplateYaml = path.resolve( - __dirname, - '../../..', - 'test/test-simple-template/template.yaml', - ); - - const [parsedDocument] = YAML.parseAllDocuments( - await fs.readFile(locationForTemplateYaml, 'utf-8'), - ); - - const template: TemplateEntityV1alpha1 = parsedDocument.toJSON(); - - template.metadata.annotations = { - [LOCATION_ANNOTATION]: `file:${locationForTemplateYaml}`, - }; - - const filePreparer = new FilePreparer(); - const resultDir = await filePreparer.prepare(template); - - expect(fs.existsSync(`${resultDir}/expected_file.ts`)).toBe(true); + it('excludes the yaml file from the temp folder', async () => { + const { resultDir } = await setupTest('test-simple-template/template.yaml'); expect(fs.existsSync(`${resultDir}/template.yaml`)).toBe(false); }); + + it('resolves relative path from the template', async () => { + const { resultDir } = await setupTest('test-simple-template/template.yaml'); + expect(fs.existsSync(`${resultDir}/expected_file.ts`)).toBe(true); + }); + + it('resolves relative path from the nested template', async () => { + const { resultDir } = await setupTest('test-nested-template/template.yaml'); + expect(fs.existsSync(`${resultDir}/expected_file.ts`)).toBe(true); + }); }); diff --git a/plugins/scaffolder-backend/src/scaffolder/prepare/file.ts b/plugins/scaffolder-backend/src/scaffolder/prepare/file.ts index 184e1a6b1f..b9e8942aae 100644 --- a/plugins/scaffolder-backend/src/scaffolder/prepare/file.ts +++ b/plugins/scaffolder-backend/src/scaffolder/prepare/file.ts @@ -48,14 +48,15 @@ export class FilePreparer implements PreparerBase { path.join(os.tmpdir(), templateId), ); - const parentDirectory = path.dirname(templateEntityLocation); + const parentDirectory = path.resolve( + path.dirname(templateEntityLocation), + template.spec.path ?? '.', + ); await fs.copy(parentDirectory, tempDir, { filter: src => src !== templateEntityLocation, }); - // TODO(blam): Need to use the `spec.path` with path.resolve to ensure that the test case for test-nested-templates will work - return tempDir; } } From 5835f06524c6de4bed99a0291443b4e0add8f4db Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 18 Jun 2020 14:10:02 +0200 Subject: [PATCH 016/182] chore(scaffolder): Tidy up some things around the PR to make a litte cleaner and add some comments in places --- .../react-ssr-template/template-info.json | 6 --- plugins/scaffolder-backend/src/run.ts | 34 ------------- .../src/scaffolder/prepare/file.test.ts | 6 ++- .../scaffolder-backend/src/service/router.ts | 12 +++-- .../src/service/standaloneServer.ts | 51 ------------------- 5 files changed, 11 insertions(+), 98 deletions(-) delete mode 100644 plugins/scaffolder-backend/sample-templates/react-ssr-template/template-info.json delete mode 100644 plugins/scaffolder-backend/src/run.ts delete mode 100644 plugins/scaffolder-backend/src/service/standaloneServer.ts diff --git a/plugins/scaffolder-backend/sample-templates/react-ssr-template/template-info.json b/plugins/scaffolder-backend/sample-templates/react-ssr-template/template-info.json deleted file mode 100644 index a733b790fa..0000000000 --- a/plugins/scaffolder-backend/sample-templates/react-ssr-template/template-info.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "id": "react-ssr-template", - "name": "SSR React Website", - "description": "Next.js application skeleton for creating isomorphic web applications.", - "ownerId": "something" -} diff --git a/plugins/scaffolder-backend/src/run.ts b/plugins/scaffolder-backend/src/run.ts deleted file mode 100644 index 133aad163e..0000000000 --- a/plugins/scaffolder-backend/src/run.ts +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { getRootLogger } from '@backstage/backend-common'; -import { startStandaloneServer } from './service/standaloneServer'; - -const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 3004; -const enableCors = process.env.PLUGIN_CORS - ? Boolean(process.env.PLUGIN_CORS) - : false; -const logger = getRootLogger(); - -startStandaloneServer({ port, enableCors, logger }).catch((err) => { - logger.error(err); - process.exit(1); -}); - -process.on('SIGINT', () => { - logger.info('CTRL+C pressed; exiting.'); - process.exit(0); -}); diff --git a/plugins/scaffolder-backend/src/scaffolder/prepare/file.test.ts b/plugins/scaffolder-backend/src/scaffolder/prepare/file.test.ts index b279b56ea3..b9472fbc8d 100644 --- a/plugins/scaffolder-backend/src/scaffolder/prepare/file.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/prepare/file.test.ts @@ -25,8 +25,10 @@ import { const setupTest = async (fixturePath: string) => { const locationForTemplateYaml = path.resolve( - __dirname, - '../../../fixtures', + path.dirname( + require.resolve('@backstage/plugin-scaffolder-backend/package'), + ), + 'fixtures', fixturePath, ); diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index 74507d528c..b05983e1b4 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -34,16 +34,17 @@ export async function createRouter( const logger = parentLogger.child({ plugin: 'scaffolder' }); router.post('/v1/jobs', async (_, res) => { - // TODO(blam): Actually make this function work + // TODO(blam): Create a unique job here and return the ID so that + // The end user can poll for updates on the current job res.status(201).json({ accepted: true }); + // TODO(blam): Take this entity from the post body sent from the frontend const mockEntity: TemplateEntityV1alpha1 = { apiVersion: 'backstage.io/v1alpha1', kind: 'Template', metadata: { annotations: { - 'backstage.io/managed-by-location': - 'file:/Users/blam/dev/spotify/backstage/plugins/scaffolder-backend/sample-templates/react-ssr-template/template.yaml', + 'backstage.io/managed-by-location': `file:${__dirname}/../../sample-templates/react-ssr-template/template.yaml`, }, name: 'react-ssr-template', title: 'React SSR Template', @@ -59,12 +60,13 @@ export async function createRouter( }, }; - // fetch the entity from service catalog here. + // Get the preparer for the mock entity const preparer = preparers.get(mockEntity); - // + // Run the preparer for the mock entity to produce a temporary directory with template in const path = await preparer.prepare(mockEntity); + // Run the templater on the mock directory with values from the post body await templater.run({ directory: path, values: { componentId: 'test' } }); }); diff --git a/plugins/scaffolder-backend/src/service/standaloneServer.ts b/plugins/scaffolder-backend/src/service/standaloneServer.ts deleted file mode 100644 index cad937d304..0000000000 --- a/plugins/scaffolder-backend/src/service/standaloneServer.ts +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { Server } from 'http'; -import { Logger } from 'winston'; -import { createTemplater, CookieCutter } from '../scaffolder'; -import { createServiceBuilder, useHotMemoize } from '@backstage/backend-common'; -import { createRouter } from './router'; -export interface ServerOptions { - port: number; - enableCors: boolean; - logger: Logger; -} - -export async function startStandaloneServer( - options: ServerOptions, -): Promise { - const logger = options.logger.child({ service: 'scaffolder-backend' }); - const templater = new CookieCutter(); - logger.debug('Creating application...'); - - const router = await createRouter({ - storage: null, - templater: createTemplater({ templater }), - logger, - }); - - const service = createServiceBuilder(module) - .enableCors({ origin: 'http://localhost:3000' }) - .addRouter('/catalog', router); - - return await service.start().catch(err => { - logger.error(err); - process.exit(1); - }); -} - -module.hot?.accept(); From 324b0a767a0b9ee097c2f4b236eb7b9e2e1e88f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Thu, 18 Jun 2020 11:19:01 +0200 Subject: [PATCH 017/182] feat(backend-common): base backend config --- app-config.yaml | 6 + packages/backend-common/package.json | 1 + .../src/service/createServiceBuilder.ts | 2 +- .../service/{ => lib}/ServiceBuilderImpl.ts | 60 ++++++++-- .../backend-common/src/service/lib/config.ts | 112 ++++++++++++++++++ packages/backend-common/src/service/types.ts | 8 ++ packages/backend/src/index.ts | 9 +- 7 files changed, 182 insertions(+), 16 deletions(-) rename packages/backend-common/src/service/{ => lib}/ServiceBuilderImpl.ts (69%) create mode 100644 packages/backend-common/src/service/lib/config.ts diff --git a/app-config.yaml b/app-config.yaml index 6ff336c727..c5777be4fa 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -4,6 +4,12 @@ app: backend: baseUrl: http://localhost:7000 + bindPort: 7000 + bindHost: localhost + cors: + origin: http://localhost:3000 + methods: [GET, POST, PUT, DELETE] + credentials: true organization: name: Spotify diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index 1a52e06013..6a827b7e86 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -29,6 +29,7 @@ "clean": "backstage-cli clean" }, "dependencies": { + "@backstage/config": "^0.1.1-alpha.9", "@types/cors": "^2.8.6", "@types/express": "^4.17.6", "compression": "^1.7.4", diff --git a/packages/backend-common/src/service/createServiceBuilder.ts b/packages/backend-common/src/service/createServiceBuilder.ts index daef612fcf..c62921afc3 100644 --- a/packages/backend-common/src/service/createServiceBuilder.ts +++ b/packages/backend-common/src/service/createServiceBuilder.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { ServiceBuilderImpl } from './ServiceBuilderImpl'; +import { ServiceBuilderImpl } from './lib/ServiceBuilderImpl'; /** * Creates a new service builder. diff --git a/packages/backend-common/src/service/ServiceBuilderImpl.ts b/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts similarity index 69% rename from packages/backend-common/src/service/ServiceBuilderImpl.ts rename to packages/backend-common/src/service/lib/ServiceBuilderImpl.ts index 931d29b841..3cceec0e87 100644 --- a/packages/backend-common/src/service/ServiceBuilderImpl.ts +++ b/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { ConfigReader } from '@backstage/config'; import compression from 'compression'; import cors from 'cors'; import express, { Router } from 'express'; @@ -21,37 +22,66 @@ import helmet from 'helmet'; import { Server } from 'http'; import stoppable from 'stoppable'; import { Logger } from 'winston'; -import { getRootLogger } from '../logging'; +import { useHotCleanup } from '../../hot'; +import { getRootLogger } from '../../logging'; import { errorHandler, notFoundHandler, requestLoggingHandler, -} from '../middleware'; -import { ServiceBuilder } from './types'; -import { useHotCleanup } from '../hot'; +} from '../../middleware'; +import { ServiceBuilder } from '../types'; +import { readBaseOptions, readCorsOptions } from './config'; const DEFAULT_PORT = 7000; +const DEFAULT_HOST = 'localhost'; export class ServiceBuilderImpl implements ServiceBuilder { private port: number | undefined; + private host: string | undefined; private logger: Logger | undefined; private corsOptions: cors.CorsOptions | undefined; private routers: [string, Router][]; - /** - * Reference to the module where builder is created - * Needed for the HMR - */ + // Reference to the module where builder is created - needed for hot module + // reloading private module: NodeModule; + constructor(module: NodeModule) { this.routers = []; this.module = module; } + loadConfig(config: ConfigReader): ServiceBuilder { + const backendConfig = config.getOptionalConfig('backend'); + if (!backendConfig) { + return this; + } + + const baseOptions = readBaseOptions(backendConfig); + if (baseOptions.bindPort) { + this.port = baseOptions.bindPort; + } + if (baseOptions.bindHost) { + this.host = baseOptions.bindHost; + } + + const corsOptions = readCorsOptions(backendConfig); + if (corsOptions) { + this.corsOptions = corsOptions; + } + + return this; + } + setPort(port: number): ServiceBuilder { this.port = port; return this; } + setHost(host: string): ServiceBuilder { + this.host = host; + return this; + } + setLogger(logger: Logger): ServiceBuilder { this.logger = logger; return this; @@ -69,7 +99,7 @@ export class ServiceBuilderImpl implements ServiceBuilder { start(): Promise { const app = express(); - const { port, logger, corsOptions } = this.getOptions(); + const { port, host, logger, corsOptions } = this.getOptions(); app.use(helmet()); if (corsOptions) { @@ -89,8 +119,9 @@ export class ServiceBuilderImpl implements ServiceBuilder { logger.error(`Failed to start up on port ${port}, ${e}`); reject(e); }); + const server = stoppable( - app.listen(port, () => { + app.listen(port, host, () => { logger.info(`Listening on port ${port}`); }), 0, @@ -108,6 +139,7 @@ export class ServiceBuilderImpl implements ServiceBuilder { private getOptions(): { port: number; + host: string; logger: Logger; corsOptions?: cors.CorsOptions; } { @@ -118,6 +150,13 @@ export class ServiceBuilderImpl implements ServiceBuilder { port = parseInt(process.env.PORT ?? '', 10) || DEFAULT_PORT; } + let host: string; + if (this.host !== undefined) { + host = this.host; + } else { + host = process.env.HOST || DEFAULT_HOST; + } + let logger: Logger; if (this.logger) { logger = this.logger; @@ -127,6 +166,7 @@ export class ServiceBuilderImpl implements ServiceBuilder { return { port, + host, logger, corsOptions: this.corsOptions, }; diff --git a/packages/backend-common/src/service/lib/config.ts b/packages/backend-common/src/service/lib/config.ts new file mode 100644 index 0000000000..ead13cccec --- /dev/null +++ b/packages/backend-common/src/service/lib/config.ts @@ -0,0 +1,112 @@ +/* + * 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 { ConfigReader } from '@backstage/config'; +import { CorsOptions } from 'cors'; + +export type BaseOptions = { + bindPort?: number; + bindHost?: string; +}; + +/** + * Reads some base options out of a config object. + * + * @param config The root of a backend config object + * @returns A base options object + * + * @example + * ```json + * { + * baseUrl: "http://localhost:7000" + * bindPort: 7000 + * bindHost: "0.0.0.0" + * } + * ``` + */ +export function readBaseOptions(config: ConfigReader): BaseOptions { + return removeUnknown({ + bindPort: config.getOptionalNumber('bindPort'), + bindHost: config.getOptionalString('bindHost'), + }); +} + +/** + * Attempts to read a CORS options object from the root of a config object. + * + * @param config The root of a backend config object + * @returns A CORS options object, or undefined if not specified + * + * @example + * ```json + * { + * cors: { + * origin: "http://localhost:3000", + * credentials: true + * } + * } + * ``` + */ +export function readCorsOptions(config: ConfigReader): CorsOptions | undefined { + const cc = config.getOptionalConfig('cors'); + if (!cc) { + return undefined; + } + + return removeUnknown({ + origin: getOptionalStringOrStrings(cc, 'origin'), + methods: getOptionalStringOrStrings(cc, 'methods'), + allowedHeaders: getOptionalStringOrStrings(cc, 'allowedHeaders'), + exposedHeaders: getOptionalStringOrStrings(cc, 'exposedHeaders'), + credentials: cc.getOptionalBoolean('credentials'), + maxAge: cc.getOptionalNumber('maxAge'), + preflightContinue: cc.getOptionalBoolean('preflightContinue'), + optionsSuccessStatus: cc.getOptionalNumber('optionsSuccessStatus'), + }); +} + +function getOptionalStringOrStrings( + config: ConfigReader, + key: string, +): string | string[] | undefined { + const value = config.getOptional(key); + if ( + value === undefined || + typeof value === 'string' || + isStringArray(value) + ) { + return value; + } + throw new Error(`Expected string or array of strings, got ${typeof value}`); +} + +function isStringArray(value: any): value is string[] { + if (!Array.isArray(value)) { + return false; + } + for (const v of value) { + if (typeof v !== 'string') { + return false; + } + } + return true; +} + +function removeUnknown(obj: T): T { + return Object.fromEntries( + Object.entries(obj).filter(([, v]) => v !== undefined), + ) as T; +} diff --git a/packages/backend-common/src/service/types.ts b/packages/backend-common/src/service/types.ts index 306f0d587e..b39df27527 100644 --- a/packages/backend-common/src/service/types.ts +++ b/packages/backend-common/src/service/types.ts @@ -14,12 +14,20 @@ * limitations under the License. */ +import { ConfigReader } from '@backstage/config'; import cors from 'cors'; import { Router } from 'express'; import { Server } from 'http'; import { Logger } from 'winston'; export type ServiceBuilder = { + /** + * Sets the service parameters based on configuration. + * + * @param config The configuration to read + */ + loadConfig(config: ConfigReader): ServiceBuilder; + /** * Sets the port to listen on. * diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index df0d08fa7d..3246c5f6f5 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -55,7 +55,9 @@ function makeCreateEnv(loadedConfigs: AppConfig[]) { } async function main() { - const createEnv = makeCreateEnv(await loadConfig()); + const configs = await loadConfig(); + const configReader = ConfigReader.fromConfigs(configs); + const createEnv = makeCreateEnv(configs); const catalogEnv = useHotMemoize(module, () => createEnv('catalog')); const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder')); @@ -63,10 +65,7 @@ async function main() { const identityEnv = useHotMemoize(module, () => createEnv('identity')); const service = createServiceBuilder(module) - .enableCors({ - origin: 'http://localhost:3000', - credentials: true, - }) + .loadConfig(configReader) .addRouter('/catalog', await catalog(catalogEnv)) .addRouter('/scaffolder', await scaffolder(scaffolderEnv)) .addRouter( From 299609a0207ab23402c9421f5b065f380a22a325 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 18 Jun 2020 17:42:38 +0200 Subject: [PATCH 018/182] chore(scaffolder): refactoring some of the entity parsing away so that we can re-use it. and fix some of the semantic namings --- .../src/scaffolder/prepare/file.ts | 26 +++------ .../src/scaffolder/prepare/helpers.ts | 56 +++++++++++++++++++ .../src/scaffolder/prepare/index.ts | 1 + .../src/scaffolder/prepare/preparers.test.ts | 54 +++++++++++++++--- .../src/scaffolder/prepare/preparers.ts | 33 +++-------- .../src/scaffolder/prepare/types.ts | 6 +- 6 files changed, 121 insertions(+), 55 deletions(-) create mode 100644 plugins/scaffolder-backend/src/scaffolder/prepare/helpers.ts diff --git a/plugins/scaffolder-backend/src/scaffolder/prepare/file.ts b/plugins/scaffolder-backend/src/scaffolder/prepare/file.ts index b9e8942aae..7a924d9a99 100644 --- a/plugins/scaffolder-backend/src/scaffolder/prepare/file.ts +++ b/plugins/scaffolder-backend/src/scaffolder/prepare/file.ts @@ -16,32 +16,20 @@ import fs from 'fs-extra'; import path from 'path'; import os from 'os'; -import { - TemplateEntityV1alpha1, - LOCATION_ANNOTATION, -} from '@backstage/catalog-model'; +import { TemplateEntityV1alpha1 } from '@backstage/catalog-model'; +import { parseLocationAnnotation } from './helpers'; import { InputError } from '@backstage/backend-common'; import { PreparerBase } from './types'; export class FilePreparer implements PreparerBase { async prepare(template: TemplateEntityV1alpha1): Promise { - const location = template?.metadata?.annotations?.[LOCATION_ANNOTATION]; + const { protocol, location } = parseLocationAnnotation(template); - const [locationType, templateEntityLocation] = (location ?? '').split( - /:(.+)/, - ); - if (locationType !== 'file') { + if (protocol !== 'file') { throw new InputError( - `Wrong location type: ${locationType}, should be 'file'`, + `Wrong location protocol: ${protocol}, should be 'file'`, ); } - - if (!templateEntityLocation) { - throw new InputError( - `Couldn't parse location for template: ${template.metadata.name}`, - ); - } - const templateId = template.metadata.name; const tempDir = await fs.promises.mkdtemp( @@ -49,12 +37,12 @@ export class FilePreparer implements PreparerBase { ); const parentDirectory = path.resolve( - path.dirname(templateEntityLocation), + path.dirname(location), template.spec.path ?? '.', ); await fs.copy(parentDirectory, tempDir, { - filter: src => src !== templateEntityLocation, + filter: src => src !== location, }); return tempDir; diff --git a/plugins/scaffolder-backend/src/scaffolder/prepare/helpers.ts b/plugins/scaffolder-backend/src/scaffolder/prepare/helpers.ts new file mode 100644 index 0000000000..3f31f19d59 --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/prepare/helpers.ts @@ -0,0 +1,56 @@ +/* + * 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 { + TemplateEntityV1alpha1, + LOCATION_ANNOTATION, +} from '@backstage/catalog-model'; +import { InputError } from '@backstage/backend-common'; +import { RemoteProtocol } from './types'; + +export type ParsedLocationAnnotation = { + protocol: RemoteProtocol; + location: string; +}; + +export const parseLocationAnnotation = ( + entity: TemplateEntityV1alpha1, +): ParsedLocationAnnotation => { + const annotation = entity.metadata.annotations?.[LOCATION_ANNOTATION]; + + if (!annotation) { + throw new InputError( + `No location annotation provided in entity: ${entity.metadata.name}`, + ); + } + + // split on the first colon for the protocol and the rest after the first split + // is the location. + const [protocol, location] = annotation.split(/:(.+)/) as [ + RemoteProtocol?, + string?, + ]; + + if (!protocol || !location) { + throw new InputError( + `Failure to parse either protocol or location for entity: ${entity.metadata.name}`, + ); + } + + return { + protocol, + location, + }; +}; diff --git a/plugins/scaffolder-backend/src/scaffolder/prepare/index.ts b/plugins/scaffolder-backend/src/scaffolder/prepare/index.ts index 748e5cebc7..b9bc3a4294 100644 --- a/plugins/scaffolder-backend/src/scaffolder/prepare/index.ts +++ b/plugins/scaffolder-backend/src/scaffolder/prepare/index.ts @@ -15,3 +15,4 @@ */ export * from './preparers'; export * from './types'; +export * from './helpers'; diff --git a/plugins/scaffolder-backend/src/scaffolder/prepare/preparers.test.ts b/plugins/scaffolder-backend/src/scaffolder/prepare/preparers.test.ts index 96e6b7d5b9..a8f4b4ae99 100644 --- a/plugins/scaffolder-backend/src/scaffolder/prepare/preparers.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/prepare/preparers.test.ts @@ -15,18 +15,54 @@ */ import { Preparers } from '.'; import { TemplateEntityV1alpha1 } from '@backstage/catalog-model'; +import { FilePreparer } from './file'; describe('Preparers', () => { + const mockTemplate: TemplateEntityV1alpha1 = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Template', + metadata: { + annotations: { + 'backstage.io/managed-by-location': + 'file:/Users/blam/dev/spotify/backstage/plugins/scaffolder-backend/sample-templates/react-ssr-template/template.yaml', + }, + name: 'react-ssr-template', + title: 'React SSR Template', + description: + 'Next.js application skeleton for creating isomorphic web applications.', + uid: '7357f4c5-aa58-4a1e-9670-18931eef771f', + etag: 'YWUxZWQyY2EtZDkxMC00MDM0LWI0ODAtMDgwMWY0YzdlMWIw', + generation: 1, + }, + spec: { + type: 'cookiecutter', + path: '.', + }, + }; it('should throw an error when the preparer for the source location is not registered', () => { const preparers = new Preparers(); - const mockTemplate: TemplateEntityV1alpha1 = { + + expect(() => preparers.get(mockTemplate)).toThrow( + expect.objectContaining({ + message: 'No preparer registered for type: "file"', + }), + ); + }); + it('should return the correct preparer when the source matches', () => { + const preparers = new Preparers(); + const preparer = new FilePreparer(); + + preparers.register('file', preparer); + + expect(preparers.get(mockTemplate)).toBe(preparer); + }); + + it('should throw an error if the metadata tag does not exist in the entity', () => { + const brokenTemplate: TemplateEntityV1alpha1 = { apiVersion: 'backstage.io/v1alpha1', kind: 'Template', metadata: { - annotations: { - 'backstage.io/managed-by-location': - 'file:/Users/blam/dev/spotify/backstage/plugins/scaffolder-backend/sample-templates/react-ssr-template/template.yaml', - }, + annotations: {}, name: 'react-ssr-template', title: 'React SSR Template', description: @@ -41,12 +77,12 @@ describe('Preparers', () => { }, }; - expect(() => preparers.get(mockTemplate)).toThrow( + const preparers = new Preparers(); + + expect(() => preparers.get(brokenTemplate)).toThrow( expect.objectContaining({ - message: 'No preparer registered for type file', + message: expect.stringContaining('No location annotation provided'), }), ); }); - it('should return the correct preparer when the source matches'); - it('should throw an error if the srouce is not available'); }); diff --git a/plugins/scaffolder-backend/src/scaffolder/prepare/preparers.ts b/plugins/scaffolder-backend/src/scaffolder/prepare/preparers.ts index 97db3a3100..046890bdca 100644 --- a/plugins/scaffolder-backend/src/scaffolder/prepare/preparers.ts +++ b/plugins/scaffolder-backend/src/scaffolder/prepare/preparers.ts @@ -14,40 +14,25 @@ * limitations under the License. */ -import { PreparerBase, RemoteLocation, PreparerBuilder } from './types'; -import { - TemplateEntityV1alpha1, - LOCATION_ANNOTATION, -} from '@backstage/catalog-model'; +import { PreparerBase, RemoteProtocol, PreparerBuilder } from './types'; +import { TemplateEntityV1alpha1 } from '@backstage/catalog-model'; +import { parseLocationAnnotation } from './helpers'; export class Preparers implements PreparerBuilder { - private preparerMap = new Map(); + private preparerMap = new Map(); - register(key: RemoteLocation, processor: PreparerBase) { - this.preparerMap.set(key, processor); + register(protocol: RemoteProtocol, preparer: PreparerBase) { + this.preparerMap.set(protocol, preparer); } get(template: TemplateEntityV1alpha1): PreparerBase { - const preparerKey = this.getPreparerKeyFromEntity(template); - const preparer = this.preparerMap.get(preparerKey); + const { protocol } = parseLocationAnnotation(template); + const preparer = this.preparerMap.get(protocol); if (!preparer) { - throw new Error(`No preparer registered for type ${preparerKey}`); + throw new Error(`No preparer registered for type: "${protocol}"`); } return preparer; } - - private getPreparerKeyFromEntity( - entity: TemplateEntityV1alpha1, - ): RemoteLocation { - const annotation = entity.metadata.annotations?.[LOCATION_ANNOTATION] ?? ''; - const [key] = annotation?.split(':'); - - if (!key) { - throw new Error('Failed to parse the location data'); - } - - return key as RemoteLocation; - } } diff --git a/plugins/scaffolder-backend/src/scaffolder/prepare/types.ts b/plugins/scaffolder-backend/src/scaffolder/prepare/types.ts index 1c703d10fd..3251bd2780 100644 --- a/plugins/scaffolder-backend/src/scaffolder/prepare/types.ts +++ b/plugins/scaffolder-backend/src/scaffolder/prepare/types.ts @@ -25,8 +25,8 @@ export type PreparerBase = { }; export type PreparerBuilder = { - register(key: RemoteLocation, preparer: PreparerBase): void; - get(key: TemplateEntityV1alpha1): PreparerBase; + register(protocol: RemoteProtocol, preparer: PreparerBase): void; + get(template: TemplateEntityV1alpha1): PreparerBase; }; -export type RemoteLocation = 'file'; +export type RemoteProtocol = 'file'; From ed0539769441f550c539b956ff93c5238a0ccb9a Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 18 Jun 2020 18:09:36 +0200 Subject: [PATCH 019/182] chore(scaffolder): fix the exporting of the FilePreparer so we can actually setup a scaffolder in the backend --- packages/backend/src/plugins/scaffolder.ts | 8 +++++++- .../scaffolder-backend/src/scaffolder/prepare/index.ts | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts index 35840daa5b..ce3a7ea2bd 100644 --- a/packages/backend/src/plugins/scaffolder.ts +++ b/packages/backend/src/plugins/scaffolder.ts @@ -17,11 +17,17 @@ import { CookieCutter, createRouter, + FilePreparer, + Preparers, } from '@backstage/plugin-scaffolder-backend'; import type { PluginEnvironment } from '../types'; export default async function createPlugin({ logger }: PluginEnvironment) { const templater = new CookieCutter(); + const filePreparer = new FilePreparer(); + const preparers = new Preparers(); - return await createRouter({ storage: null, templater, logger }); + preparers.register('file', filePreparer); + + return await createRouter({ preparers, templater, logger }); } diff --git a/plugins/scaffolder-backend/src/scaffolder/prepare/index.ts b/plugins/scaffolder-backend/src/scaffolder/prepare/index.ts index b9bc3a4294..0b1a72eaa3 100644 --- a/plugins/scaffolder-backend/src/scaffolder/prepare/index.ts +++ b/plugins/scaffolder-backend/src/scaffolder/prepare/index.ts @@ -16,3 +16,4 @@ export * from './preparers'; export * from './types'; export * from './helpers'; +export * from './file'; From 026b9547b91e5500d974c4021f23f37ae7165ae2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Thu, 18 Jun 2020 20:52:25 +0200 Subject: [PATCH 020/182] address comments --- app-config.yaml | 3 +- .../src/service/lib/ServiceBuilderImpl.ts | 37 ++++--------------- .../backend-common/src/service/lib/config.ts | 28 ++++++++++---- 3 files changed, 30 insertions(+), 38 deletions(-) diff --git a/app-config.yaml b/app-config.yaml index c5777be4fa..39527c20bd 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -4,8 +4,7 @@ app: backend: baseUrl: http://localhost:7000 - bindPort: 7000 - bindHost: localhost + listen: 0.0.0.0:7000 cors: origin: http://localhost:3000 methods: [GET, POST, PUT, DELETE] diff --git a/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts b/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts index 3cceec0e87..19418b2671 100644 --- a/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts +++ b/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts @@ -57,11 +57,11 @@ export class ServiceBuilderImpl implements ServiceBuilder { } const baseOptions = readBaseOptions(backendConfig); - if (baseOptions.bindPort) { - this.port = baseOptions.bindPort; + if (baseOptions.listenPort) { + this.port = baseOptions.listenPort; } - if (baseOptions.bindHost) { - this.host = baseOptions.bindHost; + if (baseOptions.listenHost) { + this.host = baseOptions.listenHost; } const corsOptions = readCorsOptions(backendConfig); @@ -122,7 +122,7 @@ export class ServiceBuilderImpl implements ServiceBuilder { const server = stoppable( app.listen(port, host, () => { - logger.info(`Listening on port ${port}`); + logger.info(`Listening on ${host}:${port}`); }), 0, ); @@ -143,31 +143,10 @@ export class ServiceBuilderImpl implements ServiceBuilder { logger: Logger; corsOptions?: cors.CorsOptions; } { - let port: number; - if (this.port !== undefined) { - port = this.port; - } else { - port = parseInt(process.env.PORT ?? '', 10) || DEFAULT_PORT; - } - - let host: string; - if (this.host !== undefined) { - host = this.host; - } else { - host = process.env.HOST || DEFAULT_HOST; - } - - let logger: Logger; - if (this.logger) { - logger = this.logger; - } else { - logger = getRootLogger(); - } - return { - port, - host, - logger, + port: this.port ?? DEFAULT_PORT, + host: this.host ?? DEFAULT_HOST, + logger: this.logger ?? getRootLogger(), corsOptions: this.corsOptions, }; } diff --git a/packages/backend-common/src/service/lib/config.ts b/packages/backend-common/src/service/lib/config.ts index ead13cccec..064eacc4f2 100644 --- a/packages/backend-common/src/service/lib/config.ts +++ b/packages/backend-common/src/service/lib/config.ts @@ -18,8 +18,8 @@ import { ConfigReader } from '@backstage/config'; import { CorsOptions } from 'cors'; export type BaseOptions = { - bindPort?: number; - bindHost?: string; + listenPort?: number; + listenHost?: string; }; /** @@ -31,16 +31,17 @@ export type BaseOptions = { * @example * ```json * { - * baseUrl: "http://localhost:7000" - * bindPort: 7000 - * bindHost: "0.0.0.0" + * baseUrl: "http://localhost:7000", + * listen: "0.0.0.0:7000" * } * ``` */ export function readBaseOptions(config: ConfigReader): BaseOptions { + // TODO(freben): Expand this to support more addresses and perhaps optional + const { host, port } = parseListenAddress(config.getString('listen')); return removeUnknown({ - bindPort: config.getOptionalNumber('bindPort'), - bindHost: config.getOptionalString('bindHost'), + listenPort: port, + listenHost: host, }); } @@ -110,3 +111,16 @@ function removeUnknown(obj: T): T { Object.entries(obj).filter(([, v]) => v !== undefined), ) as T; } + +function parseListenAddress(value: string): { host?: string; port?: number } { + const parts = value.split(':'); + if (parts.length === 1) { + return { port: parseInt(parts[0], 10) }; + } + if (parts.length === 2) { + return { host: parts[0], port: parseInt(parts[1], 10) }; + } + throw new Error( + `Unable to parse listen address ${value}, expected or :`, + ); +} From fce3e7576cc456f633c0a86516b9f61668fa57ae Mon Sep 17 00:00:00 2001 From: Nikita Nek Dudnik Date: Thu, 18 Jun 2020 21:06:26 +0200 Subject: [PATCH 021/182] fix(catalog): tests --- .../CatalogFilter/CatalogFilter.test.tsx | 14 ++++++-------- .../components/CatalogPage/CatalogPage.test.tsx | 2 ++ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/plugins/catalog/src/components/CatalogFilter/CatalogFilter.test.tsx b/plugins/catalog/src/components/CatalogFilter/CatalogFilter.test.tsx index 56268697c8..f7143f73a2 100644 --- a/plugins/catalog/src/components/CatalogFilter/CatalogFilter.test.tsx +++ b/plugins/catalog/src/components/CatalogFilter/CatalogFilter.test.tsx @@ -131,15 +131,13 @@ describe('Catalog Filter', () => { ); for (const key of Object.keys(defaultFilterProps.entitiesByFilter)) { - await waitFor(() => - screen.getAllByText( - new RegExp( - `(${ - defaultFilterProps.entitiesByFilter[key as EntityGroup].length - })`, - ), - ), + const matcher = new RegExp( + `(${defaultFilterProps.entitiesByFilter[key as EntityGroup].length})`, ); + await waitFor(() => screen.getAllByText(matcher)); + screen + .getAllByText(matcher) + .forEach(el => expect(el).toBeInTheDocument()); } }); diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx index 8fae75b68b..a11ff8e2a6 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx @@ -85,6 +85,7 @@ describe('CatalogPage', () => { ), ); await waitFor(() => screen.getByText(/All Services \(2\)/)); + expect(screen.getByText(/All Services \(2\)/)).toBeInTheDocument(); }); it('should filter by owner', async () => { render( @@ -103,5 +104,6 @@ describe('CatalogPage', () => { ); fireEvent.click(screen.getByText(/Owned/)); await waitFor(() => screen.getByText(/Owned \(1\)/)); + expect(screen.getByText(/Owned \(1\)/)).toBeInTheDocument(); }); }); From eac8ac8bd0fdaf5f38edd0016d93b3422c0e1e08 Mon Sep 17 00:00:00 2001 From: Nikita Nek Dudnik Date: Thu, 18 Jun 2020 21:43:52 +0200 Subject: [PATCH 022/182] fix(catalog): make plugin conform to template --- plugins/catalog/package.json | 2 +- .../CatalogFilter/CatalogFilter.test.tsx | 10 +++----- .../CatalogPage/CatalogPage.test.tsx | 17 +++++++------ yarn.lock | 25 +------------------ 4 files changed, 15 insertions(+), 39 deletions(-) diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index 37a0397862..54d5737adb 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -43,7 +43,7 @@ "@backstage/dev-utils": "^0.1.1-alpha.9", "@backstage/test-utils": "^0.1.1-alpha.9", "@testing-library/jest-dom": "^5.7.0", - "@testing-library/react": "^10.2.1", + "@testing-library/react": "^9.3.2", "@testing-library/react-hooks": "^3.3.0", "@testing-library/user-event": "^10.2.4", "@types/jest": "^25.2.2", diff --git a/plugins/catalog/src/components/CatalogFilter/CatalogFilter.test.tsx b/plugins/catalog/src/components/CatalogFilter/CatalogFilter.test.tsx index f7143f73a2..cc4cc62fdb 100644 --- a/plugins/catalog/src/components/CatalogFilter/CatalogFilter.test.tsx +++ b/plugins/catalog/src/components/CatalogFilter/CatalogFilter.test.tsx @@ -15,7 +15,7 @@ */ import React from 'react'; -import { render, fireEvent, waitFor, screen } from '@testing-library/react'; +import { render, fireEvent } from '@testing-library/react'; import { wrapInTestApp } from '@backstage/test-utils'; import { CatalogFilter, CatalogFilterGroup } from './CatalogFilter'; import { EntityGroup } from '../../data/filters'; @@ -124,7 +124,7 @@ describe('Catalog Filter', () => { }, ]; - render( + const { getAllByText } = render( wrapInTestApp( , ), @@ -134,10 +134,8 @@ describe('Catalog Filter', () => { const matcher = new RegExp( `(${defaultFilterProps.entitiesByFilter[key as EntityGroup].length})`, ); - await waitFor(() => screen.getAllByText(matcher)); - screen - .getAllByText(matcher) - .forEach(el => expect(el).toBeInTheDocument()); + const items = await getAllByText(matcher); + items.forEach(el => expect(el).toBeInTheDocument()); } }); diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx index a11ff8e2a6..7ba1c39bb7 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx @@ -24,7 +24,7 @@ import { identityApiRef, } from '@backstage/core'; import { MockErrorApi, wrapInTestApp } from '@backstage/test-utils'; -import { screen, render, fireEvent, waitFor } from '@testing-library/react'; +import { render, fireEvent } from '@testing-library/react'; import React from 'react'; import { catalogApiRef } from '../..'; import { CatalogApi } from '../../api/types'; @@ -70,7 +70,7 @@ describe('CatalogPage', () => { // related to some theme issues in mui-table // https://github.com/mbrn/material-table/issues/1293 it('should render', async () => { - render( + const { findByText } = render( wrapInTestApp( { , ), ); - await waitFor(() => screen.getByText(/All Services \(2\)/)); - expect(screen.getByText(/All Services \(2\)/)).toBeInTheDocument(); + + const items = await findByText(/All Services \(2\)/); + expect(items).toBeInTheDocument(); }); it('should filter by owner', async () => { - render( + const { findByText, getByText } = render( wrapInTestApp( { , ), ); - fireEvent.click(screen.getByText(/Owned/)); - await waitFor(() => screen.getByText(/Owned \(1\)/)); - expect(screen.getByText(/Owned \(1\)/)).toBeInTheDocument(); + fireEvent.click(getByText(/Owned/)); + const items = await findByText(/Owned \(1\)/); + expect(items).toBeInTheDocument(); }); }); diff --git a/yarn.lock b/yarn.lock index a8b1cfb937..b3b0af2615 100644 --- a/yarn.lock +++ b/yarn.lock @@ -891,7 +891,7 @@ dependencies: regenerator-runtime "^0.13.4" -"@babel/runtime@^7.10.2", "@babel/runtime@^7.5.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== @@ -3285,16 +3285,6 @@ dom-accessibility-api "^0.4.2" pretty-format "^25.1.0" -"@testing-library/dom@^7.9.0": - version "7.16.1" - resolved "https://registry.npmjs.org/@testing-library/dom/-/dom-7.16.1.tgz#a6881d53612f2e8f7bcc0e0bd8825c6788cf57f2" - integrity sha512-u0Ck7tjWDyCcGn+f77JbUHa7PqgFu62ohRxegj1/H5P3REKsM+2roCvcnWJjMSHvK354NAS/Pgi92E9z6cB7Sw== - dependencies: - "@babel/runtime" "^7.10.2" - aria-query "^4.0.2" - dom-accessibility-api "^0.4.5" - pretty-format "^25.5.0" - "@testing-library/jest-dom@^5.7.0": version "5.9.0" resolved "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.9.0.tgz#86464c66cbe75e632b8adb636f539bfd0efc2c9c" @@ -3318,14 +3308,6 @@ "@babel/runtime" "^7.5.4" "@types/testing-library__react-hooks" "^3.0.0" -"@testing-library/react@^10.2.1": - version "10.2.1" - resolved "https://registry.npmjs.org/@testing-library/react/-/react-10.2.1.tgz#f0c5ac9072ad54c29672150943f35d6617263f26" - integrity sha512-pv2jZhiZgN1/alz1aImhSasZAOPg3er2Kgcfg9fzuw7aKPLxVengqqR1n0CJANeErR1DqORauQaod+gGUgAJOQ== - dependencies: - "@babel/runtime" "^7.10.2" - "@testing-library/dom" "^7.9.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" @@ -7716,11 +7698,6 @@ dom-accessibility-api@^0.4.2: resolved "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.4.3.tgz#93ca9002eb222fd5a343b6e5e6b9cf5929411c4c" integrity sha512-JZ8iPuEHDQzq6q0k7PKMGbrIdsgBB7TRrtVOUm4nSMCExlg5qQG4KXWTH2k90yggjM4tTumRGwTKJSldMzKyLA== -dom-accessibility-api@^0.4.5: - version "0.4.5" - resolved "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.4.5.tgz#d9c1cefa89f509d8cf132ab5d250004d755e76e3" - integrity sha512-HcPDilI95nKztbVikaN2vzwvmv0sE8Y2ZJFODy/m15n7mGXLeOKGiys9qWVbFbh+aq/KYj2lqMLybBOkYAEXqg== - dom-converter@^0.2: version "0.2.0" resolved "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768" From d58ad15ffb3f8de0952769f833225991d06ae800 Mon Sep 17 00:00:00 2001 From: Nikita Nek Dudnik Date: Fri, 19 Jun 2020 04:09:41 +0200 Subject: [PATCH 023/182] fix(catalog): remove duplicate import --- packages/storybook/.storybook/apis.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/storybook/.storybook/apis.js b/packages/storybook/.storybook/apis.js index d3c78fa2d6..65b2ec5aaa 100644 --- a/packages/storybook/.storybook/apis.js +++ b/packages/storybook/.storybook/apis.js @@ -2,7 +2,6 @@ import { ApiRegistry, alertApiRef, errorApiRef, - identityApiRef, oauthRequestApiRef, OAuthRequestManager, googleAuthApiRef, From f04844441cf2130953e426dfb68ea6c00d5dfd0f Mon Sep 17 00:00:00 2001 From: blam Date: Fri, 19 Jun 2020 11:05:25 +0200 Subject: [PATCH 024/182] chore(scaffolder): move express to dependencies for reasons --- plugins/scaffolder-backend/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index d6ac8d9ec9..0edf7a8fe9 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -23,6 +23,7 @@ "dependencies": { "@backstage/backend-common": "^0.1.1-alpha.9", "@backstage/catalog-model": "^0.1.1-alpha.9", + "@types/express": "^4.17.6", "compression": "^1.7.4", "cors": "^2.8.5", "dockerode": "^3.2.0", @@ -37,7 +38,6 @@ "devDependencies": { "@backstage/cli": "^0.1.1-alpha.9", "@types/fs-extra": "^9.0.1", - "@types/express": "^4.17.6", "@types/supertest": "^2.0.8", "supertest": "^4.0.2", "yaml": "^1.10.0" From 95344229ab11c432d52a229f6410e1c23eaedc50 Mon Sep 17 00:00:00 2001 From: blam Date: Sat, 20 Jun 2020 03:33:41 +0200 Subject: [PATCH 025/182] chore(scaffolder): moving things around to make things work for the actually scaffoling --- .../react-ssr-template/hooks/post_gen_project.sh | 2 +- .../.editorconfig | 0 .../.eslintignore | 0 .../.eslintrc.js | 0 .../.github/workflows/build.yml | 0 .../.gitignore | 0 .../.nvmrc | 0 .../README.md | 0 .../babel.config.js | 0 .../jest.config.js | 0 .../next-env.d.ts | 0 .../next.config.js | 0 .../package.json | 0 .../prettier.config.js | 0 .../public/static/fonts.css | 0 .../src/__tests__/index.test.tsx | 0 .../src/components/Header.tsx | 0 .../src/pages/_app.tsx | 0 .../src/pages/_document.tsx | 0 .../src/pages/api/ping.ts | 0 .../src/pages/index.tsx | 0 .../tsconfig.json | 0 .../src/scaffolder/templater/cookiecutter.ts | 2 +- plugins/scaffolder-backend/src/scaffolder/templater/index.ts | 2 +- plugins/scaffolder-backend/src/service/router.ts | 4 +++- 25 files changed, 6 insertions(+), 4 deletions(-) rename plugins/scaffolder-backend/sample-templates/react-ssr-template/{{{cookiecutter.componentId}} => {{cookiecutter.component_id}}}/.editorconfig (100%) rename plugins/scaffolder-backend/sample-templates/react-ssr-template/{{{cookiecutter.componentId}} => {{cookiecutter.component_id}}}/.eslintignore (100%) rename plugins/scaffolder-backend/sample-templates/react-ssr-template/{{{cookiecutter.componentId}} => {{cookiecutter.component_id}}}/.eslintrc.js (100%) rename plugins/scaffolder-backend/sample-templates/react-ssr-template/{{{cookiecutter.componentId}} => {{cookiecutter.component_id}}}/.github/workflows/build.yml (100%) rename plugins/scaffolder-backend/sample-templates/react-ssr-template/{{{cookiecutter.componentId}} => {{cookiecutter.component_id}}}/.gitignore (100%) rename plugins/scaffolder-backend/sample-templates/react-ssr-template/{{{cookiecutter.componentId}} => {{cookiecutter.component_id}}}/.nvmrc (100%) rename plugins/scaffolder-backend/sample-templates/react-ssr-template/{{{cookiecutter.componentId}} => {{cookiecutter.component_id}}}/README.md (100%) rename plugins/scaffolder-backend/sample-templates/react-ssr-template/{{{cookiecutter.componentId}} => {{cookiecutter.component_id}}}/babel.config.js (100%) rename plugins/scaffolder-backend/sample-templates/react-ssr-template/{{{cookiecutter.componentId}} => {{cookiecutter.component_id}}}/jest.config.js (100%) rename plugins/scaffolder-backend/sample-templates/react-ssr-template/{{{cookiecutter.componentId}} => {{cookiecutter.component_id}}}/next-env.d.ts (100%) rename plugins/scaffolder-backend/sample-templates/react-ssr-template/{{{cookiecutter.componentId}} => {{cookiecutter.component_id}}}/next.config.js (100%) rename plugins/scaffolder-backend/sample-templates/react-ssr-template/{{{cookiecutter.componentId}} => {{cookiecutter.component_id}}}/package.json (100%) rename plugins/scaffolder-backend/sample-templates/react-ssr-template/{{{cookiecutter.componentId}} => {{cookiecutter.component_id}}}/prettier.config.js (100%) rename plugins/scaffolder-backend/sample-templates/react-ssr-template/{{{cookiecutter.componentId}} => {{cookiecutter.component_id}}}/public/static/fonts.css (100%) rename plugins/scaffolder-backend/sample-templates/react-ssr-template/{{{cookiecutter.componentId}} => {{cookiecutter.component_id}}}/src/__tests__/index.test.tsx (100%) rename plugins/scaffolder-backend/sample-templates/react-ssr-template/{{{cookiecutter.componentId}} => {{cookiecutter.component_id}}}/src/components/Header.tsx (100%) rename plugins/scaffolder-backend/sample-templates/react-ssr-template/{{{cookiecutter.componentId}} => {{cookiecutter.component_id}}}/src/pages/_app.tsx (100%) rename plugins/scaffolder-backend/sample-templates/react-ssr-template/{{{cookiecutter.componentId}} => {{cookiecutter.component_id}}}/src/pages/_document.tsx (100%) rename plugins/scaffolder-backend/sample-templates/react-ssr-template/{{{cookiecutter.componentId}} => {{cookiecutter.component_id}}}/src/pages/api/ping.ts (100%) rename plugins/scaffolder-backend/sample-templates/react-ssr-template/{{{cookiecutter.componentId}} => {{cookiecutter.component_id}}}/src/pages/index.tsx (100%) rename plugins/scaffolder-backend/sample-templates/react-ssr-template/{{{cookiecutter.componentId}} => {{cookiecutter.component_id}}}/tsconfig.json (100%) diff --git a/plugins/scaffolder-backend/sample-templates/react-ssr-template/hooks/post_gen_project.sh b/plugins/scaffolder-backend/sample-templates/react-ssr-template/hooks/post_gen_project.sh index c6d477d91a..033a102fed 100644 --- a/plugins/scaffolder-backend/sample-templates/react-ssr-template/hooks/post_gen_project.sh +++ b/plugins/scaffolder-backend/sample-templates/react-ssr-template/hooks/post_gen_project.sh @@ -9,4 +9,4 @@ sed -i -e "s/__component_id__/{{ cookiecutter.component_id }}/g" package.json mv ../../node_modules.tmp ../../\{\{cookiecutter.component_id\}\}/node_modules 2>/dev/null ||: # move back the build directory that was moved out in the pre_gen hook (if it exists) -mv ../../build.tmp ../../\{\{cookiecutter.component_id\}\}/build 2>/dev/null ||: \ No newline at end of file +mv ../../build.tmp ../../\{\{cookiecutter.component_id\}\}/build 2>/dev/null ||: diff --git a/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.componentId}}/.editorconfig b/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.component_id}}/.editorconfig similarity index 100% rename from plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.componentId}}/.editorconfig rename to plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.component_id}}/.editorconfig diff --git a/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.componentId}}/.eslintignore b/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.component_id}}/.eslintignore similarity index 100% rename from plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.componentId}}/.eslintignore rename to plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.component_id}}/.eslintignore diff --git a/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.componentId}}/.eslintrc.js b/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.component_id}}/.eslintrc.js similarity index 100% rename from plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.componentId}}/.eslintrc.js rename to plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.component_id}}/.eslintrc.js diff --git a/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.componentId}}/.github/workflows/build.yml b/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.component_id}}/.github/workflows/build.yml similarity index 100% rename from plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.componentId}}/.github/workflows/build.yml rename to plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.component_id}}/.github/workflows/build.yml diff --git a/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.componentId}}/.gitignore b/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.component_id}}/.gitignore similarity index 100% rename from plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.componentId}}/.gitignore rename to plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.component_id}}/.gitignore diff --git a/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.componentId}}/.nvmrc b/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.component_id}}/.nvmrc similarity index 100% rename from plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.componentId}}/.nvmrc rename to plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.component_id}}/.nvmrc diff --git a/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.componentId}}/README.md b/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.component_id}}/README.md similarity index 100% rename from plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.componentId}}/README.md rename to plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.component_id}}/README.md diff --git a/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.componentId}}/babel.config.js b/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.component_id}}/babel.config.js similarity index 100% rename from plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.componentId}}/babel.config.js rename to plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.component_id}}/babel.config.js diff --git a/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.componentId}}/jest.config.js b/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.component_id}}/jest.config.js similarity index 100% rename from plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.componentId}}/jest.config.js rename to plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.component_id}}/jest.config.js diff --git a/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.componentId}}/next-env.d.ts b/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.component_id}}/next-env.d.ts similarity index 100% rename from plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.componentId}}/next-env.d.ts rename to plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.component_id}}/next-env.d.ts diff --git a/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.componentId}}/next.config.js b/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.component_id}}/next.config.js similarity index 100% rename from plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.componentId}}/next.config.js rename to plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.component_id}}/next.config.js diff --git a/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.componentId}}/package.json b/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.component_id}}/package.json similarity index 100% rename from plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.componentId}}/package.json rename to plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.component_id}}/package.json diff --git a/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.componentId}}/prettier.config.js b/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.component_id}}/prettier.config.js similarity index 100% rename from plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.componentId}}/prettier.config.js rename to plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.component_id}}/prettier.config.js diff --git a/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.componentId}}/public/static/fonts.css b/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.component_id}}/public/static/fonts.css similarity index 100% rename from plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.componentId}}/public/static/fonts.css rename to plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.component_id}}/public/static/fonts.css diff --git a/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.componentId}}/src/__tests__/index.test.tsx b/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.component_id}}/src/__tests__/index.test.tsx similarity index 100% rename from plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.componentId}}/src/__tests__/index.test.tsx rename to plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.component_id}}/src/__tests__/index.test.tsx diff --git a/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.componentId}}/src/components/Header.tsx b/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.component_id}}/src/components/Header.tsx similarity index 100% rename from plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.componentId}}/src/components/Header.tsx rename to plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.component_id}}/src/components/Header.tsx diff --git a/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.componentId}}/src/pages/_app.tsx b/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.component_id}}/src/pages/_app.tsx similarity index 100% rename from plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.componentId}}/src/pages/_app.tsx rename to plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.component_id}}/src/pages/_app.tsx diff --git a/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.componentId}}/src/pages/_document.tsx b/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.component_id}}/src/pages/_document.tsx similarity index 100% rename from plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.componentId}}/src/pages/_document.tsx rename to plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.component_id}}/src/pages/_document.tsx diff --git a/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.componentId}}/src/pages/api/ping.ts b/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.component_id}}/src/pages/api/ping.ts similarity index 100% rename from plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.componentId}}/src/pages/api/ping.ts rename to plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.component_id}}/src/pages/api/ping.ts diff --git a/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.componentId}}/src/pages/index.tsx b/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.component_id}}/src/pages/index.tsx similarity index 100% rename from plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.componentId}}/src/pages/index.tsx rename to plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.component_id}}/src/pages/index.tsx diff --git a/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.componentId}}/tsconfig.json b/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.component_id}}/tsconfig.json similarity index 100% rename from plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.componentId}}/tsconfig.json rename to plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.component_id}}/tsconfig.json diff --git a/plugins/scaffolder-backend/src/scaffolder/templater/cookiecutter.ts b/plugins/scaffolder-backend/src/scaffolder/templater/cookiecutter.ts index c2bf472165..ee5265fde8 100644 --- a/plugins/scaffolder-backend/src/scaffolder/templater/cookiecutter.ts +++ b/plugins/scaffolder-backend/src/scaffolder/templater/cookiecutter.ts @@ -25,7 +25,7 @@ export class CookieCutter implements TemplaterBase { ...options.values, }; - await fs.writeJSON(options.directory, cookieInfo); + await fs.writeJSON(`${options.directory}/cookiecutter.json`, cookieInfo); return ''; // run cookie cutter with new json } diff --git a/plugins/scaffolder-backend/src/scaffolder/templater/index.ts b/plugins/scaffolder-backend/src/scaffolder/templater/index.ts index 6f728d4829..80570a1c48 100644 --- a/plugins/scaffolder-backend/src/scaffolder/templater/index.ts +++ b/plugins/scaffolder-backend/src/scaffolder/templater/index.ts @@ -15,7 +15,7 @@ */ export interface RequiredTemplateValues { - componentId: string; + component_id: string; } export interface TemplaterRunOptions { diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index b05983e1b4..c9b92494fd 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -67,7 +67,9 @@ export async function createRouter( const path = await preparer.prepare(mockEntity); // Run the templater on the mock directory with values from the post body - await templater.run({ directory: path, values: { componentId: 'test' } }); + await templater.run({ directory: path, values: { component_id: 'test' } }); + + console.warn(path); }); const app = express(); From 22fbe0aaab21373f67989158973eb1f19739957d Mon Sep 17 00:00:00 2001 From: blam Date: Sat, 20 Jun 2020 05:56:48 +0200 Subject: [PATCH 026/182] feat(scaffolder): added the ability to process github remote sources, without authenication for now at least --- packages/backend/src/plugins/scaffolder.ts | 3 + plugins/scaffolder-backend/package.json | 4 + plugins/scaffolder-backend/scripts/mock-data | 16 +- .../src/scaffolder/prepare/github.ts | 58 +++++ .../src/scaffolder/prepare/index.ts | 1 + .../src/scaffolder/prepare/types.ts | 2 +- .../scaffolder-backend/src/service/router.ts | 15 +- yarn.lock | 201 ++++++++++++++++-- 8 files changed, 274 insertions(+), 26 deletions(-) create mode 100644 plugins/scaffolder-backend/src/scaffolder/prepare/github.ts diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts index ce3a7ea2bd..95d28eacb1 100644 --- a/packages/backend/src/plugins/scaffolder.ts +++ b/packages/backend/src/plugins/scaffolder.ts @@ -18,6 +18,7 @@ import { CookieCutter, createRouter, FilePreparer, + GithubPreparer, Preparers, } from '@backstage/plugin-scaffolder-backend'; import type { PluginEnvironment } from '../types'; @@ -25,9 +26,11 @@ import type { PluginEnvironment } from '../types'; export default async function createPlugin({ logger }: PluginEnvironment) { const templater = new CookieCutter(); const filePreparer = new FilePreparer(); + const githubPreparer = new GithubPreparer(); const preparers = new Preparers(); preparers.register('file', filePreparer); + preparers.register('github', githubPreparer); return await createRouter({ preparers, templater, logger }); } diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index 0edf7a8fe9..e586300b15 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -30,14 +30,18 @@ "express": "^4.17.1", "express-promise-router": "^3.0.3", "fs-extra": "^9.0.0", + "git-url-parse": "^11.1.2", "globby": "^11.0.0", "helmet": "^3.22.0", "morgan": "^1.10.0", + "nodegit": "0.26.5", "winston": "^3.2.1" }, "devDependencies": { "@backstage/cli": "^0.1.1-alpha.9", "@types/fs-extra": "^9.0.1", + "@types/git-url-parse": "^9.0.0", + "@types/nodegit": "0.26.5", "@types/supertest": "^2.0.8", "supertest": "^4.0.2", "yaml": "^1.10.0" diff --git a/plugins/scaffolder-backend/scripts/mock-data b/plugins/scaffolder-backend/scripts/mock-data index be3fa2b6cf..7f534fec97 100755 --- a/plugins/scaffolder-backend/scripts/mock-data +++ b/plugins/scaffolder-backend/scripts/mock-data @@ -1,8 +1,14 @@ #!/usr/bin/env bash -curl \ ---location \ ---request POST 'localhost:7000/catalog/locations' \ ---header 'Content-Type: application/json' \ ---data-raw "{\"type\": \"file\", \"target\": \"$(pwd)/sample-templates/react-ssr-template/template.yaml\"}" +# curl \ +# --location \ +# --request POST 'localhost:7000/catalog/locations' \ +# --header 'Content-Type: application/json' \ +# --data-raw "{\"type\": \"file\", \"target\": \"$(pwd)/sample-templates/react-ssr-template/template.yaml\"}" + +curl \ + --location \ + --request POST 'localhost:7000/catalog/locations' \ + --header 'Content-Type: application/json' \ + --data-raw "{\"type\": \"github\", \"target\": \"https://github.com/benjdlambert/backstage-graphql-template/blob/master/template.yaml\"}" diff --git a/plugins/scaffolder-backend/src/scaffolder/prepare/github.ts b/plugins/scaffolder-backend/src/scaffolder/prepare/github.ts new file mode 100644 index 0000000000..3f711bc477 --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/prepare/github.ts @@ -0,0 +1,58 @@ +/* + * 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 fs from 'fs-extra'; +import path from 'path'; +import os from 'os'; +import { TemplateEntityV1alpha1 } from '@backstage/catalog-model'; +import { parseLocationAnnotation } from './helpers'; +import { InputError } from '@backstage/backend-common'; +import { PreparerBase } from './types'; +import GitUriParser from 'git-url-parse'; +import { Clone, CheckoutOptions } from 'nodegit'; + +export class GithubPreparer implements PreparerBase { + async prepare(template: TemplateEntityV1alpha1): Promise { + const { protocol, location } = parseLocationAnnotation(template); + + if (protocol !== 'github') { + throw new InputError( + `Wrong location protocol: ${protocol}, should be 'github'`, + ); + } + const templateId = template.metadata.name; + + const parsedGitLocation = GitUriParser(location); + const repositoryCheckoutUrl = parsedGitLocation.toString('https'); + const tempDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), templateId), + ); + + const templateDirectory = path.join( + `${path.dirname(parsedGitLocation.filepath)}`, + template.spec.path ?? '.', + ); + + const checkoutOptions = new CheckoutOptions(); + checkoutOptions.paths = [templateDirectory]; + + await Clone.clone(repositoryCheckoutUrl, tempDir, { + checkoutOpts: checkoutOptions, + // TODO(blam): Maybe need some auth here? + }); + + return path.resolve(tempDir, templateDirectory); + } +} diff --git a/plugins/scaffolder-backend/src/scaffolder/prepare/index.ts b/plugins/scaffolder-backend/src/scaffolder/prepare/index.ts index 0b1a72eaa3..4ae4f68164 100644 --- a/plugins/scaffolder-backend/src/scaffolder/prepare/index.ts +++ b/plugins/scaffolder-backend/src/scaffolder/prepare/index.ts @@ -17,3 +17,4 @@ export * from './preparers'; export * from './types'; export * from './helpers'; export * from './file'; +export * from './github'; diff --git a/plugins/scaffolder-backend/src/scaffolder/prepare/types.ts b/plugins/scaffolder-backend/src/scaffolder/prepare/types.ts index 3251bd2780..a6e42c465f 100644 --- a/plugins/scaffolder-backend/src/scaffolder/prepare/types.ts +++ b/plugins/scaffolder-backend/src/scaffolder/prepare/types.ts @@ -29,4 +29,4 @@ export type PreparerBuilder = { get(template: TemplateEntityV1alpha1): PreparerBase; }; -export type RemoteProtocol = 'file'; +export type RemoteProtocol = 'file' | 'github'; diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index c9b92494fd..ce0324ead2 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -44,19 +44,20 @@ export async function createRouter( kind: 'Template', metadata: { annotations: { - 'backstage.io/managed-by-location': `file:${__dirname}/../../sample-templates/react-ssr-template/template.yaml`, + 'backstage.io/managed-by-location': + 'github:https://github.com/benjdlambert/backstage-graphql-template/blob/master/template.yaml', }, - name: 'react-ssr-template', - title: 'React SSR Template', + name: 'graphql-starter', + title: 'GraphQL Service', description: - 'Next.js application skeleton for creating isomorphic web applications.', - uid: '7357f4c5-aa58-4a1e-9670-18931eef771f', - etag: 'YWUxZWQyY2EtZDkxMC00MDM0LWI0ODAtMDgwMWY0YzdlMWIw', + 'A GraphQL starter template for backstage to get you up and running\nthe best pracices with GraphQL\n', + uid: '9cf16bad-16e0-4213-b314-c4eec773c50b', + etag: 'ZTkxMjUxMjUtYWY3Yi00MjU2LWFkYWMtZTZjNjU5ZjJhOWM2', generation: 1, }, spec: { type: 'cookiecutter', - path: '.', + path: './template', }, }; diff --git a/yarn.lock b/yarn.lock index b1b521b438..11be8c5742 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3568,6 +3568,11 @@ dependencies: "@types/node" "*" +"@types/git-url-parse@^9.0.0": + version "9.0.0" + resolved "https://registry.npmjs.org/@types/git-url-parse/-/git-url-parse-9.0.0.tgz#aac1315a44fa4ed5a52c3820f6c3c2fb79cbd12d" + integrity sha512-kA2RxBT/r/ZuDDKwMl+vFWn1Z0lfm1/Ik6Qb91wnSzyzCDa/fkM8gIOq6ruB7xfr37n6Mj5dyivileUVKsidlg== + "@types/glob@^7.1.1": version "7.1.1" resolved "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575" @@ -3767,6 +3772,13 @@ resolved "https://registry.npmjs.org/@types/node/-/node-12.12.30.tgz#3501e6f09b954de9c404671cefdbcc5d9d7c45f6" integrity sha512-sz9MF/zk6qVr3pAnM0BSQvYIBK44tS75QC5N+VbWSE4DjCV/pJ+UzCW/F+vVnl7TkOPcuwQureKNtSSwjBTaMg== +"@types/nodegit@0.26.5": + version "0.26.5" + resolved "https://registry.npmjs.org/@types/nodegit/-/nodegit-0.26.5.tgz#f13032617da3894620b6132828f0136a65043e14" + integrity sha512-Dszf6yKBPLSPDC18QtY5LFRaRoE682/BHEhPQqwPtSBh0pG9uTfrhh1fnt61OSXiF8s4lBJunXbBpfwxvhQElQ== + dependencies: + "@types/node" "*" + "@types/normalize-package-data@^2.4.0": version "2.4.0" resolved "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e" @@ -4915,7 +4927,7 @@ arrify@^1.0.1: resolved "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0= -asap@^2.0.0: +asap@^2.0.0, asap@~2.0.3: version "2.0.6" resolved "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= @@ -5480,6 +5492,14 @@ bindings@^1.5.0: dependencies: file-uri-to-path "1.0.0" +bl@^1.0.0: + version "1.2.2" + resolved "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz#a160911717103c07410cef63ef51b397c025af9c" + integrity sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA== + dependencies: + readable-stream "^2.3.5" + safe-buffer "^5.1.1" + bl@^4.0.1: version "4.0.2" resolved "https://registry.npmjs.org/bl/-/bl-4.0.2.tgz#52b71e9088515d0606d9dd9cc7aa48dc1f98e73a" @@ -5694,11 +5714,29 @@ btoa-lite@^1.0.0: resolved "https://registry.npmjs.org/btoa-lite/-/btoa-lite-1.0.0.tgz#337766da15801210fdd956c22e9c6891ab9d0337" integrity sha1-M3dm2hWAEhD92VbCLpxokaudAzc= +buffer-alloc-unsafe@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0" + integrity sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg== + +buffer-alloc@^1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec" + integrity sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow== + dependencies: + buffer-alloc-unsafe "^1.1.0" + buffer-fill "^1.0.0" + buffer-crc32@~0.2.3: version "0.2.13" resolved "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= +buffer-fill@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c" + integrity sha1-+PeLdniYiO858gXNY39o5wISKyw= + buffer-from@1.x, buffer-from@^1.0.0: version "1.1.1" resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" @@ -6065,7 +6103,7 @@ chokidar@^3.2.2, chokidar@^3.3.0, chokidar@^3.3.1: optionalDependencies: fsevents "~2.1.2" -chownr@^1.1.1, chownr@^1.1.2: +chownr@^1.0.1, chownr@^1.1.1, chownr@^1.1.2: version "1.1.4" resolved "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== @@ -9190,6 +9228,15 @@ fs-extra@^0.30.0: path-is-absolute "^1.0.0" rimraf "^2.2.8" +fs-extra@^7.0.0: + version "7.0.1" + resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" + integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw== + dependencies: + graceful-fs "^4.1.2" + jsonfile "^4.0.0" + universalify "^0.1.0" + fs-extra@^9.0.0: version "9.0.0" resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.0.tgz#b6afc31036e247b2466dc99c29ae797d5d4580a3" @@ -11848,6 +11895,13 @@ json5@^1.0.1: dependencies: minimist "^1.2.0" +json5@^2.1.0: + version "2.1.3" + resolved "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43" + integrity sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA== + dependencies: + minimist "^1.2.5" + jsonfile@^2.1.0: version "2.4.0" resolved "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8" @@ -13231,6 +13285,11 @@ nan@^2.12.1: resolved "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== +nan@^2.14.0: + version "2.14.1" + resolved "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01" + integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw== + nano-css@^5.2.1: version "5.3.0" resolved "https://registry.npmjs.org/nano-css/-/nano-css-5.3.0.tgz#9d3cd29788d48b6a07f52aa4aec7cf4da427b6b5" @@ -13335,6 +13394,23 @@ node-forge@^0.7.0: resolved "https://registry.npmjs.org/node-forge/-/node-forge-0.7.6.tgz#fdf3b418aee1f94f0ef642cd63486c77ca9724ac" integrity sha512-sol30LUpz1jQFBjOKwbjxijiE3b6pjd74YwfD0fJOKPjF+fONKb2Yg8rYgS6+bK6VDl+/wfr4IYpC7jDzLUIfw== +node-gyp@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/node-gyp/-/node-gyp-4.0.0.tgz#972654af4e5dd0cd2a19081b4b46fe0442ba6f45" + integrity sha512-2XiryJ8sICNo6ej8d0idXDEMKfVfFK7kekGCtJAuelGsYHQxhj13KTf95swTCN2dZ/4lTfZ84Fu31jqJEEgjWA== + dependencies: + glob "^7.0.3" + graceful-fs "^4.1.2" + mkdirp "^0.5.0" + nopt "2 || 3" + npmlog "0 || 1 || 2 || 3 || 4" + osenv "0" + request "^2.87.0" + rimraf "2" + semver "~5.3.0" + tar "^4.4.8" + which "1" + node-gyp@^5.0.2: version "5.1.0" resolved "https://registry.npmjs.org/node-gyp/-/node-gyp-5.1.0.tgz#8e31260a7af4a2e2f994b0673d4e0b3866156332" @@ -13424,6 +13500,22 @@ node-pre-gyp@^0.11.0: semver "^5.3.0" tar "^4" +node-pre-gyp@^0.13.0: + version "0.13.0" + resolved "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.13.0.tgz#df9ab7b68dd6498137717838e4f92a33fc9daa42" + integrity sha512-Md1D3xnEne8b/HGVQkZZwV27WUi1ZRuZBij24TNaZwUPU3ZAFtvT6xxJGaUVillfmMKnn5oD1HoGsp2Ftik7SQ== + dependencies: + detect-libc "^1.0.2" + mkdirp "^0.5.1" + needle "^2.2.1" + nopt "^4.0.1" + npm-packlist "^1.1.6" + npmlog "^4.0.2" + rc "^1.2.7" + rimraf "^2.6.1" + semver "^5.3.0" + tar "^4" + node-releases@^1.1.29, node-releases@^1.1.52: version "1.1.52" resolved "https://registry.npmjs.org/node-releases/-/node-releases-1.1.52.tgz#bcffee3e0a758e92e44ecfaecd0a47554b0bcba9" @@ -13439,6 +13531,29 @@ node-request-interceptor@^0.2.5: debug "^4.1.1" headers-utils "^1.2.0" +nodegit-promise@~4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/nodegit-promise/-/nodegit-promise-4.0.0.tgz#5722b184f2df7327161064a791d2e842c9167b34" + integrity sha1-VyKxhPLfcycWEGSnkdLoQskWezQ= + dependencies: + asap "~2.0.3" + +nodegit@0.26.5: + version "0.26.5" + resolved "https://registry.npmjs.org/nodegit/-/nodegit-0.26.5.tgz#1534d8aaa52de7acfbbe18de28df2075de86f7d2" + integrity sha512-l9l2zhcJ0V7FYzPdXIsuJcXN8UnLuhQgM+377HJfCYE/eupL/OWtMVvUOq42F9dRsgC3bAYH9j2Xbwr0lpYVZQ== + dependencies: + fs-extra "^7.0.0" + json5 "^2.1.0" + lodash "^4.17.14" + nan "^2.14.0" + node-gyp "^4.0.0" + node-pre-gyp "^0.13.0" + promisify-node "~0.3.0" + ramda "^0.25.0" + request-promise-native "^1.0.5" + tar-fs "^1.16.3" + nodemon@^2.0.2: version "2.0.4" resolved "https://registry.npmjs.org/nodemon/-/nodemon-2.0.4.tgz#55b09319eb488d6394aa9818148c0c2d1c04c416" @@ -13455,6 +13570,13 @@ nodemon@^2.0.2: undefsafe "^2.0.2" update-notifier "^4.0.0" +"nopt@2 || 3": + version "3.0.6" + resolved "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" + integrity sha1-xkZdvwirzU2zWTF/eaxopkayj/k= + dependencies: + abbrev "1" + nopt@^4.0.1: version "4.0.3" resolved "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz#a375cad9d02fd921278d954c2254d5aa57e15e48" @@ -13585,7 +13707,7 @@ npm-run-path@^4.0.0: dependencies: path-key "^3.0.0" -npmlog@^4.0.2, npmlog@^4.1.2: +"npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.2, npmlog@^4.1.2: version "4.1.2" resolved "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== @@ -13884,7 +14006,7 @@ os-tmpdir@^1.0.0, os-tmpdir@~1.0.2: resolved "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= -osenv@^0.1.4, osenv@^0.1.5: +osenv@0, osenv@^0.1.4, osenv@^0.1.5: version "0.1.5" resolved "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g== @@ -15079,6 +15201,13 @@ promise.series@^0.2.0: resolved "https://registry.npmjs.org/promise.series/-/promise.series-0.2.0.tgz#2cc7ebe959fc3a6619c04ab4dbdc9e452d864bbd" integrity sha1-LMfr6Vn8OmYZwEq029yeRS2GS70= +promisify-node@~0.3.0: + version "0.3.0" + resolved "https://registry.npmjs.org/promisify-node/-/promisify-node-0.3.0.tgz#b4b55acf90faa7d2b8b90ca396899086c03060cf" + integrity sha1-tLVaz5D6p9K4uQyjlomQhsAwYM8= + dependencies: + nodegit-promise "~4.0.0" + prompts@^2.0.1: version "2.3.2" resolved "https://registry.npmjs.org/prompts/-/prompts-2.3.2.tgz#480572d89ecf39566d2bd3fe2c9fccb7c4c0b068" @@ -15174,6 +15303,14 @@ public-encrypt@^4.0.0: randombytes "^2.0.1" safe-buffer "^5.1.2" +pump@^1.0.0: + version "1.0.3" + resolved "https://registry.npmjs.org/pump/-/pump-1.0.3.tgz#5dfe8311c33bbf6fc18261f9f34702c47c08a954" + integrity sha512-8k0JupWme55+9tCVE+FS5ULT3K6AbgqrGa58lTT49RpyfwwcGedHqaC5LlQNdEAumn/wFsu6aPwkuPMioy8kqw== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + pump@^2.0.0: version "2.0.1" resolved "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909" @@ -15284,6 +15421,11 @@ ramda@^0.21.0: resolved "https://registry.npmjs.org/ramda/-/ramda-0.21.0.tgz#a001abedb3ff61077d4ff1d577d44de77e8d0a35" integrity sha1-oAGr7bP/YQd9T/HVd9RN536NCjU= +ramda@^0.25.0: + version "0.25.0" + resolved "https://registry.npmjs.org/ramda/-/ramda-0.25.0.tgz#8fdf68231cffa90bc2f9460390a0cb74a29b29a9" + integrity sha512-GXpfrYVPwx3K7RQ6aYT8KPS8XViSXUVJT1ONhoKPE9VAleW42YE+U+8VEyGWt41EnEQW7gwecYJriTI0pKoecQ== + randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5: version "2.1.0" resolved "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" @@ -15889,7 +16031,7 @@ read@1, read@~1.0.1: dependencies: mute-stream "~0.0.4" -"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6: +"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.0, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6: version "2.3.7" resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== @@ -16367,6 +16509,13 @@ rifm@^0.7.0: dependencies: "@babel/runtime" "^7.3.1" +rimraf@2, rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3, rimraf@^2.7.1: + version "2.7.1" + resolved "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== + dependencies: + glob "^7.1.3" + rimraf@2.6.3: version "2.6.3" resolved "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" @@ -16374,13 +16523,6 @@ rimraf@2.6.3: dependencies: glob "^7.1.3" -rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3, rimraf@^2.7.1: - version "2.7.1" - resolved "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" - integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== - dependencies: - glob "^7.1.3" - rimraf@^3.0.0: version "3.0.2" resolved "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" @@ -16681,6 +16823,11 @@ semver@^6.0.0, semver@^6.2.0, semver@^6.3.0: resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== +semver@~5.3.0: + version "5.3.0" + resolved "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" + integrity sha1-myzl094C0XxgEq0yaqa00M9U+U8= + send@0.17.1: version "0.17.1" resolved "https://registry.npmjs.org/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" @@ -17807,6 +17954,16 @@ tapable@^1.0.0, tapable@^1.1.3: resolved "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== +tar-fs@^1.16.3: + version "1.16.3" + resolved "https://registry.npmjs.org/tar-fs/-/tar-fs-1.16.3.tgz#966a628841da2c4010406a82167cbd5e0c72d509" + integrity sha512-NvCeXpYx7OsmOh8zIOP/ebG55zZmxLE0etfWRbWok+q2Qo8x/vOR/IJT1taADXPe+jsiu9axDb3X4B+iIgNlKw== + dependencies: + chownr "^1.0.1" + mkdirp "^0.5.1" + pump "^1.0.0" + tar-stream "^1.1.2" + tar-fs@~2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.1.tgz#e44086c1c60d31a4f0cf893b1c4e155dabfae9e2" @@ -17817,6 +17974,19 @@ tar-fs@~2.0.1: pump "^3.0.0" tar-stream "^2.0.0" +tar-stream@^1.1.2: + version "1.6.2" + resolved "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz#8ea55dab37972253d9a9af90fdcd559ae435c555" + integrity sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A== + dependencies: + bl "^1.0.0" + buffer-alloc "^1.2.0" + end-of-stream "^1.0.0" + fs-constants "^1.0.0" + readable-stream "^2.3.0" + to-buffer "^1.1.1" + xtend "^4.0.0" + tar-stream@^2.0.0: version "2.1.2" resolved "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.2.tgz#6d5ef1a7e5783a95ff70b69b97455a5968dc1325" @@ -18097,6 +18267,11 @@ to-arraybuffer@^1.0.0: resolved "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" integrity sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M= +to-buffer@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz#493bd48f62d7c43fcded313a03dcadb2e1213a80" + integrity sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg== + to-fast-properties@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" @@ -19185,7 +19360,7 @@ which-pm-runs@^1.0.0: resolved "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb" integrity sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs= -which@^1.2.14, which@^1.2.9, which@^1.3.1: +which@1, which@^1.2.14, which@^1.2.9, which@^1.3.1: version "1.3.1" resolved "https://registry.npmjs.org/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== From 2a0371c306c7390f48bacef9a3dbc1e0f7c5d254 Mon Sep 17 00:00:00 2001 From: blam Date: Sat, 20 Jun 2020 05:59:40 +0200 Subject: [PATCH 027/182] chore(scaffolder): resetting loading the mock data --- plugins/scaffolder-backend/scripts/mock-data | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/plugins/scaffolder-backend/scripts/mock-data b/plugins/scaffolder-backend/scripts/mock-data index 7f534fec97..be3fa2b6cf 100755 --- a/plugins/scaffolder-backend/scripts/mock-data +++ b/plugins/scaffolder-backend/scripts/mock-data @@ -1,14 +1,8 @@ #!/usr/bin/env bash -# curl \ -# --location \ -# --request POST 'localhost:7000/catalog/locations' \ -# --header 'Content-Type: application/json' \ -# --data-raw "{\"type\": \"file\", \"target\": \"$(pwd)/sample-templates/react-ssr-template/template.yaml\"}" - - curl \ - --location \ - --request POST 'localhost:7000/catalog/locations' \ - --header 'Content-Type: application/json' \ - --data-raw "{\"type\": \"github\", \"target\": \"https://github.com/benjdlambert/backstage-graphql-template/blob/master/template.yaml\"}" +--location \ +--request POST 'localhost:7000/catalog/locations' \ +--header 'Content-Type: application/json' \ +--data-raw "{\"type\": \"file\", \"target\": \"$(pwd)/sample-templates/react-ssr-template/template.yaml\"}" + From 8127491b8beee39cfd6758dcf6e44ecfe49872fd Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 21 Jun 2020 14:21:51 +0200 Subject: [PATCH 028/182] backend-common: change useHotCleanup to clean up on parents being disposed as well --- packages/backend-common/src/hot.ts | 48 ++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/packages/backend-common/src/hot.ts b/packages/backend-common/src/hot.ts index 3d4252b378..bd6454c537 100644 --- a/packages/backend-common/src/hot.ts +++ b/packages/backend-common/src/hot.ts @@ -14,10 +14,38 @@ * limitations under the License. */ +// Find all active hot module APIs of all ancestors of a module, including the module itself +function findAllAncestors(_module: NodeModule): NodeModule[] { + const ancestors = new Array(); + const parentIds = new Set(); + + function add(id: string | number, m: NodeModule) { + if (parentIds.has(id)) { + return; + } + parentIds.add(id); + ancestors.push(m); + + for (const parentId of (m as any).parents) { + const parent = require.cache[parentId]; + if (parent) { + add(parentId, parent); + } + } + } + + add(_module.id, _module); + + return ancestors; +} + /** - * This function allows devs to cleanup - * ongoing effects when module gets hot-reloaded + * useHotCleanup allows cleanup of ongoing effects when a module is + * hot-reloaded during development. The cleanup function will be called + * whenever the module itself or any of its parent modules is hot-reloaded. + * * Useful for cleaning intervals, timers, requests etc + * * @example * ```ts * const intervalId = setInterval(doStuff, 1000); @@ -28,9 +56,19 @@ */ export function useHotCleanup(_module: NodeModule, cancelEffect: () => void) { if (_module.hot) { - _module.hot.addDisposeHandler(() => { - cancelEffect(); - }); + const ancestors = findAllAncestors(_module); + let cancelled = false; + + const handler = () => { + if (!cancelled) { + cancelled = true; + cancelEffect(); + } + }; + + for (const m of ancestors) { + m.hot?.addDisposeHandler(handler); + } } } From 903ad947a2db8ce2bfe6ef869d079df2d61aa72b Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 20 Jun 2020 18:15:42 +0200 Subject: [PATCH 029/182] cli: pin esbuild to 0.5.3 in app template --- packages/cli/templates/default-app/package.json.hbs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/cli/templates/default-app/package.json.hbs b/packages/cli/templates/default-app/package.json.hbs index a96061a5bd..c70e00e4d0 100644 --- a/packages/cli/templates/default-app/package.json.hbs +++ b/packages/cli/templates/default-app/package.json.hbs @@ -31,6 +31,9 @@ "lerna": "^3.20.2", "prettier": "^1.19.1" }, + "resolutions": { + "**/esbuild": "0.5.3" + }, "prettier": "@spotify/prettier-config", "lint-staged": { "*.{js,jsx,ts,tsx}": [ From ce8d65568a500f11c96d26a48bbb264c035fbf0e Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 20 Jun 2020 20:19:08 +0200 Subject: [PATCH 030/182] cli: update public/index.html in app template --- .../packages/app/public/index.html | 29 +++++++------------ 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/packages/cli/templates/default-app/packages/app/public/index.html b/packages/cli/templates/default-app/packages/app/public/index.html index 3d01107696..ea9208ca57 100644 --- a/packages/cli/templates/default-app/packages/app/public/index.html +++ b/packages/cli/templates/default-app/packages/app/public/index.html @@ -8,47 +8,38 @@ name="description" content="Backstage is an open platform for building developer portals" /> - + - - - + + - Backstage + <%= app.title %> - +