React UI: Add Starting Screen to Individual Pages (#8909)

* Fix/removed forwarding

Signed-off-by: Levi Harrison <git@leviharrison.dev>

* Added global 'wasReady' and 'wasUnexpected'

Signed-off-by: Levi Harrison <git@leviharrison.dev>

* Eslint fixes

Signed-off-by: Levi Harrison <git@leviharrison.dev>

* Added withStartingIndicator wrapper

Signed-off-by: Levi Harrison <git@leviharrison.dev>

* Fixed condition

Signed-off-by: Levi Harrison <git@leviharrison.dev>

* Removed unused import

Signed-off-by: Levi Harrison <git@leviharrison.dev>

* Moved withStartingIndicator calls to pages index

Signed-off-by: Levi Harrison <git@leviharrison.dev>

* Fixed withStartingIndicator tests

Signed-off-by: Levi Harrison <git@leviharrison.dev>

* Fixed eslint (maybe?)

Signed-off-by: Levi Harrison <git@leviharrison.dev>

* Trailing comma

Signed-off-by: Levi Harrison <git@leviharrison.dev>

* Added prettier ignore

Signed-off-by: Levi Harrison <git@leviharrison.dev>

* Fix eslint (pt. 2)

Signed-off-by: Levi Harrison <git@leviharrison.dev>
pull/8946/head
Levi Harrison 2021-06-15 16:37:16 -04:00 committed by GitHub
parent ea3728bcac
commit eb8ca06885
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 127 additions and 95 deletions

View File

@ -4,7 +4,17 @@ import App from './App';
import Navigation from './Navbar';
import { Container } from 'reactstrap';
import { Router } from '@reach/router';
import { Alerts, Config, Flags, Rules, ServiceDiscovery, Status, Targets, TSDBStatus, PanelList } from './pages';
import {
AlertsPage,
ConfigPage,
FlagsPage,
RulesPage,
ServiceDiscoveryPage,
StatusPage,
TargetsPage,
TSDBStatusPage,
PanelListPage,
} from './pages';
describe('App', () => {
const app = shallow(<App />);
@ -13,7 +23,17 @@ describe('App', () => {
expect(app.find(Navigation)).toHaveLength(1);
});
it('routes', () => {
[Alerts, Config, Flags, Rules, ServiceDiscovery, Status, Targets, TSDBStatus, PanelList].forEach(component => {
[
AlertsPage,
ConfigPage,
FlagsPage,
RulesPage,
ServiceDiscoveryPage,
StatusPage,
TargetsPage,
TSDBStatusPage,
PanelListPage,
].forEach(component => {
const c = app.find(component);
expect(c).toHaveLength(1);
});

View File

@ -2,15 +2,23 @@ import React, { FC } from 'react';
import Navigation from './Navbar';
import { Container } from 'reactstrap';
import { Router, Redirect, navigate } from '@reach/router';
import { Router, Redirect } from '@reach/router';
import useMedia from 'use-media';
import { Alerts, Config, Flags, Rules, ServiceDiscovery, Status, Targets, TSDBStatus, PanelList, Starting } from './pages';
import {
AlertsPage,
ConfigPage,
FlagsPage,
RulesPage,
ServiceDiscoveryPage,
StatusPage,
TargetsPage,
TSDBStatusPage,
PanelListPage,
} from './pages';
import { PathPrefixContext } from './contexts/PathPrefixContext';
import { ThemeContext, themeName, themeSetting } from './contexts/ThemeContext';
import { Theme, themeLocalStorageKey } from './Theme';
import { useLocalStorage } from './hooks/useLocalStorage';
import { useFetchReady } from './hooks/useFetch';
import { usePathPrefix } from './contexts/PathPrefixContext';
interface AppProps {
consolesLink: string | null;
@ -31,7 +39,6 @@ const App: FC<AppProps> = ({ consolesLink }) => {
'/rules',
'/targets',
'/service-discovery',
'/starting',
];
if (basePath.endsWith('/')) {
basePath = basePath.slice(0, -1);
@ -45,14 +52,6 @@ const App: FC<AppProps> = ({ consolesLink }) => {
}
}
const pathPrefix = usePathPrefix();
const { ready, isLoading, isUnexpected } = useFetchReady(pathPrefix);
if (basePath !== '/starting') {
if (!ready && !isLoading && !isUnexpected) {
navigate('/starting');
}
}
const [userTheme, setUserTheme] = useLocalStorage<themeSetting>(themeLocalStorageKey, 'auto');
const browserHasThemes = useMedia('(prefers-color-scheme)');
const browserWantsDarkTheme = useMedia('(prefers-color-scheme: dark)');
@ -78,16 +77,15 @@ const App: FC<AppProps> = ({ consolesLink }) => {
NOTE: Any route added here needs to also be added to the list of
React-handled router paths ("reactRouterPaths") in /web/web.go.
*/}
<PanelList path="/graph" />
<Alerts path="/alerts" />
<Config path="/config" />
<Flags path="/flags" />
<Rules path="/rules" />
<ServiceDiscovery path="/service-discovery" />
<Status path="/status" />
<TSDBStatus path="/tsdb-status" />
<Targets path="/targets" />
<Starting path="/starting" />
<PanelListPage path="/graph" />
<AlertsPage path="/alerts" />
<ConfigPage path="/config" />
<FlagsPage path="/flags" />
<RulesPage path="/rules" />
<ServiceDiscoveryPage path="/service-discovery" />
<StatusPage path="/status" />
<TSDBStatusPage path="/tsdb-status" />
<TargetsPage path="/targets" />
</Router>
</Container>
</PathPrefixContext.Provider>

View File

@ -1,7 +1,7 @@
import * as React from 'react';
import { shallow } from 'enzyme';
import { WALReplayData } from '../../types/types';
import { StartingContent } from './Starting';
import { WALReplayData } from '../types/types';
import { StartingContent } from './withStartingIndicator';
import { Progress } from 'reactstrap';
describe('Starting', () => {

View File

@ -1,10 +1,9 @@
import React, { FC, useEffect } from 'react';
import { RouteComponentProps, navigate } from '@reach/router';
import React, { FC, ComponentType } from 'react';
import { Progress, Alert } from 'reactstrap';
import { useFetchReadyInterval } from '../../hooks/useFetch';
import { WALReplayData } from '../../types/types';
import { usePathPrefix } from '../../contexts/PathPrefixContext';
import { useFetchReadyInterval } from '../hooks/useFetch';
import { WALReplayData } from '../types/types';
import { usePathPrefix } from '../contexts/PathPrefixContext';
interface StartingContentProps {
isUnexpected: boolean;
@ -44,17 +43,13 @@ export const StartingContent: FC<StartingContentProps> = ({ status, isUnexpected
);
};
const Starting: FC<RouteComponentProps> = () => {
export const withStartingIndicator = <T extends {}>(Page: ComponentType<T>): FC<T> => ({ ...rest }) => {
const pathPrefix = usePathPrefix();
const { ready, walReplayStatus, isUnexpected } = useFetchReadyInterval(pathPrefix);
useEffect(() => {
if (ready) {
navigate('/');
}
}, [ready]);
if (ready || isUnexpected) {
return <Page {...(rest as T)} />;
}
return <StartingContent isUnexpected={isUnexpected} status={walReplayStatus.data} />;
};
export default Starting;

View File

@ -47,36 +47,7 @@ export const useFetch = <T extends {}>(url: string, options?: RequestInit): Fetc
return { response, error, isLoading };
};
export const useFetchReady = (pathPrefix: string, options?: RequestInit): FetchStateReady => {
const [ready, setReady] = useState<boolean>(false);
const [isUnexpected, setIsUnexpected] = useState<boolean>(false);
const [isLoading, setIsLoading] = useState<boolean>(true);
useEffect(() => {
const fetchData = async () => {
setIsLoading(true);
try {
const res = await fetch(`${pathPrefix}/-/ready`, { cache: 'no-store', credentials: 'same-origin', ...options });
if (res.status === 200) {
setReady(true);
}
// The server sends back a 503 if it isn't ready,
// if we get back anything else that means something has gone wrong.
if (res.status !== 503) {
setIsUnexpected(true);
} else {
setIsUnexpected(false);
}
setIsLoading(false);
} catch (error) {
setIsUnexpected(true);
}
};
fetchData();
}, [pathPrefix, options]);
return { ready, isUnexpected, isLoading };
};
let wasReady = false;
// This is used on the starting page to periodically check if the server is ready yet,
// and check the status of the WAL replay.
@ -86,33 +57,60 @@ export const useFetchReadyInterval = (pathPrefix: string, options?: RequestInit)
const [walReplayStatus, setWALReplayStatus] = useState<WALReplayStatus>({} as any);
useEffect(() => {
const interval = setInterval(async () => {
try {
let res = await fetch(`${pathPrefix}/-/ready`, { cache: 'no-store', credentials: 'same-origin', ...options });
if (res.status === 200) {
setReady(true);
if (wasReady) {
setReady(true);
} else {
// This helps avoid a memory leak.
let mounted = true;
const fetchStatus = async () => {
try {
let res = await fetch(`${pathPrefix}/-/ready`, { cache: 'no-store', credentials: 'same-origin', ...options });
if (res.status === 200) {
if (mounted) {
setReady(true);
}
wasReady = true;
clearInterval(interval);
} else if (res.status !== 503) {
if (mounted) {
setIsUnexpected(true);
}
clearInterval(interval);
return;
} else {
if (mounted) {
setIsUnexpected(false);
}
res = await fetch(`${pathPrefix}/${API_PATH}/status/walreplay`, {
cache: 'no-store',
credentials: 'same-origin',
});
if (res.ok) {
const data = (await res.json()) as WALReplayStatus;
if (mounted) {
setWALReplayStatus(data);
}
}
}
} catch (error) {
if (mounted) {
setIsUnexpected(true);
}
clearInterval(interval);
return;
}
if (res.status !== 503) {
setIsUnexpected(true);
setWALReplayStatus({ data: { last: 0, first: 0 } } as any);
} else {
setIsUnexpected(false);
};
res = await fetch(`${pathPrefix}/${API_PATH}/status/walreplay`, { cache: 'no-store', credentials: 'same-origin' });
if (res.ok) {
const data = (await res.json()) as WALReplayStatus;
setWALReplayStatus(data);
}
}
} catch (error) {
setIsUnexpected(true);
setWALReplayStatus({ data: { last: 0, first: 0 } } as any);
}
}, 1000);
return () => clearInterval(interval);
fetchStatus();
const interval = setInterval(fetchStatus, 1000);
return () => {
clearInterval(interval);
mounted = false;
};
}
}, [pathPrefix, options]);
return { ready, isUnexpected, walReplayStatus };
};

View File

@ -7,6 +7,27 @@ import Status from './status/Status';
import Targets from './targets/Targets';
import PanelList from './graph/PanelList';
import TSDBStatus from './tsdbStatus/TSDBStatus';
import Starting from './starting/Starting';
import { withStartingIndicator } from '../components/withStartingIndicator';
export { Alerts, Config, Flags, Rules, ServiceDiscovery, Status, Targets, TSDBStatus, PanelList, Starting };
const AlertsPage = withStartingIndicator(Alerts);
const ConfigPage = withStartingIndicator(Config);
const FlagsPage = withStartingIndicator(Flags);
const RulesPage = withStartingIndicator(Rules);
const ServiceDiscoveryPage = withStartingIndicator(ServiceDiscovery);
const StatusPage = withStartingIndicator(Status);
const TSDBStatusPage = withStartingIndicator(TSDBStatus);
const TargetsPage = withStartingIndicator(Targets);
const PanelListPage = withStartingIndicator(PanelList);
// prettier-ignore
export {
AlertsPage,
ConfigPage,
FlagsPage,
RulesPage,
ServiceDiscoveryPage,
StatusPage,
TSDBStatusPage,
TargetsPage,
PanelListPage
};