]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-16085 Adding IT and change select from activation modal
authorMathieu Suen <mathieu.suen@sonarsource.com>
Wed, 23 Mar 2022 17:32:30 +0000 (18:32 +0100)
committersonartech <sonartech@sonarsource.com>
Mon, 28 Mar 2022 20:02:52 +0000 (20:02 +0000)
15 files changed:
server/sonar-web/src/main/js/api/mocks/CodingRulesMock.ts [new file with mode: 0644]
server/sonar-web/src/main/js/api/rules.ts
server/sonar-web/src/main/js/apps/coding-rules/__tests__/CodingRules-it.ts [new file with mode: 0644]
server/sonar-web/src/main/js/apps/coding-rules/components/ActivationFormModal.tsx
server/sonar-web/src/main/js/apps/coding-rules/components/BulkChangeModal.tsx
server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetails.tsx
server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/ActivationFormModal-test.tsx
server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/BulkChangeModal-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/ActivationFormModal-test.tsx.snap
server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/BulkChangeModal-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/apps/coding-rules/styles.css
server/sonar-web/src/main/js/helpers/testMocks.ts
server/sonar-web/src/main/js/helpers/testReactTestingUtils.tsx
server/sonar-web/src/main/js/types/coding-rules.ts
server/sonar-web/src/main/js/types/rules.ts

diff --git a/server/sonar-web/src/main/js/api/mocks/CodingRulesMock.ts b/server/sonar-web/src/main/js/api/mocks/CodingRulesMock.ts
new file mode 100644 (file)
index 0000000..198e6cf
--- /dev/null
@@ -0,0 +1,176 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import { cloneDeep, countBy } from 'lodash';
+import { mockQualityProfile, mockRule, mockRuleRepository } from '../../helpers/testMocks';
+import { RuleRepository } from '../../types/coding-rules';
+import { SearchRulesQuery } from '../../types/rules';
+import { Rule } from '../../types/types';
+import {
+  bulkActivateRules,
+  bulkDeactivateRules,
+  Profile,
+  searchQualityProfiles,
+  SearchQualityProfilesParameters,
+  SearchQualityProfilesResponse
+} from '../quality-profiles';
+import { getRulesApp, searchRules } from '../rules';
+
+interface FacetFilter {
+  languages?: string;
+}
+
+const FACET_RULE_MAP: { [key: string]: keyof Rule } = {
+  languages: 'lang',
+  types: 'type'
+};
+export default class CodingRulesMock {
+  defaultRules: Rule[] = [];
+  rules: Rule[] = [];
+  qualityProfile: Profile[] = [];
+  repositories: RuleRepository[] = [];
+  isAdmin = false;
+  applyWithWarning = false;
+
+  constructor() {
+    this.repositories = [
+      mockRuleRepository({ key: 'repo1' }),
+      mockRuleRepository({ key: 'repo2' })
+    ];
+    this.qualityProfile = [
+      mockQualityProfile({ key: 'p1', name: 'QP Foo', language: 'java', languageName: 'Java' }),
+      mockQualityProfile({ key: 'p2', name: 'QP Bar', language: 'js' }),
+      mockQualityProfile({ key: 'p3', name: 'QP FooBar', language: 'java', languageName: 'Java' })
+    ];
+
+    this.defaultRules = [
+      mockRule({
+        key: 'rule1',
+        type: 'BUG',
+        lang: 'java',
+        langName: 'Java',
+        name: 'Awsome java rule'
+      }),
+      mockRule({ key: 'rule2', name: 'Hot hotspot', type: 'SECURITY_HOTSPOT' }),
+      mockRule({ key: 'rule3', name: 'Unknown rule' }),
+      mockRule({ key: 'rule4', type: 'BUG', lang: 'c', langName: 'C', name: 'Awsome C rule' })
+    ];
+
+    (searchRules as jest.Mock).mockImplementation(this.handleSearchRules);
+    (searchQualityProfiles as jest.Mock).mockImplementation(this.handleSearchQualityProfiles);
+    (getRulesApp as jest.Mock).mockImplementation(this.handleGetRulesApp);
+    (bulkActivateRules as jest.Mock).mockImplementation(this.handleBulkActivateRules);
+    (bulkDeactivateRules as jest.Mock).mockImplementation(this.handleBulkDeactivateRules);
+
+    this.rules = cloneDeep(this.defaultRules);
+  }
+
+  filterFacet({ languages }: FacetFilter) {
+    let filteredRules = this.rules;
+    if (languages) {
+      filteredRules = filteredRules.filter(r => r.lang && languages.includes(r.lang));
+    }
+    return filteredRules;
+  }
+
+  setIsAdmin() {
+    this.isAdmin = true;
+  }
+
+  activateWithWarning() {
+    this.applyWithWarning = true;
+  }
+
+  reset() {
+    this.isAdmin = false;
+    this.applyWithWarning = false;
+    this.rules = cloneDeep(this.defaultRules);
+  }
+
+  allRulesName() {
+    return this.rules.map(r => r.name);
+  }
+
+  allQualityProfile(language: string) {
+    return this.qualityProfile.filter(qp => qp.language === language);
+  }
+
+  handleSearchRules = ({ facets, languages, p, ps }: SearchRulesQuery) => {
+    const countFacet = (facets || '').split(',').map((facet: keyof Rule) => {
+      const facetCount = countBy(this.rules.map(r => r[FACET_RULE_MAP[facet] || facet] as string));
+      return {
+        property: facet,
+        values: Object.keys(facetCount).map(val => ({ val, count: facetCount[val] }))
+      };
+    });
+    const currentPs = ps || 10;
+    const currentP = p || 1;
+    const filteredRules = this.filterFacet({ languages });
+    const responseRules = filteredRules.slice((currentP - 1) * currentPs, currentP * currentPs);
+    return this.reply({
+      total: filteredRules.length,
+      p: currentP,
+      ps: currentPs,
+      rules: responseRules,
+      facets: countFacet
+    });
+  };
+
+  handleBulkActivateRules = () => {
+    if (this.applyWithWarning) {
+      return this.reply({
+        succeeded: this.rules.length - 1,
+        failed: 1,
+        errors: [{ msg: 'c rule c:S6069 cannot be activated on cpp profile SonarSource' }]
+      });
+    }
+    return this.reply({
+      succeeded: this.rules.length,
+      failed: 0,
+      errors: []
+    });
+  };
+
+  handleBulkDeactivateRules = () => {
+    return this.reply({
+      succeeded: this.rules.length,
+      failed: 0
+    });
+  };
+
+  handleSearchQualityProfiles = ({ language }: SearchQualityProfilesParameters = {}): Promise<
+    SearchQualityProfilesResponse
+  > => {
+    let profiles: Profile[] = this.isAdmin
+      ? this.qualityProfile.map(p => ({ ...p, actions: { edit: true } }))
+      : this.qualityProfile;
+    if (language) {
+      profiles = profiles.filter(p => p.language === language);
+    }
+    return this.reply({ profiles });
+  };
+
+  handleGetRulesApp = () => {
+    return this.reply({ canWrite: this.isAdmin, repositories: this.repositories });
+  };
+
+  reply<T>(response: T): Promise<T> {
+    return Promise.resolve(cloneDeep(response));
+  }
+}
index a2ada44f0a8de1c34d18afefb4c40686f55b0a9f..419e7ed7eefca201c0bfd7a31dfdb52801cd6d1e 100644 (file)
 import throwGlobalError from '../app/utils/throwGlobalError';
 import { getJSON, post, postJSON } from '../helpers/request';
 import { GetRulesAppResponse, SearchRulesResponse } from '../types/coding-rules';
