]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-16089 Remove select legacy from permission, projectActivity and projectManagement
authorRevanshu Paliwal <revanshu.paliwal@sonarsource.com>
Wed, 30 Mar 2022 12:35:03 +0000 (14:35 +0200)
committersonartech <sonartech@sonarsource.com>
Sat, 2 Apr 2022 20:02:34 +0000 (20:02 +0000)
server/sonar-web/src/main/js/apps/projects/filters/SearchableFilterFooter.tsx
server/sonar-web/src/main/js/apps/projects/filters/__tests__/SearchableFilterFooter-test.tsx
server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/SearchableFilterFooter-test.tsx.snap
server/sonar-web/src/main/js/apps/projectsManagement/BulkApplyTemplateModal.tsx
server/sonar-web/src/main/js/apps/projectsManagement/Search.tsx
server/sonar-web/src/main/js/apps/projectsManagement/__tests__/BulkApplyTemplateModal-test.tsx
server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Search-test.tsx
server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/BulkApplyTemplateModal-test.tsx.snap
server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/Search-test.tsx.snap

index f789c1fdb3deb710c1bfe5012ae9b1a104bb8d45..db7a854de1127a46fcd4101ad222c4ba21b028d3 100644 (file)
@@ -18,7 +18,8 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as React from 'react';
-import SelectLegacy from '../../../components/controls/SelectLegacy';
+import { components, InputProps, OptionProps } from 'react-select';
+import Select from '../../../components/controls/Select';
 import { translate } from '../../../helpers/l10n';
 import { Dict, RawQuery } from '../../../types/types';
 
@@ -39,19 +40,32 @@ export default class SearchableFilterFooter extends React.PureComponent<Props> {
     this.props.onQueryChange({ [this.props.property]: urlOptions });
   };
 
