Skip to content

Commit

Permalink
[dagit] Allow toggling repos in bulk (#7344)
Browse files Browse the repository at this point in the history
## Summary

Resolves #7083.

Add a checkbox to the `RepoSelector` to allow selecting or unselecting the entire list of repos.

This involves moving the "X of Y selected" text to the `RepoSelector` itself, as well as changing the `onToggle` function to accept an array.

## Test Plan

Load Dagit with seven repos loaded, open repo selector. Verify correct behavior of top-level checkbox and individual checkboxes.
  • Loading branch information
hellendag committed Apr 7, 2022
1 parent 4f2b613 commit f1441de
Show file tree
Hide file tree
Showing 7 changed files with 148 additions and 125 deletions.
17 changes: 6 additions & 11 deletions js_modules/dagit/packages/core/src/instance/RepoFilterButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,12 @@ export const RepoFilterButton: React.FC = () => {
onClose={() => setOpen(false)}
>
<DialogHeader icon="repo" label="Repositories" />
<div>
<Box padding={{vertical: 8, horizontal: 24}}>
{`${visibleRepos.length} of ${allRepos.length} selected`}
</Box>
<RepoSelector
options={allRepos}
onBrowse={() => setOpen(false)}
onToggle={toggleVisible}
selected={visibleRepos}
/>
</div>
<RepoSelector
options={allRepos}
onBrowse={() => setOpen(false)}
onToggle={toggleVisible}
selected={visibleRepos}
/>
<DialogFooter>
<Box padding={{top: 8}}>
<Button intent="none" onClick={() => setOpen(false)}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {RepositoryLocationStateObserver} from './RepositoryLocationStateObserver
const LoadedRepositorySection: React.FC<{
allRepos: DagsterRepoOption[];
visibleRepos: DagsterRepoOption[];
toggleVisible: (repoAddress: RepoAddress) => void;
toggleVisible: (repoAddresses: RepoAddress[]) => void;
}> = ({allRepos, visibleRepos, toggleVisible}) => {
const location = useLocation();
const workspacePath = location.pathname.split('/workspace/').pop();
Expand Down
54 changes: 30 additions & 24 deletions js_modules/dagit/packages/core/src/nav/RepoNavItem.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -130,18 +130,21 @@ export const ManyRepos = () => {
const [selected, setSelected] = React.useState<RepoSelectorOption[]>([]);

const onToggle = React.useCallback(
(address: RepoAddress) => {
const option = OPTIONS.find(
(r) => r.repository.name === address.name && r.repositoryLocation.name === address.location,
);
if (!option) {
return;
}
if (selected.includes(option)) {
setSelected(selected.filter((o) => o !== option));
} else {
setSelected([...selected, option]);
}
(addresses: RepoAddress[]) => {
addresses.forEach((address) => {
const option = OPTIONS.find(
(r) =>
r.repository.name === address.name && r.repositoryLocation.name === address.location,
);
if (!option) {
return;
}
if (selected.includes(option)) {
setSelected(selected.filter((o) => o !== option));
} else {
setSelected([...selected, option]);
}
});
},
[selected],
);
Expand All @@ -163,18 +166,21 @@ export const OneRepo = () => {
const [selected, setSelected] = React.useState<RepoSelectorOption[]>(ONE_REPO);

const onToggle = React.useCallback(
(address: RepoAddress) => {
const option = OPTIONS.find(
(r) => r.repository.name === address.name && r.repositoryLocation.name === address.location,
);
if (!option) {
return;
}
if (selected.includes(option)) {
setSelected(selected.filter((o) => o !== option));
} else {
setSelected([...selected, option]);
}
(addresses: RepoAddress[]) => {
addresses.forEach((address) => {
const option = OPTIONS.find(
(r) =>
r.repository.name === address.name && r.repositoryLocation.name === address.location,
);
if (!option) {
return;
}
if (selected.includes(option)) {
setSelected(selected.filter((o) => o !== option));
} else {
setSelected([...selected, option]);
}
});
},
[selected],
);
Expand Down
19 changes: 7 additions & 12 deletions js_modules/dagit/packages/core/src/nav/RepoNavItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import {RepoSelector, RepoSelectorOption} from './RepoSelector';
interface Props {
allRepos: RepoSelectorOption[];
selected: RepoSelectorOption[];
onToggle: (repoAddress: RepoAddress) => void;
onToggle: (repoAddresses: RepoAddress[]) => void;
}

export const RepoNavItem: React.FC<Props> = (props) => {
Expand Down Expand Up @@ -70,17 +70,12 @@ export const RepoNavItem: React.FC<Props> = (props) => {
onClose={() => setOpen(false)}
>
<DialogHeader icon="repo" label="Repositories" />
<div>
<Box padding={{vertical: 8, horizontal: 24}}>
{`${selected.length} of ${allRepos.length} selected`}
</Box>
<RepoSelector
options={allRepos}
onBrowse={() => setOpen(false)}
onToggle={onToggle}
selected={selected}
/>
</div>
<RepoSelector
options={allRepos}
onBrowse={() => setOpen(false)}
onToggle={onToggle}
selected={selected}
/>
<DialogFooter>
<Box padding={{top: 8}}>
<Button intent="none" onClick={() => setOpen(false)}>
Expand Down
27 changes: 15 additions & 12 deletions js_modules/dagit/packages/core/src/nav/RepoSelector.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,18 +126,21 @@ export const ManyRepos = () => {
const [selected, setSelected] = React.useState<RepoSelectorOption[]>([]);

const onToggle = React.useCallback(
(address: RepoAddress) => {
const option = OPTIONS.find(
(r) => r.repository.name === address.name && r.repositoryLocation.name === address.location,
);
if (!option) {
return;
}
if (selected.includes(option)) {
setSelected(selected.filter((o) => o !== option));
} else {
setSelected([...selected, option]);
}
(addresses: RepoAddress[]) => {
addresses.forEach((address) => {
const option = OPTIONS.find(
(r) =>
r.repository.name === address.name && r.repositoryLocation.name === address.location,
);
if (!option) {
return;
}
if (selected.includes(option)) {
setSelected(selected.filter((o) => o !== option));
} else {
setSelected([...selected, option]);
}
});
},
[selected],
);
Expand Down
128 changes: 75 additions & 53 deletions js_modules/dagit/packages/core/src/nav/RepoSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {Link} from 'react-router-dom';
import styled from 'styled-components/macro';

import {usePermissions} from '../app/Permissions';
import {buildRepoAddress} from '../workspace/buildRepoAddress';
import {repoAddressAsString} from '../workspace/repoAddressAsString';
import {RepoAddress} from '../workspace/types';
import {workspacePathFromAddress} from '../workspace/workspacePath';
Expand All @@ -35,7 +36,7 @@ export interface RepoSelectorOption {

interface Props {
onBrowse: () => void;
onToggle: (repoAddress: RepoAddress) => void;
onToggle: (repoAddresses: RepoAddress[]) => void;
options: RepoSelectorOption[];
selected: RepoSelectorOption[];
}
Expand All @@ -44,62 +45,83 @@ export const RepoSelector: React.FC<Props> = (props) => {
const {onBrowse, onToggle, options, selected} = props;
const {canReloadRepositoryLocation} = usePermissions();

const optionCount = options.length;
const selectedCount = selected.length;

const onToggleAll = (e: React.ChangeEvent<HTMLInputElement>) => {
const {checked} = e.target;
const reposToToggle = options
.filter((option) => (checked ? !selected.includes(option) : selected.includes(option)))
.map((option) => buildRepoAddress(option.repository.name, option.repositoryLocation.name));
onToggle(reposToToggle);
};

return (
<Table>
<tbody>
{options.map((option) => {
const checked = selected.includes(option);
const repoAddress = {
location: option.repositoryLocation.name,
name: option.repository.name,
};
const addressString = repoAddressAsString(repoAddress);
return (
<tr key={addressString}>
<td>
<Checkbox
checked={checked}
onChange={(e) => {
if (e.target instanceof HTMLInputElement) {
onToggle(repoAddress);
}
}}
id={`switch-${addressString}`}
/>
</td>
<td>
<RepoLabel htmlFor={`switch-${addressString}`}>
<Group direction="column" spacing={4}>
<Box flex={{direction: 'row'}} title={addressString}>
<RepoName>{repoAddress.name}</RepoName>
<RepoLocation>{`@${repoAddress.location}`}</RepoLocation>
</Box>
<Group direction="column" spacing={2}>
{option.repository.displayMetadata.map(({key, value}) => (
<Caption
style={{color: Colors.Gray400, fontFamily: FontFamily.monospace}}
key={key}
>{`${key}: ${value}`}</Caption>
))}
<div>
<Box padding={{vertical: 8, horizontal: 24}} flex={{alignItems: 'center', gap: 12}}>
<Checkbox
checked={selectedCount > 0}
indeterminate={!!(selectedCount && optionCount !== selectedCount)}
onChange={onToggleAll}
/>
{`${selected.length} of ${options.length} selected`}
</Box>
<Table>
<tbody>
{options.map((option) => {
const checked = selected.includes(option);
const repoAddress = {
location: option.repositoryLocation.name,
name: option.repository.name,
};
const addressString = repoAddressAsString(repoAddress);
return (
<tr key={addressString}>
<td>
<Checkbox
checked={checked}
onChange={(e) => {
if (e.target instanceof HTMLInputElement) {
onToggle([repoAddress]);
}
}}
id={`switch-${addressString}`}
/>
</td>
<td>
<RepoLabel htmlFor={`switch-${addressString}`}>
<Group direction="column" spacing={4}>
<Box flex={{direction: 'row'}} title={addressString}>
<RepoName>{repoAddress.name}</RepoName>
<RepoLocation>{`@${repoAddress.location}`}</RepoLocation>
</Box>
<Group direction="column" spacing={2}>
{option.repository.displayMetadata.map(({key, value}) => (
<Caption
style={{color: Colors.Gray400, fontFamily: FontFamily.monospace}}
key={key}
>{`${key}: ${value}`}</Caption>
))}
</Group>
</Group>
</Group>
</RepoLabel>
</td>
<td>
<Link to={workspacePathFromAddress(repoAddress)} onClick={() => onBrowse()}>
Browse
</Link>
</td>
{canReloadRepositoryLocation ? (
</RepoLabel>
</td>
<td>
<ReloadButton repoAddress={repoAddress} />
<Link to={workspacePathFromAddress(repoAddress)} onClick={() => onBrowse()}>
Browse
</Link>
</td>
) : null}
</tr>
);
})}
</tbody>
</Table>
{canReloadRepositoryLocation ? (
<td>
<ReloadButton repoAddress={repoAddress} />
</td>
) : null}
</tr>
);
})}
</tbody>
</Table>
</div>
);
};

Expand Down
26 changes: 14 additions & 12 deletions js_modules/dagit/packages/core/src/workspace/WorkspaceContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export type WorkspaceState = {
visibleRepos: DagsterRepoOption[];

refetch: () => Promise<ApolloQueryResult<RootWorkspaceQuery>>;
toggleVisible: (repoAddress: RepoAddress) => void;
toggleVisible: (repoAddresses: RepoAddress[]) => void;
};

export const WorkspaceContext = React.createContext<WorkspaceState>(
Expand Down Expand Up @@ -195,17 +195,19 @@ const useVisibleRepos = (
);

const toggleVisible = React.useCallback(
(repoAddress: RepoAddress) => {
const key = `${repoAddress.name}:${repoAddress.location}`;

setHiddenKeys((current) => {
let nextHiddenKeys = [...(current || [])];
if (nextHiddenKeys.includes(key)) {
nextHiddenKeys = nextHiddenKeys.filter((k) => k !== key);
} else {
nextHiddenKeys = [...nextHiddenKeys, key];
}
return nextHiddenKeys;
(repoAddresses: RepoAddress[]) => {
repoAddresses.forEach((repoAddress) => {
const key = `${repoAddress.name}:${repoAddress.location}`;

setHiddenKeys((current) => {
let nextHiddenKeys = [...(current || [])];
if (nextHiddenKeys.includes(key)) {
nextHiddenKeys = nextHiddenKeys.filter((k) => k !== key);
} else {
nextHiddenKeys = [...nextHiddenKeys, key];
}
return nextHiddenKeys;
});
});
},
[setHiddenKeys],
Expand Down

0 comments on commit f1441de

Please sign in to comment.