+import { SearchRulesQuery } from '../types/rules';
 import { RuleActivation, RuleDetails } from '../types/types';
 
 export function getRulesApp(): Promise<GetRulesAppResponse> {
   return getJSON('/api/rules/app').catch(throwGlobalError);
 }
 
-export function searchRules(data: {
-  activation?: boolean | string;
-  active_severities?: string;
-  asc?: boolean | string;
-  available_since?: string;
-  cwe?: string;
-  f?: string;
-  facets?: string;
-  include_external?: boolean | string;
-  inheritance?: string;
-  is_template?: boolean | string;
-  languages?: string;
-  owaspTop10?: string;
-  p?: number;
-  ps?: number;
-  q?: string;
-  qprofile?: string;
-  repositories?: string;
-  rule_key?: string;
-  s?: string;
-  sansTop25?: string;
-  severities?: string;
-  sonarsourceSecurity?: string;
-  statuses?: string;
-  tags?: string;
-  template_key?: string;
-  types?: string;
-}): Promise<SearchRulesResponse> {
+export function searchRules(data: SearchRulesQuery): Promise<SearchRulesResponse> {
   return getJSON('/api/rules/search', data).catch(throwGlobalError);
 }
 
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/__tests__/CodingRules-it.ts b/server/sonar-web/src/main/js/apps/coding-rules/__tests__/CodingRules-it.ts
new file mode 100644 (file)
index 0000000..a8de3c4
--- /dev/null
@@ -0,0 +1,197 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import { screen, waitFor, within } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import CodingRulesMock from '../../../api/mocks/CodingRulesMock';
+import { mockLoggedInUser } from '../../../helpers/testMocks';
+import { renderApp } from '../../../helpers/testReactTestingUtils';
+import { CurrentUser } from '../../../types/users';
+import routes from '../routes';
+
+jest.mock('../../../api/rules');
+jest.mock('../../../api/quality-profiles');
+
+let handler: CodingRulesMock;
+
+beforeAll(() => {
+  window.scrollTo = jest.fn();
+  handler = new CodingRulesMock();
+});
+
+afterEach(() => handler.reset());
+
+jest.setTimeout(10_000);
+
+it('should list all rules', async () => {
+  renderCodingRulesApp();
+
+  await waitFor(() => {
+    handler
+      .allRulesName()
+      .forEach(name => expect(screen.getByRole('link', { name })).toBeInTheDocument());
+  });
+});
+
+it('should have all type facet', async () => {
+  renderCodingRulesApp();
+
+  await waitFor(() => {
+    [
+      'issue.type.BUG',
+      'issue.type.VULNERABILITY',
+      'issue.type.CODE_SMELL',
+      'issue.type.SECURITY_HOTSPOT'
+    ].forEach(name => expect(screen.getByRole('link', { name })).toBeInTheDocument());
+  });
+});
+
+it('select the correct quality profile for bulk change base on language search', async () => {
+  const user = userEvent.setup();
+  handler.setIsAdmin();
+  renderCodingRulesApp(mockLoggedInUser());
+  const selectQP = handler.allQualityProfile('js')[0];
+
+  await user.click(await screen.findByRole('link', { name: 'JavaScript' }));
+  await user.click(await screen.findByRole('button', { name: 'bulk_change' }));
+  await user.click(await screen.findByRole('link', { name: 'coding_rules.activate_in…' }));
+  const dialog = screen.getByRole('dialog', {
+    name: 'coding_rules.activate_in_quality_profile (2 coding_rules._rules)'
+  });
+
+  expect(dialog).toBeInTheDocument();
+  const dialogScreen = within(dialog);
+  expect(dialogScreen.getByText(`${selectQP.name} - ${selectQP.languageName}`)).toBeInTheDocument();
+});
+
+it('no quality profile for bulk cahnge base on language search', async () => {
+  const user = userEvent.setup();
+  handler.setIsAdmin();
+  renderCodingRulesApp(mockLoggedInUser());
+
+  await user.click(await screen.findByRole('link', { name: 'C' }));
+  await user.click(await screen.findByRole('button', { name: 'bulk_change' }));
+  await user.click(await screen.findByRole('link', { name: 'coding_rules.activate_in…' }));
+  const dialog = screen.getByRole('dialog', {
+    name: 'coding_rules.activate_in_quality_profile (1 coding_rules._rules)'
+  });
+
+  expect(dialog).toBeInTheDocument();
+  const dialogScreen = within(dialog);
+  await user.click(dialogScreen.getByRole('textbox', { name: 'coding_rules.activate_in' }));
+  expect(dialogScreen.getByText('coding_rules.bulk_change.no_quality_profile')).toBeInTheDocument();
+});
+
+it('should be able to bulk activate quality profile', async () => {
+  const user = userEvent.setup();
+  handler.setIsAdmin();
+  renderCodingRulesApp(mockLoggedInUser());
+
+  const selectQPSuccess = handler.allQualityProfile('java')[0];
+  const selectQPWarning = handler.allQualityProfile('java')[1];
+
+  await user.click(await screen.findByRole('button', { name: 'bulk_change' }));
+  await user.click(await screen.findByRole('link', { name: 'coding_rules.activate_in…' }));
+
+  const dialog = screen.getByRole('dialog', {
+    name: 'coding_rules.activate_in_quality_profile (4 coding_rules._rules)'
+  });
+  expect(dialog).toBeInTheDocument();
+
+  let dialogScreen = within(dialog);
+  await user.click(dialogScreen.getByRole('textbox', { name: 'coding_rules.activate_in' }));
+  await user.click(
+    dialogScreen.getByText(`${selectQPSuccess.name} - ${selectQPSuccess.languageName}`)
+  );
+  expect(
+    dialogScreen.getByText(`${selectQPSuccess.name} - ${selectQPSuccess.languageName}`)
+  ).toBeInTheDocument();
+
+  await user.click(dialogScreen.getByRole('button', { name: 'apply' }));
+  expect(
+    dialogScreen.getByText(
+      `coding_rules.bulk_change.success.${selectQPSuccess.name}.${selectQPSuccess.languageName}.${
+        handler.allRulesName().length
+      }`
+    )
+  ).toBeInTheDocument();
+
+  await user.click(dialogScreen.getByRole('button', { name: 'close' }));
+
+  // Try bulk change when quality profile has warnning.
+  handler.activateWithWarning();
+
+  await user.click(await screen.findByRole('button', { name: 'bulk_change' }));
+  await user.click(await screen.findByRole('link', { name: 'coding_rules.activate_in…' }));
+  dialogScreen = within(
+    screen.getByRole('dialog', {
+      name: 'coding_rules.activate_in_quality_profile (4 coding_rules._rules)'
+    })
+  );
+  await user.click(dialogScreen.getByRole('textbox', { name: 'coding_rules.activate_in' }));
+  await user.click(
+    dialogScreen.getByText(`${selectQPWarning.name} - ${selectQPWarning.languageName}`)
+  );
+  await user.click(dialogScreen.getByRole('button', { name: 'apply' }));
+  expect(
+    dialogScreen.getByText(
+      `coding_rules.bulk_change.warning.${selectQPWarning.name}.${
+        selectQPWarning.languageName
+      }.${handler.allRulesName().length - 1}.1`
+    )
+  ).toBeInTheDocument();
+});
+
+it('should be able to bulk deactivate quality profile', async () => {
+  const user = userEvent.setup();
+  handler.setIsAdmin();
+  renderCodingRulesApp(mockLoggedInUser());
+
+  const selectQP = handler.allQualityProfile('java')[0];
+
+  await user.click(await screen.findByRole('button', { name: 'bulk_change' }));
+  await user.click(await screen.findByRole('link', { name: 'coding_rules.deactivate_in…' }));
+  const dialogScreen = within(
+    screen.getByRole('dialog', {
+      name: 'coding_rules.deactivate_in_quality_profile (4 coding_rules._rules)'
+    })
+  );
+  await user.click(dialogScreen.getByRole('textbox', { name: 'coding_rules.deactivate_in' }));
+
+  await user.click(dialogScreen.getByText(`${selectQP.name} - ${selectQP.languageName}`));
+  await user.click(dialogScreen.getByRole('button', { name: 'apply' }));
+  expect(
+    dialogScreen.getByText(
+      `coding_rules.bulk_change.success.${selectQP.name}.${selectQP.languageName}.${
+        handler.allRulesName().length
+      }`
+    )
+  ).toBeInTheDocument();
+});
+
+function renderCodingRulesApp(currentUser?: CurrentUser) {
+  renderApp('coding_rules', routes, {
+    currentUser,
+    languages: {
+      js: { key: 'js', name: 'JavaScript' },
+      java: { key: 'java', name: 'Java' },
+      c: { key: 'c', name: 'C' }
+    }
+  });
+}
index 76f8c2f22e932bf4853abc9a60410489b3f739dc..97a0c93be8eaeda845da8fe33b99c4c1c4338150 100644 (file)
  */
 import classNames from 'classnames';
 import * as React from 'react';
+import { components, OptionProps, OptionTypeBase, SingleValueProps } from 'react-select';
 import { activateRule, Profile } from '../../../api/quality-profiles';
 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 SeverityHelper from '../../../components/shared/SeverityHelper';
 import { Alert } from '../../../components/ui/Alert';
 import { SEVERITIES } from '../../../helpers/constants';
@@ -40,9 +41,13 @@ interface Props {
   rule: Rule | RuleDetails;
 }
 
+interface ProfileWithDeph extends Profile {
+  depth: number;
+}
+
 interface State {
   params: Dict<string>;
-  profile: string;
+  profile?: ProfileWithDeph;
   severity: string;
   submitting: boolean;
 }
@@ -55,7 +60,7 @@ export default class ActivationFormModal extends React.PureComponent<Props, Stat
     const profilesWithDepth = this.getQualityProfilesWithDepth(props);
     this.state = {
       params: this.getParams(props),
-      profile: profilesWithDepth.length > 0 ? profilesWithDepth[0].key : '',
+      profile: profilesWithDepth.length > 0 ? profilesWithDepth[0] : undefined,
       severity: props.activation ? props.activation.severity : props.rule.severity,
       submitting: false
     };
@@ -105,7 +110,7 @@ export default class ActivationFormModal extends React.PureComponent<Props, Stat
     event.preventDefault();
     this.setState({ submitting: true });
     const data = {
-      key: this.state.profile,
+      key: this.state.profile?.key || '',
       params: this.state.params,
       rule: this.props.rule.key,
       severity: this.state.severity
@@ -132,18 +137,14 @@ export default class ActivationFormModal extends React.PureComponent<Props, Stat
     this.setState((state: State) => ({ params: { ...state.params, [name]: value } }));
   };
 
-  handleProfileChange = ({ value }: { value: string }) => {
-    this.setState({ profile: value });
+  handleProfileChange = (profile: ProfileWithDeph) => {
+    this.setState({ profile });
   };
 
-  handleSeverityChange = ({ value }: { value: string }) => {
+  handleSeverityChange = ({ value }: OptionTypeBase) => {
     this.setState({ severity: value });
   };
 
-  renderSeverityOption = ({ value }: { value: string }) => {
-    return <SeverityHelper severity={value} />;
-  };
-
   render() {
     const { activation, rule } = this.props;
     const { profile, severity, submitting } = this.state;
@@ -152,6 +153,26 @@ export default class ActivationFormModal extends React.PureComponent<Props, Stat
     const isCustomRule = !!(rule as RuleDetails).templateKey;
     const activeInAllProfiles = profilesWithDepth.length <= 0;
     const isUpdateMode = !!activation;
+    const serverityOption = SEVERITIES.map(severity => ({
+      label: translate('severity', severity),
+      value: severity
+    }));
+
+    function Option(props: OptionProps<OptionTypeBase, false>) {
+      return (
+        <components.Option {...props}>
+          <SeverityHelper severity={props.data.value} />
+        </components.Option>
+      );
+    }
+
+    function SingleValue(props: SingleValueProps<OptionTypeBase>) {
+      return (
+        <components.SingleValue {...props}>
+          <SeverityHelper className="coding-rules-severity-value" severity={props.data.value} />
+        </components.SingleValue>
+      );
+    }
 
     return (
       <Modal contentLabel={this.props.modalHeader} onRequestClose={this.props.onClose} size="small">
@@ -166,34 +187,32 @@ export default class ActivationFormModal extends React.PureComponent<Props, Stat
             )}
 
             <div className="modal-field">
-              <label>{translate('coding_rules.quality_profile')}</label>
-              <SelectLegacy
+              <label id="coding-rules-quality-profile-select">
+                {translate('coding_rules.quality_profile')}
+              </label>
+              <Select
                 className="js-profile"
-                clearable={false}
-                disabled={submitting || profilesWithDepth.length === 1}
+                aria-labelledby="coding-rules-quality-profile-select"
+                isClearable={false}
+                isDisabled={submitting || profilesWithDepth.length === 1}
                 onChange={this.handleProfileChange}
-                options={profilesWithDepth.map(profile => ({
-                  label: '   '.repeat(profile.depth) + profile.name,
-                  value: profile.key
-                }))}
+                getOptionLabel={p => '   '.repeat(p.depth) + p.name}
+                options={profilesWithDepth}
                 value={profile}
               />
             </div>
             <div className="modal-field">
-              <label>{translate('severity')}</label>
-              <SelectLegacy
+              <label id="coding-rules-severity-select">{translate('severity')}</label>
+              <Select
                 className="js-severity"
-                clearable={false}
-                disabled={submitting}
+                isClearable={false}
+                isDisabled={submitting}
+                aria-labelledby="coding-rules-severity-select"
                 onChange={this.handleSeverityChange}
-                optionRenderer={this.renderSeverityOption}
-                options={SEVERITIES.map(severity => ({
-                  label: translate('severity', severity),
-                  value: severity
-                }))}
-                searchable={false}
-                value={severity}
-                valueRenderer={this.renderSeverityOption}
+                components={{ Option, SingleValue }}
+                options={serverityOption}
+                isSearchable={false}
+                value={serverityOption.find(s => s.value === severity)}
               />
             </div>
             {isCustomRule ? (
index 32c3b9b691dd3cadb94f0bf62a91d66f29f95135..9221083055e5c664753561791cd2e0cf7f61fb69 100644 (file)
@@ -150,9 +150,7 @@ export class BulkChangeModal extends React.PureComponent<Props, State> {
   renderResult = (result: ActivationResult) => {
     const { profile: profileKey } = result;
     const profile = this.props.referencedProfiles[profileKey];
-    if (!profile) {
-      return null;
-    }
+
     const { languages } = this.props;
     const language = languages[profile.language]
       ? languages[profile.language].name
index 64987cf37ce9ce93a20e6a52696cdca85752ecfb..96f59bce97d36046a162edf39b5e20e10ae37dcc 100644 (file)
@@ -38,7 +38,6 @@ import RuleDetailsProfiles from './RuleDetailsProfiles';
 interface Props {
   allowCustomRules?: boolean;
   canWrite?: boolean;
-  hideQualityProfiles?: boolean;
   onActivate: (profile: string, rule: string, activation: Activation) => void;
   onDeactivate: (profile: string, rule: string) => void;
   onDelete: (rule: string) => void;
@@ -156,7 +155,7 @@ export default class RuleDetails extends React.PureComponent<Props, State> {
       return <div className="coding-rule-details" />;
     }
 
-    const { allowCustomRules, canWrite, hideQualityProfiles, referencedProfiles } = this.props;
+    const { allowCustomRules, canWrite, referencedProfiles } = this.props;
     const { params = [] } = ruleDetails;
 
     const isCustom = !!ruleDetails.templateKey;
@@ -236,7 +235,7 @@ export default class RuleDetails extends React.PureComponent<Props, State> {
             />
           )}
 
-          {!ruleDetails.isTemplate && !hideQualityProfiles && (
+          {!ruleDetails.isTemplate && (
             <RuleDetailsProfiles
               activations={this.state.actives}
               canWrite={canWrite}
index f19aaa1e16c534ca87466f5d0eb8d9f89f75fb62..b4138caae3650d6d96cbe4887e224d984d03e2c5 100644 (file)
@@ -19,6 +19,7 @@
  */
 import { shallow } from 'enzyme';
 import * as React from 'react';
+import { activateRule } from '../../../../api/quality-profiles';
 import {
   mockQualityProfile,
   mockRule,
@@ -28,6 +29,10 @@ import {
 } from '../../../../helpers/testMocks';
 import ActivationFormModal from '../ActivationFormModal';
 
+jest.mock('../../../../api/quality-profiles', () => ({
+  activateRule: jest.fn().mockResolvedValueOnce({})
+}));
+
 it('should render correctly', () => {
   expect(shallowRender()).toMatchSnapshot('default');
   expect(
@@ -47,6 +52,29 @@ it('should render correctly', () => {
   expect(wrapper).toMatchSnapshot('submitting');
 });
 
+it('should activate rule on quality profile when submit', () => {
+  const wrapper = shallowRender();
+  wrapper.instance().handleFormSubmit(({
+    preventDefault: jest.fn()
+  } as any) as React.SyntheticEvent<HTMLFormElement>);
+  expect(activateRule).toHaveBeenCalledWith({
+    key: '',
+    params: {
+      '1': '1',
+      '2': '1'
+    },
+    rule: 'javascript:S1067',
+    severity: 'MAJOR'
+  });
+});
+
+it('should handle profile change correctly', () => {
+  const wrapper = shallowRender();
+  const qualityProfile = mockQualityProfile();
+  wrapper.instance().handleProfileChange(qualityProfile);
+  expect(wrapper.state().profile).toBe(qualityProfile);
+});
+
 function shallowRender(props: Partial<ActivationFormModal['props']> = {}) {
   return shallow<ActivationFormModal>(
     <ActivationFormModal
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/BulkChangeModal-test.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/BulkChangeModal-test.tsx
deleted file mode 100644 (file)
index 893553a..0000000
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2022 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-import { shallow } from 'enzyme';
-import * as React from 'react';
-import { bulkActivateRules, bulkDeactivateRules } from '../../../../api/quality-profiles';
-import { mockLanguage, mockQualityProfile } from '../../../../helpers/testMocks';
-import { submit, waitAndUpdate } from '../../../../helpers/testUtils';
-import { Query } from '../../query';
-import { BulkChangeModal } from '../BulkChangeModal';
-
-jest.mock('../../../../api/quality-profiles', () => ({
-  bulkActivateRules: jest.fn().mockResolvedValue({ failed: 0, succeeded: 2 }),
-  bulkDeactivateRules: jest.fn().mockResolvedValue({ failed: 2, succeeded: 0 })
-}));
-
-beforeEach(jest.clearAllMocks);
-
-it('should render correctly', () => {
-  expect(shallowRender()).toMatchSnapshot('default');
-  expect(shallowRender({ profile: undefined })).toMatchSnapshot('no profile pre-selected');
-  expect(shallowRender({ action: 'deactivate' })).toMatchSnapshot('deactivate action');
-  expect(
-    shallowRender().setState({
-      results: [
-        { failed: 2, profile: 'foo', succeeded: 0 },
-        { failed: 0, profile: 'bar', succeeded: 2 }
-      ]
-    })
-  ).toMatchSnapshot('results');
-  expect(shallowRender().setState({ submitting: true })).toMatchSnapshot('submitting');
-  expect(shallowRender().setState({ finished: true })).toMatchSnapshot('finished');
-});
-
-it('should pre-select a profile if only 1 is available', () => {
-  const profile = mockQualityProfile({
-    actions: { edit: true },
-    isBuiltIn: false,
-    key: 'foo',
-    language: 'js'
-  });
-  const wrapper = shallowRender({ profile: undefined, referencedProfiles: { foo: profile } });
-  expect(wrapper.state().selectedProfiles).toEqual([profile]);
-});
-
-it('should handle profile selection', () => {
-  const wrapper = shallowRender();
-  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 });
-  const profiles = [
-    mockQualityProfile({ name: 'foo', key: 'foo' }),
-    mockQualityProfile({ name: 'bar', key: 'bar' })
-  ];
-  wrapper.setState({ selectedProfiles: profiles });
-
-  // Activate.
-  submit(wrapper.find('form'));
-  await waitAndUpdate(wrapper);
-  expect(bulkActivateRules).toBeCalledWith(expect.objectContaining({ targetKey: 'foo' }));
-
-  await waitAndUpdate(wrapper);
-  expect(bulkActivateRules).toBeCalledWith(expect.objectContaining({ targetKey: 'bar' }));
-
-  await waitAndUpdate(wrapper);
-  expect(wrapper.state().results).toEqual([
-    { failed: 0, profile: 'foo', succeeded: 2 },
-    { failed: 0, profile: 'bar', succeeded: 2 }
-  ]);
-
-  // Deactivate.
-  wrapper.setProps({ action: 'deactivate' }).setState({ results: [] });
-  submit(wrapper.find('form'));
-  await waitAndUpdate(wrapper);
-  expect(bulkDeactivateRules).toBeCalledWith(expect.objectContaining({ targetKey: 'foo' }));
-
-  await waitAndUpdate(wrapper);
-  expect(bulkDeactivateRules).toBeCalledWith(expect.objectContaining({ targetKey: 'bar' }));
-
-  await waitAndUpdate(wrapper);
-  expect(wrapper.state().results).toEqual([
-    { failed: 2, profile: 'foo', succeeded: 0 },
-    { failed: 2, profile: 'bar', succeeded: 0 }
-  ]);
-});
-
-function shallowRender(props: Partial<BulkChangeModal['props']> = {}) {
-  return shallow<BulkChangeModal>(
-    <BulkChangeModal
-      action="activate"
-      languages={{ js: mockLanguage() }}
-      onClose={jest.fn()}
-      profile={mockQualityProfile()}
-      query={{ languages: ['js'] } as Query}
-      referencedProfiles={{
-        foo: mockQualityProfile({ key: 'foo' }),
-        bar: mockQualityProfile({ key: 'bar' })
-      }}
-      total={42}
-      {...props}
-    />
-  );
-}
index 772f62dee6b4a31a252780500964cc5cabd1570d..ccfb4ebf10853398851615d7e7564f9ad63db083 100644 (file)
@@ -27,30 +27,42 @@ exports[`should render correctly: custom rule 1`] = `
       <div
         className="modal-field"
       >
-        <label>
+        <label
+          id="coding-rules-quality-profile-select"
+        >
           coding_rules.quality_profile
         </label>
-        <SelectLegacy
+        <Select
+          aria-labelledby="coding-rules-quality-profile-select"
           className="js-profile"
-          clearable={false}
-          disabled={false}
+          getOptionLabel={[Function]}
+          isClearable={false}
+          isDisabled={false}
           onChange={[Function]}
           options={Array []}
-          value=""
         />
       </div>
       <div
         className="modal-field"
       >
-        <label>
+        <label
+          id="coding-rules-severity-select"
+        >
           severity
         </label>
-        <SelectLegacy
+        <Select
+          aria-labelledby="coding-rules-severity-select"
           className="js-severity"
-          clearable={false}
-          disabled={false}
+          components={
+            Object {
+              "Option": [Function],
+              "SingleValue": [Function],
+            }
+          }
+          isClearable={false}
+          isDisabled={false}
+          isSearchable={false}
           onChange={[Function]}
-          optionRenderer={[Function]}
           options={
             Array [
               Object {
@@ -75,9 +87,12 @@ exports[`should render correctly: custom rule 1`] = `
               },
             ]
           }
-          searchable={false}
-          value="MAJOR"
-          valueRenderer={[Function]}
+          value={
+            Object {
+              "label": "severity.MAJOR",
+              "value": "MAJOR",
+            }
+          }
         />
       </div>
       <div
@@ -136,30 +151,42 @@ exports[`should render correctly: default 1`] = `
       <div
         className="modal-field"
       >
-        <label>
+        <label
+          id="coding-rules-quality-profile-select"
+        >
           coding_rules.quality_profile
         </label>
-        <SelectLegacy
+        <Select
+          aria-labelledby="coding-rules-quality-profile-select"
           className="js-profile"
-          clearable={false}
-          disabled={false}
+          getOptionLabel={[Function]}
+          isClearable={false}
+          isDisabled={false}
           onChange={[Function]}
           options={Array []}
-          value=""
         />
       </div>
       <div
         className="modal-field"
       >
-        <label>
+        <label
+          id="coding-rules-severity-select"
+        >
           severity
         </label>
-        <SelectLegacy
+        <Select
+          aria-labelledby="coding-rules-severity-select"
           className="js-severity"
-          clearable={false}
-          disabled={false}
+          components={
+            Object {
+              "Option": [Function],
+              "SingleValue": [Function],
+            }
+          }
+          isClearable={false}
+          isDisabled={false}
+          isSearchable={false}
           onChange={[Function]}
-          optionRenderer={[Function]}
           options={
             Array [
               Object {
@@ -184,9 +211,12 @@ exports[`should render correctly: default 1`] = `
               },
             ]
           }
-          searchable={false}
-          value="MAJOR"
-          valueRenderer={[Function]}
+          value={
+            Object {
+              "label": "severity.MAJOR",
+              "value": "MAJOR",
+            }
+          }
         />
       </div>
       <div
@@ -280,30 +310,42 @@ exports[`should render correctly: submitting 1`] = `
       <div
         className="modal-field"
       >
-        <label>
+        <label
+          id="coding-rules-quality-profile-select"
+        >
           coding_rules.quality_profile
         </label>
-        <SelectLegacy
+        <Select
+          aria-labelledby="coding-rules-quality-profile-select"
           className="js-profile"
-          clearable={false}
-          disabled={true}
+          getOptionLabel={[Function]}
+          isClearable={false}
+          isDisabled={true}
           onChange={[Function]}
           options={Array []}
-          value=""
         />
       </div>
       <div
         className="modal-field"
       >
-        <label>
+        <label
+          id="coding-rules-severity-select"
+        >
           severity
         </label>
-        <SelectLegacy
+        <Select
+          aria-labelledby="coding-rules-severity-select"
           className="js-severity"
-          clearable={false}
-          disabled={true}
+          components={
+            Object {
+              "Option": [Function],
+              "SingleValue": [Function],
+            }
+          }
+          isClearable={false}
+          isDisabled={true}
+          isSearchable={false}
           onChange={[Function]}
-          optionRenderer={[Function]}
           options={
             Array [
               Object {
@@ -328,9 +370,12 @@ exports[`should render correctly: submitting 1`] = `
               },
             ]
           }
-          searchable={false}
-          value="MAJOR"
-          valueRenderer={[Function]}
+          value={
+            Object {
+              "label": "severity.MAJOR",
+              "value": "MAJOR",
+            }
+          }
         />
       </div>
       <div
@@ -422,30 +467,42 @@ exports[`should render correctly: update mode 1`] = `
       <div
         className="modal-field"
       >
-        <label>
+        <label
+          id="coding-rules-quality-profile-select"
+        >
           coding_rules.quality_profile
         </label>
-        <SelectLegacy
+        <Select
+          aria-labelledby="coding-rules-quality-profile-select"
           className="js-profile"
-          clearable={false}
-          disabled={false}
+          getOptionLabel={[Function]}
+          isClearable={false}
+          isDisabled={false}
           onChange={[Function]}
           options={Array []}
-          value=""
         />
       </div>
       <div
         className="modal-field"
       >
-        <label>
+        <label
+          id="coding-rules-severity-select"
+        >
           severity
         </label>
-        <SelectLegacy
+        <Select
+          aria-labelledby="coding-rules-severity-select"
           className="js-severity"
-          clearable={false}
-          disabled={false}
+          components={
+            Object {
+              "Option": [Function],
+              "SingleValue": [Function],
+            }
+          }
+          isClearable={false}
+          isDisabled={false}
+          isSearchable={false}
           onChange={[Function]}
-          optionRenderer={[Function]}
           options={
             Array [
               Object {
@@ -470,9 +527,12 @@ exports[`should render correctly: update mode 1`] = `
               },
             ]
           }
-          searchable={false}
-          value="MAJOR"
-          valueRenderer={[Function]}
+          value={
+            Object {
+              "label": "severity.MAJOR",
+              "value": "MAJOR",
+            }
+          }
         />
       </div>
       <div
@@ -561,37 +621,81 @@ exports[`should render correctly: with deep profiles 1`] = `
       <div
         className="modal-field"
       >
-        <label>
+        <label
+          id="coding-rules-quality-profile-select"
+        >
           coding_rules.quality_profile
         </label>
-        <SelectLegacy
+        <Select
+          aria-labelledby="coding-rules-quality-profile-select"
           className="js-profile"
-          clearable={false}
-          disabled={true}
+          getOptionLabel={[Function]}
+          isClearable={false}
+          isDisabled={true}
           onChange={[Function]}
           options={
             Array [
               Object {
-                "label": "name",
-                "value": "key",
+                "actions": Object {
+                  "edit": true,
+                },
+                "activeDeprecatedRuleCount": 2,
+                "activeRuleCount": 10,
+                "childrenCount": 0,
+                "depth": 0,
+                "isBuiltIn": false,
+                "isDefault": false,
+                "isInherited": false,
+                "key": "key",
+                "language": "js",
+                "languageName": "JavaScript",
+                "name": "name",
+                "projectCount": 3,
               },
             ]
           }
-          value="key"
+          value={
+            Object {
+              "actions": Object {
+                "edit": true,
+              },
+              "activeDeprecatedRuleCount": 2,
+              "activeRuleCount": 10,
+              "childrenCount": 0,
+              "depth": 0,
+              "isBuiltIn": false,
+              "isDefault": false,
+              "isInherited": false,
+              "key": "key",
+              "language": "js",
+              "languageName": "JavaScript",
+              "name": "name",
+              "projectCount": 3,
+            }
+          }
         />
       </div>
       <div
         className="modal-field"
       >
-        <label>
+        <label
+          id="coding-rules-severity-select"
+        >
           severity
         </label>
-        <SelectLegacy
+        <Select
+          aria-labelledby="coding-rules-severity-select"
           className="js-severity"
-          clearable={false}
-          disabled={false}
+          components={
+            Object {
+              "Option": [Function],
+              "SingleValue": [Function],
+            }
+          }
+          isClearable={false}
+          isDisabled={false}
+          isSearchable={false}
           onChange={[Function]}
-          optionRenderer={[Function]}
           options={
             Array [
               Object {
@@ -616,9 +720,12 @@ exports[`should render correctly: with deep profiles 1`] = `
               },
             ]
           }
