From 1083a2c47e7da4dfaf87e471783a12882dcb4147 Mon Sep 17 00:00:00 2001 From: Nikita Karpukhin Date: Mon, 31 Oct 2022 12:53:15 +0100 Subject: [PATCH] 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';