]> source.dussan.org Git - sonarqube.git/blob
42c028346480abcee2ac92503c47e8c9209020ee
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2023 SonarSource SA
4  * mailto:info AT sonarsource DOT com
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 3 of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19  */
20 import {
21   ButtonPrimary,
22   FormField,
23   GenericAvatar,
24   LabelValueSelectOption,
25   Modal,
26   Note,
27   SearchSelectDropdown,
28   UserGroupIcon,
29 } from 'design-system';
30 import * as React from 'react';
31 import { GroupBase, OptionProps, Options, SingleValue, components } from 'react-select';
32 import Avatar from '../../../components/ui/Avatar';
33 import { translate } from '../../../helpers/l10n';
34 import { Group as UserGroup, isUser } from '../../../types/quality-gates';
35 import { UserBase } from '../../../types/users';
36
37 export interface QualityGatePermissionsAddModalRendererProps {
38   onClose: () => void;
39   handleSearch: (
40     q: string,
41     resolve: (options: Options<LabelValueSelectOption<UserBase | UserGroup>>) => void,
42   ) => void;
43   onSelection: (selection: SingleValue<LabelValueSelectOption<UserBase | UserGroup>>) => void;
44   selection?: UserBase | UserGroup;
45   onSubmit: (event: React.SyntheticEvent<HTMLFormElement>) => void;
46   submitting: boolean;
47 }
48
49 const FORM_ID = 'quality-gate-permissions-add-modal';
50 const USER_SELECT_INPUT_ID = 'quality-gate-permissions-add-modal-select-input';
51
52 export default function QualityGatePermissionsAddModalRenderer(
53   props: Readonly<QualityGatePermissionsAddModalRendererProps>,
54 ) {
55   const { selection, submitting } = props;
56
57   const renderedSelection = React.useMemo(() => {
58     return <OptionRenderer option={selection} small />;
59   }, [selection]);
60
61   return (
62     <Modal
63       onClose={props.onClose}
64       headerTitle={translate('quality_gates.permissions.grant')}
65       body={
66         <form onSubmit={props.onSubmit} id={FORM_ID}>
67           <FormField
68             label={translate('quality_gates.permissions.search')}
69             htmlFor={USER_SELECT_INPUT_ID}
70           >
71             <SearchSelectDropdown
72               className="sw-mb-2"
73               controlAriaLabel={translate('quality_gates.permissions.search')}
74               inputId={USER_SELECT_INPUT_ID}
75               autoFocus
76               isClearable={false}
77               placeholder=""
78               defaultOptions
79               noOptionsMessage={() => translate('no_results')}
80               onChange={props.onSelection}
81               loadOptions={props.handleSearch}
82               getOptionValue={({ value }) => (isUser(value) ? value.login : value.name)}
83               controlLabel={renderedSelection}
84               components={{
85                 Option,
86               }}
87             />
88           </FormField>
89         </form>
90       }
91       primaryButton={
92         <ButtonPrimary disabled={!selection || submitting} type="submit" form={FORM_ID}>
93           {translate('add_verb')}
94         </ButtonPrimary>
95       }
96       secondaryButtonLabel={translate('cancel')}
97     />
98   );
99 }
100
101 function OptionRenderer({
102   option,
103   small = false,
104 }: Readonly<{
105   option?: UserBase | UserGroup;
106   small?: boolean;
107 }>) {
108   if (!option) {
109     return null;
110   }
111   return (
112     <>
113       {isUser(option) ? (
114         <>
115           <Avatar
116             className={small ? 'sw-my-1/2' : ''}
117             hash={option.avatar}
118             name={option.name}
119             size={small ? 'xs' : 'sm'}
120           />
121           <span className="sw-ml-2">
122             <strong className="sw-body-sm-highlight sw-mr-1">{option.name}</strong>
123             <Note>{option.login}</Note>
124           </span>
125         </>
126       ) : (
127         <>
128           <GenericAvatar
129             className={small ? 'sw-my-1/2' : ''}
130             Icon={UserGroupIcon}
131             name={option.name}
132             size={small ? 'xs' : 'sm'}
133           />
134           <strong className="sw-body-sm-highlight sw-ml-2">{option.name}</strong>
135         </>
136       )}
137     </>
138   );
139 }
140
141 function Option<
142   Option extends LabelValueSelectOption<UserBase | UserGroup>,
143   IsMulti extends boolean = false,
144   Group extends GroupBase<Option> = GroupBase<Option>,
145 >(props: OptionProps<Option, IsMulti, Group>) {
146   const {
147     data: { value },
148   } = props;
149
150   return (
151     <components.Option {...props}>
152       <div className="sw-flex sw-items-center">
153         <OptionRenderer option={value} />
154       </div>
155     </components.Option>
156   );
157 }