Skip to content

Commit

Permalink
button
Browse files Browse the repository at this point in the history
  • Loading branch information
salazarm committed Sep 29, 2023
1 parent a931c43 commit 06457c8
Show file tree
Hide file tree
Showing 5 changed files with 163 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export const RefreshableCountdown = (props: Props) => {
);
};

const RefreshButton = styled.button`
export const RefreshButton = styled.button`
border: none;
cursor: pointer;
padding: 8px;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import {Box, Button, Colors, Icon, RefreshButton, Tooltip} from '@dagster-io/ui-components';
import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';
import updateLocale from 'dayjs/plugin/updateLocale';
import React from 'react';

dayjs.extend(relativeTime);
dayjs.extend(updateLocale);

dayjs.updateLocale('en', {
relativeTime: {
future: 'in %s',
past: '%s ago',
s: '%d seconds',
m: 'a minute',
mm: '%d minutes',
h: 'an hour',
hh: '%d hours',
d: 'a day',
dd: '%d days',
M: 'a month',
MM: '%d months',
y: 'a year',
yy: '%d years',
},
});

export const AssetDataRefreshButton = ({
isRefreshing,
onRefresh,
oldestDataTimestamp,
}: {
isRefreshing: boolean;
onRefresh: () => void;
oldestDataTimestamp: number;
}) => {
return (
<Tooltip
content={
isRefreshing ? (
'Refreshing asset data…'
) : (
<Box flex={{direction: 'column', gap: 4}}>
<TimeFromNowWithSeconds timestamp={oldestDataTimestamp} />
<div>Click to refresh now</div>
</Box>
)
}
>
<Button
icon={<Icon name="refresh" color={Colors.Gray400} />}
onClick={() => {
if (!isRefreshing) {
onRefresh();
}
}}
/>
</Tooltip>
);
};

const TimeFromNowWithSeconds: React.FC<{timestamp: number}> = ({timestamp}) => {
const [text, setText] = React.useState(dayjs(timestamp).fromNow(true));
React.useEffect(() => {
const interval = setInterval(() => {
setText(dayjs(timestamp).fromNow(true));
}, 1000);
return () => {
clearInterval(interval);
};
}, [timestamp]);
return <>{text === '0s' ? 'Refreshing asset data…' : `Data is at most ${text} old`}</>;
};
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import {AssetKeyInput} from '../graphql/types';
import {isDocumentVisible, useDocumentVisibility} from '../hooks/useDocumentVisibility';
import {useDidLaunchEvent} from '../runs/RunUtils';

import {AssetDataRefreshButton} from './AssetDataRefreshButton';

const _assetKeyListeners: Record<string, Array<DataForNodeListener>> = {};
let providerListener = (_key: string, _data?: LiveDataForNode) => {};
const _cache: Record<string, LiveDataForNode> = {};
Expand Down Expand Up @@ -56,11 +58,13 @@ export function useAssetsLiveData(assetKeys: AssetKeyInput[]) {

return {
liveDataByNode: data,

refresh: React.useCallback(() => {
_resetLastFetchedOrRequested(assetKeys);
setNeedsImmediateFetch();
setIsRefreshing(true);
}, [setNeedsImmediateFetch, assetKeys]),

refreshing: React.useMemo(() => {
for (const key of assetKeys) {
const stringKey = tokenForAssetKey(key);
Expand Down Expand Up @@ -120,6 +124,16 @@ const AssetLiveDataContext = React.createContext<{
onUnsubscribed: () => {},
});

const AssetLiveDataRefreshContext = React.createContext<{
isGloballyRefreshing: boolean;
oldestDataTimestamp: number;
refresh: () => void;
}>({
isGloballyRefreshing: false,
oldestDataTimestamp: Infinity,
refresh: () => {},
});

// Map of asset keys to their last fetched time and last requested time
const lastFetchedOrRequested: Record<
string,
Expand All @@ -143,26 +157,48 @@ export const AssetLiveDataProvider = ({children}: {children: React.ReactNode}) =

const isDocumentVisible = useDocumentVisibility();

const [isGloballyRefreshing, setIsGloballyRefreshing] = React.useState(false);
const [oldestDataTimestamp, setOldestDataTimestamp] = React.useState(0);

const onUpdatingOrUpdated = React.useCallback(() => {
const allAssetKeys = Object.keys(_assetKeyListeners).filter(
(key) => _assetKeyListeners[key]?.length,
);
let isRefreshing = allAssetKeys.length ? true : false;
let oldestDataTimestamp = Infinity;
for (const key of allAssetKeys) {
if (lastFetchedOrRequested[key]?.fetched) {
isRefreshing = false;
}
oldestDataTimestamp = Math.min(
oldestDataTimestamp,
lastFetchedOrRequested[key]?.fetched ?? Infinity,
);
}
setIsGloballyRefreshing(isRefreshing);
setOldestDataTimestamp(oldestDataTimestamp === Infinity ? 0 : oldestDataTimestamp);
}, []);

React.useEffect(() => {
if (!isDocumentVisible) {
return;
}
// Check for assets to fetch every 5 seconds to simplify logic
// This means assets will be fetched at most 5 + SUBSCRIPTION_IDLE_POLL_RATE after their first fetch
// but then will be fetched every SUBSCRIPTION_IDLE_POLL_RATE after that
const interval = setInterval(() => fetchData(client), 5000);
fetchData(client);
const interval = setInterval(() => fetchData(client, onUpdatingOrUpdated), 5000);
fetchData(client, onUpdatingOrUpdated);
return () => {
clearInterval(interval);
};
}, [client, isDocumentVisible]);
}, [client, isDocumentVisible, onUpdatingOrUpdated]);

React.useEffect(() => {
if (!needsImmediateFetch) {
return;
}
const timeout = setTimeout(() => {
fetchData(client);
fetchData(client, onUpdatingOrUpdated);
setNeedsImmediateFetch(false);
// Wait BATCHING_INTERVAL before doing fetch in case the component is unmounted quickly (eg. in the case of scrolling/filtering quickly)
}, BATCHING_INTERVAL);
Expand Down Expand Up @@ -240,7 +276,19 @@ export const AssetLiveDataProvider = ({children}: {children: React.ReactNode}) =
[],
)}
>
{children}
<AssetLiveDataRefreshContext.Provider
value={{
isGloballyRefreshing,
oldestDataTimestamp,
refresh: React.useCallback(() => {
setIsGloballyRefreshing(true);
_resetLastFetchedOrRequested();
setNeedsImmediateFetch(true);
}, [setNeedsImmediateFetch]),
}}
>
{children}
</AssetLiveDataRefreshContext.Provider>
</AssetLiveDataContext.Provider>
);
};
Expand All @@ -250,6 +298,7 @@ async function _batchedQueryAssets(
assetKeys: AssetKeyInput[],
client: ApolloClient<any>,
setData: (data: Record<string, LiveDataForNode>) => void,
onUpdatingOrUpdated: () => void,
) {
// Bail if the document isn't visible
if (!assetKeys.length || isFetching) {
Expand All @@ -263,6 +312,7 @@ async function _batchedQueryAssets(
requested: requestTime,
};
});
onUpdatingOrUpdated();
const data = await _queryAssetKeys(client, assetKeys);
const fetchedTime = Date.now();
assetKeys.forEach((key) => {
Expand All @@ -271,10 +321,11 @@ async function _batchedQueryAssets(
};
});
setData(data);
onUpdatingOrUpdated();
isFetching = false;
const nextAssets = _determineAssetsToFetch();
if (nextAssets.length) {
_batchedQueryAssets(nextAssets, client, setData);
_batchedQueryAssets(nextAssets, client, setData, onUpdatingOrUpdated);
}
}

