]> source.dussan.org Git - sonarqube.git/commitdiff
Fix SONAR-10640
authorPascal Mugnier <pascal.mugnier@sonarsource.com>
Wed, 9 May 2018 10:03:03 +0000 (12:03 +0200)
committerSonarTech <sonartech@sonarsource.com>
Thu, 24 May 2018 18:20:46 +0000 (20:20 +0200)
13 files changed:
server/sonar-web/src/main/js/app/styles/components/modals.css
server/sonar-web/src/main/js/apps/quality-gates/components/AddConditionButton.tsx
server/sonar-web/src/main/js/apps/quality-gates/components/AddConditionSelect.tsx
server/sonar-web/src/main/js/apps/quality-gates/components/Condition.tsx
server/sonar-web/src/main/js/apps/quality-gates/components/ConditionModal.tsx
server/sonar-web/src/main/js/apps/quality-gates/components/ConditionOperator.tsx
server/sonar-web/src/main/js/apps/quality-gates/components/Conditions.tsx
server/sonar-web/src/main/js/apps/quality-gates/components/DeleteConditionForm.tsx
server/sonar-web/src/main/js/apps/quality-gates/components/DetailsApp.tsx
server/sonar-web/src/main/js/apps/quality-gates/components/DetailsContent.tsx
server/sonar-web/src/main/js/apps/quality-gates/components/Period.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-gates/utils.ts
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index 21d73c39a996baed3be1ca2d5aa7d1e4f82059d4..f8416e932dec3d7d00ae33dfd5c86743a9aee43c 100644 (file)
@@ -167,6 +167,10 @@ ul.modal-head-metadata li {
   font-weight: bold;
 }
 
+.modal-field .note {
+  line-height: var(--controlHeight);
+}
+
 .readonly-field {
   padding-top: 5px;
   margin-left: -5px;
index 4894313190429fe97df07488c6558af890105427..04e4133f19197d1a652b6a4164540eb7929a10b4 100644 (file)
@@ -21,10 +21,13 @@ import * as React from 'react';
 import ConditionModal from './ConditionModal';
 import { Button } from '../../../components/ui/buttons';
 import { translate } from '../../../helpers/l10n';
-import { Metric } from '../../../app/types';
+import { Metric, QualityGate, Condition } from '../../../app/types';
 
 interface Props {
   metrics: Metric[];
+  organization?: string;
+  onAddCondition: (condition: Condition) => void;
+  qualityGate: QualityGate;
 }
 
 interface State {
@@ -61,7 +64,10 @@ export default class AddConditionButton extends React.PureComponent<Props, State
           <ConditionModal
             header={translate('quality_gates.add_condition')}
             metrics={this.props.metrics}
+            onAddCondition={this.props.onAddCondition}
             onClose={this.handleModalClose}
+            organization={this.props.organization}
+            qualityGate={this.props.qualityGate}
           />
         )}
       </>
index 8539232bb3f423d0cd7c86d217610918f34b2b43..9b9f416c3d3bde089dc5630b79810f754c799052 100644 (file)
@@ -25,27 +25,34 @@ import { Metric } from '../../../app/types';
 
 interface Props {
   metrics: Metric[];
-  onAddCondition: (metric: string) => void;
+  onAddCondition: (metric: Metric) => void;
+}
+
+interface State {
+  value: number;
 }
 
 interface Option {
   disabled?: boolean;
   domain?: string;
   label: string;
-  value: string;
+  value: number;
 }
 
