Skip to content

Commit

Permalink
chore: Split determined info from Store [WEB-584] (#5541)
Browse files Browse the repository at this point in the history
  • Loading branch information
gt2345 authored Dec 7, 2022
1 parent b18d7dd commit c32ac36
Show file tree
Hide file tree
Showing 16 changed files with 166 additions and 93 deletions.
68 changes: 38 additions & 30 deletions webui/react/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import PageMessage from 'components/PageMessage';
import Router from 'components/Router';
import StoreProvider, { StoreAction, useStore, useStoreDispatch } from 'contexts/Store';
import useAuthCheck from 'hooks/useAuthCheck';
import { useFetchInfo } from 'hooks/useFetch';
import { useFetchUsers } from 'hooks/useFetch';
import useKeyTracker, { KeyCode, keyEmitter, KeyEvent } from 'hooks/useKeyTracker';
import usePageVisibility from 'hooks/usePageVisibility';
Expand All @@ -25,21 +24,30 @@ import { paths, serverAddress } from 'routes/utils';
import Spinner from 'shared/components/Spinner/Spinner';
import usePolling from 'shared/hooks/usePolling';
import { StoreContext } from 'stores';
import { initInfo, useDeterminedInfo, useEnsureInfoFetched } from 'stores/determinedInfo';
import { correctViewportHeight, refreshPage } from 'utils/browser';
import { Loadable } from 'utils/loadable';

import css from './App.module.scss';

const AppView: React.FC = () => {
const resize = useResize();
const storeDispatch = useStoreDispatch();
const { auth, info, ui } = useStore();
const { auth, ui } = useStore();
const infoLoadable = useDeterminedInfo();
const info = Loadable.getOrElse(initInfo, infoLoadable);
const [canceler] = useState(new AbortController());
const { updateTelemetry } = useTelemetry();
const checkAuth = useAuthCheck(canceler);

const isServerReachable = useMemo(() => !!info.clusterId, [info.clusterId]);
const isServerReachable = useMemo(() => {
return Loadable.match(infoLoadable, {
Loaded: (info) => !!info.clusterId,
NotLoaded: () => undefined,
});
}, [infoLoadable]);

const fetchInfo = useFetchInfo(canceler);
const fetchInfo = useEnsureInfoFetched(canceler);
const fetchUsers = useFetchUsers(canceler);

useEffect(() => {
Expand All @@ -65,6 +73,7 @@ const AppView: React.FC = () => {
* Check to make sure the WebUI version matches the platform version.
* Skip this check for development version.
*/

if (!process.env.IS_DEV && info.version !== process.env.VERSION) {
const btn = (
<Button type="primary" onClick={refreshPage}>
Expand Down Expand Up @@ -126,32 +135,31 @@ const AppView: React.FC = () => {
// Correct the viewport height size when window resize occurs.
useLayoutEffect(() => correctViewportHeight(), [resize]);

if (!info.checked) {
return <Spinner center />;
}

return (
<div className={css.base}>
{isServerReachable ? (
<SettingsProvider>
<Navigation>
<main>
<Router routes={appRoutes} />
</main>
</Navigation>
</SettingsProvider>
) : (
<PageMessage title="Server is Unreachable">
<p>
Unable to communicate with the server at &quot;{serverAddress()}&quot;. Please check the
firewall and cluster settings.
</p>
<Button onClick={refreshPage}>Try Again</Button>
</PageMessage>
)}
<Omnibar />
</div>
);
return Loadable.match(infoLoadable, {
Loaded: () => (
<div className={css.base}>
{isServerReachable ? (
<SettingsProvider>
<Navigation>
<main>
<Router routes={appRoutes} />
</main>
</Navigation>
</SettingsProvider>
) : (
<PageMessage title="Server is Unreachable">
<p>
Unable to communicate with the server at &quot;{serverAddress()}&quot;. Please check
the firewall and cluster settings.
</p>
<Button onClick={refreshPage}>Try Again</Button>
</PageMessage>
)}
<Omnibar />
</div>
),
NotLoaded: () => <Spinner center />,
});
};

const App: React.FC = () => {
Expand Down
4 changes: 2 additions & 2 deletions webui/react/src/components/Navigation.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import React, { useEffect, useState } from 'react';

import { useStore } from 'contexts/Store';
import useFeature from 'hooks/useFeature';
import { useFetchMyRoles, useFetchPinnedWorkspaces } from 'hooks/useFetch';
import Spinner from 'shared/components/Spinner/Spinner';
import useUI from 'shared/contexts/stores/UI';
import usePolling from 'shared/hooks/usePolling';
import { useClusterOverview, useFetchAgents } from 'stores/agents';
import { initInfo, useDeterminedInfo } from 'stores/determinedInfo';
import { useFetchResourcePools } from 'stores/resourcePools';
import { BrandingType, ResourceType } from 'types';
import { updateFaviconType } from 'utils/browser';
Expand All @@ -22,7 +22,7 @@ interface Props {

const Navigation: React.FC<Props> = ({ children }) => {
const { ui } = useUI();
const { info } = useStore();
const info = Loadable.getOrElse(initInfo, useDeterminedInfo());
const [canceler] = useState(new AbortController());
const overview = useClusterOverview();

Expand Down
4 changes: 3 additions & 1 deletion webui/react/src/components/NavigationSideBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { paths } from 'routes/utils';
import Icon from 'shared/components/Icon/Icon';
import useUI from 'shared/contexts/stores/UI';
import { useAgents, useClusterOverview } from 'stores/agents';
import { initInfo, useDeterminedInfo } from 'stores/determinedInfo';
import { useResourcePools } from 'stores/resourcePools';
import { BrandingType } from 'types';
import { Loadable } from 'utils/loadable';
Expand Down Expand Up @@ -111,9 +112,10 @@ const NavigationSideBar: React.FC = () => {
// `nodeRef` padding is required for CSSTransition to work with React.StrictMode.
const nodeRef = useRef(null);

const { auth, info, pinnedWorkspaces } = useStore();
const { auth, pinnedWorkspaces } = useStore();
const loadableResourcePools = useResourcePools();
const resourcePools = Loadable.getOrElse([], loadableResourcePools); // TODO show spinner when this is loading
const info = Loadable.getOrElse(initInfo, useDeterminedInfo());
const { ui } = useUI();
const agents = useAgents();
const overview = useClusterOverview();
Expand Down
4 changes: 3 additions & 1 deletion webui/react/src/components/NavigationTabbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import Icon from 'shared/components/Icon/Icon';
import useUI from 'shared/contexts/stores/UI';
import { AnyMouseEvent, routeToReactUrl } from 'shared/utils/routes';
import { useAgents, useClusterOverview } from 'stores/agents';
import { initInfo, useDeterminedInfo } from 'stores/determinedInfo';
import { useResourcePools } from 'stores/resourcePools';
import { BrandingType } from 'types';
import { Loadable } from 'utils/loadable';
Expand Down Expand Up @@ -44,9 +45,10 @@ const ToolbarItem: React.FC<ToolbarItemProps> = ({ path, status, ...props }: Too
};

const NavigationTabbar: React.FC = () => {
const { auth, info, pinnedWorkspaces } = useStore();
const { auth, pinnedWorkspaces } = useStore();
const loadableResourcePools = useResourcePools();
const resourcePools = Loadable.getOrElse([], loadableResourcePools); // TODO show spinner when this is loading
const info = Loadable.getOrElse(initInfo, useDeterminedInfo());
const { ui } = useUI();
const overview = useClusterOverview();
const agents = useAgents();
Expand Down
5 changes: 3 additions & 2 deletions webui/react/src/components/Page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import React from 'react';
import { Helmet } from 'react-helmet-async';

import PageHeader from 'components/PageHeader';
import { useStore } from 'contexts/Store';
import BasePage, { Props as BasePageProps } from 'shared/components/Page';
import { initInfo, useDeterminedInfo } from 'stores/determinedInfo';
import { BrandingType } from 'types';
import { Loadable } from 'utils/loadable';

export interface Props extends Omit<BasePageProps, 'pageHeader'> {
docTitle?: string;
Expand All @@ -22,7 +23,7 @@ const getFullDocTitle = (branding: string, title?: string, clusterName?: string)
};

const Page: React.FC<Props> = (props: Props) => {
const { info } = useStore();
const info = Loadable.getOrElse(initInfo, useDeterminedInfo());
const branding = info.branding || BrandingType.Determined;
const brandingPath = `${process.env.PUBLIC_URL}/${branding}`;

Expand Down
5 changes: 3 additions & 2 deletions webui/react/src/components/PageMessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import React from 'react';

import Logo, { Orientation } from 'components/Logo';
import Page from 'components/Page';
import { useStore } from 'contexts/Store';
import { initInfo, useDeterminedInfo } from 'stores/determinedInfo';
import { BrandingType } from 'types';
import { Loadable } from 'utils/loadable';

import css from './PageMessage.module.scss';

Expand All @@ -13,7 +14,7 @@ interface Props {
}

const PageMessage: React.FC<Props> = ({ title, children }: Props) => {
const { info } = useStore();
const info = Loadable.getOrElse(initInfo, useDeterminedInfo());
return (
<Page docTitle={title}>
<div className={css.base}>
Expand Down
27 changes: 1 addition & 26 deletions webui/react/src/contexts/Store.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { StoreProvider as UIStoreProvider } from 'shared/contexts/stores/UI';
import { clone, isEqual } from 'shared/utils/data';
import rootLogger from 'shared/utils/Logger';
import { checkDeepEquality } from 'shared/utils/store';
import { Auth, DetailedUser, DeterminedInfo, UserAssignment, UserRole, Workspace } from 'types';
import { Auth, DetailedUser, UserAssignment, UserRole, Workspace } from 'types';
import { getCookie, setCookie } from 'utils/browser';

const logger = rootLogger.extend('store');
Expand All @@ -21,7 +21,6 @@ interface OmnibarState {
interface State {
auth: Auth & { checked: boolean };

info: DeterminedInfo;
knownRoles: UserRole[];
pinnedWorkspaces: Workspace[];

Expand Down Expand Up @@ -49,11 +48,6 @@ export const StoreAction = {

SetCurrentUser: 'SetCurrentUser',

// Info
SetInfo: 'SetInfo',

SetInfoCheck: 'SetInfoCheck',

// User assignments, roles, and derived permissions
SetKnownRoles: 'SetKnownRoles',

Expand All @@ -77,8 +71,6 @@ type Action =
| { type: typeof StoreAction.ResetAuthCheck }
| { type: typeof StoreAction.SetAuth; value: Auth }
| { type: typeof StoreAction.SetAuthCheck }
| { type: typeof StoreAction.SetInfo; value: DeterminedInfo }
| { type: typeof StoreAction.SetInfoCheck }
| { type: typeof StoreAction.SetUsers; value: DetailedUser[] }
| { type: typeof StoreAction.SetCurrentUser; value: DetailedUser }
| { type: typeof StoreAction.SetPinnedWorkspaces; value: Workspace[] }
Expand All @@ -94,21 +86,9 @@ const initAuth = {
checked: false,
isAuthenticated: false,
};
export const initInfo: DeterminedInfo = {
branding: undefined,
checked: false,
clusterId: '',
clusterName: '',
featureSwitches: [],
isTelemetryEnabled: false,
masterId: '',
rbacEnabled: false,
version: process.env.VERSION || '',
};

const initState: State = {
auth: initAuth,
info: initInfo,
knownRoles: [],
pinnedWorkspaces: [],
ui: { omnibar: { isShowing: false } }, // TODO move down a level
Expand Down Expand Up @@ -164,11 +144,6 @@ const reducer = (state: State, action: Action): State => {
case StoreAction.SetAuthCheck:
if (state.auth.checked) return state;
return { ...state, auth: { ...state.auth, checked: true } };
case StoreAction.SetInfo:
if (isEqual(state.info, action.value)) return state;
return { ...state, info: action.value };
case StoreAction.SetInfoCheck:
return { ...state, info: { ...state.info, checked: true } };
case StoreAction.SetUsers:
if (isEqual(state.users, action.value)) return state;
return { ...state, users: action.value };
Expand Down
6 changes: 4 additions & 2 deletions webui/react/src/hooks/useAuthCheck.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,20 @@ import queryString from 'query-string';
import { useCallback } from 'react';
import { useLocation } from 'react-router-dom';

import { AUTH_COOKIE_KEY, StoreAction, useStore, useStoreDispatch } from 'contexts/Store';
import { AUTH_COOKIE_KEY, StoreAction, useStoreDispatch } from 'contexts/Store';
import { globalStorage } from 'globalStorage';
import { routeAll } from 'routes/utils';
import { getCurrentUser } from 'services/api';
import { updateDetApi } from 'services/apiConfig';
import { ErrorType } from 'shared/utils/error';
import { isAborted, isAuthFailure } from 'shared/utils/service';
import { initInfo, useDeterminedInfo } from 'stores/determinedInfo';
import { getCookie } from 'utils/browser';
import handleError from 'utils/error';
import { Loadable } from 'utils/loadable';

const useAuthCheck = (canceler: AbortController): (() => void) => {
const { info } = useStore();
const info = Loadable.getOrElse(initInfo, useDeterminedInfo());
const location = useLocation();
const storeDispatch = useStoreDispatch();

Expand Down
5 changes: 3 additions & 2 deletions webui/react/src/hooks/useFeature.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import queryString from 'query-string';

import { useStore } from 'contexts/Store';
import { initInfo, useDeterminedInfo } from 'stores/determinedInfo';
import { DeterminedInfo } from 'types';
import { Loadable } from 'utils/loadable';

// Add new feature switches below using `|`
export type ValidFeature =
Expand All @@ -18,7 +19,7 @@ interface FeatureHook {
}

const useFeature = (): FeatureHook => {
const { info } = useStore();
const info = Loadable.getOrElse(initInfo, useDeterminedInfo());
return { isOn: (ValidFeature) => IsOn(ValidFeature, info) };
};

Expand Down
16 changes: 1 addition & 15 deletions webui/react/src/hooks/useFetch.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,9 @@
import { useCallback } from 'react';

import { StoreAction, useStoreDispatch } from 'contexts/Store';
import { getInfo, getPermissionsSummary, getUsers, getWorkspaces, listRoles } from 'services/api';
import { getPermissionsSummary, getUsers, getWorkspaces, listRoles } from 'services/api';
import handleError from 'utils/error';

export const useFetchInfo = (canceler: AbortController): (() => Promise<void>) => {
const storeDispatch = useStoreDispatch();

return useCallback(async (): Promise<void> => {
try {
const response = await getInfo({ signal: canceler.signal });
storeDispatch({ type: StoreAction.SetInfo, value: response });
} catch (e) {
storeDispatch({ type: StoreAction.SetInfoCheck });
handleError(e);
}
}, [canceler, storeDispatch]);
};

export const useFetchUsers = (canceler: AbortController): (() => Promise<void>) => {
const storeDispatch = useStoreDispatch();

Expand Down
5 changes: 3 additions & 2 deletions webui/react/src/hooks/useTheme.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { useCallback, useEffect, useLayoutEffect, useState } from 'react';

import { useStore } from 'contexts/Store';
import { useSettings } from 'hooks/useSettings';
import useUI from 'shared/contexts/stores/UI';
import { DarkLight, globalCssVars, Mode } from 'shared/themes';
import { RecordKey } from 'shared/types';
import { camelCaseToKebab } from 'shared/utils/string';
import { initInfo, useDeterminedInfo } from 'stores/determinedInfo';
import themes from 'themes';
import { Loadable } from 'utils/loadable';

import { config, Settings } from './useTheme.settings';

Expand Down Expand Up @@ -61,7 +62,7 @@ const updateAntDesignTheme = (path: string) => {
* and storybook Theme decorators and not individual components.
*/
export const useTheme = (): void => {
const { info } = useStore();
const info = Loadable.getOrElse(initInfo, useDeterminedInfo());
const { ui, actions: uiActions } = useUI();
const [systemMode, setSystemMode] = useState<Mode>(getSystemMode());
const [isSettingsReady, setIsSettingsReady] = useState(false);
Expand Down
5 changes: 4 additions & 1 deletion webui/react/src/pages/SignIn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ import usePolling from 'shared/hooks/usePolling';
import { RecordKey } from 'shared/types';
import { locationToPath, routeToReactUrl } from 'shared/utils/routes';
import { capitalize } from 'shared/utils/string';
import { initInfo, useDeterminedInfo } from 'stores/determinedInfo';
import { BrandingType } from 'types';
import { Loadable } from 'utils/loadable';

import css from './SignIn.module.scss';

Expand All @@ -39,7 +41,8 @@ const logoConfig: Record<RecordKey, string> = {
const SignIn: React.FC = () => {
const { actions: uiActions } = useUI();
const location = useLocation();
const { auth, info } = useStore();
const { auth } = useStore();
const info = Loadable.getOrElse(initInfo, useDeterminedInfo());
const [canceler] = useState(new AbortController());
const rbacEnabled = useFeature().isOn('rbac');

Expand Down
Loading

0 comments on commit c32ac36

Please sign in to comment.