Skip to content

Commit

Permalink
chore: preparations for "Only Mine" feature support (flyteorg#411)
Browse files Browse the repository at this point in the history
The main UI is added and execution's filter is connected.
All changes leave under OnlyMine featureFlag
Signed-off-by: Eugene Jahn <eugenejahnjahn@gmail.com>
  • Loading branch information
eugenejahn committed Apr 28, 2022
1 parent 16c0721 commit 0590d1c
Show file tree
Hide file tree
Showing 19 changed files with 455 additions and 51 deletions.
19 changes: 14 additions & 5 deletions .storybook/StorybookContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { SnackbarProvider } from 'notistack';
import { ErrorBoundary } from '../src/components/common/ErrorBoundary';
import { createQueryClient } from '../src/components/data/queryCache';
import { muiTheme } from '../src/components/Theme/muiTheme';
import { LocalCacheProvider } from '../src/basics/LocalCache/ContextProvider';
import { FeatureFlagsProvider } from '../src/basics/FeatureFlags';

const useStyles = makeStyles((theme: Theme) => ({
container: {
Expand All @@ -24,11 +26,18 @@ export const StorybookContainer: React.FC = ({ children }) => {
<CssBaseline />
<ErrorBoundary>
<MemoryRouter>
<SnackbarProvider maxSnack={2} anchorOrigin={{ vertical: 'top', horizontal: 'right' }}>
<QueryClientProvider client={queryClient}>
<div className={useStyles().container}>{children}</div>
</QueryClientProvider>
</SnackbarProvider>
<FeatureFlagsProvider>
<LocalCacheProvider>
<SnackbarProvider
maxSnack={2}
anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
>
<QueryClientProvider client={queryClient}>
<div className={useStyles().container}>{children}</div>
</QueryClientProvider>
</SnackbarProvider>
</LocalCacheProvider>
</FeatureFlagsProvider>
</MemoryRouter>
</ErrorBoundary>
</ThemeProvider>
Expand Down
5 changes: 5 additions & 0 deletions src/basics/FeatureFlags/defaultConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ export enum FeatureFlag {

// Production flags
LaunchPlan = 'launch-plan',

// Test Only Mine flag
OnlyMine = 'only-mine',
}

export type FeatureFlagConfig = { [k: string]: boolean };
Expand All @@ -20,6 +23,8 @@ export const defaultFlagConfig: FeatureFlagConfig = {
// Production - new code should be turned off by default
// If you need to turn it on locally -> update runtimeConfig in ./index.tsx file
'launch-plan': false,

'only-mine': false,
};

export interface AdminVersion {
Expand Down
67 changes: 67 additions & 0 deletions src/basics/LocalCache/ContextProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// More info on Local storage: https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage
import { log } from 'common/log';
import * as React from 'react';
import { useState, createContext, useCallback } from 'react';
import { defaultLocalCacheConfig, LocalCacheItem } from './defaultConfig';

export { LocalCacheItem } from './defaultConfig';

interface LocalCacheState {
localCacheMap: Record<string, any>;
setLocalCache: (localCacheItem: LocalCacheItem, newValue: any) => void;
getLocalCache: (localCacheItem: LocalCacheItem) => any;
}

interface LocalCacheProviderProps {
children?: React.ReactNode;
}

// get the local storage value, if local storage is null, use default value
function allStorage() {
const defaultKeys = Object.keys(defaultLocalCacheConfig);

const localCacheMap: Record<string, any> = {};
let i = defaultKeys.length;
while (i--) {
const key = defaultKeys[i];
const value = localStorage.getItem(key) || defaultLocalCacheConfig[key];
localCacheMap[defaultKeys[i]] = JSON.parse(value);
}
return localCacheMap;
}

export const LocalCacheContext = createContext<LocalCacheState>({
localCacheMap: { ...allStorage() },
setLocalCache: () => {
/* Provider is not initialized */
},
getLocalCache: () => false,
});

export const LocalCacheProvider = (props: LocalCacheProviderProps) => {
const [localCacheMap, setLocalCacheMap] = useState({ ...allStorage() });

const setLocalCache = useCallback((localCacheItem: LocalCacheItem, newValue: any) => {
setLocalCacheMap((prev) => ({ ...prev, [localCacheItem]: newValue }));
}, []);

const getLocalCache = useCallback(
(localCacheItem: LocalCacheItem) => {
const localCache = localCacheMap[localCacheItem];
if (localCache == null) {
log.error(
`ERROR: LocalCacheItem ${localCacheItem} doesn't have default value provided in defaultLocalCacheConfig`,
);
return null;
}
return localCache;
},
[localCacheMap],
);

return (
<LocalCacheContext.Provider value={{ localCacheMap, setLocalCache, getLocalCache }}>
{props.children}
</LocalCacheContext.Provider>
);
};
10 changes: 10 additions & 0 deletions src/basics/LocalCache/defaultConfig.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { defaultSelectedValues } from './onlyMineDefaultConfig';

export enum LocalCacheItem {
// Test flag is created only for unit-tests
TestUndefined = 'test-undefined',
Expand All @@ -7,6 +9,10 @@ export enum LocalCacheItem {
// Production flags
ShowWorkflowVersions = 'flyte.show-workflow-versions',
ShowDomainSettings = 'flyte.show-domain-settings',

// Test Only Mine
OnlyMineToggle = 'flyte.only-mine-toggle',
OnlyMineSetting = 'flyte.only-mine-setting',
}

type LocalCacheConfig = { [k: string]: string };
Expand All @@ -23,4 +29,8 @@ export const defaultLocalCacheConfig: LocalCacheConfig = {
// Production
'flyte.show-workflow-versions': 'true',
'flyte.show-domain-settings': 'true',

// Test Only Mine
'flyte.only-mine-toggle': 'true',
'flyte.only-mine-setting': JSON.stringify(defaultSelectedValues),
};
18 changes: 6 additions & 12 deletions src/basics/LocalCache/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// More info on Local storage: https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage
import { log } from 'common/log';
import { useState } from 'react';
import { useContext } from 'react';
import { defaultLocalCacheConfig, LocalCacheItem } from './defaultConfig';
import { LocalCacheContext } from './ContextProvider';

export { LocalCacheItem } from './defaultConfig';

Expand All @@ -21,25 +22,18 @@ const getDefault = (setting: LocalCacheItem) => {
};

export function useLocalCache<T>(setting: LocalCacheItem) {
const defaultValue = getDefault(setting);
const [value, setValue] = useState<T>(() => {
const data = localStorage.getItem(setting);
const value = data ? JSON.parse(data) : defaultValue;
if (typeof value === typeof defaultValue) {
return value;
}
const localCache = useContext(LocalCacheContext);

return defaultValue;
});
const value = localCache.getLocalCache(setting);

const setLocalCache = (newValue: T) => {
localCache.setLocalCache(setting, newValue);
localStorage.setItem(setting, JSON.stringify(newValue));
setValue(newValue);
};

const clearState = () => {
localCache.setLocalCache(setting, JSON.parse(defaultLocalCacheConfig[setting]));
localStorage.removeItem(setting);
setValue(defaultValue);
};

return [value, setLocalCache, clearState];
Expand Down
13 changes: 11 additions & 2 deletions src/basics/LocalCache/localCache.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as React from 'react';
import { render, screen, act, fireEvent, getByTestId } from '@testing-library/react';
import { ClearLocalCache, LocalCacheItem, useLocalCache, onlyForTesting } from '.';
import { LocalCacheProvider } from './ContextProvider';

const SHOW_TEXT = 'SHOWED';
const HIDDEN_TEXT = 'HIDDEN';
Expand All @@ -10,7 +11,6 @@ const TestFrame = () => {

const toShow = () => setShow(true);
const toClear = () => clearShow();

return (
<div>
<div>{show ? SHOW_TEXT : HIDDEN_TEXT}</div>
Expand All @@ -20,6 +20,14 @@ const TestFrame = () => {
);
};

const TestPage = () => {
return (
<LocalCacheProvider>
<TestFrame />
</LocalCacheProvider>
);
};

describe('LocalCache', () => {
beforeAll(() => {
ClearLocalCache();
Expand All @@ -30,7 +38,8 @@ describe('LocalCache', () => {
});

it('Can be used by component as expected', () => {
const { container } = render(<TestFrame />);
const { container } = render(<TestPage />);

const show = getByTestId(container, 'show');
const clear = getByTestId(container, 'clear');

Expand Down
46 changes: 46 additions & 0 deletions src/basics/LocalCache/onlyMineDefaultConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
export enum OnlyMyFilter {
SelectAll = 'selectAll',
OnlyMyWorkflow = 'onlyMyWorkflow',
OnlyMyExecutions = 'onlyMyExecutions',
OnlyMyTasks = 'onlyMyTasks',
OnlyMyWorkflowVersions = 'onlyMyWorkflowVersions',
OnlyMyLaunchForm = 'onlyMyLaunchForm',
}

export const filterByDefault = [
{
value: 'selectAll',
label: 'Select All',
data: true,
},
{
value: 'onlyMyWorkflow',
label: 'Only My Workflow',
data: true,
},
{
value: 'onlyMyExecutions',
label: 'Only My Executions',
data: true,
},
{
value: 'onlyMyTasks',
label: 'Only My Tasks',
data: true,
},
{
value: 'onlyMyWorkflowVersions',
label: 'Only My Workflow Versions',
data: true,
},
{
value: 'onlyMyLaunchForm',
label: 'Only My Launch Form',
data: true,
},
];

export const defaultSelectedValues = Object.assign(
{},
...filterByDefault.map((x) => ({ [x.value]: x.data })),
);
53 changes: 28 additions & 25 deletions src/components/App/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { Router } from 'react-router-dom';
import { ApplicationRouter } from 'routes/ApplicationRouter';
import { history } from 'routes/history';
import { NavBarRouter } from 'routes/NavBarRouter';
import { LocalCacheProvider } from 'basics/LocalCache/ContextProvider';

const queryClient = createQueryClient();

Expand All @@ -31,31 +32,33 @@ export const AppComponent: React.FC = () => {

return (
<FeatureFlagsProvider>
<ThemeProvider theme={muiTheme}>
<SnackbarProvider
// Notifications provider https://iamhosseindhv.com/notistack/demos
maxSnack={2}
anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
TransitionComponent={Collapse}
>
<QueryClientProvider client={queryClient}>
<APIContext.Provider value={apiState}>
<QueryAuthorizationObserver />
<SkeletonTheme color={skeletonColor} highlightColor={skeletonHighlightColor}>
<CssBaseline />
<Router history={history}>
<ErrorBoundary fixed={true}>
<NavBarRouter />
<ApplicationRouter />
</ErrorBoundary>
</Router>
<SystemStatusBanner />
</SkeletonTheme>
</APIContext.Provider>
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
</SnackbarProvider>
</ThemeProvider>
<LocalCacheProvider>
<ThemeProvider theme={muiTheme}>
<SnackbarProvider
// Notifications provider https://iamhosseindhv.com/notistack/demos
maxSnack={2}
anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
TransitionComponent={Collapse}
>
<QueryClientProvider client={queryClient}>
<APIContext.Provider value={apiState}>
<QueryAuthorizationObserver />
<SkeletonTheme color={skeletonColor} highlightColor={skeletonHighlightColor}>
<CssBaseline />
<Router history={history}>
<ErrorBoundary fixed={true}>
<NavBarRouter />
<ApplicationRouter />
</ErrorBoundary>
</Router>
<SystemStatusBanner />
</SkeletonTheme>
</APIContext.Provider>
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
</SnackbarProvider>
</ThemeProvider>
</LocalCacheProvider>
</FeatureFlagsProvider>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { useState } from 'react';
import { useState, useEffect } from 'react';
import { FilterOperation, FilterOperationName } from 'models/AdminEntity/types';
import { useUserProfile } from 'components/hooks/useUserProfile';
import { useOnlyMineSelectedValue } from 'components/hooks/useOnlyMineSelectedValue';
import { OnlyMyFilter } from 'basics/LocalCache/onlyMineDefaultConfig';
import { FeatureFlag, useFeatureFlag } from 'basics/FeatureFlags';

interface OnlyMyExecutionsFilterState {
onlyMyExecutionsValue: boolean;
Expand All @@ -23,8 +26,10 @@ export function useOnlyMyExecutionsFilterState({
}: OnlyMyExecutionsFilterStateProps): OnlyMyExecutionsFilterState {
const profile = useUserProfile();
const userId = profile.value?.subject ? profile.value.subject : '';
const isFlagEnabled = useFeatureFlag(FeatureFlag.OnlyMine);
const onlyMineExecutionsSelectedValue = useOnlyMineSelectedValue(OnlyMyFilter.OnlyMyExecutions);
const [onlyMyExecutionsValue, setOnlyMyExecutionsValue] = useState<boolean>(
initialValue ?? false,
isFlagEnabled ? onlyMineExecutionsSelectedValue : initialValue ?? false, // if flag is enable let's use the value from only mine
);

const getFilter = (): FilterOperation | null => {
Expand All @@ -39,6 +44,13 @@ export function useOnlyMyExecutionsFilterState({
};
};

// update the state value when state value in olny mine change
useEffect(() => {
if (isFlagEnabled) {
setOnlyMyExecutionsValue(onlyMineExecutionsSelectedValue);
}
}, [onlyMineExecutionsSelectedValue]);

return {
onlyMyExecutionsValue,
isFilterDisabled: isFilterDisabled ?? false,
Expand Down
Loading

0 comments on commit 0590d1c

Please sign in to comment.