diff options
author | Grégoire Aubert <gregoire.aubert@sonarsource.com> | 2019-11-20 09:06:17 +0100 |
---|---|---|
committer | SonarTech <sonartech@sonarsource.com> | 2019-11-25 15:12:41 +0100 |
commit | 8a0d7256ae199f2305eb3a750258ba38976f4bd2 (patch) | |
tree | 9b4ea7db146c3cd48657e50db0f7bd6a1e0a23b0 | |
parent | 314a594674f6708eeb8cc9d15e013a3fa5697b7d (diff) | |
download | sonarqube-8a0d7256ae199f2305eb3a750258ba38976f4bd2.tar.gz sonarqube-8a0d7256ae199f2305eb3a750258ba38976f4bd2.zip |
SONAR-12500 Bug fix : can't update language's setting at the project level
10 files changed, 312 insertions, 89 deletions
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 index 58adf6ca8b3..072620bac6f 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/AdditionalCategories.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/AdditionalCategories.tsx @@ -34,17 +34,17 @@ import PullRequestDecoration from './pullRequestDecoration/PullRequestDecoration import PullRequestDecorationBinding from './pullRequestDecorationBinding/PRDecorationBinding'; export interface AdditionalCategoryComponentProps { - parentComponent: T.Component | undefined; + component: T.Component | undefined; selectedCategory: string; } export interface AdditionalCategory { - key: string; - name: string; - renderComponent: (props: AdditionalCategoryComponentProps) => React.ReactNode; availableGlobally: boolean; availableForProject: boolean; displayTab: boolean; + key: string; + name: string; + renderComponent: (props: AdditionalCategoryComponentProps) => React.ReactNode; requiresBranchesEnabled?: boolean; } @@ -110,6 +110,5 @@ function getPullRequestDecorationComponent() { } function getPullRequestDecorationBindingComponent(props: AdditionalCategoryComponentProps) { - const { parentComponent } = props; - return parentComponent && <PullRequestDecorationBinding component={parentComponent} />; + return props.component && <PullRequestDecorationBinding component={props.component} />; } diff --git a/server/sonar-web/src/main/js/apps/settings/components/AnalysisScope.tsx b/server/sonar-web/src/main/js/apps/settings/components/AnalysisScope.tsx index 4bdf1167b2f..2097da92bc4 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/AnalysisScope.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/AnalysisScope.tsx @@ -25,7 +25,7 @@ import { AdditionalCategoryComponentProps } from './AdditionalCategories'; import CategoryDefinitionsList from './CategoryDefinitionsList'; export function AnalysisScope(props: AdditionalCategoryComponentProps) { - const { parentComponent, selectedCategory } = props; + const { component, selectedCategory } = props; return ( <> @@ -56,7 +56,7 @@ export function AnalysisScope(props: AdditionalCategoryComponentProps) { </table> <div className="settings-sub-category"> - <CategoryDefinitionsList category={selectedCategory} component={parentComponent} /> + <CategoryDefinitionsList category={selectedCategory} component={component} /> </div> </> ); diff --git a/server/sonar-web/src/main/js/apps/settings/components/AppContainer.tsx b/server/sonar-web/src/main/js/apps/settings/components/AppContainer.tsx index 86a8dd1d113..df745050276 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/AppContainer.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/AppContainer.tsx @@ -109,7 +109,7 @@ export class App extends React.PureComponent<Props & WithRouterProps, State> { <div className="side-tabs-main"> {foundAdditionalCategory && shouldRenderAdditionalCategory ? ( foundAdditionalCategory.renderComponent({ - parentComponent: this.props.component, + component: this.props.component, selectedCategory: originalCategory }) ) : ( 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 index 35a88f9280a..7ec8a55a8cb 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/Languages.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/Languages.tsx @@ -25,102 +25,80 @@ 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 { AdditionalCategoryComponentProps } from './AdditionalCategories'; import { LANGUAGES_CATEGORY } from './AdditionalCategoryKeys'; import CategoryDefinitionsList from './CategoryDefinitionsList'; import { CATEGORY_OVERRIDES } from './CategoryOverrides'; -export interface LanguagesProps { +export interface LanguagesProps extends AdditionalCategoryComponentProps { 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; +export function Languages(props: LanguagesProps) { + const { categories, component, location, router, selectedCategory } = props; + const { availableLanguages, selectedLanguage } = getLanguages(categories, selectedCategory); + const handleOnChange = (newOption: SelectOption) => { 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} - /> + return ( + <> + <h2 className="settings-sub-category-name">{translate('property.category.languages')}</h2> + <div data-test="language-select"> + <Select + className="input-large" + onChange={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> - {selectedLanguage && ( - <div className="settings-sub-category"> - <CategoryDefinitionsList category={selectedLanguage} component={component} /> - </div> - )} - </> - ); + )} + </> + ); +} + +function getLanguages(categories: string[], selectedCategory: string) { + 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; } + + return { + availableLanguages, + selectedLanguage + }; } export default withRouter( diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/AdditionalCategories-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/__tests__/AdditionalCategories-test.tsx new file mode 100644 index 00000000000..77afccd8e84 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/settings/components/__tests__/AdditionalCategories-test.tsx @@ -0,0 +1,50 @@ +/* + * 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 { find } from 'lodash'; +import { mockComponent } from '../../../../helpers/testMocks'; +import { ADDITIONAL_CATEGORIES } from '../AdditionalCategories'; +import { PULL_REQUEST_DECORATION_BINDING_CATEGORY } from '../AdditionalCategoryKeys'; + +it('should render additional categories component correctly', () => { + ADDITIONAL_CATEGORIES.forEach(cat => { + expect( + cat.renderComponent({ + component: mockComponent(), + selectedCategory: 'TEST' + }) + ).toMatchSnapshot(); + }); +}); + +it('should not render pull request decoration binding component when the component is not defined', () => { + const category = find( + ADDITIONAL_CATEGORIES, + c => c.key === PULL_REQUEST_DECORATION_BINDING_CATEGORY + ); + + if (!category) { + fail('category should be defined'); + } else { + expect( + category.renderComponent({ component: undefined, selectedCategory: '' }) + ).toBeUndefined(); + } +}); diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/AnalysisScope-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/__tests__/AnalysisScope-test.tsx index 3711f71960b..df679218c1c 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/AnalysisScope-test.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/__tests__/AnalysisScope-test.tsx @@ -28,5 +28,5 @@ it('should render correctly', () => { }); function shallowRender() { - return shallow(<AnalysisScope parentComponent={mockComponent()} selectedCategory="TEST" />); + return shallow(<AnalysisScope component={mockComponent()} selectedCategory="TEST" />); } diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/AppContainer-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/__tests__/AppContainer-test.tsx index b7c605015b9..a0f3990d0f6 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/AppContainer-test.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/__tests__/AppContainer-test.tsx @@ -24,7 +24,9 @@ import { mockLocation, mockRouter } from '../../../../helpers/testMocks'; import { ANALYSIS_SCOPE_CATEGORY, LANGUAGES_CATEGORY, - NEW_CODE_PERIOD_CATEGORY + NEW_CODE_PERIOD_CATEGORY, + PULL_REQUEST_DECORATION_BINDING_CATEGORY, + PULL_REQUEST_DECORATION_CATEGORY } from '../AdditionalCategoryKeys'; import { App } from '../AppContainer'; @@ -62,6 +64,24 @@ it('should render analysis scope correctly', async () => { expect(wrapper).toMatchSnapshot(); }); +it('should render pull request decoration correctly', async () => { + const wrapper = shallowRender({ + location: mockLocation({ query: { category: PULL_REQUEST_DECORATION_CATEGORY } }) + }); + + await waitAndUpdate(wrapper); + expect(wrapper).toMatchSnapshot(); +}); + +it('should render pull request decoration binding correctly', async () => { + const wrapper = shallowRender({ + location: mockLocation({ query: { category: PULL_REQUEST_DECORATION_BINDING_CATEGORY } }) + }); + + await waitAndUpdate(wrapper); + expect(wrapper).toMatchSnapshot(); +}); + function shallowRender(props: Partial<App['props']> = {}) { return shallow( <App 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 index 1ab345b09e9..738b13c1693 100644 --- 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 @@ -17,9 +17,12 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + import { shallow } from 'enzyme'; import * as React from 'react'; -import { mockLocation, mockRouter } from '../../../../helpers/testMocks'; +import Select from 'sonar-ui-common/components/controls/Select'; +import { mockComponent, mockLocation, mockRouter } from '../../../../helpers/testMocks'; +import CategoryDefinitionsList from '../CategoryDefinitionsList'; import { Languages, LanguagesProps } from '../Languages'; it('should render correctly', () => { @@ -36,17 +39,29 @@ 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'); + expect(wrapper.find(CategoryDefinitionsList).props().category).toBe('java'); + + const { onChange } = wrapper.find(Select).props(); + + if (!onChange) { + fail('onChange should be defined'); + } else { + onChange({ label: '', originalValue: 'CoBoL', value: 'cobol' }); + expect(push).toHaveBeenCalledWith(expect.objectContaining({ query: { category: 'CoBoL' } })); + } +}); - wrapper.instance().handleOnChange({ label: '', originalValue: 'CoBoL', value: 'cobol' }); - expect(wrapper.state().selectedLanguage).toBe('cobol'); - expect(push).toHaveBeenCalledWith(expect.objectContaining({ query: { category: 'CoBoL' } })); +it('should correctly show the subcategory for a component', () => { + const component = mockComponent(); + const wrapper = shallowRender({ component }); + expect(wrapper.find(CategoryDefinitionsList).props().component).toBe(component); }); function shallowRender(props: Partial<LanguagesProps> = {}) { - return shallow<Languages>( + return shallow( <Languages categories={['Java', 'JavaScript', 'COBOL']} + component={undefined} location={mockLocation()} router={mockRouter()} selectedCategory="java" diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AdditionalCategories-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AdditionalCategories-test.tsx.snap new file mode 100644 index 00000000000..2a421ce756f --- /dev/null +++ b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AdditionalCategories-test.tsx.snap @@ -0,0 +1,91 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render additional categories component correctly 1`] = ` +<withRouter(Connect(Languages)) + component={ + Object { + "breadcrumbs": Array [], + "key": "my-project", + "name": "MyProject", + "organization": "foo", + "qualifier": "TRK", + "qualityGate": Object { + "isDefault": true, + "key": "30", + "name": "Sonar way", + }, + "qualityProfiles": Array [ + Object { + "deleted": false, + "key": "my-qp", + "language": "ts", + "name": "Sonar way", + }, + ], + "tags": Array [], + } + } + selectedCategory="TEST" +/> +`; + +exports[`should render additional categories component correctly 2`] = `<NewCodePeriod />`; + +exports[`should render additional categories component correctly 3`] = ` +<AnalysisScope + component={ + Object { + "breadcrumbs": Array [], + "key": "my-project", + "name": "MyProject", + "organization": "foo", + "qualifier": "TRK", + "qualityGate": Object { + "isDefault": true, + "key": "30", + "name": "Sonar way", + }, + "qualityProfiles": Array [ + Object { + "deleted": false, + "key": "my-qp", + "language": "ts", + "name": "Sonar way", + }, + ], + "tags": Array [], + } + } + selectedCategory="TEST" +/> +`; + +exports[`should render additional categories component correctly 4`] = `<PullRequestDecoration />`; + +exports[`should render additional categories component correctly 5`] = ` +<PRDecorationBinding + component={ + Object { + "breadcrumbs": Array [], + "key": "my-project", + "name": "MyProject", + "organization": "foo", + "qualifier": "TRK", + "qualityGate": Object { + "isDefault": true, + "key": "30", + "name": "Sonar way", + }, + "qualityProfiles": Array [ + Object { + "deleted": false, + "key": "my-qp", + "language": "ts", + "name": "Sonar way", + }, + ], + "tags": Array [], + } + } +/> +`; diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AppContainer-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AppContainer-test.tsx.snap index df09da04ef2..d3f5cdc9a8b 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AppContainer-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AppContainer-test.tsx.snap @@ -141,3 +141,73 @@ exports[`should render newCodePeriod correctly 1`] = ` </div> </div> `; + +exports[`should render pull request decoration binding correctly 1`] = ` +<div + className="page page-limited" + id="settings-page" +> + <Suggestions + suggestions="settings" + /> + <HelmetWrapper + defer={true} + encodeSpecialCharacters={true} + title="settings.page" + /> + <PageHeader /> + <div + className="side-tabs-layout settings-layout" + > + <div + className="side-tabs-side" + > + <Connect(CategoriesList) + defaultCategory="general" + selectedCategory="pull_request_decoration_binding" + /> + </div> + <div + className="side-tabs-main" + > + <Connect(SubCategoryDefinitionsList) + category="pull_request_decoration_binding" + /> + </div> + </div> +</div> +`; + +exports[`should render pull request decoration correctly 1`] = ` +<div + className="page page-limited" + id="settings-page" +> + <Suggestions + suggestions="settings" + /> + <HelmetWrapper + defer={true} + encodeSpecialCharacters={true} + title="settings.page" + /> + <PageHeader /> + <div + className="side-tabs-layout settings-layout" + > + <div + className="side-tabs-side" + > + <Connect(CategoriesList) + defaultCategory="general" + selectedCategory="pull_request_decoration" + /> + </div> + <div + className="side-tabs-main" + > + <PullRequestDecoration /> + </div> + </div> +</div> +`; |