+  optionRenderer = (props: OptionProps<{ value: string }, false>) => (
+    <components.Option
+      {...props}
+      className={'Select-option ' + (props.isFocused ? 'is-focused' : '')}
+    />
+  );
+
+  inputRenderer = (props: InputProps) => (
+    <components.Input {...props} className="it__searchable-footer-select-input" />
+  );
+
   render() {
     return (
       <div className="search-navigator-facet-footer projects-facet-footer">
-        <SelectLegacy
+        <Select
           className="input-super-large"
-          clearable={false}
           isLoading={this.props.isLoading}
           onChange={this.handleOptionChange}
           onInputChange={this.props.onInputChange}
           onOpen={this.props.onOpen}
+          components={{
+            Option: this.optionRenderer,
+            Input: this.inputRenderer
+          }}
           options={this.props.options}
           placeholder={translate('search_verb')}
-          searchable={true}
         />
       </div>
     );
index 6d4d302434775816723e5a1fcea8003af6142eb1..2568a0ced0e231655a0d601a06177213c2724269 100644 (file)
  */
 import { shallow } from 'enzyme';
 import * as React from 'react';
-import SelectLegacy from '../../../../components/controls/SelectLegacy';
+import { SelectComponentsProps } from 'react-select/src/Select';
+import {
+  mockReactSelectInputProps,
+  mockReactSelectOptionProps
+} from '../../../../helpers/mocks/react-select';
+import Select from '../../../../components/controls/Select';
 import SearchableFilterFooter from '../SearchableFilterFooter';
 
 const options = [
@@ -37,7 +42,7 @@ it('should render items without the ones in the facet', () => {
       query={{ languages: ['java'] }}
     />
   );
-  expect(wrapper.find(SelectLegacy).prop('options')).toMatchSnapshot();
+  expect(wrapper.find<SelectComponentsProps>(Select).props().options).toMatchSnapshot();
 });
 
 it('should properly handle a change of the facet value', () => {
@@ -50,6 +55,25 @@ it('should properly handle a change of the facet value', () => {
       query={{ languages: ['java'] }}
     />
   );
-  (wrapper.find(SelectLegacy).prop('onChange') as Function)({ value: 'js' });
+  wrapper.find(Select).simulate('change', { value: 'js' });
   expect(onQueryChange).toBeCalledWith({ languages: 'java,js' });
 });
+
+it('renders optionrenderer and inputrenderer', () => {
+  const wrapper = shallow<SearchableFilterFooter>(
+    <SearchableFilterFooter
+      onQueryChange={jest.fn()}
+      options={options}
+      property="languages"
+      query={{ languages: ['java'] }}
+    />
+  );
+  const OptionRendererer = wrapper.instance().optionRenderer;
+  const InputRendererer = wrapper.instance().inputRenderer;
+  expect(
+    shallow(<OptionRendererer {...mockReactSelectOptionProps({ value: 'val' })} />)
+  ).toMatchSnapshot('option renderer');
+  expect(shallow(<InputRendererer {...mockReactSelectInputProps()} />)).toMatchSnapshot(
+    'input renderer'
+  );
+});
index 6cf27012dfd01423836af3a9ad17785d5f12daf0..1da4f7f62566866bcc04e90fadac924f23478d72 100644 (file)
@@ -1,5 +1,22 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
+exports[`renders optionrenderer and inputrenderer: input renderer 1`] = `
+<Input
+  className="it__searchable-footer-select-input"
+/>
+`;
+
+exports[`renders optionrenderer and inputrenderer: option renderer 1`] = `
+<Option
+  className="Select-option "
+  data={
+    Object {
+      "value": "val",
+    }
+  }
+/>
+`;
+
 exports[`should render items without the ones in the facet 1`] = `
 Array [
   Object {
index d0a3a00fcce6a35a5b7371871cf2dc08d409e167..3ff638a18637cc0e64d3b70d89a8d13453f8b2ca 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as React from 'react';
+import { components, OptionProps } from 'react-select';
 import { bulkApplyTemplate, getPermissionTemplates } from '../../api/permissions';
 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 MandatoryFieldMarker from '../../components/ui/MandatoryFieldMarker';
 import MandatoryFieldsExplanation from '../../components/ui/MandatoryFieldsExplanation';
@@ -132,21 +133,34 @@ export default class BulkApplyTemplateModal extends React.PureComponent<Props, S
     </Alert>
   );
 
-  renderSelect = () => (
-    <div className="modal-field">
-      <label>
-        {translate('template')}
-        <MandatoryFieldMarker />
-      </label>
-      <SelectLegacy
-        clearable={false}
-        disabled={this.state.submitting}
-        onChange={this.handlePermissionTemplateChange}
-        options={this.state.permissionTemplates!.map(t => ({ label: t.name, value: t.id }))}
-        value={this.state.permissionTemplate}
-      />
-    </div>
-  );
+  renderSelect = () => {
+    const options =
+      this.state.permissionTemplates !== undefined
+        ? this.state.permissionTemplates.map(t => ({ label: t.name, value: t.id }))
+        : [];
+    return (
+      <div className="modal-field">
+        <label htmlFor="bulk-apply-template-input">
+          {translate('template')}
+          <MandatoryFieldMarker />
+        </label>
+        <Select
+          id="bulk-apply-template"
+          inputId="bulk-apply-template-input"
+          className="Select Select-value"
+          isDisabled={this.state.submitting}
+          onChange={this.handlePermissionTemplateChange}
+          components={{
+            Option: (props: OptionProps<{ value: string }, false>) => (
+              <components.Option {...props} className="Select-option" />
+            )
+          }}
+          options={options}
+          value={options.find(option => option.value === this.state.permissionTemplate)}
+        />
+      </div>
+    );
+  };
 
   render() {
     const { done, loading, permissionTemplates, submitting } = this.state;
index 81640c8c16491877206052cfad542fd07442d4b6..a9990336e4d68ac0384f66116a1788c2b16970df 100644 (file)
@@ -19,6 +19,7 @@
  */
 import { sortBy } from 'lodash';
 import * as React from 'react';
+import { components, OptionProps, SingleValueProps } from 'react-select';
 import { Project } from '../../api/components';
 import withAppStateContext from '../../app/components/app-state/withAppStateContext';
 import { Button } from '../../components/controls/buttons';
@@ -26,7 +27,7 @@ import Checkbox from '../../components/controls/Checkbox';
 import DateInput from '../../components/controls/DateInput';
 import HelpTooltip from '../../components/controls/HelpTooltip';
 import SearchBox from '../../components/controls/SearchBox';
-import SelectLegacy from '../../components/controls/SelectLegacy';
+import Select from '../../components/controls/Select';
 import QualifierIcon from '../../components/icons/QualifierIcon';
 import { translate } from '../../helpers/l10n';
 import { AppState } from '../../types/appstate';
@@ -107,6 +108,18 @@ export class Search extends React.PureComponent<Props, State> {
 
   handleVisibilityChange = ({ value }: { value: string }) => this.props.onVisibilityChanged(value);
 
+  optionRenderer = (props: OptionProps<{ value: string }, false>) => (
+    <components.Option {...props} className="Select-option">
+      {this.renderQualifierOption(props.data)}
+    </components.Option>
+  );
+
+  singleValueRenderer = (props: SingleValueProps<{ value: string }>) => (
+    <components.SingleValue {...props}>
+      {this.renderQualifierOption(props.data as { value: string; label: string })}
+    </components.SingleValue>
+  );
+
   renderCheckbox = () => {
     const isAllChecked =
       this.props.projects.length > 0 && this.props.selection.length === this.props.projects.length;
@@ -126,10 +139,10 @@ export class Search extends React.PureComponent<Props, State> {
   };
 
   renderQualifierOption = (option: { label: string; value: string }) => (
-    <span>
+    <div className="display-flex-center">
       <QualifierIcon className="little-spacer-right" qualifier={option.value} />
       {option.label}
-    </span>
+    </div>
   );
 
   renderQualifierFilter = () => {
@@ -139,38 +152,45 @@ export class Search extends React.PureComponent<Props, State> {
     }
     return (
       <td className="thin nowrap text-middle">
-        <SelectLegacy
-          className="input-medium"
-          clearable={false}
+        <Select
+          className="input-medium Select"
           disabled={!this.props.ready}
           name="projects-qualifier"
           onChange={this.handleQualifierChange}
-          optionRenderer={this.renderQualifierOption}
+          isSearchable={false}
+          components={{
+            Option: this.optionRenderer,
+            SingleValue: this.singleValueRenderer
+          }}
           options={this.getQualifierOptions()}
           searchable={false}
-          value={this.props.qualifiers}
-          valueRenderer={this.renderQualifierOption}
+          value={options.find(option => option.value === this.props.qualifiers)}
         />
       </td>
     );
   };
 
   renderVisibilityFilter = () => {
+    const options = [
+      { value: 'all', label: translate('visibility.both') },
+      { value: 'public', label: translate('visibility.public') },
+      { value: 'private', label: translate('visibility.private') }
+    ];
     return (
       <td className="thin nowrap text-middle">
-        <SelectLegacy
-          className="input-small"
-          clearable={false}
-          disabled={!this.props.ready}
+        <Select
+          className="input-small Select"
+          isDisabled={!this.props.ready}
           name="projects-visibility"
           onChange={this.handleVisibilityChange}
-          options={[
-            { value: 'all', label: translate('visibility.both') },
-            { value: 'public', label: translate('visibility.public') },
-            { value: 'private', label: translate('visibility.private') }
-          ]}
-          searchable={false}
-          value={this.props.visibility || 'all'}
+          components={{
+            Option: (props: OptionProps<{ value: string }, false>) => (
+              <components.Option {...props} className="Select-option" />
+            )
+          }}
+          options={options}
+          isSearchable={false}
+          value={options.find(option => option.value === (this.props.visibility || 'all'))}
         />
       </td>
     );
index c99ee490c310ea0daf9ba472776ecfb3e6c0ce7b..7791ca3b046bf0ef458352852da42a80fc029817 100644 (file)
@@ -19,6 +19,9 @@
  */
 import { shallow } from 'enzyme';
 import * as React from 'react';
+import { SelectComponentsProps } from 'react-select/src/Select';
+import { mockReactSelectOptionProps } from '../../../helpers/mocks/react-select';
+import Select from '../../../components/controls/Select';
 import { parseDate } from '../../../helpers/dates';
 import { click, waitAndUpdate } from '../../../helpers/testUtils';
 import BulkApplyTemplateModal, { Props } from '../BulkApplyTemplateModal';
@@ -99,6 +102,25 @@ it('bulk applies template to selected results', async () => {
   expect(wrapper).toMatchSnapshot();
 });
 
+it('renders optionrenderer', () => {
+  const wrapper = shallow(render({ qualifier: 'VW', selection: ['proj1', 'proj2'] }));
+  wrapper.setState({
+    loading: false,
+    permissionTemplate: 'foo',
+    permissionTemplates: [
+      { id: 'foo', name: 'Foo' },
+      { id: 'bar', name: 'Bar' }
+    ]
+  });
+  const wrapperSelect = wrapper.find<SelectComponentsProps>(Select);
+
+  const { Option } = wrapperSelect.props().components;
+
+  expect(<Option {...mockReactSelectOptionProps({ val: 'val' })} />).toMatchSnapshot(
+    'option renderer'
+  );
+});
+
 it('closes', () => {
   const onClose = jest.fn();
   const wrapper = shallow(render({ onClose }));
index b4e3f13b637ebf58d5d791c8f853ecbbafdaaf93..cefcf3afdef5a4a77f844b8d446f995bc0a0d886 100644 (file)
@@ -19,6 +19,7 @@
  */
 import { shallow } from 'enzyme';
 import * as React from 'react';
+import { mockReactSelectOptionProps } from '../../../helpers/mocks/react-select';
 import { mockAppState } from '../../../helpers/testMocks';
 import { click } from '../../../helpers/testUtils';
 import { Props, Search } from '../Search';
@@ -49,12 +50,26 @@ it('updates qualifier', () => {
     onQualifierChanged,
     appState: mockAppState({ qualifiers: ['TRK', 'VW', 'APP'] })
   });
-  wrapper.find('SelectLegacy[name="projects-qualifier"]').prop<Function>('onChange')({
+  wrapper.find('Select[name="projects-qualifier"]').simulate('change', {
     value: 'VW'
   });
   expect(onQualifierChanged).toBeCalledWith('VW');
 });
 
+it('renders optionrenderer and singlevaluerenderer', () => {
+  const wrapper = shallowRender({
+    appState: mockAppState({ qualifiers: ['TRK', 'VW', 'APP'] })
+  });
+  const OptionRendererer = wrapper.instance().optionRenderer;
+  const SingleValueRendererer = wrapper.instance().singleValueRenderer;
+  expect(
+    shallow(<OptionRendererer {...mockReactSelectOptionProps({ value: 'val' })} />)
+  ).toMatchSnapshot('option renderer');
+  expect(
+    shallow(<SingleValueRendererer {...mockReactSelectOptionProps({ value: 'val' })} />)
+  ).toMatchSnapshot('single value renderer');
+});
+
 it('selects provisioned', () => {
   const onProvisionedChanged = jest.fn();
   const wrapper = shallowRender({ onProvisionedChanged });
@@ -118,7 +133,7 @@ it('bulk applies permission template', () => {
 });
 
 function shallowRender(props?: { [P in keyof Props]?: Props[P] }) {
-  return shallow(
+  return shallow<Search>(
     <Search
       analyzedBefore={undefined}
       onAllDeselected={jest.fn()}
index 2dbf7ae81fbbb7e952390d8a08c06e5a5a613a43..473486e03c07f1dd180e947244059d2e5011c389 100644 (file)
@@ -59,13 +59,22 @@ exports[`bulk applies template to all results 2`] = `
     <div
       className="modal-field"
     >
-      <label>
+      <label
+        htmlFor="bulk-apply-template-input"
+      >
         template
         <MandatoryFieldMarker />
       </label>
-      <SelectLegacy
-        clearable={false}
-        disabled={false}
+      <Select
+        className="Select Select-value"
+        components={
+          Object {
+            "Option": [Function],
+          }
+        }
+        id="bulk-apply-template"
+        inputId="bulk-apply-template-input"
+        isDisabled={false}
         onChange={[Function]}
         options={
           Array [
@@ -79,7 +88,12 @@ exports[`bulk applies template to all results 2`] = `
             },
           ]
         }
-        value="foo"
+        value={
+          Object {
+            "label": "Foo",
+            "value": "foo",
+          }
+        }
       />
     </div>
   </div>
@@ -128,13 +142,22 @@ exports[`bulk applies template to all results 3`] = `
     <div
       className="modal-field"
     >
-      <label>
+      <label
+        htmlFor="bulk-apply-template-input"
+      >
         template
         <MandatoryFieldMarker />
       </label>
-      <SelectLegacy
-        clearable={false}
-        disabled={true}
+      <Select
+        className="Select Select-value"
+        components={
+          Object {
+            "Option": [Function],
+          }
+        }
+        id="bulk-apply-template"
+        inputId="bulk-apply-template-input"
+        isDisabled={true}
         onChange={[Function]}
         options={
           Array [
@@ -148,7 +171,12 @@ exports[`bulk applies template to all results 3`] = `
             },
           ]
         }
-        value="foo"
+        value={
+          Object {
+            "label": "Foo",
+            "value": "foo",
+          }
+        }
       />
     </div>
   </div>
@@ -266,13 +294,22 @@ exports[`bulk applies template to selected results 2`] = `
     <div
       className="modal-field"
     >
-      <label>
+      <label
+        htmlFor="bulk-apply-template-input"
+      >
         template
         <MandatoryFieldMarker />
       </label>
-      <SelectLegacy
-        clearable={false}
-        disabled={false}
+      <Select
+        className="Select Select-value"
+        components={
+          Object {
+            "Option": [Function],
+          }
+        }
+        id="bulk-apply-template"
+        inputId="bulk-apply-template-input"
+        isDisabled={false}
         onChange={[Function]}
         options={
           Array [
@@ -286,7 +323,12 @@ exports[`bulk applies template to selected results 2`] = `
             },
           ]
         }
-        value="foo"
+        value={
+          Object {
+            "label": "Foo",
+            "value": "foo",
+          }
+        }
       />
     </div>
   </div>
@@ -335,13 +377,22 @@ exports[`bulk applies template to selected results 3`] = `
     <div
       className="modal-field"
     >
-      <label>
+      <label
+        htmlFor="bulk-apply-template-input"
+      >
         template
         <MandatoryFieldMarker />
       </label>
-      <SelectLegacy
-        clearable={false}
-        disabled={true}
+      <Select
+        className="Select Select-value"
+        components={
+          Object {
+            "Option": [Function],
+          }
+        }
+        id="bulk-apply-template"
+        inputId="bulk-apply-template-input"
+        isDisabled={true}
         onChange={[Function]}
         options={
           Array [
@@ -355,7 +406,12 @@ exports[`bulk applies template to selected results 3`] = `
             },
           ]
         }
-        value="foo"
+        value={
+          Object {
+            "label": "Foo",
+            "value": "foo",
+          }
+        }
       />
     </div>
   </div>
@@ -413,3 +469,13 @@ exports[`bulk applies template to selected results 4`] = `
   </footer>
 </Modal>
 `;
+
+exports[`renders optionrenderer: option renderer 1`] = `
+<Option
+  data={
+    Object {
+      "val": "val",
+    }
+  }
+/>
+`;
index f6406917fb2b519095d3e1a66c7b46c995d2be31..37f731defa671f1c67ff1a87155ee15e59b91f55 100644 (file)
@@ -50,13 +50,18 @@ exports[`render qualifiers filter 1`] = `
         <td
           className="thin nowrap text-middle"
         >
-          <SelectLegacy
-            className="input-medium"
-            clearable={false}
+          <Select
+            className="input-medium Select"
+            components={
+              Object {
+                "Option": [Function],
+                "SingleValue": [Function],
+              }
+            }
             disabled={false}
+            isSearchable={false}
             name="projects-qualifier"
             onChange={[Function]}
-            optionRenderer={[Function]}
             options={
               Array [
                 Object {
@@ -74,8 +79,12 @@ exports[`render qualifiers filter 1`] = `
               ]
             }
             searchable={false}
-            value="TRK"
-            valueRenderer={[Function]}
+            value={
+              Object {
+                "label": "qualifiers.TRK",
+                "value": "TRK",
+              }
+            }
           />
         </td>
         <td
@@ -91,10 +100,15 @@ exports[`render qualifiers filter 1`] = `
         <td
           className="thin nowrap text-middle"
         >
-          <SelectLegacy
-            className="input-small"
-            clearable={false}
-            disabled={false}
+          <Select
+            className="input-small Select"
+            components={
+              Object {
+                "Option": [Function],
+              }
+            }
+            isDisabled={false}
+            isSearchable={false}
             name="projects-visibility"
             onChange={[Function]}
             options={
@@ -113,8 +127,12 @@ exports[`render qualifiers filter 1`] = `
                 },
               ]
             }
-            searchable={false}
-            value="all"
+            value={
+              Object {
+                "label": "visibility.both",
+                "value": "all",
+              }
+            }
           />
         </td>
         <td
@@ -205,10 +223,15 @@ exports[`renders 1`] = `
         <td
           className="thin nowrap text-middle"
         >
-          <SelectLegacy
-            className="input-small"
-            clearable={false}
-            disabled={false}
+          <Select
+            className="input-small Select"
+            components={
+              Object {
+                "Option": [Function],
+              }
+            }
+            isDisabled={false}
+            isSearchable={false}
             name="projects-visibility"
             onChange={[Function]}
             options={
@@ -227,8 +250,12 @@ exports[`renders 1`] = `
                 },
               ]
             }
-            searchable={false}
-            value="all"
+            value={
+              Object {
+                "label": "visibility.both",
+                "value": "all",
+              }
+            }
           />
         </td>
         <td
@@ -286,3 +313,42 @@ exports[`renders 1`] = `
   </table>
 </div>
 `;
+
+exports[`renders optionrenderer and singlevaluerenderer: option renderer 1`] = `
+<Option
+  className="Select-option"
+  data={
+    Object {
+      "value": "val",
+    }
+  }
+>
+  <div
+    className="display-flex-center"
+  >
+    <QualifierIcon
+      className="little-spacer-right"
+      qualifier="val"
+    />
+  </div>
+</Option>
+`;
+
+exports[`renders optionrenderer and singlevaluerenderer: single value renderer 1`] = `
+<SingleValue
+  data={
+    Object {
+      "value": "val",
+    }
+  }
+>
+  <div
+    className="display-flex-center"
+  >
+    <QualifierIcon
+      className="little-spacer-right"
+      qualifier="val"
+    />
+  </div>
+</SingleValue>
+`;