19049: Make participant select read only when editing vm logins
[arvados-workbench2.git] / src / views-components / sharing-dialog / participant-select.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 { Autocomplete } from 'components/autocomplete/autocomplete';
7 import { connect, DispatchProp } from 'react-redux';
8 import { ServiceRepository } from 'services/services';
9 import { FilterBuilder } from '../../services/api/filter-builder';
10 import { debounce } from 'debounce';
11 import { ListItemText, Typography } from '@material-ui/core';
12 import { noop } from 'lodash/fp';
13 import { GroupClass, GroupResource } from 'models/group';
14 import { getUserDisplayName, UserResource } from 'models/user';
15 import { Resource, ResourceKind } from 'models/resource';
16 import { ListResults } from 'services/common-service/common-service';
17
18 export interface Participant {
19     name: string;
20     uuid: string;
21 }
22
23 type ParticipantResource = GroupResource | UserResource;
24
25 interface ParticipantSelectProps {
26     items: Participant[];
27     excludedParticipants?: string[];
28     label?: string;
29     autofocus?: boolean;
30     onlyPeople?: boolean;
31     disabled?: boolean;
32
33     onBlur?: (event: React.FocusEvent<HTMLInputElement>) => void;
34     onFocus?: (event: React.FocusEvent<HTMLInputElement>) => void;
35     onCreate?: (person: Participant) => void;
36     onDelete?: (index: number) => void;
37     onSelect?: (person: Participant) => void;
38 }
39
40 interface ParticipantSelectState {
41     value: string;
42     suggestions: ParticipantResource[];
43 }
44
45 const getDisplayName = (item: GroupResource | UserResource) => {
46     switch (item.kind) {
47         case ResourceKind.USER:
48             return getUserDisplayName(item, true, true);
49         case ResourceKind.GROUP:
50             return item.name + `(${`(${(item as Resource).uuid})`})`;
51         default:
52             return (item as Resource).uuid;
53     }
54 };
55
56 export const ParticipantSelect = connect()(
57     class ParticipantSelect extends React.Component<ParticipantSelectProps & DispatchProp, ParticipantSelectState> {
58         state: ParticipantSelectState = {
59             value: '',
60             suggestions: []
61         };
62
63         render() {
64             const { label = 'Share' } = this.props;
65
66             return (
67                 <Autocomplete
68                     label={label}
69                     value={this.state.value}
70                     items={this.props.items}
71                     suggestions={this.state.suggestions}
72                     autofocus={this.props.autofocus}
73                     onChange={this.handleChange}
74                     onCreate={this.handleCreate}
75                     onSelect={this.handleSelect}
76                     onDelete={this.props.onDelete && !this.props.disabled ? this.handleDelete : undefined}
77                     onFocus={this.props.onFocus}
78                     onBlur={this.props.onBlur}
79                     renderChipValue={this.renderChipValue}
80                     renderSuggestion={this.renderSuggestion}
81                     disabled={this.props.disabled}/>
82             );
83         }
84
85         renderChipValue(chipValue: Participant) {
86             const { name, uuid } = chipValue;
87             return name || uuid;
88         }
89
90         renderSuggestion(item: ParticipantResource) {
91             return (
92                 <ListItemText>
93                     <Typography noWrap>{getDisplayName(item)}</Typography>
94                 </ListItemText>
95             );
96         }
97
98         handleDelete = (_: Participant, index: number) => {
99             const { onDelete = noop } = this.props;
100             onDelete(index);
101         }
102
103         handleCreate = () => {
104             const { onCreate } = this.props;
105             if (onCreate) {
106                 this.setState({ value: '', suggestions: [] });
107                 onCreate({
108                     name: '',
109                     uuid: this.state.value,
110                 });
111             }
112         }
113
114         handleSelect = (selection: ParticipantResource) => {
115             const { uuid } = selection;
116             const { onSelect = noop } = this.props;
117             this.setState({ value: '', suggestions: [] });
118             onSelect({
119                 name: getDisplayName(selection),
120                 uuid,
121             });
122         }
123
124         handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
125             this.setState({ value: event.target.value }, this.getSuggestions);
126         }
127
128         getSuggestions = debounce(() => this.props.dispatch<any>(this.requestSuggestions), 500);
129
130         requestSuggestions = async (_: void, __: void, { userService, groupsService }: ServiceRepository) => {
131             const { value } = this.state;
132             const limit = 5; // FIXME: Does this provide a good UX?
133
134             const filterUsers = new FilterBuilder()
135                 .addILike('any', value)
136                 .addNotIn('uuid', this.props.excludedParticipants)
137                 .getFilters();
138             const userItems: ListResults<any> = await userService.list({ filters: filterUsers, limit, count: "none" });
139
140             const filterGroups = new FilterBuilder()
141                 .addNotIn('group_class', [GroupClass.PROJECT, GroupClass.FILTER])
142                 .addNotIn('uuid', this.props.excludedParticipants)
143                 .addILike('name', value)
144                 .getFilters();
145
146             const groupItems: ListResults<any> = await groupsService.list({ filters: filterGroups, limit, count: "none" });
147             this.setState({
148                 suggestions: this.props.onlyPeople
149                     ? userItems.items
150                     : userItems.items.concat(groupItems.items)
151             });
152         }
153     });