Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ui] Show a delayed loading spinner on Overview search inputs #16607

Merged
merged 1 commit into from
Sep 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import {Meta} from '@storybook/react';
import * as React from 'react';

import {Box} from '../Box';
import {Button} from '../Button';
import {useDelayedState} from '../useDelayedState';

// eslint-disable-next-line import/no-default-export
export default {
title: 'useDelayedState',
} as Meta;

export const Default = () => {
const notDisabled = useDelayedState(5000);
return (
<Box flex={{direction: 'column', gap: 12}}>
<div>The button will become enabled after five seconds.</div>
<div>
<Button disabled={!notDisabled}>Wait for it</Button>
</div>
</Box>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import * as React from 'react';

export const useDelayedState = (delayMsec: number) => {
const [value, setValue] = React.useState(false);

React.useEffect(() => {
const timer = setTimeout(() => setValue(true), delayMsec);
return () => clearTimeout(timer);
}, [delayMsec]);

return value;
};
1 change: 1 addition & 0 deletions js_modules/dagster-ui/packages/ui-components/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export * from './components/useSuggestionsForString';
export * from './components/ErrorBoundary';
export * from './components/useViewport';
export * from './components/StyledRawCodeMirror';
export * from './components/useDelayedState';

// Global font styles, exported as styled-component components to render in
// your app tree root. E.g. <GlobalInconsolata />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {isHiddenAssetGroupJob} from '../asset-graph/Utils';
import {useDocumentTitle} from '../hooks/useDocumentTitle';
import {useQueryPersistedState} from '../hooks/useQueryPersistedState';
import {RepoFilterButton} from '../instance/RepoFilterButton';
import {SearchInputSpinner} from '../ui/SearchInputSpinner';
import {WorkspaceContext} from '../workspace/WorkspaceContext';
import {buildRepoAddress} from '../workspace/buildRepoAddress';
import {repoAddressAsHumanString} from '../workspace/repoAddressAsString';
Expand All @@ -32,7 +33,7 @@ export const OverviewJobsRoot = () => {
useTrackPageView();
useDocumentTitle('Overview | Jobs');

const {allRepos, visibleRepos} = React.useContext(WorkspaceContext);
const {allRepos, visibleRepos, loading: workspaceLoading} = React.useContext(WorkspaceContext);
const [searchValue, setSearchValue] = useQueryPersistedState<string>({
queryKey: 'search',
defaults: {search: ''},
Expand Down Expand Up @@ -128,6 +129,8 @@ export const OverviewJobsRoot = () => {
return <OverviewJobsTable repos={filteredBySearch} />;
};

const showSearchSpinner = (workspaceLoading && !repoCount) || (loading && !data);

return (
<Box flex={{direction: 'column'}} style={{height: '100%', overflow: 'hidden'}}>
<PageHeader
Expand All @@ -142,6 +145,9 @@ export const OverviewJobsRoot = () => {
<TextInput
icon="search"
value={searchValue}
rightElement={
showSearchSpinner ? <SearchInputSpinner tooltipContent="Loading jobs…" /> : undefined
}
onChange={(e) => setSearchValue(e.target.value)}
placeholder="Filter by job name…"
style={{width: '340px'}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {useQueryPersistedState} from '../hooks/useQueryPersistedState';
import {RepoFilterButton} from '../instance/RepoFilterButton';
import {RESOURCE_ENTRY_FRAGMENT} from '../resources/WorkspaceResourcesRoot';
import {ResourceEntryFragment} from '../resources/types/WorkspaceResourcesRoot.types';
import {SearchInputSpinner} from '../ui/SearchInputSpinner';
import {WorkspaceContext} from '../workspace/WorkspaceContext';
import {buildRepoAddress} from '../workspace/buildRepoAddress';
import {repoAddressAsHumanString} from '../workspace/repoAddressAsString';
Expand All @@ -36,7 +37,7 @@ export const OverviewResourcesRoot = () => {
useTrackPageView();
useDocumentTitle('Overview | Resources');

const {allRepos, visibleRepos} = React.useContext(WorkspaceContext);
const {allRepos, visibleRepos, loading: workspaceLoading} = React.useContext(WorkspaceContext);
const [searchValue, setSearchValue] = useQueryPersistedState<string>({
queryKey: 'search',
defaults: {search: ''},
Expand Down Expand Up @@ -133,6 +134,8 @@ export const OverviewResourcesRoot = () => {
return <OverviewResourcesTable repos={filteredBySearch} />;
};

const showSearchSpinner = (workspaceLoading && !repoCount) || (loading && !data);

return (
<Box flex={{direction: 'column'}} style={{height: '100%', overflow: 'hidden'}}>
<PageHeader
Expand All @@ -147,6 +150,11 @@ export const OverviewResourcesRoot = () => {
<TextInput
icon="search"
value={searchValue}
rightElement={
showSearchSpinner ? (
<SearchInputSpinner tooltipContent="Loading resources…" />
) : undefined
}
onChange={(e) => setSearchValue(e.target.value)}
placeholder="Filter by resource name…"
style={{width: '340px'}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {CheckAllBox} from '../ui/CheckAllBox';
import {useFilters} from '../ui/Filters';
import {useCodeLocationFilter} from '../ui/Filters/useCodeLocationFilter';
import {useInstigationStatusFilter} from '../ui/Filters/useInstigationStatusFilter';
import {SearchInputSpinner} from '../ui/SearchInputSpinner';
import {WorkspaceContext} from '../workspace/WorkspaceContext';
import {buildRepoAddress} from '../workspace/buildRepoAddress';
import {repoAddressAsHumanString} from '../workspace/repoAddressAsString';
Expand All @@ -55,7 +56,7 @@ export const OverviewSchedulesRoot = () => {
useTrackPageView();
useDocumentTitle('Overview | Schedules');

const {allRepos, visibleRepos} = React.useContext(WorkspaceContext);
const {allRepos, visibleRepos, loading: workspaceLoading} = React.useContext(WorkspaceContext);
const repoCount = allRepos.length;
const [searchValue, setSearchValue] = useQueryPersistedState<string>({
queryKey: 'search',
Expand Down Expand Up @@ -245,6 +246,8 @@ export const OverviewSchedulesRoot = () => {
);
};

const showSearchSpinner = (workspaceLoading && !repoCount) || (loading && !data);

return (
<Box flex={{direction: 'column'}} style={{height: '100%', overflow: 'hidden'}}>
<PageHeader
Expand All @@ -260,6 +263,11 @@ export const OverviewSchedulesRoot = () => {
<TextInput
icon="search"
value={searchValue}
rightElement={
showSearchSpinner ? (
<SearchInputSpinner tooltipContent="Loading schedules…" />
) : undefined
}
onChange={(e) => {
setSearchValue(e.target.value);
onToggleAll(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {CheckAllBox} from '../ui/CheckAllBox';
import {useFilters} from '../ui/Filters';
import {useCodeLocationFilter} from '../ui/Filters/useCodeLocationFilter';
import {useInstigationStatusFilter} from '../ui/Filters/useInstigationStatusFilter';
import {SearchInputSpinner} from '../ui/SearchInputSpinner';
import {WorkspaceContext} from '../workspace/WorkspaceContext';
import {buildRepoAddress} from '../workspace/buildRepoAddress';
import {repoAddressAsHumanString} from '../workspace/repoAddressAsString';
Expand All @@ -55,7 +56,7 @@ export const OverviewSensorsRoot = () => {
useTrackPageView();
useDocumentTitle('Overview | Sensors');

const {allRepos, visibleRepos} = React.useContext(WorkspaceContext);
const {allRepos, visibleRepos, loading: workspaceLoading} = React.useContext(WorkspaceContext);
const repoCount = allRepos.length;
const [searchValue, setSearchValue] = useQueryPersistedState<string>({
queryKey: 'search',
Expand Down Expand Up @@ -245,6 +246,8 @@ export const OverviewSensorsRoot = () => {
);
};

const showSearchSpinner = (workspaceLoading && !repoCount) || (loading && !data);

return (
<Box flex={{direction: 'column'}} style={{height: '100%', overflow: 'hidden'}}>
<PageHeader
Expand All @@ -266,6 +269,11 @@ export const OverviewSensorsRoot = () => {
<TextInput
icon="search"
value={searchValue}
rightElement={
showSearchSpinner ? (
<SearchInputSpinner tooltipContent="Loading sensors…" />
) : undefined
}
onChange={(e) => setSearchValue(e.target.value)}
placeholder="Filter by sensor name…"
style={{width: '340px'}}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import {Box, Spinner, Tooltip, useDelayedState} from '@dagster-io/ui-components';
import * as React from 'react';

interface Props {
tooltipContent: string | React.ReactElement | null;
}

const SPINNER_WAIT_MSEC = 2000;

export const SearchInputSpinner = (props: Props) => {
const {tooltipContent} = props;
const canShowSpinner = useDelayedState(SPINNER_WAIT_MSEC);

if (!canShowSpinner) {
return null;
}

return (
<Box margin={{top: 1}}>
<Tooltip placement="top" canShow={!!tooltipContent} content={tooltipContent || ''}>
<Spinner purpose="body-text" />
</Tooltip>
</Box>
);
};