aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPascal Mugnier <pascal.mugnier@sonarsource.com>2018-05-09 12:03:03 +0200
committerSonarTech <sonartech@sonarsource.com>2018-05-24 20:20:46 +0200
commit877b9bedf61c2f8a7fd4885f9500d57b0da72be7 (patch)
tree1061fdff900b5f922730f6556dacf3b2595c56a1
parent562b9dacb912c64a4c048c03bdc9a7e2505a4bbe (diff)
downloadsonarqube-877b9bedf61c2f8a7fd4885f9500d57b0da72be7.tar.gz
sonarqube-877b9bedf61c2f8a7fd4885f9500d57b0da72be7.zip
Fix SONAR-10640
-rw-r--r--server/sonar-web/src/main/js/app/styles/components/modals.css4
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/AddConditionButton.tsx8
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/AddConditionSelect.tsx24
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/Condition.tsx219
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/ConditionModal.tsx167
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/ConditionOperator.tsx71
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/Conditions.tsx40
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/DeleteConditionForm.tsx9
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/DetailsApp.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/DetailsContent.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/Period.tsx63
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/utils.ts8
-rw-r--r--sonar-core/src/main/resources/org/sonar/l10n/core.properties1
13 files changed, 337 insertions, 283 deletions
diff --git a/server/sonar-web/src/main/js/app/styles/components/modals.css b/server/sonar-web/src/main/js/app/styles/components/modals.css
index 21d73c39a99..f8416e932de 100644
--- a/server/sonar-web/src/main/js/app/styles/components/modals.css
+++ b/server/sonar-web/src/main/js/app/styles/components/modals.css
@@ -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;
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/AddConditionButton.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/AddConditionButton.tsx
index 48943131904..04e4133f191 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/AddConditionButton.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/AddConditionButton.tsx
@@ -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}
/>
)}
</>
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/AddConditionSelect.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/AddConditionSelect.tsx
index 8539232bb3f..9b9f416c3d3 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/AddConditionSelect.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/AddConditionSelect.tsx
@@ -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}
/>
);
}
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/Condition.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/Condition.tsx
index 11a644c4692..7525fd4a9c3 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/Condition.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/Condition.tsx
@@ -18,32 +18,28 @@
* 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>
)}
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionModal.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionModal.tsx
index f0c27021af4..12a557a41a5 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionModal.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionModal.tsx
@@ -20,37 +20,127 @@
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">
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionOperator.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionOperator.tsx
index 55c8d3c54d1..f75a1fe388b 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionOperator.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionOperator.tsx
@@ -19,45 +19,54 @@
*/
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}
+ />
+ );
+ }
}
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/Conditions.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/Conditions.tsx
index 07a9f8cc600..1b15c245d38 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/Conditions.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/Conditions.tsx
@@ -19,19 +19,17 @@
*/
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>
);
}
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/DeleteConditionForm.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/DeleteConditionForm.tsx
index 3b52e3a8f5f..101d06cfc89 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/DeleteConditionForm.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/DeleteConditionForm.tsx
@@ -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>
);
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsApp.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsApp.tsx
index 4c01648d41c..483d4bfcf0b 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsApp.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsApp.tsx
@@ -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) };
});
};
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsContent.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsContent.tsx
index 7463d772635..6c6a6b1938b 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsContent.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsContent.tsx
@@ -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
index 00000000000..fbb996c24da
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/Period.tsx
@@ -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 || (() => {})} />;
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/utils.ts b/server/sonar-web/src/main/js/apps/quality-gates/utils.ts
index 845d6da5594..6279a475f12 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/utils.ts
+++ b/server/sonar-web/src/main/js/apps/quality-gates/utils.ts
@@ -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 };
diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
index 2f5a9e34af8..64a545329d5 100644
--- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties
+++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
@@ -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.