]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-8217 Improve UI and fix issues with default values
authorPascal Mugnier <pascal.mugnier@sonarsource.com>
Tue, 27 Mar 2018 13:46:41 +0000 (15:46 +0200)
committerSonarTech <sonartech@sonarsource.com>
Thu, 29 Mar 2018 18:20:46 +0000 (20:20 +0200)
server/sonar-web/src/main/js/api/settings.ts
server/sonar-web/src/main/js/apps/settings/components/Definition.js
server/sonar-web/src/main/js/apps/settings/components/DefinitionActions.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/DefinitionChanges.js [deleted file]
server/sonar-web/src/main/js/apps/settings/components/DefinitionDefaults.js [deleted file]
server/sonar-web/src/main/js/apps/settings/utils.js

index 46c0ec67cf81d91afd4bbf86efdd8458de9b6b25..6df408faa6b48d8b90918df5d0c48b24bd278298 100644 (file)
@@ -23,6 +23,24 @@ import { TYPE_PROPERTY_SET } from '../apps/settings/constants';
 import { BranchParameters } from '../app/types';
 import throwGlobalError from '../app/utils/throwGlobalError';
 
+interface DefinitionField {
+  description: string;
+  key: string;
+  name: string;
+  options: string[];
+}
+
+export interface Definition {
+  category: string;
+  description: string;
+  fields: DefinitionField[];
+  key: string;
+  name: string;
+  options: string[];
+  subCategory: string;
+  type: string;
+}
+
 export function getDefinitions(component?: string): Promise<any> {
   return getJSON('/api/settings/list_definitions', { component }).then(r => r.definitions);
 }
index 31169d527cb308433bb5b22e5eb26220206551ac..b1a1f32503b219040c7cdcdb28d4ba31a391da3e 100644 (file)
@@ -23,8 +23,7 @@ import PropTypes from 'prop-types';
 import { connect } from 'react-redux';
 import classNames from 'classnames';
 import Input from './inputs/Input';
