From 50c708c1b62796cba971ed6579cb72373261e857 Mon Sep 17 00:00:00 2001 From: Grégoire Aubert Date: Mon, 19 Nov 2018 11:41:06 +0100 Subject: Rewrite remaining of the settings page to TS --- server/sonar-web/src/main/js/api/settings.ts | 17 +- .../sonar-web/src/main/js/app/components/App.tsx | 23 +- .../src/main/js/app/components/PageTracker.tsx | 9 +- .../embed-docs-modal/ProductNewsMenuItem.tsx | 9 +- .../components/nav/global/GlobalNavBranding.tsx | 12 +- server/sonar-web/src/main/js/app/types.ts | 2 + server/sonar-web/src/main/js/apps/about/actions.ts | 3 +- .../src/main/js/apps/about/components/AboutApp.tsx | 25 ++- .../account/organizations/UserOrganizations.tsx | 19 +- .../main/js/apps/account/organizations/actions.ts | 4 +- .../src/main/js/apps/marketplace/AppContainer.tsx | 4 +- .../settings/__tests__/DefinitionActions-test.tsx | 86 -------- .../__snapshots__/DefinitionActions-test.tsx.snap | 143 ------------- .../apps/settings/components/AllCategoriesList.js | 34 --- .../apps/settings/components/AllCategoriesList.tsx | 81 +++++++ .../src/main/js/apps/settings/components/App.js | 94 --------- .../js/apps/settings/components/AppContainer.js | 34 --- .../js/apps/settings/components/AppContainer.tsx | 118 +++++++++++ .../js/apps/settings/components/CategoriesList.js | 83 -------- .../settings/components/CategoryDefinitionsList.js | 39 ---- .../components/CategoryDefinitionsList.tsx | 44 ++++ .../main/js/apps/settings/components/Definition.js | 235 --------------------- .../js/apps/settings/components/Definition.tsx | 227 ++++++++++++++++++++ .../js/apps/settings/components/DefinitionsList.js | 42 ---- .../apps/settings/components/DefinitionsList.tsx | 39 ++++ .../main/js/apps/settings/components/EmailForm.js | 135 ------------ .../main/js/apps/settings/components/EmailForm.tsx | 174 +++++++++++++++ .../main/js/apps/settings/components/PageHeader.js | 51 ----- .../js/apps/settings/components/PageHeader.tsx | 44 ++++ .../components/SubCategoryDefinitionsList.js | 92 -------- .../components/SubCategoryDefinitionsList.tsx | 91 ++++++++ .../js/apps/settings/components/WildcardsHelp.js | 121 ----------- .../js/apps/settings/components/WildcardsHelp.tsx | 122 +++++++++++ .../__tests__/DefinitionActions-test.tsx | 86 ++++++++ .../__snapshots__/DefinitionActions-test.tsx.snap | 143 +++++++++++++ .../src/main/js/apps/settings/store/actions.js | 127 ----------- .../src/main/js/apps/settings/store/actions.ts | 141 +++++++++++++ .../src/main/js/apps/settings/store/definitions.ts | 76 +++++++ .../js/apps/settings/store/definitions/actions.js | 33 --- .../js/apps/settings/store/definitions/reducer.js | 72 ------- .../src/main/js/apps/settings/store/rootReducer.js | 79 ------- .../src/main/js/apps/settings/store/rootReducer.ts | 73 +++++++ .../main/js/apps/settings/store/settingsPage.ts | 113 ++++++++++ .../store/settingsPage/changedValues/actions.js | 33 --- .../store/settingsPage/changedValues/reducer.js | 37 ---- .../settings/store/settingsPage/loading/actions.js | 31 --- .../settings/store/settingsPage/loading/reducer.js | 36 ---- .../js/apps/settings/store/settingsPage/reducer.js | 37 ---- .../settingsPage/validationMessages/actions.js | 33 --- .../settingsPage/validationMessages/reducer.js | 36 ---- .../src/main/js/apps/settings/store/values.ts | 85 ++++++++ .../main/js/apps/settings/store/values/actions.ts | 29 --- .../main/js/apps/settings/store/values/reducer.js | 74 ------- .../sonar-web/src/main/js/apps/settings/types.js | 26 --- .../sonar-web/src/main/js/components/ui/Avatar.tsx | 12 +- server/sonar-web/src/main/js/store/appState.ts | 30 +-- .../sonar-web/src/main/js/store/globalMessages.ts | 4 +- server/sonar-web/src/main/js/store/rootReducer.ts | 4 +- 58 files changed, 1761 insertions(+), 1945 deletions(-) delete mode 100644 server/sonar-web/src/main/js/apps/settings/__tests__/DefinitionActions-test.tsx delete mode 100644 server/sonar-web/src/main/js/apps/settings/__tests__/__snapshots__/DefinitionActions-test.tsx.snap delete mode 100644 server/sonar-web/src/main/js/apps/settings/components/AllCategoriesList.js create mode 100644 server/sonar-web/src/main/js/apps/settings/components/AllCategoriesList.tsx delete mode 100644 server/sonar-web/src/main/js/apps/settings/components/App.js delete mode 100644 server/sonar-web/src/main/js/apps/settings/components/AppContainer.js create mode 100644 server/sonar-web/src/main/js/apps/settings/components/AppContainer.tsx delete mode 100644 server/sonar-web/src/main/js/apps/settings/components/CategoriesList.js delete mode 100644 server/sonar-web/src/main/js/apps/settings/components/CategoryDefinitionsList.js create mode 100644 server/sonar-web/src/main/js/apps/settings/components/CategoryDefinitionsList.tsx delete mode 100644 server/sonar-web/src/main/js/apps/settings/components/Definition.js create mode 100644 server/sonar-web/src/main/js/apps/settings/components/Definition.tsx delete mode 100644 server/sonar-web/src/main/js/apps/settings/components/DefinitionsList.js create mode 100644 server/sonar-web/src/main/js/apps/settings/components/DefinitionsList.tsx delete mode 100644 server/sonar-web/src/main/js/apps/settings/components/EmailForm.js create mode 100644 server/sonar-web/src/main/js/apps/settings/components/EmailForm.tsx delete mode 100644 server/sonar-web/src/main/js/apps/settings/components/PageHeader.js create mode 100644 server/sonar-web/src/main/js/apps/settings/components/PageHeader.tsx delete mode 100644 server/sonar-web/src/main/js/apps/settings/components/SubCategoryDefinitionsList.js create mode 100644 server/sonar-web/src/main/js/apps/settings/components/SubCategoryDefinitionsList.tsx delete mode 100644 server/sonar-web/src/main/js/apps/settings/components/WildcardsHelp.js create mode 100644 server/sonar-web/src/main/js/apps/settings/components/WildcardsHelp.tsx create mode 100644 server/sonar-web/src/main/js/apps/settings/components/__tests__/DefinitionActions-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/DefinitionActions-test.tsx.snap delete mode 100644 server/sonar-web/src/main/js/apps/settings/store/actions.js create mode 100644 server/sonar-web/src/main/js/apps/settings/store/actions.ts create mode 100644 server/sonar-web/src/main/js/apps/settings/store/definitions.ts delete mode 100644 server/sonar-web/src/main/js/apps/settings/store/definitions/actions.js delete mode 100644 server/sonar-web/src/main/js/apps/settings/store/definitions/reducer.js delete mode 100644 server/sonar-web/src/main/js/apps/settings/store/rootReducer.js create mode 100644 server/sonar-web/src/main/js/apps/settings/store/rootReducer.ts create mode 100644 server/sonar-web/src/main/js/apps/settings/store/settingsPage.ts delete mode 100644 server/sonar-web/src/main/js/apps/settings/store/settingsPage/changedValues/actions.js delete mode 100644 server/sonar-web/src/main/js/apps/settings/store/settingsPage/changedValues/reducer.js delete mode 100644 server/sonar-web/src/main/js/apps/settings/store/settingsPage/loading/actions.js delete mode 100644 server/sonar-web/src/main/js/apps/settings/store/settingsPage/loading/reducer.js delete mode 100644 server/sonar-web/src/main/js/apps/settings/store/settingsPage/reducer.js delete mode 100644 server/sonar-web/src/main/js/apps/settings/store/settingsPage/validationMessages/actions.js delete mode 100644 server/sonar-web/src/main/js/apps/settings/store/settingsPage/validationMessages/reducer.js create mode 100644 server/sonar-web/src/main/js/apps/settings/store/values.ts delete mode 100644 server/sonar-web/src/main/js/apps/settings/store/values/actions.ts delete mode 100644 server/sonar-web/src/main/js/apps/settings/store/values/reducer.js delete mode 100644 server/sonar-web/src/main/js/apps/settings/types.js (limited to 'server/sonar-web') diff --git a/server/sonar-web/src/main/js/api/settings.ts b/server/sonar-web/src/main/js/api/settings.ts index fbea55db86b..11b37f1d02f 100644 --- a/server/sonar-web/src/main/js/api/settings.ts +++ b/server/sonar-web/src/main/js/api/settings.ts @@ -23,12 +23,17 @@ import { BranchParameters, SettingCategoryDefinition, SettingValue, - SettingType + SettingType, + SettingDefinition } from '../app/types'; import throwGlobalError from '../app/utils/throwGlobalError'; +import { isCategoryDefinition } from '../apps/settings/utils'; export function getDefinitions(component?: string): Promise { - return getJSON('/api/settings/list_definitions', { component }).then(r => r.definitions); + return getJSON('/api/settings/list_definitions', { component }).then( + r => r.definitions, + throwGlobalError + ); } export function getValues( @@ -37,11 +42,15 @@ export function getValues( return getJSON('/api/settings/values', data).then(r => r.settings); } -export function setSettingValue(definition: any, value: any, component?: string): Promise { +export function setSettingValue( + definition: SettingDefinition, + value: any, + component?: string +): Promise { const { key } = definition; const data: RequestData = { key, component }; - if (definition.multiValues) { + if (isCategoryDefinition(definition) && definition.multiValues) { data.values = value; } else if (definition.type === SettingType.PropertySet) { data.fieldValues = value diff --git a/server/sonar-web/src/main/js/app/components/App.tsx b/server/sonar-web/src/main/js/app/components/App.tsx index e5257a4a171..df1277c2222 100644 --- a/server/sonar-web/src/main/js/app/components/App.tsx +++ b/server/sonar-web/src/main/js/app/components/App.tsx @@ -27,6 +27,7 @@ import { fetchMyOrganizations } from '../../apps/account/organizations/actions'; import { getInstance, isSonarCloud } from '../../helpers/system'; import { lazyLoad } from '../../components/lazyLoad'; import { getCurrentUser, getAppState, getGlobalSettingValue, Store } from '../../store/rootReducer'; +import { isLoggedIn } from '../../helpers/users'; const PageTracker = lazyLoad(() => import('./PageTracker')); @@ -70,10 +71,8 @@ class App extends React.PureComponent { this.mounted = true; this.props.fetchLanguages(); const { appState, currentUser } = this.props; - if (appState && currentUser) { - if (appState.organizationsEnabled && currentUser.isLoggedIn) { - this.props.fetchMyOrganizations(); - } + if (appState && isSonarCloud() && currentUser && isLoggedIn(currentUser)) { + this.props.fetchMyOrganizations(); } } @@ -104,12 +103,16 @@ class App extends React.PureComponent { } } -const mapStateToProps = (state: Store): StateProps => ({ - appState: getAppState(state), - currentUser: getCurrentUser(state), - enableGravatar: (getGlobalSettingValue(state, 'sonar.lf.enableGravatar') || {}).value === 'true', - gravatarServerUrl: (getGlobalSettingValue(state, 'sonar.lf.gravatarServerUrl') || {}).value || '' -}); +const mapStateToProps = (state: Store): StateProps => { + const enableGravatar = getGlobalSettingValue(state, 'sonar.lf.enableGravatar'); + const gravatarServerUrl = getGlobalSettingValue(state, 'sonar.lf.gravatarServerUrl'); + return { + appState: getAppState(state), + currentUser: getCurrentUser(state), + enableGravatar: Boolean(enableGravatar && enableGravatar.value === 'true'), + gravatarServerUrl: (gravatarServerUrl && gravatarServerUrl.value) || '' + }; +}; const mapDispatchToProps = ({ fetchLanguages, diff --git a/server/sonar-web/src/main/js/app/components/PageTracker.tsx b/server/sonar-web/src/main/js/app/components/PageTracker.tsx index 2ae8c85026b..e1620791c9d 100644 --- a/server/sonar-web/src/main/js/app/components/PageTracker.tsx +++ b/server/sonar-web/src/main/js/app/components/PageTracker.tsx @@ -59,8 +59,11 @@ export class PageTracker extends React.PureComponent { } } -const mapStateToProps = (state: Store): StateProps => ({ - trackingId: (getGlobalSettingValue(state, 'sonar.analytics.trackingId') || {}).value -}); +const mapStateToProps = (state: Store): StateProps => { + const trackingId = getGlobalSettingValue(state, 'sonar.analytics.trackingId'); + return { + trackingId: trackingId && trackingId.value + }; +}; export default withRouter(connect(mapStateToProps)(PageTracker)); diff --git a/server/sonar-web/src/main/js/app/components/embed-docs-modal/ProductNewsMenuItem.tsx b/server/sonar-web/src/main/js/app/components/embed-docs-modal/ProductNewsMenuItem.tsx index 3037367c20e..a2565b563f7 100644 --- a/server/sonar-web/src/main/js/app/components/embed-docs-modal/ProductNewsMenuItem.tsx +++ b/server/sonar-web/src/main/js/app/components/embed-docs-modal/ProductNewsMenuItem.tsx @@ -124,8 +124,11 @@ export class ProductNewsMenuItem extends React.PureComponent { } } -const mapStateToProps = (state: Store): StateProps => ({ - accessToken: (getGlobalSettingValue(state, 'sonar.prismic.accessToken') || {}).value -}); +const mapStateToProps = (state: Store): StateProps => { + const accessToken = getGlobalSettingValue(state, 'sonar.prismic.accessToken'); + return { + accessToken: accessToken && accessToken.value + }; +}; export default connect(mapStateToProps)(ProductNewsMenuItem); diff --git a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavBranding.tsx b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavBranding.tsx index 876a1ccc62f..7cc8a5e14a4 100644 --- a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavBranding.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavBranding.tsx @@ -50,9 +50,13 @@ export function SonarCloudNavBranding() { ); } -const mapStateToProps = (state: Store): StateProps => ({ - customLogoUrl: (getGlobalSettingValue(state, 'sonar.lf.logoUrl') || {}).value, - customLogoWidth: (getGlobalSettingValue(state, 'sonar.lf.logoWidthPx') || {}).value -}); +const mapStateToProps = (state: Store): StateProps => { + const customLogoUrl = getGlobalSettingValue(state, 'sonar.lf.logoUrl'); + const customLogoWidth = getGlobalSettingValue(state, 'sonar.lf.logoWidthPx'); + return { + customLogoUrl: customLogoUrl && customLogoUrl.value, + customLogoWidth: customLogoWidth && customLogoWidth.value + }; +}; export default connect(mapStateToProps)(GlobalNavBranding); diff --git a/server/sonar-web/src/main/js/app/types.ts b/server/sonar-web/src/main/js/app/types.ts index 542324c1cd6..427cfe2a463 100644 --- a/server/sonar-web/src/main/js/app/types.ts +++ b/server/sonar-web/src/main/js/app/types.ts @@ -86,6 +86,7 @@ export interface AppState { organizationsEnabled?: boolean; productionDatabase: boolean; qualifiers: string[]; + settings: { [key: string]: string }; standalone?: boolean; version: string; } @@ -739,6 +740,7 @@ export enum SettingType { Boolean = 'BOOLEAN', Float = 'FLOAT', Integer = 'INTEGER', + License = 'LICENSE', Long = 'LONG', SingleSelectList = 'SINGLE_SELECT_LIST', PropertySet = 'PROPERTY_SET' diff --git a/server/sonar-web/src/main/js/apps/about/actions.ts b/server/sonar-web/src/main/js/apps/about/actions.ts index c9f54bb37e1..e99ab66bfb1 100644 --- a/server/sonar-web/src/main/js/apps/about/actions.ts +++ b/server/sonar-web/src/main/js/apps/about/actions.ts @@ -19,12 +19,11 @@ */ import { Dispatch } from 'redux'; import { getValues } from '../../api/settings'; -import { receiveValues } from '../settings/store/values/actions'; +import { receiveValues } from '../settings/store/values'; export function fetchAboutPageSettings() { return (dispatch: Dispatch) => { const keys = ['sonar.lf.aboutText']; - return getValues({ keys: keys.join() }).then(values => { dispatch(receiveValues(values)); }); diff --git a/server/sonar-web/src/main/js/apps/about/components/AboutApp.tsx b/server/sonar-web/src/main/js/apps/about/components/AboutApp.tsx index 6771f875ffe..752000d6e13 100644 --- a/server/sonar-web/src/main/js/apps/about/components/AboutApp.tsx +++ b/server/sonar-web/src/main/js/apps/about/components/AboutApp.tsx @@ -48,7 +48,7 @@ import '../styles.css'; interface Props { appState: Pick; currentUser: CurrentUser; - customText?: { value: string }; + customText?: string; fetchAboutPageSettings: () => Promise; location: Location; } @@ -158,13 +158,9 @@ class AboutApp extends React.PureComponent { - {customText != null && - customText.value && ( -
- )} + {customText && ( +
+ )} @@ -195,11 +191,14 @@ class AboutApp extends React.PureComponent { } } -const mapStateToProps = (state: Store) => ({ - appState: getAppState(state), - currentUser: getCurrentUser(state), - customText: getGlobalSettingValue(state, 'sonar.lf.aboutText') -}); +const mapStateToProps = (state: Store) => { + const customText = getGlobalSettingValue(state, 'sonar.lf.aboutText'); + return { + appState: getAppState(state), + currentUser: getCurrentUser(state), + customText: customText && customText.value + }; +}; const mapDispatchToProps = { fetchAboutPageSettings } as any; diff --git a/server/sonar-web/src/main/js/apps/account/organizations/UserOrganizations.tsx b/server/sonar-web/src/main/js/apps/account/organizations/UserOrganizations.tsx index 94d4f9a7952..3ec744841b8 100644 --- a/server/sonar-web/src/main/js/apps/account/organizations/UserOrganizations.tsx +++ b/server/sonar-web/src/main/js/apps/account/organizations/UserOrganizations.tsx @@ -33,7 +33,7 @@ import { import { Organization } from '../../../app/types'; interface StateProps { - anyoneCanCreate?: { value: string }; + anyoneCanCreate: boolean; canAdmin?: boolean; organizations: Organization[]; } @@ -68,9 +68,7 @@ class UserOrganizations extends React.PureComponent { }; render() { - const anyoneCanCreate = - this.props.anyoneCanCreate != null && this.props.anyoneCanCreate.value === 'true'; - + const { anyoneCanCreate } = this.props; const canCreateOrganizations = !this.state.loading && (anyoneCanCreate || this.props.canAdmin); return ( @@ -100,11 +98,14 @@ class UserOrganizations extends React.PureComponent { } } -const mapStateToProps = (state: Store): StateProps => ({ - anyoneCanCreate: getGlobalSettingValue(state, 'sonar.organizations.anyoneCanCreate'), - canAdmin: getAppState(state).canAdmin, - organizations: getMyOrganizations(state) -}); +const mapStateToProps = (state: Store): StateProps => { + const anyoneCanCreate = getGlobalSettingValue(state, 'sonar.organizations.anyoneCanCreate'); + return { + anyoneCanCreate: Boolean(anyoneCanCreate && anyoneCanCreate.value === 'true'), + canAdmin: getAppState(state).canAdmin, + organizations: getMyOrganizations(state) + }; +}; const mapDispatchToProps = { fetchIfAnyoneCanCreateOrganizations: fetchIfAnyoneCanCreateOrganizations as any diff --git a/server/sonar-web/src/main/js/apps/account/organizations/actions.ts b/server/sonar-web/src/main/js/apps/account/organizations/actions.ts index 73fa8566dcd..329d831e107 100644 --- a/server/sonar-web/src/main/js/apps/account/organizations/actions.ts +++ b/server/sonar-web/src/main/js/apps/account/organizations/actions.ts @@ -21,7 +21,7 @@ import { Dispatch } from 'redux'; import { getOrganizations } from '../../../api/organizations'; import { receiveMyOrganizations } from '../../../store/organizations'; import { getValues } from '../../../api/settings'; -import { receiveValues } from '../../settings/store/values/actions'; +import { receiveValues } from '../../settings/store/values'; export const fetchMyOrganizations = () => (dispatch: Dispatch) => { return getOrganizations({ member: true }).then(({ organizations }) => { @@ -31,6 +31,6 @@ export const fetchMyOrganizations = () => (dispatch: Dispatch) => { export const fetchIfAnyoneCanCreateOrganizations = () => (dispatch: Dispatch) => { return getValues({ keys: 'sonar.organizations.anyoneCanCreate' }).then(values => { - dispatch(receiveValues(values, undefined)); + dispatch(receiveValues(values)); }); }; diff --git a/server/sonar-web/src/main/js/apps/marketplace/AppContainer.tsx b/server/sonar-web/src/main/js/apps/marketplace/AppContainer.tsx index 76627ae77bd..463d28dacbe 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/AppContainer.tsx +++ b/server/sonar-web/src/main/js/apps/marketplace/AppContainer.tsx @@ -36,11 +36,11 @@ interface StateToProps { } const mapStateToProps = (state: Store) => { + const updateCenterActive = getGlobalSettingValue(state, 'sonar.updatecenter.activate'); return { currentEdition: getAppState(state).edition, standaloneMode: getAppState(state).standalone, - updateCenterActive: - (getGlobalSettingValue(state, 'sonar.updatecenter.activate') || {}).value === 'true' + updateCenterActive: Boolean(updateCenterActive && updateCenterActive.value === 'true') }; }; diff --git a/server/sonar-web/src/main/js/apps/settings/__tests__/DefinitionActions-test.tsx b/server/sonar-web/src/main/js/apps/settings/__tests__/DefinitionActions-test.tsx deleted file mode 100644 index 76b1d29491c..00000000000 --- a/server/sonar-web/src/main/js/apps/settings/__tests__/DefinitionActions-test.tsx +++ /dev/null @@ -1,86 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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. - */ -/* eslint-disable import/order */ -import * as React from 'react'; -import { shallow } from 'enzyme'; -import DefinitionActions from '../components/DefinitionActions'; -import { SettingType } from '../../../app/types'; - -const definition = { - category: 'baz', - description: 'lorem', - fields: [], - key: 'key', - name: 'foobar', - options: [], - subCategory: 'bar', - type: SettingType.String -}; - -const settings = { - key: 'key', - definition, - value: 'baz' -}; - -it('displays default message when value is default', () => { - const wrapper = shallowRender('', false, true); - expect(wrapper).toMatchSnapshot(); -}); - -it('displays save button when it can be saved', () => { - const wrapper = shallowRender('foo', false, true); - expect(wrapper).toMatchSnapshot(); -}); - -it('displays cancel button when value changed and no error', () => { - const wrapper = shallowRender('foo', false, true); - expect(wrapper).toMatchSnapshot(); -}); - -it('displays cancel button when value changed and has error', () => { - const wrapper = shallowRender('foo', true, true); - expect(wrapper).toMatchSnapshot(); -}); - -it('disables save button on error', () => { - const wrapper = shallowRender('foo', true, true); - expect(wrapper).toMatchSnapshot(); -}); - -it('displays reset button when empty and not default', () => { - const wrapper = shallowRender('', true, false); - expect(wrapper).toMatchSnapshot(); -}); - -function shallowRender(changedValue: string, hasError: boolean, isDefault: boolean) { - return shallow( - {}} - onReset={() => {}} - onSave={() => {}} - setting={settings} - /> - ); -} diff --git a/server/sonar-web/src/main/js/apps/settings/__tests__/__snapshots__/DefinitionActions-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/__tests__/__snapshots__/DefinitionActions-test.tsx.snap deleted file mode 100644 index 046e36dbc27..00000000000 --- a/server/sonar-web/src/main/js/apps/settings/__tests__/__snapshots__/DefinitionActions-test.tsx.snap +++ /dev/null @@ -1,143 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`disables save button on error 1`] = ` - -
- - -
-
-`; - -exports[`displays cancel button when value changed and has error 1`] = ` - -
- - -
-
-`; - -exports[`displays cancel button when value changed and no error 1`] = ` - -
- - -
-
-`; - -exports[`displays default message when value is default 1`] = ` - -
- settings._default -
-
- - - default - : - settings.default.no_value - -
-
-`; - -exports[`displays reset button when empty and not default 1`] = ` - -
- - - default - : - settings.default.no_value - -
-
-`; - -exports[`displays save button when it can be saved 1`] = ` - -
- - -
-
-`; diff --git a/server/sonar-web/src/main/js/apps/settings/components/AllCategoriesList.js b/server/sonar-web/src/main/js/apps/settings/components/AllCategoriesList.js deleted file mode 100644 index e59bff69f20..00000000000 --- a/server/sonar-web/src/main/js/apps/settings/components/AllCategoriesList.js +++ /dev/null @@ -1,34 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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. - */ -// @flow -import React from 'react'; -import { connect } from 'react-redux'; -import CategoriesList from './CategoriesList'; -import { getSettingsAppAllCategories } from '../../../store/rootReducer'; - -function AllCategoriesList(props) { - return ; -} - -const mapStateToProps = state => ({ - categories: getSettingsAppAllCategories(state) -}); - -export default connect(mapStateToProps)(AllCategoriesList); diff --git a/server/sonar-web/src/main/js/apps/settings/components/AllCategoriesList.tsx b/server/sonar-web/src/main/js/apps/settings/components/AllCategoriesList.tsx new file mode 100644 index 00000000000..f01a2ed2b25 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/settings/components/AllCategoriesList.tsx @@ -0,0 +1,81 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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 * as classNames from 'classnames'; +import { connect } from 'react-redux'; +import { sortBy } from 'lodash'; +import { IndexLink } from 'react-router'; +import { getCategoryName } from '../utils'; +import { Component } from '../../../app/types'; +import { getSettingsAppAllCategories, Store } from '../../../store/rootReducer'; + +interface Category { + key: string; + name: string; +} + +interface Props { + categories: string[]; + component?: Component; + defaultCategory: string; + selectedCategory: string; +} + +export class CategoriesList extends React.PureComponent { + renderLink(category: Category) { + const { component, defaultCategory, selectedCategory } = this.props; + const pathname = this.props.component ? '/project/settings' : '/settings'; + const query = { + category: category.key !== defaultCategory ? category.key.toLowerCase() : undefined, + id: component && component.key + }; + return ( + + {category.name} + + ); + } + + render() { + const categoriesWithName = this.props.categories.map(key => ({ + key, + name: getCategoryName(key) + })); + const sortedCategories = sortBy(categoriesWithName, category => category.name.toLowerCase()); + return ( +
    + {sortedCategories.map(category => ( +
  • {this.renderLink(category)}
  • + ))} +
+ ); + } +} + +const mapStateToProps = (state: Store) => ({ + categories: getSettingsAppAllCategories(state) +}); + +export default connect(mapStateToProps)(CategoriesList); diff --git a/server/sonar-web/src/main/js/apps/settings/components/App.js b/server/sonar-web/src/main/js/apps/settings/components/App.js deleted file mode 100644 index 378494100f6..00000000000 --- a/server/sonar-web/src/main/js/apps/settings/components/App.js +++ /dev/null @@ -1,94 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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. - */ -// @flow -import React from 'react'; -import Helmet from 'react-helmet'; -import PageHeader from './PageHeader'; -import CategoryDefinitionsList from './CategoryDefinitionsList'; -import AllCategoriesList from './AllCategoriesList'; -import WildcardsHelp from './WildcardsHelp'; -import Suggestions from '../../../app/components/embed-docs-modal/Suggestions'; -import { translate } from '../../../helpers/l10n'; -import '../styles.css'; -import '../side-tabs.css'; - -/*:: -type Props = { - component?: { key: string }, - defaultCategory: ?string, - fetchSettings(componentKey: ?string): Promise<*>, - location: { query: {} } -}; -*/ - -/*:: -type State = { - loaded: boolean -}; -*/ - -export default class App extends React.PureComponent { - /*:: props: Props; */ - state /*: State */ = { loaded: false }; - - componentDidMount() { - const componentKey = this.props.component ? this.props.component.key : null; - this.props.fetchSettings(componentKey).then(() => this.setState({ loaded: true })); - } - - componentDidUpdate(prevProps /*: Props*/) { - if (prevProps.component !== this.props.component) { - const componentKey = this.props.component ? this.props.component.key : null; - this.props.fetchSettings(componentKey); - } - } - - render() { - if (!this.state.loaded) { - return null; - } - - const { query } = this.props.location; - const selectedCategory = query.category || this.props.defaultCategory; - - return ( -
- - - - - -
-
- -
-
- - {selectedCategory === 'exclusions' && } -
-
-
- ); - } -} diff --git a/server/sonar-web/src/main/js/apps/settings/components/AppContainer.js b/server/sonar-web/src/main/js/apps/settings/components/AppContainer.js deleted file mode 100644 index d2d4f003e7a..00000000000 --- a/server/sonar-web/src/main/js/apps/settings/components/AppContainer.js +++ /dev/null @@ -1,34 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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 { connect } from 'react-redux'; -import App from './App'; -import { fetchSettings } from '../store/actions'; -import { getSettingsAppDefaultCategory } from '../../../store/rootReducer'; - -const mapStateToProps = state => ({ - defaultCategory: getSettingsAppDefaultCategory(state) -}); - -const mapdispatchToProps = { fetchSettings }; - -export default connect( - mapStateToProps, - mapdispatchToProps -)(App); 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 new file mode 100644 index 00000000000..7a65fab906e --- /dev/null +++ b/server/sonar-web/src/main/js/apps/settings/components/AppContainer.tsx @@ -0,0 +1,118 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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 Helmet from 'react-helmet'; +import { connect } from 'react-redux'; +import { WithRouterProps } from 'react-router'; +import AllCategoriesList from './AllCategoriesList'; +import CategoryDefinitionsList from './CategoryDefinitionsList'; +import PageHeader from './PageHeader'; +import WildcardsHelp from './WildcardsHelp'; +import Suggestions from '../../../app/components/embed-docs-modal/Suggestions'; +import { fetchSettings } from '../store/actions'; +import { getSettingsAppDefaultCategory, Store } from '../../../store/rootReducer'; +import { translate } from '../../../helpers/l10n'; +import { Component } from '../../../app/types'; +import '../styles.css'; +import '../side-tabs.css'; + +interface Props { + component?: Component; + defaultCategory: string; + fetchSettings(component?: string): Promise; +} + +interface State { + loading: boolean; +} + +export class App extends React.PureComponent { + mounted = false; + state: State = { loading: true }; + + componentDidMount() { + this.mounted = true; + this.fetchSettings(); + } + + componentDidUpdate(prevProps: Props) { + if (prevProps.component !== this.props.component) { + this.fetchSettings(); + } + } + + componentWillUnmount() { + this.mounted = false; + } + + fetchSettings = () => { + const { component } = this.props; + this.props.fetchSettings(component && component.key).then(this.stopLoading, this.stopLoading); + }; + + stopLoading = () => { + if (this.mounted) { + this.setState({ loading: false }); + } + }; + + render() { + if (this.state.loading) { + return null; + } + + const { query } = this.props.location; + const selectedCategory = query.category || this.props.defaultCategory; + + return ( +
+ + + + + +
+
+ +
+
+ + {selectedCategory === 'exclusions' && } +
+
+
+ ); + } +} + +const mapStateToProps = (state: Store) => ({ + defaultCategory: getSettingsAppDefaultCategory(state) +}); + +const mapDispatchToProps = { fetchSettings: fetchSettings as any }; + +export default connect( + mapStateToProps, + mapDispatchToProps +)(App); diff --git a/server/sonar-web/src/main/js/apps/settings/components/CategoriesList.js b/server/sonar-web/src/main/js/apps/settings/components/CategoriesList.js deleted file mode 100644 index 8b61e566c2e..00000000000 --- a/server/sonar-web/src/main/js/apps/settings/components/CategoriesList.js +++ /dev/null @@ -1,83 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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. - */ -// @flow -import React from 'react'; -import { sortBy } from 'lodash'; -import { IndexLink } from 'react-router'; -import { getCategoryName } from '../utils'; - -/*:: -type Category = { - key: string, - name: string -}; -*/ - -/*:: -type Props = { - categories: Category[], - component?: { key: string }, - defaultCategory: string, - selectedCategory: string -}; -*/ - -export default class CategoriesList extends React.PureComponent { - /*:: rops: Props; */ - - renderLink(category /*: Category */) { - const query /*: Object */ = {}; - - if (category.key !== this.props.defaultCategory) { - query.category = category.key.toLowerCase(); - } - - if (this.props.component) { - query.id = this.props.component.key; - } - - const className = - category.key.toLowerCase() === this.props.selectedCategory.toLowerCase() ? 'active' : ''; - - const pathname = this.props.component ? '/project/settings' : '/settings'; - - return ( - - {category.name} - - ); - } - - render() { - const categoriesWithName = this.props.categories.map(key => ({ - key, - name: getCategoryName(key) - })); - const sortedCategories = sortBy(categoriesWithName, category => category.name.toLowerCase()); - - return ( -
    - {sortedCategories.map(category => ( -
  • {this.renderLink(category)}
  • - ))} -
- ); - } -} diff --git a/server/sonar-web/src/main/js/apps/settings/components/CategoryDefinitionsList.js b/server/sonar-web/src/main/js/apps/settings/components/CategoryDefinitionsList.js deleted file mode 100644 index 630ce8bbc98..00000000000 --- a/server/sonar-web/src/main/js/apps/settings/components/CategoryDefinitionsList.js +++ /dev/null @@ -1,39 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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. - */ -// @flow -import { connect } from 'react-redux'; -import SubCategoryDefinitionsList from './SubCategoryDefinitionsList'; -import { fetchValues } from '../store/actions'; -import { getSettingsAppSettingsForCategory } from '../../../store/rootReducer'; - -const mapStateToProps = (state, ownProps) => ({ - settings: getSettingsAppSettingsForCategory( - state, - ownProps.category, - ownProps.component ? ownProps.component.key : null - ) -}); - -const mapDispatchToProps = { fetchValues }; - -export default connect( - mapStateToProps, - mapDispatchToProps -)(SubCategoryDefinitionsList); diff --git a/server/sonar-web/src/main/js/apps/settings/components/CategoryDefinitionsList.tsx b/server/sonar-web/src/main/js/apps/settings/components/CategoryDefinitionsList.tsx new file mode 100644 index 00000000000..87f503f9ee8 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/settings/components/CategoryDefinitionsList.tsx @@ -0,0 +1,44 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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 { connect } from 'react-redux'; +import SubCategoryDefinitionsList from './SubCategoryDefinitionsList'; +import { fetchValues } from '../store/actions'; +import { getSettingsAppSettingsForCategory, Store } from '../../../store/rootReducer'; +import { Component } from '../../../app/types'; + +interface Props { + category: string; + component?: Component; +} + +const mapStateToProps = (state: Store, ownProps: Props) => ({ + settings: getSettingsAppSettingsForCategory( + state, + ownProps.category, + ownProps.component && ownProps.component.key + ) +}); + +const mapDispatchToProps = { fetchValues }; + +export default connect( + mapStateToProps, + mapDispatchToProps +)(SubCategoryDefinitionsList); diff --git a/server/sonar-web/src/main/js/apps/settings/components/Definition.js b/server/sonar-web/src/main/js/apps/settings/components/Definition.js deleted file mode 100644 index 1ce6ac3ba10..00000000000 --- a/server/sonar-web/src/main/js/apps/settings/components/Definition.js +++ /dev/null @@ -1,235 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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. - */ -// @flow -import React from 'react'; -import PropTypes from 'prop-types'; -import { connect } from 'react-redux'; -import classNames from 'classnames'; -import Input from './inputs/Input'; -import DefinitionActions from './DefinitionActions'; -import { - getPropertyName, - getPropertyDescription, - getSettingValue, - isDefaultOrInherited -} from '../utils'; -import AlertErrorIcon from '../../../components/icons-components/AlertErrorIcon'; -import AlertSuccessIcon from '../../../components/icons-components/AlertSuccessIcon'; -import { translateWithParameters, translate } from '../../../helpers/l10n'; -import { resetValue, saveValue, checkValue } from '../store/actions'; -import { passValidation } from '../store/settingsPage/validationMessages/actions'; -import { cancelChange, changeValue } from '../store/settingsPage/changedValues/actions'; -import { - getSettingsAppChangedValue, - isSettingsAppLoading, - getSettingsAppValidationMessage -} from '../../../store/rootReducer'; - -class Definition extends React.PureComponent { - /*:: mounted: boolean; */ - /*:: timeout: number; */ - - static propTypes = { - component: PropTypes.object, - setting: PropTypes.object.isRequired, - changedValue: PropTypes.any, - loading: PropTypes.bool.isRequired, - validationMessage: PropTypes.string, - - changeValue: PropTypes.func.isRequired, - cancelChange: PropTypes.func.isRequired, - saveValue: PropTypes.func.isRequired, - resetValue: PropTypes.func.isRequired, - passValidation: PropTypes.func.isRequired - }; - - state = { - success: false - }; - - componentDidMount() { - this.mounted = true; - } - - componentWillUnmount() { - this.mounted = false; - } - - safeSetState(changes) { - if (this.mounted) { - this.setState(changes); - } - } - - handleChange = value => { - clearTimeout(this.timeout); - this.props.changeValue(this.props.setting.definition.key, value); - this.handleCheck(); - }; - - handleReset = () => { - const componentKey = this.props.component ? this.props.component.key : null; - const { definition } = this.props.setting; - return this.props - .resetValue(definition.key, componentKey) - .then(() => { - this.props.cancelChange(definition.key, componentKey); - this.safeSetState({ success: true }); - this.timeout = setTimeout(() => this.safeSetState({ success: false }), 3000); - }) - .catch(() => { - /* do nothing */ - }); - }; - - handleCancel = () => { - const componentKey = this.props.component ? this.props.component.key : null; - this.props.cancelChange(this.props.setting.definition.key, componentKey); - this.props.passValidation(this.props.setting.definition.key); - }; - - handleCheck = () => { - const componentKey = this.props.component ? this.props.component.key : null; - this.props.checkValue(this.props.setting.definition.key, componentKey); - }; - - handleSave = () => { - if (this.props.changedValue != null) { - this.safeSetState({ success: false }); - const componentKey = this.props.component ? this.props.component.key : null; - this.props - .saveValue(this.props.setting.definition.key, componentKey) - .then(() => { - this.safeSetState({ success: true }); - this.timeout = setTimeout(() => this.safeSetState({ success: false }), 3000); - }) - .catch(() => { - /* do nothing */ - }); - } - }; - - render() { - const { setting, changedValue, loading } = this.props; - const { definition } = setting; - const propertyName = getPropertyName(definition); - const hasError = this.props.validationMessage != null; - - const hasValueChanged = changedValue != null; - - const className = classNames('settings-definition', { - 'settings-definition-changed': hasValueChanged - }); - - const effectiveValue = hasValueChanged ? changedValue : getSettingValue(setting); - - const isDefault = isDefaultOrInherited(setting); - - return ( -
-
-

- {propertyName} -

- -
- -
- {translateWithParameters('settings.key_x', definition.key)} -
-
- -
-
- {loading && ( - - - {translate('settings.state.saving')} - - )} - - {!loading && - hasError && ( - - - - {translateWithParameters( - 'settings.state.validation_failed', - this.props.validationMessage - )} - - - )} - - {!loading && - !hasError && - this.state.success && ( - - - {translate('settings.state.saved')} - - )} -
- - - - -
-
- ); - } -} - -const mapStateToProps = (state, ownProps) => ({ - changedValue: getSettingsAppChangedValue(state, ownProps.setting.definition.key), - loading: isSettingsAppLoading(state, ownProps.setting.definition.key), - validationMessage: getSettingsAppValidationMessage(state, ownProps.setting.definition.key) -}); - -export default connect( - mapStateToProps, - { - changeValue, - saveValue, - resetValue, - passValidation, - cancelChange, - checkValue - } -)(Definition); diff --git a/server/sonar-web/src/main/js/apps/settings/components/Definition.tsx b/server/sonar-web/src/main/js/apps/settings/components/Definition.tsx new file mode 100644 index 00000000000..d9b9b34d837 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/settings/components/Definition.tsx @@ -0,0 +1,227 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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 classNames from 'classnames'; +import Input from './inputs/Input'; +import DefinitionActions from './DefinitionActions'; +import { + getPropertyName, + getPropertyDescription, + getSettingValue, + isDefaultOrInherited +} from '../utils'; +import AlertErrorIcon from '../../../components/icons-components/AlertErrorIcon'; +import AlertSuccessIcon from '../../../components/icons-components/AlertSuccessIcon'; +import { translateWithParameters, translate } from '../../../helpers/l10n'; +import { resetValue, saveValue, checkValue } from '../store/actions'; +import { cancelChange, changeValue, passValidation } from '../store/settingsPage'; +import { + getSettingsAppChangedValue, + getSettingsAppValidationMessage, + isSettingsAppLoading, + Store +} from '../../../store/rootReducer'; +import { Component, Setting } from '../../../app/types'; + +interface Props { + cancelChange: (key: string) => void; + changedValue: any; + changeValue: (key: string, value: any) => void; + checkValue: (key: string) => boolean; + component?: Component; + loading: boolean; + passValidation: (key: string) => void; + resetValue: (key: string, component?: string) => Promise; + saveValue: (key: string, component?: string) => Promise; + setting: Setting; + validationMessage?: string; +} + +interface State { + success: boolean; +} + +export class Definition extends React.PureComponent { + timeout?: number; + mounted = false; + state = { success: false }; + + componentDidMount() { + this.mounted = true; + } + + componentWillUnmount() { + this.mounted = false; + } + + safeSetState(changes: State) { + if (this.mounted) { + this.setState(changes); + } + } + + handleChange = (value: any) => { + clearTimeout(this.timeout); + this.props.changeValue(this.props.setting.definition.key, value); + this.handleCheck(); + }; + + handleReset = () => { + const { component, setting } = this.props; + const { definition } = setting; + const componentKey = component && component.key; + return this.props.resetValue(definition.key, componentKey).then(() => { + this.props.cancelChange(definition.key); + this.safeSetState({ success: true }); + this.timeout = window.setTimeout(() => this.safeSetState({ success: false }), 3000); + }); + }; + + handleCancel = () => { + const { setting } = this.props; + this.props.cancelChange(setting.definition.key); + this.props.passValidation(setting.definition.key); + }; + + handleCheck = () => { + const { setting } = this.props; + this.props.checkValue(setting.definition.key); + }; + + handleSave = () => { + if (this.props.changedValue != null) { + this.safeSetState({ success: false }); + const { component, setting } = this.props; + this.props.saveValue(setting.definition.key, component && component.key).then( + () => { + this.safeSetState({ success: true }); + this.timeout = window.setTimeout(() => this.safeSetState({ success: false }), 3000); + }, + () => {} + ); + } + }; + + render() { + const { changedValue, loading, setting, validationMessage } = this.props; + const { definition } = setting; + const propertyName = getPropertyName(definition); + const hasError = validationMessage != null; + const hasValueChanged = changedValue != null; + const effectiveValue = hasValueChanged ? changedValue : getSettingValue(setting); + const isDefault = isDefaultOrInherited(setting); + const description = getPropertyDescription(definition); + return ( +
+
+

+ {propertyName} +

+ + {description && ( +
+ )} + +
+ {translateWithParameters('settings.key_x', definition.key)} +
+
+ +
+
+ {loading && ( + + + {translate('settings.state.saving')} + + )} + + {!loading && + validationMessage && ( + + + + {translateWithParameters('settings.state.validation_failed', validationMessage)} + + + )} + + {!loading && + !hasError && + this.state.success && ( + + + {translate('settings.state.saved')} + + )} +
+ + + + +
+
+ ); + } +} + +const mapStateToProps = (state: Store, ownProps: Pick) => ({ + changedValue: getSettingsAppChangedValue(state, ownProps.setting.definition.key), + loading: isSettingsAppLoading(state, ownProps.setting.definition.key), + validationMessage: getSettingsAppValidationMessage(state, ownProps.setting.definition.key) +}); + +const mapDispatchToProps = { + cancelChange: cancelChange as any, + changeValue: changeValue as any, + checkValue: checkValue as any, + passValidation: passValidation as any, + resetValue: resetValue as any, + saveValue: saveValue as any +}; + +export default connect( + mapStateToProps, + mapDispatchToProps +)(Definition); diff --git a/server/sonar-web/src/main/js/apps/settings/components/DefinitionsList.js b/server/sonar-web/src/main/js/apps/settings/components/DefinitionsList.js deleted file mode 100644 index d62e687ce8b..00000000000 --- a/server/sonar-web/src/main/js/apps/settings/components/DefinitionsList.js +++ /dev/null @@ -1,42 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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. - */ -// @flow -import React from 'react'; -import PropTypes from 'prop-types'; -import Definition from './Definition'; - -export default class DefinitionsList extends React.PureComponent { - static propTypes = { - component: PropTypes.object, - settings: PropTypes.array.isRequired - }; - - render() { - return ( -
    - {this.props.settings.map(setting => ( -
  • - -
  • - ))} -
