aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--server/sonar-web/package.json1
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/MetricSelect.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatePermissionsAddModalRenderer.tsx66
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/App-it.tsx11
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGatePermissionsAddModalRenderer-test.tsx12
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/QualityGatePermissionsAddModalRenderer-test.tsx.snap95
-rw-r--r--server/sonar-web/yarn.lock26
7 files changed, 144 insertions, 73 deletions
diff --git a/server/sonar-web/package.json b/server/sonar-web/package.json
index 0b31128098d..8f0bd65cd0a 100644
--- a/server/sonar-web/package.json
+++ b/server/sonar-web/package.json
@@ -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",
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/MetricSelect.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/MetricSelect.tsx
index ce6879efa16..7ff3701a4ba 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/MetricSelect.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/MetricSelect.tsx
@@ -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)}
/>
);
}
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatePermissionsAddModalRenderer.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatePermissionsAddModalRenderer.tsx
index ea2f2fbd70c..47c6190c801 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatePermissionsAddModalRenderer.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatePermissionsAddModalRenderer.tsx
@@ -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>
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/App-it.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/App-it.tsx
index 8aada4ac303..612607f9c19 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/App-it.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/App-it.tsx
@@ -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' }));
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGatePermissionsAddModalRenderer-test.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGatePermissionsAddModalRenderer-test.tsx
index 9e10425c133..da50d58ab68 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGatePermissionsAddModalRenderer-test.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGatePermissionsAddModalRenderer-test.tsx
@@ -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> = {}) {
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/QualityGatePermissionsAddModalRenderer-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/QualityGatePermissionsAddModalRenderer-test.tsx.snap
index 5994dbc36f5..f11d813ff89 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/QualityGatePermissionsAddModalRenderer-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/QualityGatePermissionsAddModalRenderer-test.tsx.snap
@@ -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>
diff --git a/server/sonar-web/yarn.lock b/server/sonar-web/yarn.lock
index b268bd105fb..0e24bb8630a 100644
--- a/server/sonar-web/yarn.lock
+++ b/server/sonar-web/yarn.lock
@@ -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"