1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
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';
18 export interface Participant {
23 type ParticipantResource = GroupResource | UserResource;
25 interface ParticipantSelectProps {
31 onBlur?: (event: React.FocusEvent<HTMLInputElement>) => void;
32 onFocus?: (event: React.FocusEvent<HTMLInputElement>) => void;
33 onCreate?: (person: Participant) => void;
34 onDelete?: (index: number) => void;
35 onSelect?: (person: Participant) => void;
38 interface ParticipantSelectState {
40 suggestions: ParticipantResource[];
43 const getDisplayName = (item: GroupResource | UserResource) => {
45 case ResourceKind.USER:
46 return getUserDisplayName(item, true);
47 case ResourceKind.GROUP:
50 return (item as Resource).uuid;
54 export const ParticipantSelect = connect()(
55 class ParticipantSelect extends React.Component<ParticipantSelectProps & DispatchProp, ParticipantSelectState> {
56 state: ParticipantSelectState = {
62 const { label = 'Share' } = this.props;
67 value={this.state.value}
68 items={this.props.items}
69 suggestions={this.state.suggestions}
70 autofocus={this.props.autofocus}
71 onChange={this.handleChange}
72 onCreate={this.handleCreate}
73 onSelect={this.handleSelect}
74 onDelete={this.handleDelete}
75 onFocus={this.props.onFocus}
76 onBlur={this.props.onBlur}
77 renderChipValue={this.renderChipValue}
78 renderSuggestion={this.renderSuggestion} />
82 renderChipValue(chipValue: Participant) {
83 const { name, uuid } = chipValue;
87 renderSuggestion(item: ParticipantResource) {
90 <Typography noWrap>{getDisplayName(item)}</Typography>
95 handleDelete = (_: Participant, index: number) => {
96 const { onDelete = noop } = this.props;
100 handleCreate = () => {
101 const { onCreate } = this.props;
103 this.setState({ value: '', suggestions: [] });
106 uuid: this.state.value,
111 handleSelect = (selection: ParticipantResource) => {
112 const { uuid } = selection;
113 const { onSelect = noop } = this.props;
114 this.setState({ value: '', suggestions: [] });
116 name: getDisplayName(selection),
121 handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
122 this.setState({ value: event.target.value }, this.getSuggestions);
125 getSuggestions = debounce(() => this.props.dispatch<any>(this.requestSuggestions), 500);
127 requestSuggestions = async (_: void, __: void, { userService, groupsService }: ServiceRepository) => {
128 const { value } = this.state;
129 const limit = 5; // FIXME: Does this provide a good UX?
131 const filterUsers = new FilterBuilder()
132 .addILike('any', value)
134 const userItems: ListResults<any> = await userService.list({ filters: filterUsers, limit, count: "none" });
136 const filterGroups = new FilterBuilder()
137 .addNotIn('group_class', [GroupClass.PROJECT, GroupClass.FILTER])
138 .addILike('name', value)
141 const groupItems: ListResults<any> = await groupsService.list({ filters: filterGroups, limit, count: "none" });
143 suggestions: this.props.onlyPeople
145 : userItems.items.concat(groupItems.items)