]> source.dussan.org Git - sonarqube.git/commitdiff
Rewrite remaining of the settings page to TS
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>
Mon, 19 Nov 2018 10:41:06 +0000 (11:41 +0100)
committersonartech <sonartech@sonarsource.com>
Fri, 30 Nov 2018 10:20:35 +0000 (11:20 +0100)
59 files changed:
server/sonar-web/src/main/js/api/settings.ts
server/sonar-web/src/main/js/app/components/App.tsx
server/sonar-web/src/main/js/app/components/PageTracker.tsx
server/sonar-web/src/main/js/app/components/embed-docs-modal/ProductNewsMenuItem.tsx
server/sonar-web/src/main/js/app/components/nav/global/GlobalNavBranding.tsx
server/sonar-web/src/main/js/app/types.ts
server/sonar-web/src/main/js/apps/about/actions.ts
server/sonar-web/src/main/js/apps/about/components/AboutApp.tsx
server/sonar-web/src/main/js/apps/account/organizations/UserOrganizations.tsx
server/sonar-web/src/main/js/apps/account/organizations/actions.ts
server/sonar-web/src/main/js/apps/marketplace/AppContainer.tsx
server/sonar-web/src/main/js/apps/settings/__tests__/DefinitionActions-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/settings/__tests__/__snapshots__/DefinitionActions-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/apps/settings/components/AllCategoriesList.js [deleted file]
server/sonar-web/src/main/js/apps/settings/components/AllCategoriesList.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/App.js [deleted file]
server/sonar-web/src/main/js/apps/settings/components/AppContainer.js [deleted file]
server/sonar-web/src/main/js/apps/settings/components/AppContainer.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/CategoriesList.js [deleted file]
server/sonar-web/src/main/js/apps/settings/components/CategoryDefinitionsList.js [deleted file]
server/sonar-web/src/main/js/apps/settings/components/CategoryDefinitionsList.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/Definition.js [deleted file]
server/sonar-web/src/main/js/apps/settings/components/Definition.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/DefinitionsList.js [deleted file]
server/sonar-web/src/main/js/apps/settings/components/DefinitionsList.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/EmailForm.js [deleted file]
server/sonar-web/src/main/js/apps/settings/components/EmailForm.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/PageHeader.js [deleted file]
server/sonar-web/src/main/js/apps/settings/components/PageHeader.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/SubCategoryDefinitionsList.js [deleted file]
server/sonar-web/src/main/js/apps/settings/components/SubCategoryDefinitionsList.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/WildcardsHelp.js [deleted file]
server/sonar-web/src/main/js/apps/settings/components/WildcardsHelp.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/__tests__/DefinitionActions-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/DefinitionActions-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/store/actions.js [deleted file]
server/sonar-web/src/main/js/apps/settings/store/actions.ts [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/store/definitions.ts [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/store/definitions/actions.js [deleted file]
server/sonar-web/src/main/js/apps/settings/store/definitions/reducer.js [deleted file]
server/sonar-web/src/main/js/apps/settings/store/rootReducer.js [deleted file]
server/sonar-web/src/main/js/apps/settings/store/rootReducer.ts [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/store/settingsPage.ts [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/store/settingsPage/changedValues/actions.js [deleted file]
server/sonar-web/src/main/js/apps/settings/store/settingsPage/changedValues/reducer.js [deleted file]
server/sonar-web/src/main/js/apps/settings/store/settingsPage/loading/actions.js [deleted file]
server/sonar-web/src/main/js/apps/settings/store/settingsPage/loading/reducer.js [deleted file]
server/sonar-web/src/main/js/apps/settings/store/settingsPage/reducer.js [deleted file]
server/sonar-web/src/main/js/apps/settings/store/settingsPage/validationMessages/actions.js [deleted file]
server/sonar-web/src/main/js/apps/settings/store/settingsPage/validationMessages/reducer.js [deleted file]
server/sonar-web/src/main/js/apps/settings/store/values.ts [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/store/values/actions.ts [deleted file]
server/sonar-web/src/main/js/apps/settings/store/values/reducer.js [deleted file]
server/sonar-web/src/main/js/apps/settings/types.js [deleted file]
server/sonar-web/src/main/js/components/ui/Avatar.tsx
server/sonar-web/src/main/js/store/appState.ts
server/sonar-web/src/main/js/store/globalMessages.ts
server/sonar-web/src/main/js/store/rootReducer.ts
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index fbea55db86bddf2b0424260887e8eb20ee68ddaa..11b37f1d02f2ceb436e93401b59ebeb1ec0f6aec 100644 (file)
@@ -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<SettingCategoryDefinition[]> {
-  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<void> {
+export function setSettingValue(
+  definition: SettingDefinition,
+  value: any,
+  component?: string
+): Promise<void> {
   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
index e5257a4a17128849f1969acc3bac4fcab0388035..df1277c2222e000206de3bc89cdbd6683656b6bf 100644 (file)
@@ -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<Props> {
     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<Props> {
   }
 }
 
-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,
index 2ae8c85026be00db7d7f66e85db4333e75994d85..e1620791c9d8492deb65811a5e0772082fdc1507 100644 (file)
@@ -59,8 +59,11 @@ export class PageTracker extends React.PureComponent<Props> {
   }
 }
 
-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));
index 3037367c20ee71583dfe5082fad9277cbe224810..a2565b563f74c4ce1dbef838c245bf622ede4432 100644 (file)
@@ -124,8 +124,11 @@ export class ProductNewsMenuItem extends React.PureComponent<Props, State> {
   }
 }
 
-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);
index 876a1ccc62f0eb1adc678aef8898a20368fad63f..7cc8a5e14a4aa2bc4af6bd874813f8d7e5a62250 100644 (file)
@@ -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);
index 542324c1cd673f7af37265be4675c347b261890d..427cfe2a463cf0824a283f7e83b9b8dfa4327a26 100644 (file)
@@ -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'
index c9f54bb37e1cc1a8455837c0510950f2ccffcb76..e99ab66bfb170adbc7eb8474ce228d73ec6d49bf 100644 (file)
  */
 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));
     });
index 6771f875ffe368ed69b2890778e5e602af17e581..752000d6e13948f7bcc4aa1ea0c9aaf90fa459bb 100644 (file)
@@ -48,7 +48,7 @@ import '../styles.css';
 interface Props {
   appState: Pick<AppState, 'defaultOrganization' | 'organizationsEnabled'>;
   currentUser: CurrentUser;
-  customText?: { value: string };
+  customText?: string;
   fetchAboutPageSettings: () => Promise<void>;
   location: Location;
 }
@@ -158,13 +158,9 @@ class AboutApp extends React.PureComponent<Props, State> {
             </div>
           </div>
 
-          {customText != null &&
-            customText.value && (
-              <div
-                className="about-page-section"
-                dangerouslySetInnerHTML={{ __html: customText.value }}
-              />
-            )}
+          {customText && (
+            <div className="about-page-section" dangerouslySetInnerHTML={{ __html: customText }} />
+          )}
 
           <AboutLanguages />
 
@@ -195,11 +191,14 @@ class AboutApp extends React.PureComponent<Props, State> {
   }
 }
 
-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;
 
index 94d4f9a79520b59d72b00ac4c8b07423789f4bae..3ec744841b80af6d9dde082b343115221c493c8b 100644 (file)
@@ -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<Props, State> {
   };
 
   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<Props, State> {
   }
 }
 
-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
index 73fa8566dcd9a9c3bd5d228a15f0b09ef172693e..329d831e10702a07d65c6947c2d1226c47b89c0a 100644 (file)
@@ -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));
   });
 };
