diff options
author | Pascal Mugnier <pascal.mugnier@sonarsource.com> | 2018-05-09 12:03:03 +0200 |
---|---|---|
committer | SonarTech <sonartech@sonarsource.com> | 2018-05-24 20:20:46 +0200 |
commit | 877b9bedf61c2f8a7fd4885f9500d57b0da72be7 (patch) | |
tree | 1061fdff900b5f922730f6556dacf3b2595c56a1 | |
parent | 562b9dacb912c64a4c048c03bdc9a7e2505a4bbe (diff) | |
download | sonarqube-877b9bedf61c2f8a7fd4885f9500d57b0da72be7.tar.gz sonarqube-877b9bedf61c2f8a7fd4885f9500d57b0da72be7.zip |
Fix SONAR-10640
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. |