aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMathieu Suen <mathieu.suen@sonarsource.com>2022-03-23 12:16:35 +0100
committersonartech <sonartech@sonarsource.com>2022-03-28 20:02:52 +0000
commita39698d0cd444d4e9c56fafb8da4171a3e3454bf (patch)
treebb3d65ebf204fbf435b817a973a29216a9c1e7fd
parentd17de865f6d8e4bc119c5e397e42e7eb5c642b0b (diff)
downloadsonarqube-a39698d0cd444d4e9c56fafb8da4171a3e3454bf.tar.gz
sonarqube-a39698d0cd444d4e9c56fafb8da4171a3e3454bf.zip
SONAR-16085 Change the bulk change quality profile select.
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/components/BulkChangeModal.tsx30
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/BulkChangeModal-test.tsx13
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/BulkChangeModal-test.tsx.snap18
-rw-r--r--server/sonar-web/src/main/js/components/controls/Select.tsx87
-rw-r--r--server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/Select-test.tsx.snap6
-rw-r--r--sonar-core/src/main/resources/org/sonar/l10n/core.properties1
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