From 1083a2c47e7da4dfaf87e471783a12882dcb4147 Mon Sep 17 00:00:00 2001 From: Nikita Karpukhin Date: Mon, 31 Oct 2022 12:53:15 +0100 Subject: [PATCH 01/18] Used Layout pattern in SettingsPage, analogous to Explore Plugin Signed-off-by: Nikita Karpukhin --- packages/app/src/App.tsx | 26 ++++-- .../DefaultSettingsPage.test.tsx} | 11 ++- .../DefaultSettingsPage.tsx | 47 +++++++++++ .../components/DefaultSettingsPage/index.ts | 17 ++++ .../SettingsLayout/SettingsLayout.tsx | 79 +++++++++++++++++++ .../src/components/SettingsLayout/index.ts | 18 +++++ .../src/components/SettingsPage.tsx | 58 ++------------ plugins/user-settings/src/components/index.ts | 1 + 8 files changed, 190 insertions(+), 67 deletions(-) rename plugins/user-settings/src/components/{SettingsPage.test.tsx => DefaultSettingsPage/DefaultSettingsPage.test.tsx} (89%) create mode 100644 plugins/user-settings/src/components/DefaultSettingsPage/DefaultSettingsPage.tsx create mode 100644 plugins/user-settings/src/components/DefaultSettingsPage/index.ts create mode 100644 plugins/user-settings/src/components/SettingsLayout/SettingsLayout.tsx create mode 100644 plugins/user-settings/src/components/SettingsLayout/index.ts diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index 2526f6740f..a21fe2ce9d 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -60,18 +60,18 @@ import { HomepageCompositionRoot } from '@backstage/plugin-home'; import { LighthousePage } from '@backstage/plugin-lighthouse'; import { NewRelicPage } from '@backstage/plugin-newrelic'; import { - ScaffolderFieldExtensions, - ScaffolderPage, NextScaffolderPage, - scaffolderPlugin, + ScaffolderFieldExtensions, ScaffolderLayouts, + ScaffolderPage, + scaffolderPlugin, } from '@backstage/plugin-scaffolder'; import { SearchPage } from '@backstage/plugin-search'; import { TechRadarPage } from '@backstage/plugin-tech-radar'; import { TechDocsIndexPage, - TechDocsReaderPage, techdocsPlugin, + TechDocsReaderPage, } from '@backstage/plugin-techdocs'; import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; import { @@ -80,8 +80,10 @@ import { TextSize, } from '@backstage/plugin-techdocs-module-addons-contrib'; import { + SettingsLayout, + UserSettingsFeatureFlags, + UserSettingsGeneral, UserSettingsPage, - UserSettingsTab, } from '@backstage/plugin-user-settings'; import { AdvancedSettings } from './components/advancedSettings'; import AlarmIcon from '@material-ui/icons/Alarm'; @@ -267,9 +269,17 @@ const routes = ( element={} /> }> - - - + + + + + + + + + + + } /> } /> diff --git a/plugins/user-settings/src/components/SettingsPage.test.tsx b/plugins/user-settings/src/components/DefaultSettingsPage/DefaultSettingsPage.test.tsx similarity index 89% rename from plugins/user-settings/src/components/SettingsPage.test.tsx rename to plugins/user-settings/src/components/DefaultSettingsPage/DefaultSettingsPage.test.tsx index 01810bcc8e..24810a149e 100644 --- a/plugins/user-settings/src/components/SettingsPage.test.tsx +++ b/plugins/user-settings/src/components/DefaultSettingsPage/DefaultSettingsPage.test.tsx @@ -16,16 +16,15 @@ import React from 'react'; import { renderWithEffects, wrapInTestApp } from '@backstage/test-utils'; -import { SettingsPage } from './SettingsPage'; -import { UserSettingsTab } from './UserSettingsTab'; +import { DefaultSettingsPage } from './DefaultSettingsPage'; +import { UserSettingsTab } from '../UserSettingsTab'; +import { useOutlet } from 'react-router'; jest.mock('react-router', () => ({ ...jest.requireActual('react-router'), useOutlet: jest.fn().mockReturnValue(undefined), })); -import { useOutlet } from 'react-router'; - describe('', () => { beforeEach(() => { (useOutlet as jest.Mock).mockReset(); @@ -33,7 +32,7 @@ describe('', () => { it('should render the settings page with 3 tabs', async () => { const { container } = await renderWithEffects( - wrapInTestApp(), + wrapInTestApp(), ); const tabs = container.querySelectorAll('[class*=MuiTabs-root] button'); @@ -48,7 +47,7 @@ describe('', () => { ); (useOutlet as jest.Mock).mockReturnValue(advancedTabRoute); const { container } = await renderWithEffects( - wrapInTestApp(), + wrapInTestApp(), ); const tabs = container.querySelectorAll('[class*=MuiTabs-root] button'); diff --git a/plugins/user-settings/src/components/DefaultSettingsPage/DefaultSettingsPage.tsx b/plugins/user-settings/src/components/DefaultSettingsPage/DefaultSettingsPage.tsx new file mode 100644 index 0000000000..4b65a26e5e --- /dev/null +++ b/plugins/user-settings/src/components/DefaultSettingsPage/DefaultSettingsPage.tsx @@ -0,0 +1,47 @@ +/* + * Copyright 2022 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 { UserSettingsAuthProviders } from '../AuthProviders'; +import { UserSettingsFeatureFlags } from '../FeatureFlags'; +import { UserSettingsGeneral } from '../General'; +import { SettingsLayout } from '../SettingsLayout'; + +/** + * @public + */ +export const DefaultSettingsPage = (props: { + providerSettings?: JSX.Element; +}) => { + const { providerSettings } = props; + + return ( + + + + + + + + + + + + ); +}; diff --git a/plugins/user-settings/src/components/DefaultSettingsPage/index.ts b/plugins/user-settings/src/components/DefaultSettingsPage/index.ts new file mode 100644 index 0000000000..34dcb9340d --- /dev/null +++ b/plugins/user-settings/src/components/DefaultSettingsPage/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2022 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 { DefaultSettingsPage } from './DefaultSettingsPage'; diff --git a/plugins/user-settings/src/components/SettingsLayout/SettingsLayout.tsx b/plugins/user-settings/src/components/SettingsLayout/SettingsLayout.tsx new file mode 100644 index 0000000000..cba2b2bf1f --- /dev/null +++ b/plugins/user-settings/src/components/SettingsLayout/SettingsLayout.tsx @@ -0,0 +1,79 @@ +/* + * Copyright 2022 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 { TabProps } from '@material-ui/core'; +import { + Header, + Page, + RoutedTabs, + useSidebarPinState, +} from '@backstage/core-components'; +import { + attachComponentData, + useElementFilter, +} from '@backstage/core-plugin-api'; + +/** @public */ +export type SubRoute = { + path: string; + title: string; + children: JSX.Element; + tabProps?: TabProps; +}; + +const dataKey = 'plugin.explore.settingsLayoutRoute'; + +const Route: (props: SubRoute) => null = () => null; +attachComponentData(Route, dataKey, true); + +// This causes all mount points that are discovered within this route to use the path of the route itself +attachComponentData(Route, 'core.gatherMountPoints', true); + +/** @public */ +export type SettingsLayoutProps = { + title?: string; + subtitle?: string; + children?: React.ReactNode; +}; + +/** + * @public + */ +export const SettingsLayout = (props: SettingsLayoutProps) => { + const { title, children } = props; + const { isMobile } = useSidebarPinState(); + + const routes = useElementFilter(children, elements => + elements + .selectByComponentData({ + key: dataKey, + withStrictError: + 'Child of SettingsLayout must be an SettingsLayout.Route', + }) + .getElements() + .map(child => child.props), + ); + + return ( + + {!isMobile &&
} + + + ); +}; + +SettingsLayout.Route = Route; diff --git a/plugins/user-settings/src/components/SettingsLayout/index.ts b/plugins/user-settings/src/components/SettingsLayout/index.ts new file mode 100644 index 0000000000..36a5633c94 --- /dev/null +++ b/plugins/user-settings/src/components/SettingsLayout/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2022 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 type { SettingsLayoutProps, SubRoute } from './SettingsLayout'; +export { SettingsLayout } from './SettingsLayout'; diff --git a/plugins/user-settings/src/components/SettingsPage.tsx b/plugins/user-settings/src/components/SettingsPage.tsx index 9803bf4761..3fa1057881 100644 --- a/plugins/user-settings/src/components/SettingsPage.tsx +++ b/plugins/user-settings/src/components/SettingsPage.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Backstage Authors + * Copyright 2022 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,60 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import { - Header, - Page, - TabbedLayout, - useSidebarPinState, -} from '@backstage/core-components'; -import React from 'react'; import { useOutlet } from 'react-router'; -import { useElementFilter } from '@backstage/core-plugin-api'; -import { UserSettingsAuthProviders } from './AuthProviders'; -import { UserSettingsFeatureFlags } from './FeatureFlags'; -import { UserSettingsGeneral } from './General'; -import { USER_SETTINGS_TAB_KEY, UserSettingsTabProps } from './UserSettingsTab'; +import React from 'react'; +import { DefaultSettingsPage } from './DefaultSettingsPage'; -/** - * @public - */ -export const SettingsPage = (props: { providerSettings?: JSX.Element }) => { - const { providerSettings } = props; - const { isMobile } = useSidebarPinState(); +export const SettingsPage = () => { const outlet = useOutlet(); - const tabs = useElementFilter(outlet, elements => - elements - .selectByComponentData({ - key: USER_SETTINGS_TAB_KEY, - }) - .getElements(), - ); - - return ( - - {!isMobile &&
} - - - - - - - - - - - - {tabs.map((child, i) => ( - - {child} - - ))} - - - ); + return <>{outlet || }; }; diff --git a/plugins/user-settings/src/components/index.ts b/plugins/user-settings/src/components/index.ts index 8dc76ee7f1..8eb43a7a10 100644 --- a/plugins/user-settings/src/components/index.ts +++ b/plugins/user-settings/src/components/index.ts @@ -21,3 +21,4 @@ export * from './General'; export * from './FeatureFlags'; export { useUserProfile } from './useUserProfileInfo'; export * from './UserSettingsTab'; +export * from './SettingsLayout'; From 408027734d9d3f65a3e88238960731774fe45df6 Mon Sep 17 00:00:00 2001 From: Nikita Karpukhin Date: Mon, 31 Oct 2022 15:15:45 +0100 Subject: [PATCH 02/18] Added check for UserSettingsTab, so that the existing code doesn't break Signed-off-by: Nikita Karpukhin --- packages/app/src/App.tsx | 18 ++------ .../DefaultSettingsPage.tsx | 9 +++- .../SettingsLayout/SettingsLayout.tsx | 2 +- .../components/SettingsPage/SettingsPage.tsx | 43 +++++++++++++++++++ .../index.ts} | 10 +---- 5 files changed, 57 insertions(+), 25 deletions(-) create mode 100644 plugins/user-settings/src/components/SettingsPage/SettingsPage.tsx rename plugins/user-settings/src/components/{SettingsPage.tsx => SettingsPage/index.ts} (70%) diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index a21fe2ce9d..0f60012759 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -80,10 +80,8 @@ import { TextSize, } from '@backstage/plugin-techdocs-module-addons-contrib'; import { - SettingsLayout, - UserSettingsFeatureFlags, - UserSettingsGeneral, UserSettingsPage, + UserSettingsTab, } from '@backstage/plugin-user-settings'; import { AdvancedSettings } from './components/advancedSettings'; import AlarmIcon from '@material-ui/icons/Alarm'; @@ -269,17 +267,9 @@ const routes = ( element={} /> }> - - - - - - - - - - - + + + } /> } /> diff --git a/plugins/user-settings/src/components/DefaultSettingsPage/DefaultSettingsPage.tsx b/plugins/user-settings/src/components/DefaultSettingsPage/DefaultSettingsPage.tsx index 4b65a26e5e..6348845763 100644 --- a/plugins/user-settings/src/components/DefaultSettingsPage/DefaultSettingsPage.tsx +++ b/plugins/user-settings/src/components/DefaultSettingsPage/DefaultSettingsPage.tsx @@ -19,14 +19,16 @@ import { UserSettingsAuthProviders } from '../AuthProviders'; import { UserSettingsFeatureFlags } from '../FeatureFlags'; import { UserSettingsGeneral } from '../General'; import { SettingsLayout } from '../SettingsLayout'; +import { UserSettingsTabProps } from '../UserSettingsTab'; /** * @public */ export const DefaultSettingsPage = (props: { + tabs?: React.ReactElement[]; providerSettings?: JSX.Element; }) => { - const { providerSettings } = props; + const { providerSettings, tabs } = props; return ( @@ -42,6 +44,11 @@ export const DefaultSettingsPage = (props: { + {tabs?.map((child, i) => ( + + {child} + + ))} ); }; diff --git a/plugins/user-settings/src/components/SettingsLayout/SettingsLayout.tsx b/plugins/user-settings/src/components/SettingsLayout/SettingsLayout.tsx index cba2b2bf1f..23394819d6 100644 --- a/plugins/user-settings/src/components/SettingsLayout/SettingsLayout.tsx +++ b/plugins/user-settings/src/components/SettingsLayout/SettingsLayout.tsx @@ -35,7 +35,7 @@ export type SubRoute = { tabProps?: TabProps; }; -const dataKey = 'plugin.explore.settingsLayoutRoute'; +const dataKey = 'plugin.user-settings.settingsLayoutRoute'; const Route: (props: SubRoute) => null = () => null; attachComponentData(Route, dataKey, true); diff --git a/plugins/user-settings/src/components/SettingsPage/SettingsPage.tsx b/plugins/user-settings/src/components/SettingsPage/SettingsPage.tsx new file mode 100644 index 0000000000..3d1f93aaef --- /dev/null +++ b/plugins/user-settings/src/components/SettingsPage/SettingsPage.tsx @@ -0,0 +1,43 @@ +/* + * Copyright 2022 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 { useOutlet } from 'react-router'; +import React from 'react'; +import { DefaultSettingsPage } from '../DefaultSettingsPage'; +import { useElementFilter } from '@backstage/core-plugin-api'; +import { + USER_SETTINGS_TAB_KEY, + UserSettingsTabProps, +} from '../UserSettingsTab'; + +export const SettingsPage = (props: { providerSettings?: JSX.Element }) => { + const { providerSettings } = props; + const outlet = useOutlet(); + const tabs = useElementFilter(outlet, elements => + elements + .selectByComponentData({ + key: USER_SETTINGS_TAB_KEY, + }) + .getElements(), + ); + + return ( + <> + {(tabs.length === 0 && outlet) || ( + + )} + + ); +}; diff --git a/plugins/user-settings/src/components/SettingsPage.tsx b/plugins/user-settings/src/components/SettingsPage/index.ts similarity index 70% rename from plugins/user-settings/src/components/SettingsPage.tsx rename to plugins/user-settings/src/components/SettingsPage/index.ts index 3fa1057881..e957a1bf09 100644 --- a/plugins/user-settings/src/components/SettingsPage.tsx +++ b/plugins/user-settings/src/components/SettingsPage/index.ts @@ -13,12 +13,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { useOutlet } from 'react-router'; -import React from 'react'; -import { DefaultSettingsPage } from './DefaultSettingsPage'; - -export const SettingsPage = () => { - const outlet = useOutlet(); - - return <>{outlet || }; -}; +export { SettingsPage } from './SettingsPage'; From a27892953bf20070fe3474adaf0a20a54fa7c323 Mon Sep 17 00:00:00 2001 From: Nikita Karpukhin Date: Mon, 31 Oct 2022 15:15:51 +0100 Subject: [PATCH 03/18] Added tests for both for UserSettingsTab and SettingsLayout setups Signed-off-by: Nikita Karpukhin --- .../DefaultSettingsPage.test.tsx | 5 +- .../SettingsPage/SettingsPage.test.tsx | 80 +++++++++++++++++++ 2 files changed, 82 insertions(+), 3 deletions(-) create mode 100644 plugins/user-settings/src/components/SettingsPage/SettingsPage.test.tsx diff --git a/plugins/user-settings/src/components/DefaultSettingsPage/DefaultSettingsPage.test.tsx b/plugins/user-settings/src/components/DefaultSettingsPage/DefaultSettingsPage.test.tsx index 24810a149e..d7bedbd5ca 100644 --- a/plugins/user-settings/src/components/DefaultSettingsPage/DefaultSettingsPage.test.tsx +++ b/plugins/user-settings/src/components/DefaultSettingsPage/DefaultSettingsPage.test.tsx @@ -25,7 +25,7 @@ jest.mock('react-router', () => ({ useOutlet: jest.fn().mockReturnValue(undefined), })); -describe('', () => { +describe('', () => { beforeEach(() => { (useOutlet as jest.Mock).mockReset(); }); @@ -45,9 +45,8 @@ describe('', () => {
Advanced settings
); - (useOutlet as jest.Mock).mockReturnValue(advancedTabRoute); const { container } = await renderWithEffects( - wrapInTestApp(), + wrapInTestApp(), ); const tabs = container.querySelectorAll('[class*=MuiTabs-root] button'); diff --git a/plugins/user-settings/src/components/SettingsPage/SettingsPage.test.tsx b/plugins/user-settings/src/components/SettingsPage/SettingsPage.test.tsx new file mode 100644 index 0000000000..883358de5e --- /dev/null +++ b/plugins/user-settings/src/components/SettingsPage/SettingsPage.test.tsx @@ -0,0 +1,80 @@ +/* + * Copyright 2022 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 { renderWithEffects, wrapInTestApp } from '@backstage/test-utils'; +import { SettingsPage } from './SettingsPage'; +import { UserSettingsTab } from '../UserSettingsTab'; +import { useOutlet } from 'react-router'; +import { SettingsLayout } from '../SettingsLayout'; + +jest.mock('react-router', () => ({ + ...jest.requireActual('react-router'), + useOutlet: jest.fn().mockReturnValue(undefined), +})); + +describe('', () => { + beforeEach(() => { + (useOutlet as jest.Mock).mockReset(); + }); + + it('should render the default settings page with 3 tabs', async () => { + const { container } = await renderWithEffects( + wrapInTestApp(), + ); + + const tabs = container.querySelectorAll('[class*=MuiTabs-root] button'); + expect(tabs).toHaveLength(3); + }); + + it('should render the default settings page with 4 tabs when extra tabs are provided', async () => { + const advancedTabRoute = ( + +
Advanced settings
+
+ ); + (useOutlet as jest.Mock).mockReturnValue(advancedTabRoute); + const { container } = await renderWithEffects( + wrapInTestApp(), + ); + + const tabs = container.querySelectorAll('[class*=MuiTabs-root] button'); + expect(tabs).toHaveLength(4); + expect(tabs[3].textContent).toEqual('Advanced'); + }); + + it('should render the custom settings page when custom layout is provided', async () => { + const customLayout = ( + + +
User settings
+
+ +
Advanced settings
+
+
+ ); + (useOutlet as jest.Mock).mockReturnValue(customLayout); + const { container } = await renderWithEffects( + wrapInTestApp(), + ); + + const tabs = container.querySelectorAll('[class*=MuiTabs-root] button'); + expect(tabs).toHaveLength(2); + expect(tabs[0].textContent).toEqual('General'); + expect(tabs[1].textContent).toEqual('Advanced'); + }); +}); From a350001484316370f2fbef97d10251d29046c90d Mon Sep 17 00:00:00 2001 From: Nikita Karpukhin Date: Mon, 31 Oct 2022 15:29:37 +0100 Subject: [PATCH 04/18] Updated README.md Signed-off-by: Nikita Karpukhin --- plugins/user-settings/README.md | 40 +++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/plugins/user-settings/README.md b/plugins/user-settings/README.md index ce07d76257..0912e34b0a 100644 --- a/plugins/user-settings/README.md +++ b/plugins/user-settings/README.md @@ -93,3 +93,43 @@ make sure you use a similar component structure as the other tabs. You can take a look at [the example extra tab](https://github.com/backstage/backstage/blob/master/packages/app/src/components/advancedSettings/AdvancedSettings.tsx) we have created in Backstage's demo app. + +To change the layout altogether, create a custom page in `packages/app/src/components/user-settings/SettingsPage.tsx`: +```typescript jsx +import React from 'react'; +import { + SettingsLayout, + UserSettingsGeneral, +} from '@backstage/plugin-user-settings'; +import { AdvancedSettings } from './advancedSettings'; + +export const SettingsPage = () => { + return ( + + + + + + + + + ); +}; + +export const settingsPage = ; +``` + +Now register the new settings page in `packages/app/src/App.tsx`: + +```typescript jsx ++ import { settingsPage } from './components/settings/settingsPage'; + +const routes = ( + +- } /> ++ }> ++ {settingsPage} ++ + +); +``` From 38064a945bc82de1db5184e5a918ca18918f9a31 Mon Sep 17 00:00:00 2001 From: Nikita Karpukhin Date: Mon, 31 Oct 2022 16:41:14 +0100 Subject: [PATCH 05/18] Added changeset Signed-off-by: Nikita Karpukhin --- .changeset/nice-phones-sin.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/nice-phones-sin.md diff --git a/.changeset/nice-phones-sin.md b/.changeset/nice-phones-sin.md new file mode 100644 index 0000000000..ade5c2de8b --- /dev/null +++ b/.changeset/nice-phones-sin.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-user-settings': patch +--- + +Added the ability to fully customize settings page From 1695ff8c82a10772c1de41363f489671e9bd3918 Mon Sep 17 00:00:00 2001 From: Nikita Karpukhin Date: Mon, 31 Oct 2022 16:45:05 +0100 Subject: [PATCH 06/18] Revert App.tsx changes Signed-off-by: Nikita Karpukhin --- packages/app/src/App.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index 0f60012759..2526f6740f 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -60,18 +60,18 @@ import { HomepageCompositionRoot } from '@backstage/plugin-home'; import { LighthousePage } from '@backstage/plugin-lighthouse'; import { NewRelicPage } from '@backstage/plugin-newrelic'; import { - NextScaffolderPage, ScaffolderFieldExtensions, - ScaffolderLayouts, ScaffolderPage, + NextScaffolderPage, scaffolderPlugin, + ScaffolderLayouts, } from '@backstage/plugin-scaffolder'; import { SearchPage } from '@backstage/plugin-search'; import { TechRadarPage } from '@backstage/plugin-tech-radar'; import { TechDocsIndexPage, - techdocsPlugin, TechDocsReaderPage, + techdocsPlugin, } from '@backstage/plugin-techdocs'; import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; import { From bec063868f4eacb0c565f2d430aa9b24c0e41604 Mon Sep 17 00:00:00 2001 From: Nikita Karpukhin Date: Mon, 31 Oct 2022 17:12:30 +0100 Subject: [PATCH 07/18] Ran Prettier on the README.md Signed-off-by: Nikita Karpukhin --- plugins/user-settings/README.md | 92 ++++++++++++++++++--------------- 1 file changed, 51 insertions(+), 41 deletions(-) diff --git a/plugins/user-settings/README.md b/plugins/user-settings/README.md index 0912e34b0a..3d500fbcc3 100644 --- a/plugins/user-settings/README.md +++ b/plugins/user-settings/README.md @@ -4,12 +4,14 @@ Welcome to the user-settings plugin! ## About the plugin -This plugin provides two components, `` is intended to be used within the [``](https://backstage.io/storybook/?path=/story/sidebar--sample-sidebar) and displays the signed-in users profile picture and name. The second component is a settings page where the user can control different settings across the App. +This plugin provides two components, `` is intended to be used within +the [``](https://backstage.io/storybook/?path=/story/sidebar--sample-sidebar) and displays the signed-in users +profile picture and name. The second component is a settings page where the user can control different settings across +the App. -It also provides a `UserSettingsStorage` implementation of the `StorageApi`, to -be used in the frontend as a persistent alternative to the builtin `WebStorage`. -Please see [the backend -README](https://github.com/backstage/backstage/tree/master/plugins/user-settings-backend) +It also provides a `UserSettingsStorage` implementation of the `StorageApi`, to be used in the frontend as a persistent +alternative to the builtin `WebStorage`. Please +see [the backend README](https://github.com/backstage/backstage/tree/master/plugins/user-settings-backend) for installation instructions. ## Components Usage @@ -17,51 +19,60 @@ for installation instructions. Add the item to the Sidebar: ```ts -import { Settings as SidebarSettings } from '@backstage/plugin-user-settings'; +import {Settings as SidebarSettings} from '@backstage/plugin-user-settings'; - - -; + + +< /SidebarPage>; ``` Add the page to the App routing: ```ts -import { UserSettingsPage } from '@backstage/plugin-user-settings'; +import {UserSettingsPage} from '@backstage/plugin-user-settings'; const AppRoutes = () => ( - } /> - -); + +} +/> +< /Routes> +) +; ``` ### Props **Auth Providers** -By default, the plugin provides a list of configured authentication providers fetched from `app-config.yaml` and displayed in the "Authentication Providers" tab. +By default, the plugin provides a list of configured authentication providers fetched from `app-config.yaml` and +displayed in the "Authentication Providers" tab. If you want to supply your own custom list of Authentication Providers, use the `providerSettings` prop: ```ts const MyAuthProviders = () => ( - - {someAction} - + + {someAction} < /ListItemSecondaryAction> + < /ListItem> ); const AppRoutes = () => ( } />} - /> - -); + path = "/settings" +element = { < SettingsRouter +providerSettings = { < MyAuthProviders / > +} +/>} +/> +< /Routes> +) +; ``` > **Note that the list of providers expects to be rendered within a MUI [``](https://material-ui.com/components/lists/)** @@ -70,10 +81,9 @@ const AppRoutes = () => ( By default, the plugin renders 3 tabs of settings; GENERAL, AUTHENTICATION PROVIDERS, and FEATURE FLAGS. -If you want to add more options for your users, -just pass the extra tabs using `UserSettingsTab` components as children of the `UserSettingsPage` route. -The path is in this case a child of the settings path, -in the example below it would be `/settings/advanced` so that you can easily link to it. +If you want to add more options for your users, just pass the extra tabs using `UserSettingsTab` components as children +of the `UserSettingsPage` route. The path is in this case a child of the settings path, in the example below it would +be `/settings/advanced` so that you can easily link to it. ```tsx import { @@ -81,55 +91,55 @@ import { UserSettingsTab, } from '@backstage/plugin-user-settings'; -}> +}> - + ; ``` -To standardize the UI of all setting tabs, -make sure you use a similar component structure as the other tabs. -You can take a look at +To standardize the UI of all setting tabs, make sure you use a similar component structure as the other tabs. You can +take a look at [the example extra tab](https://github.com/backstage/backstage/blob/master/packages/app/src/components/advancedSettings/AdvancedSettings.tsx) we have created in Backstage's demo app. To change the layout altogether, create a custom page in `packages/app/src/components/user-settings/SettingsPage.tsx`: + ```typescript jsx import React from 'react'; import { SettingsLayout, UserSettingsGeneral, } from '@backstage/plugin-user-settings'; -import { AdvancedSettings } from './advancedSettings'; +import {AdvancedSettings} from './advancedSettings'; export const SettingsPage = () => { return ( - + - + ); }; -export const settingsPage = ; +export const settingsPage = ; ``` Now register the new settings page in `packages/app/src/App.tsx`: -```typescript jsx -+ import { settingsPage } from './components/settings/settingsPage'; +```diff ++ import {settingsPage} from './components/settings/settingsPage'; const routes = ( -- } /> -+ }> -+ {settingsPage} -+ + - }/> + + }> + + {settingsPage} + + ); ``` From 8bc63687340e99352ed82ef86a0edcf008df8b6f Mon Sep 17 00:00:00 2001 From: Nikita Karpukhin Date: Mon, 31 Oct 2022 17:36:59 +0100 Subject: [PATCH 08/18] Ran Prettier on the README.md externally Signed-off-by: Nikita Karpukhin --- plugins/user-settings/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/user-settings/README.md b/plugins/user-settings/README.md index 3d500fbcc3..ce435ee3ce 100644 --- a/plugins/user-settings/README.md +++ b/plugins/user-settings/README.md @@ -91,9 +91,9 @@ import { UserSettingsTab, } from '@backstage/plugin-user-settings'; -}> +}> - + ; ``` @@ -111,22 +111,22 @@ import { SettingsLayout, UserSettingsGeneral, } from '@backstage/plugin-user-settings'; -import {AdvancedSettings} from './advancedSettings'; +import { AdvancedSettings } from './advancedSettings'; export const SettingsPage = () => { return ( - + - + ); }; -export const settingsPage = ; +export const settingsPage = ; ``` Now register the new settings page in `packages/app/src/App.tsx`: From 5d7b14027fad0b241eaaa6eafa48283a1854e675 Mon Sep 17 00:00:00 2001 From: Nikita Karpukhin Date: Mon, 31 Oct 2022 18:13:22 +0100 Subject: [PATCH 09/18] Updated api-report.md Signed-off-by: Nikita Karpukhin --- plugins/user-settings/api-report.md | 28 +++++++++++++++++++ .../components/SettingsPage/SettingsPage.tsx | 1 + 2 files changed, 29 insertions(+) diff --git a/plugins/user-settings/api-report.md b/plugins/user-settings/api-report.md index c51d4f992a..1323bb8d7f 100644 --- a/plugins/user-settings/api-report.md +++ b/plugins/user-settings/api-report.md @@ -18,10 +18,12 @@ import { Observable } from '@backstage/types'; import { ProfileInfo } from '@backstage/core-plugin-api'; import { ProfileInfoApi } from '@backstage/core-plugin-api'; import { PropsWithChildren } from 'react'; +import { default as React_2 } from 'react'; import { RouteRef } from '@backstage/core-plugin-api'; import { SessionApi } from '@backstage/core-plugin-api'; import { StorageApi } from '@backstage/core-plugin-api'; import { StorageValueSnapshot } from '@backstage/core-plugin-api'; +import { TabProps } from '@material-ui/core'; // @public (undocumented) export const DefaultProviderSettings: (props: { @@ -42,6 +44,32 @@ export const Router: (props: { providerSettings?: JSX.Element }) => JSX.Element; // @public (undocumented) export const Settings: (props: { icon?: IconComponent }) => JSX.Element; +// @public (undocumented) +export const SettingsLayout: { + (props: SettingsLayoutProps): JSX.Element; + Route: (props: SubRoute) => null; +}; + +// @public (undocumented) +export type SettingsLayoutProps = { + title?: string; + subtitle?: string; + children?: React_2.ReactNode; +}; + +// @public (undocumented) +export type SubRoute = { + path: string; + title: string; + children: JSX.Element; + tabProps?: TabProps< + React_2.ElementType, + { + component?: React_2.ElementType; + } + >; +}; + // @public (undocumented) export const USER_SETTINGS_TAB_KEY = 'user-settings.tab'; diff --git a/plugins/user-settings/src/components/SettingsPage/SettingsPage.tsx b/plugins/user-settings/src/components/SettingsPage/SettingsPage.tsx index 3d1f93aaef..8559bcd8c8 100644 --- a/plugins/user-settings/src/components/SettingsPage/SettingsPage.tsx +++ b/plugins/user-settings/src/components/SettingsPage/SettingsPage.tsx @@ -22,6 +22,7 @@ import { UserSettingsTabProps, } from '../UserSettingsTab'; +/** @public */ export const SettingsPage = (props: { providerSettings?: JSX.Element }) => { const { providerSettings } = props; const outlet = useOutlet(); From 92a0cbb2f07be3faf9ff39c3fcb3a1ae686ea45d Mon Sep 17 00:00:00 2001 From: Nikita Karpukhin Date: Fri, 4 Nov 2022 18:08:45 +0100 Subject: [PATCH 10/18] Fix style in README.md Signed-off-by: Nikita Karpukhin --- plugins/user-settings/README.md | 42 +++++++++++++-------------------- 1 file changed, 17 insertions(+), 25 deletions(-) diff --git a/plugins/user-settings/README.md b/plugins/user-settings/README.md index ce435ee3ce..c1b50710f4 100644 --- a/plugins/user-settings/README.md +++ b/plugins/user-settings/README.md @@ -18,30 +18,26 @@ for installation instructions. Add the item to the Sidebar: -```ts +```tsx import {Settings as SidebarSettings} from '@backstage/plugin-user-settings'; - - + + < /SidebarPage>; ``` Add the page to the App routing: -```ts -import {UserSettingsPage} from '@backstage/plugin-user-settings'; +```tsx +import { UserSettingsPage } from '@backstage/plugin-user-settings'; const AppRoutes = () => ( - -} -/> -< /Routes> -) -; + } /> + +); ``` ### Props @@ -53,26 +49,22 @@ displayed in the "Authentication Providers" tab. If you want to supply your own custom list of Authentication Providers, use the `providerSettings` prop: -```ts +```tsx const MyAuthProviders = () => ( - - {someAction} < /ListItemSecondaryAction> - < /ListItem> + + {someAction} + ); const AppRoutes = () => ( -} -/>} -/> -< /Routes> -) -; + path="/settings" + element={} />} + /> + +); ``` > **Note that the list of providers expects to be rendered within a MUI [``](https://material-ui.com/components/lists/)** From 031028c2ac225d51278b3dd5885f4255f6e8ae89 Mon Sep 17 00:00:00 2001 From: Nikita Karpukhin Date: Fri, 4 Nov 2022 18:18:29 +0100 Subject: [PATCH 11/18] Revert changes in README.md Signed-off-by: Nikita Karpukhin --- plugins/user-settings/README.md | 37 ++++++++++++++++----------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/plugins/user-settings/README.md b/plugins/user-settings/README.md index c1b50710f4..1d15c404e5 100644 --- a/plugins/user-settings/README.md +++ b/plugins/user-settings/README.md @@ -4,14 +4,12 @@ Welcome to the user-settings plugin! ## About the plugin -This plugin provides two components, `` is intended to be used within -the [``](https://backstage.io/storybook/?path=/story/sidebar--sample-sidebar) and displays the signed-in users -profile picture and name. The second component is a settings page where the user can control different settings across -the App. +This plugin provides two components, `` is intended to be used within the [``](https://backstage.io/storybook/?path=/story/sidebar--sample-sidebar) and displays the signed-in users profile picture and name. The second component is a settings page where the user can control different settings across the App. -It also provides a `UserSettingsStorage` implementation of the `StorageApi`, to be used in the frontend as a persistent -alternative to the builtin `WebStorage`. Please -see [the backend README](https://github.com/backstage/backstage/tree/master/plugins/user-settings-backend) +It also provides a `UserSettingsStorage` implementation of the `StorageApi`, to +be used in the frontend as a persistent alternative to the builtin `WebStorage`. +Please see [the backend +README](https://github.com/backstage/backstage/tree/master/plugins/user-settings-backend) for installation instructions. ## Components Usage @@ -19,13 +17,13 @@ for installation instructions. Add the item to the Sidebar: ```tsx -import {Settings as SidebarSettings} from '@backstage/plugin-user-settings'; +import { Settings as SidebarSettings } from '@backstage/plugin-user-settings'; - + -< /SidebarPage>; +; ``` Add the page to the App routing: @@ -44,8 +42,7 @@ const AppRoutes = () => ( **Auth Providers** -By default, the plugin provides a list of configured authentication providers fetched from `app-config.yaml` and -displayed in the "Authentication Providers" tab. +By default, the plugin provides a list of configured authentication providers fetched from `app-config.yaml` and displayed in the "Authentication Providers" tab. If you want to supply your own custom list of Authentication Providers, use the `providerSettings` prop: @@ -53,7 +50,7 @@ If you want to supply your own custom list of Authentication Providers, use the const MyAuthProviders = () => ( - {someAction} + {someAction} ); @@ -73,9 +70,10 @@ const AppRoutes = () => ( By default, the plugin renders 3 tabs of settings; GENERAL, AUTHENTICATION PROVIDERS, and FEATURE FLAGS. -If you want to add more options for your users, just pass the extra tabs using `UserSettingsTab` components as children -of the `UserSettingsPage` route. The path is in this case a child of the settings path, in the example below it would -be `/settings/advanced` so that you can easily link to it. +If you want to add more options for your users, +just pass the extra tabs using `UserSettingsTab` components as children of the `UserSettingsPage` route. +The path is in this case a child of the settings path, +in the example below it would be `/settings/advanced` so that you can easily link to it. ```tsx import { @@ -90,14 +88,15 @@ import {
; ``` -To standardize the UI of all setting tabs, make sure you use a similar component structure as the other tabs. You can -take a look at +To standardize the UI of all setting tabs, +make sure you use a similar component structure as the other tabs. +You can take a look at [the example extra tab](https://github.com/backstage/backstage/blob/master/packages/app/src/components/advancedSettings/AdvancedSettings.tsx) we have created in Backstage's demo app. To change the layout altogether, create a custom page in `packages/app/src/components/user-settings/SettingsPage.tsx`: -```typescript jsx +```tsx import React from 'react'; import { SettingsLayout, From 2b35cf99f9aed5932a0e52f312892dea8cdb682c Mon Sep 17 00:00:00 2001 From: Nikita Karpukhin Date: Fri, 4 Nov 2022 18:24:17 +0100 Subject: [PATCH 12/18] Fix diff in README.md Signed-off-by: Nikita Karpukhin --- plugins/user-settings/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/user-settings/README.md b/plugins/user-settings/README.md index 1d15c404e5..09e095a8e0 100644 --- a/plugins/user-settings/README.md +++ b/plugins/user-settings/README.md @@ -127,10 +127,10 @@ Now register the new settings page in `packages/app/src/App.tsx`: const routes = ( - - }/> - + }> - + {settingsPage} - + +- } /> ++ }> ++ {settingsPage} ++ ); ``` From fde99df4c736fe14fe5125402b1488d56bfb6c7f Mon Sep 17 00:00:00 2001 From: Nikita Karpukhin Date: Thu, 10 Nov 2022 17:19:57 +0100 Subject: [PATCH 13/18] Rename SubRoute to SettingsLayoutRouteProps Signed-off-by: Nikita Karpukhin --- plugins/user-settings/api-report.md | 4 ++-- .../src/components/SettingsLayout/SettingsLayout.tsx | 6 +++--- .../user-settings/src/components/SettingsLayout/index.ts | 5 ++++- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/plugins/user-settings/api-report.md b/plugins/user-settings/api-report.md index 1323bb8d7f..6167e343d6 100644 --- a/plugins/user-settings/api-report.md +++ b/plugins/user-settings/api-report.md @@ -47,7 +47,7 @@ export const Settings: (props: { icon?: IconComponent }) => JSX.Element; // @public (undocumented) export const SettingsLayout: { (props: SettingsLayoutProps): JSX.Element; - Route: (props: SubRoute) => null; + Route: (props: SettingsLayoutRouteProps) => null; }; // @public (undocumented) @@ -58,7 +58,7 @@ export type SettingsLayoutProps = { }; // @public (undocumented) -export type SubRoute = { +export type SettingsLayoutRouteProps = { path: string; title: string; children: JSX.Element; diff --git a/plugins/user-settings/src/components/SettingsLayout/SettingsLayout.tsx b/plugins/user-settings/src/components/SettingsLayout/SettingsLayout.tsx index 23394819d6..c120c87122 100644 --- a/plugins/user-settings/src/components/SettingsLayout/SettingsLayout.tsx +++ b/plugins/user-settings/src/components/SettingsLayout/SettingsLayout.tsx @@ -28,7 +28,7 @@ import { } from '@backstage/core-plugin-api'; /** @public */ -export type SubRoute = { +export type SettingsLayoutRouteProps = { path: string; title: string; children: JSX.Element; @@ -37,7 +37,7 @@ export type SubRoute = { const dataKey = 'plugin.user-settings.settingsLayoutRoute'; -const Route: (props: SubRoute) => null = () => null; +const Route: (props: SettingsLayoutRouteProps) => null = () => null; attachComponentData(Route, dataKey, true); // This causes all mount points that are discovered within this route to use the path of the route itself @@ -64,7 +64,7 @@ export const SettingsLayout = (props: SettingsLayoutProps) => { withStrictError: 'Child of SettingsLayout must be an SettingsLayout.Route', }) - .getElements() + .getElements() .map(child => child.props), ); diff --git a/plugins/user-settings/src/components/SettingsLayout/index.ts b/plugins/user-settings/src/components/SettingsLayout/index.ts index 36a5633c94..6e610f8078 100644 --- a/plugins/user-settings/src/components/SettingsLayout/index.ts +++ b/plugins/user-settings/src/components/SettingsLayout/index.ts @@ -14,5 +14,8 @@ * limitations under the License. */ -export type { SettingsLayoutProps, SubRoute } from './SettingsLayout'; +export type { + SettingsLayoutProps, + SettingsLayoutRouteProps, +} from './SettingsLayout'; export { SettingsLayout } from './SettingsLayout'; From c9bbb32e0b67fa80d86ce3154050532398d099dd Mon Sep 17 00:00:00 2001 From: Nikita Karpukhin Date: Thu, 10 Nov 2022 17:25:00 +0100 Subject: [PATCH 14/18] Update README Signed-off-by: Nikita Karpukhin --- plugins/user-settings/README.md | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/plugins/user-settings/README.md b/plugins/user-settings/README.md index 09e095a8e0..1549b8de65 100644 --- a/plugins/user-settings/README.md +++ b/plugins/user-settings/README.md @@ -104,20 +104,16 @@ import { } from '@backstage/plugin-user-settings'; import { AdvancedSettings } from './advancedSettings'; -export const SettingsPage = () => { - return ( - - - - - - - - - ); -}; - -export const settingsPage = ; +export const settingsPage = () => ( + + + + + + + + +); ``` Now register the new settings page in `packages/app/src/App.tsx`: From 931eb6d3bcd15a4b5964976d90a402b97b349182 Mon Sep 17 00:00:00 2001 From: Nikita Karpukhin Date: Tue, 15 Nov 2022 09:26:00 +0100 Subject: [PATCH 15/18] fix readme Signed-off-by: Nikita Karpukhin --- plugins/user-settings/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/user-settings/README.md b/plugins/user-settings/README.md index 1549b8de65..07e1d6dcbb 100644 --- a/plugins/user-settings/README.md +++ b/plugins/user-settings/README.md @@ -104,7 +104,7 @@ import { } from '@backstage/plugin-user-settings'; import { AdvancedSettings } from './advancedSettings'; -export const settingsPage = () => ( +export const settingsPage = ( From 29bdda54426c0855f5e8d67a1dbecf175e5f90f7 Mon Sep 17 00:00:00 2001 From: Nikita Karpukhin Date: Tue, 15 Nov 2022 12:46:46 +0100 Subject: [PATCH 16/18] deprecate UserSettingsTab in favour of SettingsLayout.Route Signed-off-by: Nikita Karpukhin --- .changeset/itchy-walls-boil.md | 5 +++ packages/app/src/App.tsx | 6 +-- plugins/user-settings/README.md | 8 ++-- plugins/user-settings/api-report.md | 13 ------ .../DefaultSettingsPage.test.tsx | 6 +-- .../DefaultSettingsPage.tsx | 11 ++--- .../SettingsLayout/SettingsLayout.tsx | 9 ++-- .../SettingsPage/SettingsPage.test.tsx | 5 +-- .../components/SettingsPage/SettingsPage.tsx | 23 +++++++--- .../UserSettingsTab/UserSettingsTab.tsx | 42 ------------------- .../src/components/UserSettingsTab/index.ts | 17 -------- plugins/user-settings/src/components/index.ts | 1 - 12 files changed, 43 insertions(+), 103 deletions(-) create mode 100644 .changeset/itchy-walls-boil.md delete mode 100644 plugins/user-settings/src/components/UserSettingsTab/UserSettingsTab.tsx delete mode 100644 plugins/user-settings/src/components/UserSettingsTab/index.ts diff --git a/.changeset/itchy-walls-boil.md b/.changeset/itchy-walls-boil.md new file mode 100644 index 0000000000..fa498ad948 --- /dev/null +++ b/.changeset/itchy-walls-boil.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-user-settings': minor +--- + +Deprecated UserSettingsTab in favour of SettingsLayout.Route diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index 2526f6740f..fd73e99bcb 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -80,8 +80,8 @@ import { TextSize, } from '@backstage/plugin-techdocs-module-addons-contrib'; import { + SettingsLayout, UserSettingsPage, - UserSettingsTab, } from '@backstage/plugin-user-settings'; import { AdvancedSettings } from './components/advancedSettings'; import AlarmIcon from '@material-ui/icons/Alarm'; @@ -267,9 +267,9 @@ const routes = ( element={} /> }> - + - +
} /> } /> diff --git a/plugins/user-settings/README.md b/plugins/user-settings/README.md index 07e1d6dcbb..9a99a69b36 100644 --- a/plugins/user-settings/README.md +++ b/plugins/user-settings/README.md @@ -71,20 +71,20 @@ const AppRoutes = () => ( By default, the plugin renders 3 tabs of settings; GENERAL, AUTHENTICATION PROVIDERS, and FEATURE FLAGS. If you want to add more options for your users, -just pass the extra tabs using `UserSettingsTab` components as children of the `UserSettingsPage` route. +just pass the extra tabs using `SettingsLayout.Route` components as children of the `UserSettingsPage` route. The path is in this case a child of the settings path, in the example below it would be `/settings/advanced` so that you can easily link to it. ```tsx import { + SettingsLayout, UserSettingsPage, - UserSettingsTab, } from '@backstage/plugin-user-settings'; }> - + - + ; ``` diff --git a/plugins/user-settings/api-report.md b/plugins/user-settings/api-report.md index 6167e343d6..f83c97ff73 100644 --- a/plugins/user-settings/api-report.md +++ b/plugins/user-settings/api-report.md @@ -17,7 +17,6 @@ import { JsonValue } from '@backstage/types'; import { Observable } from '@backstage/types'; import { ProfileInfo } from '@backstage/core-plugin-api'; import { ProfileInfoApi } from '@backstage/core-plugin-api'; -import { PropsWithChildren } from 'react'; import { default as React_2 } from 'react'; import { RouteRef } from '@backstage/core-plugin-api'; import { SessionApi } from '@backstage/core-plugin-api'; @@ -70,9 +69,6 @@ export type SettingsLayoutRouteProps = { >; }; -// @public (undocumented) -export const USER_SETTINGS_TAB_KEY = 'user-settings.tab'; - // @public (undocumented) export const UserSettingsAppearanceCard: () => JSX.Element; @@ -144,15 +140,6 @@ export class UserSettingsStorage implements StorageApi { snapshot(key: string): StorageValueSnapshot; } -// @public -export const UserSettingsTab: (props: UserSettingsTabProps) => JSX.Element; - -// @public (undocumented) -export type UserSettingsTabProps = PropsWithChildren<{ - path: string; - title: string; -}>; - // @public (undocumented) export const UserSettingsThemeToggle: () => JSX.Element; diff --git a/plugins/user-settings/src/components/DefaultSettingsPage/DefaultSettingsPage.test.tsx b/plugins/user-settings/src/components/DefaultSettingsPage/DefaultSettingsPage.test.tsx index d7bedbd5ca..54f10a4c5a 100644 --- a/plugins/user-settings/src/components/DefaultSettingsPage/DefaultSettingsPage.test.tsx +++ b/plugins/user-settings/src/components/DefaultSettingsPage/DefaultSettingsPage.test.tsx @@ -17,8 +17,8 @@ import React from 'react'; import { renderWithEffects, wrapInTestApp } from '@backstage/test-utils'; import { DefaultSettingsPage } from './DefaultSettingsPage'; -import { UserSettingsTab } from '../UserSettingsTab'; import { useOutlet } from 'react-router'; +import { SettingsLayout } from '../SettingsLayout'; jest.mock('react-router', () => ({ ...jest.requireActual('react-router'), @@ -41,9 +41,9 @@ describe('', () => { it('should render the settings page with 4 tabs when extra tabs are provided', async () => { const advancedTabRoute = ( - +
Advanced settings
-
+ ); const { container } = await renderWithEffects( wrapInTestApp(), diff --git a/plugins/user-settings/src/components/DefaultSettingsPage/DefaultSettingsPage.tsx b/plugins/user-settings/src/components/DefaultSettingsPage/DefaultSettingsPage.tsx index 6348845763..6b5b63d6bd 100644 --- a/plugins/user-settings/src/components/DefaultSettingsPage/DefaultSettingsPage.tsx +++ b/plugins/user-settings/src/components/DefaultSettingsPage/DefaultSettingsPage.tsx @@ -18,14 +18,13 @@ import React from 'react'; import { UserSettingsAuthProviders } from '../AuthProviders'; import { UserSettingsFeatureFlags } from '../FeatureFlags'; import { UserSettingsGeneral } from '../General'; -import { SettingsLayout } from '../SettingsLayout'; -import { UserSettingsTabProps } from '../UserSettingsTab'; +import { SettingsLayout, SettingsLayoutRouteProps } from '../SettingsLayout'; /** * @public */ export const DefaultSettingsPage = (props: { - tabs?: React.ReactElement[]; + tabs?: React.ReactElement[]; providerSettings?: JSX.Element; }) => { const { providerSettings, tabs } = props; @@ -44,11 +43,7 @@ export const DefaultSettingsPage = (props: { - {tabs?.map((child, i) => ( - - {child} - - ))} + {tabs} ); }; diff --git a/plugins/user-settings/src/components/SettingsLayout/SettingsLayout.tsx b/plugins/user-settings/src/components/SettingsLayout/SettingsLayout.tsx index c120c87122..ac6e34ced9 100644 --- a/plugins/user-settings/src/components/SettingsLayout/SettingsLayout.tsx +++ b/plugins/user-settings/src/components/SettingsLayout/SettingsLayout.tsx @@ -35,10 +35,11 @@ export type SettingsLayoutRouteProps = { tabProps?: TabProps; }; -const dataKey = 'plugin.user-settings.settingsLayoutRoute'; +export const LAYOUT_ROUTE_DATA_KEY = 'plugin.user-settings.settingsLayoutRoute'; +export const LAYOUT_DATA_KEY = 'plugin.user-settings.settingsLayout'; const Route: (props: SettingsLayoutRouteProps) => null = () => null; -attachComponentData(Route, dataKey, true); +attachComponentData(Route, LAYOUT_ROUTE_DATA_KEY, true); // This causes all mount points that are discovered within this route to use the path of the route itself attachComponentData(Route, 'core.gatherMountPoints', true); @@ -60,7 +61,7 @@ export const SettingsLayout = (props: SettingsLayoutProps) => { const routes = useElementFilter(children, elements => elements .selectByComponentData({ - key: dataKey, + key: LAYOUT_ROUTE_DATA_KEY, withStrictError: 'Child of SettingsLayout must be an SettingsLayout.Route', }) @@ -76,4 +77,6 @@ export const SettingsLayout = (props: SettingsLayoutProps) => { ); }; +attachComponentData(SettingsLayout, LAYOUT_DATA_KEY, true); + SettingsLayout.Route = Route; diff --git a/plugins/user-settings/src/components/SettingsPage/SettingsPage.test.tsx b/plugins/user-settings/src/components/SettingsPage/SettingsPage.test.tsx index 883358de5e..c2fc3d63b0 100644 --- a/plugins/user-settings/src/components/SettingsPage/SettingsPage.test.tsx +++ b/plugins/user-settings/src/components/SettingsPage/SettingsPage.test.tsx @@ -17,7 +17,6 @@ import React from 'react'; import { renderWithEffects, wrapInTestApp } from '@backstage/test-utils'; import { SettingsPage } from './SettingsPage'; -import { UserSettingsTab } from '../UserSettingsTab'; import { useOutlet } from 'react-router'; import { SettingsLayout } from '../SettingsLayout'; @@ -42,9 +41,9 @@ describe('', () => { it('should render the default settings page with 4 tabs when extra tabs are provided', async () => { const advancedTabRoute = ( - +
Advanced settings
-
+ ); (useOutlet as jest.Mock).mockReturnValue(advancedTabRoute); const { container } = await renderWithEffects( diff --git a/plugins/user-settings/src/components/SettingsPage/SettingsPage.tsx b/plugins/user-settings/src/components/SettingsPage/SettingsPage.tsx index 8559bcd8c8..eec386d60e 100644 --- a/plugins/user-settings/src/components/SettingsPage/SettingsPage.tsx +++ b/plugins/user-settings/src/components/SettingsPage/SettingsPage.tsx @@ -18,25 +18,36 @@ import React from 'react'; import { DefaultSettingsPage } from '../DefaultSettingsPage'; import { useElementFilter } from '@backstage/core-plugin-api'; import { - USER_SETTINGS_TAB_KEY, - UserSettingsTabProps, -} from '../UserSettingsTab'; + SettingsLayoutProps, + SettingsLayoutRouteProps, +} from '../SettingsLayout'; +import { + LAYOUT_DATA_KEY, + LAYOUT_ROUTE_DATA_KEY, +} from '../SettingsLayout/SettingsLayout'; /** @public */ export const SettingsPage = (props: { providerSettings?: JSX.Element }) => { const { providerSettings } = props; const outlet = useOutlet(); + const layout = useElementFilter(outlet, elements => + elements + .selectByComponentData({ + key: LAYOUT_DATA_KEY, + }) + .getElements(), + ); const tabs = useElementFilter(outlet, elements => elements .selectByComponentData({ - key: USER_SETTINGS_TAB_KEY, + key: LAYOUT_ROUTE_DATA_KEY, }) - .getElements(), + .getElements(), ); return ( <> - {(tabs.length === 0 && outlet) || ( + {(layout.length !== 0 && layout) || ( )} diff --git a/plugins/user-settings/src/components/UserSettingsTab/UserSettingsTab.tsx b/plugins/user-settings/src/components/UserSettingsTab/UserSettingsTab.tsx deleted file mode 100644 index 171c54ecf3..0000000000 --- a/plugins/user-settings/src/components/UserSettingsTab/UserSettingsTab.tsx +++ /dev/null @@ -1,42 +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, { PropsWithChildren } from 'react'; -import { attachComponentData } from '@backstage/core-plugin-api'; - -/** @public */ -export const USER_SETTINGS_TAB_KEY = 'user-settings.tab'; - -/** @public */ -export type UserSettingsTabProps = PropsWithChildren<{ - /** - * The path to the tab in the settings route - * @example `/settings/advanced` - */ - path: string; - /** The title of the tab. It will also reflect in the document title when the tab is active */ - title: string; -}>; - -/** - * Renders a tab inside the settings page - * @param props - Component props - * @public - */ -export const UserSettingsTab = (props: UserSettingsTabProps) => { - return <>{props.children}; -}; - -attachComponentData(UserSettingsTab, USER_SETTINGS_TAB_KEY, 'UserSettingsTab'); diff --git a/plugins/user-settings/src/components/UserSettingsTab/index.ts b/plugins/user-settings/src/components/UserSettingsTab/index.ts deleted file mode 100644 index d0645d4efb..0000000000 --- a/plugins/user-settings/src/components/UserSettingsTab/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export * from './UserSettingsTab'; diff --git a/plugins/user-settings/src/components/index.ts b/plugins/user-settings/src/components/index.ts index 8eb43a7a10..deb2761628 100644 --- a/plugins/user-settings/src/components/index.ts +++ b/plugins/user-settings/src/components/index.ts @@ -20,5 +20,4 @@ export * from './AuthProviders'; export * from './General'; export * from './FeatureFlags'; export { useUserProfile } from './useUserProfileInfo'; -export * from './UserSettingsTab'; export * from './SettingsLayout'; From 92dba2c25941dda627ab12ebf5ce40a5ea0413c2 Mon Sep 17 00:00:00 2001 From: Nikita Karpukhin Date: Tue, 15 Nov 2022 12:54:50 +0100 Subject: [PATCH 17/18] combine two changesets Signed-off-by: Nikita Karpukhin --- .changeset/itchy-walls-boil.md | 2 +- .changeset/nice-phones-sin.md | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) delete mode 100644 .changeset/nice-phones-sin.md diff --git a/.changeset/itchy-walls-boil.md b/.changeset/itchy-walls-boil.md index fa498ad948..2afd0287fe 100644 --- a/.changeset/itchy-walls-boil.md +++ b/.changeset/itchy-walls-boil.md @@ -2,4 +2,4 @@ '@backstage/plugin-user-settings': minor --- -Deprecated UserSettingsTab in favour of SettingsLayout.Route +Added the ability to fully customize settings page. Deprecated UserSettingsTab in favour of SettingsLayout.Route diff --git a/.changeset/nice-phones-sin.md b/.changeset/nice-phones-sin.md deleted file mode 100644 index ade5c2de8b..0000000000 --- a/.changeset/nice-phones-sin.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-user-settings': patch ---- - -Added the ability to fully customize settings page From 9a534529043cf9dc6598b7450779e3cb7acacff2 Mon Sep 17 00:00:00 2001 From: Nikita Karpukhin Date: Tue, 15 Nov 2022 16:05:29 +0100 Subject: [PATCH 18/18] Deprecate UserSettingsTab instead of removing it, add tests to check for tab content Signed-off-by: Nikita Karpukhin --- plugins/user-settings/api-report.md | 13 +++++ .../DefaultSettingsPage.test.tsx | 18 ++++++- .../SettingsLayout/SettingsLayout.tsx | 2 +- .../SettingsPage/SettingsPage.test.tsx | 29 ++++++++++- .../UserSettingsTab/UserSettingsTab.tsx | 49 +++++++++++++++++++ .../src/components/UserSettingsTab/index.ts | 17 +++++++ plugins/user-settings/src/components/index.ts | 1 + 7 files changed, 126 insertions(+), 3 deletions(-) create mode 100644 plugins/user-settings/src/components/UserSettingsTab/UserSettingsTab.tsx create mode 100644 plugins/user-settings/src/components/UserSettingsTab/index.ts diff --git a/plugins/user-settings/api-report.md b/plugins/user-settings/api-report.md index f83c97ff73..dd8f9428ca 100644 --- a/plugins/user-settings/api-report.md +++ b/plugins/user-settings/api-report.md @@ -17,6 +17,7 @@ import { JsonValue } from '@backstage/types'; import { Observable } from '@backstage/types'; import { ProfileInfo } from '@backstage/core-plugin-api'; import { ProfileInfoApi } from '@backstage/core-plugin-api'; +import { PropsWithChildren } from 'react'; import { default as React_2 } from 'react'; import { RouteRef } from '@backstage/core-plugin-api'; import { SessionApi } from '@backstage/core-plugin-api'; @@ -69,6 +70,9 @@ export type SettingsLayoutRouteProps = { >; }; +// @public @deprecated (undocumented) +export const USER_SETTINGS_TAB_KEY = 'plugin.user-settings.settingsLayoutRoute'; + // @public (undocumented) export const UserSettingsAppearanceCard: () => JSX.Element; @@ -140,6 +144,15 @@ export class UserSettingsStorage implements StorageApi { snapshot(key: string): StorageValueSnapshot; } +// @public @deprecated +export const UserSettingsTab: (props: UserSettingsTabProps) => JSX.Element; + +// @public @deprecated (undocumented) +export type UserSettingsTabProps = PropsWithChildren<{ + path: string; + title: string; +}>; + // @public (undocumented) export const UserSettingsThemeToggle: () => JSX.Element; diff --git a/plugins/user-settings/src/components/DefaultSettingsPage/DefaultSettingsPage.test.tsx b/plugins/user-settings/src/components/DefaultSettingsPage/DefaultSettingsPage.test.tsx index 54f10a4c5a..d4560bdd06 100644 --- a/plugins/user-settings/src/components/DefaultSettingsPage/DefaultSettingsPage.test.tsx +++ b/plugins/user-settings/src/components/DefaultSettingsPage/DefaultSettingsPage.test.tsx @@ -17,6 +17,7 @@ import React from 'react'; import { renderWithEffects, wrapInTestApp } from '@backstage/test-utils'; import { DefaultSettingsPage } from './DefaultSettingsPage'; +import { UserSettingsTab } from '../UserSettingsTab'; import { useOutlet } from 'react-router'; import { SettingsLayout } from '../SettingsLayout'; @@ -39,7 +40,22 @@ describe('', () => { expect(tabs).toHaveLength(3); }); - it('should render the settings page with 4 tabs when extra tabs are provided', async () => { + it('should render the settings page with 4 tabs when extra tabs are provided via UserSettingsTab', async () => { + const advancedTabRoute = ( + +
Advanced settings
+
+ ); + const { container } = await renderWithEffects( + wrapInTestApp(), + ); + + const tabs = container.querySelectorAll('[class*=MuiTabs-root] button'); + expect(tabs).toHaveLength(4); + expect(tabs[3].textContent).toEqual('Advanced'); + }); + + it('should render the settings page with 4 tabs when extra tabs are provided via SettingsLayout.Route', async () => { const advancedTabRoute = (
Advanced settings
diff --git a/plugins/user-settings/src/components/SettingsLayout/SettingsLayout.tsx b/plugins/user-settings/src/components/SettingsLayout/SettingsLayout.tsx index ac6e34ced9..9855fdd048 100644 --- a/plugins/user-settings/src/components/SettingsLayout/SettingsLayout.tsx +++ b/plugins/user-settings/src/components/SettingsLayout/SettingsLayout.tsx @@ -35,8 +35,8 @@ export type SettingsLayoutRouteProps = { tabProps?: TabProps; }; -export const LAYOUT_ROUTE_DATA_KEY = 'plugin.user-settings.settingsLayoutRoute'; export const LAYOUT_DATA_KEY = 'plugin.user-settings.settingsLayout'; +export const LAYOUT_ROUTE_DATA_KEY = 'plugin.user-settings.settingsLayoutRoute'; const Route: (props: SettingsLayoutRouteProps) => null = () => null; attachComponentData(Route, LAYOUT_ROUTE_DATA_KEY, true); diff --git a/plugins/user-settings/src/components/SettingsPage/SettingsPage.test.tsx b/plugins/user-settings/src/components/SettingsPage/SettingsPage.test.tsx index c2fc3d63b0..b6277f0b59 100644 --- a/plugins/user-settings/src/components/SettingsPage/SettingsPage.test.tsx +++ b/plugins/user-settings/src/components/SettingsPage/SettingsPage.test.tsx @@ -17,8 +17,11 @@ import React from 'react'; import { renderWithEffects, wrapInTestApp } from '@backstage/test-utils'; import { SettingsPage } from './SettingsPage'; +import { UserSettingsTab } from '../UserSettingsTab'; import { useOutlet } from 'react-router'; import { SettingsLayout } from '../SettingsLayout'; +import { screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; jest.mock('react-router', () => ({ ...jest.requireActual('react-router'), @@ -39,7 +42,23 @@ describe('', () => { expect(tabs).toHaveLength(3); }); - it('should render the default settings page with 4 tabs when extra tabs are provided', async () => { + it('should render the default settings page with 4 tabs when extra tabs are provided via UserSettingsTab', async () => { + const advancedTabRoute = ( + +
Advanced settings
+
+ ); + (useOutlet as jest.Mock).mockReturnValue(advancedTabRoute); + const { container } = await renderWithEffects( + wrapInTestApp(), + ); + + const tabs = container.querySelectorAll('[class*=MuiTabs-root] button'); + expect(tabs).toHaveLength(4); + expect(tabs[3].textContent).toEqual('Advanced'); + }); + + it('should render the default settings page with 4 tabs when extra tabs are provided via SettingsLayout.Route', async () => { const advancedTabRoute = (
Advanced settings
@@ -53,6 +72,10 @@ describe('', () => { const tabs = container.querySelectorAll('[class*=MuiTabs-root] button'); expect(tabs).toHaveLength(4); expect(tabs[3].textContent).toEqual('Advanced'); + const user = userEvent.setup(); + await user.click(screen.getByText(/Advanced/i)); + const content = container.querySelectorAll('article'); + expect(content[0].textContent).toEqual('Advanced settings'); }); it('should render the custom settings page when custom layout is provided', async () => { @@ -75,5 +98,9 @@ describe('', () => { expect(tabs).toHaveLength(2); expect(tabs[0].textContent).toEqual('General'); expect(tabs[1].textContent).toEqual('Advanced'); + const user = userEvent.setup(); + await user.click(screen.getByText(/Advanced/i)); + const content = container.querySelectorAll('article'); + expect(content[0].textContent).toEqual('Advanced settings'); }); }); diff --git a/plugins/user-settings/src/components/UserSettingsTab/UserSettingsTab.tsx b/plugins/user-settings/src/components/UserSettingsTab/UserSettingsTab.tsx new file mode 100644 index 0000000000..ebddac00c1 --- /dev/null +++ b/plugins/user-settings/src/components/UserSettingsTab/UserSettingsTab.tsx @@ -0,0 +1,49 @@ +/* + * 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, { PropsWithChildren } from 'react'; +import { attachComponentData } from '@backstage/core-plugin-api'; +import { + LAYOUT_ROUTE_DATA_KEY, + SettingsLayout, +} from '../SettingsLayout/SettingsLayout'; + +/** @public @deprecated Use SettingsLayout.Route approach instead */ +export const USER_SETTINGS_TAB_KEY = LAYOUT_ROUTE_DATA_KEY; + +/** @public @deprecated Use SettingsLayoutRouteProps instead */ +export type UserSettingsTabProps = PropsWithChildren<{ + /** + * The path to the tab in the settings route + * @example `/settings/advanced` + */ + path: string; + /** The title of the tab. It will also reflect in the document title when the tab is active */ + title: string; +}>; + +/** + * Renders a tab inside the settings page + * @param props - Component props + * @public + * @deprecated Use SettingsLayout.Route instead + */ +export const UserSettingsTab = (props: UserSettingsTabProps) => ( + + <>props.children + +); + +attachComponentData(UserSettingsTab, USER_SETTINGS_TAB_KEY, 'UserSettingsTab'); diff --git a/plugins/user-settings/src/components/UserSettingsTab/index.ts b/plugins/user-settings/src/components/UserSettingsTab/index.ts new file mode 100644 index 0000000000..d0645d4efb --- /dev/null +++ b/plugins/user-settings/src/components/UserSettingsTab/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 * from './UserSettingsTab'; diff --git a/plugins/user-settings/src/components/index.ts b/plugins/user-settings/src/components/index.ts index deb2761628..8eb43a7a10 100644 --- a/plugins/user-settings/src/components/index.ts +++ b/plugins/user-settings/src/components/index.ts @@ -20,4 +20,5 @@ export * from './AuthProviders'; export * from './General'; export * from './FeatureFlags'; export { useUserProfile } from './useUserProfileInfo'; +export * from './UserSettingsTab'; export * from './SettingsLayout';