*/
import * as React from 'react';
import HelpTooltip from 'sonar-ui-common/components/controls/HelpTooltip';
+import { Alert } from 'sonar-ui-common/components/ui/Alert';
import { translate } from 'sonar-ui-common/helpers/l10n';
import Conditions from './Conditions';
import Projects from './Projects';
return (
<div className="layout-page-main-inner">
+ {isDefault && (qualityGate.conditions === undefined || qualityGate.conditions.length === 0) && (
+ <Alert className="big-spacer-bottom" variant="warning">
+ {translate('quality_gates.is_default_no_conditions')}
+ </Alert>
+ )}
+
<Conditions
canEdit={actions.manageConditions}
conditions={conditions}
import * as React from 'react';
import { Button } from 'sonar-ui-common/components/controls/buttons';
import ModalButton from 'sonar-ui-common/components/controls/ModalButton';
+import Tooltip from 'sonar-ui-common/components/controls/Tooltip';
import { translate } from 'sonar-ui-common/helpers/l10n';
import { setQualityGateAsDefault } from '../../../api/quality-gates';
import BuiltInQualityGateBadge from './BuiltInQualityGateBadge';
render() {
const { qualityGate } = this.props;
const actions = qualityGate.actions || ({} as any);
+ const hasNoConditions =
+ qualityGate.conditions === undefined || qualityGate.conditions.length === 0;
return (
<div className="layout-page-header-panel layout-page-main-header issues-main-header">
<div className="layout-page-header-panel-inner layout-page-main-header-inner">
</ModalButton>
)}
{actions.setAsDefault && (
- <Button
- className="little-spacer-left"
- id="quality-gate-toggle-default"
- onClick={this.handleSetAsDefaultClick}>
- {translate('set_as_default')}
- </Button>
+ <Tooltip
+ overlay={
+ hasNoConditions
+ ? translate('quality_gates.cannot_set_default_no_conditions')
+ : null
+ }>
+ <Button
+ className="little-spacer-left"
+ disabled={hasNoConditions}
+ id="quality-gate-toggle-default"
+ onClick={this.handleSetAsDefaultClick}>
+ {translate('set_as_default')}
+ </Button>
+ </Tooltip>
)}
{actions.delete && (
<DeleteQualityGateForm
};
render() {
+ const { qualityGate } = this.props;
+
+ if (qualityGate.conditions === undefined || qualityGate.conditions.length === 0) {
+ return (
+ <div>{translate('quality_gates.projects.cannot_associate_projects_no_conditions')}</div>
+ );
+ }
+
return (
<SelectList
elements={this.state.projects.map(project => project.key)}
import { shallow } from 'enzyme';
import * as React from 'react';
import { mockQualityGate } from '../../../../helpers/mocks/quality-gates';
+import { mockCondition } from '../../../../helpers/testMocks';
import { DetailsContent, DetailsContentProps } from '../DetailsContent';
it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot('is not default');
expect(shallowRender({ isDefault: true })).toMatchSnapshot('is default');
+ expect(
+ shallowRender({ isDefault: true, qualityGate: mockQualityGate({ conditions: [] }) })
+ ).toMatchSnapshot('is default, no conditions');
});
function shallowRender(props: Partial<DetailsContentProps> = {}) {
onAddCondition={jest.fn()}
onRemoveCondition={jest.fn()}
onSaveCondition={jest.fn()}
- qualityGate={mockQualityGate()}
+ qualityGate={mockQualityGate({ conditions: [mockCondition()] })}
{...props}
/>
);
import { click, waitAndUpdate } from 'sonar-ui-common/helpers/testUtils';
import { setQualityGateAsDefault } from '../../../../api/quality-gates';
import { mockQualityGate } from '../../../../helpers/mocks/quality-gates';
+import { mockCondition } from '../../../../helpers/testMocks';
import DetailsHeader from '../DetailsHeader';
jest.mock('../../../../api/quality-gates', () => ({
it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot('default');
- expect(shallowRender({ qualityGate: mockQualityGate({ isBuiltIn: true }) })).toMatchSnapshot(
- 'built-in'
- );
+ expect(
+ shallowRender({
+ qualityGate: mockQualityGate({ isBuiltIn: true, conditions: [mockCondition()] })
+ })
+ ).toMatchSnapshot('built-in');
expect(
shallowRender({
qualityGate: mockQualityGate({
+ conditions: [mockCondition()],
actions: {
copy: true,
delete: true,
})
})
).toMatchSnapshot('admin actions');
+ expect(
+ shallowRender({
+ qualityGate: mockQualityGate({ actions: { setAsDefault: true }, conditions: [] })
+ })
+ ).toMatchSnapshot('no conditions, cannot set as default');
});
it('should allow the QG to be set as the default', async () => {
return shallow<DetailsHeader>(
<DetailsHeader
onSetDefault={jest.fn()}
- qualityGate={mockQualityGate()}
+ qualityGate={mockQualityGate({ conditions: [mockCondition()] })}
refreshItem={jest.fn().mockResolvedValue(null)}
refreshList={jest.fn().mockResolvedValue(null)}
{...props}
searchProjects
} from '../../../../api/quality-gates';
import { mockQualityGate } from '../../../../helpers/mocks/quality-gates';
+import { mockCondition } from '../../../../helpers/testMocks';
import Projects from '../Projects';
-const qualityGate = mockQualityGate();
-
jest.mock('../../../../api/quality-gates', () => ({
searchProjects: jest.fn().mockResolvedValue({
paging: { pageIndex: 1, pageSize: 3, total: 55 },
await waitAndUpdate(wrapper);
expect(wrapper.instance().mounted).toBe(true);
- expect(wrapper).toMatchSnapshot();
- expect(wrapper.instance().renderElement('test1')).toMatchSnapshot();
- expect(wrapper.instance().renderElement('test_foo')).toMatchSnapshot();
+ expect(wrapper).toMatchSnapshot('default');
+ expect(wrapper.instance().renderElement('test1')).toMatchSnapshot('known project');
+ expect(wrapper.instance().renderElement('test_foo')).toMatchSnapshot('unknown project');
+ expect(shallowRender({ qualityGate: mockQualityGate({ conditions: [] }) })).toMatchSnapshot(
+ 'quality gate without conditions'
+ );
expect(searchProjects).toHaveBeenCalledWith(
expect.objectContaining({
- gateName: qualityGate.name,
+ gateName: 'Foo',
page: 1,
pageSize: 100,
query: undefined,
});
function shallowRender(props: Partial<Projects['props']> = {}) {
- return shallow<Projects>(<Projects qualityGate={qualityGate} {...props} />);
+ return shallow<Projects>(
+ <Projects
+ qualityGate={mockQualityGate({ name: 'Foo', conditions: [mockCondition()] })}
+ {...props}
+ />
+ );
}
<div
className="layout-page-main-inner"
>
+ <Connect(withAppState(Conditions))
+ conditions={
+ Array [
+ Object {
+ "error": "10",
+ "id": 1,
+ "metric": "coverage",
+ "op": "LT",
+ },
+ ]
+ }
+ metrics={Object {}}
+ onAddCondition={[MockFunction]}
+ onRemoveCondition={[MockFunction]}
+ onSaveCondition={[MockFunction]}
+ qualityGate={
+ Object {
+ "conditions": Array [
+ Object {
+ "error": "10",
+ "id": 1,
+ "metric": "coverage",
+ "op": "LT",
+ },
+ ],
+ "id": "1",
+ "name": "qualitygate",
+ }
+ }
+ />
+ <div
+ className="quality-gate-section"
+ id="quality-gate-projects"
+ >
+ <header
+ className="display-flex-center spacer-bottom"
+ >
+ <h3>
+ quality_gates.projects
+ </h3>
+ <HelpTooltip
+ className="spacer-left"
+ overlay={
+ <div
+ className="big-padded-top big-padded-bottom"
+ >
+ quality_gates.projects.help
+ </div>
+ }
+ />
+ </header>
+ quality_gates.projects_for_default
+ </div>
+</div>
+`;
+
+exports[`should render correctly: is default, no conditions 1`] = `
+<div
+ className="layout-page-main-inner"
+>
+ <Alert
+ className="big-spacer-bottom"
+ variant="warning"
+ >
+ quality_gates.is_default_no_conditions
+ </Alert>
<Connect(withAppState(Conditions))
conditions={Array []}
metrics={Object {}}
onSaveCondition={[MockFunction]}
qualityGate={
Object {
+ "conditions": Array [],
"id": "1",
"name": "qualitygate",
}
className="layout-page-main-inner"
>
<Connect(withAppState(Conditions))
- conditions={Array []}
+ conditions={
+ Array [
+ Object {
+ "error": "10",
+ "id": 1,
+ "metric": "coverage",
+ "op": "LT",
+ },
+ ]
+ }
metrics={Object {}}
onAddCondition={[MockFunction]}
onRemoveCondition={[MockFunction]}
onSaveCondition={[MockFunction]}
qualityGate={
Object {
+ "conditions": Array [
+ Object {
+ "error": "10",
+ "id": 1,
+ "metric": "coverage",
+ "op": "LT",
+ },
+ ],
"id": "1",
"name": "qualitygate",
}
key="1"
qualityGate={
Object {
+ "conditions": Array [
+ Object {
+ "error": "10",
+ "id": 1,
+ "metric": "coverage",
+ "op": "LT",
+ },
+ ],
"id": "1",
"name": "qualitygate",
}
>
<Component />
</ModalButton>
- <Button
- className="little-spacer-left"
- id="quality-gate-toggle-default"
- onClick={[Function]}
+ <Tooltip
+ overlay={null}
>
- set_as_default
- </Button>
+ <Button
+ className="little-spacer-left"
+ disabled={false}
+ id="quality-gate-toggle-default"
+ onClick={[Function]}
+ >
+ set_as_default
+ </Button>
+ </Tooltip>
<withRouter(DeleteQualityGateForm)
onDelete={[MockFunction]}
qualityGate={
"rename": true,
"setAsDefault": true,
},
+ "conditions": Array [
+ Object {
+ "error": "10",
+ "id": 1,
+ "metric": "coverage",
+ "op": "LT",
+ },
+ ],
"id": "1",
"name": "qualitygate",
}
</div>
</div>
`;
+
+exports[`should render correctly: no conditions, cannot set as default 1`] = `
+<div
+ className="layout-page-header-panel layout-page-main-header issues-main-header"
+>
+ <div
+ className="layout-page-header-panel-inner layout-page-main-header-inner"
+ >
+ <div
+ className="layout-page-main-inner"
+ >
+ <div
+ className="pull-left display-flex-center"
+ >
+ <h2>
+ qualitygate
+ </h2>
+ </div>
+ <div
+ className="pull-right"
+ >
+ <Tooltip
+ overlay="quality_gates.cannot_set_default_no_conditions"
+ >
+ <Button
+ className="little-spacer-left"
+ disabled={true}
+ id="quality-gate-toggle-default"
+ onClick={[Function]}
+ >
+ set_as_default
+ </Button>
+ </Tooltip>
+ </div>
+ </div>
+ </div>
+</div>
+`;
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`should render correctly 1`] = `
+exports[`should render correctly: default 1`] = `
<SelectList
elements={
Array [
/>
`;
-exports[`should render correctly 2`] = `
+exports[`should render correctly: known project 1`] = `
<div
className="select-list-list-item"
>
</div>
`;
-exports[`should render correctly 3`] = `
+exports[`should render correctly: quality gate without conditions 1`] = `
+<div>
+ quality_gates.projects.cannot_associate_projects_no_conditions
+</div>
+`;
+
+exports[`should render correctly: unknown project 1`] = `
<div
className="select-list-list-item"
>
quality_gates.rename=Rename Quality Gate
quality_gates.delete=Delete Quality Gate
quality_gates.copy=Copy Quality Gate
+quality_gates.cannot_set_default_no_conditions=You must configure at least 1 condition before you can make this the default quality gate.
+quality_gates.is_default_no_conditions=This is the default quality gate, but it has no configured conditions. Please configure at least 1 condition for this quality gate.
quality_gates.conditions=Conditions
quality_gates.conditions.help=Your project will fail the Quality Gate if it crosses any metric thresholds set for New Code or Overall Code.
quality_gates.conditions.help.link=See also: Clean as You Code
quality_gates.projects.noResults=No Projects
quality_gates.projects.select_hint=Click to associate this project with the quality gate
quality_gates.projects.deselect_hint=Click to remove association between this project and the quality gate
+quality_gates.projects.cannot_associate_projects_no_conditions=You must configure at least 1 condition before you can assign projects to this quality gate.
quality_gates.operator.LT=is less than
quality_gates.operator.GT=is greater than
quality_gates.operator.EQ=equals