aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main/js/apps/quality-gates/components
diff options
context:
space:
mode:
Diffstat (limited to 'server/sonar-web/src/main/js/apps/quality-gates/components')
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/Condition.tsx38
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/ConditionModal.tsx68
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/Conditions.tsx135
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/ListHeader.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/MetricSelect.tsx45
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/Condition-test.tsx59
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/ConditionModal-test.tsx43
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/Conditions-test.tsx78
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/ListHeader-test.tsx33
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/MetricSelect-test.tsx32
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/Condition-test.tsx.snap189
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/ConditionModal-test.tsx.snap305
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/Conditions-test.tsx.snap493
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/ListHeader-test.tsx.snap32
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/MetricSelect-test.tsx.snap5
15 files changed, 1420 insertions, 137 deletions
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 84a6e8b4e94..fc914abf50d 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,9 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import ActionsDropdown, {
- ActionsDropdownItem
-} from 'sonar-ui-common/components/controls/ActionsDropdown';
+import { DeleteButton, EditButton } from 'sonar-ui-common/components/controls/buttons';
import ConfirmModal from 'sonar-ui-common/components/controls/ConfirmModal';
import {
getLocalizedMetricName,
@@ -29,6 +27,7 @@ import {
} from 'sonar-ui-common/helpers/l10n';
import { formatMeasure } from 'sonar-ui-common/helpers/measures';
import { deleteCondition } from '../../../api/quality-gates';
+import { getLocalizedMetricNameNoDiffMetric } from '../utils';
import ConditionModal from './ConditionModal';
interface Props {
@@ -99,29 +98,30 @@ export default class Condition extends React.PureComponent<Props, State> {
return (
<tr>
<td className="text-middle">
- {getLocalizedMetricName(metric)}
+ {getLocalizedMetricNameNoDiffMetric(metric)}
{metric.hidden && (
<span className="text-danger little-spacer-left">{translate('deprecated')}</span>
)}
</td>
- <td className="thin text-middle nowrap">{this.renderOperator()}</td>
+ <td className="text-middle nowrap">{this.renderOperator()}</td>
- <td className="thin text-middle nowrap">{formatMeasure(condition.error, metric.type)}</td>
+ <td className="text-middle nowrap">{formatMeasure(condition.error, metric.type)}</td>
{canEdit && (
- <td className="thin text-middle nowrap">
- <ActionsDropdown className="dropdown-menu-right">
- <ActionsDropdownItem className="js-condition-update" onClick={this.handleOpenUpdate}>
- {translate('update_details')}
- </ActionsDropdownItem>
- <ActionsDropdownItem
- destructive={true}
- id="condition-delete"
- onClick={this.handleDeleteClick}>
- {translate('delete')}
- </ActionsDropdownItem>
- </ActionsDropdown>
+ <>
+ <td className="text-center thin">
+ <EditButton
+ data-test="quality-gates__condition-update"
+ onClick={this.handleOpenUpdate}
+ />
+ </td>
+ <td className="text-center thin">
+ <DeleteButton
+ data-test="quality-gates__condition-delete"
+ onClick={this.handleDeleteClick}
+ />
+ </td>
{this.state.modal && (
<ConditionModal
condition={condition}
@@ -147,7 +147,7 @@ export default class Condition extends React.PureComponent<Props, State> {
)}
</ConfirmModal>
)}
- </td>
+ </>
)}
</tr>
);
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 ba87f3434a8..3ee164f96a7 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
@@ -19,8 +19,10 @@
*/
import * as React from 'react';
import ConfirmModal from 'sonar-ui-common/components/controls/ConfirmModal';
+import Radio from 'sonar-ui-common/components/controls/Radio';
import { Alert } from 'sonar-ui-common/components/ui/Alert';
import { getLocalizedMetricName, translate } from 'sonar-ui-common/helpers/l10n';
+import { isDiffMetric } from 'sonar-ui-common/helpers/measures';
import { createCondition, updateCondition } from '../../../api/quality-gates';
import { getPossibleOperators } from '../utils';
import ConditionOperator from './ConditionOperator';
@@ -43,28 +45,20 @@ interface State {
errorMessage?: string;
metric?: T.Metric;
op?: string;
+ scope: 'new' | 'overall';
}
export default class ConditionModal extends React.PureComponent<Props, State> {
- mounted = false;
-
constructor(props: Props) {
super(props);
this.state = {
error: props.condition ? props.condition.error : '',
+ scope: 'new',
metric: props.metric ? props.metric : undefined,
op: props.condition ? props.condition.op : undefined
};
}
- componentDidMount() {
- this.mounted = true;
- }
-
- componentWillUnmount() {
- this.mounted = false;
- }
-
getSinglePossibleOperator(metric: T.Metric) {
const operators = getPossibleOperators(metric);
return Array.isArray(operators) ? undefined : operators;
@@ -86,6 +80,21 @@ export default class ConditionModal extends React.PureComponent<Props, State> {
return Promise.reject();
};
+ handleScopeChange = (scope: 'new' | 'overall') => {
+ this.setState(({ metric }) => {
+ const { metrics } = this.props;
+ let correspondingMetric;
+
+ if (metric && metrics) {
+ const correspondingMetricKey =
+ scope === 'new' ? `new_${metric.key}` : metric.key.replace(/^new_/, '');
+ correspondingMetric = metrics.find(m => m.key === correspondingMetricKey);
+ }
+
+ return { scope, metric: correspondingMetric };
+ });
+ };
+
handleMetricChange = (metric: T.Metric) => {
this.setState({ metric, op: undefined, error: '' });
};
@@ -100,7 +109,7 @@ export default class ConditionModal extends React.PureComponent<Props, State> {
render() {
const { header, metrics, onClose } = this.props;
- const { op, error, metric } = this.state;
+ const { op, error, scope, metric } = this.state;
return (
<ConfirmModal
confirmButtonText={header}
@@ -110,13 +119,44 @@ export default class ConditionModal extends React.PureComponent<Props, State> {
onConfirm={this.handleFormSubmit}
size="small">
{this.state.errorMessage && <Alert variant="error">{this.state.errorMessage}</Alert>}
+
+ {this.props.metric === undefined && (
+ <div className="modal-field display-flex-center">
+ <Radio checked={scope === 'new'} onCheck={this.handleScopeChange} value="new">
+ <span data-test="quality-gates__condition-scope-new">
+ {translate('quality_gates.conditions.new_code')}
+ </span>
+ </Radio>
+ <Radio
+ checked={scope === 'overall'}
+ className="big-spacer-left"
+ onCheck={this.handleScopeChange}
+ value="overall">
+ <span data-test="quality-gates__condition-scope-overall">
+ {translate('quality_gates.conditions.overall_code')}
+ </span>
+ </Radio>
+ </div>
+ )}
+
<div className="modal-field">
- <label htmlFor="condition-metric">{translate('quality_gates.conditions.metric')}</label>
- {metrics && <MetricSelect metrics={metrics} onMetricChange={this.handleMetricChange} />}
+ <label htmlFor="condition-metric">
+ {translate('quality_gates.conditions.fails_when')}
+ </label>
+ {metrics && (
+ <MetricSelect
+ metric={metric}
+ metrics={metrics.filter(metric =>
+ scope === 'new' ? isDiffMetric(metric.key) : !isDiffMetric(metric.key)
+ )}
+ onMetricChange={this.handleMetricChange}
+ />
+ )}
{this.props.metric && (
<span className="note">{getLocalizedMetricName(this.props.metric)}</span>
)}
</div>
+
{metric && (
<>
<div className="modal-field display-inline-block">
@@ -131,7 +171,7 @@ export default class ConditionModal extends React.PureComponent<Props, State> {
</div>
<div className="modal-field display-inline-block spacer-left">
<label htmlFor="condition-threshold">
- {translate('quality_gates.conditions.error')}
+ {translate('quality_gates.conditions.value')}
</label>
<ThresholdInput
metric={metric}
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 5c04b8148a6..bb12fa3b403 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
@@ -23,17 +23,20 @@ import { Button } from 'sonar-ui-common/components/controls/buttons';
import ModalButton from 'sonar-ui-common/components/controls/ModalButton';
import { Alert } from 'sonar-ui-common/components/ui/Alert';
import { getLocalizedMetricName, translate } from 'sonar-ui-common/helpers/l10n';
+import { isDiffMetric } from 'sonar-ui-common/helpers/measures';
import DocTooltip from '../../../components/docs/DocTooltip';
+import { withAppState } from '../../../components/hoc/withAppState';
import Condition from './Condition';
import ConditionModal from './ConditionModal';
interface Props {
+ appState: Pick<T.AppState, 'branchesEnabled'>;
canEdit: boolean;
conditions: T.Condition[];
metrics: T.Dict<T.Metric>;
onAddCondition: (condition: T.Condition) => void;
- onSaveCondition: (newCondition: T.Condition, oldCondition: T.Condition) => void;
onRemoveCondition: (Condition: T.Condition) => void;
+ onSaveCondition: (newCondition: T.Condition, oldCondition: T.Condition) => void;
organization?: string;
qualityGate: T.QualityGate;
}
@@ -41,21 +44,67 @@ interface Props {
const FORBIDDEN_METRIC_TYPES = ['DATA', 'DISTRIB', 'STRING', 'BOOL'];
const FORBIDDEN_METRICS = ['alert_status', 'releasability_rating', 'security_review_rating'];
-export default class Conditions extends React.PureComponent<Props> {
- getConditionKey = (condition: T.Condition, index: number) => {
- return condition.id ? condition.id : `new-${index}`;
+export class Conditions extends React.PureComponent<Props> {
+ renderConditionsTable = (conditions: T.Condition[], scope: 'new' | 'overall') => {
+ const {
+ qualityGate,
+ metrics,
+ canEdit,
+ onRemoveCondition,
+ onSaveCondition,
+ organization
+ } = this.props;
+ return (
+ <table className="data zebra" data-test={`quality-gates__conditions-${scope}`}>
+ <thead>
+ <tr>
+ <th className="nowrap" style={{ width: 300 }}>
+ {translate('quality_gates.conditions.metric')}
+ </th>
+ <th className="nowrap">{translate('quality_gates.conditions.operator')}</th>
+ <th className="nowrap">{translate('quality_gates.conditions.value')}</th>
+ {canEdit && (
+ <>
+ <th className="thin">{translate('edit')}</th>
+ <th className="thin">{translate('delete')}</th>
+ </>
+ )}
+ </tr>
+ </thead>
+ <tbody>
+ {conditions.map(condition => (
+ <Condition
+ canEdit={canEdit}
+ condition={condition}
+ key={condition.id}
+ metric={metrics[condition.metric]}
+ onRemoveCondition={onRemoveCondition}
+ onSaveCondition={onSaveCondition}
+ organization={organization}
+ qualityGate={qualityGate}
+ />
+ ))}
+ </tbody>
+ </table>
+ );
};
render() {
- const { qualityGate, conditions, metrics, canEdit, organization } = this.props;
+ const { appState, conditions, metrics, canEdit } = this.props;
const existingConditions = conditions.filter(condition => metrics[condition.metric]);
-
const sortedConditions = sortBy(
existingConditions,
condition => metrics[condition.metric] && metrics[condition.metric].name
);
+ const sortedConditionsOnOverallMetrics = sortedConditions.filter(
+ condition => !isDiffMetric(condition.metric)
+ );
+ const sortedConditionsOnNewMetrics = sortedConditions.filter(condition =>
+ isDiffMetric(condition.metric)
+ );
+
const duplicates: T.Condition[] = [];
const savedConditions = existingConditions.filter(condition => condition.id != null);
savedConditions.forEach(condition => {
@@ -82,7 +131,7 @@ export default class Conditions extends React.PureComponent<Props> {
);
return (
- <div className="quality-gate-section" id="quality-gate-conditions">
+ <div className="quality-gate-section">
{canEdit && (
<div className="pull-right">
<ModalButton
@@ -97,11 +146,14 @@ export default class Conditions extends React.PureComponent<Props> {
/>
)}>
{({ onClick }) => (
- <Button onClick={onClick}>{translate('quality_gates.add_condition')}</Button>
+ <Button data-test="quality-gates__add-condition" onClick={onClick}>
+ {translate('quality_gates.add_condition')}
+ </Button>
)}
</ModalButton>
</div>
)}
+
<header className="display-flex-center spacer-bottom">
<h3>{translate('quality_gates.conditions')}</h3>
<DocTooltip
@@ -110,8 +162,6 @@ export default class Conditions extends React.PureComponent<Props> {
/>
</header>
- <div className="big-spacer-bottom">{translate('quality_gates.introduction')}</div>
-
{uniqDuplicates.length > 0 && (
<Alert variant="warning">
<p>{translate('quality_gates.duplicated_conditions')}</p>
@@ -123,43 +173,40 @@ export default class Conditions extends React.PureComponent<Props> {
</Alert>
)}
- {sortedConditions.length ? (
- <table className="data zebra zebra-hover" id="quality-gate-conditions">
- <thead>
- <tr>
- <th className="nowrap">
- <div className="display-inline-flex-center">
- {translate('quality_gates.conditions.metric')}
- <DocTooltip
- className="spacer-left"
- doc={import(/* webpackMode: "eager" */ 'Docs/tooltips/quality-gates/metric.md')}
- />
- </div>
- </th>
- <th className="thin nowrap">{translate('quality_gates.conditions.operator')}</th>
- <th className="thin nowrap">{translate('quality_gates.conditions.error')}</th>
- {canEdit && <th />}
- </tr>
- </thead>
- <tbody>
- {sortedConditions.map((condition, index) => (
- <Condition
- canEdit={canEdit}
- condition={condition}
- key={this.getConditionKey(condition, index)}
- metric={metrics[condition.metric]}
- onRemoveCondition={this.props.onRemoveCondition}
- onSaveCondition={this.props.onSaveCondition}
- organization={organization}
- qualityGate={qualityGate}
- />
- ))}
- </tbody>
- </table>
- ) : (
+ {sortedConditionsOnNewMetrics.length > 0 && (
+ <div className="big-spacer-top">
+ <h4>{translate('quality_gates.conditions.new_code.long')}</h4>
+
+ {appState.branchesEnabled && (
+ <p className="spacer-top spacer-bottom">
+ {translate('quality_gates.conditions.new_code.description')}
+ </p>
+ )}
+
+ {this.renderConditionsTable(sortedConditionsOnNewMetrics, 'new')}
+ </div>
+ )}
+
+ {sortedConditionsOnOverallMetrics.length > 0 && (
+ <div className="big-spacer-top">
+ <h4>{translate('quality_gates.conditions.overall_code.long')}</h4>
+
+ {appState.branchesEnabled && (
+ <p className="spacer-top spacer-bottom">
+ {translate('quality_gates.conditions.overall_code.description')}
+ </p>
+ )}
+
+ {this.renderConditionsTable(sortedConditionsOnOverallMetrics, 'overall')}
+ </div>
+ )}
+
+ {existingConditions.length === 0 && (
<div className="big-spacer-top">{translate('quality_gates.no_conditions')}</div>
)}
</div>
);
}
}
+
+export default withAppState(Conditions);
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/ListHeader.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/ListHeader.tsx
index 032692b0b2f..1f2bc2bba7e 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/ListHeader.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/ListHeader.tsx
@@ -44,7 +44,7 @@ export default function ListHeader({ canCreate, refreshQualityGates, organizatio
/>
)}>
{({ onClick }) => (
- <Button id="quality-gate-add" onClick={onClick}>
+ <Button data-test="quality-gates__add" onClick={onClick}>
{translate('create')}
</Button>
)}
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/MetricSelect.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/MetricSelect.tsx
index 2a6f9ad4a00..56300e6d6ac 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/MetricSelect.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/MetricSelect.tsx
@@ -20,56 +20,51 @@
import { sortBy } from 'lodash';
import * as React from 'react';
import Select from 'sonar-ui-common/components/controls/Select';
-import {
- getLocalizedMetricDomain,
- getLocalizedMetricName,
- translate
-} from 'sonar-ui-common/helpers/l10n';
+import { getLocalizedMetricDomain, translate } from 'sonar-ui-common/helpers/l10n';
+import { getLocalizedMetricNameNoDiffMetric } from '../utils';
interface Props {
+ metric?: T.Metric;
metrics: T.Metric[];
onMetricChange: (metric: T.Metric) => void;
}
-interface State {
- value: number;
-}
-
interface Option {
disabled?: boolean;
- domain?: string;
label: string;
- value: number;
+ value: string;
}
-export default class MetricSelect extends React.PureComponent<Props, State> {
- state = { value: -1 };
-
+export default class MetricSelect extends React.PureComponent<Props> {
handleChange = (option: Option | null) => {
- const value = option ? option.value : -1;
- this.setState({ value });
- this.props.onMetricChange(this.props.metrics[value]);
+ if (option) {
+ const { metrics } = this.props;
+ const selectedMetric = metrics.find(metric => metric.key === option.value);
+ if (selectedMetric) {
+ this.props.onMetricChange(selectedMetric);
+ }
+ }
};
render() {
- const { metrics } = this.props;
+ const { metric, metrics } = this.props;
- const options: Option[] = sortBy(
- metrics.map((metric, index) => ({
- value: index,
- label: getLocalizedMetricName(metric),
+ const options: Array<Option & { domain?: string }> = sortBy(
+ metrics.map(metric => ({
+ value: metric.key,
+ label: getLocalizedMetricNameNoDiffMetric(metric),
domain: metric.domain
})),
'domain'
);
- // use "disabled" property to emulate optgroups
+ // Use "disabled" property to emulate optgroups.
const optionsWithDomains: Option[] = [];
options.forEach((option, index, options) => {
const previous = index > 0 ? options[index - 1] : null;
if (option.domain && (!previous || previous.domain !== option.domain)) {
optionsWithDomains.push({
- value: 0,
+ value: '<domain>',
label: getLocalizedMetricDomain(option.domain),
disabled: true
});
@@ -84,7 +79,7 @@ export default class MetricSelect extends React.PureComponent<Props, State> {
onChange={this.handleChange}
options={optionsWithDomains}
placeholder={translate('search.search_for_metrics')}
- value={this.state.value}
+ value={metric && metric.key}
/>
);
}
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/Condition-test.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/Condition-test.tsx
new file mode 100644
index 00000000000..336c33538a1
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/Condition-test.tsx
@@ -0,0 +1,59 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 { shallow } from 'enzyme';
+import * as React from 'react';
+import { mockCondition, mockMetric, mockQualityGate } from '../../../../helpers/testMocks';
+import Condition from '../Condition';
+
+it('should render correctly', () => {
+ const wrapper = shallowRender();
+ expect(wrapper).toMatchSnapshot();
+});
+
+it('should render correctly with edit rights', () => {
+ const wrapper = shallowRender({ canEdit: true });
+ expect(wrapper).toMatchSnapshot();
+});
+
+it('should render the update modal correctly', () => {
+ const wrapper = shallowRender({ canEdit: true });
+ wrapper.instance().handleOpenUpdate();
+ expect(wrapper).toMatchSnapshot();
+});
+
+it('should render the delete modal correctly', () => {
+ const wrapper = shallowRender({ canEdit: true });
+ wrapper.instance().handleDeleteClick();
+ expect(wrapper).toMatchSnapshot();
+});
+
+function shallowRender(props: Partial<Condition['props']> = {}) {
+ return shallow<Condition>(
+ <Condition
+ canEdit={false}
+ condition={mockCondition()}
+ metric={mockMetric()}
+ onRemoveCondition={jest.fn()}
+ onSaveCondition={jest.fn()}
+ qualityGate={mockQualityGate()}
+ {...props}
+ />
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/ConditionModal-test.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/ConditionModal-test.tsx
index 7b243bf4b42..8b6d1d2fe15 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/ConditionModal-test.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/ConditionModal-test.tsx
@@ -19,21 +19,58 @@
*/
import { shallow } from 'enzyme';
import * as React from 'react';
-import { mockQualityGate } from '../../../../helpers/testMocks';
+import { mockMetric, mockQualityGate } from '../../../../helpers/testMocks';
import ConditionModal from '../ConditionModal';
it('should render correctly', () => {
+ expect(shallowRender()).toMatchSnapshot();
+ expect(shallowRender({ metric: mockMetric() })).toMatchSnapshot();
+});
+
+it('should correctly handle a metric selection', () => {
const wrapper = shallowRender();
+ const metric = mockMetric();
+
+ expect(wrapper.find('MetricSelect').prop('metric')).toBeUndefined();
+
+ wrapper.instance().handleMetricChange(metric);
+ expect(wrapper.find('MetricSelect').prop('metric')).toEqual(metric);
+});
+
+it('should correctly switch scope', () => {
+ const wrapper = shallowRender({
+ metrics: [
+ mockMetric({ id: 'new_coverage', key: 'new_coverage', name: 'Coverage on New Code' }),
+ mockMetric({
+ id: 'new_duplication',
+ key: 'new_duplication',
+ name: 'Duplication on New Code'
+ }),
+ mockMetric(),
+ mockMetric({ id: 'duplication', key: 'duplication', name: 'Duplication' })
+ ]
+ });
+ expect(wrapper).toMatchSnapshot();
+
+ wrapper.instance().handleScopeChange('overall');
expect(wrapper).toMatchSnapshot();
- wrapper.instance().handleMetricChange({ id: '1', key: 'foo', name: 'Foo', type: 'PERCENT' });
+ wrapper.instance().handleScopeChange('new');
expect(wrapper).toMatchSnapshot();
});
function shallowRender(props: Partial<ConditionModal['props']> = {}) {
return shallow<ConditionModal>(
<ConditionModal
- header="a"
+ header="header"
+ metrics={[
+ mockMetric({ id: 'new_coverage', key: 'new_coverage', name: 'Coverage on New Code' }),
+ mockMetric({
+ id: 'new_duplication',
+ key: 'new_duplication',
+ name: 'Duplication on New Code'
+ })
+ ]}
onAddCondition={jest.fn()}
onClose={jest.fn()}
qualityGate={mockQualityGate()}
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/Conditions-test.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/Conditions-test.tsx
new file mode 100644
index 00000000000..98982f434e1
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/Conditions-test.tsx
@@ -0,0 +1,78 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 { shallow } from 'enzyme';
+import * as React from 'react';
+import { mockCondition, mockMetric, mockQualityGate } from '../../../../helpers/testMocks';
+import { Conditions } from '../Conditions';
+
+it('should render correctly', () => {
+ expect(shallowRender()).toMatchSnapshot();
+});
+
+it('should render correctly with new code conditions', () => {
+ const wrapper = shallowRender({
+ conditions: [
+ mockCondition(),
+ mockCondition({ id: 2, metric: 'duplication' }),
+ mockCondition({ id: 3, metric: 'new_coverage' }),
+ mockCondition({ id: 4, metric: 'new_duplication' })
+ ]
+ });
+ expect(wrapper).toMatchSnapshot();
+});
+
+it('should render correctly for no conditions', () => {
+ const wrapper = shallowRender({ conditions: [] });
+ expect(wrapper).toMatchSnapshot();
+});
+
+it('should render the add conditions button and modal', () => {
+ const wrapper = shallowRender({ canEdit: true });
+ expect(wrapper).toMatchSnapshot();
+});
+
+function shallowRender(props: Partial<Conditions['props']> = {}) {
+ return shallow<Conditions>(
+ <Conditions
+ appState={{ branchesEnabled: true }}
+ canEdit={false}
+ conditions={[mockCondition(), mockCondition({ id: 2, metric: 'duplication' })]}
+ metrics={{
+ coverage: mockMetric(),
+ duplication: mockMetric({ id: 'duplication', key: 'duplication', name: 'Duplication' }),
+ new_coverage: mockMetric({
+ id: 'new_coverage',
+ key: 'new_coverage',
+ name: 'Coverage on New Code'
+ }),
+ new_duplication: mockMetric({
+ id: 'new_duplication',
+ key: 'new_duplication',
+ name: 'Duplication on New Code'
+ })
+ }}
+ onAddCondition={jest.fn()}
+ onRemoveCondition={jest.fn()}
+ onSaveCondition={jest.fn()}
+ qualityGate={mockQualityGate()}
+ {...props}
+ />
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/ListHeader-test.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/ListHeader-test.tsx
new file mode 100644
index 00000000000..651b2260124
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/ListHeader-test.tsx
@@ -0,0 +1,33 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 { shallow } from 'enzyme';
+import * as React from 'react';
+import ListHeader from '../ListHeader';
+
+it('should render correctly', () => {
+ expect(shallowRender()).toMatchSnapshot();
+ const wrapper = shallowRender({ canCreate: true });
+ expect(wrapper.find('ModalButton').exists()).toBe(true);
+ expect(wrapper.find('ModalButton').dive()).toMatchSnapshot();
+});
+
+function shallowRender(props = {}) {
+ return shallow(<ListHeader canCreate={false} refreshQualityGates={jest.fn()} {...props} />);
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/MetricSelect-test.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/MetricSelect-test.tsx
index 6210a5adc43..884627e7db1 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/MetricSelect-test.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/MetricSelect-test.tsx
@@ -19,22 +19,24 @@
*/
import { shallow } from 'enzyme';
import * as React from 'react';
+import { mockMetric } from '../../../../helpers/testMocks';
import MetricSelect from '../MetricSelect';
it('should render correctly', () => {
- expect(
- shallow(
- <MetricSelect
- metrics={[
- {
- id: '1',
- key: '1',
- name: 'metric 1',
- type: 'test'
- }
- ]}
- onMetricChange={jest.fn()}
- />
- )
- ).toMatchSnapshot();
+ expect(shallowRender()).toMatchSnapshot();
});
+
+it('should correctly handle change', () => {
+ const onMetricChange = jest.fn();
+ const metric = mockMetric();
+ const metrics = [mockMetric({ key: 'duplication' }), metric];
+ const wrapper = shallowRender({ metrics, onMetricChange });
+ wrapper.instance().handleChange({ label: metric.name, value: metric.key });
+ expect(onMetricChange).toBeCalledWith(metric);
+});
+
+function shallowRender(props: Partial<MetricSelect['props']> = {}) {
+ return shallow<MetricSelect>(
+ <MetricSelect metrics={[mockMetric()]} onMetricChange={jest.fn()} {...props} />
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/Condition-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/Condition-test.tsx.snap
new file mode 100644
index 00000000000..cd7a503cf00
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/Condition-test.tsx.snap
@@ -0,0 +1,189 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<tr>
+ <td
+ className="text-middle"
+ >
+ Coverage
+ </td>
+ <td
+ className="text-middle nowrap"
+ >
+ <span
+ className="note"
+ >
+ quality_gates.operator.LT
+ </span>
+ </td>
+ <td
+ className="text-middle nowrap"
+ >
+ 10.0%
+ </td>
+</tr>
+`;
+
+exports[`should render correctly with edit rights 1`] = `
+<tr>
+ <td
+ className="text-middle"
+ >
+ Coverage
+ </td>
+ <td
+ className="text-middle nowrap"
+ >
+ <span
+ className="note"
+ >
+ quality_gates.operator.LT
+ </span>
+ </td>
+ <td
+ className="text-middle nowrap"
+ >
+ 10.0%
+ </td>
+ <td
+ className="text-center thin"
+ >
+ <EditButton
+ data-test="quality-gates__condition-update"
+ onClick={[Function]}
+ />
+ </td>
+ <td
+ className="text-center thin"
+ >
+ <DeleteButton
+ data-test="quality-gates__condition-delete"
+ onClick={[Function]}
+ />
+ </td>
+</tr>
+`;
+
+exports[`should render the delete modal correctly 1`] = `
+<tr>
+ <td
+ className="text-middle"
+ >
+ Coverage
+ </td>
+ <td
+ className="text-middle nowrap"
+ >
+ <span
+ className="note"
+ >
+ quality_gates.operator.LT
+ </span>
+ </td>
+ <td
+ className="text-middle nowrap"
+ >
+ 10.0%
+ </td>
+ <td
+ className="text-center thin"
+ >
+ <EditButton
+ data-test="quality-gates__condition-update"
+ onClick={[Function]}
+ />
+ </td>
+ <td
+ className="text-center thin"
+ >
+ <DeleteButton
+ data-test="quality-gates__condition-delete"
+ onClick={[Function]}
+ />
+ </td>
+ <ConfirmModal
+ confirmButtonText="delete"
+ confirmData={
+ Object {
+ "error": "10",
+ "id": 1,
+ "metric": "coverage",
+ "op": "LT",
+ }
+ }
+ header="quality_gates.delete_condition"
+ isDestructive={true}
+ onClose={[Function]}
+ onConfirm={[Function]}
+ >
+ quality_gates.delete_condition.confirm.message.Coverage
+ </ConfirmModal>
+</tr>
+`;
+
+exports[`should render the update modal correctly 1`] = `
+<tr>
+ <td
+ className="text-middle"
+ >
+ Coverage
+ </td>
+ <td
+ className="text-middle nowrap"
+ >
+ <span
+ className="note"
+ >
+ quality_gates.operator.LT
+ </span>
+ </td>
+ <td
+ className="text-middle nowrap"
+ >
+ 10.0%
+ </td>
+ <td
+ className="text-center thin"
+ >
+ <EditButton
+ data-test="quality-gates__condition-update"
+ onClick={[Function]}
+ />
+ </td>
+ <td
+ className="text-center thin"
+ >
+ <DeleteButton
+ data-test="quality-gates__condition-delete"
+ onClick={[Function]}
+ />
+ </td>
+ <ConditionModal
+ condition={
+ Object {
+ "error": "10",
+ "id": 1,
+ "metric": "coverage",
+ "op": "LT",
+ }
+ }
+ header="quality_gates.update_condition"
+ metric={
+ Object {
+ "id": "coverage",
+ "key": "coverage",
+ "name": "Coverage",
+ "type": "PERCENT",
+ }
+ }
+ onAddCondition={[Function]}
+ onClose={[Function]}
+ qualityGate={
+ Object {
+ "id": 1,
+ "name": "qualitygate",
+ }
+ }
+ />
+</tr>
+`;
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/ConditionModal-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/ConditionModal-test.tsx.snap
index ab32f89c02b..711a500cdf5 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/ConditionModal-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/ConditionModal-test.tsx.snap
@@ -1,31 +1,278 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
+exports[`should correctly switch scope 1`] = `
+<ConfirmModal
+ confirmButtonText="header"
+ confirmDisable={true}
+ header="header"
+ onClose={[MockFunction]}
+ onConfirm={[Function]}
+ size="small"
+>
+ <div
+ className="modal-field display-flex-center"
+ >
+ <Radio
+ checked={true}
+ onCheck={[Function]}
+ value="new"
+ >
+ <span
+ data-test="quality-gates__condition-scope-new"
+ >
+ quality_gates.conditions.new_code
+ </span>
+ </Radio>
+ <Radio
+ checked={false}
+ className="big-spacer-left"
+ onCheck={[Function]}
+ value="overall"
+ >
+ <span
+ data-test="quality-gates__condition-scope-overall"
+ >
+ quality_gates.conditions.overall_code
+ </span>
+ </Radio>
+ </div>
+ <div
+ className="modal-field"
+ >
+ <label
+ htmlFor="condition-metric"
+ >
+ quality_gates.conditions.fails_when
+ </label>
+ <MetricSelect
+ metrics={
+ Array [
+ Object {
+ "id": "new_coverage",
+ "key": "new_coverage",
+ "name": "Coverage on New Code",
+ "type": "PERCENT",
+ },
+ Object {
+ "id": "new_duplication",
+ "key": "new_duplication",
+ "name": "Duplication on New Code",
+ "type": "PERCENT",
+ },
+ ]
+ }
+ onMetricChange={[Function]}
+ />
+ </div>
+</ConfirmModal>
+`;
+
+exports[`should correctly switch scope 2`] = `
+<ConfirmModal
+ confirmButtonText="header"
+ confirmDisable={true}
+ header="header"
+ onClose={[MockFunction]}
+ onConfirm={[Function]}
+ size="small"
+>
+ <div
+ className="modal-field display-flex-center"
+ >
+ <Radio
+ checked={false}
+ onCheck={[Function]}
+ value="new"
+ >
+ <span
+ data-test="quality-gates__condition-scope-new"
+ >
+ quality_gates.conditions.new_code
+ </span>
+ </Radio>
+ <Radio
+ checked={true}
+ className="big-spacer-left"
+ onCheck={[Function]}
+ value="overall"
+ >
+ <span
+ data-test="quality-gates__condition-scope-overall"
+ >
+ quality_gates.conditions.overall_code
+ </span>
+ </Radio>
+ </div>
+ <div
+ className="modal-field"
+ >
+ <label
+ htmlFor="condition-metric"
+ >
+ quality_gates.conditions.fails_when
+ </label>
+ <MetricSelect
+ metrics={
+ Array [
+ Object {
+ "id": "coverage",
+ "key": "coverage",
+ "name": "Coverage",
+ "type": "PERCENT",
+ },
+ Object {
+ "id": "duplication",
+ "key": "duplication",
+ "name": "Duplication",
+ "type": "PERCENT",
+ },
+ ]
+ }
+ onMetricChange={[Function]}
+ />
+ </div>
+</ConfirmModal>
+`;
+
+exports[`should correctly switch scope 3`] = `
+<ConfirmModal
+ confirmButtonText="header"
+ confirmDisable={true}
+ header="header"
+ onClose={[MockFunction]}
+ onConfirm={[Function]}
+ size="small"
+>
+ <div
+ className="modal-field display-flex-center"
+ >
+ <Radio
+ checked={true}
+ onCheck={[Function]}
+ value="new"
+ >
+ <span
+ data-test="quality-gates__condition-scope-new"
+ >
+ quality_gates.conditions.new_code
+ </span>
+ </Radio>
+ <Radio
+ checked={false}
+ className="big-spacer-left"
+ onCheck={[Function]}
+ value="overall"
+ >
+ <span
+ data-test="quality-gates__condition-scope-overall"
+ >
+ quality_gates.conditions.overall_code
+ </span>
+ </Radio>
+ </div>
+ <div
+ className="modal-field"
+ >
+ <label
+ htmlFor="condition-metric"
+ >
+ quality_gates.conditions.fails_when
+ </label>
+ <MetricSelect
+ metrics={
+ Array [
+ Object {
+ "id": "new_coverage",
+ "key": "new_coverage",
+ "name": "Coverage on New Code",
+ "type": "PERCENT",
+ },
+ Object {
+ "id": "new_duplication",
+ "key": "new_duplication",
+ "name": "Duplication on New Code",
+ "type": "PERCENT",
+ },
+ ]
+ }
+ onMetricChange={[Function]}
+ />
+ </div>
+</ConfirmModal>
+`;
+
exports[`should render correctly 1`] = `
<ConfirmModal
- confirmButtonText="a"
+ confirmButtonText="header"
confirmDisable={true}
- header="a"
+ header="header"
onClose={[MockFunction]}
onConfirm={[Function]}
size="small"
>
<div
+ className="modal-field display-flex-center"
+ >
+ <Radio
+ checked={true}
+ onCheck={[Function]}
+ value="new"
+ >
+ <span
+ data-test="quality-gates__condition-scope-new"
+ >
+ quality_gates.conditions.new_code
+ </span>
+ </Radio>
+ <Radio
+ checked={false}
+ className="big-spacer-left"
+ onCheck={[Function]}
+ value="overall"
+ >
+ <span
+ data-test="quality-gates__condition-scope-overall"
+ >
+ quality_gates.conditions.overall_code
+ </span>
+ </Radio>
+ </div>
+ <div
className="modal-field"
>
<label
htmlFor="condition-metric"
>
- quality_gates.conditions.metric
+ quality_gates.conditions.fails_when
</label>
+ <MetricSelect
+ metrics={
+ Array [
+ Object {
+ "id": "new_coverage",
+ "key": "new_coverage",
+ "name": "Coverage on New Code",
+ "type": "PERCENT",
+ },
+ Object {
+ "id": "new_duplication",
+ "key": "new_duplication",
+ "name": "Duplication on New Code",
+ "type": "PERCENT",
+ },
+ ]
+ }
+ onMetricChange={[Function]}
+ />
</div>
</ConfirmModal>
`;
exports[`should render correctly 2`] = `
<ConfirmModal
- confirmButtonText="a"
+ confirmButtonText="header"
confirmDisable={false}
- header="a"
+ header="header"
onClose={[MockFunction]}
onConfirm={[Function]}
size="small"
@@ -36,8 +283,40 @@ exports[`should render correctly 2`] = `
<label
htmlFor="condition-metric"
>
- quality_gates.conditions.metric
+ quality_gates.conditions.fails_when
</label>
+ <MetricSelect
+ metric={
+ Object {
+ "id": "coverage",
+ "key": "coverage",
+ "name": "Coverage",
+ "type": "PERCENT",
+ }
+ }
+ metrics={
+ Array [
+ Object {
+ "id": "new_coverage",
+ "key": "new_coverage",
+ "name": "Coverage on New Code",
+ "type": "PERCENT",
+ },
+ Object {
+ "id": "new_duplication",
+ "key": "new_duplication",
+ "name": "Duplication on New Code",
+ "type": "PERCENT",
+ },
+ ]
+ }
+ onMetricChange={[Function]}
+ />
+ <span
+ className="note"
+ >
+ Coverage
+ </span>
</div>
<div
className="modal-field display-inline-block"
@@ -50,9 +329,9 @@ exports[`should render correctly 2`] = `
<ConditionOperator
metric={
Object {
- "id": "1",
- "key": "foo",
- "name": "Foo",
+ "id": "coverage",
+ "key": "coverage",
+ "name": "Coverage",
"type": "PERCENT",
}
}
@@ -65,14 +344,14 @@ exports[`should render correctly 2`] = `
<label
htmlFor="condition-threshold"
>
- quality_gates.conditions.error
+ quality_gates.conditions.value
</label>
<ThresholdInput
metric={
Object {
- "id": "1",
- "key": "foo",
- "name": "Foo",
+ "id": "coverage",
+ "key": "coverage",
+ "name": "Coverage",
"type": "PERCENT",
}
}
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/Conditions-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/Conditions-test.tsx.snap
new file mode 100644
index 00000000000..d3db47fc642
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/Conditions-test.tsx.snap
@@ -0,0 +1,493 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<div
+ className="quality-gate-section"
+>
+ <header
+ className="display-flex-center spacer-bottom"
+ >
+ <h3>
+ quality_gates.conditions
+ </h3>
+ <DocTooltip
+ className="spacer-left"
+ doc={Promise {}}
+ />
+ </header>
+ <div
+ className="big-spacer-top"
+ >
+ <h4>
+ quality_gates.conditions.overall_code.long
+ </h4>
+ <p
+ className="spacer-top spacer-bottom"
+ >
+ quality_gates.conditions.overall_code.description
+ </p>
+ <table
+ className="data zebra"
+ data-test="quality-gates__conditions-overall"
+ >
+ <thead>
+ <tr>
+ <th
+ className="nowrap"
+ style={
+ Object {
+ "width": 300,
+ }
+ }
+ >
+ quality_gates.conditions.metric
+ </th>
+ <th
+ className="nowrap"
+ >
+ quality_gates.conditions.operator
+ </th>
+ <th
+ className="nowrap"
+ >
+ quality_gates.conditions.value
+ </th>
+ </tr>
+ </thead>
+ <tbody>
+ <Condition
+ canEdit={false}
+ condition={
+ Object {
+ "error": "10",
+ "id": 1,
+ "metric": "coverage",
+ "op": "LT",
+ }
+ }
+ key="1"
+ metric={
+ Object {
+ "id": "coverage",
+ "key": "coverage",
+ "name": "Coverage",
+ "type": "PERCENT",
+ }
+ }
+ onRemoveCondition={[MockFunction]}
+ onSaveCondition={[MockFunction]}
+ qualityGate={
+ Object {
+ "id": 1,
+ "name": "qualitygate",
+ }
+ }
+ />
+ <Condition
+ canEdit={false}
+ condition={
+ Object {
+ "error": "10",
+ "id": 2,
+ "metric": "duplication",
+ "op": "LT",
+ }
+ }
+ key="2"
+ metric={
+ Object {
+ "id": "duplication",
+ "key": "duplication",
+ "name": "Duplication",
+ "type": "PERCENT",
+ }
+ }
+ onRemoveCondition={[MockFunction]}
+ onSaveCondition={[MockFunction]}
+ qualityGate={
+ Object {
+ "id": 1,
+ "name": "qualitygate",
+ }
+ }
+ />
+ </tbody>
+ </table>
+ </div>
+</div>
+`;
+
+exports[`should render correctly for no conditions 1`] = `
+<div
+ className="quality-gate-section"
+>
+ <header
+ className="display-flex-center spacer-bottom"
+ >
+ <h3>
+ quality_gates.conditions
+ </h3>
+ <DocTooltip
+ className="spacer-left"
+ doc={Promise {}}
+ />
+ </header>
+ <div
+ className="big-spacer-top"
+ >
+ quality_gates.no_conditions
+ </div>
+</div>
+`;
+
+exports[`should render correctly with new code conditions 1`] = `
+<div
+ className="quality-gate-section"
+>
+ <header
+ className="display-flex-center spacer-bottom"
+ >
+ <h3>
+ quality_gates.conditions
+ </h3>
+ <DocTooltip
+ className="spacer-left"
+ doc={Promise {}}
+ />
+ </header>
+ <div
+ className="big-spacer-top"
+ >
+ <h4>
+ quality_gates.conditions.new_code.long
+ </h4>
+ <p
+ className="spacer-top spacer-bottom"
+ >
+ quality_gates.conditions.new_code.description
+ </p>
+ <table
+ className="data zebra"
+ data-test="quality-gates__conditions-new"
+ >
+ <thead>
+ <tr>
+ <th
+ className="nowrap"
+ style={
+ Object {
+ "width": 300,
+ }
+ }
+ >
+ quality_gates.conditions.metric
+ </th>
+ <th
+ className="nowrap"
+ >
+ quality_gates.conditions.operator
+ </th>
+ <th
+ className="nowrap"
+ >
+ quality_gates.conditions.value
+ </th>
+ </tr>
+ </thead>
+ <tbody>
+ <Condition
+ canEdit={false}
+ condition={
+ Object {
+ "error": "10",
+ "id": 3,
+ "metric": "new_coverage",
+ "op": "LT",
+ }
+ }
+ key="3"
+ metric={
+ Object {
+ "id": "new_coverage",
+ "key": "new_coverage",
+ "name": "Coverage on New Code",
+ "type": "PERCENT",
+ }
+ }
+ onRemoveCondition={[MockFunction]}
+ onSaveCondition={[MockFunction]}
+ qualityGate={
+ Object {
+ "id": 1,
+ "name": "qualitygate",
+ }
+ }
+ />
+ <Condition
+ canEdit={false}
+ condition={
+ Object {
+ "error": "10",
+ "id": 4,
+ "metric": "new_duplication",
+ "op": "LT",
+ }
+ }
+ key="4"
+ metric={
+ Object {
+ "id": "new_duplication",
+ "key": "new_duplication",
+ "name": "Duplication on New Code",
+ "type": "PERCENT",
+ }
+ }
+ onRemoveCondition={[MockFunction]}
+ onSaveCondition={[MockFunction]}
+ qualityGate={
+ Object {
+ "id": 1,
+ "name": "qualitygate",
+ }
+ }
+ />
+ </tbody>
+ </table>
+ </div>
+ <div
+ className="big-spacer-top"
+ >
+ <h4>
+ quality_gates.conditions.overall_code.long
+ </h4>
+ <p
+ className="spacer-top spacer-bottom"
+ >
+ quality_gates.conditions.overall_code.description
+ </p>
+ <table
+ className="data zebra"
+ data-test="quality-gates__conditions-overall"
+ >
+ <thead>
+ <tr>
+ <th
+ className="nowrap"
+ style={
+ Object {
+ "width": 300,
+ }
+ }
+ >
+ quality_gates.conditions.metric
+ </th>
+ <th
+ className="nowrap"
+ >
+ quality_gates.conditions.operator
+ </th>
+ <th
+ className="nowrap"
+ >
+ quality_gates.conditions.value
+ </th>
+ </tr>
+ </thead>
+ <tbody>
+ <Condition
+ canEdit={false}
+ condition={
+ Object {
+ "error": "10",
+ "id": 1,
+ "metric": "coverage",
+ "op": "LT",
+ }
+ }
+ key="1"
+ metric={
+ Object {
+ "id": "coverage",
+ "key": "coverage",
+ "name": "Coverage",
+ "type": "PERCENT",
+ }
+ }
+ onRemoveCondition={[MockFunction]}
+ onSaveCondition={[MockFunction]}
+ qualityGate={
+ Object {
+ "id": 1,
+ "name": "qualitygate",
+ }
+ }
+ />
+ <Condition
+ canEdit={false}
+ condition={
+ Object {
+ "error": "10",
+ "id": 2,
+ "metric": "duplication",
+ "op": "LT",
+ }
+ }
+ key="2"
+ metric={
+ Object {
+ "id": "duplication",
+ "key": "duplication",
+ "name": "Duplication",
+ "type": "PERCENT",
+ }
+ }
+ onRemoveCondition={[MockFunction]}
+ onSaveCondition={[MockFunction]}
+ qualityGate={
+ Object {
+ "id": 1,
+ "name": "qualitygate",
+ }
+ }
+ />
+ </tbody>
+ </table>
+ </div>
+</div>
+`;
+
+exports[`should render the add conditions button and modal 1`] = `
+<div
+ className="quality-gate-section"
+>
+ <div
+ className="pull-right"
+ >
+ <ModalButton
+ modal={[Function]}
+ >
+ <Component />
+ </ModalButton>
+ </div>
+ <header
+ className="display-flex-center spacer-bottom"
+ >
+ <h3>
+ quality_gates.conditions
+ </h3>
+ <DocTooltip
+ className="spacer-left"
+ doc={Promise {}}
+ />
+ </header>
+ <div
+ className="big-spacer-top"
+ >
+ <h4>
+ quality_gates.conditions.overall_code.long
+ </h4>
+ <p
+ className="spacer-top spacer-bottom"
+ >
+ quality_gates.conditions.overall_code.description
+ </p>
+ <table
+ className="data zebra"
+ data-test="quality-gates__conditions-overall"
+ >
+ <thead>
+ <tr>
+ <th
+ className="nowrap"
+ style={
+ Object {
+ "width": 300,
+ }
+ }
+ >
+ quality_gates.conditions.metric
+ </th>
+ <th
+ className="nowrap"
+ >
+ quality_gates.conditions.operator
+ </th>
+ <th
+ className="nowrap"
+ >
+ quality_gates.conditions.value
+ </th>
+ <th
+ className="thin"
+ >
+ edit
+ </th>
+ <th
+ className="thin"
+ >
+ delete
+ </th>
+ </tr>
+ </thead>
+ <tbody>
+ <Condition
+ canEdit={true}
+ condition={
+ Object {
+ "error": "10",
+ "id": 1,
+ "metric": "coverage",
+ "op": "LT",
+ }
+ }
+ key="1"
+ metric={
+ Object {
+ "id": "coverage",
+ "key": "coverage",
+ "name": "Coverage",
+ "type": "PERCENT",
+ }
+ }
+ onRemoveCondition={[MockFunction]}
+ onSaveCondition={[MockFunction]}
+ qualityGate={
+ Object {
+ "id": 1,
+ "name": "qualitygate",
+ }
+ }
+ />
+ <Condition
+ canEdit={true}
+ condition={
+ Object {
+ "error": "10",
+ "id": 2,
+ "metric": "duplication",
+ "op": "LT",
+ }
+ }
+ key="2"
+ metric={
+ Object {
+ "id": "duplication",
+ "key": "duplication",
+ "name": "Duplication",
+ "type": "PERCENT",
+ }
+ }
+ onRemoveCondition={[MockFunction]}
+ onSaveCondition={[MockFunction]}
+ qualityGate={
+ Object {
+ "id": 1,
+ "name": "qualitygate",
+ }
+ }
+ />
+ </tbody>
+ </table>
+ </div>
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/ListHeader-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/ListHeader-test.tsx.snap
new file mode 100644
index 00000000000..f12ac9a8ce6
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/ListHeader-test.tsx.snap
@@ -0,0 +1,32 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<header
+ className="page-header"
+>
+ <div
+ className="display-flex-center"
+ >
+ <h1
+ className="page-title"
+ >
+ quality_gates.page
+ </h1>
+ <DocTooltip
+ className="spacer-left"
+ doc={Promise {}}
+ />
+ </div>
+</header>
+`;
+
+exports[`should render correctly 2`] = `
+<Fragment>
+ <Button
+ data-test="quality-gates__add"
+ onClick={[Function]}
+ >
+ create
+ </Button>
+</Fragment>
+`;
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/MetricSelect-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/MetricSelect-test.tsx.snap
index b03dceb54a2..a387d03d4ed 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/MetricSelect-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/MetricSelect-test.tsx.snap
@@ -9,12 +9,11 @@ exports[`should render correctly 1`] = `
Array [
Object {
"domain": undefined,
- "label": "metric 1",
- "value": 0,
+ "label": "Coverage",
+ "value": "coverage",
},
]
}
placeholder="search.search_for_metrics"
- value={-1}
/>
`;