Expand Down Expand Up @@ -333,19 +384,24 @@ function _determineAssetsToFetch() {
return assetsWithoutData.concat(assetsToFetch).slice(0, BATCH_SIZE);
}

function fetchData(client: ApolloClient<any>) {
_batchedQueryAssets(_determineAssetsToFetch(), client, (data) => {
Object.entries(data).forEach(([key, assetData]) => {
const listeners = _assetKeyListeners[key];
providerListener(key, assetData);
if (!listeners) {
return;
}
listeners.forEach((listener) => {
listener(key, assetData);
function fetchData(client: ApolloClient<any>, onUpdatingOrUpdated: () => void) {
_batchedQueryAssets(
_determineAssetsToFetch(),
client,
(data) => {
Object.entries(data).forEach(([key, assetData]) => {
const listeners = _assetKeyListeners[key];
providerListener(key, assetData);
if (!listeners) {
return;
}
listeners.forEach((listener) => {
listener(key, assetData);
});
});
});
});
},
onUpdatingOrUpdated,
);
}

function getAllAssetKeysWithListeners(): AssetKeyInput[] {
Expand All @@ -370,3 +426,16 @@ export function _setCacheEntryForTest(assetKey: AssetKeyInput, data?: LiveDataFo
});
}
}

export function AssetLiveDataRefresh() {
const {isGloballyRefreshing, oldestDataTimestamp, refresh} = React.useContext(
AssetLiveDataRefreshContext,
);
return (
<AssetDataRefreshButton
isRefreshing={isGloballyRefreshing}
oldestDataTimestamp={oldestDataTimestamp}
onRefresh={refresh}
/>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {useHistory} from 'react-router-dom';
import styled from 'styled-components';

import {useFeatureFlags} from '../app/Flags';
import {AssetLiveDataRefresh} from '../asset-data/AssetLiveDataProvider';
import {LaunchAssetExecutionButton} from '../assets/LaunchAssetExecutionButton';
import {LaunchAssetObservationButton} from '../assets/LaunchAssetObservationButton';
import {AssetKey} from '../assets/types';
Expand Down Expand Up @@ -428,6 +429,7 @@ const AssetGraphExplorerWithData: React.FC<WithDataProps> = ({

<Box style={{position: 'absolute', right: 12, top: 8}}>
<Box flex={{alignItems: 'center', gap: 12}}>
<AssetLiveDataRefresh />
<LaunchAssetObservationButton
preferredJobName={explorerPath.pipelineName}
scope={
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,6 @@ export const Node = ({
<div
onClick={(e) => {
e.stopPropagation();
console.log('toggling open');
toggleOpen();
}}
style={{cursor: 'pointer'}}
Expand Down

0 comments on commit 06457c8

Please sign in to comment.