Show a "Refresh" button to if the content is stale
Signed-off-by: Dominik Henneke <dominik.henneke@sda-se.com>
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/plugin-techdocs': patch
|
||||
---
|
||||
|
||||
Show a "Refresh" button to if the content is stale.
|
||||
This removes the need to do a full page-reload to display more recent TechDocs content.
|
||||
@@ -148,6 +148,11 @@ createDevApp()
|
||||
|
||||
<TabbedLayout.Route path="/stale" title="Stale">
|
||||
{createPage({
|
||||
entityDocs: ({ called, content }) => {
|
||||
return called === 0
|
||||
? content
|
||||
: content.replace(/World/, 'New World');
|
||||
},
|
||||
syncDocs: () => 'updated',
|
||||
syncDocsDelay: 2000,
|
||||
})}
|
||||
|
||||
@@ -19,7 +19,7 @@ import { Progress } from '@backstage/core-components';
|
||||
import { useApi } from '@backstage/core-plugin-api';
|
||||
import { scmIntegrationsApiRef } from '@backstage/integration-react';
|
||||
import { BackstageTheme } from '@backstage/theme';
|
||||
import { CircularProgress, useTheme } from '@material-ui/core';
|
||||
import { Button, CircularProgress, useTheme } from '@material-ui/core';
|
||||
import { Alert } from '@material-ui/lab';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
@@ -50,12 +50,13 @@ export const Reader = ({ entityId, onReady }: Props) => {
|
||||
const { '*': path } = useParams();
|
||||
const theme = useTheme<BackstageTheme>();
|
||||
|
||||
const { state, content: rawPage, errorMessage, buildLog } = useReaderState(
|
||||
kind,
|
||||
namespace,
|
||||
name,
|
||||
path,
|
||||
);
|
||||
const {
|
||||
state,
|
||||
contentReload,
|
||||
content: rawPage,
|
||||
errorMessage,
|
||||
buildLog,
|
||||
} = useReaderState(kind, namespace, name, path);
|
||||
|
||||
const techdocsStorageApi = useApi(techdocsStorageApiRef);
|
||||
const [sidebars, setSidebars] = useState<HTMLElement[]>();
|
||||
@@ -338,7 +339,15 @@ export const Reader = ({ entityId, onReady }: Props) => {
|
||||
</Alert>
|
||||
)}
|
||||
{state === 'CONTENT_STALE_READY' && (
|
||||
<Alert variant="outlined" severity="success">
|
||||
<Alert
|
||||
variant="outlined"
|
||||
severity="success"
|
||||
action={
|
||||
<Button color="inherit" onClick={() => contentReload()}>
|
||||
Refresh
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
A newer version of this documentation is now available, please refresh
|
||||
to view.
|
||||
</Alert>
|
||||
|
||||
@@ -259,6 +259,7 @@ describe('useReaderState', () => {
|
||||
content: undefined,
|
||||
errorMessage: '',
|
||||
buildLog: [],
|
||||
contentReload: expect.any(Function),
|
||||
});
|
||||
|
||||
await waitForValueToChange(() => result.current.state);
|
||||
@@ -268,6 +269,7 @@ describe('useReaderState', () => {
|
||||
content: 'my content',
|
||||
errorMessage: '',
|
||||
buildLog: [],
|
||||
contentReload: expect.any(Function),
|
||||
});
|
||||
|
||||
expect(techdocsStorageApi.getEntityDocs).toBeCalledWith(
|
||||
@@ -312,6 +314,7 @@ describe('useReaderState', () => {
|
||||
content: undefined,
|
||||
errorMessage: '',
|
||||
buildLog: [],
|
||||
contentReload: expect.any(Function),
|
||||
});
|
||||
|
||||
await waitForValueToChange(() => result.current.state);
|
||||
@@ -321,6 +324,7 @@ describe('useReaderState', () => {
|
||||
content: undefined,
|
||||
errorMessage: ' Load error: NotFoundError: Page Not Found',
|
||||
buildLog: ['Line 1', 'Line 2'],
|
||||
contentReload: expect.any(Function),
|
||||
});
|
||||
|
||||
await waitForValueToChange(() => result.current.state);
|
||||
@@ -330,6 +334,7 @@ describe('useReaderState', () => {
|
||||
content: undefined,
|
||||
errorMessage: '',
|
||||
buildLog: [],
|
||||
contentReload: expect.any(Function),
|
||||
});
|
||||
|
||||
await waitForValueToChange(() => result.current.state);
|
||||
@@ -339,6 +344,7 @@ describe('useReaderState', () => {
|
||||
content: 'my content',
|
||||
errorMessage: '',
|
||||
buildLog: [],
|
||||
contentReload: expect.any(Function),
|
||||
});
|
||||
|
||||
expect(techdocsStorageApi.getEntityDocs).toBeCalledTimes(2);
|
||||
@@ -359,7 +365,12 @@ describe('useReaderState', () => {
|
||||
});
|
||||
|
||||
it('should handle stale content', async () => {
|
||||
techdocsStorageApi.getEntityDocs.mockResolvedValue('my content');
|
||||
techdocsStorageApi.getEntityDocs
|
||||
.mockResolvedValueOnce('my content')
|
||||
.mockImplementationOnce(async () => {
|
||||
await new Promise(resolve => setTimeout(resolve, 1100));
|
||||
return 'my new content';
|
||||
});
|
||||
techdocsStorageApi.syncEntityDocs.mockImplementation(
|
||||
async (_, logHandler) => {
|
||||
logHandler?.call(this, 'Line 1');
|
||||
@@ -380,6 +391,7 @@ describe('useReaderState', () => {
|
||||
content: undefined,
|
||||
errorMessage: '',
|
||||
buildLog: [],
|
||||
contentReload: expect.any(Function),
|
||||
});
|
||||
|
||||
// the content is returned but the sync is in progress
|
||||
@@ -389,6 +401,7 @@ describe('useReaderState', () => {
|
||||
content: 'my content',
|
||||
errorMessage: '',
|
||||
buildLog: ['Line 1', 'Line 2'],
|
||||
contentReload: expect.any(Function),
|
||||
});
|
||||
|
||||
// the sync takes longer than 1 seconds so the refreshing state starts
|
||||
@@ -398,17 +411,43 @@ describe('useReaderState', () => {
|
||||
content: 'my content',
|
||||
errorMessage: '',
|
||||
buildLog: ['Line 1', 'Line 2'],
|
||||
contentReload: expect.any(Function),
|
||||
});
|
||||
|
||||
// the content is up-to-date
|
||||
// the content is updated but not yet displayed
|
||||
await waitForValueToChange(() => result.current.state);
|
||||
expect(result.current).toEqual({
|
||||
state: 'CONTENT_STALE_READY',
|
||||
content: 'my content',
|
||||
errorMessage: '',
|
||||
buildLog: ['Line 1', 'Line 2'],
|
||||
contentReload: expect.any(Function),
|
||||
});
|
||||
|
||||
// reload the content
|
||||
result.current.contentReload();
|
||||
|
||||
// the new content refresh is triggered
|
||||
await waitForValueToChange(() => result.current.state);
|
||||
expect(result.current).toEqual({
|
||||
state: 'CHECKING',
|
||||
content: undefined,
|
||||
errorMessage: '',
|
||||
buildLog: [],
|
||||
contentReload: expect.any(Function),
|
||||
});
|
||||
|
||||
// the new content is loaded
|
||||
await waitForValueToChange(() => result.current.state);
|
||||
expect(result.current).toEqual({
|
||||
state: 'CONTENT_FRESH',
|
||||
content: 'my new content',
|
||||
errorMessage: '',
|
||||
buildLog: [],
|
||||
contentReload: expect.any(Function),
|
||||
});
|
||||
|
||||
expect(techdocsStorageApi.getEntityDocs).toBeCalledTimes(2);
|
||||
expect(techdocsStorageApi.getEntityDocs).toBeCalledWith(
|
||||
{ kind: 'Component', namespace: 'default', name: 'backstage' },
|
||||
'/example',
|
||||
@@ -441,6 +480,7 @@ describe('useReaderState', () => {
|
||||
content: undefined,
|
||||
errorMessage: '',
|
||||
buildLog: [],
|
||||
contentReload: expect.any(Function),
|
||||
});
|
||||
|
||||
// the content loading threw an error
|
||||
@@ -450,6 +490,7 @@ describe('useReaderState', () => {
|
||||
content: undefined,
|
||||
errorMessage: ' Load error: NotFoundError: Some error description',
|
||||
buildLog: [],
|
||||
contentReload: expect.any(Function),
|
||||
});
|
||||
|
||||
expect(techdocsStorageApi.getEntityDocs).toBeCalledWith(
|
||||
|
||||
@@ -223,6 +223,7 @@ export function useReaderState(
|
||||
path: string,
|
||||
): {
|
||||
state: ContentStateTypes;
|
||||
contentReload: () => void;
|
||||
content?: string;
|
||||
errorMessage?: string;
|
||||
buildLog: string[];
|
||||
@@ -342,6 +343,7 @@ export function useReaderState(
|
||||
|
||||
return {
|
||||
state: displayState,
|
||||
contentReload,
|
||||
content: state.content,
|
||||
errorMessage,
|
||||
buildLog: state.buildLog,
|
||||
|
||||
Reference in New Issue
Block a user