diff --git a/.changeset/proud-fishes-carry.md b/.changeset/proud-fishes-carry.md new file mode 100644 index 0000000000..3dd8a445e6 --- /dev/null +++ b/.changeset/proud-fishes-carry.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-catalog': minor +--- + +Removed deprecated `Router` and `EntityPageLayout` exports. +`Router` is replaced by plugin extensions and `EntityPageLayout` is replaced by `CatalogEntityPage`. diff --git a/plugins/catalog/api-report.md b/plugins/catalog/api-report.md index d526d4dee1..10bb71a17a 100644 --- a/plugins/catalog/api-report.md +++ b/plugins/catalog/api-report.md @@ -349,22 +349,6 @@ export const EntityListContainer: ({ // @public export const EntityOrphanWarning: () => JSX.Element; -// Warning: (ae-missing-release-tag) "EntityPageLayout" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public @deprecated -export const EntityPageLayout: { - ({ - children, - UNSTABLE_extraContextMenuItems, - UNSTABLE_contextMenuOptions, - }: EntityPageLayoutProps): JSX.Element; - Content: (_props: { - path: string; - title: string; - element: JSX.Element; - }) => null; -}; - // Warning: (ae-missing-release-tag) "EntityProcessingErrorsPanel" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public @@ -452,15 +436,6 @@ export const RelatedEntitiesCard: (props: { asRenderableEntities: (entities: Entity[]) => T[]; }) => JSX.Element; -// Warning: (ae-missing-release-tag) "Router" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public @deprecated (undocumented) -export const Router: ({ - EntityPage, -}: { - EntityPage?: React_2.ComponentType<{}> | undefined; -}) => JSX.Element; - // @public (undocumented) export type SystemDiagramCardClassKey = | 'domainNode' @@ -475,6 +450,5 @@ export type SystemDiagramCardClassKey = // src/components/CatalogTable/CatalogTable.d.ts:11:5 - (ae-forgotten-export) The symbol "columnFactories" needs to be exported by the entry point index.d.ts // src/components/EntityLayout/EntityLayout.d.ts:43:5 - (ae-forgotten-export) The symbol "EntityLayoutProps" needs to be exported by the entry point index.d.ts // src/components/EntityLayout/EntityLayout.d.ts:44:5 - (ae-forgotten-export) The symbol "SubRoute" needs to be exported by the entry point index.d.ts -// src/components/EntityPageLayout/EntityPageLayout.d.ts:22:5 - (ae-forgotten-export) The symbol "EntityPageLayoutProps" needs to be exported by the entry point index.d.ts // src/plugin.d.ts:22:5 - (ae-forgotten-export) The symbol "ColumnBreakpoints" needs to be exported by the entry point index.d.ts ``` diff --git a/plugins/catalog/src/components/EntityPageLayout/EntityPageLayout.tsx b/plugins/catalog/src/components/EntityPageLayout/EntityPageLayout.tsx deleted file mode 100644 index 52b05afa76..0000000000 --- a/plugins/catalog/src/components/EntityPageLayout/EntityPageLayout.tsx +++ /dev/null @@ -1,214 +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 { - Entity, - DEFAULT_NAMESPACE, - RELATION_OWNED_BY, -} from '@backstage/catalog-model'; -import { - EntityContext, - EntityRefLinks, - FavoriteEntity, - getEntityRelations, - InspectEntityDialog, - UnregisterEntityDialog, - useEntityCompoundName, -} from '@backstage/plugin-catalog-react'; -import { Box } from '@material-ui/core'; -import React, { useContext, useState } from 'react'; -import { useNavigate } from 'react-router'; -import { EntityContextMenu } from '../EntityContextMenu/EntityContextMenu'; -import { Tabbed } from './Tabbed'; - -import { - Content, - Header, - HeaderLabel, - Link, - Page, - Progress, - ResponseErrorPanel, - WarningPanel, -} from '@backstage/core-components'; - -import { IconComponent } from '@backstage/core-plugin-api'; - -const EntityPageTitle = ({ - entity, - title, -}: { - title: string; - entity: Entity | undefined; -}) => ( - - {title} - {entity && } - -); - -const EntityLabels = ({ entity }: { entity: Entity }) => { - const ownedByRelations = getEntityRelations(entity, RELATION_OWNED_BY); - return ( - <> - {ownedByRelations.length > 0 && ( - - } - /> - )} - {entity.spec?.lifecycle && ( - - )} - - ); -}; - -const headerProps = ( - kind: string, - namespace: string | undefined, - name: string, - entity: Entity | undefined, -): { headerTitle: string; headerType: string } => { - return { - headerTitle: `${name}${ - namespace && namespace !== DEFAULT_NAMESPACE ? ` in ${namespace}` : '' - }`, - headerType: (() => { - let t = kind.toLocaleLowerCase('en-US'); - if (entity && entity.spec && 'type' in entity.spec) { - t += ' — '; - t += (entity.spec as { type: string }).type.toLocaleLowerCase('en-US'); - } - return t; - })(), - }; -}; - -// NOTE(freben): Intentionally not exported at this point, since it's part of -// the unstable extra context menu items concept below -type ExtraContextMenuItem = { - title: string; - Icon: IconComponent; - onClick: () => void; -}; - -// unstable context menu option, eg: disable the unregister entity menu -type contextMenuOptions = { - disableUnregister: boolean; -}; - -type EntityPageLayoutProps = { - UNSTABLE_extraContextMenuItems?: ExtraContextMenuItem[]; - UNSTABLE_contextMenuOptions?: contextMenuOptions; - children?: React.ReactNode; -}; - -/** - * Old entity page, only used by the old router based hierarchies. - * - * @deprecated Please use CatalogEntityPage instead - */ -export const EntityPageLayout = ({ - children, - UNSTABLE_extraContextMenuItems, - UNSTABLE_contextMenuOptions, -}: EntityPageLayoutProps) => { - const { kind, namespace, name } = useEntityCompoundName(); - const { entity, loading, error } = useContext(EntityContext); - const { headerTitle, headerType } = headerProps( - kind, - namespace, - name, - entity!, - ); - - const [confirmationDialogOpen, setConfirmationDialogOpen] = useState(false); - const [inspectionDialogOpen, setInspectionDialogOpen] = useState(false); - const navigate = useNavigate(); - const cleanUpAfterRemoval = async () => { - setConfirmationDialogOpen(false); - navigate('/'); - }; - - return ( - -
} - pageTitleOverride={headerTitle} - type={headerType} - > - {/* TODO: Make entity labels configurable for entity kind / type */} - {entity && ( - <> - - setConfirmationDialogOpen(true)} - onInspectEntity={() => setInspectionDialogOpen(true)} - /> - - )} -
- - {loading && ( - - - - )} - - {entity && {children}} - - {error && ( - - - - )} - - {!loading && !error && !entity && ( - - - There is no {kind} with the requested{' '} - - kind, namespace, and name - - . - - - )} - - setConfirmationDialogOpen(false)} - /> - setInspectionDialogOpen(false)} - /> -
- ); -}; - -EntityPageLayout.Content = Tabbed.Content; diff --git a/plugins/catalog/src/components/EntityPageLayout/Tabbed/Tabbed.test.tsx b/plugins/catalog/src/components/EntityPageLayout/Tabbed/Tabbed.test.tsx deleted file mode 100644 index ad111380f8..0000000000 --- a/plugins/catalog/src/components/EntityPageLayout/Tabbed/Tabbed.test.tsx +++ /dev/null @@ -1,197 +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 { Tabbed } from './Tabbed'; -import { renderInTestApp } from '@backstage/test-utils'; -import { act, fireEvent } from '@testing-library/react'; -import { Routes, Route } from 'react-router'; - -describe('Tabbed layout', () => { - it('renders simplest case', async () => { - const rendered = await renderInTestApp( - - tabbed-test-content} - /> - , - ); - - expect(rendered.getByText('tabbed-test-title')).toBeInTheDocument(); - expect(rendered.getByText('tabbed-test-content')).toBeInTheDocument(); - }); - - it('throws if any other component is a child of Tabbed.Layout', async () => { - await expect( - renderInTestApp( - - tabbed-test-content} - /> -
This will cause app to throw
-
, - ), - ).rejects.toThrow(/This component only accepts/); - }); - - it('navigates when user clicks different tab', async () => { - const rendered = await renderInTestApp( - - - tabbed-test-content} - /> - tabbed-test-content-2} - /> - - } - /> - , - ); - - const secondTab = rendered.queryAllByRole('tab')[1]; - act(() => { - fireEvent.click(secondTab); - }); - - expect(rendered.getByText('tabbed-test-title')).toBeInTheDocument(); - expect(rendered.queryByText('tabbed-test-content')).not.toBeInTheDocument(); - - expect(rendered.getByText('tabbed-test-title-2')).toBeInTheDocument(); - expect(rendered.queryByText('tabbed-test-content-2')).toBeInTheDocument(); - }); - - describe('correctly delegates nested links', () => { - const renderRoute = (route: string) => - renderInTestApp( - - - tabbed-test-content} - /> - - tabbed-test-content-2 - - tabbed-test-nested-content-2} - /> - - - } - /> - - } - /> - , - { routeEntries: [route] }, - ); - - it('works for nested content', async () => { - const rendered = await renderRoute('/some-other-path/nested'); - - expect( - rendered.queryByText('tabbed-test-content'), - ).not.toBeInTheDocument(); - expect(rendered.queryByText('tabbed-test-content-2')).toBeInTheDocument(); - expect( - rendered.queryByText('tabbed-test-nested-content-2'), - ).toBeInTheDocument(); - }); - - it('works for non-nested content', async () => { - const rendered = await renderRoute('/some-other-path/'); - - expect( - rendered.queryByText('tabbed-test-content'), - ).not.toBeInTheDocument(); - expect(rendered.queryByText('tabbed-test-content-2')).toBeInTheDocument(); - expect( - rendered.queryByText('tabbed-test-nested-content-2'), - ).not.toBeInTheDocument(); - }); - }); - - it('shows only one tab contents at a time', async () => { - const rendered = await renderInTestApp( - - tabbed-test-content} - /> - tabbed-test-content-2} - /> - , - { routeEntries: ['/some-other-path'] }, - ); - - expect(rendered.getByText('tabbed-test-title')).toBeInTheDocument(); - expect(rendered.queryByText('tabbed-test-content')).not.toBeInTheDocument(); - - expect(rendered.getByText('tabbed-test-title-2')).toBeInTheDocument(); - expect(rendered.queryByText('tabbed-test-content-2')).toBeInTheDocument(); - }); - - it('redirects to the top level when no route is matching the url', async () => { - const rendered = await renderInTestApp( - - tabbed-test-content} - /> - tabbed-test-content-2} - /> - , - { routeEntries: ['/non-existing-path'] }, - ); - - expect(rendered.getByText('tabbed-test-title')).toBeInTheDocument(); - expect(rendered.getByText('tabbed-test-content')).toBeInTheDocument(); - expect(rendered.getByText('tabbed-test-title-2')).toBeInTheDocument(); - - expect( - rendered.queryByText('tabbed-test-content-2'), - ).not.toBeInTheDocument(); - }); -}); diff --git a/plugins/catalog/src/components/EntityPageLayout/Tabbed/Tabbed.tsx b/plugins/catalog/src/components/EntityPageLayout/Tabbed/Tabbed.tsx deleted file mode 100644 index 59271d2c60..0000000000 --- a/plugins/catalog/src/components/EntityPageLayout/Tabbed/Tabbed.tsx +++ /dev/null @@ -1,127 +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 { - useParams, - useNavigate, - PartialRouteObject, - matchRoutes, - RouteObject, - useRoutes, - Navigate, - RouteMatch, -} from 'react-router'; -import { Helmet } from 'react-helmet'; -import { Tab, HeaderTabs, Content } from '@backstage/core-components'; - -const getSelectedIndexOrDefault = ( - matchedRoute: RouteMatch, - tabs: Tab[], - defaultIndex = 0, -) => { - if (!matchedRoute) return defaultIndex; - const tabIndex = tabs.findIndex(t => t.id === matchedRoute.route.path); - return ~tabIndex ? tabIndex : defaultIndex; -}; - -/** - * Compound component, which allows you to define layout - * for EntityPage using Tabs as a sub-navigation mechanism - * Consists of 2 parts: Tabbed.Layout and Tabbed.Content. - * Takes care of: tabs, routes, document titles, spacing around content - * - * @example - * ```jsx - * - * This is rendered under /example/anything-here route} - * /> - * - * ``` - */ -export const Tabbed = { - Layout: ({ children }: { children: React.ReactNode }) => { - const routes: PartialRouteObject[] = []; - const tabs: Tab[] = []; - const params = useParams(); - const navigate = useNavigate(); - - React.Children.forEach(children, child => { - if (!React.isValidElement(child)) { - // Skip conditionals resolved to falses/nulls/undefineds etc - return; - } - if (child.type !== Tabbed.Content) { - throw new Error( - 'This component only accepts Content elements as direct children. Check the code of the EntityPage.', - ); - } - const pathAndId = (child as JSX.Element).props.path; - - // Child here must be then always a functional component without any wrappers - tabs.push({ - id: pathAndId, - label: (child as JSX.Element).props.title, - }); - - routes.push({ - path: pathAndId, - element: child.props.element, - }); - }); - - // Add catch-all for incorrect sub-routes - if ((routes?.[0]?.path ?? '') !== '') - routes.push({ - path: '/*', - element: , - }); - - const [matchedRoute] = - matchRoutes(routes as RouteObject[], `/${params['*']}`) ?? []; - const selectedIndex = getSelectedIndexOrDefault(matchedRoute, tabs); - const currentTab = tabs[selectedIndex]; - const title = currentTab?.label; - - const onTabChange = (index: number) => - // Remove trailing /* - // And remove leading / for relative navigation - // Note! route resolves relative to the position in the React tree, - // not relative to current location - navigate(tabs[index].id.replace(/\/\*$/, '').replace(/^\//, '')); - - const currentRouteElement = useRoutes(routes); - - if (!currentTab) return null; - return ( - <> - - - - {currentRouteElement} - - - ); - }, - Content: (_props: { path: string; title: string; element: JSX.Element }) => - null, -}; diff --git a/plugins/catalog/src/components/EntityPageLayout/Tabbed/index.ts b/plugins/catalog/src/components/EntityPageLayout/Tabbed/index.ts deleted file mode 100644 index ff14fe3b2c..0000000000 --- a/plugins/catalog/src/components/EntityPageLayout/Tabbed/index.ts +++ /dev/null @@ -1,16 +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 { Tabbed } from './Tabbed'; diff --git a/plugins/catalog/src/components/EntityPageLayout/index.ts b/plugins/catalog/src/components/EntityPageLayout/index.ts deleted file mode 100644 index 549d3d8454..0000000000 --- a/plugins/catalog/src/components/EntityPageLayout/index.ts +++ /dev/null @@ -1,16 +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 { EntityPageLayout } from './EntityPageLayout'; diff --git a/plugins/catalog/src/components/Router.tsx b/plugins/catalog/src/components/Router.tsx deleted file mode 100644 index 8dd82c29e1..0000000000 --- a/plugins/catalog/src/components/Router.tsx +++ /dev/null @@ -1,100 +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 { DEFAULT_NAMESPACE } from '@backstage/catalog-model'; -import { - AsyncEntityProvider, - useEntity, - useEntityFromUrl, -} from '@backstage/plugin-catalog-react'; -import { Typography } from '@material-ui/core'; -import React, { ComponentType, ReactNode } from 'react'; -import { Navigate, Route, Routes, useParams } from 'react-router'; -import { CatalogPage } from './CatalogPage'; -import { EntityNotFound } from './EntityNotFound'; -import { EntityPageLayout } from './EntityPageLayout'; -import { Content, Link } from '@backstage/core-components'; - -const DefaultEntityPage = () => ( - - - This is the default entity page. - - To override this component with your custom implementation, read - docs on{' '} - backstage.io/docs - - - } - /> - -); - -const EntityPageSwitch = ({ EntityPage }: { EntityPage: ComponentType }) => { - const { entity, loading, error } = useEntity(); - // Loading and error states - if (loading) return ; - if (error || !entity) return ; - - // Otherwise EntityPage provided from the App - // Note that EntityPage will include EntityPageLayout already - return ; -}; - -const OldEntityRouteRedirect = () => { - const { optionalNamespaceAndName, '*': rest } = useParams() as any; - const [name, namespace] = optionalNamespaceAndName.split(':').reverse(); - const namespaceLower = - namespace?.toLocaleLowerCase('en-US') ?? DEFAULT_NAMESPACE; - const restWithSlash = rest ? `/${rest}` : ''; - return ( - - ); -}; - -export const EntityLoader = (props: { children: ReactNode }) => ( - -); - -/** - * @deprecated Use plugin extensions instead - * */ -export const Router = ({ - EntityPage = DefaultEntityPage, -}: { - EntityPage?: ComponentType; -}) => ( - - } /> - - - - } - /> - } - /> - -); diff --git a/plugins/catalog/src/index.ts b/plugins/catalog/src/index.ts index abe34c29b9..eb05958198 100644 --- a/plugins/catalog/src/index.ts +++ b/plugins/catalog/src/index.ts @@ -30,11 +30,9 @@ export * from './components/CatalogTable/columns'; export * from './components/EntityLayout'; export * from './components/EntityOrphanWarning'; export * from './components/EntityProcessingErrorsPanel'; -export * from './components/EntityPageLayout'; export * from './components/EntitySwitch'; export * from './components/FilteredEntityLayout'; export * from './overridableComponents'; -export { Router } from './components/Router'; export { CatalogEntityPage, CatalogIndexPage,