diff --git a/.changeset/curvy-peaches-applaud.md b/.changeset/curvy-peaches-applaud.md new file mode 100644 index 0000000000..f24830ad38 --- /dev/null +++ b/.changeset/curvy-peaches-applaud.md @@ -0,0 +1,8 @@ +--- +'@backstage/core-api': patch +'@backstage/plugin-catalog-react': patch +'@backstage/plugin-github-actions': patch +'@backstage/plugin-jenkins': patch +--- + +Introduce `useRouteRefParams` to `core-api` to retrieve typed route parameters. diff --git a/packages/core-api/src/routing/hooks.test.tsx b/packages/core-api/src/routing/hooks.test.tsx index d16f9808ab..8a9b70ed7d 100644 --- a/packages/core-api/src/routing/hooks.test.tsx +++ b/packages/core-api/src/routing/hooks.test.tsx @@ -14,35 +14,35 @@ * limitations under the License. */ +import { render } from '@testing-library/react'; +import { renderHook } from '@testing-library/react-hooks'; import React, { + Context, PropsWithChildren, ReactElement, useContext, - Context, } from 'react'; -import { MemoryRouter, Routes } from 'react-router-dom'; -import { render } from '@testing-library/react'; -import { renderHook } from '@testing-library/react-hooks'; -import { VersionedValue } from '../lib/versionedValues'; -import { getGlobalSingleton } from '../lib/globalObject'; +import { MemoryRouter, Route, Routes } from 'react-router-dom'; import { createRoutableExtension } from '../extensions'; import { childDiscoverer, routeElementDiscoverer, traverseElementTree, } from '../extensions/traversal'; +import { getGlobalSingleton } from '../lib/globalObject'; +import { VersionedValue } from '../lib/versionedValues'; import { createPlugin } from '../plugin'; import { - routePathCollector, - routeParentCollector, routeObjectCollector, + routeParentCollector, + routePathCollector, } from './collectors'; -import { validateRoutes } from './validation'; -import { useRouteRef, RoutingProvider } from './hooks'; +import { createExternalRouteRef } from './ExternalRouteRef'; +import { RoutingProvider, useRouteRef, useRouteRefParams } from './hooks'; import { createRouteRef, RouteRefConfig } from './RouteRef'; import { RouteResolver } from './RouteResolver'; -import { createExternalRouteRef } from './ExternalRouteRef'; -import { AnyRouteRef, RouteFunc, RouteRef, ExternalRouteRef } from './types'; +import { AnyRouteRef, ExternalRouteRef, RouteFunc, RouteRef } from './types'; +import { validateRoutes } from './validation'; const mockConfig = (extra?: Partial>) => ({ path: '/unused', @@ -370,3 +370,36 @@ describe('v1 consumer', () => { expect(renderedHook.result.current?.({ x: 'my-x' })).toBe('/bar/my-x'); }); }); + +describe('useRouteRefParams', () => { + it('should provide types params', () => { + const routeRef = createRouteRef({ + id: 'ref1', + params: ['a', 'b'], + }); + + const Page = () => { + const params: { a: string; b: string } = useRouteRefParams(routeRef); + + return ( +
+ {params.a} + {params.b} +
+ ); + }; + + const { getByText } = render( + + + + + + + , + ); + + expect(getByText('foo')).toBeInTheDocument(); + expect(getByText('bar')).toBeInTheDocument(); + }); +}); diff --git a/packages/core-api/src/routing/hooks.tsx b/packages/core-api/src/routing/hooks.tsx index 4c4a7a6a05..1867ac5c67 100644 --- a/packages/core-api/src/routing/hooks.tsx +++ b/packages/core-api/src/routing/hooks.tsx @@ -21,7 +21,7 @@ import React, { useMemo, Context, } from 'react'; -import { useLocation } from 'react-router-dom'; +import { useLocation, useParams } from 'react-router-dom'; import { BackstageRouteObject, RouteRef, @@ -111,3 +111,9 @@ export const RoutingProvider = ({ ); }; + +export function useRouteRefParams( + _routeRef: RouteRef | SubRouteRef, +): Params { + return useParams() as Params; +} diff --git a/packages/core-api/src/routing/index.ts b/packages/core-api/src/routing/index.ts index c5441d5b23..0c01e74f3f 100644 --- a/packages/core-api/src/routing/index.ts +++ b/packages/core-api/src/routing/index.ts @@ -14,17 +14,17 @@ * limitations under the License. */ +export { createExternalRouteRef } from './ExternalRouteRef'; +export { FlatRoutes } from './FlatRoutes'; +export { useRouteRef, useRouteRefParams } from './hooks'; +export { createRouteRef } from './RouteRef'; +export type { RouteRefConfig } from './RouteRef'; +export { createSubRouteRef } from './SubRouteRef'; export type { - RouteRef, - SubRouteRef, AbsoluteRouteRef, ConcreteRoute, - MutableRouteRef, ExternalRouteRef, + MutableRouteRef, + RouteRef, + SubRouteRef, } from './types'; -export { FlatRoutes } from './FlatRoutes'; -export { createRouteRef } from './RouteRef'; -export { createSubRouteRef } from './SubRouteRef'; -export { createExternalRouteRef } from './ExternalRouteRef'; -export type { RouteRefConfig } from './RouteRef'; -export { useRouteRef } from './hooks'; diff --git a/plugins/catalog-react/src/hooks/useEntityCompoundName.ts b/plugins/catalog-react/src/hooks/useEntityCompoundName.ts index dd9c0d26ff..f76097a6dc 100644 --- a/plugins/catalog-react/src/hooks/useEntityCompoundName.ts +++ b/plugins/catalog-react/src/hooks/useEntityCompoundName.ts @@ -13,12 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { useParams } from 'react-router'; +import { useRouteRefParams } from '@backstage/core'; +import { entityRouteRef } from '../routes'; /** * Grabs entity kind, namespace, and name from the location */ export const useEntityCompoundName = () => { - const { kind, namespace, name } = useParams(); + const { kind, namespace, name } = useRouteRefParams(entityRouteRef); return { kind, namespace, name }; }; diff --git a/plugins/github-actions/src/components/WorkflowRunDetails/useWorkflowRunsDetails.ts b/plugins/github-actions/src/components/WorkflowRunDetails/useWorkflowRunsDetails.ts index d0141c27fa..3e2a5c4382 100644 --- a/plugins/github-actions/src/components/WorkflowRunDetails/useWorkflowRunsDetails.ts +++ b/plugins/github-actions/src/components/WorkflowRunDetails/useWorkflowRunsDetails.ts @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { useApi } from '@backstage/core'; -import { useParams } from 'react-router-dom'; +import { useApi, useRouteRefParams } from '@backstage/core'; import { useAsync } from 'react-use'; import { githubActionsApiRef } from '../../api'; +import { buildRouteRef } from '../../plugin'; export const useWorkflowRunsDetails = ({ hostname, @@ -28,7 +28,7 @@ export const useWorkflowRunsDetails = ({ repo: string; }) => { const api = useApi(githubActionsApiRef); - const { id } = useParams(); + const { id } = useRouteRefParams(buildRouteRef); const details = useAsync(async () => { return repo && owner ? api.getWorkflowRun({ diff --git a/plugins/github-actions/src/plugin.ts b/plugins/github-actions/src/plugin.ts index d296b4ad30..f66e96a981 100644 --- a/plugins/github-actions/src/plugin.ts +++ b/plugins/github-actions/src/plugin.ts @@ -33,6 +33,7 @@ export const rootRouteRef = createRouteRef({ export const buildRouteRef = createRouteRef({ path: ':id', + params: ['id'], title: 'GitHub Actions Workflow Run', }); diff --git a/plugins/jenkins/src/components/BuildWithStepsPage/BuildWithStepsPage.tsx b/plugins/jenkins/src/components/BuildWithStepsPage/BuildWithStepsPage.tsx index 480dfd6dea..1b7ccb8121 100644 --- a/plugins/jenkins/src/components/BuildWithStepsPage/BuildWithStepsPage.tsx +++ b/plugins/jenkins/src/components/BuildWithStepsPage/BuildWithStepsPage.tsx @@ -13,25 +13,25 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; -import { useParams } from 'react-router-dom'; -import { Breadcrumbs, Content, Link } from '@backstage/core'; +import { Breadcrumbs, Content, Link, useRouteRefParams } from '@backstage/core'; import { Box, - Typography, - Paper, - TableContainer, - Table, - TableRow, - TableCell, - TableBody, Link as MaterialLink, + Paper, + Table, + TableBody, + TableCell, + TableContainer, + TableRow, + Typography, } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; +import ExternalLinkIcon from '@material-ui/icons/Launch'; +import React from 'react'; +import { buildRouteRef } from '../../plugin'; +import { JenkinsRunStatus } from '../BuildsPage/lib/Status'; import { useBuildWithSteps } from '../useBuildWithSteps'; import { useProjectSlugFromEntity } from '../useProjectSlugFromEntity'; -import { JenkinsRunStatus } from '../BuildsPage/lib/Status'; -import ExternalLinkIcon from '@material-ui/icons/Launch'; const useStyles = makeStyles(theme => ({ root: { @@ -48,7 +48,7 @@ const useStyles = makeStyles(theme => ({ const BuildWithStepsView = () => { const projectName = useProjectSlugFromEntity(); - const { branch, buildNumber } = useParams(); + const { branch, buildNumber } = useRouteRefParams(buildRouteRef); const classes = useStyles(); const buildPath = `${projectName}/${branch}/${buildNumber}`; const [{ value }] = useBuildWithSteps(buildPath); diff --git a/plugins/jenkins/src/plugin.ts b/plugins/jenkins/src/plugin.ts index 85001d9bbc..db4122349c 100644 --- a/plugins/jenkins/src/plugin.ts +++ b/plugins/jenkins/src/plugin.ts @@ -15,14 +15,14 @@ */ import { - createPlugin, - createRouteRef, createApiFactory, - discoveryApiRef, - createRoutableExtension, createComponentExtension, + createPlugin, + createRoutableExtension, + createRouteRef, + discoveryApiRef, } from '@backstage/core'; -import { jenkinsApiRef, JenkinsApi } from './api'; +import { JenkinsApi, jenkinsApiRef } from './api'; export const rootRouteRef = createRouteRef({ path: '', @@ -31,6 +31,7 @@ export const rootRouteRef = createRouteRef({ export const buildRouteRef = createRouteRef({ path: 'run/:branch/:buildNumber', + params: ['branch', 'buildNumber'], title: 'Jenkins run', });