*/ | */ | ||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import HelpTooltip from 'sonar-ui-common/components/controls/HelpTooltip'; | 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 { translate } from 'sonar-ui-common/helpers/l10n'; | ||||
import Conditions from './Conditions'; | import Conditions from './Conditions'; | ||||
import Projects from './Projects'; | import Projects from './Projects'; | ||||
return ( | return ( | ||||
<div className="layout-page-main-inner"> | <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 | <Conditions | ||||
canEdit={actions.manageConditions} | canEdit={actions.manageConditions} | ||||
conditions={conditions} | conditions={conditions} |
import * as React from 'react'; | import * as React from 'react'; | ||||
import { Button } from 'sonar-ui-common/components/controls/buttons'; | import { Button } from 'sonar-ui-common/components/controls/buttons'; | ||||
import ModalButton from 'sonar-ui-common/components/controls/ModalButton'; | 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 { translate } from 'sonar-ui-common/helpers/l10n'; | ||||
import { setQualityGateAsDefault } from '../../../api/quality-gates'; | import { setQualityGateAsDefault } from '../../../api/quality-gates'; | ||||
import BuiltInQualityGateBadge from './BuiltInQualityGateBadge'; | import BuiltInQualityGateBadge from './BuiltInQualityGateBadge'; | ||||
render() { | render() { | ||||
const { qualityGate } = this.props; | const { qualityGate } = this.props; | ||||
const actions = qualityGate.actions || ({} as any); | const actions = qualityGate.actions || ({} as any); | ||||
const hasNoConditions = | |||||
qualityGate.conditions === undefined || qualityGate.conditions.length === 0; | |||||
return ( | return ( | ||||
<div className="layout-page-header-panel layout-page-main-header issues-main-header"> | <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-header-panel-inner layout-page-main-header-inner"> | ||||
</ModalButton> | </ModalButton> | ||||
)} | )} | ||||
{actions.setAsDefault && ( | {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 && ( | {actions.delete && ( | ||||
<DeleteQualityGateForm | <DeleteQualityGateForm |
}; | }; | ||||
render() { | 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 ( | return ( | ||||
<SelectList | <SelectList | ||||
elements={this.state.projects.map(project => project.key)} | elements={this.state.projects.map(project => project.key)} |
import { shallow } from 'enzyme'; | import { shallow } from 'enzyme'; | ||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import { mockQualityGate } from '../../../../helpers/mocks/quality-gates'; | import { mockQualityGate } from '../../../../helpers/mocks/quality-gates'; | ||||
import { mockCondition } from '../../../../helpers/testMocks'; | |||||
import { DetailsContent, DetailsContentProps } from '../DetailsContent'; | import { DetailsContent, DetailsContentProps } from '../DetailsContent'; | ||||
it('should render correctly', () => { | it('should render correctly', () => { | ||||
expect(shallowRender()).toMatchSnapshot('is not default'); | expect(shallowRender()).toMatchSnapshot('is not default'); | ||||
expect(shallowRender({ isDefault: true })).toMatchSnapshot('is 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> = {}) { | function shallowRender(props: Partial<DetailsContentProps> = {}) { | ||||
onAddCondition={jest.fn()} | onAddCondition={jest.fn()} | ||||
onRemoveCondition={jest.fn()} | onRemoveCondition={jest.fn()} | ||||
onSaveCondition={jest.fn()} | onSaveCondition={jest.fn()} | ||||
qualityGate={mockQualityGate()} | |||||
qualityGate={mockQualityGate({ conditions: [mockCondition()] })} | |||||
{...props} | {...props} | ||||
/> | /> | ||||
); | ); |
import { click, waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; | import { click, waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; | ||||
import { setQualityGateAsDefault } from '../../../../api/quality-gates'; | import { setQualityGateAsDefault } from '../../../../api/quality-gates'; | ||||
import { mockQualityGate } from '../../../../helpers/mocks/quality-gates'; | import { mockQualityGate } from '../../../../helpers/mocks/quality-gates'; | ||||
import { mockCondition } from '../../../../helpers/testMocks'; | |||||
import DetailsHeader from '../DetailsHeader'; | import DetailsHeader from '../DetailsHeader'; | ||||
jest.mock('../../../../api/quality-gates', () => ({ | jest.mock('../../../../api/quality-gates', () => ({ | ||||
it('should render correctly', () => { | it('should render correctly', () => { | ||||
expect(shallowRender()).toMatchSnapshot('default'); | 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( | expect( | ||||
shallowRender({ | shallowRender({ | ||||
qualityGate: mockQualityGate({ | qualityGate: mockQualityGate({ | ||||
conditions: [mockCondition()], | |||||
actions: { | actions: { | ||||
copy: true, | copy: true, | ||||
delete: true, | delete: true, | ||||
}) | }) | ||||
}) | }) | ||||
).toMatchSnapshot('admin actions'); | ).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 () => { | it('should allow the QG to be set as the default', async () => { | ||||
return shallow<DetailsHeader>( | return shallow<DetailsHeader>( | ||||
<DetailsHeader | <DetailsHeader | ||||
onSetDefault={jest.fn()} | onSetDefault={jest.fn()} | ||||
qualityGate={mockQualityGate()} | |||||
qualityGate={mockQualityGate({ conditions: [mockCondition()] })} | |||||
refreshItem={jest.fn().mockResolvedValue(null)} | refreshItem={jest.fn().mockResolvedValue(null)} | ||||
refreshList={jest.fn().mockResolvedValue(null)} | refreshList={jest.fn().mockResolvedValue(null)} | ||||
{...props} | {...props} |
searchProjects | searchProjects | ||||
} from '../../../../api/quality-gates'; | } from '../../../../api/quality-gates'; | ||||
import { mockQualityGate } from '../../../../helpers/mocks/quality-gates'; | import { mockQualityGate } from '../../../../helpers/mocks/quality-gates'; | ||||
import { mockCondition } from '../../../../helpers/testMocks'; | |||||
import Projects from '../Projects'; | import Projects from '../Projects'; | ||||
const qualityGate = mockQualityGate(); | |||||
jest.mock('../../../../api/quality-gates', () => ({ | jest.mock('../../../../api/quality-gates', () => ({ | ||||
searchProjects: jest.fn().mockResolvedValue({ | searchProjects: jest.fn().mockResolvedValue({ | ||||
paging: { pageIndex: 1, pageSize: 3, total: 55 }, | paging: { pageIndex: 1, pageSize: 3, total: 55 }, | ||||
await waitAndUpdate(wrapper); | await waitAndUpdate(wrapper); | ||||
expect(wrapper.instance().mounted).toBe(true); | 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(searchProjects).toHaveBeenCalledWith( | ||||
expect.objectContaining({ | expect.objectContaining({ | ||||
gateName: qualityGate.name, | |||||
gateName: 'Foo', | |||||
page: 1, | page: 1, | ||||
pageSize: 100, | pageSize: 100, | ||||
query: undefined, | query: undefined, | ||||
}); | }); | ||||
function shallowRender(props: Partial<Projects['props']> = {}) { | 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 | <div | ||||
className="layout-page-main-inner" | 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)) | <Connect(withAppState(Conditions)) | ||||
conditions={Array []} | conditions={Array []} | ||||
metrics={Object {}} | metrics={Object {}} | ||||
onSaveCondition={[MockFunction]} | onSaveCondition={[MockFunction]} | ||||
qualityGate={ | qualityGate={ | ||||
Object { | Object { | ||||
"conditions": Array [], | |||||
"id": "1", | "id": "1", | ||||
"name": "qualitygate", | "name": "qualitygate", | ||||
} | } | ||||
className="layout-page-main-inner" | className="layout-page-main-inner" | ||||
> | > | ||||
<Connect(withAppState(Conditions)) | <Connect(withAppState(Conditions)) | ||||
conditions={Array []} | |||||
conditions={ | |||||
Array [ | |||||
Object { | |||||
"error": "10", | |||||
"id": 1, | |||||
"metric": "coverage", | |||||
"op": "LT", | |||||
}, | |||||
] | |||||
} | |||||
metrics={Object {}} | metrics={Object {}} | ||||
onAddCondition={[MockFunction]} | onAddCondition={[MockFunction]} | ||||
onRemoveCondition={[MockFunction]} | onRemoveCondition={[MockFunction]} | ||||
onSaveCondition={[MockFunction]} | onSaveCondition={[MockFunction]} | ||||
qualityGate={ | qualityGate={ | ||||
Object { | Object { | ||||
"conditions": Array [ | |||||
Object { | |||||
"error": "10", | |||||
"id": 1, | |||||
"metric": "coverage", | |||||
"op": "LT", | |||||
}, | |||||
], | |||||
"id": "1", | "id": "1", | ||||
"name": "qualitygate", | "name": "qualitygate", | ||||
} | } | ||||
key="1" | key="1" | ||||
qualityGate={ | qualityGate={ | ||||
Object { | Object { | ||||
"conditions": Array [ | |||||
Object { | |||||
"error": "10", | |||||
"id": 1, | |||||
"metric": "coverage", | |||||
"op": "LT", | |||||
}, | |||||
], | |||||
"id": "1", | "id": "1", | ||||
"name": "qualitygate", | "name": "qualitygate", | ||||
} | } |
> | > | ||||
<Component /> | <Component /> | ||||
</ModalButton> | </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) | <withRouter(DeleteQualityGateForm) | ||||
onDelete={[MockFunction]} | onDelete={[MockFunction]} | ||||
qualityGate={ | qualityGate={ | ||||
"rename": true, | "rename": true, | ||||
"setAsDefault": true, | "setAsDefault": true, | ||||
}, | }, | ||||
"conditions": Array [ | |||||
Object { | |||||
"error": "10", | |||||
"id": 1, | |||||
"metric": "coverage", | |||||
"op": "LT", | |||||
}, | |||||
], | |||||
"id": "1", | "id": "1", | ||||
"name": "qualitygate", | "name": "qualitygate", | ||||
} | } | ||||
</div> | </div> | ||||
</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 | // Jest Snapshot v1, https://goo.gl/fbAQLP | ||||
exports[`should render correctly 1`] = ` | |||||
exports[`should render correctly: default 1`] = ` | |||||
<SelectList | <SelectList | ||||
elements={ | elements={ | ||||
Array [ | Array [ | ||||
/> | /> | ||||
`; | `; | ||||
exports[`should render correctly 2`] = ` | |||||
exports[`should render correctly: known project 1`] = ` | |||||
<div | <div | ||||
className="select-list-list-item" | className="select-list-list-item" | ||||
> | > | ||||
</div> | </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 | <div | ||||
className="select-list-list-item" | className="select-list-list-item" | ||||
> | > |
quality_gates.rename=Rename Quality Gate | quality_gates.rename=Rename Quality Gate | ||||
quality_gates.delete=Delete Quality Gate | quality_gates.delete=Delete Quality Gate | ||||
quality_gates.copy=Copy 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=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=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.conditions.help.link=See also: Clean as You Code | ||||
quality_gates.projects.noResults=No Projects | quality_gates.projects.noResults=No Projects | ||||
quality_gates.projects.select_hint=Click to associate this project with the quality gate | 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.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.LT=is less than | ||||
quality_gates.operator.GT=is greater than | quality_gates.operator.GT=is greater than | ||||
quality_gates.operator.EQ=equals | quality_gates.operator.EQ=equals |