-export default class AddConditionSelect extends React.PureComponent<Props> {
-  handleChange = (option: Option) => {
-    this.props.onAddCondition(option.value);
+export default class AddConditionSelect extends React.PureComponent<Props, State> {
+  state = { value: -1 };
+
+  handleChange = ({ value }: Option) => {
+    this.setState({ value });
+    this.props.onAddCondition(this.props.metrics[value]);
   };
 
   render() {
     const { metrics } = this.props;
 
     const options: Option[] = sortBy(
-      metrics.map(metric => ({
-        value: metric.key,
+      metrics.map((metric, index) => ({
+        value: index,
         label: getLocalizedMetricName(metric),
         domain: metric.domain
       })),
@@ -58,7 +65,7 @@ export default class AddConditionSelect extends React.PureComponent<Props> {
       const previous = index > 0 ? options[index - 1] : null;
       if (option.domain && (!previous || previous.domain !== option.domain)) {
         optionsWithDomains.push({
-          value: option.domain,
+          value: 0,
           label: getLocalizedMetricDomain(option.domain),
           disabled: true
         });
@@ -72,6 +79,7 @@ export default class AddConditionSelect extends React.PureComponent<Props> {
         onChange={this.handleChange}
         options={optionsWithDomains}
         placeholder={translate('quality_gates.add_condition')}
+        value={this.state.value}
       />
     );
   }
index 11a644c46927cca2c7136a375102aa714094c2d1..7525fd4a9c3f613c5a337d09d2343bac951fbfe6 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as React from 'react';
+import ConditionOperator from './ConditionOperator';
+import Period from './Period';
+import ConditionModal from './ConditionModal';
 import DeleteConditionForm from './DeleteConditionForm';
-import ThresholdInput from './ThresholdInput';
-import { createCondition, updateCondition } from '../../../api/quality-gates';
 import { Condition as ICondition, Metric, QualityGate } from '../../../app/types';
-import Checkbox from '../../../components/controls/Checkbox';
-import Select from '../../../components/controls/Select';
-import { Button, ResetButtonLink } from '../../../components/ui/buttons';
+import ActionsDropdown, { ActionsDropdownItem } from '../../../components/controls/ActionsDropdown';
 import { translate, getLocalizedMetricName } from '../../../helpers/l10n';
-import { formatMeasure, isDiffMetric } from '../../../helpers/measures';
+import { formatMeasure } from '../../../helpers/measures';
 
 interface Props {
   condition: ICondition;
   canEdit: boolean;
   metric: Metric;
   organization?: string;
-  onAddCondition: (metric: string) => void;
-  onError: (error: any) => void;
   onRemoveCondition: (Condition: ICondition) => void;
-  onResetError: () => void;
   onSaveCondition: (newCondition: ICondition, oldCondition: ICondition) => void;
   qualityGate: QualityGate;
 }
 
 interface State {
-  changed: boolean;
   error: string;
+  modal: boolean;
   op?: string;
   period?: number;
   warning: string;
@@ -53,144 +49,28 @@ export default class Condition extends React.PureComponent<Props, State> {
   constructor(props: Props) {
     super(props);
     this.state = {
-      changed: false,
       period: props.condition.period,
+      modal: false,
       op: props.condition.op,
       warning: props.condition.warning || '',
       error: props.condition.error || ''
     };
   }
 
-  getUpdatedCondition = () => {
-    const { metric } = this.props;
-    const data: ICondition = {
-      metric: metric.key,
-      op: metric.type === 'RATING' ? 'GT' : this.state.op,
-      warning: this.state.warning,
-      error: this.state.error
-    };
-
-    const { period } = this.state;
-    if (period && metric.type !== 'RATING') {
-      data.period = period;
-    }
-
-    if (isDiffMetric(metric.key)) {
-      data.period = 1;
-    }
-    return data;
-  };
-
-  handleOperatorChange = ({ value }: any) => this.setState({ changed: true, op: value });
-
-  handlePeriodChange = (checked: boolean) => {
-    const period = checked ? 1 : undefined;
-    this.setState({ changed: true, period });
-  };
-
-  handleWarningChange = (warning: string) => this.setState({ changed: true, warning });
-
-  handleErrorChange = (error: string) => this.setState({ changed: true, error });
-
-  handleSaveClick = () => {
-    const { qualityGate, organization } = this.props;
-    const data = this.getUpdatedCondition();
-    createCondition({ gateId: qualityGate.id, organization, ...data }).then(
-      this.handleConditionResponse,
-      this.props.onError
-    );
-  };
-
-  handleUpdateClick = () => {
-    const { condition, organization } = this.props;
-    const data: ICondition = {
-      id: condition.id,
-      ...this.getUpdatedCondition()
-    };
-
-    updateCondition({ organization, ...data }).then(
-      this.handleConditionResponse,
-      this.props.onError
-    );
-  };
-
-  handleConditionResponse = (newCondition: ICondition) => {
+  handleUpdateCondition = (newCondition: ICondition) => {
     this.props.onSaveCondition(newCondition, this.props.condition);
-    this.props.onResetError();
-    this.setState({ changed: false });
   };
 
   handleCancelClick = () => {
     this.props.onRemoveCondition(this.props.condition);
   };
 
-  renderPeriodValue() {
-    const { condition, metric } = this.props;
-    const isLeakSelected = !!this.state.period;
-    const isRating = metric.type === 'RATING';
+  handleOpenUpdate = () => this.setState({ modal: true });
 
-    if (isDiffMetric(condition.metric)) {
-      return (
-        <span className="note">{translate('quality_gates.condition.leak.unconditional')}</span>
-      );
-    }
-
-    if (isRating) {
-      return <span className="note">{translate('quality_gates.condition.leak.never')}</span>;
-    }
-
-    return isLeakSelected
-      ? translate('quality_gates.condition.leak.yes')
-      : translate('quality_gates.condition.leak.no');
-  }
-
-  renderPeriod() {
-    const { condition, metric, canEdit } = this.props;
-    const isRating = metric.type === 'RATING';
-    const isLeakSelected = !!this.state.period;
-
-    if (isRating || isDiffMetric(condition.metric) || !canEdit) {
-      return this.renderPeriodValue();
-    }
-
-    return <Checkbox checked={isLeakSelected} onCheck={this.handlePeriodChange} />;
-  }
-
-  renderOperator() {
-    const { condition, canEdit, metric } = this.props;
-
-    if (!canEdit && condition.op) {
-      return metric.type === 'RATING'
-        ? translate('quality_gates.operator', condition.op, 'rating')
-        : translate('quality_gates.operator', condition.op);
-    }
-
-    if (metric.type === 'RATING') {
-      return <span className="note">{translate('quality_gates.operator.GT.rating')}</span>;
-    }
-
-    const operators = ['LT', 'GT', 'EQ', 'NE'];
-    const operatorOptions = operators.map(op => {
-      const label = translate('quality_gates.operator', op);
-      return { label, value: op };
-    });
-
-    return (
-      <Select
-        autoFocus={true}
-        className="input-medium"
-        clearable={false}
-        name="operator"
-        onChange={this.handleOperatorChange}
-        options={operatorOptions}
-        searchable={false}
-        value={this.state.op}
-      />
-    );
-  }
+  handleUpdateClose = () => this.setState({ modal: false });
 
   render() {
-    const { condition, canEdit, metric, organization } = this.props;
+    const { condition, canEdit, metric, organization, qualityGate } = this.props;
     return (
       <tr>
         <td className="text-middle">
@@ -200,64 +80,41 @@ export default class Condition extends React.PureComponent<Props, State> {
           )}
         </td>
 
-        <td className="thin text-middle nowrap">{this.renderPeriod()}</td>
-
-        <td className="thin text-middle nowrap">{this.renderOperator()}</td>
-
         <td className="thin text-middle nowrap">
-          {canEdit ? (
-            <ThresholdInput
-              metric={metric}
-              name="warning"
-              onChange={this.handleWarningChange}
-              value={this.state.warning}
-            />
-          ) : (
-            formatMeasure(condition.warning, metric.type)
-          )}
+          <Period canEdit={false} metric={metric} period={condition.period === 1} />
         </td>
 
         <td className="thin text-middle nowrap">
-          {canEdit ? (
-            <ThresholdInput
-              metric={metric}
-              name="error"
-              onChange={this.handleErrorChange}
-              value={this.state.error}
-            />
-          ) : (
-            formatMeasure(condition.error, metric.type)
-          )}
+          <ConditionOperator canEdit={false} metric={metric} op={condition.op} />
         </td>
 
+        <td className="thin text-middle nowrap">{formatMeasure(condition.warning, metric.type)}</td>
+
+        <td className="thin text-middle nowrap">{formatMeasure(condition.error, metric.type)}</td>
+
         {canEdit && (
           <td className="thin text-middle nowrap">
-            {condition.id ? (
-              <div>
-                <Button
-                  className="update-condition"
-                  disabled={!this.state.changed}
-                  onClick={this.handleUpdateClick}>
-                  {translate('update_verb')}
-                </Button>
-                <DeleteConditionForm
-                  condition={condition}
-                  metric={metric}
-                  onDelete={this.props.onRemoveCondition}
-                  organization={organization}
-                />
-              </div>
-            ) : (
-              <div>
-                <Button className="add-condition" onClick={this.handleSaveClick}>
-                  {translate('add_verb')}
-                </Button>
-                <ResetButtonLink
-                  className="cancel-add-condition spacer-left"
-                  onClick={this.handleCancelClick}>
-                  {translate('cancel')}
-                </ResetButtonLink>
-              </div>
+            <ActionsDropdown className="dropdown-menu-right">
+              <ActionsDropdownItem className="js-condition-update" onClick={this.handleOpenUpdate}>
+                {translate('update_details')}
+              </ActionsDropdownItem>
+              <DeleteConditionForm
+                condition={condition}
+                metric={metric}
+                onDelete={this.props.onRemoveCondition}
+                organization={organization}
+              />
+            </ActionsDropdown>
+            {this.state.modal && (
+              <ConditionModal
+                condition={condition}
+                header={translate('quality_gates.update_condition')}
+                metric={metric}
+                onAddCondition={this.handleUpdateCondition}
+                onClose={this.handleUpdateClose}
+                organization={organization}
+                qualityGate={qualityGate}
+              />
             )}
           </td>
         )}
index f0c27021af4459aa81403dc55fbdcea14e885da0..12a557a41a5ba6199f3e791a631fad3c4a59fe07 100644 (file)
 import * as React from 'react';
 import AddConditionSelect from './AddConditionSelect';
 import ConditionOperator from './ConditionOperator';
+import ThresholdInput from './ThresholdInput';
+import Period from './Period';
 import Modal from '../../../components/controls/Modal';
 import { SubmitButton, ResetButtonLink } from '../../../components/ui/buttons';
-import { translate } from '../../../helpers/l10n';
-import { Metric } from '../../../app/types';
+import { translate, getLocalizedMetricName } from '../../../helpers/l10n';
+import { Metric, QualityGate, Condition } from '../../../app/types';
+import { createCondition, updateCondition } from '../../../api/quality-gates';
+import { isDiffMetric } from '../../../helpers/measures';
+import { parseError } from '../../../helpers/request';
 
 interface Props {
-  metrics: Metric[];
+  condition?: Condition;
+  metric?: Metric;
+  metrics?: Metric[];
   header: string;
+  onAddCondition: (condition: Condition) => void;
   onClose: () => void;
+  organization?: string;
+  qualityGate: QualityGate;
 }
 
 interface State {
-  metric: string;
+  error: string;
+  errorMessage?: string;
+  metric?: Metric;
+  op?: string;
+  period: boolean;
   submitting: boolean;
+  warning: string;
 }
 
 export default class ConditionModal extends React.PureComponent<Props, State> {
-  state = { metric: '', submitting: false };
+  constructor(props: Props) {
+    super(props);
+    this.state = {
+      error: props.condition ? props.condition.error : '',
+      period: props.condition ? props.condition.period === 1 : false,
+      warning: props.condition ? props.condition.warning : '',
+      metric: props.metric ? props.metric : undefined,
+      op: props.condition ? props.condition.op : undefined,
+      submitting: false
+    };
+  }
+
+  handleError = (error: any) => {
+    parseError(error).then(
+      message => {
+        this.setState({ errorMessage: message });
+      },
+      () => {}
+    );
+  };
+
+  getUpdatedCondition = (metric: Metric) => {
+    const data: Condition = {
+      metric: metric.key,
+      op: metric.type === 'RATING' ? 'GT' : this.state.op,
+      warning: this.state.warning,
+      error: this.state.error
+    };
+
+    const { period } = this.state;
+    if (period && metric.type !== 'RATING') {
+      data.period = period ? 1 : 0;
+    }
+
+    if (isDiffMetric(metric.key)) {
+      data.period = 1;
+    }
+    return data;
+  };
+
+  handleConditionResponse = (newCondition: Condition) => {
+    this.setState({ errorMessage: undefined, submitting: false });
+    this.props.onAddCondition(newCondition);
+    this.props.onClose();
+  };
 
   handleFormSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => {
     event.preventDefault();
-    this.setState({ submitting: true });
+
+    if (this.state.metric) {
+      const { condition, qualityGate, organization } = this.props;
+      this.setState({ submitting: true });
+
+      if (condition) {
+        const data: Condition = {
+          id: condition.id,
+          ...this.getUpdatedCondition(this.state.metric)
+        };
+
+        updateCondition({ organization, ...data }).then(
+          this.handleConditionResponse,
+          this.handleError
+        );
+      } else {
+        const data = this.getUpdatedCondition(this.state.metric);
+
+        createCondition({ gateId: qualityGate.id, organization, ...data }).then(
+          this.handleConditionResponse,
+          this.handleError
+        );
+      }
+    }
   };
 
-  handleChooseType = (metric: string) => {
+  handleChooseType = (metric: Metric) => {
     this.setState({ metric });
   };
 
+  handlePeriodChange = (period: boolean) => this.setState({ period });
+
+  handleOperatorChange = (op: string) => this.setState({ op });
+
+  handleWarningChange = (warning: string) => this.setState({ warning });
+
+  handleErrorChange = (error: string) => this.setState({ error });
+
   render() {
     const { header, metrics, onClose } = this.props;
-    const { submitting } = this.state;
+    const { period, op, warning, error, metric, submitting } = this.state;
     return (
       <Modal contentLabel={header} onRequestClose={onClose}>
         <form onSubmit={this.handleFormSubmit}>
@@ -59,23 +149,60 @@ export default class ConditionModal extends React.PureComponent<Props, State> {
           </div>
 
           <div className="modal-body">
+            {this.state.errorMessage && (
+              <div className="alert alert-danger">{this.state.errorMessage}</div>
+            )}
             <div className="modal-field">
               <label htmlFor="create-user-login">
                 {translate('quality_gates.conditions.metric')}
               </label>
-              <AddConditionSelect metrics={metrics} onAddCondition={this.handleChooseType} />
-            </div>
-            <div className="modal-field">
-              <label htmlFor="create-user-login">
-                {translate('quality_gates.conditions.metric')}
-              </label>
-              <ConditionOperator
-                canEdit={true}
-                condition={}
-                metric={this.state.metric}
-                onOperatorChange={() => {}}
-              />
+              {metrics && (
+                <AddConditionSelect metrics={metrics} onAddCondition={this.handleChooseType} />
+              )}
+              {this.props.metric && (
+                <span className="note">{getLocalizedMetricName(this.props.metric)}</span>
+              )}
             </div>
+            {metric && (
+              <>
+                <div className="modal-field">
+                  <label>{translate('quality_gates.conditions.leak')}</label>
+                  <Period
+                    canEdit={true}
+                    metric={metric}
+                    onPeriodChange={this.handlePeriodChange}
+                    period={period}
+                  />
+                </div>
+                <div className="modal-field">
+                  <label>{translate('quality_gates.conditions.operator')}</label>
+                  <ConditionOperator
+                    canEdit={true}
+                    metric={metric}
+                    onOperatorChange={this.handleOperatorChange}
+                    op={op}
+                  />
+                </div>
+                <div className="modal-field">
+                  <label>{translate('quality_gates.conditions.warning')}</label>
+                  <ThresholdInput
+                    metric={metric}
+                    name="warning"
+                    onChange={this.handleWarningChange}
+                    value={warning}
+                  />
+                </div>
+                <div className="modal-field">
+                  <label>{translate('quality_gates.conditions.error')}</label>
+                  <ThresholdInput
+                    metric={metric}
+                    name="error"
+                    onChange={this.handleErrorChange}
+                    value={error}
+                  />
+                </div>
+              </>
+            )}
           </div>
 
           <div className="modal-foot">
index 55c8d3c54d1b8bde44f7bf9b15a1e959b8fba72c..f75a1fe388b95f47660f3b98a1984e083b9a7914 100644 (file)
  */
 import * as React from 'react';
 import Select from '../../../components/controls/Select';
-import { Condition as ICondition, Metric } from '../../../app/types';
+import { Metric } from '../../../app/types';
 import { translate } from '../../../helpers/l10n';
 
 interface Props {
-  condition: ICondition;
+  op?: string;
   canEdit: boolean;
   metric: Metric;
-  onOperatorChange: ({ value }: any) => void;
+  onOperatorChange?: (op: string) => void;
 }
 
-export default function ConditionOperator({ condition, canEdit, metric, onOperatorChange }: Props) {
-  if (!canEdit && condition.op) {
-    return metric.type === 'RATING' ? (
-      <span className="note">{translate('quality_gates.operator', condition.op, 'rating')}</span>
-    ) : (
-      <span className="note">{translate('quality_gates.operator', condition.op)}</span>
-    );
-  }
+export default class ConditionOperator extends React.PureComponent<Props> {
+  handleChange = ({ value }: { label: string; value: string }) => {
+    if (this.props.onOperatorChange) {
+      this.props.onOperatorChange(value);
+    }
+  };
 
-  if (metric.type === 'RATING') {
-    return <span className="note">{translate('quality_gates.operator.GT.rating')}</span>;
-  }
+  render() {
+    const { canEdit, metric, op } = this.props;
+    if (!canEdit && op) {
+      return metric.type === 'RATING' ? (
+        <span className="note">{translate('quality_gates.operator', op, 'rating')}</span>
+      ) : (
+        <span className="note">{translate('quality_gates.operator', op)}</span>
+      );
+    }
 
-  const operators = ['LT', 'GT', 'EQ', 'NE'];
-  const operatorOptions = operators.map(op => {
-    const label = translate('quality_gates.operator', op);
-    return { label, value: op };
-  });
+    if (metric.type === 'RATING') {
+      return <span className="note">{translate('quality_gates.operator.GT.rating')}</span>;
+    }
 
-  return (
-    <Select
-      autofocus={true}
-      className="input-medium"
-      clearable={false}
-      name="operator"
-      onChange={onOperatorChange}
-      options={operatorOptions}
-      searchable={false}
-      value={condition.op}
-    />
-  );
+    const operators = ['LT', 'GT', 'EQ', 'NE'];
+    const operatorOptions = operators.map(op => {
+      const label = translate('quality_gates.operator', op);
+      return { label, value: op };
+    });
+
+    return (
+      <Select
+        autofocus={true}
+        className="input-medium"
+        clearable={false}
+        name="operator"
+        onChange={this.handleChange}
+        options={operatorOptions}
+        searchable={false}
+        value={op}
+      />
+    );
+  }
 }
index 07a9f8cc60080555f76f6e90a49f4600b524822a..1b15c245d387846e4a9370b712a9ec2947953198 100644 (file)
  */
 import * as React from 'react';
 import { differenceWith, map, sortBy, uniqBy } from 'lodash';
-import AddConditionSelect from './AddConditionSelect';
 import Condition from './Condition';
 import AddConditionButton from './AddConditionButton';
 import DocTooltip from '../../../components/docs/DocTooltip';
 import { translate, getLocalizedMetricName } from '../../../helpers/l10n';
 import { Condition as ICondition, Metric, QualityGate } from '../../../app/types';
-import { parseError } from '../../../helpers/request';
 
 interface Props {
   canEdit: boolean;
   conditions: ICondition[];
   metrics: { [key: string]: Metric };
-  onAddCondition: (metric: string) => void;
+  onAddCondition: (condition: ICondition) => void;
   onSaveCondition: (newCondition: ICondition, oldCondition: ICondition) => void;
   onRemoveCondition: (Condition: ICondition) => void;
   organization?: string;
@@ -55,19 +53,6 @@ export default class Conditions extends React.PureComponent<Props, State> {
     return condition.id ? condition.id : `new-${index}`;
   };
 
-  handleError = (error: any) => {
-    parseError(error).then(
-      message => {
-        this.setState({ error: message });
-      },
-      () => {}
-    );
-  };
-
-  handleResetError = () => {
-    this.setState({ error: undefined });
-  };
-
   render() {
     const { qualityGate, conditions, metrics, canEdit, organization } = this.props;
 
@@ -104,9 +89,16 @@ export default class Conditions extends React.PureComponent<Props, State> {
 
     return (
       <div className="quality-gate-section" id="quality-gate-conditions">
-        <div className="pull-right">
-          <AddConditionButton metrics={availableMetrics} />
-        </div>
+        {canEdit && (
+          <div className="pull-right">
+            <AddConditionButton
+              metrics={availableMetrics}
+              onAddCondition={this.props.onAddCondition}
+              organization={organization}
+              qualityGate={qualityGate}
+            />
+          </div>
+        )}
         <header className="display-flex-center spacer-bottom">
           <h3>{translate('quality_gates.conditions')}</h3>
           <DocTooltip className="spacer-left" doc="quality-gates/quality-gate-conditions" />
@@ -146,10 +138,7 @@ export default class Conditions extends React.PureComponent<Props, State> {
                   condition={condition}
                   key={this.getConditionKey(condition, index)}
                   metric={metrics[condition.metric]}
-                  onAddCondition={this.props.onAddCondition}
-                  onError={this.handleError}
                   onRemoveCondition={this.props.onRemoveCondition}
-                  onResetError={this.handleResetError}
                   onSaveCondition={this.props.onSaveCondition}
                   organization={organization}
                   qualityGate={qualityGate}
@@ -160,13 +149,6 @@ export default class Conditions extends React.PureComponent<Props, State> {
         ) : (
           <div className="big-spacer-top">{translate('quality_gates.no_conditions')}</div>
         )}
-
-        {canEdit && (
-          <AddConditionSelect
-            metrics={availableMetrics}
-            onAddCondition={this.props.onAddCondition}
-          />
-        )}
       </div>
     );
   }
index 3b52e3a8f5fe190580fee327a65824db6dedd325..101d06cfc893d8252cea30b0beb1a4115c185c18 100644 (file)
@@ -21,7 +21,7 @@ import * as React from 'react';
 import { deleteCondition } from '../../../api/quality-gates';
 import { Metric, Condition } from '../../../app/types';
 import ConfirmButton from '../../../components/controls/ConfirmButton';
-import { Button } from '../../../components/ui/buttons';
+import { ActionsDropdownItem } from '../../../components/controls/ActionsDropdown';
 import { getLocalizedMetricName, translate, translateWithParameters } from '../../../helpers/l10n';
 
 interface Props {
@@ -54,9 +54,12 @@ export default class DeleteConditionForm extends React.PureComponent<Props> {
         modalHeader={translate('quality_gates.delete_condition')}
         onConfirm={this.onDelete}>
         {({ onClick }) => (
-          <Button className="delete-condition little-spacer-left button-red" onClick={onClick}>
+          <ActionsDropdownItem
+            className="js-condition-deactivate"
+            destructive={true}
+            onClick={onClick}>
             {translate('delete')}
-          </Button>
+          </ActionsDropdownItem>
         )}
       </ConfirmButton>
     );
index 4c01648d41cde1f7fa6fb79bd87bdf1bfaf030af..483d4bfcf0b6d2c03d5e5873f56ca08537644b18 100644 (file)
@@ -93,12 +93,12 @@ export class DetailsApp extends React.PureComponent<Props, State> {
     );
   };
 
-  handleAddCondition = (metric: string) => {
+  handleAddCondition = (condition: Condition) => {
     this.setState(({ qualityGate }) => {
       if (!qualityGate) {
         return undefined;
       }
-      return { qualityGate: addCondition(qualityGate, metric) };
+      return { qualityGate: addCondition(qualityGate, condition) };
     });
   };
 
index 7463d772635acf1403496828ae5e402a1c3974a7..6c6a6b1938b9b429ca06232280d80679ca8940a6 100644 (file)
@@ -28,7 +28,7 @@ interface Props {
   isDefault?: boolean;
   metrics: { [key: string]: Metric };
   organization?: string;
-  onAddCondition: (metric: string) => void;
+  onAddCondition: (condition: ICondition) => void;
   onRemoveCondition: (Condition: ICondition) => void;
   onSaveCondition: (newCondition: ICondition, oldCondition: ICondition) => void;
   qualityGate: QualityGate;
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/Period.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/Period.tsx
new file mode 100644 (file)
index 0000000..fbb996c
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * 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 Checkbox from '../../../components/controls/Checkbox';
+import { Metric } from '../../../app/types';
+import { isDiffMetric } from '../../../helpers/measures';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+  canEdit: boolean;
+  metric: Metric;
+  onPeriodChange?: (checked: boolean) => void;
+  period: boolean;
+}
+
+export default class Period extends React.PureComponent<Props> {
+  renderPeriodValue() {
+    const { metric, period } = this.props;
+    const isRating = metric.type === 'RATING';
+
+    if (isDiffMetric(metric.key)) {
+      return (
+        <span className="note">{translate('quality_gates.condition.leak.unconditional')}</span>
+      );
+    }
+
+    if (isRating) {
+      return <span className="note">{translate('quality_gates.condition.leak.never')}</span>;
+    }
+
+    return period
+      ? translate('quality_gates.condition.leak.yes')
+      : translate('quality_gates.condition.leak.no');
+  }
+
+  render() {
+    const { canEdit, metric, onPeriodChange, period } = this.props;
+    const isRating = metric && metric.type === 'RATING';
+
+    if (isRating || isDiffMetric(metric.key) || !canEdit) {
+      return this.renderPeriodValue();
+    }
+
+    return <Checkbox checked={period} onCheck={onPeriodChange || (() => {})} />;
+  }
+}
index 845d6da55947b7088b7a51dcc7122fd892ddefd6..6279a475f120d990b7de6c22ad29fead1e05c0c7 100644 (file)
@@ -25,13 +25,7 @@ export function checkIfDefault(qualityGate: QualityGate, list: QualityGate[]): b
   return (finding && finding.isDefault) || false;
 }
 
-export function addCondition(qualityGate: QualityGate, metric: string): QualityGate {
-  const condition: Condition = {
-    metric,
-    op: 'LT',
-    warning: '',
-    error: ''
-  };
+export function addCondition(qualityGate: QualityGate, condition: Condition): QualityGate {
   const oldConditions = qualityGate.conditions || [];
   const conditions = [...oldConditions, condition];
   return { ...qualityGate, conditions };
index 2f5a9e34af826427deffd0039a2cc5ad6c7f6da4..64a545329d5b8c122908e387f799abb2d475fed5 100644 (file)
@@ -1153,6 +1153,7 @@ quality_gates.copy=Copy Quality Gate
 quality_gates.conditions=Conditions
 quality_gates.projects=Projects
 quality_gates.add_condition=Add Condition
+quality_gates.update_condition=Update Condition
 quality_gates.condition.leak.never=Never
 quality_gates.no_conditions=No Conditions
 quality_gates.introduction=Only project measures are checked against thresholds. Sub-projects, directories and files are ignored.