diff options
author | Mathieu Suen <mathieu.suen@sonarsource.com> | 2022-03-23 12:16:35 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2022-03-28 20:02:52 +0000 |
commit | a39698d0cd444d4e9c56fafb8da4171a3e3454bf (patch) | |
tree | bb3d65ebf204fbf435b817a973a29216a9c1e7fd | |
parent | d17de865f6d8e4bc119c5e397e42e7eb5c642b0b (diff) | |
download | sonarqube-a39698d0cd444d4e9c56fafb8da4171a3e3454bf.tar.gz sonarqube-a39698d0cd444d4e9c56fafb8da4171a3e3454bf.zip |
SONAR-16085 Change the bulk change quality profile select.
6 files changed, 115 insertions, 40 deletions
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/BulkChangeModal.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/BulkChangeModal.tsx index 700e29367d7..32c3b9b691d 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/BulkChangeModal.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/BulkChangeModal.tsx @@ -22,7 +22,7 @@ import { bulkActivateRules, bulkDeactivateRules, Profile } from '../../../api/qu import withLanguagesContext from '../../../app/components/languages/withLanguagesContext'; import { ResetButtonLink, SubmitButton } from '../../../components/controls/buttons'; import Modal from '../../../components/controls/Modal'; -import SelectLegacy from '../../../components/controls/SelectLegacy'; +import Select from '../../../components/controls/Select'; import { Alert } from '../../../components/ui/Alert'; import { translate, translateWithParameters } from '../../../helpers/l10n'; import { formatMeasure } from '../../../helpers/measures'; @@ -49,7 +49,7 @@ interface ActivationResult { interface State { finished: boolean; results: ActivationResult[]; - selectedProfiles: any[]; + selectedProfiles: Profile[]; submitting: boolean; } @@ -63,7 +63,7 @@ export class BulkChangeModal extends React.PureComponent<Props, State> { const selectedProfiles = []; const availableProfiles = this.getAvailableQualityProfiles(props); if (availableProfiles.length === 1) { - selectedProfiles.push(availableProfiles[0].key); + selectedProfiles.push(availableProfiles[0]); } this.state = { finished: false, results: [], selectedProfiles, submitting: false }; @@ -77,8 +77,7 @@ export class BulkChangeModal extends React.PureComponent<Props, State> { this.mounted = false; } - handleProfileSelect = (options: { value: string }[]) => { - const selectedProfiles = options.map(option => option.value); + handleProfileSelect = (selectedProfiles: Profile[]) => { this.setState({ selectedProfiles }); }; @@ -116,7 +115,7 @@ export class BulkChangeModal extends React.PureComponent<Props, State> { // otherwise take all profiles selected in the dropdown const profiles: string[] = this.props.profile ? [this.props.profile.key] - : this.state.selectedProfiles; + : this.state.selectedProfiles.map(p => p.key); for (const profile of profiles) { looper = looper @@ -180,15 +179,18 @@ export class BulkChangeModal extends React.PureComponent<Props, State> { renderProfileSelect = () => { const profiles = this.getAvailableQualityProfiles(); - const options = profiles.map(profile => ({ - label: `${profile.name} - ${profile.languageName}`, - value: profile.key - })); + return ( - <SelectLegacy - multi={true} + <Select + aria-labelledby="coding-rules-bulk-change-profile-header" + isMulti={true} + isClearable={false} + isSearchable={true} + noOptionsMessage={() => translate('coding_rules.bulk_change.no_quality_profile')} + getOptionLabel={profile => `${profile.name} - ${profile.languageName}`} + getOptionValue={profile => profile.key} onChange={this.handleProfileSelect} - options={options} + options={profiles} value={this.state.selectedProfiles} /> ); @@ -220,7 +222,7 @@ export class BulkChangeModal extends React.PureComponent<Props, State> { {!this.state.finished && !this.state.submitting && ( <div className="modal-field"> <h3> - <label htmlFor="coding-rules-bulk-change-profile"> + <label id="coding-rules-bulk-change-profile-header"> {action === 'activate' ? translate('coding_rules.activate_in') : translate('coding_rules.deactivate_in')} diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/BulkChangeModal-test.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/BulkChangeModal-test.tsx index 2c3d43654be..893553a0d52 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/BulkChangeModal-test.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/BulkChangeModal-test.tsx @@ -56,18 +56,23 @@ it('should pre-select a profile if only 1 is available', () => { language: 'js' }); const wrapper = shallowRender({ profile: undefined, referencedProfiles: { foo: profile } }); - expect(wrapper.state().selectedProfiles).toEqual(['foo']); + expect(wrapper.state().selectedProfiles).toEqual([profile]); }); it('should handle profile selection', () => { const wrapper = shallowRender(); - wrapper.instance().handleProfileSelect([{ value: 'foo' }, { value: 'bar' }]); - expect(wrapper.state().selectedProfiles).toEqual(['foo', 'bar']); + const profiles = [mockQualityProfile({ name: 'foo' }), mockQualityProfile({ name: 'bar' })]; + wrapper.instance().handleProfileSelect(profiles); + expect(wrapper.state().selectedProfiles).toEqual(profiles); }); it('should handle form submission', async () => { const wrapper = shallowRender({ profile: undefined }); - wrapper.setState({ selectedProfiles: ['foo', 'bar'] }); + const profiles = [ + mockQualityProfile({ name: 'foo', key: 'foo' }), + mockQualityProfile({ name: 'bar', key: 'bar' }) + ]; + wrapper.setState({ selectedProfiles: profiles }); // Activate. submit(wrapper.find('form')); diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/BulkChangeModal-test.tsx.snap b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/BulkChangeModal-test.tsx.snap index cef2f30066b..de47bfb5ebe 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/BulkChangeModal-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/BulkChangeModal-test.tsx.snap @@ -24,7 +24,7 @@ exports[`should render correctly: deactivate action 1`] = ` > <h3> <label - htmlFor="coding-rules-bulk-change-profile" + id="coding-rules-bulk-change-profile" > coding_rules.deactivate_in </label> @@ -79,7 +79,7 @@ exports[`should render correctly: default 1`] = ` > <h3> <label - htmlFor="coding-rules-bulk-change-profile" + id="coding-rules-bulk-change-profile" > coding_rules.activate_in </label> @@ -166,13 +166,19 @@ exports[`should render correctly: no profile pre-selected 1`] = ` > <h3> <label - htmlFor="coding-rules-bulk-change-profile" + id="coding-rules-bulk-change-profile" > coding_rules.activate_in </label> </h3> - <SelectLegacy - multi={true} + <Select + aria-labelledby="coding-rules-bulk-change-profile" + getOptionLabel={[Function]} + getOptionValue={[Function]} + isClearable={false} + isMulti={true} + isSearchable={true} + noOptionsMessage={[Function]} onChange={[Function]} options={Array []} value={Array []} @@ -234,7 +240,7 @@ exports[`should render correctly: results 1`] = ` > <h3> <label - htmlFor="coding-rules-bulk-change-profile" + id="coding-rules-bulk-change-profile" > coding_rules.activate_in </label> diff --git a/server/sonar-web/src/main/js/components/controls/Select.tsx b/server/sonar-web/src/main/js/components/controls/Select.tsx index 91d04865a1e..131050a156a 100644 --- a/server/sonar-web/src/main/js/components/controls/Select.tsx +++ b/server/sonar-web/src/main/js/components/controls/Select.tsx @@ -20,6 +20,7 @@ import styled from '@emotion/styled'; import * as React from 'react'; import ReactSelect, { GroupTypeBase, IndicatorProps, Props, StylesConfig } from 'react-select'; +import { MultiValueRemoveProps } from 'react-select/src/components/MultiValue'; import { colors, others, sizes, zIndexes } from '../../app/theme'; const ArrowSpan = styled.span` @@ -40,13 +41,18 @@ export default function Select< return <ArrowSpan {...innerProps} />; } + function MultiValueRemove(props: MultiValueRemoveProps<Option, Group>) { + return <div {...props.innerProps}>×</div>; + } + return ( <ReactSelect {...props} styles={selectStyle<Option, IsMulti, Group>()} components={{ ...props.components, - DropdownIndicator + DropdownIndicator, + MultiValueRemove }} /> ); @@ -63,13 +69,14 @@ export function selectStyle< display: 'inline-block', verticalAlign: 'middle', fontSize: '12px', - textAlign: 'left' + textAlign: 'left', + width: '100%' }), control: () => ({ position: 'relative', display: 'table', width: '100%', - height: `${sizes.controlHeight}`, + minHeight: `${sizes.controlHeight}`, lineHeight: `calc(${sizes.controlHeight} - 2px)`, border: `1px solid ${colors.gray80}`, borderCollapse: 'separate', @@ -94,20 +101,32 @@ export function selectStyle< textOverflow: 'ellipsis', whiteSpace: 'nowrap' }), - valueContainer: () => ({ - bottom: 0, - left: 0, - lineHeight: '23px', - paddingLeft: '8px', - paddingRight: '24px', - position: 'absolute', - right: 0, - top: 0, - maxWidth: '100%', - overflow: 'hidden', - textOverflow: 'ellipsis', - whiteSpace: 'nowrap' + input: () => ({ + paddingLeft: '1px' }), + valueContainer: (_provided, state) => { + if (state.hasValue && state.isMulti) { + return { + lineHeight: '23px', + paddingLeft: '1px' + }; + } + + return { + bottom: 0, + left: 0, + lineHeight: '23px', + paddingLeft: '8px', + paddingRight: '24px', + position: 'absolute', + right: 0, + top: 0, + maxWidth: '100%', + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap' + }; + }, indicatorsContainer: () => ({ cursor: 'pointer', display: 'table-cell', @@ -117,6 +136,39 @@ export function selectStyle< width: '20px', paddingRight: '5px' }), + multiValue: () => ({ + display: 'inline-block', + backgroundColor: 'rgba(0, 126, 255, 0.08)', + borderRadius: '2px', + border: '1px solid rgba(0, 126, 255, 0.24)', + color: '#333', + maxWidth: '200px', + fontSize: '12px', + lineHeight: '14px', + margin: '1px 4px 1px 1px', + verticalAlign: 'top' + }), + multiValueLabel: () => ({ + display: 'inline-block', + cursor: 'default', + padding: '2px 5px', + overflow: 'hidden', + marginRight: 'auto', + maxWidth: 'calc(200px - 28px)', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + verticalAlign: 'middle' + }), + multiValueRemove: () => ({ + order: '-1', + cursor: 'pointer', + borderLeft: '1px solid rgba(0, 126, 255, 0.24)', + verticalAlign: 'middle', + padding: '1px 5px', + fontSize: '12px', + lineHeight: '14px', + display: 'inline-block' + }), menu: () => ({ borderBottomRightRadius: '4px', borderBottomLeftRadius: '4px', @@ -139,6 +191,9 @@ export function selectStyle< padding: '5px 0', overflowY: 'auto' }), + placeholder: () => ({ + color: '#666' + }), option: (_provided, state) => ({ display: 'block', lineHeight: '20px', diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/Select-test.tsx.snap b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/Select-test.tsx.snap index 67f7fe54a30..e0a4f475588 100644 --- a/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/Select-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/Select-test.tsx.snap @@ -5,6 +5,7 @@ exports[`Select should render correctly: default 1`] = ` components={ Object { "DropdownIndicator": [Function], + "MultiValueRemove": [Function], } } defaultInputValue="" @@ -15,9 +16,14 @@ exports[`Select should render correctly: default 1`] = ` "container": [Function], "control": [Function], "indicatorsContainer": [Function], + "input": [Function], "menu": [Function], "menuList": [Function], + "multiValue": [Function], + "multiValueLabel": [Function], + "multiValueRemove": [Function], "option": [Function], + "placeholder": [Function], "singleValue": [Function], "valueContainer": [Function], } diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index 52f3b72c70c..8da222bc358 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -1760,6 +1760,7 @@ coding_rules.available_since=Available Since coding_rules.bulk_change=Bulk Change coding_rules.bulk_change.success={2} rule(s) changed in profile {0} - {1} coding_rules.bulk_change.warning={2} rule(s) changed, {3} rule(s) ignored in profile {0} - {1} +coding_rules.bulk_change.no_quality_profile=No quality profile. coding_rules.can_not_bulk_change=Bulk change is only available when you have a custom Quality Profile to target. You can create a customizable Quality Profile based on a built-in one by Copying or Extending it in the Quality Profiles list. coding_rules.can_not_deactivate=This rule is inherited and cannot be deactivated. coding_rules.change_details=Change Details of Quality Profile |