From 00c87518042b269101079ad63f6fbea2fadd84ab Mon Sep 17 00:00:00 2001 From: Philipp Hugenroth Date: Thu, 8 Jul 2021 13:17:40 +0200 Subject: [PATCH 001/106] Wrap catalog filters in accordion for smaller screens Signed-off-by: Philipp Hugenroth --- .../CatalogFilter/CatalogFilter.tsx | 76 +++++++++++++++++++ .../src/components/CatalogFilter/index.ts | 17 +++++ .../components/CatalogPage/CatalogPage.tsx | 64 ++++++---------- 3 files changed, 117 insertions(+), 40 deletions(-) create mode 100644 plugins/catalog/src/components/CatalogFilter/CatalogFilter.tsx create mode 100644 plugins/catalog/src/components/CatalogFilter/index.ts diff --git a/plugins/catalog/src/components/CatalogFilter/CatalogFilter.tsx b/plugins/catalog/src/components/CatalogFilter/CatalogFilter.tsx new file mode 100644 index 0000000000..a8673b395a --- /dev/null +++ b/plugins/catalog/src/components/CatalogFilter/CatalogFilter.tsx @@ -0,0 +1,76 @@ +/* + * 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. + */ + +import { + EntityKindPicker, + EntityLifecyclePicker, + EntityOwnerPicker, + EntityTagPicker, + EntityTypePicker, + UserListFilterKind, + UserListPicker, +} from '@backstage/plugin-catalog-react'; +import { + Accordion, + AccordionDetails, + AccordionSummary, + Grid, + isWidthDown, + Typography, + withWidth, +} from '@material-ui/core'; +import { Breakpoint } from '@material-ui/core/styles/createBreakpoints'; +import ExpandMore from '@material-ui/icons/ExpandMore'; +import React, { PropsWithChildren } from 'react'; + +interface IProps { + initiallySelectedFilter?: UserListFilterKind; +} + +const CatalogFilterWrapper = withWidth()( + ({ width, children }: PropsWithChildren<{ width: Breakpoint }>) => + isWidthDown('md', width) ? ( + + }> + Filters + + {children} + + ) : ( + {children} + ), +); + +export const CatalogFilter = ({ + initiallySelectedFilter = 'owned', +}: IProps) => ( + + + + + + + + + + + + + + +); diff --git a/plugins/catalog/src/components/CatalogFilter/index.ts b/plugins/catalog/src/components/CatalogFilter/index.ts new file mode 100644 index 0000000000..dc57ef02e2 --- /dev/null +++ b/plugins/catalog/src/components/CatalogFilter/index.ts @@ -0,0 +1,17 @@ +/* + * 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 { CatalogFilter } from './CatalogFilter'; diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx index 089e61f0fb..6f8dd0dd2b 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx @@ -15,16 +15,10 @@ */ import React from 'react'; -import { Grid } from '@material-ui/core'; +import { Grid, withWidth } from '@material-ui/core'; import { - EntityKindPicker, - EntityLifecyclePicker, EntityListProvider, - EntityOwnerPicker, - EntityTagPicker, - EntityTypePicker, UserListFilterKind, - UserListPicker, } from '@backstage/plugin-catalog-react'; import { CatalogTable } from '../CatalogTable'; @@ -38,6 +32,7 @@ import { TableColumn, TableProps, } from '@backstage/core-components'; +import { CatalogFilter } from '../CatalogFilter'; export type CatalogPageProps = { initiallySelectedFilter?: UserListFilterKind; @@ -45,40 +40,29 @@ export type CatalogPageProps = { actions?: TableProps['actions']; }; -export const CatalogPage = ({ - initiallySelectedFilter = 'owned', - columns, - actions, -}: CatalogPageProps) => ( - - - - - All your software catalog entities - - - - - - - - - - + + + ); + }, ); From 4fe222ff389841655682ea5e4e050751d95b2139 Mon Sep 17 00:00:00 2001 From: Philipp Hugenroth Date: Thu, 8 Jul 2021 13:37:00 +0200 Subject: [PATCH 002/106] Add changeset Signed-off-by: Philipp Hugenroth --- .changeset/fuzzy-wolves-travel.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/fuzzy-wolves-travel.md diff --git a/.changeset/fuzzy-wolves-travel.md b/.changeset/fuzzy-wolves-travel.md new file mode 100644 index 0000000000..6b51be64c0 --- /dev/null +++ b/.changeset/fuzzy-wolves-travel.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog': patch +--- + +Move filter to CatalogFilter component & wrap them in an accordion on smaller screens to improve the UX From 9eaabc365509451a89227589c63cbb2e6c0d05ef Mon Sep 17 00:00:00 2001 From: Philipp Hugenroth Date: Thu, 8 Jul 2021 13:54:27 +0200 Subject: [PATCH 003/106] Remove unused theme HOC Signed-off-by: Philipp Hugenroth --- .../components/CatalogPage/CatalogPage.tsx | 56 ++++++++++--------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx index 6f8dd0dd2b..a97116dd80 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx @@ -15,7 +15,7 @@ */ import React from 'react'; -import { Grid, withWidth } from '@material-ui/core'; +import { Grid } from '@material-ui/core'; import { EntityListProvider, UserListFilterKind, @@ -40,29 +40,31 @@ export type CatalogPageProps = { actions?: TableProps['actions']; }; -export const CatalogPage = withWidth()( - ({ columns, actions, initiallySelectedFilter }: CatalogPageProps) => { - return ( - - - - - All your software catalog entities - - - - - - - - - - - - - - ); - }, -); +export const CatalogPage = ({ + columns, + actions, + initiallySelectedFilter, +}: CatalogPageProps) => { + return ( + + + + + All your software catalog entities + + + + + + + + + + + + + + ); +}; From 713949231f33689b4cddfaeb2afaf294f0897815 Mon Sep 17 00:00:00 2001 From: Philipp Hugenroth Date: Fri, 9 Jul 2021 11:51:19 +0200 Subject: [PATCH 004/106] Fix tests to support window.matchMedia & add tests for responsiveness Signed-off-by: Philipp Hugenroth --- .../src/testUtils/mockBreakpoint.ts | 34 ++++++++++---- .../CatalogPage/CatalogPage.test.tsx | 9 ++++ .../components/CatalogPage/CatalogPage.tsx | 46 +++++++++---------- 3 files changed, 55 insertions(+), 34 deletions(-) diff --git a/packages/test-utils/src/testUtils/mockBreakpoint.ts b/packages/test-utils/src/testUtils/mockBreakpoint.ts index 37f0e761b6..0e04f58f70 100644 --- a/packages/test-utils/src/testUtils/mockBreakpoint.ts +++ b/packages/test-utils/src/testUtils/mockBreakpoint.ts @@ -18,6 +18,8 @@ import { act } from '@testing-library/react'; type Breakpoint = 'xs' | 'sm' | 'md' | 'lg' | 'xl'; +const defaultBreakpoint: Breakpoint = 'xl'; + const queryToBreakpoint = { '(min-width:1920px)': 'xl', '(min-width:1280px)': 'lg', @@ -26,14 +28,17 @@ const queryToBreakpoint = { '(min-width:0px)': 'xs', } as Record; -function toBreakpoint(query: string) { - const breakpoint = queryToBreakpoint[query]; - if (!breakpoint) { - throw new Error( - `received unknown media query in breakpoint mock: '${query}'`, - ); - } - return breakpoint; +/** + * Converts media query string to Breakpoint. + * Chooses the default breakpoint if no breakpoint is set in the media query. + * + * @param query media query string + * @returns Breakpoint + */ +function toBreakpoint(query: string): Breakpoint { + return queryToBreakpoint[query] + ? queryToBreakpoint[query] + : defaultBreakpoint; } type Listener = (event: { matches: boolean }) => void; @@ -41,6 +46,8 @@ type Listener = (event: { matches: boolean }) => void; interface QueryList { addListener(listener: Listener): void; removeListener(listener: Listener): void; + addEventListener(listener: Listener): void; + removeEventListener(listener: Listener): void; matches: boolean; } @@ -50,12 +57,15 @@ interface Query { listeners: Set; } -export default function mockBreakpoint(initialBreakpoint: Breakpoint = 'xl') { +export default function mockBreakpoint( + initialBreakpoint: Breakpoint = defaultBreakpoint, +) { let currentBreakpoint = initialBreakpoint; const queries = Array(); const previousMatchMedia: any = (window as any).matchMedia; + // https://jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom (window as any).matchMedia = (query: string): QueryList => { const listeners = new Set(); @@ -66,6 +76,12 @@ export default function mockBreakpoint(initialBreakpoint: Breakpoint = 'xl') { removeListener(listener) { listeners.delete(listener); }, + addEventListener(listener) { + listeners.add(listener); + }, + removeEventListener(listener) { + listeners.delete(listener); + }, matches: toBreakpoint(query) === currentBreakpoint, }; diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx index 584eea0850..d5e31a16a7 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx @@ -26,6 +26,7 @@ import { MockStorageApi, renderWithEffects, wrapInTestApp, + mockBreakpoint, } from '@backstage/test-utils'; import { fireEvent, screen } from '@testing-library/react'; import React from 'react'; @@ -111,6 +112,8 @@ describe('CatalogPage', () => { getProfile: () => testProfile, }; + const { set: setBreakpoint } = mockBreakpoint(); + const renderWrapped = (children: React.ReactNode) => renderWithEffects( wrapInTestApp( @@ -244,4 +247,10 @@ describe('CatalogPage', () => { screen.findByText(/Starred \(1\)/), ).resolves.toBeInTheDocument(); }); + + it('should wrap filter in accordion on smaller screens', async () => { + setBreakpoint('sm'); + const { findByText } = await renderWrapped(); + await expect(findByText(/Filters/)).resolves.toBeInTheDocument(); + }); }); diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx index a97116dd80..9eeb91ca3c 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx @@ -43,28 +43,24 @@ export type CatalogPageProps = { export const CatalogPage = ({ columns, actions, - initiallySelectedFilter, -}: CatalogPageProps) => { - return ( - - - - - All your software catalog entities - - - - - - - - - - - - - - ); -}; + initiallySelectedFilter = 'owned', +}: CatalogPageProps) => ( + + + + + All your software catalog entities + + + + + + + + + + + + + +); From 84feb21147018aa37d2e0a6105216fd2fa11c10f Mon Sep 17 00:00:00 2001 From: Philipp Hugenroth Date: Thu, 8 Jul 2021 13:17:40 +0200 Subject: [PATCH 005/106] Wrap catalog filters in accordion for smaller screens Signed-off-by: Philipp Hugenroth --- .../CatalogFilter/CatalogFilter.tsx | 76 +++++++++++++++++++ .../src/components/CatalogFilter/index.ts | 17 +++++ .../components/CatalogPage/CatalogPage.tsx | 64 ++++++---------- 3 files changed, 117 insertions(+), 40 deletions(-) create mode 100644 plugins/catalog/src/components/CatalogFilter/CatalogFilter.tsx create mode 100644 plugins/catalog/src/components/CatalogFilter/index.ts diff --git a/plugins/catalog/src/components/CatalogFilter/CatalogFilter.tsx b/plugins/catalog/src/components/CatalogFilter/CatalogFilter.tsx new file mode 100644 index 0000000000..a8673b395a --- /dev/null +++ b/plugins/catalog/src/components/CatalogFilter/CatalogFilter.tsx @@ -0,0 +1,76 @@ +/* + * 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. + */ + +import { + EntityKindPicker, + EntityLifecyclePicker, + EntityOwnerPicker, + EntityTagPicker, + EntityTypePicker, + UserListFilterKind, + UserListPicker, +} from '@backstage/plugin-catalog-react'; +import { + Accordion, + AccordionDetails, + AccordionSummary, + Grid, + isWidthDown, + Typography, + withWidth, +} from '@material-ui/core'; +import { Breakpoint } from '@material-ui/core/styles/createBreakpoints'; +import ExpandMore from '@material-ui/icons/ExpandMore'; +import React, { PropsWithChildren } from 'react'; + +interface IProps { + initiallySelectedFilter?: UserListFilterKind; +} + +const CatalogFilterWrapper = withWidth()( + ({ width, children }: PropsWithChildren<{ width: Breakpoint }>) => + isWidthDown('md', width) ? ( + + }> + Filters + + {children} + + ) : ( + {children} + ), +); + +export const CatalogFilter = ({ + initiallySelectedFilter = 'owned', +}: IProps) => ( + + + + + + + + + + + + + + +); diff --git a/plugins/catalog/src/components/CatalogFilter/index.ts b/plugins/catalog/src/components/CatalogFilter/index.ts new file mode 100644 index 0000000000..dc57ef02e2 --- /dev/null +++ b/plugins/catalog/src/components/CatalogFilter/index.ts @@ -0,0 +1,17 @@ +/* + * 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 { CatalogFilter } from './CatalogFilter'; diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx index 089e61f0fb..6f8dd0dd2b 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx @@ -15,16 +15,10 @@ */ import React from 'react'; -import { Grid } from '@material-ui/core'; +import { Grid, withWidth } from '@material-ui/core'; import { - EntityKindPicker, - EntityLifecyclePicker, EntityListProvider, - EntityOwnerPicker, - EntityTagPicker, - EntityTypePicker, UserListFilterKind, - UserListPicker, } from '@backstage/plugin-catalog-react'; import { CatalogTable } from '../CatalogTable'; @@ -38,6 +32,7 @@ import { TableColumn, TableProps, } from '@backstage/core-components'; +import { CatalogFilter } from '../CatalogFilter'; export type CatalogPageProps = { initiallySelectedFilter?: UserListFilterKind; @@ -45,40 +40,29 @@ export type CatalogPageProps = { actions?: TableProps['actions']; }; -export const CatalogPage = ({ - initiallySelectedFilter = 'owned', - columns, - actions, -}: CatalogPageProps) => ( - - - - - All your software catalog entities - - - - - - - - - - + + + ); + }, ); From e50d7bf9ac62bbb6944ba76ab29523266eb2bc58 Mon Sep 17 00:00:00 2001 From: Philipp Hugenroth Date: Thu, 8 Jul 2021 13:37:00 +0200 Subject: [PATCH 006/106] Add changeset Signed-off-by: Philipp Hugenroth --- .changeset/fuzzy-wolves-travel.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/fuzzy-wolves-travel.md diff --git a/.changeset/fuzzy-wolves-travel.md b/.changeset/fuzzy-wolves-travel.md new file mode 100644 index 0000000000..6b51be64c0 --- /dev/null +++ b/.changeset/fuzzy-wolves-travel.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog': patch +--- + +Move filter to CatalogFilter component & wrap them in an accordion on smaller screens to improve the UX From 9cf8779ddafdf465e0857c725c118f2f82941ee5 Mon Sep 17 00:00:00 2001 From: Philipp Hugenroth Date: Thu, 8 Jul 2021 13:54:27 +0200 Subject: [PATCH 007/106] Remove unused theme HOC Signed-off-by: Philipp Hugenroth --- .../components/CatalogPage/CatalogPage.tsx | 56 ++++++++++--------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx index 6f8dd0dd2b..a97116dd80 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx @@ -15,7 +15,7 @@ */ import React from 'react'; -import { Grid, withWidth } from '@material-ui/core'; +import { Grid } from '@material-ui/core'; import { EntityListProvider, UserListFilterKind, @@ -40,29 +40,31 @@ export type CatalogPageProps = { actions?: TableProps['actions']; }; -export const CatalogPage = withWidth()( - ({ columns, actions, initiallySelectedFilter }: CatalogPageProps) => { - return ( - - - - - All your software catalog entities - - - - - - - - - - - - - - ); - }, -); +export const CatalogPage = ({ + columns, + actions, + initiallySelectedFilter, +}: CatalogPageProps) => { + return ( + + + + + All your software catalog entities + + + + + + + + + + + + + + ); +}; From db935a4c102d1e629c98c387ca1cf9879561be7b Mon Sep 17 00:00:00 2001 From: Philipp Hugenroth Date: Fri, 9 Jul 2021 11:51:19 +0200 Subject: [PATCH 008/106] Fix tests to support window.matchMedia & add tests for responsiveness Signed-off-by: Philipp Hugenroth --- .../src/testUtils/mockBreakpoint.ts | 34 ++++++++++---- .../CatalogPage/CatalogPage.test.tsx | 9 ++++ .../components/CatalogPage/CatalogPage.tsx | 46 +++++++++---------- 3 files changed, 55 insertions(+), 34 deletions(-) diff --git a/packages/test-utils/src/testUtils/mockBreakpoint.ts b/packages/test-utils/src/testUtils/mockBreakpoint.ts index 37f0e761b6..0e04f58f70 100644 --- a/packages/test-utils/src/testUtils/mockBreakpoint.ts +++ b/packages/test-utils/src/testUtils/mockBreakpoint.ts @@ -18,6 +18,8 @@ import { act } from '@testing-library/react'; type Breakpoint = 'xs' | 'sm' | 'md' | 'lg' | 'xl'; +const defaultBreakpoint: Breakpoint = 'xl'; + const queryToBreakpoint = { '(min-width:1920px)': 'xl', '(min-width:1280px)': 'lg', @@ -26,14 +28,17 @@ const queryToBreakpoint = { '(min-width:0px)': 'xs', } as Record; -function toBreakpoint(query: string) { - const breakpoint = queryToBreakpoint[query]; - if (!breakpoint) { - throw new Error( - `received unknown media query in breakpoint mock: '${query}'`, - ); - } - return breakpoint; +/** + * Converts media query string to Breakpoint. + * Chooses the default breakpoint if no breakpoint is set in the media query. + * + * @param query media query string + * @returns Breakpoint + */ +function toBreakpoint(query: string): Breakpoint { + return queryToBreakpoint[query] + ? queryToBreakpoint[query] + : defaultBreakpoint; } type Listener = (event: { matches: boolean }) => void; @@ -41,6 +46,8 @@ type Listener = (event: { matches: boolean }) => void; interface QueryList { addListener(listener: Listener): void; removeListener(listener: Listener): void; + addEventListener(listener: Listener): void; + removeEventListener(listener: Listener): void; matches: boolean; } @@ -50,12 +57,15 @@ interface Query { listeners: Set; } -export default function mockBreakpoint(initialBreakpoint: Breakpoint = 'xl') { +export default function mockBreakpoint( + initialBreakpoint: Breakpoint = defaultBreakpoint, +) { let currentBreakpoint = initialBreakpoint; const queries = Array(); const previousMatchMedia: any = (window as any).matchMedia; + // https://jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom (window as any).matchMedia = (query: string): QueryList => { const listeners = new Set(); @@ -66,6 +76,12 @@ export default function mockBreakpoint(initialBreakpoint: Breakpoint = 'xl') { removeListener(listener) { listeners.delete(listener); }, + addEventListener(listener) { + listeners.add(listener); + }, + removeEventListener(listener) { + listeners.delete(listener); + }, matches: toBreakpoint(query) === currentBreakpoint, }; diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx index 584eea0850..d5e31a16a7 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx @@ -26,6 +26,7 @@ import { MockStorageApi, renderWithEffects, wrapInTestApp, + mockBreakpoint, } from '@backstage/test-utils'; import { fireEvent, screen } from '@testing-library/react'; import React from 'react'; @@ -111,6 +112,8 @@ describe('CatalogPage', () => { getProfile: () => testProfile, }; + const { set: setBreakpoint } = mockBreakpoint(); + const renderWrapped = (children: React.ReactNode) => renderWithEffects( wrapInTestApp( @@ -244,4 +247,10 @@ describe('CatalogPage', () => { screen.findByText(/Starred \(1\)/), ).resolves.toBeInTheDocument(); }); + + it('should wrap filter in accordion on smaller screens', async () => { + setBreakpoint('sm'); + const { findByText } = await renderWrapped(); + await expect(findByText(/Filters/)).resolves.toBeInTheDocument(); + }); }); diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx index a97116dd80..9eeb91ca3c 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx @@ -43,28 +43,24 @@ export type CatalogPageProps = { export const CatalogPage = ({ columns, actions, - initiallySelectedFilter, -}: CatalogPageProps) => { - return ( - - - - - All your software catalog entities - - - - - - - - - - - - - - ); -}; + initiallySelectedFilter = 'owned', +}: CatalogPageProps) => ( + + + + + All your software catalog entities + + + + + + + + + + + + + +); From 2a295bcec92f465900c48d95be06b10029570842 Mon Sep 17 00:00:00 2001 From: Philipp Hugenroth Date: Mon, 12 Jul 2021 14:41:30 +0200 Subject: [PATCH 009/106] Add TablePage components - Change CatalogPage to use generic TablePage Signed-off-by: Philipp Hugenroth --- .../src/layout/Header/Header.tsx | 2 +- .../src/layout/TablePage/Layout.tsx | 37 ++++------ .../src/layout/TablePage/TablePage.tsx | 68 +++++++++++++++++++ .../src/layout/TablePage}/index.ts | 5 +- packages/core-components/src/layout/index.ts | 1 + .../components/CatalogPage/CatalogPage.tsx | 57 ++++++++-------- .../src/components/CatalogPage/index.ts | 1 - .../CreateComponentButton.tsx | 38 ----------- 8 files changed, 115 insertions(+), 94 deletions(-) rename plugins/catalog/src/components/CatalogPage/CatalogLayout.tsx => packages/core-components/src/layout/TablePage/Layout.tsx (52%) create mode 100644 packages/core-components/src/layout/TablePage/TablePage.tsx rename {plugins/catalog/src/components/CreateComponentButton => packages/core-components/src/layout/TablePage}/index.ts (84%) delete mode 100644 plugins/catalog/src/components/CreateComponentButton/CreateComponentButton.tsx diff --git a/packages/core-components/src/layout/Header/Header.tsx b/packages/core-components/src/layout/Header/Header.tsx index 96d024f524..8c51f2f7fc 100644 --- a/packages/core-components/src/layout/Header/Header.tsx +++ b/packages/core-components/src/layout/Header/Header.tsx @@ -88,7 +88,7 @@ const useStyles = makeStyles(theme => ({ type HeaderStyles = ReturnType; -type Props = { +export type Props = { component?: ReactNode; pageTitleOverride?: string; style?: CSSProperties; diff --git a/plugins/catalog/src/components/CatalogPage/CatalogLayout.tsx b/packages/core-components/src/layout/TablePage/Layout.tsx similarity index 52% rename from plugins/catalog/src/components/CatalogPage/CatalogLayout.tsx rename to packages/core-components/src/layout/TablePage/Layout.tsx index 2471ac9df2..79ad9c24b8 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogLayout.tsx +++ b/packages/core-components/src/layout/TablePage/Layout.tsx @@ -16,27 +16,20 @@ import React from 'react'; -import { configApiRef, useApi } from '@backstage/core-plugin-api'; -import { Header, Page } from '@backstage/core-components'; +import { Header, Page } from '../../'; +import { Props as HeaderProps } from '../../layout/Header/Header'; -type Props = { - children?: React.ReactNode; -}; +export interface IProps extends HeaderProps { + themeId: string; +} -export const CatalogLayout = ({ children }: Props) => { - const orgName = - useApi(configApiRef).getOptionalString('organization.name') ?? 'Backstage'; - - return ( - -
- {children} - - ); -}; - -export default CatalogLayout; +export const Layout = ({ + themeId, + children, + ...props +}: React.PropsWithChildren) => ( + +
+ {children} + +); diff --git a/packages/core-components/src/layout/TablePage/TablePage.tsx b/packages/core-components/src/layout/TablePage/TablePage.tsx new file mode 100644 index 0000000000..f58da0b553 --- /dev/null +++ b/packages/core-components/src/layout/TablePage/TablePage.tsx @@ -0,0 +1,68 @@ +/* + * 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. + */ + +import React from 'react'; +import { Grid } from '@material-ui/core'; + +import { Content, ContentHeader, SupportButton, Button } from '../..'; +import { Layout, IProps as LayoutProps } from './Layout'; +import { Link as RouterLink } from 'react-router-dom'; + +interface IProps extends LayoutProps { + supportMessage: string; + filter: React.ReactNode; + header: string; + headerLink?: string; +} + +const TablePageLink = ({ link }: { link?: string }) => { + return link ? ( + + ) : null; +}; + +export const TablePage = ({ + supportMessage, + filter, + children, + header, + headerLink, + ...props +}: React.PropsWithChildren) => ( + + + + + {supportMessage} + + + + {filter} + + + {children} + + + + +); diff --git a/plugins/catalog/src/components/CreateComponentButton/index.ts b/packages/core-components/src/layout/TablePage/index.ts similarity index 84% rename from plugins/catalog/src/components/CreateComponentButton/index.ts rename to packages/core-components/src/layout/TablePage/index.ts index e525bef782..5df8aa4c40 100644 --- a/plugins/catalog/src/components/CreateComponentButton/index.ts +++ b/packages/core-components/src/layout/TablePage/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 The Backstage Authors + * 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. @@ -13,4 +13,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { CreateComponentButton } from './CreateComponentButton'; + +export { TablePage } from './TablePage'; diff --git a/packages/core-components/src/layout/index.ts b/packages/core-components/src/layout/index.ts index 4abde642dc..dce33c7f2a 100644 --- a/packages/core-components/src/layout/index.ts +++ b/packages/core-components/src/layout/index.ts @@ -29,3 +29,4 @@ export * from './Sidebar'; export * from './SignInPage'; export * from './TabbedCard'; export * from './Breadcrumbs'; +export * from './TablePage'; diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx index 9eeb91ca3c..bd23e5ccdb 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx @@ -15,24 +15,17 @@ */ import React from 'react'; -import { Grid } from '@material-ui/core'; import { EntityListProvider, UserListFilterKind, } from '@backstage/plugin-catalog-react'; import { CatalogTable } from '../CatalogTable'; +import { configApiRef, useApi, useRouteRef } from '@backstage/core-plugin-api'; import { EntityRow } from '../CatalogTable/types'; -import CatalogLayout from './CatalogLayout'; -import { CreateComponentButton } from '../CreateComponentButton'; -import { - Content, - ContentHeader, - SupportButton, - TableColumn, - TableProps, -} from '@backstage/core-components'; +import { TableColumn, TableProps, TablePage } from '@backstage/core-components'; import { CatalogFilter } from '../CatalogFilter'; +import { createComponentRouteRef } from '../../routes'; export type CatalogPageProps = { initiallySelectedFilter?: UserListFilterKind; @@ -44,23 +37,27 @@ export const CatalogPage = ({ columns, actions, initiallySelectedFilter = 'owned', -}: CatalogPageProps) => ( - - - - - All your software catalog entities - - - - - - - - - - - - - -); +}: CatalogPageProps) => { + const orgName = + useApi(configApiRef).getOptionalString('organization.name') ?? 'Backstage'; + const createComponentLink = useRouteRef(createComponentRouteRef); + + return ( + + + } + title={`${orgName} Catalog`} + subtitle={`Catalog of software components at ${orgName}`} + themeId="home" + pageTitleOverride="Home" + headerLink={createComponentLink ? createComponentLink() : ''} + > + + + + ); +}; diff --git a/plugins/catalog/src/components/CatalogPage/index.ts b/plugins/catalog/src/components/CatalogPage/index.ts index 66c3c2f4b3..dc7ed68229 100644 --- a/plugins/catalog/src/components/CatalogPage/index.ts +++ b/plugins/catalog/src/components/CatalogPage/index.ts @@ -13,5 +13,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { CatalogLayout } from './CatalogLayout'; export { CatalogPage } from './CatalogPage'; diff --git a/plugins/catalog/src/components/CreateComponentButton/CreateComponentButton.tsx b/plugins/catalog/src/components/CreateComponentButton/CreateComponentButton.tsx deleted file mode 100644 index 482a0c0a3c..0000000000 --- a/plugins/catalog/src/components/CreateComponentButton/CreateComponentButton.tsx +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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 React from 'react'; -import { Link as RouterLink } from 'react-router-dom'; -import { Button } from '@material-ui/core'; -import { createComponentRouteRef } from '../../routes'; -import { useRouteRef } from '@backstage/core-plugin-api'; - -export const CreateComponentButton = () => { - const createComponentLink = useRouteRef(createComponentRouteRef); - - if (!createComponentLink) return null; - - return ( - - ); -}; From 48ec80da3ac8caec10b8529fd579610fb3cb6880 Mon Sep 17 00:00:00 2001 From: Philipp Hugenroth Date: Mon, 12 Jul 2021 17:27:44 +0200 Subject: [PATCH 010/106] Apply generic table page to ApiExplorer Signed-off-by: Philipp Hugenroth --- .../src/layout/TablePage/TablePage.tsx | 35 ++++--- .../ApiExplorerPage/ApiExplorerLayout.tsx | 40 -------- .../ApiExplorerPage/ApiExplorerPage.tsx | 94 +++++++------------ .../CatalogFilter/CatalogFilter.tsx | 4 +- .../components/CatalogPage/CatalogPage.tsx | 13 +-- plugins/catalog/src/index.ts | 3 +- 6 files changed, 68 insertions(+), 121 deletions(-) delete mode 100644 plugins/api-docs/src/components/ApiExplorerPage/ApiExplorerLayout.tsx diff --git a/packages/core-components/src/layout/TablePage/TablePage.tsx b/packages/core-components/src/layout/TablePage/TablePage.tsx index f58da0b553..5bf69f4e64 100644 --- a/packages/core-components/src/layout/TablePage/TablePage.tsx +++ b/packages/core-components/src/layout/TablePage/TablePage.tsx @@ -21,22 +21,27 @@ import { Content, ContentHeader, SupportButton, Button } from '../..'; import { Layout, IProps as LayoutProps } from './Layout'; import { Link as RouterLink } from 'react-router-dom'; -interface IProps extends LayoutProps { - supportMessage: string; - filter: React.ReactNode; - header: string; - headerLink?: string; +interface ILinkProps { + contentLink?: string; + contentLinkText?: string; } -const TablePageLink = ({ link }: { link?: string }) => { - return link ? ( +type IProps = LayoutProps & + ILinkProps & { + contentTitle: string; + supportMessage: string; + filter: React.ReactNode; + }; + +const TablePageLink = ({ contentLink, contentLinkText }: ILinkProps) => { + return contentLink && contentLinkText ? ( ) : null; }; @@ -45,14 +50,18 @@ export const TablePage = ({ supportMessage, filter, children, - header, - headerLink, + contentTitle, + contentLink, + contentLinkText, ...props }: React.PropsWithChildren) => ( - - + + {supportMessage} diff --git a/plugins/api-docs/src/components/ApiExplorerPage/ApiExplorerLayout.tsx b/plugins/api-docs/src/components/ApiExplorerPage/ApiExplorerLayout.tsx deleted file mode 100644 index 5638c23634..0000000000 --- a/plugins/api-docs/src/components/ApiExplorerPage/ApiExplorerLayout.tsx +++ /dev/null @@ -1,40 +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. - */ - -import React from 'react'; - -import { Header, Page } from '@backstage/core-components'; -import { useApi, configApiRef } from '@backstage/core-plugin-api'; - -type Props = { - children?: React.ReactNode; -}; -export const ApiExplorerLayout = ({ children }: Props) => { - const configApi = useApi(configApiRef); - const generatedSubtitle = `${ - configApi.getOptionalString('organization.name') ?? 'Backstage' - } API Explorer`; - return ( - -
- {children} - - ); -}; diff --git a/plugins/api-docs/src/components/ApiExplorerPage/ApiExplorerPage.tsx b/plugins/api-docs/src/components/ApiExplorerPage/ApiExplorerPage.tsx index 9a8b725ffc..bcc18e4021 100644 --- a/plugins/api-docs/src/components/ApiExplorerPage/ApiExplorerPage.tsx +++ b/plugins/api-docs/src/components/ApiExplorerPage/ApiExplorerPage.tsx @@ -14,39 +14,19 @@ * limitations under the License. */ +import { TableColumn, TablePage } from '@backstage/core-components'; +import { configApiRef, useApi, useRouteRef } from '@backstage/core-plugin-api'; +import { + CatalogFilter, + CatalogTable, + CatalogTableRow, +} from '@backstage/plugin-catalog'; import { - EntityKindPicker, - EntityLifecyclePicker, EntityListProvider, - EntityOwnerPicker, - EntityTagPicker, - EntityTypePicker, UserListFilterKind, - UserListPicker, } from '@backstage/plugin-catalog-react'; -import { CatalogTable, CatalogTableRow } from '@backstage/plugin-catalog'; -import { Button, makeStyles } from '@material-ui/core'; import React from 'react'; -import { Link as RouterLink } from 'react-router-dom'; import { createComponentRouteRef } from '../../routes'; -import { ApiExplorerLayout } from './ApiExplorerLayout'; - -import { - Content, - ContentHeader, - SupportButton, - TableColumn, -} from '@backstage/core-components'; -import { useRouteRef } from '@backstage/core-plugin-api'; - -const useStyles = makeStyles(theme => ({ - contentWrapper: { - display: 'grid', - gridTemplateAreas: "'filters' 'table'", - gridTemplateColumns: '250px 1fr', - gridColumnGap: theme.spacing(2), - }, -})); const defaultColumns: TableColumn[] = [ CatalogTable.columns.createNameColumn({ defaultKind: 'API' }), @@ -58,8 +38,11 @@ const defaultColumns: TableColumn[] = [ CatalogTable.columns.createTagsColumn(), ]; -export type ApiExplorerPageProps = { +interface IApiExplorerePageFilterProps { initiallySelectedFilter?: UserListFilterKind; +} + +export type ApiExplorerPageProps = IApiExplorerePageFilterProps & { columns?: TableColumn[]; }; @@ -67,39 +50,32 @@ export const ApiExplorerPage = ({ initiallySelectedFilter = 'all', columns, }: ApiExplorerPageProps) => { - const styles = useStyles(); const createComponentLink = useRouteRef(createComponentRouteRef); + const configApi = useApi(configApiRef); + const generatedSubtitle = `${ + configApi.getOptionalString('organization.name') ?? 'Backstage' + } API Explorer`; return ( - - - - {createComponentLink && ( - - )} - All your APIs - -
- -
-
- -
-
-
-
+ + + } + > + + + ); }; diff --git a/plugins/catalog/src/components/CatalogFilter/CatalogFilter.tsx b/plugins/catalog/src/components/CatalogFilter/CatalogFilter.tsx index a8673b395a..6704f429ee 100644 --- a/plugins/catalog/src/components/CatalogFilter/CatalogFilter.tsx +++ b/plugins/catalog/src/components/CatalogFilter/CatalogFilter.tsx @@ -38,6 +38,7 @@ import React, { PropsWithChildren } from 'react'; interface IProps { initiallySelectedFilter?: UserListFilterKind; + initialFilter?: 'component' | 'api'; } const CatalogFilterWrapper = withWidth()( @@ -56,11 +57,12 @@ const CatalogFilterWrapper = withWidth()( export const CatalogFilter = ({ initiallySelectedFilter = 'owned', + initialFilter = 'component', }: IProps) => ( - diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx index bd23e5ccdb..702d9f2ed3 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx @@ -45,16 +45,17 @@ export const CatalogPage = ({ return ( } - title={`${orgName} Catalog`} - subtitle={`Catalog of software components at ${orgName}`} - themeId="home" - pageTitleOverride="Home" - headerLink={createComponentLink ? createComponentLink() : ''} > diff --git a/plugins/catalog/src/index.ts b/plugins/catalog/src/index.ts index f2f28679a5..7c8d7ac122 100644 --- a/plugins/catalog/src/index.ts +++ b/plugins/catalog/src/index.ts @@ -15,11 +15,9 @@ */ export * from './components/AboutCard'; -export { CatalogLayout } from './components/CatalogPage'; export { CatalogResultListItem } from './components/CatalogResultListItem'; export { CatalogTable } from './components/CatalogTable'; export type { EntityRow as CatalogTableRow } from './components/CatalogTable'; -export { CreateComponentButton } from './components/CreateComponentButton'; export { EntityLayout } from './components/EntityLayout'; export * from './components/EntityOrphanWarning'; export { EntityPageLayout } from './components/EntityPageLayout'; @@ -42,3 +40,4 @@ export { EntitySystemDiagramCard, } from './plugin'; export * from './components/CatalogTable/columns'; +export * from './components/CatalogFilter'; From 45b5fc3a83ef6fd4b87422d09ab9e1aba2865a4f Mon Sep 17 00:00:00 2001 From: Philipp Hugenroth Date: Mon, 12 Jul 2021 17:57:37 +0200 Subject: [PATCH 011/106] Add changeset for combined PR Signed-off-by: Philipp Hugenroth --- .changeset/fuzzy-wolves-travel.md | 5 ----- .changeset/green-lies-hammer.md | 8 ++++++++ 2 files changed, 8 insertions(+), 5 deletions(-) delete mode 100644 .changeset/fuzzy-wolves-travel.md create mode 100644 .changeset/green-lies-hammer.md diff --git a/.changeset/fuzzy-wolves-travel.md b/.changeset/fuzzy-wolves-travel.md deleted file mode 100644 index 6b51be64c0..0000000000 --- a/.changeset/fuzzy-wolves-travel.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog': patch ---- - -Move filter to CatalogFilter component & wrap them in an accordion on smaller screens to improve the UX diff --git a/.changeset/green-lies-hammer.md b/.changeset/green-lies-hammer.md new file mode 100644 index 0000000000..bf0a9a3995 --- /dev/null +++ b/.changeset/green-lies-hammer.md @@ -0,0 +1,8 @@ +--- +'@backstage/core-components': patch +'@backstage/test-utils': patch +'@backstage/plugin-api-docs': patch +'@backstage/plugin-catalog': patch +--- + +Build a general TablePage component to enforce a more responsive layout for the Catalog & API page. Filters are now wrapped in an accordion on smaller screens, enabling the user to see & interact better with the table. Additionally, a test was added, to check that the responsive wrapping is working for the Catalog page. From 2934eb3a5036e25505ab7f220fba86b66ee27af9 Mon Sep 17 00:00:00 2001 From: Philipp Hugenroth Date: Tue, 13 Jul 2021 20:33:33 +0200 Subject: [PATCH 012/106] [DRAFT] Drawer Filters Signed-off-by: Philipp Hugenroth --- .../layout/ContentHeader/ContentHeader.tsx | 6 +- .../src/layout/ContentHeader/index.ts | 2 +- .../Layout.tsx => Page/PageWithHeader.tsx} | 2 +- .../core-components/src/layout/Page/index.ts | 1 + .../src/layout/TablePage/TablePage.tsx | 77 ----------- packages/core-components/src/layout/index.ts | 1 - .../ApiExplorerPage/ApiExplorerPage.tsx | 61 +-------- .../components/CatalogPage/CatalogPage.tsx | 120 ++++++++++++++---- .../CreateComponentButton.tsx | 38 ++++++ .../CreateComponentButton/indext.ts | 17 +++ .../FilterContainer/FilterContainer.tsx | 57 +++++++++ .../src/components/FilterContainer}/index.ts | 4 +- .../FilteredTableLayout.tsx | 30 +++++ .../components/FilteredTableLayout/index.ts | 17 +++ 14 files changed, 269 insertions(+), 164 deletions(-) rename packages/core-components/src/layout/{TablePage/Layout.tsx => Page/PageWithHeader.tsx} (96%) delete mode 100644 packages/core-components/src/layout/TablePage/TablePage.tsx create mode 100644 plugins/catalog/src/components/CreateComponentButton/CreateComponentButton.tsx create mode 100644 plugins/catalog/src/components/CreateComponentButton/indext.ts create mode 100644 plugins/catalog/src/components/FilterContainer/FilterContainer.tsx rename {packages/core-components/src/layout/TablePage => plugins/catalog/src/components/FilterContainer}/index.ts (85%) create mode 100644 plugins/catalog/src/components/FilteredTableLayout/FilteredTableLayout.tsx create mode 100644 plugins/catalog/src/components/FilteredTableLayout/index.ts diff --git a/packages/core-components/src/layout/ContentHeader/ContentHeader.tsx b/packages/core-components/src/layout/ContentHeader/ContentHeader.tsx index 1e2ad0b5d4..5c773770be 100644 --- a/packages/core-components/src/layout/ContentHeader/ContentHeader.tsx +++ b/packages/core-components/src/layout/ContentHeader/ContentHeader.tsx @@ -59,10 +59,10 @@ const useStyles = (props: ContentHeaderProps) => type DefaultTitleProps = { title?: string; - className: string; + className?: string; }; -const DefaultTitle = ({ +export const ContentHeaderTitle = ({ title = 'Unknown page', className, }: DefaultTitleProps) => ( @@ -95,7 +95,7 @@ export const ContentHeader = ({ const renderedTitle = TitleComponent ? ( ) : ( - + ); return ( diff --git a/packages/core-components/src/layout/ContentHeader/index.ts b/packages/core-components/src/layout/ContentHeader/index.ts index 537a2b6ed9..1a4aacd305 100644 --- a/packages/core-components/src/layout/ContentHeader/index.ts +++ b/packages/core-components/src/layout/ContentHeader/index.ts @@ -14,4 +14,4 @@ * limitations under the License. */ -export { ContentHeader } from './ContentHeader'; +export { ContentHeader, ContentHeaderTitle } from './ContentHeader'; diff --git a/packages/core-components/src/layout/TablePage/Layout.tsx b/packages/core-components/src/layout/Page/PageWithHeader.tsx similarity index 96% rename from packages/core-components/src/layout/TablePage/Layout.tsx rename to packages/core-components/src/layout/Page/PageWithHeader.tsx index 79ad9c24b8..e20788e2e2 100644 --- a/packages/core-components/src/layout/TablePage/Layout.tsx +++ b/packages/core-components/src/layout/Page/PageWithHeader.tsx @@ -23,7 +23,7 @@ export interface IProps extends HeaderProps { themeId: string; } -export const Layout = ({ +export const PageWithHeader = ({ themeId, children, ...props diff --git a/packages/core-components/src/layout/Page/index.ts b/packages/core-components/src/layout/Page/index.ts index d2523e8467..91db73657e 100644 --- a/packages/core-components/src/layout/Page/index.ts +++ b/packages/core-components/src/layout/Page/index.ts @@ -15,3 +15,4 @@ */ export { Page } from './Page'; +export { PageWithHeader } from './PageWithHeader'; diff --git a/packages/core-components/src/layout/TablePage/TablePage.tsx b/packages/core-components/src/layout/TablePage/TablePage.tsx deleted file mode 100644 index 5bf69f4e64..0000000000 --- a/packages/core-components/src/layout/TablePage/TablePage.tsx +++ /dev/null @@ -1,77 +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. - */ - -import React from 'react'; -import { Grid } from '@material-ui/core'; - -import { Content, ContentHeader, SupportButton, Button } from '../..'; -import { Layout, IProps as LayoutProps } from './Layout'; -import { Link as RouterLink } from 'react-router-dom'; - -interface ILinkProps { - contentLink?: string; - contentLinkText?: string; -} - -type IProps = LayoutProps & - ILinkProps & { - contentTitle: string; - supportMessage: string; - filter: React.ReactNode; - }; - -const TablePageLink = ({ contentLink, contentLinkText }: ILinkProps) => { - return contentLink && contentLinkText ? ( - - ) : null; -}; - -export const TablePage = ({ - supportMessage, - filter, - children, - contentTitle, - contentLink, - contentLinkText, - ...props -}: React.PropsWithChildren) => ( - - - - - {supportMessage} - - - - {filter} - - - {children} - - - - -); diff --git a/packages/core-components/src/layout/index.ts b/packages/core-components/src/layout/index.ts index dce33c7f2a..4abde642dc 100644 --- a/packages/core-components/src/layout/index.ts +++ b/packages/core-components/src/layout/index.ts @@ -29,4 +29,3 @@ export * from './Sidebar'; export * from './SignInPage'; export * from './TabbedCard'; export * from './Breadcrumbs'; -export * from './TablePage'; diff --git a/plugins/api-docs/src/components/ApiExplorerPage/ApiExplorerPage.tsx b/plugins/api-docs/src/components/ApiExplorerPage/ApiExplorerPage.tsx index bcc18e4021..32fab2c1ec 100644 --- a/plugins/api-docs/src/components/ApiExplorerPage/ApiExplorerPage.tsx +++ b/plugins/api-docs/src/components/ApiExplorerPage/ApiExplorerPage.tsx @@ -14,29 +14,10 @@ * limitations under the License. */ -import { TableColumn, TablePage } from '@backstage/core-components'; -import { configApiRef, useApi, useRouteRef } from '@backstage/core-plugin-api'; -import { - CatalogFilter, - CatalogTable, - CatalogTableRow, -} from '@backstage/plugin-catalog'; -import { - EntityListProvider, - UserListFilterKind, -} from '@backstage/plugin-catalog-react'; +import { TableColumn } from '@backstage/core-components'; +import { CatalogTableRow } from '@backstage/plugin-catalog'; +import { UserListFilterKind } from '@backstage/plugin-catalog-react'; import React from 'react'; -import { createComponentRouteRef } from '../../routes'; - -const defaultColumns: TableColumn[] = [ - CatalogTable.columns.createNameColumn({ defaultKind: 'API' }), - CatalogTable.columns.createSystemColumn(), - CatalogTable.columns.createOwnerColumn(), - CatalogTable.columns.createSpecTypeColumn(), - CatalogTable.columns.createSpecLifecycleColumn(), - CatalogTable.columns.createMetadataDescriptionColumn(), - CatalogTable.columns.createTagsColumn(), -]; interface IApiExplorerePageFilterProps { initiallySelectedFilter?: UserListFilterKind; @@ -46,36 +27,6 @@ export type ApiExplorerPageProps = IApiExplorerePageFilterProps & { columns?: TableColumn[]; }; -export const ApiExplorerPage = ({ - initiallySelectedFilter = 'all', - columns, -}: ApiExplorerPageProps) => { - const createComponentLink = useRouteRef(createComponentRouteRef); - const configApi = useApi(configApiRef); - const generatedSubtitle = `${ - configApi.getOptionalString('organization.name') ?? 'Backstage' - } API Explorer`; - - return ( - - - } - > - - - - ); -}; +export const ApiExplorerPage = ({}: ApiExplorerPageProps) => ( +
Please revert me...
+); diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx index 702d9f2ed3..50fb2433df 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx @@ -14,18 +14,35 @@ * limitations under the License. */ -import React from 'react'; import { + Content, + ContentHeader, + ContentHeaderTitle, + PageWithHeader, + SupportButton, + TableColumn, + TableProps, +} from '@backstage/core-components'; +import { configApiRef, useApi } from '@backstage/core-plugin-api'; +import { + EntityKindPicker, + EntityLifecyclePicker, EntityListProvider, + EntityOwnerPicker, + EntityTagPicker, + EntityTypePicker, UserListFilterKind, + UserListPicker, } from '@backstage/plugin-catalog-react'; +import { BackstageTheme } from '@backstage/theme'; +import { Box, Grid, IconButton, useMediaQuery } from '@material-ui/core'; +import FilterListIcon from '@material-ui/icons/FilterList'; +import React, { useState } from 'react'; import { CatalogTable } from '../CatalogTable'; -import { configApiRef, useApi, useRouteRef } from '@backstage/core-plugin-api'; - import { EntityRow } from '../CatalogTable/types'; -import { TableColumn, TableProps, TablePage } from '@backstage/core-components'; -import { CatalogFilter } from '../CatalogFilter'; -import { createComponentRouteRef } from '../../routes'; +import { CreateComponentButton } from '../CreateComponentButton/CreateComponentButton'; +import { FilterContainer } from '../FilterContainer'; +import { FilteredTableLayout } from '../FilteredTableLayout'; export type CatalogPageProps = { initiallySelectedFilter?: UserListFilterKind; @@ -33,32 +50,87 @@ export type CatalogPageProps = { actions?: TableProps['actions']; }; +const CatalogPageHeaderAction = ({ + showFilter, + toggleFilter, + isMidSizeScreen, +}: { + showFilter: boolean; + toggleFilter: (showFilter: boolean) => void; + isMidSizeScreen: boolean; +}) => + isMidSizeScreen ? ( + + + toggleFilter(!showFilter)}> + + + + ) : ( + + ); + +const CatalogPageHeader = ({ + children, + ...props +}: React.PropsWithChildren<{ + showFilter: boolean; + toggleFilter: (showFilter: boolean) => void; + isMidSizeScreen: boolean; +}>) => { + return ( + } + > + {children} + + ); +}; + export const CatalogPage = ({ columns, actions, initiallySelectedFilter = 'owned', }: CatalogPageProps) => { + const [showFilter, toggleFilter] = useState(false); + const isMidSizeScreen = useMediaQuery(theme => + theme.breakpoints.down('md'), + ); + const orgName = useApi(configApiRef).getOptionalString('organization.name') ?? 'Backstage'; - const createComponentLink = useRouteRef(createComponentRouteRef); return ( - - - } - > - - - + + + + + All your software catalog entities + + + + + + + + + + + + ); }; diff --git a/plugins/catalog/src/components/CreateComponentButton/CreateComponentButton.tsx b/plugins/catalog/src/components/CreateComponentButton/CreateComponentButton.tsx new file mode 100644 index 0000000000..482a0c0a3c --- /dev/null +++ b/plugins/catalog/src/components/CreateComponentButton/CreateComponentButton.tsx @@ -0,0 +1,38 @@ +/* + * 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 React from 'react'; +import { Link as RouterLink } from 'react-router-dom'; +import { Button } from '@material-ui/core'; +import { createComponentRouteRef } from '../../routes'; +import { useRouteRef } from '@backstage/core-plugin-api'; + +export const CreateComponentButton = () => { + const createComponentLink = useRouteRef(createComponentRouteRef); + + if (!createComponentLink) return null; + + return ( + + ); +}; diff --git a/plugins/catalog/src/components/CreateComponentButton/indext.ts b/plugins/catalog/src/components/CreateComponentButton/indext.ts new file mode 100644 index 0000000000..edf59d18a6 --- /dev/null +++ b/plugins/catalog/src/components/CreateComponentButton/indext.ts @@ -0,0 +1,17 @@ +/* + * 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. + */ + +export { CreateComponentButton } from './CreateComponentButton'; diff --git a/plugins/catalog/src/components/FilterContainer/FilterContainer.tsx b/plugins/catalog/src/components/FilterContainer/FilterContainer.tsx new file mode 100644 index 0000000000..4083062478 --- /dev/null +++ b/plugins/catalog/src/components/FilterContainer/FilterContainer.tsx @@ -0,0 +1,57 @@ +/* + * 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 { Box, Drawer, Grid } from '@material-ui/core'; +import React from 'react'; + +interface IProps { + showFilter: boolean; + toggleFilter: (showFilter: boolean) => void; + isMidSizeScreen: boolean; +} + +export const FilterContainer = ({ + children, + showFilter, + toggleFilter, + isMidSizeScreen, +}: React.PropsWithChildren) => + isMidSizeScreen ? ( + { + toggleFilter(false); + }} + elevation={0} + disableAutoFocus + PaperProps={{ + style: { position: 'absolute', width: '250px' }, + }} + BackdropProps={{ style: { position: 'absolute' } }} + ModalProps={{ + container: document.getElementById('drawer-container'), + disableEnforceFocus: true, + style: { position: 'absolute' }, + }} + variant="temporary" + > + {children} + + ) : ( + + {children} + + ); diff --git a/packages/core-components/src/layout/TablePage/index.ts b/plugins/catalog/src/components/FilterContainer/index.ts similarity index 85% rename from packages/core-components/src/layout/TablePage/index.ts rename to plugins/catalog/src/components/FilterContainer/index.ts index 5df8aa4c40..a25071e3c5 100644 --- a/packages/core-components/src/layout/TablePage/index.ts +++ b/plugins/catalog/src/components/FilterContainer/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Backstage Authors + * 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. @@ -14,4 +14,4 @@ * limitations under the License. */ -export { TablePage } from './TablePage'; +export { FilterContainer } from './FilterContainer'; diff --git a/plugins/catalog/src/components/FilteredTableLayout/FilteredTableLayout.tsx b/plugins/catalog/src/components/FilteredTableLayout/FilteredTableLayout.tsx new file mode 100644 index 0000000000..a0debe618f --- /dev/null +++ b/plugins/catalog/src/components/FilteredTableLayout/FilteredTableLayout.tsx @@ -0,0 +1,30 @@ +/* + * 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 React from 'react'; +import { Grid } from '@material-ui/core'; + +interface IProps {} + +export const FilteredTableLayout = ({ + children, +}: React.PropsWithChildren) => { + return ( + + {children} + + ); +}; diff --git a/plugins/catalog/src/components/FilteredTableLayout/index.ts b/plugins/catalog/src/components/FilteredTableLayout/index.ts new file mode 100644 index 0000000000..77d49a773a --- /dev/null +++ b/plugins/catalog/src/components/FilteredTableLayout/index.ts @@ -0,0 +1,17 @@ +/* + * 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. + */ + +export { FilteredTableLayout } from './FilteredTableLayout'; From f3b58ecd8ea57df25ccae137672e47bebdf38d1c Mon Sep 17 00:00:00 2001 From: Philipp Hugenroth Date: Wed, 14 Jul 2021 11:10:08 +0200 Subject: [PATCH 013/106] Reorganize Drawer & move drawer state to EntityListProvider Signed-off-by: Philipp Hugenroth --- .../src/hooks/useEntityListProvider.tsx | 7 ++ .../components/CatalogPage/CatalogPage.tsx | 79 ++++--------------- .../CatalogPage/CatalogPageHeader.tsx | 50 ++++++++++++ .../CreateComponentButton.tsx | 4 +- .../FilterContainer.tsx | 40 ++++------ .../TableContainer.tsx} | 9 ++- .../components/FilteredTableLayout/index.ts | 2 + 7 files changed, 97 insertions(+), 94 deletions(-) create mode 100644 plugins/catalog/src/components/CatalogPage/CatalogPageHeader.tsx rename plugins/catalog/src/components/{FilterContainer => FilteredTableLayout}/FilterContainer.tsx (53%) rename plugins/catalog/src/components/{FilterContainer/index.ts => FilteredTableLayout/TableContainer.tsx} (74%) diff --git a/plugins/catalog-react/src/hooks/useEntityListProvider.tsx b/plugins/catalog-react/src/hooks/useEntityListProvider.tsx index 8ca3efd243..7db2205deb 100644 --- a/plugins/catalog-react/src/hooks/useEntityListProvider.tsx +++ b/plugins/catalog-react/src/hooks/useEntityListProvider.tsx @@ -84,6 +84,9 @@ export type EntityListContextProps< */ queryParameters: Partial>; + showFiltersDrawer: boolean; + toggleFiltersDrawer: (showFiltersDrawer: boolean) => void; + loading: boolean; error?: Error; }; @@ -108,6 +111,8 @@ export const EntityListProvider = ({ const [requestedFilters, setRequestedFilters] = useState( {} as EntityFilters, ); + // Show + const [showFiltersDrawer, toggleFiltersDrawer] = useState(false); const [outputState, setOutputState] = useState>({ appliedFilters: {} as EntityFilters, entities: [], @@ -203,6 +208,8 @@ export const EntityListProvider = ({ updateFilters, queryParameters: outputState.queryParameters, loading, + showFiltersDrawer, + toggleFiltersDrawer, error, }} > diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx index 50fb2433df..3df46ff0c0 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx @@ -16,8 +16,6 @@ import { Content, - ContentHeader, - ContentHeaderTitle, PageWithHeader, SupportButton, TableColumn, @@ -34,15 +32,16 @@ import { UserListFilterKind, UserListPicker, } from '@backstage/plugin-catalog-react'; -import { BackstageTheme } from '@backstage/theme'; -import { Box, Grid, IconButton, useMediaQuery } from '@material-ui/core'; -import FilterListIcon from '@material-ui/icons/FilterList'; -import React, { useState } from 'react'; +import React from 'react'; import { CatalogTable } from '../CatalogTable'; import { EntityRow } from '../CatalogTable/types'; import { CreateComponentButton } from '../CreateComponentButton/CreateComponentButton'; -import { FilterContainer } from '../FilterContainer'; -import { FilteredTableLayout } from '../FilteredTableLayout'; +import { + FilteredTableLayout, + TableContainer, + FilterContainer, +} from '../FilteredTableLayout'; +import { CatalogPageHeader } from './CatalogPageHeader'; export type CatalogPageProps = { initiallySelectedFilter?: UserListFilterKind; @@ -50,74 +49,24 @@ export type CatalogPageProps = { actions?: TableProps['actions']; }; -const CatalogPageHeaderAction = ({ - showFilter, - toggleFilter, - isMidSizeScreen, -}: { - showFilter: boolean; - toggleFilter: (showFilter: boolean) => void; - isMidSizeScreen: boolean; -}) => - isMidSizeScreen ? ( - - - toggleFilter(!showFilter)}> - - - - ) : ( - - ); - -const CatalogPageHeader = ({ - children, - ...props -}: React.PropsWithChildren<{ - showFilter: boolean; - toggleFilter: (showFilter: boolean) => void; - isMidSizeScreen: boolean; -}>) => { - return ( - } - > - {children} - - ); -}; - export const CatalogPage = ({ columns, actions, initiallySelectedFilter = 'owned', }: CatalogPageProps) => { - const [showFilter, toggleFilter] = useState(false); - const isMidSizeScreen = useMediaQuery(theme => - theme.breakpoints.down('md'), - ); - const orgName = useApi(configApiRef).getOptionalString('organization.name') ?? 'Backstage'; return ( - - - All your software catalog entities - + + + All your software catalog entities + - + - + - + diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPageHeader.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPageHeader.tsx new file mode 100644 index 0000000000..f628aef596 --- /dev/null +++ b/plugins/catalog/src/components/CatalogPage/CatalogPageHeader.tsx @@ -0,0 +1,50 @@ +/* + * 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 { ContentHeader, ContentHeaderTitle } from '@backstage/core-components'; +import { useEntityListProvider } from '@backstage/plugin-catalog-react'; +import { BackstageTheme } from '@backstage/theme'; +import { Box, IconButton, useMediaQuery } from '@material-ui/core'; +import FilterListIcon from '@material-ui/icons/FilterList'; +import React from 'react'; + +const CatalogPageHeaderAction = () => { + const isMidSizeScreen = useMediaQuery(theme => + theme.breakpoints.down('md'), + ); + const { showFiltersDrawer, toggleFiltersDrawer } = useEntityListProvider(); + + return isMidSizeScreen ? ( + + + toggleFiltersDrawer(!showFiltersDrawer)}> + + + + ) : ( + + ); +}; + +export const CatalogPageHeader = ({ + children, +}: React.PropsWithChildren<{}>) => { + return ( + }> + {children} + + ); +}; diff --git a/plugins/catalog/src/components/CreateComponentButton/CreateComponentButton.tsx b/plugins/catalog/src/components/CreateComponentButton/CreateComponentButton.tsx index 482a0c0a3c..4be93c1aa0 100644 --- a/plugins/catalog/src/components/CreateComponentButton/CreateComponentButton.tsx +++ b/plugins/catalog/src/components/CreateComponentButton/CreateComponentButton.tsx @@ -23,9 +23,7 @@ import { useRouteRef } from '@backstage/core-plugin-api'; export const CreateComponentButton = () => { const createComponentLink = useRouteRef(createComponentRouteRef); - if (!createComponentLink) return null; - - return ( + return !createComponentLink ? null : ( ( diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx index 3df46ff0c0..5da46eec97 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx @@ -16,6 +16,7 @@ import { Content, + ContentHeader, PageWithHeader, SupportButton, TableColumn, @@ -41,7 +42,6 @@ import { TableContainer, FilterContainer, } from '../FilteredTableLayout'; -import { CatalogPageHeader } from './CatalogPageHeader'; export type CatalogPageProps = { initiallySelectedFilter?: UserListFilterKind; @@ -60,11 +60,11 @@ export const CatalogPage = ({ return ( + + + All your software catalog entities + - - - All your software catalog entities -
); diff --git a/packages/core-components/src/layout/HeaderLabel/HeaderLabel.tsx b/packages/core-components/src/layout/HeaderLabel/HeaderLabel.tsx index 1534656f24..c7adf23204 100644 --- a/packages/core-components/src/layout/HeaderLabel/HeaderLabel.tsx +++ b/packages/core-components/src/layout/HeaderLabel/HeaderLabel.tsx @@ -14,13 +14,12 @@ * limitations under the License. */ -import { Link, makeStyles, Typography } from '@material-ui/core'; +import { Link, makeStyles, Typography, Grid } from '@material-ui/core'; import React from 'react'; const useStyles = makeStyles(theme => ({ root: { textAlign: 'left', - margin: theme.spacing(2), display: 'inline-block', }, label: { @@ -64,9 +63,11 @@ export const HeaderLabel = ({ label, value, url }: HeaderLabelProps) => { /> ); return ( - - {label} - {url ? {content} : content} - + + + {label} + {url ? {content} : content} + + ); }; From 5b0b5baa5e0b20596d8083197a00bbd605340d28 Mon Sep 17 00:00:00 2001 From: Philipp Hugenroth Date: Wed, 14 Jul 2021 18:52:03 +0200 Subject: [PATCH 019/106] Fix Grid in EntityPage for smaller screens Signed-off-by: Philipp Hugenroth --- .../app/src/components/catalog/EntityPage.tsx | 30 ++++++++++++------- .../src/components/EmptyState/EmptyState.tsx | 20 +++++++------ .../components/EmptyState/EmptyStateImage.tsx | 2 +- plugins/api-docs/README.md | 16 +++++----- 4 files changed, 40 insertions(+), 28 deletions(-) diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx index 16107a6cc0..7965561516 100644 --- a/packages/app/src/components/catalog/EntityPage.tsx +++ b/packages/app/src/components/catalog/EntityPage.tsx @@ -135,6 +135,12 @@ const EntityLayoutWrapper = (props: { children?: ReactNode }) => { ); }; +/** + * TODO: For the MUI Grid to work there have to be "xs" set on every GridItem, + * such that the seperation of space is clear from the smalles screen size upwards + * https://material-ui.com/components/grid/#basic-grid. + */ + export const cicdContent = ( @@ -292,10 +298,10 @@ const serviceEntityPage = ( - + - + @@ -303,10 +309,10 @@ const serviceEntityPage = ( - + - + @@ -431,15 +437,17 @@ const apiPage = ( - + - - - - - - + + + + + + + + diff --git a/packages/core-components/src/components/EmptyState/EmptyState.tsx b/packages/core-components/src/components/EmptyState/EmptyState.tsx index 1d8d5a798f..5d55c5da93 100644 --- a/packages/core-components/src/components/EmptyState/EmptyState.tsx +++ b/packages/core-components/src/components/EmptyState/EmptyState.tsx @@ -49,15 +49,17 @@ export const EmptyState = ({ title, description, missing, action }: Props) => { className={classes.root} spacing={2} > - - - {title} - - - {description} - - - {action} + + + + {title} + + + {description} + + + {action} + diff --git a/packages/core-components/src/components/EmptyState/EmptyStateImage.tsx b/packages/core-components/src/components/EmptyState/EmptyStateImage.tsx index a76f0863f7..660e7fc947 100644 --- a/packages/core-components/src/components/EmptyState/EmptyStateImage.tsx +++ b/packages/core-components/src/components/EmptyState/EmptyStateImage.tsx @@ -29,7 +29,7 @@ const useStyles = makeStyles({ generalImg: { width: '95%', zIndex: 2, - position: 'absolute', + position: 'relative', left: '50%', top: '50%', transform: 'translate(-50%, 15%)', diff --git a/plugins/api-docs/README.md b/plugins/api-docs/README.md index 60d8986d9b..d5ed198dc1 100644 --- a/plugins/api-docs/README.md +++ b/plugins/api-docs/README.md @@ -59,15 +59,17 @@ const apiPage = ( - + - - - - - - + + + + + + + + From 417aeb2e71c63ee0a4d0373f9716dfe9c1463d99 Mon Sep 17 00:00:00 2001 From: Philipp Hugenroth Date: Thu, 15 Jul 2021 12:57:23 +0200 Subject: [PATCH 020/106] Update API report Signed-off-by: Philipp Hugenroth --- packages/core-components/api-report.md | 10 ++++++ plugins/catalog/api-report.md | 48 ++++++++++++++++++-------- 2 files changed, 44 insertions(+), 14 deletions(-) diff --git a/packages/core-components/api-report.md b/packages/core-components/api-report.md index e915e004ee..bfc6852a97 100644 --- a/packages/core-components/api-report.md +++ b/packages/core-components/api-report.md @@ -1202,6 +1202,16 @@ export const Page: ({ children, }: PropsWithChildren) => JSX.Element; +// Warning: (ae-forgotten-export) The symbol "IProps" needs to be exported by the entry point index.d.ts +// Warning: (ae-missing-release-tag) "PageWithHeader" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const PageWithHeader: ({ + themeId, + children, + ...props +}: React_2.PropsWithChildren) => JSX.Element; + // Warning: (ae-missing-release-tag) "Progress" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) diff --git a/plugins/catalog/api-report.md b/plugins/catalog/api-report.md index a85954906a..b3169d5c29 100644 --- a/plugins/catalog/api-report.md +++ b/plugins/catalog/api-report.md @@ -30,7 +30,7 @@ export function AboutCard({ variant }: AboutCardProps): JSX.Element; // Warning: (ae-missing-release-tag) "AboutContent" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const AboutContent: ({ entity }: Props_2) => JSX.Element; +export const AboutContent: ({ entity }: Props) => JSX.Element; // Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "AboutField" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -41,29 +41,32 @@ export const AboutField: ({ value, gridSizes, children, -}: Props_3) => JSX.Element; +}: Props_2) => JSX.Element; // Warning: (ae-missing-release-tag) "CatalogEntityPage" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export const CatalogEntityPage: () => JSX.Element; +// Warning: (ae-forgotten-export) The symbol "IProps" needs to be exported by the entry point index.d.ts +// Warning: (ae-missing-release-tag) "CatalogFilter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const CatalogFilter: ({ + initiallySelectedFilter, + initialFilter, +}: IProps) => JSX.Element; + // Warning: (ae-forgotten-export) The symbol "CatalogPageProps" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "CatalogIndexPage" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export const CatalogIndexPage: ({ - initiallySelectedFilter, columns, actions, + initiallySelectedFilter, }: CatalogPageProps) => JSX.Element; -// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts -// Warning: (ae-missing-release-tag) "CatalogLayout" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export const CatalogLayout: ({ children }: Props) => JSX.Element; - // Warning: (ae-missing-release-tag) "catalogPlugin" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -110,11 +113,6 @@ export type CatalogTableRow = { }; }; -// Warning: (ae-missing-release-tag) "CreateComponentButton" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export const CreateComponentButton: () => JSX.Element | null; - // Warning: (ae-missing-release-tag) "createMetadataDescriptionColumn" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -287,6 +285,21 @@ export const EntitySwitch: { // @public (undocumented) export const EntitySystemDiagramCard: SystemDiagramCard; +// Warning: (ae-missing-release-tag) "FilterContainer" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const FilterContainer: ({ + children, +}: React_2.PropsWithChildren<{}>) => JSX.Element; + +// Warning: (ae-forgotten-export) The symbol "IProps" needs to be exported by the entry point index.d.ts +// Warning: (ae-missing-release-tag) "FilteredTableLayout" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const FilteredTableLayout: ({ + children, +}: React_2.PropsWithChildren) => JSX.Element; + // Warning: (ae-missing-release-tag) "isComponentType" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -316,6 +329,13 @@ export const Router: ({ EntityPage?: React_2.ComponentType<{}> | undefined; }) => JSX.Element; +// Warning: (ae-missing-release-tag) "TableContainer" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const TableContainer: ({ + children, +}: React_2.PropsWithChildren<{}>) => JSX.Element; + // Warnings were encountered during analysis: // // src/components/CatalogTable/CatalogTable.d.ts:10:5 - (ae-forgotten-export) The symbol "CatalogTableProps" needs to be exported by the entry point index.d.ts From 9266b80ab36a17aadd81673e830576beb50416b2 Mon Sep 17 00:00:00 2001 From: Jussi Hallila Date: Tue, 13 Jul 2021 09:07:18 +0200 Subject: [PATCH 021/106] Implement DefaultTechDocsCollator * Implements a collator for tech docs. * Retrieves mkdocs created search index for entities that have documentation configured * Registers collator to expose tech docs content to be searchable * Adds pagination to example search * Modifies example search to contain tech docs * Displays docs results with link to docs and the entity name as title. * Creates a reusable type filter to be located in the search package. * Add tests for type filter Signed-off-by: Jussi Hallila --- .changeset/funny-dragons-sneeze.md | 5 + .changeset/great-tips-hammer.md | 5 + .changeset/neat-wolves-cross.md | 5 + packages/app/package.json | 2 + .../app/src/components/search/SearchPage.tsx | 91 ++++--- packages/backend/src/plugins/search.ts | 6 + plugins/search/api-report.md | 11 + .../components/SearchType/SearchType.test.tsx | 231 ++++++++++++++++++ .../src/components/SearchType/SearchType.tsx | 110 +++++++++ .../search/src/components/SearchType/index.ts | 17 ++ plugins/search/src/components/index.tsx | 1 + plugins/search/src/index.ts | 1 + plugins/techdocs-backend/api-report.md | 51 ++++ plugins/techdocs-backend/package.json | 4 + plugins/techdocs-backend/src/index.ts | 1 + .../search/DefaultTechDocsCollator.test.ts | 149 +++++++++++ .../src/search/DefaultTechDocsCollator.ts | 149 +++++++++++ plugins/techdocs-backend/src/search/index.ts | 17 ++ plugins/techdocs/api-report.md | 11 + plugins/techdocs/package.json | 1 + .../DocsResultListItem.test.tsx | 50 ++++ .../DocsResultListItem/DocsResultListItem.tsx | 60 +++++ .../components/DocsResultListItem/index.ts | 17 ++ plugins/techdocs/src/index.ts | 1 + 24 files changed, 968 insertions(+), 28 deletions(-) create mode 100644 .changeset/funny-dragons-sneeze.md create mode 100644 .changeset/great-tips-hammer.md create mode 100644 .changeset/neat-wolves-cross.md create mode 100644 plugins/search/src/components/SearchType/SearchType.test.tsx create mode 100644 plugins/search/src/components/SearchType/SearchType.tsx create mode 100644 plugins/search/src/components/SearchType/index.ts create mode 100644 plugins/techdocs-backend/src/search/DefaultTechDocsCollator.test.ts create mode 100644 plugins/techdocs-backend/src/search/DefaultTechDocsCollator.ts create mode 100644 plugins/techdocs-backend/src/search/index.ts create mode 100644 plugins/techdocs/src/components/DocsResultListItem/DocsResultListItem.test.tsx create mode 100644 plugins/techdocs/src/components/DocsResultListItem/DocsResultListItem.tsx create mode 100644 plugins/techdocs/src/components/DocsResultListItem/index.ts diff --git a/.changeset/funny-dragons-sneeze.md b/.changeset/funny-dragons-sneeze.md new file mode 100644 index 0000000000..758603e811 --- /dev/null +++ b/.changeset/funny-dragons-sneeze.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +Add search list item to display tech docs search results diff --git a/.changeset/great-tips-hammer.md b/.changeset/great-tips-hammer.md new file mode 100644 index 0000000000..2f31b59568 --- /dev/null +++ b/.changeset/great-tips-hammer.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs-backend': patch +--- + +Implements tech docs collator to retrieve and expose search indexes for entities that have tech docs configured diff --git a/.changeset/neat-wolves-cross.md b/.changeset/neat-wolves-cross.md new file mode 100644 index 0000000000..555245d42d --- /dev/null +++ b/.changeset/neat-wolves-cross.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search': patch +--- + +Adding a type filter to new search diff --git a/packages/app/package.json b/packages/app/package.json index d86e0f27e9..cc38248a55 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -39,9 +39,11 @@ "@backstage/plugin-techdocs": "^0.10.0", "@backstage/plugin-todo": "^0.1.5", "@backstage/plugin-user-settings": "^0.3.0", + "@backstage/search-common": "^0.1.2", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", + "@material-ui/lab": "4.0.0-alpha.45", "@octokit/rest": "^18.5.3", "@roadiehq/backstage-plugin-buildkite": "^1.0.4", "@roadiehq/backstage-plugin-github-insights": "^1.1.15", diff --git a/packages/app/src/components/search/SearchPage.tsx b/packages/app/src/components/search/SearchPage.tsx index 35c4a4ac1c..6fe02de837 100644 --- a/packages/app/src/components/search/SearchPage.tsx +++ b/packages/app/src/components/search/SearchPage.tsx @@ -14,17 +14,20 @@ * limitations under the License. */ -import React from 'react'; +import React, { useState } from 'react'; import { makeStyles, Theme, Grid, List, Paper } from '@material-ui/core'; - +import Pagination from '@material-ui/lab/Pagination'; import { CatalogResultListItem } from '@backstage/plugin-catalog'; import { SearchBar, SearchFilter, SearchResult, + SearchType, DefaultResultListItem, } from '@backstage/plugin-search'; import { Content, Header, Lifecycle, Page } from '@backstage/core-components'; +import { DocsResultListItem } from '@backstage/plugin-techdocs'; +import { SearchResultSet } from '@backstage/search-common'; const useStyles = makeStyles((theme: Theme) => ({ bar: { @@ -34,15 +37,63 @@ const useStyles = makeStyles((theme: Theme) => ({ padding: theme.spacing(2), }, filter: { - '& + &': { - marginTop: theme.spacing(2.5), - }, + marginTop: theme.spacing(2.5), }, })); +// TODO: Move this into the search plugin once pagination is natively supported. +// See: https://github.com/backstage/backstage/issues/6062 +const SearchResultList = ({ results }: SearchResultSet) => { + const pageSize = 10; + const [page, setPage] = useState(1); + const changePage = (_: any, pageIndex: number) => { + setPage(pageIndex); + }; + const pageAmount = Math.ceil((results.length || 0) / pageSize); + return ( + <> + + {results + .slice(pageSize * (page - 1), pageSize * page) + .map(({ type, document }) => { + switch (type) { + case 'software-catalog': + return ( + + ); + case 'techdocs': + return ( + + ); + default: + return ( + + ); + } + })} + + + + ); +}; + const SearchPage = () => { const classes = useStyles(); - return (
} /> @@ -55,6 +106,11 @@ const SearchPage = () => { + { - {({ results }) => ( - - {results.map(({ type, document }) => { - switch (type) { - case 'software-catalog': - return ( - - ); - default: - return ( - - ); - } - })} - - )} + {({ results }) => } diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts index 4a1e415c74..7e7f6ae400 100644 --- a/packages/backend/src/plugins/search.ts +++ b/packages/backend/src/plugins/search.ts @@ -21,6 +21,7 @@ import { } from '@backstage/plugin-search-backend-node'; import { PluginEnvironment } from '../types'; import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend'; +import { DefaultTechDocsCollator } from '@backstage/plugin-techdocs-backend'; export default async function createPlugin({ logger, @@ -37,6 +38,11 @@ export default async function createPlugin({ collator: new DefaultCatalogCollator({ discovery }), }); + indexBuilder.addCollator({ + defaultRefreshIntervalSeconds: 600, + collator: new DefaultTechDocsCollator({ discovery, logger }), + }); + // The scheduler controls when documents are gathered from collators and sent // to the search engine for indexing. const { scheduler } = await indexBuilder.build(); diff --git a/plugins/search/api-report.md b/plugins/search/api-report.md index 505a8989b6..525136713d 100644 --- a/plugins/search/api-report.md +++ b/plugins/search/api-report.md @@ -143,6 +143,17 @@ export const SearchResult: ({ children: (results: { results: SearchResult_2[] }) => JSX.Element; }) => JSX.Element; +// Warning: (ae-forgotten-export) The symbol "SearchTypeProps" needs to be exported by the entry point index.d.ts +// Warning: (ae-missing-release-tag) "SearchType" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const SearchType: ({ + values, + className, + name, + defaultValue, +}: SearchTypeProps) => JSX.Element; + // Warning: (ae-missing-release-tag) "SidebarSearch" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) diff --git a/plugins/search/src/components/SearchType/SearchType.test.tsx b/plugins/search/src/components/SearchType/SearchType.test.tsx new file mode 100644 index 0000000000..1ca032e41d --- /dev/null +++ b/plugins/search/src/components/SearchType/SearchType.test.tsx @@ -0,0 +1,231 @@ +/* + * 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 React from 'react'; +import { screen, render, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { SearchType } from './SearchType'; + +import { SearchContextProvider } from '../SearchContext'; +import { useApi } from '@backstage/core-plugin-api'; + +jest.mock('@backstage/core-plugin-api', () => ({ + ...jest.requireActual('@backstage/core-plugin-api'), + useApi: jest.fn().mockReturnValue({}), +})); + +describe('SearchType', () => { + const initialState = { + term: '', + filters: {}, + types: [], + pageCursor: '', + }; + + const name = 'field'; + const values = ['value1', 'value2']; + const typeValues = ['preselected']; + + const query = jest.fn().mockResolvedValue({}); + (useApi as jest.Mock).mockReturnValue({ query: query }); + + afterAll(() => { + jest.resetAllMocks(); + }); + + describe('Type Filter', () => { + it('Renders field name and values when provided as props', async () => { + render( + + + , + ); + + await waitFor(() => { + expect(screen.getByText(name)).toBeInTheDocument(); + }); + + userEvent.click(screen.getByRole('button')); + + await waitFor(() => { + expect(screen.getByRole('listbox')).toBeInTheDocument(); + }); + + expect( + screen.getByRole('option', { name: values[0] }), + ).toBeInTheDocument(); + expect( + screen.getByRole('option', { name: values[1] }), + ).toBeInTheDocument(); + }); + + it('Renders correctly based on type filter state', async () => { + render( + + + , + ); + + await waitFor(() => { + expect(screen.getByText(name)).toBeInTheDocument(); + }); + + userEvent.click(screen.getByRole('button')); + + await waitFor(() => { + expect(screen.getByRole('listbox')).toBeInTheDocument(); + }); + + expect(screen.getByRole('option', { name: values[0] })).toHaveAttribute( + 'aria-selected', + 'true', + ); + expect( + screen.getByRole('option', { name: values[1] }), + ).not.toHaveAttribute('aria-selected'); + expect(screen.getByRole('option', { name: 'All' })).not.toHaveAttribute( + 'aria-selected', + ); + }); + + it('Renders correctly based on type filter defaultValue', async () => { + render( + + + , + ); + + await waitFor(() => { + expect(screen.getByText(name)).toBeInTheDocument(); + }); + + userEvent.click(screen.getByRole('button')); + + await waitFor(() => { + expect(screen.getByRole('listbox')).toBeInTheDocument(); + }); + + expect(screen.getByRole('option', { name: values[0] })).toHaveAttribute( + 'aria-selected', + 'true', + ); + expect( + screen.getByRole('option', { name: values[1] }), + ).not.toHaveAttribute('aria-selected'); + expect(screen.getByRole('option', { name: 'All' })).not.toHaveAttribute( + 'aria-selected', + ); + }); + + it('Selecting a value sets type filter state', async () => { + render( + + + , + ); + + await waitFor(() => { + expect(screen.getByText(name)).toBeInTheDocument(); + }); + + const button = screen.getByRole('button'); + + userEvent.click(button); + + await waitFor(() => { + expect(screen.getByRole('listbox')).toBeInTheDocument(); + }); + + userEvent.click(screen.getByRole('option', { name: values[0] })); + + await waitFor(() => { + expect(query).toHaveBeenLastCalledWith( + expect.objectContaining({ + types: [values[0]], + }), + ); + }); + + userEvent.click(button); + + await waitFor(() => { + expect(screen.getByRole('listbox')).toBeInTheDocument(); + }); + + userEvent.click(screen.getByRole('option', { name: 'All' })); + + await waitFor(() => { + expect(query).toHaveBeenLastCalledWith( + expect.objectContaining({ + types: [], + }), + ); + }); + }); + + it('Selecting a value maintains unrelated filter state, selecting All defaults to default empty state', async () => { + render( + + + , + ); + + await waitFor(() => { + expect(screen.getByText(name)).toBeInTheDocument(); + }); + + const button = screen.getByRole('button'); + + userEvent.click(button); + + await waitFor(() => { + expect(screen.getByRole('listbox')).toBeInTheDocument(); + }); + + userEvent.click(screen.getByRole('option', { name: values[0] })); + + await waitFor(() => { + expect(query).toHaveBeenLastCalledWith( + expect.objectContaining({ + types: [...typeValues, values[0]], + }), + ); + }); + + userEvent.click(button); + + await waitFor(() => { + expect(screen.getByRole('listbox')).toBeInTheDocument(); + }); + + userEvent.click(screen.getByRole('option', { name: 'All' })); + + await waitFor(() => { + expect(query).toHaveBeenLastCalledWith(expect.objectContaining([])); + }); + }); + }); +}); diff --git a/plugins/search/src/components/SearchType/SearchType.tsx b/plugins/search/src/components/SearchType/SearchType.tsx new file mode 100644 index 0000000000..6e57879064 --- /dev/null +++ b/plugins/search/src/components/SearchType/SearchType.tsx @@ -0,0 +1,110 @@ +/* + * 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 { useSearch } from '../SearchContext'; +import { useEffectOnce } from 'react-use'; +import React, { ChangeEvent } from 'react'; +import { + Chip, + FormControl, + InputLabel, + makeStyles, + MenuItem, + Select, +} from '@material-ui/core'; + +const useStyles = makeStyles({ + label: { + textTransform: 'capitalize', + }, + chips: { + display: 'flex', + flexWrap: 'wrap', + }, + chip: { + margin: 2, + }, +}); + +export type SearchTypeProps = { + className?: string; + name: string; + values?: string[]; + defaultValue?: string[] | string | null; +}; + +const SearchType = ({ + values = [], + className, + name, + defaultValue, +}: SearchTypeProps) => { + const classes = useStyles(); + const { types, setTypes } = useSearch(); + + useEffectOnce(() => { + if (defaultValue && Array.isArray(defaultValue)) { + setTypes(defaultValue); + } else if (defaultValue) { + setTypes([defaultValue]); + } + }); + + const handleChange = (e: ChangeEvent<{ value: unknown }>) => { + const value = e.target.value as string[]; + if (!value || value.includes('*')) { + setTypes([]); + } else { + setTypes(value.filter(it => it !== 'All')); + } + }; + + return ( + + + {name} + + + + ); +}; + +export { SearchType }; diff --git a/plugins/search/src/components/SearchType/index.ts b/plugins/search/src/components/SearchType/index.ts new file mode 100644 index 0000000000..400f3d2a46 --- /dev/null +++ b/plugins/search/src/components/SearchType/index.ts @@ -0,0 +1,17 @@ +/* + * 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. + */ + +export { SearchType } from './SearchType'; diff --git a/plugins/search/src/components/index.tsx b/plugins/search/src/components/index.tsx index 245c31598d..889eb36334 100644 --- a/plugins/search/src/components/index.tsx +++ b/plugins/search/src/components/index.tsx @@ -16,6 +16,7 @@ export * from './Filters'; export * from './SearchFilter'; +export * from './SearchType'; export * from './SearchBar'; export * from './SearchPage'; export * from './SearchResult'; diff --git a/plugins/search/src/index.ts b/plugins/search/src/index.ts index ecd6015b1c..f09ba739bd 100644 --- a/plugins/search/src/index.ts +++ b/plugins/search/src/index.ts @@ -32,6 +32,7 @@ export { useSearch, SearchPage as Router, SearchFilter, + SearchType, SearchFilterNext, SidebarSearch, } from './components'; diff --git a/plugins/techdocs-backend/api-report.md b/plugins/techdocs-backend/api-report.md index 3d1738cb12..f617b796aa 100644 --- a/plugins/techdocs-backend/api-report.md +++ b/plugins/techdocs-backend/api-report.md @@ -3,9 +3,12 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { CatalogApi } from '@backstage/catalog-client'; import { Config } from '@backstage/config'; +import { DocumentCollator } from '@backstage/search-common'; import express from 'express'; import { GeneratorBuilder } from '@backstage/techdocs-common'; +import { IndexableDocument } from '@backstage/search-common'; import { Knex } from 'knex'; import { Logger as Logger_2 } from 'winston'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; @@ -18,6 +21,54 @@ import { PublisherBase } from '@backstage/techdocs-common'; // @public (undocumented) export function createRouter(options: RouterOptions): Promise; +// Warning: (ae-missing-release-tag) "DefaultTechDocsCollator" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export class DefaultTechDocsCollator implements DocumentCollator { + constructor({ + discovery, + locationTemplate, + logger, + catalogClient, + parallelismLimit, + }: { + discovery: PluginEndpointDiscovery; + logger: Logger_2; + locationTemplate?: string; + catalogClient?: CatalogApi; + parallelismLimit?: number; + }); + // (undocumented) + protected applyArgsToFormat( + format: string, + args: Record, + ): string; + // (undocumented) + protected discovery: PluginEndpointDiscovery; + // (undocumented) + execute(): Promise; + // (undocumented) + protected locationTemplate: string; + // (undocumented) + readonly type: string; +} + +// Warning: (ae-missing-release-tag) "TechDocsDocument" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface TechDocsDocument extends IndexableDocument { + // (undocumented) + kind: string; + // (undocumented) + lifecycle: string; + // (undocumented) + name: string; + // (undocumented) + namespace: string; + // (undocumented) + owner: string; +} + export * from '@backstage/techdocs-common'; // (No @packageDocumentation comment for this package) diff --git a/plugins/techdocs-backend/package.json b/plugins/techdocs-backend/package.json index 76aadc79cf..24f3f754ca 100644 --- a/plugins/techdocs-backend/package.json +++ b/plugins/techdocs-backend/package.json @@ -35,6 +35,7 @@ "@backstage/catalog-model": "^0.9.0", "@backstage/config": "^0.1.5", "@backstage/errors": "^0.1.1", + "@backstage/search-common": "^0.1.2", "@backstage/techdocs-common": "^0.6.8", "@types/express": "^4.17.6", "cross-fetch": "^3.0.6", @@ -43,10 +44,13 @@ "express-promise-router": "^4.1.0", "fs-extra": "9.1.0", "knex": "^0.95.1", + "lodash": "^4.17.21", + "p-limit": "^3.1.0", "winston": "^3.2.1" }, "devDependencies": { "@backstage/cli": "^0.7.4", + "@backstage/test-utils": "^0.1.14", "@types/dockerode": "^3.2.1", "msw": "^0.29.0", "supertest": "^6.1.3" diff --git a/plugins/techdocs-backend/src/index.ts b/plugins/techdocs-backend/src/index.ts index 6d1e551629..e2a89237db 100644 --- a/plugins/techdocs-backend/src/index.ts +++ b/plugins/techdocs-backend/src/index.ts @@ -15,4 +15,5 @@ */ export { createRouter } from './service/router'; +export * from './search'; export * from '@backstage/techdocs-common'; diff --git a/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.test.ts b/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.test.ts new file mode 100644 index 0000000000..618bb54d60 --- /dev/null +++ b/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.test.ts @@ -0,0 +1,149 @@ +/* + * 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 { + PluginEndpointDiscovery, + getVoidLogger, +} from '@backstage/backend-common'; +import { Entity } from '@backstage/catalog-model'; +import { DefaultTechDocsCollator } from './DefaultTechDocsCollator'; +import { msw } from '@backstage/test-utils'; +import { setupServer } from 'msw/node'; +import { rest } from 'msw'; + +const logger = getVoidLogger(); + +const mockSearchDocIndex = { + config: { + lang: ['en'], + min_search_length: 3, + prebuild_index: false, + separator: '[\\s\\-]+', + }, + docs: [ + { + location: '', + text: 'docs docs docs', + title: 'Home', + }, + { + location: 'local-development/', + text: 'Docs for first subtitle', + title: 'Local development', + }, + { + location: 'local-development/#development', + text: 'Docs for sub-subtitle', + title: 'Development', + }, + ], +}; + +const expectedEntities: Entity[] = [ + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'test-entity-with-docs', + description: 'Documented description', + annotations: { + 'backstage.io/techdocs-ref': './', + }, + }, + spec: { + type: 'dog', + lifecycle: 'experimental', + owner: 'someone', + }, + }, + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'test-entity', + description: 'The expected description', + }, + spec: { + type: 'some-type', + lifecycle: 'experimental', + }, + }, +]; + +describe('DefaultTechDocsCollator', () => { + let mockDiscoveryApi: jest.Mocked; + let collator: DefaultTechDocsCollator; + + const worker = setupServer(); + msw.setupDefaultHandlers(worker); + beforeEach(() => { + mockDiscoveryApi = { + getBaseUrl: jest.fn().mockResolvedValue('http://test-backend'), + getExternalBaseUrl: jest.fn(), + }; + collator = new DefaultTechDocsCollator({ + discovery: mockDiscoveryApi, + logger, + }); + + worker.use( + rest.get( + 'http://test-backend/static/docs/default/Component/test-entity-with-docs/search/search_index.json', + (_, res, ctx) => res(ctx.status(200), ctx.json(mockSearchDocIndex)), + ), + rest.get('http://test-backend/entities', (_, res, ctx) => + res(ctx.status(200), ctx.json(expectedEntities)), + ), + ); + }); + + it('fetches from the configured catalog and tech docs services', async () => { + const documents = await collator.execute(); + expect(mockDiscoveryApi.getBaseUrl).toHaveBeenCalledWith('catalog'); + expect(mockDiscoveryApi.getBaseUrl).toHaveBeenCalledWith('techdocs'); + expect(documents).toHaveLength(mockSearchDocIndex.docs.length); + }); + + it('should create documents for each tech docs search index', async () => { + const documents = await collator.execute(); + const entity = expectedEntities[0]; + documents.forEach((document, idx) => { + expect(document).toMatchObject({ + title: mockSearchDocIndex.docs[idx].title, + location: `/docs/default/Component/${entity.metadata.name}/${mockSearchDocIndex.docs[idx].location}`, + text: mockSearchDocIndex.docs[idx].text, + namespace: 'default', + componentType: entity!.spec!.type, + lifecycle: entity!.spec!.lifecycle, + owner: '', + }); + }); + }); + + it('maps a returned entity with a custom locationTemplate', async () => { + // Provide an alternate location template. + collator = new DefaultTechDocsCollator({ + discovery: mockDiscoveryApi, + locationTemplate: '/software/:name', + logger, + }); + + const documents = await collator.execute(); + expect(documents[0]).toMatchObject({ + location: '/software/test-entity-with-docs', + }); + }); +}); diff --git a/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.ts b/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.ts new file mode 100644 index 0000000000..7c176da9fb --- /dev/null +++ b/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.ts @@ -0,0 +1,149 @@ +/* + * 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 { PluginEndpointDiscovery } from '@backstage/backend-common'; +import { Entity, RELATION_OWNED_BY } from '@backstage/catalog-model'; +import { IndexableDocument, DocumentCollator } from '@backstage/search-common'; +import fetch from 'cross-fetch'; +import unescape from 'lodash/unescape'; +import { Logger } from 'winston'; +import pLimit from 'p-limit'; +import { CatalogApi, CatalogClient } from '@backstage/catalog-client'; + +interface MkSearchIndexDoc { + title: string; + text: string; + location: string; +} + +export interface TechDocsDocument extends IndexableDocument { + kind: string; + namespace: string; + name: string; + lifecycle: string; + owner: string; +} + +export class DefaultTechDocsCollator implements DocumentCollator { + protected discovery: PluginEndpointDiscovery; + protected locationTemplate: string; + private readonly logger: Logger; + private readonly catalogClient: CatalogApi; + private readonly parallelismLimit: number; + public readonly type: string = 'techdocs'; + + constructor({ + discovery, + locationTemplate, + logger, + catalogClient, + parallelismLimit = 10, + }: { + discovery: PluginEndpointDiscovery; + logger: Logger; + locationTemplate?: string; + catalogClient?: CatalogApi; + parallelismLimit?: number; + }) { + this.discovery = discovery; + this.locationTemplate = + locationTemplate || '/docs/:namespace/:kind/:name/:path'; + this.logger = logger; + this.catalogClient = + catalogClient || new CatalogClient({ discoveryApi: discovery }); + this.parallelismLimit = parallelismLimit; + } + + async execute() { + const limit = pLimit(this.parallelismLimit); + const techDocsBaseUrl = await this.discovery.getBaseUrl('techdocs'); + const entities = await this.catalogClient.getEntities({ + fields: [ + 'kind', + 'namespace', + 'metadata.annotations', + 'metadata.name', + 'metadata.namespace', + 'spec.type', + 'spec.lifecycle', + 'relations', + ], + }); + const docPromises = entities.items + .filter(it => it.metadata?.annotations?.['backstage.io/techdocs-ref']) + .map((entity: Entity) => + limit( + async (): Promise => { + const entityInfo = { + kind: entity.kind, + namespace: entity.metadata.namespace || 'default', + name: entity.metadata.name, + }; + + try { + const searchIndexResponse = await fetch( + DefaultTechDocsCollator.constructDocsIndexUrl( + techDocsBaseUrl, + entityInfo, + ), + ); + const searchIndex = await searchIndexResponse.json(); + + return searchIndex.docs.map((doc: MkSearchIndexDoc) => ({ + title: unescape(doc.title), + text: unescape(doc.text || ''), + location: this.applyArgsToFormat(this.locationTemplate, { + ...entityInfo, + path: doc.location, + }), + ...entityInfo, + componentType: entity.spec?.type?.toString() || 'other', + lifecycle: (entity.spec?.lifecycle as string) || '', + owner: + entity.relations?.find(r => r.type === RELATION_OWNED_BY) + ?.target?.name || '', + })); + } catch (e) { + this.logger.warn( + `Failed to retrieve tech docs search index for entity ${entityInfo.namespace}/${entityInfo.kind}/${entityInfo.name}`, + e, + ); + return []; + } + }, + ), + ); + return (await Promise.all(docPromises)).flat(); + } + + protected applyArgsToFormat( + format: string, + args: Record, + ): string { + let formatted = format; + for (const [key, value] of Object.entries(args)) { + formatted = formatted.replace(`:${key}`, value); + } + return formatted; + } + + private static constructDocsIndexUrl( + techDocsBaseUrl: string, + entityInfo: { kind: string; namespace: string; name: string }, + ) { + return `${techDocsBaseUrl}/static/docs/${entityInfo.namespace}/${entityInfo.kind}/${entityInfo.name}/search/search_index.json`; + } +} diff --git a/plugins/techdocs-backend/src/search/index.ts b/plugins/techdocs-backend/src/search/index.ts new file mode 100644 index 0000000000..74f348d769 --- /dev/null +++ b/plugins/techdocs-backend/src/search/index.ts @@ -0,0 +1,17 @@ +/* + * 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. + */ +export { DefaultTechDocsCollator } from './DefaultTechDocsCollator'; +export type { TechDocsDocument } from './DefaultTechDocsCollator'; diff --git a/plugins/techdocs/api-report.md b/plugins/techdocs/api-report.md index d63303c134..fdac1a8aab 100644 --- a/plugins/techdocs/api-report.md +++ b/plugins/techdocs/api-report.md @@ -25,6 +25,17 @@ export const DocsCardGrid: ({ entities: Entity[] | undefined; }) => JSX.Element | null; +// Warning: (ae-missing-release-tag) "DocsResultListItem" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const DocsResultListItem: ({ + result, + lineClamp, +}: { + result: any; + lineClamp?: number | undefined; +}) => JSX.Element; + // Warning: (ae-missing-release-tag) "DocsTable" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index 140be7d886..653fc99146 100644 --- a/plugins/techdocs/package.json +++ b/plugins/techdocs/package.json @@ -51,6 +51,7 @@ "react-router": "6.0.0-beta.0", "react-router-dom": "6.0.0-beta.0", "react-use": "^17.2.4", + "react-text-truncate": "^0.16.0", "sanitize-html": "^2.3.2" }, "devDependencies": { diff --git a/plugins/techdocs/src/components/DocsResultListItem/DocsResultListItem.test.tsx b/plugins/techdocs/src/components/DocsResultListItem/DocsResultListItem.test.tsx new file mode 100644 index 0000000000..41de2fefe4 --- /dev/null +++ b/plugins/techdocs/src/components/DocsResultListItem/DocsResultListItem.test.tsx @@ -0,0 +1,50 @@ +/* + * 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 React from 'react'; +import { render } from '@testing-library/react'; +import { DocsResultListItem } from './DocsResultListItem'; + +// Using canvas to render text.. +jest.mock('react-text-truncate', () => { + return ({ text }: { text: string }) => {text}; +}); + +const validResult = { + location: 'https://backstage.io/docs', + title: 'Documentation', + text: + 'Backstage is an open-source developer portal that puts the developer experience first.', + kind: 'library', + namespace: '', + name: 'Backstage', + lifecycle: 'production', +}; + +describe('DocsResultListItem test', () => { + it('should render search doc passed in', async () => { + const { findByText } = render(); + + expect( + await findByText('Documentation | Backstage docs'), + ).toBeInTheDocument(); + expect( + await findByText( + 'Backstage is an open-source developer portal that puts the developer experience first.', + ), + ).toBeInTheDocument(); + }); +}); diff --git a/plugins/techdocs/src/components/DocsResultListItem/DocsResultListItem.tsx b/plugins/techdocs/src/components/DocsResultListItem/DocsResultListItem.tsx new file mode 100644 index 0000000000..52aa45a97e --- /dev/null +++ b/plugins/techdocs/src/components/DocsResultListItem/DocsResultListItem.tsx @@ -0,0 +1,60 @@ +/* + * 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 React from 'react'; +import { Divider, ListItem, ListItemText, makeStyles } from '@material-ui/core'; +import { Link } from '@backstage/core-components'; +import TextTruncate from 'react-text-truncate'; + +const useStyles = makeStyles({ + flexContainer: { + flexWrap: 'wrap', + }, + itemText: { + width: '100%', + marginBottom: '1rem', + }, +}); + +export const DocsResultListItem = ({ + result, + lineClamp = 5, +}: { + result: any; + lineClamp?: number; +}) => { + const classes = useStyles(); + return ( + + + + } + /> + + + + ); +}; diff --git a/plugins/techdocs/src/components/DocsResultListItem/index.ts b/plugins/techdocs/src/components/DocsResultListItem/index.ts new file mode 100644 index 0000000000..4e1e511ea1 --- /dev/null +++ b/plugins/techdocs/src/components/DocsResultListItem/index.ts @@ -0,0 +1,17 @@ +/* + * 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. + */ + +export { DocsResultListItem } from './DocsResultListItem'; diff --git a/plugins/techdocs/src/index.ts b/plugins/techdocs/src/index.ts index d645d128f7..66502a4585 100644 --- a/plugins/techdocs/src/index.ts +++ b/plugins/techdocs/src/index.ts @@ -19,6 +19,7 @@ export { techdocsApiRef, techdocsStorageApiRef } from './api'; export type { TechDocsApi, TechDocsStorageApi } from './api'; export { TechDocsClient, TechDocsStorageClient } from './client'; export type { PanelType } from './home/components/TechDocsCustomHome'; +export * from './components/DocsResultListItem'; export { DocsCardGrid, DocsTable, From 5c4cc638549a3eae7489d5beba07b1e0db56e68d Mon Sep 17 00:00:00 2001 From: Philipp Hugenroth Date: Thu, 15 Jul 2021 14:00:24 +0200 Subject: [PATCH 022/106] Use ellipsis in the header Signed-off-by: Philipp Hugenroth --- .../src/layout/Header/Header.tsx | 1 + .../components/EntityLayout/EntityLayout.tsx | 39 ++++++++++++------- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/packages/core-components/src/layout/Header/Header.tsx b/packages/core-components/src/layout/Header/Header.tsx index b02ba29d88..572f1f8c32 100644 --- a/packages/core-components/src/layout/Header/Header.tsx +++ b/packages/core-components/src/layout/Header/Header.tsx @@ -42,6 +42,7 @@ const useStyles = makeStyles(theme => ({ backgroundSize: 'cover', }, leftItemsBox: { + maxWidth: '100%', flex: '1 1 auto', marginBottom: theme.spacing(1), }, diff --git a/plugins/catalog/src/components/EntityLayout/EntityLayout.tsx b/plugins/catalog/src/components/EntityLayout/EntityLayout.tsx index 9cb0b7c961..a5b85082cc 100644 --- a/plugins/catalog/src/components/EntityLayout/EntityLayout.tsx +++ b/plugins/catalog/src/components/EntityLayout/EntityLayout.tsx @@ -20,9 +20,17 @@ import { RELATION_OWNED_BY, } from '@backstage/catalog-model'; import { - useElementFilter, + Content, + Header, + HeaderLabel, + Page, + Progress, + RoutedTabs, +} from '@backstage/core-components'; +import { attachComponentData, IconComponent, + useElementFilter, } from '@backstage/core-plugin-api'; import { EntityContext, @@ -37,14 +45,6 @@ import { useNavigate } from 'react-router'; import { EntityContextMenu } from '../EntityContextMenu/EntityContextMenu'; import { FavouriteEntity } from '../FavouriteEntity/FavouriteEntity'; import { UnregisterEntityDialog } from '../UnregisterEntityDialog/UnregisterEntityDialog'; -import { - Content, - Header, - HeaderLabel, - Page, - Progress, - RoutedTabs, -} from '@backstage/core-components'; type SubRoute = { path: string; @@ -68,12 +68,21 @@ const EntityLayoutTitle = ({ }: { title: string; entity: Entity | undefined; -}) => ( - - {title} - {entity && } - -); +}) => { + return ( + + + {title} + + {entity && } + + ); +}; const headerProps = ( paramKind: string | undefined, From 378cc6a54bdb3f4f149201c32b018d7f93f3b560 Mon Sep 17 00:00:00 2001 From: Dominik Henneke Date: Thu, 15 Jul 2021 11:22:31 +0200 Subject: [PATCH 023/106] Only update the path when the content is updated Signed-off-by: Dominik Henneke --- .changeset/techdocs-fifty-cameras-listen.md | 5 + .../techdocs/src/reader/components/Reader.tsx | 4 +- .../reader/components/useReaderState.test.tsx | 160 +++++++++++++++--- .../src/reader/components/useReaderState.ts | 26 ++- 4 files changed, 157 insertions(+), 38 deletions(-) create mode 100644 .changeset/techdocs-fifty-cameras-listen.md diff --git a/.changeset/techdocs-fifty-cameras-listen.md b/.changeset/techdocs-fifty-cameras-listen.md new file mode 100644 index 0000000000..9071004acd --- /dev/null +++ b/.changeset/techdocs-fifty-cameras-listen.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +Only update the `path` when the content is updated. diff --git a/plugins/techdocs/src/reader/components/Reader.tsx b/plugins/techdocs/src/reader/components/Reader.tsx index d63ddf8125..76349784e7 100644 --- a/plugins/techdocs/src/reader/components/Reader.tsx +++ b/plugins/techdocs/src/reader/components/Reader.tsx @@ -47,17 +47,17 @@ type Props = { export const Reader = ({ entityId, onReady }: Props) => { const { kind, namespace, name } = entityId; - const { '*': path } = useParams(); const theme = useTheme(); const { state, + path, contentReload, content: rawPage, contentErrorMessage, syncErrorMessage, buildLog, - } = useReaderState(kind, namespace, name, path); + } = useReaderState(kind, namespace, name, useParams()['*']); const techdocsStorageApi = useApi(techdocsStorageApiRef); const [sidebars, setSidebars] = useState(); diff --git a/plugins/techdocs/src/reader/components/useReaderState.test.tsx b/plugins/techdocs/src/reader/components/useReaderState.test.tsx index 9d9a8e2b1a..ebc9277096 100644 --- a/plugins/techdocs/src/reader/components/useReaderState.test.tsx +++ b/plugins/techdocs/src/reader/components/useReaderState.test.tsx @@ -86,7 +86,7 @@ describe('useReaderState', () => { }; it('should return a copy of the state', () => { - expect(reducer(oldState, { type: 'navigate', path: '/' })).toEqual({ + expect(reducer(oldState, { type: 'content', path: '/' })).toEqual({ activeSyncState: 'CHECKING', contentLoading: false, path: '/', @@ -102,13 +102,11 @@ describe('useReaderState', () => { }); it.each` - type | oldActiveSyncState | newActiveSyncState - ${'content'} | ${'BUILD_READY'} | ${'UP_TO_DATE'} - ${'content'} | ${'BUILD_READY_RELOAD'} | ${'UP_TO_DATE'} - ${'navigate'} | ${'BUILD_READY'} | ${'UP_TO_DATE'} - ${'navigate'} | ${'BUILD_READY_RELOAD'} | ${'UP_TO_DATE'} - ${'sync'} | ${'BUILD_READY'} | ${undefined} - ${'sync'} | ${'BUILD_READY_RELOAD'} | ${undefined} + type | oldActiveSyncState | newActiveSyncState + ${'content'} | ${'BUILD_READY'} | ${'UP_TO_DATE'} + ${'content'} | ${'BUILD_READY_RELOAD'} | ${'UP_TO_DATE'} + ${'sync'} | ${'BUILD_READY'} | ${undefined} + ${'sync'} | ${'BUILD_READY_RELOAD'} | ${undefined} `( 'should, when type=$type and activeSyncState=$oldActiveSyncState, set activeSyncState=$newActiveSyncState', ({ type, oldActiveSyncState, newActiveSyncState }) => { @@ -164,6 +162,27 @@ describe('useReaderState', () => { }); }); + it('should set content and update path', () => { + expect( + reducer( + { + ...oldState, + contentLoading: true, + }, + { + type: 'content', + content: 'asdf', + path: '/new-path', + }, + ), + ).toEqual({ + ...oldState, + contentLoading: false, + content: 'asdf', + path: '/new-path', + }); + }); + it('should set error', () => { expect( reducer( @@ -185,20 +204,6 @@ describe('useReaderState', () => { }); }); - describe('"navigate" action', () => { - it('should work', () => { - expect( - reducer(oldState, { - type: 'navigate', - path: '/', - }), - ).toEqual({ - ...oldState, - path: '/', - }); - }); - }); - describe('"sync" action', () => { it('should update state', () => { expect( @@ -256,6 +261,7 @@ describe('useReaderState', () => { expect(result.current).toEqual({ state: 'CHECKING', + path: '/example', content: undefined, contentErrorMessage: undefined, syncErrorMessage: undefined, @@ -267,6 +273,7 @@ describe('useReaderState', () => { expect(result.current).toEqual({ state: 'CONTENT_FRESH', + path: '/example', content: 'my content', contentErrorMessage: undefined, syncErrorMessage: undefined, @@ -313,6 +320,7 @@ describe('useReaderState', () => { expect(result.current).toEqual({ state: 'CHECKING', + path: '/example', content: undefined, contentErrorMessage: undefined, syncErrorMessage: undefined, @@ -324,6 +332,7 @@ describe('useReaderState', () => { expect(result.current).toEqual({ state: 'INITIAL_BUILD', + path: '/example', content: undefined, contentErrorMessage: 'NotFoundError: Page Not Found', syncErrorMessage: undefined, @@ -335,6 +344,7 @@ describe('useReaderState', () => { expect(result.current).toEqual({ state: 'CHECKING', + path: '/example', content: undefined, contentErrorMessage: undefined, syncErrorMessage: undefined, @@ -346,6 +356,7 @@ describe('useReaderState', () => { expect(result.current).toEqual({ state: 'CONTENT_FRESH', + path: '/example', content: 'my content', contentErrorMessage: undefined, syncErrorMessage: undefined, @@ -394,6 +405,7 @@ describe('useReaderState', () => { expect(result.current).toEqual({ state: 'CHECKING', + path: '/example', content: undefined, contentErrorMessage: undefined, syncErrorMessage: undefined, @@ -405,6 +417,7 @@ describe('useReaderState', () => { await waitForValueToChange(() => result.current.state); expect(result.current).toEqual({ state: 'CONTENT_FRESH', + path: '/example', content: 'my content', contentErrorMessage: undefined, syncErrorMessage: undefined, @@ -416,6 +429,7 @@ describe('useReaderState', () => { await waitForValueToChange(() => result.current.state); expect(result.current).toEqual({ state: 'CONTENT_STALE_REFRESHING', + path: '/example', content: 'my content', contentErrorMessage: undefined, syncErrorMessage: undefined, @@ -427,6 +441,7 @@ describe('useReaderState', () => { await waitForValueToChange(() => result.current.state); expect(result.current).toEqual({ state: 'CONTENT_STALE_READY', + path: '/example', content: 'my content', contentErrorMessage: undefined, syncErrorMessage: undefined, @@ -441,6 +456,7 @@ describe('useReaderState', () => { await waitForValueToChange(() => result.current.state); expect(result.current).toEqual({ state: 'CHECKING', + path: '/example', content: undefined, contentErrorMessage: undefined, syncErrorMessage: undefined, @@ -452,6 +468,7 @@ describe('useReaderState', () => { await waitForValueToChange(() => result.current.state); expect(result.current).toEqual({ state: 'CONTENT_FRESH', + path: '/example', content: 'my new content', contentErrorMessage: undefined, syncErrorMessage: undefined, @@ -475,6 +492,103 @@ describe('useReaderState', () => { }); }); + it('should handle navigation', async () => { + techdocsStorageApi.getEntityDocs + .mockResolvedValueOnce('my content') + .mockImplementationOnce(async () => { + await new Promise(resolve => setTimeout(resolve, 1100)); + return 'my new content'; + }) + .mockRejectedValueOnce(new NotFoundError('Some error description')); + techdocsStorageApi.syncEntityDocs.mockResolvedValue('cached'); + + await act(async () => { + const { result, waitForValueToChange, rerender } = await renderHook( + ({ path }: { path: string }) => + useReaderState('Component', 'default', 'backstage', path), + { initialProps: { path: '/example' }, wrapper: Wrapper as any }, + ); + + expect(result.current).toEqual({ + state: 'CHECKING', + path: '/example', + content: undefined, + contentErrorMessage: undefined, + syncErrorMessage: undefined, + buildLog: [], + contentReload: expect.any(Function), + }); + + // show the content + await waitForValueToChange(() => result.current.state); + expect(result.current).toEqual({ + state: 'CONTENT_FRESH', + path: '/example', + content: 'my content', + contentErrorMessage: undefined, + syncErrorMessage: undefined, + buildLog: [], + contentReload: expect.any(Function), + }); + + // navigate + rerender({ path: '/new' }); + + await waitForValueToChange(() => result.current.state); + expect(result.current).toEqual({ + state: 'CHECKING', + path: '/example', + content: undefined, + contentErrorMessage: undefined, + syncErrorMessage: undefined, + buildLog: [], + contentReload: expect.any(Function), + }); + + await waitForValueToChange(() => result.current.state); + expect(result.current).toEqual({ + state: 'CONTENT_FRESH', + path: '/new', + content: 'my new content', + contentErrorMessage: undefined, + syncErrorMessage: undefined, + buildLog: [], + contentReload: expect.any(Function), + }); + + // navigate + rerender({ path: '/missing' }); + + await waitForValueToChange(() => result.current.state); + expect(result.current).toEqual({ + state: 'CONTENT_NOT_FOUND', + path: '/missing', + content: undefined, + contentErrorMessage: 'NotFoundError: Some error description', + syncErrorMessage: undefined, + buildLog: [], + contentReload: expect.any(Function), + }); + + expect(techdocsStorageApi.getEntityDocs).toBeCalledWith( + { kind: 'Component', namespace: 'default', name: 'backstage' }, + '/example', + ); + expect(techdocsStorageApi.getEntityDocs).toBeCalledWith( + { kind: 'Component', namespace: 'default', name: 'backstage' }, + '/new', + ); + expect(techdocsStorageApi.syncEntityDocs).toBeCalledWith( + { + kind: 'Component', + namespace: 'default', + name: 'backstage', + }, + expect.any(Function), + ); + }); + }); + it('should handle content error', async () => { techdocsStorageApi.getEntityDocs.mockRejectedValue( new NotFoundError('Some error description'), @@ -489,6 +603,7 @@ describe('useReaderState', () => { expect(result.current).toEqual({ state: 'CHECKING', + path: '/example', content: undefined, contentErrorMessage: undefined, syncErrorMessage: undefined, @@ -500,6 +615,7 @@ describe('useReaderState', () => { await waitForValueToChange(() => result.current.state); expect(result.current).toEqual({ state: 'CONTENT_NOT_FOUND', + path: '/example', content: undefined, contentErrorMessage: 'NotFoundError: Some error description', syncErrorMessage: undefined, diff --git a/plugins/techdocs/src/reader/components/useReaderState.ts b/plugins/techdocs/src/reader/components/useReaderState.ts index 178c0746bf..fabcfa2687 100644 --- a/plugins/techdocs/src/reader/components/useReaderState.ts +++ b/plugins/techdocs/src/reader/components/useReaderState.ts @@ -15,7 +15,7 @@ */ import { useApi } from '@backstage/core-plugin-api'; -import { useEffect, useMemo, useReducer, useRef } from 'react'; +import { useMemo, useReducer, useRef } from 'react'; import { useAsync, useAsyncRetry } from 'react-use'; import { techdocsStorageApiRef } from '../../api'; @@ -133,11 +133,11 @@ type ReducerActions = } | { type: 'content'; + path?: string; content?: string; contentLoading?: true; contentError?: Error; } - | { type: 'navigate'; path: string } | { type: 'buildLog'; log: string }; type ReducerState = { @@ -187,15 +187,15 @@ export function reducer( break; case 'content': + if (typeof action.path === 'string') { + newState.path = action.path; + } + newState.content = action.content; newState.contentLoading = action.contentLoading ?? false; newState.contentError = action.contentError; break; - case 'navigate': - newState.path = action.path; - break; - case 'buildLog': newState.buildLog = newState.buildLog.concat(action.log); break; @@ -207,7 +207,7 @@ export function reducer( // a navigation or a content update loads fresh content so the build is updated to being up-to-date if ( ['BUILD_READY', 'BUILD_READY_RELOAD'].includes(newState.activeSyncState) && - ['content', 'navigate'].includes(action.type) + ['content'].includes(action.type) ) { newState.activeSyncState = 'UP_TO_DATE'; newState.buildLog = []; @@ -223,6 +223,7 @@ export function useReaderState( path: string, ): { state: ContentStateTypes; + path: string; contentReload: () => void; content?: string; contentErrorMessage?: string; @@ -238,11 +239,6 @@ export function useReaderState( const techdocsStorageApi = useApi(techdocsStorageApiRef); - // convert all path changes into actions - useEffect(() => { - dispatch({ type: 'navigate', path }); - }, [path]); - // try to load the content. the function will fire events and we don't care for the return values const { retry: contentReload } = useAsyncRetry(async () => { dispatch({ type: 'content', contentLoading: true }); @@ -253,11 +249,12 @@ export function useReaderState( path, ); - dispatch({ type: 'content', content: entityDocs }); + // update content and path at the same time + dispatch({ type: 'content', content: entityDocs, path }); return entityDocs; } catch (e) { - dispatch({ type: 'content', contentError: e }); + dispatch({ type: 'content', contentError: e, path }); } return undefined; @@ -335,6 +332,7 @@ export function useReaderState( return { state: displayState, contentReload, + path: state.path, content: state.content, contentErrorMessage: state.contentError?.toString(), syncErrorMessage: state.syncError?.toString(), From 3d3c43f0a70337fa6db69da2387088f2acce4705 Mon Sep 17 00:00:00 2001 From: Dominik Henneke Date: Thu, 15 Jul 2021 14:05:51 +0200 Subject: [PATCH 024/106] Create a dedicated `contentLoading` action that keeps old content This change makes sure that the content is removed when an error occurs such as a link to a missing page. It also stills shows the old content while the new one is loaded. The (delayed) loading indicator still shows that new content is loaded. Signed-off-by: Dominik Henneke --- .../techdocs/src/reader/components/Reader.tsx | 4 ++ .../reader/components/useReaderState.test.tsx | 51 +++++++++++++++---- .../src/reader/components/useReaderState.ts | 18 +++++-- 3 files changed, 58 insertions(+), 15 deletions(-) diff --git a/plugins/techdocs/src/reader/components/Reader.tsx b/plugins/techdocs/src/reader/components/Reader.tsx index 76349784e7..f981394036 100644 --- a/plugins/techdocs/src/reader/components/Reader.tsx +++ b/plugins/techdocs/src/reader/components/Reader.tsx @@ -96,6 +96,10 @@ export const Reader = ({ entityId, onReady }: Props) => { useEffect(() => { if (!rawPage || !shadowDomRef.current) { + // clear the shadow dom if no content is available + if (shadowDomRef.current?.shadowRoot) { + shadowDomRef.current.shadowRoot.innerHTML = ''; + } return; } if (onReady) { diff --git a/plugins/techdocs/src/reader/components/useReaderState.test.tsx b/plugins/techdocs/src/reader/components/useReaderState.test.tsx index ebc9277096..b0d4e0326a 100644 --- a/plugins/techdocs/src/reader/components/useReaderState.test.tsx +++ b/plugins/techdocs/src/reader/components/useReaderState.test.tsx @@ -102,11 +102,13 @@ describe('useReaderState', () => { }); it.each` - type | oldActiveSyncState | newActiveSyncState - ${'content'} | ${'BUILD_READY'} | ${'UP_TO_DATE'} - ${'content'} | ${'BUILD_READY_RELOAD'} | ${'UP_TO_DATE'} - ${'sync'} | ${'BUILD_READY'} | ${undefined} - ${'sync'} | ${'BUILD_READY_RELOAD'} | ${undefined} + type | oldActiveSyncState | newActiveSyncState + ${'contentLoading'} | ${'BUILD_READY'} | ${'UP_TO_DATE'} + ${'contentLoading'} | ${'BUILD_READY_RELOAD'} | ${'UP_TO_DATE'} + ${'content'} | ${'BUILD_READY'} | ${'UP_TO_DATE'} + ${'content'} | ${'BUILD_READY_RELOAD'} | ${'UP_TO_DATE'} + ${'sync'} | ${'BUILD_READY'} | ${undefined /* undefined, because we don't set an input */} + ${'sync'} | ${'BUILD_READY_RELOAD'} | ${undefined /* undefined, because we don't set an input */} `( 'should, when type=$type and activeSyncState=$oldActiveSyncState, set activeSyncState=$newActiveSyncState', ({ type, oldActiveSyncState, newActiveSyncState }) => { @@ -122,18 +124,45 @@ describe('useReaderState', () => { }, ); - describe('"content" action', () => { + describe('"contentLoading" action', () => { it('should set loading', () => { + expect( + reducer(oldState, { + type: 'contentLoading', + }), + ).toEqual({ + ...oldState, + contentLoading: true, + }); + }); + + it('should keep content', () => { expect( reducer( { ...oldState, content: 'some-old-content', + }, + { + type: 'contentLoading', + }, + ), + ).toEqual({ + ...oldState, + contentLoading: true, + content: 'some-old-content', + }); + }); + + it('should reset errors', () => { + expect( + reducer( + { + ...oldState, contentError: new Error(), }, { - type: 'content', - contentLoading: true, + type: 'contentLoading', }, ), ).toEqual({ @@ -141,7 +170,9 @@ describe('useReaderState', () => { contentLoading: true, }); }); + }); + describe('"content" action', () => { it('should set content', () => { expect( reducer( @@ -457,7 +488,7 @@ describe('useReaderState', () => { expect(result.current).toEqual({ state: 'CHECKING', path: '/example', - content: undefined, + content: 'my content', contentErrorMessage: undefined, syncErrorMessage: undefined, buildLog: [], @@ -538,7 +569,7 @@ describe('useReaderState', () => { expect(result.current).toEqual({ state: 'CHECKING', path: '/example', - content: undefined, + content: 'my content', contentErrorMessage: undefined, syncErrorMessage: undefined, buildLog: [], diff --git a/plugins/techdocs/src/reader/components/useReaderState.ts b/plugins/techdocs/src/reader/components/useReaderState.ts index fabcfa2687..07bd338de9 100644 --- a/plugins/techdocs/src/reader/components/useReaderState.ts +++ b/plugins/techdocs/src/reader/components/useReaderState.ts @@ -131,11 +131,11 @@ type ReducerActions = state: SyncStates; syncError?: Error; } + | { type: 'contentLoading' } | { type: 'content'; path?: string; content?: string; - contentLoading?: true; contentError?: Error; } | { type: 'buildLog'; log: string }; @@ -186,13 +186,21 @@ export function reducer( newState.syncError = action.syncError; break; + case 'contentLoading': + newState.contentLoading = true; + + // only reset errors but keep the old content until it is replaced by the 'content' action + newState.contentError = undefined; + break; + case 'content': + // only override the path if it is part of the action if (typeof action.path === 'string') { newState.path = action.path; } + newState.contentLoading = false; newState.content = action.content; - newState.contentLoading = action.contentLoading ?? false; newState.contentError = action.contentError; break; @@ -204,10 +212,10 @@ export function reducer( throw new Error(); } - // a navigation or a content update loads fresh content so the build is updated to being up-to-date + // a content update loads fresh content so the build is updated to being up-to-date if ( ['BUILD_READY', 'BUILD_READY_RELOAD'].includes(newState.activeSyncState) && - ['content'].includes(action.type) + ['contentLoading', 'content'].includes(action.type) ) { newState.activeSyncState = 'UP_TO_DATE'; newState.buildLog = []; @@ -241,7 +249,7 @@ export function useReaderState( // try to load the content. the function will fire events and we don't care for the return values const { retry: contentReload } = useAsyncRetry(async () => { - dispatch({ type: 'content', contentLoading: true }); + dispatch({ type: 'contentLoading' }); try { const entityDocs = await techdocsStorageApi.getEntityDocs( From 214e7c52d14c02c0dccd6e4f623066c103e74619 Mon Sep 17 00:00:00 2001 From: Dominik Henneke Date: Wed, 7 Jul 2021 10:43:54 +0200 Subject: [PATCH 025/106] Refactor the techdocs transformers to return `Promise`s and await all transformations Signed-off-by: Dominik Henneke --- .changeset/techdocs-spotty-tables-beam.md | 5 ++ .../techdocs/src/reader/components/Reader.tsx | 12 ++-- .../reader/transformers/addBaseUrl.test.ts | 35 ++++++------ .../src/reader/transformers/addBaseUrl.ts | 30 +++++----- .../transformers/addGitFeedbackLink.test.ts | 20 +++---- .../transformers/addLinkClickListener.test.ts | 8 +-- .../src/reader/transformers/index.test.ts | 4 +- .../src/reader/transformers/injectCss.test.ts | 6 +- .../reader/transformers/onCssReady.test.ts | 21 ++++--- .../src/reader/transformers/onCssReady.ts | 6 +- .../transformers/removeMkdocsHeader.test.ts | 26 +++++---- .../transformers/rewriteDocLinks.test.ts | 12 ++-- .../transformers/sanitizeDOM/index.test.ts | 55 +++++++++++-------- .../transformers/simplifyMkdocsFooter.test.ts | 26 +++++---- .../src/reader/transformers/transformer.ts | 12 ++-- plugins/techdocs/src/test-utils/shadowDom.ts | 8 +-- 16 files changed, 159 insertions(+), 127 deletions(-) create mode 100644 .changeset/techdocs-spotty-tables-beam.md diff --git a/.changeset/techdocs-spotty-tables-beam.md b/.changeset/techdocs-spotty-tables-beam.md new file mode 100644 index 0000000000..397817b196 --- /dev/null +++ b/.changeset/techdocs-spotty-tables-beam.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +Refactor the techdocs transformers to return `Promise`s and await all transformations. diff --git a/plugins/techdocs/src/reader/components/Reader.tsx b/plugins/techdocs/src/reader/components/Reader.tsx index d63ddf8125..535419dc10 100644 --- a/plugins/techdocs/src/reader/components/Reader.tsx +++ b/plugins/techdocs/src/reader/components/Reader.tsx @@ -23,6 +23,7 @@ import { Button, CircularProgress, useTheme } from '@material-ui/core'; import { Alert } from '@material-ui/lab'; import React, { useCallback, useEffect, useRef, useState } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; +import { useAsync } from 'react-use'; import { techdocsStorageApiRef } from '../../api'; import { addBaseUrl, @@ -94,15 +95,16 @@ export const Reader = ({ entityId, onReady }: Props) => { // an update to "state" might lead to an updated UI so we include it as a trigger }, [updateSidebarPosition, state]); - useEffect(() => { + useAsync(async () => { if (!rawPage || !shadowDomRef.current) { return; } if (onReady) { onReady(); } + // Pre-render - const transformedElement = transformer(rawPage, [ + const transformedElement = await transformer(rawPage, [ sanitizeDOM(), addBaseUrl({ techdocsStorageApi, @@ -236,7 +238,7 @@ export const Reader = ({ entityId, onReady }: Props) => { }), ]); - if (!transformedElement) { + if (!transformedElement?.innerHTML) { return; // An unexpected error occurred } @@ -252,7 +254,7 @@ export const Reader = ({ entityId, onReady }: Props) => { window.scroll({ top: 0 }); // Post-render - transformer(shadowRoot.children[0], [ + await transformer(shadowRoot.children[0], [ dom => { setTimeout(() => { // Scoll to the desired anchor on initial navigation @@ -281,7 +283,7 @@ export const Reader = ({ entityId, onReady }: Props) => { }, }), onCssReady({ - docStorageUrl: techdocsStorageApi.getApiOrigin(), + docStorageUrl: await techdocsStorageApi.getApiOrigin(), onLoading: (dom: Element) => { (dom as HTMLElement).style.setProperty('opacity', '0'); }, diff --git a/plugins/techdocs/src/reader/transformers/addBaseUrl.test.ts b/plugins/techdocs/src/reader/transformers/addBaseUrl.test.ts index f04c9963f0..11e8375420 100644 --- a/plugins/techdocs/src/reader/transformers/addBaseUrl.test.ts +++ b/plugins/techdocs/src/reader/transformers/addBaseUrl.test.ts @@ -15,9 +15,9 @@ */ import { waitFor } from '@testing-library/react'; -import { createTestShadowDom } from '../../test-utils'; -import { addBaseUrl } from '../transformers'; import { TechDocsStorageApi } from '../../api'; +import { createTestShadowDom } from '../../test-utils'; +import { addBaseUrl } from './addBaseUrl'; const DOC_STORAGE_URL = 'https://example-host.storage.googleapis.com'; const API_ORIGIN_URL = 'https://backstage.example.com/api/techdocs'; @@ -62,8 +62,8 @@ describe('addBaseUrl', () => { global.fetch = originalFetch; }); - it('contains relative paths', () => { - createTestShadowDom(fixture, { + it('contains relative paths', async () => { + await createTestShadowDom(fixture, { preTransformers: [ addBaseUrl({ techdocsStorageApi, @@ -110,7 +110,7 @@ describe('addBaseUrl', () => { text: jest.fn().mockResolvedValue(svgContent), }); - const root = createTestShadowDom('', { + const root = await createTestShadowDom('', { preTransformers: [ addBaseUrl({ techdocsStorageApi, @@ -137,7 +137,7 @@ describe('addBaseUrl', () => { text: jest.fn().mockResolvedValue(svgContent), }); - const root = createTestShadowDom( + const root = await createTestShadowDom( ``, { preTransformers: [ @@ -162,16 +162,19 @@ describe('addBaseUrl', () => { it('does not inline external svgs', async () => { const expectedSrc = 'https://example.com/test.svg'; - const root = createTestShadowDom(``, { - preTransformers: [ - addBaseUrl({ - techdocsStorageApi, - entityId: mockEntityId, - path: '', - }), - ], - postTransformers: [], - }); + const root = await createTestShadowDom( + ``, + { + preTransformers: [ + addBaseUrl({ + techdocsStorageApi, + entityId: mockEntityId, + path: '', + }), + ], + postTransformers: [], + }, + ); await new Promise(done => { process.nextTick(() => { diff --git a/plugins/techdocs/src/reader/transformers/addBaseUrl.ts b/plugins/techdocs/src/reader/transformers/addBaseUrl.ts index f21cb8c47a..8f08ec83a6 100644 --- a/plugins/techdocs/src/reader/transformers/addBaseUrl.ts +++ b/plugins/techdocs/src/reader/transformers/addBaseUrl.ts @@ -14,8 +14,8 @@ * limitations under the License. */ import { EntityName } from '@backstage/catalog-model'; -import type { Transformer } from './transformer'; import { TechDocsStorageApi } from '../../api'; +import type { Transformer } from './transformer'; type AddBaseUrlOptions = { techdocsStorageApi: TechDocsStorageApi; @@ -44,14 +44,15 @@ export const addBaseUrl = ({ entityId, path, }: AddBaseUrlOptions): Transformer => { - return dom => { - const updateDom = ( + return async dom => { + const apiOrigin = await techdocsStorageApi.getApiOrigin(); + + const updateDom = async ( list: HTMLCollectionOf | NodeListOf, attributeName: string, - ): void => { - Array.from(list) - .filter(elem => !!elem.getAttribute(attributeName)) - .forEach(async (elem: T) => { + ) => { + for (const elem of list) { + if (elem.hasAttribute(attributeName)) { const elemAttribute = elem.getAttribute(attributeName); if (!elemAttribute) return; @@ -61,7 +62,7 @@ export const addBaseUrl = ({ entityId, path, ); - const apiOrigin = await techdocsStorageApi.getApiOrigin(); + if (isSvgNeedingInlining(attributeName, elemAttribute, apiOrigin)) { try { const svg = await fetch(newValue, { credentials: 'include' }); @@ -76,13 +77,16 @@ export const addBaseUrl = ({ } else { elem.setAttribute(attributeName, newValue); } - }); + } + } }; - updateDom(dom.querySelectorAll('img'), 'src'); - updateDom(dom.querySelectorAll('script'), 'src'); - updateDom(dom.querySelectorAll('link'), 'href'); - updateDom(dom.querySelectorAll('a[download]'), 'href'); + await Promise.all([ + updateDom(dom.querySelectorAll('img'), 'src'), + updateDom(dom.querySelectorAll('script'), 'src'), + updateDom(dom.querySelectorAll('link'), 'href'), + updateDom(dom.querySelectorAll('a[download]'), 'href'), + ]); return dom; }; diff --git a/plugins/techdocs/src/reader/transformers/addGitFeedbackLink.test.ts b/plugins/techdocs/src/reader/transformers/addGitFeedbackLink.test.ts index 2165afac98..0b8e431781 100644 --- a/plugins/techdocs/src/reader/transformers/addGitFeedbackLink.test.ts +++ b/plugins/techdocs/src/reader/transformers/addGitFeedbackLink.test.ts @@ -28,8 +28,8 @@ const integrations = ScmIntegrations.fromConfig( ); describe('addGitFeedbackLink', () => { - it('adds a feedback link when a Gitlab source edit link is available', () => { - const shadowDom = createTestShadowDom( + it('adds a feedback link when a Gitlab source edit link is available', async () => { + const shadowDom = await createTestShadowDom( ` @@ -53,8 +53,8 @@ describe('addGitFeedbackLink', () => { ); }); - it('adds a feedback link when a Github source edit link is available', () => { - const shadowDom = createTestShadowDom( + it('adds a feedback link when a Github source edit link is available', async () => { + const shadowDom = await createTestShadowDom( ` @@ -78,8 +78,8 @@ describe('addGitFeedbackLink', () => { ); }); - it('does not add a feedback link when no source edit link is available', () => { - const shadowDom = createTestShadowDom( + it('does not add a feedback link when no source edit link is available', async () => { + const shadowDom = await createTestShadowDom( ` @@ -97,8 +97,8 @@ describe('addGitFeedbackLink', () => { expect(shadowDom.querySelector('#git-feedback-link')).toBeFalsy(); }); - it('does not add a feedback link when a Gitlab or Github source edit link is not available', () => { - const shadowDom = createTestShadowDom( + it('does not add a feedback link when a Gitlab or Github source edit link is not available', async () => { + const shadowDom = await createTestShadowDom( ` @@ -117,8 +117,8 @@ describe('addGitFeedbackLink', () => { expect(shadowDom.querySelector('#git-feedback-link')).toBeFalsy(); }); - it('adds a feedback link when a Gitlab or Github source edit link is not available but hostname matches an integrations host', () => { - const shadowDom = createTestShadowDom( + it('adds a feedback link when a Gitlab or Github source edit link is not available but hostname matches an integrations host', async () => { + const shadowDom = await createTestShadowDom( ` diff --git a/plugins/techdocs/src/reader/transformers/addLinkClickListener.test.ts b/plugins/techdocs/src/reader/transformers/addLinkClickListener.test.ts index 2d2d54a31c..fe3a6e557c 100644 --- a/plugins/techdocs/src/reader/transformers/addLinkClickListener.test.ts +++ b/plugins/techdocs/src/reader/transformers/addLinkClickListener.test.ts @@ -18,9 +18,9 @@ import { createTestShadowDom } from '../../test-utils'; import { addLinkClickListener } from './addLinkClickListener'; describe('addLinkClickListener', () => { - it('calls onClick when a link has been clicked', () => { + it('calls onClick when a link has been clicked', async () => { const fn = jest.fn(); - const shadowDom = createTestShadowDom( + const shadowDom = await createTestShadowDom( ` @@ -45,9 +45,9 @@ describe('addLinkClickListener', () => { expect(fn).toHaveBeenCalledTimes(1); }); - it('does not call onClick when a link links to another baseUrl', () => { + it('does not call onClick when a link links to another baseUrl', async () => { const fn = jest.fn(); - const shadowDom = createTestShadowDom( + const shadowDom = await createTestShadowDom( ` diff --git a/plugins/techdocs/src/reader/transformers/index.test.ts b/plugins/techdocs/src/reader/transformers/index.test.ts index 30607aedcd..16b42266df 100644 --- a/plugins/techdocs/src/reader/transformers/index.test.ts +++ b/plugins/techdocs/src/reader/transformers/index.test.ts @@ -17,14 +17,14 @@ import { Transformer, transform } from './transformer'; describe('transform', () => { - it('calls the transformers', () => { + it('calls the transformers', async () => { const fn = jest.fn(); const mockTransformer = (): Transformer => (dom: Element) => { fn(dom); return dom; }; - transform('', [mockTransformer()]); + await transform('', [mockTransformer()]); expect(fn).toHaveBeenCalledTimes(1); expect(fn).toHaveBeenCalledWith(expect.any(Element)); diff --git a/plugins/techdocs/src/reader/transformers/injectCss.test.ts b/plugins/techdocs/src/reader/transformers/injectCss.test.ts index 368d077b43..6d0eb8daa9 100644 --- a/plugins/techdocs/src/reader/transformers/injectCss.test.ts +++ b/plugins/techdocs/src/reader/transformers/injectCss.test.ts @@ -15,10 +15,10 @@ */ import { createTestShadowDom } from '../../test-utils'; -import { injectCss } from '../transformers'; +import { injectCss } from './injectCss'; describe('injectCss', () => { - it('should inject style with passed css in head', () => { + it('should inject style with passed css in head', async () => { const html = ` @@ -27,7 +27,7 @@ describe('injectCss', () => { `; const injectedCss = '* {background-color: #fff}'; - const shadowDom = createTestShadowDom(html, { + const shadowDom = await createTestShadowDom(html, { preTransformers: [injectCss({ css: injectedCss })], postTransformers: [], }); diff --git a/plugins/techdocs/src/reader/transformers/onCssReady.test.ts b/plugins/techdocs/src/reader/transformers/onCssReady.test.ts index 3174e23f5c..b2a5aa9760 100644 --- a/plugins/techdocs/src/reader/transformers/onCssReady.test.ts +++ b/plugins/techdocs/src/reader/transformers/onCssReady.test.ts @@ -15,16 +15,15 @@ */ import { - createTestShadowDom, - mockStylesheetEventListener, - executeStylesheetEventListeners, clearStylesheetEventListeners, + createTestShadowDom, + executeStylesheetEventListeners, + mockStylesheetEventListener, } from '../../test-utils'; -import { onCssReady } from '../transformers'; +import { onCssReady } from './onCssReady'; -const docStorageUrl: Promise = Promise.resolve( - 'https://techdocs-mock-sites.storage.googleapis.com', -); +const docStorageUrl: string = + 'https://techdocs-mock-sites.storage.googleapis.com'; const fixture = ` @@ -48,11 +47,11 @@ describe('onCssReady', () => { clearStylesheetEventListeners(); }); - it('does not call onLoading and onLoaded without the onCssReady transformer', () => { + it('does not call onLoading and onLoaded without the onCssReady transformer', async () => { const onLoading = jest.fn(); const onLoaded = jest.fn(); - createTestShadowDom(fixture, { + await createTestShadowDom(fixture, { preTransformers: [], postTransformers: [], }); @@ -62,11 +61,11 @@ describe('onCssReady', () => { expect(onLoaded).not.toHaveBeenCalled(); }); - it('calls the onLoading and onLoaded correctly', () => { + it('calls the onLoading and onLoaded correctly', async () => { const onLoading = jest.fn(); const onLoaded = jest.fn(); - createTestShadowDom(fixture, { + await createTestShadowDom(fixture, { preTransformers: [], postTransformers: [ onCssReady({ diff --git a/plugins/techdocs/src/reader/transformers/onCssReady.ts b/plugins/techdocs/src/reader/transformers/onCssReady.ts index 936c4306a7..e9406ba632 100644 --- a/plugins/techdocs/src/reader/transformers/onCssReady.ts +++ b/plugins/techdocs/src/reader/transformers/onCssReady.ts @@ -17,7 +17,7 @@ import type { Transformer } from './transformer'; type OnCssReadyOptions = { - docStorageUrl: Promise; + docStorageUrl: string; onLoading: (dom: Element) => void; onLoaded: (dom: Element) => void; }; @@ -30,9 +30,7 @@ export const onCssReady = ({ return dom => { const cssPages = Array.from( dom.querySelectorAll('head > link[rel="stylesheet"]'), - ).filter(async elem => - elem.getAttribute('href')?.startsWith(await docStorageUrl), - ); + ).filter(elem => elem.getAttribute('href')?.startsWith(docStorageUrl)); let count = cssPages.length; diff --git a/plugins/techdocs/src/reader/transformers/removeMkdocsHeader.test.ts b/plugins/techdocs/src/reader/transformers/removeMkdocsHeader.test.ts index 70d6b2fa4a..60f0b9ca30 100644 --- a/plugins/techdocs/src/reader/transformers/removeMkdocsHeader.test.ts +++ b/plugins/techdocs/src/reader/transformers/removeMkdocsHeader.test.ts @@ -18,20 +18,26 @@ import { createTestShadowDom, FIXTURES } from '../../test-utils'; import { removeMkdocsHeader } from '../transformers'; describe('removeMkdocsHeader', () => { - it('does not remove mkdocs header', () => { - const shadowDom = createTestShadowDom(FIXTURES.FIXTURE_STANDARD_PAGE, { - preTransformers: [], - postTransformers: [], - }); + it('does not remove mkdocs header', async () => { + const shadowDom = await createTestShadowDom( + FIXTURES.FIXTURE_STANDARD_PAGE, + { + preTransformers: [], + postTransformers: [], + }, + ); expect(shadowDom.querySelector('.md-header')).toBeTruthy(); }); - it('does remove mkdocs header', () => { - const shadowDom = createTestShadowDom(FIXTURES.FIXTURE_STANDARD_PAGE, { - preTransformers: [removeMkdocsHeader()], - postTransformers: [], - }); + it('does remove mkdocs header', async () => { + const shadowDom = await createTestShadowDom( + FIXTURES.FIXTURE_STANDARD_PAGE, + { + preTransformers: [removeMkdocsHeader()], + postTransformers: [], + }, + ); expect(shadowDom.querySelector('.md-header')).toBeFalsy(); }); diff --git a/plugins/techdocs/src/reader/transformers/rewriteDocLinks.test.ts b/plugins/techdocs/src/reader/transformers/rewriteDocLinks.test.ts index 62b80cd514..66fc5f29c1 100644 --- a/plugins/techdocs/src/reader/transformers/rewriteDocLinks.test.ts +++ b/plugins/techdocs/src/reader/transformers/rewriteDocLinks.test.ts @@ -19,8 +19,8 @@ import { rewriteDocLinks } from '../transformers'; import { normalizeUrl } from './rewriteDocLinks'; describe('rewriteDocLinks', () => { - it('should not do anything', () => { - const shadowDom = createTestShadowDom(` + it('should not do anything', async () => { + const shadowDom = await createTestShadowDom(` Test Test Test @@ -35,8 +35,8 @@ describe('rewriteDocLinks', () => { ]); }); - it('should transform a href with localhost as baseUrl', () => { - const shadowDom = createTestShadowDom( + it('should transform a href with localhost as baseUrl', async () => { + const shadowDom = await createTestShadowDom( ` Test Test @@ -57,9 +57,9 @@ describe('rewriteDocLinks', () => { ]); }); - it('should rewrite non-parseable URLs as text', () => { + it('should rewrite non-parseable URLs as text', async () => { const expectedText = `www.my-internet.[top-level-domain]/pathname/[URLkey]`; - const shadowDom = createTestShadowDom( + const shadowDom = await createTestShadowDom( `${expectedText}`, { preTransformers: [rewriteDocLinks()], diff --git a/plugins/techdocs/src/reader/transformers/sanitizeDOM/index.test.ts b/plugins/techdocs/src/reader/transformers/sanitizeDOM/index.test.ts index 8456cf7c23..6296793e2a 100644 --- a/plugins/techdocs/src/reader/transformers/sanitizeDOM/index.test.ts +++ b/plugins/techdocs/src/reader/transformers/sanitizeDOM/index.test.ts @@ -16,7 +16,7 @@ import { createTestShadowDom, FIXTURES } from '../../../test-utils'; import { Transformer } from '../index'; -import { sanitizeDOM } from '../sanitizeDOM'; +import { sanitizeDOM } from './index'; const injectMaliciousLink = (): Transformer => dom => { const link = document.createElement('a'); @@ -27,55 +27,64 @@ const injectMaliciousLink = (): Transformer => dom => { }; describe('sanitizeDOM', () => { - it('contains a script tag', () => { - const shadowDom = createTestShadowDom(FIXTURES.FIXTURE_STANDARD_PAGE); + it('contains a script tag', async () => { + const shadowDom = await createTestShadowDom(FIXTURES.FIXTURE_STANDARD_PAGE); expect(shadowDom.querySelectorAll('script').length).toBeGreaterThan(0); }); - it('does not contain a script tag', () => { - const shadowDom = createTestShadowDom(FIXTURES.FIXTURE_STANDARD_PAGE, { - preTransformers: [sanitizeDOM()], - postTransformers: [], - }); + it('does not contain a script tag', async () => { + const shadowDom = await createTestShadowDom( + FIXTURES.FIXTURE_STANDARD_PAGE, + { + preTransformers: [sanitizeDOM()], + postTransformers: [], + }, + ); expect(shadowDom.querySelectorAll('script').length).toBe(0); }); - it('contains link with a onClick attribute', () => { - const shadowDom = createTestShadowDom(FIXTURES.FIXTURE_STANDARD_PAGE, { - preTransformers: [injectMaliciousLink()], - postTransformers: [], - }); + it('contains link with a onClick attribute', async () => { + const shadowDom = await createTestShadowDom( + FIXTURES.FIXTURE_STANDARD_PAGE, + { + preTransformers: [injectMaliciousLink()], + postTransformers: [], + }, + ); expect( shadowDom.querySelector('#test-malicious-link')?.hasAttribute('onclick'), ).toBeTruthy(); }); - it('does not contain link with a onClick attribute', () => { - const shadowDom = createTestShadowDom(FIXTURES.FIXTURE_STANDARD_PAGE, { - preTransformers: [sanitizeDOM()], - postTransformers: [], - }); + it('does not contain link with a onClick attribute', async () => { + const shadowDom = await createTestShadowDom( + FIXTURES.FIXTURE_STANDARD_PAGE, + { + preTransformers: [sanitizeDOM()], + postTransformers: [], + }, + ); expect( shadowDom.querySelector('#test-malicious-link')?.hasAttribute('onclick'), ).toBeFalsy(); }); - it('removes style tags', () => { + it('removes style tags', async () => { const html = ` - `; - const shadowDom = createTestShadowDom(html, { + const shadowDom = await createTestShadowDom(html, { preTransformers: [sanitizeDOM()], postTransformers: [], }); @@ -83,7 +92,7 @@ describe('sanitizeDOM', () => { expect(shadowDom.querySelectorAll('style').length).toEqual(0); }); - it('does not remove link tags', () => { + it('does not remove link tags', async () => { const html = ` @@ -94,7 +103,7 @@ describe('sanitizeDOM', () => { `; - const shadowDom = createTestShadowDom(html, { + const shadowDom = await createTestShadowDom(html, { preTransformers: [sanitizeDOM()], postTransformers: [], }); diff --git a/plugins/techdocs/src/reader/transformers/simplifyMkdocsFooter.test.ts b/plugins/techdocs/src/reader/transformers/simplifyMkdocsFooter.test.ts index dbc3761e80..020befd522 100644 --- a/plugins/techdocs/src/reader/transformers/simplifyMkdocsFooter.test.ts +++ b/plugins/techdocs/src/reader/transformers/simplifyMkdocsFooter.test.ts @@ -18,20 +18,26 @@ import { createTestShadowDom, FIXTURES } from '../../test-utils'; import { simplifyMkdocsFooter } from './simplifyMkdocsFooter'; describe('simplifyMkdocsFooter', () => { - it('does not remove mkdocs copyright', () => { - const shadowDom = createTestShadowDom(FIXTURES.FIXTURE_STANDARD_PAGE, { - preTransformers: [], - postTransformers: [], - }); + it('does not remove mkdocs copyright', async () => { + const shadowDom = await createTestShadowDom( + FIXTURES.FIXTURE_STANDARD_PAGE, + { + preTransformers: [], + postTransformers: [], + }, + ); expect(shadowDom.querySelector('.md-footer-copyright')).toBeTruthy(); }); - it('does remove mkdocs copyright', () => { - const shadowDom = createTestShadowDom(FIXTURES.FIXTURE_STANDARD_PAGE, { - preTransformers: [simplifyMkdocsFooter()], - postTransformers: [], - }); + it('does remove mkdocs copyright', async () => { + const shadowDom = await createTestShadowDom( + FIXTURES.FIXTURE_STANDARD_PAGE, + { + preTransformers: [simplifyMkdocsFooter()], + postTransformers: [], + }, + ); expect(shadowDom.querySelector('.md-footer-copyright')).toBeFalsy(); }); diff --git a/plugins/techdocs/src/reader/transformers/transformer.ts b/plugins/techdocs/src/reader/transformers/transformer.ts index 7b440befbf..fc52f42b5c 100644 --- a/plugins/techdocs/src/reader/transformers/transformer.ts +++ b/plugins/techdocs/src/reader/transformers/transformer.ts @@ -14,12 +14,12 @@ * limitations under the License. */ -export type Transformer = (dom: Element) => Element; +export type Transformer = (dom: Element) => Element | Promise; -export const transform = ( +export const transform = async ( html: string | Element, transformers: Transformer[], -): Element => { +): Promise => { let dom: Element; if (typeof html === 'string') { @@ -30,9 +30,9 @@ export const transform = ( throw new Error('dom is not a recognized type'); } - transformers.forEach(transformer => { - dom = transformer(dom); - }); + for (const transformer of transformers) { + dom = await transformer(dom); + } return dom; }; diff --git a/plugins/techdocs/src/test-utils/shadowDom.ts b/plugins/techdocs/src/test-utils/shadowDom.ts index 61055a87bd..f8289bf6d9 100644 --- a/plugins/techdocs/src/test-utils/shadowDom.ts +++ b/plugins/techdocs/src/test-utils/shadowDom.ts @@ -22,13 +22,13 @@ export type CreateTestShadowDomOptions = { postTransformers: Transformer[]; }; -export const createTestShadowDom = ( +export const createTestShadowDom = async ( fixture: string, opts: CreateTestShadowDomOptions = { preTransformers: [], postTransformers: [], }, -): ShadowRoot => { +): Promise => { const divElement = document.createElement('div'); divElement.attachShadow({ mode: 'open' }); document.body.appendChild(divElement); @@ -39,7 +39,7 @@ export const createTestShadowDom = ( 'text/html', ).documentElement; if (opts.preTransformers) { - dom = transformer(dom, opts.preTransformers); + dom = await transformer(dom, opts.preTransformers); } // Mount the UI @@ -47,7 +47,7 @@ export const createTestShadowDom = ( // Transformers after the UI is rendered if (opts.postTransformers) { - transformer(dom, opts.postTransformers); + await transformer(dom, opts.postTransformers); } return divElement.shadowRoot!; From bcfdd852c253091ded73e10da7ab9a6e3b8005a8 Mon Sep 17 00:00:00 2001 From: Philipp Hugenroth Date: Thu, 15 Jul 2021 15:12:16 +0200 Subject: [PATCH 026/106] Break error message text for docs on EntityPage Signed-off-by: Philipp Hugenroth --- .../techdocs/src/reader/components/Reader.tsx | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/plugins/techdocs/src/reader/components/Reader.tsx b/plugins/techdocs/src/reader/components/Reader.tsx index d63ddf8125..9dd8a2d656 100644 --- a/plugins/techdocs/src/reader/components/Reader.tsx +++ b/plugins/techdocs/src/reader/components/Reader.tsx @@ -19,7 +19,12 @@ import { Progress } from '@backstage/core-components'; import { useApi } from '@backstage/core-plugin-api'; import { scmIntegrationsApiRef } from '@backstage/integration-react'; import { BackstageTheme } from '@backstage/theme'; -import { Button, CircularProgress, useTheme } from '@material-ui/core'; +import { + Button, + CircularProgress, + makeStyles, + useTheme, +} from '@material-ui/core'; import { Alert } from '@material-ui/lab'; import React, { useCallback, useEffect, useRef, useState } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; @@ -45,10 +50,17 @@ type Props = { onReady?: () => void; }; +const useStyles = makeStyles(() => ({ + message: { + overflowWrap: 'anywhere', + }, +})); + export const Reader = ({ entityId, onReady }: Props) => { const { kind, namespace, name } = entityId; const { '*': path } = useParams(); const theme = useTheme(); + const classes = useStyles(); const { state, @@ -369,6 +381,7 @@ export const Reader = ({ entityId, onReady }: Props) => { variant="outlined" severity="error" action={} + classes={{ message: classes.message }} > Building a newer version of this documentation failed.{' '} {syncErrorMessage} @@ -381,6 +394,7 @@ export const Reader = ({ entityId, onReady }: Props) => { variant="outlined" severity="error" action={} + classes={{ message: classes.message }} > Building a newer version of this documentation failed.{' '} {syncErrorMessage} From 73b7f246e4ff24179caffd56eb5cb49e3b2f83f3 Mon Sep 17 00:00:00 2001 From: Philipp Hugenroth Date: Thu, 15 Jul 2021 18:56:16 +0200 Subject: [PATCH 027/106] Fix testing for MUI breakpoints Signed-off-by: Philipp Hugenroth --- .../src/testUtils/mockBreakpoint.ts | 112 ++++-------------- .../CatalogPage/CatalogPage.test.tsx | 17 ++- .../FilteredTableLayout/FilterContainer.tsx | 3 +- 3 files changed, 36 insertions(+), 96 deletions(-) diff --git a/packages/test-utils/src/testUtils/mockBreakpoint.ts b/packages/test-utils/src/testUtils/mockBreakpoint.ts index 0e04f58f70..3335284f88 100644 --- a/packages/test-utils/src/testUtils/mockBreakpoint.ts +++ b/packages/test-utils/src/testUtils/mockBreakpoint.ts @@ -14,96 +14,30 @@ * limitations under the License. */ -import { act } from '@testing-library/react'; - -type Breakpoint = 'xs' | 'sm' | 'md' | 'lg' | 'xl'; - -const defaultBreakpoint: Breakpoint = 'xl'; - -const queryToBreakpoint = { - '(min-width:1920px)': 'xl', - '(min-width:1280px)': 'lg', - '(min-width:960px)': 'md', - '(min-width:600px)': 'sm', - '(min-width:0px)': 'xs', -} as Record; - /** - * Converts media query string to Breakpoint. - * Chooses the default breakpoint if no breakpoint is set in the media query. + * This is a mocking method suggested in the Jest Doc's, as it is not implemented in JSDOM yet. + * It can be used to mock values when the MUI `useMediaQuery` hook if it is used in a tested component. * - * @param query media query string - * @returns Breakpoint + * For issues checkout the documentation: + * https://jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom + * + * If there are any updates from MUI React on testing `useMediaQuery` this mock should be replaced + * https://material-ui.com/components/use-media-query/#testing + * + * @param matchMediaOptions */ -function toBreakpoint(query: string): Breakpoint { - return queryToBreakpoint[query] - ? queryToBreakpoint[query] - : defaultBreakpoint; -} - -type Listener = (event: { matches: boolean }) => void; - -interface QueryList { - addListener(listener: Listener): void; - removeListener(listener: Listener): void; - addEventListener(listener: Listener): void; - removeEventListener(listener: Listener): void; - matches: boolean; -} - -interface Query { - query: string; - queryList: QueryList; - listeners: Set; -} - -export default function mockBreakpoint( - initialBreakpoint: Breakpoint = defaultBreakpoint, -) { - let currentBreakpoint = initialBreakpoint; - const queries = Array(); - - const previousMatchMedia: any = (window as any).matchMedia; - - // https://jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom - (window as any).matchMedia = (query: string): QueryList => { - const listeners = new Set(); - - const queryList: QueryList = { - addListener(listener) { - listeners.add(listener); - }, - removeListener(listener) { - listeners.delete(listener); - }, - addEventListener(listener) { - listeners.add(listener); - }, - removeEventListener(listener) { - listeners.delete(listener); - }, - matches: toBreakpoint(query) === currentBreakpoint, - }; - - queries.push({ query, queryList, listeners }); - - return queryList; - }; - - return { - set(breakpoint: Breakpoint) { - currentBreakpoint = breakpoint; - - act(() => { - queries.forEach(({ query, queryList, listeners }) => { - const matches = toBreakpoint(query) === breakpoint; - queryList.matches = matches; - listeners.forEach(listener => listener({ matches })); - }); - }); - }, - remove() { - (window as any).matchMedia = previousMatchMedia; - }, - }; +export default function mockBreakpoint({ matches = false }) { + Object.defineProperty(window, 'matchMedia', { + writable: true, + value: jest.fn().mockImplementation(query => ({ + matches: matches, + media: query, + onchange: null, + addListener: jest.fn(), // deprecated + removeListener: jest.fn(), // deprecated + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + dispatchEvent: jest.fn(), + })), + }); } diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx index d5e31a16a7..db0613dc47 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx @@ -112,8 +112,6 @@ describe('CatalogPage', () => { getProfile: () => testProfile, }; - const { set: setBreakpoint } = mockBreakpoint(); - const renderWrapped = (children: React.ReactNode) => renderWithEffects( wrapInTestApp( @@ -248,9 +246,16 @@ describe('CatalogPage', () => { ).resolves.toBeInTheDocument(); }); - it('should wrap filter in accordion on smaller screens', async () => { - setBreakpoint('sm'); - const { findByText } = await renderWrapped(); - await expect(findByText(/Filters/)).resolves.toBeInTheDocument(); + it('should wrap filter in drawer on smaller screens', async () => { + mockBreakpoint({ matches: true }); + const { findByText, getByTestId } = await renderWrapped(); + expect(getByTestId('entity-filters-drawer')).toBeInTheDocument(); + await expect(findByText('Filters')).resolves.toBeInTheDocument(); + }); + + it('should wrap filter in grid on larger screens', async () => { + mockBreakpoint({ matches: false }); + const { getByTestId } = await renderWrapped(); + expect(getByTestId('entity-filters-grid')).toBeInTheDocument(); }); }); diff --git a/plugins/catalog/src/components/FilteredTableLayout/FilterContainer.tsx b/plugins/catalog/src/components/FilteredTableLayout/FilterContainer.tsx index a394393f64..0fa26f6c66 100644 --- a/plugins/catalog/src/components/FilteredTableLayout/FilterContainer.tsx +++ b/plugins/catalog/src/components/FilteredTableLayout/FilterContainer.tsx @@ -27,6 +27,7 @@ export const FilterContainer = ({ children }: React.PropsWithChildren<{}>) => { return isMidSizeScreen ? ( { toggleFiltersDrawer(false); @@ -43,7 +44,7 @@ export const FilterContainer = ({ children }: React.PropsWithChildren<{}>) => { {children} ) : ( - + {children} ); From 3b7ac8c9caab981a72d30447d1b6827cbd54cbee Mon Sep 17 00:00:00 2001 From: Philipp Hugenroth Date: Thu, 15 Jul 2021 19:13:56 +0200 Subject: [PATCH 028/106] Fix styles to align text Signed-off-by: Philipp Hugenroth --- packages/core-components/src/layout/Header/Header.tsx | 1 - packages/core-components/src/layout/HeaderLabel/HeaderLabel.tsx | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/core-components/src/layout/Header/Header.tsx b/packages/core-components/src/layout/Header/Header.tsx index 572f1f8c32..41729f682b 100644 --- a/packages/core-components/src/layout/Header/Header.tsx +++ b/packages/core-components/src/layout/Header/Header.tsx @@ -56,7 +56,6 @@ const useStyles = makeStyles(theme => ({ }, title: { color: theme.palette.bursts.fontColor, - lineHeight: '1.0em', wordBreak: 'break-all', fontSize: 'calc(24px + 6 * ((100vw - 320px) / 680))', marginBottom: theme.spacing(1), diff --git a/packages/core-components/src/layout/HeaderLabel/HeaderLabel.tsx b/packages/core-components/src/layout/HeaderLabel/HeaderLabel.tsx index c7adf23204..8c3df0ba72 100644 --- a/packages/core-components/src/layout/HeaderLabel/HeaderLabel.tsx +++ b/packages/core-components/src/layout/HeaderLabel/HeaderLabel.tsx @@ -29,7 +29,7 @@ const useStyles = makeStyles(theme => ({ letterSpacing: 0, fontSize: 14, height: '16px', - marginBottom: 2, + marginBottom: 4, }, value: { color: 'rgba(255, 255, 255, 0.8)', From 3ee8e758929a3b9b66f83ace341a7144eb3fff6d Mon Sep 17 00:00:00 2001 From: Philipp Hugenroth Date: Thu, 15 Jul 2021 19:39:20 +0200 Subject: [PATCH 029/106] Updated test-utils API report Signed-off-by: Philipp Hugenroth --- packages/test-utils/api-report.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/packages/test-utils/api-report.md b/packages/test-utils/api-report.md index d85db1feb7..187867d24f 100644 --- a/packages/test-utils/api-report.md +++ b/packages/test-utils/api-report.md @@ -15,16 +15,15 @@ import { RouteRef } from '@backstage/core-plugin-api'; import { StorageApi } from '@backstage/core-plugin-api'; import { StorageValueChange } from '@backstage/core-plugin-api'; -// Warning: (ae-forgotten-export) The symbol "Breakpoint" needs to be exported by the entry point index.d.ts +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen // Warning: (ae-missing-release-tag) "mockBreakpoint" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // -// @public (undocumented) -export function mockBreakpoint( - initialBreakpoint?: Breakpoint, -): { - set(breakpoint: Breakpoint): void; - remove(): void; -}; +// @public +export function mockBreakpoint({ + matches, +}: { + matches?: boolean | undefined; +}): void; // Warning: (ae-missing-release-tag) "MockErrorApi" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // From d32d01e5bcfb73e4c7bd56ef662d48a37e0e9636 Mon Sep 17 00:00:00 2001 From: Dominik Henneke Date: Fri, 9 Jul 2021 09:54:42 +0200 Subject: [PATCH 030/106] Introduce the annotation `backstage.io/techdocs-ref: ` as an alias for `backstage.io/techdocs-ref: dir:` Signed-off-by: Dominik Henneke --- .changeset/techdocs-twenty-donuts-eat.md | 89 +++++++ packages/techdocs-common/api-report.md | 39 ++- packages/techdocs-common/src/helpers.test.ts | 239 +++++++++++++++++- packages/techdocs-common/src/helpers.ts | 128 +++++++++- .../src/stages/prepare/dir.test.ts | 18 +- .../techdocs-common/src/stages/prepare/dir.ts | 93 ++----- .../src/stages/prepare/preparers.ts | 30 ++- plugins/techdocs-backend/package.json | 1 + .../src/DocsBuilder/builder.ts | 10 +- .../src/service/DocsSynchronizer.test.ts | 2 + .../src/service/DocsSynchronizer.ts | 6 + .../techdocs-backend/src/service/router.ts | 11 +- 12 files changed, 556 insertions(+), 110 deletions(-) create mode 100644 .changeset/techdocs-twenty-donuts-eat.md diff --git a/.changeset/techdocs-twenty-donuts-eat.md b/.changeset/techdocs-twenty-donuts-eat.md new file mode 100644 index 0000000000..b4f9a067c3 --- /dev/null +++ b/.changeset/techdocs-twenty-donuts-eat.md @@ -0,0 +1,89 @@ +--- +'@backstage/techdocs-common': minor +'@backstage/plugin-techdocs-backend': minor +--- + +Introduce the annotation `backstage.io/techdocs-ref: ` as an alias for `backstage.io/techdocs-ref: dir:`. +This annotation works with both the basic and the recommended flow, however, it will be most useful with the basic approach. + +In addition, this change removes the support of the deprecated `github`, `gitlab`, and `azure/api` locations from the `dir` reference preparer. + +#### Example Usage + +The new annotation is convenient if the documentation is stored in the same location, i.e. the same git repository, as the `catalog-info.yaml`. +While it is still supported to add full URLs such as `backstage.io/techdocs-ref: url:https://...` for custom setups, documentation is mostly stored in the same repository as the entity definition. +By automatically resolving the target relative to the registration location of the entity, the configuration overhead for this default setup is minimized. +Since it leverages the `@backstage/integrations` package for the URL resolution, this is compatible with every supported source. + +Consider the following examples: + +> Note that the short version `` is only an alias for the still supported `dir:`. + +1. "I have a repository with a single `catalog-info.yaml` and a TechDocs page in the root folder!" + +``` +https://github.com/backstage/example/tree/main/ + |- catalog-info.yaml + | > apiVersion: backstage.io/v1alpha1 + | > kind: Component + | > metadata: + | > name: example + | > annotations: + | > backstage.io/techdocs-ref: . # -> same folder + | > spec: {} + |- docs/ + |- mkdocs.yml +``` + +2. "I have a repository with a single `catalog-info.yaml` and my TechDocs page in located in a folder!" + +``` +https://bitbucket.org/my-owner/my-project/src/master/ + |- catalog-info.yaml + | > apiVersion: backstage.io/v1alpha1 + | > kind: Component + | > metadata: + | > name: example + | > annotations: + | > backstage.io/techdocs-ref: ./some-folder # -> subfolder + | > spec: {} + |- some-folder/ + |- docs/ + |- mkdocs.yml +``` + +3. "I have a mono repository that hosts multiple components!" + +``` +https://dev.azure.com/organization/project/_git/repository + |- my-1st-module/ + |- catalog-info.yaml + | > apiVersion: backstage.io/v1alpha1 + | > kind: Component + | > metadata: + | > name: my-1st-module + | > annotations: + | > backstage.io/techdocs-ref: . # -> same folder + | > spec: {} + |- docs/ + |- mkdocs.yml + |- my-2nd-module/ + |- catalog-info.yaml + | > apiVersion: backstage.io/v1alpha1 + | > kind: Component + | > metadata: + | > name: my-2nd-module + | > annotations: + | > backstage.io/techdocs-ref: . # -> same folder + | > spec: {} + |- docs/ + |- mkdocs.yml + |- catalog-info.yaml + | > apiVersion: backstage.io/v1alpha1 + | > kind: Location + | > metadata: + | > name: example + | > spec: + | > targets: + | > - ./*/catalog-info.yaml +``` diff --git a/packages/techdocs-common/api-report.md b/packages/techdocs-common/api-report.md index 7c44dc5484..9fc1795c13 100644 --- a/packages/techdocs-common/api-report.md +++ b/packages/techdocs-common/api-report.md @@ -15,6 +15,7 @@ import { GitHubIntegrationConfig } from '@backstage/integration'; import { GitLabIntegrationConfig } from '@backstage/integration'; import { Logger as Logger_2 } from 'winston'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; +import { ScmIntegrationRegistry } from '@backstage/integration'; import { UrlReader } from '@backstage/backend-common'; import { Writable } from 'stream'; @@ -47,9 +48,15 @@ export class CommonGitPreparer implements PreparerBase { // // @public (undocumented) export class DirectoryPreparer implements PreparerBase { - constructor(config: Config, logger: Logger_2, reader: UrlReader); + constructor(config: Config, _logger: Logger_2, reader: UrlReader); // (undocumented) - prepare(entity: Entity): Promise; + prepare( + entity: Entity, + options?: { + logger?: Logger_2; + etag?: string; + }, + ): Promise; } // Warning: (ae-missing-release-tag) "GeneratorBase" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -130,6 +137,18 @@ export const getDefaultBranch: ( config: Config, ) => Promise; +// Warning: (ae-missing-release-tag) "getDirLocation" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export const getDirLocation: ( + entity: Entity, +) => + | { + type: 'dir'; + target: string; + } + | undefined; + // Warning: (ae-missing-release-tag) "getDocFilesFromRepository" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -189,7 +208,10 @@ export const getLastCommitTimestamp: ( // Warning: (ae-missing-release-tag) "getLocationForEntity" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const getLocationForEntity: (entity: Entity) => ParsedLocationAnnotation; +export const getLocationForEntity: ( + entity: Entity, + scmIntegration: ScmIntegrationRegistry, +) => ParsedLocationAnnotation; // Warning: (ae-missing-release-tag) "getTokenForGitRepo" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -355,6 +377,17 @@ export type TechDocsMetadata = { etag: string; }; +// Warning: (ae-missing-release-tag) "transformDirLocation" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export const transformDirLocation: ( + entity: Entity, + scmIntegrations: ScmIntegrationRegistry, +) => { + type: 'dir' | 'url'; + target: string; +}; + // Warning: (ae-missing-release-tag) "UrlPreparer" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) diff --git a/packages/techdocs-common/src/helpers.test.ts b/packages/techdocs-common/src/helpers.test.ts index b94cbba572..6b677c6f5e 100644 --- a/packages/techdocs-common/src/helpers.test.ts +++ b/packages/techdocs-common/src/helpers.test.ts @@ -19,14 +19,27 @@ import { SearchResponse, UrlReader, } from '@backstage/backend-common'; -import { Entity } from '@backstage/catalog-model'; +import { Entity, getEntitySourceLocation } from '@backstage/catalog-model'; +import { ConfigReader } from '@backstage/config'; +import { ScmIntegrations } from '@backstage/integration'; +import os from 'os'; +import path from 'path'; import { Readable } from 'stream'; import { + getDirLocation, getDocFilesFromRepository, getLocationForEntity, parseReferenceAnnotation, + transformDirLocation, } from './helpers'; +jest.mock('@backstage/catalog-model', () => ({ + ...jest.requireActual('@backstage/catalog-model'), + getEntitySourceLocation: jest.fn(), +})); + +const rootDir = os.platform() === 'win32' ? 'C:\\rootDir' : '/rootDir'; + const entityBase: Entity = { metadata: { namespace: 'default', @@ -81,6 +94,10 @@ const mockEntityWithBadAnnotation: Entity = { }, }; +const scmIntegrations = ScmIntegrations.fromConfig(new ConfigReader({})); + +afterEach(() => jest.resetAllMocks()); + describe('parseReferenceAnnotation', () => { it('should parse annotation', () => { const parsedLocationAnnotation = parseReferenceAnnotation( @@ -109,10 +126,230 @@ describe('parseReferenceAnnotation', () => { }); }); +describe('getDirLocation', () => { + it.each` + techdocsRef | responseTarget + ${undefined} | ${undefined} + ${16} | ${undefined} + ${'.'} | ${'.'} + ${'dir:.'} | ${'.'} + ${'./relative'} | ${'./relative'} + ${'dir:./relative'} | ${'./relative'} + ${'dir:https://github.com...'} | ${'https://github.com...'} + ${'url:https://github.com...'} | ${undefined} + `( + 'should handle "backstage.io/techdocs-ref: $techdocsRef" correctly', + ({ techdocsRef, responseTarget }) => { + const entity = { + apiVersion: '1', + kind: 'Component', + metadata: { + name: 'test', + annotations: { + 'backstage.io/techdocs-ref': techdocsRef, + }, + }, + }; + + const result = getDirLocation(entity); + + expect(result).toEqual( + responseTarget && { type: 'dir', target: responseTarget }, + ); + }, + ); + + it('Reject https urls and hint to the url: location type', async () => { + const entity = { + apiVersion: '1', + kind: 'Component', + metadata: { + name: 'test', + annotations: { + 'backstage.io/techdocs-ref': 'https://github.com/backstage/backstage', + }, + }, + }; + + expect(() => getDirLocation(entity)).toThrow( + /please prefix it with 'url:'/, + ); + }); +}); + +describe('transformDirLocation', () => { + it('should reject missing annotation', () => { + const entity = { + apiVersion: '1', + kind: 'Component', + metadata: { + name: 'test', + }, + }; + + expect(() => transformDirLocation(entity, scmIntegrations)).toThrow( + /No techdocs location annotation provided in entity: component:default\/test/, + ); + }); + + it.each` + techdocsRef | target + ${'.'} | ${'https://my-url/folder/'} + ${'./sub-folder'} | ${'https://my-url/folder/sub-folder'} + `( + 'should transform "$techdocsRef" for url type locations', + ({ techdocsRef, target }) => { + (getEntitySourceLocation as jest.Mock).mockReturnValue({ + type: 'url', + target: 'https://my-url/folder/', + }); + + const entity = { + apiVersion: '1', + kind: 'Component', + metadata: { + name: 'test', + annotations: { + 'backstage.io/techdocs-ref': techdocsRef, + }, + }, + }; + + const result = transformDirLocation(entity, scmIntegrations); + + expect(result).toEqual({ type: 'url', target }); + }, + ); + + it.each` + techdocsRef | target + ${'.'} | ${path.join(rootDir, 'working-copy')} + ${'./sub-folder'} | ${path.join(rootDir, 'working-copy', 'sub-folder')} + `( + 'should transform "$techdocsRef" for file type locations', + ({ techdocsRef, target }) => { + (getEntitySourceLocation as jest.Mock).mockReturnValue({ + type: 'file', + target: path.join(rootDir, 'working-copy', 'catalog-info.yaml'), + }); + + const entity = { + apiVersion: '1', + kind: 'Component', + metadata: { + name: 'test', + annotations: { + 'backstage.io/techdocs-ref': techdocsRef, + }, + }, + }; + + const result = transformDirLocation(entity, scmIntegrations); + + expect(result).toEqual({ type: 'dir', target }); + }, + ); + + it('should reject unsafe file location', () => { + (getEntitySourceLocation as jest.Mock).mockReturnValue({ + type: 'file', + target: '/tmp/catalog-info.yaml', + }); + + const entity = { + apiVersion: '1', + kind: 'Component', + metadata: { + name: 'test', + annotations: { + 'backstage.io/techdocs-ref': '..', + }, + }, + }; + + expect(() => transformDirLocation(entity, scmIntegrations)).toThrow( + /Relative path is not allowed to refer to a directory outside its parent/, + ); + }); + + it('should reject other location types', () => { + (getEntitySourceLocation as jest.Mock).mockReturnValue({ + type: 'other', + target: '/', + }); + + const entity = { + apiVersion: '1', + kind: 'Component', + metadata: { + name: 'test', + annotations: { + 'backstage.io/techdocs-ref': '.', + }, + }, + }; + + expect(() => transformDirLocation(entity, scmIntegrations)).toThrow( + /Unable to resolve location type other/, + ); + }); +}); + describe('getLocationForEntity', () => { + it('should handle implicit dir locations', () => { + (getEntitySourceLocation as jest.Mock).mockReturnValue({ + type: 'url', + target: 'https://my-url/folder/', + }); + + const entity = { + apiVersion: '1', + kind: 'Component', + metadata: { + name: 'test', + annotations: { + 'backstage.io/techdocs-ref': '.', + }, + }, + }; + + const parsedLocationAnnotation = getLocationForEntity( + entity, + scmIntegrations, + ); + expect(parsedLocationAnnotation.type).toBe('url'); + expect(parsedLocationAnnotation.target).toBe('https://my-url/folder/'); + }); + + it('should handle dir locations', () => { + (getEntitySourceLocation as jest.Mock).mockReturnValue({ + type: 'url', + target: 'https://my-url/folder/', + }); + + const entity = { + apiVersion: '1', + kind: 'Component', + metadata: { + name: 'test', + annotations: { + 'backstage.io/techdocs-ref': 'dir:.', + }, + }, + }; + + const parsedLocationAnnotation = getLocationForEntity( + entity, + scmIntegrations, + ); + expect(parsedLocationAnnotation.type).toBe('url'); + expect(parsedLocationAnnotation.target).toBe('https://my-url/folder/'); + }); + it('should get location for entity', () => { const parsedLocationAnnotation = getLocationForEntity( mockEntityWithAnnotation, + scmIntegrations, ); expect(parsedLocationAnnotation.type).toBe('url'); expect(parsedLocationAnnotation.target).toBe( diff --git a/packages/techdocs-common/src/helpers.ts b/packages/techdocs-common/src/helpers.ts index cac8a7047f..bb640c1cce 100644 --- a/packages/techdocs-common/src/helpers.ts +++ b/packages/techdocs-common/src/helpers.ts @@ -14,10 +14,20 @@ * limitations under the License. */ -import { Git, UrlReader } from '@backstage/backend-common'; -import { InputError } from '@backstage/errors'; -import { Entity, parseLocationReference } from '@backstage/catalog-model'; +import { + Git, + resolveSafeChildPath, + UrlReader, +} from '@backstage/backend-common'; +import { + Entity, + getEntitySourceLocation, + parseLocationReference, + stringifyEntityRef, +} from '@backstage/catalog-model'; import { Config } from '@backstage/config'; +import { InputError } from '@backstage/errors'; +import { ScmIntegrationRegistry } from '@backstage/integration'; import fs from 'fs-extra'; import parseGitUrl from 'git-url-parse'; import os from 'os'; @@ -50,9 +60,113 @@ export const parseReferenceAnnotation = ( }; }; +/** + * Check if the entity provides a `backstage.io/techdocs-ref` annotation of type `dir` + * and return the annotation. It accepts the two variants `` and `dir:`. + * + * @param entity - the entity to check + */ +export const getDirLocation = ( + entity: Entity, +): { type: 'dir'; target: string } | undefined => { + const annotation = entity.metadata.annotations?.['backstage.io/techdocs-ref']; + + if (!annotation) { + return undefined; + } + + if (typeof annotation !== 'string') { + return undefined; + } + + // if the string doesn't contain `:`, interpret it as the target of a dir type + if (!annotation.includes(':')) { + return { type: 'dir', target: annotation }; + } + + // note that `backstage.io/techdocs-ref: https://...` is invalid and will throw + const reference = parseLocationReference(annotation); + + if (reference.type === 'dir') { + return { + type: 'dir', + target: reference.target, + }; + } + + // ignore any other types + return undefined; +}; + +/** + * TechDocs references of type `dir` are relative the source location of the entity. + * This function transforms relative references to absolute ones, based on the + * location the entity was ingested from. If the entity was registered by a `url` + * location, it returns a `url` location with a resolved target that points to the + * targeted subfolder. If the entity was registered by a `file` location, it returns + * an absolute `dir` location. + * + * @param entity - the entity with annotations + * @param scmIntegrations - access to the scmIntegrationt to do url transformations + * @throws if the entity doesn't specify a `dir` location or is ingested from an unsupported location. + * @returns the transformed location with an absolute target. + */ +export const transformDirLocation = ( + entity: Entity, + scmIntegrations: ScmIntegrationRegistry, +): { type: 'dir' | 'url'; target: string } => { + const dirLocation = getDirLocation(entity); + + if (!dirLocation) { + throw new InputError( + `No techdocs location annotation provided in entity: ${stringifyEntityRef( + entity, + )}`, + ); + } + + const location = getEntitySourceLocation(entity); + + switch (location.type) { + case 'url': { + const target = scmIntegrations.resolveUrl({ + url: dirLocation.target, + base: location.target, + }); + + return { + type: 'url', + target, + }; + } + + case 'file': { + // only permit targets in the same folder as the target of the `file` location! + const target = resolveSafeChildPath( + path.dirname(location.target), + dirLocation.target, + ); + + return { + type: 'dir', + target, + }; + } + + default: + throw new InputError(`Unable to resolve location type ${location.type}`); + } +}; + export const getLocationForEntity = ( entity: Entity, + scmIntegration: ScmIntegrationRegistry, ): ParsedLocationAnnotation => { + // try to resolve relative references first + if (getDirLocation(entity) !== undefined) { + return transformDirLocation(entity, scmIntegration); + } + const { type, target } = parseReferenceAnnotation( 'backstage.io/techdocs-ref', entity, @@ -64,14 +178,6 @@ export const getLocationForEntity = ( case 'azure/api': case 'url': return { type, target }; - case 'dir': - if (path.isAbsolute(target)) { - return { type, target }; - } - return parseReferenceAnnotation( - 'backstage.io/managed-by-location', - entity, - ); default: throw new Error(`Invalid reference annotation ${type}`); } diff --git a/packages/techdocs-common/src/stages/prepare/dir.test.ts b/packages/techdocs-common/src/stages/prepare/dir.test.ts index 6992a35c65..019bf51fe9 100644 --- a/packages/techdocs-common/src/stages/prepare/dir.test.ts +++ b/packages/techdocs-common/src/stages/prepare/dir.test.ts @@ -15,7 +15,6 @@ */ import { getVoidLogger, UrlReader } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; -import { checkoutGitRepository } from '../../helpers'; import { DirectoryPreparer } from './dir'; function normalizePath(path: string) { @@ -27,8 +26,6 @@ function normalizePath(path: string) { jest.mock('../../helpers', () => ({ ...jest.requireActual<{}>('../../helpers'), - checkoutGitRepository: jest.fn(() => '/tmp/backstage-repo/org/name/branch/'), - getLastCommitTimestamp: jest.fn(() => 12345678), })); const logger = getVoidLogger(); @@ -71,7 +68,7 @@ describe('directory preparer', () => { expect(normalizePath(preparedDir)).toEqual('/directory/our-documentation'); }); - it('should merge managed-by-location and techdocs-ref when techdocs-ref is absolute', async () => { + it('should reject when techdocs-ref is absolute', async () => { const directoryPreparer = new DirectoryPreparer( mockConfig, logger, @@ -84,11 +81,12 @@ describe('directory preparer', () => { 'backstage.io/techdocs-ref': 'dir:/our-documentation/techdocs', }); - const { preparedDir } = await directoryPreparer.prepare(mockEntity); - expect(normalizePath(preparedDir)).toEqual('/our-documentation/techdocs'); + await expect(directoryPreparer.prepare(mockEntity)).rejects.toThrow( + /Relative path is not allowed to refer to a directory outside its parent/, + ); }); - it('should merge managed-by-location and techdocs-ref when managed-by-location is a git repository', async () => { + it('should reject when managed-by-location is a git repository', async () => { const directoryPreparer = new DirectoryPreparer( mockConfig, logger, @@ -101,10 +99,8 @@ describe('directory preparer', () => { 'backstage.io/techdocs-ref': 'dir:./docs', }); - const { preparedDir } = await directoryPreparer.prepare(mockEntity); - expect(normalizePath(preparedDir)).toEqual( - '/tmp/backstage-repo/org/name/branch/docs', + await expect(directoryPreparer.prepare(mockEntity)).rejects.toThrow( + /Unable to resolve location type github/, ); - expect(checkoutGitRepository).toHaveBeenCalledTimes(1); }); }); diff --git a/packages/techdocs-common/src/stages/prepare/dir.ts b/packages/techdocs-common/src/stages/prepare/dir.ts index 9ec4b4d37a..b0ba2a36b3 100644 --- a/packages/techdocs-common/src/stages/prepare/dir.ts +++ b/packages/techdocs-common/src/stages/prepare/dir.ts @@ -15,104 +15,57 @@ */ import { UrlReader } from '@backstage/backend-common'; -import { InputError, NotModifiedError } from '@backstage/errors'; import { Entity } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; -import parseGitUrl from 'git-url-parse'; -import path from 'path'; -import { Logger } from 'winston'; +import { InputError } from '@backstage/errors'; import { - checkoutGitRepository, - getLastCommitTimestamp, - parseReferenceAnnotation, -} from '../../helpers'; + ScmIntegrationRegistry, + ScmIntegrations, +} from '@backstage/integration'; +import { Logger } from 'winston'; +import { transformDirLocation } from '../../helpers'; import { PreparerBase, PreparerResponse } from './types'; export class DirectoryPreparer implements PreparerBase { - constructor( - private readonly config: Config, - private readonly logger: Logger, - private readonly reader: UrlReader, - ) { - this.config = config; - this.logger = logger; + private readonly scmIntegrations: ScmIntegrationRegistry; + private readonly reader: UrlReader; + + constructor(config: Config, _logger: Logger, reader: UrlReader) { this.reader = reader; + this.scmIntegrations = ScmIntegrations.fromConfig(config); } - private async resolveManagedByLocationToDir( + async prepare( entity: Entity, - options?: { etag?: string }, + options?: { logger?: Logger; etag?: string }, ): Promise { - const { type, target } = parseReferenceAnnotation( - 'backstage.io/managed-by-location', - entity, - ); + const { type, target } = transformDirLocation(entity, this.scmIntegrations); - this.logger.debug( - `Building docs for entity with type 'dir' and managed-by-location '${type}'`, - ); switch (type) { case 'url': { + options?.logger?.info(`Download documentation from ${target}`); + // the target is an absolute url since it has already been transformed const response = await this.reader.readTree(target, { etag: options?.etag, }); - const preparedDir = await response.dir(); + return { - preparedDir, + preparedDir: await response.dir(), etag: response.etag, }; } - case 'github': - case 'gitlab': - case 'azure/api': { - const parsedGitLocation = parseGitUrl(target); - const repoLocation = await checkoutGitRepository( - target, - this.config, - this.logger, - ); - // Check if etag has changed for cache invalidation. - const etag = await getLastCommitTimestamp(repoLocation, this.logger); - if (options?.etag === etag.toString()) { - throw new NotModifiedError(); - } + case 'dir': { return { - preparedDir: path.dirname( - path.join(repoLocation, parsedGitLocation.filepath), - ), - etag: etag.toString(), - }; - } - case 'file': - return { - preparedDir: path.dirname(target), + // the transformation already validated that the target is in a safe location + preparedDir: target, // Instead of supporting caching on local sources, use techdocs-cli for local development and debugging. etag: '', }; + } + default: throw new InputError(`Unable to resolve location type ${type}`); } } - - async prepare(entity: Entity): Promise { - this.logger.warn( - 'You are using the legacy dir preparer in TechDocs which will be removed in near future (March 2021). ' + - 'Migrate to URL reader by updating `backstage.io/techdocs-ref` annotation in `catalog-info.yaml` ' + - 'to be prefixed with `url:`. Read the migration guide and benefits at https://github.com/backstage/backstage/issues/4409 ', - ); - - const { target } = parseReferenceAnnotation( - 'backstage.io/techdocs-ref', - entity, - ); - - // This will throw NotModified error if etag has not changed. - const response = await this.resolveManagedByLocationToDir(entity); - - return { - preparedDir: path.resolve(response.preparedDir, target), - etag: response.etag, - }; - } } diff --git a/packages/techdocs-common/src/stages/prepare/preparers.ts b/packages/techdocs-common/src/stages/prepare/preparers.ts index 5c78d96f15..48e99d48f5 100644 --- a/packages/techdocs-common/src/stages/prepare/preparers.ts +++ b/packages/techdocs-common/src/stages/prepare/preparers.ts @@ -13,15 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { UrlReader } from '@backstage/backend-common'; -import { Entity } from '@backstage/catalog-model'; +import { Entity, parseLocationReference } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; +import { InputError } from '@backstage/errors'; import { Logger } from 'winston'; -import { parseReferenceAnnotation } from '../../helpers'; -import { DirectoryPreparer } from './dir'; +import { getDirLocation } from '../../helpers'; import { CommonGitPreparer } from './commonGit'; -import { UrlPreparer } from './url'; +import { DirectoryPreparer } from './dir'; import { PreparerBase, PreparerBuilder, RemoteProtocol } from './types'; +import { UrlPreparer } from './url'; type factoryOptions = { logger: Logger; @@ -61,11 +63,21 @@ export class Preparers implements PreparerBuilder { } get(entity: Entity): PreparerBase { - const { type } = parseReferenceAnnotation( - 'backstage.io/techdocs-ref', - entity, - ); - const preparer = this.preparerMap.get(type); + const annotation = + entity.metadata.annotations?.['backstage.io/techdocs-ref']; + if (!annotation) { + throw new InputError( + `No location annotation provided in entity: ${entity.metadata.name}`, + ); + } + + // the dir processor handles both `` and `dir:` + if (getDirLocation(entity) !== undefined) { + return this.preparerMap.get('dir')!; + } + + const { type } = parseLocationReference(annotation); + const preparer = this.preparerMap.get(type as RemoteProtocol); if (!preparer) { throw new Error(`No preparer registered for type: "${type}"`); diff --git a/plugins/techdocs-backend/package.json b/plugins/techdocs-backend/package.json index 76aadc79cf..4ae17d307c 100644 --- a/plugins/techdocs-backend/package.json +++ b/plugins/techdocs-backend/package.json @@ -35,6 +35,7 @@ "@backstage/catalog-model": "^0.9.0", "@backstage/config": "^0.1.5", "@backstage/errors": "^0.1.1", + "@backstage/integration": "^0.5.8", "@backstage/techdocs-common": "^0.6.8", "@types/express": "^4.17.6", "cross-fetch": "^3.0.6", diff --git a/plugins/techdocs-backend/src/DocsBuilder/builder.ts b/plugins/techdocs-backend/src/DocsBuilder/builder.ts index 2cd6f57154..5327062233 100644 --- a/plugins/techdocs-backend/src/DocsBuilder/builder.ts +++ b/plugins/techdocs-backend/src/DocsBuilder/builder.ts @@ -20,6 +20,7 @@ import { } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; import { NotModifiedError } from '@backstage/errors'; +import { ScmIntegrationRegistry } from '@backstage/integration'; import { GeneratorBase, GeneratorBuilder, @@ -43,6 +44,7 @@ type DocsBuilderArguments = { entity: Entity; logger: Logger; config: Config; + scmIntegrations: ScmIntegrationRegistry; logStream?: Writable; }; @@ -53,6 +55,7 @@ export class DocsBuilder { private entity: Entity; private logger: Logger; private config: Config; + private scmIntegrations: ScmIntegrationRegistry; private logStream: Writable | undefined; constructor({ @@ -62,6 +65,7 @@ export class DocsBuilder { entity, logger, config, + scmIntegrations, logStream, }: DocsBuilderArguments) { this.preparer = preparers.get(entity); @@ -70,6 +74,7 @@ export class DocsBuilder { this.entity = entity; this.logger = logger; this.config = config; + this.scmIntegrations = scmIntegrations; this.logStream = logStream; } @@ -166,7 +171,10 @@ export class DocsBuilder { path.join(tmpdirResolvedPath, 'techdocs-tmp-'), ); - const parsedLocationAnnotation = getLocationForEntity(this.entity); + const parsedLocationAnnotation = getLocationForEntity( + this.entity, + this.scmIntegrations, + ); await this.generator.run({ inputDir: preparedDir, outputDir, diff --git a/plugins/techdocs-backend/src/service/DocsSynchronizer.test.ts b/plugins/techdocs-backend/src/service/DocsSynchronizer.test.ts index 409dc6e128..ac60f3da3a 100644 --- a/plugins/techdocs-backend/src/service/DocsSynchronizer.test.ts +++ b/plugins/techdocs-backend/src/service/DocsSynchronizer.test.ts @@ -19,6 +19,7 @@ import { PluginEndpointDiscovery, } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; +import { ScmIntegrations } from '@backstage/integration'; import { GeneratorBuilder, PreparerBuilder, @@ -69,6 +70,7 @@ describe('DocsSynchronizer', () => { publisher, config: new ConfigReader({}), logger: getVoidLogger(), + scmIntegrations: ScmIntegrations.fromConfig(new ConfigReader({})), }); }); diff --git a/plugins/techdocs-backend/src/service/DocsSynchronizer.ts b/plugins/techdocs-backend/src/service/DocsSynchronizer.ts index 9b51381748..5e5d91b625 100644 --- a/plugins/techdocs-backend/src/service/DocsSynchronizer.ts +++ b/plugins/techdocs-backend/src/service/DocsSynchronizer.ts @@ -17,6 +17,7 @@ import { Entity } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; import { NotFoundError } from '@backstage/errors'; +import { ScmIntegrationRegistry } from '@backstage/integration'; import { GeneratorBuilder, PreparerBuilder, @@ -36,19 +37,23 @@ export class DocsSynchronizer { private readonly publisher: PublisherBase; private readonly logger: winston.Logger; private readonly config: Config; + private readonly scmIntegrations: ScmIntegrationRegistry; constructor({ publisher, logger, config, + scmIntegrations, }: { publisher: PublisherBase; logger: winston.Logger; config: Config; + scmIntegrations: ScmIntegrationRegistry; }) { this.config = config; this.logger = logger; this.publisher = publisher; + this.scmIntegrations = scmIntegrations; } async doSync({ @@ -94,6 +99,7 @@ export class DocsSynchronizer { logger: taskLogger, entity, config: this.config, + scmIntegrations: this.scmIntegrations, logStream, }); diff --git a/plugins/techdocs-backend/src/service/router.ts b/plugins/techdocs-backend/src/service/router.ts index b889312abd..e560a895b6 100644 --- a/plugins/techdocs-backend/src/service/router.ts +++ b/plugins/techdocs-backend/src/service/router.ts @@ -29,6 +29,7 @@ import express, { Response } from 'express'; import Router from 'express-promise-router'; import { Knex } from 'knex'; import { Logger } from 'winston'; +import { ScmIntegrations } from '@backstage/integration'; import { DocsSynchronizer, DocsSynchronizerSyncOpts } from './DocsSynchronizer'; /** @@ -79,10 +80,12 @@ export async function createRouter( const router = Router(); const { publisher, config, logger, discovery } = options; const catalogClient = new CatalogClient({ discoveryApi: discovery }); + const scmIntegrations = ScmIntegrations.fromConfig(config); const docsSynchronizer = new DocsSynchronizer({ - publisher: publisher, - logger: logger, - config: config, + publisher, + logger, + config, + scmIntegrations, }); router.get('/metadata/techdocs/:namespace/:kind/:name', async (req, res) => { @@ -126,7 +129,7 @@ export async function createRouter( ) ).json()) as Entity; - const locationMetadata = getLocationForEntity(entity); + const locationMetadata = getLocationForEntity(entity, scmIntegrations); res.json({ ...entity, locationMetadata }); } catch (err) { logger.info( From b044fa6f7cc9a1690b8554528706e7f3c08c53c3 Mon Sep 17 00:00:00 2001 From: Philipp Hugenroth Date: Fri, 16 Jul 2021 13:19:27 +0200 Subject: [PATCH 031/106] Clean up code according to review Signed-off-by: Philipp Hugenroth --- .changeset/green-lies-hammer.md | 4 +- .../software-catalog/catalog-customization.md | 42 +++++----- .../SupportButton/SupportButton.tsx | 5 +- .../layout/ContentHeader/ContentHeader.tsx | 6 +- .../src/layout/Page/PageWithHeader.tsx | 10 +-- .../ApiExplorerPage/ApiExplorerPage.tsx | 17 ++-- .../src/hooks/useEntityListProvider.tsx | 7 -- .../catalog-react/src/testUtils/providers.tsx | 3 - .../CatalogFilter/CatalogFilter.tsx | 78 ------------------- .../components/CatalogPage/CatalogPage.tsx | 14 ++-- .../CreateComponentButton.tsx | 4 +- .../{indext.ts => index.ts} | 0 .../EntityListContainer.tsx} | 4 +- .../FilteredEntityLayout/FilterContainer.tsx | 68 ++++++++++++++++ .../FilteredEntityLayout.tsx} | 11 ++- .../index.ts | 4 +- .../FilteredTableLayout/FilterContainer.tsx | 51 ------------ .../FilteredTableLayout.tsx | 52 ------------- plugins/catalog/src/index.ts | 3 +- 19 files changed, 136 insertions(+), 247 deletions(-) delete mode 100644 plugins/catalog/src/components/CatalogFilter/CatalogFilter.tsx rename plugins/catalog/src/components/CreateComponentButton/{indext.ts => index.ts} (100%) rename plugins/catalog/src/components/{FilteredTableLayout/TableContainer.tsx => FilteredEntityLayout/EntityListContainer.tsx} (84%) create mode 100644 plugins/catalog/src/components/FilteredEntityLayout/FilterContainer.tsx rename plugins/catalog/src/components/{CatalogFilter/index.ts => FilteredEntityLayout/FilteredEntityLayout.tsx} (65%) rename plugins/catalog/src/components/{FilteredTableLayout => FilteredEntityLayout}/index.ts (84%) delete mode 100644 plugins/catalog/src/components/FilteredTableLayout/FilterContainer.tsx delete mode 100644 plugins/catalog/src/components/FilteredTableLayout/FilteredTableLayout.tsx diff --git a/.changeset/green-lies-hammer.md b/.changeset/green-lies-hammer.md index bf0a9a3995..fc7f6e301d 100644 --- a/.changeset/green-lies-hammer.md +++ b/.changeset/green-lies-hammer.md @@ -5,4 +5,6 @@ '@backstage/plugin-catalog': patch --- -Build a general TablePage component to enforce a more responsive layout for the Catalog & API page. Filters are now wrapped in an accordion on smaller screens, enabling the user to see & interact better with the table. Additionally, a test was added, to check that the responsive wrapping is working for the Catalog page. +Changing the layout of the Catalog & API page to use responsive wrappers for table & filters. The goal is to enforce a responsive layout & that the Catalog & API page are better useable on smaller screens by showing more of the main content. To apply this changes to your custom catalog page, you can add the wrappers following the updated documentation on catalog customization. + +Additionally, the tests for Material UI breakpoints were adjusted & used to test the responsive wrappers. diff --git a/docs/features/software-catalog/catalog-customization.md b/docs/features/software-catalog/catalog-customization.md index e0228bf7d0..4049b005f1 100644 --- a/docs/features/software-catalog/catalog-customization.md +++ b/docs/features/software-catalog/catalog-customization.md @@ -29,25 +29,27 @@ default catalog page and create a component in a // https://github.com/backstage/backstage/blob/master/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx export const CustomCatalogPage = () => { return ( - + All your software catalog entities -
- -
+ + +
- -
-
+ + + + + +
-
+ ); }; ``` @@ -141,14 +143,18 @@ export const CustomCatalogPage = () => { return ( ... -
-
- + + +
... }; diff --git a/packages/core-components/src/components/SupportButton/SupportButton.tsx b/packages/core-components/src/components/SupportButton/SupportButton.tsx index 53c9b20273..45012e50a4 100644 --- a/packages/core-components/src/components/SupportButton/SupportButton.tsx +++ b/packages/core-components/src/components/SupportButton/SupportButton.tsx @@ -100,8 +100,9 @@ export const SupportButton = ({ title, children }: SupportButtonProps) => { color="primary" onClick={onClickHandler} > - - + + + Support }, })); -type DefaultTitleProps = { +type ContentHeaderTitleProps = { title?: string; className?: string; }; @@ -65,7 +65,7 @@ type DefaultTitleProps = { const ContentHeaderTitle = ({ title = 'Unknown page', className, -}: DefaultTitleProps) => ( +}: ContentHeaderTitleProps) => ( { +type ThemedHeaderProps = ComponentProps & { themeId: string; -} +}; export const PageWithHeader = ({ themeId, children, ...props -}: React.PropsWithChildren) => ( +}: React.PropsWithChildren) => (
{children} diff --git a/plugins/api-docs/src/components/ApiExplorerPage/ApiExplorerPage.tsx b/plugins/api-docs/src/components/ApiExplorerPage/ApiExplorerPage.tsx index bf62d06b44..361ce7b55b 100644 --- a/plugins/api-docs/src/components/ApiExplorerPage/ApiExplorerPage.tsx +++ b/plugins/api-docs/src/components/ApiExplorerPage/ApiExplorerPage.tsx @@ -25,8 +25,8 @@ import { configApiRef, useApi, useRouteRef } from '@backstage/core-plugin-api'; import { CatalogTable, CatalogTableRow, - FilteredTableLayout, - TableContainer, + FilteredEntityLayout, + EntityListContainer, FilterContainer, } from '@backstage/plugin-catalog'; import { @@ -54,11 +54,8 @@ const defaultColumns: TableColumn[] = [ CatalogTable.columns.createTagsColumn(), ]; -interface IApiExplorerePageFilterProps { +type ApiExplorerPageProps = { initiallySelectedFilter?: UserListFilterKind; -} - -export type ApiExplorerPageProps = IApiExplorerePageFilterProps & { columns?: TableColumn[]; }; @@ -94,7 +91,7 @@ export const ApiExplorerPage = ({ All your APIs - + - + - - + + diff --git a/plugins/catalog-react/src/hooks/useEntityListProvider.tsx b/plugins/catalog-react/src/hooks/useEntityListProvider.tsx index 7db2205deb..8ca3efd243 100644 --- a/plugins/catalog-react/src/hooks/useEntityListProvider.tsx +++ b/plugins/catalog-react/src/hooks/useEntityListProvider.tsx @@ -84,9 +84,6 @@ export type EntityListContextProps< */ queryParameters: Partial>; - showFiltersDrawer: boolean; - toggleFiltersDrawer: (showFiltersDrawer: boolean) => void; - loading: boolean; error?: Error; }; @@ -111,8 +108,6 @@ export const EntityListProvider = ({ const [requestedFilters, setRequestedFilters] = useState( {} as EntityFilters, ); - // Show - const [showFiltersDrawer, toggleFiltersDrawer] = useState(false); const [outputState, setOutputState] = useState>({ appliedFilters: {} as EntityFilters, entities: [], @@ -208,8 +203,6 @@ export const EntityListProvider = ({ updateFilters, queryParameters: outputState.queryParameters, loading, - showFiltersDrawer, - toggleFiltersDrawer, error, }} > diff --git a/plugins/catalog-react/src/testUtils/providers.tsx b/plugins/catalog-react/src/testUtils/providers.tsx index b8fdd3d8fe..1f9e4f40d6 100644 --- a/plugins/catalog-react/src/testUtils/providers.tsx +++ b/plugins/catalog-react/src/testUtils/providers.tsx @@ -32,7 +32,6 @@ export const MockEntityListContextProvider = ({ const [filters, setFilters] = useState( value.filters ?? {}, ); - const [showFiltersDrawer, toggleFiltersDrawer] = useState(false); const updateFilters = useCallback( ( update: @@ -57,8 +56,6 @@ export const MockEntityListContextProvider = ({ filters, loading: false, queryParameters: {}, - showFiltersDrawer, - toggleFiltersDrawer, }; // Extract value.filters to avoid overwriting it; some tests exercise filter updates. The value diff --git a/plugins/catalog/src/components/CatalogFilter/CatalogFilter.tsx b/plugins/catalog/src/components/CatalogFilter/CatalogFilter.tsx deleted file mode 100644 index 6704f429ee..0000000000 --- a/plugins/catalog/src/components/CatalogFilter/CatalogFilter.tsx +++ /dev/null @@ -1,78 +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. - */ - -import { - EntityKindPicker, - EntityLifecyclePicker, - EntityOwnerPicker, - EntityTagPicker, - EntityTypePicker, - UserListFilterKind, - UserListPicker, -} from '@backstage/plugin-catalog-react'; -import { - Accordion, - AccordionDetails, - AccordionSummary, - Grid, - isWidthDown, - Typography, - withWidth, -} from '@material-ui/core'; -import { Breakpoint } from '@material-ui/core/styles/createBreakpoints'; -import ExpandMore from '@material-ui/icons/ExpandMore'; -import React, { PropsWithChildren } from 'react'; - -interface IProps { - initiallySelectedFilter?: UserListFilterKind; - initialFilter?: 'component' | 'api'; -} - -const CatalogFilterWrapper = withWidth()( - ({ width, children }: PropsWithChildren<{ width: Breakpoint }>) => - isWidthDown('md', width) ? ( - - }> - Filters - - {children} - - ) : ( - {children} - ), -); - -export const CatalogFilter = ({ - initiallySelectedFilter = 'owned', - initialFilter = 'component', -}: IProps) => ( - - - - - - - - - - - - - - -); diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx index 5da46eec97..0fe3504c6d 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx @@ -38,10 +38,10 @@ import { CatalogTable } from '../CatalogTable'; import { EntityRow } from '../CatalogTable/types'; import { CreateComponentButton } from '../CreateComponentButton/CreateComponentButton'; import { - FilteredTableLayout, - TableContainer, + FilteredEntityLayout, + EntityListContainer, FilterContainer, -} from '../FilteredTableLayout'; +} from '../FilteredEntityLayout'; export type CatalogPageProps = { initiallySelectedFilter?: UserListFilterKind; @@ -65,7 +65,7 @@ export const CatalogPage = ({ All your software catalog entities - + - + - - + + diff --git a/plugins/catalog/src/components/CreateComponentButton/CreateComponentButton.tsx b/plugins/catalog/src/components/CreateComponentButton/CreateComponentButton.tsx index 4be93c1aa0..ae2fc4b718 100644 --- a/plugins/catalog/src/components/CreateComponentButton/CreateComponentButton.tsx +++ b/plugins/catalog/src/components/CreateComponentButton/CreateComponentButton.tsx @@ -23,7 +23,7 @@ import { useRouteRef } from '@backstage/core-plugin-api'; export const CreateComponentButton = () => { const createComponentLink = useRouteRef(createComponentRouteRef); - return !createComponentLink ? null : ( + return createComponentLink ? ( - ); + ) : null; }; diff --git a/plugins/catalog/src/components/CreateComponentButton/indext.ts b/plugins/catalog/src/components/CreateComponentButton/index.ts similarity index 100% rename from plugins/catalog/src/components/CreateComponentButton/indext.ts rename to plugins/catalog/src/components/CreateComponentButton/index.ts diff --git a/plugins/catalog/src/components/FilteredTableLayout/TableContainer.tsx b/plugins/catalog/src/components/FilteredEntityLayout/EntityListContainer.tsx similarity index 84% rename from plugins/catalog/src/components/FilteredTableLayout/TableContainer.tsx rename to plugins/catalog/src/components/FilteredEntityLayout/EntityListContainer.tsx index f5f5ae0de6..a39ef76b87 100644 --- a/plugins/catalog/src/components/FilteredTableLayout/TableContainer.tsx +++ b/plugins/catalog/src/components/FilteredEntityLayout/EntityListContainer.tsx @@ -15,9 +15,9 @@ */ import { Grid } from '@material-ui/core'; -import React from 'react'; +import React, { PropsWithChildren } from 'react'; -export const TableContainer = ({ children }: React.PropsWithChildren<{}>) => ( +export const EntityListContainer = ({ children }: PropsWithChildren<{}>) => ( {children} diff --git a/plugins/catalog/src/components/FilteredEntityLayout/FilterContainer.tsx b/plugins/catalog/src/components/FilteredEntityLayout/FilterContainer.tsx new file mode 100644 index 0000000000..6502811332 --- /dev/null +++ b/plugins/catalog/src/components/FilteredEntityLayout/FilterContainer.tsx @@ -0,0 +1,68 @@ +/* + * 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 { BackstageTheme } from '@backstage/theme'; +import { + Box, + Button, + Drawer, + Grid, + useMediaQuery, + useTheme, +} from '@material-ui/core'; +import FilterListIcon from '@material-ui/icons/FilterList'; +import React, { useState } from 'react'; + +export const FilterContainer = ({ children }: React.PropsWithChildren<{}>) => { + const isMidSizeScreen = useMediaQuery(theme => + theme.breakpoints.down('md'), + ); + const theme = useTheme(); + const [showFiltersDrawer, toggleFiltersDrawer] = useState(false); + + return isMidSizeScreen ? ( + <> + + { + toggleFiltersDrawer(false); + }} + elevation={0} + anchor="left" + disableAutoFocus + keepMounted + variant="temporary" + > + {children} + + + ) : ( + + {children} + + ); +}; diff --git a/plugins/catalog/src/components/CatalogFilter/index.ts b/plugins/catalog/src/components/FilteredEntityLayout/FilteredEntityLayout.tsx similarity index 65% rename from plugins/catalog/src/components/CatalogFilter/index.ts rename to plugins/catalog/src/components/FilteredEntityLayout/FilteredEntityLayout.tsx index dc57ef02e2..628ad9fee6 100644 --- a/plugins/catalog/src/components/CatalogFilter/index.ts +++ b/plugins/catalog/src/components/FilteredEntityLayout/FilteredEntityLayout.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Backstage Authors + * 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. @@ -14,4 +14,11 @@ * limitations under the License. */ -export { CatalogFilter } from './CatalogFilter'; +import { Grid } from '@material-ui/core'; +import React, { PropsWithChildren } from 'react'; + +export const FilteredEntityLayout = ({ children }: PropsWithChildren<{}>) => ( + + {children} + +); diff --git a/plugins/catalog/src/components/FilteredTableLayout/index.ts b/plugins/catalog/src/components/FilteredEntityLayout/index.ts similarity index 84% rename from plugins/catalog/src/components/FilteredTableLayout/index.ts rename to plugins/catalog/src/components/FilteredEntityLayout/index.ts index 9fa89259ab..ded61a4edc 100644 --- a/plugins/catalog/src/components/FilteredTableLayout/index.ts +++ b/plugins/catalog/src/components/FilteredEntityLayout/index.ts @@ -14,6 +14,6 @@ * limitations under the License. */ -export { FilteredTableLayout } from './FilteredTableLayout'; +export { FilteredEntityLayout } from './FilteredEntityLayout'; export { FilterContainer } from './FilterContainer'; -export { TableContainer } from './TableContainer'; +export { EntityListContainer } from './EntityListContainer'; diff --git a/plugins/catalog/src/components/FilteredTableLayout/FilterContainer.tsx b/plugins/catalog/src/components/FilteredTableLayout/FilterContainer.tsx deleted file mode 100644 index 0fa26f6c66..0000000000 --- a/plugins/catalog/src/components/FilteredTableLayout/FilterContainer.tsx +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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 { Box, Drawer, Grid, useMediaQuery } from '@material-ui/core'; -import { useEntityListProvider } from '@backstage/plugin-catalog-react'; -import { BackstageTheme } from '@backstage/theme'; -import React from 'react'; - -export const FilterContainer = ({ children }: React.PropsWithChildren<{}>) => { - const isMidSizeScreen = useMediaQuery(theme => - theme.breakpoints.down('md'), - ); - const { showFiltersDrawer, toggleFiltersDrawer } = useEntityListProvider(); - - return isMidSizeScreen ? ( - { - toggleFiltersDrawer(false); - }} - PaperProps={{ - style: { width: '300px' }, - }} - elevation={0} - anchor="left" - disableAutoFocus - keepMounted - variant="temporary" - > - {children} - - ) : ( - - {children} - - ); -}; diff --git a/plugins/catalog/src/components/FilteredTableLayout/FilteredTableLayout.tsx b/plugins/catalog/src/components/FilteredTableLayout/FilteredTableLayout.tsx deleted file mode 100644 index 0820a4d865..0000000000 --- a/plugins/catalog/src/components/FilteredTableLayout/FilteredTableLayout.tsx +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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 { useEntityListProvider } from '@backstage/plugin-catalog-react'; -import { BackstageTheme } from '@backstage/theme'; -import { Box, Button, Grid, useMediaQuery, useTheme } from '@material-ui/core'; -import FilterListIcon from '@material-ui/icons/FilterList'; -import React, { Fragment } from 'react'; - -interface IProps {} - -export const FilteredTableLayout = ({ - children, -}: React.PropsWithChildren) => { - const isMidSizeScreen = useMediaQuery(theme => - theme.breakpoints.down('md'), - ); - const { showFiltersDrawer, toggleFiltersDrawer } = useEntityListProvider(); - const theme = useTheme(); - - return ( - - {isMidSizeScreen && ( - - )} - - {children} - - - ); -}; diff --git a/plugins/catalog/src/index.ts b/plugins/catalog/src/index.ts index 8585177cd9..4b348580f7 100644 --- a/plugins/catalog/src/index.ts +++ b/plugins/catalog/src/index.ts @@ -40,5 +40,4 @@ export { EntitySystemDiagramCard, } from './plugin'; export * from './components/CatalogTable/columns'; -export * from './components/CatalogFilter'; -export * from './components/FilteredTableLayout'; +export * from './components/FilteredEntityLayout'; From 5858056f793659f2809913806b34a74240939694 Mon Sep 17 00:00:00 2001 From: Philipp Hugenroth Date: Fri, 16 Jul 2021 13:53:49 +0200 Subject: [PATCH 032/106] Update API reports Add Title to Filters Drawer Signed-off-by: Philipp Hugenroth --- packages/core-components/api-report.md | 4 +-- .../src/layout/Page/PageWithHeader.tsx | 6 ++-- plugins/api-docs/api-report.md | 6 ++-- plugins/catalog/api-report.md | 30 +++++++------------ .../FilteredEntityLayout/FilterContainer.tsx | 12 +++++++- 5 files changed, 30 insertions(+), 28 deletions(-) diff --git a/packages/core-components/api-report.md b/packages/core-components/api-report.md index bfc6852a97..e8d3214beb 100644 --- a/packages/core-components/api-report.md +++ b/packages/core-components/api-report.md @@ -1202,7 +1202,7 @@ export const Page: ({ children, }: PropsWithChildren) => JSX.Element; -// Warning: (ae-forgotten-export) The symbol "IProps" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "ThemedHeaderProps" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "PageWithHeader" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -1210,7 +1210,7 @@ export const PageWithHeader: ({ themeId, children, ...props -}: React_2.PropsWithChildren) => JSX.Element; +}: React_2.PropsWithChildren) => JSX.Element; // Warning: (ae-missing-release-tag) "Progress" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // diff --git a/packages/core-components/src/layout/Page/PageWithHeader.tsx b/packages/core-components/src/layout/Page/PageWithHeader.tsx index 4155805476..6095b4df16 100644 --- a/packages/core-components/src/layout/Page/PageWithHeader.tsx +++ b/packages/core-components/src/layout/Page/PageWithHeader.tsx @@ -14,12 +14,12 @@ * limitations under the License. */ -import React, { ComponentProps } from 'react'; +import React, { PropsWithChildren, ComponentProps } from 'react'; import { Header } from '../Header'; import { Page } from './Page'; -type ThemedHeaderProps = ComponentProps & { +export type ThemedHeaderProps = ComponentProps & { themeId: string; }; @@ -27,7 +27,7 @@ export const PageWithHeader = ({ themeId, children, ...props -}: React.PropsWithChildren) => ( +}: PropsWithChildren) => (
{children} diff --git a/plugins/api-docs/api-report.md b/plugins/api-docs/api-report.md index a368cf5675..639485a42d 100644 --- a/plugins/api-docs/api-report.md +++ b/plugins/api-docs/api-report.md @@ -52,14 +52,16 @@ const apiDocsPlugin: BackstagePlugin< export { apiDocsPlugin }; export { apiDocsPlugin as plugin }; -// Warning: (ae-forgotten-export) The symbol "ApiExplorerPageProps" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "ApiExplorerPage" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export const ApiExplorerPage: ({ initiallySelectedFilter, columns, -}: ApiExplorerPageProps) => JSX.Element; +}: { + initiallySelectedFilter?: UserListFilterKind | undefined; + columns?: TableColumn[] | undefined; +}) => JSX.Element; // Warning: (ae-missing-release-tag) "ApiTypeTitle" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // diff --git a/plugins/catalog/api-report.md b/plugins/catalog/api-report.md index b3169d5c29..4ed69230ec 100644 --- a/plugins/catalog/api-report.md +++ b/plugins/catalog/api-report.md @@ -48,15 +48,6 @@ export const AboutField: ({ // @public (undocumented) export const CatalogEntityPage: () => JSX.Element; -// Warning: (ae-forgotten-export) The symbol "IProps" needs to be exported by the entry point index.d.ts -// Warning: (ae-missing-release-tag) "CatalogFilter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export const CatalogFilter: ({ - initiallySelectedFilter, - initialFilter, -}: IProps) => JSX.Element; - // Warning: (ae-forgotten-export) The symbol "CatalogPageProps" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "CatalogIndexPage" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -247,6 +238,13 @@ export const EntityLinksCard: ({ variant?: 'gridItem' | undefined; }) => JSX.Element; +// Warning: (ae-missing-release-tag) "EntityListContainer" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const EntityListContainer: ({ + children, +}: PropsWithChildren<{}>) => JSX.Element; + // Warning: (ae-missing-release-tag) "EntityOrphanWarning" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public @@ -292,13 +290,12 @@ export const FilterContainer: ({ children, }: React_2.PropsWithChildren<{}>) => JSX.Element; -// Warning: (ae-forgotten-export) The symbol "IProps" needs to be exported by the entry point index.d.ts -// Warning: (ae-missing-release-tag) "FilteredTableLayout" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// Warning: (ae-missing-release-tag) "FilteredEntityLayout" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const FilteredTableLayout: ({ +export const FilteredEntityLayout: ({ children, -}: React_2.PropsWithChildren) => JSX.Element; +}: PropsWithChildren<{}>) => JSX.Element; // Warning: (ae-missing-release-tag) "isComponentType" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -329,13 +326,6 @@ export const Router: ({ EntityPage?: React_2.ComponentType<{}> | undefined; }) => JSX.Element; -// Warning: (ae-missing-release-tag) "TableContainer" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export const TableContainer: ({ - children, -}: React_2.PropsWithChildren<{}>) => JSX.Element; - // Warnings were encountered during analysis: // // src/components/CatalogTable/CatalogTable.d.ts:10:5 - (ae-forgotten-export) The symbol "CatalogTableProps" needs to be exported by the entry point index.d.ts diff --git a/plugins/catalog/src/components/FilteredEntityLayout/FilterContainer.tsx b/plugins/catalog/src/components/FilteredEntityLayout/FilterContainer.tsx index 6502811332..693d2f23b3 100644 --- a/plugins/catalog/src/components/FilteredEntityLayout/FilterContainer.tsx +++ b/plugins/catalog/src/components/FilteredEntityLayout/FilterContainer.tsx @@ -20,6 +20,7 @@ import { Button, Drawer, Grid, + Typography, useMediaQuery, useTheme, } from '@material-ui/core'; @@ -57,7 +58,16 @@ export const FilterContainer = ({ children }: React.PropsWithChildren<{}>) => { keepMounted variant="temporary" > - {children} + + + Filters + + {children} + ) : ( From 4e1b7d5cd7a7da1c8f75b4b3aee7cc921302c710 Mon Sep 17 00:00:00 2001 From: Philipp Hugenroth Date: Fri, 16 Jul 2021 14:13:43 +0200 Subject: [PATCH 033/106] Update API report Signed-off-by: Philipp Hugenroth --- packages/core-components/api-report.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core-components/api-report.md b/packages/core-components/api-report.md index e8d3214beb..7f381deb4b 100644 --- a/packages/core-components/api-report.md +++ b/packages/core-components/api-report.md @@ -1210,7 +1210,7 @@ export const PageWithHeader: ({ themeId, children, ...props -}: React_2.PropsWithChildren) => JSX.Element; +}: PropsWithChildren) => JSX.Element; // Warning: (ae-missing-release-tag) "Progress" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // From d488326303b353ddfbcf2223c693240154e59745 Mon Sep 17 00:00:00 2001 From: Philipp Hugenroth Date: Fri, 16 Jul 2021 14:53:25 +0200 Subject: [PATCH 034/106] Fix test Signed-off-by: Philipp Hugenroth --- .../catalog/src/components/CatalogPage/CatalogPage.test.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx index db0613dc47..598f534d14 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx @@ -248,9 +248,8 @@ describe('CatalogPage', () => { it('should wrap filter in drawer on smaller screens', async () => { mockBreakpoint({ matches: true }); - const { findByText, getByTestId } = await renderWrapped(); + const { getByTestId } = await renderWrapped(); expect(getByTestId('entity-filters-drawer')).toBeInTheDocument(); - await expect(findByText('Filters')).resolves.toBeInTheDocument(); }); it('should wrap filter in grid on larger screens', async () => { From aa966c05d7b4915a99782304b272b41a61a03660 Mon Sep 17 00:00:00 2001 From: Philipp Hugenroth Date: Fri, 16 Jul 2021 15:37:21 +0200 Subject: [PATCH 035/106] Adjust header styles Signed-off-by: Philipp Hugenroth --- .../src/layout/Header/Header.tsx | 12 +++++++--- .../src/layout/HeaderLabel/HeaderLabel.tsx | 24 +++++++++++-------- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/packages/core-components/src/layout/Header/Header.tsx b/packages/core-components/src/layout/Header/Header.tsx index 41729f682b..1af1e2cc2f 100644 --- a/packages/core-components/src/layout/Header/Header.tsx +++ b/packages/core-components/src/layout/Header/Header.tsx @@ -22,13 +22,14 @@ import { Helmet } from 'react-helmet'; import { Link } from '../../components/Link'; import { Breadcrumbs } from '../Breadcrumbs'; +const minHeaderHeight = 118; + const useStyles = makeStyles(theme => ({ header: { gridArea: 'pageHeader', padding: theme.spacing(3), height: 'fit-content', - // Where does this number come from? :/ - minHeight: 118, + minHeight: minHeaderHeight, width: '100%', boxShadow: '0 0 8px 3px rgba(20, 20, 20, 0.3)', position: 'relative', @@ -215,7 +216,12 @@ export const Header = ({ /> - + {children}
diff --git a/packages/core-components/src/layout/HeaderLabel/HeaderLabel.tsx b/packages/core-components/src/layout/HeaderLabel/HeaderLabel.tsx index 8c3df0ba72..f9d8b4d345 100644 --- a/packages/core-components/src/layout/HeaderLabel/HeaderLabel.tsx +++ b/packages/core-components/src/layout/HeaderLabel/HeaderLabel.tsx @@ -23,19 +23,15 @@ const useStyles = makeStyles(theme => ({ display: 'inline-block', }, label: { - color: '#FFFFFF', + color: theme.palette.common.white, fontWeight: 'bold', - lineHeight: '16px', letterSpacing: 0, - fontSize: 14, - height: '16px', - marginBottom: 4, + fontSize: theme.typography.fontSize, + marginBottom: theme.spacing(1), }, value: { color: 'rgba(255, 255, 255, 0.8)', - lineHeight: '16px', - fontSize: 14, - height: '16px', + fontSize: theme.typography.fontSize, }, })); @@ -65,8 +61,16 @@ export const HeaderLabel = ({ label, value, url }: HeaderLabelProps) => { return ( - {label} - {url ? {content} : content} + + {label} + + {url ? ( + + {content} + + ) : ( + content + )} ); From 03bf17e9b82ab94d5a4efecb43f35143ff32d9e0 Mon Sep 17 00:00:00 2001 From: Philipp Hugenroth Date: Fri, 16 Jul 2021 16:19:22 +0200 Subject: [PATCH 036/106] Add changeset Adjust HeaderLabel to use theme properties Signed-off-by: Philipp Hugenroth --- .changeset/poor-jars-sniff.md | 10 ++++++++++ .../src/layout/HeaderLabel/HeaderLabel.tsx | 17 +++++------------ .../app/src/components/catalog/EntityPage.tsx | 7 +++++++ 3 files changed, 22 insertions(+), 12 deletions(-) create mode 100644 .changeset/poor-jars-sniff.md diff --git a/.changeset/poor-jars-sniff.md b/.changeset/poor-jars-sniff.md new file mode 100644 index 0000000000..c45cd174a0 --- /dev/null +++ b/.changeset/poor-jars-sniff.md @@ -0,0 +1,10 @@ +--- +'example-app': patch +'@backstage/core-components': patch +'@backstage/create-app': patch +'@backstage/plugin-api-docs': patch +'@backstage/plugin-catalog': patch +'@backstage/plugin-techdocs': patch +--- + +Improve the responsiveness of the EntityPage UI. With this the Header component should scale with the screen size & wrapping should not cause overflowing/blocking of links. Additionally enforce the Pages using the Grid Layout to use it across all screen sizes & to wrap as intended. diff --git a/packages/core-components/src/layout/HeaderLabel/HeaderLabel.tsx b/packages/core-components/src/layout/HeaderLabel/HeaderLabel.tsx index f9d8b4d345..8557ceddf7 100644 --- a/packages/core-components/src/layout/HeaderLabel/HeaderLabel.tsx +++ b/packages/core-components/src/layout/HeaderLabel/HeaderLabel.tsx @@ -20,18 +20,19 @@ import React from 'react'; const useStyles = makeStyles(theme => ({ root: { textAlign: 'left', - display: 'inline-block', }, label: { color: theme.palette.common.white, fontWeight: 'bold', letterSpacing: 0, fontSize: theme.typography.fontSize, - marginBottom: theme.spacing(1), + marginBottom: theme.spacing(1) / 2, + lineHeight: 1, }, value: { color: 'rgba(255, 255, 255, 0.8)', fontSize: theme.typography.fontSize, + lineHeight: 1, }, })); @@ -61,16 +62,8 @@ export const HeaderLabel = ({ label, value, url }: HeaderLabelProps) => { return ( - - {label} - - {url ? ( - - {content} - - ) : ( - content - )} + {label} + {url ? {content} : content} ); diff --git a/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx b/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx index e3dd5be3c7..f7eb7c6349 100644 --- a/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx +++ b/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx @@ -157,6 +157,13 @@ const websiteEntityPage = ( ); +/** + * TODO: For the MUI Grid to work there have to be "xs" set on every GridItem, + * such that the seperation of space is clear from the smalles screen size upwards + * https://material-ui.com/components/grid/#basic-grid. + */ + + const defaultEntityPage = ( From 6e5aed1c9942e8456167000d49fce51f27cc7230 Mon Sep 17 00:00:00 2001 From: Phil Kuang Date: Fri, 16 Jul 2021 15:04:33 -0400 Subject: [PATCH 037/106] fix(techdocs): properly validate mkdocs docs_dir property Signed-off-by: Phil Kuang --- .changeset/angry-spies-warn.md | 5 +++++ .../generate/__fixtures__/mkdocs_valid_doc_dir.yml | 3 +++ .../src/stages/generate/helpers.test.ts | 10 ++++++++++ .../techdocs-common/src/stages/generate/helpers.ts | 6 +++++- 4 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 .changeset/angry-spies-warn.md create mode 100644 packages/techdocs-common/src/stages/generate/__fixtures__/mkdocs_valid_doc_dir.yml diff --git a/.changeset/angry-spies-warn.md b/.changeset/angry-spies-warn.md new file mode 100644 index 0000000000..171827748a --- /dev/null +++ b/.changeset/angry-spies-warn.md @@ -0,0 +1,5 @@ +--- +'@backstage/techdocs-common': patch +--- + +Fix validation of mkdocs.yml docs_dir diff --git a/packages/techdocs-common/src/stages/generate/__fixtures__/mkdocs_valid_doc_dir.yml b/packages/techdocs-common/src/stages/generate/__fixtures__/mkdocs_valid_doc_dir.yml new file mode 100644 index 0000000000..e75b06ada7 --- /dev/null +++ b/packages/techdocs-common/src/stages/generate/__fixtures__/mkdocs_valid_doc_dir.yml @@ -0,0 +1,3 @@ +site_name: Test site name +site_description: Test site description +docs_dir: docs/ diff --git a/packages/techdocs-common/src/stages/generate/helpers.test.ts b/packages/techdocs-common/src/stages/generate/helpers.test.ts index b6b71e3e2b..a5a6d99c29 100644 --- a/packages/techdocs-common/src/stages/generate/helpers.test.ts +++ b/packages/techdocs-common/src/stages/generate/helpers.test.ts @@ -47,6 +47,9 @@ const mkdocsYmlWithExtensions = fs.readFileSync( const mkdocsYmlWithRepoUrl = fs.readFileSync( resolvePath(__filename, '../__fixtures__/mkdocs_with_repo_url.yml'), ); +const mkdocsYmlWithValidDocDir = fs.readFileSync( + resolvePath(__filename, '../__fixtures__/mkdocs_valid_doc_dir.yml'), +); const mkdocsYmlWithInvalidDocDir = fs.readFileSync( resolvePath(__filename, '../__fixtures__/mkdocs_invalid_doc_dir.yml'), ); @@ -336,6 +339,7 @@ describe('helpers', () => { mockFs({ '/mkdocs.yml': mkdocsYml, '/mkdocs_with_extensions.yml': mkdocsYmlWithExtensions, + '/mkdocs_valid_doc_dir.yml': mkdocsYmlWithValidDocDir, '/mkdocs_invalid_doc_dir.yml': mkdocsYmlWithInvalidDocDir, }); }); @@ -351,6 +355,12 @@ describe('helpers', () => { ).resolves.toBeUndefined(); }); + it('should return true on when a valid docs_dir is present', async () => { + await expect( + validateMkdocsYaml(inputDir, '/mkdocs_valid_doc_dir.yml'), + ).resolves.toBeUndefined(); + }); + it('should return false on absolute doc_dir path', async () => { await expect( validateMkdocsYaml(inputDir, '/mkdocs_invalid_doc_dir.yml'), diff --git a/packages/techdocs-common/src/stages/generate/helpers.ts b/packages/techdocs-common/src/stages/generate/helpers.ts index 086377b4ef..590e75e7b2 100644 --- a/packages/techdocs-common/src/stages/generate/helpers.ts +++ b/packages/techdocs-common/src/stages/generate/helpers.ts @@ -19,6 +19,7 @@ import { isChildPath } from '@backstage/backend-common'; import { spawn } from 'child_process'; import fs from 'fs-extra'; import yaml, { DEFAULT_SCHEMA, Type } from 'js-yaml'; +import { resolve as resolvePath } from 'path'; import { PassThrough, Writable } from 'stream'; import { Logger } from 'winston'; import { ParsedLocationAnnotation } from '../../helpers'; @@ -178,7 +179,10 @@ export const validateMkdocsYaml = async ( schema: MKDOCS_SCHEMA, }); - if (mkdocsYml.docs_dir && !isChildPath(inputDir, mkdocsYml.docs_dir)) { + if ( + mkdocsYml.docs_dir && + !isChildPath(inputDir, resolvePath(inputDir, mkdocsYml.docs_dir)) + ) { throw new Error( `docs_dir configuration value in mkdocs can't be an absolute directory or start with ../ for security reasons. Use relative paths instead which are resolved relative to your mkdocs.yml file location.`, From c642324708b3e71e175adf2a6bea7d7f052749e3 Mon Sep 17 00:00:00 2001 From: Philipp Hugenroth Date: Mon, 19 Jul 2021 11:06:28 +0200 Subject: [PATCH 038/106] Legacy option for overflow wrap & update changeset bump Signed-off-by: Philipp Hugenroth --- .changeset/poor-jars-sniff.md | 1 - packages/app/src/components/catalog/EntityPage.tsx | 2 +- .../packages/app/src/components/catalog/EntityPage.tsx | 2 +- plugins/techdocs/src/reader/components/Reader.tsx | 3 +++ 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.changeset/poor-jars-sniff.md b/.changeset/poor-jars-sniff.md index c45cd174a0..4c0c5d8133 100644 --- a/.changeset/poor-jars-sniff.md +++ b/.changeset/poor-jars-sniff.md @@ -1,5 +1,4 @@ --- -'example-app': patch '@backstage/core-components': patch '@backstage/create-app': patch '@backstage/plugin-api-docs': patch diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx index 7965561516..137b359afd 100644 --- a/packages/app/src/components/catalog/EntityPage.tsx +++ b/packages/app/src/components/catalog/EntityPage.tsx @@ -137,7 +137,7 @@ const EntityLayoutWrapper = (props: { children?: ReactNode }) => { /** * TODO: For the MUI Grid to work there have to be "xs" set on every GridItem, - * such that the seperation of space is clear from the smalles screen size upwards + * such that the seperation of space is clear from the smallest screen size upwards * https://material-ui.com/components/grid/#basic-grid. */ diff --git a/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx b/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx index f7eb7c6349..7ebbe53aa0 100644 --- a/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx +++ b/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx @@ -159,7 +159,7 @@ const websiteEntityPage = ( /** * TODO: For the MUI Grid to work there have to be "xs" set on every GridItem, - * such that the seperation of space is clear from the smalles screen size upwards + * such that the seperation of space is clear from the smallest screen size upwards * https://material-ui.com/components/grid/#basic-grid. */ diff --git a/plugins/techdocs/src/reader/components/Reader.tsx b/plugins/techdocs/src/reader/components/Reader.tsx index 9dd8a2d656..3cecf4babe 100644 --- a/plugins/techdocs/src/reader/components/Reader.tsx +++ b/plugins/techdocs/src/reader/components/Reader.tsx @@ -52,6 +52,9 @@ type Props = { const useStyles = makeStyles(() => ({ message: { + // `word-break: break-word` is deprecated, but gives legacy support to browsers not supporting `overflow-wrap` yet + // https://developer.mozilla.org/en-US/docs/Web/CSS/word-break + wordBreak: 'break-word', overflowWrap: 'anywhere', }, })); From f3d5a811b0ea067cb631825c5a154ef72551792a Mon Sep 17 00:00:00 2001 From: Alexander Klein Date: Mon, 19 Jul 2021 11:09:31 +0200 Subject: [PATCH 039/106] style: increase sidebar search input hitbox Increase the padding of the sidebar search input. Fixes linked issue. refs #6529 Signed-off-by: Alexander Klein Signed-off-by: Alexander Klein --- packages/core-components/src/layout/Sidebar/Items.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/core-components/src/layout/Sidebar/Items.tsx b/packages/core-components/src/layout/Sidebar/Items.tsx index 28abbce53c..6fdf3d56dd 100644 --- a/packages/core-components/src/layout/Sidebar/Items.tsx +++ b/packages/core-components/src/layout/Sidebar/Items.tsx @@ -97,6 +97,9 @@ const useStyles = makeStyles(theme => { fontWeight: 'bold', fontSize: theme.typography.fontSize, }, + searchFieldHTMLInput: { + padding: '15px 0 17px', + }, searchContainer: { width: drawerWidthOpen - iconContainerWidth, }, @@ -271,6 +274,9 @@ export const SidebarSearchField = (props: SidebarSearchFieldProps) => { disableUnderline: true, className: classes.searchField, }} + inputProps={{ + className: classes.searchFieldHTMLInput, + }} /> From 9a751bb28fb6c477f3909f2eebd135f5ef244d3d Mon Sep 17 00:00:00 2001 From: Alexander Klein Date: Mon, 19 Jul 2021 11:29:50 +0200 Subject: [PATCH 040/106] docs: add changeset Signed-off-by: Alexander Klein Signed-off-by: Alexander Klein --- .changeset/chilled-cars-cough.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/chilled-cars-cough.md diff --git a/.changeset/chilled-cars-cough.md b/.changeset/chilled-cars-cough.md new file mode 100644 index 0000000000..b6406fcab1 --- /dev/null +++ b/.changeset/chilled-cars-cough.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': patch +--- + +Increase the vertical padding of the sidebar search input field to match the height of the parent anchor tag. This prevents users from accidentally navigating to the search page when they actually wanted to use the search input directly. From f39ee49343286c9221bb2819e385b914208da130 Mon Sep 17 00:00:00 2001 From: Philipp Hugenroth Date: Mon, 19 Jul 2021 12:23:05 +0200 Subject: [PATCH 041/106] Adjust props to fit general conventions Signed-off-by: Philipp Hugenroth --- packages/core-components/api-report.md | 4 ++-- packages/core-components/src/layout/Page/PageWithHeader.tsx | 4 ++-- plugins/catalog/api-report.md | 2 +- .../src/components/FilteredEntityLayout/FilterContainer.tsx | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/core-components/api-report.md b/packages/core-components/api-report.md index 7f381deb4b..ccafa4cfa7 100644 --- a/packages/core-components/api-report.md +++ b/packages/core-components/api-report.md @@ -1202,7 +1202,7 @@ export const Page: ({ children, }: PropsWithChildren) => JSX.Element; -// Warning: (ae-forgotten-export) The symbol "ThemedHeaderProps" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "PageWithHeaderProps" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "PageWithHeader" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -1210,7 +1210,7 @@ export const PageWithHeader: ({ themeId, children, ...props -}: PropsWithChildren) => JSX.Element; +}: PropsWithChildren) => JSX.Element; // Warning: (ae-missing-release-tag) "Progress" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // diff --git a/packages/core-components/src/layout/Page/PageWithHeader.tsx b/packages/core-components/src/layout/Page/PageWithHeader.tsx index 6095b4df16..70a6c1f693 100644 --- a/packages/core-components/src/layout/Page/PageWithHeader.tsx +++ b/packages/core-components/src/layout/Page/PageWithHeader.tsx @@ -19,7 +19,7 @@ import React, { PropsWithChildren, ComponentProps } from 'react'; import { Header } from '../Header'; import { Page } from './Page'; -export type ThemedHeaderProps = ComponentProps & { +type PageWithHeaderProps = ComponentProps & { themeId: string; }; @@ -27,7 +27,7 @@ export const PageWithHeader = ({ themeId, children, ...props -}: PropsWithChildren) => ( +}: PropsWithChildren) => (
{children} diff --git a/plugins/catalog/api-report.md b/plugins/catalog/api-report.md index 4ed69230ec..5a4ddefaca 100644 --- a/plugins/catalog/api-report.md +++ b/plugins/catalog/api-report.md @@ -288,7 +288,7 @@ export const EntitySystemDiagramCard: SystemDiagramCard; // @public (undocumented) export const FilterContainer: ({ children, -}: React_2.PropsWithChildren<{}>) => JSX.Element; +}: PropsWithChildren<{}>) => JSX.Element; // Warning: (ae-missing-release-tag) "FilteredEntityLayout" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // diff --git a/plugins/catalog/src/components/FilteredEntityLayout/FilterContainer.tsx b/plugins/catalog/src/components/FilteredEntityLayout/FilterContainer.tsx index 693d2f23b3..d38e800f46 100644 --- a/plugins/catalog/src/components/FilteredEntityLayout/FilterContainer.tsx +++ b/plugins/catalog/src/components/FilteredEntityLayout/FilterContainer.tsx @@ -25,9 +25,9 @@ import { useTheme, } from '@material-ui/core'; import FilterListIcon from '@material-ui/icons/FilterList'; -import React, { useState } from 'react'; +import React, { useState, PropsWithChildren } from 'react'; -export const FilterContainer = ({ children }: React.PropsWithChildren<{}>) => { +export const FilterContainer = ({ children }: PropsWithChildren<{}>) => { const isMidSizeScreen = useMediaQuery(theme => theme.breakpoints.down('md'), ); From f25357273165e618e94a1ffb89396f4dd3be4533 Mon Sep 17 00:00:00 2001 From: Dominik Henneke Date: Mon, 19 Jul 2021 14:58:04 +0200 Subject: [PATCH 042/106] Implement the etag functionality in the `readUrl` method of `FetchUrlReader` Signed-off-by: Dominik Henneke --- .changeset/friendly-tips-jam.md | 5 ++ .../src/reading/FetchUrlReader.test.ts | 88 +++++++++++++++++++ .../src/reading/FetchUrlReader.ts | 34 ++++--- 3 files changed, 115 insertions(+), 12 deletions(-) create mode 100644 .changeset/friendly-tips-jam.md diff --git a/.changeset/friendly-tips-jam.md b/.changeset/friendly-tips-jam.md new file mode 100644 index 0000000000..806b632ba1 --- /dev/null +++ b/.changeset/friendly-tips-jam.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-common': patch +--- + +Implement the etag functionality in the `readUrl` method of `FetchUrlReader`. diff --git a/packages/backend-common/src/reading/FetchUrlReader.test.ts b/packages/backend-common/src/reading/FetchUrlReader.test.ts index 169cbfbf66..e8c16c5f0a 100644 --- a/packages/backend-common/src/reading/FetchUrlReader.test.ts +++ b/packages/backend-common/src/reading/FetchUrlReader.test.ts @@ -15,12 +15,16 @@ */ import { ConfigReader } from '@backstage/config'; +import { NotFoundError, NotModifiedError } from '@backstage/errors'; import { msw } from '@backstage/test-utils'; +import { rest } from 'msw'; import { setupServer } from 'msw/node'; import { getVoidLogger } from '../logging'; import { FetchUrlReader } from './FetchUrlReader'; import { DefaultReadTreeResponseFactory } from './tree'; +const fetchUrlReader = new FetchUrlReader(); + describe('FetchUrlReader', () => { const worker = setupServer(); @@ -30,6 +34,39 @@ describe('FetchUrlReader', () => { jest.clearAllMocks(); }); + beforeEach(() => { + worker.use( + rest.get('https://backstage.io/some-resource', (req, res, ctx) => { + if (req.headers.get('if-none-match') === 'foo') { + return res( + ctx.status(304), + ctx.set('Content-Type', 'text/plain'), + ctx.set('etag', 'foo'), + ); + } + + return res( + ctx.status(200), + ctx.set('Content-Type', 'text/plain'), + ctx.set('etag', 'foo'), + ctx.body('content foo'), + ); + }), + ); + + worker.use( + rest.get('https://backstage.io/not-exists', (_req, res, ctx) => { + return res(ctx.status(404)); + }), + ); + + worker.use( + rest.get('https://backstage.io/error', (_req, res, ctx) => { + return res(ctx.status(500), ctx.body('An internal error occured')); + }), + ); + }); + it('factory should create a single entry with a predicate that matches config', async () => { const entries = FetchUrlReader.factory({ config: new ConfigReader({ @@ -70,4 +107,55 @@ describe('FetchUrlReader', () => { expect(predicate(new URL('https://a.examples.org:700/test'))).toBe(true); expect(predicate(new URL('https://a.b.examples.org:700/test'))).toBe(true); }); + + describe('read', () => { + it('should return etag from the response', async () => { + const buffer = await fetchUrlReader.read( + 'https://backstage.io/some-resource', + ); + expect(buffer.toString()).toBe('content foo'); + }); + + it('should throw NotFound if server responds with 404', async () => { + await expect( + fetchUrlReader.read('https://backstage.io/not-exists'), + ).rejects.toThrow(NotFoundError); + }); + + it('should throw Error if server responds with 500', async () => { + await expect( + fetchUrlReader.read('https://backstage.io/error'), + ).rejects.toThrow(Error); + }); + }); + + describe('readUrl', () => { + it('should throw NotModified if server responds with 304', async () => { + await expect( + fetchUrlReader.readUrl('https://backstage.io/some-resource', { + etag: 'foo', + }), + ).rejects.toThrow(NotModifiedError); + }); + + it('should return etag from the response', async () => { + const response = await fetchUrlReader.readUrl( + 'https://backstage.io/some-resource', + ); + expect(response.etag).toBe('foo'); + expect((await response.buffer()).toString()).toEqual('content foo'); + }); + + it('should throw NotFound if server responds with 404', async () => { + await expect( + fetchUrlReader.readUrl('https://backstage.io/not-exists'), + ).rejects.toThrow(NotFoundError); + }); + + it('should throw Error if server responds with 500', async () => { + await expect( + fetchUrlReader.readUrl('https://backstage.io/error'), + ).rejects.toThrow(Error); + }); + }); }); diff --git a/packages/backend-common/src/reading/FetchUrlReader.ts b/packages/backend-common/src/reading/FetchUrlReader.ts index 30468158aa..3177ee1f8e 100644 --- a/packages/backend-common/src/reading/FetchUrlReader.ts +++ b/packages/backend-common/src/reading/FetchUrlReader.ts @@ -14,8 +14,8 @@ * limitations under the License. */ +import { NotFoundError, NotModifiedError } from '@backstage/errors'; import fetch from 'cross-fetch'; -import { NotFoundError } from '@backstage/errors'; import { ReaderFactory, ReadTreeResponse, @@ -57,15 +57,34 @@ export class FetchUrlReader implements UrlReader { }; async read(url: string): Promise { + const response = await this.readUrl(url); + return response.buffer(); + } + + async readUrl( + url: string, + options?: ReadUrlOptions, + ): Promise { let response: Response; try { - response = await fetch(url); + response = await fetch(url, { + headers: { + ...(options?.etag && { 'If-None-Match': options.etag }), + }, + }); } catch (e) { throw new Error(`Unable to read ${url}, ${e}`); } + if (response.status === 304) { + throw new NotModifiedError(); + } + if (response.ok) { - return Buffer.from(await response.text()); + return { + buffer: async () => Buffer.from(await response.text()), + etag: response.headers.get('ETag') ?? undefined, + }; } const message = `could not read ${url}, ${response.status} ${response.statusText}`; @@ -75,15 +94,6 @@ export class FetchUrlReader implements UrlReader { throw new Error(message); } - async readUrl( - url: string, - _options?: ReadUrlOptions, - ): Promise { - // TODO etag is not implemented yet. - const buffer = await this.read(url); - return { buffer: async () => buffer }; - } - async readTree(): Promise { throw new Error('FetchUrlReader does not implement readTree'); } From 525a8c696acb25b6b7c8c8a3f7b9d12bc7c218da Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Jul 2021 04:10:29 +0000 Subject: [PATCH 043/106] chore(deps-dev): bump @spotify/prettier-config in /microsite Bumps [@spotify/prettier-config](https://github.com/spotify/web-scripts) from 10.0.0 to 11.0.0. - [Release notes](https://github.com/spotify/web-scripts/releases) - [Changelog](https://github.com/spotify/web-scripts/blob/master/CHANGELOG.md) - [Commits](https://github.com/spotify/web-scripts/compare/v10.0.0...v11.0.0) --- updated-dependencies: - dependency-name: "@spotify/prettier-config" dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- microsite/package.json | 2 +- microsite/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/microsite/package.json b/microsite/package.json index eee549fb70..532bb62d15 100644 --- a/microsite/package.json +++ b/microsite/package.json @@ -16,7 +16,7 @@ "lock:check": "yarn-lock-check" }, "devDependencies": { - "@spotify/prettier-config": "^10.0.0", + "@spotify/prettier-config": "^11.0.0", "docusaurus": "^2.0.0-alpha.70", "js-yaml": "^4.1.0", "prettier": "^2.3.2", diff --git a/microsite/yarn.lock b/microsite/yarn.lock index 1da9288808..fd492787ee 100644 --- a/microsite/yarn.lock +++ b/microsite/yarn.lock @@ -909,10 +909,10 @@ resolved "https://registry.npmjs.org/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd" integrity sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow== -"@spotify/prettier-config@^10.0.0": - version "10.0.0" - resolved "https://registry.npmjs.org/@spotify/prettier-config/-/prettier-config-10.0.0.tgz#fa076d98d2e7e6c53dd3d86a696307a7010bd056" - integrity sha512-VYOdo8P7lIScAkl02nB9KpUAuOYMManryBIBuKJkAw5D3aVtLobfmdIKvdV6MqEmGMEQPbn7w/UpnjJYhUH+IA== +"@spotify/prettier-config@^11.0.0": + version "11.0.0" + resolved "https://registry.yarnpkg.com/@spotify/prettier-config/-/prettier-config-11.0.0.tgz#d91e0546a8c1c0f7299e2edc7e44306e9be210f6" + integrity sha512-dOI13j1uHMZkRxhZuge/ugOE7Aqcg7Nxki932lDZuXyY4G8CGxkc/66PeQ8pR4PCzThHORXo7Ptvau6bh101lQ== "@types/cheerio@^0.22.8": version "0.22.23" From c87bed34bc3b3500007853367fb0ae06c9cc23d7 Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 19 Jul 2021 15:48:42 +0200 Subject: [PATCH 044/106] chore: fix yarn.lock for microsite Signed-off-by: blam --- microsite/yarn.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/microsite/yarn.lock b/microsite/yarn.lock index fd492787ee..0f7ddb9c60 100644 --- a/microsite/yarn.lock +++ b/microsite/yarn.lock @@ -911,7 +911,7 @@ "@spotify/prettier-config@^11.0.0": version "11.0.0" - resolved "https://registry.yarnpkg.com/@spotify/prettier-config/-/prettier-config-11.0.0.tgz#d91e0546a8c1c0f7299e2edc7e44306e9be210f6" + resolved "https://registry.npmjs.org/@spotify/prettier-config/-/prettier-config-11.0.0.tgz#d91e0546a8c1c0f7299e2edc7e44306e9be210f6" integrity sha512-dOI13j1uHMZkRxhZuge/ugOE7Aqcg7Nxki932lDZuXyY4G8CGxkc/66PeQ8pR4PCzThHORXo7Ptvau6bh101lQ== "@types/cheerio@^0.22.8": From 6d59d17a7fbb2dc91629c9ed3c380878841966eb Mon Sep 17 00:00:00 2001 From: Nicolas Arnold Date: Wed, 14 Jul 2021 10:20:23 +0100 Subject: [PATCH 045/106] Add functionality to the kubernetes plugin that allows users to assume role This change adds a new field to the kubernetes plugin configuration that allows a user to configure an assumed role so that they can access other kubernetes cluster. Signed-off-by: Nicolas Arnold --- plugins/kubernetes-backend/package.json | 4 +- .../ConfigClusterLocator.test.ts | 6 ++ .../cluster-locator/ConfigClusterLocator.ts | 1 + .../src/cluster-locator/index.test.ts | 3 + .../AwsIamKubernetesAuthTranslator.test.ts | 80 ++++++++++++---- .../AwsIamKubernetesAuthTranslator.ts | 91 ++++++++++++++----- .../GoogleKubernetesAuthTranslator.ts | 3 +- .../KubernetesAuthTranslatorGenerator.test.ts | 15 ++- .../ServiceAccountKubernetesAuthTranslator.ts | 3 +- .../src/service/KubernetesFanOutHandler.ts | 12 +-- .../src/service/KubernetesFetcher.ts | 38 ++++---- .../kubernetes-backend/src/service/router.ts | 5 +- plugins/kubernetes-backend/src/types/types.ts | 1 + 13 files changed, 184 insertions(+), 78 deletions(-) diff --git a/plugins/kubernetes-backend/package.json b/plugins/kubernetes-backend/package.json index 6bc7382f54..720321a180 100644 --- a/plugins/kubernetes-backend/package.json +++ b/plugins/kubernetes-backend/package.json @@ -55,7 +55,9 @@ "devDependencies": { "@backstage/cli": "^0.7.4", "@types/aws4": "^1.5.1", - "supertest": "^6.1.3" + "supertest": "^6.1.3", + "aws-sdk-mock": "^5.2.1", + "bdd-lazy-var": "^2.6.0" }, "files": [ "dist", diff --git a/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.test.ts b/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.test.ts index ac4a742828..eb5e6ff605 100644 --- a/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.test.ts +++ b/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.test.ts @@ -35,6 +35,7 @@ describe('ConfigClusterLocator', () => { const config: Config = new ConfigReader({ clusters: [ { + assumeRole: 'SomeRole', name: 'cluster1', url: 'http://localhost:8080', authProvider: 'serviceAccount', @@ -48,6 +49,7 @@ describe('ConfigClusterLocator', () => { expect(result).toStrictEqual([ { + assumeRole: 'SomeRole', name: 'cluster1', serviceAccountToken: undefined, url: 'http://localhost:8080', @@ -61,6 +63,7 @@ describe('ConfigClusterLocator', () => { const config: Config = new ConfigReader({ clusters: [ { + assumeRole: 'SomeRole', name: 'cluster1', serviceAccountToken: 'token', url: 'http://localhost:8080', @@ -68,6 +71,7 @@ describe('ConfigClusterLocator', () => { skipTLSVerify: false, }, { + assumeRole: undefined, name: 'cluster2', url: 'http://localhost:8081', authProvider: 'google', @@ -82,6 +86,7 @@ describe('ConfigClusterLocator', () => { expect(result).toStrictEqual([ { + assumeRole: 'SomeRole', name: 'cluster1', serviceAccountToken: 'token', url: 'http://localhost:8080', @@ -89,6 +94,7 @@ describe('ConfigClusterLocator', () => { skipTLSVerify: false, }, { + assumeRole: undefined, name: 'cluster2', serviceAccountToken: undefined, url: 'http://localhost:8081', diff --git a/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.ts b/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.ts index 268cca0193..d2fdbe4211 100644 --- a/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.ts +++ b/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.ts @@ -35,6 +35,7 @@ export class ConfigClusterLocator implements KubernetesClustersSupplier { serviceAccountToken: c.getOptionalString('serviceAccountToken'), skipTLSVerify: c.getOptionalBoolean('skipTLSVerify') ?? false, authProvider: c.getString('authProvider'), + assumeRole: c.getOptionalString('assumeRole'), }; }), ); diff --git a/plugins/kubernetes-backend/src/cluster-locator/index.test.ts b/plugins/kubernetes-backend/src/cluster-locator/index.test.ts index 95be99a8a5..9725d294a9 100644 --- a/plugins/kubernetes-backend/src/cluster-locator/index.test.ts +++ b/plugins/kubernetes-backend/src/cluster-locator/index.test.ts @@ -27,6 +27,7 @@ describe('getCombinedClusterDetails', () => { type: 'config', clusters: [ { + assumeRole: 'SomeRole', name: 'cluster1', serviceAccountToken: 'token', url: 'http://localhost:8080', @@ -49,6 +50,7 @@ describe('getCombinedClusterDetails', () => { expect(result).toStrictEqual([ { + assumeRole: 'SomeRole', name: 'cluster1', serviceAccountToken: 'token', url: 'http://localhost:8080', @@ -56,6 +58,7 @@ describe('getCombinedClusterDetails', () => { skipTLSVerify: false, }, { + assumeRole: undefined, name: 'cluster2', serviceAccountToken: undefined, url: 'http://localhost:8081', diff --git a/plugins/kubernetes-backend/src/kubernetes-auth-translator/AwsIamKubernetesAuthTranslator.test.ts b/plugins/kubernetes-backend/src/kubernetes-auth-translator/AwsIamKubernetesAuthTranslator.test.ts index 348ad541d5..d5179fc07a 100644 --- a/plugins/kubernetes-backend/src/kubernetes-auth-translator/AwsIamKubernetesAuthTranslator.test.ts +++ b/plugins/kubernetes-backend/src/kubernetes-auth-translator/AwsIamKubernetesAuthTranslator.test.ts @@ -14,15 +14,49 @@ * limitations under the License. */ import AWS from 'aws-sdk'; +import AWSMock from 'aws-sdk-mock'; import { AwsIamKubernetesAuthTranslator } from './AwsIamKubernetesAuthTranslator'; +import { get, def } from 'bdd-lazy-var'; describe('AwsIamKubernetesAuthTranslator tests', () => { + let valid: boolean = true; + let role: any = undefined; + let response: any = { + Credentials: { + AccessKeyId: 'bloop', + SecretAccessKey: 'omg-so-secret', + SessionToken: 'token', + }, + }; + + AWSMock.setSDKInstance(AWS); + beforeEach(() => { jest.resetAllMocks(); }); - it('returns a signed url for aws credentials', async () => { - const authTranslator = new AwsIamKubernetesAuthTranslator(); + afterAll(() => { + jest.resetAllMocks(); + }); + + def('subject', () => { + AWSMock.mock('STS', 'assumeRole', (_params: any, callback: Function) => { + callback(null, response); + }); + + const authTranslator = new AwsIamKubernetesAuthTranslator(); + jest + .spyOn(authTranslator, 'validCredentials') + .mockImplementation(() => valid); + return authTranslator.decorateClusterDetailsWithAuth({ + assumeRole: role, + name: 'test-cluster', + url: '', + authProvider: 'aws', + }); + }); + + it('returns a signed url for aws credentials', async () => { // These credentials are not real. // Pulled from example in docs: https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html AWS.config.credentials = new AWS.Credentials( @@ -30,24 +64,38 @@ describe('AwsIamKubernetesAuthTranslator tests', () => { 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY', ); - const clusterDetails = await authTranslator.decorateClusterDetailsWithAuth({ - name: 'test-cluster', - url: '', - authProvider: 'aws', + const subject = await get('subject'); + expect(subject.serviceAccountToken).toBeDefined(); + }); + + describe('When the role is assumed', () => { + // These credentials are not real. + // Pulled from example in docs: https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html + AWS.config.credentials = new AWS.Credentials( + 'AKIAIOSFODNN7EXAMPLE', + 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY', + ); + role = 'SomeRole'; + + describe('When the role is valid', () => { + it('returns a signed url for aws credentials', async () => { + const subject = await get('subject'); + expect(subject.serviceAccountToken).toBeDefined(); + }); + }); + + describe('When the role is invalid', () => { + it('returns the original AWS credentials', async () => { + response = undefined; + + await expect(get('subject')).rejects.toThrow(/Unable to assume role:/); + }); }); - expect(clusterDetails.serviceAccountToken).toBeDefined(); }); it('throws when unable to get aws credentials', async () => { + valid = false; AWS.config.credentials = undefined; - const authTranslator = new AwsIamKubernetesAuthTranslator(); - const promise = authTranslator.decorateClusterDetailsWithAuth({ - name: 'test-cluster', - url: '', - authProvider: 'aws', - }); - await expect(promise).rejects.toThrow( - 'Could not load credentials from any providers', - ); + await expect(get('subject')).rejects.toThrow('No AWS credentials found'); }); }); diff --git a/plugins/kubernetes-backend/src/kubernetes-auth-translator/AwsIamKubernetesAuthTranslator.ts b/plugins/kubernetes-backend/src/kubernetes-auth-translator/AwsIamKubernetesAuthTranslator.ts index 0909926d0d..15dfa9e14e 100644 --- a/plugins/kubernetes-backend/src/kubernetes-auth-translator/AwsIamKubernetesAuthTranslator.ts +++ b/plugins/kubernetes-backend/src/kubernetes-auth-translator/AwsIamKubernetesAuthTranslator.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import AWS, { Credentials } from 'aws-sdk'; +import AWS from 'aws-sdk'; import { sign } from 'aws4'; import { ClusterDetails } from '../types/types'; import { KubernetesAuthTranslator } from './types'; @@ -21,31 +21,80 @@ import { KubernetesAuthTranslator } from './types'; const base64 = (str: string) => Buffer.from(str.toString(), 'binary').toString('base64'); const prepend = (prep: string) => (str: string) => prep + str; -const replace = (search: string | RegExp, substitution: string) => ( - str: string, -) => str.replace(search, substitution); -const pipe = (fns: ReadonlyArray) => (thing: string): string => - fns.reduce((val, fn) => fn(val), thing); +const replace = + (search: string | RegExp, substitution: string) => (str: string) => + str.replace(search, substitution); +const pipe = + (fns: ReadonlyArray) => + (thing: string): string => + fns.reduce((val, fn) => fn(val), thing); const removePadding = replace(/=+$/, ''); const makeUrlSafe = pipe([replace('+', '-'), replace('/', '_')]); +type SigningCreds = { + accessKeyId: string | undefined; + secretAccessKey: string | undefined; + sessionToken: string | undefined; +}; + export class AwsIamKubernetesAuthTranslator - implements KubernetesAuthTranslator { - async getBearerToken(clusterName: string): Promise { - const credentials = await new Promise((resolve, reject) => { - AWS.config.getCredentials(err => { + implements KubernetesAuthTranslator +{ + validCredentials(creds: SigningCreds): boolean { + if (!creds.accessKeyId || !creds.secretAccessKey || !creds.sessionToken) { + return false; + } + return true; + } + + async getCredentials(assumeRole: string | undefined): Promise { + return new Promise(async (resolve, reject) => { + await AWS.config.getCredentials(err => { if (err) { + console.error('Unable to load aws config.'); reject(err); - } else { - resolve(AWS.config.credentials); } }); - }); - if (!(credentials instanceof Credentials)) { - throw new Error('no AWS credentials found.'); - } - await credentials.getPromise(); + let creds: SigningCreds = { + accessKeyId: AWS.config.credentials?.accessKeyId, + secretAccessKey: AWS.config.credentials?.secretAccessKey, + sessionToken: AWS.config.credentials?.sessionToken, + }; + + if (!this.validCredentials(creds)) + return reject(Error('No AWS credentials found.')); + if (!assumeRole) return resolve(creds); + + try { + const params = { + RoleArn: assumeRole, + RoleSessionName: 'backstage-login', + }; + const assumedRole = await new AWS.STS().assumeRole(params).promise(); + + if (!assumedRole.Credentials) { + throw new Error(`No credentials returned for role ${assumeRole}`); + } + + creds = { + accessKeyId: assumedRole.Credentials.AccessKeyId, + secretAccessKey: assumedRole.Credentials.SecretAccessKey, + sessionToken: assumedRole.Credentials.SessionToken, + }; + } catch (e) { + console.warn(`There was an error assuming the role: ${e}`); + return reject(Error(`Unable to assume role: ${e}`)); + } + return resolve(creds); + }); + } + async getBearerToken( + clusterName: string, + assumeRole: string | undefined, + ): Promise { + const credentials = await this.getCredentials(assumeRole); + const request = { host: `sts.amazonaws.com`, path: `/?Action=GetCallerIdentity&Version=2011-06-15&X-Amz-Expires=60`, @@ -54,11 +103,8 @@ export class AwsIamKubernetesAuthTranslator }, signQuery: true, }; - const signedRequest = sign(request, { - accessKeyId: credentials.accessKeyId, - secretAccessKey: credentials.secretAccessKey, - sessionToken: credentials.sessionToken, - }); + + const signedRequest = sign(request, credentials); return pipe([ (signed: any) => `https://${signed.host}${signed.path}`, @@ -79,6 +125,7 @@ export class AwsIamKubernetesAuthTranslator clusterDetailsWithAuthToken.serviceAccountToken = await this.getBearerToken( clusterDetails.name, + clusterDetails.assumeRole, ); return clusterDetailsWithAuthToken; } diff --git a/plugins/kubernetes-backend/src/kubernetes-auth-translator/GoogleKubernetesAuthTranslator.ts b/plugins/kubernetes-backend/src/kubernetes-auth-translator/GoogleKubernetesAuthTranslator.ts index 02a7f314af..62ffd823cf 100644 --- a/plugins/kubernetes-backend/src/kubernetes-auth-translator/GoogleKubernetesAuthTranslator.ts +++ b/plugins/kubernetes-backend/src/kubernetes-auth-translator/GoogleKubernetesAuthTranslator.ts @@ -19,7 +19,8 @@ import { ClusterDetails } from '../types/types'; import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common'; export class GoogleKubernetesAuthTranslator - implements KubernetesAuthTranslator { + implements KubernetesAuthTranslator +{ async decorateClusterDetailsWithAuth( clusterDetails: ClusterDetails, requestBody: KubernetesRequestBody, diff --git a/plugins/kubernetes-backend/src/kubernetes-auth-translator/KubernetesAuthTranslatorGenerator.test.ts b/plugins/kubernetes-backend/src/kubernetes-auth-translator/KubernetesAuthTranslatorGenerator.test.ts index 2d900b0bd1..33592b1c41 100644 --- a/plugins/kubernetes-backend/src/kubernetes-auth-translator/KubernetesAuthTranslatorGenerator.test.ts +++ b/plugins/kubernetes-backend/src/kubernetes-auth-translator/KubernetesAuthTranslatorGenerator.test.ts @@ -24,23 +24,20 @@ describe('getKubernetesAuthTranslatorInstance', () => { const sut = KubernetesAuthTranslatorGenerator; it('can return an auth translator for google auth', () => { - const authTranslator: KubernetesAuthTranslator = sut.getKubernetesAuthTranslatorInstance( - 'google', - ); + const authTranslator: KubernetesAuthTranslator = + sut.getKubernetesAuthTranslatorInstance('google'); expect(authTranslator instanceof GoogleKubernetesAuthTranslator).toBe(true); }); it('can return an auth translator for aws auth', () => { - const authTranslator: KubernetesAuthTranslator = sut.getKubernetesAuthTranslatorInstance( - 'aws', - ); + const authTranslator: KubernetesAuthTranslator = + sut.getKubernetesAuthTranslatorInstance('aws'); expect(authTranslator instanceof AwsIamKubernetesAuthTranslator).toBe(true); }); it('can return an auth translator for serviceAccount auth', () => { - const authTranslator: KubernetesAuthTranslator = sut.getKubernetesAuthTranslatorInstance( - 'serviceAccount', - ); + const authTranslator: KubernetesAuthTranslator = + sut.getKubernetesAuthTranslatorInstance('serviceAccount'); expect( authTranslator instanceof ServiceAccountKubernetesAuthTranslator, ).toBe(true); diff --git a/plugins/kubernetes-backend/src/kubernetes-auth-translator/ServiceAccountKubernetesAuthTranslator.ts b/plugins/kubernetes-backend/src/kubernetes-auth-translator/ServiceAccountKubernetesAuthTranslator.ts index c433abf4de..098ffe3330 100644 --- a/plugins/kubernetes-backend/src/kubernetes-auth-translator/ServiceAccountKubernetesAuthTranslator.ts +++ b/plugins/kubernetes-backend/src/kubernetes-auth-translator/ServiceAccountKubernetesAuthTranslator.ts @@ -19,7 +19,8 @@ import { ClusterDetails } from '../types/types'; import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common'; export class ServiceAccountKubernetesAuthTranslator - implements KubernetesAuthTranslator { + implements KubernetesAuthTranslator +{ async decorateClusterDetailsWithAuth( clusterDetails: ClusterDetails, // To ignore TS6133 linting error where it detects 'requestBody' is declared but its value is never read. diff --git a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts index 3039560aad..e088fddd41 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts @@ -63,15 +63,15 @@ export class KubernetesFanOutHandler { 'backstage.io/kubernetes-id' ] || requestBody.entity?.metadata?.name; - const clusterDetails: ClusterDetails[] = await this.serviceLocator.getClustersByServiceId( - entityName, - ); + const clusterDetails: ClusterDetails[] = + await this.serviceLocator.getClustersByServiceId(entityName); // Execute all of these async actions simultaneously/without blocking sequentially as no common object is modified by them const promises: Promise[] = clusterDetails.map(cd => { - const kubernetesAuthTranslator: KubernetesAuthTranslator = KubernetesAuthTranslatorGenerator.getKubernetesAuthTranslatorInstance( - cd.authProvider, - ); + const kubernetesAuthTranslator: KubernetesAuthTranslator = + KubernetesAuthTranslatorGenerator.getKubernetesAuthTranslatorInstance( + cd.authProvider, + ); return kubernetesAuthTranslator.decorateClusterDetailsWithAuth( cd, requestBody, diff --git a/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts b/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts index 76c3b0aac0..e17b47e17a 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts @@ -167,10 +167,9 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { labelSelector, ).then(r => ({ type: type, resources: r })); case 'services': - return this.fetchServicesForService( - clusterDetails, - labelSelector, - ).then(r => ({ type: type, resources: r })); + return this.fetchServicesForService(clusterDetails, labelSelector).then( + r => ({ type: type, resources: r }), + ); case 'horizontalpodautoscalers': return this.fetchHorizontalPodAutoscalersForService( clusterDetails, @@ -192,9 +191,8 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { customResource: CustomResource, labelSelector: string, ): Promise { - const customObjects = this.kubernetesClientProvider.getCustomObjectsClient( - clusterDetails, - ); + const customObjects = + this.kubernetesClientProvider.getCustomObjectsClient(clusterDetails); return customObjects .listClusterCustomObject( @@ -217,18 +215,20 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { client: Clients, ) => Promise<{ body: { items: Array }; response: http.IncomingMessage }>, ): Promise> { - const core = this.kubernetesClientProvider.getCoreClientByClusterDetails( - clusterDetails, - ); - const apps = this.kubernetesClientProvider.getAppsClientByClusterDetails( - clusterDetails, - ); - const autoscaling = this.kubernetesClientProvider.getAutoscalingClientByClusterDetails( - clusterDetails, - ); - const networkingBeta1 = this.kubernetesClientProvider.getNetworkingBeta1Client( - clusterDetails, - ); + const core = + this.kubernetesClientProvider.getCoreClientByClusterDetails( + clusterDetails, + ); + const apps = + this.kubernetesClientProvider.getAppsClientByClusterDetails( + clusterDetails, + ); + const autoscaling = + this.kubernetesClientProvider.getAutoscalingClientByClusterDetails( + clusterDetails, + ); + const networkingBeta1 = + this.kubernetesClientProvider.getNetworkingBeta1Client(clusterDetails); this.logger.debug(`calling cluster=${clusterDetails.name}`); return fn({ core, apps, autoscaling, networkingBeta1 }).then(({ body }) => { diff --git a/plugins/kubernetes-backend/src/service/router.ts b/plugins/kubernetes-backend/src/service/router.ts index 984b3f14a0..beebd6fa65 100644 --- a/plugins/kubernetes-backend/src/service/router.ts +++ b/plugins/kubernetes-backend/src/service/router.ts @@ -70,9 +70,8 @@ export const makeRouter = ( const serviceId = req.params.serviceId; const requestBody: KubernetesRequestBody = req.body; try { - const response = await kubernetesFanOutHandler.getKubernetesObjectsByEntity( - requestBody, - ); + const response = + await kubernetesFanOutHandler.getKubernetesObjectsByEntity(requestBody); res.json(response); } catch (e) { logger.error( diff --git a/plugins/kubernetes-backend/src/types/types.ts b/plugins/kubernetes-backend/src/types/types.ts index c6e1668a5a..5150eba8cf 100644 --- a/plugins/kubernetes-backend/src/types/types.ts +++ b/plugins/kubernetes-backend/src/types/types.ts @@ -76,4 +76,5 @@ export interface ClusterDetails { authProvider: string; serviceAccountToken?: string | undefined; skipTLSVerify?: boolean; + assumeRole?: string; } From 5bd57f8f5dfced87a104f2f104f90b6732e89de1 Mon Sep 17 00:00:00 2001 From: Nicolas Arnold Date: Wed, 14 Jul 2021 10:27:42 +0100 Subject: [PATCH 046/106] Adding changesets Signed-off-by: Nicolas Arnold --- .changeset/blue-feet-poke.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/blue-feet-poke.md diff --git a/.changeset/blue-feet-poke.md b/.changeset/blue-feet-poke.md new file mode 100644 index 0000000000..3be886c687 --- /dev/null +++ b/.changeset/blue-feet-poke.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-kubernetes-backend': minor +--- + +Support assume role on kubernetes api configuration for AWS. From f69d4085834203b2c78fb951fdeb8ccb0ffbaac2 Mon Sep 17 00:00:00 2001 From: Nicolas Arnold Date: Wed, 14 Jul 2021 10:42:56 +0100 Subject: [PATCH 047/106] Add API.md for plugin Signed-off-by: Nicolas Arnold --- plugins/kubernetes-backend/api-report.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/kubernetes-backend/api-report.md b/plugins/kubernetes-backend/api-report.md index 1592f20880..b7ece06ed5 100644 --- a/plugins/kubernetes-backend/api-report.md +++ b/plugins/kubernetes-backend/api-report.md @@ -14,6 +14,8 @@ import { Logger as Logger_2 } from 'winston'; // // @public (undocumented) export interface ClusterDetails { + // (undocumented) + assumeRole?: string; // (undocumented) authProvider: string; // (undocumented) From ec98274a7ef5fbca1baaec20606433d9dee9b1e6 Mon Sep 17 00:00:00 2001 From: Nicolas Arnold Date: Wed, 14 Jul 2021 12:50:24 +0100 Subject: [PATCH 048/106] Creating a Cluster details object per k8s provider Signed-off-by: Nicolas Arnold --- plugins/kubernetes-backend/api-report.md | 17 ++++++- .../ConfigClusterLocator.test.ts | 50 ++++++++++++++++--- .../cluster-locator/ConfigClusterLocator.ts | 24 +++++++-- .../src/cluster-locator/GkeClusterLocator.ts | 4 +- .../src/cluster-locator/index.test.ts | 3 -- .../AwsIamKubernetesAuthTranslator.ts | 23 ++++----- .../GoogleKubernetesAuthTranslator.ts | 11 ++-- .../KubernetesAuthTranslatorGenerator.test.ts | 15 +++--- .../ServiceAccountKubernetesAuthTranslator.ts | 9 ++-- .../src/service/KubernetesFanOutHandler.ts | 12 ++--- .../src/service/KubernetesFetcher.ts | 38 +++++++------- .../kubernetes-backend/src/service/router.ts | 5 +- plugins/kubernetes-backend/src/types/types.ts | 10 +++- 13 files changed, 147 insertions(+), 74 deletions(-) diff --git a/plugins/kubernetes-backend/api-report.md b/plugins/kubernetes-backend/api-report.md index b7ece06ed5..0de29bd2f5 100644 --- a/plugins/kubernetes-backend/api-report.md +++ b/plugins/kubernetes-backend/api-report.md @@ -13,9 +13,13 @@ import { Logger as Logger_2 } from 'winston'; // Warning: (ae-missing-release-tag) "ClusterDetails" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ClusterDetails { +export interface AWSClusterDetails extends ClusterDetails { // (undocumented) assumeRole?: string; +} + +// @public (undocumented) +export interface ClusterDetails { // (undocumented) authProvider: string; // (undocumented) @@ -57,6 +61,9 @@ export interface FetchResponseWrapper { // Warning: (ae-missing-release-tag) "KubernetesClustersSupplier" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // +// @public (undocumented) +export interface GKEClusterDetails extends ClusterDetails {} + // @public (undocumented) export interface KubernetesClustersSupplier { // (undocumented) @@ -109,7 +116,10 @@ export const makeRouter: ( // @public (undocumented) export interface ObjectFetchParams { // (undocumented) - clusterDetails: ClusterDetails; + clusterDetails: + | AWSClusterDetails + | GKEClusterDetails + | ServiceAccountClusterDetails; // (undocumented) customResources: CustomResource[]; // (undocumented) @@ -134,6 +144,9 @@ export interface RouterOptions { // Warning: (ae-missing-release-tag) "ServiceLocatorMethod" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // +// @public (undocumented) +export interface ServiceAccountClusterDetails extends ClusterDetails {} + // @public (undocumented) export type ServiceLocatorMethod = 'multiTenant' | 'http'; diff --git a/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.test.ts b/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.test.ts index eb5e6ff605..1642ca5580 100644 --- a/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.test.ts +++ b/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.test.ts @@ -35,7 +35,6 @@ describe('ConfigClusterLocator', () => { const config: Config = new ConfigReader({ clusters: [ { - assumeRole: 'SomeRole', name: 'cluster1', url: 'http://localhost:8080', authProvider: 'serviceAccount', @@ -49,7 +48,6 @@ describe('ConfigClusterLocator', () => { expect(result).toStrictEqual([ { - assumeRole: 'SomeRole', name: 'cluster1', serviceAccountToken: undefined, url: 'http://localhost:8080', @@ -63,7 +61,6 @@ describe('ConfigClusterLocator', () => { const config: Config = new ConfigReader({ clusters: [ { - assumeRole: 'SomeRole', name: 'cluster1', serviceAccountToken: 'token', url: 'http://localhost:8080', @@ -71,7 +68,6 @@ describe('ConfigClusterLocator', () => { skipTLSVerify: false, }, { - assumeRole: undefined, name: 'cluster2', url: 'http://localhost:8081', authProvider: 'google', @@ -86,7 +82,6 @@ describe('ConfigClusterLocator', () => { expect(result).toStrictEqual([ { - assumeRole: 'SomeRole', name: 'cluster1', serviceAccountToken: 'token', url: 'http://localhost:8080', @@ -94,7 +89,6 @@ describe('ConfigClusterLocator', () => { skipTLSVerify: false, }, { - assumeRole: undefined, name: 'cluster2', serviceAccountToken: undefined, url: 'http://localhost:8081', @@ -103,4 +97,48 @@ describe('ConfigClusterLocator', () => { }, ]); }); + + it('one aws cluster with assumeRole and one without', async () => { + const config: Config = new ConfigReader({ + clusters: [ + { + name: 'cluster1', + serviceAccountToken: 'token', + url: 'http://localhost:8080', + authProvider: 'aws', + skipTLSVerify: false, + }, + { + assumeRole: 'SomeRole', + name: 'cluster2', + url: 'http://localhost:8081', + authProvider: 'aws', + skipTLSVerify: true, + }, + ], + }); + + const sut = ConfigClusterLocator.fromConfig(config); + + const result = await sut.getClusters(); + + expect(result).toStrictEqual([ + { + assumeRole: undefined, + name: 'cluster1', + serviceAccountToken: 'token', + url: 'http://localhost:8080', + authProvider: 'aws', + skipTLSVerify: false, + }, + { + assumeRole: 'SomeRole', + name: 'cluster2', + serviceAccountToken: undefined, + url: 'http://localhost:8081', + authProvider: 'aws', + skipTLSVerify: true, + }, + ]); + }); }); diff --git a/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.ts b/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.ts index d2fdbe4211..2899445b2a 100644 --- a/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.ts +++ b/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.ts @@ -29,14 +29,32 @@ export class ConfigClusterLocator implements KubernetesClustersSupplier { // is required if authProvider is serviceAccount return new ConfigClusterLocator( config.getConfigArray('clusters').map(c => { - return { + const authProvider = c.getString('authProvider'); + const clusterDetails = { name: c.getString('name'), url: c.getString('url'), serviceAccountToken: c.getOptionalString('serviceAccountToken'), skipTLSVerify: c.getOptionalBoolean('skipTLSVerify') ?? false, - authProvider: c.getString('authProvider'), - assumeRole: c.getOptionalString('assumeRole'), + authProvider: authProvider, }; + + switch (authProvider) { + case 'google': { + return clusterDetails; + } + case 'aws': { + const assumeRole = c.getOptionalString('assumeRole'); + return { assumeRole, ...clusterDetails }; + } + case 'serviceAccount': { + return clusterDetails; + } + default: { + throw new Error( + `authProvider "${authProvider}" has no config associated with it`, + ); + } + } }), ); } diff --git a/plugins/kubernetes-backend/src/cluster-locator/GkeClusterLocator.ts b/plugins/kubernetes-backend/src/cluster-locator/GkeClusterLocator.ts index 92212d0224..3cc6c216ae 100644 --- a/plugins/kubernetes-backend/src/cluster-locator/GkeClusterLocator.ts +++ b/plugins/kubernetes-backend/src/cluster-locator/GkeClusterLocator.ts @@ -16,7 +16,7 @@ import { Config } from '@backstage/config'; import * as container from '@google-cloud/container'; -import { ClusterDetails, KubernetesClustersSupplier } from '../types/types'; +import { GKEClusterDetails, KubernetesClustersSupplier } from '../types/types'; type GkeClusterLocatorOptions = { projectId: string; @@ -49,7 +49,7 @@ export class GkeClusterLocator implements KubernetesClustersSupplier { ); } - async getClusters(): Promise { + async getClusters(): Promise { const { projectId, region, skipTLSVerify } = this.options; const request = { parent: `projects/${projectId}/locations/${region}`, diff --git a/plugins/kubernetes-backend/src/cluster-locator/index.test.ts b/plugins/kubernetes-backend/src/cluster-locator/index.test.ts index 9725d294a9..95be99a8a5 100644 --- a/plugins/kubernetes-backend/src/cluster-locator/index.test.ts +++ b/plugins/kubernetes-backend/src/cluster-locator/index.test.ts @@ -27,7 +27,6 @@ describe('getCombinedClusterDetails', () => { type: 'config', clusters: [ { - assumeRole: 'SomeRole', name: 'cluster1', serviceAccountToken: 'token', url: 'http://localhost:8080', @@ -50,7 +49,6 @@ describe('getCombinedClusterDetails', () => { expect(result).toStrictEqual([ { - assumeRole: 'SomeRole', name: 'cluster1', serviceAccountToken: 'token', url: 'http://localhost:8080', @@ -58,7 +56,6 @@ describe('getCombinedClusterDetails', () => { skipTLSVerify: false, }, { - assumeRole: undefined, name: 'cluster2', serviceAccountToken: undefined, url: 'http://localhost:8081', diff --git a/plugins/kubernetes-backend/src/kubernetes-auth-translator/AwsIamKubernetesAuthTranslator.ts b/plugins/kubernetes-backend/src/kubernetes-auth-translator/AwsIamKubernetesAuthTranslator.ts index 15dfa9e14e..e9004818a2 100644 --- a/plugins/kubernetes-backend/src/kubernetes-auth-translator/AwsIamKubernetesAuthTranslator.ts +++ b/plugins/kubernetes-backend/src/kubernetes-auth-translator/AwsIamKubernetesAuthTranslator.ts @@ -15,19 +15,17 @@ */ import AWS from 'aws-sdk'; import { sign } from 'aws4'; -import { ClusterDetails } from '../types/types'; +import { AWSClusterDetails } from '../types/types'; import { KubernetesAuthTranslator } from './types'; const base64 = (str: string) => Buffer.from(str.toString(), 'binary').toString('base64'); const prepend = (prep: string) => (str: string) => prep + str; -const replace = - (search: string | RegExp, substitution: string) => (str: string) => - str.replace(search, substitution); -const pipe = - (fns: ReadonlyArray) => - (thing: string): string => - fns.reduce((val, fn) => fn(val), thing); +const replace = (search: string | RegExp, substitution: string) => ( + str: string, +) => str.replace(search, substitution); +const pipe = (fns: ReadonlyArray) => (thing: string): string => + fns.reduce((val, fn) => fn(val), thing); const removePadding = replace(/=+$/, ''); const makeUrlSafe = pipe([replace('+', '-'), replace('/', '_')]); @@ -38,8 +36,7 @@ type SigningCreds = { }; export class AwsIamKubernetesAuthTranslator - implements KubernetesAuthTranslator -{ + implements KubernetesAuthTranslator { validCredentials(creds: SigningCreds): boolean { if (!creds.accessKeyId || !creds.secretAccessKey || !creds.sessionToken) { return false; @@ -116,9 +113,9 @@ export class AwsIamKubernetesAuthTranslator } async decorateClusterDetailsWithAuth( - clusterDetails: ClusterDetails, - ): Promise { - const clusterDetailsWithAuthToken: ClusterDetails = Object.assign( + clusterDetails: AWSClusterDetails, + ): Promise { + const clusterDetailsWithAuthToken: AWSClusterDetails = Object.assign( {}, clusterDetails, ); diff --git a/plugins/kubernetes-backend/src/kubernetes-auth-translator/GoogleKubernetesAuthTranslator.ts b/plugins/kubernetes-backend/src/kubernetes-auth-translator/GoogleKubernetesAuthTranslator.ts index 62ffd823cf..eacba4d3e1 100644 --- a/plugins/kubernetes-backend/src/kubernetes-auth-translator/GoogleKubernetesAuthTranslator.ts +++ b/plugins/kubernetes-backend/src/kubernetes-auth-translator/GoogleKubernetesAuthTranslator.ts @@ -15,17 +15,16 @@ */ import { KubernetesAuthTranslator } from './types'; -import { ClusterDetails } from '../types/types'; +import { GKEClusterDetails } from '../types/types'; import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common'; export class GoogleKubernetesAuthTranslator - implements KubernetesAuthTranslator -{ + implements KubernetesAuthTranslator { async decorateClusterDetailsWithAuth( - clusterDetails: ClusterDetails, + clusterDetails: GKEClusterDetails, requestBody: KubernetesRequestBody, - ): Promise { - const clusterDetailsWithAuthToken: ClusterDetails = Object.assign( + ): Promise { + const clusterDetailsWithAuthToken: GKEClusterDetails = Object.assign( {}, clusterDetails, ); diff --git a/plugins/kubernetes-backend/src/kubernetes-auth-translator/KubernetesAuthTranslatorGenerator.test.ts b/plugins/kubernetes-backend/src/kubernetes-auth-translator/KubernetesAuthTranslatorGenerator.test.ts index 33592b1c41..2d900b0bd1 100644 --- a/plugins/kubernetes-backend/src/kubernetes-auth-translator/KubernetesAuthTranslatorGenerator.test.ts +++ b/plugins/kubernetes-backend/src/kubernetes-auth-translator/KubernetesAuthTranslatorGenerator.test.ts @@ -24,20 +24,23 @@ describe('getKubernetesAuthTranslatorInstance', () => { const sut = KubernetesAuthTranslatorGenerator; it('can return an auth translator for google auth', () => { - const authTranslator: KubernetesAuthTranslator = - sut.getKubernetesAuthTranslatorInstance('google'); + const authTranslator: KubernetesAuthTranslator = sut.getKubernetesAuthTranslatorInstance( + 'google', + ); expect(authTranslator instanceof GoogleKubernetesAuthTranslator).toBe(true); }); it('can return an auth translator for aws auth', () => { - const authTranslator: KubernetesAuthTranslator = - sut.getKubernetesAuthTranslatorInstance('aws'); + const authTranslator: KubernetesAuthTranslator = sut.getKubernetesAuthTranslatorInstance( + 'aws', + ); expect(authTranslator instanceof AwsIamKubernetesAuthTranslator).toBe(true); }); it('can return an auth translator for serviceAccount auth', () => { - const authTranslator: KubernetesAuthTranslator = - sut.getKubernetesAuthTranslatorInstance('serviceAccount'); + const authTranslator: KubernetesAuthTranslator = sut.getKubernetesAuthTranslatorInstance( + 'serviceAccount', + ); expect( authTranslator instanceof ServiceAccountKubernetesAuthTranslator, ).toBe(true); diff --git a/plugins/kubernetes-backend/src/kubernetes-auth-translator/ServiceAccountKubernetesAuthTranslator.ts b/plugins/kubernetes-backend/src/kubernetes-auth-translator/ServiceAccountKubernetesAuthTranslator.ts index 098ffe3330..1c3add3c0d 100644 --- a/plugins/kubernetes-backend/src/kubernetes-auth-translator/ServiceAccountKubernetesAuthTranslator.ts +++ b/plugins/kubernetes-backend/src/kubernetes-auth-translator/ServiceAccountKubernetesAuthTranslator.ts @@ -15,19 +15,18 @@ */ import { KubernetesAuthTranslator } from './types'; -import { ClusterDetails } from '../types/types'; +import { ServiceAccountClusterDetails } from '../types/types'; import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common'; export class ServiceAccountKubernetesAuthTranslator - implements KubernetesAuthTranslator -{ + implements KubernetesAuthTranslator { async decorateClusterDetailsWithAuth( - clusterDetails: ClusterDetails, + clusterDetails: ServiceAccountClusterDetails, // To ignore TS6133 linting error where it detects 'requestBody' is declared but its value is never read. // @ts-ignore-start requestBody: KubernetesRequestBody, // eslint-disable-line @typescript-eslint/no-unused-vars // @ts-ignore-end - ): Promise { + ): Promise { return clusterDetails; } } diff --git a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts index e088fddd41..3039560aad 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts @@ -63,15 +63,15 @@ export class KubernetesFanOutHandler { 'backstage.io/kubernetes-id' ] || requestBody.entity?.metadata?.name; - const clusterDetails: ClusterDetails[] = - await this.serviceLocator.getClustersByServiceId(entityName); + const clusterDetails: ClusterDetails[] = await this.serviceLocator.getClustersByServiceId( + entityName, + ); // Execute all of these async actions simultaneously/without blocking sequentially as no common object is modified by them const promises: Promise[] = clusterDetails.map(cd => { - const kubernetesAuthTranslator: KubernetesAuthTranslator = - KubernetesAuthTranslatorGenerator.getKubernetesAuthTranslatorInstance( - cd.authProvider, - ); + const kubernetesAuthTranslator: KubernetesAuthTranslator = KubernetesAuthTranslatorGenerator.getKubernetesAuthTranslatorInstance( + cd.authProvider, + ); return kubernetesAuthTranslator.decorateClusterDetailsWithAuth( cd, requestBody, diff --git a/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts b/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts index e17b47e17a..76c3b0aac0 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts @@ -167,9 +167,10 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { labelSelector, ).then(r => ({ type: type, resources: r })); case 'services': - return this.fetchServicesForService(clusterDetails, labelSelector).then( - r => ({ type: type, resources: r }), - ); + return this.fetchServicesForService( + clusterDetails, + labelSelector, + ).then(r => ({ type: type, resources: r })); case 'horizontalpodautoscalers': return this.fetchHorizontalPodAutoscalersForService( clusterDetails, @@ -191,8 +192,9 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { customResource: CustomResource, labelSelector: string, ): Promise { - const customObjects = - this.kubernetesClientProvider.getCustomObjectsClient(clusterDetails); + const customObjects = this.kubernetesClientProvider.getCustomObjectsClient( + clusterDetails, + ); return customObjects .listClusterCustomObject( @@ -215,20 +217,18 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { client: Clients, ) => Promise<{ body: { items: Array }; response: http.IncomingMessage }>, ): Promise> { - const core = - this.kubernetesClientProvider.getCoreClientByClusterDetails( - clusterDetails, - ); - const apps = - this.kubernetesClientProvider.getAppsClientByClusterDetails( - clusterDetails, - ); - const autoscaling = - this.kubernetesClientProvider.getAutoscalingClientByClusterDetails( - clusterDetails, - ); - const networkingBeta1 = - this.kubernetesClientProvider.getNetworkingBeta1Client(clusterDetails); + const core = this.kubernetesClientProvider.getCoreClientByClusterDetails( + clusterDetails, + ); + const apps = this.kubernetesClientProvider.getAppsClientByClusterDetails( + clusterDetails, + ); + const autoscaling = this.kubernetesClientProvider.getAutoscalingClientByClusterDetails( + clusterDetails, + ); + const networkingBeta1 = this.kubernetesClientProvider.getNetworkingBeta1Client( + clusterDetails, + ); this.logger.debug(`calling cluster=${clusterDetails.name}`); return fn({ core, apps, autoscaling, networkingBeta1 }).then(({ body }) => { diff --git a/plugins/kubernetes-backend/src/service/router.ts b/plugins/kubernetes-backend/src/service/router.ts index beebd6fa65..984b3f14a0 100644 --- a/plugins/kubernetes-backend/src/service/router.ts +++ b/plugins/kubernetes-backend/src/service/router.ts @@ -70,8 +70,9 @@ export const makeRouter = ( const serviceId = req.params.serviceId; const requestBody: KubernetesRequestBody = req.body; try { - const response = - await kubernetesFanOutHandler.getKubernetesObjectsByEntity(requestBody); + const response = await kubernetesFanOutHandler.getKubernetesObjectsByEntity( + requestBody, + ); res.json(response); } catch (e) { logger.error( diff --git a/plugins/kubernetes-backend/src/types/types.ts b/plugins/kubernetes-backend/src/types/types.ts index 5150eba8cf..c5f555d588 100644 --- a/plugins/kubernetes-backend/src/types/types.ts +++ b/plugins/kubernetes-backend/src/types/types.ts @@ -27,7 +27,10 @@ export interface CustomResource { export interface ObjectFetchParams { serviceId: string; - clusterDetails: ClusterDetails; + clusterDetails: + | AWSClusterDetails + | GKEClusterDetails + | ServiceAccountClusterDetails; objectTypesToFetch: Set; labelSelector: string; customResources: CustomResource[]; @@ -76,5 +79,10 @@ export interface ClusterDetails { authProvider: string; serviceAccountToken?: string | undefined; skipTLSVerify?: boolean; +} + +export interface GKEClusterDetails extends ClusterDetails {} +export interface ServiceAccountClusterDetails extends ClusterDetails {} +export interface AWSClusterDetails extends ClusterDetails { assumeRole?: string; } From 9b1524ad3348c3f2a69ef1ba8b08d7853710ee75 Mon Sep 17 00:00:00 2001 From: Dominik Henneke Date: Mon, 19 Jul 2021 16:55:44 +0200 Subject: [PATCH 049/106] Update changeset Signed-off-by: Dominik Henneke --- .changeset/techdocs-fifty-cameras-listen.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.changeset/techdocs-fifty-cameras-listen.md b/.changeset/techdocs-fifty-cameras-listen.md index 9071004acd..fb6be9d709 100644 --- a/.changeset/techdocs-fifty-cameras-listen.md +++ b/.changeset/techdocs-fifty-cameras-listen.md @@ -3,3 +3,5 @@ --- Only update the `path` when the content is updated. +If content and path are updated independently, the frontend rendering is triggered twice on each navigation: Once for the `path` change (with the old content) and once for the new content. +This might result in a flickering rendering that is caused by the async frontend preprocessing, and the fact that replacing the shadow dom content is expensive. From eee05803ae9aee3c093d6ab3d39cb6953b8d3609 Mon Sep 17 00:00:00 2001 From: Dominik Henneke Date: Mon, 19 Jul 2021 17:47:03 +0200 Subject: [PATCH 050/106] Update @backstage/backend-common to `^0.8.6` Signed-off-by: Dominik Henneke --- .changeset/fifty-panthers-play.md | 5 +++++ plugins/jenkins-backend/package.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/fifty-panthers-play.md diff --git a/.changeset/fifty-panthers-play.md b/.changeset/fifty-panthers-play.md new file mode 100644 index 0000000000..1ea00ef223 --- /dev/null +++ b/.changeset/fifty-panthers-play.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-jenkins-backend': patch +--- + +Update `@backstage/backend-common` to `^0.8.6` diff --git a/plugins/jenkins-backend/package.json b/plugins/jenkins-backend/package.json index 408d714aaa..11c6c3cc0a 100644 --- a/plugins/jenkins-backend/package.json +++ b/plugins/jenkins-backend/package.json @@ -21,7 +21,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.8.5s", + "@backstage/backend-common": "^0.8.6", "@backstage/catalog-client": "^0.3.16", "@backstage/catalog-model": "^0.9.0", "@backstage/config": "^0.1.5", From 76fbdbc322e54825b623894928ea04b510351867 Mon Sep 17 00:00:00 2001 From: Nicolas Arnold Date: Mon, 19 Jul 2021 17:47:53 +0100 Subject: [PATCH 051/106] Modifying changesets and explicitly returning a promise for the AWS Creds This changes modifies the changeset to only include a patch (as per comments above). I have also changed the flow of retrieving the AWS creds. Previously, they were being returned after they were "resolved". Now we await and resolve a promise to guarantee that the credentials have been loaded. Signed-off-by: Nicolas Arnold --- .changeset/blue-feet-poke.md | 2 +- plugins/kubernetes-backend/api-report.md | 12 +++-- .../AwsIamKubernetesAuthTranslator.test.ts | 46 +++++++++++++------ .../AwsIamKubernetesAuthTranslator.ts | 38 ++++++++++----- 4 files changed, 68 insertions(+), 30 deletions(-) diff --git a/.changeset/blue-feet-poke.md b/.changeset/blue-feet-poke.md index 3be886c687..1a9e32a11a 100644 --- a/.changeset/blue-feet-poke.md +++ b/.changeset/blue-feet-poke.md @@ -1,5 +1,5 @@ --- -'@backstage/plugin-kubernetes-backend': minor +'@backstage/plugin-kubernetes-backend': patch --- Support assume role on kubernetes api configuration for AWS. diff --git a/plugins/kubernetes-backend/api-report.md b/plugins/kubernetes-backend/api-report.md index 0de29bd2f5..9f35fda79f 100644 --- a/plugins/kubernetes-backend/api-report.md +++ b/plugins/kubernetes-backend/api-report.md @@ -10,7 +10,7 @@ import { KubernetesFetchError } from '@backstage/plugin-kubernetes-common'; import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common'; import { Logger as Logger_2 } from 'winston'; -// Warning: (ae-missing-release-tag) "ClusterDetails" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// Warning: (ae-missing-release-tag) "AWSClusterDetails" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export interface AWSClusterDetails extends ClusterDetails { @@ -18,6 +18,8 @@ export interface AWSClusterDetails extends ClusterDetails { assumeRole?: string; } +// Warning: (ae-missing-release-tag) "ClusterDetails" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// // @public (undocumented) export interface ClusterDetails { // (undocumented) @@ -59,11 +61,13 @@ export interface FetchResponseWrapper { responses: FetchResponse[]; } -// Warning: (ae-missing-release-tag) "KubernetesClustersSupplier" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// Warning: (ae-missing-release-tag) "GKEClusterDetails" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export interface GKEClusterDetails extends ClusterDetails {} +// Warning: (ae-missing-release-tag) "KubernetesClustersSupplier" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// // @public (undocumented) export interface KubernetesClustersSupplier { // (undocumented) @@ -142,11 +146,13 @@ export interface RouterOptions { logger: Logger_2; } -// Warning: (ae-missing-release-tag) "ServiceLocatorMethod" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// Warning: (ae-missing-release-tag) "ServiceAccountClusterDetails" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export interface ServiceAccountClusterDetails extends ClusterDetails {} +// Warning: (ae-missing-release-tag) "ServiceLocatorMethod" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// // @public (undocumented) export type ServiceLocatorMethod = 'multiTenant' | 'http'; diff --git a/plugins/kubernetes-backend/src/kubernetes-auth-translator/AwsIamKubernetesAuthTranslator.test.ts b/plugins/kubernetes-backend/src/kubernetes-auth-translator/AwsIamKubernetesAuthTranslator.test.ts index d5179fc07a..9060ce9239 100644 --- a/plugins/kubernetes-backend/src/kubernetes-auth-translator/AwsIamKubernetesAuthTranslator.test.ts +++ b/plugins/kubernetes-backend/src/kubernetes-auth-translator/AwsIamKubernetesAuthTranslator.test.ts @@ -19,16 +19,23 @@ import { AwsIamKubernetesAuthTranslator } from './AwsIamKubernetesAuthTranslator import { get, def } from 'bdd-lazy-var'; describe('AwsIamKubernetesAuthTranslator tests', () => { - let valid: boolean = true; let role: any = undefined; - let response: any = { + const credentials: any = { + accessKeyId: 'bloop', + secretAccessKey: 'omg-so-secret', + sessionToken: 'token', + }; + + let assumeResponse: any = { Credentials: { - AccessKeyId: 'bloop', - SecretAccessKey: 'omg-so-secret', - SessionToken: 'token', + AccessKeyId: credentials.accessKeyId, + SecretAccessKey: credentials.secretAccessKey, + SessionToken: credentials.sessionToken, }, }; + let credentialsResponse: any = new AWS.Credentials(credentials); + AWSMock.setSDKInstance(AWS); beforeEach(() => { @@ -41,13 +48,15 @@ describe('AwsIamKubernetesAuthTranslator tests', () => { def('subject', () => { AWSMock.mock('STS', 'assumeRole', (_params: any, callback: Function) => { - callback(null, response); + callback(null, assumeResponse); }); const authTranslator = new AwsIamKubernetesAuthTranslator(); + jest - .spyOn(authTranslator, 'validCredentials') - .mockImplementation(() => valid); + .spyOn(authTranslator, 'awsGetCredentials') + .mockImplementation(async () => credentialsResponse); + return authTranslator.decorateClusterDetailsWithAuth({ assumeRole: role, name: 'test-cluster', @@ -86,16 +95,25 @@ describe('AwsIamKubernetesAuthTranslator tests', () => { describe('When the role is invalid', () => { it('returns the original AWS credentials', async () => { - response = undefined; - + assumeResponse = undefined; await expect(get('subject')).rejects.toThrow(/Unable to assume role:/); }); }); }); - it('throws when unable to get aws credentials', async () => { - valid = false; - AWS.config.credentials = undefined; - await expect(get('subject')).rejects.toThrow('No AWS credentials found'); + describe('When no creds are returned from AWS', () => { + it('throws unable to get aws credentials', async () => { + credentialsResponse = new Error(); + await expect(get('subject')).rejects.toThrow('No AWS credentials found.'); + }); + }); + + describe('When invalid creds are returned from AWS', () => { + it('throws credentials are invalid to get aws credentials', async () => { + credentialsResponse = new AWS.Credentials(credentialsResponse); + await expect(get('subject')).rejects.toThrow( + 'Invalid AWS credentials found.', + ); + }); }); }); diff --git a/plugins/kubernetes-backend/src/kubernetes-auth-translator/AwsIamKubernetesAuthTranslator.ts b/plugins/kubernetes-backend/src/kubernetes-auth-translator/AwsIamKubernetesAuthTranslator.ts index e9004818a2..c15022d504 100644 --- a/plugins/kubernetes-backend/src/kubernetes-auth-translator/AwsIamKubernetesAuthTranslator.ts +++ b/plugins/kubernetes-backend/src/kubernetes-auth-translator/AwsIamKubernetesAuthTranslator.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import AWS from 'aws-sdk'; +import AWS, { Credentials } from 'aws-sdk'; import { sign } from 'aws4'; import { AWSClusterDetails } from '../types/types'; import { KubernetesAuthTranslator } from './types'; @@ -38,29 +38,43 @@ type SigningCreds = { export class AwsIamKubernetesAuthTranslator implements KubernetesAuthTranslator { validCredentials(creds: SigningCreds): boolean { - if (!creds.accessKeyId || !creds.secretAccessKey || !creds.sessionToken) { + if ( + !creds?.accessKeyId || + !creds?.secretAccessKey || + !creds?.sessionToken + ) { return false; } return true; } + awsGetCredentials = async (): Promise => { + return new Promise((resolve, reject) => { + AWS.config.getCredentials(err => { + if (err) { + return reject(err); + } + + return resolve(AWS.config.credentials as Credentials); + }); + }); + }; + async getCredentials(assumeRole: string | undefined): Promise { return new Promise(async (resolve, reject) => { - await AWS.config.getCredentials(err => { - if (err) { - console.error('Unable to load aws config.'); - reject(err); - } - }); + const awsCreds = await this.awsGetCredentials(); + + if (!(awsCreds instanceof Credentials)) + return reject(Error('No AWS credentials found.')); let creds: SigningCreds = { - accessKeyId: AWS.config.credentials?.accessKeyId, - secretAccessKey: AWS.config.credentials?.secretAccessKey, - sessionToken: AWS.config.credentials?.sessionToken, + accessKeyId: awsCreds.accessKeyId, + secretAccessKey: awsCreds.secretAccessKey, + sessionToken: awsCreds.sessionToken, }; if (!this.validCredentials(creds)) - return reject(Error('No AWS credentials found.')); + return reject(Error('Invalid AWS credentials found.')); if (!assumeRole) return resolve(creds); try { From 249cbfc893d7f96cd948d39e7e5e33158ecaef83 Mon Sep 17 00:00:00 2001 From: Tim Hansen Date: Mon, 19 Jul 2021 12:18:29 -0600 Subject: [PATCH 052/106] Minor tweaks, button fixes Signed-off-by: Tim Hansen --- .changeset/green-lies-hammer.md | 4 +- .../software-catalog/catalog-customization.md | 40 +++++++++++-------- .../SupportButton/SupportButton.tsx | 26 ++++++------ .../layout/ContentHeader/ContentHeader.tsx | 6 +-- plugins/catalog/api-report.md | 5 +++ .../components/CatalogPage/CatalogPage.tsx | 2 +- .../FilteredEntityLayout/FilterContainer.tsx | 18 +++------ plugins/catalog/src/index.ts | 11 ++--- 8 files changed, 59 insertions(+), 53 deletions(-) diff --git a/.changeset/green-lies-hammer.md b/.changeset/green-lies-hammer.md index fc7f6e301d..af1c2e6e92 100644 --- a/.changeset/green-lies-hammer.md +++ b/.changeset/green-lies-hammer.md @@ -5,6 +5,4 @@ '@backstage/plugin-catalog': patch --- -Changing the layout of the Catalog & API page to use responsive wrappers for table & filters. The goal is to enforce a responsive layout & that the Catalog & API page are better useable on smaller screens by showing more of the main content. To apply this changes to your custom catalog page, you can add the wrappers following the updated documentation on catalog customization. - -Additionally, the tests for Material UI breakpoints were adjusted & used to test the responsive wrappers. +Updated the layout of catalog and API index pages to handle smaller screen sizes. This adds responsive wrappers to the entity tables, and switches filters to a drawer when width-constrained. If you have created a custom catalog or API index page, you will need to update the page structure to match the updated [catalog customization](https://backstage.io/docs/features/software-catalog/catalog-customization) documentation. diff --git a/docs/features/software-catalog/catalog-customization.md b/docs/features/software-catalog/catalog-customization.md index 4049b005f1..0c11d8f476 100644 --- a/docs/features/software-catalog/catalog-customization.md +++ b/docs/features/software-catalog/catalog-customization.md @@ -27,7 +27,11 @@ default catalog page and create a component in a ```tsx // imports, etc omitted for brevity. for full source see: // https://github.com/backstage/backstage/blob/master/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx -export const CustomCatalogPage = () => { +export const CustomCatalogPage = ({ + columns, + actions, + initiallySelectedFilter = 'owned', +}: CatalogPageProps) => { return ( @@ -139,23 +143,27 @@ export const EntitySecurityTierPicker = () => { Now we can add the component to `CustomCatalogPage`: ```diff -export const CustomCatalogPage = () => { +export const CustomCatalogPage = ({ + columns, + actions, + initiallySelectedFilter = 'owned', +}: CatalogPageProps) => { return ( ... - - - - - + + + + + ... }; ``` diff --git a/packages/core-components/src/components/SupportButton/SupportButton.tsx b/packages/core-components/src/components/SupportButton/SupportButton.tsx index 45012e50a4..3e719acb95 100644 --- a/packages/core-components/src/components/SupportButton/SupportButton.tsx +++ b/packages/core-components/src/components/SupportButton/SupportButton.tsx @@ -28,7 +28,7 @@ import { makeStyles, Popover, } from '@material-ui/core'; -import React, { Fragment, MouseEventHandler, useState } from 'react'; +import React, { MouseEventHandler, useState } from 'react'; import { SupportItem, SupportItemLink, useSupportConfig } from '../../hooks'; import { Link } from '../Link'; @@ -94,17 +94,17 @@ export const SupportButton = ({ title, children }: SupportButtonProps) => { }; return ( - - + <> + + + { - + ); }; diff --git a/packages/core-components/src/layout/ContentHeader/ContentHeader.tsx b/packages/core-components/src/layout/ContentHeader/ContentHeader.tsx index 0bd377e5af..62cb80cffa 100644 --- a/packages/core-components/src/layout/ContentHeader/ContentHeader.tsx +++ b/packages/core-components/src/layout/ContentHeader/ContentHeader.tsx @@ -18,7 +18,7 @@ * TODO favoriteable capability */ -import React, { ComponentType, Fragment, PropsWithChildren } from 'react'; +import React, { ComponentType, PropsWithChildren } from 'react'; import { Typography, makeStyles } from '@material-ui/core'; import { Helmet } from 'react-helmet'; @@ -98,7 +98,7 @@ export const ContentHeader = ({ ); return ( - + <>
@@ -111,6 +111,6 @@ export const ContentHeader = ({
{children}
-
+ ); }; diff --git a/plugins/catalog/api-report.md b/plugins/catalog/api-report.md index 5a4ddefaca..1b6e2cd4d0 100644 --- a/plugins/catalog/api-report.md +++ b/plugins/catalog/api-report.md @@ -104,6 +104,11 @@ export type CatalogTableRow = { }; }; +// Warning: (ae-missing-release-tag) "CreateComponentButton" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const CreateComponentButton: () => JSX.Element | null; + // Warning: (ae-missing-release-tag) "createMetadataDescriptionColumn" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx index 0fe3504c6d..963e04e5e2 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx @@ -36,7 +36,7 @@ import { import React from 'react'; import { CatalogTable } from '../CatalogTable'; import { EntityRow } from '../CatalogTable/types'; -import { CreateComponentButton } from '../CreateComponentButton/CreateComponentButton'; +import { CreateComponentButton } from '../CreateComponentButton'; import { FilteredEntityLayout, EntityListContainer, diff --git a/plugins/catalog/src/components/FilteredEntityLayout/FilterContainer.tsx b/plugins/catalog/src/components/FilteredEntityLayout/FilterContainer.tsx index d38e800f46..f21f9c77a3 100644 --- a/plugins/catalog/src/components/FilteredEntityLayout/FilterContainer.tsx +++ b/plugins/catalog/src/components/FilteredEntityLayout/FilterContainer.tsx @@ -32,27 +32,21 @@ export const FilterContainer = ({ children }: PropsWithChildren<{}>) => { theme.breakpoints.down('md'), ); const theme = useTheme(); - const [showFiltersDrawer, toggleFiltersDrawer] = useState(false); + const [filterDrawerOpen, setFilterDrawerOpen] = useState(false); return isMidSizeScreen ? ( <> { - toggleFiltersDrawer(false); - }} - elevation={0} + open={filterDrawerOpen} + onClose={() => setFilterDrawerOpen(false)} anchor="left" disableAutoFocus keepMounted diff --git a/plugins/catalog/src/index.ts b/plugins/catalog/src/index.ts index 4b348580f7..2ac7f10227 100644 --- a/plugins/catalog/src/index.ts +++ b/plugins/catalog/src/index.ts @@ -15,13 +15,16 @@ */ export * from './components/AboutCard'; -export { CatalogResultListItem } from './components/CatalogResultListItem'; +export * from './components/CatalogResultListItem'; export { CatalogTable } from './components/CatalogTable'; export type { EntityRow as CatalogTableRow } from './components/CatalogTable'; -export { EntityLayout } from './components/EntityLayout'; +export * from './components/CatalogTable/columns'; +export * from './components/CreateComponentButton'; +export * from './components/EntityLayout'; export * from './components/EntityOrphanWarning'; -export { EntityPageLayout } from './components/EntityPageLayout'; +export * from './components/EntityPageLayout'; export * from './components/EntitySwitch'; +export * from './components/FilteredEntityLayout'; export { Router } from './components/Router'; export { CatalogEntityPage, @@ -39,5 +42,3 @@ export { EntityLinksCard, EntitySystemDiagramCard, } from './plugin'; -export * from './components/CatalogTable/columns'; -export * from './components/FilteredEntityLayout'; From 8d6408d05cbc26862fa82fcb524f252acee724ee Mon Sep 17 00:00:00 2001 From: Philipp Hugenroth Date: Mon, 19 Jul 2021 21:57:18 +0200 Subject: [PATCH 053/106] Adjust tests to use only text & test drawer visibility Signed-off-by: Philipp Hugenroth --- .../src/components/CatalogPage/CatalogPage.test.tsx | 12 ++++++++---- .../FilteredEntityLayout/FilterContainer.tsx | 3 +-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx index 598f534d14..b462cfb6b6 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx @@ -248,13 +248,17 @@ describe('CatalogPage', () => { it('should wrap filter in drawer on smaller screens', async () => { mockBreakpoint({ matches: true }); - const { getByTestId } = await renderWrapped(); - expect(getByTestId('entity-filters-drawer')).toBeInTheDocument(); + const { getAllByText } = await renderWrapped(); + const elems = getAllByText('Filters'); + expect(elems[0]).toBeVisible(); + expect(elems[1]).not.toBeVisible(); + fireEvent.click(elems[0]); + expect(elems[1]).toBeVisible(); }); it('should wrap filter in grid on larger screens', async () => { mockBreakpoint({ matches: false }); - const { getByTestId } = await renderWrapped(); - expect(getByTestId('entity-filters-grid')).toBeInTheDocument(); + const { queryAllByText } = await renderWrapped(); + expect(queryAllByText('Filters').length).toBe(0); }); }); diff --git a/plugins/catalog/src/components/FilteredEntityLayout/FilterContainer.tsx b/plugins/catalog/src/components/FilteredEntityLayout/FilterContainer.tsx index f21f9c77a3..683dbf8b40 100644 --- a/plugins/catalog/src/components/FilteredEntityLayout/FilterContainer.tsx +++ b/plugins/catalog/src/components/FilteredEntityLayout/FilterContainer.tsx @@ -44,7 +44,6 @@ export const FilterContainer = ({ children }: PropsWithChildren<{}>) => { Filters setFilterDrawerOpen(false)} anchor="left" @@ -65,7 +64,7 @@ export const FilterContainer = ({ children }: PropsWithChildren<{}>) => { ) : ( - + {children} ); From cedb602a42c1dba62dbebba3384de953c3b37ff6 Mon Sep 17 00:00:00 2001 From: Fidel Coria Date: Mon, 19 Jul 2021 16:29:30 -0500 Subject: [PATCH 054/106] custom theme on devApp for single plugin Signed-off-by: Fidel Coria --- packages/dev-utils/src/devApp/render.tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/dev-utils/src/devApp/render.tsx b/packages/dev-utils/src/devApp/render.tsx index 508eb105f1..e6338cd467 100644 --- a/packages/dev-utils/src/devApp/render.tsx +++ b/packages/dev-utils/src/devApp/render.tsx @@ -37,6 +37,7 @@ import { import { AnyApiFactory, ApiFactory, + AppTheme, attachComponentData, configApiRef, createApiFactory, @@ -79,6 +80,7 @@ class DevAppBuilder { private readonly sidebarItems = new Array(); private defaultPage?: string; + private themes?: Array; /** * Register one or more plugins to render in the dev app @@ -144,6 +146,14 @@ class DevAppBuilder { return this; } + /** + * Adds an array of themes to overide the default theme. + */ + addThemes(themes: AppTheme[]) { + this.themes = themes; + return this; + } + /** * Build a DevApp component using the resources registered so far */ @@ -166,6 +176,7 @@ class DevAppBuilder { const app = createApp({ apis, plugins: this.plugins, + themes: this.themes, bindRoutes: ({ bind }) => { for (const plugin of this.plugins ?? []) { const targets: Record> = {}; From 01001a32425db56c39239f94dcf2fee55bddf203 Mon Sep 17 00:00:00 2001 From: Fidel Coria Date: Mon, 19 Jul 2021 17:39:07 -0500 Subject: [PATCH 055/106] add changeset Signed-off-by: Fidel Coria --- .changeset/wild-fishes-suffer.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/wild-fishes-suffer.md diff --git a/.changeset/wild-fishes-suffer.md b/.changeset/wild-fishes-suffer.md new file mode 100644 index 0000000000..5b914860b2 --- /dev/null +++ b/.changeset/wild-fishes-suffer.md @@ -0,0 +1,5 @@ +--- +'@backstage/dev-utils': patch +--- + +Allow custom theme for dev app. From b0750ca4fb04256cc8b68fd06f48c7b6420161b1 Mon Sep 17 00:00:00 2001 From: Fidel Coria Date: Mon, 19 Jul 2021 18:01:22 -0500 Subject: [PATCH 056/106] add api-report.md for dev-utils Signed-off-by: Fidel Coria --- packages/dev-utils/api-report.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/dev-utils/api-report.md b/packages/dev-utils/api-report.md index 6790992ecf..5f58b297d5 100644 --- a/packages/dev-utils/api-report.md +++ b/packages/dev-utils/api-report.md @@ -6,6 +6,7 @@ /// import { ApiFactory } from '@backstage/core-plugin-api'; +import { AppTheme } from '@backstage/core-plugin-api'; import { ComponentType } from 'react'; import { createPlugin } from '@backstage/core-plugin-api'; import { Entity } from '@backstage/catalog-model'; From 2f732b832036e0dc7a34c88fe965edc35e9246d3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Jul 2021 04:09:52 +0000 Subject: [PATCH 057/106] chore(deps): bump webpack from 4.44.2 to 4.46.0 Bumps [webpack](https://github.com/webpack/webpack) from 4.44.2 to 4.46.0. - [Release notes](https://github.com/webpack/webpack/releases) - [Commits](https://github.com/webpack/webpack/compare/v4.44.2...v4.46.0) --- updated-dependencies: - dependency-name: webpack dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- yarn.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/yarn.lock b/yarn.lock index 8cd71b039e..1a88200c58 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11727,10 +11727,10 @@ endent@^2.0.1: fast-json-parse "^1.0.3" objectorarray "^1.0.4" -enhanced-resolve@^4.0.0, enhanced-resolve@^4.3.0: - version "4.3.0" - resolved "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.3.0.tgz#3b806f3bfafc1ec7de69551ef93cca46c1704126" - integrity sha512-3e87LvavsdxyoCfGusJnrZ5G8SLPOFeHSNpZI/ATL9a5leXo2k0w6MKnbqhdBad9qTobSfB20Ld7UmgoNbAZkQ== +enhanced-resolve@^4.0.0, enhanced-resolve@^4.5.0: + version "4.5.0" + resolved "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz#2f3cfd84dbe3b487f18f2db2ef1e064a571ca5ec" + integrity sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg== dependencies: graceful-fs "^4.1.2" memory-fs "^0.5.0" @@ -25978,9 +25978,9 @@ webpack-virtual-modules@^0.2.2: debug "^3.0.0" webpack@^4.41.6, webpack@^4.44.2: - version "4.44.2" - resolved "https://registry.npmjs.org/webpack/-/webpack-4.44.2.tgz#6bfe2b0af055c8b2d1e90ed2cd9363f841266b72" - integrity sha512-6KJVGlCxYdISyurpQ0IPTklv+DULv05rs2hseIXer6D7KrUicRDLFb4IUM1S6LUAKypPM/nSiVSuv8jHu1m3/Q== + version "4.46.0" + resolved "https://registry.npmjs.org/webpack/-/webpack-4.46.0.tgz#bf9b4404ea20a073605e0a011d188d77cb6ad542" + integrity sha512-6jJuJjg8znb/xRItk7bkT0+Q7AHCYjjFnvKIWQPkNIOyRqoCGvkOs0ipeQzrqz4l5FtN5ZI/ukEHroeX/o1/5Q== dependencies: "@webassemblyjs/ast" "1.9.0" "@webassemblyjs/helper-module-context" "1.9.0" @@ -25990,7 +25990,7 @@ webpack@^4.41.6, webpack@^4.44.2: ajv "^6.10.2" ajv-keywords "^3.4.1" chrome-trace-event "^1.0.2" - enhanced-resolve "^4.3.0" + enhanced-resolve "^4.5.0" eslint-scope "^4.0.3" json-parse-better-errors "^1.0.2" loader-runner "^2.4.0" From b16daa790791663ed40044364be3a3dc301eb108 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Jul 2021 04:17:06 +0000 Subject: [PATCH 058/106] chore(deps): bump isomorphic-git from 1.8.0 to 1.9.2 Bumps [isomorphic-git](https://github.com/isomorphic-git/isomorphic-git) from 1.8.0 to 1.9.2. - [Release notes](https://github.com/isomorphic-git/isomorphic-git/releases) - [Changelog](https://github.com/isomorphic-git/isomorphic-git/blob/main/docs/in-the-news.md) - [Commits](https://github.com/isomorphic-git/isomorphic-git/compare/v1.8.0...v1.9.2) --- updated-dependencies: - dependency-name: isomorphic-git dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 8cd71b039e..922a24a97b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15689,9 +15689,9 @@ isomorphic-form-data@~2.0.0: form-data "^2.3.2" isomorphic-git@^1.8.0: - version "1.8.0" - resolved "https://registry.npmjs.org/isomorphic-git/-/isomorphic-git-1.8.0.tgz#50440650a64706a321cbea1af955c1cf1110b238" - integrity sha512-TWJvQh+++eFrEG0IFS/jLhMwsBoCOX1/Dsw9q8no59Mp1K0jEjSHXFWv2P04PwkxcIpePkXVBI5YFcFT2nkuQg== + version "1.9.2" + resolved "https://registry.npmjs.org/isomorphic-git/-/isomorphic-git-1.9.2.tgz#0e492dbcd9873070b2a57eef257a45b90020ed72" + integrity sha512-puCXcGgtkDXdMYLZlAEGbpkbmHn/Q4Lsl2uMFwMLOKmmr8Qe7Fe3+c6k2+aHW3rMdJYg9xTv95BJ+PRzR8Ydww== dependencies: async-lock "^1.1.0" clean-git-ref "^2.0.1" From 93be473b54baadffe523895ba7d03a1385980bdf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Jul 2021 04:20:27 +0000 Subject: [PATCH 059/106] chore(deps): bump @azure/msal-node from 1.1.0 to 1.2.0 Bumps [@azure/msal-node](https://github.com/AzureAD/microsoft-authentication-library-for-js) from 1.1.0 to 1.2.0. - [Release notes](https://github.com/AzureAD/microsoft-authentication-library-for-js/releases) - [Commits](https://github.com/AzureAD/microsoft-authentication-library-for-js/compare/v1.1.0...v1.2.0) --- updated-dependencies: - dependency-name: "@azure/msal-node" dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- yarn.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/yarn.lock b/yarn.lock index 8cd71b039e..2b87b35f06 100644 --- a/yarn.lock +++ b/yarn.lock @@ -211,10 +211,10 @@ dependencies: debug "^4.1.1" -"@azure/msal-common@^4.3.0": - version "4.3.0" - resolved "https://registry.npmjs.org/@azure/msal-common/-/msal-common-4.3.0.tgz#b540e92748656724088bf77192e59943a93135bc" - integrity sha512-jFqUWe83wVb6O8cNGGBFg2QlKvqM1ezUgJTEV7kIsAPX0RXhGFE4B1DLNt6hCnkTXDbw+KGW0zgxOEr4MJQwLw== +"@azure/msal-common@^4.4.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== dependencies: debug "^4.1.1" @@ -229,11 +229,11 @@ uuid "^8.3.0" "@azure/msal-node@^1.1.0": - version "1.1.0" - resolved "https://registry.npmjs.org/@azure/msal-node/-/msal-node-1.1.0.tgz#e472cfadead169f8832066ae6c2d6b8eef4e89e4" - integrity sha512-gMO9aZdWOzufp1PcdD5ID25DdS9eInxgeCqx4Tk8PVU6Z7RxJQhoMzS64cJhGdpYgeIQwKljtF0CLCcPFxew/w== + version "1.2.0" + resolved "https://registry.npmjs.org/@azure/msal-node/-/msal-node-1.2.0.tgz#d08a5fb3391436715cb37c6eb44816013bdfa11e" + integrity sha512-79o5n483vslc7Qegh9+0BsxODRmlk6YYjVdl9jvwmAuF+i+oylq57e7RVhTVocKCbLCIMOKARI14JyKdDbW0WA== dependencies: - "@azure/msal-common" "^4.3.0" + "@azure/msal-common" "^4.4.0" axios "^0.21.1" jsonwebtoken "^8.5.1" uuid "^8.3.0" From 65a4d898318e9e078199def138f6cc44b258f85b Mon Sep 17 00:00:00 2001 From: Nicolas Arnold Date: Tue, 20 Jul 2021 09:15:03 +0100 Subject: [PATCH 060/106] Refactoring the valid credentials method Signed-off-by: Nicolas Arnold --- .../AwsIamKubernetesAuthTranslator.ts | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/plugins/kubernetes-backend/src/kubernetes-auth-translator/AwsIamKubernetesAuthTranslator.ts b/plugins/kubernetes-backend/src/kubernetes-auth-translator/AwsIamKubernetesAuthTranslator.ts index c15022d504..101bed32a3 100644 --- a/plugins/kubernetes-backend/src/kubernetes-auth-translator/AwsIamKubernetesAuthTranslator.ts +++ b/plugins/kubernetes-backend/src/kubernetes-auth-translator/AwsIamKubernetesAuthTranslator.ts @@ -38,14 +38,9 @@ type SigningCreds = { export class AwsIamKubernetesAuthTranslator implements KubernetesAuthTranslator { validCredentials(creds: SigningCreds): boolean { - if ( - !creds?.accessKeyId || - !creds?.secretAccessKey || - !creds?.sessionToken - ) { - return false; - } - return true; + return ((creds?.accessKeyId && + creds?.secretAccessKey && + creds?.sessionToken) as unknown) as boolean; } awsGetCredentials = async (): Promise => { From bdd6ab5f192ee4b56352783cff94042284284334 Mon Sep 17 00:00:00 2001 From: Carlo Colombo Date: Mon, 19 Jul 2021 15:16:09 +0200 Subject: [PATCH 061/106] Adds configuration for request logging handler when creating the backend express app Signed-off-by: Carlo Colombo --- .changeset/fuzzy-pots-whisper.md | 15 +++++++++++++++ packages/backend-common/api-report.md | 11 +++++++++++ packages/backend-common/src/service/index.ts | 2 +- .../src/service/lib/ServiceBuilderImpl.ts | 16 +++++++++++++--- packages/backend-common/src/service/types.ts | 13 +++++++++++++ 5 files changed, 53 insertions(+), 4 deletions(-) create mode 100644 .changeset/fuzzy-pots-whisper.md diff --git a/.changeset/fuzzy-pots-whisper.md b/.changeset/fuzzy-pots-whisper.md new file mode 100644 index 0000000000..772ba1fb43 --- /dev/null +++ b/.changeset/fuzzy-pots-whisper.md @@ -0,0 +1,15 @@ +--- +'@backstage/backend-common': patch +--- + +It's possible to customize the request logging handler when building the service. For example in your `backend` + +``` + const service = createServiceBuilder(module) + .loadConfig(config) + .setRequestLoggingHandler((logger?: Logger): RequestHandler => { + const actualLogger = (logger || getRootLogger()).child({ + type: 'incomingRequest', + }); + return expressWinston.logger({ ... +``` diff --git a/packages/backend-common/api-report.md b/packages/backend-common/api-report.md index 571a26e5dc..2652dad122 100644 --- a/packages/backend-common/api-report.md +++ b/packages/backend-common/api-report.md @@ -439,6 +439,13 @@ export type ReadTreeResponseFile = { // @public export function requestLoggingHandler(logger?: Logger_2): RequestHandler; +// Warning: (ae-missing-release-tag) "RequestLoggingHandlerFactory" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type RequestLoggingHandlerFactory = ( + logger?: Logger_2, +) => RequestHandler; + // Warning: (ae-missing-release-tag) "resolvePackagePath" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public @@ -492,6 +499,9 @@ export type ServiceBuilder = { enableCors(options: cors.CorsOptions): ServiceBuilder; setHttpsSettings(settings: HttpsSettings): ServiceBuilder; addRouter(root: string, router: Router | RequestHandler): ServiceBuilder; + setRequestLoggingHandler( + requestLoggingHandler: RequestLoggingHandlerFactory, + ): ServiceBuilder; start(): Promise; }; @@ -593,6 +603,7 @@ export function useHotMemoize(_module: NodeModule, valueFactory: () => T): T; // src/service/types.d.ts:57:5 - (ae-forgotten-export) The symbol "HttpsSettings" needs to be exported by the entry point index.d.ts // src/service/types.d.ts:61:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen // src/service/types.d.ts:62:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/service/types.d.ts:70:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen // (No @packageDocumentation comment for this package) ``` diff --git a/packages/backend-common/src/service/index.ts b/packages/backend-common/src/service/index.ts index 4eb0bf4a5a..d01f25fad3 100644 --- a/packages/backend-common/src/service/index.ts +++ b/packages/backend-common/src/service/index.ts @@ -16,4 +16,4 @@ export { createServiceBuilder } from './createServiceBuilder'; export { createStatusCheckRouter } from './createStatusCheckRouter'; -export type { ServiceBuilder } from './types'; +export type { ServiceBuilder, RequestLoggingHandlerFactory } from './types'; diff --git a/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts b/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts index 380d61abc8..7a3ebfed70 100644 --- a/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts +++ b/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts @@ -27,9 +27,9 @@ import { getRootLogger } from '../../logging'; import { errorHandler, notFoundHandler, - requestLoggingHandler, + requestLoggingHandler as defaultRequestLoggingHandler, } from '../../middleware'; -import { ServiceBuilder } from '../types'; +import { RequestLoggingHandlerFactory, ServiceBuilder } from '../types'; import { CspOptions, HttpsSettings, @@ -65,6 +65,7 @@ export class ServiceBuilderImpl implements ServiceBuilder { private cspOptions: Record | undefined; private httpsSettings: HttpsSettings | undefined; private routers: [string, Router][]; + private requestLoggingHandler: RequestLoggingHandlerFactory | undefined; // Reference to the module where builder is created - needed for hot module // reloading private module: NodeModule; @@ -144,6 +145,13 @@ export class ServiceBuilderImpl implements ServiceBuilder { return this; } + setRequestLoggingHandler( + requestLoggingHandler: RequestLoggingHandlerFactory, + ) { + this.requestLoggingHandler = requestLoggingHandler; + return this; + } + async start(): Promise { const app = express(); const { @@ -160,7 +168,9 @@ export class ServiceBuilderImpl implements ServiceBuilder { app.use(cors(corsOptions)); } app.use(compression()); - app.use(requestLoggingHandler(logger)); + app.use( + (this.requestLoggingHandler ?? defaultRequestLoggingHandler)(logger), + ); for (const [root, route] of this.routers) { app.use(root, route); } diff --git a/packages/backend-common/src/service/types.ts b/packages/backend-common/src/service/types.ts index 70f62acfca..f845397ca2 100644 --- a/packages/backend-common/src/service/types.ts +++ b/packages/backend-common/src/service/types.ts @@ -85,8 +85,21 @@ export type ServiceBuilder = { */ addRouter(root: string, router: Router | RequestHandler): ServiceBuilder; + /** + * Set the request logging handler + * + * If no handler is given the default one is used + * + * @param requestLoggingHandler a factory function that given a logger returns an handler + */ + setRequestLoggingHandler( + requestLoggingHandler: RequestLoggingHandlerFactory, + ): ServiceBuilder; + /** * Starts the server using the given settings. */ start(): Promise; }; + +export type RequestLoggingHandlerFactory = (logger?: Logger) => RequestHandler; From f5067d16609e7690d6fe9e5abb957d8048f47232 Mon Sep 17 00:00:00 2001 From: Philipp Hugenroth Date: Tue, 20 Jul 2021 11:02:28 +0200 Subject: [PATCH 062/106] Adjust test to be more specific Signed-off-by: Philipp Hugenroth --- .../components/CatalogPage/CatalogPage.test.tsx | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx index b462cfb6b6..e09aec55e7 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx @@ -248,17 +248,10 @@ describe('CatalogPage', () => { it('should wrap filter in drawer on smaller screens', async () => { mockBreakpoint({ matches: true }); - const { getAllByText } = await renderWrapped(); - const elems = getAllByText('Filters'); - expect(elems[0]).toBeVisible(); - expect(elems[1]).not.toBeVisible(); - fireEvent.click(elems[0]); - expect(elems[1]).toBeVisible(); - }); - - it('should wrap filter in grid on larger screens', async () => { - mockBreakpoint({ matches: false }); - const { queryAllByText } = await renderWrapped(); - expect(queryAllByText('Filters').length).toBe(0); + const { getByRole } = await renderWrapped(); + const button = getByRole('button', { name: 'Filters' }); + expect(getByRole('presentation', { hidden: true })).toBeInTheDocument(); + fireEvent.click(button); + expect(getByRole('presentation')).toBeVisible(); }); }); From dfcf5dbc1eee5f822083c14e9502c07f9b8846ec Mon Sep 17 00:00:00 2001 From: Philipp Hugenroth Date: Tue, 20 Jul 2021 12:09:52 +0200 Subject: [PATCH 063/106] Improve documentation of change to enable a better responsive implementation by tusers Clean up according to review Signed-off-by: Philipp Hugenroth --- .changeset/poor-jars-sniff.md | 8 +++++++- .../app/src/components/catalog/EntityPage.tsx | 5 +++-- .../src/layout/Header/Header.tsx | 20 +++++-------------- .../app/src/components/catalog/EntityPage.tsx | 6 +++--- 4 files changed, 18 insertions(+), 21 deletions(-) diff --git a/.changeset/poor-jars-sniff.md b/.changeset/poor-jars-sniff.md index 4c0c5d8133..b2be713a93 100644 --- a/.changeset/poor-jars-sniff.md +++ b/.changeset/poor-jars-sniff.md @@ -1,9 +1,15 @@ --- '@backstage/core-components': patch '@backstage/create-app': patch -'@backstage/plugin-api-docs': patch '@backstage/plugin-catalog': patch '@backstage/plugin-techdocs': patch --- Improve the responsiveness of the EntityPage UI. With this the Header component should scale with the screen size & wrapping should not cause overflowing/blocking of links. Additionally enforce the Pages using the Grid Layout to use it across all screen sizes & to wrap as intended. + +To benefit from the improved responsive layout, the `EntityPage` in existing Backstage applications should be updated to set the `xs` column size on each grid item in the page, as this does not default. For example: + +```diff +- ++ +``` diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx index 137b359afd..4e2e32cc86 100644 --- a/packages/app/src/components/catalog/EntityPage.tsx +++ b/packages/app/src/components/catalog/EntityPage.tsx @@ -136,8 +136,9 @@ const EntityLayoutWrapper = (props: { children?: ReactNode }) => { }; /** - * TODO: For the MUI Grid to work there have to be "xs" set on every GridItem, - * such that the seperation of space is clear from the smallest screen size upwards + * NOTE: This page is designed to work on small screens such as mobile devices. + * This is based on Material UI Grid. If breakpoints are used, each grid item must set the `xs` prop to a column size or to `true`, + * since this does not default. If no breakpoints are used, the items will equitably share the asvailable space. * https://material-ui.com/components/grid/#basic-grid. */ diff --git a/packages/core-components/src/layout/Header/Header.tsx b/packages/core-components/src/layout/Header/Header.tsx index 1af1e2cc2f..227c6d1a60 100644 --- a/packages/core-components/src/layout/Header/Header.tsx +++ b/packages/core-components/src/layout/Header/Header.tsx @@ -16,7 +16,7 @@ import { useApi, configApiRef } from '@backstage/core-plugin-api'; import { BackstageTheme } from '@backstage/theme'; -import { makeStyles, Tooltip, Typography, Grid } from '@material-ui/core'; +import { Box, Grid, makeStyles, Tooltip, Typography } from '@material-ui/core'; import React, { CSSProperties, PropsWithChildren, ReactNode } from 'react'; import { Helmet } from 'react-helmet'; import { Link } from '../../components/Link'; @@ -44,15 +44,10 @@ const useStyles = makeStyles(theme => ({ }, leftItemsBox: { maxWidth: '100%', - flex: '1 1 auto', + flexGrow: 1, marginBottom: theme.spacing(1), }, rightItemsBox: { - flex: '0 1 auto', - display: 'flex', - flexDirection: 'row', - flexWrap: 'wrap', - alignItems: 'center', width: 'auto', }, title: { @@ -202,7 +197,7 @@ export const Header = ({ <>
-
+ -
- + + {children}
diff --git a/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx b/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx index 7ebbe53aa0..2074a2712c 100644 --- a/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx +++ b/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx @@ -158,12 +158,12 @@ const websiteEntityPage = ( ); /** - * TODO: For the MUI Grid to work there have to be "xs" set on every GridItem, - * such that the seperation of space is clear from the smallest screen size upwards + * NOTE: This page is designed to work on small screens such as mobile devices. + * This is based on Material UI Grid. If breakpoints are used, each grid item must set the `xs` prop to a column size or to `true`, + * since this does not default. If no breakpoints are used, the items will equitably share the asvailable space. * https://material-ui.com/components/grid/#basic-grid. */ - const defaultEntityPage = ( From c8dbc9aaa1294db252cccfacee93800d10e79e50 Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Tue, 20 Jul 2021 12:42:04 +0200 Subject: [PATCH 064/106] techdocs: add a helpful link to docs page for warning Signed-off-by: Himanshu Mishra --- packages/techdocs-common/src/stages/generate/techdocs.test.ts | 3 ++- packages/techdocs-common/src/stages/generate/techdocs.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/techdocs-common/src/stages/generate/techdocs.test.ts b/packages/techdocs-common/src/stages/generate/techdocs.test.ts index f8d5128adc..f3a91657e1 100644 --- a/packages/techdocs-common/src/stages/generate/techdocs.test.ts +++ b/packages/techdocs-common/src/stages/generate/techdocs.test.ts @@ -132,7 +132,8 @@ describe('readGeneratorConfig', () => { runIn: 'local', }); expect(logger.warn).toHaveBeenCalledWith( - `The 'techdocs.generators.techdocs' configuration key is deprecated and will be removed in the future. Please use 'techdocs.generator' instead.`, + `The 'techdocs.generators.techdocs' configuration key is deprecated and will be removed in the future. Please use 'techdocs.generator' instead. ` + + `See here https://backstage.io/docs/features/techdocs/configuration`, ); }); }); diff --git a/packages/techdocs-common/src/stages/generate/techdocs.ts b/packages/techdocs-common/src/stages/generate/techdocs.ts index 6847a74300..9d6bf38c54 100644 --- a/packages/techdocs-common/src/stages/generate/techdocs.ts +++ b/packages/techdocs-common/src/stages/generate/techdocs.ts @@ -168,7 +168,8 @@ export function readGeneratorConfig( if (legacyGeneratorType) { logger.warn( - `The 'techdocs.generators.techdocs' configuration key is deprecated and will be removed in the future. Please use 'techdocs.generator' instead.`, + `The 'techdocs.generators.techdocs' configuration key is deprecated and will be removed in the future. Please use 'techdocs.generator' instead. ` + + `See here https://backstage.io/docs/features/techdocs/configuration`, ); } From 250984333e8356db2daaca56424390a4a804f123 Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Tue, 20 Jul 2021 12:44:24 +0200 Subject: [PATCH 065/106] techdocs: add changeset Signed-off-by: Himanshu Mishra --- .changeset/techdocs-proud-apples-wonder.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/techdocs-proud-apples-wonder.md diff --git a/.changeset/techdocs-proud-apples-wonder.md b/.changeset/techdocs-proud-apples-wonder.md new file mode 100644 index 0000000000..901ee26472 --- /dev/null +++ b/.changeset/techdocs-proud-apples-wonder.md @@ -0,0 +1,5 @@ +--- +'@backstage/techdocs-common': patch +--- + +Add link to https://backstage.io/docs/features/techdocs/configuration in the log warning message about updating techdocs.generate key. From 1b056f8ee2d11873364411f53837c158a1b14137 Mon Sep 17 00:00:00 2001 From: Francesco Corti Date: Tue, 20 Jul 2021 12:10:14 +0200 Subject: [PATCH 066/106] Updated roadmap. Signed-off-by: blam --- docs/overview/roadmap.md | 152 ++++++++++++++++++--------------------- 1 file changed, 68 insertions(+), 84 deletions(-) diff --git a/docs/overview/roadmap.md b/docs/overview/roadmap.md index 0013c99c6b..dbd60be6af 100644 --- a/docs/overview/roadmap.md +++ b/docs/overview/roadmap.md @@ -1,116 +1,102 @@ + --- id: roadmap -title: Project roadmap -description: Roadmap of Backstage Project +title: Roadmap +description: Roadmap of Backstage --- -## Current status +## The Backstage Roadmap -> Backstage is currently under rapid development. This means that you can expect -> APIs and features to evolve. It is also recommended that teams who adopt -> Backstage today [upgrade their installation](../cli/commands.md#versionsbump) -> as new [releases](https://github.com/backstage/backstage/releases) become -> available, as Backwards compatibility is not yet guaranteed. +Backstage is currently under rapid development. This page details the project’s public roadmap, the result of ongoing collaboration between the core maintainers and the broader Backstage community. Treat the roadmap as an ever-evolving guide to keep us aligned as a community on: -## Phases + - Upcoming enhancements and benefits, + - Planning contributions and support, + - Planning the project’s adoption, + - Understanding what things are coming soon, + - Avoiding duplication of work -We have divided the project into three high-level _phases_: +### How to influence the roadmap -- 🐣 **Phase 1:** Extensible frontend platform (Done ✅) - You will be able to - easily create a single consistent UI layer for your internal infrastructure - and tools. A set of reusable - [UX patterns and components](https://backstage.io/storybook) help ensure a - consistent experience between tools. +As we evolve Backstage, we want you to contribute actively in the journey to define the most effective developer experience in the world. -- 🐢 **Phase 2:** Software Catalog - ([alpha released](https://backstage.io/blog/2020/06/22/backstage-service-catalog-alpha)) - - With a single catalog, Backstage makes it easy for a team to manage ten - services — and makes it possible for your company to manage thousands of them. +A roadmap is only useful if it captures real needs. If you have success stories, feedback, or ideas, we want to hear from you! If you plan to work (or are already working) on a new or existing feature, please let us know, so that we can update the roadmap accordingly. We are also happy to share knowledge and context that will help your feature land successfully. -- 🐇 **Phase 3:** Ecosystem (ongoing, see - [Plugin Marketplace](https://backstage.io/plugins)) - Everyone's - infrastructure stack is different. By fostering a vibrant community of - contributors we hope to provide an ecosystem of Open Source - plugins/integrations that allows you to pick the tools that match your stack. +You can also head over to the [CONTRIBUTING](https://github.com/backstage/backstage/blob/master/CONTRIBUTING.md) guidelines to get started. -## Detailed roadmap +If you have specific questions about the roadmap, please create an [issue](https://github.com/backstage/backstage/issues/new/choose), ping us on [Discord](https://discord.gg/awD6SxgQ), or [book time](http://calendly.com/spotify-backstage) with the Spotify team. -If you have questions about the roadmap or want to provide feedback, we would -love to hear from you! Please create an -[Issue](https://github.com/backstage/backstage/issues/new/choose), ping us on -[Discord](https://discord.gg/EBHEGzX) or reach out directly at -[backstage-interest@spotify.com](mailto:backstage-interest@spotify.com). +### How to read the roadmap -Want to help out? Awesome ❤️ Head over to -[CONTRIBUTING](https://github.com/backstage/backstage/blob/master/CONTRIBUTING.md) -guidelines to get started. +The Backstage roadmap lays out both [“what’s next”](#What’s-next) and [“future work”](#Future-work). With "next" we mean features planned for release within the ongoing quarter starting in July until September 2021 included. With "future" we mean features in the radar, but not yet scheduled. -### Ongoing work 🚧 +The long-term roadmap (12 - 36 months) is not detailed in the public roadmap. Third-party contributions are also not currently included in the roadmap. Let us know about any ongoing developments and we’re happy to include it here as well. -- **[Platform stabilization](https://github.com/backstage/backstage/milestone/19)** - - Stabilize the core of Backstage, including its core features, so that the - platform can be depended on for production use. After this, plugins will - require little-to-no maintenance. +### Roadmap evolution -- **[Kubernetes plugin for service owners](https://github.com/backstage/backstage/issues/2857)** - - Improve native support for Kubernetes, making it easier for service owners to - see and manage their services running in K8s, regardless if that's locally, in - AWS, GCS, Azure, or elsewhere. +Will this roadmap change? Obviously! -- **[Search platform](../features/search/README.md)** - Evolve the basic search - functionality currently available into a platform that **a)** enables search - across the software catalog, TechDocs, and any other information exposed by - plugins, and **b)** supports a variety of search engine technologies. +Roadmaps are always evolving and ours is no different; you can expect to see this updated roughly every month. -- **[Software Templates V2](https://github.com/backstage/backstage/issues/2771)** - - Expand the templates to make the steps more composable by adding the ability - to add more steps for custom logic, including webhooks and using authorization - from integrations. +## What’s next -### Future work 🔮 +The feature set below is planned for the ongoing quarter, and grouped by theme. The list order doesn’t necessarily reflect priority, and the development/release cycle will vary based on maintainer schedules. -- **Golden Path for Plugin Development** - Create an easy, standardized way for - developers to build plugins that will encourage contributions and lead to a - richer ecosystem for everyone. +### Backstage Core -- **[GraphQL API](https://github.com/backstage/backstage/milestone/13)** - A - GraphQL API will open up the rich metadata provided by Backstage in a single - query. Plugins can easily query this API as well as extend the model where - needed. +The following features are planned for release: -- **Inter-Plugin Communication** - **[Under consideration]** Establish more - clearly defined patterns for plugins to communicate. +* **Composable homepage:** We’re seeing lots of interest from the community in reusable components to build a homepage experience where users can easily surface what they might find useful to start their tasks. Check out the [milestone](https://github.com/backstage/backstage/milestone/34) for further details. +* **Improved responsiveness:** Check out the [RFC here](https://github.com/backstage/backstage/issues/6318) for further details on how to improve the responsiveness for Backstage's UI. -- **Improved Access Control** - **[Under consideration]** Provide finer grained - access controls and management for better control of the platform user - experience. +### Software Templates -### Plugins +The following features are planned for release:: -Building and maintaining [plugins](https://backstage.io/plugins) is the work of -the entire Backstage community. +* **Re-creation/resubmission in case of failure:** Speed up productivity by allowing developers to relaunch a project after a failure or any unexpected problem. In the current version, this task requires retyping and a full re-creation from scratch. +* **Performance and usability improvements for contributors:** Reach a relevant improvement in templating's performance through the replacement of [handlebars](https://handlebarsjs.com/). Other replacements will be considered as part of this task (possibly [cookiecutter](https://cookiecutter.readthedocs.io/)) for easier software template creation, allowing more contributors to reach their goals without having to learn new tooling. +* **Improved extensibility through inclusion:** Make software templates more maintainable and extensible by adding `$include` support for parameters. +* **Authenticated job creation:** Created jobs will be able to run with an authenticated user with all actions tracked for future consumption and evidence. Track users creating jobs and make “jobs created by me” reporting available. -A list of plugins that are in development is -[available here](https://github.com/backstage/backstage/issues?q=is%3Aissue+is%3Aopen+label%3Aplugin+sort%3Areactions-%2B1-desc). -We strongly recommend to upvote 👍 plugins you are interested in. This helps us -and the community prioritize what plugins to build. +### Software Catalog -Are you missing a plugin for your favorite tool? Please -[suggest a new one](https://github.com/backstage/backstage/issues/new?labels=plugin&template=plugin_template.md&title=%5BPlugin%5D+THE+PLUGIN+NAME). -Chances are that someone will jump in and help build it. +The following features are planned for release: -### Community Initiatives 🧑‍🤝‍🧑 +* **Request For Comments (RFC) for composability improvements (routing):** Enable plugins to be auto-added and make plugin installation and upgrades easier for all Backstage users. This includes information card layouts, entity pages containing content and hooking the external header, considering the support of a separate deployment, and configuration for plugins. +* **Removing duplicated entities in catalog:** As any adopter knows, a software catalog can contain thousands or more entities and it is very important to avoid duplications in naming to prevent failures. With this development task, two entities with the same name won't be allowed as described [here](https://github.com/backstage/backstage/issues/4760). +* **Connecting identity to ownership to prepare for role-based access control ([RBAC](https://en.wikipedia.org/wiki/Role-based_access_control)):** This is a first step to supporting RBAC for the software catalog (see the [future work section](#Future-work) for further details). Provide each entity within the software catalog with a recognized owner. +* **Catalog performance improvements through improved caching:** Fix the performance gaps in the catalog processor, which currently doesn’t have a strong caching mechanism. The current version often requires fetching a relevant amount of data, especially at scale. -- [**Backstage Community Sessions**](https://github.com/backstage/community#meetups) - - A monthly meetup for the community to come together to share and learn about - the latest happenings in Backstage. +### Search -- **Backstage Hackathons** - (Coming soon) Open to everyone in our Backstage - community, a celebration of you, the project and building awesome things - together +The following features are planned for release: -### Completed milestones ✅ +* ElasticSearch integration: Add ElasticSearch to the Search Platform as the underlying search engine. Check out the [milestone here](https://github.com/backstage/backstage/milestone/27) for further details. +### TechDocs + +The following features are planned for release: + +* **TechDocs beta release:** Fix remaining bugs to get TechDocs to Beta. Check out the [milestone here](https://github.com/backstage/backstage/milestone/29) for further details. + +## Future work + +The following feature list doesn’t represent a commitment to develop and the list order doesn’t reflect any priority or importance. But these features are on the maintainers’ radar, with clear interest expressed by the community. + +* **Improved UX design:** Provide a better Backstage user experience through visual guidelines and templates, especially navigation across plug-ins and portal functionalities. +* **Catalog composability (routing):** Follow up development after the RFC planned for the ongoing quarter (see [what’s next](#What’s-next) for further details). +* **Catalog-import improvements:** Provide a faster (scalability) and better (more features like move/rename) way to import entities into the Software Catalog. Importing items in the Software Catalog is crucial for creating a Backstage proof-of-concept or testing/planning for broader organizational adoption. This enhancement better supports getting developers to use Backstage with less effort and customization. +* **Catalog improvements:** Add pagination and sourcing to Software Catalog. +* **[GraphQL](https://graphql.org/) support:** Introduce the ability to query Backstage backend services with a standard query language for APIs. +* **Software templates performance improvements through decoupling a separate worker:** Improve performance through decoupling resource-consuming services and making them asynchronous. In the current version, project auto-creation through the Software Templating system can consume a lot of resources and bottleneck many concurrent projects created simultaneously. +* **API discovery and documentation:** Add better support for the [gRPC](https://grpc.io/). +* **Adding TechDocs search to the Search Platform:** Having this capability in place will provide a better and new major version of the Search Platform (v3.0). You can refer to the [milestone here](https://github.com/backstage/backstage/milestone/28) for further details. +* **TechDocs GA release:** Work toward enhancements necessary to get TechDocs to general availability. Check out the [milestone here](https://github.com/backstage/backstage/milestone/30) for further details. + +## Completed milestones + +Read more about the completed (and released) features for reference. + +- [[Search] Out-of-the-Box Implementation (Alpha)](https://github.com/backstage/backstage/milestone/26) - [Deploy a product demo at `demo.backstage.io`](https://demo.backstage.io) - [Kubernetes plugin - v1](https://github.com/backstage/backstage/tree/master/plugins/kubernetes) - [Helm charts](https://github.com/backstage/backstage/tree/master/contrib/chart/backstage) @@ -128,6 +114,4 @@ Chances are that someone will jump in and help build it. - [Service API documentation](https://github.com/backstage/backstage/pull/1737) - Backstage Software Catalog can read from: GitHub, GitLab, [Bitbucket](https://github.com/backstage/backstage/pull/1938) -- Support auth providers: Google, Okta, GitHub, GitLab, - [auth0](https://github.com/backstage/backstage/pull/1611), - [AWS](https://github.com/backstage/backstage/pull/1990) +- Support auth providers: Google, Okta, GitHub, GitLab, [auth0](https://github.com/backstage/backstage/pull/1611), [AWS](https://github.com/backstage/backstage/pull/1990) From b17407c9d1a9e0dcd316776db67c3522bd1973f3 Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 20 Jul 2021 14:55:20 +0200 Subject: [PATCH 067/106] chore: run prettier and fix PR Signed-off-by: blam --- .github/styles/vocab.txt | 1 + docs/overview/roadmap.md | 185 ++++++++++++++++++++++++++++----------- 2 files changed, 136 insertions(+), 50 deletions(-) diff --git a/.github/styles/vocab.txt b/.github/styles/vocab.txt index e6af881f1e..186207041e 100644 --- a/.github/styles/vocab.txt +++ b/.github/styles/vocab.txt @@ -212,6 +212,7 @@ repos rerender Reusability reusability +roadmaps rollbar Rollbar Rollup diff --git a/docs/overview/roadmap.md b/docs/overview/roadmap.md index dbd60be6af..e1ff8c13bc 100644 --- a/docs/overview/roadmap.md +++ b/docs/overview/roadmap.md @@ -1,4 +1,3 @@ - --- id: roadmap title: Roadmap @@ -7,94 +6,178 @@ description: Roadmap of Backstage ## The Backstage Roadmap -Backstage is currently under rapid development. This page details the project’s public roadmap, the result of ongoing collaboration between the core maintainers and the broader Backstage community. Treat the roadmap as an ever-evolving guide to keep us aligned as a community on: +Backstage is currently under rapid development. This page details the project’s +public roadmap, the result of ongoing collaboration between the core maintainers +and the broader Backstage community. Treat the roadmap as an ever-evolving guide +to keep us aligned as a community on: - - Upcoming enhancements and benefits, - - Planning contributions and support, - - Planning the project’s adoption, - - Understanding what things are coming soon, - - Avoiding duplication of work +- Upcoming enhancements and benefits, +- Planning contributions and support, +- Planning the project’s adoption, +- Understanding what things are coming soon, +- Avoiding duplication of work ### How to influence the roadmap -As we evolve Backstage, we want you to contribute actively in the journey to define the most effective developer experience in the world. +As we evolve Backstage, we want you to contribute actively in the journey to +define the most effective developer experience in the world. -A roadmap is only useful if it captures real needs. If you have success stories, feedback, or ideas, we want to hear from you! If you plan to work (or are already working) on a new or existing feature, please let us know, so that we can update the roadmap accordingly. We are also happy to share knowledge and context that will help your feature land successfully. +A roadmap is only useful if it captures real needs. If you have success stories, +feedback, or ideas, we want to hear from you! If you plan to work (or are +already working) on a new or existing feature, please let us know, so that we +can update the roadmap accordingly. We are also happy to share knowledge and +context that will help your feature land successfully. -You can also head over to the [CONTRIBUTING](https://github.com/backstage/backstage/blob/master/CONTRIBUTING.md) guidelines to get started. +You can also head over to the +[CONTRIBUTING](https://github.com/backstage/backstage/blob/master/CONTRIBUTING.md) +guidelines to get started. -If you have specific questions about the roadmap, please create an [issue](https://github.com/backstage/backstage/issues/new/choose), ping us on [Discord](https://discord.gg/awD6SxgQ), or [book time](http://calendly.com/spotify-backstage) with the Spotify team. +If you have specific questions about the roadmap, please create an +[issue](https://github.com/backstage/backstage/issues/new/choose), ping us on +[Discord](https://discord.gg/awD6SxgQ), or +[book time](http://calendly.com/spotify-backstage) with the Spotify team. -### How to read the roadmap +### How to read the roadmap -The Backstage roadmap lays out both [“what’s next”](#What’s-next) and [“future work”](#Future-work). With "next" we mean features planned for release within the ongoing quarter starting in July until September 2021 included. With "future" we mean features in the radar, but not yet scheduled. +The Backstage roadmap lays out both [“what’s next”](#What’s-next) and +[“future work”](#Future-work). With "next" we mean features planned for release +within the ongoing quarter starting in July until September 2021 included. With +"future" we mean features in the radar, but not yet scheduled. -The long-term roadmap (12 - 36 months) is not detailed in the public roadmap. Third-party contributions are also not currently included in the roadmap. Let us know about any ongoing developments and we’re happy to include it here as well. +The long-term roadmap (12 - 36 months) is not detailed in the public roadmap. +Third-party contributions are also not currently included in the roadmap. Let us +know about any ongoing developments and we’re happy to include it here as well. ### Roadmap evolution Will this roadmap change? Obviously! -Roadmaps are always evolving and ours is no different; you can expect to see this updated roughly every month. +Roadmap are always evolving and ours is no different; you can expect to see this +updated roughly every month. ## What’s next -The feature set below is planned for the ongoing quarter, and grouped by theme. The list order doesn’t necessarily reflect priority, and the development/release cycle will vary based on maintainer schedules. +The feature set below is planned for the ongoing quarter, and grouped by theme. +The list order doesn’t necessarily reflect priority, and the development/release +cycle will vary based on maintainer schedules. -### Backstage Core +### Backstage Core -The following features are planned for release: +The following features are planned for release: -* **Composable homepage:** We’re seeing lots of interest from the community in reusable components to build a homepage experience where users can easily surface what they might find useful to start their tasks. Check out the [milestone](https://github.com/backstage/backstage/milestone/34) for further details. -* **Improved responsiveness:** Check out the [RFC here](https://github.com/backstage/backstage/issues/6318) for further details on how to improve the responsiveness for Backstage's UI. +- **Composable homepage:** We’re seeing lots of interest from the community in + reusable components to build a homepage experience where users can easily + surface what they might find useful to start their tasks. Check out the + [milestone](https://github.com/backstage/backstage/milestone/34) for further + details. +- **Improved responsiveness:** Check out the + [RFC here](https://github.com/backstage/backstage/issues/6318) for further + details on how to improve the responsiveness for Backstage's UI. ### Software Templates -The following features are planned for release:: +The following features are planned for release:: -* **Re-creation/resubmission in case of failure:** Speed up productivity by allowing developers to relaunch a project after a failure or any unexpected problem. In the current version, this task requires retyping and a full re-creation from scratch. -* **Performance and usability improvements for contributors:** Reach a relevant improvement in templating's performance through the replacement of [handlebars](https://handlebarsjs.com/). Other replacements will be considered as part of this task (possibly [cookiecutter](https://cookiecutter.readthedocs.io/)) for easier software template creation, allowing more contributors to reach their goals without having to learn new tooling. -* **Improved extensibility through inclusion:** Make software templates more maintainable and extensible by adding `$include` support for parameters. -* **Authenticated job creation:** Created jobs will be able to run with an authenticated user with all actions tracked for future consumption and evidence. Track users creating jobs and make “jobs created by me” reporting available. +- **Re-creation/resubmission in case of failure:** Speed up productivity by + allowing developers to relaunch a project after a failure or any unexpected + problem. In the current version, this task requires retyping and a full + re-creation from scratch. +- **Performance and usability improvements for contributors:** Reach a relevant + improvement in templating's performance through the replacement of + [handlebars](https://handlebarsjs.com/). Other replacements will be considered + as part of this task (possibly + [cookiecutter](https://cookiecutter.readthedocs.io/)) for easier software + template creation, allowing more contributors to reach their goals without + having to learn new tooling. +- **Improved extensibility through inclusion:** Make software templates more + maintainable and extensible by adding `$include` support for parameters. +- **Authenticated job creation:** Created jobs will be able to run with an + authenticated user with all actions tracked for future consumption and + evidence. Track users creating jobs and make “jobs created by me” reporting + available. -### Software Catalog +### Software Catalog -The following features are planned for release: +The following features are planned for release: -* **Request For Comments (RFC) for composability improvements (routing):** Enable plugins to be auto-added and make plugin installation and upgrades easier for all Backstage users. This includes information card layouts, entity pages containing content and hooking the external header, considering the support of a separate deployment, and configuration for plugins. -* **Removing duplicated entities in catalog:** As any adopter knows, a software catalog can contain thousands or more entities and it is very important to avoid duplications in naming to prevent failures. With this development task, two entities with the same name won't be allowed as described [here](https://github.com/backstage/backstage/issues/4760). -* **Connecting identity to ownership to prepare for role-based access control ([RBAC](https://en.wikipedia.org/wiki/Role-based_access_control)):** This is a first step to supporting RBAC for the software catalog (see the [future work section](#Future-work) for further details). Provide each entity within the software catalog with a recognized owner. -* **Catalog performance improvements through improved caching:** Fix the performance gaps in the catalog processor, which currently doesn’t have a strong caching mechanism. The current version often requires fetching a relevant amount of data, especially at scale. +- **Request For Comments (RFC) for composability improvements (routing):** + Enable plugins to be auto-added and make plugin installation and upgrades + easier for all Backstage users. This includes information card layouts, entity + pages containing content and hooking the external header, considering the + support of a separate deployment, and configuration for plugins. +- **Removing duplicated entities in catalog:** As any adopter knows, a software + catalog can contain thousands or more entities and it is very important to + avoid duplications in naming to prevent failures. With this development task, + two entities with the same name won't be allowed as described + [here](https://github.com/backstage/backstage/issues/4760). +- **Connecting identity to ownership to prepare for role-based access control + ([RBAC](https://en.wikipedia.org/wiki/Role-based_access_control)):** This is a + first step to supporting RBAC for the software catalog (see the + [future work section](#Future-work) for further details). Provide each entity + within the software catalog with a recognized owner. +- **Catalog performance improvements through improved caching:** Fix the + performance gaps in the catalog processor, which currently doesn’t have a + strong caching mechanism. The current version often requires fetching a + relevant amount of data, especially at scale. -### Search +### Search -The following features are planned for release: +The following features are planned for release: -* ElasticSearch integration: Add ElasticSearch to the Search Platform as the underlying search engine. Check out the [milestone here](https://github.com/backstage/backstage/milestone/27) for further details. +- ElasticSearch integration: Add ElasticSearch to the Search Platform as the + underlying search engine. Check out the + [milestone here](https://github.com/backstage/backstage/milestone/27) for + further details. -### TechDocs +### TechDocs -The following features are planned for release: +The following features are planned for release: -* **TechDocs beta release:** Fix remaining bugs to get TechDocs to Beta. Check out the [milestone here](https://github.com/backstage/backstage/milestone/29) for further details. +- **TechDocs beta release:** Fix remaining bugs to get TechDocs to Beta. Check + out the [milestone here](https://github.com/backstage/backstage/milestone/29) + for further details. -## Future work +## Future work -The following feature list doesn’t represent a commitment to develop and the list order doesn’t reflect any priority or importance. But these features are on the maintainers’ radar, with clear interest expressed by the community. +The following feature list doesn’t represent a commitment to develop and the +list order doesn’t reflect any priority or importance. But these features are on +the maintainers’ radar, with clear interest expressed by the community. -* **Improved UX design:** Provide a better Backstage user experience through visual guidelines and templates, especially navigation across plug-ins and portal functionalities. -* **Catalog composability (routing):** Follow up development after the RFC planned for the ongoing quarter (see [what’s next](#What’s-next) for further details). -* **Catalog-import improvements:** Provide a faster (scalability) and better (more features like move/rename) way to import entities into the Software Catalog. Importing items in the Software Catalog is crucial for creating a Backstage proof-of-concept or testing/planning for broader organizational adoption. This enhancement better supports getting developers to use Backstage with less effort and customization. -* **Catalog improvements:** Add pagination and sourcing to Software Catalog. -* **[GraphQL](https://graphql.org/) support:** Introduce the ability to query Backstage backend services with a standard query language for APIs. -* **Software templates performance improvements through decoupling a separate worker:** Improve performance through decoupling resource-consuming services and making them asynchronous. In the current version, project auto-creation through the Software Templating system can consume a lot of resources and bottleneck many concurrent projects created simultaneously. -* **API discovery and documentation:** Add better support for the [gRPC](https://grpc.io/). -* **Adding TechDocs search to the Search Platform:** Having this capability in place will provide a better and new major version of the Search Platform (v3.0). You can refer to the [milestone here](https://github.com/backstage/backstage/milestone/28) for further details. -* **TechDocs GA release:** Work toward enhancements necessary to get TechDocs to general availability. Check out the [milestone here](https://github.com/backstage/backstage/milestone/30) for further details. +- **Improved UX design:** Provide a better Backstage user experience through + visual guidelines and templates, especially navigation across plug-ins and + portal functionalities. +- **Catalog composability (routing):** Follow up development after the RFC + planned for the ongoing quarter (see [what’s next](#What’s-next) for further + details). +- **Catalog-import improvements:** Provide a faster (scalability) and better + (more features like move/rename) way to import entities into the Software + Catalog. Importing items in the Software Catalog is crucial for creating a + Backstage proof-of-concept or testing/planning for broader organizational + adoption. This enhancement better supports getting developers to use Backstage + with less effort and customization. +- **Catalog improvements:** Add pagination and sourcing to Software Catalog. +- **[GraphQL](https://graphql.org/) support:** Introduce the ability to query + Backstage backend services with a standard query language for APIs. +- **Software templates performance improvements through decoupling a separate + worker:** Improve performance through decoupling resource-consuming services + and making them asynchronous. In the current version, project auto-creation + through the Software Templating system can consume a lot of resources and + bottleneck many concurrent projects created simultaneously. +- **API discovery and documentation:** Add better support for the + [gRPC](https://grpc.io/). +- **Adding TechDocs search to the Search Platform:** Having this capability in + place will provide a better and new major version of the Search Platform + (v3.0). You can refer to the + [milestone here](https://github.com/backstage/backstage/milestone/28) for + further details. +- **TechDocs GA release:** Work toward enhancements necessary to get TechDocs to + general availability. Check out the + [milestone here](https://github.com/backstage/backstage/milestone/30) for + further details. ## Completed milestones -Read more about the completed (and released) features for reference. +Read more about the completed (and released) features for reference. - [[Search] Out-of-the-Box Implementation (Alpha)](https://github.com/backstage/backstage/milestone/26) - [Deploy a product demo at `demo.backstage.io`](https://demo.backstage.io) @@ -114,4 +197,6 @@ Read more about the completed (and released) features for reference. - [Service API documentation](https://github.com/backstage/backstage/pull/1737) - Backstage Software Catalog can read from: GitHub, GitLab, [Bitbucket](https://github.com/backstage/backstage/pull/1938) -- Support auth providers: Google, Okta, GitHub, GitLab, [auth0](https://github.com/backstage/backstage/pull/1611), [AWS](https://github.com/backstage/backstage/pull/1990) +- Support auth providers: Google, Okta, GitHub, GitLab, + [auth0](https://github.com/backstage/backstage/pull/1611), + [AWS](https://github.com/backstage/backstage/pull/1990) From 7e84b1bd9f4fab234ff15b513fdf3c26afcade10 Mon Sep 17 00:00:00 2001 From: Nicolas Arnold Date: Tue, 20 Jul 2021 14:20:06 +0100 Subject: [PATCH 068/106] Making changes backwards compatible by keeping the ClusterDetails object as an accepted type. Signed-off-by: Nicolas Arnold --- plugins/kubernetes-backend/api-report.md | 3 ++- plugins/kubernetes-backend/src/types/types.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/kubernetes-backend/api-report.md b/plugins/kubernetes-backend/api-report.md index 9f35fda79f..72d80050d8 100644 --- a/plugins/kubernetes-backend/api-report.md +++ b/plugins/kubernetes-backend/api-report.md @@ -123,7 +123,8 @@ export interface ObjectFetchParams { clusterDetails: | AWSClusterDetails | GKEClusterDetails - | ServiceAccountClusterDetails; + | ServiceAccountClusterDetails + | ClusterDetails; // (undocumented) customResources: CustomResource[]; // (undocumented) diff --git a/plugins/kubernetes-backend/src/types/types.ts b/plugins/kubernetes-backend/src/types/types.ts index c5f555d588..6409229968 100644 --- a/plugins/kubernetes-backend/src/types/types.ts +++ b/plugins/kubernetes-backend/src/types/types.ts @@ -30,7 +30,8 @@ export interface ObjectFetchParams { clusterDetails: | AWSClusterDetails | GKEClusterDetails - | ServiceAccountClusterDetails; + | ServiceAccountClusterDetails + | ClusterDetails; objectTypesToFetch: Set; labelSelector: string; customResources: CustomResource[]; From 2567c066d5b67b60cec69b57818beb08475ba7b4 Mon Sep 17 00:00:00 2001 From: Roy Jacobs Date: Tue, 20 Jul 2021 16:56:28 +0200 Subject: [PATCH 069/106] Export TokenIssuer so that it may be used by non-native auth providers Signed-off-by: Roy Jacobs --- .changeset/eighty-jokes-roll.md | 5 +++++ plugins/auth-backend/api-report.md | 13 ++++++++++++- plugins/auth-backend/src/index.ts | 1 + 3 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 .changeset/eighty-jokes-roll.md diff --git a/.changeset/eighty-jokes-roll.md b/.changeset/eighty-jokes-roll.md new file mode 100644 index 0000000000..d51cd3a6dc --- /dev/null +++ b/.changeset/eighty-jokes-roll.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend': patch +--- + +TokenIssuer is now exported so it may be used by auth providers that are not bundled with Backstage diff --git a/plugins/auth-backend/api-report.md b/plugins/auth-backend/api-report.md index 174d640031..1ada822262 100644 --- a/plugins/auth-backend/api-report.md +++ b/plugins/auth-backend/api-report.md @@ -346,6 +346,16 @@ export interface RouterOptions { providerFactories?: ProviderFactories; } +// Warning: (ae-missing-release-tag) "TokenIssuer" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export type TokenIssuer = { + issueToken(params: TokenParams): Promise; + listPublicKeys(): Promise<{ + keys: AnyJWK[]; + }>; +}; + // Warning: (ae-missing-release-tag) "verifyNonce" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -366,9 +376,10 @@ export type WebMessageResponse = // Warnings were encountered during analysis: // +// src/identity/types.d.ts:25:5 - (ae-forgotten-export) The symbol "TokenParams" needs to be exported by the entry point index.d.ts +// 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/google/provider.d.ts:36:5 - (ae-forgotten-export) The symbol "AuthHandler" needs to be exported by the entry point index.d.ts // src/providers/types.d.ts:105:5 - (ae-forgotten-export) The symbol "AuthProviderConfig" needs to be exported by the entry point index.d.ts -// src/providers/types.d.ts:108:5 - (ae-forgotten-export) The symbol "TokenIssuer" needs to be exported by the entry point index.d.ts // src/providers/types.d.ts:111:5 - (ae-forgotten-export) The symbol "ExperimentalIdentityResolver" needs to be exported by the entry point index.d.ts // src/providers/types.d.ts:128:8 - (tsdoc-missing-deprecation-message) The @deprecated block must include a deprecation message, e.g. describing the recommended alternative diff --git a/plugins/auth-backend/src/index.ts b/plugins/auth-backend/src/index.ts index d3ea83efd6..6b15b1fc81 100644 --- a/plugins/auth-backend/src/index.ts +++ b/plugins/auth-backend/src/index.ts @@ -16,6 +16,7 @@ export * from './service/router'; export { IdentityClient } from './identity'; +export type { TokenIssuer } from './identity'; export * from './providers'; // flow package provides 2 functions From 1b994b4b6ecae8865d165d7aba50ca1a869ac3a6 Mon Sep 17 00:00:00 2001 From: Francesco Corti Date: Tue, 20 Jul 2021 18:07:13 +0200 Subject: [PATCH 070/106] Roadmap review for breaking links. Signed-off-by: Francesco Corti --- docs/overview/roadmap.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/overview/roadmap.md b/docs/overview/roadmap.md index e1ff8c13bc..fa34ca3d9a 100644 --- a/docs/overview/roadmap.md +++ b/docs/overview/roadmap.md @@ -39,8 +39,8 @@ If you have specific questions about the roadmap, please create an ### How to read the roadmap -The Backstage roadmap lays out both [“what’s next”](#What’s-next) and -[“future work”](#Future-work). With "next" we mean features planned for release +The Backstage roadmap lays out both [“what’s next”](#whats-next) and +[“future work”](#future-work). With "next" we mean features planned for release within the ongoing quarter starting in July until September 2021 included. With "future" we mean features in the radar, but not yet scheduled. @@ -113,7 +113,7 @@ The following features are planned for release: - **Connecting identity to ownership to prepare for role-based access control ([RBAC](https://en.wikipedia.org/wiki/Role-based_access_control)):** This is a first step to supporting RBAC for the software catalog (see the - [future work section](#Future-work) for further details). Provide each entity + [future work section](#future-work) for further details). Provide each entity within the software catalog with a recognized owner. - **Catalog performance improvements through improved caching:** Fix the performance gaps in the catalog processor, which currently doesn’t have a @@ -147,7 +147,7 @@ the maintainers’ radar, with clear interest expressed by the community. visual guidelines and templates, especially navigation across plug-ins and portal functionalities. - **Catalog composability (routing):** Follow up development after the RFC - planned for the ongoing quarter (see [what’s next](#What’s-next) for further + planned for the ongoing quarter (see [what’s next](#whats-next) for further details). - **Catalog-import improvements:** Provide a faster (scalability) and better (more features like move/rename) way to import entities into the Software From 442e1a52b8977c5b458331c4e05e05c4852d80ca Mon Sep 17 00:00:00 2001 From: daftgopher Date: Tue, 20 Jul 2021 23:10:12 -0400 Subject: [PATCH 071/106] Minor text agreement correction Signed-off-by: daftgopher --- docs/plugins/structure-of-a-plugin.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/plugins/structure-of-a-plugin.md b/docs/plugins/structure-of-a-plugin.md index beffcdb313..147f916a0d 100644 --- a/docs/plugins/structure-of-a-plugin.md +++ b/docs/plugins/structure-of-a-plugin.md @@ -99,14 +99,14 @@ You may tweak these components, rename them and/or replace them completely. ## Connecting the plugin to the Backstage app -There are three things needed for a Backstage app to start making use of a +There are two things needed for a Backstage app to start making use of a plugin. 1. Add plugin as dependency in `app/package.json` 2. Import and use one or more plugin extensions, for example in `app/src/App.tsx`. -Luckily these three steps happen automatically when you create a plugin with the +Luckily both of these steps happen automatically when you create a plugin with the Backstage CLI. ## Talking to the outside world From 903f3323c29def19f25f4b9b13bb626fe5e2c177 Mon Sep 17 00:00:00 2001 From: Tim Hansen Date: Tue, 20 Jul 2021 21:18:31 -0600 Subject: [PATCH 072/106] Fix misleading catalog-import heading Signed-off-by: Tim Hansen --- .changeset/funny-moose-tie.md | 5 +++ .../src/components/ImportComponentPage.tsx | 34 +++++-------------- 2 files changed, 14 insertions(+), 25 deletions(-) create mode 100644 .changeset/funny-moose-tie.md diff --git a/.changeset/funny-moose-tie.md b/.changeset/funny-moose-tie.md new file mode 100644 index 0000000000..3d3ab5f632 --- /dev/null +++ b/.changeset/funny-moose-tie.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-import': patch +--- + +Fix heading that wrongly implied catalog-import supports entity discovery for multiple integrations. diff --git a/plugins/catalog-import/src/components/ImportComponentPage.tsx b/plugins/catalog-import/src/components/ImportComponentPage.tsx index 6bf5af24ef..f4ca144165 100644 --- a/plugins/catalog-import/src/components/ImportComponentPage.tsx +++ b/plugins/catalog-import/src/components/ImportComponentPage.tsx @@ -14,12 +14,12 @@ * limitations under the License. */ -import { Grid, Typography } from '@material-ui/core'; +import { Chip, Grid, Typography } from '@material-ui/core'; import React from 'react'; import { ImportStepper } from './ImportStepper'; import { StepperProviderOpts } from './ImportStepper/defaults'; -import { ConfigApi, configApiRef, useApi } from '@backstage/core-plugin-api'; +import { configApiRef, useApi } from '@backstage/core-plugin-api'; import { Content, ContentHeader, @@ -29,30 +29,12 @@ import { SupportButton, } from '@backstage/core-components'; -function repositories(configApi: ConfigApi): string[] { - const integrations = configApi.getConfig('integrations'); - const repos = []; - if (integrations.has('github')) { - repos.push('GitHub'); - } - if (integrations.has('bitbucket')) { - repos.push('Bitbucket'); - } - if (integrations.has('gitlab')) { - repos.push('GitLab'); - } - if (integrations.has('azure')) { - repos.push('Azure'); - } - return repos; -} - export const ImportComponentPage = (opts: StepperProviderOpts) => { const configApi = useApi(configApiRef); const appTitle = configApi.getOptional('app.title') || 'Backstage'; - const repos = repositories(configApi); - const repositoryString = repos.join(', ').replace(/, (\w*)$/, ' or $1'); + const integrations = configApi.getConfig('integrations'); + const hasGithubIntegration = integrations.has('github'); return ( @@ -76,7 +58,8 @@ export const ImportComponentPage = (opts: StepperProviderOpts) => { }} > - Enter the URL to your SCM repository to add it to {appTitle}. + Enter the URL to your source code repository to add it to{' '} + {appTitle}. Link to an existing entity file @@ -91,10 +74,11 @@ export const ImportComponentPage = (opts: StepperProviderOpts) => { The wizard analyzes the file, previews the entities, and adds them to the {appTitle} catalog. - {repos.length > 0 && ( + {hasGithubIntegration && ( <> - Link to a {repositoryString} repository + Link to a repository{' '} + Date: Wed, 21 Jul 2021 04:13:46 +0000 Subject: [PATCH 073/106] chore(deps-dev): bump nodemon from 2.0.7 to 2.0.12 Bumps [nodemon](https://github.com/remy/nodemon) from 2.0.7 to 2.0.12. - [Release notes](https://github.com/remy/nodemon/releases) - [Commits](https://github.com/remy/nodemon/compare/v2.0.7...v2.0.12) --- updated-dependencies: - dependency-name: nodemon dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index abb07ca464..03dc018a06 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18899,9 +18899,9 @@ node-releases@^1.1.52, node-releases@^1.1.61, node-releases@^1.1.71: integrity sha512-LLUo+PpH3dU6XizX3iVoubUNheF/owjXCZZ5yACDxNnPtgFuludV1ZL3ayK1kVep42Rmm0+R9/Y60NQbZ2bifw== nodemon@^2.0.2: - version "2.0.7" - resolved "https://registry.npmjs.org/nodemon/-/nodemon-2.0.7.tgz#6f030a0a0ebe3ea1ba2a38f71bf9bab4841ced32" - integrity sha512-XHzK69Awgnec9UzHr1kc8EomQh4sjTQ8oRf8TsGrSmHDx9/UmiGG9E/mM3BuTfNeFwdNBvrqQq/RHL0xIeyFOA== + version "2.0.12" + resolved "https://registry.npmjs.org/nodemon/-/nodemon-2.0.12.tgz#5dae4e162b617b91f1873b3bfea215dd71e144d5" + integrity sha512-egCTmNZdObdBxUBw6ZNwvZ/xzk24CKRs5K6d+5zbmrMr7rOpPmfPeF6OxM3DDpaRx331CQRFEktn+wrFFfBSOA== dependencies: chokidar "^3.2.2" debug "^3.2.6" From ab32efd88ab0617bd5885e3ac9b8c0446c207aff Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 21 Jul 2021 04:14:50 +0000 Subject: [PATCH 074/106] chore(deps): bump immer from 9.0.1 to 9.0.5 Bumps [immer](https://github.com/immerjs/immer) from 9.0.1 to 9.0.5. - [Release notes](https://github.com/immerjs/immer/releases) - [Commits](https://github.com/immerjs/immer/compare/v9.0.1...v9.0.5) --- updated-dependencies: - dependency-name: immer dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index abb07ca464..db8f48ca60 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14745,9 +14745,9 @@ immer@8.0.1: integrity sha512-aqXhGP7//Gui2+UrEtvxZxSquQVXTpZ7KDxfCcKAF3Vysvw0CViVaW9RZ1j1xlIYqaaaipBoqdqeibkc18PNvA== immer@^9.0.1: - version "9.0.1" - resolved "https://registry.npmjs.org/immer/-/immer-9.0.1.tgz#1116368e051f9a0fd188c5136b6efb74ed69c57f" - integrity sha512-7CCw1DSgr8kKYXTYOI1qMM/f5qxT5vIVMeGLDCDX8CSxsggr1Sjdoha4OhsP0AZ1UvWbyZlILHvLjaynuu02Mg== + version "9.0.5" + resolved "https://registry.npmjs.org/immer/-/immer-9.0.5.tgz#a7154f34fe7064f15f00554cc94c66cc0bf453ec" + integrity sha512-2WuIehr2y4lmYz9gaQzetPR2ECniCifk4ORaQbU3g5EalLt+0IVTosEPJ5BoYl/75ky2mivzdRzV8wWgQGOSYQ== immutable@>=3.8.2, immutable@^3.8.1, immutable@^3.8.2, immutable@^3.x.x: version "3.8.2" From 47029e79a05989703e4477f8caf80b447483f087 Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Wed, 21 Jul 2021 09:08:23 +0200 Subject: [PATCH 075/106] docs: run prettier Signed-off-by: Himanshu Mishra --- docs/plugins/structure-of-a-plugin.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/plugins/structure-of-a-plugin.md b/docs/plugins/structure-of-a-plugin.md index 147f916a0d..99a55745c5 100644 --- a/docs/plugins/structure-of-a-plugin.md +++ b/docs/plugins/structure-of-a-plugin.md @@ -99,15 +99,14 @@ You may tweak these components, rename them and/or replace them completely. ## Connecting the plugin to the Backstage app -There are two things needed for a Backstage app to start making use of a -plugin. +There are two things needed for a Backstage app to start making use of a plugin. 1. Add plugin as dependency in `app/package.json` 2. Import and use one or more plugin extensions, for example in `app/src/App.tsx`. -Luckily both of these steps happen automatically when you create a plugin with the -Backstage CLI. +Luckily both of these steps happen automatically when you create a plugin with +the Backstage CLI. ## Talking to the outside world From 11e0652f8641f23ebb87186e74ff6d96969d0c75 Mon Sep 17 00:00:00 2001 From: Erik Engervall Date: Wed, 21 Jul 2021 10:59:17 +0200 Subject: [PATCH 076/106] Introduce ability to add custom features Also, add more metadata to success callbacks Signed-off-by: Erik Engervall --- plugins/git-release-manager/dev/index.tsx | 76 +++++++++++++++---- .../src/GitReleaseManager.tsx | 35 ++++++--- .../CreateReleaseCandidate.tsx | 4 +- .../hooks/useCreateReleaseCandidate.ts | 16 +++- .../src/features/Features.tsx | 8 ++ .../src/features/Patch/Patch.tsx | 4 +- .../src/features/Patch/PatchBody.tsx | 4 +- .../src/features/Patch/hooks/usePatch.ts | 28 ++++--- .../src/features/PromoteRc/PromoteRc.tsx | 4 +- .../src/features/PromoteRc/PromoteRcBody.tsx | 4 +- .../features/PromoteRc/hooks/usePromoteRc.ts | 22 ++++-- .../git-release-manager/src/types/types.ts | 26 ++++--- 12 files changed, 166 insertions(+), 65 deletions(-) diff --git a/plugins/git-release-manager/dev/index.tsx b/plugins/git-release-manager/dev/index.tsx index 2a0170a61e..1876995bf1 100644 --- a/plugins/git-release-manager/dev/index.tsx +++ b/plugins/git-release-manager/dev/index.tsx @@ -16,7 +16,7 @@ import React from 'react'; import { createDevApp } from '@backstage/dev-utils'; -import { Box, Typography } from '@material-ui/core'; +import { Box, Button, Typography } from '@material-ui/core'; import { gitReleaseManagerPlugin, GitReleaseManagerPage } from '../src/plugin'; import { InfoCardPlus } from '../src/components/InfoCardPlus'; @@ -44,10 +44,16 @@ createDevApp() Dev notes + Configure plugin statically by passing props to the `GitHubReleaseManagerPage` component + + + Note that the static configuration points towards private + repositories and will thus not work for everyone. + Dev notes - Each feature can be omitted - Success callbacks can also be added + + Each feature can be individually omitted as well as have success + callback attached to them + { + onSuccess: args => { // eslint-disable-next-line no-console console.log( - 'Custom success callback for Create RC', - comparisonUrl, - createdTag, - gitReleaseName, - gitReleaseUrl, - previousTag, + 'Custom success callback for Create RC with the following args', ); + console.log(JSON.stringify(args, null, 2)); // eslint-disable-line no-console }, }, promoteRc: { @@ -108,4 +106,50 @@ createDevApp() ), }) + .addPage({ + title: 'Custom', + path: '/custom', + element: ( + + + Dev notes + + The custom feature's return value can either be a React Element or + an array of React Elements. + + + + { + return ( + + I'm a custom feature + + + + ); + }, + }, + }} + /> + + ), + }) .render(); diff --git a/plugins/git-release-manager/src/GitReleaseManager.tsx b/plugins/git-release-manager/src/GitReleaseManager.tsx index 44669f9bc7..bca19b9b13 100644 --- a/plugins/git-release-manager/src/GitReleaseManager.tsx +++ b/plugins/git-release-manager/src/GitReleaseManager.tsx @@ -18,12 +18,14 @@ import React from 'react'; import { useAsync } from 'react-use'; import { Alert } from '@material-ui/lab'; import { Box } from '@material-ui/core'; +import { useApi } from '@backstage/core-plugin-api'; +import { ContentHeader, Progress } from '@backstage/core-components'; import { ComponentConfig, - ComponentConfigCreateRc, - ComponentConfigPatch, - ComponentConfigPromoteRc, + CreateRcOnSuccessArgs, + PatchOnSuccessArgs, + PromoteRcOnSuccessArgs, } from './types/types'; import { Features } from './features/Features'; import { gitReleaseManagerApiRef } from './api/serviceApiRef'; @@ -33,18 +35,33 @@ import { ProjectContext, Project } from './contexts/ProjectContext'; import { RepoDetailsForm } from './features/RepoDetailsForm/RepoDetailsForm'; import { useQueryHandler } from './hooks/useQueryHandler'; import { UserContext } from './contexts/UserContext'; - -import { useApi } from '@backstage/core-plugin-api'; -import { ContentHeader, Progress } from '@backstage/core-components'; +import { + GetBranchResult, + GetLatestReleaseResult, + GetRepositoryResult, +} from './api/GitReleaseClient'; interface GitReleaseManagerProps { project?: Omit; features?: { info?: Pick, 'omit'>; stats?: Pick, 'omit'>; - createRc?: ComponentConfigCreateRc; - promoteRc?: ComponentConfigPromoteRc; - patch?: ComponentConfigPatch; + createRc?: ComponentConfig; + promoteRc?: ComponentConfig; + patch?: ComponentConfig; + custom?: { + factory: ({ + latestRelease, + project, + releaseBranch, + repository, + }: { + latestRelease: GetLatestReleaseResult['latestRelease'] | null; + project: Project; + releaseBranch: GetBranchResult['branch'] | null; + repository: GetRepositoryResult['repository']; + }) => React.ReactElement | React.ReactElement[]; + }; }; } diff --git a/plugins/git-release-manager/src/features/CreateReleaseCandidate/CreateReleaseCandidate.tsx b/plugins/git-release-manager/src/features/CreateReleaseCandidate/CreateReleaseCandidate.tsx index e99fd9267e..7f24241e66 100644 --- a/plugins/git-release-manager/src/features/CreateReleaseCandidate/CreateReleaseCandidate.tsx +++ b/plugins/git-release-manager/src/features/CreateReleaseCandidate/CreateReleaseCandidate.tsx @@ -31,7 +31,7 @@ import { GetLatestReleaseResult, GetRepositoryResult, } from '../../api/GitReleaseClient'; -import { ComponentConfigCreateRc } from '../../types/types'; +import { ComponentConfig, CreateRcOnSuccessArgs } from '../../types/types'; import { Differ } from '../../components/Differ'; import { getReleaseCandidateGitInfo } from '../../helpers/getReleaseCandidateGitInfo'; import { InfoCardPlus } from '../../components/InfoCardPlus'; @@ -45,7 +45,7 @@ interface CreateReleaseCandidateProps { defaultBranch: GetRepositoryResult['repository']['defaultBranch']; latestRelease: GetLatestReleaseResult['latestRelease']; releaseBranch: GetBranchResult['branch'] | null; - onSuccess?: ComponentConfigCreateRc['onSuccess']; + onSuccess?: ComponentConfig['onSuccess']; } const InfoCardPlusWrapper = ({ children }: { children: React.ReactNode }) => { diff --git a/plugins/git-release-manager/src/features/CreateReleaseCandidate/hooks/useCreateReleaseCandidate.ts b/plugins/git-release-manager/src/features/CreateReleaseCandidate/hooks/useCreateReleaseCandidate.ts index 0d6d4ebb4f..9a89d59c54 100644 --- a/plugins/git-release-manager/src/features/CreateReleaseCandidate/hooks/useCreateReleaseCandidate.ts +++ b/plugins/git-release-manager/src/features/CreateReleaseCandidate/hooks/useCreateReleaseCandidate.ts @@ -21,7 +21,11 @@ import { GetRepositoryResult, } from '../../../api/GitReleaseClient'; -import { CardHook, ComponentConfigCreateRc } from '../../../types/types'; +import { + CardHook, + ComponentConfig, + CreateRcOnSuccessArgs, +} from '../../../types/types'; import { getReleaseCandidateGitInfo } from '../../../helpers/getReleaseCandidateGitInfo'; import { gitReleaseManagerApiRef } from '../../../api/serviceApiRef'; import { GitReleaseManagerError } from '../../../errors/GitReleaseManagerError'; @@ -31,12 +35,12 @@ import { useResponseSteps } from '../../../hooks/useResponseSteps'; import { useUserContext } from '../../../contexts/UserContext'; import { useApi } from '@backstage/core-plugin-api'; -interface UseCreateReleaseCandidate { +export interface UseCreateReleaseCandidate { defaultBranch: GetRepositoryResult['repository']['defaultBranch']; latestRelease: GetLatestReleaseResult['latestRelease']; releaseCandidateGitInfo: ReturnType; project: Project; - onSuccess?: ComponentConfigCreateRc['onSuccess']; + onSuccess?: ComponentConfig['onSuccess']; } export function useCreateReleaseCandidate({ @@ -266,6 +270,12 @@ export function useCreateReleaseCandidate({ try { await onSuccess({ + input: { + defaultBranch, + latestRelease, + releaseCandidateGitInfo, + project, + }, comparisonUrl: getComparisonRes.value.htmlUrl, createdTag: createReleaseRes.value.tagName, gitReleaseName: createReleaseRes.value.name, diff --git a/plugins/git-release-manager/src/features/Features.tsx b/plugins/git-release-manager/src/features/Features.tsx index 4014f26f73..b8386c41cc 100644 --- a/plugins/git-release-manager/src/features/Features.tsx +++ b/plugins/git-release-manager/src/features/Features.tsx @@ -142,6 +142,14 @@ export function Features({ onSuccess={features?.patch?.onSuccess} /> )} + + {features?.custom?.factory && + features.custom.factory({ + latestRelease: gitBatchInfo.value.latestRelease, + project, + releaseBranch: gitBatchInfo.value.releaseBranch, + repository: gitBatchInfo.value.repository, + })} ); diff --git a/plugins/git-release-manager/src/features/Patch/Patch.tsx b/plugins/git-release-manager/src/features/Patch/Patch.tsx index 53fc901da7..c902ab0dc5 100644 --- a/plugins/git-release-manager/src/features/Patch/Patch.tsx +++ b/plugins/git-release-manager/src/features/Patch/Patch.tsx @@ -22,7 +22,7 @@ import { GetBranchResult, GetLatestReleaseResult, } from '../../api/GitReleaseClient'; -import { ComponentConfigPatch } from '../../types/types'; +import { ComponentConfig, PatchOnSuccessArgs } from '../../types/types'; import { getBumpedTag } from '../../helpers/getBumpedTag'; import { InfoCardPlus } from '../../components/InfoCardPlus'; import { NoLatestRelease } from '../../components/NoLatestRelease'; @@ -32,7 +32,7 @@ import { useProjectContext } from '../../contexts/ProjectContext'; interface PatchProps { latestRelease: GetLatestReleaseResult['latestRelease']; releaseBranch: GetBranchResult['branch'] | null; - onSuccess?: ComponentConfigPatch['onSuccess']; + onSuccess?: ComponentConfig['onSuccess']; } export const Patch = ({ diff --git a/plugins/git-release-manager/src/features/Patch/PatchBody.tsx b/plugins/git-release-manager/src/features/Patch/PatchBody.tsx index b4e705fa58..01f5bc9fed 100644 --- a/plugins/git-release-manager/src/features/Patch/PatchBody.tsx +++ b/plugins/git-release-manager/src/features/Patch/PatchBody.tsx @@ -38,7 +38,7 @@ import { GetLatestReleaseResult, } from '../../api/GitReleaseClient'; import { CalverTagParts } from '../../helpers/tagParts/getCalverTagParts'; -import { ComponentConfigPatch } from '../../types/types'; +import { ComponentConfig, PatchOnSuccessArgs } from '../../types/types'; import { Differ } from '../../components/Differ'; import { getPatchCommitSuffix } from './helpers/getPatchCommitSuffix'; import { gitReleaseManagerApiRef } from '../../api/serviceApiRef'; @@ -56,7 +56,7 @@ interface PatchBodyProps { bumpedTag: string; latestRelease: NonNullable; releaseBranch: GetBranchResult['branch']; - onSuccess?: ComponentConfigPatch['onSuccess']; + onSuccess?: ComponentConfig['onSuccess']; tagParts: NonNullable; } diff --git a/plugins/git-release-manager/src/features/Patch/hooks/usePatch.ts b/plugins/git-release-manager/src/features/Patch/hooks/usePatch.ts index 5d3f9829ad..a2cbb7aefb 100644 --- a/plugins/git-release-manager/src/features/Patch/hooks/usePatch.ts +++ b/plugins/git-release-manager/src/features/Patch/hooks/usePatch.ts @@ -22,7 +22,11 @@ import { } from '../../../api/GitReleaseClient'; import { CalverTagParts } from '../../../helpers/tagParts/getCalverTagParts'; -import { ComponentConfigPatch, CardHook } from '../../../types/types'; +import { + CardHook, + ComponentConfig, + PatchOnSuccessArgs, +} from '../../../types/types'; import { getPatchCommitSuffix } from '../helpers/getPatchCommitSuffix'; import { gitReleaseManagerApiRef } from '../../../api/serviceApiRef'; import { Project } from '../../../contexts/ProjectContext'; @@ -32,12 +36,12 @@ import { useResponseSteps } from '../../../hooks/useResponseSteps'; import { useUserContext } from '../../../contexts/UserContext'; import { useApi } from '@backstage/core-plugin-api'; -interface Patch { +export interface UsePatch { bumpedTag: string; latestRelease: NonNullable; project: Project; tagParts: NonNullable; - onSuccess?: ComponentConfigPatch['onSuccess']; + onSuccess?: ComponentConfig['onSuccess']; } // Inspiration: https://stackoverflow.com/questions/53859199/how-to-cherry-pick-through-githubs-api @@ -47,7 +51,7 @@ export function usePatch({ project, tagParts, onSuccess, -}: Patch): CardHook { +}: UsePatch): CardHook { const pluginApiClient = useApi(gitReleaseManagerApiRef); const { user } = useUserContext(); const { @@ -337,13 +341,19 @@ ${selectedPatchCommit.commit.message}`, try { await onSuccess?.({ - updatedReleaseUrl: updatedReleaseRes.value.htmlUrl, - updatedReleaseName: updatedReleaseRes.value.name, - previousTag: latestRelease.tagName, - patchedTag: updatedReleaseRes.value.tagName, - patchCommitUrl: releaseBranchRes.value.selectedPatchCommit.htmlUrl, + input: { + bumpedTag, + latestRelease, + project, + tagParts, + }, patchCommitMessage: releaseBranchRes.value.selectedPatchCommit.commit.message, + patchCommitUrl: releaseBranchRes.value.selectedPatchCommit.htmlUrl, + patchedTag: updatedReleaseRes.value.tagName, + previousTag: latestRelease.tagName, + updatedReleaseName: updatedReleaseRes.value.name, + updatedReleaseUrl: updatedReleaseRes.value.htmlUrl, }); } catch (error) { asyncCatcher(error); diff --git a/plugins/git-release-manager/src/features/PromoteRc/PromoteRc.tsx b/plugins/git-release-manager/src/features/PromoteRc/PromoteRc.tsx index 8d42119748..341ecbbce4 100644 --- a/plugins/git-release-manager/src/features/PromoteRc/PromoteRc.tsx +++ b/plugins/git-release-manager/src/features/PromoteRc/PromoteRc.tsx @@ -18,7 +18,7 @@ import React from 'react'; import { Alert, AlertTitle } from '@material-ui/lab'; import { Box, Typography } from '@material-ui/core'; -import { ComponentConfigPromoteRc } from '../../types/types'; +import { ComponentConfig, PromoteRcOnSuccessArgs } from '../../types/types'; import { GetLatestReleaseResult } from '../../api/GitReleaseClient'; import { InfoCardPlus } from '../../components/InfoCardPlus'; import { NoLatestRelease } from '../../components/NoLatestRelease'; @@ -27,7 +27,7 @@ import { TEST_IDS } from '../../test-helpers/test-ids'; interface PromoteRcProps { latestRelease: GetLatestReleaseResult['latestRelease']; - onSuccess?: ComponentConfigPromoteRc['onSuccess']; + onSuccess?: ComponentConfig['onSuccess']; } export const PromoteRc = ({ latestRelease, onSuccess }: PromoteRcProps) => { diff --git a/plugins/git-release-manager/src/features/PromoteRc/PromoteRcBody.tsx b/plugins/git-release-manager/src/features/PromoteRc/PromoteRcBody.tsx index d3ce5e4a70..3ca4518bbc 100644 --- a/plugins/git-release-manager/src/features/PromoteRc/PromoteRcBody.tsx +++ b/plugins/git-release-manager/src/features/PromoteRc/PromoteRcBody.tsx @@ -17,7 +17,7 @@ import React from 'react'; import { Button, Typography, Box } from '@material-ui/core'; -import { ComponentConfigPromoteRc } from '../../types/types'; +import { ComponentConfig, PromoteRcOnSuccessArgs } from '../../types/types'; import { Differ } from '../../components/Differ'; import { GetLatestReleaseResult } from '../../api/GitReleaseClient'; import { ResponseStepDialog } from '../../components/ResponseStepDialog/ResponseStepDialog'; @@ -26,7 +26,7 @@ import { usePromoteRc } from './hooks/usePromoteRc'; interface PromoteRcBodyProps { rcRelease: NonNullable; - onSuccess?: ComponentConfigPromoteRc['onSuccess']; + onSuccess?: ComponentConfig['onSuccess']; } export const PromoteRcBody = ({ rcRelease, onSuccess }: PromoteRcBodyProps) => { diff --git a/plugins/git-release-manager/src/features/PromoteRc/hooks/usePromoteRc.ts b/plugins/git-release-manager/src/features/PromoteRc/hooks/usePromoteRc.ts index 357ba1e0a6..f3a87d3c74 100644 --- a/plugins/git-release-manager/src/features/PromoteRc/hooks/usePromoteRc.ts +++ b/plugins/git-release-manager/src/features/PromoteRc/hooks/usePromoteRc.ts @@ -16,7 +16,11 @@ import { useState, useEffect } from 'react'; import { useAsync, useAsyncFn } from 'react-use'; -import { CardHook, ComponentConfigPromoteRc } from '../../../types/types'; +import { + CardHook, + ComponentConfig, + PromoteRcOnSuccessArgs, +} from '../../../types/types'; import { GetLatestReleaseResult } from '../../../api/GitReleaseClient'; import { gitReleaseManagerApiRef } from '../../../api/serviceApiRef'; @@ -27,17 +31,17 @@ import { useResponseSteps } from '../../../hooks/useResponseSteps'; import { useUserContext } from '../../../contexts/UserContext'; import { useApi } from '@backstage/core-plugin-api'; -interface PromoteRc { +export interface UsePromoteRc { rcRelease: NonNullable; releaseVersion: string; - onSuccess?: ComponentConfigPromoteRc['onSuccess']; + onSuccess?: ComponentConfig['onSuccess']; } export function usePromoteRc({ rcRelease, releaseVersion, onSuccess, -}: PromoteRc): CardHook { +}: UsePromoteRc): CardHook { const pluginApiClient = useApi(gitReleaseManagerApiRef); const { user } = useUserContext(); const { project } = useProjectContext(); @@ -170,12 +174,16 @@ export function usePromoteRc({ try { await onSuccess?.({ - gitReleaseUrl: promotedReleaseRes.value.htmlUrl, + input: { + rcRelease, + releaseVersion, + }, gitReleaseName: promotedReleaseRes.value.name, - previousTagUrl: rcRelease.htmlUrl, + gitReleaseUrl: promotedReleaseRes.value.htmlUrl, previousTag: rcRelease.tagName, - updatedTagUrl: promotedReleaseRes.value.htmlUrl, + previousTagUrl: rcRelease.htmlUrl, updatedTag: promotedReleaseRes.value.tagName, + updatedTagUrl: promotedReleaseRes.value.htmlUrl, }); } catch (error) { asyncCatcher(error); diff --git a/plugins/git-release-manager/src/types/types.ts b/plugins/git-release-manager/src/types/types.ts index fd45e55ba2..39418fa9d3 100644 --- a/plugins/git-release-manager/src/types/types.ts +++ b/plugins/git-release-manager/src/types/types.ts @@ -14,21 +14,26 @@ * limitations under the License. */ -export type ComponentConfig = { +import { UseCreateReleaseCandidate } from '../features/CreateReleaseCandidate/hooks/useCreateReleaseCandidate'; +import { UsePatch } from '../features/Patch/hooks/usePatch'; +import { UsePromoteRc } from '../features/PromoteRc/hooks/usePromoteRc'; + +export type ComponentConfig = { omit?: boolean; - onSuccess?: (args: Args) => Promise | void; + onSuccess?: (args: OnSuccessArgs) => Promise | void; }; -interface CreateRcOnSuccessArgs { - gitReleaseUrl: string; - gitReleaseName: string | null; +export interface CreateRcOnSuccessArgs { + input: Omit; comparisonUrl: string; - previousTag?: string; createdTag: string; + gitReleaseName: string | null; + gitReleaseUrl: string; + previousTag?: string; } -export type ComponentConfigCreateRc = ComponentConfig; -interface PromoteRcOnSuccessArgs { +export interface PromoteRcOnSuccessArgs { + input: Omit; gitReleaseUrl: string; gitReleaseName: string | null; previousTagUrl: string; @@ -36,9 +41,9 @@ interface PromoteRcOnSuccessArgs { updatedTagUrl: string; updatedTag: string; } -export type ComponentConfigPromoteRc = ComponentConfig; -interface PatchOnSuccessArgs { +export interface PatchOnSuccessArgs { + input: Omit; updatedReleaseUrl: string; updatedReleaseName: string | null; previousTag: string; @@ -46,7 +51,6 @@ interface PatchOnSuccessArgs { patchCommitUrl: string; patchCommitMessage: string; } -export type ComponentConfigPatch = ComponentConfig; export interface ResponseStep { message: string | React.ReactNode; From a2d8922c94f076433db2aef96d6b14dc19efa00f Mon Sep 17 00:00:00 2001 From: Erik Engervall Date: Wed, 21 Jul 2021 11:00:27 +0200 Subject: [PATCH 077/106] Add changeset & apply changes in api-report.md & package.json to make CI happy Signed-off-by: Erik Engervall --- .changeset/mighty-books-decide.md | 7 +++++++ plugins/git-release-manager/api-report.md | 1 + plugins/git-release-manager/package.json | 5 +++-- 3 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 .changeset/mighty-books-decide.md diff --git a/.changeset/mighty-books-decide.md b/.changeset/mighty-books-decide.md new file mode 100644 index 0000000000..6a25a2d2b5 --- /dev/null +++ b/.changeset/mighty-books-decide.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-git-release-manager': minor +--- + +Enable users to add custom features + +Add more metadata to success callbacks diff --git a/plugins/git-release-manager/api-report.md b/plugins/git-release-manager/api-report.md index 2497fed470..018a0f4be8 100644 --- a/plugins/git-release-manager/api-report.md +++ b/plugins/git-release-manager/api-report.md @@ -7,6 +7,7 @@ import { ApiRef } from '@backstage/core-plugin-api'; import { BackstagePlugin } from '@backstage/core-plugin-api'; +import { default as React_2 } from 'react'; import { RouteRef } from '@backstage/core-plugin-api'; // Warning: (ae-forgotten-export) The symbol "GitReleaseApi" needs to be exported by the entry point index.d.ts diff --git a/plugins/git-release-manager/package.json b/plugins/git-release-manager/package.json index 35c5056904..676550ae10 100644 --- a/plugins/git-release-manager/package.json +++ b/plugins/git-release-manager/package.json @@ -28,12 +28,13 @@ "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", "@octokit/rest": "^18.5.3", + "@types/react": "^16.9", "luxon": "^1.26.0", "qs": "^6.10.1", - "react": "^16.13.1", "react-dom": "^16.13.1", "react-router": "6.0.0-beta.0", "react-use": "^17.2.4", + "react": "^16.13.1", "recharts": "^1.8.5" }, "devDependencies": { @@ -42,8 +43,8 @@ "@backstage/dev-utils": "^0.2.2", "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", - "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^3.4.2", + "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", "@types/jest": "^26.0.7", "@types/node": "^14.14.32", From 221d7d0603271855fe8e5073e0a4659088ea9b74 Mon Sep 17 00:00:00 2001 From: Yousif Al-Raheem Date: Wed, 21 Jul 2021 11:43:41 +0200 Subject: [PATCH 078/106] added retry to use entity hook Signed-off-by: Yousif Al-Raheem --- .changeset/early-socks-beg.md | 6 ++++++ plugins/catalog-react/src/hooks/useEntity.ts | 12 +++++++----- .../EntityLoaderProvider/EntityLoaderProvider.tsx | 4 ++-- 3 files changed, 15 insertions(+), 7 deletions(-) create mode 100644 .changeset/early-socks-beg.md diff --git a/.changeset/early-socks-beg.md b/.changeset/early-socks-beg.md new file mode 100644 index 0000000000..33265fe8f1 --- /dev/null +++ b/.changeset/early-socks-beg.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-catalog': minor +'@backstage/plugin-catalog-react': minor +--- + +added retry callback to useEntity hook diff --git a/plugins/catalog-react/src/hooks/useEntity.ts b/plugins/catalog-react/src/hooks/useEntity.ts index 465cffcf9b..a83e573753 100644 --- a/plugins/catalog-react/src/hooks/useEntity.ts +++ b/plugins/catalog-react/src/hooks/useEntity.ts @@ -17,7 +17,7 @@ import { Entity } from '@backstage/catalog-model'; import { errorApiRef, useApi } from '@backstage/core-plugin-api'; import { createContext, useContext, useEffect } from 'react'; import { useNavigate } from 'react-router'; -import { useAsync } from 'react-use'; +import { useAsyncRetry } from 'react-use'; import { catalogApiRef } from '../api'; import { useEntityCompoundName } from './useEntityCompoundName'; @@ -25,12 +25,14 @@ type EntityLoadingStatus = { entity?: Entity; loading: boolean; error?: Error; + retry?: VoidFunction; }; export const EntityContext = createContext({ entity: undefined, loading: true, error: undefined, + retry: () => {}, }); export const useEntityFromUrl = (): EntityLoadingStatus => { @@ -39,7 +41,7 @@ export const useEntityFromUrl = (): EntityLoadingStatus => { const errorApi = useApi(errorApiRef); const catalogApi = useApi(catalogApiRef); - const { value: entity, error, loading } = useAsync( + const { value: entity, error, loading, retry } = useAsyncRetry( () => catalogApi.getEntityByName({ kind, namespace, name }), [catalogApi, kind, namespace, name], ); @@ -51,13 +53,13 @@ export const useEntityFromUrl = (): EntityLoadingStatus => { } }, [errorApi, navigate, error, loading, entity, name]); - return { entity, loading, error }; + return { entity, loading, error, retry }; }; /** * Grab the current entity from the context and its current loading state. */ export function useEntity() { - const { entity, loading, error } = useContext(EntityContext); - return { entity: entity as T, loading, error }; + const { entity, loading, error, retry } = useContext(EntityContext); + return { entity: entity as T, loading, error, retry }; } diff --git a/plugins/catalog/src/components/EntityLoaderProvider/EntityLoaderProvider.tsx b/plugins/catalog/src/components/EntityLoaderProvider/EntityLoaderProvider.tsx index 7f9f728161..20828b390b 100644 --- a/plugins/catalog/src/components/EntityLoaderProvider/EntityLoaderProvider.tsx +++ b/plugins/catalog/src/components/EntityLoaderProvider/EntityLoaderProvider.tsx @@ -20,10 +20,10 @@ import { import React, { ReactNode } from 'react'; export const EntityLoaderProvider = ({ children }: { children: ReactNode }) => { - const { entity, loading, error } = useEntityFromUrl(); + const { entity, loading, error, retry } = useEntityFromUrl(); return ( - + {children} ); From ed6092f76a606d0d367788c96f4fe6eee8500110 Mon Sep 17 00:00:00 2001 From: Dominik Henneke Date: Wed, 21 Jul 2021 12:42:24 +0200 Subject: [PATCH 079/106] Handle and forward errors from the DocsBuilder instantiation to the user Signed-off-by: Dominik Henneke --- .../src/service/DocsSynchronizer.ts | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/plugins/techdocs-backend/src/service/DocsSynchronizer.ts b/plugins/techdocs-backend/src/service/DocsSynchronizer.ts index 5e5d91b625..463b895564 100644 --- a/plugins/techdocs-backend/src/service/DocsSynchronizer.ts +++ b/plugins/techdocs-backend/src/service/DocsSynchronizer.ts @@ -92,20 +92,20 @@ export class DocsSynchronizer { return; } - const docsBuilder = new DocsBuilder({ - preparers, - generators, - publisher: this.publisher, - logger: taskLogger, - entity, - config: this.config, - scmIntegrations: this.scmIntegrations, - logStream, - }); - let foundDocs = false; try { + const docsBuilder = new DocsBuilder({ + preparers, + generators, + publisher: this.publisher, + logger: taskLogger, + entity, + config: this.config, + scmIntegrations: this.scmIntegrations, + logStream, + }); + const updated = await docsBuilder.build(); if (!updated) { From 445440a4084fd11739b52ba64871ed422b88f971 Mon Sep 17 00:00:00 2001 From: Dominik Henneke Date: Wed, 21 Jul 2021 13:52:29 +0200 Subject: [PATCH 080/106] Make sure all old transformations are cancelled when the hook dependencies changed Signed-off-by: Dominik Henneke --- .../techdocs/src/reader/components/Reader.tsx | 287 ++++++++++-------- 1 file changed, 154 insertions(+), 133 deletions(-) diff --git a/plugins/techdocs/src/reader/components/Reader.tsx b/plugins/techdocs/src/reader/components/Reader.tsx index d253070827..f861641708 100644 --- a/plugins/techdocs/src/reader/components/Reader.tsx +++ b/plugins/techdocs/src/reader/components/Reader.tsx @@ -28,7 +28,6 @@ import { import { Alert } from '@material-ui/lab'; import React, { useCallback, useEffect, useRef, useState } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; -import { useAsync } from 'react-use'; import { techdocsStorageApiRef } from '../../api'; import { addBaseUrl, @@ -110,32 +109,26 @@ export const Reader = ({ entityId, onReady }: Props) => { // an update to "state" might lead to an updated UI so we include it as a trigger }, [updateSidebarPosition, state]); - useAsync(async () => { - if (!rawPage || !shadowDomRef.current) { - return; - } - if (onReady) { - onReady(); - } - - // Pre-render - const transformedElement = await transformer(rawPage, [ - sanitizeDOM(), - addBaseUrl({ - techdocsStorageApi, - entityId: { - kind, - name, - namespace, - }, - path, - }), - rewriteDocLinks(), - removeMkdocsHeader(), - simplifyMkdocsFooter(), - addGitFeedbackLink(scmIntegrationsApi), - injectCss({ - css: ` + // a function that performs transformations that are executed prior to adding it to the DOM + const preRender = useCallback( + (rawContent: string, contentPath: string) => + transformer(rawContent, [ + sanitizeDOM(), + addBaseUrl({ + techdocsStorageApi, + entityId: { + kind, + name, + namespace, + }, + path: contentPath, + }), + rewriteDocLinks(), + removeMkdocsHeader(), + simplifyMkdocsFooter(), + addGitFeedbackLink(scmIntegrationsApi), + injectCss({ + css: ` body { font-family: ${theme.typography.fontFamily}; --md-text-color: ${theme.palette.text.primary}; @@ -192,21 +185,21 @@ export const Reader = ({ entityId, onReady }: Props) => { } } `, - }), - injectCss({ - // Disable CSS animations on link colors as they lead to issues in dark - // mode. The dark mode color theme is applied later and theirfore there - // is always an animation from light to dark mode when navigation - // between pages. - css: ` + }), + injectCss({ + // Disable CSS animations on link colors as they lead to issues in dark + // mode. The dark mode color theme is applied later and theirfore there + // is always an animation from light to dark mode when navigation + // between pages. + css: ` .md-nav__link, .md-typeset a, .md-typeset a::before, .md-typeset .headerlink { transition: none; } `, - }), - injectCss({ - // Properly style code blocks. - css: ` + }), + injectCss({ + // Properly style code blocks. + css: ` .md-typeset pre > code::-webkit-scrollbar-thumb { background-color: hsla(0, 0%, 0%, 0.32); } @@ -214,17 +207,17 @@ export const Reader = ({ entityId, onReady }: Props) => { background-color: hsla(0, 0%, 0%, 0.87); } `, - }), - injectCss({ - // Admonitions and others are using SVG masks to define icons. These - // masks are defined as CSS variables. - // As the MkDocs output is rendered in shadow DOM, the CSS variable - // definitions on the root selector are not applied. Instead, the have - // to be applied on :host. - // As there is no way to transform the served main*.css yet (for - // example in the backend), we have to copy from main*.css and modify - // them. - css: ` + }), + injectCss({ + // Admonitions and others are using SVG masks to define icons. These + // masks are defined as CSS variables. + // As the MkDocs output is rendered in shadow DOM, the CSS variable + // definitions on the root selector are not applied. Instead, the have + // to be applied on :host. + // As there is no way to transform the served main*.css yet (for + // example in the backend), we have to copy from main*.css and modify + // them. + css: ` :host { --md-admonition-icon--note: url('data:image/svg+xml;charset=utf-8,'); --md-admonition-icon--abstract: url('data:image/svg+xml;charset=utf-8,'); @@ -250,97 +243,125 @@ export const Reader = ({ entityId, onReady }: Props) => { --md-tasklist-icon--checked: url('data:image/svg+xml;charset=utf-8,'); } `, - }), - ]); + }), + ]), + [ + kind, + name, + namespace, + scmIntegrationsApi, + techdocsStorageApi, + theme.palette.background.default, + theme.palette.background.paper, + theme.palette.primary.main, + theme.palette.text.primary, + theme.typography.fontFamily, + ], + ); - if (!transformedElement?.innerHTML) { - return; // An unexpected error occurred + // a function that performs transformations that are executed after adding it to the DOM + const postRender = useCallback( + async (shadowRoot: ShadowRoot) => + transformer(shadowRoot.children[0], [ + dom => { + setTimeout(() => { + // Scoll to the desired anchor on initial navigation + if (window.location.hash) { + const hash = window.location.hash.slice(1); + shadowRoot?.getElementById(hash)?.scrollIntoView(); + } + }, 200); + return dom; + }, + addLinkClickListener({ + baseUrl: window.location.origin, + onClick: (_: MouseEvent, url: string) => { + const parsedUrl = new URL(url); + + if (parsedUrl.hash) { + navigate(`${parsedUrl.pathname}${parsedUrl.hash}`); + + // Scroll to hash if it's on the current page + shadowRoot + ?.getElementById(parsedUrl.hash.slice(1)) + ?.scrollIntoView(); + } else { + navigate(parsedUrl.pathname); + } + }, + }), + onCssReady({ + docStorageUrl: await techdocsStorageApi.getApiOrigin(), + onLoading: (dom: Element) => { + (dom as HTMLElement).style.setProperty('opacity', '0'); + }, + onLoaded: (dom: Element) => { + (dom as HTMLElement).style.removeProperty('opacity'); + // disable MkDocs drawer toggling ('for' attribute => checkbox mechanism) + (dom as HTMLElement) + .querySelector('.md-nav__title') + ?.removeAttribute('for'); + const sideDivs: HTMLElement[] = Array.from( + shadowRoot!.querySelectorAll('.md-sidebar'), + ); + setSidebars(sideDivs); + // set sidebar height so they don't initially render in wrong position + const docTopPosition = (dom as HTMLElement).getBoundingClientRect() + .top; + const mdTabs = dom.querySelector('.md-container > .md-tabs'); + sideDivs!.forEach(sidebar => { + sidebar.style.top = mdTabs + ? `${docTopPosition + mdTabs.getBoundingClientRect().height}px` + : `${docTopPosition}px`; + }); + }, + }), + ]), + [navigate, techdocsStorageApi], + ); + + useEffect(() => { + if (!rawPage || !shadowDomRef.current) { + return () => {}; + } + if (onReady) { + onReady(); } - const shadowDiv: HTMLElement = shadowDomRef.current!; - const shadowRoot = - shadowDiv.shadowRoot || shadowDiv.attachShadow({ mode: 'open' }); - Array.from(shadowRoot.children).forEach(child => - shadowRoot.removeChild(child), - ); - shadowRoot.appendChild(transformedElement); + // if false, there is already a newer execution of this effect + let shouldReplaceContent = true; - // Scroll to top after render - window.scroll({ top: 0 }); + // Pre-render + preRender(rawPage, path).then(async transformedElement => { + if (!transformedElement?.innerHTML) { + return; // An unexpected error occurred + } - // Post-render - await transformer(shadowRoot.children[0], [ - dom => { - setTimeout(() => { - // Scoll to the desired anchor on initial navigation - if (window.location.hash) { - const hash = window.location.hash.slice(1); - shadowRoot?.getElementById(hash)?.scrollIntoView(); - } - }, 200); - return dom; - }, - addLinkClickListener({ - baseUrl: window.location.origin, - onClick: (_: MouseEvent, url: string) => { - const parsedUrl = new URL(url); + // don't manipulate the shadow dom if this isn't the latest effect execution + if (!shouldReplaceContent) { + return; + } - if (parsedUrl.hash) { - navigate(`${parsedUrl.pathname}${parsedUrl.hash}`); + const shadowDiv: HTMLElement = shadowDomRef.current!; + const shadowRoot = + shadowDiv.shadowRoot || shadowDiv.attachShadow({ mode: 'open' }); + Array.from(shadowRoot.children).forEach(child => + shadowRoot.removeChild(child), + ); + shadowRoot.appendChild(transformedElement); - // Scroll to hash if it's on the current page - shadowRoot - ?.getElementById(parsedUrl.hash.slice(1)) - ?.scrollIntoView(); - } else { - navigate(parsedUrl.pathname); - } - }, - }), - onCssReady({ - docStorageUrl: await techdocsStorageApi.getApiOrigin(), - onLoading: (dom: Element) => { - (dom as HTMLElement).style.setProperty('opacity', '0'); - }, - onLoaded: (dom: Element) => { - (dom as HTMLElement).style.removeProperty('opacity'); - // disable MkDocs drawer toggling ('for' attribute => checkbox mechanism) - (dom as HTMLElement) - .querySelector('.md-nav__title') - ?.removeAttribute('for'); - const sideDivs: HTMLElement[] = Array.from( - shadowRoot!.querySelectorAll('.md-sidebar'), - ); - setSidebars(sideDivs); - // set sidebar height so they don't initially render in wrong position - const docTopPosition = (dom as HTMLElement).getBoundingClientRect() - .top; - const mdTabs = dom.querySelector('.md-container > .md-tabs'); - sideDivs!.forEach(sidebar => { - sidebar.style.top = mdTabs - ? `${docTopPosition + mdTabs.getBoundingClientRect().height}px` - : `${docTopPosition}px`; - }); - }, - }), - ]); - }, [ - path, - kind, - namespace, - name, - rawPage, - navigate, - onReady, - shadowDomRef, - techdocsStorageApi, - theme.typography.fontFamily, - theme.palette.text.primary, - theme.palette.primary.main, - theme.palette.background.paper, - theme.palette.background.default, - scmIntegrationsApi, - ]); + // Scroll to top after render + window.scroll({ top: 0 }); + + // Post-render + await postRender(shadowRoot); + }); + + // cancel this execution + return () => { + shouldReplaceContent = false; + }; + }, [onReady, path, postRender, preRender, rawPage]); return ( <> From 8359313600330aa1ac171b13552b742a59547c84 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 21 Jul 2021 14:32:55 +0200 Subject: [PATCH 081/106] chore: refactoring to use `theme.spacing` Signed-off-by: blam --- packages/core-components/src/layout/Sidebar/Items.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/core-components/src/layout/Sidebar/Items.tsx b/packages/core-components/src/layout/Sidebar/Items.tsx index 6fdf3d56dd..3944df0b50 100644 --- a/packages/core-components/src/layout/Sidebar/Items.tsx +++ b/packages/core-components/src/layout/Sidebar/Items.tsx @@ -44,7 +44,6 @@ const useStyles = makeStyles(theme => { drawerWidthOpen, iconContainerWidth, } = sidebarConfig; - return { root: { color: theme.palette.navigation.color, @@ -98,7 +97,7 @@ const useStyles = makeStyles(theme => { fontSize: theme.typography.fontSize, }, searchFieldHTMLInput: { - padding: '15px 0 17px', + padding: `${theme.spacing(2)} 0 ${theme.spacing(2)}`, }, searchContainer: { width: drawerWidthOpen - iconContainerWidth, From e8dc2b893130e7017fec14892f0eccb200c1b7ac Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 21 Jul 2021 12:48:56 +0000 Subject: [PATCH 082/106] chore(deps-dev): bump @types/jenkins from 0.23.1 to 0.23.2 Bumps [@types/jenkins](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/jenkins) from 0.23.1 to 0.23.2. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/jenkins) --- updated-dependencies: - dependency-name: "@types/jenkins" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 9431895914..3921f057bc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5846,9 +5846,9 @@ "@types/istanbul-lib-report" "*" "@types/jenkins@^0.23.1": - version "0.23.1" - resolved "https://registry.npmjs.org/@types/jenkins/-/jenkins-0.23.1.tgz#d0f0ef5511beff975c91cbd2365e580d700ca7f9" - integrity sha512-3oGxVCq+5esbjb0BQXUv0Iz0/7ogJxmzaxKtxwwMik5vGtRvfjWf/sXGA1RzkVAG0+rJUZNKStjKRdtqJfEyRg== + version "0.23.2" + resolved "https://registry.npmjs.org/@types/jenkins/-/jenkins-0.23.2.tgz#96736a2be4904efdfe7fe2650569479fd77dc48e" + integrity sha512-BELmIZ6brxwGFqBHfLjLTjYRWAUqcT1d2BydH1CcRTZEjCYw3DRVfZkXU7BVlyIsKXm2ZMIKVkEjKKtVDvPiXA== dependencies: "@types/node" "*" From d591b764d0f9e2de01b6caadccd046a93d901b7e Mon Sep 17 00:00:00 2001 From: Dominik Henneke Date: Wed, 21 Jul 2021 15:13:16 +0200 Subject: [PATCH 083/106] Remove the short-form annotation and only support the full `dir:.` option Signed-off-by: Dominik Henneke --- .changeset/techdocs-twenty-donuts-eat.md | 17 +-- packages/techdocs-common/api-report.md | 13 +- packages/techdocs-common/src/helpers.test.ts | 137 ++++-------------- packages/techdocs-common/src/helpers.ts | 72 ++------- .../techdocs-common/src/stages/prepare/dir.ts | 19 ++- .../src/stages/prepare/preparers.ts | 23 +-- 6 files changed, 73 insertions(+), 208 deletions(-) diff --git a/.changeset/techdocs-twenty-donuts-eat.md b/.changeset/techdocs-twenty-donuts-eat.md index b4f9a067c3..a95ec5a752 100644 --- a/.changeset/techdocs-twenty-donuts-eat.md +++ b/.changeset/techdocs-twenty-donuts-eat.md @@ -3,22 +3,21 @@ '@backstage/plugin-techdocs-backend': minor --- -Introduce the annotation `backstage.io/techdocs-ref: ` as an alias for `backstage.io/techdocs-ref: dir:`. -This annotation works with both the basic and the recommended flow, however, it will be most useful with the basic approach. +Improve the annotation `backstage.io/techdocs-ref: dir:` that links to a path that is relative to the source of the annotated entity. +This annotation works with the basic and the recommended flow, however, it will be most useful with the basic approach. +This change remove the deprecation of the `dir` reference and provides first-class support for it. In addition, this change removes the support of the deprecated `github`, `gitlab`, and `azure/api` locations from the `dir` reference preparer. #### Example Usage -The new annotation is convenient if the documentation is stored in the same location, i.e. the same git repository, as the `catalog-info.yaml`. +The annotation is convenient if the documentation is stored in the same location, i.e. the same git repository, as the `catalog-info.yaml`. While it is still supported to add full URLs such as `backstage.io/techdocs-ref: url:https://...` for custom setups, documentation is mostly stored in the same repository as the entity definition. By automatically resolving the target relative to the registration location of the entity, the configuration overhead for this default setup is minimized. Since it leverages the `@backstage/integrations` package for the URL resolution, this is compatible with every supported source. Consider the following examples: -> Note that the short version `` is only an alias for the still supported `dir:`. - 1. "I have a repository with a single `catalog-info.yaml` and a TechDocs page in the root folder!" ``` @@ -29,7 +28,7 @@ https://github.com/backstage/example/tree/main/ | > metadata: | > name: example | > annotations: - | > backstage.io/techdocs-ref: . # -> same folder + | > backstage.io/techdocs-ref: dir:. # -> same folder | > spec: {} |- docs/ |- mkdocs.yml @@ -45,7 +44,7 @@ https://bitbucket.org/my-owner/my-project/src/master/ | > metadata: | > name: example | > annotations: - | > backstage.io/techdocs-ref: ./some-folder # -> subfolder + | > backstage.io/techdocs-ref: dir:./some-folder # -> subfolder | > spec: {} |- some-folder/ |- docs/ @@ -63,7 +62,7 @@ https://dev.azure.com/organization/project/_git/repository | > metadata: | > name: my-1st-module | > annotations: - | > backstage.io/techdocs-ref: . # -> same folder + | > backstage.io/techdocs-ref: dir:. # -> same folder | > spec: {} |- docs/ |- mkdocs.yml @@ -74,7 +73,7 @@ https://dev.azure.com/organization/project/_git/repository | > metadata: | > name: my-2nd-module | > annotations: - | > backstage.io/techdocs-ref: . # -> same folder + | > backstage.io/techdocs-ref: dir:. # -> same folder | > spec: {} |- docs/ |- mkdocs.yml diff --git a/packages/techdocs-common/api-report.md b/packages/techdocs-common/api-report.md index 9fc1795c13..6e845c9797 100644 --- a/packages/techdocs-common/api-report.md +++ b/packages/techdocs-common/api-report.md @@ -137,18 +137,6 @@ export const getDefaultBranch: ( config: Config, ) => Promise; -// Warning: (ae-missing-release-tag) "getDirLocation" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public -export const getDirLocation: ( - entity: Entity, -) => - | { - type: 'dir'; - target: string; - } - | undefined; - // Warning: (ae-missing-release-tag) "getDocFilesFromRepository" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -382,6 +370,7 @@ export type TechDocsMetadata = { // @public export const transformDirLocation: ( entity: Entity, + dirAnnotation: ParsedLocationAnnotation, scmIntegrations: ScmIntegrationRegistry, ) => { type: 'dir' | 'url'; diff --git a/packages/techdocs-common/src/helpers.test.ts b/packages/techdocs-common/src/helpers.test.ts index 6b677c6f5e..69f09d92ed 100644 --- a/packages/techdocs-common/src/helpers.test.ts +++ b/packages/techdocs-common/src/helpers.test.ts @@ -26,7 +26,6 @@ import os from 'os'; import path from 'path'; import { Readable } from 'stream'; import { - getDirLocation, getDocFilesFromRepository, getLocationForEntity, parseReferenceAnnotation, @@ -126,76 +125,11 @@ describe('parseReferenceAnnotation', () => { }); }); -describe('getDirLocation', () => { - it.each` - techdocsRef | responseTarget - ${undefined} | ${undefined} - ${16} | ${undefined} - ${'.'} | ${'.'} - ${'dir:.'} | ${'.'} - ${'./relative'} | ${'./relative'} - ${'dir:./relative'} | ${'./relative'} - ${'dir:https://github.com...'} | ${'https://github.com...'} - ${'url:https://github.com...'} | ${undefined} - `( - 'should handle "backstage.io/techdocs-ref: $techdocsRef" correctly', - ({ techdocsRef, responseTarget }) => { - const entity = { - apiVersion: '1', - kind: 'Component', - metadata: { - name: 'test', - annotations: { - 'backstage.io/techdocs-ref': techdocsRef, - }, - }, - }; - - const result = getDirLocation(entity); - - expect(result).toEqual( - responseTarget && { type: 'dir', target: responseTarget }, - ); - }, - ); - - it('Reject https urls and hint to the url: location type', async () => { - const entity = { - apiVersion: '1', - kind: 'Component', - metadata: { - name: 'test', - annotations: { - 'backstage.io/techdocs-ref': 'https://github.com/backstage/backstage', - }, - }, - }; - - expect(() => getDirLocation(entity)).toThrow( - /please prefix it with 'url:'/, - ); - }); -}); - describe('transformDirLocation', () => { - it('should reject missing annotation', () => { - const entity = { - apiVersion: '1', - kind: 'Component', - metadata: { - name: 'test', - }, - }; - - expect(() => transformDirLocation(entity, scmIntegrations)).toThrow( - /No techdocs location annotation provided in entity: component:default\/test/, - ); - }); - it.each` - techdocsRef | target - ${'.'} | ${'https://my-url/folder/'} - ${'./sub-folder'} | ${'https://my-url/folder/sub-folder'} + techdocsRef | target + ${'dir:.'} | ${'https://my-url/folder/'} + ${'dir:./sub-folder'} | ${'https://my-url/folder/sub-folder'} `( 'should transform "$techdocsRef" for url type locations', ({ techdocsRef, target }) => { @@ -215,16 +149,20 @@ describe('transformDirLocation', () => { }, }; - const result = transformDirLocation(entity, scmIntegrations); + const result = transformDirLocation( + entity, + parseReferenceAnnotation('backstage.io/techdocs-ref', entity), + scmIntegrations, + ); expect(result).toEqual({ type: 'url', target }); }, ); it.each` - techdocsRef | target - ${'.'} | ${path.join(rootDir, 'working-copy')} - ${'./sub-folder'} | ${path.join(rootDir, 'working-copy', 'sub-folder')} + techdocsRef | target + ${'dir:.'} | ${path.join(rootDir, 'working-copy')} + ${'dir:./sub-folder'} | ${path.join(rootDir, 'working-copy', 'sub-folder')} `( 'should transform "$techdocsRef" for file type locations', ({ techdocsRef, target }) => { @@ -244,7 +182,11 @@ describe('transformDirLocation', () => { }, }; - const result = transformDirLocation(entity, scmIntegrations); + const result = transformDirLocation( + entity, + parseReferenceAnnotation('backstage.io/techdocs-ref', entity), + scmIntegrations, + ); expect(result).toEqual({ type: 'dir', target }); }, @@ -262,12 +204,18 @@ describe('transformDirLocation', () => { metadata: { name: 'test', annotations: { - 'backstage.io/techdocs-ref': '..', + 'backstage.io/techdocs-ref': 'dir:..', }, }, }; - expect(() => transformDirLocation(entity, scmIntegrations)).toThrow( + expect(() => + transformDirLocation( + entity, + parseReferenceAnnotation('backstage.io/techdocs-ref', entity), + scmIntegrations, + ), + ).toThrow( /Relative path is not allowed to refer to a directory outside its parent/, ); }); @@ -284,43 +232,22 @@ describe('transformDirLocation', () => { metadata: { name: 'test', annotations: { - 'backstage.io/techdocs-ref': '.', + 'backstage.io/techdocs-ref': 'dir:.', }, }, }; - expect(() => transformDirLocation(entity, scmIntegrations)).toThrow( - /Unable to resolve location type other/, - ); + expect(() => + transformDirLocation( + entity, + parseReferenceAnnotation('backstage.io/techdocs-ref', entity), + scmIntegrations, + ), + ).toThrow(/Unable to resolve location type other/); }); }); describe('getLocationForEntity', () => { - it('should handle implicit dir locations', () => { - (getEntitySourceLocation as jest.Mock).mockReturnValue({ - type: 'url', - target: 'https://my-url/folder/', - }); - - const entity = { - apiVersion: '1', - kind: 'Component', - metadata: { - name: 'test', - annotations: { - 'backstage.io/techdocs-ref': '.', - }, - }, - }; - - const parsedLocationAnnotation = getLocationForEntity( - entity, - scmIntegrations, - ); - expect(parsedLocationAnnotation.type).toBe('url'); - expect(parsedLocationAnnotation.target).toBe('https://my-url/folder/'); - }); - it('should handle dir locations', () => { (getEntitySourceLocation as jest.Mock).mockReturnValue({ type: 'url', diff --git a/packages/techdocs-common/src/helpers.ts b/packages/techdocs-common/src/helpers.ts index bb640c1cce..bb6461a37c 100644 --- a/packages/techdocs-common/src/helpers.ts +++ b/packages/techdocs-common/src/helpers.ts @@ -23,7 +23,6 @@ import { Entity, getEntitySourceLocation, parseLocationReference, - stringifyEntityRef, } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; import { InputError } from '@backstage/errors'; @@ -60,44 +59,6 @@ export const parseReferenceAnnotation = ( }; }; -/** - * Check if the entity provides a `backstage.io/techdocs-ref` annotation of type `dir` - * and return the annotation. It accepts the two variants `` and `dir:`. - * - * @param entity - the entity to check - */ -export const getDirLocation = ( - entity: Entity, -): { type: 'dir'; target: string } | undefined => { - const annotation = entity.metadata.annotations?.['backstage.io/techdocs-ref']; - - if (!annotation) { - return undefined; - } - - if (typeof annotation !== 'string') { - return undefined; - } - - // if the string doesn't contain `:`, interpret it as the target of a dir type - if (!annotation.includes(':')) { - return { type: 'dir', target: annotation }; - } - - // note that `backstage.io/techdocs-ref: https://...` is invalid and will throw - const reference = parseLocationReference(annotation); - - if (reference.type === 'dir') { - return { - type: 'dir', - target: reference.target, - }; - } - - // ignore any other types - return undefined; -}; - /** * TechDocs references of type `dir` are relative the source location of the entity. * This function transforms relative references to absolute ones, based on the @@ -107,30 +68,22 @@ export const getDirLocation = ( * an absolute `dir` location. * * @param entity - the entity with annotations - * @param scmIntegrations - access to the scmIntegrationt to do url transformations + * @param dirAnnotation - the parsed techdocs-ref annotation of type 'dir' + * @param scmIntegrations - access to the scmIntegration to do url transformations * @throws if the entity doesn't specify a `dir` location or is ingested from an unsupported location. * @returns the transformed location with an absolute target. */ export const transformDirLocation = ( entity: Entity, + dirAnnotation: ParsedLocationAnnotation, scmIntegrations: ScmIntegrationRegistry, ): { type: 'dir' | 'url'; target: string } => { - const dirLocation = getDirLocation(entity); - - if (!dirLocation) { - throw new InputError( - `No techdocs location annotation provided in entity: ${stringifyEntityRef( - entity, - )}`, - ); - } - const location = getEntitySourceLocation(entity); switch (location.type) { case 'url': { const target = scmIntegrations.resolveUrl({ - url: dirLocation.target, + url: dirAnnotation.target, base: location.target, }); @@ -144,7 +97,7 @@ export const transformDirLocation = ( // only permit targets in the same folder as the target of the `file` location! const target = resolveSafeChildPath( path.dirname(location.target), - dirLocation.target, + dirAnnotation.target, ); return { @@ -162,24 +115,21 @@ export const getLocationForEntity = ( entity: Entity, scmIntegration: ScmIntegrationRegistry, ): ParsedLocationAnnotation => { - // try to resolve relative references first - if (getDirLocation(entity) !== undefined) { - return transformDirLocation(entity, scmIntegration); - } - - const { type, target } = parseReferenceAnnotation( + const annotation = parseReferenceAnnotation( 'backstage.io/techdocs-ref', entity, ); - switch (type) { + switch (annotation.type) { case 'github': case 'gitlab': case 'azure/api': case 'url': - return { type, target }; + return annotation; + case 'dir': + return transformDirLocation(entity, annotation, scmIntegration); default: - throw new Error(`Invalid reference annotation ${type}`); + throw new Error(`Invalid reference annotation ${annotation.type}`); } }; diff --git a/packages/techdocs-common/src/stages/prepare/dir.ts b/packages/techdocs-common/src/stages/prepare/dir.ts index b0ba2a36b3..ade0b00162 100644 --- a/packages/techdocs-common/src/stages/prepare/dir.ts +++ b/packages/techdocs-common/src/stages/prepare/dir.ts @@ -23,7 +23,7 @@ import { ScmIntegrations, } from '@backstage/integration'; import { Logger } from 'winston'; -import { transformDirLocation } from '../../helpers'; +import { parseReferenceAnnotation, transformDirLocation } from '../../helpers'; import { PreparerBase, PreparerResponse } from './types'; export class DirectoryPreparer implements PreparerBase { @@ -39,18 +39,29 @@ export class DirectoryPreparer implements PreparerBase { entity: Entity, options?: { logger?: Logger; etag?: string }, ): Promise { - const { type, target } = transformDirLocation(entity, this.scmIntegrations); + const annotation = parseReferenceAnnotation( + 'backstage.io/techdocs-ref', + entity, + ); + const { type, target } = transformDirLocation( + entity, + annotation, + this.scmIntegrations, + ); switch (type) { case 'url': { - options?.logger?.info(`Download documentation from ${target}`); + options?.logger?.debug(`Reading files from ${target}`); // the target is an absolute url since it has already been transformed const response = await this.reader.readTree(target, { etag: options?.etag, }); + const preparedDir = await response.dir(); + + options?.logger?.debug(`Tree downloaded and stored at ${preparedDir}`); return { - preparedDir: await response.dir(), + preparedDir, etag: response.etag, }; } diff --git a/packages/techdocs-common/src/stages/prepare/preparers.ts b/packages/techdocs-common/src/stages/prepare/preparers.ts index 48e99d48f5..12fcb01013 100644 --- a/packages/techdocs-common/src/stages/prepare/preparers.ts +++ b/packages/techdocs-common/src/stages/prepare/preparers.ts @@ -15,11 +15,10 @@ */ import { UrlReader } from '@backstage/backend-common'; -import { Entity, parseLocationReference } from '@backstage/catalog-model'; +import { Entity } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; -import { InputError } from '@backstage/errors'; import { Logger } from 'winston'; -import { getDirLocation } from '../../helpers'; +import { parseReferenceAnnotation } from '../../helpers'; import { CommonGitPreparer } from './commonGit'; import { DirectoryPreparer } from './dir'; import { PreparerBase, PreparerBuilder, RemoteProtocol } from './types'; @@ -63,20 +62,10 @@ export class Preparers implements PreparerBuilder { } get(entity: Entity): PreparerBase { - const annotation = - entity.metadata.annotations?.['backstage.io/techdocs-ref']; - if (!annotation) { - throw new InputError( - `No location annotation provided in entity: ${entity.metadata.name}`, - ); - } - - // the dir processor handles both `` and `dir:` - if (getDirLocation(entity) !== undefined) { - return this.preparerMap.get('dir')!; - } - - const { type } = parseLocationReference(annotation); + const { type } = parseReferenceAnnotation( + 'backstage.io/techdocs-ref', + entity, + ); const preparer = this.preparerMap.get(type as RemoteProtocol); if (!preparer) { From 296d8e3832774ad92629f65dd208e00d24b157db Mon Sep 17 00:00:00 2001 From: Dominik Henneke Date: Wed, 21 Jul 2021 15:18:22 +0200 Subject: [PATCH 084/106] Delete obsolete changes Signed-off-by: Dominik Henneke --- packages/techdocs-common/src/stages/prepare/preparers.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/techdocs-common/src/stages/prepare/preparers.ts b/packages/techdocs-common/src/stages/prepare/preparers.ts index 12fcb01013..5c78d96f15 100644 --- a/packages/techdocs-common/src/stages/prepare/preparers.ts +++ b/packages/techdocs-common/src/stages/prepare/preparers.ts @@ -13,16 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - import { UrlReader } from '@backstage/backend-common'; import { Entity } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; import { Logger } from 'winston'; import { parseReferenceAnnotation } from '../../helpers'; -import { CommonGitPreparer } from './commonGit'; import { DirectoryPreparer } from './dir'; -import { PreparerBase, PreparerBuilder, RemoteProtocol } from './types'; +import { CommonGitPreparer } from './commonGit'; import { UrlPreparer } from './url'; +import { PreparerBase, PreparerBuilder, RemoteProtocol } from './types'; type factoryOptions = { logger: Logger; @@ -66,7 +65,7 @@ export class Preparers implements PreparerBuilder { 'backstage.io/techdocs-ref', entity, ); - const preparer = this.preparerMap.get(type as RemoteProtocol); + const preparer = this.preparerMap.get(type); if (!preparer) { throw new Error(`No preparer registered for type: "${type}"`); From 9fba5ac5e668a3c75fd8bd2d43ccc72301e2dccb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Jul 2021 04:13:04 +0000 Subject: [PATCH 085/106] chore(deps): bump @azure/identity from 1.2.2 to 1.4.0 Bumps [@azure/identity](https://github.com/Azure/azure-sdk-for-js) from 1.2.2 to 1.4.0. - [Release notes](https://github.com/Azure/azure-sdk-for-js/releases) - [Commits](https://github.com/Azure/azure-sdk-for-js/compare/@azure/identity_1.2.2...@azure/identity_1.4.0) --- updated-dependencies: - dependency-name: "@azure/identity" dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- yarn.lock | 132 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 84 insertions(+), 48 deletions(-) diff --git a/yarn.lock b/yarn.lock index 9431895914..ffbb94d16d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -129,6 +129,14 @@ "@azure/abort-controller" "^1.0.0" tslib "^2.0.0" +"@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== + dependencies: + "@azure/abort-controller" "^1.0.0" + tslib "^2.2.0" + "@azure/core-http@^1.2.0": version "1.2.2" resolved "https://registry.npmjs.org/@azure/core-http/-/core-http-1.2.2.tgz#a6f7717184fd2657d3acabd1d64dfdc0bd531ce3" @@ -150,6 +158,27 @@ uuid "^8.3.0" xml2js "^0.4.19" +"@azure/core-http@^2.0.0": + version "2.0.0" + resolved "https://registry.npmjs.org/@azure/core-http/-/core-http-2.0.0.tgz#3decae3a80ec19d5d2fbe2cd21834858743ff348" + integrity sha512-VBOfUh0z9ZF1WVqrLCtiGWMjkKic171p6mLXRkJKu+p5wuQTb4cU3bPq7nB6UuGAK17LI7hnU0SzydlCQrBuOw== + dependencies: + "@azure/abort-controller" "^1.0.0" + "@azure/core-asynciterator-polyfill" "^1.0.0" + "@azure/core-auth" "^1.3.0" + "@azure/core-tracing" "1.0.0-preview.12" + "@azure/logger" "^1.0.0" + "@types/node-fetch" "^2.5.0" + "@types/tunnel" "^0.0.1" + form-data "^3.0.0" + node-fetch "^2.6.0" + process "^0.11.10" + tough-cookie "^4.0.0" + tslib "^2.2.0" + tunnel "^0.0.6" + uuid "^8.3.0" + xml2js "^0.4.19" + "@azure/core-lro@^1.0.2": version "1.0.3" resolved "https://registry.npmjs.org/@azure/core-lro/-/core-lro-1.0.3.tgz#1ddfb4ecdb81ce87b5f5d972ffe2acbbc46e524e" @@ -167,6 +196,14 @@ dependencies: "@azure/core-asynciterator-polyfill" "^1.0.0" +"@azure/core-tracing@1.0.0-preview.12": + version "1.0.0-preview.12" + resolved "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.0-preview.12.tgz#f53ff452c0742ad981c244f97d93d37ca2b5e139" + integrity sha512-nvo2Wc4EKZGN6eFu9n3U7OXmASmL8VxoPIH7xaD6OlQqi44bouF0YIi9ID5rEsKLiAU59IYx6M297nqWVMWPDg== + dependencies: + "@opentelemetry/api" "^1.0.0" + 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" @@ -177,25 +214,26 @@ tslib "^2.0.0" "@azure/identity@^1.2.2": - version "1.2.2" - resolved "https://registry.npmjs.org/@azure/identity/-/identity-1.2.2.tgz#00d673c8881778c55777fcc76e822b42466d3fc7" - integrity sha512-aYkeNXl52aEHW1iOZQJb3SC7Vvbu87f01iNT+pSVHwj09LpN9+gP/Lb9uoWy36Fgv9WlukM55LbjLSbb1Renqw== + version "1.4.0" + resolved "https://registry.npmjs.org/@azure/identity/-/identity-1.4.0.tgz#dfd2f641cf5815d1184acb5ef01ab6859e4d9eb4" + integrity sha512-nusX+L1qrCuQbRWQqPcgUCj07EvDVOgPVMnNS/cVtH8lfaGjWU6vdDJ49gROruh1jNjjZC0qpJBaM7OsK84zkw== dependencies: - "@azure/core-http" "^1.2.0" - "@azure/core-tracing" "1.0.0-preview.9" + "@azure/core-http" "^2.0.0" + "@azure/core-tracing" "1.0.0-preview.12" "@azure/logger" "^1.0.0" - "@azure/msal-node" "1.0.0-beta.3" - "@opentelemetry/api" "^0.10.2" + "@azure/msal-node" "1.0.0-beta.6" + "@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" + stoppable "^1.1.0" tslib "^2.0.0" uuid "^8.3.0" optionalDependencies: - keytar "^5.4.0" + keytar "^7.3.0" "@azure/logger@^1.0.0": version "1.0.1" @@ -204,10 +242,10 @@ dependencies: tslib "^2.0.0" -"@azure/msal-common@^2.1.0": - version "2.1.0" - resolved "https://registry.npmjs.org/@azure/msal-common/-/msal-common-2.1.0.tgz#a4bc17e254d6ec524016f13267947dd4ff4a624d" - integrity sha512-Y1Id+jG59S3eY2ZQQtUA/lxwbRcgjcWaiib9YX+SwV3zeRauKfEiZT7l3z+lwV+T+Sst20F6l1mJsfQcfE7CEQ== +"@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== dependencies: debug "^4.1.1" @@ -218,12 +256,12 @@ dependencies: debug "^4.1.1" -"@azure/msal-node@1.0.0-beta.3": - version "1.0.0-beta.3" - resolved "https://registry.npmjs.org/@azure/msal-node/-/msal-node-1.0.0-beta.3.tgz#c84c7948028b39e48b901f5fac35bdedcbc8772e" - integrity sha512-/KfYRfrsOIrZONvo/0Vi5umuqbPBtCWNtmRvkse64uI0C4CP/W4WXwRD42VMws/8LtKvr1I5rYlYgFzt5zDz/A== +"@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== dependencies: - "@azure/msal-common" "^2.1.0" + "@azure/msal-common" "^4.0.0" axios "^0.21.1" jsonwebtoken "^8.5.1" uuid "^8.3.0" @@ -3900,6 +3938,11 @@ dependencies: "@opentelemetry/context-base" "^0.10.2" +"@opentelemetry/api@^1.0.0": + version "1.0.1" + resolved "https://registry.npmjs.org/@opentelemetry/api/-/api-1.0.1.tgz#03c72f548431da5820a0c8864d1401e348e7e79f" + integrity sha512-H5Djcc2txGAINgf3TNaq4yFofYSIK3722PM89S/3R8FuI/eqi1UscajlXk7EBkG9s2pxss/q6SHlpturaavXaw== + "@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" @@ -16809,13 +16852,13 @@ 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@^5.4.0: - version "5.6.0" - resolved "https://registry.npmjs.org/keytar/-/keytar-5.6.0.tgz#7b5d4bd043d17211163640be6c4a27a49b12bb39" - integrity sha512-ueulhshHSGoryfRXaIvTj0BV1yB0KddBGhGoqCxSN9LR1Ks1GKuuCdVhF+2/YOs5fMl6MlTI9On1a4DHDXoTow== +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: - nan "2.14.1" - prebuild-install "5.3.3" + node-addon-api "^3.0.0" + prebuild-install "^6.0.0" keyv-memcache@^1.2.5: version "1.2.5" @@ -18396,6 +18439,11 @@ 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" @@ -18603,7 +18651,7 @@ named-placeholders@^1.1.2: dependencies: lru-cache "^4.1.3" -nan@2.14.1, nan@^2.12.1, nan@^2.14.0: +nan@^2.12.1, nan@^2.14.0: version "2.14.1" resolved "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01" integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw== @@ -18713,10 +18761,10 @@ no-case@^3.0.4: lower-case "^2.0.2" tslib "^2.0.3" -node-abi@^2.7.0: - version "2.19.3" - resolved "https://registry.npmjs.org/node-abi/-/node-abi-2.19.3.tgz#252f5dcab12dad1b5503b2d27eddd4733930282d" - integrity sha512-9xZrlyfvKhWme2EXFKQhZRp1yNWT/uI1luYPr3sFl+H4keYY4xR+1jO7mvTTijIsHf1M+QDe9uWuKeEpLInIlg== +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" @@ -18914,11 +18962,6 @@ nodemon@^2.0.2: undefsafe "^2.0.3" update-notifier "^4.1.0" -noop-logger@^0.1.1: - version "0.1.1" - resolved "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz#94a2b1633c4f1317553007d8966fd0e841b6a4c2" - integrity sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI= - "nopt@2 || 3": version "3.0.6" resolved "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" @@ -20773,26 +20816,24 @@ postgres-interval@^1.1.0: dependencies: xtend "^4.0.0" -prebuild-install@5.3.3: - version "5.3.3" - resolved "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.3.3.tgz#ef4052baac60d465f5ba6bf003c9c1de79b9da8e" - integrity sha512-GV+nsUXuPW2p8Zy7SarF/2W/oiK8bFQgJcncoJ0d7kRpekEA0ftChjfEaF9/Y+QJEc/wFR7RAEa8lYByuUIe2g== +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.0" - mkdirp "^0.5.1" + minimist "^1.2.3" + mkdirp-classic "^0.5.3" napi-build-utils "^1.0.1" - node-abi "^2.7.0" - noop-logger "^0.1.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" - which-pm-runs "^1.0.0" precond@0.2: version "0.2.3" @@ -24999,7 +25040,7 @@ tslib@^1.10.0, tslib@^1.11.1, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2, tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@~2.3.0: +tslib@^2, tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.2.0, tslib@~2.3.0: version "2.3.0" resolved "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e" integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg== @@ -26097,11 +26138,6 @@ which-module@^2.0.0: resolved "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= -which-pm-runs@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb" - integrity sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs= - which-pm@2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/which-pm/-/which-pm-2.0.0.tgz#8245609ecfe64bf751d0eef2f376d83bf1ddb7ae" From 232d75f697121cb64d7209312a4de659748808f8 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 21 Jul 2021 15:54:44 +0200 Subject: [PATCH 086/106] chore: reworking deps again Signed-off-by: blam --- packages/techdocs-common/package.json | 4 +- yarn.lock | 94 +++++++++++++++------------ 2 files changed, 56 insertions(+), 42 deletions(-) diff --git a/packages/techdocs-common/package.json b/packages/techdocs-common/package.json index 5240e527e7..efa12db78c 100644 --- a/packages/techdocs-common/package.json +++ b/packages/techdocs-common/package.json @@ -36,8 +36,8 @@ "url": "https://github.com/backstage/backstage/issues" }, "dependencies": { - "@azure/identity": "^1.2.2", - "@azure/storage-blob": "^12.4.0", + "@azure/identity": "^1.5.0", + "@azure/storage-blob": "^12.6.0", "@backstage/backend-common": "^0.8.6", "@backstage/catalog-model": "^0.9.0", "@backstage/config": "^0.1.5", diff --git a/yarn.lock b/yarn.lock index ffbb94d16d..0708683665 100644 --- a/yarn.lock +++ b/yarn.lock @@ -137,6 +137,18 @@ "@azure/abort-controller" "^1.0.0" tslib "^2.2.0" +"@azure/core-client@^1.0.0": + version "1.2.2" + resolved "https://registry.npmjs.org/@azure/core-client/-/core-client-1.2.2.tgz#29c781e5ccd4da968cea89d3b909ab573a3a7bec" + integrity sha512-VYFR2qiczjBrSfpQSbo5s8FJhXaJFz2tP01MOrpNJaOqnSNEKcY35I79b1Ty7s8qHGvc5/YMJ745l3B7abncFQ== + dependencies: + "@azure/abort-controller" "^1.0.0" + "@azure/core-asynciterator-polyfill" "^1.0.0" + "@azure/core-auth" "^1.3.0" + "@azure/core-rest-pipeline" "^1.1.0" + "@azure/core-tracing" "1.0.0-preview.12" + tslib "^2.2.0" + "@azure/core-http@^1.2.0": version "1.2.2" resolved "https://registry.npmjs.org/@azure/core-http/-/core-http-1.2.2.tgz#a6f7717184fd2657d3acabd1d64dfdc0bd531ce3" @@ -158,27 +170,6 @@ uuid "^8.3.0" xml2js "^0.4.19" -"@azure/core-http@^2.0.0": - version "2.0.0" - resolved "https://registry.npmjs.org/@azure/core-http/-/core-http-2.0.0.tgz#3decae3a80ec19d5d2fbe2cd21834858743ff348" - integrity sha512-VBOfUh0z9ZF1WVqrLCtiGWMjkKic171p6mLXRkJKu+p5wuQTb4cU3bPq7nB6UuGAK17LI7hnU0SzydlCQrBuOw== - dependencies: - "@azure/abort-controller" "^1.0.0" - "@azure/core-asynciterator-polyfill" "^1.0.0" - "@azure/core-auth" "^1.3.0" - "@azure/core-tracing" "1.0.0-preview.12" - "@azure/logger" "^1.0.0" - "@types/node-fetch" "^2.5.0" - "@types/tunnel" "^0.0.1" - form-data "^3.0.0" - node-fetch "^2.6.0" - process "^0.11.10" - tough-cookie "^4.0.0" - tslib "^2.2.0" - tunnel "^0.0.6" - uuid "^8.3.0" - xml2js "^0.4.19" - "@azure/core-lro@^1.0.2": version "1.0.3" resolved "https://registry.npmjs.org/@azure/core-lro/-/core-lro-1.0.3.tgz#1ddfb4ecdb81ce87b5f5d972ffe2acbbc46e524e" @@ -196,6 +187,30 @@ dependencies: "@azure/core-asynciterator-polyfill" "^1.0.0" +"@azure/core-rest-pipeline@^1.1.0": + version "1.1.1" + resolved "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.1.1.tgz#53b4278f5c417c3da9f133511064375362b68a12" + integrity sha512-ObF8iTEDXIG7/NlL28ni9bR3XLJwgm2S3GWO4aNW6CsTCFVoY9HMdbBtN7xOB+pUQwifehifXNnootbzzuwJnw== + dependencies: + "@azure/abort-controller" "^1.0.0" + "@azure/core-auth" "^1.3.0" + "@azure/core-tracing" "1.0.0-preview.12" + "@azure/logger" "^1.0.0" + form-data "^3.0.0" + http-proxy-agent "^4.0.1" + https-proxy-agent "^5.0.0" + tslib "^2.2.0" + uuid "^8.3.0" + +"@azure/core-tracing@1.0.0-preview.11": + version "1.0.0-preview.11" + resolved "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.0-preview.11.tgz#bdfb2ba73cd6c39b7d6c207b9522eb98e08b4ddd" + integrity sha512-frF0pJc9HTmKncVokhBxCqipjbql02DThQ1ZJ9wLi7SDMLdPAFyDI5xZNzX5guLz+/DtPkY+SGK2li9FIXqshQ== + dependencies: + "@opencensus/web-types" "0.0.7" + "@opentelemetry/api" "1.0.0-rc.0" + tslib "^2.0.0" + "@azure/core-tracing@1.0.0-preview.12": version "1.0.0-preview.12" resolved "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.0-preview.12.tgz#f53ff452c0742ad981c244f97d93d37ca2b5e139" @@ -213,12 +228,14 @@ "@opentelemetry/api" "^0.10.2" tslib "^2.0.0" -"@azure/identity@^1.2.2": - version "1.4.0" - resolved "https://registry.npmjs.org/@azure/identity/-/identity-1.4.0.tgz#dfd2f641cf5815d1184acb5ef01ab6859e4d9eb4" - integrity sha512-nusX+L1qrCuQbRWQqPcgUCj07EvDVOgPVMnNS/cVtH8lfaGjWU6vdDJ49gROruh1jNjjZC0qpJBaM7OsK84zkw== +"@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== dependencies: - "@azure/core-http" "^2.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/logger" "^1.0.0" "@azure/msal-node" "1.0.0-beta.6" @@ -242,14 +259,7 @@ 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== - dependencies: - debug "^4.1.1" - -"@azure/msal-common@^4.4.0": +"@azure/msal-common@^4.0.0", "@azure/msal-common@^4.4.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== @@ -276,18 +286,17 @@ jsonwebtoken "^8.5.1" uuid "^8.3.0" -"@azure/storage-blob@^12.4.0": - version "12.4.0" - resolved "https://registry.npmjs.org/@azure/storage-blob/-/storage-blob-12.4.0.tgz#7127ddd9f413105e2c3688691bc4c6245d0806b3" - integrity sha512-OnhVSoKD1HzBB79/rFzPbC4w9TdzFXeoOwkX+aIu3rb8qvN0VaqvUqZXSrBCyG2LcLyVkY4MPCJQBrmEUm9kvw== +"@azure/storage-blob@^12.6.0": + version "12.6.0" + resolved "https://registry.npmjs.org/@azure/storage-blob/-/storage-blob-12.6.0.tgz#9905d80e5f908a573cc65e1cb8302abc32818844" + integrity sha512-cAzsae+5ZdhugQfIT7o5SlVyF2Sc+HygZdPO41ZYdXklfGUyEt+5K4PyM5HQDc0MTVt6x7+waXcaAXT2eF9E6A== dependencies: "@azure/abort-controller" "^1.0.0" "@azure/core-http" "^1.2.0" "@azure/core-lro" "^1.0.2" "@azure/core-paging" "^1.1.1" - "@azure/core-tracing" "1.0.0-preview.9" + "@azure/core-tracing" "1.0.0-preview.11" "@azure/logger" "^1.0.0" - "@opentelemetry/api" "^0.10.2" events "^3.0.0" tslib "^2.0.0" @@ -3931,6 +3940,11 @@ resolved "https://registry.npmjs.org/@opencensus/web-types/-/web-types-0.0.7.tgz#4426de1fe5aa8f624db395d2152b902874f0570a" integrity sha512-xB+w7ZDAu3YBzqH44rCmG9/RlrOmFuDPt/bpf17eJr8eZSrLt7nc7LnWdxM9Mmoj/YKMHpxRg28txu3TcpiL+g== +"@opentelemetry/api@1.0.0-rc.0": + version "1.0.0-rc.0" + resolved "https://registry.npmjs.org/@opentelemetry/api/-/api-1.0.0-rc.0.tgz#0c7c3f5e1285f99cedb563d74ad1adb9822b5144" + integrity sha512-iXKByCMfrlO5S6Oh97BuM56tM2cIBB0XsL/vWF/AtJrJEKx4MC/Xdu0xDsGXMGcNWpqF7ujMsjjnp0+UHBwnDQ== + "@opentelemetry/api@^0.10.2": version "0.10.2" resolved "https://registry.npmjs.org/@opentelemetry/api/-/api-0.10.2.tgz#9647b881f3e1654089ff7ea59d587b2d35060654" From 2f1a0475221b31af8e0b4d26460fce2b0266d4fd Mon Sep 17 00:00:00 2001 From: Juan Lulkin Date: Wed, 21 Jul 2021 16:44:53 +0200 Subject: [PATCH 087/106] Align switches left and allows clicking row Signed-off-by: Juan Lulkin --- .../FeatureFlags/FeatureFlagsItem.tsx | 36 ++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/plugins/user-settings/src/components/FeatureFlags/FeatureFlagsItem.tsx b/plugins/user-settings/src/components/FeatureFlags/FeatureFlagsItem.tsx index 62861d99a9..893fa24fec 100644 --- a/plugins/user-settings/src/components/FeatureFlags/FeatureFlagsItem.tsx +++ b/plugins/user-settings/src/components/FeatureFlags/FeatureFlagsItem.tsx @@ -17,8 +17,10 @@ import React from 'react'; import { ListItem, + Divider, ListItemSecondaryAction, ListItemText, + ListItemIcon, Switch, Tooltip, } from '@material-ui/core'; @@ -31,20 +33,22 @@ type Props = { }; export const FlagItem = ({ flag, enabled, toggleHandler }: Props) => ( - - - - - toggleHandler(flag.name)} - name={flag.name} - /> - - - + <> + toggleHandler(flag.name)}> + + + toggleHandler(flag.name)} + name={flag.name} + /> + + + + + ); From b5953c1df36cf6029dcd55eb1ef29c18d8a2e193 Mon Sep 17 00:00:00 2001 From: Juan Lulkin Date: Wed, 21 Jul 2021 16:49:57 +0200 Subject: [PATCH 088/106] Adds changeset Signed-off-by: Juan Lulkin --- .changeset/late-taxis-smile.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/late-taxis-smile.md diff --git a/.changeset/late-taxis-smile.md b/.changeset/late-taxis-smile.md new file mode 100644 index 0000000000..80bab5fb54 --- /dev/null +++ b/.changeset/late-taxis-smile.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-user-settings': patch +--- + +Aligns switch left and allows clicking on rows From d7cee2ea00e49a801403c0119b8d3c05ba6ffe7b Mon Sep 17 00:00:00 2001 From: Juan Lulkin Date: Wed, 21 Jul 2021 16:55:43 +0200 Subject: [PATCH 089/106] Fix bubbling of events Signed-off-by: Juan Lulkin --- .../src/components/FeatureFlags/FeatureFlagsItem.tsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/plugins/user-settings/src/components/FeatureFlags/FeatureFlagsItem.tsx b/plugins/user-settings/src/components/FeatureFlags/FeatureFlagsItem.tsx index 893fa24fec..6c9e6c811c 100644 --- a/plugins/user-settings/src/components/FeatureFlags/FeatureFlagsItem.tsx +++ b/plugins/user-settings/src/components/FeatureFlags/FeatureFlagsItem.tsx @@ -37,12 +37,7 @@ export const FlagItem = ({ flag, enabled, toggleHandler }: Props) => ( toggleHandler(flag.name)}> - toggleHandler(flag.name)} - name={flag.name} - /> + Date: Wed, 21 Jul 2021 16:56:58 +0200 Subject: [PATCH 090/106] Remove unused components Signed-off-by: Juan Lulkin --- .../src/components/FeatureFlags/FeatureFlagsItem.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/plugins/user-settings/src/components/FeatureFlags/FeatureFlagsItem.tsx b/plugins/user-settings/src/components/FeatureFlags/FeatureFlagsItem.tsx index 6c9e6c811c..d32f2c27f7 100644 --- a/plugins/user-settings/src/components/FeatureFlags/FeatureFlagsItem.tsx +++ b/plugins/user-settings/src/components/FeatureFlags/FeatureFlagsItem.tsx @@ -17,8 +17,6 @@ import React from 'react'; import { ListItem, - Divider, - ListItemSecondaryAction, ListItemText, ListItemIcon, Switch, From c914db284f948c69d675a884b69a1c2c6663804f Mon Sep 17 00:00:00 2001 From: Andrea Falzetti Date: Wed, 14 Jul 2021 17:17:39 +0100 Subject: [PATCH 091/106] feat(scaffolder): add gha dispatch workflow action Signed-off-by: Andrea Falzetti --- .../builtin/ci/githubActionsDispatch.test.ts | 134 ++++++++++++++++++ .../builtin/ci/githubActionsDispatch.ts | 124 ++++++++++++++++ .../scaffolder/actions/builtin/ci/index.ts | 17 +++ .../actions/builtin/createBuiltinActions.ts | 5 + 4 files changed, 280 insertions(+) create mode 100644 plugins/scaffolder-backend/src/scaffolder/actions/builtin/ci/githubActionsDispatch.test.ts create mode 100644 plugins/scaffolder-backend/src/scaffolder/actions/builtin/ci/githubActionsDispatch.ts create mode 100644 plugins/scaffolder-backend/src/scaffolder/actions/builtin/ci/index.ts diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/ci/githubActionsDispatch.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/ci/githubActionsDispatch.test.ts new file mode 100644 index 0000000000..606881e47a --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/ci/githubActionsDispatch.test.ts @@ -0,0 +1,134 @@ +/* + * 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. + */ + +jest.mock('@octokit/rest'); +jest.mock('@backstage/integration'); + +import { createGithubActionsDispatchAction } from './githubActionsDispatch'; +import { ScmIntegrations } from '@backstage/integration'; +import { ConfigReader } from '@backstage/config'; +import { getVoidLogger } from '@backstage/backend-common'; +import { PassThrough } from 'stream'; +// import { when } from 'jest-when'; + +describe('ci:github-actions-dispatch', () => { + const config = new ConfigReader({ + integrations: { + github: [ + { host: 'github.com', token: 'tokenlols' }, + { host: 'ghe.github.com' }, + ], + }, + }); + + const integrations = ScmIntegrations.fromConfig(config); + const action = createGithubActionsDispatchAction({ integrations, config }); + + const mockContext = { + input: { + owner: 'a-owner', + repoUrl: 'github.com?repo=repo&owner=owner', + repoName: 'repository-name', + workflowId: 'a-workflow-id', + branchOrTagName: 'main', + }, + workspacePath: 'lol', + logger: getVoidLogger(), + logStream: new PassThrough(), + output: jest.fn(), + createTemporaryDirectory: jest.fn(), + }; + + const { mockGithubClient } = require('@octokit/rest'); + mockGithubClient.rest = { + actions: { + createWorkflowDispatch: jest.fn(), + }, + }; + + beforeEach(() => { + jest.resetAllMocks(); + }); + + // it('should throw if there is no integration config provided', async () => { + // const actionWithNoIntegrations = createGithubActionsDispatchAction({ + // integrations: { + // ...integrations, + // github: { + // list: () => [], + // byHost: jest.fn(), + // byUrl: jest.fn(), + // }, + // }, + // config, + // }); + // await expect(actionWithNoIntegrations.handler(mockContext)).rejects.toThrow( + // /No matching integration configuration/, + // ); + // }); + + // it('should throw if there is no token in the integration config that is returned', async () => { + // const mockedProvider = jest.fn(); + // const mockedIntegrationConfig = jest.fn(); + // GithubCredentialsProvider.create.mockReturnOnce(mockedProvider); + // const mockedArray = [ + // { + // config: { + // host: 'github.com', + // }, + // }, + // ] as GitHubIntegration[]; + // const integrations1 = { + // github: { + // list: () => mockedArray, + // byHost: mockedIntegrationConfig, + // byUrl: jest.fn(), + // }, + // } as ScmIntegrationRegistry; + // const actionWithNoIntegrations = createGithubActionsDispatchAction({ + // integrations: integrations1, + // config, + // }); + // mockedProvider.mockResolvedValue(undefined); + + // await expect(actionWithNoIntegrations.handler(mockContext)).rejects.toThrow( + // /No token available for host/, + // ); + // }); + + it('should call the githubApis for creating WorkflowDispatch', async () => { + mockGithubClient.rest.actions.createWorkflowDispatch.mockResolvedValue({ + message: 'Success', + }); + const owner = 'dx'; + const repoName = 'test1'; + const workflowId = 'dispatch_workflow'; + const branchOrTagName = 'main'; + const ctx = Object.assign({}, mockContext, { + input: { owner, repoName, workflowId, branchOrTagName }, + }); + await action.handler(ctx); + + expect( + mockGithubClient.rest.actions.createWorkflowDispatch, + ).toHaveBeenCalledWith({ + owner, + repo: repoName, + workflow_id: workflowId, + ref: branchOrTagName, + }); + }); +}); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/ci/githubActionsDispatch.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/ci/githubActionsDispatch.ts new file mode 100644 index 0000000000..fbeeb4c9ac --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/ci/githubActionsDispatch.ts @@ -0,0 +1,124 @@ +/* + * 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 { InputError } from '@backstage/errors'; +import { + GithubCredentialsProvider, + ScmIntegrationRegistry, +} from '@backstage/integration'; +import { Octokit } from '@octokit/rest'; +import { createTemplateAction } from '../../createTemplateAction'; +import { Config } from '@backstage/config'; + +const host = 'github.com'; + +export function createGithubActionsDispatchAction(options: { + integrations: ScmIntegrationRegistry; + config: Config; +}) { + const { integrations } = options; + + const credentialsProviders = new Map( + integrations.github.list().map(integration => { + const provider = GithubCredentialsProvider.create(integration.config); + return [integration.config.host, provider]; + }), + ); + + return createTemplateAction<{ + owner: string; + repoName: string; + workflowId: string; + branchOrTagName: string; + }>({ + id: 'ci:github-actions-dispatch', + description: + 'Dispatches a GitHub Action workflow for a given branch or tag', + schema: { + input: { + type: 'object', + required: ['owner', 'repoName', 'workflowId', 'branchOrTagName'], + properties: { + owner: { + title: 'Repository Owner', + description: 'GitHub Org or User name owner of the repository', + type: 'string', + }, + repoName: { + title: 'Repository Name', + description: `Repo`, + type: 'string', + }, + workflowId: { + title: 'Workflow ID', + description: 'The GitHub Action Workflow filename', + type: 'string', + }, + branchOrTagName: { + title: 'Branch or Tag name', + description: + 'The git branch or tag name used to dispatch the workflow', + type: 'string', + }, + }, + }, + }, + async handler(ctx) { + const { owner, repoName, workflowId, branchOrTagName } = ctx.input; + + ctx.logger.info( + `Dispatching workflow ${workflowId} for repo ${owner}/${repoName} on ${branchOrTagName}`, + ); + + const credentialsProvider = credentialsProviders.get(host); + const integrationConfig = integrations.github.byHost(host); + + if (!credentialsProvider || !integrationConfig) { + throw new InputError( + `No matching integration configuration for host ${host}, please check your integrations config`, + ); + } + + // TODO(blam): Consider changing this API to have owner, repo interface instead of URL as the it's + // needless to create URL and then parse again the other side. + const { token } = await credentialsProvider.getCredentials({ + url: `https://${host}/${encodeURIComponent(owner)}/${encodeURIComponent( + repoName, + )}`, + }); + + if (!token) { + throw new InputError( + `No token available for host: ${host}, with owner ${owner}, and repo ${repoName}`, + ); + } + + const client = new Octokit({ + auth: ctx.token ?? token, + baseUrl: integrationConfig.config.apiBaseUrl, + previews: ['nebula-preview'], + }); + + await client.rest.actions.createWorkflowDispatch({ + owner, + repo, + workflow_id: workflowId, + ref: branchOrTagName, + }); + + ctx.logger.info(`Workflow ${workflowId} dispatched successfully`); + }, + }); +} diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/ci/index.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/ci/index.ts new file mode 100644 index 0000000000..50abd2c3dc --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/ci/index.ts @@ -0,0 +1,17 @@ +/* + * 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. + */ + +export { createGithubActionsDispatchAction } from './githubActionsDispatch'; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts index ae5a7d0697..48c13436fa 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts @@ -37,6 +37,7 @@ import { createPublishGithubPullRequestAction, createPublishGitlabAction, } from './publish'; +import { createGithubActionsDispatchAction } from './ci'; export const createBuiltinActions = (options: { reader: UrlReader; @@ -91,5 +92,9 @@ export const createBuiltinActions = (options: { createCatalogWriteAction(), createFilesystemDeleteAction(), createFilesystemRenameAction(), + createGithubActionsDispatchAction({ + integrations, + config, + }), ]; }; From a370ecab04a59c4a50ba6e92b9d321532a53e240 Mon Sep 17 00:00:00 2001 From: Yogesh Lonkar Date: Thu, 15 Jul 2021 13:17:05 +0200 Subject: [PATCH 092/106] fix unit test Signed-off-by: Yogesh Lonkar Signed-off-by: Andrea Falzetti --- .../builtin/ci/githubActionsDispatch.test.ts | 157 +++++++++++------- .../builtin/ci/githubActionsDispatch.ts | 3 +- 2 files changed, 97 insertions(+), 63 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/ci/githubActionsDispatch.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/ci/githubActionsDispatch.test.ts index 606881e47a..6564671664 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/ci/githubActionsDispatch.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/ci/githubActionsDispatch.test.ts @@ -13,16 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +// @ts-nocheck -jest.mock('@octokit/rest'); jest.mock('@backstage/integration'); +jest.mock('@octokit/rest', () => ({ Octokit: jest.fn() })); import { createGithubActionsDispatchAction } from './githubActionsDispatch'; -import { ScmIntegrations } from '@backstage/integration'; +import { + ScmIntegrations, + GithubCredentialsProvider, +} from '@backstage/integration'; import { ConfigReader } from '@backstage/config'; import { getVoidLogger } from '@backstage/backend-common'; import { PassThrough } from 'stream'; -// import { when } from 'jest-when'; + +import { Octokit } from '@octokit/rest'; describe('ci:github-actions-dispatch', () => { const config = new ConfigReader({ @@ -34,9 +39,6 @@ describe('ci:github-actions-dispatch', () => { }, }); - const integrations = ScmIntegrations.fromConfig(config); - const action = createGithubActionsDispatchAction({ integrations, config }); - const mockContext = { input: { owner: 'a-owner', @@ -52,67 +54,101 @@ describe('ci:github-actions-dispatch', () => { createTemporaryDirectory: jest.fn(), }; - const { mockGithubClient } = require('@octokit/rest'); - mockGithubClient.rest = { - actions: { - createWorkflowDispatch: jest.fn(), - }, - }; - beforeEach(() => { jest.resetAllMocks(); }); - // it('should throw if there is no integration config provided', async () => { - // const actionWithNoIntegrations = createGithubActionsDispatchAction({ - // integrations: { - // ...integrations, - // github: { - // list: () => [], - // byHost: jest.fn(), - // byUrl: jest.fn(), - // }, - // }, - // config, - // }); - // await expect(actionWithNoIntegrations.handler(mockContext)).rejects.toThrow( - // /No matching integration configuration/, - // ); - // }); + it('should throw if there is no integration config provided', async () => { + const integrations = { + github: { + list: () => [], + byHost: jest.fn(), + byUrl: jest.fn(), + }, + } as ScmIntegrations; + const actionWithNoIntegrations = createGithubActionsDispatchAction({ + integrations, + config, + }); + await expect(actionWithNoIntegrations.handler(mockContext)).rejects.toThrow( + /No matching integration configuration/, + ); + }); - // it('should throw if there is no token in the integration config that is returned', async () => { - // const mockedProvider = jest.fn(); - // const mockedIntegrationConfig = jest.fn(); - // GithubCredentialsProvider.create.mockReturnOnce(mockedProvider); - // const mockedArray = [ - // { - // config: { - // host: 'github.com', - // }, - // }, - // ] as GitHubIntegration[]; - // const integrations1 = { - // github: { - // list: () => mockedArray, - // byHost: mockedIntegrationConfig, - // byUrl: jest.fn(), - // }, - // } as ScmIntegrationRegistry; - // const actionWithNoIntegrations = createGithubActionsDispatchAction({ - // integrations: integrations1, - // config, - // }); - // mockedProvider.mockResolvedValue(undefined); - - // await expect(actionWithNoIntegrations.handler(mockContext)).rejects.toThrow( - // /No token available for host/, - // ); - // }); + it('should throw if no token is provided', async () => { + const integrations = { + github: { + list: () => [ + { + config: { + host: 'github.com', + }, + }, + ], + byHost: () => ({ + config: { + apiBaseUrl: 'https://api.github.com', + }, + }), + byUrl: jest.fn(), + }, + } as ScmIntegrations; + const mockedCredentialsProvider = { + getCredentials: jest.fn(), + }; + mockedCredentialsProvider.getCredentials.mockResolvedValue({}); + GithubCredentialsProvider.create.mockReturnValueOnce( + mockedCredentialsProvider, + ); + const actionWithNoIntegrations = createGithubActionsDispatchAction({ + integrations, + config, + }); + await expect(actionWithNoIntegrations.handler(mockContext)).rejects.toThrow( + /No token available for host/, + ); + }); it('should call the githubApis for creating WorkflowDispatch', async () => { - mockGithubClient.rest.actions.createWorkflowDispatch.mockResolvedValue({ + const integrations = { + github: { + list: () => [ + { + config: { + host: 'github.com', + }, + }, + ], + byHost: () => ({ + config: { + apiBaseUrl: 'https://api.github.com', + }, + }), + byUrl: jest.fn(), + }, + } as ScmIntegrations; + const mockedCredentialsProvider = { + getCredentials: jest.fn(), + }; + mockedCredentialsProvider.getCredentials.mockResolvedValue({ + token: 'test-token', + }); + GithubCredentialsProvider.create.mockReturnValueOnce( + mockedCredentialsProvider, + ); + const mockedCreateWorkflowDispatch = jest.fn(); + mockedCreateWorkflowDispatch.mockResolvedValue({ message: 'Success', }); + Octokit.mockImplementation(() => { + return { + rest: { + actions: { + createWorkflowDispatch: mockedCreateWorkflowDispatch, + }, + }, + }; + }); const owner = 'dx'; const repoName = 'test1'; const workflowId = 'dispatch_workflow'; @@ -120,11 +156,10 @@ describe('ci:github-actions-dispatch', () => { const ctx = Object.assign({}, mockContext, { input: { owner, repoName, workflowId, branchOrTagName }, }); + const action = createGithubActionsDispatchAction({ integrations, config }); await action.handler(ctx); - expect( - mockGithubClient.rest.actions.createWorkflowDispatch, - ).toHaveBeenCalledWith({ + expect(mockedCreateWorkflowDispatch).toHaveBeenCalledWith({ owner, repo: repoName, workflow_id: workflowId, diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/ci/githubActionsDispatch.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/ci/githubActionsDispatch.ts index fbeeb4c9ac..e720695b13 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/ci/githubActionsDispatch.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/ci/githubActionsDispatch.ts @@ -104,7 +104,6 @@ export function createGithubActionsDispatchAction(options: { `No token available for host: ${host}, with owner ${owner}, and repo ${repoName}`, ); } - const client = new Octokit({ auth: ctx.token ?? token, baseUrl: integrationConfig.config.apiBaseUrl, @@ -113,7 +112,7 @@ export function createGithubActionsDispatchAction(options: { await client.rest.actions.createWorkflowDispatch({ owner, - repo, + repo: repoName, workflow_id: workflowId, ref: branchOrTagName, }); From c73f53bc2f0eeec75e28c7633ce1b38421e3242f Mon Sep 17 00:00:00 2001 From: Andrea Falzetti Date: Thu, 15 Jul 2021 15:29:30 +0100 Subject: [PATCH 093/106] docs: add changeset Signed-off-by: Andrea Falzetti --- .changeset/spotty-actors-invite.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/spotty-actors-invite.md diff --git a/.changeset/spotty-actors-invite.md b/.changeset/spotty-actors-invite.md new file mode 100644 index 0000000000..96f179c475 --- /dev/null +++ b/.changeset/spotty-actors-invite.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +--- + +Add new built-in action ci:github-actions-dispatch From 2ca8855b0d5257f903e63ed527f889f50cb69633 Mon Sep 17 00:00:00 2001 From: Andrea Falzetti Date: Fri, 16 Jul 2021 09:31:36 +0100 Subject: [PATCH 094/106] fix: remove ctx.token Signed-off-by: Andrea Falzetti --- .../src/scaffolder/actions/builtin/ci/githubActionsDispatch.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/ci/githubActionsDispatch.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/ci/githubActionsDispatch.ts index e720695b13..99e745f852 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/ci/githubActionsDispatch.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/ci/githubActionsDispatch.ts @@ -105,7 +105,7 @@ export function createGithubActionsDispatchAction(options: { ); } const client = new Octokit({ - auth: ctx.token ?? token, + auth: token, baseUrl: integrationConfig.config.apiBaseUrl, previews: ['nebula-preview'], }); From f928a8f9bfcf1ecd84404ca2c0d62ab7b5be8fc4 Mon Sep 17 00:00:00 2001 From: Andrea Falzetti Date: Fri, 16 Jul 2021 09:44:53 +0100 Subject: [PATCH 095/106] chore: remove comment Signed-off-by: Andrea Falzetti --- .../src/scaffolder/actions/builtin/ci/githubActionsDispatch.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/ci/githubActionsDispatch.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/ci/githubActionsDispatch.ts index 99e745f852..d7a5a26ccb 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/ci/githubActionsDispatch.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/ci/githubActionsDispatch.ts @@ -91,8 +91,6 @@ export function createGithubActionsDispatchAction(options: { ); } - // TODO(blam): Consider changing this API to have owner, repo interface instead of URL as the it's - // needless to create URL and then parse again the other side. const { token } = await credentialsProvider.getCredentials({ url: `https://${host}/${encodeURIComponent(owner)}/${encodeURIComponent( repoName, @@ -104,6 +102,7 @@ export function createGithubActionsDispatchAction(options: { `No token available for host: ${host}, with owner ${owner}, and repo ${repoName}`, ); } + const client = new Octokit({ auth: token, baseUrl: integrationConfig.config.apiBaseUrl, From 8fb0874dd44c1098d0120fb9e93bb26e9995f250 Mon Sep 17 00:00:00 2001 From: Andrea Falzetti Date: Wed, 21 Jul 2021 16:50:01 +0100 Subject: [PATCH 096/106] refactor: use repoUrl Signed-off-by: Andrea Falzetti --- .../__mocks__/@octokit/rest/index.ts | 5 + .../builtin/ci/githubActionsDispatch.test.ts | 169 ------------------ .../actions/builtin/createBuiltinActions.ts | 3 +- .../github/githubActionsDispatch.test.ts | 117 ++++++++++++ .../{ci => github}/githubActionsDispatch.ts | 35 ++-- .../actions/builtin/{ci => github}/index.ts | 0 .../src/scaffolder/actions/builtin/index.ts | 2 +- 7 files changed, 138 insertions(+), 193 deletions(-) delete mode 100644 plugins/scaffolder-backend/src/scaffolder/actions/builtin/ci/githubActionsDispatch.test.ts create mode 100644 plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubActionsDispatch.test.ts rename plugins/scaffolder-backend/src/scaffolder/actions/builtin/{ci => github}/githubActionsDispatch.ts (80%) rename plugins/scaffolder-backend/src/scaffolder/actions/builtin/{ci => github}/index.ts (100%) diff --git a/plugins/scaffolder-backend/src/scaffolder/__mocks__/@octokit/rest/index.ts b/plugins/scaffolder-backend/src/scaffolder/__mocks__/@octokit/rest/index.ts index 75cdfc208f..7505c3a30e 100644 --- a/plugins/scaffolder-backend/src/scaffolder/__mocks__/@octokit/rest/index.ts +++ b/plugins/scaffolder-backend/src/scaffolder/__mocks__/@octokit/rest/index.ts @@ -15,6 +15,11 @@ */ export const mockGithubClient = { + rest: { + actions: { + createWorkflowDispatch: jest.fn(), + }, + }, repos: { createInOrg: jest.fn(), createForAuthenticatedUser: jest.fn(), diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/ci/githubActionsDispatch.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/ci/githubActionsDispatch.test.ts deleted file mode 100644 index 6564671664..0000000000 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/ci/githubActionsDispatch.test.ts +++ /dev/null @@ -1,169 +0,0 @@ -/* - * 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. - */ -// @ts-nocheck - -jest.mock('@backstage/integration'); -jest.mock('@octokit/rest', () => ({ Octokit: jest.fn() })); - -import { createGithubActionsDispatchAction } from './githubActionsDispatch'; -import { - ScmIntegrations, - GithubCredentialsProvider, -} from '@backstage/integration'; -import { ConfigReader } from '@backstage/config'; -import { getVoidLogger } from '@backstage/backend-common'; -import { PassThrough } from 'stream'; - -import { Octokit } from '@octokit/rest'; - -describe('ci:github-actions-dispatch', () => { - const config = new ConfigReader({ - integrations: { - github: [ - { host: 'github.com', token: 'tokenlols' }, - { host: 'ghe.github.com' }, - ], - }, - }); - - const mockContext = { - input: { - owner: 'a-owner', - repoUrl: 'github.com?repo=repo&owner=owner', - repoName: 'repository-name', - workflowId: 'a-workflow-id', - branchOrTagName: 'main', - }, - workspacePath: 'lol', - logger: getVoidLogger(), - logStream: new PassThrough(), - output: jest.fn(), - createTemporaryDirectory: jest.fn(), - }; - - beforeEach(() => { - jest.resetAllMocks(); - }); - - it('should throw if there is no integration config provided', async () => { - const integrations = { - github: { - list: () => [], - byHost: jest.fn(), - byUrl: jest.fn(), - }, - } as ScmIntegrations; - const actionWithNoIntegrations = createGithubActionsDispatchAction({ - integrations, - config, - }); - await expect(actionWithNoIntegrations.handler(mockContext)).rejects.toThrow( - /No matching integration configuration/, - ); - }); - - it('should throw if no token is provided', async () => { - const integrations = { - github: { - list: () => [ - { - config: { - host: 'github.com', - }, - }, - ], - byHost: () => ({ - config: { - apiBaseUrl: 'https://api.github.com', - }, - }), - byUrl: jest.fn(), - }, - } as ScmIntegrations; - const mockedCredentialsProvider = { - getCredentials: jest.fn(), - }; - mockedCredentialsProvider.getCredentials.mockResolvedValue({}); - GithubCredentialsProvider.create.mockReturnValueOnce( - mockedCredentialsProvider, - ); - const actionWithNoIntegrations = createGithubActionsDispatchAction({ - integrations, - config, - }); - await expect(actionWithNoIntegrations.handler(mockContext)).rejects.toThrow( - /No token available for host/, - ); - }); - - it('should call the githubApis for creating WorkflowDispatch', async () => { - const integrations = { - github: { - list: () => [ - { - config: { - host: 'github.com', - }, - }, - ], - byHost: () => ({ - config: { - apiBaseUrl: 'https://api.github.com', - }, - }), - byUrl: jest.fn(), - }, - } as ScmIntegrations; - const mockedCredentialsProvider = { - getCredentials: jest.fn(), - }; - mockedCredentialsProvider.getCredentials.mockResolvedValue({ - token: 'test-token', - }); - GithubCredentialsProvider.create.mockReturnValueOnce( - mockedCredentialsProvider, - ); - const mockedCreateWorkflowDispatch = jest.fn(); - mockedCreateWorkflowDispatch.mockResolvedValue({ - message: 'Success', - }); - Octokit.mockImplementation(() => { - return { - rest: { - actions: { - createWorkflowDispatch: mockedCreateWorkflowDispatch, - }, - }, - }; - }); - const owner = 'dx'; - const repoName = 'test1'; - const workflowId = 'dispatch_workflow'; - const branchOrTagName = 'main'; - const ctx = Object.assign({}, mockContext, { - input: { owner, repoName, workflowId, branchOrTagName }, - }); - const action = createGithubActionsDispatchAction({ integrations, config }); - await action.handler(ctx); - - expect(mockedCreateWorkflowDispatch).toHaveBeenCalledWith({ - owner, - repo: repoName, - workflow_id: workflowId, - ref: branchOrTagName, - }); - }); -}); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts index 48c13436fa..b3d1460270 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts @@ -37,7 +37,7 @@ import { createPublishGithubPullRequestAction, createPublishGitlabAction, } from './publish'; -import { createGithubActionsDispatchAction } from './ci'; +import { createGithubActionsDispatchAction } from './github'; export const createBuiltinActions = (options: { reader: UrlReader; @@ -94,7 +94,6 @@ export const createBuiltinActions = (options: { createFilesystemRenameAction(), createGithubActionsDispatchAction({ integrations, - config, }), ]; }; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubActionsDispatch.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubActionsDispatch.test.ts new file mode 100644 index 0000000000..29e4338a39 --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubActionsDispatch.test.ts @@ -0,0 +1,117 @@ +/* + * 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. + */ + +jest.mock('@octokit/rest'); + +import { createGithubActionsDispatchAction } from './githubActionsDispatch'; +import { ScmIntegrations } from '@backstage/integration'; +import { ConfigReader } from '@backstage/config'; +import { getVoidLogger } from '@backstage/backend-common'; +import { PassThrough } from 'stream'; + +describe('github:actions:dispatch', () => { + const config = new ConfigReader({ + integrations: { + github: [ + { host: 'github.com', token: 'tokenlols' }, + { host: 'ghe.github.com' }, + ], + }, + }); + + const integrations = ScmIntegrations.fromConfig(config); + const action = createGithubActionsDispatchAction({ integrations }); + + const mockContext = { + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + workflowId: 'a-workflow-id', + branchOrTagName: 'main', + }, + workspacePath: 'lol', + logger: getVoidLogger(), + logStream: new PassThrough(), + output: jest.fn(), + createTemporaryDirectory: jest.fn(), + }; + + const { mockGithubClient } = require('@octokit/rest'); + + beforeEach(() => { + jest.resetAllMocks(); + }); + + it('should throw an error when the repoUrl is not well formed', async () => { + await expect( + action.handler({ + ...mockContext, + input: { repoUrl: 'github.com?repo=bob' }, + }), + ).rejects.toThrow(/missing owner/); + + await expect( + action.handler({ + ...mockContext, + input: { repoUrl: 'github.com?owner=owner' }, + }), + ).rejects.toThrow(/missing repo/); + }); + + it('should throw if there is no integration config provided', async () => { + await expect( + action.handler({ + ...mockContext, + input: { repoUrl: 'missing.com?repo=bob&owner=owner' }, + }), + ).rejects.toThrow(/No matching integration configuration/); + }); + + it('should throw if there is no token in the integration config that is returned', async () => { + await expect( + action.handler({ + ...mockContext, + input: { + repoUrl: 'ghe.github.com?repo=bob&owner=owner', + }, + }), + ).rejects.toThrow(/No token available for host/); + }); + + it('should call the githubApis for creating WorkflowDispatch', async () => { + mockGithubClient.rest.actions.createWorkflowDispatch.mockResolvedValue({ + data: { + foo: 'bar', + }, + }); + + const repoUrl = 'github.com?repo=repo&owner=owner'; + const workflowId = 'dispatch_workflow'; + const branchOrTagName = 'main'; + const ctx = Object.assign({}, mockContext, { + input: { repoUrl, workflowId, branchOrTagName }, + }); + await action.handler(ctx); + + expect( + mockGithubClient.rest.actions.createWorkflowDispatch, + ).toHaveBeenCalledWith({ + owner: 'owner', + repo: 'repo', + workflow_id: workflowId, + ref: branchOrTagName, + }); + }); +}); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/ci/githubActionsDispatch.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubActionsDispatch.ts similarity index 80% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/ci/githubActionsDispatch.ts rename to plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubActionsDispatch.ts index d7a5a26ccb..7f48a155ed 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/ci/githubActionsDispatch.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubActionsDispatch.ts @@ -19,14 +19,11 @@ import { ScmIntegrationRegistry, } from '@backstage/integration'; import { Octokit } from '@octokit/rest'; +import { parseRepoUrl } from '../publish/util'; import { createTemplateAction } from '../../createTemplateAction'; -import { Config } from '@backstage/config'; - -const host = 'github.com'; export function createGithubActionsDispatchAction(options: { integrations: ScmIntegrationRegistry; - config: Config; }) { const { integrations } = options; @@ -38,27 +35,21 @@ export function createGithubActionsDispatchAction(options: { ); return createTemplateAction<{ - owner: string; - repoName: string; + repoUrl: string; workflowId: string; branchOrTagName: string; }>({ - id: 'ci:github-actions-dispatch', + id: 'github:actions:dispatch', description: 'Dispatches a GitHub Action workflow for a given branch or tag', schema: { input: { type: 'object', - required: ['owner', 'repoName', 'workflowId', 'branchOrTagName'], + required: ['repoUrl', 'workflowId', 'branchOrTagName'], properties: { - owner: { - title: 'Repository Owner', - description: 'GitHub Org or User name owner of the repository', - type: 'string', - }, - repoName: { - title: 'Repository Name', - description: `Repo`, + repoUrl: { + title: 'Repository Location', + description: `Accepts the format 'github.com?repo=reponame&owner=owner' where 'reponame' is the new repository name and 'owner' is an organization or username`, type: 'string', }, workflowId: { @@ -76,10 +67,12 @@ export function createGithubActionsDispatchAction(options: { }, }, async handler(ctx) { - const { owner, repoName, workflowId, branchOrTagName } = ctx.input; + const { repoUrl, workflowId, branchOrTagName } = ctx.input; + + const { owner, repo, host } = parseRepoUrl(repoUrl); ctx.logger.info( - `Dispatching workflow ${workflowId} for repo ${owner}/${repoName} on ${branchOrTagName}`, + `Dispatching workflow ${workflowId} for repo ${repoUrl} on ${branchOrTagName}`, ); const credentialsProvider = credentialsProviders.get(host); @@ -93,13 +86,13 @@ export function createGithubActionsDispatchAction(options: { const { token } = await credentialsProvider.getCredentials({ url: `https://${host}/${encodeURIComponent(owner)}/${encodeURIComponent( - repoName, + repo, )}`, }); if (!token) { throw new InputError( - `No token available for host: ${host}, with owner ${owner}, and repo ${repoName}`, + `No token available for host: ${host}, with owner ${owner}, and repo ${repo}`, ); } @@ -111,7 +104,7 @@ export function createGithubActionsDispatchAction(options: { await client.rest.actions.createWorkflowDispatch({ owner, - repo: repoName, + repo, workflow_id: workflowId, ref: branchOrTagName, }); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/ci/index.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/index.ts similarity index 100% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/ci/index.ts rename to plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/index.ts diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/index.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/index.ts index 2a583f369a..02c15abab1 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/index.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/index.ts @@ -20,6 +20,6 @@ export * from './debug'; export * from './fetch'; export * from './filesystem'; export * from './publish'; - export { createFetchCookiecutterAction } from '@backstage/plugin-scaffolder-backend-module-cookiecutter'; +export * from './github'; export { runCommand } from './helpers'; From 8db7ea4c2fde31dacb31240fbae2335c43760da1 Mon Sep 17 00:00:00 2001 From: Andrea Falzetti Date: Wed, 21 Jul 2021 17:14:09 +0100 Subject: [PATCH 097/106] docs: add api-reports Signed-off-by: Andrea Falzetti --- plugins/scaffolder-backend/api-report.md | 7 +++++++ .../src/scaffolder/actions/builtin/index.ts | 3 ++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/plugins/scaffolder-backend/api-report.md b/plugins/scaffolder-backend/api-report.md index 0e7a25534c..baf85bce63 100644 --- a/plugins/scaffolder-backend/api-report.md +++ b/plugins/scaffolder-backend/api-report.md @@ -107,6 +107,13 @@ export const createFilesystemDeleteAction: () => TemplateAction; // @public (undocumented) export const createFilesystemRenameAction: () => TemplateAction; +// Warning: (ae-missing-release-tag) "createGithubActionsDispatchAction" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export function createGithubActionsDispatchAction(options: { + integrations: ScmIntegrationRegistry; +}): TemplateAction; + // Warning: (ae-missing-release-tag) "createPublishAzureAction" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/index.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/index.ts index 02c15abab1..2265f9b4f7 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/index.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/index.ts @@ -20,6 +20,7 @@ export * from './debug'; export * from './fetch'; export * from './filesystem'; export * from './publish'; -export { createFetchCookiecutterAction } from '@backstage/plugin-scaffolder-backend-module-cookiecutter'; export * from './github'; + +export { createFetchCookiecutterAction } from '@backstage/plugin-scaffolder-backend-module-cookiecutter'; export { runCommand } from './helpers'; From bd81f07c17964a9ba88a078e0613f19dfb1e5cbe Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 21 Jul 2021 19:57:50 +0200 Subject: [PATCH 098/106] chore: fixing yarn.lock Signed-off-by: blam --- packages/techdocs-common/package.json | 2 +- yarn.lock | 26 +++++++++++--------------- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/packages/techdocs-common/package.json b/packages/techdocs-common/package.json index efa12db78c..3fac9df64f 100644 --- a/packages/techdocs-common/package.json +++ b/packages/techdocs-common/package.json @@ -37,7 +37,7 @@ }, "dependencies": { "@azure/identity": "^1.5.0", - "@azure/storage-blob": "^12.6.0", + "@azure/storage-blob": "^12.5.0", "@backstage/backend-common": "^0.8.6", "@backstage/catalog-model": "^0.9.0", "@backstage/config": "^0.1.5", diff --git a/yarn.lock b/yarn.lock index 0708683665..f95dffc3c3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -202,13 +202,13 @@ tslib "^2.2.0" uuid "^8.3.0" -"@azure/core-tracing@1.0.0-preview.11": - version "1.0.0-preview.11" - resolved "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.0-preview.11.tgz#bdfb2ba73cd6c39b7d6c207b9522eb98e08b4ddd" - integrity sha512-frF0pJc9HTmKncVokhBxCqipjbql02DThQ1ZJ9wLi7SDMLdPAFyDI5xZNzX5guLz+/DtPkY+SGK2li9FIXqshQ== +"@azure/core-tracing@1.0.0-preview.10": + version "1.0.0-preview.10" + resolved "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.0-preview.10.tgz#e7060272145dddad4486765030d1b037cd52a8ea" + integrity sha512-iIwjtMwQnsxB7cYkugMx+s4W1nfy3+pT/ceo+uW1fv4YDgYe84nh+QP0fEC9IH/3UATLSWbIBemdMHzk2APUrw== dependencies: "@opencensus/web-types" "0.0.7" - "@opentelemetry/api" "1.0.0-rc.0" + "@opentelemetry/api" "^0.10.2" tslib "^2.0.0" "@azure/core-tracing@1.0.0-preview.12": @@ -286,17 +286,18 @@ jsonwebtoken "^8.5.1" uuid "^8.3.0" -"@azure/storage-blob@^12.6.0": - version "12.6.0" - resolved "https://registry.npmjs.org/@azure/storage-blob/-/storage-blob-12.6.0.tgz#9905d80e5f908a573cc65e1cb8302abc32818844" - integrity sha512-cAzsae+5ZdhugQfIT7o5SlVyF2Sc+HygZdPO41ZYdXklfGUyEt+5K4PyM5HQDc0MTVt6x7+waXcaAXT2eF9E6A== +"@azure/storage-blob@^12.5.0": + version "12.5.0" + resolved "https://registry.npmjs.org/@azure/storage-blob/-/storage-blob-12.5.0.tgz#1ddd8837d9a15ebe355e795375d13b406f2cb496" + integrity sha512-DgoefgODst2IPkkQsNdhtYdyJgSsAZC1pEujO6aD5y7uFy5GnzhYliobSrp204jYRyK5XeJ9iiePmy/SPtTbLA== dependencies: "@azure/abort-controller" "^1.0.0" "@azure/core-http" "^1.2.0" "@azure/core-lro" "^1.0.2" "@azure/core-paging" "^1.1.1" - "@azure/core-tracing" "1.0.0-preview.11" + "@azure/core-tracing" "1.0.0-preview.10" "@azure/logger" "^1.0.0" + "@opentelemetry/api" "^0.10.2" events "^3.0.0" tslib "^2.0.0" @@ -3940,11 +3941,6 @@ resolved "https://registry.npmjs.org/@opencensus/web-types/-/web-types-0.0.7.tgz#4426de1fe5aa8f624db395d2152b902874f0570a" integrity sha512-xB+w7ZDAu3YBzqH44rCmG9/RlrOmFuDPt/bpf17eJr8eZSrLt7nc7LnWdxM9Mmoj/YKMHpxRg28txu3TcpiL+g== -"@opentelemetry/api@1.0.0-rc.0": - version "1.0.0-rc.0" - resolved "https://registry.npmjs.org/@opentelemetry/api/-/api-1.0.0-rc.0.tgz#0c7c3f5e1285f99cedb563d74ad1adb9822b5144" - integrity sha512-iXKByCMfrlO5S6Oh97BuM56tM2cIBB0XsL/vWF/AtJrJEKx4MC/Xdu0xDsGXMGcNWpqF7ujMsjjnp0+UHBwnDQ== - "@opentelemetry/api@^0.10.2": version "0.10.2" resolved "https://registry.npmjs.org/@opentelemetry/api/-/api-0.10.2.tgz#9647b881f3e1654089ff7ea59d587b2d35060654" From 88f2a786df05e96bd1ac22f5100d8db8a80ab6a1 Mon Sep 17 00:00:00 2001 From: Tim Hansen Date: Wed, 21 Jul 2021 15:26:06 -0600 Subject: [PATCH 099/106] Remove Python requirement from docs Signed-off-by: Tim Hansen --- docs/features/software-templates/configuration.md | 9 ++++++--- docs/getting-started/index.md | 8 +++----- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/docs/features/software-templates/configuration.md b/docs/features/software-templates/configuration.md index cdd8166889..737f77f67b 100644 --- a/docs/features/software-templates/configuration.md +++ b/docs/features/software-templates/configuration.md @@ -33,9 +33,12 @@ scaffolder: ### Disabling Docker in Docker situation (Optional) -Software Templates use -[Cookiecutter](https://github.com/cookiecutter/cookiecutter) as a templating -library. By default it will use the +Software templates use the `fetch:template` action by default, which requires no +external dependencies and offers a +[Cookiecutter-compatible mode](https://backstage.io/docs/features/software-templates/builtin-actions#using-cookiecuttercompat-mode). +There is also a `fetch:cookiecutter` action, which uses +[Cookiecutter](https://github.com/cookiecutter/cookiecutter) directly for +templating. By default, the `fetch:cookiecutter` action will use the [scaffolder-backend/Cookiecutter](https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend/scripts/Cookiecutter.dockerfile) docker image. diff --git a/docs/getting-started/index.md b/docs/getting-started/index.md index 13b52181df..b01afac5c3 100644 --- a/docs/getting-started/index.md +++ b/docs/getting-started/index.md @@ -24,11 +24,9 @@ an easier path to make Pull Requests. Backstage provides the `@backstage/create-app` package to scaffold standalone instances of Backstage. You will need to have [Node.js](https://nodejs.org/en/download/) Active LTS Release installed -(currently v14), [Yarn](https://classic.yarnpkg.com/en/docs/install) and -[Python](https://www.python.org/downloads/) (although you likely have it -already). You will also need to have -[Docker](https://docs.docker.com/engine/install/) installed to use some features -like Software Templates and TechDocs. +(currently v14) and [Yarn](https://classic.yarnpkg.com/en/docs/install). You +will also need to have [Docker](https://docs.docker.com/engine/install/) +installed to use some features like Software Templates and TechDocs. Using `npx` you can then run the following to create an app in a chosen subdirectory of your current working directory: From b6e36c9a01a6d25a5530bcdd1deea1e8c444e55e Mon Sep 17 00:00:00 2001 From: Juan Lulkin Date: Thu, 22 Jul 2021 10:27:04 +0200 Subject: [PATCH 100/106] Remove unused fragment Signed-off-by: Juan Lulkin --- .../FeatureFlags/FeatureFlagsItem.tsx | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/plugins/user-settings/src/components/FeatureFlags/FeatureFlagsItem.tsx b/plugins/user-settings/src/components/FeatureFlags/FeatureFlagsItem.tsx index d32f2c27f7..e97b33cd92 100644 --- a/plugins/user-settings/src/components/FeatureFlags/FeatureFlagsItem.tsx +++ b/plugins/user-settings/src/components/FeatureFlags/FeatureFlagsItem.tsx @@ -31,17 +31,15 @@ type Props = { }; export const FlagItem = ({ flag, enabled, toggleHandler }: Props) => ( - <> - toggleHandler(flag.name)}> - - - - - - - - + toggleHandler(flag.name)}> + + + + + + + ); From cad2c8ac25e85b2e6c316d121fce3f19bbd3d668 Mon Sep 17 00:00:00 2001 From: Dominik Henneke Date: Thu, 22 Jul 2021 10:40:30 +0200 Subject: [PATCH 101/106] Sync yarn.lock Signed-off-by: Dominik Henneke --- yarn.lock | 110 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 107 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 3921f057bc..4d163dbe64 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4228,6 +4228,13 @@ resolved "https://registry.npmjs.org/@sindresorhus/is/-/is-4.0.0.tgz#2ff674e9611b45b528896d820d3d7a812de2f0e4" integrity sha512-FyD2meJpDPjyNQejSjvnhpgI/azsQkA4lGbuu5BQZfjvJ9cbRZXzeWL2HceCekW4lixO9JPesIIQkSoLjeJHNQ== +"@sinonjs/commons@^1.6.0", "@sinonjs/commons@^1.8.3": + version "1.8.3" + resolved "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d" + integrity sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ== + dependencies: + type-detect "4.0.8" + "@sinonjs/commons@^1.7.0": version "1.7.1" resolved "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.7.1.tgz#da5fd19a5f71177a53778073978873964f49acf1" @@ -4242,6 +4249,27 @@ dependencies: "@sinonjs/commons" "^1.7.0" +"@sinonjs/fake-timers@^7.0.4", "@sinonjs/fake-timers@^7.1.0": + version "7.1.2" + resolved "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-7.1.2.tgz#2524eae70c4910edccf99b2f4e6efc5894aff7b5" + integrity sha512-iQADsW4LBMISqZ6Ci1dupJL9pprqwcVFTcOsEmQOEhW+KLCVn/Y4Jrvg2k19fIHCp+iFprriYPTdRcQR8NbUPg== + dependencies: + "@sinonjs/commons" "^1.7.0" + +"@sinonjs/samsam@^6.0.2": + version "6.0.2" + resolved "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-6.0.2.tgz#a0117d823260f282c04bff5f8704bdc2ac6910bb" + integrity sha512-jxPRPp9n93ci7b8hMfJOFDPRLFYadN6FSpeROFTR4UNF4i5b+EK6m4QXPO46BDhFgRy1JuS87zAnFOzCUwMJcQ== + dependencies: + "@sinonjs/commons" "^1.6.0" + lodash.get "^4.4.2" + type-detect "^4.0.8" + +"@sinonjs/text-encoding@^0.7.1": + version "0.7.1" + resolved "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz#8da5c6530915653f3a1f38fd5f101d8c3f8079c5" + integrity sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ== + "@spotify/eslint-config-base@^9.0.0": version "9.0.2" resolved "https://registry.npmjs.org/@spotify/eslint-config-base/-/eslint-config-base-9.0.2.tgz#a4830f610f40de935de795d3def486c3e5064ee4" @@ -7825,6 +7853,15 @@ autoprefixer@^9.7.2: postcss "^7.0.32" postcss-value-parser "^4.1.0" +aws-sdk-mock@^5.2.1: + version "5.2.1" + resolved "https://registry.npmjs.org/aws-sdk-mock/-/aws-sdk-mock-5.2.1.tgz#126d4d5362c96b7d1d0bd87708a99d626c19ffd4" + integrity sha512-dY7zA1p/lX335V4/aOJ2L8ggXC3a5zokTJFZlZVW3uU+Zej7u+V7WrEcN5TVaJAnk4auT263T6EK/OHW4WjKhw== + dependencies: + aws-sdk "^2.928.0" + sinon "^11.1.1" + traverse "^0.6.6" + aws-sdk@^2.382.0, aws-sdk@^2.840.0: version "2.922.0" resolved "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.922.0.tgz#4568be067dceaaeda5d2d5a7e2f22666687f0b32" @@ -7840,6 +7877,21 @@ aws-sdk@^2.382.0, aws-sdk@^2.840.0: uuid "3.3.2" xml2js "0.4.19" +aws-sdk@^2.928.0: + version "2.951.0" + resolved "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.951.0.tgz#3cd8c0a4610407623ae8f1724f431fd328441033" + integrity sha512-YPqhdESUzd4+pSuGJcfMnG1qNVbmZjnmsa85Z9jofR1ilIpuV31onIiFHv8iubM59ETok/+zy3QOmxRSLYzFmQ== + dependencies: + buffer "4.9.2" + events "1.1.1" + ieee754 "1.1.13" + jmespath "0.15.0" + querystring "0.2.0" + sax "1.2.1" + url "0.10.3" + uuid "3.3.2" + xml2js "0.4.19" + aws-sign2@~0.7.0: version "0.7.0" resolved "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" @@ -8380,6 +8432,11 @@ bcrypt-pbkdf@^1.0.0, bcrypt-pbkdf@^1.0.2: dependencies: tweetnacl "^0.14.3" +bdd-lazy-var@^2.6.0: + version "2.6.1" + resolved "https://registry.npmjs.org/bdd-lazy-var/-/bdd-lazy-var-2.6.1.tgz#ca03fb36d68c5a507c0ba9a4d53160b899e6b7cb" + integrity sha512-X3ADwcFji/IHIrYJhTTpaiWhoOx4pl4whdAx1dmvdeUPsMUb7fVYFvf/Q33VEAEAVkEwi5rgNSZ0Y9oOVeQV+A== + before-after-hook@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.1.0.tgz#b6c03487f44e24200dd30ca5e6a1979c5d2fb635" @@ -15636,6 +15693,11 @@ is-yarn-global@^0.3.0: resolved "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232" integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw== +isarray@0.0.1: + version "0.0.1" + resolved "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" + integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= + isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" @@ -16765,6 +16827,11 @@ junk@^3.1.0: resolved "https://registry.npmjs.org/junk/-/junk-3.1.0.tgz#31499098d902b7e98c5d9b9c80f43457a88abfa1" integrity sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ== +just-extend@^4.0.2: + version "4.2.1" + resolved "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz#ef5e589afb61e5d66b24eca749409a8939a8c744" + integrity sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg== + jwa@^1.4.1: version "1.4.1" resolved "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" @@ -17363,7 +17430,7 @@ lodash.flattendeep@^4.0.0: resolved "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" integrity sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI= -lodash.get@^4, lodash.get@^4.0.0: +lodash.get@^4, lodash.get@^4.0.0, lodash.get@^4.4.2: version "4.4.2" resolved "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= @@ -18705,6 +18772,17 @@ nice-try@^1.0.4: resolved "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== +nise@^5.1.0: + version "5.1.0" + resolved "https://registry.npmjs.org/nise/-/nise-5.1.0.tgz#713ef3ed138252daef20ec035ab62b7a28be645c" + integrity sha512-W5WlHu+wvo3PaKLsJJkgPup2LrsXCcm7AWwyNZkUnn5rwPkuPBi3Iwk5SQtN0mv+K65k7nKKjwNQ30wg3wLAQQ== + dependencies: + "@sinonjs/commons" "^1.7.0" + "@sinonjs/fake-timers" "^7.0.4" + "@sinonjs/text-encoding" "^0.7.1" + just-extend "^4.0.2" + path-to-regexp "^1.7.0" + no-case@^3.0.4: version "3.0.4" resolved "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d" @@ -20018,6 +20096,13 @@ path-to-regexp@0.1.7: resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= +path-to-regexp@^1.7.0: + version "1.8.0" + resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a" + integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA== + dependencies: + isarray "0.0.1" + path-type@^1.0.0: version "1.1.0" resolved "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" @@ -23211,6 +23296,18 @@ simple-swizzle@^0.2.2: dependencies: is-arrayish "^0.3.1" +sinon@^11.1.1: + version "11.1.1" + resolved "https://registry.npmjs.org/sinon/-/sinon-11.1.1.tgz#99a295a8b6f0fadbbb7e004076f3ae54fc6eab91" + integrity sha512-ZSSmlkSyhUWbkF01Z9tEbxZLF/5tRC9eojCdFh33gtQaP7ITQVaMWQHGuFM7Cuf/KEfihuh1tTl3/ABju3AQMg== + dependencies: + "@sinonjs/commons" "^1.8.3" + "@sinonjs/fake-timers" "^7.1.0" + "@sinonjs/samsam" "^6.0.2" + diff "^5.0.0" + nise "^5.1.0" + supports-color "^7.2.0" + sisteransi@^1.0.5: version "1.0.5" resolved "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" @@ -24195,6 +24292,13 @@ supports-color@^7.0.0, supports-color@^7.1.0: dependencies: has-flag "^4.0.0" +supports-color@^7.2.0: + version "7.2.0" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + supports-color@^8.1.0, supports-color@^8.1.1: version "8.1.1" resolved "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" @@ -24858,7 +24962,7 @@ tr46@^2.0.2: resolved "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9" integrity sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk= -traverse@~0.6.6: +traverse@^0.6.6, traverse@~0.6.6: version "0.6.6" resolved "https://registry.npmjs.org/traverse/-/traverse-0.6.6.tgz#cbdf560fd7b9af632502fed40f918c157ea97137" integrity sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc= @@ -25074,7 +25178,7 @@ type-check@~0.3.2: dependencies: prelude-ls "~1.1.2" -type-detect@4.0.8: +type-detect@4.0.8, type-detect@^4.0.8: version "4.0.8" resolved "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== From 3891efb00059a7a4f77fe0f34874c6cfc61e92f5 Mon Sep 17 00:00:00 2001 From: Yousif Al-Raheem Date: Thu, 22 Jul 2021 11:24:36 +0200 Subject: [PATCH 102/106] rename retry to refresh Signed-off-by: Yousif Al-Raheem --- .changeset/early-socks-beg.md | 4 ++-- plugins/catalog-react/src/hooks/useEntity.ts | 12 ++++++------ .../EntityLoaderProvider/EntityLoaderProvider.tsx | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.changeset/early-socks-beg.md b/.changeset/early-socks-beg.md index 33265fe8f1..915ca8f63a 100644 --- a/.changeset/early-socks-beg.md +++ b/.changeset/early-socks-beg.md @@ -1,6 +1,6 @@ --- -'@backstage/plugin-catalog': minor -'@backstage/plugin-catalog-react': minor +'@backstage/plugin-catalog': patch +'@backstage/plugin-catalog-react': patch --- added retry callback to useEntity hook diff --git a/plugins/catalog-react/src/hooks/useEntity.ts b/plugins/catalog-react/src/hooks/useEntity.ts index a83e573753..6fe25fb115 100644 --- a/plugins/catalog-react/src/hooks/useEntity.ts +++ b/plugins/catalog-react/src/hooks/useEntity.ts @@ -25,14 +25,14 @@ type EntityLoadingStatus = { entity?: Entity; loading: boolean; error?: Error; - retry?: VoidFunction; + refresh?: VoidFunction; }; export const EntityContext = createContext({ entity: undefined, loading: true, error: undefined, - retry: () => {}, + refresh: () => {}, }); export const useEntityFromUrl = (): EntityLoadingStatus => { @@ -41,7 +41,7 @@ export const useEntityFromUrl = (): EntityLoadingStatus => { const errorApi = useApi(errorApiRef); const catalogApi = useApi(catalogApiRef); - const { value: entity, error, loading, retry } = useAsyncRetry( + const { value: entity, error, loading, retry: refresh } = useAsyncRetry( () => catalogApi.getEntityByName({ kind, namespace, name }), [catalogApi, kind, namespace, name], ); @@ -53,13 +53,13 @@ export const useEntityFromUrl = (): EntityLoadingStatus => { } }, [errorApi, navigate, error, loading, entity, name]); - return { entity, loading, error, retry }; + return { entity, loading, error, refresh }; }; /** * Grab the current entity from the context and its current loading state. */ export function useEntity() { - const { entity, loading, error, retry } = useContext(EntityContext); - return { entity: entity as T, loading, error, retry }; + const { entity, loading, error, refresh } = useContext(EntityContext); + return { entity: entity as T, loading, error, refresh }; } diff --git a/plugins/catalog/src/components/EntityLoaderProvider/EntityLoaderProvider.tsx b/plugins/catalog/src/components/EntityLoaderProvider/EntityLoaderProvider.tsx index 20828b390b..8d2feae7da 100644 --- a/plugins/catalog/src/components/EntityLoaderProvider/EntityLoaderProvider.tsx +++ b/plugins/catalog/src/components/EntityLoaderProvider/EntityLoaderProvider.tsx @@ -20,10 +20,10 @@ import { import React, { ReactNode } from 'react'; export const EntityLoaderProvider = ({ children }: { children: ReactNode }) => { - const { entity, loading, error, retry } = useEntityFromUrl(); + const { entity, loading, error, refresh } = useEntityFromUrl(); return ( - + {children} ); From 964250ac076691ed1f666f918f49ef0c9388004d Mon Sep 17 00:00:00 2001 From: Yousif Al-Raheem Date: Thu, 22 Jul 2021 11:44:01 +0200 Subject: [PATCH 103/106] generated api reports Signed-off-by: Yousif Al-Raheem --- plugins/catalog-react/api-report.md | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/catalog-react/api-report.md b/plugins/catalog-react/api-report.md index 03ee9a391a..2555cc7c0c 100644 --- a/plugins/catalog-react/api-report.md +++ b/plugins/catalog-react/api-report.md @@ -697,6 +697,7 @@ export function useEntity(): { entity: T; loading: boolean; error: Error | undefined; + refresh: VoidFunction | undefined; }; // Warning: (ae-missing-release-tag) "useEntityCompoundName" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) From f51fcaeba27db7c2cec21d822a66a1b46170d413 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 22 Jul 2021 10:52:38 +0000 Subject: [PATCH 104/106] Version Packages --- .changeset/angry-spies-warn.md | 5 -- .changeset/blue-feet-poke.md | 5 -- .changeset/chilled-cars-cough.md | 5 -- .changeset/clever-pigs-drum.md | 5 -- .changeset/early-socks-beg.md | 6 -- .changeset/eighty-jokes-roll.md | 5 -- .changeset/fifty-panthers-play.md | 5 -- .changeset/forty-singers-look.md | 5 -- .changeset/friendly-tips-jam.md | 5 -- .changeset/funny-dragons-sneeze.md | 5 -- .changeset/funny-moose-tie.md | 5 -- .changeset/fuzzy-pots-whisper.md | 15 ---- .changeset/great-tips-hammer.md | 5 -- .changeset/green-lies-hammer.md | 8 -- .changeset/honest-ghosts-listen.md | 5 -- .changeset/late-taxis-smile.md | 5 -- .changeset/mighty-books-decide.md | 7 -- .changeset/nasty-worms-approve.md | 5 -- .changeset/neat-wolves-cross.md | 5 -- .changeset/poor-jars-sniff.md | 15 ---- .changeset/spotty-actors-invite.md | 5 -- .changeset/spotty-pandas-deny.md | 5 -- .changeset/techdocs-fifty-cameras-listen.md | 7 -- .changeset/techdocs-proud-apples-wonder.md | 5 -- .changeset/techdocs-spotty-tables-beam.md | 5 -- .changeset/techdocs-twenty-donuts-eat.md | 88 ------------------- .changeset/techdocs-young-gorillas-share.md | 5 -- .changeset/wicked-emus-deny.md | 8 -- .changeset/wild-fishes-suffer.md | 5 -- packages/backend-common/CHANGELOG.md | 17 ++++ packages/backend-common/package.json | 6 +- packages/backend/CHANGELOG.md | 13 +++ packages/backend/package.json | 18 ++-- packages/catalog-client/CHANGELOG.md | 6 ++ packages/catalog-client/package.json | 4 +- packages/cli/CHANGELOG.md | 6 ++ packages/cli/package.json | 12 +-- packages/codemods/CHANGELOG.md | 8 ++ packages/codemods/package.json | 2 +- packages/core-app-api/CHANGELOG.md | 8 ++ packages/core-app-api/package.json | 8 +- packages/core-components/CHANGELOG.md | 15 ++++ packages/core-components/package.json | 8 +- packages/create-app/CHANGELOG.md | 17 ++++ packages/create-app/package.json | 2 +- packages/dev-utils/CHANGELOG.md | 11 +++ packages/dev-utils/package.json | 12 +-- packages/techdocs-common/CHANGELOG.md | 95 ++++++++++++++++++++ packages/techdocs-common/package.json | 6 +- packages/test-utils/CHANGELOG.md | 8 ++ packages/test-utils/package.json | 6 +- plugins/api-docs/CHANGELOG.md | 10 +++ plugins/api-docs/package.json | 16 ++-- plugins/auth-backend/CHANGELOG.md | 10 +++ plugins/auth-backend/package.json | 10 +-- plugins/badges/package.json | 10 +-- plugins/bitrise/package.json | 10 +-- plugins/catalog-import/CHANGELOG.md | 10 +++ plugins/catalog-import/package.json | 16 ++-- plugins/catalog-react/CHANGELOG.md | 10 +++ plugins/catalog-react/package.json | 14 +-- plugins/catalog/CHANGELOG.md | 21 +++++ plugins/catalog/package.json | 16 ++-- plugins/circleci/package.json | 10 +-- plugins/cloudbuild/package.json | 10 +-- plugins/code-coverage/package.json | 10 +-- plugins/config-schema/package.json | 10 +-- plugins/cost-insights/package.json | 10 +-- plugins/explore/package.json | 10 +-- plugins/fossa/package.json | 10 +-- plugins/gcp-projects/package.json | 10 +-- plugins/git-release-manager/CHANGELOG.md | 13 +++ plugins/git-release-manager/package.json | 12 +-- plugins/github-actions/package.json | 10 +-- plugins/github-deployments/package.json | 10 +-- plugins/gitops-profiles/package.json | 10 +-- plugins/graphiql/package.json | 10 +-- plugins/ilert/package.json | 10 +-- plugins/jenkins-backend/CHANGELOG.md | 10 +++ plugins/jenkins-backend/package.json | 8 +- plugins/jenkins/package.json | 10 +-- plugins/kafka/package.json | 10 +-- plugins/kubernetes-backend/CHANGELOG.md | 8 ++ plugins/kubernetes-backend/package.json | 6 +- plugins/kubernetes/package.json | 10 +-- plugins/lighthouse/package.json | 10 +-- plugins/newrelic/package.json | 10 +-- plugins/org/package.json | 10 +-- plugins/pagerduty/package.json | 10 +-- plugins/register-component/package.json | 10 +-- plugins/rollbar/package.json | 10 +-- plugins/scaffolder-backend/CHANGELOG.md | 14 +++ plugins/scaffolder-backend/package.json | 10 +-- plugins/scaffolder/package.json | 10 +-- plugins/search/CHANGELOG.md | 9 ++ plugins/search/package.json | 14 +-- plugins/sentry/package.json | 10 +-- plugins/shortcuts/package.json | 10 +-- plugins/sonarqube/package.json | 10 +-- plugins/splunk-on-call/package.json | 10 +-- plugins/tech-radar/package.json | 10 +-- plugins/techdocs-backend/CHANGELOG.md | 96 +++++++++++++++++++++ plugins/techdocs-backend/package.json | 12 +-- plugins/techdocs/CHANGELOG.md | 23 +++++ plugins/techdocs/package.json | 14 +-- plugins/todo/package.json | 10 +-- plugins/user-settings/CHANGELOG.md | 8 ++ plugins/user-settings/package.json | 12 +-- plugins/welcome/package.json | 10 +-- plugins/xcmetrics/package.json | 10 +-- 110 files changed, 733 insertions(+), 546 deletions(-) delete mode 100644 .changeset/angry-spies-warn.md delete mode 100644 .changeset/blue-feet-poke.md delete mode 100644 .changeset/chilled-cars-cough.md delete mode 100644 .changeset/clever-pigs-drum.md delete mode 100644 .changeset/early-socks-beg.md delete mode 100644 .changeset/eighty-jokes-roll.md delete mode 100644 .changeset/fifty-panthers-play.md delete mode 100644 .changeset/forty-singers-look.md delete mode 100644 .changeset/friendly-tips-jam.md delete mode 100644 .changeset/funny-dragons-sneeze.md delete mode 100644 .changeset/funny-moose-tie.md delete mode 100644 .changeset/fuzzy-pots-whisper.md delete mode 100644 .changeset/great-tips-hammer.md delete mode 100644 .changeset/green-lies-hammer.md delete mode 100644 .changeset/honest-ghosts-listen.md delete mode 100644 .changeset/late-taxis-smile.md delete mode 100644 .changeset/mighty-books-decide.md delete mode 100644 .changeset/nasty-worms-approve.md delete mode 100644 .changeset/neat-wolves-cross.md delete mode 100644 .changeset/poor-jars-sniff.md delete mode 100644 .changeset/spotty-actors-invite.md delete mode 100644 .changeset/spotty-pandas-deny.md delete mode 100644 .changeset/techdocs-fifty-cameras-listen.md delete mode 100644 .changeset/techdocs-proud-apples-wonder.md delete mode 100644 .changeset/techdocs-spotty-tables-beam.md delete mode 100644 .changeset/techdocs-twenty-donuts-eat.md delete mode 100644 .changeset/techdocs-young-gorillas-share.md delete mode 100644 .changeset/wicked-emus-deny.md delete mode 100644 .changeset/wild-fishes-suffer.md create mode 100644 plugins/jenkins-backend/CHANGELOG.md diff --git a/.changeset/angry-spies-warn.md b/.changeset/angry-spies-warn.md deleted file mode 100644 index 171827748a..0000000000 --- a/.changeset/angry-spies-warn.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/techdocs-common': patch ---- - -Fix validation of mkdocs.yml docs_dir diff --git a/.changeset/blue-feet-poke.md b/.changeset/blue-feet-poke.md deleted file mode 100644 index 1a9e32a11a..0000000000 --- a/.changeset/blue-feet-poke.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-kubernetes-backend': patch ---- - -Support assume role on kubernetes api configuration for AWS. diff --git a/.changeset/chilled-cars-cough.md b/.changeset/chilled-cars-cough.md deleted file mode 100644 index b6406fcab1..0000000000 --- a/.changeset/chilled-cars-cough.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core-components': patch ---- - -Increase the vertical padding of the sidebar search input field to match the height of the parent anchor tag. This prevents users from accidentally navigating to the search page when they actually wanted to use the search input directly. diff --git a/.changeset/clever-pigs-drum.md b/.changeset/clever-pigs-drum.md deleted file mode 100644 index bbc8ee5f90..0000000000 --- a/.changeset/clever-pigs-drum.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/catalog-client': patch ---- - -Export `CatalogRequestOptions` type diff --git a/.changeset/early-socks-beg.md b/.changeset/early-socks-beg.md deleted file mode 100644 index 915ca8f63a..0000000000 --- a/.changeset/early-socks-beg.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-catalog': patch -'@backstage/plugin-catalog-react': patch ---- - -added retry callback to useEntity hook diff --git a/.changeset/eighty-jokes-roll.md b/.changeset/eighty-jokes-roll.md deleted file mode 100644 index d51cd3a6dc..0000000000 --- a/.changeset/eighty-jokes-roll.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-auth-backend': patch ---- - -TokenIssuer is now exported so it may be used by auth providers that are not bundled with Backstage diff --git a/.changeset/fifty-panthers-play.md b/.changeset/fifty-panthers-play.md deleted file mode 100644 index 1ea00ef223..0000000000 --- a/.changeset/fifty-panthers-play.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-jenkins-backend': patch ---- - -Update `@backstage/backend-common` to `^0.8.6` diff --git a/.changeset/forty-singers-look.md b/.changeset/forty-singers-look.md deleted file mode 100644 index 1b3919e214..0000000000 --- a/.changeset/forty-singers-look.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core-app-api': patch ---- - -Fix a bug in `FlatRoutes` that prevented outlets from working with the root route, as well as matching root routes too broadly. diff --git a/.changeset/friendly-tips-jam.md b/.changeset/friendly-tips-jam.md deleted file mode 100644 index 806b632ba1..0000000000 --- a/.changeset/friendly-tips-jam.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-common': patch ---- - -Implement the etag functionality in the `readUrl` method of `FetchUrlReader`. diff --git a/.changeset/funny-dragons-sneeze.md b/.changeset/funny-dragons-sneeze.md deleted file mode 100644 index 758603e811..0000000000 --- a/.changeset/funny-dragons-sneeze.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-techdocs': patch ---- - -Add search list item to display tech docs search results diff --git a/.changeset/funny-moose-tie.md b/.changeset/funny-moose-tie.md deleted file mode 100644 index 3d3ab5f632..0000000000 --- a/.changeset/funny-moose-tie.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-import': patch ---- - -Fix heading that wrongly implied catalog-import supports entity discovery for multiple integrations. diff --git a/.changeset/fuzzy-pots-whisper.md b/.changeset/fuzzy-pots-whisper.md deleted file mode 100644 index 772ba1fb43..0000000000 --- a/.changeset/fuzzy-pots-whisper.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -'@backstage/backend-common': patch ---- - -It's possible to customize the request logging handler when building the service. For example in your `backend` - -``` - const service = createServiceBuilder(module) - .loadConfig(config) - .setRequestLoggingHandler((logger?: Logger): RequestHandler => { - const actualLogger = (logger || getRootLogger()).child({ - type: 'incomingRequest', - }); - return expressWinston.logger({ ... -``` diff --git a/.changeset/great-tips-hammer.md b/.changeset/great-tips-hammer.md deleted file mode 100644 index 2f31b59568..0000000000 --- a/.changeset/great-tips-hammer.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-techdocs-backend': patch ---- - -Implements tech docs collator to retrieve and expose search indexes for entities that have tech docs configured diff --git a/.changeset/green-lies-hammer.md b/.changeset/green-lies-hammer.md deleted file mode 100644 index af1c2e6e92..0000000000 --- a/.changeset/green-lies-hammer.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -'@backstage/core-components': patch -'@backstage/test-utils': patch -'@backstage/plugin-api-docs': patch -'@backstage/plugin-catalog': patch ---- - -Updated the layout of catalog and API index pages to handle smaller screen sizes. This adds responsive wrappers to the entity tables, and switches filters to a drawer when width-constrained. If you have created a custom catalog or API index page, you will need to update the page structure to match the updated [catalog customization](https://backstage.io/docs/features/software-catalog/catalog-customization) documentation. diff --git a/.changeset/honest-ghosts-listen.md b/.changeset/honest-ghosts-listen.md deleted file mode 100644 index 411a7e8908..0000000000 --- a/.changeset/honest-ghosts-listen.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -chore: bump `eslint` to `7.30.0` diff --git a/.changeset/late-taxis-smile.md b/.changeset/late-taxis-smile.md deleted file mode 100644 index 80bab5fb54..0000000000 --- a/.changeset/late-taxis-smile.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-user-settings': patch ---- - -Aligns switch left and allows clicking on rows diff --git a/.changeset/mighty-books-decide.md b/.changeset/mighty-books-decide.md deleted file mode 100644 index 6a25a2d2b5..0000000000 --- a/.changeset/mighty-books-decide.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/plugin-git-release-manager': minor ---- - -Enable users to add custom features - -Add more metadata to success callbacks diff --git a/.changeset/nasty-worms-approve.md b/.changeset/nasty-worms-approve.md deleted file mode 100644 index c3ed7c1b35..0000000000 --- a/.changeset/nasty-worms-approve.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog': patch ---- - -Export `CatalogClientWrapper` class diff --git a/.changeset/neat-wolves-cross.md b/.changeset/neat-wolves-cross.md deleted file mode 100644 index 555245d42d..0000000000 --- a/.changeset/neat-wolves-cross.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-search': patch ---- - -Adding a type filter to new search diff --git a/.changeset/poor-jars-sniff.md b/.changeset/poor-jars-sniff.md deleted file mode 100644 index b2be713a93..0000000000 --- a/.changeset/poor-jars-sniff.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -'@backstage/core-components': patch -'@backstage/create-app': patch -'@backstage/plugin-catalog': patch -'@backstage/plugin-techdocs': patch ---- - -Improve the responsiveness of the EntityPage UI. With this the Header component should scale with the screen size & wrapping should not cause overflowing/blocking of links. Additionally enforce the Pages using the Grid Layout to use it across all screen sizes & to wrap as intended. - -To benefit from the improved responsive layout, the `EntityPage` in existing Backstage applications should be updated to set the `xs` column size on each grid item in the page, as this does not default. For example: - -```diff -- -+ -``` diff --git a/.changeset/spotty-actors-invite.md b/.changeset/spotty-actors-invite.md deleted file mode 100644 index 96f179c475..0000000000 --- a/.changeset/spotty-actors-invite.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend': patch ---- - -Add new built-in action ci:github-actions-dispatch diff --git a/.changeset/spotty-pandas-deny.md b/.changeset/spotty-pandas-deny.md deleted file mode 100644 index c3da6afe2b..0000000000 --- a/.changeset/spotty-pandas-deny.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend': patch ---- - -- Move out the `cookiecutter` templating to its own module that is depended on by the `scaffolder-backend` plugin. No breaking change yet, but we will drop first class support for `cookiecutter` in the future and it will become an opt-in feature. diff --git a/.changeset/techdocs-fifty-cameras-listen.md b/.changeset/techdocs-fifty-cameras-listen.md deleted file mode 100644 index fb6be9d709..0000000000 --- a/.changeset/techdocs-fifty-cameras-listen.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/plugin-techdocs': patch ---- - -Only update the `path` when the content is updated. -If content and path are updated independently, the frontend rendering is triggered twice on each navigation: Once for the `path` change (with the old content) and once for the new content. -This might result in a flickering rendering that is caused by the async frontend preprocessing, and the fact that replacing the shadow dom content is expensive. diff --git a/.changeset/techdocs-proud-apples-wonder.md b/.changeset/techdocs-proud-apples-wonder.md deleted file mode 100644 index 901ee26472..0000000000 --- a/.changeset/techdocs-proud-apples-wonder.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/techdocs-common': patch ---- - -Add link to https://backstage.io/docs/features/techdocs/configuration in the log warning message about updating techdocs.generate key. diff --git a/.changeset/techdocs-spotty-tables-beam.md b/.changeset/techdocs-spotty-tables-beam.md deleted file mode 100644 index 397817b196..0000000000 --- a/.changeset/techdocs-spotty-tables-beam.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-techdocs': patch ---- - -Refactor the techdocs transformers to return `Promise`s and await all transformations. diff --git a/.changeset/techdocs-twenty-donuts-eat.md b/.changeset/techdocs-twenty-donuts-eat.md deleted file mode 100644 index a95ec5a752..0000000000 --- a/.changeset/techdocs-twenty-donuts-eat.md +++ /dev/null @@ -1,88 +0,0 @@ ---- -'@backstage/techdocs-common': minor -'@backstage/plugin-techdocs-backend': minor ---- - -Improve the annotation `backstage.io/techdocs-ref: dir:` that links to a path that is relative to the source of the annotated entity. -This annotation works with the basic and the recommended flow, however, it will be most useful with the basic approach. - -This change remove the deprecation of the `dir` reference and provides first-class support for it. -In addition, this change removes the support of the deprecated `github`, `gitlab`, and `azure/api` locations from the `dir` reference preparer. - -#### Example Usage - -The annotation is convenient if the documentation is stored in the same location, i.e. the same git repository, as the `catalog-info.yaml`. -While it is still supported to add full URLs such as `backstage.io/techdocs-ref: url:https://...` for custom setups, documentation is mostly stored in the same repository as the entity definition. -By automatically resolving the target relative to the registration location of the entity, the configuration overhead for this default setup is minimized. -Since it leverages the `@backstage/integrations` package for the URL resolution, this is compatible with every supported source. - -Consider the following examples: - -1. "I have a repository with a single `catalog-info.yaml` and a TechDocs page in the root folder!" - -``` -https://github.com/backstage/example/tree/main/ - |- catalog-info.yaml - | > apiVersion: backstage.io/v1alpha1 - | > kind: Component - | > metadata: - | > name: example - | > annotations: - | > backstage.io/techdocs-ref: dir:. # -> same folder - | > spec: {} - |- docs/ - |- mkdocs.yml -``` - -2. "I have a repository with a single `catalog-info.yaml` and my TechDocs page in located in a folder!" - -``` -https://bitbucket.org/my-owner/my-project/src/master/ - |- catalog-info.yaml - | > apiVersion: backstage.io/v1alpha1 - | > kind: Component - | > metadata: - | > name: example - | > annotations: - | > backstage.io/techdocs-ref: dir:./some-folder # -> subfolder - | > spec: {} - |- some-folder/ - |- docs/ - |- mkdocs.yml -``` - -3. "I have a mono repository that hosts multiple components!" - -``` -https://dev.azure.com/organization/project/_git/repository - |- my-1st-module/ - |- catalog-info.yaml - | > apiVersion: backstage.io/v1alpha1 - | > kind: Component - | > metadata: - | > name: my-1st-module - | > annotations: - | > backstage.io/techdocs-ref: dir:. # -> same folder - | > spec: {} - |- docs/ - |- mkdocs.yml - |- my-2nd-module/ - |- catalog-info.yaml - | > apiVersion: backstage.io/v1alpha1 - | > kind: Component - | > metadata: - | > name: my-2nd-module - | > annotations: - | > backstage.io/techdocs-ref: dir:. # -> same folder - | > spec: {} - |- docs/ - |- mkdocs.yml - |- catalog-info.yaml - | > apiVersion: backstage.io/v1alpha1 - | > kind: Location - | > metadata: - | > name: example - | > spec: - | > targets: - | > - ./*/catalog-info.yaml -``` diff --git a/.changeset/techdocs-young-gorillas-share.md b/.changeset/techdocs-young-gorillas-share.md deleted file mode 100644 index 6b1d3f03da..0000000000 --- a/.changeset/techdocs-young-gorillas-share.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-techdocs': patch ---- - -Handle error responses in `getTechDocsMetadata` and `getEntityMetadata` such that `` doesn't throw errors. diff --git a/.changeset/wicked-emus-deny.md b/.changeset/wicked-emus-deny.md deleted file mode 100644 index ff6788dbb2..0000000000 --- a/.changeset/wicked-emus-deny.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -'@backstage/create-app': patch -'@backstage/plugin-scaffolder-backend': patch ---- - -Moved sample software templates to the [backstage/software-templates](https://github.com/backstage/software-templates) repository. If you previously referenced the sample templates straight from `scaffolder-backend` plugin in the main [backstage/backstage](https://github.com/backstage/backstage) repository in your `app-config.yaml`, these references will need to be updated. - -See https://github.com/backstage/software-templates diff --git a/.changeset/wild-fishes-suffer.md b/.changeset/wild-fishes-suffer.md deleted file mode 100644 index 5b914860b2..0000000000 --- a/.changeset/wild-fishes-suffer.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/dev-utils': patch ---- - -Allow custom theme for dev app. diff --git a/packages/backend-common/CHANGELOG.md b/packages/backend-common/CHANGELOG.md index 13601fb8b3..582badb685 100644 --- a/packages/backend-common/CHANGELOG.md +++ b/packages/backend-common/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/backend-common +## 0.8.7 + +### Patch Changes + +- f25357273: Implement the etag functionality in the `readUrl` method of `FetchUrlReader`. +- bdd6ab5f1: It's possible to customize the request logging handler when building the service. For example in your `backend` + + ``` + const service = createServiceBuilder(module) + .loadConfig(config) + .setRequestLoggingHandler((logger?: Logger): RequestHandler => { + const actualLogger = (logger || getRootLogger()).child({ + type: 'incomingRequest', + }); + return expressWinston.logger({ ... + ``` + ## 0.8.6 ### Patch Changes diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index 739e372857..51ef8128e7 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-common", "description": "Common functionality library for Backstage backends", - "version": "0.8.6", + "version": "0.8.7", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -76,8 +76,8 @@ } }, "devDependencies": { - "@backstage/cli": "^0.7.4", - "@backstage/test-utils": "^0.1.12", + "@backstage/cli": "^0.7.5", + "@backstage/test-utils": "^0.1.15", "@types/archiver": "^5.1.0", "@types/compression": "^1.7.0", "@types/concat-stream": "^1.6.0", diff --git a/packages/backend/CHANGELOG.md b/packages/backend/CHANGELOG.md index 56f6f2da72..a3e08db6b3 100644 --- a/packages/backend/CHANGELOG.md +++ b/packages/backend/CHANGELOG.md @@ -1,5 +1,18 @@ # example-backend +## 0.2.38 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes-backend@0.3.11 + - @backstage/catalog-client@0.3.17 + - @backstage/plugin-auth-backend@0.3.18 + - @backstage/plugin-jenkins-backend@0.1.2 + - @backstage/backend-common@0.8.7 + - @backstage/plugin-techdocs-backend@0.9.0 + - @backstage/plugin-scaffolder-backend@0.14.1 + ## 0.2.37 ### Patch Changes diff --git a/packages/backend/package.json b/packages/backend/package.json index 315c966a07..9fc455801b 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,6 +1,6 @@ { "name": "example-backend", - "version": "0.2.37", + "version": "0.2.38", "main": "dist/index.cjs.js", "types": "src/index.ts", "license": "Apache-2.0", @@ -27,27 +27,27 @@ "migrate:create": "knex migrate:make -x ts" }, "dependencies": { - "@backstage/backend-common": "^0.8.6", - "@backstage/catalog-client": "^0.3.16", + "@backstage/backend-common": "^0.8.7", + "@backstage/catalog-client": "^0.3.17", "@backstage/catalog-model": "^0.9.0", "@backstage/config": "^0.1.5", "@backstage/integration": "^0.5.8", "@backstage/plugin-app-backend": "^0.3.15", - "@backstage/plugin-auth-backend": "^0.3.17", + "@backstage/plugin-auth-backend": "^0.3.18", "@backstage/plugin-badges-backend": "^0.1.8", "@backstage/plugin-catalog-backend": "^0.13.0", "@backstage/plugin-code-coverage-backend": "^0.1.8", "@backstage/plugin-graphql-backend": "^0.1.8", - "@backstage/plugin-jenkins-backend": "^0.1.1", - "@backstage/plugin-kubernetes-backend": "^0.3.10", + "@backstage/plugin-jenkins-backend": "^0.1.2", + "@backstage/plugin-kubernetes-backend": "^0.3.11", "@backstage/plugin-kafka-backend": "^0.2.8", "@backstage/plugin-proxy-backend": "^0.2.9", "@backstage/plugin-rollbar-backend": "^0.1.13", - "@backstage/plugin-scaffolder-backend": "^0.14.0", + "@backstage/plugin-scaffolder-backend": "^0.14.1", "@backstage/plugin-scaffolder-backend-module-rails": "^0.1.3", "@backstage/plugin-search-backend": "^0.2.3", "@backstage/plugin-search-backend-node": "^0.4.0", - "@backstage/plugin-techdocs-backend": "^0.8.7", + "@backstage/plugin-techdocs-backend": "^0.9.0", "@backstage/plugin-todo-backend": "^0.1.8", "@gitbeaker/node": "^30.2.0", "@octokit/rest": "^18.5.3", @@ -63,7 +63,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.7.4", + "@backstage/cli": "^0.7.5", "@types/dockerode": "^3.2.1", "@types/express": "^4.17.6", "@types/express-serve-static-core": "^4.17.5" diff --git a/packages/catalog-client/CHANGELOG.md b/packages/catalog-client/CHANGELOG.md index 70b79b961f..d417f74f96 100644 --- a/packages/catalog-client/CHANGELOG.md +++ b/packages/catalog-client/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/catalog-client +## 0.3.17 + +### Patch Changes + +- 71c936eb6: Export `CatalogRequestOptions` type + ## 0.3.16 ### Patch Changes diff --git a/packages/catalog-client/package.json b/packages/catalog-client/package.json index fbb37d2449..a114871a6e 100644 --- a/packages/catalog-client/package.json +++ b/packages/catalog-client/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/catalog-client", - "version": "0.3.16", + "version": "0.3.17", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -35,7 +35,7 @@ "cross-fetch": "^3.0.6" }, "devDependencies": { - "@backstage/cli": "^0.7.3", + "@backstage/cli": "^0.7.5", "@types/jest": "^26.0.7", "msw": "^0.29.0" }, diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 3dba2e4ec7..b2877572fe 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/cli +## 0.7.5 + +### Patch Changes + +- 9a96b5da7: chore: bump `eslint` to `7.30.0` + ## 0.7.4 ### Patch Changes diff --git a/packages/cli/package.json b/packages/cli/package.json index c539b1a382..1adaeaf725 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/cli", "description": "CLI for developing Backstage plugins and apps", - "version": "0.7.4", + "version": "0.7.5", "private": false, "publishConfig": { "access": "public" @@ -118,13 +118,13 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/backend-common": "^0.8.6", + "@backstage/backend-common": "^0.8.7", "@backstage/config": "^0.1.5", - "@backstage/core-components": "^0.1.4", + "@backstage/core-components": "^0.1.6", "@backstage/core-plugin-api": "^0.1.3", - "@backstage/core-app-api": "^0.1.4", - "@backstage/dev-utils": "^0.2.2", - "@backstage/test-utils": "^0.1.14", + "@backstage/core-app-api": "^0.1.5", + "@backstage/dev-utils": "^0.2.3", + "@backstage/test-utils": "^0.1.15", "@backstage/theme": "^0.2.8", "@types/diff": "^5.0.0", "@types/express": "^4.17.6", diff --git a/packages/codemods/CHANGELOG.md b/packages/codemods/CHANGELOG.md index dc1f694dca..0c104d6c33 100644 --- a/packages/codemods/CHANGELOG.md +++ b/packages/codemods/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/codemods +## 0.1.6 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.1.6 + - @backstage/core-app-api@0.1.5 + ## 0.1.5 ### Patch Changes diff --git a/packages/codemods/package.json b/packages/codemods/package.json index 9cf5730126..19c71c843c 100644 --- a/packages/codemods/package.json +++ b/packages/codemods/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/codemods", "description": "A collection of codemods for Backstage projects", - "version": "0.1.5", + "version": "0.1.6", "private": false, "publishConfig": { "access": "public", diff --git a/packages/core-app-api/CHANGELOG.md b/packages/core-app-api/CHANGELOG.md index 4f09f231c3..8e188a6577 100644 --- a/packages/core-app-api/CHANGELOG.md +++ b/packages/core-app-api/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/core-app-api +## 0.1.5 + +### Patch Changes + +- ea249c6e6: Fix a bug in `FlatRoutes` that prevented outlets from working with the root route, as well as matching root routes too broadly. +- Updated dependencies + - @backstage/core-components@0.1.6 + ## 0.1.4 ### Patch Changes diff --git a/packages/core-app-api/package.json b/packages/core-app-api/package.json index 0f6773497b..41e7c52393 100644 --- a/packages/core-app-api/package.json +++ b/packages/core-app-api/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core-app-api", "description": "Core app API used by Backstage apps", - "version": "0.1.4", + "version": "0.1.5", "private": false, "publishConfig": { "access": "public", @@ -29,7 +29,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-components": "^0.1.4", + "@backstage/core-components": "^0.1.6", "@backstage/config": "^0.1.3", "@backstage/core-plugin-api": "^0.1.3", "@backstage/theme": "^0.2.8", @@ -44,8 +44,8 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.7.3", - "@backstage/test-utils": "^0.1.14", + "@backstage/cli": "^0.7.5", + "@backstage/test-utils": "^0.1.15", "@backstage/test-utils-core": "^0.1.1", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", diff --git a/packages/core-components/CHANGELOG.md b/packages/core-components/CHANGELOG.md index 1536cf3fb2..8372b25563 100644 --- a/packages/core-components/CHANGELOG.md +++ b/packages/core-components/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/core-components +## 0.1.6 + +### Patch Changes + +- 9a751bb28: Increase the vertical padding of the sidebar search input field to match the height of the parent anchor tag. This prevents users from accidentally navigating to the search page when they actually wanted to use the search input directly. +- 45b5fc3a8: Updated the layout of catalog and API index pages to handle smaller screen sizes. This adds responsive wrappers to the entity tables, and switches filters to a drawer when width-constrained. If you have created a custom catalog or API index page, you will need to update the page structure to match the updated [catalog customization](https://backstage.io/docs/features/software-catalog/catalog-customization) documentation. +- 03bf17e9b: Improve the responsiveness of the EntityPage UI. With this the Header component should scale with the screen size & wrapping should not cause overflowing/blocking of links. Additionally enforce the Pages using the Grid Layout to use it across all screen sizes & to wrap as intended. + + To benefit from the improved responsive layout, the `EntityPage` in existing Backstage applications should be updated to set the `xs` column size on each grid item in the page, as this does not default. For example: + + ```diff + - + + + ``` + ## 0.1.5 ### Patch Changes diff --git a/packages/core-components/package.json b/packages/core-components/package.json index 0bd4b95134..944002c74f 100644 --- a/packages/core-components/package.json +++ b/packages/core-components/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core-components", "description": "Core components used by Backstage plugins and apps", - "version": "0.1.5", + "version": "0.1.6", "private": false, "publishConfig": { "access": "public", @@ -70,9 +70,9 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/core-app-api": "^0.1.4", - "@backstage/cli": "^0.7.3", - "@backstage/test-utils": "^0.1.13", + "@backstage/core-app-api": "^0.1.5", + "@backstage/cli": "^0.7.5", + "@backstage/test-utils": "^0.1.15", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md index 86a7c3e3fe..1bbb66710b 100644 --- a/packages/create-app/CHANGELOG.md +++ b/packages/create-app/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/create-app +## 0.3.32 + +### Patch Changes + +- 03bf17e9b: Improve the responsiveness of the EntityPage UI. With this the Header component should scale with the screen size & wrapping should not cause overflowing/blocking of links. Additionally enforce the Pages using the Grid Layout to use it across all screen sizes & to wrap as intended. + + To benefit from the improved responsive layout, the `EntityPage` in existing Backstage applications should be updated to set the `xs` column size on each grid item in the page, as this does not default. For example: + + ```diff + - + + + ``` + +- eb740ee24: Moved sample software templates to the [backstage/software-templates](https://github.com/backstage/software-templates) repository. If you previously referenced the sample templates straight from `scaffolder-backend` plugin in the main [backstage/backstage](https://github.com/backstage/backstage) repository in your `app-config.yaml`, these references will need to be updated. + + See https://github.com/backstage/software-templates + ## 0.3.31 ### Patch Changes diff --git a/packages/create-app/package.json b/packages/create-app/package.json index 52fa3f7237..cedcba42c7 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/create-app", "description": "Create app package for Backstage", - "version": "0.3.31", + "version": "0.3.32", "private": false, "publishConfig": { "access": "public" diff --git a/packages/dev-utils/CHANGELOG.md b/packages/dev-utils/CHANGELOG.md index af3a283a04..ea620350ef 100644 --- a/packages/dev-utils/CHANGELOG.md +++ b/packages/dev-utils/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/dev-utils +## 0.2.3 + +### Patch Changes + +- 01001a324: Allow custom theme for dev app. +- Updated dependencies + - @backstage/core-components@0.1.6 + - @backstage/plugin-catalog-react@0.3.1 + - @backstage/core-app-api@0.1.5 + - @backstage/test-utils@0.1.15 + ## 0.2.2 ### Patch Changes diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index 2825a53822..a80745284c 100644 --- a/packages/dev-utils/package.json +++ b/packages/dev-utils/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/dev-utils", "description": "Utilities for developing Backstage plugins.", - "version": "0.2.2", + "version": "0.2.3", "private": false, "publishConfig": { "access": "public", @@ -29,13 +29,13 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-app-api": "^0.1.3", - "@backstage/core-components": "^0.1.5", + "@backstage/core-app-api": "^0.1.5", + "@backstage/core-components": "^0.1.6", "@backstage/core-plugin-api": "^0.1.3", "@backstage/catalog-model": "^0.9.0", "@backstage/integration-react": "^0.1.4", - "@backstage/plugin-catalog-react": "^0.3.0", - "@backstage/test-utils": "^0.1.14", + "@backstage/plugin-catalog-react": "^0.3.1", + "@backstage/test-utils": "^0.1.15", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -50,7 +50,7 @@ "react-router-dom": "6.0.0-beta.0" }, "devDependencies": { - "@backstage/cli": "^0.7.4", + "@backstage/cli": "^0.7.5", "@types/jest": "^26.0.7", "@types/node": "^14.14.32" }, diff --git a/packages/techdocs-common/CHANGELOG.md b/packages/techdocs-common/CHANGELOG.md index c26be7fa7c..ab5eff10e3 100644 --- a/packages/techdocs-common/CHANGELOG.md +++ b/packages/techdocs-common/CHANGELOG.md @@ -1,5 +1,100 @@ # @backstage/techdocs-common +## 0.7.0 + +### Minor Changes + +- d32d01e5b: Improve the annotation `backstage.io/techdocs-ref: dir:` that links to a path that is relative to the source of the annotated entity. + This annotation works with the basic and the recommended flow, however, it will be most useful with the basic approach. + + This change remove the deprecation of the `dir` reference and provides first-class support for it. + In addition, this change removes the support of the deprecated `github`, `gitlab`, and `azure/api` locations from the `dir` reference preparer. + + #### Example Usage + + The annotation is convenient if the documentation is stored in the same location, i.e. the same git repository, as the `catalog-info.yaml`. + While it is still supported to add full URLs such as `backstage.io/techdocs-ref: url:https://...` for custom setups, documentation is mostly stored in the same repository as the entity definition. + By automatically resolving the target relative to the registration location of the entity, the configuration overhead for this default setup is minimized. + Since it leverages the `@backstage/integrations` package for the URL resolution, this is compatible with every supported source. + + Consider the following examples: + + 1. "I have a repository with a single `catalog-info.yaml` and a TechDocs page in the root folder!" + + ``` + https://github.com/backstage/example/tree/main/ + |- catalog-info.yaml + | > apiVersion: backstage.io/v1alpha1 + | > kind: Component + | > metadata: + | > name: example + | > annotations: + | > backstage.io/techdocs-ref: dir:. # -> same folder + | > spec: {} + |- docs/ + |- mkdocs.yml + ``` + + 2. "I have a repository with a single `catalog-info.yaml` and my TechDocs page in located in a folder!" + + ``` + https://bitbucket.org/my-owner/my-project/src/master/ + |- catalog-info.yaml + | > apiVersion: backstage.io/v1alpha1 + | > kind: Component + | > metadata: + | > name: example + | > annotations: + | > backstage.io/techdocs-ref: dir:./some-folder # -> subfolder + | > spec: {} + |- some-folder/ + |- docs/ + |- mkdocs.yml + ``` + + 3. "I have a mono repository that hosts multiple components!" + + ``` + https://dev.azure.com/organization/project/_git/repository + |- my-1st-module/ + |- catalog-info.yaml + | > apiVersion: backstage.io/v1alpha1 + | > kind: Component + | > metadata: + | > name: my-1st-module + | > annotations: + | > backstage.io/techdocs-ref: dir:. # -> same folder + | > spec: {} + |- docs/ + |- mkdocs.yml + |- my-2nd-module/ + |- catalog-info.yaml + | > apiVersion: backstage.io/v1alpha1 + | > kind: Component + | > metadata: + | > name: my-2nd-module + | > annotations: + | > backstage.io/techdocs-ref: dir:. # -> same folder + | > spec: {} + |- docs/ + |- mkdocs.yml + |- catalog-info.yaml + | > apiVersion: backstage.io/v1alpha1 + | > kind: Location + | > metadata: + | > name: example + | > spec: + | > targets: + | > - ./*/catalog-info.yaml + ``` + +### Patch Changes + +- 6e5aed1c9: Fix validation of mkdocs.yml docs_dir +- 250984333: Add link to https://backstage.io/docs/features/techdocs/configuration in the log warning message about updating techdocs.generate key. +- Updated dependencies + - @backstage/backend-common@0.8.7 + ## 0.6.8 ### Patch Changes diff --git a/packages/techdocs-common/package.json b/packages/techdocs-common/package.json index 3fac9df64f..bb81c875fd 100644 --- a/packages/techdocs-common/package.json +++ b/packages/techdocs-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/techdocs-common", "description": "Common functionalities for TechDocs, to be shared between techdocs-backend plugin and techdocs-cli", - "version": "0.6.8", + "version": "0.7.0", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -38,7 +38,7 @@ "dependencies": { "@azure/identity": "^1.5.0", "@azure/storage-blob": "^12.5.0", - "@backstage/backend-common": "^0.8.6", + "@backstage/backend-common": "^0.8.7", "@backstage/catalog-model": "^0.9.0", "@backstage/config": "^0.1.5", "@backstage/errors": "^0.1.1", @@ -60,7 +60,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.7.4", + "@backstage/cli": "^0.7.5", "@types/fs-extra": "^9.0.5", "@types/git-url-parse": "^9.0.0", "@types/js-yaml": "^4.0.0", diff --git a/packages/test-utils/CHANGELOG.md b/packages/test-utils/CHANGELOG.md index 68e45cd831..12627e55a0 100644 --- a/packages/test-utils/CHANGELOG.md +++ b/packages/test-utils/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/test-utils +## 0.1.15 + +### Patch Changes + +- 45b5fc3a8: Updated the layout of catalog and API index pages to handle smaller screen sizes. This adds responsive wrappers to the entity tables, and switches filters to a drawer when width-constrained. If you have created a custom catalog or API index page, you will need to update the page structure to match the updated [catalog customization](https://backstage.io/docs/features/software-catalog/catalog-customization) documentation. +- Updated dependencies + - @backstage/core-app-api@0.1.5 + ## 0.1.14 ### Patch Changes diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index 797a23b17f..cfe4f2198e 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/test-utils", "description": "Utilities to test Backstage plugins and apps.", - "version": "0.1.14", + "version": "0.1.15", "private": false, "publishConfig": { "access": "public", @@ -29,7 +29,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-app-api": "^0.1.3", + "@backstage/core-app-api": "^0.1.5", "@backstage/core-plugin-api": "^0.1.3", "@backstage/test-utils-core": "^0.1.1", "@backstage/theme": "^0.2.8", @@ -46,7 +46,7 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.7.2", + "@backstage/cli": "^0.7.5", "@types/jest": "^26.0.7", "@types/node": "^14.14.32" }, diff --git a/plugins/api-docs/CHANGELOG.md b/plugins/api-docs/CHANGELOG.md index bcb21865c1..dfa9130b4d 100644 --- a/plugins/api-docs/CHANGELOG.md +++ b/plugins/api-docs/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-api-docs +## 0.6.3 + +### Patch Changes + +- 45b5fc3a8: Updated the layout of catalog and API index pages to handle smaller screen sizes. This adds responsive wrappers to the entity tables, and switches filters to a drawer when width-constrained. If you have created a custom catalog or API index page, you will need to update the page structure to match the updated [catalog customization](https://backstage.io/docs/features/software-catalog/catalog-customization) documentation. +- Updated dependencies + - @backstage/core-components@0.1.6 + - @backstage/plugin-catalog@0.6.8 + - @backstage/plugin-catalog-react@0.3.1 + ## 0.6.2 ### Patch Changes diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index 7d4edc41f8..f02945c14c 100644 --- a/plugins/api-docs/package.json +++ b/plugins/api-docs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-api-docs", - "version": "0.6.2", + "version": "0.6.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,10 +31,10 @@ "dependencies": { "@asyncapi/react-component": "^0.23.0", "@backstage/catalog-model": "^0.9.0", - "@backstage/core-components": "^0.1.5", + "@backstage/core-components": "^0.1.6", "@backstage/core-plugin-api": "^0.1.3", - "@backstage/plugin-catalog": "^0.6.7", - "@backstage/plugin-catalog-react": "^0.3.0", + "@backstage/plugin-catalog": "^0.6.8", + "@backstage/plugin-catalog-react": "^0.3.1", "@backstage/theme": "^0.2.8", "@material-icons/font": "^1.0.2", "@material-ui/core": "^4.11.0", @@ -51,10 +51,10 @@ "swagger-ui-react": "^3.37.2" }, "devDependencies": { - "@backstage/cli": "^0.7.4", - "@backstage/core-app-api": "^0.1.4", - "@backstage/dev-utils": "^0.2.2", - "@backstage/test-utils": "^0.1.14", + "@backstage/cli": "^0.7.5", + "@backstage/core-app-api": "^0.1.5", + "@backstage/dev-utils": "^0.2.3", + "@backstage/test-utils": "^0.1.15", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/auth-backend/CHANGELOG.md b/plugins/auth-backend/CHANGELOG.md index 362eda35a7..218e20cd0e 100644 --- a/plugins/auth-backend/CHANGELOG.md +++ b/plugins/auth-backend/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-auth-backend +## 0.3.18 + +### Patch Changes + +- 2567c066d: TokenIssuer is now exported so it may be used by auth providers that are not bundled with Backstage +- Updated dependencies + - @backstage/catalog-client@0.3.17 + - @backstage/backend-common@0.8.7 + - @backstage/test-utils@0.1.15 + ## 0.3.17 ### Patch Changes diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index 6e2e35997e..07097a880f 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend", - "version": "0.3.17", + "version": "0.3.18", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -29,12 +29,12 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.8.6", - "@backstage/catalog-client": "^0.3.16", + "@backstage/backend-common": "^0.8.7", + "@backstage/catalog-client": "^0.3.17", "@backstage/catalog-model": "^0.9.0", "@backstage/config": "^0.1.5", "@backstage/errors": "^0.1.1", - "@backstage/test-utils": "^0.1.14", + "@backstage/test-utils": "^0.1.15", "@types/express": "^4.17.6", "@types/passport": "^1.0.3", "compression": "^1.7.4", @@ -68,7 +68,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.7.4", + "@backstage/cli": "^0.7.5", "@types/body-parser": "^1.19.0", "@types/cookie-parser": "^1.4.2", "@types/express-session": "^1.17.2", diff --git a/plugins/badges/package.json b/plugins/badges/package.json index 2009457705..3d38bc6d92 100644 --- a/plugins/badges/package.json +++ b/plugins/badges/package.json @@ -21,7 +21,7 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.0", - "@backstage/core-components": "^0.1.5", + "@backstage/core-components": "^0.1.6", "@backstage/core-plugin-api": "^0.1.3", "@backstage/errors": "^0.1.1", "@backstage/plugin-catalog-react": "^0.3.0", @@ -35,10 +35,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.4", - "@backstage/core-app-api": "^0.1.4", - "@backstage/dev-utils": "^0.2.2", - "@backstage/test-utils": "^0.1.14", + "@backstage/cli": "^0.7.5", + "@backstage/core-app-api": "^0.1.5", + "@backstage/dev-utils": "^0.2.3", + "@backstage/test-utils": "^0.1.15", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/bitrise/package.json b/plugins/bitrise/package.json index cf7a5d33e1..00eb35f055 100644 --- a/plugins/bitrise/package.json +++ b/plugins/bitrise/package.json @@ -21,7 +21,7 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.0", - "@backstage/core-components": "^0.1.5", + "@backstage/core-components": "^0.1.6", "@backstage/core-plugin-api": "^0.1.3", "@backstage/plugin-catalog-react": "^0.3.0", "@backstage/theme": "^0.2.8", @@ -38,10 +38,10 @@ "recharts": "^1.8.5" }, "devDependencies": { - "@backstage/cli": "^0.7.4", - "@backstage/core-app-api": "^0.1.4", - "@backstage/dev-utils": "^0.2.2", - "@backstage/test-utils": "^0.1.14", + "@backstage/cli": "^0.7.5", + "@backstage/core-app-api": "^0.1.5", + "@backstage/dev-utils": "^0.2.3", + "@backstage/test-utils": "^0.1.15", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/catalog-import/CHANGELOG.md b/plugins/catalog-import/CHANGELOG.md index df4bd02cb9..ad1a05da5e 100644 --- a/plugins/catalog-import/CHANGELOG.md +++ b/plugins/catalog-import/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-catalog-import +## 0.5.14 + +### Patch Changes + +- 903f3323c: Fix heading that wrongly implied catalog-import supports entity discovery for multiple integrations. +- Updated dependencies + - @backstage/core-components@0.1.6 + - @backstage/catalog-client@0.3.17 + - @backstage/plugin-catalog-react@0.3.1 + ## 0.5.13 ### Patch Changes diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index 857dc0a837..d6f371bc65 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-import", - "version": "0.5.13", + "version": "0.5.14", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,13 +30,13 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-client": "^0.3.16", + "@backstage/catalog-client": "^0.3.17", "@backstage/catalog-model": "^0.9.0", - "@backstage/core-components": "^0.1.5", + "@backstage/core-components": "^0.1.6", "@backstage/core-plugin-api": "^0.1.3", "@backstage/integration": "^0.5.8", "@backstage/integration-react": "^0.1.4", - "@backstage/plugin-catalog-react": "^0.3.0", + "@backstage/plugin-catalog-react": "^0.3.1", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -54,10 +54,10 @@ "yaml": "^1.10.0" }, "devDependencies": { - "@backstage/cli": "^0.7.4", - "@backstage/core-app-api": "^0.1.4", - "@backstage/dev-utils": "^0.2.2", - "@backstage/test-utils": "^0.1.14", + "@backstage/cli": "^0.7.5", + "@backstage/core-app-api": "^0.1.5", + "@backstage/dev-utils": "^0.2.3", + "@backstage/test-utils": "^0.1.15", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^3.3.0", diff --git a/plugins/catalog-react/CHANGELOG.md b/plugins/catalog-react/CHANGELOG.md index aa2636df02..b0ce191ec8 100644 --- a/plugins/catalog-react/CHANGELOG.md +++ b/plugins/catalog-react/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-catalog-react +## 0.3.1 + +### Patch Changes + +- 221d7d060: added retry callback to useEntity hook +- Updated dependencies + - @backstage/core-components@0.1.6 + - @backstage/catalog-client@0.3.17 + - @backstage/core-app-api@0.1.5 + ## 0.3.0 ### Minor Changes diff --git a/plugins/catalog-react/package.json b/plugins/catalog-react/package.json index e1b3beda4e..d0cb5ac947 100644 --- a/plugins/catalog-react/package.json +++ b/plugins/catalog-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-react", - "version": "0.3.0", + "version": "0.3.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -28,10 +28,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-client": "^0.3.16", + "@backstage/catalog-client": "^0.3.17", "@backstage/catalog-model": "^0.9.0", - "@backstage/core-app-api": "^0.1.4", - "@backstage/core-components": "^0.1.5", + "@backstage/core-app-api": "^0.1.5", + "@backstage/core-components": "^0.1.6", "@backstage/core-plugin-api": "^0.1.3", "@backstage/integration": "^0.5.8", "@material-ui/core": "^4.11.0", @@ -46,9 +46,9 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.4", - "@backstage/dev-utils": "^0.2.2", - "@backstage/test-utils": "^0.1.14", + "@backstage/cli": "^0.7.5", + "@backstage/dev-utils": "^0.2.3", + "@backstage/test-utils": "^0.1.15", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^3.3.0", diff --git a/plugins/catalog/CHANGELOG.md b/plugins/catalog/CHANGELOG.md index 2255aae527..295c1fe15c 100644 --- a/plugins/catalog/CHANGELOG.md +++ b/plugins/catalog/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/plugin-catalog +## 0.6.8 + +### Patch Changes + +- 221d7d060: added retry callback to useEntity hook +- 45b5fc3a8: Updated the layout of catalog and API index pages to handle smaller screen sizes. This adds responsive wrappers to the entity tables, and switches filters to a drawer when width-constrained. If you have created a custom catalog or API index page, you will need to update the page structure to match the updated [catalog customization](https://backstage.io/docs/features/software-catalog/catalog-customization) documentation. +- 71c936eb6: Export `CatalogClientWrapper` class +- 03bf17e9b: Improve the responsiveness of the EntityPage UI. With this the Header component should scale with the screen size & wrapping should not cause overflowing/blocking of links. Additionally enforce the Pages using the Grid Layout to use it across all screen sizes & to wrap as intended. + + To benefit from the improved responsive layout, the `EntityPage` in existing Backstage applications should be updated to set the `xs` column size on each grid item in the page, as this does not default. For example: + + ```diff + - + + + ``` + +- Updated dependencies + - @backstage/core-components@0.1.6 + - @backstage/catalog-client@0.3.17 + - @backstage/plugin-catalog-react@0.3.1 + ## 0.6.7 ### Patch Changes diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index e89b368474..821e029616 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog", - "version": "0.6.7", + "version": "0.6.8", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,14 +30,14 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-client": "^0.3.16", + "@backstage/catalog-client": "^0.3.17", "@backstage/catalog-model": "^0.9.0", - "@backstage/core-components": "^0.1.5", + "@backstage/core-components": "^0.1.6", "@backstage/core-plugin-api": "^0.1.3", "@backstage/errors": "^0.1.1", "@backstage/integration": "^0.5.8", "@backstage/integration-react": "^0.1.4", - "@backstage/plugin-catalog-react": "^0.3.0", + "@backstage/plugin-catalog-react": "^0.3.1", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -54,10 +54,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.4", - "@backstage/core-app-api": "^0.1.4", - "@backstage/dev-utils": "^0.2.2", - "@backstage/test-utils": "^0.1.14", + "@backstage/cli": "^0.7.5", + "@backstage/core-app-api": "^0.1.5", + "@backstage/dev-utils": "^0.2.3", + "@backstage/test-utils": "^0.1.15", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^3.3.0", diff --git a/plugins/circleci/package.json b/plugins/circleci/package.json index caee4585f3..52aae08b57 100644 --- a/plugins/circleci/package.json +++ b/plugins/circleci/package.json @@ -32,7 +32,7 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.0", - "@backstage/core-components": "^0.1.5", + "@backstage/core-components": "^0.1.6", "@backstage/core-plugin-api": "^0.1.3", "@backstage/plugin-catalog-react": "^0.3.0", "@backstage/theme": "^0.2.8", @@ -50,10 +50,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.4", - "@backstage/core-app-api": "^0.1.4", - "@backstage/dev-utils": "^0.2.2", - "@backstage/test-utils": "^0.1.14", + "@backstage/cli": "^0.7.5", + "@backstage/core-app-api": "^0.1.5", + "@backstage/dev-utils": "^0.2.3", + "@backstage/test-utils": "^0.1.15", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/cloudbuild/package.json b/plugins/cloudbuild/package.json index da5c749378..ee9f8a4e0c 100644 --- a/plugins/cloudbuild/package.json +++ b/plugins/cloudbuild/package.json @@ -31,7 +31,7 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.0", - "@backstage/core-components": "^0.1.5", + "@backstage/core-components": "^0.1.6", "@backstage/core-plugin-api": "^0.1.3", "@backstage/plugin-catalog-react": "^0.3.0", "@backstage/theme": "^0.2.8", @@ -48,10 +48,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.4", - "@backstage/core-app-api": "^0.1.4", - "@backstage/dev-utils": "^0.2.2", - "@backstage/test-utils": "^0.1.14", + "@backstage/cli": "^0.7.5", + "@backstage/core-app-api": "^0.1.5", + "@backstage/dev-utils": "^0.2.3", + "@backstage/test-utils": "^0.1.15", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/code-coverage/package.json b/plugins/code-coverage/package.json index 11844beebb..bd632972df 100644 --- a/plugins/code-coverage/package.json +++ b/plugins/code-coverage/package.json @@ -22,7 +22,7 @@ "dependencies": { "@backstage/catalog-model": "^0.9.0", "@backstage/config": "^0.1.4", - "@backstage/core-components": "^0.1.5", + "@backstage/core-components": "^0.1.6", "@backstage/core-plugin-api": "^0.1.3", "@backstage/errors": "^0.1.1", "@backstage/plugin-catalog-react": "^0.3.0", @@ -40,10 +40,10 @@ "recharts": "^1.8.5" }, "devDependencies": { - "@backstage/cli": "^0.7.4", - "@backstage/core-app-api": "^0.1.4", - "@backstage/dev-utils": "^0.2.2", - "@backstage/test-utils": "^0.1.14", + "@backstage/cli": "^0.7.5", + "@backstage/core-app-api": "^0.1.5", + "@backstage/dev-utils": "^0.2.3", + "@backstage/test-utils": "^0.1.15", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/config-schema/package.json b/plugins/config-schema/package.json index 5fe1d3a612..7ef279f123 100644 --- a/plugins/config-schema/package.json +++ b/plugins/config-schema/package.json @@ -21,7 +21,7 @@ }, "dependencies": { "@backstage/config": "^0.1.4", - "@backstage/core-components": "^0.1.5", + "@backstage/core-components": "^0.1.6", "@backstage/core-plugin-api": "^0.1.3", "@backstage/errors": "^0.1.1", "@backstage/theme": "^0.2.8", @@ -35,10 +35,10 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.7.4", - "@backstage/core-app-api": "^0.1.4", - "@backstage/dev-utils": "^0.2.2", - "@backstage/test-utils": "^0.1.14", + "@backstage/cli": "^0.7.5", + "@backstage/core-app-api": "^0.1.5", + "@backstage/dev-utils": "^0.2.3", + "@backstage/test-utils": "^0.1.15", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/cost-insights/package.json b/plugins/cost-insights/package.json index feb6d4122c..2f6fac1479 100644 --- a/plugins/cost-insights/package.json +++ b/plugins/cost-insights/package.json @@ -31,7 +31,7 @@ }, "dependencies": { "@backstage/config": "^0.1.5", - "@backstage/core-components": "^0.1.5", + "@backstage/core-components": "^0.1.6", "@backstage/core-plugin-api": "^0.1.3", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", @@ -54,10 +54,10 @@ "yup": "^0.29.3" }, "devDependencies": { - "@backstage/cli": "^0.7.4", - "@backstage/core-app-api": "^0.1.4", - "@backstage/dev-utils": "^0.2.2", - "@backstage/test-utils": "^0.1.14", + "@backstage/cli": "^0.7.5", + "@backstage/core-app-api": "^0.1.5", + "@backstage/dev-utils": "^0.2.3", + "@backstage/test-utils": "^0.1.15", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/explore/package.json b/plugins/explore/package.json index 7da4fe89fd..2059632c08 100644 --- a/plugins/explore/package.json +++ b/plugins/explore/package.json @@ -31,7 +31,7 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.0", - "@backstage/core-components": "^0.1.5", + "@backstage/core-components": "^0.1.6", "@backstage/core-plugin-api": "^0.1.3", "@backstage/plugin-catalog-react": "^0.3.0", "@backstage/plugin-explore-react": "^0.0.6", @@ -48,10 +48,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.4", - "@backstage/core-app-api": "^0.1.4", - "@backstage/dev-utils": "^0.2.2", - "@backstage/test-utils": "^0.1.14", + "@backstage/cli": "^0.7.5", + "@backstage/core-app-api": "^0.1.5", + "@backstage/dev-utils": "^0.2.3", + "@backstage/test-utils": "^0.1.15", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/fossa/package.json b/plugins/fossa/package.json index 7cb5b5febf..cc4a27a5c0 100644 --- a/plugins/fossa/package.json +++ b/plugins/fossa/package.json @@ -32,7 +32,7 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.0", - "@backstage/core-components": "^0.1.5", + "@backstage/core-components": "^0.1.6", "@backstage/core-plugin-api": "^0.1.3", "@backstage/errors": "^0.1.1", "@backstage/plugin-catalog-react": "^0.3.0", @@ -48,10 +48,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.4", - "@backstage/core-app-api": "^0.1.4", - "@backstage/dev-utils": "^0.2.2", - "@backstage/test-utils": "^0.1.14", + "@backstage/cli": "^0.7.5", + "@backstage/core-app-api": "^0.1.5", + "@backstage/dev-utils": "^0.2.3", + "@backstage/test-utils": "^0.1.15", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/gcp-projects/package.json b/plugins/gcp-projects/package.json index 3e37832e46..1d04d7f1d5 100644 --- a/plugins/gcp-projects/package.json +++ b/plugins/gcp-projects/package.json @@ -30,7 +30,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-components": "^0.1.5", + "@backstage/core-components": "^0.1.6", "@backstage/core-plugin-api": "^0.1.3", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", @@ -42,10 +42,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.4", - "@backstage/core-app-api": "^0.1.4", - "@backstage/dev-utils": "^0.2.2", - "@backstage/test-utils": "^0.1.14", + "@backstage/cli": "^0.7.5", + "@backstage/core-app-api": "^0.1.5", + "@backstage/dev-utils": "^0.2.3", + "@backstage/test-utils": "^0.1.15", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/git-release-manager/CHANGELOG.md b/plugins/git-release-manager/CHANGELOG.md index 19538e822a..6f2eaae984 100644 --- a/plugins/git-release-manager/CHANGELOG.md +++ b/plugins/git-release-manager/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-git-release-manager +## 0.2.0 + +### Minor Changes + +- a2d8922c9: Enable users to add custom features + + Add more metadata to success callbacks + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.1.6 + ## 0.1.3 ### Patch Changes diff --git a/plugins/git-release-manager/package.json b/plugins/git-release-manager/package.json index 676550ae10..5598fcd6ef 100644 --- a/plugins/git-release-manager/package.json +++ b/plugins/git-release-manager/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-git-release-manager", - "version": "0.1.3", + "version": "0.2.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,7 +20,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-components": "^0.1.5", + "@backstage/core-components": "^0.1.6", "@backstage/core-plugin-api": "^0.1.3", "@backstage/integration": "^0.5.6", "@backstage/theme": "^0.2.8", @@ -38,10 +38,10 @@ "recharts": "^1.8.5" }, "devDependencies": { - "@backstage/cli": "^0.7.4", - "@backstage/core-app-api": "^0.1.4", - "@backstage/dev-utils": "^0.2.2", - "@backstage/test-utils": "^0.1.14", + "@backstage/cli": "^0.7.5", + "@backstage/core-app-api": "^0.1.5", + "@backstage/dev-utils": "^0.2.3", + "@backstage/test-utils": "^0.1.15", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react-hooks": "^3.4.2", "@testing-library/react": "^11.2.5", diff --git a/plugins/github-actions/package.json b/plugins/github-actions/package.json index 383f880f2b..d85d1e53fe 100644 --- a/plugins/github-actions/package.json +++ b/plugins/github-actions/package.json @@ -33,7 +33,7 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.0", - "@backstage/core-components": "^0.1.5", + "@backstage/core-components": "^0.1.6", "@backstage/core-plugin-api": "^0.1.3", "@backstage/integration": "^0.5.8", "@backstage/plugin-catalog-react": "^0.3.0", @@ -51,10 +51,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.4", - "@backstage/core-app-api": "^0.1.4", - "@backstage/dev-utils": "^0.2.2", - "@backstage/test-utils": "^0.1.14", + "@backstage/cli": "^0.7.5", + "@backstage/core-app-api": "^0.1.5", + "@backstage/dev-utils": "^0.2.3", + "@backstage/test-utils": "^0.1.15", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/github-deployments/package.json b/plugins/github-deployments/package.json index 07973b1a03..9561083063 100644 --- a/plugins/github-deployments/package.json +++ b/plugins/github-deployments/package.json @@ -21,7 +21,7 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.0", - "@backstage/core-components": "^0.1.5", + "@backstage/core-components": "^0.1.6", "@backstage/core-plugin-api": "^0.1.3", "@backstage/errors": "^0.1.1", "@backstage/integration": "^0.5.8", @@ -38,10 +38,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.4", - "@backstage/core-app-api": "^0.1.4", - "@backstage/dev-utils": "^0.2.2", - "@backstage/test-utils": "^0.1.14", + "@backstage/cli": "^0.7.5", + "@backstage/core-app-api": "^0.1.5", + "@backstage/dev-utils": "^0.2.3", + "@backstage/test-utils": "^0.1.15", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/gitops-profiles/package.json b/plugins/gitops-profiles/package.json index 246a703d95..e7e45ee2a6 100644 --- a/plugins/gitops-profiles/package.json +++ b/plugins/gitops-profiles/package.json @@ -31,7 +31,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-components": "^0.1.5", + "@backstage/core-components": "^0.1.6", "@backstage/core-plugin-api": "^0.1.3", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", @@ -43,10 +43,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.4", - "@backstage/core-app-api": "^0.1.4", - "@backstage/dev-utils": "^0.2.2", - "@backstage/test-utils": "^0.1.14", + "@backstage/cli": "^0.7.5", + "@backstage/core-app-api": "^0.1.5", + "@backstage/dev-utils": "^0.2.3", + "@backstage/test-utils": "^0.1.15", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/graphiql/package.json b/plugins/graphiql/package.json index c289761bc3..46f44cdcf4 100644 --- a/plugins/graphiql/package.json +++ b/plugins/graphiql/package.json @@ -31,7 +31,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-components": "^0.1.5", + "@backstage/core-components": "^0.1.6", "@backstage/core-plugin-api": "^0.1.3", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", @@ -44,10 +44,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.4", - "@backstage/core-app-api": "^0.1.4", - "@backstage/dev-utils": "^0.2.2", - "@backstage/test-utils": "^0.1.14", + "@backstage/cli": "^0.7.5", + "@backstage/core-app-api": "^0.1.5", + "@backstage/dev-utils": "^0.2.3", + "@backstage/test-utils": "^0.1.15", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/ilert/package.json b/plugins/ilert/package.json index e5bde7eca7..01a8e7b013 100644 --- a/plugins/ilert/package.json +++ b/plugins/ilert/package.json @@ -21,7 +21,7 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.0", - "@backstage/core-components": "^0.1.5", + "@backstage/core-components": "^0.1.6", "@backstage/core-plugin-api": "^0.1.3", "@backstage/errors": "^0.1.1", "@backstage/plugin-catalog-react": "^0.3.0", @@ -38,10 +38,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.4", - "@backstage/core-app-api": "^0.1.4", - "@backstage/dev-utils": "^0.2.2", - "@backstage/test-utils": "^0.1.14", + "@backstage/cli": "^0.7.5", + "@backstage/core-app-api": "^0.1.5", + "@backstage/dev-utils": "^0.2.3", + "@backstage/test-utils": "^0.1.15", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/jenkins-backend/CHANGELOG.md b/plugins/jenkins-backend/CHANGELOG.md new file mode 100644 index 0000000000..8f2a4bc576 --- /dev/null +++ b/plugins/jenkins-backend/CHANGELOG.md @@ -0,0 +1,10 @@ +# @backstage/plugin-jenkins-backend + +## 0.1.2 + +### Patch Changes + +- eee05803a: Update `@backstage/backend-common` to `^0.8.6` +- Updated dependencies + - @backstage/catalog-client@0.3.17 + - @backstage/backend-common@0.8.7 diff --git a/plugins/jenkins-backend/package.json b/plugins/jenkins-backend/package.json index 11c6c3cc0a..6f7beb9444 100644 --- a/plugins/jenkins-backend/package.json +++ b/plugins/jenkins-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-jenkins-backend", - "version": "0.1.1", + "version": "0.1.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,8 +21,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.8.6", - "@backstage/catalog-client": "^0.3.16", + "@backstage/backend-common": "^0.8.7", + "@backstage/catalog-client": "^0.3.17", "@backstage/catalog-model": "^0.9.0", "@backstage/config": "^0.1.5", "@types/express": "^4.17.6", @@ -34,7 +34,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.7.3", + "@backstage/cli": "^0.7.5", "@types/jenkins": "^0.23.1", "@types/supertest": "^2.0.8", "msw": "^0.29.0", diff --git a/plugins/jenkins/package.json b/plugins/jenkins/package.json index e4f3c6e9d8..6030beee8e 100644 --- a/plugins/jenkins/package.json +++ b/plugins/jenkins/package.json @@ -32,7 +32,7 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.0", - "@backstage/core-components": "^0.1.5", + "@backstage/core-components": "^0.1.6", "@backstage/core-plugin-api": "^0.1.3", "@backstage/plugin-catalog-react": "^0.3.0", "@backstage/theme": "^0.2.8", @@ -47,10 +47,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.4", - "@backstage/core-app-api": "^0.1.4", - "@backstage/dev-utils": "^0.2.2", - "@backstage/test-utils": "^0.1.14", + "@backstage/cli": "^0.7.5", + "@backstage/core-app-api": "^0.1.5", + "@backstage/dev-utils": "^0.2.3", + "@backstage/test-utils": "^0.1.15", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/kafka/package.json b/plugins/kafka/package.json index 3e74e9e374..a7328b0c0c 100644 --- a/plugins/kafka/package.json +++ b/plugins/kafka/package.json @@ -21,7 +21,7 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.0", - "@backstage/core-components": "^0.1.5", + "@backstage/core-components": "^0.1.6", "@backstage/core-plugin-api": "^0.1.3", "@backstage/plugin-catalog-react": "^0.3.0", "@backstage/theme": "^0.2.8", @@ -34,10 +34,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.4", - "@backstage/core-app-api": "^0.1.4", - "@backstage/dev-utils": "^0.2.2", - "@backstage/test-utils": "^0.1.14", + "@backstage/cli": "^0.7.5", + "@backstage/core-app-api": "^0.1.5", + "@backstage/dev-utils": "^0.2.3", + "@backstage/test-utils": "^0.1.15", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^3.4.2", diff --git a/plugins/kubernetes-backend/CHANGELOG.md b/plugins/kubernetes-backend/CHANGELOG.md index 7925c8bf4e..b5870c43e3 100644 --- a/plugins/kubernetes-backend/CHANGELOG.md +++ b/plugins/kubernetes-backend/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-kubernetes-backend +## 0.3.11 + +### Patch Changes + +- 5bd57f8f5: Support assume role on kubernetes api configuration for AWS. +- Updated dependencies + - @backstage/backend-common@0.8.7 + ## 0.3.10 ### Patch Changes diff --git a/plugins/kubernetes-backend/package.json b/plugins/kubernetes-backend/package.json index 720321a180..b76fb0a442 100644 --- a/plugins/kubernetes-backend/package.json +++ b/plugins/kubernetes-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes-backend", - "version": "0.3.10", + "version": "0.3.11", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,7 +31,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.8.6", + "@backstage/backend-common": "^0.8.7", "@backstage/catalog-model": "^0.9.0", "@backstage/config": "^0.1.5", "@backstage/plugin-kubernetes-common": "^0.1.2", @@ -53,7 +53,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.7.4", + "@backstage/cli": "^0.7.5", "@types/aws4": "^1.5.1", "supertest": "^6.1.3", "aws-sdk-mock": "^5.2.1", diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json index 2319f08087..ac9675bcf7 100644 --- a/plugins/kubernetes/package.json +++ b/plugins/kubernetes/package.json @@ -32,7 +32,7 @@ "dependencies": { "@backstage/catalog-model": "^0.9.0", "@backstage/config": "^0.1.5", - "@backstage/core-components": "^0.1.5", + "@backstage/core-components": "^0.1.6", "@backstage/core-plugin-api": "^0.1.3", "@backstage/plugin-catalog-react": "^0.3.0", "@backstage/plugin-kubernetes-common": "^0.1.2", @@ -50,10 +50,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.4", - "@backstage/core-app-api": "^0.1.4", - "@backstage/dev-utils": "^0.2.2", - "@backstage/test-utils": "^0.1.14", + "@backstage/cli": "^0.7.5", + "@backstage/core-app-api": "^0.1.5", + "@backstage/dev-utils": "^0.2.3", + "@backstage/test-utils": "^0.1.15", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^3.4.2", diff --git a/plugins/lighthouse/package.json b/plugins/lighthouse/package.json index f66ea35cff..8ceaa35d20 100644 --- a/plugins/lighthouse/package.json +++ b/plugins/lighthouse/package.json @@ -33,7 +33,7 @@ "dependencies": { "@backstage/catalog-model": "^0.9.0", "@backstage/config": "^0.1.4", - "@backstage/core-components": "^0.1.5", + "@backstage/core-components": "^0.1.6", "@backstage/core-plugin-api": "^0.1.3", "@backstage/plugin-catalog-react": "^0.3.0", "@backstage/theme": "^0.2.8", @@ -47,10 +47,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.4", - "@backstage/core-app-api": "^0.1.4", - "@backstage/dev-utils": "^0.2.2", - "@backstage/test-utils": "^0.1.14", + "@backstage/cli": "^0.7.5", + "@backstage/core-app-api": "^0.1.5", + "@backstage/dev-utils": "^0.2.3", + "@backstage/test-utils": "^0.1.15", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/newrelic/package.json b/plugins/newrelic/package.json index e3fa37faeb..e927cf6eef 100644 --- a/plugins/newrelic/package.json +++ b/plugins/newrelic/package.json @@ -31,7 +31,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-components": "^0.1.5", + "@backstage/core-components": "^0.1.6", "@backstage/core-plugin-api": "^0.1.3", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", @@ -42,10 +42,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.4", - "@backstage/core-app-api": "^0.1.4", - "@backstage/dev-utils": "^0.2.2", - "@backstage/test-utils": "^0.1.14", + "@backstage/cli": "^0.7.5", + "@backstage/core-app-api": "^0.1.5", + "@backstage/dev-utils": "^0.2.3", + "@backstage/test-utils": "^0.1.15", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/org/package.json b/plugins/org/package.json index 2fa17a28de..f721675584 100644 --- a/plugins/org/package.json +++ b/plugins/org/package.json @@ -21,7 +21,7 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.0", - "@backstage/core-components": "^0.1.5", + "@backstage/core-components": "^0.1.6", "@backstage/core-plugin-api": "^0.1.3", "@backstage/plugin-catalog-react": "^0.3.0", "@backstage/theme": "^0.2.8", @@ -35,10 +35,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.4", - "@backstage/core-app-api": "^0.1.4", - "@backstage/dev-utils": "^0.2.2", - "@backstage/test-utils": "^0.1.14", + "@backstage/cli": "^0.7.5", + "@backstage/core-app-api": "^0.1.5", + "@backstage/dev-utils": "^0.2.3", + "@backstage/test-utils": "^0.1.15", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/pagerduty/package.json b/plugins/pagerduty/package.json index 2f02cce9b3..2a5c086e50 100644 --- a/plugins/pagerduty/package.json +++ b/plugins/pagerduty/package.json @@ -31,7 +31,7 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.0", - "@backstage/core-components": "^0.1.5", + "@backstage/core-components": "^0.1.6", "@backstage/core-plugin-api": "^0.1.3", "@backstage/plugin-catalog-react": "^0.3.0", "@backstage/theme": "^0.2.8", @@ -47,10 +47,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.4", - "@backstage/core-app-api": "^0.1.4", - "@backstage/dev-utils": "^0.2.2", - "@backstage/test-utils": "^0.1.14", + "@backstage/cli": "^0.7.5", + "@backstage/core-app-api": "^0.1.5", + "@backstage/dev-utils": "^0.2.3", + "@backstage/test-utils": "^0.1.15", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/register-component/package.json b/plugins/register-component/package.json index d763924b69..c9a4ca161b 100644 --- a/plugins/register-component/package.json +++ b/plugins/register-component/package.json @@ -31,7 +31,7 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.0", - "@backstage/core-components": "^0.1.5", + "@backstage/core-components": "^0.1.6", "@backstage/core-plugin-api": "^0.1.3", "@backstage/plugin-catalog-react": "^0.3.0", "@backstage/theme": "^0.2.8", @@ -46,10 +46,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.4", - "@backstage/core-app-api": "^0.1.4", - "@backstage/dev-utils": "^0.2.2", - "@backstage/test-utils": "^0.1.14", + "@backstage/cli": "^0.7.5", + "@backstage/core-app-api": "^0.1.5", + "@backstage/dev-utils": "^0.2.3", + "@backstage/test-utils": "^0.1.15", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/rollbar/package.json b/plugins/rollbar/package.json index 92a338079c..f95bba8076 100644 --- a/plugins/rollbar/package.json +++ b/plugins/rollbar/package.json @@ -32,7 +32,7 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.0", - "@backstage/core-components": "^0.1.5", + "@backstage/core-components": "^0.1.6", "@backstage/core-plugin-api": "^0.1.3", "@backstage/plugin-catalog-react": "^0.3.0", "@backstage/theme": "^0.2.8", @@ -48,10 +48,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.4", - "@backstage/core-app-api": "^0.1.4", - "@backstage/dev-utils": "^0.2.2", - "@backstage/test-utils": "^0.1.14", + "@backstage/cli": "^0.7.5", + "@backstage/core-app-api": "^0.1.5", + "@backstage/dev-utils": "^0.2.3", + "@backstage/test-utils": "^0.1.15", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^3.3.0", diff --git a/plugins/scaffolder-backend/CHANGELOG.md b/plugins/scaffolder-backend/CHANGELOG.md index 39d68a6f1d..8e55ca4b59 100644 --- a/plugins/scaffolder-backend/CHANGELOG.md +++ b/plugins/scaffolder-backend/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-scaffolder-backend +## 0.14.1 + +### Patch Changes + +- c73f53bc2: Add new built-in action ci:github-actions-dispatch +- 7cea90592: - Move out the `cookiecutter` templating to its own module that is depended on by the `scaffolder-backend` plugin. No breaking change yet, but we will drop first class support for `cookiecutter` in the future and it will become an opt-in feature. +- eb740ee24: Moved sample software templates to the [backstage/software-templates](https://github.com/backstage/software-templates) repository. If you previously referenced the sample templates straight from `scaffolder-backend` plugin in the main [backstage/backstage](https://github.com/backstage/backstage) repository in your `app-config.yaml`, these references will need to be updated. + + See https://github.com/backstage/software-templates + +- Updated dependencies + - @backstage/catalog-client@0.3.17 + - @backstage/backend-common@0.8.7 + ## 0.14.0 ### Minor Changes diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index e44a22403c..490c7cc0d2 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend", - "version": "0.14.0", + "version": "0.14.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -29,8 +29,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.8.6", - "@backstage/catalog-client": "^0.3.16", + "@backstage/backend-common": "^0.8.7", + "@backstage/catalog-client": "^0.3.17", "@backstage/catalog-model": "^0.9.0", "@backstage/config": "^0.1.5", "@backstage/errors": "^0.1.1", @@ -66,8 +66,8 @@ "yaml": "^1.10.0" }, "devDependencies": { - "@backstage/cli": "^0.7.4", - "@backstage/test-utils": "^0.1.14", + "@backstage/cli": "^0.7.5", + "@backstage/test-utils": "^0.1.15", "@types/command-exists": "^1.2.0", "@types/fs-extra": "^9.0.1", "@types/git-url-parse": "^9.0.0", diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index 78f9c269d9..edb2f44d94 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -33,7 +33,7 @@ "@backstage/catalog-client": "^0.3.16", "@backstage/catalog-model": "^0.9.0", "@backstage/config": "^0.1.5", - "@backstage/core-components": "^0.1.5", + "@backstage/core-components": "^0.1.6", "@backstage/core-plugin-api": "^0.1.3", "@backstage/errors": "^0.1.1", "@backstage/integration": "^0.5.8", @@ -63,10 +63,10 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.7.4", - "@backstage/core-app-api": "^0.1.4", - "@backstage/dev-utils": "^0.2.2", - "@backstage/test-utils": "^0.1.14", + "@backstage/cli": "^0.7.5", + "@backstage/core-app-api": "^0.1.5", + "@backstage/dev-utils": "^0.2.3", + "@backstage/test-utils": "^0.1.15", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^3.3.0", diff --git a/plugins/search/CHANGELOG.md b/plugins/search/CHANGELOG.md index af6cd39e94..0e19f854bd 100644 --- a/plugins/search/CHANGELOG.md +++ b/plugins/search/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-search +## 0.4.4 + +### Patch Changes + +- 9266b80ab: Adding a type filter to new search +- Updated dependencies + - @backstage/core-components@0.1.6 + - @backstage/plugin-catalog-react@0.3.1 + ## 0.4.3 ### Patch Changes diff --git a/plugins/search/package.json b/plugins/search/package.json index 9f394f616b..a6ca45497a 100644 --- a/plugins/search/package.json +++ b/plugins/search/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search", - "version": "0.4.3", + "version": "0.4.4", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,10 +31,10 @@ "dependencies": { "@backstage/catalog-model": "^0.9.0", "@backstage/config": "^0.1.5", - "@backstage/core-components": "^0.1.5", + "@backstage/core-components": "^0.1.6", "@backstage/core-plugin-api": "^0.1.3", "@backstage/errors": "^0.1.1", - "@backstage/plugin-catalog-react": "^0.3.0", + "@backstage/plugin-catalog-react": "^0.3.1", "@backstage/search-common": "^0.1.2", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", @@ -49,10 +49,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.4", - "@backstage/core-app-api": "^0.1.4", - "@backstage/dev-utils": "^0.2.2", - "@backstage/test-utils": "^0.1.14", + "@backstage/cli": "^0.7.5", + "@backstage/core-app-api": "^0.1.5", + "@backstage/dev-utils": "^0.2.3", + "@backstage/test-utils": "^0.1.15", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^3.4.2", diff --git a/plugins/sentry/package.json b/plugins/sentry/package.json index cfc10baf24..989a18a6d9 100644 --- a/plugins/sentry/package.json +++ b/plugins/sentry/package.json @@ -32,7 +32,7 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.0", - "@backstage/core-components": "^0.1.5", + "@backstage/core-components": "^0.1.6", "@backstage/core-plugin-api": "^0.1.3", "@backstage/plugin-catalog-react": "^0.3.0", "@backstage/theme": "^0.2.8", @@ -47,10 +47,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.4", - "@backstage/core-app-api": "^0.1.4", - "@backstage/dev-utils": "^0.2.2", - "@backstage/test-utils": "^0.1.14", + "@backstage/cli": "^0.7.5", + "@backstage/core-app-api": "^0.1.5", + "@backstage/dev-utils": "^0.2.3", + "@backstage/test-utils": "^0.1.15", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/shortcuts/package.json b/plugins/shortcuts/package.json index b3b03c4205..293d71eeb9 100644 --- a/plugins/shortcuts/package.json +++ b/plugins/shortcuts/package.json @@ -20,7 +20,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-components": "^0.1.5", + "@backstage/core-components": "^0.1.6", "@backstage/core-plugin-api": "^0.1.3", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", @@ -36,10 +36,10 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.7.4", - "@backstage/core-app-api": "^0.1.4", - "@backstage/dev-utils": "^0.2.2", - "@backstage/test-utils": "^0.1.14", + "@backstage/cli": "^0.7.5", + "@backstage/core-app-api": "^0.1.5", + "@backstage/dev-utils": "^0.2.3", + "@backstage/test-utils": "^0.1.15", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/sonarqube/package.json b/plugins/sonarqube/package.json index 34e70fe1e1..a132da5702 100644 --- a/plugins/sonarqube/package.json +++ b/plugins/sonarqube/package.json @@ -33,7 +33,7 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.0", - "@backstage/core-components": "^0.1.5", + "@backstage/core-components": "^0.1.6", "@backstage/core-plugin-api": "^0.1.3", "@backstage/plugin-catalog-react": "^0.3.0", "@backstage/theme": "^0.2.8", @@ -48,10 +48,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.4", - "@backstage/core-app-api": "^0.1.4", - "@backstage/dev-utils": "^0.2.2", - "@backstage/test-utils": "^0.1.14", + "@backstage/cli": "^0.7.5", + "@backstage/core-app-api": "^0.1.5", + "@backstage/dev-utils": "^0.2.3", + "@backstage/test-utils": "^0.1.15", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/splunk-on-call/package.json b/plugins/splunk-on-call/package.json index ed2ce923b0..d779df3f97 100644 --- a/plugins/splunk-on-call/package.json +++ b/plugins/splunk-on-call/package.json @@ -31,7 +31,7 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.0", - "@backstage/core-components": "^0.1.5", + "@backstage/core-components": "^0.1.6", "@backstage/core-plugin-api": "^0.1.3", "@backstage/plugin-catalog-react": "^0.3.0", "@backstage/theme": "^0.2.8", @@ -46,10 +46,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.4", - "@backstage/core-app-api": "^0.1.4", - "@backstage/dev-utils": "^0.2.2", - "@backstage/test-utils": "^0.1.14", + "@backstage/cli": "^0.7.5", + "@backstage/core-app-api": "^0.1.5", + "@backstage/dev-utils": "^0.2.3", + "@backstage/test-utils": "^0.1.15", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/tech-radar/package.json b/plugins/tech-radar/package.json index 05b6689100..305061087b 100644 --- a/plugins/tech-radar/package.json +++ b/plugins/tech-radar/package.json @@ -30,7 +30,7 @@ "start": "backstage-cli plugin:serve" }, "dependencies": { - "@backstage/core-components": "^0.1.5", + "@backstage/core-components": "^0.1.6", "@backstage/core-plugin-api": "^0.1.3", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", @@ -44,10 +44,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.4", - "@backstage/core-app-api": "^0.1.4", - "@backstage/dev-utils": "^0.2.2", - "@backstage/test-utils": "^0.1.14", + "@backstage/cli": "^0.7.5", + "@backstage/core-app-api": "^0.1.5", + "@backstage/dev-utils": "^0.2.3", + "@backstage/test-utils": "^0.1.15", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/techdocs-backend/CHANGELOG.md b/plugins/techdocs-backend/CHANGELOG.md index 85acec31f9..4acab08f26 100644 --- a/plugins/techdocs-backend/CHANGELOG.md +++ b/plugins/techdocs-backend/CHANGELOG.md @@ -1,5 +1,101 @@ # @backstage/plugin-techdocs-backend +## 0.9.0 + +### Minor Changes + +- d32d01e5b: Improve the annotation `backstage.io/techdocs-ref: dir:` that links to a path that is relative to the source of the annotated entity. + This annotation works with the basic and the recommended flow, however, it will be most useful with the basic approach. + + This change remove the deprecation of the `dir` reference and provides first-class support for it. + In addition, this change removes the support of the deprecated `github`, `gitlab`, and `azure/api` locations from the `dir` reference preparer. + + #### Example Usage + + The annotation is convenient if the documentation is stored in the same location, i.e. the same git repository, as the `catalog-info.yaml`. + While it is still supported to add full URLs such as `backstage.io/techdocs-ref: url:https://...` for custom setups, documentation is mostly stored in the same repository as the entity definition. + By automatically resolving the target relative to the registration location of the entity, the configuration overhead for this default setup is minimized. + Since it leverages the `@backstage/integrations` package for the URL resolution, this is compatible with every supported source. + + Consider the following examples: + + 1. "I have a repository with a single `catalog-info.yaml` and a TechDocs page in the root folder!" + + ``` + https://github.com/backstage/example/tree/main/ + |- catalog-info.yaml + | > apiVersion: backstage.io/v1alpha1 + | > kind: Component + | > metadata: + | > name: example + | > annotations: + | > backstage.io/techdocs-ref: dir:. # -> same folder + | > spec: {} + |- docs/ + |- mkdocs.yml + ``` + + 2. "I have a repository with a single `catalog-info.yaml` and my TechDocs page in located in a folder!" + + ``` + https://bitbucket.org/my-owner/my-project/src/master/ + |- catalog-info.yaml + | > apiVersion: backstage.io/v1alpha1 + | > kind: Component + | > metadata: + | > name: example + | > annotations: + | > backstage.io/techdocs-ref: dir:./some-folder # -> subfolder + | > spec: {} + |- some-folder/ + |- docs/ + |- mkdocs.yml + ``` + + 3. "I have a mono repository that hosts multiple components!" + + ``` + https://dev.azure.com/organization/project/_git/repository + |- my-1st-module/ + |- catalog-info.yaml + | > apiVersion: backstage.io/v1alpha1 + | > kind: Component + | > metadata: + | > name: my-1st-module + | > annotations: + | > backstage.io/techdocs-ref: dir:. # -> same folder + | > spec: {} + |- docs/ + |- mkdocs.yml + |- my-2nd-module/ + |- catalog-info.yaml + | > apiVersion: backstage.io/v1alpha1 + | > kind: Component + | > metadata: + | > name: my-2nd-module + | > annotations: + | > backstage.io/techdocs-ref: dir:. # -> same folder + | > spec: {} + |- docs/ + |- mkdocs.yml + |- catalog-info.yaml + | > apiVersion: backstage.io/v1alpha1 + | > kind: Location + | > metadata: + | > name: example + | > spec: + | > targets: + | > - ./*/catalog-info.yaml + ``` + +### Patch Changes + +- 9266b80ab: Implements tech docs collator to retrieve and expose search indexes for entities that have tech docs configured +- Updated dependencies + - @backstage/techdocs-common@0.7.0 + - @backstage/catalog-client@0.3.17 + - @backstage/backend-common@0.8.7 + ## 0.8.7 ### Patch Changes diff --git a/plugins/techdocs-backend/package.json b/plugins/techdocs-backend/package.json index ed7985ae6e..a6a4fbe46a 100644 --- a/plugins/techdocs-backend/package.json +++ b/plugins/techdocs-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-backend", - "version": "0.8.7", + "version": "0.9.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,14 +30,14 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.8.6", - "@backstage/catalog-client": "^0.3.16", + "@backstage/backend-common": "^0.8.7", + "@backstage/catalog-client": "^0.3.17", "@backstage/catalog-model": "^0.9.0", "@backstage/config": "^0.1.5", "@backstage/errors": "^0.1.1", "@backstage/integration": "^0.5.8", "@backstage/search-common": "^0.1.2", - "@backstage/techdocs-common": "^0.6.8", + "@backstage/techdocs-common": "^0.7.0", "@types/express": "^4.17.6", "cross-fetch": "^3.0.6", "dockerode": "^3.2.1", @@ -50,8 +50,8 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.7.4", - "@backstage/test-utils": "^0.1.14", + "@backstage/cli": "^0.7.5", + "@backstage/test-utils": "^0.1.15", "@types/dockerode": "^3.2.1", "msw": "^0.29.0", "supertest": "^6.1.3" diff --git a/plugins/techdocs/CHANGELOG.md b/plugins/techdocs/CHANGELOG.md index c89f9dc01c..89c0ee04fd 100644 --- a/plugins/techdocs/CHANGELOG.md +++ b/plugins/techdocs/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/plugin-techdocs +## 0.10.1 + +### Patch Changes + +- 9266b80ab: Add search list item to display tech docs search results +- 03bf17e9b: Improve the responsiveness of the EntityPage UI. With this the Header component should scale with the screen size & wrapping should not cause overflowing/blocking of links. Additionally enforce the Pages using the Grid Layout to use it across all screen sizes & to wrap as intended. + + To benefit from the improved responsive layout, the `EntityPage` in existing Backstage applications should be updated to set the `xs` column size on each grid item in the page, as this does not default. For example: + + ```diff + - + + + ``` + +- 378cc6a54: Only update the `path` when the content is updated. + If content and path are updated independently, the frontend rendering is triggered twice on each navigation: Once for the `path` change (with the old content) and once for the new content. + This might result in a flickering rendering that is caused by the async frontend preprocessing, and the fact that replacing the shadow dom content is expensive. +- 214e7c52d: Refactor the techdocs transformers to return `Promise`s and await all transformations. +- e35b13afa: Handle error responses in `getTechDocsMetadata` and `getEntityMetadata` such that `` doesn't throw errors. +- Updated dependencies + - @backstage/core-components@0.1.6 + - @backstage/plugin-catalog-react@0.3.1 + ## 0.10.0 ### Minor Changes diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index 653fc99146..c1f8954b6c 100644 --- a/plugins/techdocs/package.json +++ b/plugins/techdocs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs", - "version": "0.10.0", + "version": "0.10.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,12 +33,12 @@ "dependencies": { "@backstage/catalog-model": "^0.9.0", "@backstage/config": "^0.1.5", - "@backstage/core-components": "^0.1.5", + "@backstage/core-components": "^0.1.6", "@backstage/core-plugin-api": "^0.1.3", "@backstage/errors": "^0.1.1", "@backstage/integration": "^0.5.8", "@backstage/integration-react": "^0.1.4", - "@backstage/plugin-catalog-react": "^0.3.0", + "@backstage/plugin-catalog-react": "^0.3.1", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -55,10 +55,10 @@ "sanitize-html": "^2.3.2" }, "devDependencies": { - "@backstage/cli": "^0.7.4", - "@backstage/core-app-api": "^0.1.4", - "@backstage/dev-utils": "^0.2.2", - "@backstage/test-utils": "^0.1.14", + "@backstage/cli": "^0.7.5", + "@backstage/core-app-api": "^0.1.5", + "@backstage/dev-utils": "^0.2.3", + "@backstage/test-utils": "^0.1.15", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^3.4.2", diff --git a/plugins/todo/package.json b/plugins/todo/package.json index cac45bcd0a..ece2509882 100644 --- a/plugins/todo/package.json +++ b/plugins/todo/package.json @@ -27,7 +27,7 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.0", - "@backstage/core-components": "^0.1.5", + "@backstage/core-components": "^0.1.6", "@backstage/core-plugin-api": "^0.1.3", "@backstage/errors": "^0.1.1", "@backstage/plugin-catalog-react": "^0.3.0", @@ -40,10 +40,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.4", - "@backstage/core-app-api": "^0.1.4", - "@backstage/dev-utils": "^0.2.2", - "@backstage/test-utils": "^0.1.14", + "@backstage/cli": "^0.7.5", + "@backstage/core-app-api": "^0.1.5", + "@backstage/dev-utils": "^0.2.3", + "@backstage/test-utils": "^0.1.15", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/user-settings/CHANGELOG.md b/plugins/user-settings/CHANGELOG.md index 0a4194cc29..94324eed10 100644 --- a/plugins/user-settings/CHANGELOG.md +++ b/plugins/user-settings/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-user-settings +## 0.3.1 + +### Patch Changes + +- b5953c1df: Aligns switch left and allows clicking on rows +- Updated dependencies + - @backstage/core-components@0.1.6 + ## 0.3.0 ### Minor Changes diff --git a/plugins/user-settings/package.json b/plugins/user-settings/package.json index 8a6480182e..7a450a188e 100644 --- a/plugins/user-settings/package.json +++ b/plugins/user-settings/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-user-settings", - "version": "0.3.0", + "version": "0.3.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,7 +30,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-components": "^0.1.5", + "@backstage/core-components": "^0.1.6", "@backstage/core-plugin-api": "^0.1.3", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", @@ -42,11 +42,11 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.4", - "@backstage/core-app-api": "^0.1.4", + "@backstage/cli": "^0.7.5", + "@backstage/core-app-api": "^0.1.5", "@backstage/core-plugin-api": "^0.1.3", - "@backstage/dev-utils": "^0.2.2", - "@backstage/test-utils": "^0.1.14", + "@backstage/dev-utils": "^0.2.3", + "@backstage/test-utils": "^0.1.15", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/welcome/package.json b/plugins/welcome/package.json index 50ab900e7f..68cb992e40 100644 --- a/plugins/welcome/package.json +++ b/plugins/welcome/package.json @@ -30,7 +30,7 @@ "start": "backstage-cli plugin:serve" }, "dependencies": { - "@backstage/core-components": "^0.1.5", + "@backstage/core-components": "^0.1.6", "@backstage/core-plugin-api": "^0.1.3", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", @@ -42,10 +42,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.4", - "@backstage/core-app-api": "^0.1.4", - "@backstage/dev-utils": "^0.2.2", - "@backstage/test-utils": "^0.1.14", + "@backstage/cli": "^0.7.5", + "@backstage/core-app-api": "^0.1.5", + "@backstage/dev-utils": "^0.2.3", + "@backstage/test-utils": "^0.1.15", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/xcmetrics/package.json b/plugins/xcmetrics/package.json index 07275dbba7..b939c3e5bf 100644 --- a/plugins/xcmetrics/package.json +++ b/plugins/xcmetrics/package.json @@ -20,7 +20,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-components": "^0.1.5", + "@backstage/core-components": "^0.1.6", "@backstage/core-plugin-api": "^0.1.3", "@backstage/errors": "^0.1.1", "@backstage/theme": "^0.2.8", @@ -33,10 +33,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.4", - "@backstage/core-app-api": "^0.1.4", - "@backstage/dev-utils": "^0.2.2", - "@backstage/test-utils": "^0.1.14", + "@backstage/cli": "^0.7.5", + "@backstage/core-app-api": "^0.1.5", + "@backstage/dev-utils": "^0.2.3", + "@backstage/test-utils": "^0.1.15", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", From 9c11ecad38e2f1312a867bc366cfb1b6b3b27713 Mon Sep 17 00:00:00 2001 From: Miguel Pessoa Date: Thu, 22 Jul 2021 12:08:22 -0300 Subject: [PATCH 105/106] Add QuintoAndar to the adopters list Signed-off-by: blam --- ADOPTERS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/ADOPTERS.md b/ADOPTERS.md index 9845f1a929..58c293b0ce 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -38,3 +38,4 @@ | [Workrise](https://www.workrise.com/) | [Michael Rode](https://github.com/michaelrode) | Developer portal, main gateway to our infrastructure, documentation and internal tooling. | | [RedVentures](https://www.redventures.com/) | [Chris Diaz](https://github.com/codingdiaz) | Developer portal that brings everything an engineer needs to provide value into a single pane of glass. | | [MavTek](https://www.mavtek.com/) | [@fgascon](https://github.com/fgascon) | Developer portal focused on standardizing practices, centralizing documentation and streamlining developer practices. | +| [QuintoAndar](https://www.quintoandar.com.br/) | [@quintoandar](https://github.com/quintoandar) | Developer portal, services catalog and centralization of service metrics. | From 797d3872fece4d6da35ab4ef71165912fd8e351b Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 22 Jul 2021 17:40:59 +0200 Subject: [PATCH 106/106] chore: prettier Signed-off-by: blam --- ADOPTERS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ADOPTERS.md b/ADOPTERS.md index 58c293b0ce..041242c083 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -38,4 +38,4 @@ | [Workrise](https://www.workrise.com/) | [Michael Rode](https://github.com/michaelrode) | Developer portal, main gateway to our infrastructure, documentation and internal tooling. | | [RedVentures](https://www.redventures.com/) | [Chris Diaz](https://github.com/codingdiaz) | Developer portal that brings everything an engineer needs to provide value into a single pane of glass. | | [MavTek](https://www.mavtek.com/) | [@fgascon](https://github.com/fgascon) | Developer portal focused on standardizing practices, centralizing documentation and streamlining developer practices. | -| [QuintoAndar](https://www.quintoandar.com.br/) | [@quintoandar](https://github.com/quintoandar) | Developer portal, services catalog and centralization of service metrics. | +| [QuintoAndar](https://www.quintoandar.com.br/) | [@quintoandar](https://github.com/quintoandar) | Developer portal, services catalog and centralization of service metrics. |