From 21c6c132eecd67958d7342dff1b1119963ba9f92 Mon Sep 17 00:00:00 2001 From: Jeremy Davis Date: Wed, 26 Jun 2024 17:24:41 +0200 Subject: [PATCH] SONAR-22418 Migrate QualityGatePermissionsAddModal --- .../QualityGatePermissionsAddModal.tsx | 53 ++++++--- ...QualityGatePermissionsAddModalRenderer.tsx | 111 +++++------------- .../components/__tests__/QualityGate-it.tsx | 12 +- .../src/main/js/apps/quality-gates/utils.ts | 6 +- 4 files changed, 74 insertions(+), 108 deletions(-) diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatePermissionsAddModal.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatePermissionsAddModal.tsx index 865e0dee61d..3998b6d2507 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatePermissionsAddModal.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatePermissionsAddModal.tsx @@ -17,19 +17,15 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { LabelValueSelectOption } from 'design-system'; import { debounce } from 'lodash'; import * as React from 'react'; -import { Options } from 'react-select'; import { searchGroups, searchUsers } from '../../../api/quality-gates'; import { Group, SearchPermissionsParameters, isUser } from '../../../types/quality-gates'; import { QualityGate } from '../../../types/types'; import { UserBase } from '../../../types/users'; +import { QGPermissionOption } from '../utils'; import QualityGatePermissionsAddModalRenderer from './QualityGatePermissionsAddModalRenderer'; -type Option = UserBase | Group; -export type OptionWithValue = Option & { value: string }; - interface Props { onClose: () => void; onSubmit: (selection: UserBase | Group) => void; @@ -38,23 +34,25 @@ interface Props { } interface State { + loading: boolean; + options: Array; selection?: UserBase | Group; } const DEBOUNCE_DELAY = 250; export default class QualityGatePermissionsAddModal extends React.Component { - state: State = {}; + state: State = { + loading: false, + options: [], + }; constructor(props: Props) { super(props); this.handleSearch = debounce(this.handleSearch, DEBOUNCE_DELAY); } - handleSearch = ( - q: string, - resolve: (options: Options>) => void, - ) => { + handleSearch = (q: string) => { const { qualityGate } = this.props; const queryParams: SearchPermissionsParameters = { @@ -63,19 +61,34 @@ export default class QualityGatePermissionsAddModal extends React.Component - [...usersResponse.users, ...groupsResponse.groups].map((o) => ({ - value: o, - label: isUser(o) ? `${o.name} ${o.login}` : o.name, - })), + [...usersResponse.users, ...groupsResponse.groups].map( + (o) => + ({ + ...o, + value: isUser(o) ? o.login : o.name, + label: isUser(o) ? o.name ?? o.login : o.name, + }) as QGPermissionOption, + ), ) - .then(resolve) - .catch(() => resolve([])); + .then((options) => { + this.setState({ loading: false, options }); + }) + .catch(() => { + this.setState({ loading: false, options: [] }); + }); }; - handleSelection = ({ value }: LabelValueSelectOption) => { - this.setState({ selection: value }); + handleSelection = (selectionKey?: string) => { + this.setState(({ options }) => { + const selectedOption = selectionKey + ? options.find((o) => (isUser(o) ? o.login : o.name) === selectionKey) + : undefined; + return { selection: selectedOption }; + }); }; handleSubmit = (event: React.SyntheticEvent) => { @@ -88,13 +101,15 @@ export default class QualityGatePermissionsAddModal extends React.Component>) => void, - ) => void; + handleSearch: (q: string) => void; + loading: boolean; onClose: () => void; - onSelection: (selection: SingleValue>) => void; + onSelection: (selection: string) => void; onSubmit: (event: React.SyntheticEvent) => void; + options: QGPermissionOption[]; selection?: UserBase | UserGroup; submitting: boolean; } @@ -52,11 +43,9 @@ const USER_SELECT_INPUT_ID = 'quality-gate-permissions-add-modal-select-input'; export default function QualityGatePermissionsAddModalRenderer( props: Readonly, ) { - const { selection, submitting } = props; + const { loading, options, selection, submitting } = props; - const renderedSelection = React.useMemo(() => { - return ; - }, [selection]); + const selectValue = selection && isUser(selection) ? selection.login : selection?.name; return ( - - translate('no_results')} - onChange={props.onSelection} - loadOptions={props.handleSearch} - getOptionValue={({ value }: LabelValueSelectOption) => - isUser(value) ? value.login : value.name - } - controlLabel={renderedSelection} - components={{ - Option, - }} - /> - + labelNotFound={translate('select.search.noMatches')} + onChange={props.onSelection} + onSearch={props.handleSearch} + optionComponent={OptionRenderer} + value={selectValue} + /> } primaryButton={ @@ -98,60 +78,27 @@ export default function QualityGatePermissionsAddModalRenderer( ); } -function OptionRenderer({ - option, - small = false, -}: Readonly<{ - option?: UserBase | UserGroup; - small?: boolean; -}>) { +function OptionRenderer(option: Readonly) { if (!option) { return null; } return ( - <> +
{isUser(option) ? ( <> - - + +
{option.name} +
{option.login} - +
) : ( <> - + {option.name} )} - - ); -} - -function Option< - Option extends LabelValueSelectOption, - IsMulti extends boolean = false, - Group extends GroupBase
); } diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGate-it.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGate-it.tsx index f7552d13c32..f37c00c96a1 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGate-it.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGate-it.tsx @@ -735,7 +735,7 @@ describe('The Permissions section', () => { }); await user.click(grantPermissionButton); const popup = screen.getByRole('dialog'); - const searchUserInput = within(popup).getByRole('combobox', { + const searchUserInput = within(popup).getByRole('searchbox', { name: 'quality_gates.permissions.search', }); expect(searchUserInput).toBeInTheDocument(); @@ -744,7 +744,7 @@ describe('The Permissions section', () => { }); expect(addUserButton).toBeDisabled(); await user.click(searchUserInput); - await user.click(screen.getByText('userlogin')); + await user.click(screen.getByRole('option', { name: 'userlogin' })); expect(addUserButton).toBeEnabled(); await user.click(addUserButton); expect(screen.getByText('userlogin')).toBeInTheDocument(); @@ -784,14 +784,14 @@ describe('The Permissions section', () => { }); await user.click(grantPermissionButton); const popup = screen.getByRole('dialog'); - const searchUserInput = within(popup).getByRole('combobox', { + const searchUserInput = within(popup).getByRole('searchbox', { name: 'quality_gates.permissions.search', }); const addUserButton = screen.getByRole('button', { name: 'add_verb', }); await user.click(searchUserInput); - await user.click(within(popup).getByLabelText('Foo')); + await user.click(within(popup).getByRole('option', { name: 'Foo Foo' })); await user.click(addUserButton); expect(screen.getByText('Foo')).toBeInTheDocument(); @@ -817,12 +817,12 @@ describe('The Permissions section', () => { }); await user.click(grantPermissionButton); const popup = screen.getByRole('dialog'); - const searchUserInput = within(popup).getByRole('combobox', { + const searchUserInput = within(popup).getByRole('searchbox', { name: 'quality_gates.permissions.search', }); await user.click(searchUserInput); - expect(screen.getByText('no_results')).toBeInTheDocument(); + expect(screen.getByText('select.search.noMatches')).toBeInTheDocument(); }); }); diff --git a/server/sonar-web/src/main/js/apps/quality-gates/utils.ts b/server/sonar-web/src/main/js/apps/quality-gates/utils.ts index fe1cff63906..c971c53b396 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/utils.ts +++ b/server/sonar-web/src/main/js/apps/quality-gates/utils.ts @@ -21,7 +21,8 @@ import { sortBy } from 'lodash'; import { MetricKey } from '~sonar-aligned/types/metrics'; import { getLocalizedMetricName } from '../../helpers/l10n'; import { isDiffMetric } from '../../helpers/measures'; -import { CaycStatus, Condition, Dict, Metric, QualityGate } from '../../types/types'; +import { CaycStatus, Condition, Dict, Group, Metric, QualityGate } from '../../types/types'; +import { UserBase } from '../../types/users'; interface GroupedByMetricConditions { caycConditions: Condition[]; @@ -44,6 +45,9 @@ type UnoptimizedCaycMetricKeys = type AllCaycMetricKeys = OptimizedCaycMetricKeys | UnoptimizedCaycMetricKeys; +type UserOrGroup = UserBase | Group; +export type QGPermissionOption = UserOrGroup & { label: string; value: string }; + const COMMON_CONDITIONS: Record< CommonCaycMetricKeys, Condition & { shouldRenderOperator?: boolean } -- 2.39.5