fix(catalog): change route pattern to entities (#3133)

This commit is contained in:
Fredrik Adelöw
2020-10-28 10:02:38 +01:00
committed by GitHub
parent 33454c0f20
commit 0b956f21b4
13 changed files with 101 additions and 90 deletions
+10
View File
@@ -0,0 +1,10 @@
---
'@backstage/plugin-catalog': minor
---
The URL path for a catalog entity has changed,
- from: `/catalog/:kind/:optionalNamespaceAndName`
- to: `/catalog/:namespace/:kind/:name`
Redirects are in place, so disruptions for users should not happen.
@@ -31,7 +31,7 @@ import React, { useCallback, useMemo, useState } from 'react';
import { Link as RouterLink } from 'react-router-dom';
import { catalogApiRef } from '../../api/types';
import { EntityFilterGroupsProvider, useFilteredEntities } from '../../filter';
import { useStarredEntities } from '../../hooks/useStarredEntites';
import { useStarredEntities } from '../../hooks/useStarredEntities';
import { ButtonGroup, CatalogFilter } from '../CatalogFilter/CatalogFilter';
import { CatalogTable } from '../CatalogTable/CatalogTable';
import { ResultsFilter } from '../ResultsFilter/ResultsFilter';
@@ -22,8 +22,8 @@ import { Alert } from '@material-ui/lab';
import React from 'react';
import { generatePath, Link as RouterLink } from 'react-router-dom';
import { findLocationForEntityMeta } from '../../data/utils';
import { useStarredEntities } from '../../hooks/useStarredEntites';
import { entityRoute } from '../../routes';
import { useStarredEntities } from '../../hooks/useStarredEntities';
import { entityRoute, entityRouteParams } from '../../routes';
import {
favouriteEntityIcon,
favouriteEntityTooltip,
@@ -38,13 +38,7 @@ const columns: TableColumn<Entity>[] = [
<Link
component={RouterLink}
to={generatePath(entityRoute.path, {
optionalNamespaceAndName: [
entity.metadata.namespace,
entity.metadata.name,
]
.filter(Boolean)
.join(':'),
kind: entity.kind,
...entityRouteParams(entity),
selectedTabId: 'overview',
})}
>
@@ -13,17 +13,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, { useState, useContext } from 'react';
import { useParams, useNavigate } from 'react-router';
import { EntityContext } from '../../hooks/useEntity';
import { Page, Header, HeaderLabel, Content, Progress } from '@backstage/core';
import { Entity, ENTITY_DEFAULT_NAMESPACE } from '@backstage/catalog-model';
import { FavouriteEntity } from '../FavouriteEntity/FavouriteEntity';
import { Content, Header, HeaderLabel, Page, Progress } from '@backstage/core';
import { Box } from '@material-ui/core';
import { EntityContextMenu } from '../EntityContextMenu/EntityContextMenu';
import { UnregisterEntityDialog } from '../UnregisterEntityDialog/UnregisterEntityDialog';
import { Alert } from '@material-ui/lab';
import React, { PropsWithChildren, useContext, useState } from 'react';
import { useNavigate } from 'react-router';
import { EntityContext } from '../../hooks/useEntity';
import { EntityContextMenu } from '../EntityContextMenu/EntityContextMenu';
import { FavouriteEntity } from '../FavouriteEntity/FavouriteEntity';
import { UnregisterEntityDialog } from '../UnregisterEntityDialog/UnregisterEntityDialog';
import { useEntityCompoundName } from '../useEntityCompoundName';
import { Tabbed } from './Tabbed';
const EntityPageTitle = ({
@@ -62,17 +62,8 @@ function headerProps(
};
}
export const EntityPageLayout = ({
children,
}: {
children?: React.ReactNode;
}) => {
const { optionalNamespaceAndName, kind } = useParams() as {
optionalNamespaceAndName: string;
kind: string;
};
const [name, namespace] = optionalNamespaceAndName.split(':').reverse();
export const EntityPageLayout = ({ children }: PropsWithChildren<{}>) => {
const { kind, namespace, name } = useEntityCompoundName();
const { entity, loading, error } = useContext(EntityContext);
const { headerTitle, headerType } = headerProps(
kind,
@@ -18,7 +18,7 @@ import React, { ComponentProps } from 'react';
import { IconButton, Tooltip, withStyles } from '@material-ui/core';
import StarBorder from '@material-ui/icons/StarBorder';
import Star from '@material-ui/icons/Star';
import { useStarredEntities } from '../../hooks/useStarredEntites';
import { useStarredEntities } from '../../hooks/useStarredEntities';
import { Entity } from '@backstage/catalog-model';
type Props = ComponentProps<typeof IconButton> & { entity: Entity };
+22 -5
View File
@@ -13,16 +13,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ENTITY_DEFAULT_NAMESPACE } from '@backstage/catalog-model';
import { Content } from '@backstage/core';
import { Link, Typography } from '@material-ui/core';
import React, { ComponentType } from 'react';
import { Navigate, Route, Routes, useParams } from 'react-router';
import { useEntity } from '../hooks/useEntity';
import { entityRoute, rootRoute } from '../routes';
import { CatalogPage } from './CatalogPage';
import { EntityNotFound } from './EntityNotFound';
import { EntityPageLayout } from './EntityPageLayout';
import { Route, Routes } from 'react-router';
import { entityRoute, rootRoute } from '../routes';
import { Content } from '@backstage/core';
import { Typography, Link } from '@material-ui/core';
import { EntityProvider } from './EntityProvider';
import { useEntity } from '../hooks/useEntity';
const DefaultEntityPage = () => (
<EntityPageLayout>
@@ -56,6 +57,18 @@ const EntityPageSwitch = ({ EntityPage }: { EntityPage: ComponentType }) => {
return <EntityPage />;
};
const OldEntityRouteRedirect = () => {
const { optionalNamespaceAndName, '*': rest } = useParams() as any;
const [name, namespace] = optionalNamespaceAndName.split(':').reverse();
const namespaceLower = namespace?.toLowerCase() ?? ENTITY_DEFAULT_NAMESPACE;
const restWithSlash = rest ? `/${rest}` : '';
return (
<Navigate
to={`../../${namespaceLower}/component/${name}${restWithSlash}`}
/>
);
};
export const Router = ({
EntityPage = DefaultEntityPage,
}: {
@@ -71,5 +84,9 @@ export const Router = ({
</EntityProvider>
}
/>
<Route
path="Component/:optionalNamespaceAndName/*"
element={<OldEntityRouteRedirect />}
/>
</Routes>
);
@@ -16,11 +16,9 @@
import { useParams } from 'react-router';
/**
* Grabs entity kind and name + optional namespace from location
* Grabs entity kind, namespace, and name from the location
*/
export const useEntityCompoundName = () => {
const params = useParams();
const { kind, optionalNamespaceAndName = '' } = params;
const [name, namespace] = optionalNamespaceAndName.split(':').reverse();
return { kind, name, namespace };
const { kind, namespace, name } = useParams();
return { kind, namespace, name };
};
+7 -7
View File
@@ -13,12 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useEffect, createContext, useContext } from 'react';
import { useNavigate, useParams } from 'react-router';
import { useApi, errorApiRef } from '@backstage/core';
import { catalogApiRef } from '../api/types';
import { useAsync } from 'react-use';
import { Entity } from '@backstage/catalog-model';
import { errorApiRef, useApi } from '@backstage/core';
import { createContext, useContext, useEffect } from 'react';
import { useNavigate } from 'react-router';
import { useAsync } from 'react-use';
import { catalogApiRef } from '../api/types';
import { useEntityCompoundName } from '../components/useEntityCompoundName';
type EntityLoadingStatus = {
entity?: Entity;
@@ -33,8 +34,7 @@ export const EntityContext = createContext<EntityLoadingStatus>({
});
export const useEntityFromUrl = (): EntityLoadingStatus => {
const { optionalNamespaceAndName, kind } = useParams();
const [name, namespace] = optionalNamespaceAndName.split(':').reverse();
const { kind, namespace, name } = useEntityCompoundName();
const navigate = useNavigate();
const errorApi = useApi(errorApiRef);
const catalogApi = useApi(catalogApiRef);
@@ -16,7 +16,7 @@
import React from 'react';
import { renderHook, act } from '@testing-library/react-hooks';
import { useStarredEntities } from './useStarredEntites';
import { useStarredEntities } from './useStarredEntities';
import {
ApiProvider,
ApiRegistry,
+13 -1
View File
@@ -14,6 +14,7 @@
* limitations under the License.
*/
import { Entity, ENTITY_DEFAULT_NAMESPACE } from '@backstage/catalog-model';
import { createRouteRef } from '@backstage/core';
const NoIcon = () => null;
@@ -25,6 +26,17 @@ export const rootRoute = createRouteRef({
});
export const entityRoute = createRouteRef({
icon: NoIcon,
path: ':kind/:optionalNamespaceAndName/*',
path: ':namespace/:kind/:name/*',
title: 'Entity',
});
// Utility function to get suitable route params for entityRoute, given an
// entity instance
export function entityRouteParams(entity: Entity) {
return {
kind: entity.kind.toLowerCase(),
namespace:
entity.metadata.namespace?.toLowerCase() ?? ENTITY_DEFAULT_NAMESPACE,
name: entity.metadata.name,
} as const;
}
@@ -14,23 +14,23 @@
* limitations under the License.
*/
import React from 'react';
import { Entity } from '@backstage/catalog-model';
import { RouteRef, StructuredMetadataTable } from '@backstage/core';
import { entityRoute, entityRouteParams } from '@backstage/plugin-catalog';
import {
Button,
Dialog,
DialogTitle,
DialogActions,
DialogContent,
DialogContentText,
DialogTitle,
Divider,
Link,
List,
ListItem,
Link,
Divider,
DialogActions,
Button,
} from '@material-ui/core';
import { Entity } from '@backstage/catalog-model';
import { StructuredMetadataTable, RouteRef } from '@backstage/core';
import React from 'react';
import { generatePath, resolvePath } from 'react-router';
import { entityRoute } from '@backstage/plugin-catalog';
import { Link as RouterLink } from 'react-router-dom';
type Props = {
@@ -47,22 +47,16 @@ const getEntityCatalogPath = ({
entity: Entity;
catalogRouteRef: RouteRef;
}) => {
const optionalNamespaceAndName = [
entity.metadata.namespace,
entity.metadata.name,
]
.filter(Boolean)
.join(':');
const relativeEntityPathInsideCatalog = generatePath(entityRoute.path, {
optionalNamespaceAndName,
kind: entity.kind,
});
const relativeEntityPathInsideCatalog = generatePath(
entityRoute.path,
entityRouteParams(entity),
);
const resolvedAbsolutePath = resolvePath(
relativeEntityPathInsideCatalog,
catalogRouteRef.path,
)?.pathname;
return resolvedAbsolutePath;
};
@@ -13,21 +13,21 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, { useState, useEffect } from 'react';
import {
Dialog,
LinearProgress,
DialogTitle,
DialogContent,
DialogActions,
} from '@material-ui/core';
import { JobStage } from '../JobStage/JobStage';
import { useJobPolling } from './useJobPolling';
import { Job } from '../../types';
import { TemplateEntityV1alpha1 } from '@backstage/catalog-model';
import { Button } from '@backstage/core';
import { entityRoute } from '@backstage/plugin-catalog';
import { entityRoute, entityRouteParams } from '@backstage/plugin-catalog';
import {
Dialog,
DialogActions,
DialogContent,
DialogTitle,
LinearProgress,
} from '@material-ui/core';
import React, { useEffect, useState } from 'react';
import { generatePath } from 'react-router-dom';
import { Job } from '../../types';
import { JobStage } from '../JobStage/JobStage';
import { useJobPolling } from './useJobPolling';
type Props = {
onClose: () => void;
@@ -75,15 +75,10 @@ export const JobStatusModal = ({
{entity && (
<DialogActions>
<Button
to={generatePath(`/catalog/${entityRoute.path}`, {
kind: entity.kind,
optionalNamespaceAndName: [
entity.metadata.namespace,
entity.metadata.name,
]
.filter(Boolean)
.join(':'),
})}
to={generatePath(
`/catalog/${entityRoute.path}`,
entityRouteParams(entity),
)}
>
View in catalog
</Button>