Parcourir la source

SONAR-16085 Change the bulk change quality profile select.

tags/9.4.0.54424
Mathieu Suen il y a 2 ans
Parent
révision
a39698d0cd

+ 16
- 14
server/sonar-web/src/main/js/apps/coding-rules/components/BulkChangeModal.tsx Voir le fichier

@@ -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')}

+ 9
- 4
server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/BulkChangeModal-test.tsx Voir le fichier

@@ -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'));

+ 12
- 6
server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/BulkChangeModal-test.tsx.snap Voir le fichier

@@ -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>

+ 71
- 16
server/sonar-web/src/main/js/components/controls/Select.tsx Voir le fichier

@@ -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',

+ 6
- 0
server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/Select-test.tsx.snap Voir le fichier

@@ -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],
}

+ 1
- 0
sonar-core/src/main/resources/org/sonar/l10n/core.properties Voir le fichier

@@ -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

Chargement…
Annuler
Enregistrer