-import DefinitionDefaults from './DefinitionDefaults';
-import DefinitionChanges from './DefinitionChanges';
+import DefinitionActions from './DefinitionActions';
 import {
   getPropertyName,
   getPropertyDescription,
@@ -63,8 +62,7 @@ class Definition extends React.PureComponent {
   };
 
   state = {
-    success: false,
-    hasError: false
+    success: false
   };
 
   componentDidMount() {
@@ -98,7 +96,7 @@ class Definition extends React.PureComponent {
       .resetValue(definition.key, componentKey)
       .then(() => {
         this.props.cancelChange(definition.key, componentKey);
-        this.safeSetState({ success: true, hasError: false });
+        this.safeSetState({ success: true });
         this.timeout = setTimeout(() => this.safeSetState({ success: false }), 3000);
       })
       .catch(() => {
@@ -114,8 +112,7 @@ class Definition extends React.PureComponent {
 
   handleCheck = () => {
     const componentKey = this.props.component ? this.props.component.key : null;
-    const hasError = !this.props.checkValue(this.props.setting.definition.key, componentKey);
-    this.safeSetState({ hasError });
+    this.props.checkValue(this.props.setting.definition.key, componentKey);
   };
 
   handleSave = () => {
@@ -136,9 +133,9 @@ class Definition extends React.PureComponent {
 
   render() {
     const { setting, changedValue, loading } = this.props;
-    const { hasError } = this.state;
     const { definition } = setting;
     const propertyName = getPropertyName(definition);
+    const hasError = this.props.validationMessage != null;
 
     const hasValueChanged = changedValue != null;
 
@@ -177,7 +174,7 @@ class Definition extends React.PureComponent {
             )}
 
             {!loading &&
-              this.props.validationMessage != null && (
+              hasError && (
                 <span className="text-danger">
                   <AlertErrorIcon className="spacer-right" />
                   <span>
@@ -190,7 +187,7 @@ class Definition extends React.PureComponent {
               )}
 
             {!loading &&
-              this.props.validationMessage == null &&
+              !hasError &&
               this.state.success && (
                 <span className="text-success">
                   <AlertSuccessIcon className="spacer-right" />
@@ -207,21 +204,15 @@ class Definition extends React.PureComponent {
             value={effectiveValue}
           />
 
-          {(!hasValueChanged || hasError) && (
-            <DefinitionDefaults
-              isDefault={isDefault}
-              onReset={this.handleReset}
-              setting={setting}
-            />
-          )}
-
-          {hasValueChanged && (
-            <DefinitionChanges
-              enableSave={!hasError}
-              onCancel={this.handleCancel}
-              onSave={this.handleSave}
-            />
-          )}
+          <DefinitionActions
+            changedValue={changedValue}
+            hasError={hasError}
+            isDefault={isDefault}
+            onCancel={this.handleCancel}
+            onReset={this.handleReset}
+            onSave={this.handleSave}
+            setting={setting}
+          />
         </div>
       </div>
     );
diff --git a/server/sonar-web/src/main/js/apps/settings/components/DefinitionActions.tsx b/server/sonar-web/src/main/js/apps/settings/components/DefinitionActions.tsx
new file mode 100644 (file)
index 0000000..c5a4e79
--- /dev/null
@@ -0,0 +1,127 @@
+/*
+ * 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 Modal from '../../../components/controls/Modal';
+import { isEmptyValue, getDefaultValue, getSettingValue } from '../utils';
+import { translate } from '../../../helpers/l10n';
+import { Button } from '../../../components/ui/buttons';
+import { SettingValue, Definition } from '../../../api/settings';
+
+type Props = {
+  changedValue: string;
+  hasError: boolean;
+  isDefault: boolean;
+  onCancel: () => void;
+  onReset: () => void;
+  onSave: () => void;
+  setting: SettingValue & { definition: Definition };
+  valueChanged: boolean;
+};
+
+type State = { reseting: boolean };
+
+export default class DefinitionActions extends React.PureComponent<Props, State> {
+  state: State = { reseting: false };
+
+  handleClose = () => {
+    this.setState({ reseting: false });
+  };
+
+  handleReset = () => {
+    this.setState({ reseting: true });
+  };
+
+  handleSubmit = () => {
+    this.props.onReset();
+    this.handleClose();
+  };
+
+  renderModal() {
+    const header = translate('settings.reset_confirm.title');
+    return (
+      <Modal contentLabel={header} onRequestClose={this.handleClose}>
+        <header className="modal-head">
+          <h2>{header}</h2>
+        </header>
+        <form onSubmit={this.handleSubmit}>
+          <div className="modal-body">
+            <p>{translate('settings.reset_confirm.description')}</p>
+          </div>
+          <footer className="modal-foot">
+            <button className="button-red">{translate('reset_verb')}</button>
+            <button className="button-link" onClick={this.handleClose} type="reset">
+              {translate('cancel')}
+            </button>
+          </footer>
+        </form>
+      </Modal>
+    );
+  }
+
+  render() {
+    const { setting, isDefault, changedValue } = this.props;
+    const hasValueChanged = changedValue != null;
+    const canBeReset = !isDefault && isEmptyValue(setting.definition, changedValue);
+    const isExplicitlySet =
+      !isDefault && !isEmptyValue(setting.definition, getSettingValue(setting));
+
+    return (
+      <>
+        {isDefault && (
+          <div className="spacer-top note" style={{ lineHeight: '24px' }}>
+            {translate('settings._default')}
+          </div>
+        )}
+        <div className="settings-definition-changes nowrap">
+          {hasValueChanged && (
+            <Button
+              className="spacer-right button-success"
+              disabled={this.props.hasError}
+              onClick={this.props.onSave}>
+              {translate('save')}
+            </Button>
+          )}
+
+          {canBeReset && (
+            <Button className="spacer-right" onClick={this.handleReset}>
+              {translate('reset_verb')}
+            </Button>
+          )}
+
+          {hasValueChanged && (
+            <Button className="spacer-right button-link" onClick={this.props.onCancel}>
+              {translate('cancel')}
+            </Button>
+          )}
+
+          {isExplicitlySet && (
+            <span className="note">
+              {translate('default')}
+              {': '}
+              {getDefaultValue(setting)}
+            </span>
+          )}
+
+          {this.state.reseting && this.renderModal()}
+        </div>
+      </>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/DefinitionChanges.js b/server/sonar-web/src/main/js/apps/settings/components/DefinitionChanges.js
deleted file mode 100644 (file)
index a3eb663..0000000
+++ /dev/null
@@ -1,65 +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 { translate } from '../../../helpers/l10n';
-
-/*::
-type Props = {
-  enableSave: boolean
-};
-*/
-
-export default class DefinitionChanges extends React.PureComponent {
-  static propTypes = {
-    onSave: PropTypes.func.isRequired,
-    onCancel: PropTypes.func.isRequired
-  };
-
-  handleSaveClick(e /*: Object */) {
-    e.preventDefault();
-    e.target.blur();
-    this.props.onSave();
-  }
-
-  handleCancelChange(e /*: Object */) {
-    e.preventDefault();
-    e.target.blur();
-    this.props.onCancel();
-  }
-
-  render() {
-    return (
-      <div className="settings-definition-changes">
-        {this.props.enableSave && (
-          <button className="js-save-changes button-success" onClick={e => this.handleSaveClick(e)}>
-            {translate('save')}
-          </button>
-        )}
-        <button
-          className="js-cancel-changes big-spacer-left button-link"
-          onClick={e => this.handleCancelChange(e)}>
-          {translate('cancel')}
-        </button>
-      </div>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/DefinitionDefaults.js b/server/sonar-web/src/main/js/apps/settings/components/DefinitionDefaults.js
deleted file mode 100644 (file)
index 4c19a0a..0000000
+++ /dev/null
@@ -1,107 +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 { getSettingValue, isEmptyValue, getDefaultValue } from '../utils';
-import Modal from '../../../components/controls/Modal';
-import { translate } from '../../../helpers/l10n';
-
-/*::
-type Props = {
-  isDefault: boolean,
-  onReset: () => void,
-  setting: Object
-};
-*/
-/*::
-type State = { reseting: boolean };
-*/
-
-export default class DefinitionDefaults extends React.PureComponent {
-  /*:: props: Props; */
-  state /*: State*/ = { reseting: false };
-
-  handleClose = () => {
-    this.setState({ reseting: false });
-  };
-
-  handleReset = (e /*: Event & {target: HTMLElement} */) => {
-    e.preventDefault();
-    e.target.blur();
-    this.setState({ reseting: true });
-  };
-
-  handleSubmit = (event /*: Event */) => {
-    event.preventDefault();
-    this.props.onReset();
-    this.handleClose();
-  };
-
-  renderModal() {
-    const header = translate('settings.reset_confirm.title');
-    return (
-      <Modal contentLabel={header} onRequestClose={this.handleClose}>
-        <header className="modal-head">
-          <h2>{header}</h2>
-        </header>
-        <form onSubmit={this.handleSubmit}>
-          <div className="modal-body">
-            <p>{translate('settings.reset_confirm.description')}</p>
-          </div>
-          <footer className="modal-foot">
-            <button className="button-red">{translate('reset_verb')}</button>
-            <button className="button-link" onClick={this.handleClose} type="reset">
-              {translate('cancel')}
-            </button>
-          </footer>
-        </form>
-      </Modal>
-    );
-  }
-
-  render() {
-    const { setting, isDefault } = this.props;
-    const { definition } = setting;
-
-    const isExplicitlySet = !isDefault && !isEmptyValue(definition, getSettingValue(setting));
-
-    return (
-      <div>
-        {isDefault && (
-          <div className="spacer-top note" style={{ lineHeight: '24px' }}>
-            {translate('settings._default')}
-          </div>
-        )}
-
-        {isExplicitlySet && (
-          <div className="spacer-top nowrap">
-            <button onClick={this.handleReset}>{translate('reset_verb')}</button>
-            <span className="spacer-left note">
-              {translate('default')}
-              {': '}
-              {getDefaultValue(setting)}
-            </span>
-          </div>
-        )}
-        {this.state.reseting && this.renderModal()}
-      </div>
-    );
-  }
-}
index b7aa3a8e34a3807f2191553886a7fb0b92c67fd7..b05eac7f43ed685ba898e40d2b8fe6e560fa9315 100644 (file)
@@ -115,12 +115,16 @@ function getParentValue(setting) {
  * @returns {string}
  */
 export function getDefaultValue(setting) {
-  const parentValue = getParentValue(setting);
+  let parentValue = getParentValue(setting);
 
   if (parentValue == null) {
-    return setting.definition.defaultValue
-      ? setting.definition.defaultValue
-      : translate('settings.default.no_value');
+    if (setting.definition.defaultValue) {
+      return setting.definition.defaultValue;
+    }
+    parentValue = getSettingValue(setting);
+    if (parentValue == null) {
+      return translate('settings.default.no_value');
+    }
   }
 
   if (setting.definition.multiValues) {