@@ -127,6 +127,7 @@ | |||
"postcss-calc": "7.0.2", | |||
"postcss-custom-properties": "9.1.1", | |||
"prettier": "1.19.1", | |||
"react-select-event": "5.4.0", | |||
"remark": "11.0.2", | |||
"remark-react": "7", | |||
"ts-jest": "27.1.3", |
@@ -20,7 +20,7 @@ | |||
import { sortBy } from 'lodash'; | |||
import * as React from 'react'; | |||
import withMetricsContext from '../../../app/components/metrics/withMetricsContext'; | |||
import SelectLegacy from '../../../components/controls/SelectLegacy'; | |||
import Select from '../../../components/controls/Select'; | |||
import { getLocalizedMetricDomain, translate } from '../../../helpers/l10n'; | |||
import { Dict, Metric } from '../../../types/types'; | |||
import { getLocalizedMetricNameNoDiffMetric } from '../utils'; | |||
@@ -76,13 +76,13 @@ export class MetricSelectComponent extends React.PureComponent<Props> { | |||
}); | |||
return ( | |||
<SelectLegacy | |||
<Select | |||
className="text-middle quality-gate-metric-select" | |||
id="condition-metric" | |||
onChange={this.handleChange} | |||
options={optionsWithDomains} | |||
placeholder={translate('search.search_for_metrics')} | |||
value={metric && metric.key} | |||
value={optionsWithDomains.find(o => o.value === metric?.key)} | |||
/> | |||
); | |||
} |
@@ -17,10 +17,12 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { identity, omit } from 'lodash'; | |||
import * as React from 'react'; | |||
import { components, ControlProps, OptionProps, SingleValueProps } from 'react-select'; | |||
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 GroupIcon from '../../../components/icons/GroupIcon'; | |||
import Avatar from '../../../components/ui/Avatar'; | |||
import { translate } from '../../../helpers/l10n'; | |||
@@ -31,7 +33,7 @@ export interface QualityGatePermissionsAddModalRendererProps { | |||
onClose: () => void; | |||
onInputChange: (query: string) => void; | |||
onSubmit: (event: React.SyntheticEvent<HTMLFormElement>) => void; | |||
onSelection: (selection: UserBase | Group) => void; | |||
onSelection: (selection: Option) => void; | |||
submitting: boolean; | |||
loading: boolean; | |||
query: string; | |||
@@ -39,7 +41,7 @@ export interface QualityGatePermissionsAddModalRendererProps { | |||
selection?: UserBase | Group; | |||
} | |||
type Option = (UserBase | Group) & { value: string }; | |||
export type Option = (UserBase | Group) & { value: string }; | |||
export default function QualityGatePermissionsAddModalRenderer( | |||
props: QualityGatePermissionsAddModalRendererProps | |||
@@ -50,6 +52,8 @@ export default function QualityGatePermissionsAddModalRenderer( | |||
const noResultsText = translate('no_results'); | |||
const options = searchResults.map(r => ({ ...r, value: getValue(r) })); | |||
return ( | |||
<Modal contentLabel={header} onRequestClose={props.onClose}> | |||
<header className="modal-head"> | |||
@@ -59,22 +63,24 @@ export default function QualityGatePermissionsAddModalRenderer( | |||
<div className="modal-body"> | |||
<div className="modal-field"> | |||
<label>{translate('quality_gates.permissions.search')}</label> | |||
<SelectLegacy | |||
autoFocus={true} | |||
<Select | |||
className="Select-big" | |||
clearable={false} | |||
// disable default react-select filtering | |||
filterOptions={i => i} | |||
autoFocus={true} | |||
isClearable={false} | |||
isSearchable={true} | |||
placeholder="" | |||
isLoading={loading} | |||
noResultsText={noResultsText} | |||
filterOptions={identity} | |||
noOptionsMessage={() => noResultsText} | |||
onChange={props.onSelection} | |||
onInputChange={props.onInputChange} | |||
optionRenderer={optionRenderer} | |||
options={searchResults.map(r => ({ ...r, value: isUser(r) ? r.login : r.name }))} | |||
placeholder="" | |||
searchable={true} | |||
value={selection} | |||
valueRenderer={optionRenderer} | |||
components={{ | |||
Option: optionRenderer, | |||
SingleValue: singleValueRenderer, | |||
Control: controlRenderer | |||
}} | |||
options={options} | |||
value={options.find(o => o.value === (selection && getValue(selection)))} | |||
/> | |||
</div> | |||
</div> | |||
@@ -88,7 +94,11 @@ export default function QualityGatePermissionsAddModalRenderer( | |||
); | |||
} | |||
function optionRenderer(option: Option) { | |||
function getValue(option: UserBase | Group) { | |||
return isUser(option) ? option.login : option.name; | |||
} | |||
export function customOptions(option: Option) { | |||
return ( | |||
<> | |||
{isUser(option) ? ( | |||
@@ -101,3 +111,27 @@ function optionRenderer(option: Option) { | |||
</> | |||
); | |||
} | |||
function optionRenderer(props: OptionProps<Option, false>) { | |||
return ( | |||
<components.Option {...props} className="Select-option"> | |||
{customOptions(props.data)} | |||
</components.Option> | |||
); | |||
} | |||
function singleValueRenderer(props: SingleValueProps<Option>) { | |||
return ( | |||
<components.SingleValue {...props} className="Select-value-label"> | |||
{customOptions(props.data)} | |||
</components.SingleValue> | |||
); | |||
} | |||
function controlRenderer(props: ControlProps<Option, false>) { | |||
return ( | |||
<components.Control {...omit(props, ['children'])} className="abs-height-100 Select-control"> | |||
{props.children} | |||
</components.Control> | |||
); | |||
} |
@@ -19,6 +19,7 @@ | |||
*/ | |||
import { screen, waitFor, within } from '@testing-library/react'; | |||
import userEvent from '@testing-library/user-event'; | |||
import selectEvent from 'react-select-event'; | |||
import { QualityGatesServiceMock } from '../../../../api/mocks/QualityGatesServiceMock'; | |||
import { mockAppState } from '../../../../helpers/testMocks'; | |||
import { renderApp } from '../../../../helpers/testReactTestingUtils'; | |||
@@ -146,8 +147,7 @@ it('should be able to add a condition', async () => { | |||
let dialog = within(screen.getByRole('dialog')); | |||
await user.click(dialog.getByRole('radio', { name: 'quality_gates.conditions.new_code' })); | |||
await user.click(dialog.getByRole('combobox')); | |||
await user.click(dialog.getByRole('option', { name: 'Issues' })); | |||
await selectEvent.select(dialog.getByRole('textbox'), ['Issues']); | |||
await user.click(dialog.getByRole('textbox', { name: 'quality_gates.conditions.value' })); | |||
await user.keyboard('12{Enter}'); | |||
@@ -161,9 +161,7 @@ it('should be able to add a condition', async () => { | |||
await user.click(await screen.findByText('quality_gates.add_condition')); | |||
dialog = within(screen.getByRole('dialog')); | |||
await user.click(dialog.getByLabelText('quality_gates.conditions.fails_when')); | |||
await user.click(dialog.getByRole('option', { name: 'Info Issues' })); | |||
await selectEvent.select(dialog.getByRole('textbox'), ['Info Issues']); | |||
await user.click(dialog.getByRole('radio', { name: 'quality_gates.conditions.overall_code' })); | |||
await user.click(dialog.getByLabelText('quality_gates.conditions.operator')); | |||
@@ -183,8 +181,7 @@ it('should be able to add a condition', async () => { | |||
dialog = within(screen.getByRole('dialog')); | |||
await user.click(dialog.getByRole('radio', { name: 'quality_gates.conditions.overall_code' })); | |||
await user.click(dialog.getByLabelText('quality_gates.conditions.fails_when')); | |||
await user.click(dialog.getByRole('option', { name: 'Maintainability Rating' })); | |||
await selectEvent.select(dialog.getByRole('textbox'), ['Maintainability Rating']); | |||
await user.click(dialog.getByLabelText('quality_gates.conditions.value')); | |||
await user.click(dialog.getByText('B')); | |||
await user.click(dialog.getByRole('button', { name: 'quality_gates.add_condition' })); |
@@ -19,9 +19,9 @@ | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import SelectLegacy from '../../../../components/controls/SelectLegacy'; | |||
import { mockUserBase } from '../../../../helpers/mocks/users'; | |||
import QualityGatePermissionsAddModalRenderer, { | |||
customOptions, | |||
QualityGatePermissionsAddModalRendererProps | |||
} from '../QualityGatePermissionsAddModalRenderer'; | |||
@@ -38,12 +38,10 @@ it('should render correctly', () => { | |||
}); | |||
it('should render options correctly', () => { | |||
const wrapper = shallowRender(); | |||
const { optionRenderer = () => null } = wrapper.find(SelectLegacy).props(); | |||
expect(optionRenderer({ avatar: 'A', name: 'name', login: 'login' })).toMatchSnapshot('user'); | |||
expect(optionRenderer({ name: 'group name' })).toMatchSnapshot('group'); | |||
expect( | |||
customOptions({ avatar: 'A', name: 'name', login: 'login', value: 'login' }) | |||
).toMatchSnapshot('user'); | |||
expect(customOptions({ name: 'group name', value: 'group name' })).toMatchSnapshot('group'); | |||
}); | |||
function shallowRender(overrides: Partial<QualityGatePermissionsAddModalRendererProps> = {}) { |
@@ -24,20 +24,25 @@ exports[`should render correctly: default 1`] = ` | |||
<label> | |||
quality_gates.permissions.search | |||
</label> | |||
<SelectLegacy | |||
<Select | |||
autoFocus={true} | |||
className="Select-big" | |||
clearable={false} | |||
components={ | |||
Object { | |||
"Control": [Function], | |||
"Option": [Function], | |||
"SingleValue": [Function], | |||
} | |||
} | |||
filterOptions={[Function]} | |||
isClearable={false} | |||
isLoading={false} | |||
noResultsText="no_results" | |||
isSearchable={true} | |||
noOptionsMessage={[Function]} | |||
onChange={[MockFunction]} | |||
onInputChange={[MockFunction]} | |||
optionRenderer={[Function]} | |||
options={Array []} | |||
placeholder="" | |||
searchable={true} | |||
valueRenderer={[Function]} | |||
/> | |||
</div> | |||
</div> | |||
@@ -83,16 +88,23 @@ exports[`should render correctly: query and results 1`] = ` | |||
<label> | |||
quality_gates.permissions.search | |||
</label> | |||
<SelectLegacy | |||
<Select | |||
autoFocus={true} | |||
className="Select-big" | |||
clearable={false} | |||
components={ | |||
Object { | |||
"Control": [Function], | |||
"Option": [Function], | |||
"SingleValue": [Function], | |||
} | |||
} | |||
filterOptions={[Function]} | |||
isClearable={false} | |||
isLoading={false} | |||
noResultsText="no_results" | |||
isSearchable={true} | |||
noOptionsMessage={[Function]} | |||
onChange={[MockFunction]} | |||
onInputChange={[MockFunction]} | |||
optionRenderer={[Function]} | |||
options={ | |||
Array [ | |||
Object { | |||
@@ -106,8 +118,6 @@ exports[`should render correctly: query and results 1`] = ` | |||
] | |||
} | |||
placeholder="" | |||
searchable={true} | |||
valueRenderer={[Function]} | |||
/> | |||
</div> | |||
</div> | |||
@@ -153,25 +163,25 @@ exports[`should render correctly: selection 1`] = ` | |||
<label> | |||
quality_gates.permissions.search | |||
</label> | |||
<SelectLegacy | |||
<Select | |||
autoFocus={true} | |||
className="Select-big" | |||
clearable={false} | |||
components={ | |||
Object { | |||
"Control": [Function], | |||
"Option": [Function], | |||
"SingleValue": [Function], | |||
} | |||
} | |||
filterOptions={[Function]} | |||
isClearable={false} | |||
isLoading={false} | |||
noResultsText="no_results" | |||
isSearchable={true} | |||
noOptionsMessage={[Function]} | |||
onChange={[MockFunction]} | |||
onInputChange={[MockFunction]} | |||
optionRenderer={[Function]} | |||
options={Array []} | |||
placeholder="" | |||
searchable={true} | |||
value={ | |||
Object { | |||
"login": "userlogin", | |||
} | |||
} | |||
valueRenderer={[Function]} | |||
/> | |||
</div> | |||
</div> | |||
@@ -217,20 +227,25 @@ exports[`should render correctly: short query 1`] = ` | |||
<label> | |||
quality_gates.permissions.search | |||
</label> | |||
<SelectLegacy | |||
<Select | |||
autoFocus={true} | |||
className="Select-big" | |||
clearable={false} | |||
components={ | |||
Object { | |||
"Control": [Function], | |||
"Option": [Function], | |||
"SingleValue": [Function], | |||
} | |||
} | |||
filterOptions={[Function]} | |||
isClearable={false} | |||
isLoading={false} | |||
noResultsText="no_results" | |||
isSearchable={true} | |||
noOptionsMessage={[Function]} | |||
onChange={[MockFunction]} | |||
onInputChange={[MockFunction]} | |||
optionRenderer={[Function]} | |||
options={Array []} | |||
placeholder="" | |||
searchable={true} | |||
valueRenderer={[Function]} | |||
/> | |||
</div> | |||
</div> | |||
@@ -276,25 +291,25 @@ exports[`should render correctly: submitting 1`] = ` | |||
<label> | |||
quality_gates.permissions.search | |||
</label> | |||
<SelectLegacy | |||
<Select | |||
autoFocus={true} | |||
className="Select-big" | |||
clearable={false} | |||
components={ | |||
Object { | |||
"Control": [Function], | |||
"Option": [Function], | |||
"SingleValue": [Function], | |||
} | |||
} | |||
filterOptions={[Function]} | |||
isClearable={false} | |||
isLoading={false} | |||
noResultsText="no_results" | |||
isSearchable={true} | |||
noOptionsMessage={[Function]} | |||
onChange={[MockFunction]} | |||
onInputChange={[MockFunction]} | |||
optionRenderer={[Function]} | |||
options={Array []} | |||
placeholder="" | |||
searchable={true} | |||
value={ | |||
Object { | |||
"login": "userlogin", | |||
} | |||
} | |||
valueRenderer={[Function]} | |||
/> | |||
</div> | |||
</div> |
@@ -2722,6 +2722,22 @@ __metadata: | |||
languageName: node | |||
linkType: hard | |||
"@testing-library/dom@npm:>=7": | |||
version: 8.12.0 | |||
resolution: "@testing-library/dom@npm:8.12.0" | |||
dependencies: | |||
"@babel/code-frame": ^7.10.4 | |||
"@babel/runtime": ^7.12.5 | |||
"@types/aria-query": ^4.2.0 | |||
aria-query: ^5.0.0 | |||
chalk: ^4.1.0 | |||
dom-accessibility-api: ^0.5.9 | |||
lz-string: ^1.4.4 | |||
pretty-format: ^27.0.2 | |||
checksum: 2bbf5fa5c1e883571c440ccee76c0568fa5153b43c097456dd7146797256687352bfca9db574e0e78a022ce14722a6acaaba5f680ee16b95e12405501713d34d | |||
languageName: node | |||
linkType: hard | |||
"@testing-library/jest-dom@npm:5.16.2": | |||
version: 5.16.2 | |||
resolution: "@testing-library/jest-dom@npm:5.16.2" | |||
@@ -3612,6 +3628,7 @@ __metadata: | |||
react-redux: 5.1.1 | |||
react-router: 3.2.6 | |||
react-select: 4.3.1 | |||
react-select-event: 5.4.0 | |||
react-select-legacy: "npm:react-select@1.2.1" | |||
react-virtualized: 9.22.3 | |||
redux: 4.1.2 | |||
@@ -10600,6 +10617,15 @@ __metadata: | |||
languageName: node | |||
linkType: hard | |||
"react-select-event@npm:5.4.0": | |||
version: 5.4.0 | |||
resolution: "react-select-event@npm:5.4.0" | |||
dependencies: | |||
"@testing-library/dom": ">=7" | |||
checksum: 244b1bdff5374b56a7d8156859a10dfd3191573ac419a8f91a64283cde48517df83c90f2df1891fc74bceb548d21ace9a9cb87e252bf2f83f78e305ea577af53 | |||
languageName: node | |||
linkType: hard | |||
"react-select-legacy@npm:react-select@1.2.1": | |||
version: 1.2.1 | |||
resolution: "react-select@npm:1.2.1" |