-          searchable={false}
-          value="MAJOR"
-          valueRenderer={[Function]}
+          value={
+            Object {
+              "label": "severity.MAJOR",
+              "value": "MAJOR",
+            }
+          }
         />
       </div>
       <div
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/BulkChangeModal-test.tsx.snap b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/BulkChangeModal-test.tsx.snap
deleted file mode 100644 (file)
index de47bfb..0000000
+++ /dev/null
@@ -1,313 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly: deactivate action 1`] = `
-<Modal
-  contentLabel="coding_rules.deactivate_in_quality_profile (42 coding_rules._rules)"
-  onRequestClose={[MockFunction]}
-  size="small"
->
-  <form
-    onSubmit={[Function]}
-  >
-    <header
-      className="modal-head"
-    >
-      <h2>
-        coding_rules.deactivate_in_quality_profile (42 coding_rules._rules)
-      </h2>
-    </header>
-    <div
-      className="modal-body"
-    >
-      <div
-        className="modal-field"
-      >
-        <h3>
-          <label
-            id="coding-rules-bulk-change-profile"
-          >
-            coding_rules.deactivate_in
-          </label>
-        </h3>
-        <span>
-          name
-           — 
-          are_you_sure
-        </span>
-      </div>
-    </div>
-    <footer
-      className="modal-foot"
-    >
-      <SubmitButton
-        disabled={false}
-        id="coding-rules-submit-bulk-change"
-      >
-        apply
-      </SubmitButton>
-      <ResetButtonLink
-        onClick={[MockFunction]}
-      >
-        cancel
-      </ResetButtonLink>
-    </footer>
-  </form>
-</Modal>
-`;
-
-exports[`should render correctly: default 1`] = `
-<Modal
-  contentLabel="coding_rules.activate_in_quality_profile (42 coding_rules._rules)"
-  onRequestClose={[MockFunction]}
-  size="small"
->
-  <form
-    onSubmit={[Function]}
-  >
-    <header
-      className="modal-head"
-    >
-      <h2>
-        coding_rules.activate_in_quality_profile (42 coding_rules._rules)
-      </h2>
-    </header>
-    <div
-      className="modal-body"
-    >
-      <div
-        className="modal-field"
-      >
-        <h3>
-          <label
-            id="coding-rules-bulk-change-profile"
-          >
-            coding_rules.activate_in
-          </label>
-        </h3>
-        <span>
-          name
-           — 
-          are_you_sure
-        </span>
-      </div>
-    </div>
-    <footer
-      className="modal-foot"
-    >
-      <SubmitButton
-        disabled={false}
-        id="coding-rules-submit-bulk-change"
-      >
-        apply
-      </SubmitButton>
-      <ResetButtonLink
-        onClick={[MockFunction]}
-      >
-        cancel
-      </ResetButtonLink>
-    </footer>
-  </form>
-</Modal>
-`;
-
-exports[`should render correctly: finished 1`] = `
-<Modal
-  contentLabel="coding_rules.activate_in_quality_profile (42 coding_rules._rules)"
-  onRequestClose={[MockFunction]}
-  size="small"
->
-  <form
-    onSubmit={[Function]}
-  >
-    <header
-      className="modal-head"
-    >
-      <h2>
-        coding_rules.activate_in_quality_profile (42 coding_rules._rules)
-      </h2>
-    </header>
-    <div
-      className="modal-body"
-    />
-    <footer
-      className="modal-foot"
-    >
-      <ResetButtonLink
-        onClick={[MockFunction]}
-      >
-        close
-      </ResetButtonLink>
-    </footer>
-  </form>
-</Modal>
-`;
-
-exports[`should render correctly: no profile pre-selected 1`] = `
-<Modal
-  contentLabel="coding_rules.activate_in_quality_profile (42 coding_rules._rules)"
-  onRequestClose={[MockFunction]}
-  size="small"
->
-  <form
-    onSubmit={[Function]}
-  >
-    <header
-      className="modal-head"
-    >
-      <h2>
-        coding_rules.activate_in_quality_profile (42 coding_rules._rules)
-      </h2>
-    </header>
-    <div
-      className="modal-body"
-    >
-      <div
-        className="modal-field"
-      >
-        <h3>
-          <label
-            id="coding-rules-bulk-change-profile"
-          >
-            coding_rules.activate_in
-          </label>
-        </h3>
-        <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 []}
-        />
-      </div>
-    </div>
-    <footer
-      className="modal-foot"
-    >
-      <SubmitButton
-        disabled={false}
-        id="coding-rules-submit-bulk-change"
-      >
-        apply
-      </SubmitButton>
-      <ResetButtonLink
-        onClick={[MockFunction]}
-      >
-        cancel
-      </ResetButtonLink>
-    </footer>
-  </form>
-</Modal>
-`;
-
-exports[`should render correctly: results 1`] = `
-<Modal
-  contentLabel="coding_rules.activate_in_quality_profile (42 coding_rules._rules)"
-  onRequestClose={[MockFunction]}
-  size="small"
->
-  <form
-    onSubmit={[Function]}
-  >
-    <header
-      className="modal-head"
-    >
-      <h2>
-        coding_rules.activate_in_quality_profile (42 coding_rules._rules)
-      </h2>
-    </header>
-    <div
-      className="modal-body"
-    >
-      <Alert
-        key="foo"
-        variant="warning"
-      >
-        coding_rules.bulk_change.warning.name.CSS.0.2
-      </Alert>
-      <Alert
-        key="bar"
-        variant="success"
-      >
-        coding_rules.bulk_change.success.name.CSS.2
-      </Alert>
-      <div
-        className="modal-field"
-      >
-        <h3>
-          <label
-            id="coding-rules-bulk-change-profile"
-          >
-            coding_rules.activate_in
-          </label>
-        </h3>
-        <span>
-          name
-           — 
-          are_you_sure
-        </span>
-      </div>
-    </div>
-    <footer
-      className="modal-foot"
-    >
-      <SubmitButton
-        disabled={false}
-        id="coding-rules-submit-bulk-change"
-      >
-        apply
-      </SubmitButton>
-      <ResetButtonLink
-        onClick={[MockFunction]}
-      >
-        cancel
-      </ResetButtonLink>
-    </footer>
-  </form>
-</Modal>
-`;
-
-exports[`should render correctly: submitting 1`] = `
-<Modal
-  contentLabel="coding_rules.activate_in_quality_profile (42 coding_rules._rules)"
-  onRequestClose={[MockFunction]}
-  size="small"
->
-  <form
-    onSubmit={[Function]}
-  >
-    <header
-      className="modal-head"
-    >
-      <h2>
-        coding_rules.activate_in_quality_profile (42 coding_rules._rules)
-      </h2>
-    </header>
-    <div
-      className="modal-body"
-    />
-    <footer
-      className="modal-foot"
-    >
-      <i
-        className="spinner spacer-right"
-      />
-      <SubmitButton
-        disabled={true}
-        id="coding-rules-submit-bulk-change"
-      >
-        apply
-      </SubmitButton>
-      <ResetButtonLink
-        onClick={[MockFunction]}
-      >
-        cancel
-      </ResetButtonLink>
-    </footer>
-  </form>
-</Modal>
-`;
index f3d5897218d6800c90d0df7e028af31bc54cdc2a..d8f26b52468ba86d8dae6352e556196962d44fa5 100644 (file)
   line-height: 1;
 }
 
-.coding-rules-details-tag-edit-cancel {
+.coding-rules-details-tag-edit-cancel,
+.coding-rules-severity-value svg {
   vertical-align: middle;
 }
 
index 7269073b7603d60a684f7b1f1f91af5a771c3538..ff43df271a464899884b5eda94228910db3979b4 100644 (file)
@@ -23,6 +23,7 @@ import { createStore, Store } from 'redux';
 import { DocumentationEntry } from '../apps/documentation/utils';
 import { Exporter, Profile } from '../apps/quality-profiles/types';
 import { AppState } from '../types/appstate';
+import { RuleRepository } from '../types/coding-rules';
 import { EditionKey } from '../types/editions';
 import { RawIssue } from '../types/issues';
 import { Language } from '../types/languages';
@@ -832,8 +833,6 @@ export function mockDumpStatus(props: Partial<DumpStatus> = {}): DumpStatus {
   };
 }
 
-export function mockRuleRepository(
-  override: Partial<{ key: string; language: string; name: string }> = {}
-) {
+export function mockRuleRepository(override: Partial<RuleRepository> = {}) {
   return { key: 'css', language: 'css', name: 'SonarQube', ...override };
 }
index 92025a107c21351e32b20df0e867546a0bfb9d6a..68c07d46158b2e0d6d0e8543716ea973464d1fe6 100644 (file)
@@ -27,12 +27,13 @@ import { createMemoryHistory, Route, RouteComponent, RouteConfig, Router } from
 import { Store } from 'redux';
 import AppStateContextProvider from '../app/components/app-state/AppStateContextProvider';
 import CurrentUserContextProvider from '../app/components/current-user/CurrentUserContextProvider';
+import { LanguagesContext } from '../app/components/languages/LanguagesContext';
 import { MetricsContext } from '../app/components/metrics/MetricsContext';
 import getStore from '../app/utils/getStore';
 import { RouteWithChildRoutes } from '../app/utils/startReactApp';
 import { Store as State } from '../store/rootReducer';
 import { AppState } from '../types/appstate';
-import { Dict, Metric } from '../types/types';
+import { Dict, Languages, Metric } from '../types/types';
 import { CurrentUser } from '../types/users';
 import { DEFAULT_METRICS } from './mocks/metrics';
 import { mockAppState, mockCurrentUser } from './testMocks';
@@ -42,6 +43,7 @@ interface RenderContext {
   store?: Store<State, any>;
   history?: History;
   appState?: AppState;
+  languages?: Languages;
   currentUser?: CurrentUser;
 }
 
@@ -56,7 +58,7 @@ export function renderComponentApp(
 export function renderApp(
   indexPath: string,
   routes: RouteConfig,
-  context: RenderContext
+  context?: RenderContext
 ): RenderResult {
   return renderRoutedApp(
     <RouteWithChildRoutes path={indexPath} childRoutes={routes} />,
@@ -73,7 +75,8 @@ function renderRoutedApp(
     metrics = DEFAULT_METRICS,
     store = getStore(),
     appState = mockAppState(),
-    history = createMemoryHistory()
+    history = createMemoryHistory(),
+    languages = {}
   }: RenderContext = {}
 ): RenderResult {
   history.push(`/${indexPath}`);
@@ -82,11 +85,13 @@ function renderRoutedApp(
       <IntlProvider defaultLocale="en" locale="en">
         <MetricsContext.Provider value={metrics}>
           <Provider store={store}>
-            <CurrentUserContextProvider currentUser={currentUser}>
-              <AppStateContextProvider appState={appState}>
-                <Router history={history}>{children}</Router>
-              </AppStateContextProvider>
-            </CurrentUserContextProvider>
+            <LanguagesContext.Provider value={languages}>
+              <CurrentUserContextProvider currentUser={currentUser}>
+                <AppStateContextProvider appState={appState}>
+                  <Router history={history}>{children}</Router>
+                </AppStateContextProvider>
+              </CurrentUserContextProvider>
+            </LanguagesContext.Provider>
           </Provider>
         </MetricsContext.Provider>
       </IntlProvider>
index b902375855b27f87b1b19964e8d86a8a0be904e4..0c2a761ec1b57d4a5475214e9ece044481f9c96e 100644 (file)
  */
 import { Dict, Rule, RuleActivation } from './types';
 
+export interface RuleRepository {
+  key: string;
+  language: string;
+  name: string;
+}
+
 export interface GetRulesAppResponse {
   canWrite?: boolean;
-  repositories: { key: string; language: string; name: string }[];
+  repositories: RuleRepository[];
 }
 
 export interface SearchRulesResponse {
index 1c9d9810ee5774ff19b6ed200a077cb760c07187..7d8a9a6f4f3a168295ef5a5198b1d66af8e3fc5b 100644 (file)
@@ -23,3 +23,32 @@ export enum RuleStatus {
   Deprecated = 'DEPRECATED',
   Removed = 'REMOVED'
 }
+
+export interface SearchRulesQuery {
+  activation?: boolean | string;
+  active_severities?: string;
+  asc?: boolean | string;
+  available_since?: string;
+  cwe?: string;
+  f?: string;
+  facets?: string;
+  include_external?: boolean | string;
+  inheritance?: string;
+  is_template?: boolean | string;
+  languages?: string;
+  owaspTop10?: string;
+  p?: number;
+  ps?: number;
+  q?: string;
+  qprofile?: string;
+  repositories?: string;
+  rule_key?: string;
+  s?: string;
+  sansTop25?: string;
+  severities?: string;
+  sonarsourceSecurity?: string;
+  statuses?: string;
+  tags?: string;
+  template_key?: string;
+  types?: string;
+}