From c6fdddec77341830a4ca04e90de255e6cba7fc37 Mon Sep 17 00:00:00 2001 From: djamaile Date: Tue, 23 Nov 2021 22:40:03 +0100 Subject: [PATCH 01/57] feat: hide owned selector picker if user doesn't own an entity Signed-off-by: djamaile --- .changeset/modern-buses-protect.md | 5 +++++ .../UserListPicker/UserListPicker.tsx | 22 ++++++++++++++++--- 2 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 .changeset/modern-buses-protect.md diff --git a/.changeset/modern-buses-protect.md b/.changeset/modern-buses-protect.md new file mode 100644 index 0000000000..d943d6c06a --- /dev/null +++ b/.changeset/modern-buses-protect.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-react': patch +--- + +when a user doesn't own any entity, the user won't get the option to select on owned diff --git a/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx b/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx index f861355762..e5658feec9 100644 --- a/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx +++ b/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx @@ -176,12 +176,14 @@ export const UserListPicker = ({ setEntitiesWithoutUserFilter(backendEntities.filter(filterFn)); }, [filters, backendEntities]); + const totalOwnedUserEntities = entitiesWithoutUserFilter.filter(entity => + ownedFilter.filterEntity(entity), + ).length; + function getFilterCount(id: UserListFilterKind) { switch (id) { case 'owned': - return entitiesWithoutUserFilter.filter(entity => - ownedFilter.filterEntity(entity), - ).length; + return totalOwnedUserEntities; case 'starred': return entitiesWithoutUserFilter.filter(entity => starredFilter.filterEntity(entity), @@ -191,6 +193,20 @@ export const UserListPicker = ({ } } + function removeListItem( + arr: ButtonGroup[], + itemID: 'all' | 'owned' | 'starred', + ): ButtonGroup[] { + const index = arr[0].items.map(item => item.id).indexOf(itemID); + arr[0].items.splice(index, 1); + return arr; + } + + // should we do the same for starred? + if (totalOwnedUserEntities < 1) { + removeListItem(filterGroups, 'owned'); + } + return ( {filterGroups.map(group => ( From 26e8a1fc1d7f8f40c4dc4d5c8d297a36cd5783a3 Mon Sep 17 00:00:00 2001 From: djamaile Date: Sun, 28 Nov 2021 23:53:11 +0100 Subject: [PATCH 02/57] feat: hide owned selector picker for user and groups and redirect to all of user owns nothing Signed-off-by: djamaile --- .../UserListPicker/UserListPicker.tsx | 53 ++++++++++--------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx b/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx index e5658feec9..594f2f37d3 100644 --- a/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx +++ b/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx @@ -122,17 +122,7 @@ export const UserListPicker = ({ const classes = useStyles(); const configApi = useApi(configApiRef); const orgName = configApi.getOptionalString('organization.name') ?? 'Company'; - - // Remove group items that aren't in availableFilters and exclude - // any now-empty groups. - const filterGroups = getFilterGroups(orgName) - .map(filterGroup => ({ - ...filterGroup, - items: filterGroup.items.filter( - ({ id }) => !availableFilters || availableFilters.includes(id), - ), - })) - .filter(({ items }) => !!items.length); + const [filterGroups, setFilterGroups] = useState(); const { filters, updateFilters, backendEntities, queryParameters } = useEntityListProvider(); @@ -193,23 +183,38 @@ export const UserListPicker = ({ } } - function removeListItem( - arr: ButtonGroup[], - itemID: 'all' | 'owned' | 'starred', - ): ButtonGroup[] { - const index = arr[0].items.map(item => item.id).indexOf(itemID); - arr[0].items.splice(index, 1); - return arr; - } + const removeOwnedFromItemList = (itemList: ButtonGroup[]) => { + const index = itemList[0].items.map(item => item.id).indexOf('owned'); + itemList[0].items.splice(index, 1); + return itemList; + }; - // should we do the same for starred? - if (totalOwnedUserEntities < 1) { - removeListItem(filterGroups, 'owned'); - } + useEffect(() => { + // Remove group items that aren't in availableFilters and exclude + // any now-empty groups. + const defaultFilterGroups = getFilterGroups(orgName) + .map(filterGroup => ({ + ...filterGroup, + items: filterGroup.items.filter( + ({ id }) => !availableFilters || availableFilters.includes(id), + ), + })) + .filter(({ items }) => !!items.length); + if (totalOwnedUserEntities < 1) { + setSelectedUserFilter('all'); + } + if (['group', 'user'].some(kind => kind === queryParameters.kind)) { + setFilterGroups(removeOwnedFromItemList(defaultFilterGroups)); + } + return () => + setFilterGroups(prevState => + prevState !== defaultFilterGroups ? defaultFilterGroups : prevState, + ); + }, [totalOwnedUserEntities, queryParameters, availableFilters, orgName]); return ( - {filterGroups.map(group => ( + {filterGroups?.map(group => ( {group.name} From 5f44cf9fec6e2899ef4c9a64d3e1a82122f33ee7 Mon Sep 17 00:00:00 2001 From: djamaile Date: Mon, 29 Nov 2021 00:23:58 +0100 Subject: [PATCH 03/57] feat: trying useCallback approach Signed-off-by: djamaile --- .../UserListPicker/UserListPicker.tsx | 56 ++++++++++++------- 1 file changed, 37 insertions(+), 19 deletions(-) diff --git a/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx b/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx index 594f2f37d3..f2db87a101 100644 --- a/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx +++ b/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx @@ -33,7 +33,13 @@ import { import SettingsIcon from '@material-ui/icons/Settings'; import StarIcon from '@material-ui/icons/Star'; import { compact } from 'lodash'; -import React, { Fragment, useEffect, useMemo, useState } from 'react'; +import React, { + Fragment, + useCallback, + useEffect, + useMemo, + useState, +} from 'react'; import { UserListFilter } from '../../filters'; import { useEntityListProvider, @@ -122,7 +128,30 @@ export const UserListPicker = ({ const classes = useStyles(); const configApi = useApi(configApiRef); const orgName = configApi.getOptionalString('organization.name') ?? 'Company'; - const [filterGroups, setFilterGroups] = useState(); + // Remove group items that aren't in availableFilters and exclude + // any now-empty groups. + const initialFilterGroup = getFilterGroups(orgName) + .map(filterGroup => ({ + ...filterGroup, + items: filterGroup.items.filter( + ({ id }) => !availableFilters || availableFilters.includes(id), + ), + })) + .filter(({ items }) => !!items.length); + const [filterGroups, setFilterGroups] = + useState(initialFilterGroup); + const setDefaultFilterGroups = useCallback(() => { + setFilterGroups( + getFilterGroups(orgName) + .map(filterGroup => ({ + ...filterGroup, + items: filterGroup.items.filter( + ({ id }) => !availableFilters || availableFilters.includes(id), + ), + })) + .filter(({ items }) => !!items.length), + ); + }, [availableFilters, orgName]); const { filters, updateFilters, backendEntities, queryParameters } = useEntityListProvider(); @@ -190,31 +219,20 @@ export const UserListPicker = ({ }; useEffect(() => { - // Remove group items that aren't in availableFilters and exclude - // any now-empty groups. - const defaultFilterGroups = getFilterGroups(orgName) - .map(filterGroup => ({ - ...filterGroup, - items: filterGroup.items.filter( - ({ id }) => !availableFilters || availableFilters.includes(id), - ), - })) - .filter(({ items }) => !!items.length); if (totalOwnedUserEntities < 1) { setSelectedUserFilter('all'); } if (['group', 'user'].some(kind => kind === queryParameters.kind)) { - setFilterGroups(removeOwnedFromItemList(defaultFilterGroups)); - } - return () => - setFilterGroups(prevState => - prevState !== defaultFilterGroups ? defaultFilterGroups : prevState, + setFilterGroups(currentFilterGroups => + removeOwnedFromItemList(currentFilterGroups), ); - }, [totalOwnedUserEntities, queryParameters, availableFilters, orgName]); + } + return () => setDefaultFilterGroups(); + }, [totalOwnedUserEntities, queryParameters, setDefaultFilterGroups]); return ( - {filterGroups?.map(group => ( + {filterGroups.map(group => ( {group.name} From 7c6cb5a12fc92ad6ce27478da157f6c259f642c0 Mon Sep 17 00:00:00 2001 From: djamaile Date: Wed, 1 Dec 2021 22:46:23 +0100 Subject: [PATCH 04/57] chore: remove callback function Signed-off-by: djamaile --- .../UserListPicker/UserListPicker.tsx | 64 ++++++++----------- 1 file changed, 25 insertions(+), 39 deletions(-) diff --git a/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx b/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx index f2db87a101..a36f5f78b8 100644 --- a/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx +++ b/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx @@ -33,13 +33,7 @@ import { import SettingsIcon from '@material-ui/icons/Settings'; import StarIcon from '@material-ui/icons/Star'; import { compact } from 'lodash'; -import React, { - Fragment, - useCallback, - useEffect, - useMemo, - useState, -} from 'react'; +import React, { Fragment, useEffect, useMemo, useState } from 'react'; import { UserListFilter } from '../../filters'; import { useEntityListProvider, @@ -128,30 +122,7 @@ export const UserListPicker = ({ const classes = useStyles(); const configApi = useApi(configApiRef); const orgName = configApi.getOptionalString('organization.name') ?? 'Company'; - // Remove group items that aren't in availableFilters and exclude - // any now-empty groups. - const initialFilterGroup = getFilterGroups(orgName) - .map(filterGroup => ({ - ...filterGroup, - items: filterGroup.items.filter( - ({ id }) => !availableFilters || availableFilters.includes(id), - ), - })) - .filter(({ items }) => !!items.length); - const [filterGroups, setFilterGroups] = - useState(initialFilterGroup); - const setDefaultFilterGroups = useCallback(() => { - setFilterGroups( - getFilterGroups(orgName) - .map(filterGroup => ({ - ...filterGroup, - items: filterGroup.items.filter( - ({ id }) => !availableFilters || availableFilters.includes(id), - ), - })) - .filter(({ items }) => !!items.length), - ); - }, [availableFilters, orgName]); + const [filterGroups, setFilterGroups] = useState([]); const { filters, updateFilters, backendEntities, queryParameters } = useEntityListProvider(); @@ -212,6 +183,12 @@ export const UserListPicker = ({ } } + useEffect(() => { + if (totalOwnedUserEntities < 1) { + setSelectedUserFilter('all'); + } + }, [totalOwnedUserEntities]); + const removeOwnedFromItemList = (itemList: ButtonGroup[]) => { const index = itemList[0].items.map(item => item.id).indexOf('owned'); itemList[0].items.splice(index, 1); @@ -219,16 +196,25 @@ export const UserListPicker = ({ }; useEffect(() => { - if (totalOwnedUserEntities < 1) { - setSelectedUserFilter('all'); - } + // Remove group items that aren't in availableFilters and exclude + // any now-empty groups. + const initialFilterGroup = getFilterGroups(orgName) + .map(filterGroup => ({ + ...filterGroup, + items: filterGroup.items.filter( + ({ id }) => !availableFilters || availableFilters.includes(id), + ), + })) + .filter(({ items }) => !!items.length); + // TODO: avoid hardcoding kinds here if (['group', 'user'].some(kind => kind === queryParameters.kind)) { - setFilterGroups(currentFilterGroups => - removeOwnedFromItemList(currentFilterGroups), - ); + setFilterGroups(removeOwnedFromItemList(initialFilterGroup)); } - return () => setDefaultFilterGroups(); - }, [totalOwnedUserEntities, queryParameters, setDefaultFilterGroups]); + return () => + setFilterGroups(prevState => + prevState !== initialFilterGroup ? initialFilterGroup : prevState, + ); + }, [queryParameters, availableFilters, orgName]); return ( From 4a0292c67b420b55d69313c0830308a97b6aa8ac Mon Sep 17 00:00:00 2001 From: djamaile Date: Sat, 4 Dec 2021 16:59:33 +0100 Subject: [PATCH 05/57] refactor: remove useEffect functionality Signed-off-by: djamaile --- backstage.json | 3 + .../UserListPicker/UserListPicker.tsx | 81 +++++++------------ 2 files changed, 34 insertions(+), 50 deletions(-) create mode 100644 backstage.json diff --git a/backstage.json b/backstage.json new file mode 100644 index 0000000000..77e9232b75 --- /dev/null +++ b/backstage.json @@ -0,0 +1,3 @@ +{ + "version": "0.4.5" +} diff --git a/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx b/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx index a36f5f78b8..867c5230d4 100644 --- a/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx +++ b/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx @@ -122,16 +122,24 @@ export const UserListPicker = ({ const classes = useStyles(); const configApi = useApi(configApiRef); const orgName = configApi.getOptionalString('organization.name') ?? 'Company'; - const [filterGroups, setFilterGroups] = useState([]); - + const userAndGroupFilterIds = ['starred', 'all']; const { filters, updateFilters, backendEntities, queryParameters } = useEntityListProvider(); + // Remove group items that aren't in availableFilters and exclude + // any now-empty groups. + const filterGroups = getFilterGroups(orgName) + .map(filterGroup => ({ + ...filterGroup, + items: filterGroup.items.filter(({ id }) => + ['group', 'user'].some(kind => kind === queryParameters.kind) + ? userAndGroupFilterIds.includes(id) + : !availableFilters || availableFilters.includes(id), + ), + })) + .filter(({ items }) => !!items.length); const { isStarredEntity } = useStarredEntities(); const { isOwnedEntity } = useEntityOwnership(); - const [selectedUserFilter, setSelectedUserFilter] = useState( - [queryParameters.user].flat()[0] ?? initialFilter, - ); // Static filters; used for generating counts of potentially unselected kinds const ownedFilter = useMemo( @@ -143,18 +151,6 @@ export const UserListPicker = ({ [isOwnedEntity, isStarredEntity], ); - useEffect(() => { - updateFilters({ - user: selectedUserFilter - ? new UserListFilter( - selectedUserFilter as UserListFilterKind, - isOwnedEntity, - isStarredEntity, - ) - : undefined, - }); - }, [selectedUserFilter, isOwnedEntity, isStarredEntity, updateFilters]); - // To show proper counts for each section, apply all other frontend filters _except_ the user // filter that's controlled by this picker. const [entitiesWithoutUserFilter, setEntitiesWithoutUserFilter] = @@ -170,6 +166,24 @@ export const UserListPicker = ({ ownedFilter.filterEntity(entity), ).length; + const [selectedUserFilter, setSelectedUserFilter] = useState( + totalOwnedUserEntities < 1 + ? 'all' + : [queryParameters.user].flat()[0] ?? initialFilter, + ); + + useEffect(() => { + updateFilters({ + user: selectedUserFilter + ? new UserListFilter( + selectedUserFilter as UserListFilterKind, + isOwnedEntity, + isStarredEntity, + ) + : undefined, + }); + }, [selectedUserFilter, isOwnedEntity, isStarredEntity, updateFilters]); + function getFilterCount(id: UserListFilterKind) { switch (id) { case 'owned': @@ -183,39 +197,6 @@ export const UserListPicker = ({ } } - useEffect(() => { - if (totalOwnedUserEntities < 1) { - setSelectedUserFilter('all'); - } - }, [totalOwnedUserEntities]); - - const removeOwnedFromItemList = (itemList: ButtonGroup[]) => { - const index = itemList[0].items.map(item => item.id).indexOf('owned'); - itemList[0].items.splice(index, 1); - return itemList; - }; - - useEffect(() => { - // Remove group items that aren't in availableFilters and exclude - // any now-empty groups. - const initialFilterGroup = getFilterGroups(orgName) - .map(filterGroup => ({ - ...filterGroup, - items: filterGroup.items.filter( - ({ id }) => !availableFilters || availableFilters.includes(id), - ), - })) - .filter(({ items }) => !!items.length); - // TODO: avoid hardcoding kinds here - if (['group', 'user'].some(kind => kind === queryParameters.kind)) { - setFilterGroups(removeOwnedFromItemList(initialFilterGroup)); - } - return () => - setFilterGroups(prevState => - prevState !== initialFilterGroup ? initialFilterGroup : prevState, - ); - }, [queryParameters, availableFilters, orgName]); - return ( {filterGroups.map(group => ( From 95881167dcf1ab29b4f1dd803cb07d7949bd2800 Mon Sep 17 00:00:00 2001 From: djamaile Date: Tue, 7 Dec 2021 23:50:09 +0100 Subject: [PATCH 06/57] chore: some clean up Signed-off-by: djamaile --- .changeset/modern-buses-protect.md | 4 ++- .../UserListPicker/UserListPicker.tsx | 25 ++++++++++--------- .../src/hooks/useEntityListProvider.test.tsx | 19 ++++++++++++-- 3 files changed, 33 insertions(+), 15 deletions(-) diff --git a/.changeset/modern-buses-protect.md b/.changeset/modern-buses-protect.md index d943d6c06a..2aed52eca0 100644 --- a/.changeset/modern-buses-protect.md +++ b/.changeset/modern-buses-protect.md @@ -2,4 +2,6 @@ '@backstage/plugin-catalog-react': patch --- -when a user doesn't own any entity, the user won't get the option to select on owned +When a user has zero owned entities when viewing an entity kind in the catalog +page, it will be automatically redirected to see all the entities. Furthermore, +for the kind User and Group there are no longer the owned selector. diff --git a/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx b/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx index 867c5230d4..9aafdf1437 100644 --- a/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx +++ b/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx @@ -122,15 +122,17 @@ export const UserListPicker = ({ const classes = useStyles(); const configApi = useApi(configApiRef); const orgName = configApi.getOptionalString('organization.name') ?? 'Company'; - const userAndGroupFilterIds = ['starred', 'all']; const { filters, updateFilters, backendEntities, queryParameters } = useEntityListProvider(); + // Remove group items that aren't in availableFilters and exclude // any now-empty groups. + const userAndGroupFilterIds = ['starred', 'all']; const filterGroups = getFilterGroups(orgName) .map(filterGroup => ({ ...filterGroup, items: filterGroup.items.filter(({ id }) => + // TODO: avoid hardcoding kinds here ['group', 'user'].some(kind => kind === queryParameters.kind) ? userAndGroupFilterIds.includes(id) : !availableFilters || availableFilters.includes(id), @@ -155,21 +157,13 @@ export const UserListPicker = ({ // filter that's controlled by this picker. const [entitiesWithoutUserFilter, setEntitiesWithoutUserFilter] = useState(backendEntities); - useEffect(() => { - const filterFn = reduceEntityFilters( - compact(Object.values({ ...filters, user: undefined })), - ); - setEntitiesWithoutUserFilter(backendEntities.filter(filterFn)); - }, [filters, backendEntities]); - const totalOwnedUserEntities = entitiesWithoutUserFilter.filter(entity => ownedFilter.filterEntity(entity), ).length; - const [selectedUserFilter, setSelectedUserFilter] = useState( - totalOwnedUserEntities < 1 - ? 'all' - : [queryParameters.user].flat()[0] ?? initialFilter, + totalOwnedUserEntities > 0 + ? [queryParameters.user].flat()[0] ?? initialFilter + : 'all', ); useEffect(() => { @@ -184,6 +178,13 @@ export const UserListPicker = ({ }); }, [selectedUserFilter, isOwnedEntity, isStarredEntity, updateFilters]); + useEffect(() => { + const filterFn = reduceEntityFilters( + compact(Object.values({ ...filters, user: undefined })), + ); + setEntitiesWithoutUserFilter(backendEntities.filter(filterFn)); + }, [filters, backendEntities]); + function getFilterCount(id: UserListFilterKind) { switch (id) { case 'owned': diff --git a/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx b/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx index b96f63304a..fd5cf3b9dd 100644 --- a/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx +++ b/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx @@ -137,12 +137,27 @@ describe('', () => { const { result, waitFor } = renderHook(() => useEntityListProvider(), { wrapper, initialProps: { - userFilter: 'owned', + userFilter: 'all', }, }); await waitFor(() => !!result.current.entities.length); expect(result.current.backendEntities.length).toBe(2); - expect(result.current.entities.length).toBe(1); + + act(() => + result.current.updateFilters({ + user: new UserListFilter( + 'owned', + entity => entity.metadata.name === 'component-1', + () => true, + ), + }), + ); + + await waitFor(() => { + expect(result.current.backendEntities.length).toBe(2); + expect(result.current.entities.length).toBe(1); + expect(mockCatalogApi.getEntities).toHaveBeenCalledTimes(1); + }); }); it('resolves query param filter values', async () => { From 30e3fbb34b9679db783c24c9b238ba46c35178b3 Mon Sep 17 00:00:00 2001 From: djamaile Date: Thu, 9 Dec 2021 23:23:58 +0100 Subject: [PATCH 07/57] chore: remove backstage.json Signed-off-by: djamaile --- backstage.json | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 backstage.json diff --git a/backstage.json b/backstage.json deleted file mode 100644 index 77e9232b75..0000000000 --- a/backstage.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "version": "0.4.5" -} From fbc20dc18e82a7c558408b297256bba3402957e6 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Tue, 21 Dec 2021 09:15:16 +0100 Subject: [PATCH 08/57] ADR: Use node-fetch for data fetching Signed-off-by: Johan Haals --- .../adr013-use-node-fetch.md | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 docs/architecture-decisions/adr013-use-node-fetch.md diff --git a/docs/architecture-decisions/adr013-use-node-fetch.md b/docs/architecture-decisions/adr013-use-node-fetch.md new file mode 100644 index 0000000000..8a6f61545d --- /dev/null +++ b/docs/architecture-decisions/adr013-use-node-fetch.md @@ -0,0 +1,22 @@ +--- +id: adrs-adr013 +title: 'ADR013: Use node-fetch for data fetching' +description: Architecture Decision Record (ADR) for HTTP data fetching packages +--- + +## Context + +Using multiple HTTP packages for data fetching increases the complexity and the +support burden of keeping said package up to date. + +## Decision + +Node packages should use the `node-fetch` package for HTTP data fetching. +Isomorphic packages should use the `cross-fetch` as a development dependency and +rely on the built-in `fetch` API. + +## Consequences + +We will gradually transition away from third party packages such as `axios`, +`got` and others. Once we have transitioned to `node-fetch` we will add lint +rules to enforce this decision. From e5703a34829635733bd66c0c54e36c7b6bd59741 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Tue, 21 Dec 2021 11:21:04 +0100 Subject: [PATCH 09/57] Add fetch examples MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Signed-off-by: Johan Haals --- .../adr013-use-node-fetch.md | 59 +++++++++++++++++-- 1 file changed, 54 insertions(+), 5 deletions(-) diff --git a/docs/architecture-decisions/adr013-use-node-fetch.md b/docs/architecture-decisions/adr013-use-node-fetch.md index 8a6f61545d..d802988ae4 100644 --- a/docs/architecture-decisions/adr013-use-node-fetch.md +++ b/docs/architecture-decisions/adr013-use-node-fetch.md @@ -1,7 +1,8 @@ --- id: adrs-adr013 -title: 'ADR013: Use node-fetch for data fetching' -description: Architecture Decision Record (ADR) for HTTP data fetching packages +title: 'ADR013: Proper use of HTTP fetching libraries' +# prettier-ignore +description: Architecture Decision Record (ADR) for the proper use of fetchApiRef, node-fetch, and cross-fetch for data fetching. --- ## Context @@ -11,9 +12,57 @@ support burden of keeping said package up to date. ## Decision -Node packages should use the `node-fetch` package for HTTP data fetching. -Isomorphic packages should use the `cross-fetch` as a development dependency and -rely on the built-in `fetch` API. +Backend (node) packages should use the `node-fetch` package for HTTP data +fetching. Example: + +```ts +import fetch from 'node-fetch'; +import { ResponseError } from '@backstage/errors'; + +const response = await fetch('https://example.com/api/v1/users.json'); +if (!response.ok) { + throw await ResponseError.fromResponse(response); +} +const users = await response.json(); +``` + +Frontend plugins and packages should prefer to use the +[`fetchApiRef`](https://backstage.io/docs/reference/core-plugin-api.fetchapiref). +It uses `cross-fetch` internally. Example: + +```ts +import { useApi } from '@backstage/core-plugin-api'; +const { fetch } = useApi(fetchApiRef); + +const response = await fetch('https://example.com/api/v1/users.json'); +if (!response.ok) { + throw await ResponseError.fromResponse(response); +} +const users = await response.json(); +``` + +Isomorphic packages should have a dependency on the `cross-fetch` package for +mocking and type definitions. Preferably, classes and functions in isomorphic +packages should accept an argument of type `typeof fetch` to let callers supply +their preferred implementation of `fetch`. This lets them adorn the calls with +auth or other information, and track metrics etc, in a cross-platform way. +Example: + +```ts +import crossFetch from 'cross-fetch'; + +export class MyClient { + private readonly fetch: typeof crossFetch; + + constructor(options: { fetch?: typeof crossFetch }) { + this.fetch = options.fetch || crossFetch; + } + + async users() { + return await this.fetch('https://example.com/api/v1/users.json'); + } +} +``` ## Consequences From 1d260170907fb0cef2a4fc07e143919036897b6e Mon Sep 17 00:00:00 2001 From: "jean-philippe.blary" Date: Tue, 21 Dec 2021 13:42:20 +0100 Subject: [PATCH 10/57] fix(cli): skip findPackages if not inside a monorepo Signed-off-by: jean-philippe.blary --- .changeset/light-singers-double.md | 5 +++++ packages/cli/src/lib/config.ts | 14 +++++++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) create mode 100644 .changeset/light-singers-double.md diff --git a/.changeset/light-singers-double.md b/.changeset/light-singers-double.md new file mode 100644 index 0000000000..e21017373d --- /dev/null +++ b/.changeset/light-singers-double.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Fix issue with plugin:serve for Plugins not using Lerna monorepo. diff --git a/packages/cli/src/lib/config.ts b/packages/cli/src/lib/config.ts index f85db276b2..986e409e58 100644 --- a/packages/cli/src/lib/config.ts +++ b/packages/cli/src/lib/config.ts @@ -44,9 +44,17 @@ export async function loadCliConfig(options: Options) { const project = new Project(paths.targetDir); const packages = await project.getPackages(); - const localPackageNames = options.fromPackage - ? findPackages(packages, options.fromPackage) - : packages.map((p: any) => p.name); + let localPackageNames; + if (options.fromPackage) { + if (packages.length) { + localPackageNames = findPackages(packages, options.fromPackage); + } else { + // No packages: it means that it's not a monorepo (e.g. standalone plugin) + localPackageNames = [options.fromPackage]; + } + } else { + localPackageNames = packages.map((p: any) => p.name); + } const schema = await loadConfigSchema({ dependencies: localPackageNames, From e3b11376cb93a5637e192dea7dc1f375f5a75bf6 Mon Sep 17 00:00:00 2001 From: Fidel Coria Date: Wed, 22 Dec 2021 10:22:12 -0600 Subject: [PATCH 11/57] fix: enforce cookie ssl in production Signed-off-by: Fidel Coria --- plugins/auth-backend/src/service/router.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/auth-backend/src/service/router.ts b/plugins/auth-backend/src/service/router.ts index 6a96d421da..6451207d2d 100644 --- a/plugins/auth-backend/src/service/router.ts +++ b/plugins/auth-backend/src/service/router.ts @@ -68,7 +68,8 @@ export async function createRouter( if (secret) { router.use(cookieParser(secret)); // TODO: Configure the server-side session storage. The default MemoryStore is not designed for production - router.use(session({ secret, saveUninitialized: false, resave: false })); + const enforceCookieSSL = process.env.NODE_ENV === 'production'; + router.use(session({ secret, saveUninitialized: false, resave: false, cookie: { secure: enforceCookieSSL } })); router.use(passport.initialize()); router.use(passport.session()); } else { From eb48e78886d10960ae316164de322bf0042bdbdb Mon Sep 17 00:00:00 2001 From: Fidel Coria Date: Wed, 22 Dec 2021 10:27:14 -0600 Subject: [PATCH 12/57] add changeset Signed-off-by: Fidel Coria --- .changeset/twenty-hornets-train.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/twenty-hornets-train.md diff --git a/.changeset/twenty-hornets-train.md b/.changeset/twenty-hornets-train.md new file mode 100644 index 0000000000..492404b573 --- /dev/null +++ b/.changeset/twenty-hornets-train.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend': patch +--- + +Enforce cookie ssl protection when in production for auth-backend sessions From 5801adbd18509948219c4b8b0b17a43b1892d8f7 Mon Sep 17 00:00:00 2001 From: Fidel Coria Date: Wed, 22 Dec 2021 10:47:36 -0600 Subject: [PATCH 13/57] use authUrl for condition Signed-off-by: Fidel Coria --- plugins/auth-backend/src/service/router.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/plugins/auth-backend/src/service/router.ts b/plugins/auth-backend/src/service/router.ts index 6451207d2d..deef6ddcf0 100644 --- a/plugins/auth-backend/src/service/router.ts +++ b/plugins/auth-backend/src/service/router.ts @@ -68,8 +68,15 @@ export async function createRouter( if (secret) { router.use(cookieParser(secret)); // TODO: Configure the server-side session storage. The default MemoryStore is not designed for production - const enforceCookieSSL = process.env.NODE_ENV === 'production'; - router.use(session({ secret, saveUninitialized: false, resave: false, cookie: { secure: enforceCookieSSL } })); + const enforceCookieSSL = authUrl.startsWith('https'); + router.use( + session({ + secret, + saveUninitialized: false, + resave: false, + cookie: { secure: enforceCookieSSL }, + }), + ); router.use(passport.initialize()); router.use(passport.session()); } else { From 27073a49a128a5f86efcca70404c2b8aaafbe6aa Mon Sep 17 00:00:00 2001 From: Fidel Coria Date: Wed, 22 Dec 2021 10:50:11 -0600 Subject: [PATCH 14/57] fix spelling in changeset Signed-off-by: Fidel Coria --- .changeset/twenty-hornets-train.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/twenty-hornets-train.md b/.changeset/twenty-hornets-train.md index 492404b573..957bfd00da 100644 --- a/.changeset/twenty-hornets-train.md +++ b/.changeset/twenty-hornets-train.md @@ -2,4 +2,4 @@ '@backstage/plugin-auth-backend': patch --- -Enforce cookie ssl protection when in production for auth-backend sessions +Enforce cookie SSL protection when in production for auth-backend sessions From c5e175cde95ceba6e324fdc06343f140e077ab4f Mon Sep 17 00:00:00 2001 From: Colton Padden Date: Wed, 22 Dec 2021 14:14:31 -0500 Subject: [PATCH 15/57] remove axios dependency from rollbar-backend Signed-off-by: Colton Padden --- .changeset/plenty-flies-repair.md | 5 +++ plugins/rollbar-backend/package.json | 4 ++- .../src/api/RollbarApi.test.ts | 34 ++++++++++++++++++- plugins/rollbar-backend/src/api/RollbarApi.ts | 13 +++---- 4 files changed, 48 insertions(+), 8 deletions(-) create mode 100644 .changeset/plenty-flies-repair.md diff --git a/.changeset/plenty-flies-repair.md b/.changeset/plenty-flies-repair.md new file mode 100644 index 0000000000..7e9690a7f3 --- /dev/null +++ b/.changeset/plenty-flies-repair.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-rollbar-backend': patch +--- + +Replace the usage of `axios` with `node-fetch` in the Rollbar API diff --git a/plugins/rollbar-backend/package.json b/plugins/rollbar-backend/package.json index 6fbecdbc3d..3c7cbd5ee9 100644 --- a/plugins/rollbar-backend/package.json +++ b/plugins/rollbar-backend/package.json @@ -33,8 +33,8 @@ "dependencies": { "@backstage/backend-common": "^0.9.12", "@backstage/config": "^0.1.10", + "@backstage/test-utils": "^0.1.24", "@types/express": "^4.17.6", - "axios": "^0.24.0", "camelcase-keys": "^6.2.2", "compression": "^1.7.4", "cors": "^2.8.5", @@ -44,12 +44,14 @@ "helmet": "^4.0.0", "lodash": "^4.17.21", "morgan": "^1.10.0", + "node-fetch": "^2.6.1", "winston": "^3.2.1", "yn": "^4.0.0" }, "devDependencies": { "@backstage/cli": "^0.10.0", "@types/supertest": "^2.0.8", + "msw": "^0.36.3", "supertest": "^6.1.3" }, "files": [ diff --git a/plugins/rollbar-backend/src/api/RollbarApi.test.ts b/plugins/rollbar-backend/src/api/RollbarApi.test.ts index 66a3955f3a..8936bb1839 100644 --- a/plugins/rollbar-backend/src/api/RollbarApi.test.ts +++ b/plugins/rollbar-backend/src/api/RollbarApi.test.ts @@ -14,7 +14,12 @@ * limitations under the License. */ -import { getRequestHeaders } from './RollbarApi'; +import { getRequestHeaders, RollbarApi } from './RollbarApi'; +import { setupRequestMockHandlers } from '@backstage/test-utils'; +import { rest } from 'msw'; +import { setupServer } from 'msw/node'; +import { getVoidLogger } from '@backstage/backend-common'; +import { RollbarProject } from './types'; describe('RollbarApi', () => { describe('getRequestHeaders', () => { @@ -26,4 +31,31 @@ describe('RollbarApi', () => { }); }); }); + + describe('getAllProjects', () => { + const server = setupServer(); + setupRequestMockHandlers(server); + + const mockBaseUrl = 'https://api.rollbar.com/api/1'; + + const mockProjects: RollbarProject[] = [ + { id: 123, name: 'abc', accountId: 1, status: 'enabled' }, + { id: 456, name: 'xyz', accountId: 1, status: 'enabled' }, + ]; + + const setupHandlers = () => { + server.use( + rest.get(`${mockBaseUrl}/projects`, (_, res, ctx) => { + return res(ctx.json({ result: mockProjects })); + }), + ); + }; + + it('should return all projects with a name attribute', async () => { + setupHandlers(); + const api = new RollbarApi('my-access-token', getVoidLogger()); + const projects = await api.getAllProjects(); + expect(projects).toEqual(mockProjects); + }); + }); }); diff --git a/plugins/rollbar-backend/src/api/RollbarApi.ts b/plugins/rollbar-backend/src/api/RollbarApi.ts index 67d0535942..245164b531 100644 --- a/plugins/rollbar-backend/src/api/RollbarApi.ts +++ b/plugins/rollbar-backend/src/api/RollbarApi.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import axios from 'axios'; import { Logger } from 'winston'; import camelcaseKeys from 'camelcase-keys'; import { buildQuery } from '../util'; @@ -25,6 +24,7 @@ import { RollbarProjectAccessToken, RollbarTopActiveItem, } from './types'; +import fetch from 'node-fetch'; const baseUrl = 'https://api.rollbar.com/api/1'; @@ -110,11 +110,12 @@ export class RollbarApi { this.logger.info(`Calling Rollbar REST API, ${fullUrl}`); } - return axios - .get(fullUrl, getRequestHeaders(accessToken || this.accessToken || '')) - .then(response => - camelcaseKeys(response?.data?.result, { deep: true }), - ); + return fetch( + fullUrl, + getRequestHeaders(accessToken || this.accessToken || ''), + ) + .then(response => response.json()) + .then(json => camelcaseKeys(json?.result, { deep: true })); } private async getForProject( From f0f81f6cc7a56e71e462987dee5850deb28f4917 Mon Sep 17 00:00:00 2001 From: Colton Padden Date: Wed, 22 Dec 2021 14:23:16 -0500 Subject: [PATCH 16/57] remove got dependency from auth-backend Signed-off-by: Colton Padden --- .changeset/chatty-gifts-fry.md | 5 ++++ plugins/auth-backend/package.json | 1 - .../src/providers/microsoft/provider.ts | 25 +++++++++---------- 3 files changed, 17 insertions(+), 14 deletions(-) create mode 100644 .changeset/chatty-gifts-fry.md diff --git a/.changeset/chatty-gifts-fry.md b/.changeset/chatty-gifts-fry.md new file mode 100644 index 0000000000..25f0a70bd9 --- /dev/null +++ b/.changeset/chatty-gifts-fry.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend': patch +--- + +Replaces the usage of `got` with `node-fetch` in the `getUserPhoto` method of the Microsoft provider diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index c7d208eef2..5a7769c0fb 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -46,7 +46,6 @@ "express-promise-router": "^4.1.0", "express-session": "^1.17.1", "fs-extra": "9.1.0", - "got": "^11.5.2", "helmet": "^4.0.0", "jose": "^1.27.1", "jwt-decode": "^3.1.0", diff --git a/plugins/auth-backend/src/providers/microsoft/provider.ts b/plugins/auth-backend/src/providers/microsoft/provider.ts index 2dc259fe74..66bb32cfdd 100644 --- a/plugins/auth-backend/src/providers/microsoft/provider.ts +++ b/plugins/auth-backend/src/providers/microsoft/provider.ts @@ -45,7 +45,7 @@ import { SignInResolver, } from '../types'; import { Logger } from 'winston'; -import got from 'got'; +import fetch from 'node-fetch'; type PrivateInfo = { refreshToken: string; @@ -173,19 +173,18 @@ export class MicrosoftAuthProvider implements OAuthHandlers { private getUserPhoto(accessToken: string): Promise { return new Promise(resolve => { - got - .get('https://graph.microsoft.com/v1.0/me/photos/48x48/$value', { - encoding: 'binary', - responseType: 'buffer', - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }) - .then(photoData => { - const photoURL = `data:image/jpeg;base64,${Buffer.from( - photoData.body, + fetch('https://graph.microsoft.com/v1.0/me/photos/48x48/$value', { + headers: { + Authorization: `Bearer ${accessToken}`, + }, + }) + .then(response => response.arrayBuffer()) + .then(arrayBuffer => { + console.log(Buffer.from(arrayBuffer).toString('utf-8')); + const imageUrl = `data:image/jpeg;base64,${Buffer.from( + arrayBuffer, ).toString('base64')}`; - resolve(photoURL); + resolve(imageUrl); }) .catch(error => { this.logger.warn( From 22bb6fad0c9b675822083b08dbd6cd2b8aab45a9 Mon Sep 17 00:00:00 2001 From: Colton Padden Date: Wed, 22 Dec 2021 14:23:27 -0500 Subject: [PATCH 17/57] update yarn.lock after axios and got top-level dependency removal Signed-off-by: Colton Padden --- yarn.lock | 96 ++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 77 insertions(+), 19 deletions(-) diff --git a/yarn.lock b/yarn.lock index 422531d3a4..1a91885e6c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4927,7 +4927,7 @@ "@types/set-cookie-parser" "^2.4.0" set-cookie-parser "^2.4.6" -"@mswjs/interceptors@^0.12.6": +"@mswjs/interceptors@^0.12.6", "@mswjs/interceptors@^0.12.7": version "0.12.7" resolved "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.12.7.tgz#0d1cd4cd31a0f663e0455993951201faa09d0909" integrity sha512-eGjZ3JRAt0Fzi5FgXiV/P3bJGj0NqsN7vBS0J0FO2AQRQ0jCKQS4lEFm4wvlSgKQNfeuc/Vz6d81VtU3Gkx/zg== @@ -9996,13 +9996,6 @@ axios@^0.21.1, axios@^0.21.4: dependencies: follow-redirects "^1.14.0" -axios@^0.24.0: - version "0.24.0" - resolved "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz#804e6fa1e4b9c5288501dd9dff56a7a0940d20d6" - integrity sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA== - dependencies: - follow-redirects "^1.14.4" - axobject-query@^2.2.0: version "2.2.0" resolved "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be" @@ -11104,6 +11097,14 @@ chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.2, chalk@^2.4. escape-string-regexp "^1.0.5" supports-color "^5.3.0" +chalk@4.1.1, chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1: + version "4.1.1" + resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz#c80b3fab28bf6371e6863325eee67e618b77e6ad" + integrity sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + chalk@^1.0.0, chalk@^1.1.3: version "1.1.3" resolved "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" @@ -11123,14 +11124,6 @@ chalk@^3.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1: - version "4.1.1" - resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz#c80b3fab28bf6371e6863325eee67e618b77e6ad" - integrity sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - change-case-all@1.0.14: version "1.0.14" resolved "https://registry.npmjs.org/change-case-all/-/change-case-all-1.0.14.tgz#bac04da08ad143278d0ac3dda7eccd39280bfba1" @@ -15248,7 +15241,7 @@ fn.name@1.x.x: resolved "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc" integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw== -follow-redirects@^1.0.0, follow-redirects@^1.14.0, follow-redirects@^1.14.4: +follow-redirects@^1.0.0, follow-redirects@^1.14.0: version "1.14.6" resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.6.tgz#8cfb281bbc035b3c067d6cd975b0f6ade6e855cd" integrity sha512-fhUl5EwSJbbl8AR+uYL2KQDxLkdSjZGR36xy46AO7cOMTrCMON6Sa28FmAnC2tRTDbd/Uuzz3aJBv7EBN7JH8A== @@ -16096,7 +16089,7 @@ google-p12-pem@^3.0.3: dependencies: node-forge "^0.10.0" -got@^11.5.2, got@^11.8.0, got@^11.8.2: +got@^11.8.0, got@^11.8.2: version "11.8.2" resolved "https://registry.npmjs.org/got/-/got-11.8.2.tgz#7abb3959ea28c31f3576f1576c1effce23f33599" integrity sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ== @@ -17242,7 +17235,7 @@ inquirer@^8.0.0: strip-ansi "^6.0.0" through "^2.3.6" -inquirer@^8.1.1: +inquirer@^8.1.1, inquirer@^8.2.0: version "8.2.0" resolved "https://registry.npmjs.org/inquirer/-/inquirer-8.2.0.tgz#f44f008dd344bbfc4b30031f45d984e034a3ac3a" integrity sha512-0crLweprevJ02tTuA6ThpoAERAGyVILC4sS74uib58Xf/zSr1/ZWtmm7D5CI+bSQEaA04f0K7idaHpQbSWgiVQ== @@ -21383,6 +21376,32 @@ msw@^0.35.0: type-fest "^1.2.2" yargs "^17.0.1" +msw@^0.36.3: + version "0.36.3" + resolved "https://registry.npmjs.org/msw/-/msw-0.36.3.tgz#7feb243a5fcf563806d45edc027bc36144741170" + integrity sha512-Itzp/QhKaleZoslXDrNik3ramW9ynqzOdbwydX2ehBSSaZd5QoiAl/bHYcV33R6CEZcJgIX1N4s+G6XkF/bhkA== + dependencies: + "@mswjs/cookies" "^0.1.6" + "@mswjs/interceptors" "^0.12.7" + "@open-draft/until" "^1.0.3" + "@types/cookie" "^0.4.1" + "@types/inquirer" "^8.1.3" + "@types/js-levenshtein" "^1.1.0" + chalk "4.1.1" + chokidar "^3.4.2" + cookie "^0.4.1" + graphql "^15.5.1" + headers-utils "^3.0.2" + inquirer "^8.2.0" + is-node-process "^1.0.1" + js-levenshtein "^1.1.6" + node-fetch "^2.6.1" + path-to-regexp "^6.2.0" + statuses "^2.0.0" + strict-event-emitter "^0.2.0" + type-fest "^1.2.2" + yargs "^17.3.0" + multicast-dns-service-types@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901" @@ -22997,6 +23016,11 @@ path-to-regexp@^1.7.0: dependencies: isarray "0.0.1" +path-to-regexp@^6.2.0: + version "6.2.0" + resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.0.tgz#f7b3803336104c346889adece614669230645f38" + integrity sha512-f66KywYG6+43afgE/8j/GoiNyygk/bnoCbps++3ErRKsIYkGGupyv07R2Ok5m9i67Iqc+T2g1eAUGUPzWhYTyg== + path-type@^1.0.0: version "1.1.0" resolved "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" @@ -26969,6 +26993,15 @@ string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.0" +string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + "string.prototype.matchall@^4.0.0 || ^3.0.1", string.prototype.matchall@^4.0.6: version "4.0.6" resolved "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.6.tgz#5abb5dabc94c7b0ea2380f65ba610b3a544b15fa" @@ -27066,6 +27099,13 @@ strip-ansi@^4.0.0: dependencies: ansi-regex "^3.0.0" +strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + strip-ansi@^7.0.0: version "7.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2" @@ -29943,6 +29983,11 @@ yargs-parser@^18.1.2, yargs-parser@^18.1.3: camelcase "^5.0.0" decamelize "^1.2.0" +yargs-parser@^21.0.0: + version "21.0.0" + resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.0.tgz#a485d3966be4317426dd56bdb6a30131b281dc55" + integrity sha512-z9kApYUOCwoeZ78rfRYYWdiU/iNL6mwwYlkkZfJoyMR1xps+NEBX5X7XmRpxkZHhXJ6+Ey00IwKxBBSW9FIjyA== + yargs-parser@^3.2.0: version "3.2.0" resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-3.2.0.tgz#5081355d19d9d0c8c5d81ada908cb4e6d186664f" @@ -30007,6 +30052,19 @@ yargs@^17.1.1: y18n "^5.0.5" yargs-parser "^20.2.2" +yargs@^17.3.0: + version "17.3.0" + resolved "https://registry.npmjs.org/yargs/-/yargs-17.3.0.tgz#295c4ffd0eef148ef3e48f7a2e0f58d0e4f26b1c" + integrity sha512-GQl1pWyDoGptFPJx9b9L6kmR33TGusZvXIZUT+BOz9f7X2L94oeAskFYLEg/FkhV06zZPBYLvLZRWeYId29lew== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.0.0" + yargs@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/yargs/-/yargs-5.0.0.tgz#3355144977d05757dbb86d6e38ec056123b3a66e" From da676a49ab9998a40432d02e453359f164d22dd4 Mon Sep 17 00:00:00 2001 From: Erik Larsson Date: Wed, 22 Dec 2021 22:16:49 +0100 Subject: [PATCH 18/57] fixes api auth bug in techdocs backend Signed-off-by: Erik Larsson --- .changeset/seven-tomatoes-smash.md | 5 +++++ .../techdocs-backend/src/search/DefaultTechDocsCollator.ts | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 .changeset/seven-tomatoes-smash.md diff --git a/.changeset/seven-tomatoes-smash.md b/.changeset/seven-tomatoes-smash.md new file mode 100644 index 0000000000..e76e9aa4e2 --- /dev/null +++ b/.changeset/seven-tomatoes-smash.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs-backend': minor +--- + +fixes api auth bug in techdocs backend diff --git a/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.ts b/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.ts index fa8a1156fe..1d3a3dbc3a 100644 --- a/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.ts +++ b/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.ts @@ -123,6 +123,11 @@ export class DefaultTechDocsCollator implements DocumentCollator { techDocsBaseUrl, entityInfo, ), + { + headers: { + Authorization: `Bearer ${token}`, + }, + }, ); const searchIndex = await searchIndexResponse.json(); From bc0c76e89dc465d008c1c33f367eed03370c86aa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Dec 2021 04:08:20 +0000 Subject: [PATCH 19/57] build(deps): bump typescript-json-schema from 0.51.0 to 0.52.0 Bumps [typescript-json-schema](https://github.com/YousefED/typescript-json-schema) from 0.51.0 to 0.52.0. - [Release notes](https://github.com/YousefED/typescript-json-schema/releases) - [Commits](https://github.com/YousefED/typescript-json-schema/compare/v0.51.0...v0.52.0) --- updated-dependencies: - dependency-name: typescript-json-schema dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- packages/config-loader/package.json | 2 +- yarn.lock | 71 +++++++++-------------------- 2 files changed, 22 insertions(+), 51 deletions(-) diff --git a/packages/config-loader/package.json b/packages/config-loader/package.json index 78f8da3443..dbe4ecb67c 100644 --- a/packages/config-loader/package.json +++ b/packages/config-loader/package.json @@ -42,7 +42,7 @@ "json-schema-merge-allof": "^0.8.1", "json-schema-traverse": "^1.0.0", "node-fetch": "^2.6.1", - "typescript-json-schema": "^0.51.0", + "typescript-json-schema": "^0.52.0", "yaml": "^1.9.2", "yup": "^0.32.9" }, diff --git a/yarn.lock b/yarn.lock index fe8a92d1a5..b054dc8182 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6975,11 +6975,6 @@ resolved "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz#95f2d167ffb9b8d2068b0b235302fafd4df711f2" integrity sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg== -"@tsconfig/node16@^1.0.1": - version "1.0.1" - resolved "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.1.tgz#a6ca6a9a0ff366af433f42f5f0e124794ff6b8f1" - integrity sha512-FTgBI767POY/lKNDNbIzgAX6miIDBs6NTCbdlDb8TrWovHsSvaVIZDlTqym29C6UqhzwcJx4CYr+AlrMywA0cA== - "@tsconfig/node16@^1.0.2": version "1.0.2" resolved "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz#423c77877d0569db20e1fc80885ac4118314010e" @@ -25833,6 +25828,11 @@ safe-stable-stringify@^1.1.0: resolved "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-1.1.1.tgz#c8a220ab525cd94e60ebf47ddc404d610dc5d84a" integrity sha512-ERq4hUjKDbJfE4+XtZLFPCDi8Vb1JqaxAPTxWFLBx8XcAlf9Bda/ZJdVezs/NAfsMQScyIlUMx+Yeu7P7rx5jw== +safe-stable-stringify@^2.2.0: + version "2.3.1" + resolved "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.3.1.tgz#ab67cbe1fe7d40603ca641c5e765cb942d04fc73" + integrity sha512-kYBSfT+troD9cDA85VDnHZ1rpHC50O0g1e6WlGHVCz/g+JS+9WKLj+XwFYyR8UbrZN8ll9HUpDAAddY58MGisg== + "safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" @@ -28088,23 +28088,7 @@ ts-log@^2.2.3: resolved "https://registry.npmjs.org/ts-log/-/ts-log-2.2.3.tgz#4da5640fe25a9fb52642cd32391c886721318efb" integrity sha512-XvB+OdKSJ708Dmf9ore4Uf/q62AYDTzFcAdxc8KNML1mmAWywRFVt/dn1KYJH8Agt5UJNujfM3znU5PxgAzA2w== -ts-node@^10.0.0: - version "10.0.0" - resolved "https://registry.npmjs.org/ts-node/-/ts-node-10.0.0.tgz#05f10b9a716b0b624129ad44f0ea05dac84ba3be" - integrity sha512-ROWeOIUvfFbPZkoDis0L/55Fk+6gFQNZwwKPLinacRl6tsxstTF1DbAcLKkovwnpKMVvOMHP1TIbnwXwtLg1gg== - dependencies: - "@tsconfig/node10" "^1.0.7" - "@tsconfig/node12" "^1.0.7" - "@tsconfig/node14" "^1.0.0" - "@tsconfig/node16" "^1.0.1" - arg "^4.1.0" - create-require "^1.1.0" - diff "^4.0.1" - make-error "^1.1.1" - source-map-support "^0.5.17" - yn "3.1.1" - -ts-node@^10.2.1, ts-node@^10.4.0: +ts-node@^10.0.0, ts-node@^10.2.1, ts-node@^10.4.0: version "10.4.0" resolved "https://registry.npmjs.org/ts-node/-/ts-node-10.4.0.tgz#680f88945885f4e6cf450e7f0d6223dd404895f7" integrity sha512-g0FlPvvCXSIO1JDF6S232P5jPYqBkRL9qly81ZgAOSU7rwI0stphCgd2kLiCrU9DjQCrJMWEqcNSjQL02s6d8A== @@ -28323,33 +28307,33 @@ typedarray@^0.0.6: resolved "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typescript-json-schema@^0.51.0: - version "0.51.0" - resolved "https://registry.npmjs.org/typescript-json-schema/-/typescript-json-schema-0.51.0.tgz#e2abff69b8564c98c0edef2c13d55ef10fd71427" - integrity sha512-POhWbUNs2oaBti1W9k/JwS+uDsaZD9J/KQiZ/iXRQEOD0lTn9VmshIls9tn+A9X6O+smPjeEz5NEy6WTkCCzrQ== +typescript-json-schema@^0.52.0: + version "0.52.0" + resolved "https://registry.npmjs.org/typescript-json-schema/-/typescript-json-schema-0.52.0.tgz#954560ec90e5486e8f7a5b7706ec59286a708e29" + integrity sha512-3ZdHzx116gZ+D9LmMl5/+d1G3Rpt8baWngKzepYWHnXbAa8Winv64CmFRqLlMKneE1c40yugYDFcWdyX1FjGzQ== dependencies: "@types/json-schema" "^7.0.9" "@types/node" "^16.9.2" glob "^7.1.7" - json-stable-stringify "^1.0.1" + safe-stable-stringify "^2.2.0" ts-node "^10.2.1" - typescript "~4.2.3" + typescript "~4.4.4" yargs "^17.1.1" -typescript@^4.0.3, typescript@~4.2.3: - version "4.2.4" - resolved "https://registry.npmjs.org/typescript/-/typescript-4.2.4.tgz#8610b59747de028fda898a8aef0e103f156d0961" - integrity sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg== +typescript@^4.0.3, typescript@~4.5.2: + version "4.5.4" + resolved "https://registry.npmjs.org/typescript/-/typescript-4.5.4.tgz#a17d3a0263bf5c8723b9c52f43c5084edf13c2e8" + integrity sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg== typescript@~4.3.5: version "4.3.5" resolved "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz#4d1c37cc16e893973c45a06886b7113234f119f4" integrity sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA== -typescript@~4.5.2: - version "4.5.4" - resolved "https://registry.npmjs.org/typescript/-/typescript-4.5.4.tgz#a17d3a0263bf5c8723b9c52f43c5084edf13c2e8" - integrity sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg== +typescript@~4.4.4: + version "4.4.4" + resolved "https://registry.npmjs.org/typescript/-/typescript-4.4.4.tgz#2cd01a1a1f160704d3101fd5a58ff0f9fcb8030c" + integrity sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA== ua-parser-js@^0.7.18: version "0.7.28" @@ -29941,20 +29925,7 @@ yargs@^16.1.1, yargs@^16.2.0: y18n "^5.0.5" yargs-parser "^20.2.2" -yargs@^17.0.0, yargs@^17.0.1: - version "17.0.1" - resolved "https://registry.npmjs.org/yargs/-/yargs-17.0.1.tgz#6a1ced4ed5ee0b388010ba9fd67af83b9362e0bb" - integrity sha512-xBBulfCc8Y6gLFcrPvtqKz9hz8SO0l1Ni8GgDekvBX2ro0HRQImDGnikfc33cgzcYUSncapnNcZDjVFIH3f6KQ== - dependencies: - cliui "^7.0.2" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.0" - y18n "^5.0.5" - yargs-parser "^20.2.2" - -yargs@^17.1.1: +yargs@^17.0.0, yargs@^17.0.1, yargs@^17.1.1: version "17.2.1" resolved "https://registry.npmjs.org/yargs/-/yargs-17.2.1.tgz#e2c95b9796a0e1f7f3bf4427863b42e0418191ea" integrity sha512-XfR8du6ua4K6uLGm5S6fA+FIJom/MdJcFNVY8geLlp2v8GYbOXD4EB1tPNZsRn4vBzKGMgb5DRZMeWuFc2GO8Q== From 8d00dc427c4871364d3514471aa6787b6b17a903 Mon Sep 17 00:00:00 2001 From: goenning Date: Wed, 22 Dec 2021 16:58:40 +0000 Subject: [PATCH 20/57] ability to add custom operators Signed-off-by: goenning --- .changeset/spicy-moons-poke.md | 5 + .../JsonRulesEngineFactChecker.test.ts | 120 ++++++++++++++++-- .../src/service/JsonRulesEngineFactChecker.ts | 33 ++++- 3 files changed, 144 insertions(+), 14 deletions(-) create mode 100644 .changeset/spicy-moons-poke.md diff --git a/.changeset/spicy-moons-poke.md b/.changeset/spicy-moons-poke.md new file mode 100644 index 0000000000..fdf130c491 --- /dev/null +++ b/.changeset/spicy-moons-poke.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-tech-insights-backend-module-jsonfc': minor +--- + +ability to add custom operators diff --git a/plugins/tech-insights-backend-module-jsonfc/src/service/JsonRulesEngineFactChecker.test.ts b/plugins/tech-insights-backend-module-jsonfc/src/service/JsonRulesEngineFactChecker.test.ts index 5c33e9e369..923f1554b6 100644 --- a/plugins/tech-insights-backend-module-jsonfc/src/service/JsonRulesEngineFactChecker.test.ts +++ b/plugins/tech-insights-backend-module-jsonfc/src/service/JsonRulesEngineFactChecker.test.ts @@ -24,6 +24,7 @@ import { } from '../index'; import { getVoidLogger } from '@backstage/backend-common'; import { TechInsightJsonRuleCheck } from '../types'; +import { Operator } from 'json-rules-engine'; const testChecks: Record = { broken: [ @@ -127,6 +128,49 @@ const testChecks: Record = { }, }, ], + + customOperator: [ + { + id: 'customOperatorTestCheck', + name: 'customOperatorTestCheck', + type: JSON_RULE_ENGINE_CHECK_TYPE, + description: 'Check For Testing using Custom Operator', + factIds: ['test-factretriever'], + rule: { + conditions: { + all: [ + { + fact: 'testnumberfact', + operator: 'isDivisibleBy', + value: 2, + }, + ], + }, + }, + }, + ], + + invalidCustomOperator: [ + { + id: 'invalidCustomOperatorTestCheck', + name: 'invalidCustomOperatorTestCheck', + type: JSON_RULE_ENGINE_CHECK_TYPE, + description: + 'Check For Testing using a Custom Operator that is not registered', + factIds: ['test-factretriever'], + rule: { + conditions: { + all: [ + { + fact: 'testnumberfact', + operator: 'isOdd', + value: 2, + }, + ], + }, + }, + }, + ], }; const latestSchemasMock = jest.fn().mockImplementation(() => [ @@ -166,6 +210,9 @@ describe('JsonRulesEngineFactChecker', () => { const factChecker = new JsonRulesEngineFactCheckerFactory({ checkRegistry: mockCheckRegistry, checks: [], + operators: [ + new Operator('isDivisibleBy', (a, b) => a % b === 0), + ], logger: getVoidLogger(), }).construct(mockRepository); @@ -234,6 +281,52 @@ describe('JsonRulesEngineFactChecker', () => { }); }); + it('should use custom operators when defined', async () => { + latestFactsByIdsMock.mockImplementation(() => + Promise.resolve({ + ['test-factretriever']: { + id: 'test-factretriever', + facts: { + testnumberfact: 4, + }, + }, + }), + ); + const results = await factChecker.runChecks('a/a/a', ['customOperator']); + expect(results).toHaveLength(1); + expect(results[0]).toMatchObject({ + facts: { + testnumberfact: { + value: 4, + type: 'integer', + description: '', + }, + }, + result: true, + check: { + id: 'customOperatorTestCheck', + type: JSON_RULE_ENGINE_CHECK_TYPE, + name: 'customOperatorTestCheck', + description: 'Check For Testing using Custom Operator', + factIds: ['test-factretriever'], + rule: { + conditions: { + all: [ + { + fact: 'testnumberfact', + factResult: 4, + operator: 'isDivisibleBy', + result: true, + value: 2, + }, + ], + priority: 1, + }, + }, + }, + }); + }); + it('should gracefully handle multiple check at once', async () => { const results = await factChecker.runChecks('a/a/a', [ 'simple', @@ -307,17 +400,22 @@ describe('JsonRulesEngineFactChecker', () => { }); describe('when validating checks', () => { - it('should succeed on valid rules', async () => { - const validationResponse = await factChecker.validate( - testChecks.simple[0], - ); - expect(validationResponse.valid).toBeTruthy(); - }); - it('should fail on broken rules', async () => { - const validationResponse = await factChecker.validate( - testChecks.broken[0], - ); - expect(validationResponse.valid).toBeFalsy(); + [testChecks.simple[0], testChecks.customOperator[0]].forEach(check => { + it(`should succeed on valid rule: ${check.name}`, async () => { + const validationResponse = await factChecker.validate(check); + expect(validationResponse.valid).toBeTruthy(); + }); }); + + [testChecks.broken[0], testChecks.invalidCustomOperator[0]].forEach( + check => { + it(`should succeed on invalid rule: ${check.name}`, async () => { + const validationResponse = await factChecker.validate( + testChecks.broken[0], + ); + expect(validationResponse.valid).toBeFalsy(); + }); + }, + ); }); }); diff --git a/plugins/tech-insights-backend-module-jsonfc/src/service/JsonRulesEngineFactChecker.ts b/plugins/tech-insights-backend-module-jsonfc/src/service/JsonRulesEngineFactChecker.ts index 2623a5c2ee..aef09d5434 100644 --- a/plugins/tech-insights-backend-module-jsonfc/src/service/JsonRulesEngineFactChecker.ts +++ b/plugins/tech-insights-backend-module-jsonfc/src/service/JsonRulesEngineFactChecker.ts @@ -23,11 +23,16 @@ import { CheckValidationResponse, } from '@backstage/plugin-tech-insights-node'; import { FactResponse } from '@backstage/plugin-tech-insights-common'; -import { Engine, EngineResult, TopLevelCondition } from 'json-rules-engine'; +import { + Engine, + EngineResult, + Operator, + TopLevelCondition, +} from 'json-rules-engine'; import { DefaultCheckRegistry } from './CheckRegistry'; import { Logger } from 'winston'; import { pick } from 'lodash'; -import Ajv from 'ajv'; +import Ajv, { SchemaObject } from 'ajv'; import * as validationSchema from './validation-schema.json'; import { JSON_RULE_ENGINE_CHECK_TYPE } from '../constants'; @@ -46,6 +51,7 @@ export type JsonRulesEngineFactCheckerOptions = { repository: TechInsightsStore; logger: Logger; checkRegistry?: TechInsightCheckRegistry; + operators?: Operator[]; }; /** @@ -60,15 +66,27 @@ export class JsonRulesEngineFactChecker private readonly checkRegistry: TechInsightCheckRegistry; private repository: TechInsightsStore; private readonly logger: Logger; + private readonly validationSchema: SchemaObject; + private readonly operators: Operator[]; constructor({ checks, repository, logger, checkRegistry, + operators, }: JsonRulesEngineFactCheckerOptions) { this.repository = repository; this.logger = logger; + this.operators = operators || []; + this.validationSchema = JSON.parse(JSON.stringify(validationSchema)); + + this.operators.forEach(op => { + this.validationSchema.definitions.condition.properties.operator.anyOf.push( + { const: op.name }, + ); + }); + checks.forEach(check => this.validate(check)); this.checkRegistry = checkRegistry ?? @@ -80,6 +98,10 @@ export class JsonRulesEngineFactChecker checks?: string[], ): Promise { const engine = new Engine(); + this.operators.forEach(op => { + engine.addOperator(op); + }); + const techInsightChecks = checks ? await this.checkRegistry.getAll(checks) : await this.checkRegistry.list(); @@ -125,7 +147,7 @@ export class JsonRulesEngineFactChecker check: TechInsightJsonRuleCheck, ): Promise { const ajv = new Ajv({ verbose: true }); - const validator = ajv.compile(validationSchema); + const validator = ajv.compile(this.validationSchema); const isValidToSchema = validator(check.rule); if (check.type !== JSON_RULE_ENGINE_CHECK_TYPE) { const msg = `Only ${JSON_RULE_ENGINE_CHECK_TYPE} checks can be registered to this fact checker`; @@ -317,6 +339,7 @@ export type JsonRulesEngineFactCheckerFactoryOptions = { checks: TechInsightJsonRuleCheck[]; logger: Logger; checkRegistry?: TechInsightCheckRegistry; + operators?: Operator[]; }; /** @@ -330,15 +353,18 @@ export class JsonRulesEngineFactCheckerFactory { private readonly checks: TechInsightJsonRuleCheck[]; private readonly logger: Logger; private readonly checkRegistry?: TechInsightCheckRegistry; + private readonly operators?: Operator[]; constructor({ checks, logger, checkRegistry, + operators, }: JsonRulesEngineFactCheckerFactoryOptions) { this.logger = logger; this.checks = checks; this.checkRegistry = checkRegistry; + this.operators = operators; } /** @@ -352,6 +378,7 @@ export class JsonRulesEngineFactCheckerFactory { logger: this.logger, checkRegistry: this.checkRegistry, repository, + operators: this.operators, }); } } From d0db4a56f103b6fd0e790f6bbb9e00f5e51cef6b Mon Sep 17 00:00:00 2001 From: goenning Date: Wed, 22 Dec 2021 17:14:24 +0000 Subject: [PATCH 21/57] update api reports Signed-off-by: goenning --- plugins/tech-insights-backend-module-jsonfc/api-report.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/tech-insights-backend-module-jsonfc/api-report.md b/plugins/tech-insights-backend-module-jsonfc/api-report.md index 7093fbfc2b..2e62e7d6a0 100644 --- a/plugins/tech-insights-backend-module-jsonfc/api-report.md +++ b/plugins/tech-insights-backend-module-jsonfc/api-report.md @@ -8,6 +8,7 @@ import { CheckResponse } from '@backstage/plugin-tech-insights-common'; import { CheckValidationResponse } from '@backstage/plugin-tech-insights-node'; import { FactChecker } from '@backstage/plugin-tech-insights-node'; import { Logger as Logger_2 } from 'winston'; +import { Operator } from 'json-rules-engine'; import { TechInsightCheck } from '@backstage/plugin-tech-insights-node'; import { TechInsightCheckRegistry } from '@backstage/plugin-tech-insights-node'; import { TechInsightsStore } from '@backstage/plugin-tech-insights-node'; @@ -50,6 +51,7 @@ export class JsonRulesEngineFactChecker repository, logger, checkRegistry, + operators, }: JsonRulesEngineFactCheckerOptions); // (undocumented) getChecks(): Promise; @@ -68,6 +70,7 @@ export class JsonRulesEngineFactCheckerFactory { checks, logger, checkRegistry, + operators, }: JsonRulesEngineFactCheckerFactoryOptions); // (undocumented) construct(repository: TechInsightsStore): JsonRulesEngineFactChecker; @@ -78,6 +81,7 @@ export type JsonRulesEngineFactCheckerFactoryOptions = { checks: TechInsightJsonRuleCheck[]; logger: Logger_2; checkRegistry?: TechInsightCheckRegistry; + operators?: Operator[]; }; // @public @@ -86,6 +90,7 @@ export type JsonRulesEngineFactCheckerOptions = { repository: TechInsightsStore; logger: Logger_2; checkRegistry?: TechInsightCheckRegistry; + operators?: Operator[]; }; // @public (undocumented) From fc6530ea191ed62014691bc628e5a8e6f5e2e241 Mon Sep 17 00:00:00 2001 From: goenning Date: Wed, 22 Dec 2021 17:22:25 +0000 Subject: [PATCH 22/57] fix unit test name Signed-off-by: goenning --- .../src/service/JsonRulesEngineFactChecker.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/tech-insights-backend-module-jsonfc/src/service/JsonRulesEngineFactChecker.test.ts b/plugins/tech-insights-backend-module-jsonfc/src/service/JsonRulesEngineFactChecker.test.ts index 923f1554b6..28b5a776fa 100644 --- a/plugins/tech-insights-backend-module-jsonfc/src/service/JsonRulesEngineFactChecker.test.ts +++ b/plugins/tech-insights-backend-module-jsonfc/src/service/JsonRulesEngineFactChecker.test.ts @@ -409,7 +409,7 @@ describe('JsonRulesEngineFactChecker', () => { [testChecks.broken[0], testChecks.invalidCustomOperator[0]].forEach( check => { - it(`should succeed on invalid rule: ${check.name}`, async () => { + it(`should fail on broken rules: ${check.name}`, async () => { const validationResponse = await factChecker.validate( testChecks.broken[0], ); From 273c9aa1ca26a169dd61673ff907e6068f5cddd6 Mon Sep 17 00:00:00 2001 From: goenning Date: Thu, 23 Dec 2021 11:03:06 +0000 Subject: [PATCH 23/57] fix unit test Signed-off-by: goenning --- .../service/JsonRulesEngineFactChecker.test.ts | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/plugins/tech-insights-backend-module-jsonfc/src/service/JsonRulesEngineFactChecker.test.ts b/plugins/tech-insights-backend-module-jsonfc/src/service/JsonRulesEngineFactChecker.test.ts index 28b5a776fa..c808713c3b 100644 --- a/plugins/tech-insights-backend-module-jsonfc/src/service/JsonRulesEngineFactChecker.test.ts +++ b/plugins/tech-insights-backend-module-jsonfc/src/service/JsonRulesEngineFactChecker.test.ts @@ -282,27 +282,17 @@ describe('JsonRulesEngineFactChecker', () => { }); it('should use custom operators when defined', async () => { - latestFactsByIdsMock.mockImplementation(() => - Promise.resolve({ - ['test-factretriever']: { - id: 'test-factretriever', - facts: { - testnumberfact: 4, - }, - }, - }), - ); const results = await factChecker.runChecks('a/a/a', ['customOperator']); expect(results).toHaveLength(1); expect(results[0]).toMatchObject({ facts: { testnumberfact: { - value: 4, + value: 3, type: 'integer', description: '', }, }, - result: true, + result: false, check: { id: 'customOperatorTestCheck', type: JSON_RULE_ENGINE_CHECK_TYPE, @@ -314,9 +304,9 @@ describe('JsonRulesEngineFactChecker', () => { all: [ { fact: 'testnumberfact', - factResult: 4, + factResult: 3, operator: 'isDivisibleBy', - result: true, + result: false, value: 2, }, ], From 52ba8760d8bbbf81427efe2bfd50c7c92eb9f7ad Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Dec 2021 13:12:38 +0000 Subject: [PATCH 24/57] build(deps): bump @azure/identity from 1.5.0 to 2.0.1 Bumps [@azure/identity](https://github.com/Azure/azure-sdk-for-js) from 1.5.0 to 2.0.1. - [Release notes](https://github.com/Azure/azure-sdk-for-js/releases) - [Commits](https://github.com/Azure/azure-sdk-for-js/compare/@azure/identity_1.5.0...@azure/identity_2.0.1) --- updated-dependencies: - dependency-name: "@azure/identity" dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- packages/techdocs-common/package.json | 2 +- yarn.lock | 175 +++++++++----------------- 2 files changed, 62 insertions(+), 115 deletions(-) diff --git a/packages/techdocs-common/package.json b/packages/techdocs-common/package.json index 9285b7672b..7d50a2bcba 100644 --- a/packages/techdocs-common/package.json +++ b/packages/techdocs-common/package.json @@ -36,7 +36,7 @@ "url": "https://github.com/backstage/backstage/issues" }, "dependencies": { - "@azure/identity": "^1.5.0", + "@azure/identity": "^2.0.1", "@azure/storage-blob": "^12.5.0", "@backstage/backend-common": "^0.10.0", "@backstage/catalog-model": "^0.9.7", diff --git a/yarn.lock b/yarn.lock index 402993ff0a..0bee86e295 100644 --- a/yarn.lock +++ b/yarn.lock @@ -127,15 +127,7 @@ resolved "https://registry.npmjs.org/@azure/core-asynciterator-polyfill/-/core-asynciterator-polyfill-1.0.0.tgz#dcccebb88406e5c76e0e1d52e8cc4c43a68b3ee7" integrity sha512-kmv8CGrPfN9SwMwrkiBK9VTQYxdFQEGe0BmQk+M8io56P9KNzpAxcWE/1fxJj7uouwN4kXF0BHW8DNlgx+wtCg== -"@azure/core-auth@^1.1.3": - version "1.1.4" - resolved "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.1.4.tgz#af9a334acf3cb9c49e6013e6caf6dc9d43476030" - integrity sha512-+j1embyH1jqf04AIfJPdLafd5SC1y6z1Jz4i+USR1XkTp6KM8P5u4/AjmWMVoEQdM/M29PJcRDZcCEWjK9S1bw== - dependencies: - "@azure/abort-controller" "^1.0.0" - tslib "^2.0.0" - -"@azure/core-auth@^1.3.0": +"@azure/core-auth@^1.1.3", "@azure/core-auth@^1.3.0": version "1.3.2" resolved "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.3.2.tgz#6a2c248576c26df365f6c7881ca04b7f6d08e3d0" integrity sha512-7CU6DmCHIZp5ZPiZ9r3J17lTKMmYsm/zGvNkjArQwPkrLlZ1TZ+EUYfGgh2X31OLMVAQCTJZW4cXHJi02EbJnA== @@ -225,6 +217,14 @@ "@opentelemetry/api" "^1.0.0" tslib "^2.2.0" +"@azure/core-tracing@1.0.0-preview.13": + version "1.0.0-preview.13" + resolved "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.0-preview.13.tgz#55883d40ae2042f6f1e12b17dd0c0d34c536d644" + integrity sha512-KxDlhXyMlh2Jhj2ykX6vNEU0Vou4nHr025KoSEiz7cS3BNiHNaZcdECk/DmLkEB0as5T7b/TpRcehJ5yV6NeXQ== + dependencies: + "@opentelemetry/api" "^1.0.1" + tslib "^2.2.0" + "@azure/core-tracing@1.0.0-preview.9": version "1.0.0-preview.9" resolved "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.0-preview.9.tgz#84f3b85572013f9d9b85e1e5d89787aa180787eb" @@ -234,29 +234,35 @@ "@opentelemetry/api" "^0.10.2" tslib "^2.0.0" -"@azure/identity@^1.5.0": - version "1.5.0" - resolved "https://registry.npmjs.org/@azure/identity/-/identity-1.5.0.tgz#0ac832b95adaac00b4718d92b43b2c9c5ab42d2d" - integrity sha512-djgywuWtX6720seqNOPmGM1hY54oHnjRT0MLIOzacMARTZuEtAIaFFvMPBlUIMQdtSGhdjH+/MS1/9PE8j83eA== +"@azure/core-util@^1.0.0-beta.1": + version "1.0.0-beta.1" + resolved "https://registry.npmjs.org/@azure/core-util/-/core-util-1.0.0-beta.1.tgz#2efd2c74b4b0a38180369f50fe274a3c4cd36e98" + integrity sha512-pS6cup979/qyuyNP9chIybK2qVkJ3MarbY/bx3JcGKE6An6dRweLnsfJfU2ydqUI/B51Rjnn59ajHIhCUTwWZw== dependencies: + tslib "^2.0.0" + +"@azure/identity@^2.0.1": + version "2.0.1" + resolved "https://registry.npmjs.org/@azure/identity/-/identity-2.0.1.tgz#31107506371e520bc874647a9e4384cfd2f85103" + integrity sha512-gdGGuLKlKIQaf2RefA84keoBfmWfiAntbW2SzcdKvwLSGzsio/qkyY3sYUpXRz/sqLDxguuimgZukp7TPgwIlg== + dependencies: + "@azure/abort-controller" "^1.0.0" "@azure/core-auth" "^1.3.0" "@azure/core-client" "^1.0.0" "@azure/core-rest-pipeline" "^1.1.0" - "@azure/core-tracing" "1.0.0-preview.12" + "@azure/core-tracing" "1.0.0-preview.13" + "@azure/core-util" "^1.0.0-beta.1" "@azure/logger" "^1.0.0" - "@azure/msal-node" "1.0.0-beta.6" + "@azure/msal-browser" "^2.16.0" + "@azure/msal-common" "^4.5.1" + "@azure/msal-node" "^1.3.0" "@types/stoppable" "^1.1.0" - axios "^0.21.1" events "^3.0.0" jws "^4.0.0" - msal "^1.0.2" - open "^7.0.0" - qs "^6.7.0" + open "^8.0.0" stoppable "^1.1.0" - tslib "^2.0.0" + tslib "^2.2.0" uuid "^8.3.0" - optionalDependencies: - keytar "^7.3.0" "@azure/logger@^1.0.0": version "1.0.1" @@ -265,36 +271,33 @@ dependencies: tslib "^2.0.0" -"@azure/msal-common@^4.0.0": - version "4.4.0" - resolved "https://registry.npmjs.org/@azure/msal-common/-/msal-common-4.4.0.tgz#818526042f78838ebc332fb735e7de64d8bccb45" - integrity sha512-Qrs33Ctt2KM7NxArFPIUKc8UbIcm7zYxJFdJeQ9k7HKBhVk3e88CUz1Mw33cS/Jr+YA1H02OAzHg++bJ+4SFyQ== +"@azure/msal-browser@^2.16.0": + version "2.20.0" + resolved "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-2.20.0.tgz#78e34395048c4a8842400d4168b2fb3bdd3c854e" + integrity sha512-Fl8boo38fPNlEm84fRCulbTfHJo+Z/i+1gcdJTG+PqmrkMOUVTdpkwznGh6ZQdAM34uumEgzukmqMr8lVKrytA== + dependencies: + "@azure/msal-common" "^5.2.0" + +"@azure/msal-common@^4.5.1": + version "4.5.1" + resolved "https://registry.npmjs.org/@azure/msal-common/-/msal-common-4.5.1.tgz#f35af8b634ae24aebd0906deb237c0db1afa5826" + integrity sha512-/i5dXM+QAtO+6atYd5oHGBAx48EGSISkXNXViheliOQe+SIFMDo3gSq3lL54W0suOSAsVPws3XnTaIHlla0PIQ== dependencies: debug "^4.1.1" -"@azure/msal-common@^5.0.1": - version "5.0.1" - resolved "https://registry.npmjs.org/@azure/msal-common/-/msal-common-5.0.1.tgz#234030b340cc63575e84190b96a8a336b69c7698" - integrity sha512-CmPR3XM9+CGUu7V/+bAwDxyN6XqWJJhVLmv7utT3sbgay4l5roVXsD1t4wURTs8PwzxmmnJOrhvvGhoDxUW69g== +"@azure/msal-common@^5.2.0": + version "5.2.0" + resolved "https://registry.npmjs.org/@azure/msal-common/-/msal-common-5.2.0.tgz#49440e04f4d0961fc5a1a1718fbe5e4eae2db5db" + integrity sha512-oVc4soy5MEZOp9NvCDqBk57mtiUTJXQQ8Z8S/4UiRQP8RG8snuCFQUs9xxdIfvl2FWIvgiBz+SMByyjTaRX42Q== dependencies: debug "^4.1.1" -"@azure/msal-node@1.0.0-beta.6": - version "1.0.0-beta.6" - resolved "https://registry.npmjs.org/@azure/msal-node/-/msal-node-1.0.0-beta.6.tgz#da6bc3a3a861057c85586055960e069f162548ee" - integrity sha512-ZQI11Uz1j0HJohb9JZLRD8z0moVcPks1AFW4Q/Gcl67+QvH4aKEJti7fjCcipEEZYb/qzLSO8U6IZgPYytsiJQ== +"@azure/msal-node@^1.1.0", "@azure/msal-node@^1.3.0": + version "1.4.0" + resolved "https://registry.npmjs.org/@azure/msal-node/-/msal-node-1.4.0.tgz#660685804fbdc533b10cc699f16323e27ec582c6" + integrity sha512-Ek6hqOFUi5QEAxZ55awM8y1N+9SzS9Qh8ijF4RDLtFuHzqP7xXmMnVC1lae45FlH55DUOo7dg/smuDJnb4kw6g== dependencies: - "@azure/msal-common" "^4.0.0" - axios "^0.21.1" - jsonwebtoken "^8.5.1" - uuid "^8.3.0" - -"@azure/msal-node@^1.1.0": - version "1.3.2" - resolved "https://registry.npmjs.org/@azure/msal-node/-/msal-node-1.3.2.tgz#17665397c04b9cad57b42eb6e21d5d6f35d9b83a" - integrity sha512-aKU2lVRKhZa1IJ/Za/Ir6qlythQ3FHz0g0px3SbM4iC1otyr3ANS4mIn/6fmkpZDIHc8eAgJh2KMep1Yn2zpig== - dependencies: - "@azure/msal-common" "^5.0.1" + "@azure/msal-common" "^5.2.0" axios "^0.21.4" jsonwebtoken "^8.5.1" uuid "^8.3.0" @@ -5437,6 +5440,11 @@ resolved "https://registry.npmjs.org/@opentelemetry/api/-/api-1.0.1.tgz#03c72f548431da5820a0c8864d1401e348e7e79f" integrity sha512-H5Djcc2txGAINgf3TNaq4yFofYSIK3722PM89S/3R8FuI/eqi1UscajlXk7EBkG9s2pxss/q6SHlpturaavXaw== +"@opentelemetry/api@^1.0.1": + version "1.0.4" + resolved "https://registry.npmjs.org/@opentelemetry/api/-/api-1.0.4.tgz#a167e46c10d05a07ab299fc518793b0cff8f6924" + integrity sha512-BuJuXRSJNQ3QoKA6GWWDyuLpOUck+9hAXNMCnrloc1aWVoy6Xq6t9PUV08aBZ4Lutqq2LEHM486bpZqoViScog== + "@opentelemetry/context-base@^0.10.2": version "0.10.2" resolved "https://registry.npmjs.org/@opentelemetry/context-base/-/context-base-0.10.2.tgz#55bea904b2b91aa8a8675df9eaba5961bddb1def" @@ -14684,11 +14692,6 @@ expand-brackets@^2.1.4: snapdragon "^0.8.1" to-regex "^3.0.1" -expand-template@^2.0.3: - version "2.0.3" - resolved "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" - integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== - expect@^24.8.0: version "24.9.0" resolved "https://registry.npmjs.org/expect/-/expect-24.9.0.tgz#b75165b4817074fa4a157794f46fe9f1ba15b6ca" @@ -15835,11 +15838,6 @@ gitconfiglocal@^1.0.0: dependencies: ini "^1.3.2" -github-from-package@0.0.0: - version "0.0.0" - resolved "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" - integrity sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4= - glob-base@^0.3.0: version "0.3.0" resolved "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4" @@ -19261,14 +19259,6 @@ kafkajs@^1.16.0-beta.6: resolved "https://registry.npmjs.org/kafkajs/-/kafkajs-1.16.0-beta.21.tgz#5736bcef7b505714642a82d6dc0d1507fc0ae817" integrity sha512-6iarOOnKTaei0EK+a+K2V/bBA7YgvpA69tZwnVF85PxGlvoG/wqKpfRNh2Mb04uiNTEwBYNEIO7hAFElEM6/AA== -keytar@^7.3.0: - version "7.7.0" - resolved "https://registry.npmjs.org/keytar/-/keytar-7.7.0.tgz#3002b106c01631aa79b1aa9ee0493b94179bbbd2" - integrity sha512-YEY9HWqThQc5q5xbXbRwsZTh2PJ36OSYRjSv3NN2xf5s5dpLTjEZnC2YikR29OaVybf9nQ0dJ/80i40RS97t/A== - dependencies: - node-addon-api "^3.0.0" - prebuild-install "^6.0.0" - keyv-memcache@^1.2.5: version "1.2.7" resolved "https://registry.npmjs.org/keyv-memcache/-/keyv-memcache-1.2.7.tgz#b8a43eeecdb11ad8f4d6d64abd4298d014c74955" @@ -21285,11 +21275,6 @@ mkdirp-classic@^0.5.2: resolved "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.2.tgz#54c441ce4c96cd7790e10b41a87aa51068ecab2b" integrity sha512-ejdnDQcR75gwknmMw/tx02AuRs8jCtqFoFqDZMjiNxsu85sRIJVXDKHuLYvUUPRBUtV2FpSZa9bL1BUa3BdR2g== -mkdirp-classic@^0.5.3: - version "0.5.3" - resolved "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" - integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== - mkdirp-infer-owner@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/mkdirp-infer-owner/-/mkdirp-infer-owner-2.0.0.tgz#55d3b368e7d89065c38f32fd38e638f0ab61d316" @@ -21381,13 +21366,6 @@ ms@^2.0.0, ms@^2.1.1, ms@^2.1.3: resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -msal@^1.0.2: - version "1.4.4" - resolved "https://registry.npmjs.org/msal/-/msal-1.4.4.tgz#3f9b5a4442aa711c12ab8e88b8ed89b293f99711" - integrity sha512-aOBD/L6jAsizDFzKxxvXxH0FEDjp6Inr3Ufi/Y2o7KCFKN+akoE2sLeszEb/0Y3VxHxK0F0ea7xQ/HHTomKivw== - dependencies: - tslib "^1.9.3" - msw@^0.35.0: version "0.35.0" resolved "https://registry.npmjs.org/msw/-/msw-0.35.0.tgz#18a4ceb6c822ef226a30421d434413bc45030d38" @@ -21533,11 +21511,6 @@ nanomatch@^1.2.9: snapdragon "^0.8.1" to-regex "^3.0.1" -napi-build-utils@^1.0.1: - version "1.0.2" - resolved "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806" - integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg== - native-url@^0.2.6: version "0.2.6" resolved "https://registry.npmjs.org/native-url/-/native-url-0.2.6.tgz#ca1258f5ace169c716ff44eccbddb674e10399ae" @@ -21613,13 +21586,6 @@ no-case@^3.0.4: lower-case "^2.0.2" tslib "^2.0.3" -node-abi@^2.21.0: - version "2.30.0" - resolved "https://registry.npmjs.org/node-abi/-/node-abi-2.30.0.tgz#8be53bf3e7945a34eea10e0fc9a5982776cf550b" - integrity sha512-g6bZh3YCKQRdwuO/tSZZYJAw622SjsRfJ2X0Iy4sSOHZ34/sPPdVBn8fev2tj7njzLwuqPw9uMtGsGkO5kIQvg== - dependencies: - semver "^5.4.1" - node-abort-controller@^3.0.1: version "3.0.1" resolved "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.0.1.tgz#f91fa50b1dee3f909afabb7e261b1e1d6b0cb74e" @@ -22032,7 +21998,7 @@ npm-run-path@^4.0.0, npm-run-path@^4.0.1: dependencies: path-key "^3.0.0" -"npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.1, 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== @@ -22278,7 +22244,7 @@ onetime@^5.1.0, onetime@^5.1.2: dependencies: mimic-fn "^2.1.0" -open@^7.0.0, open@^7.0.2, open@^7.0.3: +open@^7.0.2, open@^7.0.3: version "7.3.1" resolved "https://registry.npmjs.org/open/-/open-7.3.1.tgz#111119cb919ca1acd988f49685c4fdd0f4755356" integrity sha512-f2wt9DCBKKjlFbjzGb8MOAW8LH8F0mrs1zc7KTjAJ9PZNQbfenzWbNP1VZJvw6ICMG9r14Ah6yfwPn7T7i646A== @@ -22286,10 +22252,10 @@ open@^7.0.0, open@^7.0.2, open@^7.0.3: is-docker "^2.0.0" is-wsl "^2.1.1" -open@^8.0.9: - version "8.2.1" - resolved "https://registry.npmjs.org/open/-/open-8.2.1.tgz#82de42da0ccbf429bc12d099dad2e0975e14e8af" - integrity sha512-rXILpcQlkF/QuFez2BJDf3GsqpjGKbkUUToAIGo9A0Q6ZkoSGogZJulrUdwRkrAsoQvoZsrjCYt8+zblOk7JQQ== +open@^8.0.0, open@^8.0.9: + version "8.4.0" + resolved "https://registry.npmjs.org/open/-/open-8.4.0.tgz#345321ae18f8138f82565a910fdc6b39e8c244f8" + integrity sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q== dependencies: define-lazy-prop "^2.0.0" is-docker "^2.1.1" @@ -23739,25 +23705,6 @@ postgres-interval@^1.1.0: dependencies: xtend "^4.0.0" -prebuild-install@^6.0.0: - version "6.1.3" - resolved "https://registry.npmjs.org/prebuild-install/-/prebuild-install-6.1.3.tgz#8ea1f9d7386a0b30f7ef20247e36f8b2b82825a2" - integrity sha512-iqqSR84tNYQUQHRXalSKdIaM8Ov1QxOVuBNWI7+BzZWv6Ih9k75wOnH1rGQ9WWTaaLkTpxWKIciOF0KyfM74+Q== - dependencies: - detect-libc "^1.0.3" - expand-template "^2.0.3" - github-from-package "0.0.0" - minimist "^1.2.3" - mkdirp-classic "^0.5.3" - napi-build-utils "^1.0.1" - node-abi "^2.21.0" - npmlog "^4.0.1" - pump "^3.0.0" - rc "^1.2.7" - simple-get "^3.0.3" - tar-fs "^2.0.0" - tunnel-agent "^0.6.0" - precond@0.2: version "0.2.3" resolved "https://registry.npmjs.org/precond/-/precond-0.2.3.tgz#aa9591bcaa24923f1e0f4849d240f47efc1075ac" @@ -24184,7 +24131,7 @@ qs@6.7.0: resolved "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== -qs@^6.10.0, qs@^6.10.1, qs@^6.7.0, qs@^6.9.1, qs@^6.9.4, qs@^6.9.6: +qs@^6.10.0, qs@^6.10.1, qs@^6.9.1, qs@^6.9.4, qs@^6.9.6: version "6.10.1" resolved "https://registry.npmjs.org/qs/-/qs-6.10.1.tgz#4931482fa8d647a5aab799c5271d2133b981fb6a" integrity sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg== @@ -27512,7 +27459,7 @@ tar-fs@2.0.0: pump "^3.0.0" tar-stream "^2.0.0" -tar-fs@^2.0.0, tar-fs@^2.1.1: +tar-fs@^2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng== From d078377f674d72eec7477e15e735a2470ec32545 Mon Sep 17 00:00:00 2001 From: Phil Kuang Date: Wed, 15 Dec 2021 15:53:48 -0500 Subject: [PATCH 25/57] feat(scaffolder): support updating template inputs of tasks Signed-off-by: Phil Kuang --- .changeset/poor-zoos-invent.md | 7 ++ plugins/scaffolder-backend/api-report.md | 56 ++------------ .../src/scaffolder/tasks/types.ts | 73 ++++-------------- plugins/scaffolder-common/api-report.md | 59 +++++++++++++++ plugins/scaffolder-common/src/TaskSpec.ts | 74 +++++++++++++++++++ plugins/scaffolder-common/src/index.ts | 2 + plugins/scaffolder/api-report.md | 2 +- plugins/scaffolder/package.json | 1 + .../src/components/TaskPage/TaskPage.tsx | 42 ++++++++++- .../components/TemplatePage/TemplatePage.tsx | 8 +- plugins/scaffolder/src/types.ts | 13 +--- 11 files changed, 215 insertions(+), 122 deletions(-) create mode 100644 .changeset/poor-zoos-invent.md create mode 100644 plugins/scaffolder-common/src/TaskSpec.ts diff --git a/.changeset/poor-zoos-invent.md b/.changeset/poor-zoos-invent.md new file mode 100644 index 0000000000..a1c87bd3ad --- /dev/null +++ b/.changeset/poor-zoos-invent.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-scaffolder': patch +'@backstage/plugin-scaffolder-backend': patch +'@backstage/plugin-scaffolder-common': patch +--- + +Support navigating back to pre-filled templates to update inputs of scaffolder tasks for resubmission diff --git a/plugins/scaffolder-backend/api-report.md b/plugins/scaffolder-backend/api-report.md index b7c1a9146c..5c891b1802 100644 --- a/plugins/scaffolder-backend/api-report.md +++ b/plugins/scaffolder-backend/api-report.md @@ -25,7 +25,11 @@ import { Schema } from 'jsonschema'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { ScmIntegrations } from '@backstage/integration'; import { SpawnOptionsWithoutStdio } from 'child_process'; +import { TaskSpec } from '@backstage/plugin-scaffolder-common'; +import { TaskSpecV1beta2 } from '@backstage/plugin-scaffolder-common'; +import { TaskSpecV1beta3 } from '@backstage/plugin-scaffolder-common'; import { TemplateEntityV1beta2 } from '@backstage/catalog-model'; +import { TemplateMetadata } from '@backstage/plugin-scaffolder-common'; import { UrlReader } from '@backstage/backend-common'; import { Writable } from 'stream'; @@ -432,52 +436,11 @@ export type TaskSecrets = { token: string | undefined; }; -// @public -export type TaskSpec = TaskSpecV1beta2 | TaskSpecV1beta3; +export { TaskSpec }; -// @public -export interface TaskSpecV1beta2 { - // (undocumented) - apiVersion: 'backstage.io/v1beta2'; - // (undocumented) - baseUrl?: string; - // (undocumented) - metadata?: TemplateMetadata; - // (undocumented) - output: { - [name: string]: string; - }; - // (undocumented) - steps: Array<{ - id: string; - name: string; - action: string; - input?: JsonObject; - if?: string | boolean; - }>; - // (undocumented) - values: JsonObject; -} +export { TaskSpecV1beta2 }; -// @public -export interface TaskSpecV1beta3 { - // (undocumented) - apiVersion: 'scaffolder.backstage.io/v1beta3'; - // (undocumented) - baseUrl?: string; - // (undocumented) - metadata?: TemplateMetadata; - // (undocumented) - output: { - [name: string]: JsonValue; - }; - // (undocumented) - parameters: JsonObject; - // Warning: (ae-forgotten-export) The symbol "TaskStep" needs to be exported by the entry point index.d.ts - // - // (undocumented) - steps: TaskStep[]; -} +export { TaskSpecV1beta3 }; // @public export interface TaskState { @@ -573,8 +536,5 @@ export class TemplateActionRegistry { ): void; } -// @public -export type TemplateMetadata = { - name: string; -}; +export { TemplateMetadata }; ``` diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts index b492f8ad2c..1a69f28fb6 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts @@ -15,6 +15,21 @@ */ import { JsonValue, JsonObject } from '@backstage/types'; +import { + TaskSpec, + TaskStep, + TemplateMetadata, + TaskSpecV1beta2, + TaskSpecV1beta3, +} from '@backstage/plugin-scaffolder-common'; + +export type { + TaskSpec, + TaskStep, + TemplateMetadata, + TaskSpecV1beta2, + TaskSpecV1beta3, +}; /** * Status @@ -69,64 +84,6 @@ export type SerializedTaskEvent = { createdAt: string; }; -/** - * TemplateMetadata - * - * @public - */ -export type TemplateMetadata = { - name: string; -}; - -/** - * TaskSpecV1beta2 - * - * @public - */ -export interface TaskSpecV1beta2 { - apiVersion: 'backstage.io/v1beta2'; - baseUrl?: string; - values: JsonObject; - steps: Array<{ - id: string; - name: string; - action: string; - input?: JsonObject; - if?: string | boolean; - }>; - output: { [name: string]: string }; - metadata?: TemplateMetadata; -} - -export interface TaskStep { - id: string; - name: string; - action: string; - input?: JsonObject; - if?: string | boolean; -} - -/** - * TaskSpecV1beta3 - * - * @public - */ -export interface TaskSpecV1beta3 { - apiVersion: 'scaffolder.backstage.io/v1beta3'; - baseUrl?: string; - parameters: JsonObject; - steps: TaskStep[]; - output: { [name: string]: JsonValue }; - metadata?: TemplateMetadata; -} - -/** - * TaskSpec - * - * @public - */ -export type TaskSpec = TaskSpecV1beta2 | TaskSpecV1beta3; - /** * TaskSecrets * diff --git a/plugins/scaffolder-common/api-report.md b/plugins/scaffolder-common/api-report.md index 8485e224b2..3bca8e74ed 100644 --- a/plugins/scaffolder-common/api-report.md +++ b/plugins/scaffolder-common/api-report.md @@ -6,6 +6,60 @@ import { Entity } from '@backstage/catalog-model'; import { JsonObject } from '@backstage/types'; import { JSONSchema } from '@backstage/catalog-model'; +import { JsonValue } from '@backstage/types'; + +// @public +export type TaskSpec = TaskSpecV1beta2 | TaskSpecV1beta3; + +// @public +export interface TaskSpecV1beta2 { + // (undocumented) + apiVersion: 'backstage.io/v1beta2'; + // (undocumented) + baseUrl?: string; + // (undocumented) + metadata?: TemplateMetadata; + // (undocumented) + output: { + [name: string]: string; + }; + // (undocumented) + steps: TaskStep[]; + // (undocumented) + values: JsonObject; +} + +// @public +export interface TaskSpecV1beta3 { + // (undocumented) + apiVersion: 'scaffolder.backstage.io/v1beta3'; + // (undocumented) + baseUrl?: string; + // (undocumented) + metadata?: TemplateMetadata; + // (undocumented) + output: { + [name: string]: JsonValue; + }; + // (undocumented) + parameters: JsonObject; + // (undocumented) + steps: TaskStep[]; +} + +// @public +export interface TaskStep { + // (undocumented) + action: string; + // (undocumented) + id: string; + // (undocumented) + if?: string | boolean; + // (undocumented) + input?: JsonObject; + // (undocumented) + name: string; +} // @public (undocumented) export interface TemplateEntityV1beta3 extends Entity { @@ -33,4 +87,9 @@ export interface TemplateEntityV1beta3 extends Entity { // @public (undocumented) export const templateEntityV1beta3Schema: JSONSchema; + +// @public +export type TemplateMetadata = { + name: string; +}; ``` diff --git a/plugins/scaffolder-common/src/TaskSpec.ts b/plugins/scaffolder-common/src/TaskSpec.ts new file mode 100644 index 0000000000..8d8ccedbad --- /dev/null +++ b/plugins/scaffolder-common/src/TaskSpec.ts @@ -0,0 +1,74 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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 { JsonValue, JsonObject } from '@backstage/types'; + +/** + * TemplateMetadata + * + * @public + */ +export type TemplateMetadata = { + name: string; +}; + +/** + * TaskStep + * + * @public + */ +export interface TaskStep { + id: string; + name: string; + action: string; + input?: JsonObject; + if?: string | boolean; +} + +/** + * TaskSpecV1beta2 + * + * @public + */ +export interface TaskSpecV1beta2 { + apiVersion: 'backstage.io/v1beta2'; + baseUrl?: string; + values: JsonObject; + steps: TaskStep[]; + output: { [name: string]: string }; + metadata?: TemplateMetadata; +} + +/** + * TaskSpecV1beta3 + * + * @public + */ +export interface TaskSpecV1beta3 { + apiVersion: 'scaffolder.backstage.io/v1beta3'; + baseUrl?: string; + parameters: JsonObject; + steps: TaskStep[]; + output: { [name: string]: JsonValue }; + metadata?: TemplateMetadata; +} + +/** + * TaskSpec + * + * @public + */ +export type TaskSpec = TaskSpecV1beta2 | TaskSpecV1beta3; diff --git a/plugins/scaffolder-common/src/index.ts b/plugins/scaffolder-common/src/index.ts index 3b6fa71d7a..1197f48fed 100644 --- a/plugins/scaffolder-common/src/index.ts +++ b/plugins/scaffolder-common/src/index.ts @@ -30,3 +30,5 @@ export const templateEntityV1beta3Schema: JSONSchema = v1beta3Schema as Omit< JSONSchema, 'examples' >; + +export * from './TaskSpec'; diff --git a/plugins/scaffolder/api-report.md b/plugins/scaffolder/api-report.md index 45419e82eb..4f6928e72d 100644 --- a/plugins/scaffolder/api-report.md +++ b/plugins/scaffolder/api-report.md @@ -21,12 +21,12 @@ import { IconButton } from '@material-ui/core'; import { IdentityApi } from '@backstage/core-plugin-api'; import { JsonObject } from '@backstage/types'; import { JSONSchema } from '@backstage/catalog-model'; -import { JsonValue } from '@backstage/types'; import { Observable } from '@backstage/types'; import { default as React_2 } from 'react'; import { ReactNode } from 'react'; import { RouteRef } from '@backstage/core-plugin-api'; import { ScmIntegrationRegistry } from '@backstage/integration'; +import { TaskSpec } from '@backstage/plugin-scaffolder-common'; import { TemplateEntityV1beta2 } from '@backstage/catalog-model'; // Warning: (ae-missing-release-tag) "createScaffolderFieldExtension" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index b464e86724..2c9112a77a 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -40,6 +40,7 @@ "@backstage/integration": "^0.6.10", "@backstage/integration-react": "^0.1.16", "@backstage/plugin-catalog-react": "^0.6.8", + "@backstage/plugin-scaffolder-common": "^0.1.1", "@backstage/theme": "^0.2.14", "@backstage/types": "^0.1.1", "@material-ui/core": "^4.12.2", diff --git a/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx b/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx index 58ec3dfc02..b27f116b96 100644 --- a/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx +++ b/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx @@ -22,8 +22,10 @@ import { Page, LogViewer, } from '@backstage/core-components'; +import { useRouteRef } from '@backstage/core-plugin-api'; import { BackstageTheme } from '@backstage/theme'; import { + Button, CircularProgress, Paper, StepButton, @@ -40,9 +42,11 @@ import Check from '@material-ui/icons/Check'; import FiberManualRecordIcon from '@material-ui/icons/FiberManualRecord'; import classNames from 'classnames'; import { DateTime, Interval } from 'luxon'; +import qs from 'qs'; import React, { memo, useEffect, useMemo, useState } from 'react'; -import { useParams } from 'react-router'; +import { generatePath, useNavigate, useParams } from 'react-router'; import { useInterval } from 'react-use'; +import { rootRouteRef } from '../../routes'; import { Status, TaskOutput } from '../../types'; import { useTaskEventStream } from '../hooks/useEventStream'; import { TaskPageLinks } from './TaskPageLinks'; @@ -56,8 +60,8 @@ const useStyles = makeStyles((theme: Theme) => width: '100%', }, button: { - marginTop: theme.spacing(1), - marginRight: theme.spacing(1), + marginBottom: theme.spacing(2), + marginLeft: theme.spacing(2), }, actionsContainer: { marginBottom: theme.spacing(2), @@ -215,6 +219,9 @@ const hasLinks = ({ entityRef, remoteUrl, links = [] }: TaskOutput): boolean => !!(entityRef || remoteUrl || links.length > 0); export const TaskPage = () => { + const classes = useStyles(); + const navigate = useNavigate(); + const rootLink = useRouteRef(rootRouteRef); const [userSelectedStepId, setUserSelectedStepId] = useState< string | undefined >(undefined); @@ -266,6 +273,26 @@ export const TaskPage = () => { const { output } = taskStream; + const handleStartOver = () => { + if (!taskStream.task || !taskStream.task?.spec.metadata?.name) { + navigate(generatePath(rootLink())); + } + + const formData = + taskStream.task!.spec.apiVersion === 'backstage.io/v1beta2' + ? taskStream.task!.spec.values + : taskStream.task!.spec.parameters; + + navigate( + generatePath( + `${rootLink()}/templates/:templateName?${qs.stringify({ formData })}`, + { + templateName: taskStream.task!.spec.metadata!.name, + }, + ), + ); + }; + return (
{ {output && hasLinks(output) && ( )} + diff --git a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx index 3f70f4effd..e4d2cea04e 100644 --- a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx +++ b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx @@ -16,6 +16,7 @@ import { JsonObject, JsonValue } from '@backstage/types'; import { LinearProgress } from '@material-ui/core'; import { FormValidation, IChangeEvent } from '@rjsf/core'; +import qs from 'qs'; import React, { useCallback, useState } from 'react'; import { generatePath, Navigate, useNavigate } from 'react-router'; import { useParams } from 'react-router-dom'; @@ -120,7 +121,12 @@ export const TemplatePage = ({ const navigate = useNavigate(); const rootLink = useRouteRef(rootRouteRef); const { schema, loading, error } = useTemplateParameterSchema(templateName); - const [formState, setFormState] = useState({}); + const query = qs.parse(window.location.search, { + ignoreQueryPrefix: true, + }); + const [formState, setFormState] = useState( + (query.formData ?? {}) as Record, + ); const handleFormReset = () => setFormState({}); const handleChange = useCallback( (e: IChangeEvent) => setFormState(e.formData), diff --git a/plugins/scaffolder/src/types.ts b/plugins/scaffolder/src/types.ts index 5597082f22..291f680710 100644 --- a/plugins/scaffolder/src/types.ts +++ b/plugins/scaffolder/src/types.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { JSONSchema } from '@backstage/catalog-model'; -import { JsonValue } from '@backstage/types'; +import { TaskSpec } from '@backstage/plugin-scaffolder-common'; export type Status = 'open' | 'processing' | 'failed' | 'completed' | 'skipped'; export type JobStatus = 'PENDING' | 'STARTED' | 'COMPLETED' | 'FAILED'; @@ -39,18 +39,9 @@ export type Stage = { endedAt?: string; }; -export type ScaffolderStep = { - id: string; - name: string; - action: string; - parameters?: { [name: string]: JsonValue }; -}; - export type ScaffolderTask = { id: string; - spec: { - steps: ScaffolderStep[]; - }; + spec: TaskSpec; status: 'failed' | 'completed' | 'processing' | 'open' | 'cancelled'; lastHeartbeatAt: string; createdAt: string; From 8e20d72cd4f857c63ec5a818b2f66b640f2639c3 Mon Sep 17 00:00:00 2001 From: Phil Kuang Date: Tue, 21 Dec 2021 14:50:55 -0500 Subject: [PATCH 26/57] feat(TemplatePage): serialize form data into url for back navigation Signed-off-by: Phil Kuang --- .../components/TemplatePage/TemplatePage.tsx | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx index e4d2cea04e..9d1e7a3e2d 100644 --- a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx +++ b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx @@ -121,12 +121,13 @@ export const TemplatePage = ({ const navigate = useNavigate(); const rootLink = useRouteRef(rootRouteRef); const { schema, loading, error } = useTemplateParameterSchema(templateName); - const query = qs.parse(window.location.search, { - ignoreQueryPrefix: true, + const [formState, setFormState] = useState>(() => { + const query = qs.parse(window.location.search, { + ignoreQueryPrefix: true, + }); + + return query.formData ?? {}; }); - const [formState, setFormState] = useState( - (query.formData ?? {}) as Record, - ); const handleFormReset = () => setFormState({}); const handleChange = useCallback( (e: IChangeEvent) => setFormState(e.formData), @@ -135,6 +136,18 @@ export const TemplatePage = ({ const handleCreate = async () => { const id = await scaffolderApi.scaffold(templateName, formState); + + const formParams = qs.stringify( + { formData: formState }, + { addQueryPrefix: true }, + ); + const newUrl = `${window.location.pathname}${formParams}`; + // We use direct history manipulation since useSearchParams and + // useNavigate in react-router-dom cause unnecessary extra rerenders. + // Also make sure to replace the state rather than pushing to avoid + // extra back/forward slots. + window.history?.replaceState(null, document.title, newUrl); + navigate(generatePath(`${rootLink()}/tasks/:taskId`, { taskId: id })); }; From 7e45b03f4d7c890f88e7f3b07fab682b2db986d6 Mon Sep 17 00:00:00 2001 From: Colton Padden Date: Thu, 23 Dec 2021 10:57:36 -0500 Subject: [PATCH 27/57] add msw intercept for graph.microsoft and test photo query response Signed-off-by: Colton Padden --- .../src/providers/microsoft/provider.test.ts | 132 +++++++++++++----- .../src/providers/microsoft/provider.ts | 1 - 2 files changed, 99 insertions(+), 34 deletions(-) diff --git a/plugins/auth-backend/src/providers/microsoft/provider.test.ts b/plugins/auth-backend/src/providers/microsoft/provider.test.ts index 429e6691b6..2734878077 100644 --- a/plugins/auth-backend/src/providers/microsoft/provider.test.ts +++ b/plugins/auth-backend/src/providers/microsoft/provider.test.ts @@ -20,6 +20,9 @@ import { OAuthResult } from '../../lib/oauth'; import { getVoidLogger } from '@backstage/backend-common'; import { TokenIssuer } from '../../identity/types'; import { CatalogIdentityClient } from '../../lib/catalog'; +import { setupRequestMockHandlers } from '@backstage/test-utils'; +import { rest } from 'msw'; +import { setupServer } from 'msw/node'; const mockFrameHandler = jest.spyOn( helpers, @@ -28,8 +31,62 @@ const mockFrameHandler = jest.spyOn( () => Promise<{ result: OAuthResult; privateInfo: any }> >; +const mockResult = { + result: { + fullProfile: { + emails: [ + { + type: 'work', + value: 'conrad@example.com', + }, + ], + displayName: 'Conrad', + name: { + familyName: 'Ribas', + givenName: 'Francisco', + }, + id: 'conrad', + provider: 'microsoft', + photos: [ + { + value: 'some-data', + }, + ], + }, + params: { + id_token: 'idToken', + scope: 'scope', + expires_in: 123, + }, + accessToken: 'accessToken', + }, + privateInfo: { + refreshToken: 'wacka', + }, +}; + +const server = setupServer(); +setupRequestMockHandlers(server); + +const setupHandlers = () => { + server.use( + rest.get( + 'https://graph.microsoft.com/v1.0/me/photos/*', + async (_, res, ctx) => { + const imageBuffer = new Uint8Array([104, 111, 119, 100, 121]).buffer; + return res( + ctx.set('Content-Length', imageBuffer.byteLength.toString()), + ctx.set('Content-Type', 'image/jpeg'), + ctx.body(imageBuffer), + ); + }, + ), + ); +}; + describe('createMicrosoftProvider', () => { it('should auth', async () => { + setupHandlers(); const tokenIssuer = { issueToken: jest.fn(), listPublicKeys: jest.fn(), @@ -55,39 +112,7 @@ describe('createMicrosoftProvider', () => { callbackUrl: 'mock', }); - mockFrameHandler.mockResolvedValueOnce({ - result: { - fullProfile: { - emails: [ - { - type: 'work', - value: 'conrad@example.com', - }, - ], - displayName: 'Conrad', - name: { - familyName: 'Ribas', - givenName: 'Francisco', - }, - id: 'conrad', - provider: 'microsoft', - photos: [ - { - value: 'some-data', - }, - ], - }, - params: { - id_token: 'idToken', - scope: 'scope', - expires_in: 123, - }, - accessToken: 'accessToken', - }, - privateInfo: { - refreshToken: 'wacka', - }, - }); + mockFrameHandler.mockResolvedValueOnce(mockResult); const { response } = await provider.handler({} as any); expect(response).toEqual({ providerInfo: { @@ -103,4 +128,45 @@ describe('createMicrosoftProvider', () => { }, }); }); + + it('should return the base64 encoded photo data of the profile', async () => { + setupHandlers(); + const tokenIssuer = { + issueToken: jest.fn(), + listPublicKeys: jest.fn(), + }; + const catalogIdentityClient = { + findUser: jest.fn(), + }; + + const provider = new MicrosoftAuthProvider({ + logger: getVoidLogger(), + catalogIdentityClient: + catalogIdentityClient as unknown as CatalogIdentityClient, + tokenIssuer: tokenIssuer as unknown as TokenIssuer, + authHandler: async ({ fullProfile }) => ({ + profile: { + email: fullProfile.emails![0]!.value, + displayName: fullProfile.displayName, + picture: 'http://microsoft.com/lols', + }, + }), + clientId: 'mock', + clientSecret: 'mock', + callbackUrl: 'mock', + // define resolver to return user `info` for photo validation + signInResolver: async (info, _) => { + return { + id: 'user.name', + token: 'token', + info: info, + }; + }, + }); + mockFrameHandler.mockResolvedValueOnce(mockResult); + const { response } = await provider.handler({} as any); + const overloadedIdentity = response.backstageIdentity as any; + const photo = overloadedIdentity.info.result.fullProfile.photos[0]; + expect(photo.value).toEqual('data:image/jpeg;base64,aG93ZHk='); + }); }); diff --git a/plugins/auth-backend/src/providers/microsoft/provider.ts b/plugins/auth-backend/src/providers/microsoft/provider.ts index 66bb32cfdd..b21218c066 100644 --- a/plugins/auth-backend/src/providers/microsoft/provider.ts +++ b/plugins/auth-backend/src/providers/microsoft/provider.ts @@ -180,7 +180,6 @@ export class MicrosoftAuthProvider implements OAuthHandlers { }) .then(response => response.arrayBuffer()) .then(arrayBuffer => { - console.log(Buffer.from(arrayBuffer).toString('utf-8')); const imageUrl = `data:image/jpeg;base64,${Buffer.from( arrayBuffer, ).toString('base64')}`; From d83079fc11f11fae4817615ba271d144ec4082dc Mon Sep 17 00:00:00 2001 From: goenning Date: Thu, 23 Dec 2021 16:04:38 +0000 Subject: [PATCH 28/57] expose techInsightsApiRef Signed-off-by: goenning --- .changeset/slimy-socks-pump.md | 5 +++++ plugins/tech-insights/api-report.md | 10 ++++++++++ plugins/tech-insights/src/index.ts | 2 ++ 3 files changed, 17 insertions(+) create mode 100644 .changeset/slimy-socks-pump.md diff --git a/.changeset/slimy-socks-pump.md b/.changeset/slimy-socks-pump.md new file mode 100644 index 0000000000..7fae7da78a --- /dev/null +++ b/.changeset/slimy-socks-pump.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-tech-insights': patch +--- + +expose apiRef diff --git a/plugins/tech-insights/api-report.md b/plugins/tech-insights/api-report.md index e7f30a2b30..279545091c 100644 --- a/plugins/tech-insights/api-report.md +++ b/plugins/tech-insights/api-report.md @@ -5,12 +5,22 @@ ```ts /// +import { ApiRef } from '@backstage/core-plugin-api'; import { BackstagePlugin } from '@backstage/core-plugin-api'; +import { CheckResult } from '@backstage/plugin-tech-insights-common'; +import { EntityName } from '@backstage/catalog-model'; +import { default as React_2 } from 'react'; import { RouteRef } from '@backstage/core-plugin-api'; // @public (undocumented) export const EntityTechInsightsScorecardContent: () => JSX.Element; +// Warning: (ae-forgotten-export) The symbol "TechInsightsApi" needs to be exported by the entry point index.d.ts +// Warning: (ae-missing-release-tag) "techInsightsApiRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const techInsightsApiRef: ApiRef; + // @public (undocumented) export const techInsightsPlugin: BackstagePlugin< { diff --git a/plugins/tech-insights/src/index.ts b/plugins/tech-insights/src/index.ts index 273c11fd71..a6a4bee3e3 100644 --- a/plugins/tech-insights/src/index.ts +++ b/plugins/tech-insights/src/index.ts @@ -17,3 +17,5 @@ export { techInsightsPlugin, EntityTechInsightsScorecardContent, } from './plugin'; + +export { techInsightsApiRef } from './api/TechInsightsApi'; From 94cdf5d1bdd32167d392c4b70cf07a00e1ef5934 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Thu, 23 Dec 2021 17:17:56 +0100 Subject: [PATCH 29/57] Memory cache client should share memory. Signed-off-by: Eric Peterson --- .changeset/dar-jag-gar.md | 5 +++++ .../src/cache/CacheManager.test.ts | 16 ++++++++++++++++ .../backend-common/src/cache/CacheManager.ts | 8 ++++++++ 3 files changed, 29 insertions(+) create mode 100644 .changeset/dar-jag-gar.md diff --git a/.changeset/dar-jag-gar.md b/.changeset/dar-jag-gar.md new file mode 100644 index 0000000000..5989614101 --- /dev/null +++ b/.changeset/dar-jag-gar.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-common': patch +--- + +In-memory cache clients instantiated from the same cache manager now share the same memory space. diff --git a/packages/backend-common/src/cache/CacheManager.test.ts b/packages/backend-common/src/cache/CacheManager.test.ts index fb73687f54..ce5b6f5999 100644 --- a/packages/backend-common/src/cache/CacheManager.test.ts +++ b/packages/backend-common/src/cache/CacheManager.test.ts @@ -147,6 +147,22 @@ describe('CacheManager', () => { }); }); + it('shares memory across multiple instances of the memory client', () => { + const manager = CacheManager.fromConfig(defaultConfig()); + const plugin = 'test-plugin'; + + // Instantiate two in-memory clients. + manager.forPlugin(plugin).getClient({ defaultTtl: 10 }); + manager.forPlugin(plugin).getClient({ defaultTtl: 10 }); + + const cache = Keyv as unknown as jest.Mock; + const mockCall2 = cache.mock.calls.splice(-1)[0][0]; + const mockCall1 = cache.mock.calls.splice(-1)[0][0]; + + // Note: .toBe() checks referential identity of object instances. + expect(mockCall1.store).toBe(mockCall2.store); + }); + it('returns a memcache client when configured', () => { const expectedHost = '127.0.0.1:11211'; const manager = CacheManager.fromConfig( diff --git a/packages/backend-common/src/cache/CacheManager.ts b/packages/backend-common/src/cache/CacheManager.ts index d3a1504187..896c08fb94 100644 --- a/packages/backend-common/src/cache/CacheManager.ts +++ b/packages/backend-common/src/cache/CacheManager.ts @@ -42,6 +42,13 @@ export class CacheManager { none: this.getNoneClient, }; + /** + * Shared memory store for the in-memory cache client. Sharing the same Map + * instance ensures get/set/delete operations hit the same store, regardless + * of where/when a client is instantiated. + */ + private readonly memoryStore = new Map(); + private readonly logger: Logger; private readonly store: keyof CacheManager['storeFactories']; private readonly connection: string; @@ -133,6 +140,7 @@ export class CacheManager { return new Keyv({ namespace: pluginId, ttl: defaultTtl, + store: this.memoryStore, }); } From c75c918048743cdbed58fde99f8647d9b845acec Mon Sep 17 00:00:00 2001 From: goenning Date: Thu, 23 Dec 2021 16:24:29 +0000 Subject: [PATCH 30/57] add @types/react dependency Signed-off-by: goenning --- plugins/tech-insights/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/tech-insights/package.json b/plugins/tech-insights/package.json index 9466433b8e..b238ddaafd 100644 --- a/plugins/tech-insights/package.json +++ b/plugins/tech-insights/package.json @@ -35,6 +35,7 @@ "react-use": "^17.2.4" }, "peerDependencies": { + "@types/react": "^16.13.1 || ^17.0.0", "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { From 8f3f8dbe7607045ab50e0431331867e95fddcd2c Mon Sep 17 00:00:00 2001 From: mufaddal motiwala Date: Fri, 24 Dec 2021 18:57:25 +0530 Subject: [PATCH 31/57] Add reviewer for plugins/newrelic-dashboard Signed-off-by: mufaddal motiwala --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 9cc5a2378e..a587419d50 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -43,3 +43,4 @@ /.changeset/techdocs-* @backstage/techdocs-core /cypress/src/integration/plugins/techdocs.spec.ts @backstage/techdocs-core /plugins/apache-airflow @backstage/reviewers @cmpadden +/plugins/newrelic-dashboard @backstage/reviewers @mufaddal7 From 2c9e29a352c55510ec457c89a04f7f298a8881f4 Mon Sep 17 00:00:00 2001 From: djamaile Date: Mon, 27 Dec 2021 00:24:05 +0100 Subject: [PATCH 32/57] test: add fire event click to tests Signed-off-by: djamaile --- .../components/CatalogPage/CatalogPage.test.tsx | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx index 0a46580c12..bd216a26ad 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx @@ -181,7 +181,7 @@ describe('CatalogPage', () => { { title: 'Bar', field: 'entity.bar' }, { title: 'Baz', field: 'entity.spec.lifecycle' }, ]; - const { getAllByRole } = await renderWrapped( + const { getByTestId, getAllByRole } = await renderWrapped( , ); @@ -189,12 +189,14 @@ describe('CatalogPage', () => { c => c.tagName === 'SPAN', ); const columnHeaderLabels = columnHeader.map(c => c.textContent); - expect(columnHeaderLabels).toEqual(['Foo', 'Bar', 'Baz', 'Actions']); }, 20_000); it('should render the default actions of an item in the grid', async () => { - const { findByTitle, findByText } = await renderWrapped(); + const { getByTestId, findByTitle, findByText } = await renderWrapped( + , + ); + fireEvent.click(getByTestId('user-picker-owned')); expect(await findByText(/Owned \(1\)/)).toBeInTheDocument(); expect(await findByTitle(/View/)).toBeInTheDocument(); expect(await findByTitle(/Edit/)).toBeInTheDocument(); @@ -221,9 +223,11 @@ describe('CatalogPage', () => { }, ]; - const { findByTitle, findByText } = await renderWrapped( + const { getByTestId, findByTitle, findByText } = await renderWrapped( , ); + fireEvent.click(getByTestId('user-picker-owned')); + expect(await findByText(/Owned \(1\)/)).toBeInTheDocument(); expect(await findByText(/Owned \(1\)/)).toBeInTheDocument(); expect(await findByTitle(/Foo Action/)).toBeInTheDocument(); expect(await findByTitle(/Bar Action/)).toBeInTheDocument(); @@ -235,6 +239,7 @@ describe('CatalogPage', () => { // https://github.com/mbrn/material-table/issues/1293 it('should render', async () => { const { findByText, getByTestId } = await renderWrapped(); + fireEvent.click(getByTestId('user-picker-owned')); await expect(findByText(/Owned \(1\)/)).resolves.toBeInTheDocument(); fireEvent.click(getByTestId('user-picker-all')); await expect(findByText(/All \(2\)/)).resolves.toBeInTheDocument(); @@ -250,7 +255,8 @@ describe('CatalogPage', () => { // this test is for fixing the bug after favoriting an entity, the matching // entities defaulting to "owned" filter and not based on the selected filter it('should render the correct entities filtered on the selected filter', async () => { - await renderWrapped(); + const { getByTestId } = await renderWrapped(); + fireEvent.click(getByTestId('user-picker-owned')); await expect(screen.findByText(/Owned \(1\)/)).resolves.toBeInTheDocument(); fireEvent.click(screen.getByTestId('user-picker-starred')); await expect( From ffe4a9a8a8e6de6f8317dac53a43234d38421000 Mon Sep 17 00:00:00 2001 From: djamaile Date: Mon, 27 Dec 2021 00:34:31 +0100 Subject: [PATCH 33/57] fix: remove unused var Signed-off-by: djamaile --- plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx index bd216a26ad..4dcab6f4f7 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx @@ -181,7 +181,7 @@ describe('CatalogPage', () => { { title: 'Bar', field: 'entity.bar' }, { title: 'Baz', field: 'entity.spec.lifecycle' }, ]; - const { getByTestId, getAllByRole } = await renderWrapped( + const { getAllByRole } = await renderWrapped( , ); From caf278c9f93041382682f77d82b9d95741278e5c Mon Sep 17 00:00:00 2001 From: djamaile Date: Mon, 27 Dec 2021 01:59:04 +0100 Subject: [PATCH 34/57] fix: remove double line Signed-off-by: djamaile --- plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx index 4dcab6f4f7..effd9f5bc6 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx @@ -228,7 +228,6 @@ describe('CatalogPage', () => { ); fireEvent.click(getByTestId('user-picker-owned')); expect(await findByText(/Owned \(1\)/)).toBeInTheDocument(); - expect(await findByText(/Owned \(1\)/)).toBeInTheDocument(); expect(await findByTitle(/Foo Action/)).toBeInTheDocument(); expect(await findByTitle(/Bar Action/)).toBeInTheDocument(); expect((await findByTitle(/Bar Action/)).firstChild).toBeDisabled(); From 8ae291133972379514e1da4da5188ceb7d489817 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Dec 2021 04:12:52 +0000 Subject: [PATCH 35/57] build(deps): bump mysql2 from 2.2.5 to 2.3.3 Bumps [mysql2](https://github.com/sidorares/node-mysql2) from 2.2.5 to 2.3.3. - [Release notes](https://github.com/sidorares/node-mysql2/releases) - [Changelog](https://github.com/sidorares/node-mysql2/blob/master/Changelog.md) - [Commits](https://github.com/sidorares/node-mysql2/compare/v2.2.5...v2.3.3) --- updated-dependencies: - dependency-name: mysql2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- yarn.lock | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/yarn.lock b/yarn.lock index 5ed5806f6f..a9305eab2d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13232,10 +13232,10 @@ delegates@^1.0.0: resolved "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= -denque@^1.4.1: - version "1.5.0" - resolved "https://registry.npmjs.org/denque/-/denque-1.5.0.tgz#773de0686ff2d8ec2ff92914316a47b73b1c73de" - integrity sha512-CYiCSgIF1p6EUByQPlGkKnP1M9g0ZV3qMIrqMqZqdwazygIA/YP2vrbcyl1h/WppKJTdl1F85cXIle+394iDAQ== +denque@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/denque/-/denque-2.0.1.tgz#bcef4c1b80dc32efe97515744f21a4229ab8934a" + integrity sha512-tfiWc6BQLXNLpNiR5iGd0Ocu3P3VpxfzFiqubLgMfhfOw9WyvgJBd46CClNn9k3qfbjvT//0cf7AlYRX/OslMQ== depd@^1.1.2, depd@~1.1.2: version "1.1.2" @@ -17003,10 +17003,10 @@ iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4: dependencies: safer-buffer ">= 2.1.2 < 3" -iconv-lite@^0.6.2: - version "0.6.2" - resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz#ce13d1875b0c3a674bd6a04b7f76b01b1b6ded01" - integrity sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ== +iconv-lite@^0.6.2, iconv-lite@^0.6.3: + version "0.6.3" + resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== dependencies: safer-buffer ">= 2.1.2 < 3.0.0" @@ -21458,13 +21458,13 @@ mv@~2: rimraf "~2.4.0" mysql2@^2.2.5: - version "2.2.5" - resolved "https://registry.npmjs.org/mysql2/-/mysql2-2.2.5.tgz#72624ffb4816f80f96b9c97fedd8c00935f9f340" - integrity sha512-XRqPNxcZTpmFdXbJqb+/CtYVLCx14x1RTeNMD4954L331APu75IC74GDqnZMEt1kwaXy6TySo55rF2F3YJS78g== + version "2.3.3" + resolved "https://registry.npmjs.org/mysql2/-/mysql2-2.3.3.tgz#944f3deca4b16629052ff8614fbf89d5552545a0" + integrity sha512-wxJUev6LgMSgACDkb/InIFxDprRa6T95+VEoR+xPvtngtccNH2dGjEB/fVZ8yg1gWv1510c9CvXuJHi5zUm0ZA== dependencies: - denque "^1.4.1" + denque "^2.0.1" generate-function "^2.3.1" - iconv-lite "^0.6.2" + iconv-lite "^0.6.3" long "^4.0.0" lru-cache "^6.0.0" named-placeholders "^1.1.2" From e3b49153b76932a08b2a33b7149921efec4ee81b Mon Sep 17 00:00:00 2001 From: goenning Date: Mon, 27 Dec 2021 12:08:42 +0000 Subject: [PATCH 36/57] add example of custom operator Signed-off-by: goenning --- .changeset/spicy-moons-poke.md | 2 +- .../README.md | 29 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/.changeset/spicy-moons-poke.md b/.changeset/spicy-moons-poke.md index fdf130c491..cc39d629bf 100644 --- a/.changeset/spicy-moons-poke.md +++ b/.changeset/spicy-moons-poke.md @@ -1,5 +1,5 @@ --- -'@backstage/plugin-tech-insights-backend-module-jsonfc': minor +'@backstage/plugin-tech-insights-backend-module-jsonfc': patch --- ability to add custom operators diff --git a/plugins/tech-insights-backend-module-jsonfc/README.md b/plugins/tech-insights-backend-module-jsonfc/README.md index 108b1c8564..6703f4b9f9 100644 --- a/plugins/tech-insights-backend-module-jsonfc/README.md +++ b/plugins/tech-insights-backend-module-jsonfc/README.md @@ -85,3 +85,32 @@ export const exampleCheck: TechInsightJsonRuleCheck = { }, }; ``` + +# Custom operators + +json-rules-engine supports a limited [number of built-in operators](https://github.com/CacheControl/json-rules-engine/blob/master/docs/rules.md#operators) that can be used in conditions. You can add your own operators by adding them to the `operators` array in the `JsonRulesEngineFactCheckerFactory` constructor. For example: + +```diff +const myFactCheckerFactory = new JsonRulesEngineFactCheckerFactory({ + checks: [], + logger, ++ operators: [ new Operator("startsWith", (a, b) => a.startsWith(b) ] +}) +``` + +And you can then use it in your checks like this: + +```js +... +rule: { + conditions: { + any: [ + { + fact: 'version', + operator: 'startsWith', + value: '12', + }, + ], + }, +} +``` From d088a484db72c18d22e4a68a4126a23d98cdbd36 Mon Sep 17 00:00:00 2001 From: goenning Date: Mon, 27 Dec 2021 12:10:12 +0000 Subject: [PATCH 37/57] add import to example Signed-off-by: goenning --- plugins/tech-insights-backend-module-jsonfc/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/tech-insights-backend-module-jsonfc/README.md b/plugins/tech-insights-backend-module-jsonfc/README.md index 6703f4b9f9..aab57fae91 100644 --- a/plugins/tech-insights-backend-module-jsonfc/README.md +++ b/plugins/tech-insights-backend-module-jsonfc/README.md @@ -91,6 +91,8 @@ export const exampleCheck: TechInsightJsonRuleCheck = { json-rules-engine supports a limited [number of built-in operators](https://github.com/CacheControl/json-rules-engine/blob/master/docs/rules.md#operators) that can be used in conditions. You can add your own operators by adding them to the `operators` array in the `JsonRulesEngineFactCheckerFactory` constructor. For example: ```diff ++ import { Operator } from 'json-rules-engine'; + const myFactCheckerFactory = new JsonRulesEngineFactCheckerFactory({ checks: [], logger, From c2c87687718822f9b588bc80ade4b2a15b978a88 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 27 Dec 2021 14:29:06 +0100 Subject: [PATCH 38/57] Create cyan-goats-confess.md Signed-off-by: Patrik Oldsberg --- .changeset/cyan-goats-confess.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/cyan-goats-confess.md diff --git a/.changeset/cyan-goats-confess.md b/.changeset/cyan-goats-confess.md new file mode 100644 index 0000000000..0ba500dc7f --- /dev/null +++ b/.changeset/cyan-goats-confess.md @@ -0,0 +1,5 @@ +--- +'@backstage/techdocs-common': patch +--- + +Bump `@azure/identity` from `^1.5.0` to `^2.0.1`. From 7858c2abdc5a2d984d4ca9d17b4193c3808efe27 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 27 Dec 2021 16:03:29 +0100 Subject: [PATCH 39/57] cost-insights: avoid re-export of all of test-utils Signed-off-by: Patrik Oldsberg --- .changeset/curly-bugs-stare.md | 5 ++ .../ProductInsights/ProductInsights.test.tsx | 9 ++-- .../ProductInsightsCard.test.tsx | 9 ++-- .../cost-insights/src/testUtils/providers.tsx | 52 ------------------- 4 files changed, 13 insertions(+), 62 deletions(-) create mode 100644 .changeset/curly-bugs-stare.md diff --git a/.changeset/curly-bugs-stare.md b/.changeset/curly-bugs-stare.md new file mode 100644 index 0000000000..88e1d86e84 --- /dev/null +++ b/.changeset/curly-bugs-stare.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-cost-insights': patch +--- + +Fixed an accidental re-export of `@backstage/test-utils` that broke this plugin in the most recent release. diff --git a/plugins/cost-insights/src/components/ProductInsights/ProductInsights.test.tsx b/plugins/cost-insights/src/components/ProductInsights/ProductInsights.test.tsx index 7253febd71..128c9d50b9 100644 --- a/plugins/cost-insights/src/components/ProductInsights/ProductInsights.test.tsx +++ b/plugins/cost-insights/src/components/ProductInsights/ProductInsights.test.tsx @@ -15,13 +15,12 @@ */ import React from 'react'; -import { renderInTestApp } from '@backstage/test-utils'; +import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; import { ProductInsights } from './ProductInsights'; -import { ProductInsightsOptions } from '../../api'; +import { costInsightsApiRef, ProductInsightsOptions } from '../../api'; import { mockDefaultLoadingState, MockConfigProvider, - MockCostInsightsApiProvider, MockCurrencyProvider, MockFilterProvider, MockBillingDateProvider, @@ -139,7 +138,7 @@ const costInsightsApi = { function renderInContext(children: JSX.Element) { return renderInTestApp( - + @@ -151,7 +150,7 @@ function renderInContext(children: JSX.Element) { - , + , ); } diff --git a/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.test.tsx b/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.test.tsx index 6cc1817e1c..9fc816bbbf 100644 --- a/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.test.tsx +++ b/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.test.tsx @@ -15,15 +15,14 @@ */ import React from 'react'; -import { renderInTestApp } from '@backstage/test-utils'; +import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; import { ProductInsightsCard } from './ProductInsightsCard'; -import { CostInsightsApi } from '../../api'; +import { CostInsightsApi, costInsightsApiRef } from '../../api'; import { createMockEntity, mockDefaultLoadingState, MockComputeEngine, MockConfigProvider, - MockCostInsightsApiProvider, MockCurrencyProvider, MockBillingDateProvider, MockScrollProvider, @@ -55,7 +54,7 @@ const renderProductInsightsCardInTestApp = async ( onSelectAsync = jest.fn(() => Promise.resolve(mockProductCost)), ) => await renderInTestApp( - + @@ -71,7 +70,7 @@ const renderProductInsightsCardInTestApp = async ( - , + , ); describe('', () => { diff --git a/plugins/cost-insights/src/testUtils/providers.tsx b/plugins/cost-insights/src/testUtils/providers.tsx index fb18c1a68c..efcb15b5a3 100644 --- a/plugins/cost-insights/src/testUtils/providers.tsx +++ b/plugins/cost-insights/src/testUtils/providers.tsx @@ -15,7 +15,6 @@ */ import React, { PropsWithChildren } from 'react'; -import { costInsightsApiRef, CostInsightsApi } from '../api'; import { LoadingContext, LoadingContextProps } from '../hooks/useLoading'; import { GroupsContext, GroupsContextProps } from '../hooks/useGroups'; import { FilterContext, FilterContextProps } from '../hooks/useFilters'; @@ -28,12 +27,6 @@ import { import { ScrollContext, ScrollContextProps } from '../hooks/useScroll'; import { Group, Duration } from '../types'; -// TODO(Rugvip): Could be good to have a clear place to put test utils that is linted accordingly -// eslint-disable-next-line import/no-extraneous-dependencies -import { IdentityApi, identityApiRef } from '@backstage/core-plugin-api'; -// eslint-disable-next-line import/no-extraneous-dependencies -import { TestApiProvider } from '@backstage/test-utils'; - type PartialPropsWithChildren = PropsWithChildren>; export const MockGroups: Group[] = [{ id: 'tech' }, { id: 'mock-group' }]; @@ -172,48 +165,3 @@ export const MockGroupsProvider = ({ ); }; - -export type MockCostInsightsApiProviderProps = PartialPropsWithChildren<{ - identityApi: Partial; - costInsightsApi: Partial; -}>; - -export const MockCostInsightsApiProvider = ({ - children, - ...context -}: MockCostInsightsApiProviderProps) => { - const defaultIdentityApi: IdentityApi = { - getProfile: jest.fn(), - getIdToken: jest.fn(), - getUserId: jest.fn(), - signOut: jest.fn(), - getProfileInfo: jest.fn(), - getBackstageIdentity: jest.fn(), - getCredentials: jest.fn(), - }; - - const defaultCostInsightsApi: CostInsightsApi = { - getAlerts: jest.fn(), - getDailyMetricData: jest.fn(), - getGroupDailyCost: jest.fn(), - getGroupProjects: jest.fn(), - getLastCompleteBillingDate: jest.fn(), - getProductInsights: jest.fn(), - getProjectDailyCost: jest.fn(), - getUserGroups: jest.fn(), - }; - - return ( - - {children} - - ); -}; From 6e4080d31bd64ff9b68897a88dfa00200e2045bf Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Mon, 27 Dec 2021 16:25:20 +0100 Subject: [PATCH 40/57] Add minify option to build command Signed-off-by: Vincenzo Scamporlino --- .changeset/tame-ways-prove.md | 5 +++++ docs/local-dev/cli-commands.md | 3 ++- packages/cli/src/commands/build.ts | 2 +- packages/cli/src/commands/index.ts | 1 + 4 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 .changeset/tame-ways-prove.md diff --git a/.changeset/tame-ways-prove.md b/.changeset/tame-ways-prove.md new file mode 100644 index 0000000000..4f625d0a05 --- /dev/null +++ b/.changeset/tame-ways-prove.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Add option to build command for minifying the generated code diff --git a/docs/local-dev/cli-commands.md b/docs/local-dev/cli-commands.md index 318b37f34e..412e3c4598 100644 --- a/docs/local-dev/cli-commands.md +++ b/docs/local-dev/cli-commands.md @@ -434,7 +434,8 @@ Usage: backstage-cli build [options] Options: --outputs <formats> List of formats to output [types,cjs,esm] - -h, --help display help for command + --minify Minify the generated code + -h, --help display help for command ``` ## lint diff --git a/packages/cli/src/commands/build.ts b/packages/cli/src/commands/build.ts index dec0b1a293..d312811396 100644 --- a/packages/cli/src/commands/build.ts +++ b/packages/cli/src/commands/build.ts @@ -33,5 +33,5 @@ export default async (cmd: Command) => { outputs = new Set([Output.types, Output.esm, Output.cjs]); } - await buildPackage({ outputs }); + await buildPackage({ outputs, minify: cmd.minify }); }; diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts index 94a8104ff1..bef5dfc285 100644 --- a/packages/cli/src/commands/index.ts +++ b/packages/cli/src/commands/index.ts @@ -138,6 +138,7 @@ export function registerCommands(program: CommanderStatic) { .command('build') .description('Build a package for publishing') .option('--outputs ', 'List of formats to output [types,cjs,esm]') + .option('--minify', 'Minify the generated code') .action(lazy(() => import('./build').then(m => m.default))); program From 84663d59a308d8b929a527d2d6797f56bf3e1bff Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 27 Dec 2021 19:21:20 +0100 Subject: [PATCH 41/57] Create small-points-allow.md Signed-off-by: Patrik Oldsberg --- .changeset/small-points-allow.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/small-points-allow.md diff --git a/.changeset/small-points-allow.md b/.changeset/small-points-allow.md new file mode 100644 index 0000000000..2228fc8883 --- /dev/null +++ b/.changeset/small-points-allow.md @@ -0,0 +1,5 @@ +--- +'@backstage/config-loader': patch +--- + +Bump `typescript-json-schema` from `^0.51.0` to `^0.52.0`. From 089445ae0f6bf278a24d13b4ca23b67fcf058234 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Dec 2021 04:11:22 +0000 Subject: [PATCH 42/57] build(deps-dev): bump @types/mock-fs from 4.13.0 to 4.13.1 Bumps [@types/mock-fs](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/mock-fs) from 4.13.0 to 4.13.1. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/mock-fs) --- updated-dependencies: - dependency-name: "@types/mock-fs" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- yarn.lock | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 4c9a81dd72..6912aa1648 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2542,6 +2542,24 @@ react-router "6.0.0-beta.0" react-use "^17.2.4" +"@backstage/test-utils@^0.1.24": + version "0.1.24" + resolved "https://registry.npmjs.org/@backstage/test-utils/-/test-utils-0.1.24.tgz#1af2ea1fe3daa7df5545cc6d41271d59c2076f48" + integrity sha512-zfqhY5AS8tNOFoP8Kmpb3zE1vCbbHCXwsvxtTEq7UsRJiGpzTMN7pn+GQpddMgMHJc+IB0y25Xflw3grNEKTbA== + dependencies: + "@backstage/core-app-api" "^0.2.0" + "@backstage/core-plugin-api" "^0.3.0" + "@backstage/theme" "^0.2.14" + "@backstage/types" "^0.1.1" + "@material-ui/core" "^4.12.2" + "@material-ui/icons" "^4.11.2" + "@testing-library/jest-dom" "^5.10.1" + "@testing-library/react" "^11.2.5" + "@testing-library/user-event" "^13.1.8" + react-router "6.0.0-beta.0" + react-router-dom "6.0.0-beta.0" + zen-observable "^0.8.15" + "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" @@ -7853,9 +7871,9 @@ "@types/node" "*" "@types/mock-fs@^4.10.0", "@types/mock-fs@^4.13.0": - version "4.13.0" - resolved "https://registry.npmjs.org/@types/mock-fs/-/mock-fs-4.13.0.tgz#b8b01cd2db588668b2532ecd21b1babd3fffb2c0" - integrity sha512-FUqxhURwqFtFBCuUj3uQMp7rPSQs//b3O9XecAVxhqS9y4/W8SIJEZFq2mmpnFVZBXwR/2OyPLE97CpyYiB8Mw== + version "4.13.1" + resolved "https://registry.npmjs.org/@types/mock-fs/-/mock-fs-4.13.1.tgz#9201554ceb23671badbfa8ac3f1fa9e0706305be" + integrity sha512-m6nFAJ3lBSnqbvDZioawRvpLXSaPyn52Srf7OfzjubYbYX8MTUdIgDxQl0wEapm4m/pNYSd9TXocpQ0TvZFlYA== dependencies: "@types/node" "*" From d6aec1c5060324fee36e013f8d36036ac5d62c1f Mon Sep 17 00:00:00 2001 From: Greg Taylor Date: Mon, 27 Dec 2021 23:59:11 -0800 Subject: [PATCH 43/57] Fix techdocs links to the CLI (#8650) The techdocs docs were written prior to the techdocs-cli being crunched into the monorepo. This commit fixes a few links that still point to the old separte spotify/techdocs-cli repo. Signed-off-by: Greg Taylor --- docs/features/techdocs/configuring-ci-cd.md | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/docs/features/techdocs/configuring-ci-cd.md b/docs/features/techdocs/configuring-ci-cd.md index 0dec7ec24e..96d841eadf 100644 --- a/docs/features/techdocs/configuring-ci-cd.md +++ b/docs/features/techdocs/configuring-ci-cd.md @@ -10,7 +10,7 @@ TechDocs reads the static generated documentation files from a cloud storage bucket (GCS, AWS S3, etc.). The documentation site is generated on the CI/CD workflow associated with the repository containing the documentation files. This document explains the steps needed to generate docs on CI and publish to a cloud -storage using [`techdocs-cli`](https://github.com/backstage/techdocs-cli). +storage using [`techdocs-cli`](./cli.md). The steps here target all kinds of CI providers (GitHub Actions, CircleCI, Jenkins, etc.). Specific tools for individual providers will also be made @@ -40,9 +40,8 @@ techdocs-cli publish --publisher-type awsS3 --storage-name -- That's it! -Take a look at -[`techdocs-cli` README](https://github.com/backstage/techdocs-cli) for the -complete command reference, details, and options. +Take a look at [`techdocs-cli`](./cli.md) for the complete command reference, +details, and options. ## Steps @@ -74,7 +73,7 @@ Install [`npx`](https://www.npmjs.com/package/npx) to use it for running `techdocs-cli`. Or you can install using `npm install -g @techdocs/cli`. We are going to use the -[`techdocs-cli generate`](https://github.com/backstage/techdocs-cli#generate-techdocs-site-from-a-documentation-project) +[`techdocs-cli generate`](./cli.md#generate-techdocs-site-from-a-documentation-project) command in this step. ```sh @@ -93,8 +92,7 @@ necessary authentication environment variables. - [AWS authentication](https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/loading-node-credentials-environment.html) And then run the -[`techdocs-cli publish`](https://github.com/backstage/techdocs-cli#publish-generated-techdocs-sites) -command. +[`techdocs-cli publish`](./cli.md#publish-generated-techdocs-sites) command. ```sh npx @techdocs/cli publish --publisher-type --storage-name --entity --directory ./site From 152bd9ba2b0b5f3c537c9c771ce903a7d62550b6 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 28 Dec 2021 10:55:47 +0100 Subject: [PATCH 44/57] rollback-backend: bump and move test-utils to devDep Signed-off-by: Patrik Oldsberg --- .changeset/angry-eels-watch.md | 5 +++++ plugins/rollbar-backend/package.json | 2 +- yarn.lock | 18 ------------------ 3 files changed, 6 insertions(+), 19 deletions(-) create mode 100644 .changeset/angry-eels-watch.md diff --git a/.changeset/angry-eels-watch.md b/.changeset/angry-eels-watch.md new file mode 100644 index 0000000000..cd252d5558 --- /dev/null +++ b/.changeset/angry-eels-watch.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-rollbar-backend': patch +--- + +Moved `@backstage/test-utils` to `devDependencies`. diff --git a/plugins/rollbar-backend/package.json b/plugins/rollbar-backend/package.json index 0ba09bc710..def1514909 100644 --- a/plugins/rollbar-backend/package.json +++ b/plugins/rollbar-backend/package.json @@ -33,7 +33,6 @@ "dependencies": { "@backstage/backend-common": "^0.10.0", "@backstage/config": "^0.1.10", - "@backstage/test-utils": "^0.1.24", "@types/express": "^4.17.6", "camelcase-keys": "^6.2.2", "compression": "^1.7.4", @@ -50,6 +49,7 @@ }, "devDependencies": { "@backstage/cli": "^0.10.3", + "@backstage/test-utils": "^0.2.0", "@types/supertest": "^2.0.8", "msw": "^0.36.3", "supertest": "^6.1.3" diff --git a/yarn.lock b/yarn.lock index 6912aa1648..321aad5f21 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2542,24 +2542,6 @@ react-router "6.0.0-beta.0" react-use "^17.2.4" -"@backstage/test-utils@^0.1.24": - version "0.1.24" - resolved "https://registry.npmjs.org/@backstage/test-utils/-/test-utils-0.1.24.tgz#1af2ea1fe3daa7df5545cc6d41271d59c2076f48" - integrity sha512-zfqhY5AS8tNOFoP8Kmpb3zE1vCbbHCXwsvxtTEq7UsRJiGpzTMN7pn+GQpddMgMHJc+IB0y25Xflw3grNEKTbA== - dependencies: - "@backstage/core-app-api" "^0.2.0" - "@backstage/core-plugin-api" "^0.3.0" - "@backstage/theme" "^0.2.14" - "@backstage/types" "^0.1.1" - "@material-ui/core" "^4.12.2" - "@material-ui/icons" "^4.11.2" - "@testing-library/jest-dom" "^5.10.1" - "@testing-library/react" "^11.2.5" - "@testing-library/user-event" "^13.1.8" - react-router "6.0.0-beta.0" - react-router-dom "6.0.0-beta.0" - zen-observable "^0.8.15" - "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" From a9abafa9dff54a7730cbf75b245dac4962898f2e Mon Sep 17 00:00:00 2001 From: Carlo Giuseppe Sergi Date: Fri, 24 Dec 2021 14:22:03 +0100 Subject: [PATCH 45/57] fixing refresh token okta provider Signed-off-by: Carlo Giuseppe Sergi --- .changeset/eleven-pianos-fail.md | 5 +++++ .../auth-backend/src/providers/okta/provider.ts | 14 ++++++++------ 2 files changed, 13 insertions(+), 6 deletions(-) create mode 100644 .changeset/eleven-pianos-fail.md diff --git a/.changeset/eleven-pianos-fail.md b/.changeset/eleven-pianos-fail.md new file mode 100644 index 0000000000..5b0aa76ae9 --- /dev/null +++ b/.changeset/eleven-pianos-fail.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend': patch +--- + +Fixed bug on refresh token on Okta provider, now it gets the refresh token and it sends it into providerInfo diff --git a/plugins/auth-backend/src/providers/okta/provider.ts b/plugins/auth-backend/src/providers/okta/provider.ts index dfffca9da7..69bbbd8c84 100644 --- a/plugins/auth-backend/src/providers/okta/provider.ts +++ b/plugins/auth-backend/src/providers/okta/provider.ts @@ -148,11 +148,12 @@ export class OktaAuthProvider implements OAuthHandlers { } async refresh(req: OAuthRefreshRequest): Promise { - const { accessToken, params } = await executeRefreshTokenStrategy( - this._strategy, - req.refreshToken, - req.scope, - ); + const { accessToken, refreshToken, params } = + await executeRefreshTokenStrategy( + this._strategy, + req.refreshToken, + req.scope, + ); const fullProfile = await executeFetchUserProfileStrategy( this._strategy, @@ -163,7 +164,7 @@ export class OktaAuthProvider implements OAuthHandlers { fullProfile, params, accessToken, - refreshToken: req.refreshToken, + refreshToken, }); } @@ -176,6 +177,7 @@ export class OktaAuthProvider implements OAuthHandlers { accessToken: result.accessToken, scope: result.params.scope, expiresInSeconds: result.params.expires_in, + refreshToken: result.refreshToken, }, profile, }; From d85f2850f341bba1c5d84fa3aa246445e8c93644 Mon Sep 17 00:00:00 2001 From: Guilherme Oenning Date: Tue, 28 Dec 2021 10:06:50 +0000 Subject: [PATCH 46/57] Update .changeset/slimy-socks-pump.md Co-authored-by: Patrik Oldsberg Signed-off-by: goenning --- .changeset/slimy-socks-pump.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/slimy-socks-pump.md b/.changeset/slimy-socks-pump.md index 7fae7da78a..f7252340f1 100644 --- a/.changeset/slimy-socks-pump.md +++ b/.changeset/slimy-socks-pump.md @@ -2,4 +2,4 @@ '@backstage/plugin-tech-insights': patch --- -expose apiRef +Export `techInsightsApiRef` and associated types. From a69651d69d4451130fba9ed6b0752c422f464867 Mon Sep 17 00:00:00 2001 From: goenning Date: Tue, 28 Dec 2021 10:45:23 +0000 Subject: [PATCH 47/57] add documentation to exported types Signed-off-by: goenning --- plugins/tech-insights/api-report.md | 35 ++++++++++++++++--- .../tech-insights/src/api/TechInsightsApi.ts | 10 ++++++ plugins/tech-insights/src/api/types.ts | 6 ++++ .../src/components/CheckResultRenderer.tsx | 5 +++ plugins/tech-insights/src/index.ts | 3 ++ 5 files changed, 55 insertions(+), 4 deletions(-) diff --git a/plugins/tech-insights/api-report.md b/plugins/tech-insights/api-report.md index 279545091c..1849508f63 100644 --- a/plugins/tech-insights/api-report.md +++ b/plugins/tech-insights/api-report.md @@ -12,13 +12,40 @@ import { EntityName } from '@backstage/catalog-model'; import { default as React_2 } from 'react'; import { RouteRef } from '@backstage/core-plugin-api'; +// @public +export type Check = { + id: string; + type: string; + name: string; + description: string; + factIds: string[]; +}; + +// @public +export type CheckResultRenderer = { + type: string; + title: string; + description: string; + component: React_2.ReactElement; +}; + // @public (undocumented) export const EntityTechInsightsScorecardContent: () => JSX.Element; -// Warning: (ae-forgotten-export) The symbol "TechInsightsApi" needs to be exported by the entry point index.d.ts -// Warning: (ae-missing-release-tag) "techInsightsApiRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public +export interface TechInsightsApi { + // (undocumented) + getAllChecks(): Promise; + // (undocumented) + getScorecardsDefinition: ( + type: string, + value: CheckResult[], + ) => CheckResultRenderer | undefined; + // (undocumented) + runChecks(entityParams: EntityName, checks?: Check[]): Promise; +} + +// @public export const techInsightsApiRef: ApiRef; // @public (undocumented) diff --git a/plugins/tech-insights/src/api/TechInsightsApi.ts b/plugins/tech-insights/src/api/TechInsightsApi.ts index d3ff9ac2ff..6c5f272e7f 100644 --- a/plugins/tech-insights/src/api/TechInsightsApi.ts +++ b/plugins/tech-insights/src/api/TechInsightsApi.ts @@ -20,10 +20,20 @@ import { Check } from './types'; import { CheckResultRenderer } from '../components/CheckResultRenderer'; import { EntityName } from '@backstage/catalog-model'; +/** + * {@link @backstage/core-plugin-api#ApiRef} for the {@link TechInsightsApi} + * + * @public + */ export const techInsightsApiRef = createApiRef({ id: 'plugin.techinsights.service', }); +/** + * API client interface for the Tech Insights plugin + * + * @public + */ export interface TechInsightsApi { getScorecardsDefinition: ( type: string, diff --git a/plugins/tech-insights/src/api/types.ts b/plugins/tech-insights/src/api/types.ts index 20071ba0c9..10dfa06420 100644 --- a/plugins/tech-insights/src/api/types.ts +++ b/plugins/tech-insights/src/api/types.ts @@ -13,6 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + +/** + * Represents a single check defined on the TechInsights backend. + * + * @public + */ export type Check = { id: string; type: string; diff --git a/plugins/tech-insights/src/components/CheckResultRenderer.tsx b/plugins/tech-insights/src/components/CheckResultRenderer.tsx index 60e0fad9c5..d57d547384 100644 --- a/plugins/tech-insights/src/components/CheckResultRenderer.tsx +++ b/plugins/tech-insights/src/components/CheckResultRenderer.tsx @@ -18,6 +18,11 @@ import { CheckResult } from '@backstage/plugin-tech-insights-common'; import React from 'react'; import { BooleanCheck } from './BooleanCheck'; +/** + * Defines a react component that is responsible for rendering a results of a given type. + * + * @public + */ export type CheckResultRenderer = { type: string; title: string; diff --git a/plugins/tech-insights/src/index.ts b/plugins/tech-insights/src/index.ts index a6a4bee3e3..b59ac453ce 100644 --- a/plugins/tech-insights/src/index.ts +++ b/plugins/tech-insights/src/index.ts @@ -19,3 +19,6 @@ export { } from './plugin'; export { techInsightsApiRef } from './api/TechInsightsApi'; +export type { TechInsightsApi } from './api/TechInsightsApi'; +export type { Check } from './api/types'; +export type { CheckResultRenderer } from './components/CheckResultRenderer'; From 6bad2cfbd3f0c11034a3c70cf8f3e5724f2bbaf0 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 28 Dec 2021 14:14:46 +0100 Subject: [PATCH 48/57] docs: add adrs to sidebar and mkdocs Signed-off-by: Patrik Oldsberg --- microsite/sidebars.json | 3 ++- mkdocs.yml | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/microsite/sidebars.json b/microsite/sidebars.json index 89eb282d14..d342012f18 100644 --- a/microsite/sidebars.json +++ b/microsite/sidebars.json @@ -285,7 +285,8 @@ "architecture-decisions/adrs-adr009", "architecture-decisions/adrs-adr010", "architecture-decisions/adrs-adr011", - "architecture-decisions/adrs-adr012" + "architecture-decisions/adrs-adr012", + "architecture-decisions/adrs-adr013" ], "FAQ": ["FAQ"] } diff --git a/mkdocs.yml b/mkdocs.yml index 2eed377819..dfa4f461d1 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -185,6 +185,8 @@ nav: - ADR009 - Entity References: 'architecture-decisions/adr009-entity-references.md' - ADR010 - Luxon Date Library: 'architecture-decisions/adr010-luxon-date-library.md' - ADR011 - Plugin Package Structure: 'architecture-decisions/adr011-plugin-package-structure.md' + - ADR012 - Plugin Package Structure: 'architecture-decisions/adr012-use-luxon-locale-and-date-presets.md' + - ADR013 - Plugin Package Structure: 'architecture-decisions/adr013-use-node-fetch.md' - Support: - Backstage Project Structure: 'support/project-structure.md' - Glossary: glossary.md From 6d8e3a96513bcf231f5087c3ba8c1f67bcc852ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Wed, 22 Dec 2021 12:35:23 +0100 Subject: [PATCH 49/57] Internal cleanup of the exports structure in the search plugin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/four-waves-tan.md | 5 +++ .../DefaultResultListItem.stories.tsx | 6 +-- .../LegacySearchPage/LegacySearchPage.tsx | 1 + .../SearchBar/SearchBar.stories.tsx | 5 ++- .../SearchFilter/SearchFilter.stories.tsx | 5 ++- .../SearchModal/SearchModal.stories.tsx | 8 ++-- .../SearchModal/SearchModal.test.tsx | 1 + .../components/SearchModal/SearchModal.tsx | 1 + .../components/SearchPage/SearchPage.test.tsx | 2 +- .../SearchResult/SearchResult.stories.tsx | 10 +++-- .../SearchType/SearchType.stories.tsx | 4 +- plugins/search/src/components/index.tsx | 29 -------------- plugins/search/src/index.ts | 40 +++++++++---------- 13 files changed, 48 insertions(+), 69 deletions(-) create mode 100644 .changeset/four-waves-tan.md delete mode 100644 plugins/search/src/components/index.tsx diff --git a/.changeset/four-waves-tan.md b/.changeset/four-waves-tan.md new file mode 100644 index 0000000000..444891d31b --- /dev/null +++ b/.changeset/four-waves-tan.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search': patch +--- + +Internal cleanup of the exports structure diff --git a/plugins/search/src/components/DefaultResultListItem/DefaultResultListItem.stories.tsx b/plugins/search/src/components/DefaultResultListItem/DefaultResultListItem.stories.tsx index 0445a35943..baf00767db 100644 --- a/plugins/search/src/components/DefaultResultListItem/DefaultResultListItem.stories.tsx +++ b/plugins/search/src/components/DefaultResultListItem/DefaultResultListItem.stories.tsx @@ -14,13 +14,13 @@ * limitations under the License. */ -import React from 'react'; +import { Button } from '@backstage/core-components'; import { Grid } from '@material-ui/core'; import FindInPageIcon from '@material-ui/icons/FindInPage'; import GroupIcon from '@material-ui/icons/Group'; -import { Button } from '@backstage/core-components'; -import { DefaultResultListItem } from '../index'; +import React from 'react'; import { MemoryRouter } from 'react-router'; +import { DefaultResultListItem } from './DefaultResultListItem'; export default { title: 'Plugins/Search/DefaultResultListItem', diff --git a/plugins/search/src/components/LegacySearchPage/LegacySearchPage.tsx b/plugins/search/src/components/LegacySearchPage/LegacySearchPage.tsx index c8f2e538cb..ec0fc3acb9 100644 --- a/plugins/search/src/components/LegacySearchPage/LegacySearchPage.tsx +++ b/plugins/search/src/components/LegacySearchPage/LegacySearchPage.tsx @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { Grid } from '@material-ui/core'; import React, { useEffect, useState } from 'react'; import { useDebounce } from 'react-use'; diff --git a/plugins/search/src/components/SearchBar/SearchBar.stories.tsx b/plugins/search/src/components/SearchBar/SearchBar.stories.tsx index d00f391bc7..8d81e15e56 100644 --- a/plugins/search/src/components/SearchBar/SearchBar.stories.tsx +++ b/plugins/search/src/components/SearchBar/SearchBar.stories.tsx @@ -14,10 +14,11 @@ * limitations under the License. */ +import { Grid, makeStyles, Paper } from '@material-ui/core'; import React from 'react'; -import { Paper, Grid, makeStyles } from '@material-ui/core'; -import { SearchBar, SearchContext } from '../index'; import { MemoryRouter } from 'react-router'; +import { SearchContext } from '../SearchContext'; +import { SearchBar } from './SearchBar'; export default { title: 'Plugins/Search/SearchBar', diff --git a/plugins/search/src/components/SearchFilter/SearchFilter.stories.tsx b/plugins/search/src/components/SearchFilter/SearchFilter.stories.tsx index 190856fca8..c98c1106e6 100644 --- a/plugins/search/src/components/SearchFilter/SearchFilter.stories.tsx +++ b/plugins/search/src/components/SearchFilter/SearchFilter.stories.tsx @@ -14,10 +14,11 @@ * limitations under the License. */ -import React from 'react'; import { Grid, Paper } from '@material-ui/core'; -import { SearchFilter, SearchContext } from '../index'; +import React from 'react'; import { MemoryRouter } from 'react-router'; +import { SearchContext } from '../SearchContext'; +import { SearchFilter } from './SearchFilter'; export default { title: 'Plugins/Search/SearchFilter', diff --git a/plugins/search/src/components/SearchModal/SearchModal.stories.tsx b/plugins/search/src/components/SearchModal/SearchModal.stories.tsx index 6fa93c6588..c0a774ab46 100644 --- a/plugins/search/src/components/SearchModal/SearchModal.stories.tsx +++ b/plugins/search/src/components/SearchModal/SearchModal.stories.tsx @@ -14,14 +14,14 @@ * limitations under the License. */ -import React, { ComponentType } from 'react'; -import { Button } from '@material-ui/core'; import { ApiProvider, ApiRegistry } from '@backstage/core-app-api'; import { wrapInTestApp } from '@backstage/test-utils'; -import { SearchModal } from '../index'; -import { useSearch, SearchContextProvider } from '../SearchContext'; +import { Button } from '@material-ui/core'; +import React, { ComponentType } from 'react'; import { searchApiRef } from '../../apis'; import { rootRouteRef } from '../../plugin'; +import { SearchContextProvider, useSearch } from '../SearchContext'; +import { SearchModal } from './SearchModal'; const mockSearchApi = { query: () => diff --git a/plugins/search/src/components/SearchModal/SearchModal.test.tsx b/plugins/search/src/components/SearchModal/SearchModal.test.tsx index ff9622dfb9..d3209f9a9b 100644 --- a/plugins/search/src/components/SearchModal/SearchModal.test.tsx +++ b/plugins/search/src/components/SearchModal/SearchModal.test.tsx @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import React from 'react'; import { screen } from '@testing-library/react'; import { renderInTestApp, TestApiRegistry } from '@backstage/test-utils'; diff --git a/plugins/search/src/components/SearchModal/SearchModal.tsx b/plugins/search/src/components/SearchModal/SearchModal.tsx index ef1eaf650a..8b4da51592 100644 --- a/plugins/search/src/components/SearchModal/SearchModal.tsx +++ b/plugins/search/src/components/SearchModal/SearchModal.tsx @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import React from 'react'; import { Dialog, diff --git a/plugins/search/src/components/SearchPage/SearchPage.test.tsx b/plugins/search/src/components/SearchPage/SearchPage.test.tsx index c9980b3bf7..9486196584 100644 --- a/plugins/search/src/components/SearchPage/SearchPage.test.tsx +++ b/plugins/search/src/components/SearchPage/SearchPage.test.tsx @@ -18,7 +18,7 @@ import { renderInTestApp } from '@backstage/test-utils'; import React from 'react'; import { useLocation, useOutlet } from 'react-router'; import { useSearch } from '../SearchContext'; -import { SearchPage } from './'; +import { SearchPage } from './SearchPage'; jest.mock('react-router', () => ({ ...jest.requireActual('react-router'), diff --git a/plugins/search/src/components/SearchResult/SearchResult.stories.tsx b/plugins/search/src/components/SearchResult/SearchResult.stories.tsx index aebaecb8b4..82e4fc2916 100644 --- a/plugins/search/src/components/SearchResult/SearchResult.stories.tsx +++ b/plugins/search/src/components/SearchResult/SearchResult.stories.tsx @@ -14,11 +14,13 @@ * limitations under the License. */ -import React from 'react'; -import { List, ListItem } from '@material-ui/core'; -import { SearchResult, SearchContext, DefaultResultListItem } from '../index'; -import { MemoryRouter } from 'react-router'; import { Link } from '@backstage/core-components'; +import { List, ListItem } from '@material-ui/core'; +import React from 'react'; +import { MemoryRouter } from 'react-router'; +import { DefaultResultListItem } from '../DefaultResultListItem'; +import { SearchContext } from '../SearchContext'; +import { SearchResult } from './SearchResult'; export default { title: 'Plugins/Search/SearchResult', diff --git a/plugins/search/src/components/SearchType/SearchType.stories.tsx b/plugins/search/src/components/SearchType/SearchType.stories.tsx index da662d8fef..29dd66a3a6 100644 --- a/plugins/search/src/components/SearchType/SearchType.stories.tsx +++ b/plugins/search/src/components/SearchType/SearchType.stories.tsx @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, { useState } from 'react'; -import { SearchType } from '../index'; +import React, { useState } from 'react'; import { SearchContext } from '../SearchContext'; +import { SearchType } from './SearchType'; export default { title: 'Plugins/Search/SearchType', diff --git a/plugins/search/src/components/index.tsx b/plugins/search/src/components/index.tsx deleted file mode 100644 index e413e9505d..0000000000 --- a/plugins/search/src/components/index.tsx +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * 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 './DefaultResultListItem'; -export * from './Filters'; -export * from './SearchBar'; -export * from './SearchContext'; -export * from './SearchFilter'; -export * from './SearchModal'; -export * from './SearchPage'; -export * from './SearchResult'; -export * from './SearchResultPager'; -export * from './SearchType'; -export * from './SidebarSearch'; -export * from './SidebarSearchModal'; -export * from './HomePageComponent'; diff --git a/plugins/search/src/index.ts b/plugins/search/src/index.ts index 5bfdcf4318..fc0a149c88 100644 --- a/plugins/search/src/index.ts +++ b/plugins/search/src/index.ts @@ -22,30 +22,26 @@ export { searchApiRef } from './apis'; export type { SearchApi } from './apis'; -export { - Filters, - FiltersButton, - SearchBar, - SearchBarBase, - SearchContextProvider, - SearchFilter, - SearchFilterNext, - SearchModal, - SearchPage as Router, - SearchResultPager, - SearchType, - SidebarSearch, - useSearch, -} from './components'; + +export { Filters, FiltersButton } from './components/Filters'; +export type { FiltersState } from './components/Filters'; +export type { HomePageSearchBarProps } from './components/HomePageComponent'; +export { SearchBar, SearchBarBase } from './components/SearchBar'; export type { - SearchModalProps, - SidebarSearchModalProps, - HomePageSearchBarProps, - SidebarSearchProps, - FiltersState, - SearchBarProps, SearchBarBaseProps, -} from './components'; + SearchBarProps, +} from './components/SearchBar'; +export { SearchContextProvider, useSearch } from './components/SearchContext'; +export { SearchFilter, SearchFilterNext } from './components/SearchFilter'; +export { SearchModal } from './components/SearchModal'; +export type { SearchModalProps } from './components/SearchModal'; +export { SearchPage as Router } from './components/SearchPage'; +export { SearchResultPager } from './components/SearchResultPager'; +export { SearchType } from './components/SearchType'; +export { SidebarSearch } from './components/SidebarSearch'; +export type { SidebarSearchProps } from './components/SidebarSearch'; +export type { SidebarSearchModalProps } from './components/SidebarSearchModal'; + export { DefaultResultListItem, HomePageSearchBar, From b29d97633061f0bd1a067afdbeda18a651493235 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 28 Dec 2021 14:35:11 +0100 Subject: [PATCH 50/57] auth-backend: refactor auth0 to use sign-in resolver Signed-off-by: Patrik Oldsberg --- .../src/providers/auth0/provider.ts | 153 ++++++++++++++---- plugins/auth-backend/src/providers/index.ts | 7 +- 2 files changed, 122 insertions(+), 38 deletions(-) diff --git a/plugins/auth-backend/src/providers/auth0/provider.ts b/plugins/auth-backend/src/providers/auth0/provider.ts index 7aa98c3e65..4293e3e481 100644 --- a/plugins/auth-backend/src/providers/auth0/provider.ts +++ b/plugins/auth-backend/src/providers/auth0/provider.ts @@ -36,7 +36,15 @@ import { makeProfileInfo, PassportDoneCallback, } from '../../lib/passport'; -import { RedirectInfo, AuthProviderFactory } from '../types'; +import { + RedirectInfo, + AuthProviderFactory, + AuthHandler, + SignInResolver, +} from '../types'; +import { CatalogIdentityClient } from '../../lib/catalog'; +import { TokenIssuer } from '../../identity'; +import { Logger } from 'winston'; type PrivateInfo = { refreshToken: string; @@ -44,12 +52,27 @@ type PrivateInfo = { export type Auth0AuthProviderOptions = OAuthProviderOptions & { domain: string; + signInResolver?: SignInResolver; + authHandler: AuthHandler; + tokenIssuer: TokenIssuer; + catalogIdentityClient: CatalogIdentityClient; + logger: Logger; }; export class Auth0AuthProvider implements OAuthHandlers { private readonly _strategy: Auth0Strategy; + private readonly signInResolver?: SignInResolver; + private readonly authHandler: AuthHandler; + private readonly tokenIssuer: TokenIssuer; + private readonly catalogIdentityClient: CatalogIdentityClient; + private readonly logger: Logger; constructor(options: Auth0AuthProviderOptions) { + this.signInResolver = options.signInResolver; + this.authHandler = options.authHandler; + this.tokenIssuer = options.tokenIssuer; + this.catalogIdentityClient = options.catalogIdentityClient; + this.logger = options.logger; this._strategy = new Auth0Strategy( { clientID: options.clientId, @@ -98,18 +121,8 @@ export class Auth0AuthProvider implements OAuthHandlers { PrivateInfo >(req, this._strategy); - const profile = makeProfileInfo(result.fullProfile, result.params.id_token); - return { - response: await this.populateIdentity({ - profile, - providerInfo: { - idToken: result.params.id_token, - accessToken: result.accessToken, - scope: result.params.scope, - expiresInSeconds: result.params.expires_in, - }, - }), + response: await this.handleResult(result), refreshToken: privateInfo.refreshToken, }; } @@ -125,53 +138,123 @@ export class Auth0AuthProvider implements OAuthHandlers { this._strategy, accessToken, ); - const profile = makeProfileInfo(fullProfile, params.id_token); - return this.populateIdentity({ - providerInfo: { - accessToken, - idToken: params.id_token, - expiresInSeconds: params.expires_in, - scope: params.scope, - }, - profile, + return this.handleResult({ + fullProfile, + params, + accessToken, + refreshToken: req.refreshToken, }); } - // Use this function to grab the user profile info from the token - // Then populate the profile with it - private async populateIdentity( - response: OAuthResponse, - ): Promise { - const { profile } = response; + private async handleResult(result: OAuthResult) { + const { profile } = await this.authHandler(result); - if (!profile.email) { - throw new Error('Profile does not contain an email'); + const response: OAuthResponse = { + providerInfo: { + idToken: result.params.id_token, + accessToken: result.accessToken, + scope: result.params.scope, + expiresInSeconds: result.params.expires_in, + }, + profile, + }; + + if (this.signInResolver) { + response.backstageIdentity = await this.signInResolver( + { + result, + profile, + }, + { + tokenIssuer: this.tokenIssuer, + catalogIdentityClient: this.catalogIdentityClient, + logger: this.logger, + }, + ); } - const id = profile.email.split('@')[0]; - - return { ...response, backstageIdentity: { id, token: '' } }; + return response; } } -export type Auth0ProviderOptions = {}; +const defaultSignInResolver: SignInResolver = async ( + info, + ctx, +) => { + const { profile } = info; + + if (!profile.email) { + throw new Error('Profile does not contain an email'); + } + + const id = profile.email.split('@')[0]; + + const token = await ctx.tokenIssuer.issueToken({ + claims: { sub: id, ent: [`user:default/${id}`] }, + }); + + return { id, token }; +}; + +export type Auth0ProviderOptions = { + /** + * The profile transformation function used to verify and convert the auth response + * into the profile that will be presented to the user. + */ + authHandler?: AuthHandler; + + /** + * Configure sign-in for this provider, without it the provider can not be used to sign users in. + */ + signIn?: { + /** + * Maps an auth result to a Backstage identity for the user. + */ + resolver: SignInResolver; + }; +}; export const createAuth0Provider = ( - _options?: Auth0ProviderOptions, + options?: Auth0ProviderOptions, ): AuthProviderFactory => { - return ({ providerId, globalConfig, config, tokenIssuer }) => + return ({ + providerId, + globalConfig, + config, + tokenIssuer, + catalogApi, + logger, + }) => OAuthEnvironmentHandler.mapConfig(config, envConfig => { const clientId = envConfig.getString('clientId'); const clientSecret = envConfig.getString('clientSecret'); const domain = envConfig.getString('domain'); const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`; + const catalogIdentityClient = new CatalogIdentityClient({ + catalogApi, + tokenIssuer, + }); + + const authHandler: AuthHandler = options?.authHandler + ? options.authHandler + : async ({ fullProfile, params }) => ({ + profile: makeProfileInfo(fullProfile, params.id_token), + }); + + const signInResolver = options?.signIn?.resolver ?? defaultSignInResolver; + const provider = new Auth0AuthProvider({ clientId, clientSecret, callbackUrl, domain, + authHandler, + signInResolver, + tokenIssuer, + catalogIdentityClient, + logger, }); return OAuthAdapter.fromConfig(globalConfig, provider, { diff --git a/plugins/auth-backend/src/providers/index.ts b/plugins/auth-backend/src/providers/index.ts index 9589e97265..aba41bdb73 100644 --- a/plugins/auth-backend/src/providers/index.ts +++ b/plugins/auth-backend/src/providers/index.ts @@ -14,6 +14,10 @@ * limitations under the License. */ +export * from './atlassian'; +export * from './auth0'; +export * from './aws-alb'; +export * from './bitbucket'; export * from './github'; export * from './gitlab'; export * from './google'; @@ -21,9 +25,6 @@ export * from './microsoft'; export * from './oauth2'; export * from './oidc'; export * from './okta'; -export * from './bitbucket'; -export * from './atlassian'; -export * from './aws-alb'; export * from './saml'; export { factories as defaultAuthProviderFactories } from './factories'; From 04b1d4be44ee435dd138835db90a1e0761fb5c2d Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 28 Dec 2021 15:26:36 +0100 Subject: [PATCH 51/57] auth-backend: refactor onelogin to use sign-in resolver Signed-off-by: Patrik Oldsberg --- plugins/auth-backend/src/providers/index.ts | 1 + .../src/providers/onelogin/provider.ts | 150 ++++++++++++++---- 2 files changed, 118 insertions(+), 33 deletions(-) diff --git a/plugins/auth-backend/src/providers/index.ts b/plugins/auth-backend/src/providers/index.ts index aba41bdb73..3f71dd2c26 100644 --- a/plugins/auth-backend/src/providers/index.ts +++ b/plugins/auth-backend/src/providers/index.ts @@ -25,6 +25,7 @@ export * from './microsoft'; export * from './oauth2'; export * from './oidc'; export * from './okta'; +export * from './onelogin'; export * from './saml'; export { factories as defaultAuthProviderFactories } from './factories'; diff --git a/plugins/auth-backend/src/providers/onelogin/provider.ts b/plugins/auth-backend/src/providers/onelogin/provider.ts index 66e8b0bfc5..97aec51a1e 100644 --- a/plugins/auth-backend/src/providers/onelogin/provider.ts +++ b/plugins/auth-backend/src/providers/onelogin/provider.ts @@ -36,7 +36,15 @@ import { executeFetchUserProfileStrategy, PassportDoneCallback, } from '../../lib/passport'; -import { RedirectInfo, AuthProviderFactory } from '../types'; +import { + RedirectInfo, + AuthProviderFactory, + AuthHandler, + SignInResolver, +} from '../types'; +import { CatalogIdentityClient } from '../../lib/catalog'; +import { Logger } from 'winston'; +import { TokenIssuer } from '../../identity'; type PrivateInfo = { refreshToken: string; @@ -44,12 +52,27 @@ type PrivateInfo = { export type Options = OAuthProviderOptions & { issuer: string; + signInResolver?: SignInResolver; + authHandler: AuthHandler; + tokenIssuer: TokenIssuer; + catalogIdentityClient: CatalogIdentityClient; + logger: Logger; }; export class OneLoginProvider implements OAuthHandlers { private readonly _strategy: any; + private readonly signInResolver?: SignInResolver; + private readonly authHandler: AuthHandler; + private readonly tokenIssuer: TokenIssuer; + private readonly catalogIdentityClient: CatalogIdentityClient; + private readonly logger: Logger; constructor(options: Options) { + this.signInResolver = options.signInResolver; + this.authHandler = options.authHandler; + this.tokenIssuer = options.tokenIssuer; + this.catalogIdentityClient = options.catalogIdentityClient; + this.logger = options.logger; this._strategy = new OneLoginStrategy( { issuer: options.issuer, @@ -97,18 +120,8 @@ export class OneLoginProvider implements OAuthHandlers { PrivateInfo >(req, this._strategy); - const profile = makeProfileInfo(result.fullProfile, result.params.id_token); - return { - response: await this.populateIdentity({ - profile, - providerInfo: { - idToken: result.params.id_token, - accessToken: result.accessToken, - scope: result.params.scope, - expiresInSeconds: result.params.expires_in, - }, - }), + response: await this.handleResult(result), refreshToken: privateInfo.refreshToken, }; } @@ -124,51 +137,122 @@ export class OneLoginProvider implements OAuthHandlers { this._strategy, accessToken, ); - const profile = makeProfileInfo(fullProfile, params.id_token); - return this.populateIdentity({ - providerInfo: { - accessToken, - idToken: params.id_token, - expiresInSeconds: params.expires_in, - scope: params.scope, - }, - profile, + return this.handleResult({ + fullProfile, + params, + accessToken, }); } - private async populateIdentity( - response: OAuthResponse, - ): Promise { - const { profile } = response; + private async handleResult(result: OAuthResult) { + const { profile } = await this.authHandler(result); - if (!profile.email) { - throw new Error('OIDC profile contained no email'); + const response: OAuthResponse = { + providerInfo: { + idToken: result.params.id_token, + accessToken: result.accessToken, + scope: result.params.scope, + expiresInSeconds: result.params.expires_in, + }, + profile, + }; + + if (this.signInResolver) { + response.backstageIdentity = await this.signInResolver( + { + result, + profile, + }, + { + tokenIssuer: this.tokenIssuer, + catalogIdentityClient: this.catalogIdentityClient, + logger: this.logger, + }, + ); } - const id = profile.email.split('@')[0]; - - return { ...response, backstageIdentity: { id, token: '' } }; + return response; } } -export type OneLoginProviderOptions = {}; +const defaultSignInResolver: SignInResolver = async ( + info, + ctx, +) => { + const { profile } = info; + + if (!profile.email) { + throw new Error('OIDC profile contained no email'); + } + + const id = profile.email.split('@')[0]; + + const token = await ctx.tokenIssuer.issueToken({ + claims: { sub: id, ent: [`user:default/${id}`] }, + }); + + return { id, token }; +}; + +export type OneLoginProviderOptions = { + /** + * The profile transformation function used to verify and convert the auth response + * into the profile that will be presented to the user. + */ + authHandler?: AuthHandler; + + /** + * Configure sign-in for this provider, without it the provider can not be used to sign users in. + */ + signIn?: { + /** + * Maps an auth result to a Backstage identity for the user. + */ + resolver: SignInResolver; + }; +}; export const createOneLoginProvider = ( - _options?: OneLoginProviderOptions, + options?: OneLoginProviderOptions, ): AuthProviderFactory => { - return ({ providerId, globalConfig, config, tokenIssuer }) => + return ({ + providerId, + globalConfig, + config, + tokenIssuer, + catalogApi, + logger, + }) => OAuthEnvironmentHandler.mapConfig(config, envConfig => { const clientId = envConfig.getString('clientId'); const clientSecret = envConfig.getString('clientSecret'); const issuer = envConfig.getString('issuer'); const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`; + const catalogIdentityClient = new CatalogIdentityClient({ + catalogApi, + tokenIssuer, + }); + + const authHandler: AuthHandler = options?.authHandler + ? options.authHandler + : async ({ fullProfile, params }) => ({ + profile: makeProfileInfo(fullProfile, params.id_token), + }); + + const signInResolver = options?.signIn?.resolver ?? defaultSignInResolver; + const provider = new OneLoginProvider({ clientId, clientSecret, callbackUrl, issuer, + authHandler, + signInResolver, + tokenIssuer, + catalogIdentityClient, + logger, }); return OAuthAdapter.fromConfig(globalConfig, provider, { From 2f26120a36cb69adcfa8f19d3f1e9cc3b9b6cdc7 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 28 Dec 2021 16:01:01 +0100 Subject: [PATCH 52/57] auth-backend: changeset and api report update for auth0 and onelogin Signed-off-by: Patrik Oldsberg --- .changeset/eighty-dancers-heal.md | 5 ++++ plugins/auth-backend/api-report.md | 26 +++++++++++++++++++ .../src/providers/auth0/provider.ts | 2 ++ .../src/providers/onelogin/provider.ts | 2 ++ 4 files changed, 35 insertions(+) create mode 100644 .changeset/eighty-dancers-heal.md diff --git a/.changeset/eighty-dancers-heal.md b/.changeset/eighty-dancers-heal.md new file mode 100644 index 0000000000..9286e75d2c --- /dev/null +++ b/.changeset/eighty-dancers-heal.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend': patch +--- + +Update `auth0` and `onelogin` providers to allow for `authHandler` and `signIn.resolver` configuration. diff --git a/plugins/auth-backend/api-report.md b/plugins/auth-backend/api-report.md index 9ed523c678..29de38a1f4 100644 --- a/plugins/auth-backend/api-report.md +++ b/plugins/auth-backend/api-report.md @@ -47,6 +47,14 @@ export type AtlassianProviderOptions = { }; }; +// @public (undocumented) +export type Auth0ProviderOptions = { + authHandler?: AuthHandler; + signIn?: { + resolver: SignInResolver; + }; +}; + // @public export type AuthHandler = ( input: AuthResult, @@ -219,6 +227,11 @@ export const createAtlassianProvider: ( options?: AtlassianProviderOptions | undefined, ) => AuthProviderFactory; +// @public (undocumented) +export const createAuth0Provider: ( + options?: Auth0ProviderOptions | undefined, +) => AuthProviderFactory; + // Warning: (ae-missing-release-tag) "createAwsAlbProvider" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -282,6 +295,11 @@ export const createOktaProvider: ( _options?: OktaProviderOptions | undefined, ) => AuthProviderFactory; +// @public (undocumented) +export const createOneLoginProvider: ( + options?: OneLoginProviderOptions | undefined, +) => AuthProviderFactory; + // Warning: (ae-missing-release-tag) "createOriginFilter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -572,6 +590,14 @@ export type OktaProviderOptions = { }; }; +// @public (undocumented) +export type OneLoginProviderOptions = { + authHandler?: AuthHandler; + signIn?: { + resolver: SignInResolver; + }; +}; + // Warning: (ae-missing-release-tag) "postMessageResponse" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) diff --git a/plugins/auth-backend/src/providers/auth0/provider.ts b/plugins/auth-backend/src/providers/auth0/provider.ts index 4293e3e481..583f95c621 100644 --- a/plugins/auth-backend/src/providers/auth0/provider.ts +++ b/plugins/auth-backend/src/providers/auth0/provider.ts @@ -197,6 +197,7 @@ const defaultSignInResolver: SignInResolver = async ( return { id, token }; }; +/** @public */ export type Auth0ProviderOptions = { /** * The profile transformation function used to verify and convert the auth response @@ -215,6 +216,7 @@ export type Auth0ProviderOptions = { }; }; +/** @public */ export const createAuth0Provider = ( options?: Auth0ProviderOptions, ): AuthProviderFactory => { diff --git a/plugins/auth-backend/src/providers/onelogin/provider.ts b/plugins/auth-backend/src/providers/onelogin/provider.ts index 97aec51a1e..8cb06ea7a7 100644 --- a/plugins/auth-backend/src/providers/onelogin/provider.ts +++ b/plugins/auth-backend/src/providers/onelogin/provider.ts @@ -195,6 +195,7 @@ const defaultSignInResolver: SignInResolver = async ( return { id, token }; }; +/** @public */ export type OneLoginProviderOptions = { /** * The profile transformation function used to verify and convert the auth response @@ -213,6 +214,7 @@ export type OneLoginProviderOptions = { }; }; +/** @public */ export const createOneLoginProvider = ( options?: OneLoginProviderOptions, ): AuthProviderFactory => { From 88e153dc4420c77fe2dfd60b0c1bbdba6ccebc03 Mon Sep 17 00:00:00 2001 From: Erik Larsson Date: Tue, 28 Dec 2021 16:09:55 +0100 Subject: [PATCH 53/57] improve changeset description Signed-off-by: Erik Larsson --- .changeset/seven-tomatoes-smash.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/seven-tomatoes-smash.md b/.changeset/seven-tomatoes-smash.md index e76e9aa4e2..98ae643738 100644 --- a/.changeset/seven-tomatoes-smash.md +++ b/.changeset/seven-tomatoes-smash.md @@ -1,5 +1,5 @@ --- -'@backstage/plugin-techdocs-backend': minor +'@backstage/plugin-techdocs-backend': patch --- -fixes api auth bug in techdocs backend +Add support for API auth in DefaultTechDocsCollator From 62f36517072ebe147fac6b8894a14135e5662e37 Mon Sep 17 00:00:00 2001 From: Tim Hansen Date: Tue, 28 Dec 2021 09:19:09 -0700 Subject: [PATCH 54/57] Add warning to default backend Signed-off-by: Tim Hansen --- .../templates/default-app/packages/backend/src/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/create-app/templates/default-app/packages/backend/src/index.ts b/packages/create-app/templates/default-app/packages/backend/src/index.ts index fbeabcff36..08d21e61f7 100644 --- a/packages/create-app/templates/default-app/packages/backend/src/index.ts +++ b/packages/create-app/templates/default-app/packages/backend/src/index.ts @@ -81,6 +81,8 @@ async function main() { apiRouter.use('/techdocs', await techdocs(techdocsEnv)); apiRouter.use('/proxy', await proxy(proxyEnv)); apiRouter.use('/search', await search(searchEnv)); + + // Add backends ABOVE this line; this 404 handler is the catch-all fallback apiRouter.use(notFoundHandler()); const service = createServiceBuilder(module) From c88cdacc1abce1f77b93a3cf1757211cfba64e79 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 28 Dec 2021 17:30:00 +0100 Subject: [PATCH 55/57] auth-backend: always exchange and never return refresh tokens to clients Signed-off-by: Patrik Oldsberg --- .changeset/four-phones-shave.md | 29 ++++++++++ plugins/auth-backend/api-report.md | 25 +++++---- .../src/lib/oauth/OAuthAdapter.test.ts | 30 +++++++---- .../src/lib/oauth/OAuthAdapter.ts | 12 ++--- plugins/auth-backend/src/lib/oauth/types.ts | 9 ++-- .../src/providers/atlassian/provider.test.ts | 54 ++++++++++--------- .../src/providers/atlassian/provider.ts | 38 ++++++------- .../src/providers/auth0/provider.ts | 31 +++++------ .../src/providers/bitbucket/provider.ts | 31 +++++------ .../src/providers/github/provider.test.ts | 34 ++++++------ .../src/providers/github/provider.ts | 33 ++++++------ .../src/providers/gitlab/provider.test.ts | 32 +++++------ .../src/providers/gitlab/provider.ts | 37 ++++++------- .../src/providers/google/provider.ts | 32 +++++------ .../src/providers/microsoft/provider.ts | 31 +++++------ .../src/providers/oauth2/provider.ts | 27 ++++------ .../src/providers/oidc/provider.ts | 24 ++++----- .../src/providers/okta/provider.ts | 19 ++++--- .../src/providers/onelogin/provider.ts | 30 ++++++----- 19 files changed, 296 insertions(+), 262 deletions(-) create mode 100644 .changeset/four-phones-shave.md diff --git a/.changeset/four-phones-shave.md b/.changeset/four-phones-shave.md new file mode 100644 index 0000000000..1ef58bdadb --- /dev/null +++ b/.changeset/four-phones-shave.md @@ -0,0 +1,29 @@ +--- +'@backstage/plugin-auth-backend': minor +--- + +Avoid ever returning OAuth refresh tokens back to the client, and always exchange refresh tokens for a new one when available for all providers. + +This comes with a breaking change to the TypeScript API for custom auth providers. The `refresh` method of `OAuthHandlers` implementation must now return a `{ response, refreshToken }` object rather than a direct response. Existing `refresh` implementations are typically migrated by changing an existing return expression that looks like this: + +```ts +return await this.handleResult({ + fullProfile, + params, + accessToken, + refreshToken, +}); +``` + +Into the following: + +```ts +return { + response: await this.handleResult({ + fullProfile, + params, + accessToken, + }), + refreshToken, +}; +``` diff --git a/plugins/auth-backend/api-report.md b/plugins/auth-backend/api-report.md index 29de38a1f4..af46d874df 100644 --- a/plugins/auth-backend/api-report.md +++ b/plugins/auth-backend/api-report.md @@ -27,10 +27,13 @@ export class AtlassianAuthProvider implements OAuthHandlers { // (undocumented) handler(req: express.Request): Promise<{ response: OAuthResponse; - refreshToken: string; + refreshToken: string | undefined; }>; // (undocumented) - refresh(req: OAuthRefreshRequest): Promise; + refresh(req: OAuthRefreshRequest): Promise<{ + response: OAuthResponse; + refreshToken: string | undefined; + }>; // Warning: (ae-forgotten-export) The symbol "RedirectInfo" needs to be exported by the entry point index.d.ts // // (undocumented) @@ -488,7 +491,10 @@ export interface OAuthHandlers { // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' - refresh?(req: OAuthRefreshRequest): Promise; + refresh?(req: OAuthRefreshRequest): Promise<{ + response: OAuthResponse; + refreshToken?: string; + }>; // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen // Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}' // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen @@ -503,7 +509,6 @@ export type OAuthProviderInfo = { idToken?: string; expiresInSeconds?: number; scope: string; - refreshToken?: string; }; // Warning: (ae-missing-release-tag) "OAuthProviderOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -703,11 +708,11 @@ export type WebMessageResponse = // // src/identity/types.d.ts:31:9 - (ae-forgotten-export) The symbol "AnyJWK" needs to be exported by the entry point index.d.ts // src/providers/aws-alb/provider.d.ts:77:5 - (ae-forgotten-export) The symbol "AwsAlbResult" needs to be exported by the entry point index.d.ts -// src/providers/github/provider.d.ts:71:58 - (tsdoc-escape-greater-than) The ">" character should be escaped using a backslash to avoid confusion with an HTML tag -// src/providers/github/provider.d.ts:71:90 - (tsdoc-escape-greater-than) The ">" character should be escaped using a backslash to avoid confusion with an HTML tag -// src/providers/github/provider.d.ts:71:89 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag -// src/providers/github/provider.d.ts:71:67 - (tsdoc-malformed-html-name) Invalid HTML element: Expecting an HTML name -// src/providers/github/provider.d.ts:71:68 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" -// src/providers/github/provider.d.ts:78:5 - (ae-forgotten-export) The symbol "StateEncoder" needs to be exported by the entry point index.d.ts +// src/providers/github/provider.d.ts:74:58 - (tsdoc-escape-greater-than) The ">" character should be escaped using a backslash to avoid confusion with an HTML tag +// src/providers/github/provider.d.ts:74:90 - (tsdoc-escape-greater-than) The ">" character should be escaped using a backslash to avoid confusion with an HTML tag +// src/providers/github/provider.d.ts:74:89 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag +// src/providers/github/provider.d.ts:74:67 - (tsdoc-malformed-html-name) Invalid HTML element: Expecting an HTML name +// src/providers/github/provider.d.ts:74:68 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" +// src/providers/github/provider.d.ts:81:5 - (ae-forgotten-export) The symbol "StateEncoder" needs to be exported by the entry point index.d.ts // src/providers/types.d.ts:100:5 - (ae-forgotten-export) The symbol "AuthProviderConfig" needs to be exported by the entry point index.d.ts ``` diff --git a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.ts b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.ts index a3fc77bc02..98f4cd9fa1 100644 --- a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.ts +++ b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.ts @@ -57,7 +57,10 @@ describe('OAuthAdapter', () => { }; } async refresh() { - return mockResponseData; + return { + response: mockResponseData, + refreshToken: 'token', + }; } } const providerInstance = new MyAuthProvider(); @@ -257,7 +260,10 @@ describe('OAuthAdapter', () => { }); it('correctly populates incomplete identities', async () => { - const mockRefresh = jest.fn, [express.Request]>(); + const mockRefresh = jest.fn< + Promise<{ response: OAuthResponse }>, + [express.Request] + >(); const oauthProvider = new OAuthAdapter( { @@ -291,10 +297,12 @@ describe('OAuthAdapter', () => { // Without a token mockRefresh.mockResolvedValueOnce({ - ...mockResponseData, - backstageIdentity: { - id: 'foo', - token: '', + response: { + ...mockResponseData, + backstageIdentity: { + id: 'foo', + token: '', + }, }, }); await oauthProvider.refresh(mockRequest, mockResponse); @@ -315,10 +323,12 @@ describe('OAuthAdapter', () => { // With a token mockRefresh.mockResolvedValueOnce({ - ...mockResponseData, - backstageIdentity: { - id: 'foo', - token: `z.${mkTokenBody({ sub: 'user:my-ns/foo' })}.z`, + response: { + ...mockResponseData, + backstageIdentity: { + id: 'foo', + token: `z.${mkTokenBody({ sub: 'user:my-ns/foo' })}.z`, + }, }, }); await oauthProvider.refresh(mockRequest, mockResponse); diff --git a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts index 4d5d507aa1..ce4b52ef4f 100644 --- a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts +++ b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts @@ -212,19 +212,15 @@ export class OAuthAdapter implements AuthProviderRouteHandlers { const forwardReq = Object.assign(req, { scope, refreshToken }); // get new access_token - const response = await this.handlers.refresh( - forwardReq as OAuthRefreshRequest, - ); + const { response, refreshToken: newRefreshToken } = + await this.handlers.refresh(forwardReq as OAuthRefreshRequest); const backstageIdentity = await this.populateIdentity( response.backstageIdentity, ); - if ( - response.providerInfo.refreshToken && - response.providerInfo.refreshToken !== refreshToken - ) { - this.setRefreshTokenCookie(res, response.providerInfo.refreshToken); + if (newRefreshToken && newRefreshToken !== refreshToken) { + this.setRefreshTokenCookie(res, newRefreshToken); } res.status(200).json({ ...response, backstageIdentity }); diff --git a/plugins/auth-backend/src/lib/oauth/types.ts b/plugins/auth-backend/src/lib/oauth/types.ts index cd1439b399..f54d04d15b 100644 --- a/plugins/auth-backend/src/lib/oauth/types.ts +++ b/plugins/auth-backend/src/lib/oauth/types.ts @@ -79,10 +79,6 @@ export type OAuthProviderInfo = { * Scopes granted for the access token. */ scope: string; - /** - * A refresh token issued for the signed in user - */ - refreshToken?: string; }; export type OAuthState = { @@ -130,7 +126,10 @@ export interface OAuthHandlers { * @param {string} refreshToken * @param {string} scope */ - refresh?(req: OAuthRefreshRequest): Promise; + refresh?(req: OAuthRefreshRequest): Promise<{ + response: OAuthResponse; + refreshToken?: string; + }>; /** * (Optional) Sign out of the auth provider. diff --git a/plugins/auth-backend/src/providers/atlassian/provider.test.ts b/plugins/auth-backend/src/providers/atlassian/provider.test.ts index 7bed582f81..29241dd17b 100644 --- a/plugins/auth-backend/src/providers/atlassian/provider.test.ts +++ b/plugins/auth-backend/src/providers/atlassian/provider.test.ts @@ -78,20 +78,22 @@ describe('createAtlassianProvider', () => { refreshToken: 'wacka', }, }); - const { response } = await provider.handler({} as any); - expect(response).toEqual({ - providerInfo: { - accessToken: 'accessToken', - expiresInSeconds: 123, - idToken: 'idToken', - scope: 'scope', - refreshToken: 'wacka', - }, - profile: { - email: 'conrad@example.com', - displayName: 'Conrad', - picture: 'http://google.com/lols', + const result = await provider.handler({} as any); + expect(result).toEqual({ + response: { + providerInfo: { + accessToken: 'accessToken', + expiresInSeconds: 123, + idToken: 'idToken', + scope: 'scope', + }, + profile: { + email: 'conrad@example.com', + displayName: 'Conrad', + picture: 'http://google.com/lols', + }, }, + refreshToken: 'wacka', }); }); @@ -127,20 +129,22 @@ describe('createAtlassianProvider', () => { ], }); - const response = await provider.refresh({} as any); + const result = await provider.refresh({} as any); - expect(response).toEqual({ - profile: { - displayName: 'Mocked User', - email: 'mockuser@gmail.com', - picture: 'http://google.com/lols', - }, - providerInfo: { - accessToken: 'a.b.c', - idToken: 'my-id', - refreshToken: 'dont-forget-to-send-refresh', - scope: 'read_user', + expect(result).toEqual({ + response: { + profile: { + displayName: 'Mocked User', + email: 'mockuser@gmail.com', + picture: 'http://google.com/lols', + }, + providerInfo: { + accessToken: 'a.b.c', + idToken: 'my-id', + scope: 'read_user', + }, }, + refreshToken: 'dont-forget-to-send-refresh', }); }); }); diff --git a/plugins/auth-backend/src/providers/atlassian/provider.ts b/plugins/auth-backend/src/providers/atlassian/provider.ts index e19f29a3a4..ba402eace0 100644 --- a/plugins/auth-backend/src/providers/atlassian/provider.ts +++ b/plugins/auth-backend/src/providers/atlassian/provider.ts @@ -107,9 +107,7 @@ export class AtlassianAuthProvider implements OAuthHandlers { }); } - async handler( - req: express.Request, - ): Promise<{ response: OAuthResponse; refreshToken: string }> { + async handler(req: express.Request) { const { result } = await executeFrameHandlerStrategy( req, this._strategy, @@ -117,7 +115,7 @@ export class AtlassianAuthProvider implements OAuthHandlers { return { response: await this.handleResult(result), - refreshToken: result.refreshToken ?? '', + refreshToken: result.refreshToken, }; } @@ -128,7 +126,6 @@ export class AtlassianAuthProvider implements OAuthHandlers { providerInfo: { idToken: result.params.id_token, accessToken: result.accessToken, - refreshToken: result.refreshToken, scope: result.params.scope, expiresInSeconds: result.params.expires_in, }, @@ -152,28 +149,27 @@ export class AtlassianAuthProvider implements OAuthHandlers { return response; } - async refresh(req: OAuthRefreshRequest): Promise { - const { - accessToken, - params, - refreshToken: newRefreshToken, - } = await executeRefreshTokenStrategy( - this._strategy, - req.refreshToken, - req.scope, - ); + async refresh(req: OAuthRefreshRequest) { + const { accessToken, params, refreshToken } = + await executeRefreshTokenStrategy( + this._strategy, + req.refreshToken, + req.scope, + ); const fullProfile = await executeFetchUserProfileStrategy( this._strategy, accessToken, ); - return this.handleResult({ - fullProfile, - params, - accessToken, - refreshToken: newRefreshToken, - }); + return { + response: await this.handleResult({ + fullProfile, + params, + accessToken, + }), + refreshToken, + }; } } diff --git a/plugins/auth-backend/src/providers/auth0/provider.ts b/plugins/auth-backend/src/providers/auth0/provider.ts index 583f95c621..4677e07b4d 100644 --- a/plugins/auth-backend/src/providers/auth0/provider.ts +++ b/plugins/auth-backend/src/providers/auth0/provider.ts @@ -113,9 +113,7 @@ export class Auth0AuthProvider implements OAuthHandlers { }); } - async handler( - req: express.Request, - ): Promise<{ response: OAuthResponse; refreshToken: string }> { + async handler(req: express.Request) { const { result, privateInfo } = await executeFrameHandlerStrategy< OAuthResult, PrivateInfo @@ -127,24 +125,27 @@ export class Auth0AuthProvider implements OAuthHandlers { }; } - async refresh(req: OAuthRefreshRequest): Promise { - const { accessToken, params } = await executeRefreshTokenStrategy( - this._strategy, - req.refreshToken, - req.scope, - ); + async refresh(req: OAuthRefreshRequest) { + const { accessToken, refreshToken, params } = + await executeRefreshTokenStrategy( + this._strategy, + req.refreshToken, + req.scope, + ); const fullProfile = await executeFetchUserProfileStrategy( this._strategy, accessToken, ); - return this.handleResult({ - fullProfile, - params, - accessToken, - refreshToken: req.refreshToken, - }); + return { + response: await this.handleResult({ + fullProfile, + params, + accessToken, + }), + refreshToken, + }; } private async handleResult(result: OAuthResult) { diff --git a/plugins/auth-backend/src/providers/bitbucket/provider.ts b/plugins/auth-backend/src/providers/bitbucket/provider.ts index 3a518a6baa..5ad9a739d9 100644 --- a/plugins/auth-backend/src/providers/bitbucket/provider.ts +++ b/plugins/auth-backend/src/providers/bitbucket/provider.ts @@ -138,9 +138,7 @@ export class BitbucketAuthProvider implements OAuthHandlers { }); } - async handler( - req: express.Request, - ): Promise<{ response: OAuthResponse; refreshToken: string }> { + async handler(req: express.Request) { const { result, privateInfo } = await executeFrameHandlerStrategy< OAuthResult, PrivateInfo @@ -152,22 +150,25 @@ export class BitbucketAuthProvider implements OAuthHandlers { }; } - async refresh(req: OAuthRefreshRequest): Promise { - const { accessToken, params } = await executeRefreshTokenStrategy( - this._strategy, - req.refreshToken, - req.scope, - ); + async refresh(req: OAuthRefreshRequest) { + const { accessToken, refreshToken, params } = + await executeRefreshTokenStrategy( + this._strategy, + req.refreshToken, + req.scope, + ); const fullProfile = await executeFetchUserProfileStrategy( this._strategy, accessToken, ); - return this.handleResult({ - fullProfile, - params, - accessToken, - refreshToken: req.refreshToken, - }); + return { + response: await this.handleResult({ + fullProfile, + params, + accessToken, + }), + refreshToken, + }; } private async handleResult(result: BitbucketOAuthResult) { diff --git a/plugins/auth-backend/src/providers/github/provider.test.ts b/plugins/auth-backend/src/providers/github/provider.test.ts index e418ab22c2..b11ac7f5a1 100644 --- a/plugins/auth-backend/src/providers/github/provider.test.ts +++ b/plugins/auth-backend/src/providers/github/provider.test.ts @@ -316,24 +316,26 @@ describe('GithubAuthProvider', () => { ], }); - const response = await provider.refresh({} as any); + const result = await provider.refresh({} as any); - expect(response).toEqual({ - backstageIdentity: { - id: 'mockuser', - token: 'token-for-mockuser', - }, - profile: { - displayName: 'Mocked User', - email: 'mockuser@gmail.com', - picture: undefined, - }, - providerInfo: { - accessToken: 'a.b.c', - refreshToken: 'dont-forget-to-send-refresh', - expiresInSeconds: 123, - scope: 'read_user', + expect(result).toEqual({ + response: { + backstageIdentity: { + id: 'mockuser', + token: 'token-for-mockuser', + }, + profile: { + displayName: 'Mocked User', + email: 'mockuser@gmail.com', + picture: undefined, + }, + providerInfo: { + accessToken: 'a.b.c', + expiresInSeconds: 123, + scope: 'read_user', + }, }, + refreshToken: 'dont-forget-to-send-refresh', }); }); }); diff --git a/plugins/auth-backend/src/providers/github/provider.ts b/plugins/auth-backend/src/providers/github/provider.ts index c7e82cc5ff..b832f4f77b 100644 --- a/plugins/auth-backend/src/providers/github/provider.ts +++ b/plugins/auth-backend/src/providers/github/provider.ts @@ -129,26 +129,26 @@ export class GithubAuthProvider implements OAuthHandlers { }; } - async refresh(req: OAuthRefreshRequest): Promise { - const { - accessToken, - refreshToken: newRefreshToken, - params, - } = await executeRefreshTokenStrategy( - this._strategy, - req.refreshToken, - req.scope, - ); + async refresh(req: OAuthRefreshRequest) { + const { accessToken, refreshToken, params } = + await executeRefreshTokenStrategy( + this._strategy, + req.refreshToken, + req.scope, + ); const fullProfile = await executeFetchUserProfileStrategy( this._strategy, accessToken, ); - return this.handleResult({ - fullProfile, - params, - accessToken, - refreshToken: newRefreshToken, - }); + + return { + response: await this.handleResult({ + fullProfile, + params, + accessToken, + }), + refreshToken, + }; } private async handleResult(result: GithubOAuthResult) { @@ -158,7 +158,6 @@ export class GithubAuthProvider implements OAuthHandlers { const response: OAuthResponse = { providerInfo: { accessToken: result.accessToken, - refreshToken: result.refreshToken, // GitHub expires the old refresh token when used scope: result.params.scope, expiresInSeconds: expiresInStr === undefined ? undefined : Number(expiresInStr), diff --git a/plugins/auth-backend/src/providers/gitlab/provider.test.ts b/plugins/auth-backend/src/providers/gitlab/provider.test.ts index d1a84b43e8..f90de3c75b 100644 --- a/plugins/auth-backend/src/providers/gitlab/provider.test.ts +++ b/plugins/auth-backend/src/providers/gitlab/provider.test.ts @@ -184,23 +184,25 @@ describe('GitlabAuthProvider', () => { ], }); - const response = await provider.refresh({} as any); + const result = await provider.refresh({} as any); - expect(response).toEqual({ - backstageIdentity: { - id: 'mockuser', - }, - profile: { - displayName: 'Mocked User', - email: 'mockuser@gmail.com', - picture: 'http://gitlab.com/lols', - }, - providerInfo: { - accessToken: 'a.b.c', - idToken: 'my-id', - refreshToken: 'dont-forget-to-send-refresh', - scope: 'read_user', + expect(result).toEqual({ + response: { + backstageIdentity: { + id: 'mockuser', + }, + profile: { + displayName: 'Mocked User', + email: 'mockuser@gmail.com', + picture: 'http://gitlab.com/lols', + }, + providerInfo: { + accessToken: 'a.b.c', + idToken: 'my-id', + scope: 'read_user', + }, }, + refreshToken: 'dont-forget-to-send-refresh', }); }); }); diff --git a/plugins/auth-backend/src/providers/gitlab/provider.ts b/plugins/auth-backend/src/providers/gitlab/provider.ts index 6ca405d15f..dedbb7bd5b 100644 --- a/plugins/auth-backend/src/providers/gitlab/provider.ts +++ b/plugins/auth-backend/src/providers/gitlab/provider.ts @@ -132,9 +132,7 @@ export class GitlabAuthProvider implements OAuthHandlers { }); } - async handler( - req: express.Request, - ): Promise<{ response: OAuthResponse; refreshToken: string }> { + async handler(req: express.Request) { const { result, privateInfo } = await executeFrameHandlerStrategy< OAuthResult, PrivateInfo @@ -146,28 +144,26 @@ export class GitlabAuthProvider implements OAuthHandlers { }; } - async refresh(req: OAuthRefreshRequest): Promise { - const { - accessToken, - refreshToken: newRefreshToken, - params, - } = await executeRefreshTokenStrategy( - this._strategy, - req.refreshToken, - req.scope, - ); + async refresh(req: OAuthRefreshRequest) { + const { accessToken, refreshToken, params } = + await executeRefreshTokenStrategy( + this._strategy, + req.refreshToken, + req.scope, + ); const fullProfile = await executeFetchUserProfileStrategy( this._strategy, accessToken, ); - - return this.handleResult({ - fullProfile, - params, - accessToken, - refreshToken: newRefreshToken, - }); + return { + response: await this.handleResult({ + fullProfile, + params, + accessToken, + }), + refreshToken, + }; } private async handleResult(result: OAuthResult): Promise { @@ -177,7 +173,6 @@ export class GitlabAuthProvider implements OAuthHandlers { providerInfo: { idToken: result.params.id_token, accessToken: result.accessToken, - refreshToken: result.refreshToken, // GitLab expires the old refresh token when used scope: result.params.scope, expiresInSeconds: result.params.expires_in, }, diff --git a/plugins/auth-backend/src/providers/google/provider.ts b/plugins/auth-backend/src/providers/google/provider.ts index d85fc2de2d..10123bfe6f 100644 --- a/plugins/auth-backend/src/providers/google/provider.ts +++ b/plugins/auth-backend/src/providers/google/provider.ts @@ -113,9 +113,7 @@ export class GoogleAuthProvider implements OAuthHandlers { }); } - async handler( - req: express.Request, - ): Promise<{ response: OAuthResponse; refreshToken: string }> { + async handler(req: express.Request) { const { result, privateInfo } = await executeFrameHandlerStrategy< OAuthResult, PrivateInfo @@ -127,22 +125,26 @@ export class GoogleAuthProvider implements OAuthHandlers { }; } - async refresh(req: OAuthRefreshRequest): Promise { - const { accessToken, params } = await executeRefreshTokenStrategy( - this._strategy, - req.refreshToken, - req.scope, - ); + async refresh(req: OAuthRefreshRequest) { + const { accessToken, refreshToken, params } = + await executeRefreshTokenStrategy( + this._strategy, + req.refreshToken, + req.scope, + ); const fullProfile = await executeFetchUserProfileStrategy( this._strategy, accessToken, ); - return this.handleResult({ - fullProfile, - params, - accessToken, - refreshToken: req.refreshToken, - }); + + return { + response: await this.handleResult({ + fullProfile, + params, + accessToken, + }), + refreshToken, + }; } private async handleResult(result: OAuthResult) { diff --git a/plugins/auth-backend/src/providers/microsoft/provider.ts b/plugins/auth-backend/src/providers/microsoft/provider.ts index b21218c066..7e928a2786 100644 --- a/plugins/auth-backend/src/providers/microsoft/provider.ts +++ b/plugins/auth-backend/src/providers/microsoft/provider.ts @@ -104,9 +104,7 @@ export class MicrosoftAuthProvider implements OAuthHandlers { }); } - async handler( - req: express.Request, - ): Promise<{ response: OAuthResponse; refreshToken: string }> { + async handler(req: express.Request) { const { result, privateInfo } = await executeFrameHandlerStrategy< OAuthResult, PrivateInfo @@ -118,24 +116,27 @@ export class MicrosoftAuthProvider implements OAuthHandlers { }; } - async refresh(req: OAuthRefreshRequest): Promise { - const { accessToken, params } = await executeRefreshTokenStrategy( - this._strategy, - req.refreshToken, - req.scope, - ); + async refresh(req: OAuthRefreshRequest) { + const { accessToken, refreshToken, params } = + await executeRefreshTokenStrategy( + this._strategy, + req.refreshToken, + req.scope, + ); const fullProfile = await executeFetchUserProfileStrategy( this._strategy, accessToken, ); - return this.handleResult({ - fullProfile, - params, - accessToken, - refreshToken: req.refreshToken, - }); + return { + response: await this.handleResult({ + fullProfile, + params, + accessToken, + }), + refreshToken, + }; } private async handleResult(result: OAuthResult) { diff --git a/plugins/auth-backend/src/providers/oauth2/provider.ts b/plugins/auth-backend/src/providers/oauth2/provider.ts index 2f9c739860..dc4afbac48 100644 --- a/plugins/auth-backend/src/providers/oauth2/provider.ts +++ b/plugins/auth-backend/src/providers/oauth2/provider.ts @@ -127,9 +127,7 @@ export class OAuth2AuthProvider implements OAuthHandlers { }); } - async handler( - req: express.Request, - ): Promise<{ response: OAuthResponse; refreshToken: string }> { + async handler(req: express.Request) { const { result, privateInfo } = await executeFrameHandlerStrategy< OAuthResult, PrivateInfo @@ -141,29 +139,27 @@ export class OAuth2AuthProvider implements OAuthHandlers { }; } - async refresh(req: OAuthRefreshRequest): Promise { + async refresh(req: OAuthRefreshRequest) { const refreshTokenResponse = await executeRefreshTokenStrategy( this._strategy, req.refreshToken, req.scope, ); - const { - accessToken, - params, - refreshToken: updatedRefreshToken, - } = refreshTokenResponse; + const { accessToken, params, refreshToken } = refreshTokenResponse; const fullProfile = await executeFetchUserProfileStrategy( this._strategy, accessToken, ); - return this.handleResult({ - fullProfile, - params, - accessToken, - refreshToken: updatedRefreshToken, - }); + return { + response: await this.handleResult({ + fullProfile, + params, + accessToken, + }), + refreshToken, + }; } private async handleResult(result: OAuthResult) { @@ -175,7 +171,6 @@ export class OAuth2AuthProvider implements OAuthHandlers { accessToken: result.accessToken, scope: result.params.scope, expiresInSeconds: result.params.expires_in, - refreshToken: result.refreshToken, }, profile, }; diff --git a/plugins/auth-backend/src/providers/oidc/provider.ts b/plugins/auth-backend/src/providers/oidc/provider.ts index fe4c042500..e5bfd14f40 100644 --- a/plugins/auth-backend/src/providers/oidc/provider.ts +++ b/plugins/auth-backend/src/providers/oidc/provider.ts @@ -112,34 +112,31 @@ export class OidcAuthProvider implements OAuthHandlers { return await executeRedirectStrategy(req, strategy, options); } - async handler( - req: express.Request, - ): Promise<{ response: OAuthResponse; refreshToken?: string }> { + async handler(req: express.Request) { const { strategy } = await this.implementation; - const strategyResponse = await executeFrameHandlerStrategy< + const { result, privateInfo } = await executeFrameHandlerStrategy< OidcAuthResult, PrivateInfo >(req, strategy); - const { - result: { userinfo, tokenset }, - privateInfo, - } = strategyResponse; - const identityResponse = await this.handleResult({ tokenset, userinfo }); return { - response: identityResponse, + response: await this.handleResult(result), refreshToken: privateInfo.refreshToken, }; } - async refresh(req: OAuthRefreshRequest): Promise { + async refresh(req: OAuthRefreshRequest) { const { client } = await this.implementation; const tokenset = await client.refresh(req.refreshToken); if (!tokenset.access_token) { throw new Error('Refresh failed'); } - const profile = await client.userinfo(tokenset.access_token); - return this.handleResult({ tokenset, userinfo: profile }); + const userinfo = await client.userinfo(tokenset.access_token); + + return { + response: await this.handleResult({ tokenset, userinfo }), + refreshToken: tokenset.refresh_token, + }; } private async setupStrategy(options: Options): Promise { @@ -190,7 +187,6 @@ export class OidcAuthProvider implements OAuthHandlers { providerInfo: { idToken: result.tokenset.id_token, accessToken: result.tokenset.access_token!, - refreshToken: result.tokenset.refresh_token, scope: result.tokenset.scope!, expiresInSeconds: result.tokenset.expires_in, }, diff --git a/plugins/auth-backend/src/providers/okta/provider.ts b/plugins/auth-backend/src/providers/okta/provider.ts index 69bbbd8c84..1c74e171ad 100644 --- a/plugins/auth-backend/src/providers/okta/provider.ts +++ b/plugins/auth-backend/src/providers/okta/provider.ts @@ -133,9 +133,7 @@ export class OktaAuthProvider implements OAuthHandlers { }); } - async handler( - req: express.Request, - ): Promise<{ response: OAuthResponse; refreshToken: string }> { + async handler(req: express.Request) { const { result, privateInfo } = await executeFrameHandlerStrategy< OAuthResult, PrivateInfo @@ -147,7 +145,7 @@ export class OktaAuthProvider implements OAuthHandlers { }; } - async refresh(req: OAuthRefreshRequest): Promise { + async refresh(req: OAuthRefreshRequest) { const { accessToken, refreshToken, params } = await executeRefreshTokenStrategy( this._strategy, @@ -160,12 +158,14 @@ export class OktaAuthProvider implements OAuthHandlers { accessToken, ); - return this.handleResult({ - fullProfile, - params, - accessToken, + return { + response: await this.handleResult({ + fullProfile, + params, + accessToken, + }), refreshToken, - }); + }; } private async handleResult(result: OAuthResult) { @@ -177,7 +177,6 @@ export class OktaAuthProvider implements OAuthHandlers { accessToken: result.accessToken, scope: result.params.scope, expiresInSeconds: result.params.expires_in, - refreshToken: result.refreshToken, }, profile, }; diff --git a/plugins/auth-backend/src/providers/onelogin/provider.ts b/plugins/auth-backend/src/providers/onelogin/provider.ts index 8cb06ea7a7..df20982f30 100644 --- a/plugins/auth-backend/src/providers/onelogin/provider.ts +++ b/plugins/auth-backend/src/providers/onelogin/provider.ts @@ -112,9 +112,7 @@ export class OneLoginProvider implements OAuthHandlers { }); } - async handler( - req: express.Request, - ): Promise<{ response: OAuthResponse; refreshToken: string }> { + async handler(req: express.Request) { const { result, privateInfo } = await executeFrameHandlerStrategy< OAuthResult, PrivateInfo @@ -126,23 +124,27 @@ export class OneLoginProvider implements OAuthHandlers { }; } - async refresh(req: OAuthRefreshRequest): Promise { - const { accessToken, params } = await executeRefreshTokenStrategy( - this._strategy, - req.refreshToken, - req.scope, - ); + async refresh(req: OAuthRefreshRequest) { + const { accessToken, refreshToken, params } = + await executeRefreshTokenStrategy( + this._strategy, + req.refreshToken, + req.scope, + ); const fullProfile = await executeFetchUserProfileStrategy( this._strategy, accessToken, ); - return this.handleResult({ - fullProfile, - params, - accessToken, - }); + return { + response: await this.handleResult({ + fullProfile, + params, + accessToken, + }), + refreshToken, + }; } private async handleResult(result: OAuthResult) { From 613ad12960e53ce842e6a79574a62f7d6c04c890 Mon Sep 17 00:00:00 2001 From: Tim Hansen Date: Tue, 28 Dec 2021 09:30:37 -0700 Subject: [PATCH 56/57] Add changeset Signed-off-by: Tim Hansen --- .changeset/modern-waves-lay.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/modern-waves-lay.md diff --git a/.changeset/modern-waves-lay.md b/.changeset/modern-waves-lay.md new file mode 100644 index 0000000000..c0d9804379 --- /dev/null +++ b/.changeset/modern-waves-lay.md @@ -0,0 +1,5 @@ +--- +'@backstage/create-app': patch +--- + +Add a comment to the default backend about the fallback 404 handler. From 77a5d0fb6fce6e388f16b08b54c3bc72e260f476 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 28 Dec 2021 17:37:55 +0100 Subject: [PATCH 57/57] auth-backend: let adapter populate identity token for auth0 and onelogin Signed-off-by: Patrik Oldsberg --- plugins/auth-backend/src/providers/auth0/provider.ts | 11 ++--------- .../auth-backend/src/providers/onelogin/provider.ts | 11 ++--------- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/plugins/auth-backend/src/providers/auth0/provider.ts b/plugins/auth-backend/src/providers/auth0/provider.ts index 583f95c621..f14d1b8fd5 100644 --- a/plugins/auth-backend/src/providers/auth0/provider.ts +++ b/plugins/auth-backend/src/providers/auth0/provider.ts @@ -178,10 +178,7 @@ export class Auth0AuthProvider implements OAuthHandlers { } } -const defaultSignInResolver: SignInResolver = async ( - info, - ctx, -) => { +const defaultSignInResolver: SignInResolver = async info => { const { profile } = info; if (!profile.email) { @@ -190,11 +187,7 @@ const defaultSignInResolver: SignInResolver = async ( const id = profile.email.split('@')[0]; - const token = await ctx.tokenIssuer.issueToken({ - claims: { sub: id, ent: [`user:default/${id}`] }, - }); - - return { id, token }; + return { id, token: '' }; }; /** @public */ diff --git a/plugins/auth-backend/src/providers/onelogin/provider.ts b/plugins/auth-backend/src/providers/onelogin/provider.ts index 8cb06ea7a7..4f52e31f5b 100644 --- a/plugins/auth-backend/src/providers/onelogin/provider.ts +++ b/plugins/auth-backend/src/providers/onelogin/provider.ts @@ -176,10 +176,7 @@ export class OneLoginProvider implements OAuthHandlers { } } -const defaultSignInResolver: SignInResolver = async ( - info, - ctx, -) => { +const defaultSignInResolver: SignInResolver = async info => { const { profile } = info; if (!profile.email) { @@ -188,11 +185,7 @@ const defaultSignInResolver: SignInResolver = async ( const id = profile.email.split('@')[0]; - const token = await ctx.tokenIssuer.issueToken({ - claims: { sub: id, ent: [`user:default/${id}`] }, - }); - - return { id, token }; + return { id, token: '' }; }; /** @public */