From: Wouter Admiraal Date: Thu, 12 Aug 2021 11:32:26 +0000 (+0200) Subject: SONAR-13154 Prevent assigning projects to Quality Gate with no conditions, or setting... X-Git-Tag: 9.1.0.47736~147 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=0971ca99e937be30a54965bf616f78ec4779d108;p=sonarqube.git SONAR-13154 Prevent assigning projects to Quality Gate with no conditions, or setting it as the default --- 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 ca5f4f0c64f..9f0476dfc33 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 @@ -19,6 +19,7 @@ */ 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'; @@ -40,6 +41,12 @@ export function DetailsContent(props: DetailsContentProps) { return (
+ {isDefault && (qualityGate.conditions === undefined || qualityGate.conditions.length === 0) && ( + + {translate('quality_gates.is_default_no_conditions')} + + )} + { render() { const { qualityGate } = this.props; const actions = qualityGate.actions || ({} as any); + const hasNoConditions = + qualityGate.conditions === undefined || qualityGate.conditions.length === 0; return (
@@ -101,12 +104,20 @@ export default class DetailsHeader extends React.PureComponent { )} {actions.setAsDefault && ( - + + + )} {actions.delete && ( { }; render() { + const { qualityGate } = this.props; + + if (qualityGate.conditions === undefined || qualityGate.conditions.length === 0) { + return ( +
{translate('quality_gates.projects.cannot_associate_projects_no_conditions')}
+ ); + } + return ( project.key)} diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/DetailsContent-test.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/DetailsContent-test.tsx index 10d18f4ddd9..8f8c3c7af2f 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/DetailsContent-test.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/DetailsContent-test.tsx @@ -20,11 +20,15 @@ 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 = {}) { @@ -34,7 +38,7 @@ function shallowRender(props: Partial = {}) { onAddCondition={jest.fn()} onRemoveCondition={jest.fn()} onSaveCondition={jest.fn()} - qualityGate={mockQualityGate()} + qualityGate={mockQualityGate({ conditions: [mockCondition()] })} {...props} /> ); diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/DetailsHeader-test.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/DetailsHeader-test.tsx index 0d7ba10bb1a..0fd8c9bf2f8 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/DetailsHeader-test.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/DetailsHeader-test.tsx @@ -22,6 +22,7 @@ import * as React from 'react'; 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', () => ({ @@ -32,12 +33,15 @@ beforeEach(jest.clearAllMocks); 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, @@ -47,6 +51,11 @@ it('should render correctly', () => { }) }) ).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 () => { @@ -79,7 +88,7 @@ function shallowRender(props: Partial = {}) { return shallow( ({ searchProjects: jest.fn().mockResolvedValue({ paging: { pageIndex: 1, pageSize: 3, total: 55 }, @@ -62,13 +61,16 @@ it('should render correctly', async () => { 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, @@ -108,5 +110,10 @@ it('should handle deselection properly', async () => { }); function shallowRender(props: Partial = {}) { - return shallow(); + return shallow( + + ); } diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/DetailsContent-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/DetailsContent-test.tsx.snap index 59ba0ebfd65..a8261ab3b2e 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/DetailsContent-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/DetailsContent-test.tsx.snap @@ -4,6 +4,72 @@ exports[`should render correctly: is default 1`] = `
+ +
+
+

+ quality_gates.projects +

+ + quality_gates.projects.help +
+ } + /> + + quality_gates.projects_for_default +
+
+`; + +exports[`should render correctly: is default, no conditions 1`] = ` +
+ + quality_gates.is_default_no_conditions + - + +
`; + +exports[`should render correctly: no conditions, cannot set as default 1`] = ` +
+
+
+
+

+ qualitygate +

+
+
+ + + +
+
+
+
+`; diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/Projects-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/Projects-test.tsx.snap index 58db09fcecd..417137c72cc 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/Projects-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/Projects-test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`should render correctly 1`] = ` +exports[`should render correctly: default 1`] = ` `; -exports[`should render correctly 2`] = ` +exports[`should render correctly: known project 1`] = `
@@ -44,7 +44,13 @@ exports[`should render correctly 2`] = `
`; -exports[`should render correctly 3`] = ` +exports[`should render correctly: quality gate without conditions 1`] = ` +
+ quality_gates.projects.cannot_associate_projects_no_conditions +
+`; + +exports[`should render correctly: unknown project 1`] = `
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 674543e8332..0fe177ed8ad 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -1638,6 +1638,8 @@ quality_gates.create=Create Quality Gate 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 @@ -1656,6 +1658,7 @@ quality_gates.projects.all=All 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