]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-8884 Group languages related settings in a single Languages tab
authorPhilippe Perrin <philippe.perrin@sonarsource.com>
Wed, 2 Oct 2019 16:39:32 +0000 (18:39 +0200)
committerSonarTech <sonartech@sonarsource.com>
Mon, 7 Oct 2019 18:21:05 +0000 (20:21 +0200)
14 files changed:
server/sonar-web/src/main/js/apps/settings/components/AdditionalCategories.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/AdditionalCategoryKeys.ts [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/AllCategoriesList.tsx
server/sonar-web/src/main/js/apps/settings/components/AppContainer.tsx
server/sonar-web/src/main/js/apps/settings/components/CategoryOverrides.ts [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/Languages.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/__tests__/AllCategoriesList-test.tsx
server/sonar-web/src/main/js/apps/settings/components/__tests__/AppContainer-test.tsx
server/sonar-web/src/main/js/apps/settings/components/__tests__/Languages-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AllCategoriesList-test.tsx.snap
server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AppContainer-test.tsx.snap
server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/Languages-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/styles.css
sonar-core/src/main/resources/org/sonar/l10n/core.properties

diff --git a/server/sonar-web/src/main/js/apps/settings/components/AdditionalCategories.tsx b/server/sonar-web/src/main/js/apps/settings/components/AdditionalCategories.tsx
new file mode 100644 (file)
index 0000000..4a4e634
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 * as React from 'react';
+import { translate } from 'sonar-ui-common/helpers/l10n';
+import { LANGUAGES_CATEGORY, NEW_CODE_PERIOD_CATEGORY } from './AdditionalCategoryKeys';
+import Languages from './Languages';
+import NewCodePeriod from './NewCodePeriod';
+
+export interface AdditionalCategory {
+  key: string;
+  name: string;
+  renderComponent: (
+    parentComponent: T.Component | undefined,
+    selectedCategory: string
+  ) => JSX.Element;
+  availableGlobally: boolean;
+  availableForProject: boolean;
+}
+
+export const ADDITIONAL_CATEGORIES: AdditionalCategory[] = [
+  {
+    key: LANGUAGES_CATEGORY,
+    name: translate('property.category.languages'),
+    renderComponent: getLanguagesComponent,
+    availableGlobally: true,
+    availableForProject: true
+  },
+  {
+    key: NEW_CODE_PERIOD_CATEGORY,
+    name: translate('settings.new_code_period.category'),
+    renderComponent: getNewCodePeriodComponent,
+    availableGlobally: true,
+    availableForProject: false
+  }
+];
+
+function getLanguagesComponent(component: any, originalCategory: string) {
+  return <Languages component={component} selectedCategory={originalCategory} />;
+}
+
+function getNewCodePeriodComponent() {
+  return <NewCodePeriod />;
+}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/AdditionalCategoryKeys.ts b/server/sonar-web/src/main/js/apps/settings/components/AdditionalCategoryKeys.ts
new file mode 100644 (file)
index 0000000..a1ef533
--- /dev/null
@@ -0,0 +1,22 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.
+ */
+
+export const LANGUAGES_CATEGORY = 'languages';
+export const NEW_CODE_PERIOD_CATEGORY = 'new_code_period';
index 02cedc53b0932c11e89e711304132d5f16ef5ad2..29e22177b697a1c7ef00803f55673d7c6d747b74 100644 (file)
@@ -22,27 +22,24 @@ import { sortBy } from 'lodash';
 import * as React from 'react';
 import { connect } from 'react-redux';
 import { IndexLink } from 'react-router';
-import { translate } from 'sonar-ui-common/helpers/l10n';
 import { getSettingsAppAllCategories, Store } from '../../../store/rootReducer';
 import { getCategoryName } from '../utils';
+import { ADDITIONAL_CATEGORIES } from './AdditionalCategories';
+import { CATEGORY_OVERRIDES } from './CategoryOverrides';
 
 interface Category {
   key: string;
   name: string;
 }
 
-interface Props {
+export interface CategoriesListProps {
   categories: string[];
   component?: T.Component;
   defaultCategory: string;
   selectedCategory: string;
 }
 
-const FIXED_CATEGORIES = [
-  { key: 'new_code_period', name: translate('settings.new_code_period.category') }
-];
-
-export class CategoriesList extends React.PureComponent<Props> {
+export class CategoriesList extends React.PureComponent<CategoriesListProps> {
   renderLink(category: Category) {
     const { component, defaultCategory, selectedCategory } = this.props;
     const pathname = this.props.component ? '/project/settings' : '/settings';
@@ -64,11 +61,18 @@ export class CategoriesList extends React.PureComponent<Props> {
 
   render() {
     const categoriesWithName = this.props.categories
+      .filter(key => !CATEGORY_OVERRIDES[key.toLowerCase()])
       .map(key => ({
         key,
         name: getCategoryName(key)
       }))
-      .concat(!this.props.component ? FIXED_CATEGORIES : []);
+      .concat(
+        this.props.component
+          ? // Project settings
+            ADDITIONAL_CATEGORIES.filter(c => c.availableForProject)
+          : // Global settings
+            ADDITIONAL_CATEGORIES.filter(c => c.availableGlobally)
+      );
     const sortedCategories = sortBy(categoriesWithName, category => category.name.toLowerCase());
     return (
       <ul className="side-tabs-menu">
index 3a2d3a956da3abb7965757237fe92a9ed2fe65d1..0880ce8edffc402d6e12363d71e966345c15ff94 100644 (file)
@@ -17,6 +17,8 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+
+import { find } from 'lodash';
 import * as React from 'react';
 import Helmet from 'react-helmet';
 import { connect } from 'react-redux';
@@ -27,9 +29,10 @@ import { getSettingsAppDefaultCategory, Store } from '../../../store/rootReducer
 import '../side-tabs.css';
 import { fetchSettings } from '../store/actions';
 import '../styles.css';
+import { ADDITIONAL_CATEGORIES } from './AdditionalCategories';
 import AllCategoriesList from './AllCategoriesList';
 import CategoryDefinitionsList from './CategoryDefinitionsList';
-import NewCodePeriod from './NewCodePeriod';
+import { CATEGORY_OVERRIDES } from './CategoryOverrides';
 import PageHeader from './PageHeader';
 import WildcardsHelp from './WildcardsHelp';
 
@@ -79,7 +82,16 @@ export class App extends React.PureComponent<Props & WithRouterProps, State> {
     }
 
     const { query } = this.props.location;
-    const selectedCategory = query.category || this.props.defaultCategory;
+
+    const originalCategory = (query.category as string) || this.props.defaultCategory;
+    const overriddenCategory = CATEGORY_OVERRIDES[originalCategory.toLowerCase()];
+    const selectedCategory = overriddenCategory || originalCategory;
+    const foundAdditionalCategory = find(ADDITIONAL_CATEGORIES, c => c.key === selectedCategory);
+    const isProjectSettings = this.props.component;
+    const shouldRenderAdditionalCategory =
+      foundAdditionalCategory &&
+      ((isProjectSettings && foundAdditionalCategory.availableForProject) ||
+        (!isProjectSettings && foundAdditionalCategory.availableGlobally));
 
     return (
       <div className="page page-limited" id="settings-page">
@@ -97,8 +109,8 @@ export class App extends React.PureComponent<Props & WithRouterProps, State> {
             />
           </div>
           <div className="side-tabs-main">
-            {!this.props.component && selectedCategory === 'new_code_period' ? (
-              <NewCodePeriod />
+            {foundAdditionalCategory && shouldRenderAdditionalCategory ? (
+              foundAdditionalCategory.renderComponent(this.props.component, originalCategory)
             ) : (
               <CategoryDefinitionsList
                 category={selectedCategory}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/CategoryOverrides.ts b/server/sonar-web/src/main/js/apps/settings/components/CategoryOverrides.ts
new file mode 100644 (file)
index 0000000..df96008
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 { LANGUAGES_CATEGORY } from './AdditionalCategoryKeys';
+
+export const CATEGORY_OVERRIDES: T.Dict<string> = {
+  abap: LANGUAGES_CATEGORY,
+  apex: LANGUAGES_CATEGORY,
+  'c / c++ / objective-c': LANGUAGES_CATEGORY,
+  'c#': LANGUAGES_CATEGORY,
+  cobol: LANGUAGES_CATEGORY,
+  css: LANGUAGES_CATEGORY,
+  flex: LANGUAGES_CATEGORY,
+  go: LANGUAGES_CATEGORY,
+  html: LANGUAGES_CATEGORY,
+  java: LANGUAGES_CATEGORY,
+  javascript: LANGUAGES_CATEGORY,
+  kotlin: LANGUAGES_CATEGORY,
+  php: LANGUAGES_CATEGORY,
+  'pl/i': LANGUAGES_CATEGORY,
+  'pl/sql': LANGUAGES_CATEGORY,
+  python: LANGUAGES_CATEGORY,
+  rpg: LANGUAGES_CATEGORY,
+  ruby: LANGUAGES_CATEGORY,
+  scala: LANGUAGES_CATEGORY,
+  swift: LANGUAGES_CATEGORY,
+  't-sql': LANGUAGES_CATEGORY,
+  typescript: LANGUAGES_CATEGORY,
+  'vb.net': LANGUAGES_CATEGORY,
+  'visual basic': LANGUAGES_CATEGORY,
+  xml: LANGUAGES_CATEGORY
+};
diff --git a/server/sonar-web/src/main/js/apps/settings/components/Languages.tsx b/server/sonar-web/src/main/js/apps/settings/components/Languages.tsx
new file mode 100644 (file)
index 0000000..35a88f9
--- /dev/null
@@ -0,0 +1,130 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 * as React from 'react';
+import { connect } from 'react-redux';
+import Select from 'sonar-ui-common/components/controls/Select';
+import { translate } from 'sonar-ui-common/helpers/l10n';
+import { Location, Router, withRouter } from '../../../components/hoc/withRouter';
+import { getSettingsAppAllCategories, Store } from '../../../store/rootReducer';
+import { getCategoryName } from '../utils';
+import { LANGUAGES_CATEGORY } from './AdditionalCategoryKeys';
+import CategoryDefinitionsList from './CategoryDefinitionsList';
+import { CATEGORY_OVERRIDES } from './CategoryOverrides';
+
+export interface LanguagesProps {
+  categories: string[];
+  component?: T.Component;
+  location: Location;
+  selectedCategory: string;
+  router: Router;
+}
+
+interface LanguagesState {
+  availableLanguages: SelectOption[];
+  selectedLanguage: string | undefined;
+}
+
+interface SelectOption {
+  label: string;
+  originalValue: string;
+  value: string;
+}
+
+export class Languages extends React.PureComponent<LanguagesProps, LanguagesState> {
+  constructor(props: LanguagesProps) {
+    super(props);
+
+    this.state = {
+      availableLanguages: [],
+      selectedLanguage: undefined
+    };
+  }
+
+  componentDidMount() {
+    const { selectedCategory, categories } = this.props;
+    const lowerCasedLanguagesCategory = LANGUAGES_CATEGORY.toLowerCase();
+    const lowerCasedSelectedCategory = selectedCategory.toLowerCase();
+
+    const availableLanguages = categories
+      .filter(c => CATEGORY_OVERRIDES[c.toLowerCase()] === lowerCasedLanguagesCategory)
+      .map(c => ({
+        label: getCategoryName(c),
+        value: c.toLowerCase(),
+        originalValue: c
+      }));
+
+    let selectedLanguage = undefined;
+
+    if (
+      lowerCasedSelectedCategory !== lowerCasedLanguagesCategory &&
+      availableLanguages.find(c => c.value === lowerCasedSelectedCategory)
+    ) {
+      selectedLanguage = lowerCasedSelectedCategory;
+    }
+
+    this.setState({
+      availableLanguages,
+      selectedLanguage
+    });
+  }
+
+  handleOnChange = (newOption: SelectOption) => {
+    this.setState({ selectedLanguage: newOption.value });
+
+    const { location, router } = this.props;
+
+    router.push({
+      ...location,
+      query: { ...location.query, category: newOption.originalValue }
+    });
+  };
+
+  render() {
+    const { component } = this.props;
+    const { availableLanguages, selectedLanguage } = this.state;
+
+    return (
+      <>
+        <h2 className="settings-sub-category-name">{translate('property.category.languages')}</h2>
+        <div data-test="language-select">
+          <Select
+            className="input-large"
+            onChange={this.handleOnChange}
+            options={availableLanguages}
+            placeholder={translate('settings.languages.select_a_language_placeholder')}
+            value={selectedLanguage}
+          />
+        </div>
+        {selectedLanguage && (
+          <div className="settings-sub-category">
+            <CategoryDefinitionsList category={selectedLanguage} component={component} />
+          </div>
+        )}
+      </>
+    );
+  }
+}
+
+export default withRouter(
+  connect((state: Store) => ({
+    categories: getSettingsAppAllCategories(state)
+  }))(Languages)
+);
index 699ee214aabfc2c5bcb915ae26a3dcc8c7135522..9bbdb156d932b8bcd1210fe0e63a5014d7c7a2c6 100644 (file)
  */
 import { shallow } from 'enzyme';
 import * as React from 'react';
-import { CategoriesList } from '../AllCategoriesList';
+import { mockComponent } from '../../../../helpers/testMocks';
+import { AdditionalCategory } from '../AdditionalCategories';
+import { CategoriesList, CategoriesListProps } from '../AllCategoriesList';
 
-it('should render correctly', () => {
-  const wrapper = shallowRender();
-  expect(wrapper).toMatchSnapshot();
-  expect(wrapper.find('li')).toHaveLength(3);
+jest.mock('../AdditionalCategories', () => ({
+  ADDITIONAL_CATEGORIES: [
+    {
+      key: 'CAT_1',
+      name: 'CAT_1_NAME',
+      renderComponent: jest.fn(),
+      availableGlobally: true,
+      availableForProject: true
+    },
+    {
+      key: 'CAT_2',
+      name: 'CAT_2_NAME',
+      renderComponent: jest.fn(),
+      availableGlobally: true,
+      availableForProject: false
+    },
+    {
+      key: 'CAT_3',
+      name: 'CAT_3_NAME',
+      renderComponent: jest.fn(),
+      availableGlobally: false,
+      availableForProject: true
+    }
+  ] as AdditionalCategory[]
+}));
+
+it('should render correctly in global mode', () => {
+  expect(shallowRender()).toMatchSnapshot();
+});
+
+it('should render correctly in project mode', () => {
+  expect(shallowRender({ component: mockComponent() })).toMatchSnapshot();
 });
 
-function shallowRender(props: Partial<CategoriesList['props']> = {}) {
-  const categories = ['COBOL', 'general'];
+function shallowRender(props?: Partial<CategoriesListProps>) {
+  const categories = ['general'];
 
   return shallow(
     <CategoriesList
index 65bbcb4824e41af0629c429c0bbfe4ff95f74495..46e41ab1b67a4daaa1894152f3079569a121f0e4 100644 (file)
@@ -19,8 +19,9 @@
  */
 import { shallow } from 'enzyme';
 import * as React from 'react';
-import { App } from '../AppContainer';
 import { mockLocation, mockRouter } from '../../../../helpers/testMocks';
+import { LANGUAGES_CATEGORY, NEW_CODE_PERIOD_CATEGORY } from '../AdditionalCategoryKeys';
+import { App } from '../AppContainer';
 
 it('should render correctly', () => {
   const wrapper = shallowRender();
@@ -29,7 +30,14 @@ it('should render correctly', () => {
 
 it('should render newCodePeriod correctly', () => {
   const wrapper = shallowRender({
-    location: mockLocation({ query: { category: 'new_code_period' } })
+    location: mockLocation({ query: { category: NEW_CODE_PERIOD_CATEGORY } })
+  });
+  expect(wrapper).toMatchSnapshot();
+});
+
+it('should render languages correctly', () => {
+  const wrapper = shallowRender({
+    location: mockLocation({ query: { category: LANGUAGES_CATEGORY } })
   });
   expect(wrapper).toMatchSnapshot();
 });
diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/Languages-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/__tests__/Languages-test.tsx
new file mode 100644 (file)
index 0000000..1ab345b
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 { mockLocation, mockRouter } from '../../../../helpers/testMocks';
+import { Languages, LanguagesProps } from '../Languages';
+
+it('should render correctly', () => {
+  const wrapper = shallowRender();
+  expect(wrapper).toMatchSnapshot();
+});
+
+it('should render correctly with an unknow language', () => {
+  const wrapper = shallowRender({ selectedCategory: 'unknown' });
+  expect(wrapper).toMatchSnapshot();
+});
+
+it('should correctly handle a change of the selected language', () => {
+  const push = jest.fn();
+  const router = mockRouter({ push });
+  const wrapper = shallowRender({ router });
+  expect(wrapper.state().selectedLanguage).toBe('java');
+
+  wrapper.instance().handleOnChange({ label: '', originalValue: 'CoBoL', value: 'cobol' });
+  expect(wrapper.state().selectedLanguage).toBe('cobol');
+  expect(push).toHaveBeenCalledWith(expect.objectContaining({ query: { category: 'CoBoL' } }));
+});
+
+function shallowRender(props: Partial<LanguagesProps> = {}) {
+  return shallow<Languages>(
+    <Languages
+      categories={['Java', 'JavaScript', 'COBOL']}
+      location={mockLocation()}
+      router={mockRouter()}
+      selectedCategory="java"
+      {...props}
+    />
+  );
+}
index 88899f054657ccc3d6a9a1764397bed812e32df4..d2529468bd1f3143df3b55442aa7901338efaec1 100644 (file)
@@ -1,26 +1,45 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`should render correctly 1`] = `
+exports[`should render correctly in global mode 1`] = `
 <ul
   className="side-tabs-menu"
 >
   <li
-    key="COBOL"
+    key="CAT_1"
   >
     <IndexLink
       className=""
-      title="COBOL"
+      title="CAT_1_NAME"
       to={
         Object {
           "pathname": "/settings",
           "query": Object {
-            "category": "cobol",
+            "category": "cat_1",
             "id": undefined,
           },
         }
       }
     >
-      COBOL
+      CAT_1_NAME
+    </IndexLink>
+  </li>
+  <li
+    key="CAT_2"
+  >
+    <IndexLink
+      className=""
+      title="CAT_2_NAME"
+      to={
+        Object {
+          "pathname": "/settings",
+          "query": Object {
+            "category": "cat_2",
+            "id": undefined,
+          },
+        }
+      }
+    >
+      CAT_2_NAME
     </IndexLink>
   </li>
   <li
@@ -42,23 +61,68 @@ exports[`should render correctly 1`] = `
       general
     </IndexLink>
   </li>
+</ul>
+`;
+
+exports[`should render correctly in project mode 1`] = `
+<ul
+  className="side-tabs-menu"
+>
   <li
-    key="new_code_period"
+    key="CAT_1"
   >
     <IndexLink
       className=""
-      title="settings.new_code_period.category"
+      title="CAT_1_NAME"
       to={
         Object {
-          "pathname": "/settings",
+          "pathname": "/project/settings",
           "query": Object {
-            "category": "new_code_period",
-            "id": undefined,
+            "category": "cat_1",
+            "id": "my-project",
           },
         }
       }
     >
-      settings.new_code_period.category
+      CAT_1_NAME
+    </IndexLink>
+  </li>
+  <li
+    key="CAT_3"
+  >
+    <IndexLink
+      className=""
+      title="CAT_3_NAME"
+      to={
+        Object {
+          "pathname": "/project/settings",
+          "query": Object {
+            "category": "cat_3",
+            "id": "my-project",
+          },
+        }
+      }
+    >
+      CAT_3_NAME
+    </IndexLink>
+  </li>
+  <li
+    key="general"
+  >
+    <IndexLink
+      className=""
+      title="general"
+      to={
+        Object {
+          "pathname": "/project/settings",
+          "query": Object {
+            "category": undefined,
+            "id": "my-project",
+          },
+        }
+      }
+    >
+      general
     </IndexLink>
   </li>
 </ul>
index 1c4a7ae75b877f44033ec28b74b36c97b45214fd..2f43729d145a12d6f55116a8d2c7a90adc7ebe24 100644 (file)
@@ -2,4 +2,6 @@
 
 exports[`should render correctly 1`] = `""`;
 
+exports[`should render languages correctly 1`] = `""`;
+
 exports[`should render newCodePeriod correctly 1`] = `""`;
diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/Languages-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/Languages-test.tsx.snap
new file mode 100644 (file)
index 0000000..b351829
--- /dev/null
@@ -0,0 +1,85 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<Fragment>
+  <h2
+    className="settings-sub-category-name"
+  >
+    property.category.languages
+  </h2>
+  <div
+    data-test="language-select"
+  >
+    <Select
+      className="input-large"
+      onChange={[Function]}
+      options={
+        Array [
+          Object {
+            "label": "Java",
+            "originalValue": "Java",
+            "value": "java",
+          },
+          Object {
+            "label": "JavaScript",
+            "originalValue": "JavaScript",
+            "value": "javascript",
+          },
+          Object {
+            "label": "COBOL",
+            "originalValue": "COBOL",
+            "value": "cobol",
+          },
+        ]
+      }
+      placeholder="settings.languages.select_a_language_placeholder"
+      value="java"
+    />
+  </div>
+  <div
+    className="settings-sub-category"
+  >
+    <Connect(SubCategoryDefinitionsList)
+      category="java"
+    />
+  </div>
+</Fragment>
+`;
+
+exports[`should render correctly with an unknow language 1`] = `
+<Fragment>
+  <h2
+    className="settings-sub-category-name"
+  >
+    property.category.languages
+  </h2>
+  <div
+    data-test="language-select"
+  >
+    <Select
+      className="input-large"
+      onChange={[Function]}
+      options={
+        Array [
+          Object {
+            "label": "Java",
+            "originalValue": "Java",
+            "value": "java",
+          },
+          Object {
+            "label": "JavaScript",
+            "originalValue": "JavaScript",
+            "value": "javascript",
+          },
+          Object {
+            "label": "COBOL",
+            "originalValue": "COBOL",
+            "value": "cobol",
+          },
+        ]
+      }
+      placeholder="settings.languages.select_a_language_placeholder"
+    />
+  </div>
+</Fragment>
+`;
index bdbca6a2ad73dfc1b708d6b9ae641b821462818b..37198351c10fa40558e335e6fc27f5fef50f00d5 100644 (file)
   border-top: 1px dotted var(--barBorderColor);
 }
 
-.settings-sub-categories-list {
-}
-
-.settings-sub-categories-list > li {
-}
-
-.settings-sub-categories-list > li + li {
+.settings-sub-categories-list > li + li,
+.settings-sub-category {
   margin: 30px -20px 0;
   padding: 30px 20px;
   border-top: 1px solid var(--barBorderColor);
index 707315c440f84f7a6564ad67c0321da144f34d89..105b7be3c1ca6584b4db3278005a96a18ffb5086 100644 (file)
@@ -920,6 +920,7 @@ settings.new_code_period.title=Default New Code Period behavior
 settings.new_code_period.description=The New Code Period is the period used to compare measures and track new issues. {link}
 settings.new_code_period.description2=This setting is the default for all projects. A specific New Code Period setting can be configured at project level.
 
+settings.languages.select_a_language_placeholder=Select a language
 
 property.category.general=General
 property.category.general.email=Email
@@ -941,6 +942,7 @@ property.category.duplications=Duplications
 property.category.localization=Localization
 property.category.exclusions=Analysis Scope
 property.category.webhooks=Webhooks
+property.category.languages=Languages
 property.sonar.inclusions.name=Source File Inclusions
 property.sonar.inclusions.description=Patterns used to include some source files and only these ones in analysis.
 property.sonar.test.inclusions.name=Test File Inclusions