Skip to content

Commit ffcd101

Browse files
authored
feat(bulk-import): allow user to select repositories from side panel (#1430)
Signed-off-by: Yi Cai <[email protected]>
1 parent 29ae1c3 commit ffcd101

11 files changed

+1116
-195
lines changed
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import React, { useState } from 'react';
2+
3+
import { Link } from '@backstage/core-components';
4+
5+
import {
6+
Button,
7+
Card,
8+
Container,
9+
Drawer,
10+
IconButton,
11+
makeStyles,
12+
Typography,
13+
} from '@material-ui/core';
14+
import CloseIcon from '@material-ui/icons/Close';
15+
import OpenInNewIcon from '@mui/icons-material/OpenInNew';
16+
17+
import { AddRepositoriesData, AddRepositoriesFormValues } from '../../types';
18+
import { urlHelper } from '../../utils/repository-utils';
19+
import { AddRepositoriesTableToolbar } from './AddRepositoriesTableToolbar';
20+
import { RepositoriesTable } from './RepositoriesTable';
21+
22+
type AddRepositoriesDrawerProps = {
23+
open: boolean;
24+
onClose: () => void;
25+
onSelect: (ids: number[], drawerOrgId: number) => void;
26+
title: string;
27+
data: AddRepositoriesData;
28+
selectedRepositoriesFormData: AddRepositoriesFormValues;
29+
checkedRepos: number[];
30+
};
31+
32+
const useStyles = makeStyles(theme => ({
33+
createButton: {
34+
marginRight: theme.spacing(1),
35+
},
36+
sidePanelfooter: {
37+
display: 'flex',
38+
flexDirection: 'row',
39+
justifyContent: 'right',
40+
marginTop: theme.spacing(2),
41+
position: 'fixed',
42+
bottom: '20px',
43+
},
44+
drawerPaper: {
45+
['@media (max-width: 960px)']: {
46+
'& > div[class*="MuiDrawer-paper-"]': {
47+
width: '-webkit-fill-available',
48+
},
49+
},
50+
},
51+
drawerContainer: {
52+
padding: '20px',
53+
height: '100%',
54+
display: 'flex',
55+
flexDirection: 'column',
56+
},
57+
}));
58+
59+
export const AddRepositoriesDrawer = ({
60+
open,
61+
onClose,
62+
onSelect,
63+
title,
64+
data,
65+
selectedRepositoriesFormData,
66+
checkedRepos,
67+
}: AddRepositoriesDrawerProps) => {
68+
const classes = useStyles();
69+
const [searchString, setSearchString] = useState<string>('');
70+
71+
const [selectedReposID, setSelectedReposID] =
72+
useState<number[]>(checkedRepos);
73+
74+
const updateSelectedReposInDrawer = (ids: number[]) => {
75+
setSelectedReposID(ids);
76+
};
77+
78+
const handleSelectRepoFromDrawer = (selected: number[]) => {
79+
onSelect(selected, data?.id);
80+
onClose();
81+
};
82+
83+
return (
84+
<Drawer
85+
anchor="right"
86+
open={open}
87+
variant="temporary"
88+
className={classes.drawerPaper}
89+
>
90+
<Container className={classes.drawerContainer}>
91+
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
92+
<div>
93+
<Typography variant="h5">{data?.name}</Typography>
94+
<Link to={data?.url}>
95+
{urlHelper(data?.url)}
96+
<OpenInNewIcon
97+
style={{ verticalAlign: 'sub', paddingTop: '7px' }}
98+
/>
99+
</Link>
100+
</div>
101+
<div>
102+
<IconButton onClick={onClose} className="align-right">
103+
<CloseIcon />
104+
</IconButton>
105+
</div>
106+
</div>
107+
<Card style={{ marginTop: '20px', marginBottom: '60px' }}>
108+
<AddRepositoriesTableToolbar
109+
title={title}
110+
setSearchString={setSearchString}
111+
selectedReposFromDrawer={selectedReposID}
112+
selectedRepositoriesFormData={selectedRepositoriesFormData}
113+
activeOrganization={data}
114+
/>
115+
<RepositoriesTable
116+
searchString={searchString}
117+
selectedOrgRepos={selectedReposID}
118+
updateSelectedReposInDrawer={updateSelectedReposInDrawer}
119+
drawerOrganization={data}
120+
/>
121+
</Card>
122+
<div className={classes.sidePanelfooter}>
123+
<span>
124+
<Button
125+
variant="contained"
126+
color="primary"
127+
onClick={() => handleSelectRepoFromDrawer(selectedReposID)}
128+
className={classes.createButton}
129+
aria-labelledby="select-from-drawer"
130+
>
131+
Select
132+
</Button>
133+
</span>
134+
<span>
135+
<Button
136+
aria-labelledby="cancel-drawer-select"
137+
variant="outlined"
138+
onClick={onClose}
139+
>
140+
Cancel
141+
</Button>
142+
</span>
143+
</div>
144+
</Container>
145+
</Drawer>
146+
);
147+
};

plugins/bulk-import/src/components/AddRepositories/AddRepositoriesTableToolbar.tsx

Lines changed: 40 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@ import Toolbar from '@mui/material/Toolbar';
66
import Typography from '@mui/material/Typography';
77
import { FormikErrors } from 'formik';
88

9-
import { AddRepositoriesFormValues } from '../../types';
10-
import { getRepositoriesSelected } from '../../utils/repository-utils';
9+
import { AddRepositoriesData, AddRepositoriesFormValues } from '../../types';
1110
import { RepositoriesSearchBar } from './AddRepositoriesSearchBar';
1211

1312
export const AddRepositoriesTableToolbar = ({
@@ -16,24 +15,29 @@ export const AddRepositoriesTableToolbar = ({
1615
selectedRepositoriesFormData,
1716
setFieldValue,
1817
onPageChange,
18+
activeOrganization,
19+
selectedReposFromDrawer,
1920
}: {
2021
title: string;
2122
setSearchString: (str: string) => void;
2223
selectedRepositoriesFormData: AddRepositoriesFormValues;
23-
setFieldValue: (
24+
setFieldValue?: (
2425
field: string,
2526
value: any,
2627
shouldValidate?: boolean,
2728
) => Promise<FormikErrors<AddRepositoriesFormValues>> | Promise<void>;
28-
onPageChange: (page: number) => void;
29+
onPageChange?: (page: number) => void;
30+
activeOrganization?: AddRepositoriesData;
31+
selectedReposFromDrawer?: number[];
2932
}) => {
3033
const [selection, setSelection] = React.useState('repository');
3134
const [search, setSearch] = React.useState<string>('');
35+
const [selectedReposNumber, setSelectedReposNumber] = React.useState(0);
3236
const handleToggle = (
3337
_event: React.MouseEvent<HTMLElement>,
3438
type: string,
3539
) => {
36-
if (type) {
40+
if (type && setFieldValue && onPageChange) {
3741
setSelection(type);
3842
setFieldValue('repositoryType', type);
3943
onPageChange(0);
@@ -45,6 +49,23 @@ export const AddRepositoriesTableToolbar = ({
4549
setSearch(filter);
4650
};
4751

52+
React.useEffect(() => {
53+
if (activeOrganization && selectedReposFromDrawer) {
54+
const thisSelectedReposCount = activeOrganization.repositories?.filter(
55+
repo => selectedReposFromDrawer.includes(repo.id) && repo.id > -1,
56+
).length;
57+
setSelectedReposNumber(thisSelectedReposCount || 0);
58+
} else {
59+
setSelectedReposNumber(
60+
selectedRepositoriesFormData.repositories?.length || 0,
61+
);
62+
}
63+
}, [
64+
selectedReposFromDrawer,
65+
selectedRepositoriesFormData,
66+
activeOrganization,
67+
]);
68+
4869
return (
4970
<Toolbar
5071
sx={{
@@ -54,19 +75,21 @@ export const AddRepositoriesTableToolbar = ({
5475
}}
5576
>
5677
<Typography sx={{ flex: '1 1 100%' }} variant="h5" id={title}>
57-
{`${title} (${getRepositoriesSelected(selectedRepositoriesFormData)})`}
78+
{`${title} (${selectedReposNumber})`}
5879
</Typography>
59-
<ToggleButtonGroup
60-
size="medium"
61-
color="primary"
62-
value={selection}
63-
exclusive
64-
onChange={handleToggle}
65-
aria-label="repository-type"
66-
>
67-
<ToggleButton value="repository">Repositories</ToggleButton>
68-
<ToggleButton value="organization">Organization</ToggleButton>
69-
</ToggleButtonGroup>
80+
{!activeOrganization && (
81+
<ToggleButtonGroup
82+
size="medium"
83+
color="primary"
84+
value={selection}
85+
exclusive
86+
onChange={handleToggle}
87+
aria-label="repository-type"
88+
>
89+
<ToggleButton value="repository">Repositories</ToggleButton>
90+
<ToggleButton value="organization">Organization</ToggleButton>
91+
</ToggleButtonGroup>
92+
)}
7093
<RepositoriesSearchBar value={search} onChange={handleSearch} />
7194
</Toolbar>
7295
);

plugins/bulk-import/src/components/AddRepositories/OrganizationTableRow.tsx

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,32 +10,45 @@ import { AddRepositoriesData } from '../../types';
1010
import {
1111
getRepositoryStatusForOrg,
1212
getSelectedRepositories,
13+
urlHelper,
1314
} from '../../utils/repository-utils';
1415

16+
const tableCellStyle = {
17+
lineHeight: '1.5rem',
18+
fontSize: '0.875rem',
19+
padding: '15px 16px 15px 24px',
20+
};
21+
1522
export const OrganizationTableRow = ({
23+
onOrgRowSelected,
1624
data,
25+
alreadyAdded,
1726
}: {
27+
onOrgRowSelected: (org: AddRepositoriesData) => void;
1828
data: AddRepositoriesData;
29+
alreadyAdded: number;
1930
}) => {
2031
return (
2132
<TableRow hover>
22-
<TableCell component="th" scope="row" padding="none">
33+
<TableCell component="th" scope="row" padding="none" sx={tableCellStyle}>
2334
{data.name}
2435
</TableCell>
25-
<TableCell align="left">
36+
<TableCell align="left" sx={tableCellStyle}>
2637
<Link to={data.url}>
2738
<>
28-
{data.url}
39+
{urlHelper(data.url)}
2940
<OpenInNewIcon
30-
style={{ verticalAlign: 'bottom', paddingTop: '7px' }}
41+
style={{ verticalAlign: 'sub', paddingTop: '7px' }}
3142
/>
3243
</>
3344
</Link>
3445
</TableCell>
35-
<TableCell align="left">
36-
<>{getSelectedRepositories(data.selectedRepositories)}</>
46+
<TableCell align="left" sx={tableCellStyle}>
47+
{getSelectedRepositories(onOrgRowSelected, data, alreadyAdded)}
48+
</TableCell>
49+
<TableCell align="left" sx={tableCellStyle}>
50+
{getRepositoryStatusForOrg(data, alreadyAdded)}
3751
</TableCell>
38-
<TableCell align="left">{getRepositoryStatusForOrg(data)}</TableCell>
3952
</TableRow>
4053
);
4154
};
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { TableColumn } from '@backstage/core-components';
2+
3+
export const ReposSelectDrawerColumnHeader: TableColumn[] = [
4+
{
5+
id: 'name',
6+
title: 'Name',
7+
field: 'name',
8+
},
9+
{
10+
id: 'url',
11+
title: 'URL',
12+
field: 'url',
13+
},
14+
{
15+
id: 'catalogInfoYaml',
16+
title: '',
17+
field: 'catalogInfoYaml',
18+
},
19+
];

0 commit comments

Comments
 (0)