Skip to content

Commit

Permalink
Add small toggles to Asset Details graphs, consolidate localStorage-b…
Browse files Browse the repository at this point in the history
…acked state (#6762)
  • Loading branch information
bengotow committed Feb 24, 2022
1 parent 5f3966f commit 7495fea
Show file tree
Hide file tree
Showing 19 changed files with 174 additions and 132 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import * as React from 'react';

import {getJSONForKey} from '../hooks/useStateWithStorage';

// Internal LocalStorage data format and mutation helpers

export interface IStorageData {
Expand Down Expand Up @@ -114,18 +116,6 @@ function getKey(namespace: string) {
return `dagit.v2.${namespace}`;
}

export function getJSONForKey(key: string) {
try {
const jsonString = window.localStorage.getItem(key);
if (jsonString) {
return JSON.parse(jsonString);
}
} catch (err) {
// noop
}
return undefined;
}

function getStorageDataForNamespace(namespace: string, initial: Partial<IExecutionSession> = {}) {
if (_data && _dataNamespace === namespace) {
return _data;
Expand Down Expand Up @@ -163,7 +153,7 @@ current localStorage namespace in memory (in _data above) and React keeps a simp
version flag it can use to trigger a re-render after changes are saved, so changing
namespaces changes the returned data immediately.
*/
export function useStorage(
export function useExecutionSessionStorage(
repositoryName: string,
pipelineName: string,
initial: Partial<IExecutionSession> = {},
Expand Down
2 changes: 1 addition & 1 deletion js_modules/dagit/packages/core/src/app/Flags.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import memoize from 'lodash/memoize';
import * as React from 'react';

import {getJSONForKey} from './LocalStorage';
import {getJSONForKey} from '../hooks/useStateWithStorage';

const DAGIT_FLAGS_KEY = 'DAGIT_FLAGS';

Expand Down
22 changes: 9 additions & 13 deletions js_modules/dagit/packages/core/src/app/time/TimezoneContext.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,18 @@
import * as React from 'react';

import {useStateWithStorage} from '../../hooks/useStateWithStorage';

const TimezoneStorageKey = 'TimezonePreference';

type Value = [string, (next: string) => void];
export const TimezoneContext = React.createContext<
[string, React.Dispatch<React.SetStateAction<string | undefined>>]
>(['UTC', () => '']);

export const TimezoneContext = React.createContext<Value>(['UTC', () => {}]);
const validateTimezone = (saved: string | undefined) =>
typeof saved === 'string' ? saved : 'Automatic';

export const TimezoneProvider: React.FC = (props) => {
const [value, setValue] = React.useState(
() => window.localStorage.getItem(TimezoneStorageKey) || 'Automatic',
);

const onChange = React.useCallback((tz: string) => {
window.localStorage.setItem(TimezoneStorageKey, tz);
setValue(tz);
}, []);

const provided: Value = React.useMemo(() => [value, onChange], [value, onChange]);
const state = useStateWithStorage(TimezoneStorageKey, validateTimezone);

return <TimezoneContext.Provider value={provided}>{props.children}</TimezoneContext.Provider>;
return <TimezoneContext.Provider value={state}>{props.children}</TimezoneContext.Provider>;
};
59 changes: 46 additions & 13 deletions js_modules/dagit/packages/core/src/assets/AssetEvents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ import {
Caption,
Subheading,
Warning,
Checkbox,
} from '@dagster-io/ui';
import flatMap from 'lodash/flatMap';
import uniq from 'lodash/uniq';
import qs from 'qs';
import * as React from 'react';
import {Link} from 'react-router-dom';

import {useStateWithStorage} from '../hooks/useStateWithStorage';
import {METADATA_ENTRY_FRAGMENT} from '../metadata/MetadataEntry';
import {SidebarSection} from '../pipelines/SidebarComponents';
import {titleForRun} from '../runs/RunUtils';
Expand Down Expand Up @@ -387,6 +389,8 @@ export const AssetEvents: React.FC<Props> = ({
);
};

const validateHiddenGraphsState = (json: string[]) => (Array.isArray(json) ? json : []);

const AssetMaterializationGraphs: React.FC<{
groups: AssetEventGroup[];
xAxis: 'partition' | 'time';
Expand All @@ -399,7 +403,21 @@ const AssetMaterializationGraphs: React.FC<{
}, [props.groups]);

const graphDataByMetadataLabel = extractNumericData(reversed, props.xAxis);
const [graphedLabels] = React.useState(() => Object.keys(graphDataByMetadataLabel).slice(0, 4));
const graphLabels = Object.keys(graphDataByMetadataLabel).slice(0, 20).sort();

const [collapsedLabels, setCollapsedLabels] = useStateWithStorage(
'hidden-graphs',
validateHiddenGraphsState,
);

const toggleCollapsed = React.useCallback(
(label: string) => {
setCollapsedLabels((current = []) =>
current.includes(label) ? current.filter((c) => c !== label) : [...current, label],
);
},
[setCollapsedLabels],
);

return (
<>
Expand All @@ -411,37 +429,52 @@ const AssetMaterializationGraphs: React.FC<{
flexDirection: 'column',
}}
>
{[...graphedLabels].sort().map((label) => (
{graphLabels.map((label) => (
<Box
key={label}
style={{width: '100%'}}
border={{side: 'bottom', width: 1, color: ColorsWIP.KeylineGray}}
>
{props.asSidebarSection ? (
<Box padding={{horizontal: 24, top: 8}}>
<Box padding={{horizontal: 24, top: 8}} flex={{justifyContent: 'space-between'}}>
<Caption style={{fontWeight: 700}}>{label}</Caption>
<Checkbox
format="switch"
checked={!collapsedLabels.includes(label)}
onChange={() => toggleCollapsed(label)}
size="small"
/>
</Box>
) : (
<Box
padding={{horizontal: 24, vertical: 16}}
border={{side: 'bottom', width: 1, color: ColorsWIP.KeylineGray}}
flex={{justifyContent: 'space-between'}}
>
<Subheading>{label}</Subheading>
<Checkbox
format="switch"
checked={!collapsedLabels.includes(label)}
onChange={() => toggleCollapsed(label)}
size="small"
/>
</Box>
)}
<Box padding={{horizontal: 24, vertical: 16}}>
<AssetValueGraph
label={label}
width="100%"
data={graphDataByMetadataLabel[label]}
xHover={xHover}
onHoverX={(x) => x !== xHover && setXHover(x)}
/>
</Box>
{!collapsedLabels.includes(label) ? (
<Box padding={{horizontal: 24, vertical: 16}}>
<AssetValueGraph
label={label}
width="100%"
data={graphDataByMetadataLabel[label]}
xHover={xHover}
onHoverX={(x) => x !== xHover && setXHover(x)}
/>
</Box>
) : undefined}
</Box>
))}
</div>
{graphedLabels.length === 0 ? (
{graphLabels.length === 0 ? (
<Box padding={{horizontal: 24, top: 64}}>
<NonIdealState
shrinkable
Expand Down
2 changes: 1 addition & 1 deletion js_modules/dagit/packages/core/src/assets/AssetView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ export const AssetView: React.FC<Props> = ({assetKey}) => {
style={{height: 390}}
flex={{direction: 'row', justifyContent: 'center', alignItems: 'center'}}
>
<Spinner purpose="section" />
<Spinner purpose="page" />
</Box>
) : params.asOf ? (
<Box
Expand Down
22 changes: 6 additions & 16 deletions js_modules/dagit/packages/core/src/assets/useAssetView.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,14 @@
import * as React from 'react';
import {useStateWithStorage} from '../hooks/useStateWithStorage';

const ASSET_VIEW_KEY = 'AssetViewPreference';

type View = 'flat' | 'directory' | 'graph';

type Output = [View, (update: View) => void];
const validateSavedAssetView = (storedValue: any) =>
storedValue === 'flat' || storedValue === 'directory' || storedValue === 'graph'
? storedValue
: 'flat';

export const useAssetView = () => {
const [view, setView] = React.useState<View>(() => {
const storedValue = window.localStorage.getItem(ASSET_VIEW_KEY);
if (storedValue === 'flat' || storedValue === 'directory' || storedValue === 'graph') {
return storedValue;
}
return 'flat';
});

const onChange = React.useCallback((update: View) => {
window.localStorage.setItem(ASSET_VIEW_KEY, update);
setView(update);
}, []);

return React.useMemo(() => [view, onChange], [view, onChange]) as Output;
return useStateWithStorage<View>(ASSET_VIEW_KEY, validateSavedAssetView);
};
28 changes: 8 additions & 20 deletions js_modules/dagit/packages/core/src/gantt/useGanttChartMode.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,16 @@
import * as React from 'react';
import {useStateWithStorage} from '../hooks/useStateWithStorage';

import {GanttChartMode} from './Constants';

const GANTT_CHART_MODE_KEY = 'GanttChartModePreference';

type Output = [GanttChartMode, (update: GanttChartMode) => void];
const validateSavedMode = (storedValue: string) =>
storedValue === GanttChartMode.FLAT ||
storedValue === GanttChartMode.WATERFALL ||
storedValue === GanttChartMode.WATERFALL_TIMED
? storedValue
: GanttChartMode.WATERFALL_TIMED;

export const useGanttChartMode = () => {
const [mode, setMode] = React.useState<GanttChartMode>(() => {
const storedValue = window.localStorage.getItem(GANTT_CHART_MODE_KEY);
if (
storedValue === GanttChartMode.FLAT ||
storedValue === GanttChartMode.WATERFALL ||
storedValue === GanttChartMode.WATERFALL_TIMED
) {
return storedValue;
}
return GanttChartMode.WATERFALL_TIMED;
});

const onChange = React.useCallback((update: GanttChartMode) => {
window.localStorage.setItem(GANTT_CHART_MODE_KEY, update);
setMode(update);
}, []);

return React.useMemo(() => [mode, onChange], [mode, onChange]) as Output;
return useStateWithStorage(GANTT_CHART_MODE_KEY, validateSavedMode);
};
49 changes: 49 additions & 0 deletions js_modules/dagit/packages/core/src/hooks/useStateWithStorage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React from 'react';

export function getJSONForKey(key: string) {
let stored = undefined;
try {
stored = window.localStorage.getItem(key);
if (stored) {
return JSON.parse(stored);
}
} catch (err) {
if (typeof stored === 'string') {
// With useStateWithStorage, some values like timezone are moving from `UTC` to `"UTC"`
// in LocalStorage. To read the old values, pass through raw string values. We can
// remove this a few months after 0.14.1 is released.
return stored;
}
return undefined;
}
}

export function useStateWithStorage<T>(key: string, validate: (json: any) => T) {
const [version, setVersion] = React.useState(0);

// Note: This hook doesn't keep the loaded data in state -- instead it uses a version bit and
// a ref to load the value from localStorage when the `key` changes or when the `version` changes.
// This allows us to immediately return the saved value for `key` in the same render.

const state = React.useMemo(() => {
return validate(getJSONForKey(key));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [validate, key, version]);

const setState = React.useCallback(
(input: React.SetStateAction<T>) => {
const next = input instanceof Function ? input(validate(getJSONForKey(key))) : input;
if (next === undefined) {
window.localStorage.removeItem(key);
} else {
window.localStorage.setItem(key, JSON.stringify(next));
}
setVersion((v) => v + 1);
return next;
},
[validate, key],
);

const value = React.useMemo(() => [state, setState], [state, setState]);
return value as [T, React.Dispatch<React.SetStateAction<T | undefined>>];
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import * as React from 'react';
import styled from 'styled-components/macro';

import {showCustomAlert} from '../app/CustomAlertProvider';
import {IExecutionSession} from '../app/LocalStorage';
import {IExecutionSession} from '../app/ExecutionSessionStorage';
import {PythonErrorInfo, PYTHON_ERROR_FRAGMENT} from '../app/PythonErrorInfo';
import {ShortcutHandler} from '../app/ShortcutHandler';
import {PythonErrorFragment} from '../app/types/PythonErrorFragment';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ import {
IExecutionSessionChanges,
PipelineRunTag,
SessionBase,
useStorage,
} from '../app/LocalStorage';
useExecutionSessionStorage,
} from '../app/ExecutionSessionStorage';
import {PythonErrorInfo} from '../app/PythonErrorInfo';
import {ShortcutHandler} from '../app/ShortcutHandler';
import {ConfigEditor} from '../configeditor/ConfigEditor';
Expand Down Expand Up @@ -163,7 +163,11 @@ const LaunchpadSessionContainer: React.FC<LaunchpadSessionContainerProps> = (pro
return {};
}, [isJob, partitionSets.results, presets]);

const [data, onSave] = useStorage(repoAddress.name || '', pipeline.name, initialDataForMode);
const [data, onSave] = useExecutionSessionStorage(
repoAddress.name || '',
pipeline.name,
initialDataForMode,
);

const currentSession = data.sessions[data.current];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import qs from 'qs';
import * as React from 'react';
import {Redirect, useParams} from 'react-router-dom';

import {IExecutionSession, applyCreateSession, useStorage} from '../app/LocalStorage';
import {
IExecutionSession,
applyCreateSession,
useExecutionSessionStorage,
} from '../app/ExecutionSessionStorage';
import {usePermissions} from '../app/Permissions';
import {explorerPathFromString} from '../pipelines/PipelinePathUtils';
import {useJobTitle} from '../pipelines/useJobTitle';
Expand Down Expand Up @@ -37,7 +41,7 @@ const LaunchpadSetupAllowedRoot: React.FC<Props> = (props) => {

useJobTitle(explorerPath, isJob);

const [data, onSave] = useStorage(repoAddress.name, pipelineName);
const [data, onSave] = useExecutionSessionStorage(repoAddress.name, pipelineName);
const queryString = qs.parse(window.location.search, {ignoreQueryPrefix: true});

React.useEffect(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
applyRemoveSession,
applySelectSession,
IStorageData,
} from '../app/LocalStorage';
} from '../app/ExecutionSessionStorage';

interface ExecutationTabProps {
canRemove?: boolean;
Expand Down
2 changes: 1 addition & 1 deletion js_modules/dagit/packages/core/src/launchpad/TagEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
import * as React from 'react';
import styled from 'styled-components/macro';

import {PipelineRunTag} from '../app/LocalStorage';
import {PipelineRunTag} from '../app/ExecutionSessionStorage';
import {ShortcutHandler} from '../app/ShortcutHandler';
import {RunTag} from '../runs/RunTag';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {Intent} from '@blueprintjs/core';
import * as React from 'react';

import {SharedToaster} from '../app/DomUtils';
import {useInvalidateConfigsForRepo} from '../app/LocalStorage';
import {useInvalidateConfigsForRepo} from '../app/ExecutionSessionStorage';
import {PYTHON_ERROR_FRAGMENT} from '../app/PythonErrorInfo';
import {PythonErrorFragment} from '../app/types/PythonErrorFragment';
import {RepositoryLocationLoadStatus} from '../types/globalTypes';
Expand Down

0 comments on commit 7495fea

Please sign in to comment.