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:
Dominik Henneke
2021-07-05 14:07:44 +02:00
parent 3af126cddf
commit 136a919748
5 changed files with 73 additions and 10 deletions
+6
View File
@@ -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.
+5
View File
@@ -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,