From: Grégoire Aubert Date: Thu, 30 Nov 2017 16:22:26 +0000 (+0100) Subject: SONAR-10151 Pass organization parameter in all quality gates ws X-Git-Tag: 7.0-RC1~151 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=762ed740328cab541c17af9a64ae933be407c65f;p=sonarqube.git SONAR-10151 Pass organization parameter in all quality gates ws --- diff --git a/server/sonar-web/src/main/js/api/quality-gates.ts b/server/sonar-web/src/main/js/api/quality-gates.ts index 90b03cbe338..93fd2fd6e07 100644 --- a/server/sonar-web/src/main/js/api/quality-gates.ts +++ b/server/sonar-web/src/main/js/api/quality-gates.ts @@ -17,16 +17,19 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { getJSON, post, postJSON, RequestData } from '../helpers/request'; +import { getJSON, post, postJSON } from '../helpers/request'; import throwGlobalError from '../app/utils/throwGlobalError'; -interface Condition { - error?: string; - id: number; +export interface ConditionBase { + error: string; metric: string; - op: string; + op?: string; period?: number; - warning?: string; + warning: string; +} + +export interface Condition extends ConditionBase { + id: number; } export interface QualityGate { @@ -45,15 +48,20 @@ export interface QualityGate { name: string; } -export function fetchQualityGates(): Promise<{ +export function fetchQualityGates(data: { + organization?: string; +}): Promise<{ actions: { create: boolean }; qualitygates: QualityGate[]; }> { - return getJSON('/api/qualitygates/list').catch(throwGlobalError); + return getJSON('/api/qualitygates/list', data).catch(throwGlobalError); } -export function fetchQualityGate(id: number): Promise { - return getJSON('/api/qualitygates/show', { id }).catch(throwGlobalError); +export function fetchQualityGate(data: { + id: number; + organization?: string; +}): Promise { + return getJSON('/api/qualitygates/show', data).catch(throwGlobalError); } export function createQualityGate(data: { @@ -63,8 +71,11 @@ export function createQualityGate(data: { return postJSON('/api/qualitygates/create', data).catch(throwGlobalError); } -export function deleteQualityGate(id: number): Promise { - return post('/api/qualitygates/destroy', { id }); +export function deleteQualityGate(data: { + id: number; + organization?: string; +}): Promise { + return post('/api/qualitygates/destroy', data).catch(throwGlobalError); } export function renameQualityGate(data: { @@ -83,46 +94,63 @@ export function copyQualityGate(data: { return postJSON('/api/qualitygates/copy', data).catch(throwGlobalError); } -export function setQualityGateAsDefault(id: number): Promise { - return post('/api/qualitygates/set_as_default', { id }).catch(throwGlobalError); +export function setQualityGateAsDefault(data: { + id: number; + organization?: string; +}): Promise { + return post('/api/qualitygates/set_as_default', data).catch(throwGlobalError); } -export function createCondition(gateId: number, condition: RequestData): Promise { - return postJSON('/api/qualitygates/create_condition', { ...condition, gateId }); +export function createCondition( + data: { + gateId: number; + organization?: string; + } & ConditionBase +): Promise { + return postJSON('/api/qualitygates/create_condition', data); } -export function updateCondition(condition: RequestData): Promise { - return postJSON('/api/qualitygates/update_condition', condition); +export function updateCondition(data: { organization?: string } & Condition): Promise { + return postJSON('/api/qualitygates/update_condition', data); } -export function deleteCondition(id: number): Promise { - return post('/api/qualitygates/delete_condition', { id }); +export function deleteCondition(data: { id: number; organization?: string }): Promise { + return post('/api/qualitygates/delete_condition', data); } -export function getGateForProject(project: string): Promise { - return getJSON('/api/qualitygates/get_by_project', { project }).then( +export function getGateForProject(data: { + organization?: string; + project: string; +}): Promise { + return getJSON('/api/qualitygates/get_by_project', data).then( ({ qualityGate }) => qualityGate && { ...qualityGate, isDefault: qualityGate.default - } + }, + throwGlobalError ); } -export function associateGateWithProject( - gateId: number, - projectKey: string -): Promise { - return post('/api/qualitygates/select', { gateId, projectKey }).catch(throwGlobalError); +export function associateGateWithProject(data: { + gateId: number; + organization?: string; + projectKey: string; +}): Promise { + return post('/api/qualitygates/select', data).catch(throwGlobalError); } -export function dissociateGateWithProject( - gateId: number, - projectKey: string -): Promise { - return post('/api/qualitygates/deselect', { gateId, projectKey }).catch(throwGlobalError); +export function dissociateGateWithProject(data: { + gateId: number; + organization?: string; + projectKey: string; +}): Promise { + return post('/api/qualitygates/deselect', data).catch(throwGlobalError); } -export function getApplicationQualityGate(application: string): Promise { - return getJSON('/api/qualitygates/application_status', { application }); +export function getApplicationQualityGate(data: { + application: string; + organization?: string; +}): Promise { + return getJSON('/api/qualitygates/application_status', data).catch(throwGlobalError); } diff --git a/server/sonar-web/src/main/js/apps/overview/components/App.js b/server/sonar-web/src/main/js/apps/overview/components/App.js index 8ce26596388..cab5eacead8 100644 --- a/server/sonar-web/src/main/js/apps/overview/components/App.js +++ b/server/sonar-web/src/main/js/apps/overview/components/App.js @@ -34,7 +34,8 @@ type Props = { id: string, key: string, qualifier: string, - tags: Array + tags: Array, + organization?: string }, onComponentChange: {} => void, router: Object diff --git a/server/sonar-web/src/main/js/apps/overview/qualityGate/ApplicationQualityGate.js b/server/sonar-web/src/main/js/apps/overview/qualityGate/ApplicationQualityGate.js index 8149f8963f4..92a248cec4a 100644 --- a/server/sonar-web/src/main/js/apps/overview/qualityGate/ApplicationQualityGate.js +++ b/server/sonar-web/src/main/js/apps/overview/qualityGate/ApplicationQualityGate.js @@ -27,7 +27,7 @@ import { translate } from '../../../helpers/l10n'; /*:: type Props = { - component: { key: string } + component: { key: string, organization?: string } }; */ @@ -68,8 +68,12 @@ export default class ApplicationQualityGate extends React.PureComponent { } fetchDetails = () => { + const { component } = this.props; this.setState({ loading: true }); - getApplicationQualityGate(this.props.component.key).then( + getApplicationQualityGate({ + application: component.key, + organization: component.organization + }).then( ({ status, projects, metrics }) => { if (this.mounted) { this.setState({ diff --git a/server/sonar-web/src/main/js/apps/overview/types.js b/server/sonar-web/src/main/js/apps/overview/types.js index bc3c77aaea7..5a5cdb5df77 100644 --- a/server/sonar-web/src/main/js/apps/overview/types.js +++ b/server/sonar-web/src/main/js/apps/overview/types.js @@ -23,7 +23,8 @@ export type Component = { id: string, key: string, - qualifier: string + qualifier: string, + organization?: string }; */ diff --git a/server/sonar-web/src/main/js/apps/projectQualityGate/App.tsx b/server/sonar-web/src/main/js/apps/projectQualityGate/App.tsx index 95b899a9aa3..5297b4ecbe4 100644 --- a/server/sonar-web/src/main/js/apps/projectQualityGate/App.tsx +++ b/server/sonar-web/src/main/js/apps/projectQualityGate/App.tsx @@ -35,6 +35,7 @@ import { translate } from '../../helpers/l10n'; interface Props { component: Component; + onComponentChange: (changes: {}) => void; } interface State { @@ -67,8 +68,12 @@ export default class App extends React.PureComponent { } fetchQualityGates() { + const { component } = this.props; this.setState({ loading: true }); - Promise.all([fetchQualityGates(), getGateForProject(this.props.component.key)]).then( + Promise.all([ + fetchQualityGates({ organization: component.organization }), + getGateForProject({ organization: component.organization, project: component.key }) + ]).then( ([{ qualitygates: allGates }, gate]) => { if (this.mounted) { this.setState({ allGates, gate, loading: false }); @@ -82,16 +87,21 @@ export default class App extends React.PureComponent { ); } - handleChangeGate = (oldId: number | undefined, newId: number | undefined) => { + handleChangeGate = (oldId?: number, newId?: number) => { const { allGates } = this.state; - if ((!oldId && !newId) || !allGates) { return Promise.resolve(); } + const { component } = this.props; + const requestData = { + gateId: newId ? newId : oldId!, + organization: component.organization, + projectKey: component.key + }; const request = newId - ? associateGateWithProject(newId, this.props.component.key) - : dissociateGateWithProject(oldId!, this.props.component.key); + ? associateGateWithProject(requestData) + : dissociateGateWithProject(requestData); return request.then(() => { if (this.mounted) { @@ -100,6 +110,7 @@ export default class App extends React.PureComponent { const newGate = allGates.find(gate => gate.id === newId); if (newGate) { this.setState({ gate: newGate }); + this.props.onComponentChange({ qualityGate: newGate }); } } else { this.setState({ gate: undefined }); diff --git a/server/sonar-web/src/main/js/apps/projectQualityGate/Form.tsx b/server/sonar-web/src/main/js/apps/projectQualityGate/Form.tsx index 909767bfed9..8b762e76f19 100644 --- a/server/sonar-web/src/main/js/apps/projectQualityGate/Form.tsx +++ b/server/sonar-web/src/main/js/apps/projectQualityGate/Form.tsx @@ -25,7 +25,7 @@ import { translate } from '../../helpers/l10n'; interface Props { allGates: QualityGate[]; gate?: QualityGate; - onChange: (oldGate: number | undefined, newGate: number) => Promise; + onChange: (oldGate?: number, newGate?: number) => Promise; } interface State { diff --git a/server/sonar-web/src/main/js/apps/projectQualityGate/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/projectQualityGate/__tests__/App-test.tsx index d04a0ee2f6e..2252afd252e 100644 --- a/server/sonar-web/src/main/js/apps/projectQualityGate/__tests__/App-test.tsx +++ b/server/sonar-web/src/main/js/apps/projectQualityGate/__tests__/App-test.tsx @@ -72,16 +72,18 @@ beforeEach(() => { it('checks permissions', () => { handleRequiredAuthorization.mockClear(); - mount(); + mount( + + ); expect(handleRequiredAuthorization).toBeCalled(); }); it('fetches quality gates', () => { fetchQualityGates.mockClear(); getGateForProject.mockClear(); - mount(); - expect(fetchQualityGates).toBeCalledWith(); - expect(getGateForProject).toBeCalledWith('component'); + mount(); + expect(fetchQualityGates).toBeCalledWith({ organization: 'org' }); + expect(getGateForProject).toBeCalledWith({ organization: 'org', project: 'component' }); }); it('changes quality gate from custom to default', () => { @@ -89,28 +91,44 @@ it('changes quality gate from custom to default', () => { const allGates = [gate, randomGate('bar', true), randomGate('baz')]; const wrapper = mountRender(allGates, gate); wrapper.find('Form').prop('onChange')('foo', 'bar'); - expect(associateGateWithProject).toBeCalledWith('bar', 'component'); + expect(associateGateWithProject).toBeCalledWith({ + gateId: 'bar', + organization: 'org', + projectKey: 'component' + }); }); it('changes quality gate from custom to custom', () => { const allGates = [randomGate('foo'), randomGate('bar', true), randomGate('baz')]; const wrapper = mountRender(allGates, randomGate('foo')); wrapper.find('Form').prop('onChange')('foo', 'baz'); - expect(associateGateWithProject).toBeCalledWith('baz', 'component'); + expect(associateGateWithProject).toBeCalledWith({ + gateId: 'baz', + organization: 'org', + projectKey: 'component' + }); }); it('changes quality gate from custom to none', () => { const allGates = [randomGate('foo'), randomGate('bar'), randomGate('baz')]; const wrapper = mountRender(allGates, randomGate('foo')); wrapper.find('Form').prop('onChange')('foo', undefined); - expect(dissociateGateWithProject).toBeCalledWith('foo', 'component'); + expect(dissociateGateWithProject).toBeCalledWith({ + gateId: 'foo', + organization: 'org', + projectKey: 'component' + }); }); it('changes quality gate from none to custom', () => { const allGates = [randomGate('foo'), randomGate('bar'), randomGate('baz')]; const wrapper = mountRender(allGates); wrapper.find('Form').prop('onChange')(undefined, 'baz'); - expect(associateGateWithProject).toBeCalledWith('baz', 'component'); + expect(associateGateWithProject).toBeCalledWith({ + gateId: 'baz', + organization: 'org', + projectKey: 'component' + }); }); function randomGate(id: string, isDefault = false) { @@ -118,7 +136,7 @@ function randomGate(id: string, isDefault = false) { } function mountRender(allGates: any[], gate?: any) { - const wrapper = mount(); + const wrapper = mount(); wrapper.setState({ allGates, loading: false, gate }); return wrapper; } diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/BuiltInQualityGateBadge.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/BuiltInQualityGateBadge.tsx index 5907e9d5770..7496c7cefe1 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/BuiltInQualityGateBadge.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/BuiltInQualityGateBadge.tsx @@ -36,8 +36,10 @@ export default function BuiltInQualityGateBadge({ className, tooltip = true }: P const overlay = (
-

{translate('quality_gates.built_in.description.1')}

-

{translate('quality_gates.built_in.description.2')}

+ {translate('quality_gates.built_in.description.1')} + + {translate('quality_gates.built_in.description.2')} +
); diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/Condition.js b/server/sonar-web/src/main/js/apps/quality-gates/components/Condition.js deleted file mode 100644 index c66890153f3..00000000000 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/Condition.js +++ /dev/null @@ -1,283 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2017 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 React, { Component } from 'react'; -import ThresholdInput from './ThresholdInput'; -import DeleteConditionView from '../views/gate-conditions-delete-view'; -import Checkbox from '../../../components/controls/Checkbox'; -import { createCondition, updateCondition } from '../../../api/quality-gates'; -import Select from '../../../components/controls/Select'; -import { translate, getLocalizedMetricName } from '../../../helpers/l10n'; -import { formatMeasure } from '../../../helpers/measures'; - -export default class Condition extends Component { - constructor(props) { - super(props); - this.state = { - changed: false, - period: props.condition.period, - op: props.condition.op, - warning: props.condition.warning || '', - error: props.condition.error || '' - }; - } - - componentDidMount() { - if (!this.props.condition.id && this.operator) { - this.operator.focus(); - } - } - - handleOperatorChange = ({ value }) => { - this.setState({ changed: true, op: value }); - }; - - handlePeriodChange = checked => { - const period = checked ? '1' : undefined; - this.setState({ changed: true, period }); - }; - - handleWarningChange = value => { - this.setState({ changed: true, warning: value }); - }; - - handleErrorChange = value => { - this.setState({ changed: true, error: value }); - }; - - handleSaveClick = e => { - const { qualityGate, condition, metric, onSaveCondition, onError, onResetError } = this.props; - const { period } = this.state; - const data = { - metric: condition.metric, - op: metric.type === 'RATING' ? 'GT' : this.state.op, - warning: this.state.warning, - error: this.state.error - }; - - if (period && metric.type !== 'RATING') { - data.period = period; - } - - if (metric.key.indexOf('new_') === 0) { - data.period = '1'; - } - - e.preventDefault(); - createCondition(qualityGate.id, data) - .then(newCondition => { - this.setState({ changed: false }); - onSaveCondition(condition, newCondition); - onResetError(); - }) - .catch(onError); - }; - - handleUpdateClick = e => { - const { condition, onSaveCondition, metric, onError, onResetError } = this.props; - const { period } = this.state; - const data = { - id: condition.id, - metric: condition.metric, - op: metric.type === 'RATING' ? 'GT' : this.state.op, - warning: this.state.warning, - error: this.state.error - }; - - if (period && metric.type !== 'RATING') { - data.period = period; - } - - if (metric.key.indexOf('new_') === 0) { - data.period = '1'; - } - - e.preventDefault(); - updateCondition(data) - .then(newCondition => { - this.setState({ changed: false }); - onSaveCondition(condition, newCondition); - onResetError(); - }) - .catch(onError); - }; - - handleDeleteClick = e => { - const { qualityGate, condition, metric, onDeleteCondition } = this.props; - - e.preventDefault(); - new DeleteConditionView({ - qualityGate, - condition, - metric, - onDelete: () => onDeleteCondition(condition) - }).render(); - }; - - handleCancelClick = e => { - const { condition, onDeleteCondition } = this.props; - - e.preventDefault(); - onDeleteCondition(condition); - }; - - renderPeriodValue() { - const { condition, metric } = this.props; - const isLeakSelected = !!this.state.period; - const isDiffMetric = condition.metric.indexOf('new_') === 0; - const isRating = metric.type === 'RATING'; - - if (isDiffMetric) { - return ( - {translate('quality_gates.condition.leak.unconditional')} - ); - } - - if (isRating) { - return {translate('quality_gates.condition.leak.never')}; - } - - return isLeakSelected - ? translate('quality_gates.condition.leak.yes') - : translate('quality_gates.condition.leak.no'); - } - - renderPeriod() { - const { condition, metric, edit } = this.props; - - const isDiffMetric = condition.metric.indexOf('new_') === 0; - const isRating = metric.type === 'RATING'; - const isLeakSelected = !!this.state.period; - - if (isRating || isDiffMetric || !edit) { - return this.renderPeriodValue(); - } - - return ; - } - - renderOperator() { - const { condition, edit, metric } = this.props; - - if (!edit) { - return metric.type === 'RATING' - ? translate('quality_gates.operator', condition.op, 'rating') - : translate('quality_gates.operator', condition.op); - } - - if (metric.type === 'RATING') { - return {translate('quality_gates.operator.GT.rating')}; - } - - const operators = ['LT', 'GT', 'EQ', 'NE']; - const operatorOptions = operators.map(op => { - const label = translate('quality_gates.operator', op); - return { label, value: op }; - }); - - return ( - + ); + } + + render() { + const { condition, edit, metric, organization } = this.props; + return ( + + + {getLocalizedMetricName(metric)} + {metric.hidden && ( + {translate('deprecated')} + )} + + + {this.renderPeriod()} + + {this.renderOperator()} + + + {edit ? ( + + ) : ( + formatMeasure(condition.warning, metric.type) + )} + + + + {edit ? ( + + ) : ( + formatMeasure(condition.error, metric.type) + )} + + + {edit && ( + + {condition.id ? ( +
+ + + {this.state.openDeleteCondition && ( + + )} +
+ ) : ( +
+ + + {translate('cancel')} + +
+ )} + + )} + + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/Conditions.js b/server/sonar-web/src/main/js/apps/quality-gates/components/Conditions.js index 7986f5a1816..547543cb215 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/Conditions.js +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/Conditions.js @@ -62,7 +62,8 @@ export default class Conditions extends React.PureComponent { edit, onAddCondition, onSaveCondition, - onDeleteCondition + onDeleteCondition, + organization } = this.props; const existingConditions = conditions.filter(condition => metrics[condition.metric]); @@ -130,6 +131,7 @@ export default class Conditions extends React.PureComponent { onDeleteCondition={onDeleteCondition} onError={this.handleError.bind(this)} onResetError={this.handleResetError.bind(this)} + organization={organization} /> ))} 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 new file mode 100644 index 00000000000..c4d1ea5fcfa --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/DeleteConditionForm.tsx @@ -0,0 +1,100 @@ +/* +* SonarQube +* Copyright (C) 2009-2017 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 Modal from '../../../components/controls/Modal'; +import { Metric } from '../../../app/types'; +import { Condition, deleteCondition } from '../../../api/quality-gates'; +import { getLocalizedMetricName, translate, translateWithParameters } from '../../../helpers/l10n'; + +interface Props { + condition: Condition; + metric: Metric; + onClose: () => void; + onDelete: (condition: Condition) => void; + organization?: string; +} + +interface State { + loading: boolean; +} + +export default class DeleteConditionForm extends React.PureComponent { + mounted: boolean; + state: State = { loading: false }; + + componentDidMount() { + this.mounted = true; + } + + componentWillUnmount() { + this.mounted = false; + } + + handleCancelClick = (event: React.SyntheticEvent) => { + event.preventDefault(); + this.props.onClose(); + }; + + handleFormSubmit = (event: React.SyntheticEvent) => { + event.preventDefault(); + const { organization, condition } = this.props; + this.setState({ loading: true }); + deleteCondition({ id: condition.id, organization }).then( + () => this.props.onDelete(condition), + () => { + if (this.mounted) { + this.setState({ loading: false }); + } + } + ); + }; + + render() { + const { metric } = this.props; + const header = translate('quality_gates.delete_condition'); + + return ( + +
+
+

{header}

+
+
+

+ {translateWithParameters( + 'quality_gates.delete_condition.confirm.message', + getLocalizedMetricName(metric) + )} +

+
+
+ {this.state.loading && } + + + {translate('cancel')} + +
+ +
+ ); + } +} diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/DeleteQualityGateForm.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/DeleteQualityGateForm.tsx index ab80d7e9df9..6bb533c415c 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/DeleteQualityGateForm.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/DeleteQualityGateForm.tsx @@ -62,7 +62,7 @@ export default class DeleteQualityGateForm extends React.PureComponent { this.props.onDelete(qualityGate); this.context.router.replace(getQualityGatesUrl(organization)); diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/Details.js b/server/sonar-web/src/main/js/apps/quality-gates/components/Details.js index b6ff64b5c87..a231079b6a2 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/Details.js +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/Details.js @@ -20,7 +20,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import Helmet from 'react-helmet'; -import { fetchQualityGate, setQualityGateAsDefault } from '../../../api/quality-gates'; +import { fetchQualityGate } from '../../../api/quality-gates'; import DetailsHeader from './DetailsHeader'; import DetailsContent from './DetailsContent'; @@ -41,10 +41,10 @@ export default class Details extends React.PureComponent { } fetchDetails = () => - fetchQualityGate(this.props.params.id).then( - qualityGate => this.props.onShow(qualityGate), - () => {} - ); + fetchQualityGate({ + id: this.props.params.id, + organization: this.props.organization && this.props.organization.key + }).then(qualityGate => this.props.onShow(qualityGate), () => {}); render() { const { organization, metrics, qualityGate } = this.props; @@ -72,6 +72,7 @@ export default class Details extends React.PureComponent { onAddCondition={onAddCondition} onSaveCondition={onSaveCondition} onDeleteCondition={onDeleteCondition} + organization={organization && organization.key} /> ); diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsContent.js b/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsContent.js index 0489459a58c..8a424be367e 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsContent.js +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsContent.js @@ -24,7 +24,7 @@ import { translate } from '../../../helpers/l10n'; export default class DetailsContent extends React.PureComponent { render() { - const { gate, metrics } = this.props; + const { gate, metrics, organization } = this.props; const { onAddCondition, onDeleteCondition, onSaveCondition } = this.props; const conditions = gate.conditions || []; const actions = gate.actions || {}; @@ -43,6 +43,7 @@ export default class DetailsContent extends React.PureComponent { onAddCondition={onAddCondition} onSaveCondition={onSaveCondition} onDeleteCondition={onDeleteCondition} + organization={organization} />
@@ -50,7 +51,11 @@ export default class DetailsContent extends React.PureComponent { {gate.isDefault ? ( defaultMessage ) : ( - + )}
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsHeader.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsHeader.tsx index aa3d836b237..f781cc253b8 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsHeader.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsHeader.tsx @@ -22,7 +22,7 @@ import BuiltInQualityGateBadge from './BuiltInQualityGateBadge'; import RenameQualityGateForm from './RenameQualityGateForm'; import CopyQualityGateForm from './CopyQualityGateForm'; import DeleteQualityGateForm from './DeleteQualityGateForm'; -import { QualityGate, setQualityGateAsDefault } from '../../../api/quality-gates'; +import { fetchQualityGate, QualityGate, setQualityGateAsDefault } from '../../../api/quality-gates'; import { translate } from '../../../helpers/l10n'; interface Props { @@ -53,9 +53,11 @@ export default class DetailsHeader extends React.PureComponent { handleSetAsDefaultClick = (e: React.SyntheticEvent) => { e.preventDefault(); - const { qualityGate, onSetAsDefault } = this.props; + const { qualityGate, onSetAsDefault, organization } = this.props; if (!qualityGate.isDefault) { - setQualityGateAsDefault(qualityGate.id).then(() => onSetAsDefault(qualityGate), () => {}); + setQualityGateAsDefault({ id: qualityGate.id, organization }) + .then(() => fetchQualityGate({ id: qualityGate.id, organization })) + .then(qualityGate => onSetAsDefault(qualityGate), () => {}); } }; diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/Projects.js b/server/sonar-web/src/main/js/apps/quality-gates/components/Projects.js index 33a9c447d13..344bf1e2881 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/Projects.js +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/Projects.js @@ -44,12 +44,13 @@ export default class Projects extends React.PureComponent { } renderView() { - const { qualityGate, edit } = this.props; + const { qualityGate, edit, organization } = this.props; this.projectsView = new ProjectsView({ qualityGate, edit, - container: this.refs.container + container: this.refs.container, + organization }); this.projectsView.render(); } diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatesApp.js b/server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatesApp.js index 96c3ed76f9a..0ec8d28794d 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatesApp.js +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatesApp.js @@ -51,15 +51,20 @@ export default class QualityGatesApp extends Component { } fetchQualityGates = () => - fetchQualityGates().then(({ actions, qualitygates: qualityGates }) => { - const { organization, updateStore } = this.props; - updateStore({ actions, qualityGates }); - if (qualityGates && qualityGates.length === 1 && !actions.create) { - this.context.router.replace( - getQualityGateUrl(String(qualityGates[0].id), organization && organization.key) - ); - } - }); + fetchQualityGates({ + organization: this.props.organization && this.props.organization.key + }).then( + ({ actions, qualitygates: qualityGates }) => { + const { organization, updateStore } = this.props; + updateStore({ actions, qualityGates }); + if (qualityGates && qualityGates.length === 1 && !actions.create) { + this.context.router.replace( + getQualityGateUrl(String(qualityGates[0].id), organization && organization.key) + ); + } + }, + () => {} + ); render() { const { children, qualityGates, actions, organization } = this.props; diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/ThresholdInput.js b/server/sonar-web/src/main/js/apps/quality-gates/components/ThresholdInput.js deleted file mode 100644 index b00a7e8419f..00000000000 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/ThresholdInput.js +++ /dev/null @@ -1,88 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2017 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 React from 'react'; -import PropTypes from 'prop-types'; -import Select from '../../../components/controls/Select'; - -export default class ThresholdInput extends React.PureComponent { - static propTypes = { - name: PropTypes.string.isRequired, - value: PropTypes.any, - metric: PropTypes.object.isRequired, - onChange: PropTypes.func.isRequired - }; - - handleChange = e => { - this.props.onChange(e.target.value); - }; - - handleSelectChange = option => { - if (option) { - this.props.onChange(option.value); - } else { - this.props.onChange(''); - } - }; - - renderRatingInput() { - const { name, value } = this.props; - - const options = [ - { label: 'A', value: '1' }, - { label: 'B', value: '2' }, - { label: 'C', value: '3' }, - { label: 'D', value: '4' } - ]; - - const realValue = value === '' ? null : value; - - return ( - - ); - } -} diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/ThresholdInput.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/ThresholdInput.tsx new file mode 100644 index 00000000000..cf60765a6f3 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/ThresholdInput.tsx @@ -0,0 +1,85 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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 Select from '../../../components/controls/Select'; +import { Metric } from '../../../app/types'; + +interface Props { + name: string; + value: string; + metric: Metric; + onChange: (value: string) => void; +} + +export default class ThresholdInput extends React.PureComponent { + handleChange = (e: React.SyntheticEvent) => { + this.props.onChange(e.currentTarget.value); + }; + + handleSelectChange = (option: any) => { + if (option) { + this.props.onChange(option.value); + } else { + this.props.onChange(''); + } + }; + + renderRatingInput() { + const { name, value } = this.props; + + const options = [ + { label: 'A', value: '1' }, + { label: 'B', value: '2' }, + { label: 'C', value: '3' }, + { label: 'D', value: '4' } + ]; + + return ( + + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/ThresholdInput-test.js b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/ThresholdInput-test.js deleted file mode 100644 index 32727393559..00000000000 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/ThresholdInput-test.js +++ /dev/null @@ -1,71 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2017 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 React from 'react'; -import { shallow } from 'enzyme'; -import ThresholdInput from '../ThresholdInput'; -import { change } from '../../../../helpers/testUtils'; - -describe('on strings', () => { - it('should render text input', () => { - const input = shallow( - - ).find('input'); - expect(input.length).toEqual(1); - expect(input.prop('name')).toEqual('foo'); - expect(input.prop('value')).toEqual('2'); - }); - - it('should change', () => { - const onChange = jest.fn(); - const input = shallow( - - ).find('input'); - change(input, 'bar'); - expect(onChange).toBeCalledWith('bar'); - }); -}); - -describe('on ratings', () => { - it('should render Select', () => { - const select = shallow( - - ).find('Select'); - expect(select.length).toEqual(1); - expect(select.prop('value')).toEqual('2'); - }); - - it('should set', () => { - const onChange = jest.fn(); - const select = shallow( - - ).find('Select'); - select.prop('onChange')({ label: 'D', value: '4' }); - expect(onChange).toBeCalledWith('4'); - }); - - it('should unset', () => { - const onChange = jest.fn(); - const select = shallow( - - ).find('Select'); - select.prop('onChange')(null); - expect(onChange).toBeCalledWith(''); - }); -}); diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/ThresholdInput-test.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/ThresholdInput-test.tsx new file mode 100644 index 00000000000..42f29a6a6a8 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/ThresholdInput-test.tsx @@ -0,0 +1,73 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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 { shallow } from 'enzyme'; +import ThresholdInput from '../ThresholdInput'; +import { change } from '../../../../helpers/testUtils'; + +describe('on strings', () => { + const metric = { key: 'foo', name: 'Foo', type: 'INTEGER' }; + it('should render text input', () => { + const input = shallow( + + ).find('input'); + expect(input.length).toEqual(1); + expect(input.prop('name')).toEqual('foo'); + expect(input.prop('value')).toEqual('2'); + }); + + it('should change', () => { + const onChange = jest.fn(); + const input = shallow( + + ).find('input'); + change(input, 'bar'); + expect(onChange).toBeCalledWith('bar'); + }); +}); + +describe('on ratings', () => { + const metric = { key: 'foo', name: 'Foo', type: 'RATING' }; + it('should render Select', () => { + const select = shallow( + + ).find('Select'); + expect(select.length).toEqual(1); + expect(select.prop('value')).toEqual('2'); + }); + + it('should set', () => { + const onChange = jest.fn(); + const select = shallow( + + ).find('Select'); + (select.prop('onChange') as Function)({ label: 'D', value: '4' }); + expect(onChange).toBeCalledWith('4'); + }); + + it('should unset', () => { + const onChange = jest.fn(); + const select = shallow( + + ).find('Select'); + (select.prop('onChange') as Function)(null); + expect(onChange).toBeCalledWith(''); + }); +}); diff --git a/server/sonar-web/src/main/js/apps/quality-gates/store/actions.js b/server/sonar-web/src/main/js/apps/quality-gates/store/actions.js index ad94c833969..e8b688273b2 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/store/actions.js +++ b/server/sonar-web/src/main/js/apps/quality-gates/store/actions.js @@ -66,7 +66,7 @@ export function copyQualityGate(qualityGate) { }; } -export const SET_AS_DEFAULT = 'SET_AS_DEFAULT'; +export const SET_AS_DEFAULT = 'qualityGates/SET_AS_DEFAULT'; export function setQualityGateAsDefault(qualityGate) { return { type: SET_AS_DEFAULT, diff --git a/server/sonar-web/src/main/js/apps/quality-gates/store/rootReducer.js b/server/sonar-web/src/main/js/apps/quality-gates/store/rootReducer.js index 56078fc6518..b63d134e2ac 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/store/rootReducer.js +++ b/server/sonar-web/src/main/js/apps/quality-gates/store/rootReducer.js @@ -70,7 +70,7 @@ export default function rootReducer(state = initialState, action = {}) { return { ...candidate, isDefault: candidate.id === action.qualityGate.id }; }), qualityGate: { - ...state.qualityGate, + ...action.qualityGate, isDefault: state.qualityGate.id === action.qualityGate.id } }; diff --git a/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gates-condition-delete.hbs b/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gates-condition-delete.hbs deleted file mode 100644 index 5ca1ac2944c..00000000000 --- a/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gates-condition-delete.hbs +++ /dev/null @@ -1,13 +0,0 @@ -
- - - -
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/views/gate-conditions-delete-view.js b/server/sonar-web/src/main/js/apps/quality-gates/views/gate-conditions-delete-view.js deleted file mode 100644 index f9f01acf285..00000000000 --- a/server/sonar-web/src/main/js/apps/quality-gates/views/gate-conditions-delete-view.js +++ /dev/null @@ -1,54 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2017 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 ModalForm from '../../../components/common/modal-form'; -import Template from '../templates/quality-gates-condition-delete.hbs'; -import { deleteCondition } from '../../../api/quality-gates'; -import { getLocalizedMetricName } from '../../../helpers/l10n'; -import { parseError } from '../../../helpers/request'; - -export default ModalForm.extend({ - template: Template, - - onFormSubmit() { - ModalForm.prototype.onFormSubmit.apply(this, arguments); - this.disableForm(); - this.sendRequest(); - }, - - sendRequest() { - return deleteCondition(this.options.condition.id).then( - () => { - this.destroy(); - this.options.onDelete(); - }, - error => { - this.enableForm(); - parseError(error).then(msg => this.showErrors([{ msg }])); - } - ); - }, - - serializeData() { - return { - metric: this.options.metric, - localizedMetricName: getLocalizedMetricName(this.options.metric) - }; - } -}); diff --git a/server/sonar-web/src/main/js/apps/quality-gates/views/gate-projects-view.js b/server/sonar-web/src/main/js/apps/quality-gates/views/gate-projects-view.js index 1b54745a857..6c648020b99 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/views/gate-projects-view.js +++ b/server/sonar-web/src/main/js/apps/quality-gates/views/gate-projects-view.js @@ -25,18 +25,16 @@ import { translate } from '../../../helpers/l10n'; export default Marionette.ItemView.extend({ template: () => {}, - initialize(options) { - this.organization = options.organization; - }, - onRender() { - const { qualityGate } = this.options; + const { qualityGate, organization } = this.options; const extra = { gateId: qualityGate.id }; - if (this.organization) { - extra.organization = this.organization.key; + let orgQuery = ''; + if (organization) { + extra.organization = organization; + orgQuery = '&organization=' + organization; } new SelectList({ @@ -47,7 +45,7 @@ export default Marionette.ItemView.extend({ dangerouslyUnescapedHtmlFormat(item) { return escapeHtml(item.name); }, - searchUrl: window.baseUrl + '/api/qualitygates/search?gateId=' + qualityGate.id, + searchUrl: `${window.baseUrl}/api/qualitygates/search?gateId=${qualityGate.id}${orgQuery}`, selectUrl: window.baseUrl + '/api/qualitygates/select', deselectUrl: window.baseUrl + '/api/qualitygates/deselect', extra,