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