]> source.dussan.org Git - sonarqube.git/blob
963f7f8302d7e1b50c480cf19ce780be1804c4b2
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2024 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               defaultOptions
77               noOptionsMessage={() => translate('no_results')}
78               onChange={props.onSelection}
79               loadOptions={props.handleSearch}
80               getOptionValue={({ value }) => (isUser(value) ? value.login : value.name)}
81               controlLabel={renderedSelection}
82               components={{
83                 Option,
84               }}
85             />
86           </FormField>
87         </form>
88       }
89       primaryButton={
90         <ButtonPrimary disabled={!selection || submitting} type="submit" form={FORM_ID}>
91           {translate('add_verb')}
92         </ButtonPrimary>
93       }
94       secondaryButtonLabel={translate('cancel')}
95     />
96   );
97 }
98
99 function OptionRenderer({
100   option,
101   small = false,
102 }: Readonly<{
103   option?: UserBase | UserGroup;
104   small?: boolean;
105 }>) {
106   if (!option) {
107     return null;
108   }
109   return (
110     <>
111       {isUser(option) ? (
112         <>
113           <Avatar
114             className={small ? 'sw-my-1' : ''}
115             hash={option.avatar}
116             name={option.name}
117             size={small ? 'xs' : 'sm'}
118           />
119           <span className="sw-ml-2">
120             <strong className="sw-body-sm-highlight sw-mr-1">{option.name}</strong>
121             <Note>{option.login}</Note>
122           </span>
123         </>
124       ) : (
125         <>
126           <GenericAvatar
127             className={small ? 'sw-my-1' : ''}
128             Icon={UserGroupIcon}
129             name={option.name}
130             size={small ? 'xs' : 'sm'}
131           />
132           <strong className="sw-body-sm-highlight sw-ml-2">{option.name}</strong>
133         </>
134       )}
135     </>
136   );
137 }
138
139 function Option<
140   Option extends LabelValueSelectOption<UserBase | UserGroup>,
141   IsMulti extends boolean = false,
142   Group extends GroupBase<Option> = GroupBase<Option>,
143 >(props: OptionProps<Option, IsMulti, Group>) {
144   const {
145     data: { value },
146   } = props;
147
148   return (
149     <components.Option {...props}>
150       <div className="sw-flex sw-items-center">
151         <OptionRenderer option={value} />
152       </div>
153     </components.Option>
154   );
155 }