Redirect: Add documentation. Cleanup (#26041)
* Add documentation. Cleanup. Add guardrail not to redirect to current url. --------- Signed-off-by: Sydney Achinger <sydneynicoleachinger@spotify.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-techdocs': patch
|
||||
---
|
||||
|
||||
Refactor TechDocs' mkdocs-redirects support.
|
||||
@@ -841,3 +841,8 @@ metadata:
|
||||
annotations:
|
||||
backstage.io/techdocs-entity: system:default/example
|
||||
```
|
||||
|
||||
## How to resolve broken links from moved or renamed pages in your documentation site
|
||||
|
||||
TechDocs supports using the [mkdocs-redirects](https://github.com/mkdocs/mkdocs-redirects/tree/master) plugin to create a redirect map for any TechDocs site. This allows broken links from renamed or moved pages in your site to be redirected to their specified replacement.
|
||||
TechDocs will notify the user that the page they are trying to access is no longer maintained. Then, they will be redirected. External site redirects are not supported. If an external redirect is provided, the user will instead be redirected to the index page of the documentation site.
|
||||
|
||||
@@ -44,9 +44,9 @@ import {
|
||||
copyToClipboard,
|
||||
useSanitizerTransformer,
|
||||
useStylesTransformer,
|
||||
handleMetaRedirects,
|
||||
} from '../../transformers';
|
||||
import { useNavigateUrl } from './useNavigateUrl';
|
||||
import { handleMetaRedirects } from '../../transformers/handleMetaRedirects';
|
||||
|
||||
const MOBILE_MEDIA_QUERY = 'screen and (max-width: 76.1875em)';
|
||||
|
||||
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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 { makeStyles } from '@material-ui/core/styles';
|
||||
import React, { useState } from 'react';
|
||||
import Snackbar from '@material-ui/core/Snackbar';
|
||||
import Button from '@material-ui/core/Button';
|
||||
|
||||
type TechDocsRedirectNotificationProps = {
|
||||
handleButtonClick: () => void;
|
||||
message: string;
|
||||
autoHideDuration: number;
|
||||
};
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
button: {
|
||||
color: theme.palette.primary.light,
|
||||
textDecoration: 'underline',
|
||||
},
|
||||
}));
|
||||
|
||||
export const TechDocsRedirectNotification = ({
|
||||
message,
|
||||
handleButtonClick,
|
||||
autoHideDuration,
|
||||
}: TechDocsRedirectNotificationProps) => {
|
||||
const classes = useStyles();
|
||||
const [open, setOpen] = useState(true);
|
||||
|
||||
const handleClose = () => setOpen(false);
|
||||
|
||||
return (
|
||||
<Snackbar
|
||||
open={open}
|
||||
anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
|
||||
autoHideDuration={autoHideDuration}
|
||||
color="primary"
|
||||
onClose={handleClose}
|
||||
message={message}
|
||||
action={
|
||||
<Button
|
||||
classes={{ root: classes.button }}
|
||||
size="small"
|
||||
onClick={() => {
|
||||
handleClose();
|
||||
handleButtonClick();
|
||||
}}
|
||||
>
|
||||
Redirect now
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { TechDocsRedirectNotification } from './TechDocsRedirectNotification';
|
||||
@@ -82,7 +82,9 @@ describe('handleMetaRedirects', () => {
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
jest.runAllTimers();
|
||||
expect(navigate).toHaveBeenCalledWith('/docs/default/component/testEntity');
|
||||
expect(navigate).toHaveBeenCalledWith(
|
||||
'http://localhost/docs/default/component/testEntity',
|
||||
);
|
||||
});
|
||||
|
||||
it('should navigate to absolute URL if meta redirect tag is present and not external', async () => {
|
||||
|
||||
@@ -16,64 +16,19 @@
|
||||
|
||||
import { Transformer } from './transformer';
|
||||
import { normalizeUrl } from './rewriteDocLinks';
|
||||
import Snackbar from '@material-ui/core/Snackbar';
|
||||
import React, { useState } from 'react';
|
||||
import React from 'react';
|
||||
import { renderReactElement } from './renderReactElement';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
|
||||
type RedirectNotificationProps = {
|
||||
handleButtonClick: () => void;
|
||||
message: string;
|
||||
autoHideDuration: number;
|
||||
};
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
button: {
|
||||
color: theme.palette.primary.light,
|
||||
textDecoration: 'underline',
|
||||
},
|
||||
}));
|
||||
const RedirectNotification = ({
|
||||
message,
|
||||
handleButtonClick,
|
||||
autoHideDuration,
|
||||
}: RedirectNotificationProps) => {
|
||||
const classes = useStyles();
|
||||
const [open, setOpen] = useState(true);
|
||||
const handleClose = () => {
|
||||
setOpen(prev => !prev);
|
||||
};
|
||||
|
||||
return (
|
||||
<Snackbar
|
||||
open={open}
|
||||
anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
|
||||
autoHideDuration={autoHideDuration}
|
||||
color="primary"
|
||||
onClose={handleClose}
|
||||
message={message}
|
||||
action={
|
||||
<Button
|
||||
classes={{ root: classes.button }}
|
||||
size="small"
|
||||
onClick={handleButtonClick}
|
||||
>
|
||||
Redirect now
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
import { TechDocsRedirectNotification } from '../components/TechDocsRedirectNotification';
|
||||
|
||||
export const handleMetaRedirects = (
|
||||
navigate: (to: string) => void,
|
||||
entityName: string,
|
||||
): Transformer => {
|
||||
const redirectAfterMs = 4000;
|
||||
const redirectAfterMs = 3000;
|
||||
|
||||
const determineRedirectURL = (metaUrl: string) => {
|
||||
const normalizedCurrentUrl = normalizeUrl(window.location.href);
|
||||
// If metaUrl is relative, it will be resolved with base href. If it is absolute, it will replace the base href when creating URL object.
|
||||
// When creating URL object, if the metaUrl is relative, it will be resolved with base href. If it is absolute, it will replace the base href.
|
||||
const absoluteRedirectObj = new URL(metaUrl, normalizedCurrentUrl);
|
||||
const isExternalRedirect =
|
||||
absoluteRedirectObj.hostname !== window.location.hostname;
|
||||
@@ -85,9 +40,8 @@ export const handleMetaRedirects = (
|
||||
0,
|
||||
indexOfSiteHome + entityName.length,
|
||||
);
|
||||
return siteHomePath;
|
||||
return new URL(siteHomePath, normalizedCurrentUrl).href;
|
||||
}
|
||||
// The navigate function from dom.tsx is a wrapper around react-router navigate function that helps absolute url redirects.
|
||||
return absoluteRedirectObj.href;
|
||||
};
|
||||
|
||||
@@ -97,15 +51,23 @@ export const handleMetaRedirects = (
|
||||
const metaContentParameters = elem
|
||||
.getAttribute('content')
|
||||
?.split('url=');
|
||||
|
||||
if (!metaContentParameters || metaContentParameters.length < 2) {
|
||||
continue;
|
||||
return dom;
|
||||
}
|
||||
|
||||
const metaUrl = metaContentParameters[1];
|
||||
const redirectURL = determineRedirectURL(metaUrl);
|
||||
|
||||
// If the current URL is the same as the redirect URL, do not proceed with the redirect.
|
||||
if (window.location.href === redirectURL) {
|
||||
return dom;
|
||||
}
|
||||
|
||||
const container = document.createElement('div');
|
||||
|
||||
renderReactElement(
|
||||
<RedirectNotification
|
||||
<TechDocsRedirectNotification
|
||||
message="This TechDocs page is no longer maintained. Will automatically redirect to the designated replacement."
|
||||
handleButtonClick={() => navigate(redirectURL)}
|
||||
autoHideDuration={redirectAfterMs}
|
||||
|
||||
@@ -27,3 +27,4 @@ export * from './simplifyMkdocsFooter';
|
||||
export * from './onCssReady';
|
||||
export * from './scrollIntoNavigation';
|
||||
export * from './transformer';
|
||||
export * from './handleMetaRedirects';
|
||||
|
||||
Reference in New Issue
Block a user