- ); - } -} diff --git a/server/sonar-web/src/main/js/apps/settings/components/DefinitionsList.tsx b/server/sonar-web/src/main/js/apps/settings/components/DefinitionsList.tsx new file mode 100644 index 00000000000..2f597afc239 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/settings/components/DefinitionsList.tsx @@ -0,0 +1,39 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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 Definition from './Definition'; +import { Component, Setting } from '../../../app/types'; + +interface Props { + component?: Component; + settings: Setting[]; +} + +export default function DefinitionsList({ component, settings }: Props) { + return ( +
    + {settings.map(setting => ( +
  • + +
  • + ))} +
+ ); +} diff --git a/server/sonar-web/src/main/js/apps/settings/components/EmailForm.js b/server/sonar-web/src/main/js/apps/settings/components/EmailForm.js deleted file mode 100644 index e3777e25633..00000000000 --- a/server/sonar-web/src/main/js/apps/settings/components/EmailForm.js +++ /dev/null @@ -1,135 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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 React from 'react'; -import { connect } from 'react-redux'; -import { translate, translateWithParameters } from '../../../helpers/l10n'; -import { sendTestEmail } from '../../../api/settings'; -import { parseError } from '../../../helpers/request'; -import { getCurrentUser } from '../../../store/rootReducer'; -import { SubmitButton } from '../../../components/ui/buttons'; -import { Alert } from '../../../components/ui/Alert'; - -class EmailForm extends React.PureComponent { - constructor(props) { - super(props); - this.state = { - recipient: this.props.currentUser.email, - subject: translate('email_configuration.test.subject'), - message: translate('email_configuration.test.message_text'), - loading: false, - success: false, - error: null - }; - } - - handleFormSubmit = event => { - event.preventDefault(); - this.setState({ success: false, error: null, loading: true }); - const { recipient, subject, message } = this.state; - sendTestEmail(recipient, subject, message).then( - () => this.setState({ success: true, loading: false }), - error => parseError(error).then(message => this.setState({ error: message, loading: false })) - ); - }; - - render() { - return ( -
-

{translate('email_configuration.test.title')}

- -
- {this.state.success && ( -
- - {translateWithParameters( - 'email_configuration.test.email_was_sent_to_x', - this.state.recipient - )} - -
- )} - - {this.state.error != null && ( -
- {this.state.error} -
- )} - -
- - this.setState({ recipient: e.target.value })} - required={true} - type="email" - value={this.state.recipient} - /> -
-
- - this.setState({ subject: e.target.value })} - type="text" - value={this.state.subject} - /> -
-
- -