diff --git a/.changeset/olive-berries-jump.md b/.changeset/olive-berries-jump.md
new file mode 100644
index 0000000000..d8e9fb1da6
--- /dev/null
+++ b/.changeset/olive-berries-jump.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-techdocs': patch
+---
+
+Added support for the Search bar in docs residing in the entity page tab, and not only the global "/docs" page.
diff --git a/plugins/techdocs/report.api.md b/plugins/techdocs/report.api.md
index c58a946287..2cc3934f62 100644
--- a/plugins/techdocs/report.api.md
+++ b/plugins/techdocs/report.api.md
@@ -371,6 +371,7 @@ export const TechDocsReaderPageContent: (
export type TechDocsReaderPageContentProps = {
entityRef?: CompoundEntityRef;
withSearch?: boolean;
+ searchResultUrlMapper?: (url: string) => string;
onReady?: () => void;
};
@@ -431,6 +432,7 @@ export type TechDocsSearchProps = {
entityId: CompoundEntityRef;
entityTitle?: string;
debounceTime?: number;
+ searchResultUrlMapper?: (url: string) => string;
};
// @public
diff --git a/plugins/techdocs/src/EntityPageDocs.tsx b/plugins/techdocs/src/EntityPageDocs.tsx
index 07222190ae..70a0517ded 100644
--- a/plugins/techdocs/src/EntityPageDocs.tsx
+++ b/plugins/techdocs/src/EntityPageDocs.tsx
@@ -25,12 +25,24 @@ import React from 'react';
import { TechDocsReaderPage } from './plugin';
import { TechDocsReaderPageContent } from './reader/components/TechDocsReaderPageContent';
import { TechDocsReaderPageSubheader } from './reader/components/TechDocsReaderPageSubheader';
+import { useEntityPageTechDocsRedirect } from './search/hooks/useTechDocsLocation';
-type EntityPageDocsProps = { entity: Entity };
+type EntityPageDocsProps = {
+ entity: Entity;
+ /**
+ * Show or hide the content search bar, defaults to true.
+ */
+ withSearch?: boolean;
+};
-export const EntityPageDocs = ({ entity }: EntityPageDocsProps) => {
+export const EntityPageDocs = ({
+ entity,
+ withSearch = true,
+}: EntityPageDocsProps) => {
let entityRef = getCompoundEntityRef(entity);
+ const searchResultUrlMapper = useEntityPageTechDocsRedirect(entityRef);
+
if (entity.metadata.annotations?.[TECHDOCS_EXTERNAL_ANNOTATION]) {
try {
entityRef = parseEntityRef(
@@ -44,7 +56,10 @@ export const EntityPageDocs = ({ entity }: EntityPageDocsProps) => {
return (
-
+
);
};
diff --git a/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/TechDocsReaderPageContent.tsx b/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/TechDocsReaderPageContent.tsx
index 21418a716e..f8b3129feb 100644
--- a/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/TechDocsReaderPageContent.tsx
+++ b/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/TechDocsReaderPageContent.tsx
@@ -64,6 +64,13 @@ export type TechDocsReaderPageContentProps = {
* Show or hide the search bar, defaults to true.
*/
withSearch?: boolean;
+ /**
+ * If {@link TechDocsReaderPageContentProps.withSearch | withSearch} is true,
+ * this will redirect the search result urls, e.g. turn search results into
+ * links within the "Docs" tab of the entity page, instead of the global docs
+ * page.
+ */
+ searchResultUrlMapper?: (url: string) => string;
/**
* Callback called when the content is rendered.
*/
@@ -76,7 +83,7 @@ export type TechDocsReaderPageContentProps = {
*/
export const TechDocsReaderPageContent = withTechDocsReaderProvider(
(props: TechDocsReaderPageContentProps) => {
- const { withSearch = true, onReady } = props;
+ const { withSearch = true, searchResultUrlMapper, onReady } = props;
const classes = useStyles();
const {
@@ -142,6 +149,7 @@ export const TechDocsReaderPageContent = withTechDocsReaderProvider(
)}
diff --git a/plugins/techdocs/src/search/components/TechDocsSearch.tsx b/plugins/techdocs/src/search/components/TechDocsSearch.tsx
index ce77d547db..b283581fa2 100644
--- a/plugins/techdocs/src/search/components/TechDocsSearch.tsx
+++ b/plugins/techdocs/src/search/components/TechDocsSearch.tsx
@@ -34,6 +34,7 @@ export type TechDocsSearchProps = {
entityId: CompoundEntityRef;
entityTitle?: string;
debounceTime?: number;
+ searchResultUrlMapper?: (url: string) => string;
};
type TechDocsDoc = {
@@ -58,7 +59,12 @@ const isTechDocsSearchResult = (
};
const TechDocsSearchBar = (props: TechDocsSearchProps) => {
- const { entityId, entityTitle, debounceTime = 150 } = props;
+ const {
+ entityId,
+ entityTitle,
+ debounceTime = 150,
+ searchResultUrlMapper,
+ } = props;
const [open, setOpen] = useState(false);
const navigate = useNavigate();
const {
@@ -102,7 +108,9 @@ const TechDocsSearchBar = (props: TechDocsSearchProps) => {
) => {
if (isTechDocsSearchResult(selection)) {
const { location } = selection.document;
- navigate(location);
+ navigate(
+ searchResultUrlMapper ? searchResultUrlMapper(location) : location,
+ );
}
};
diff --git a/plugins/techdocs/src/search/hooks/useTechDocsLocation.ts b/plugins/techdocs/src/search/hooks/useTechDocsLocation.ts
new file mode 100644
index 0000000000..737dd2839c
--- /dev/null
+++ b/plugins/techdocs/src/search/hooks/useTechDocsLocation.ts
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2024 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 { useMemo } from 'react';
+
+import { CompoundEntityRef } from '@backstage/catalog-model';
+import { useRouteRef } from '@backstage/core-plugin-api';
+
+import { rootCatalogDocsRouteRef, rootDocsRouteRef } from '../../routes';
+
+const trimStartSlash = (path: string) => path.replace(/^\/+/, '');
+const trimEndSlash = (path: string) => path.replace(/\/+$/, '');
+
+/**
+ * Returns a function that takes a location to a Tech Docs entry, and returns a
+ * new location, re-routed to the catalog page tab.
+ *
+ * @internal
+ */
+export function useEntityPageTechDocsRedirect(entityRef: CompoundEntityRef) {
+ const { kind, name, namespace } = entityRef;
+
+ const routeDocsRoot = useRouteRef(rootDocsRouteRef);
+ const routeDocsCatalog = useRouteRef(rootCatalogDocsRouteRef);
+
+ // Re-routes a /docs/:namespace/:kind/:name/* location into
+ // /catalog/:namespace/:kind/:name/docs/*, while handling situations where
+ // these defaults are changed.
+ // eslint-disable-next-line react-hooks/rules-of-hooks
+ const reRouteLocationToCatalog = useMemo(() => {
+ const rootDocsPath = trimEndSlash(routeDocsRoot({ kind, namespace, name }));
+ const catalogDocsPath = trimEndSlash(routeDocsCatalog());
+
+ return (url: string): string => {
+ if (
+ url
+ .toLocaleLowerCase('en-US')
+ .startsWith(rootDocsPath.toLocaleLowerCase('en-US'))
+ ) {
+ const suffix = trimStartSlash(url.slice(rootDocsPath.length));
+ return suffix.length === 0 || suffix.startsWith('#')
+ ? `${catalogDocsPath}${suffix}`
+ : `${catalogDocsPath}/${suffix}`;
+ }
+ return url;
+ };
+ }, [routeDocsRoot, routeDocsCatalog, kind, name, namespace]);
+
+ return reRouteLocationToCatalog;
+}