18559: Add user profile tests.
[arvados-workbench2.git] / src / views / user-profile-panel / user-profile-panel-root.tsx
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 import React from 'react';
6 import { Field, InjectedFormProps } from "redux-form";
7 import { TextField } from "components/text-field/text-field";
8 import { DataExplorer } from "views-components/data-explorer/data-explorer";
9 import { NativeSelectField } from "components/select-field/select-field";
10 import {
11     StyleRulesCallback,
12     WithStyles,
13     withStyles,
14     Card,
15     CardContent,
16     Button,
17     Typography,
18     Grid,
19     InputLabel,
20     Tabs, Tab,
21     Paper
22 } from '@material-ui/core';
23 import { ArvadosTheme } from 'common/custom-theme';
24 import { User } from "models/user";
25 import { DataTableDefaultView } from 'components/data-table-default-view/data-table-default-view';
26 import { MY_ACCOUNT_VALIDATION } from "validators/validators";
27 import { USER_PROFILE_PANEL_ID } from 'store/user-profile/user-profile-actions';
28 import { noop } from 'lodash';
29 import { GroupsIcon } from 'components/icon/icon';
30 import { DataColumns } from 'components/data-table/data-table';
31 import { ResourceLinkHeadUuid, ResourceLinkHeadPermissionLevel, ResourceLinkHead, ResourceLinkDelete, ResourceLinkTailIsVisible } from 'views-components/data-explorer/renderers';
32 import { createTree } from 'models/tree';
33
34 type CssRules = 'root' | 'adminRoot' | 'gridItem' | 'label' | 'readOnlyValue' | 'title' | 'description' | 'actions' | 'content';
35
36 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
37     root: {
38         width: '100%',
39         overflow: 'auto'
40     },
41     adminRoot: {
42         // ...theme.mixins.gutters()
43     },
44     gridItem: {
45         height: 45,
46         marginBottom: 20
47     },
48     label: {
49         fontSize: '0.675rem',
50         color: theme.palette.grey['600']
51     },
52     readOnlyValue: {
53         fontSize: '0.875rem',
54     },
55     title: {
56         fontSize: '1.1rem',
57     },
58     description: {
59         color: theme.palette.grey["600"]
60     },
61     actions: {
62         display: 'flex',
63         justifyContent: 'flex-end'
64     },
65     content: {
66         // reserve space for the tab bar
67         height: `calc(100% - ${theme.spacing.unit * 7}px)`,
68     }
69 });
70
71 export interface UserProfilePanelRootActionProps {
72     openSetupShellAccount: (uuid: string) => void;
73     loginAs: (uuid: string) => void;
74     openDeactivateDialog: (uuid: string) => void;
75 }
76
77 export interface UserProfilePanelRootDataProps {
78     isAdmin: boolean;
79     isSelf: boolean;
80     isPristine: boolean;
81     isValid: boolean;
82     initialValues?: User;
83     localCluster: string;
84 }
85
86 const RoleTypes = [
87     { key: '', value: '' },
88     { key: 'Bio-informatician', value: 'Bio-informatician' },
89     { key: 'Data Scientist', value: 'Data Scientist' },
90     { key: 'Analyst', value: 'Analyst' },
91     { key: 'Researcher', value: 'Researcher' },
92     { key: 'Software Developer', value: 'Software Developer' },
93     { key: 'System Administrator', value: 'System Administrator' },
94     { key: 'Other', value: 'Other' }
95 ];
96
97 type UserProfilePanelRootProps = InjectedFormProps<{}> & UserProfilePanelRootActionProps & UserProfilePanelRootDataProps & WithStyles<CssRules>;
98
99 export enum UserProfileGroupsColumnNames {
100     NAME = "Name",
101     PERMISSION = "Permission",
102     VISIBLE = "Visible to other members",
103     UUID = "UUID",
104     REMOVE = "Remove",
105 }
106
107 enum TABS {
108     PROFILE = "PROFILE",
109     GROUPS = "GROUPS",
110     ADMIN = "ADMIN",
111
112 }
113
114 export const userProfileGroupsColumns: DataColumns<string> = [
115     {
116         name: UserProfileGroupsColumnNames.NAME,
117         selected: true,
118         configurable: true,
119         filters: createTree(),
120         render: uuid => <ResourceLinkHead uuid={uuid} />
121     },
122     {
123         name: UserProfileGroupsColumnNames.PERMISSION,
124         selected: true,
125         configurable: true,
126         filters: createTree(),
127         render: uuid => <ResourceLinkHeadPermissionLevel uuid={uuid} />
128     },
129     {
130         name: UserProfileGroupsColumnNames.VISIBLE,
131         selected: true,
132         configurable: true,
133         filters: createTree(),
134         render: uuid => <ResourceLinkTailIsVisible uuid={uuid} />
135     },
136     {
137         name: UserProfileGroupsColumnNames.UUID,
138         selected: true,
139         configurable: true,
140         filters: createTree(),
141         render: uuid => <ResourceLinkHeadUuid uuid={uuid} />
142     },
143     {
144         name: UserProfileGroupsColumnNames.REMOVE,
145         selected: true,
146         configurable: true,
147         filters: createTree(),
148         render: uuid => <ResourceLinkDelete uuid={uuid} />
149     },
150 ];
151
152 const ReadOnlyField = withStyles(styles)(
153     (props: ({ label: string, input: {value: string} }) & WithStyles<CssRules> ) => (
154         <Grid item xs={12} data-cy="field">
155             <Typography className={props.classes.label}>
156                 {props.label}
157             </Typography>
158             <Typography className={props.classes.readOnlyValue} data-cy="value">
159                 {props.input.value}
160             </Typography>
161         </Grid>
162     )
163 );
164
165 export const UserProfilePanelRoot = withStyles(styles)(
166     class extends React.Component<UserProfilePanelRootProps> {
167         state = {
168             value: TABS.PROFILE,
169         };
170
171         componentDidMount() {
172             this.setState({ value: TABS.PROFILE});
173         }
174
175         render() {
176             return <Paper className={this.props.classes.root}>
177                 <Tabs value={this.state.value} onChange={this.handleChange} variant={"fullWidth"}>
178                     <Tab label={TABS.PROFILE} value={TABS.PROFILE} />
179                     <Tab label={TABS.GROUPS} value={TABS.GROUPS} />
180                     {this.props.isAdmin && <Tab label={TABS.ADMIN} value={TABS.ADMIN} />}
181                 </Tabs>
182                 {this.state.value === TABS.PROFILE &&
183                     <CardContent>
184                         <form onSubmit={this.props.handleSubmit} data-cy="profile-form">
185                             <Grid container spacing={24}>
186                                 <Grid item className={this.props.classes.gridItem} sm={6} xs={12} data-cy="firstName">
187                                     <Field
188                                         label="First name"
189                                         name="firstName"
190                                         component={ReadOnlyField as any}
191                                         disabled
192                                     />
193                                 </Grid>
194                                 <Grid item className={this.props.classes.gridItem} sm={6} xs={12} data-cy="lastName">
195                                     <Field
196                                         label="Last name"
197                                         name="lastName"
198                                         component={ReadOnlyField as any}
199                                         disabled
200                                     />
201                                 </Grid>
202                                 <Grid item className={this.props.classes.gridItem} sm={6} xs={12} data-cy="email">
203                                     <Field
204                                         label="E-mail"
205                                         name="email"
206                                         component={ReadOnlyField as any}
207                                         disabled
208                                     />
209                                 </Grid>
210                                 <Grid item className={this.props.classes.gridItem} sm={6} xs={12} data-cy="username">
211                                     <Field
212                                         label="Username"
213                                         name="username"
214                                         component={ReadOnlyField as any}
215                                         disabled
216                                     />
217                                 </Grid>
218                                 <Grid item className={this.props.classes.gridItem} sm={6} xs={12}>
219                                     <Field
220                                         label="Organization"
221                                         name="prefs.profile.organization"
222                                         component={TextField as any}
223                                         disabled={!this.props.isAdmin && !this.props.isSelf}
224                                     />
225                                 </Grid>
226                                 <Grid item className={this.props.classes.gridItem} sm={6} xs={12}>
227                                     <Field
228                                         label="E-mail at Organization"
229                                         name="prefs.profile.organization_email"
230                                         component={TextField as any}
231                                         disabled={!this.props.isAdmin && !this.props.isSelf}
232                                     />
233                                 </Grid>
234                                 <Grid item className={this.props.classes.gridItem} sm={6} xs={12}>
235                                     <InputLabel className={this.props.classes.label} htmlFor="prefs.profile.role">Role</InputLabel>
236                                     <Field
237                                         id="prefs.profile.role"
238                                         name="prefs.profile.role"
239                                         component={NativeSelectField as any}
240                                         items={RoleTypes}
241                                         disabled={!this.props.isAdmin && !this.props.isSelf}
242                                     />
243                                 </Grid>
244                                 <Grid item className={this.props.classes.gridItem} sm={6} xs={12}>
245                                     <Field
246                                         label="Website"
247                                         name="prefs.profile.website_url"
248                                         component={TextField as any}
249                                         disabled={!this.props.isAdmin && !this.props.isSelf}
250                                     />
251                                 </Grid>
252                                 <Grid item sm={12}>
253                                     <Grid container direction="row" justify="flex-end">
254                                         <Button color="primary" onClick={this.props.reset} disabled={this.props.isPristine}>Discard changes</Button>
255                                         <Button
256                                             color="primary"
257                                             variant="contained"
258                                             type="submit"
259                                             disabled={this.props.isPristine || this.props.invalid || this.props.submitting}>
260                                             Save changes
261                                         </Button>
262                                     </Grid>
263                                 </Grid>
264                             </Grid>
265                         </form >
266                     </CardContent>
267                 }
268                 {this.state.value === TABS.GROUPS &&
269                     <div className={this.props.classes.content}>
270                         <DataExplorer
271                                 id={USER_PROFILE_PANEL_ID}
272                                 data-cy="user-profile-groups-data-explorer"
273                                 onRowClick={noop}
274                                 onRowDoubleClick={noop}
275                                 onContextMenu={noop}
276                                 contextMenuColumn={false}
277                                 hideColumnSelector
278                                 hideSearchInput
279                                 paperProps={{
280                                     elevation: 0,
281                                 }}
282                                 dataTableDefaultView={
283                                     <DataTableDefaultView
284                                         icon={GroupsIcon}
285                                         messages={['Group list is empty.']} />
286                                 } />
287                     </div>}
288                 {this.props.isAdmin && this.state.value === TABS.ADMIN &&
289                     <Paper elevation={0} className={this.props.classes.adminRoot}>
290                         <Card elevation={0}>
291                             <CardContent>
292                                 <Grid container
293                                     direction="row"
294                                     justify={'flex-end'}
295                                     alignItems={'center'}>
296                                     <Grid item xs>
297                                         <Typography variant="h6" className={this.props.classes.title}>
298                                             Setup Account
299                                         </Typography>
300                                         <Typography variant="body1" className={this.props.classes.description}>
301                                             This button sets up a user. After setup, they will be able use Arvados. This dialog box also allows you to optionally set up a shell account for this user. The login name is automatically generated from the user's e-mail address.
302                                         </Typography>
303                                     </Grid>
304                                     <Grid item sm={'auto'} xs={12}>
305                                         <Button variant="contained"
306                                             color="primary"
307                                             onClick={() => {this.props.openSetupShellAccount(this.props.initialValues.uuid)}}
308                                             disabled={false}>
309                                             Setup Account
310                                         </Button>
311                                     </Grid>
312                                 </Grid>
313                             </CardContent>
314                         </Card>
315                         <Card elevation={0}>
316                             <CardContent>
317                                 <Grid container
318                                     direction="row"
319                                     justify={'flex-end'}
320                                     alignItems={'center'}>
321                                     <Grid item xs>
322                                         <Typography variant="h6" className={this.props.classes.title}>
323                                             Deactivate
324                                         </Typography>
325                                         <Typography variant="body1" className={this.props.classes.description}>
326                                             As an admin, you can deactivate and reset this user. This will remove all repository/VM permissions for the user. If you "setup" the user again, the user will have to sign the user agreement again. You may also want to reassign data ownership.
327                                         </Typography>
328                                     </Grid>
329                                     <Grid item sm={'auto'} xs={12}>
330                                         <Button variant="contained"
331                                             color="primary"
332                                             onClick={() => {this.props.openDeactivateDialog(this.props.initialValues.uuid)}}
333                                             disabled={false}>
334                                             Deactivate
335                                         </Button>
336                                     </Grid>
337                                 </Grid>
338                             </CardContent>
339                         </Card>
340                         <Card elevation={0}>
341                             <CardContent>
342                                 <Grid container
343                                     direction="row"
344                                     justify={'flex-end'}
345                                     alignItems={'center'}>
346                                     <Grid item xs>
347                                         <Typography variant="h6" className={this.props.classes.title}>
348                                             Log In
349                                         </Typography>
350                                         <Typography variant="body1" className={this.props.classes.description}>
351                                             As an admin, you can log in as this user. When you’ve finished, you will need to log out and log in again with your own account.
352                                         </Typography>
353                                     </Grid>
354                                     <Grid item sm={'auto'} xs={12}>
355                                         <Button variant="contained"
356                                             color="primary"
357                                             onClick={() => {this.props.loginAs(this.props.initialValues.uuid)}}
358                                             disabled={false}>
359                                             Log In
360                                         </Button>
361                                     </Grid>
362                                 </Grid>
363                             </CardContent>
364                         </Card>
365                     </Paper>}
366             </Paper >;
367         }
368
369         handleChange = (event: React.MouseEvent<HTMLElement>, value: number) => {
370             this.setState({ value });
371         }
372
373     }
374 );