index 76627ae77bd69a6231a6a6a7ecf6f1719b45535a..463d28dacbef38707fd41340f71ef935bc2f29ec 100644 (file)
@@ -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 (file)
index 76b1d29..0000000
+++ /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(
-    <DefinitionActions
-      changedValue={changedValue}
-      hasError={hasError}
-      hasValueChanged={changedValue !== ''}
-      isDefault={isDefault}
-      onCancel={() => {}}
-      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 (file)
index 046e36d..0000000
+++ /dev/null
@@ -1,143 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`disables save button on error 1`] = `
-<Fragment>
-  <div
-    className="settings-definition-changes nowrap"
-  >
-    <Button
-      className="spacer-right button-success"
-      disabled={true}
-      onClick={[Function]}
-    >
-      save
-    </Button>
-    <Button
-      className="spacer-right button-link"
-      onClick={[Function]}
-    >
-      cancel
-    </Button>
-  </div>
-</Fragment>
-`;
-
-exports[`displays cancel button when value changed and has error 1`] = `
-<Fragment>
-  <div
-    className="settings-definition-changes nowrap"
-  >
-    <Button
-      className="spacer-right button-success"
-      disabled={true}
-      onClick={[Function]}
-    >
-      save
-    </Button>
-    <Button
-      className="spacer-right button-link"
-      onClick={[Function]}
-    >
-      cancel
-    </Button>
-  </div>
-</Fragment>
-`;
-
-exports[`displays cancel button when value changed and no error 1`] = `
-<Fragment>
-  <div
-    className="settings-definition-changes nowrap"
-  >
-    <Button
-      className="spacer-right button-success"
-      disabled={false}
-      onClick={[Function]}
-    >
-      save
-    </Button>
-    <Button
-      className="spacer-right button-link"
-      onClick={[Function]}
-    >
-      cancel
-    </Button>
-  </div>
-</Fragment>
-`;
-
-exports[`displays default message when value is default 1`] = `
-<Fragment>
-  <div
-    className="spacer-top note"
-    style={
-      Object {
-        "lineHeight": "24px",
-      }
-    }
-  >
-    settings._default
-  </div>
-  <div
-    className="settings-definition-changes nowrap"
-  >
-    <Button
-      className="spacer-right"
-      onClick={[Function]}
-    >
-      reset_verb
-    </Button>
-    <span
-      className="note"
-    >
-      default
-      : 
-      settings.default.no_value
-    </span>
-  </div>
-</Fragment>
-`;
-
-exports[`displays reset button when empty and not default 1`] = `
-<Fragment>
-  <div
-    className="settings-definition-changes nowrap"
-  >
-    <Button
-      className="spacer-right"
-      onClick={[Function]}
-    >
-      reset_verb
-    </Button>
-    <span
-      className="note"
-    >
-      default
-      : 
-      settings.default.no_value
-    </span>
-  </div>
-</Fragment>
-`;
-
-exports[`displays save button when it can be saved 1`] = `
-<Fragment>
-  <div
-    className="settings-definition-changes nowrap"
-  >
-    <Button
-      className="spacer-right button-success"
-      disabled={false}
-      onClick={[Function]}
-    >
-      save
-    </Button>
-    <Button
-      className="spacer-right button-link"
-      onClick={[Function]}
-    >
-      cancel
-    </Button>
-  </div>
-</Fragment>
-`;
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 (file)
index e59bff6..0000000
+++ /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 <CategoriesList {...props} />;
-}
-
-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 (file)
index 0000000..f01a2ed
--- /dev/null
@@ -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<Props> {
+  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 (
+      <IndexLink
+        className={classNames({
+          active: category.key.toLowerCase() === selectedCategory.toLowerCase()
+        })}
+        title={category.name}
+        to={{ pathname, query }}>
+        {category.name}
+      </IndexLink>
+    );
+  }
+
+  render() {
+    const categoriesWithName = this.props.categories.map(key => ({
+      key,
+      name: getCategoryName(key)
+    }));
+    const sortedCategories = sortBy(categoriesWithName, category => category.name.toLowerCase());
+    return (
+      <ul className="side-tabs-menu">
+        {sortedCategories.map(category => (
+          <li key={category.key}>{this.renderLink(category)}</li>
+        ))}
+      </ul>
+    );
+  }
+}
+
+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 (file)
index 3784941..0000000
+++ /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 (
-      <div className="page page-limited" id="settings-page">
-        <Suggestions suggestions="settings" />
-        <Helmet title={translate('settings.page')} />
-
-        <PageHeader component={this.props.component} />
-
-        <div className="side-tabs-layout settings-layout">
-          <div className="side-tabs-side">
-            <AllCategoriesList
-              component={this.props.component}
-              defaultCategory={this.props.defaultCategory}
-              selectedCategory={selectedCategory}
-            />
-          </div>
-          <div className="side-tabs-main">
-            <CategoryDefinitionsList category={selectedCategory} component={this.props.component} />
-            {selectedCategory === 'exclusions' && <WildcardsHelp />}
-          </div>
-        </div>
-      </div>
-    );
-  }
-}
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 (file)
index d2d4f00..0000000
+++ /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 (file)
index 0000000..7a65fab
--- /dev/null
@@ -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<void>;
+}
+
+interface State {
+  loading: boolean;
+}
+
+export class App extends React.PureComponent<Props & WithRouterProps, State> {
+  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 (
+      <div className="page page-limited" id="settings-page">
+        <Suggestions suggestions="settings" />
+        <Helmet title={translate('settings.page')} />
+
+        <PageHeader component={this.props.component} />
+
+        <div className="side-tabs-layout settings-layout">
+          <div className="side-tabs-side">
+            <AllCategoriesList
+              component={this.props.component}
+              defaultCategory={this.props.defaultCategory}
+              selectedCategory={selectedCategory}
+            />
+          </div>
+          <div className="side-tabs-main">
+            <CategoryDefinitionsList category={selectedCategory} component={this.props.component} />
+            {selectedCategory === 'exclusions' && <WildcardsHelp />}
+          </div>
+        </div>
+      </div>
+    );
+  }
+}
+
+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 (file)
index 8b61e56..0000000
+++ /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 (
-      <IndexLink className={className} title={category.name} to={{ pathname, query }}>
-        {category.name}
-      </IndexLink>
-    );
-  }
-
-  render() {
-    const categoriesWithName = this.props.categories.map(key => ({
-      key,
-      name: getCategoryName(key)
-    }));
-    const sortedCategories = sortBy(categoriesWithName, category => category.name.toLowerCase());
-
-    return (
-      <ul className="side-tabs-menu">
-        {sortedCategories.map(category => (
-          <li key={category.key}>{this.renderLink(category)}</li>
-        ))}
-      </ul>
-    );
-  }
-}
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 (file)
index 630ce8b..0000000
+++ /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 (file)
index 0000000..87f503f
--- /dev/null
@@ -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 (file)
index 1ce6ac3..0000000
+++ /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 (
-      <div className={className} data-key={definition.key}>
-        <div className="settings-definition-left">
-          <h3 className="settings-definition-name" title={propertyName}>
-            {propertyName}
-          </h3>
-
-          <div
-            className="markdown small spacer-top"
-            dangerouslySetInnerHTML={{ __html: getPropertyDescription(definition) }}
-          />
-
-          <div className="settings-definition-key note little-spacer-top">
-            {translateWithParameters('settings.key_x', definition.key)}
-          </div>
-        </div>
-
-        <div className="settings-definition-right">
-          <div className="settings-definition-state">
-            {loading && (
-              <span className="text-info">
-                <i className="spinner spacer-right" />
-                {translate('settings.state.saving')}
-              </span>
-            )}
-
-            {!loading &&
-              hasError && (
-                <span className="text-danger">
-                  <AlertErrorIcon className="spacer-right" />
-                  <span>
-                    {translateWithParameters(
-                      'settings.state.validation_failed',
-                      this.props.validationMessage
-                    )}
-                  </span>
-                </span>
-              )}
-
-            {!loading &&
-              !hasError &&
-              this.state.success && (
-                <span className="text-success">
-                  <AlertSuccessIcon className="spacer-right" />
-                  {translate('settings.state.saved')}
-                </span>
-              )}
-          </div>
-
-          <Input
-            hasValueChanged={hasValueChanged}
-            onCancel={this.handleCancel}
-            onChange={this.handleChange}
-            onSave={this.handleSave}
-            setting={setting}
-            value={effectiveValue}
-          />
-
-          <DefinitionActions
-            changedValue={changedValue}
-            hasError={hasError}
-            hasValueChanged={hasValueChanged}
-            isDefault={isDefault}
-            onCancel={this.handleCancel}
-            onReset={this.handleReset}
-            onSave={this.handleSave}
-            setting={setting}
-          />
-        </div>
-      </div>
-    );
-  }
-}
-
-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 (file)
index 0000000..d9b9b34
--- /dev/null
@@ -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<void>;
+  saveValue: (key: string, component?: string) => Promise<void>;
+  setting: Setting;
+  validationMessage?: string;
+}
+
+interface State {
+  success: boolean;
+}
+
+export class Definition extends React.PureComponent<Props, State> {
+  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 (
+      <div
+        className={classNames('settings-definition', {
+          'settings-definition-changed': hasValueChanged
+        })}
+        data-key={definition.key}>
+        <div className="settings-definition-left">
+          <h3 className="settings-definition-name" title={propertyName}>
+            {propertyName}
+          </h3>
+
+          {description && (
+            <div
+              className="markdown small spacer-top"
+              dangerouslySetInnerHTML={{ __html: description }}
+            />
+          )}
+
+          <div className="settings-definition-key note little-spacer-top">
+            {translateWithParameters('settings.key_x', definition.key)}
+          </div>
+        </div>
+
+        <div className="settings-definition-right">
+          <div className="settings-definition-state">
+            {loading && (
+              <span className="text-info">
+                <i className="spinner spacer-right" />
+                {translate('settings.state.saving')}
+              </span>
+            )}
+
+            {!loading &&
+              validationMessage && (
+                <span className="text-danger">
+                  <AlertErrorIcon className="spacer-right" />
+                  <span>
+                    {translateWithParameters('settings.state.validation_failed', validationMessage)}
+                  </span>
+                </span>
+              )}
+
+            {!loading &&
+              !hasError &&
+              this.state.success && (
+                <span className="text-success">
+                  <AlertSuccessIcon className="spacer-right" />
+                  {translate('settings.state.saved')}
+                </span>
+              )}
+          </div>
+
+          <Input
+            hasValueChanged={hasValueChanged}
+            onCancel={this.handleCancel}
+            onChange={this.handleChange}
+            onSave={this.handleSave}
+            setting={setting}
+            value={effectiveValue}
+          />
+
+          <DefinitionActions
+            changedValue={changedValue}
+            hasError={hasError}
+            hasValueChanged={hasValueChanged}
+            isDefault={isDefault}
+            onCancel={this.handleCancel}
+            onReset={this.handleReset}
+            onSave={this.handleSave}
+            setting={setting}
+          />
+        </div>
+      </div>
+    );
+  }
+}
+
+const mapStateToProps = (state: Store, ownProps: Pick<Props, 'setting'>) => ({
+  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 (file)
index d62e687..0000000
+++ /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 (
-      <ul className="settings-definitions-list">
-        {this.props.settings.map(setting => (
-          <li key={setting.definition.key}>
-            <Definition component={this.props.component} setting={setting} />
-          </li>
-        ))}
-      </ul>
-    );
-  }
-}
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 (file)
index 0000000..2f597af
--- /dev/null
@@ -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 (
+    <ul className="settings-definitions-list">
+      {settings.map(setting => (
+        <li key={setting.definition.key}>
+          <Definition component={component} setting={setting} />
+        </li>
+      ))}
+    </ul>
+  );
+}
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 (file)
index e3777e2..0000000
+++ /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 (
-      <div className="huge-spacer-top">
-        <h3 className="spacer-bottom">{translate('email_configuration.test.title')}</h3>
-
-        <form onSubmit={this.handleFormSubmit} style={{ marginLeft: 201 }}>
-          {this.state.success && (
-            <div className="modal-field">
-              <Alert variant="success">
-                {translateWithParameters(
-                  'email_configuration.test.email_was_sent_to_x',
-                  this.state.recipient
-                )}
-              </Alert>
-            </div>
-          )}
-
-          {this.state.error != null && (
-            <div className="modal-field">
-              <Alert variant="error">{this.state.error}</Alert>
-            </div>
-          )}
-
-          <div className="modal-field">
-            <label htmlFor="test-email-to">
-              {translate('email_configuration.test.to_address')}
-              <em className="mandatory">*</em>
-            </label>
-            <input
-              className="settings-large-input"
-              disabled={this.state.loading}
-              id="test-email-to"
-              onChange={e => this.setState({ recipient: e.target.value })}
-              required={true}
-              type="email"
-              value={this.state.recipient}
-            />
-          </div>
-          <div className="modal-field">
-            <label htmlFor="test-email-subject">
-              {translate('email_configuration.test.subject')}
-            </label>
-            <input
-              className="settings-large-input"
-              disabled={this.state.loading}
-              id="test-email-subject"
-              onChange={e => this.setState({ subject: e.target.value })}
-              type="text"
-              value={this.state.subject}
-            />
-          </div>
-          <div className="modal-field">
-            <label htmlFor="test-email-message">
-              {translate('email_configuration.test.message')}
-              <em className="mandatory">*</em>
-            </label>
-            <textarea
-              className="settings-large-input"
-              disabled={this.state.loading}
-              id="test-email-title"
-              onChange={e => this.setState({ message: e.target.value })}
-              required={true}
-              rows="5"
-              value={this.state.message}
-            />
-          </div>
-
-          <div className="modal-field">
-            {this.state.loading && <i className="spacer-right spinner" />}
-            <SubmitButton disabled={this.state.loading}>
-              {translate('email_configuration.test.send')}
-            </SubmitButton>
-          </div>
-        </form>
-      </div>
-    );
-  }
-}
-
-const mapStateToProps = state => ({
-  currentUser: getCurrentUser(state)
-});
-
-export default connect(mapStateToProps)(EmailForm);
diff --git a/server/sonar-web/src/main/js/apps/settings/components/EmailForm.tsx b/server/sonar-web/src/main/js/apps/settings/components/EmailForm.tsx
new file mode 100644 (file)
index 0000000..f151611
--- /dev/null
@@ -0,0 +1,174 @@
+/*
+ * 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 { translate, translateWithParameters } from '../../../helpers/l10n';
+import { sendTestEmail } from '../../../api/settings';
+import { parseError } from '../../../helpers/request';
+import { SubmitButton } from '../../../components/ui/buttons';
+import { Alert } from '../../../components/ui/Alert';
+import { LoggedInUser } from '../../../app/types';
+import { withCurrentUser } from '../../../components/hoc/withCurrentUser';
+
+interface Props {
+  currentUser: LoggedInUser;
+}
+
+interface State {
+  recipient: string;
+  subject: string;
+  message: string;
+  loading: boolean;
+  success: boolean;
+  error?: string;
+}
+
+class EmailForm extends React.PureComponent<Props, State> {
+  mounted = false;
+
+  constructor(props: 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
+    };
+  }
+
+  componentDidMount() {
+    this.mounted = true;
+  }
+
+  componentWillUnmount() {
+    this.mounted = false;
+  }
+
+  handleError = (error: { response: Response }) => {
+    return parseError(error).then(message => {
+      if (this.mounted) {
+        this.setState({ error: message, loading: false });
+      }
+    });
+  };
+
+  handleFormSubmit = (event: React.FormEvent) => {
+    event.preventDefault();
+    this.setState({ success: false, error: undefined, loading: true });
+    const { recipient, subject, message } = this.state;
+    sendTestEmail(recipient, subject, message).then(() => {
+      if (this.mounted) {
+        this.setState({ success: true, loading: false });
+      }
+    }, this.handleError);
+  };
+
+  onRecipientChange = (event: React.ChangeEvent<HTMLInputElement>) => {
+    this.setState({ recipient: event.target.value });
+  };
+
+  onSubjectChange = (event: React.ChangeEvent<HTMLInputElement>) => {
+    this.setState({ subject: event.target.value });
+  };
+
+  onMessageChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
+    this.setState({ message: event.target.value });
+  };
+
+  render() {
+    return (
+      <div className="huge-spacer-top">
+        <h3 className="spacer-bottom">{translate('email_configuration.test.title')}</h3>
+
+        <form onSubmit={this.handleFormSubmit} style={{ marginLeft: 201 }}>
+          {this.state.success && (
+            <div className="modal-field">
+              <Alert variant="success">
+                {translateWithParameters(
+                  'email_configuration.test.email_was_sent_to_x',
+                  this.state.recipient
+                )}
+              </Alert>
+            </div>
+          )}
+
+          {this.state.error != null && (
+            <div className="modal-field">
+              <Alert variant="error">{this.state.error}</Alert>
+            </div>
+          )}
+
+          <div className="modal-field">
+            <label htmlFor="test-email-to">
+              {translate('email_configuration.test.to_address')}
+              <em className="mandatory">*</em>
+            </label>
+            <input
+              className="settings-large-input"
+              disabled={this.state.loading}
+              id="test-email-to"
+              onChange={this.onRecipientChange}
+              required={true}
+              type="email"
+              value={this.state.recipient}
+            />
+          </div>
+          <div className="modal-field">
+            <label htmlFor="test-email-subject">
+              {translate('email_configuration.test.subject')}
+            </label>
+            <input
+              className="settings-large-input"
+              disabled={this.state.loading}
+              id="test-email-subject"
+              onChange={this.onSubjectChange}
+              type="text"
+              value={this.state.subject}
+            />
+          </div>
+          <div className="modal-field">
+            <label htmlFor="test-email-message">
+              {translate('email_configuration.test.message')}
+              <em className="mandatory">*</em>
+            </label>
+            <textarea
+              className="settings-large-input"
+              disabled={this.state.loading}
+              id="test-email-title"
+              onChange={this.onMessageChange}
+              required={true}
+              rows={5}
+              value={this.state.message}
+            />
+          </div>
+
+          <div className="modal-field">
+            {this.state.loading && <i className="spacer-right spinner" />}
+            <SubmitButton disabled={this.state.loading}>
+              {translate('email_configuration.test.send')}
+            </SubmitButton>
+          </div>
+        </form>
+      </div>
+    );
+  }
+}
+
+export default withCurrentUser(EmailForm);
diff --git a/server/sonar-web/src/main/js/apps/settings/components/PageHeader.js b/server/sonar-web/src/main/js/apps/settings/components/PageHeader.js
deleted file mode 100644 (file)
index 77e21d0..0000000
+++ /dev/null
@@ -1,51 +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 InstanceMessage from '../../../components/common/InstanceMessage';
-import { translate } from '../../../helpers/l10n';
-
-export default class PageHeader extends React.PureComponent {
-  static propTypes = {
-    component: PropTypes.object
-  };
-
-  render() {
-    const title =
-      this.props.component != null
-        ? translate('project_settings.page')
-        : translate('settings.page');
-
-    const description =
-      this.props.component != null ? (
-        translate('project_settings.page.description')
-      ) : (
-        <InstanceMessage message={translate('settings.page.description')} />
-      );
-
-    return (
-      <header className="page-header">
-        <h1 className="page-title">{title}</h1>
-        <div className="page-description">{description}</div>
-      </header>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/PageHeader.tsx b/server/sonar-web/src/main/js/apps/settings/components/PageHeader.tsx
new file mode 100644 (file)
index 0000000..18b2f64
--- /dev/null
@@ -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 * as React from 'react';
+import InstanceMessage from '../../../components/common/InstanceMessage';
+import { translate } from '../../../helpers/l10n';
+import { Component } from '../../../app/types';
+
+interface Props {
+  component?: Component;
+}
+
+export default function PageHeader({ component }: Props) {
+  const title = component ? translate('project_settings.page') : translate('settings.page');
+
+  const description = component ? (
+    translate('project_settings.page.description')
+  ) : (
+    <InstanceMessage message={translate('settings.page.description')} />
+  );
+
+  return (
+    <header className="page-header">
+      <h1 className="page-title">{title}</h1>
+      <div className="page-description">{description}</div>
+    </header>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/SubCategoryDefinitionsList.js b/server/sonar-web/src/main/js/apps/settings/components/SubCategoryDefinitionsList.js
deleted file mode 100644 (file)
index 075d96c..0000000
+++ /dev/null
@@ -1,92 +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 { groupBy, isEqual, sortBy } from 'lodash';
-import DefinitionsList from './DefinitionsList';
-import EmailForm from './EmailForm';
-import { getSubCategoryName, getSubCategoryDescription } from '../utils';
-
-export default class SubCategoryDefinitionsList extends React.PureComponent {
-  static propTypes = {
-    component: PropTypes.object,
-    fetchValues: PropTypes.func,
-    settings: PropTypes.array.isRequired
-  };
-
-  componentDidMount() {
-    this.fetchValues();
-  }
-
-  componentDidUpdate(prevProps /*: Object */) {
-    const prevKeys = prevProps.settings.map(setting => setting.definition.key);
-    const keys = this.props.settings.map(setting => setting.definition.key);
-    if (prevProps.component !== this.props.component || !isEqual(prevKeys, keys)) {
-      this.fetchValues();
-    }
-  }
-
-  fetchValues() {
-    const keys = this.props.settings.map(setting => setting.definition.key).join();
-    this.props.fetchValues(keys, this.props.component && this.props.component.key);
-  }
-
-  renderEmailForm(subCategoryKey /*: string */) {
-    const isEmailSettings = this.props.category === 'general' && subCategoryKey === 'email';
-    if (!isEmailSettings) {
-      return null;
-    }
-    return <EmailForm />;
-  }
-
-  render() {
-    const bySubCategory = groupBy(this.props.settings, setting => setting.definition.subCategory);
-    const subCategories = Object.keys(bySubCategory).map(key => ({
-      key,
-      name: getSubCategoryName(bySubCategory[key][0].definition.category, key),
-      description: getSubCategoryDescription(bySubCategory[key][0].definition.category, key)
-    }));
-    const sortedSubCategories = sortBy(subCategories, subCategory =>
-      subCategory.name.toLowerCase()
-    );
-
-    return (
-      <ul className="settings-sub-categories-list">
-        {sortedSubCategories.map(subCategory => (
-          <li key={subCategory.key}>
-            <h2 className="settings-sub-category-name">{subCategory.name}</h2>
-            {subCategory.description != null && (
-              <div
-                className="settings-sub-category-description markdown"
-                dangerouslySetInnerHTML={{ __html: subCategory.description }}
-              />
-            )}
-            <DefinitionsList
-              component={this.props.component}
-              settings={bySubCategory[subCategory.key]}
-            />
-            {this.renderEmailForm(subCategory.key)}
-          </li>
-        ))}
-      </ul>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/SubCategoryDefinitionsList.tsx b/server/sonar-web/src/main/js/apps/settings/components/SubCategoryDefinitionsList.tsx
new file mode 100644 (file)
index 0000000..34fa14c
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * 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 { groupBy, isEqual, sortBy } from 'lodash';
+import DefinitionsList from './DefinitionsList';
+import EmailForm from './EmailForm';
+import { getSubCategoryName, getSubCategoryDescription } from '../utils';
+import { Component, SettingCategoryDefinition, Setting } from '../../../app/types';
+
+interface Props {
+  category: string;
+  component?: Component;
+  fetchValues: Function;
+  settings: Array<Setting & { definition: SettingCategoryDefinition }>;
+}
+
+export default class SubCategoryDefinitionsList extends React.PureComponent<Props> {
+  componentDidMount() {
+    this.fetchValues();
+  }
+
+  componentDidUpdate(prevProps: Props) {
+    const prevKeys = prevProps.settings.map(setting => setting.definition.key);
+    const keys = this.props.settings.map(setting => setting.definition.key);
+    if (prevProps.component !== this.props.component || !isEqual(prevKeys, keys)) {
+      this.fetchValues();
+    }
+  }
+
+  fetchValues() {
+    const keys = this.props.settings.map(setting => setting.definition.key).join();
+    this.props.fetchValues(keys, this.props.component && this.props.component.key);
+  }
+
+  renderEmailForm = (subCategoryKey: string) => {
+    const isEmailSettings = this.props.category === 'general' && subCategoryKey === 'email';
+    if (!isEmailSettings) {
+      return null;
+    }
+    return <EmailForm />;
+  };
+
+  render() {
+    const bySubCategory = groupBy(this.props.settings, setting => setting.definition.subCategory);
+    const subCategories = Object.keys(bySubCategory).map(key => ({
+      key,
+      name: getSubCategoryName(bySubCategory[key][0].definition.category, key),
+      description: getSubCategoryDescription(bySubCategory[key][0].definition.category, key)
+    }));
+    const sortedSubCategories = sortBy(subCategories, subCategory =>
+      subCategory.name.toLowerCase()
+    );
+    return (
+      <ul className="settings-sub-categories-list">
+        {sortedSubCategories.map(subCategory => (
+          <li key={subCategory.key}>
+            <h2 className="settings-sub-category-name">{subCategory.name}</h2>
+            {subCategory.description != null && (
+              <div
+                className="settings-sub-category-description markdown"
+                dangerouslySetInnerHTML={{ __html: subCategory.description }}
+              />
+            )}
+            <DefinitionsList
+              component={this.props.component}
+              settings={bySubCategory[subCategory.key]}
+            />
+            {this.renderEmailForm(subCategory.key)}
+          </li>
+        ))}
+      </ul>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/WildcardsHelp.js b/server/sonar-web/src/main/js/apps/settings/components/WildcardsHelp.js
deleted file mode 100644 (file)
index 42c2047..0000000
+++ /dev/null
@@ -1,121 +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';
-
-export default function WildcardsHelp() {
-  return (
-    <div className="huge-spacer-top">
-      <h2 className="spacer-bottom">Wildcards</h2>
-      <p className="spacer-bottom">Following rules are applied:</p>
-
-      <table className="data spacer-bottom">
-        <tbody>
-          <tr>
-            <td>*</td>
-            <td>Match zero or more characters</td>
-          </tr>
-          <tr>
-            <td>**</td>
-            <td>Match zero or more directories</td>
-          </tr>
-          <tr>
-            <td>?</td>
-            <td>Match a single character</td>
-          </tr>
-        </tbody>
-      </table>
-
-      <table className="data zebra">
-        <thead>
-          <tr>
-            <th>Example</th>
-            <th>Matches</th>
-            <th>Does not match</th>
-          </tr>
-        </thead>
-        <tbody>
-          <tr>
-            <td>**/foo/*.js</td>
-            <td>
-              <ul>
-                <li>src/foo/bar.js</li>
-                <li>lib/ui/foo/bar.js</li>
-              </ul>
-            </td>
-            <td>
-              <ul>
-                <li>src/bar.js</li>
-                <li>src/foo2/bar.js</li>
-              </ul>
-            </td>
-          </tr>
-          <tr>
-            <td>src/foo/*bar*.js</td>
-            <td>
-              <ul>
-                <li>src/foo/bar.js</li>
-                <li>src/foo/bar1.js</li>
-                <li>src/foo/bar123.js</li>
-                <li>src/foo/123bar123.js</li>
-              </ul>
-            </td>
-            <td>
-              <ul>
-                <li>src/foo/ui/bar.js</li>
-                <li>src/bar.js</li>
-              </ul>
-            </td>
-          </tr>
-          <tr>
-            <td>src/foo/**</td>
-            <td>
-              <ul>
-                <li>src/foo/bar.js</li>
-                <li>src/foo/ui/bar.js</li>
-              </ul>
-            </td>
-            <td>
-              <ul>
-                <li>src/bar/foo/bar.js</li>
-                <li>src/bar.js</li>
-              </ul>
-            </td>
-          </tr>
-          <tr>
-            <td>**/foo?.js</td>
-            <td>
-              <ul>
-                <li>src/foo1.js</li>
-                <li>src/bar/foo1.js</li>
-              </ul>
-            </td>
-            <td>
-              <ul>
-                <li>src/foo.js</li>
-                <li>src/foo12.js</li>
-                <li>src/12foo3.js</li>
-              </ul>
-            </td>
-          </tr>
-        </tbody>
-      </table>
-    </div>
-  );
-}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/WildcardsHelp.tsx b/server/sonar-web/src/main/js/apps/settings/components/WildcardsHelp.tsx
new file mode 100644 (file)
index 0000000..bcebe58
--- /dev/null
@@ -0,0 +1,122 @@
+/*
+ * 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 { translate } from '../../../helpers/l10n';
+
+export default function WildcardsHelp() {
+  return (
+    <div className="huge-spacer-top">
+      <h2 className="spacer-bottom">{translate('settings.wildcards')}</h2>
+      <p className="spacer-bottom">{translate('settings.wildcards.following_rules_are_applied')}</p>
+
+      <table className="data spacer-bottom">
+        <tbody>
+          <tr>
+            <td>*</td>
+            <td>{translate('settings.wildcards.zero_more_char')}</td>
+          </tr>
+          <tr>
+            <td>**</td>
+            <td>{translate('settings.wildcards.zero_more_dir')}</td>
+          </tr>
+          <tr>
+            <td>?</td>
+            <td>{translate('settings.wildcards.single_char')}</td>
+          </tr>
+        </tbody>
+      </table>
+
+      <table className="data zebra">
+        <thead>
+          <tr>
+            <th>{translate('example')}</th>
+            <th>{translate('settings.wildcards.matches')}</th>
+            <th>{translate('settings.wildcards.does_no_match')}</th>
+          </tr>
+        </thead>
+        <tbody>
+          <tr>
+            <td>{'**/foo/*.js'}</td>
+            <td>
+              <ul>
+                <li>{'src/foo/bar.js'}</li>
+                <li>{'lib/ui/foo/bar.js'}</li>
+              </ul>
+            </td>
+            <td>
+              <ul>
+                <li>{'src/bar.js'}</li>
+                <li>{'src/foo2/bar.js'}</li>
+              </ul>
+            </td>
+          </tr>
+          <tr>
+            <td>{'src/foo/*bar*.js'}</td>
+            <td>
+              <ul>
+                <li>{'src/foo/bar.js'}</li>
+                <li>{'src/foo/bar1.js'}</li>
+                <li>{'src/foo/bar123.js'}</li>
+                <li>{'src/foo/123bar123.js'}</li>
+              </ul>
+            </td>
+            <td>
+              <ul>
+                <li>{'src/foo/ui/bar.js'}</li>
+                <li>{'src/bar.js'}</li>
+              </ul>
+            </td>
+          </tr>
+          <tr>
+            <td>{'src/foo/**'}</td>
+            <td>
+              <ul>
+                <li>{'src/foo/bar.js'}</li>
+                <li>{'src/foo/ui/bar.js'}</li>
+              </ul>
+            </td>
+            <td>
+              <ul>
+                <li>{'src/bar/foo/bar.js'}</li>
+                <li>{'src/bar.js'}</li>
+              </ul>
+            </td>
+          </tr>
+          <tr>
+            <td>{'**/foo?.js'}</td>
+            <td>
+              <ul>
+                <li>{'src/foo1.js'}</li>
+                <li>{'src/bar/foo1.js'}</li>
+              </ul>
+            </td>
+            <td>
+              <ul>
+                <li>{'src/foo.js'}</li>
+                <li>{'src/foo12.js'}</li>
+                <li>{'src/12foo3.js'}</li>
+              </ul>
+            </td>
+          </tr>
+        </tbody>
+      </table>
+    </div>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/DefinitionActions-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/__tests__/DefinitionActions-test.tsx
new file mode 100644 (file)
index 0000000..432be0e
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ * 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 '../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(
+    <DefinitionActions
+      changedValue={changedValue}
+      hasError={hasError}
+      hasValueChanged={changedValue !== ''}
+      isDefault={isDefault}
+      onCancel={() => {}}
+      onReset={() => {}}
+      onSave={() => {}}
+      setting={settings}
+    />
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/DefinitionActions-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/DefinitionActions-test.tsx.snap
new file mode 100644 (file)
index 0000000..046e36d
--- /dev/null
@@ -0,0 +1,143 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`disables save button on error 1`] = `
+<Fragment>
+  <div
+    className="settings-definition-changes nowrap"
+  >
+    <Button
+      className="spacer-right button-success"
+      disabled={true}
+      onClick={[Function]}
+    >
+      save
+    </Button>
+    <Button
+      className="spacer-right button-link"
+      onClick={[Function]}
+    >
+      cancel
+    </Button>
+  </div>
+</Fragment>
+`;
+
+exports[`displays cancel button when value changed and has error 1`] = `
+<Fragment>
+  <div
+    className="settings-definition-changes nowrap"
+  >
+    <Button
+      className="spacer-right button-success"
+      disabled={true}
+      onClick={[Function]}
+    >
+      save
+    </Button>
+    <Button
+      className="spacer-right button-link"
+      onClick={[Function]}
+    >
+      cancel
+    </Button>
+  </div>
+</Fragment>
+`;
+
+exports[`displays cancel button when value changed and no error 1`] = `
+<Fragment>
+  <div
+    className="settings-definition-changes nowrap"
+  >
+    <Button
+      className="spacer-right button-success"
+      disabled={false}
+      onClick={[Function]}
+    >
+      save
+    </Button>
+    <Button
+      className="spacer-right button-link"
+      onClick={[Function]}
+    >
+      cancel
+    </Button>
+  </div>
+</Fragment>
+`;
+
+exports[`displays default message when value is default 1`] = `
+<Fragment>
+  <div
+    className="spacer-top note"
+    style={
+      Object {
+        "lineHeight": "24px",
+      }
+    }
+  >
+    settings._default
+  </div>
+  <div
+    className="settings-definition-changes nowrap"
+  >
+    <Button
+      className="spacer-right"
+      onClick={[Function]}
+    >
+      reset_verb
+    </Button>
+    <span
+      className="note"
+    >
+      default
+      : 
+      settings.default.no_value
+    </span>
+  </div>
+</Fragment>
+`;
+
+exports[`displays reset button when empty and not default 1`] = `
+<Fragment>
+  <div
+    className="settings-definition-changes nowrap"
+  >
+    <Button
+      className="spacer-right"
+      onClick={[Function]}
+    >
+      reset_verb
+    </Button>
+    <span
+      className="note"
+    >
+      default
+      : 
+      settings.default.no_value
+    </span>
+  </div>
+</Fragment>
+`;
+
+exports[`displays save button when it can be saved 1`] = `
+<Fragment>
+  <div
+    className="settings-definition-changes nowrap"
+  >
+    <Button
+      className="spacer-right button-success"
+      disabled={false}
+      onClick={[Function]}
+    >
+      save
+    </Button>
+    <Button
+      className="spacer-right button-link"
+      onClick={[Function]}
+    >
+      cancel
+    </Button>
+  </div>
+</Fragment>
+`;
diff --git a/server/sonar-web/src/main/js/apps/settings/store/actions.js b/server/sonar-web/src/main/js/apps/settings/store/actions.js
deleted file mode 100644 (file)
index 34bdf54..0000000
+++ /dev/null
@@ -1,127 +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 { receiveValues } from './values/actions';
-import { receiveDefinitions } from './definitions/actions';
-import { startLoading, stopLoading } from './settingsPage/loading/actions';
-import { passValidation, failValidation } from './settingsPage/validationMessages/actions';
-import { cancelChange } from './settingsPage/changedValues/actions';
-import {
-  getDefinitions,
-  getValues,
-  setSettingValue,
-  resetSettingValue
-} from '../../../api/settings';
-import { parseError } from '../../../helpers/request';
-import { addGlobalErrorMessage, closeAllGlobalMessages } from '../../../store/globalMessages';
-import { isEmptyValue } from '../utils';
-import { translate } from '../../../helpers/l10n';
-import { getSettingsAppDefinition, getSettingsAppChangedValue } from '../../../store/rootReducer';
-
-export const fetchSettings = componentKey => dispatch => {
-  return getDefinitions(componentKey).then(
-    definitions => {
-      const filtered = definitions
-        .filter(definition => definition.type !== 'LICENSE')
-        // do not display this setting on project level
-        .filter(
-          definition =>
-            componentKey == null || definition.key !== 'sonar.branch.longLivedBranches.regex'
-        );
-      dispatch(receiveDefinitions(filtered));
-    },
-    e => parseError(e).then(message => dispatch(addGlobalErrorMessage(message)))
-  );
-};
-
-export const fetchValues = (keys, component) => dispatch =>
-  getValues({ keys, component }).then(
-    settings => {
-      dispatch(receiveValues(settings, component));
-      dispatch(closeAllGlobalMessages());
-    },
-    () => {}
-  );
-
-export const checkValue = (key, componentKey) => (dispatch, getState) => {
-  const state = getState();
-  const definition = getSettingsAppDefinition(state, key);
-  const value = getSettingsAppChangedValue(state, key);
-
-  if (isEmptyValue(definition, value)) {
-    if (definition.defaultValue === undefined) {
-      dispatch(failValidation(key, translate('settings.state.value_cant_be_empty_no_default')));
-    } else {
-      dispatch(failValidation(key, translate('settings.state.value_cant_be_empty')));
-    }
-    return false;
-  }
-
-  dispatch(passValidation(key));
-  return true;
-};
-
-export const saveValue = (key, componentKey) => (dispatch, getState) => {
-  dispatch(startLoading(key));
-
-  const state = getState();
-  const definition = getSettingsAppDefinition(state, key);
-  const value = getSettingsAppChangedValue(state, key);
-
-  if (isEmptyValue(definition, value)) {
-    dispatch(failValidation(key, translate('settings.state.value_cant_be_empty')));
-    dispatch(stopLoading(key));
-    return Promise.reject();
-  }
-
-  return setSettingValue(definition, value, componentKey)
-    .then(() => getValues({ keys: key, component: componentKey }))
-    .then(values => {
-      dispatch(receiveValues(values, componentKey));
-      dispatch(cancelChange(key));
-      dispatch(passValidation(key));
-      dispatch(stopLoading(key));
-    })
-    .catch(e => {
-      dispatch(stopLoading(key));
-      parseError(e).then(message => dispatch(failValidation(key, message)));
-      return Promise.reject();
-    });
-};
-
-export const resetValue = (key, componentKey) => dispatch => {
-  dispatch(startLoading(key));
-
-  return resetSettingValue({ keys: key, component: componentKey })
-    .then(() => getValues({ keys: key, component: componentKey }))
-    .then(values => {
-      if (values.length > 0) {
-        dispatch(receiveValues(values, componentKey));
-      } else {
-        dispatch(receiveValues([{ key }], componentKey));
-      }
-      dispatch(passValidation(key));
-      dispatch(stopLoading(key));
-    })
-    .catch(e => {
-      dispatch(stopLoading(key));
-      parseError(e).then(message => dispatch(failValidation(key, message)));
-      return Promise.reject();
-    });
-};
diff --git a/server/sonar-web/src/main/js/apps/settings/store/actions.ts b/server/sonar-web/src/main/js/apps/settings/store/actions.ts
new file mode 100644 (file)
index 0000000..d3b3526
--- /dev/null
@@ -0,0 +1,141 @@
+/*
+ * 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 { Dispatch } from 'redux';
+import { receiveValues } from './values';
+import { receiveDefinitions } from './definitions';
+import {
+  cancelChange,
+  failValidation,
+  passValidation,
+  startLoading,
+  stopLoading
+} from './settingsPage';
+import {
+  getDefinitions,
+  getValues,
+  setSettingValue,
+  resetSettingValue
+} from '../../../api/settings';
+import { parseError } from '../../../helpers/request';
+import { closeAllGlobalMessages } from '../../../store/globalMessages';
+import { isEmptyValue } from '../utils';
+import { translate } from '../../../helpers/l10n';
+import {
+  getSettingsAppDefinition,
+  getSettingsAppChangedValue,
+  Store
+} from '../../../store/rootReducer';
+import { SettingType } from '../../../app/types';
+
+export function fetchSettings(component?: string) {
+  return (dispatch: Dispatch) => {
+    return getDefinitions(component).then(definitions => {
+      const filtered = definitions
+        .filter(definition => definition.type !== SettingType.License)
+        // do not display this setting on project level
+        .filter(
+          definition => !component || definition.key !== 'sonar.branch.longLivedBranches.regex'
+        );
+      dispatch(receiveDefinitions(filtered));
+    });
+  };
+}
+
+export function fetchValues(keys: string, component?: string) {
+  return (dispatch: Dispatch) =>
+    getValues({ keys, component }).then(settings => {
+      dispatch(receiveValues(settings, component));
+      dispatch(closeAllGlobalMessages());
+    });
+}
+
+export function checkValue(key: string) {
+  return (dispatch: Dispatch, getState: () => Store) => {
+    const state = getState();
+    const definition = getSettingsAppDefinition(state, key);
+    const value = getSettingsAppChangedValue(state, key);
+
+    if (isEmptyValue(definition, value)) {
+      if (definition.defaultValue === undefined) {
+        dispatch(failValidation(key, translate('settings.state.value_cant_be_empty_no_default')));
+      } else {
+        dispatch(failValidation(key, translate('settings.state.value_cant_be_empty')));
+      }
+      return false;
+    }
+
+    dispatch(passValidation(key));
+    return true;
+  };
+}
+
+export function saveValue(key: string, component?: string) {
+  return (dispatch: Dispatch, getState: () => Store) => {
+    dispatch(startLoading(key));
+    const state = getState();
+    const definition = getSettingsAppDefinition(state, key);
+    const value = getSettingsAppChangedValue(state, key);
+
+    if (isEmptyValue(definition, value)) {
+      dispatch(failValidation(key, translate('settings.state.value_cant_be_empty')));
+      dispatch(stopLoading(key));
+      return Promise.reject();
+    }
+
+    return setSettingValue(definition, value, component)
+      .then(() => getValues({ keys: key, component }))
+      .then(values => {
+        dispatch(receiveValues(values, component));
+        dispatch(cancelChange(key));
+        dispatch(passValidation(key));
+        dispatch(stopLoading(key));
+      })
+      .catch(handleError(key, dispatch));
+  };
+}
+
+export function resetValue(key: string, component?: string) {
+  return (dispatch: Dispatch) => {
+    dispatch(startLoading(key));
+
+    return resetSettingValue({ keys: key, component })
+      .then(() => getValues({ keys: key, component }))
+      .then(values => {
+        if (values.length > 0) {
+          dispatch(receiveValues(values, component));
+        } else {
+          dispatch(receiveValues([{ key }], component));
+        }
+        dispatch(passValidation(key));
+        dispatch(stopLoading(key));
+      })
+      .catch(handleError(key, dispatch));
+  };
+}
+
+function handleError(key: string, dispatch: Dispatch) {
+  return (error: { response: Response }) => {
+    dispatch(stopLoading(key));
+    return parseError(error).then(message => {
+      dispatch(failValidation(key, message));
+      return Promise.reject();
+    });
+  };
+}
diff --git a/server/sonar-web/src/main/js/apps/settings/store/definitions.ts b/server/sonar-web/src/main/js/apps/settings/store/definitions.ts
new file mode 100644 (file)
index 0000000..fef2420
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * 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 { keyBy, sortBy, uniqBy } from 'lodash';
+import { ActionType } from '../../../store/utils/actions';
+import { SettingCategoryDefinition } from '../../../app/types';
+import { DEFAULT_CATEGORY, getCategoryName } from '../utils';
+
+const enum Actions {
+  ReceiveDefinitions = 'RECEIVE_DEFINITIONS'
+}
+
+type Action = ActionType<typeof receiveDefinitions, Actions.ReceiveDefinitions>;
+
+export interface State {
+  [key: string]: SettingCategoryDefinition;
+}
+
+export function receiveDefinitions(definitions: SettingCategoryDefinition[]) {
+  return { type: Actions.ReceiveDefinitions, definitions };
+}
+
+export default function components(state: State = {}, action: Action) {
+  if (action.type === Actions.ReceiveDefinitions) {
+    return keyBy(action.definitions, 'key');
+  }
+  return state;
+}
+
+export function getDefinition(state: State, key: string) {
+  return state[key];
+}
+
+export function getAllDefinitions(state: State) {
+  return Object.keys(state).map(key => state[key]);
+}
+
+export function getDefinitionsForCategory(state: State, category: string) {
+  return getAllDefinitions(state).filter(
+    definition => definition.category.toLowerCase() === category.toLowerCase()
+  );
+}
+
+export function getAllCategories(state: State) {
+  return uniqBy(getAllDefinitions(state).map(definition => definition.category), category =>
+    category.toLowerCase()
+  );
+}
+
+export function getDefaultCategory(state: State) {
+  const categories = getAllCategories(state);
+  if (categories.includes(DEFAULT_CATEGORY)) {
+    return DEFAULT_CATEGORY;
+  } else {
+    const sortedCategories = sortBy(categories, category =>
+      getCategoryName(category).toLowerCase()
+    );
+    return sortedCategories[0];
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/settings/store/definitions/actions.js b/server/sonar-web/src/main/js/apps/settings/store/definitions/actions.js
deleted file mode 100644 (file)
index e80a3ea..0000000
+++ /dev/null
@@ -1,33 +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 type { Definition } from '../../types'; */
-
-export const RECEIVE_DEFINITIONS /*: string */ = 'RECEIVE_DEFINITIONS';
-
-/**
- * Receive definitions action creator
- * @param {Array} definitions
- * @returns {Object}
- */
-export const receiveDefinitions = (definitions /*: Definition[] */) => ({
-  type: RECEIVE_DEFINITIONS,
-  definitions
-});
diff --git a/server/sonar-web/src/main/js/apps/settings/store/definitions/reducer.js b/server/sonar-web/src/main/js/apps/settings/store/definitions/reducer.js
deleted file mode 100644 (file)
index 8d5e175..0000000
+++ /dev/null
@@ -1,72 +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 { keyBy, sortBy, uniqBy } from 'lodash';
-import { RECEIVE_DEFINITIONS } from './actions';
-import { DEFAULT_CATEGORY, getCategoryName } from '../../utils';
-/*:: import type { Definition } from '../../types'; */
-
-/*::
-type State = { [key: string]: Definition };
-*/
-
-/*::
-type Action = { type: string, definitions: Definition[] };
-*/
-
-const reducer = (state /*: State */ = {}, action /*: Action */) => {
-  if (action.type === RECEIVE_DEFINITIONS) {
-    return keyBy(action.definitions, 'key');
-  }
-
-  return state;
-};
-
-export default reducer;
-
-export function getDefinition(state /*: State */, key /*: string */) /*: Definition */ {
-  return state[key];
-}
-
-export function getAllDefinitions(state /*: State */) /*: Definition[] */ {
-  return Object.keys(state).map(key => state[key]);
-}
-
-export const getDefinitionsForCategory = (state /*: State */, category /*: string */) =>
-  getAllDefinitions(state).filter(
-    definition => definition.category.toLowerCase() === category.toLowerCase()
-  );
-
-export const getAllCategories = (state /*: State */) =>
-  uniqBy(getAllDefinitions(state).map(definition => definition.category), category =>
-    category.toLowerCase()
-  );
-
-export const getDefaultCategory = (state /*: State */) => {
-  const categories = getAllCategories(state);
-  if (categories.includes(DEFAULT_CATEGORY)) {
-    return DEFAULT_CATEGORY;
-  } else {
-    const sortedCategories = sortBy(categories, category =>
-      getCategoryName(category).toLowerCase()
-    );
-    return sortedCategories[0];
-  }
-};
diff --git a/server/sonar-web/src/main/js/apps/settings/store/rootReducer.js b/server/sonar-web/src/main/js/apps/settings/store/rootReducer.js
deleted file mode 100644 (file)
index 24b8555..0000000
+++ /dev/null
@@ -1,79 +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 { combineReducers } from 'redux';
-import definitions, * as fromDefinitions from './definitions/reducer';
-import values, * as fromValues from './values/reducer';
-import settingsPage, * as fromSettingsPage from './settingsPage/reducer';
-import globalMessages, * as fromGlobalMessages from '../../../store/globalMessages';
-/*:: import type { State as GlobalMessagesState } from '../../../store/globalMessages'; */
-/*:: import type { State as ValuesState } from './values/reducer'; */
-
-/*::
-type State = {
-  definitions: {},
-  globalMessages: GlobalMessagesState,
-  settingsPage: {},
-  values: ValuesState
-};
-*/
-
-const rootReducer = combineReducers({
-  definitions,
-  values,
-  settingsPage,
-  globalMessages
-});
-
-export default rootReducer;
-
-export const getDefinition = (state /*: State */, key /*: string */) =>
-  fromDefinitions.getDefinition(state.definitions, key);
-
-export const getAllCategories = (state /*: State */) =>
-  fromDefinitions.getAllCategories(state.definitions);
-
-export const getDefaultCategory = (state /*: State */) =>
-  fromDefinitions.getDefaultCategory(state.definitions);
-
-export const getValue = (state /*: State */, key /*: string */, componentKey /*: ?string */) =>
-  fromValues.getValue(state.values, key, componentKey);
-
-export const getSettingsForCategory = (
-  state /*: State */,
-  category /*: string */,
-  componentKey /*: ?string */
-) =>
-  fromDefinitions.getDefinitionsForCategory(state.definitions, category).map(definition => ({
-    ...getValue(state, definition.key, componentKey),
-    definition
-  }));
-
-export const getChangedValue = (state /*: State */, key /*: string */) =>
-  fromSettingsPage.getChangedValue(state.settingsPage, key);
-
-export const isLoading = (state /*: State */, key /*: string */) =>
-  fromSettingsPage.isLoading(state.settingsPage, key);
-
-export const getValidationMessage = (state /*: State */, key /*: string */) =>
-  fromSettingsPage.getValidationMessage(state.settingsPage, key);
-
-export const getGlobalMessages = (state /*: State */) =>
-  fromGlobalMessages.getGlobalMessages(state.globalMessages);
diff --git a/server/sonar-web/src/main/js/apps/settings/store/rootReducer.ts b/server/sonar-web/src/main/js/apps/settings/store/rootReducer.ts
new file mode 100644 (file)
index 0000000..625d649
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * 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 { combineReducers } from 'redux';
+import definitions, * as fromDefinitions from './definitions';
+import values, * as fromValues from './values';
+import settingsPage, * as fromSettingsPage from './settingsPage';
+import globalMessages, * as fromGlobalMessages from '../../../store/globalMessages';
+
+interface State {
+  definitions: fromDefinitions.State;
+  globalMessages: fromGlobalMessages.State;
+  settingsPage: fromSettingsPage.State;
+  values: fromValues.State;
+}
+
+export default combineReducers({ definitions, values, settingsPage, globalMessages });
+
+export function getDefinition(state: State, key: string) {
+  return fromDefinitions.getDefinition(state.definitions, key);
+}
+
+export function getAllCategories(state: State) {
+  return fromDefinitions.getAllCategories(state.definitions);
+}
+
+export function getDefaultCategory(state: State) {
+  return fromDefinitions.getDefaultCategory(state.definitions);
+}
+
+export function getValue(state: State, key: string, component?: string) {
+  return fromValues.getValue(state.values, key, component);
+}
+
+export function getSettingsForCategory(state: State, category: string, component?: string) {
+  return fromDefinitions.getDefinitionsForCategory(state.definitions, category).map(definition => ({
+    key: definition.key,
+    ...getValue(state, definition.key, component),
+    definition
+  }));
+}
+
+export function getChangedValue(state: State, key: string) {
+  return fromSettingsPage.getChangedValue(state.settingsPage, key);
+}
+
+export function isLoading(state: State, key: string) {
+  return fromSettingsPage.isLoading(state.settingsPage, key);
+}
+
+export function getValidationMessage(state: State, key: string) {
+  return fromSettingsPage.getValidationMessage(state.settingsPage, key);
+}
+
+export function getGlobalMessages(state: State) {
+  return fromGlobalMessages.getGlobalMessages(state.globalMessages);
+}
diff --git a/server/sonar-web/src/main/js/apps/settings/store/settingsPage.ts b/server/sonar-web/src/main/js/apps/settings/store/settingsPage.ts
new file mode 100644 (file)
index 0000000..25365a8
--- /dev/null
@@ -0,0 +1,113 @@
+/*
+ * 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 { omit } from 'lodash';
+import { combineReducers } from 'redux';
+import { ActionType } from '../../../store/utils/actions';
+
+const enum Actions {
+  CancelChange = 'settingsPage/CANCEL_CHANGE',
+  ChangeValue = 'settingsPage/CHANGE_VALUE',
+  FailValidation = 'settingsPage/FAIL_VALIDATION',
+  PassValidation = 'settingsPage/PASS_VALIDATION',
+  StartLoading = 'settingsPage/START_LOADING',
+  StopLoading = 'settingsPage/STOP_LOADING'
+}
+
+type Action =
+  | ActionType<typeof cancelChange, Actions.CancelChange>
+  | ActionType<typeof changeValue, Actions.ChangeValue>
+  | ActionType<typeof failValidation, Actions.FailValidation>
+  | ActionType<typeof passValidation, Actions.PassValidation>
+  | ActionType<typeof startLoading, Actions.StartLoading>
+  | ActionType<typeof stopLoading, Actions.StopLoading>;
+
+export interface State {
+  changedValues: { [key: string]: any };
+  loading: { [key: string]: boolean };
+  validationMessages: { [key: string]: string };
+}
+
+export function cancelChange(key: string) {
+  return { type: Actions.CancelChange, key };
+}
+
+export function changeValue(key: string, value: any) {
+  return { type: Actions.ChangeValue, key, value };
+}
+
+function changedValues(state: State['changedValues'] = {}, action: Action) {
+  if (action.type === Actions.ChangeValue) {
+    return { ...state, [action.key]: action.value };
+  }
+  if (action.type === Actions.CancelChange) {
+    return omit(state, action.key);
+  }
+  return state;
+}
+
+export function failValidation(key: string, message: string) {
+  return { type: Actions.FailValidation, key, message };
+}
+
+export function passValidation(key: string) {
+  return { type: Actions.PassValidation, key };
+}
+
+function validationMessages(state: State['validationMessages'] = {}, action: Action) {
+  if (action.type === Actions.FailValidation) {
+    return { ...state, [action.key]: action.message };
+  }
+  if (action.type === Actions.PassValidation) {
+    return omit(state, action.key);
+  }
+  return state;
+}
+
+export function startLoading(key: string) {
+  return { type: Actions.StartLoading, key };
+}
+
+export function stopLoading(key: string) {
+  return { type: Actions.StopLoading, key };
+}
+
+function loading(state: State['loading'] = {}, action: Action) {
+  if (action.type === Actions.StartLoading) {
+    return { ...state, [action.key]: true };
+  }
+  if (action.type === Actions.StopLoading) {
+    return { ...state, [action.key]: false };
+  }
+  return state;
+}
+
+export default combineReducers({ changedValues, loading, validationMessages });
+
+export function getChangedValue(state: State, key: string) {
+  return state.changedValues[key];
+}
+
+export function getValidationMessage(state: State, key: string): string | undefined {
+  return state.validationMessages[key];
+}
+
+export function isLoading(state: State, key: string) {
+  return Boolean(state.loading[key]);
+}
diff --git a/server/sonar-web/src/main/js/apps/settings/store/settingsPage/changedValues/actions.js b/server/sonar-web/src/main/js/apps/settings/store/settingsPage/changedValues/actions.js
deleted file mode 100644 (file)
index 0364e25..0000000
+++ /dev/null
@@ -1,33 +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.
- */
-export const CHANGE_VALUE = 'settingsPage/CHANGE_VALUE';
-
-export const changeValue = (key, value) => ({
-  type: CHANGE_VALUE,
-  key,
-  value
-});
-
-export const CANCEL_CHANGE = 'settingsPage/CANCEL_CHANGE';
-
-export const cancelChange = key => ({
-  type: CANCEL_CHANGE,
-  key
-});
diff --git a/server/sonar-web/src/main/js/apps/settings/store/settingsPage/changedValues/reducer.js b/server/sonar-web/src/main/js/apps/settings/store/settingsPage/changedValues/reducer.js
deleted file mode 100644 (file)
index 1c6978d..0000000
+++ /dev/null
@@ -1,37 +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 { omit } from 'lodash';
-import { CHANGE_VALUE, CANCEL_CHANGE } from './actions';
-
-const reducer = (state = {}, action = {}) => {
-  if (action.type === CHANGE_VALUE) {
-    return { ...state, [action.key]: action.value };
-  }
-
-  if (action.type === CANCEL_CHANGE) {
-    return omit(state, action.key);
-  }
-
-  return state;
-};
-
-export default reducer;
-
-export const getChangedValue = (state, key) => state[key];
diff --git a/server/sonar-web/src/main/js/apps/settings/store/settingsPage/loading/actions.js b/server/sonar-web/src/main/js/apps/settings/store/settingsPage/loading/actions.js
deleted file mode 100644 (file)
index 291c0fe..0000000
+++ /dev/null
@@ -1,31 +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.
- */
-export const START_LOADING = 'settingsPage/START_LOADING';
-
-export const startLoading = key => ({
-  type: START_LOADING,
-  key
-});
-export const STOP_LOADING = 'settingsPage/STOP_LOADING';
-
-export const stopLoading = key => ({
-  type: STOP_LOADING,
-  key
-});
diff --git a/server/sonar-web/src/main/js/apps/settings/store/settingsPage/loading/reducer.js b/server/sonar-web/src/main/js/apps/settings/store/settingsPage/loading/reducer.js
deleted file mode 100644 (file)
index e220fab..0000000
+++ /dev/null
@@ -1,36 +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 { START_LOADING, STOP_LOADING } from './actions';
-
-const reducer = (state = {}, action = {}) => {
-  if (action.type === START_LOADING) {
-    return { ...state, [action.key]: true };
-  }
-
-  if (action.type === STOP_LOADING) {
-    return { ...state, [action.key]: false };
-  }
-
-  return state;
-};
-
-export default reducer;
-
-export const isLoading = (state, key) => !!state[key];
diff --git a/server/sonar-web/src/main/js/apps/settings/store/settingsPage/reducer.js b/server/sonar-web/src/main/js/apps/settings/store/settingsPage/reducer.js
deleted file mode 100644 (file)
index f19e058..0000000
+++ /dev/null
@@ -1,37 +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 { combineReducers } from 'redux';
-import changedValues, * as fromChangedValues from './changedValues/reducer';
-import validationMessages, * as fromValidationMessages from './validationMessages/reducer';
-import loading, * as fromLoading from './loading/reducer';
-
-export default combineReducers({
-  changedValues,
-  validationMessages,
-  loading
-});
-
-export const getChangedValue = (state, key) =>
-  fromChangedValues.getChangedValue(state.changedValues, key);
-
-export const getValidationMessage = (state, key) =>
-  fromValidationMessages.getValidationMessage(state.validationMessages, key);
-
-export const isLoading = (state, key) => fromLoading.isLoading(state.loading, key);
diff --git a/server/sonar-web/src/main/js/apps/settings/store/settingsPage/validationMessages/actions.js b/server/sonar-web/src/main/js/apps/settings/store/settingsPage/validationMessages/actions.js
deleted file mode 100644 (file)
index d073581..0000000
+++ /dev/null
@@ -1,33 +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.
- */
-export const FAIL_VALIDATION = 'settingsPage/FAIL_VALIDATION';
-
-export const failValidation = (key, message) => ({
-  type: FAIL_VALIDATION,
-  key,
-  message
-});
-
-export const PASS_VALIDATION = 'settingsPage/PASS_VALIDATION';
-
-export const passValidation = key => ({
-  type: PASS_VALIDATION,
-  key
-});
diff --git a/server/sonar-web/src/main/js/apps/settings/store/settingsPage/validationMessages/reducer.js b/server/sonar-web/src/main/js/apps/settings/store/settingsPage/validationMessages/reducer.js
deleted file mode 100644 (file)
index 9b8da6f..0000000
+++ /dev/null
@@ -1,36 +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 { FAIL_VALIDATION, PASS_VALIDATION } from './actions';
-
-const reducer = (state = {}, action = {}) => {
-  if (action.type === FAIL_VALIDATION) {
-    return { ...state, [action.key]: action.message };
-  }
-
-  if (action.type === PASS_VALIDATION) {
-    return { ...state, [action.key]: null };
-  }
-
-  return state;
-};
-
-export default reducer;
-
-export const getValidationMessage = (state, key) => state[key];
diff --git a/server/sonar-web/src/main/js/apps/settings/store/values.ts b/server/sonar-web/src/main/js/apps/settings/store/values.ts
new file mode 100644 (file)
index 0000000..e00727a
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ * 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 { combineReducers } from 'redux';
+import { keyBy } from 'lodash';
+import { ActionType } from '../../../store/utils/actions';
+import { Action as AppStateAction, Actions as AppStateActions } from '../../../store/appState';
+import { SettingValue } from '../../../app/types';
+
+enum Actions {
+  receiveValues = 'RECEIVE_VALUES'
+}
+
+type Action = ActionType<typeof receiveValues, Actions.receiveValues>;
+
+interface SettingsState {
+  [key: string]: SettingValue;
+}
+
+export interface State {
+  components: { [component: string]: SettingsState };
+  global: SettingsState;
+}
+
+export function receiveValues(
+  settings: Array<{ key: string; value?: string }>,
+  component?: string
+) {
+  return { type: Actions.receiveValues, settings, component };
+}
+
+function components(state: State['components'] = {}, action: Action) {
+  const { component: key } = action;
+  if (!key) {
+    return state;
+  }
+  if (action.type === Actions.receiveValues) {
+    const settingsByKey = keyBy(action.settings, 'key');
+    return { ...state, [key]: { ...(state[key] || {}), ...settingsByKey } };
+  }
+  return state;
+}
+
+function global(state: State['components'] = {}, action: Action | AppStateAction) {
+  if (action.type === Actions.receiveValues) {
+    if (action.component) {
+      return state;
+    }
+    const settingsByKey = keyBy(action.settings, 'key');
+    return { ...state, ...settingsByKey };
+  }
+  if (action.type === AppStateActions.SetAppState) {
+    const settingsByKey: SettingsState = {};
+    Object.keys(action.appState.settings).forEach(
+      key => (settingsByKey[key] = { key, value: action.appState.settings[key] })
+    );
+    return { ...state, ...settingsByKey };
+  }
+  return state;
+}
+
+export default combineReducers({ components, global });
+
+export function getValue(state: State, key: string, component?: string): SettingValue | undefined {
+  if (component) {
+    return state.components[component] && state.components[component][key];
+  }
+  return state.global[key];
+}
diff --git a/server/sonar-web/src/main/js/apps/settings/store/values/actions.ts b/server/sonar-web/src/main/js/apps/settings/store/values/actions.ts
deleted file mode 100644 (file)
index a7d6f8f..0000000
+++ /dev/null
@@ -1,29 +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.
- */
-interface SettingValue {
-  key: string;
-  value?: string;
-}
-
-export const RECEIVE_VALUES = 'RECEIVE_VALUES';
-
-export function receiveValues(settings: SettingValue[], componentKey?: string) {
-  return { type: RECEIVE_VALUES, settings, componentKey };
-}
diff --git a/server/sonar-web/src/main/js/apps/settings/store/values/reducer.js b/server/sonar-web/src/main/js/apps/settings/store/values/reducer.js
deleted file mode 100644 (file)
index 5da0fdc..0000000
+++ /dev/null
@@ -1,74 +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 { combineReducers } from 'redux';
-import { keyBy } from 'lodash';
-import { RECEIVE_VALUES } from './actions';
-
-/*::
-type SettingsState = { [key: string]: {} };
-type ComponentsState = { [key: string]: SettingsState };
-export type State = { components: ComponentsState, global: SettingsState };
-*/
-
-const componentsSettings = (state /*: ComponentsState */ = {}, action /*: Object */) => {
-  if (!action.componentKey) {
-    return state;
-  }
-
-  const key = action.componentKey;
-  if (action.type === RECEIVE_VALUES) {
-    const settingsByKey = keyBy(action.settings, 'key');
-    return { ...state, [key]: { ...(state[key] || {}), ...settingsByKey } };
-  }
-
-  return state;
-};
-
-const globalSettings = (state /*: SettingsState */ = {}, action /*: Object */) => {
-  if (action.componentKey) {
-    return state;
-  }
-
-  if (action.type === RECEIVE_VALUES) {
-    const settingsByKey = keyBy(action.settings, 'key');
-    return { ...state, ...settingsByKey };
-  }
-
-  if (action.type === 'SET_APP_STATE') {
-    const settingsByKey = {};
-    Object.keys(action.appState.settings).forEach(
-      key => (settingsByKey[key] = { value: action.appState.settings[key] })
-    );
-    return { ...state, ...settingsByKey };
-  }
-
-  return state;
-};
-
-export default combineReducers({ components: componentsSettings, global: globalSettings });
-
-export const getValue = (state /*: State */, key /*: string */, componentKey /*: ?string */) => {
-  let settings = state.global;
-  if (componentKey) {
-    settings = state.components[componentKey];
-  }
-  return settings && settings[key];
-};
diff --git a/server/sonar-web/src/main/js/apps/settings/types.js b/server/sonar-web/src/main/js/apps/settings/types.js
deleted file mode 100644 (file)
index c0f10cb..0000000
+++ /dev/null
@@ -1,26 +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
-/*::
-export type Definition = {
-  key: string,
-  category: string
-};
-*/
index ad31cabb65d012c7756faa29084c20d4b0a84ef5..6f32f8930cbd63cd6d6deceb35af363b0b18f51a 100644 (file)
@@ -55,10 +55,14 @@ function Avatar(props: Props) {
   );
 }
 
-const mapStateToProps = (state: Store) => ({
-  enableGravatar: (getGlobalSettingValue(state, 'sonar.lf.enableGravatar') || {}).value === 'true',
-  gravatarServerUrl: (getGlobalSettingValue(state, 'sonar.lf.gravatarServerUrl') || {}).value
-});
+const mapStateToProps = (state: Store) => {
+  const enableGravatar = getGlobalSettingValue(state, 'sonar.lf.enableGravatar');
+  const gravatarServerUrl = getGlobalSettingValue(state, 'sonar.lf.gravatarServerUrl');
+  return {
+    enableGravatar: Boolean(enableGravatar && enableGravatar.value === 'true'),
+    gravatarServerUrl: (gravatarServerUrl && gravatarServerUrl.value) || ''
+  };
+};
 
 export default connect(mapStateToProps)(Avatar);
 
index 19a5e546944b6749f23bb43e8bd2aa0395f8516d..89f31e98e12c8e62b06470bf369d6b6d7083012b 100644 (file)
@@ -21,21 +21,27 @@ import { ActionType } from './utils/actions';
 import { Extension, AppState } from '../app/types';
 import { EditionKey } from '../apps/marketplace/utils';
 
-type Action =
-  | ActionType<typeof setAppState, 'SET_APP_STATE'>
-  | ActionType<typeof setAdminPages, 'SET_ADMIN_PAGES'>
-  | ActionType<typeof requireAuthorization, 'REQUIRE_AUTHORIZATION'>;
+export const enum Actions {
+  SetAppState = 'SET_APP_STATE',
+  SetAdminPages = 'SET_ADMIN_PAGES',
+  RequireAuthorization = 'REQUIRE_AUTHORIZATION'
+}
+
+export type Action =
+  | ActionType<typeof setAppState, Actions.SetAppState>
+  | ActionType<typeof setAdminPages, Actions.SetAdminPages>
+  | ActionType<typeof requireAuthorization, Actions.RequireAuthorization>;
 
 export function setAppState(appState: AppState) {
-  return { type: 'SET_APP_STATE', appState };
+  return { type: Actions.SetAppState, appState };
 }
 
 export function setAdminPages(adminPages: Extension[]) {
-  return { type: 'SET_ADMIN_PAGES', adminPages };
+  return { type: Actions.SetAdminPages, adminPages };
 }
 
 export function requireAuthorization() {
-  return { type: 'REQUIRE_AUTHORIZATION' };
+  return { type: Actions.RequireAuthorization };
 }
 
 const defaultValue: AppState = {
@@ -46,21 +52,19 @@ const defaultValue: AppState = {
   organizationsEnabled: false,
   productionDatabase: true,
   qualifiers: [],
+  settings: {},
   version: ''
 };
 
 export default function(state: AppState = defaultValue, action: Action): AppState {
-  if (action.type === 'SET_APP_STATE') {
+  if (action.type === Actions.SetAppState) {
     return { ...state, ...action.appState };
   }
-
-  if (action.type === 'SET_ADMIN_PAGES') {
+  if (action.type === Actions.SetAdminPages) {
     return { ...state, adminPages: action.adminPages };
   }
-
-  if (action.type === 'REQUIRE_AUTHORIZATION') {
+  if (action.type === Actions.RequireAuthorization) {
     return { ...state, authorizationError: true };
   }
-
   return state;
 }
index eabb58635a80f96d19a997e17ab4f07dd44ab632..b1059021a6d112270a241f04fb3c8a1b690b2ea9 100644 (file)
@@ -41,8 +41,8 @@ export function closeGlobalMessage(id: string) {
   return { type: 'CLOSE_GLOBAL_MESSAGE', id };
 }
 
-export function closeAllGlobalMessages(id: string) {
-  return { type: 'CLOSE_ALL_GLOBAL_MESSAGES', id };
+export function closeAllGlobalMessages() {
+  return { type: 'CLOSE_ALL_GLOBAL_MESSAGES' };
 }
 
 type Action =
index 3d3bcfacec753d68ea8b916e95b26e465d66448d..336fe056b6bf6d7e066b12ff23f459cd62f207c9 100644 (file)
@@ -109,9 +109,9 @@ export function getSettingsAppDefaultCategory(state: Store) {
 export function getSettingsAppSettingsForCategory(
   state: Store,
   category: string,
-  componentKey: string
+  component?: string
 ) {
-  return fromSettingsApp.getSettingsForCategory(state.settingsApp, category, componentKey);
+  return fromSettingsApp.getSettingsForCategory(state.settingsApp, category, component);
 }
 
 export function getSettingsAppChangedValue(state: Store, key: string) {
index 99037fcc5aea0ffdbc2ea3c7dee7a42e4f3c31d2..a6a4e18ec3678592776b10319e159bf0c5fd2746 100644 (file)
@@ -63,6 +63,7 @@ duplications=Duplications
 end_date=End Date
 edit=Edit
 events=Events
+example=Example
 explore=Explore
 false=False
 favorite=Favorite
@@ -855,7 +856,14 @@ settings.default.no_value=<no value>
 settings.default.complex_value=<complex value>
 settings.default.password=<password>
 settings.reset_confirm.title=Reset Setting
-settings.reset_confirm.description=Are you sure that you want to reset this setting ?
+settings.reset_confirm.description=Are you sure that you want to reset this setting?
+settings.wildcards=Wildcards
+settings.wildcards.following_rules_are_applied=Following rules are applied:
+settings.wildcards.zero_more_char=Match zero or more characters
+settings.wildcards.zero_more_dir=Match zero or more directories
+settings.wildcards.single_char=Match a single character
+settings.wildcards.matches=Matches
+settings.wildcards.does_no_match=Does not match
 
 property.category.general=General
 property.category.general.email=Email