diff options
66 files changed, 1643 insertions, 1318 deletions
diff --git a/it/it-tests/src/test/resources/qualityGate/QualityGateUiTest/should_display_quality_gates_page.html b/it/it-tests/src/test/resources/qualityGate/QualityGateUiTest/should_display_quality_gates_page.html index a702ec9e45f..22e16831674 100644 --- a/it/it-tests/src/test/resources/qualityGate/QualityGateUiTest/should_display_quality_gates_page.html +++ b/it/it-tests/src/test/resources/qualityGate/QualityGateUiTest/should_display_quality_gates_page.html @@ -40,17 +40,17 @@ </tr> <tr> <td>waitForElementPresent</td> - <td>css=.js-list a</td> + <td>css=.quality-gates-results a</td> <td></td> </tr> <tr> <td>click</td> - <td>css=.js-list a</td> + <td>css=.quality-gates-results a</td> <td></td> </tr> <tr> <td>waitForElementPresent</td> - <td>css=.js-conditions</td> + <td>id=quality-gate-conditions</td> <td></td> </tr> </tbody> diff --git a/server/sonar-web/package.json b/server/sonar-web/package.json index 662070ef3f0..b68a902c342 100644 --- a/server/sonar-web/package.json +++ b/server/sonar-web/package.json @@ -57,6 +57,7 @@ "react-dom": "0.14.2", "react-redux": "4.0.1", "react-router": "2.0.0", + "react-router-redux": "^4.0.0", "react-select": "1.0.0-beta6", "redux": "3.0.5", "redux-logger": "2.2.1", diff --git a/server/sonar-web/src/main/js/api/quality-gates.js b/server/sonar-web/src/main/js/api/quality-gates.js new file mode 100644 index 00000000000..a32b0295d1d --- /dev/null +++ b/server/sonar-web/src/main/js/api/quality-gates.js @@ -0,0 +1,87 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { getJSON, post, postJSON } from '../helpers/request'; + +export function fetchQualityGatesAppDetails () { + const url = '/api/qualitygates/app'; + + return getJSON(url); +} + +export function fetchQualityGates () { + const url = '/api/qualitygates/list'; + + return getJSON(url).then(r => r.qualitygates.map(qualityGate => { + return { + ...qualityGate, + isDefault: qualityGate.id === r.default + }; + })); +} + +export function fetchQualityGate (id) { + const url = '/api/qualitygates/show'; + return getJSON(url, { id }); +} + +export function createQualityGate (name) { + const url = '/api/qualitygates/create'; + return postJSON(url, { name }); +} + +export function deleteQualityGate (id) { + const url = '/api/qualitygates/destroy'; + return post(url, { id }); +} + +export function renameQualityGate (id, name) { + const url = '/api/qualitygates/rename'; + return post(url, { id, name }); +} + +export function copyQualityGate (id, name) { + const url = '/api/qualitygates/copy'; + return postJSON(url, { id, name }); +} + +export function setQualityGateAsDefault (id) { + const url = '/api/qualitygates/set_as_default'; + return post(url, { id }); +} + +export function unsetQualityGateAsDefault (id) { + const url = '/api/qualitygates/unset_default'; + return post(url, { id }); +} + +export function createCondition (gateId, condition) { + const url = '/api/qualitygates/create_condition'; + return postJSON(url, { ...condition, gateId }); +} + +export function updateCondition (condition) { + const url = '/api/qualitygates/update_condition'; + return postJSON(url, { ...condition }); +} + +export function deleteCondition (id) { + const url = '/api/qualitygates/delete_condition'; + return post(url, { id }); +} diff --git a/server/sonar-web/src/main/js/apps/quality-gates/app.js b/server/sonar-web/src/main/js/apps/quality-gates/app.js index 4cae39632a8..43834a5ceea 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/app.js +++ b/server/sonar-web/src/main/js/apps/quality-gates/app.js @@ -17,66 +17,45 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import $ from 'jquery'; -import Backbone from 'backbone'; -import Marionette from 'backbone.marionette'; -import Gates from './gates'; -import GatesView from './gates-view'; -import ActionsView from './actions-view'; -import Router from './router'; -import Layout from './layout'; -import Controller from './controller'; - -const App = new Marionette.Application(); - -const init = function () { - const options = window.sonarqube; - // Layout - this.layout = new Layout({ el: options.el }); - this.layout.render(); - $('#footer').addClass('search-navigator-footer'); - - // Gates List - this.gates = new Gates(); - - // Controller - this.controller = new Controller({ app: this }); - - // Header - this.actionsView = new ActionsView({ - canEdit: this.canEdit, - collection: this.gates - }); - this.layout.actionsRegion.show(this.actionsView); - - // List - this.gatesView = new GatesView({ - canEdit: this.canEdit, - collection: this.gates +import React from 'react'; +import { render } from 'react-dom'; +import { Router, Route, IndexRoute, Redirect, useRouterHistory } from 'react-router'; +import { createHistory } from 'history'; +import { combineReducers } from 'redux'; +import { Provider } from 'react-redux'; +import { syncHistoryWithStore, routerReducer, routerMiddleware } from 'react-router-redux'; + +import QualityGatesAppContainer from './containers/QualityGatesAppContainer'; +import Intro from './components/Intro'; +import DetailsContainer from './containers/DetailsContainer'; +import rootReducer from './store/reducers'; +import configureStore from '../../components/store/configureStore'; + +window.sonarqube.appStarted.then(options => { + const el = document.querySelector(options.el); + + const history = useRouterHistory(createHistory)({ + basename: '/quality_gates' }); - this.layout.resultsRegion.show(this.gatesView); - // Router - this.router = new Router({ app: this }); - Backbone.history.start({ - pushState: true, - root: options.urlRoot + const finalReducer = combineReducers({ + rootReducer, + routing: routerReducer }); -}; - -const appXHR = $.get('/api/qualitygates/app') - .done(function (r) { - App.canEdit = r.edit; - App.periods = r.periods; - App.metrics = r.metrics; - }); -App.on('start', function (options) { - appXHR.done(function () { - init.call(App, options); - }); + const store = configureStore(finalReducer, [routerMiddleware(history)]); + + const finalHistory = syncHistoryWithStore(history, store); + + render(( + <Provider store={store}> + <Router history={finalHistory}> + <Route path="/" component={QualityGatesAppContainer}> + <IndexRoute component={Intro}/> + <Route path="show/:id" component={DetailsContainer}/> + <Redirect from="/index" to="/"/> + </Route> + </Router> + </Provider> + ), el); }); - -window.sonarqube.appStarted.then(options => App.start(options)); - - diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/AddConditionForm.js b/server/sonar-web/src/main/js/apps/quality-gates/components/AddConditionForm.js new file mode 100644 index 00000000000..6cd0229391e --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/AddConditionForm.js @@ -0,0 +1,67 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 _ from 'underscore'; +import React from 'react'; +import Select from 'react-select'; + +import { translate } from '../../../helpers/l10n'; + +export default function AddConditionForm ({ metrics, onSelect }) { + function handleChange (option) { + const metric = option.value; + // e.target.value = ''; + onSelect(metric); + } + + const metricsToDisplay = metrics.filter(metric => !metric.hidden); + const sortedMetrics = _.sortBy(metricsToDisplay, 'domain'); + const options = sortedMetrics.map(metric => { + return { + value: metric.key, + label: metric.name, + domain: metric.domain + }; + }); + + // use "disabled" property to emulate optgroups + const optionsWithDomains = []; + options.forEach((option, index, options) => { + const previous = index > 0 ? options[index - 1] : null; + if (!previous || previous.domain !== option.domain) { + optionsWithDomains.push({ + value: option.domain, + label: option.domain, + disabled: true + }); + } + optionsWithDomains.push(option); + }); + + return ( + <div className="big-spacer-top panel bg-muted"> + <Select + id="quality-gate-new-condition-metric" + className="text-middle input-large" + options={optionsWithDomains} + placeholder={translate('quality_gates.add_condition')} + onChange={handleChange}/> + </div> + ); +} 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 new file mode 100644 index 00000000000..550b8905499 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/Condition.js @@ -0,0 +1,253 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 Select from 'react-select'; + +import DeleteConditionView from '../views/gate-conditions-delete-view'; +import Checkbox from '../../../components/shared/checkbox'; +import { createCondition, updateCondition } from '../../../api/quality-gates'; +import { translate } from '../../../helpers/l10n'; +import { formatMeasure } from '../../../helpers/measures'; + +export default class Condition extends Component { + constructor (props) { + super(props); + this.handleChange = this.handleChange.bind(this); + this.state = { + changed: false, + period: props.condition.period, + op: props.condition.op + }; + } + + componentDidMount () { + const { condition } = this.props; + + if (!condition.id) { + this.refs.operator.focus(); + } + } + + handleChange () { + this.setState({ changed: true }); + } + + handleOperatorChange (option) { + const { value } = option; + this.setState({ changed: true, op: value }); + } + + handlePeriodChange (checked) { + const period = checked ? '1' : undefined; + this.setState({ changed: true, period }); + } + + handleSaveClick (e) { + const { qualityGate, condition, onSaveCondition } = this.props; + const period = this.state.period; + const data = { + metric: condition.metric, + op: this.state.op, + warning: this.refs.warning.value, + error: this.refs.error.value + }; + + if (period) { + data.period = period; + } + + e.preventDefault(); + createCondition(qualityGate.id, data).then(newCondition => { + this.setState({ changed: false }); + onSaveCondition(condition, newCondition); + }); + } + + handleUpdateClick (e) { + const { condition, onSaveCondition } = this.props; + const period = this.state.period; + const data = { + id: condition.id, + metric: condition.metric, + op: this.state.op, + warning: this.refs.warning.value, + error: this.refs.error.value + }; + + if (period) { + data.period = period; + } + + e.preventDefault(); + updateCondition(data).then(newCondition => { + this.setState({ changed: false }); + onSaveCondition(condition, newCondition); + }); + } + + handleDeleteClick (e) { + const { qualityGate, condition, metrics, onDeleteCondition } = this.props; + const metric = metrics.find(metric => metric.key === condition.metric); + + 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 } = this.props; + const isLeakSelected = !!this.state.period; + const isDiffMetric = condition.metric.indexOf('new_') === 0; + + if (isDiffMetric) { + return ( + <span className="note"> + {translate('quality_gates.condition.leak.unconditional')} + </span> + ); + } else { + return isLeakSelected ? + translate('quality_gates.condition.leak.yes') : + translate('quality_gates.condition.leak.no'); + } + } + + render () { + const { condition, edit, metrics } = this.props; + const metric = metrics.find(metric => metric.key === condition.metric); + const isDiffMetric = condition.metric.indexOf('new_') === 0; + const isLeakSelected = !!this.state.period; + const operators = ['LT', 'GT', 'EQ', 'NE']; + const operatorOptions = operators.map(op => { + return { + label: translate('quality_gates.operator', op), + value: op + }; + }); + + return ( + <tr> + <td className="text-middle nowrap"> + {metric.name} + {metric.hidden && ( + <span className="text-danger little-spacer-left"> + {translate('deprecated')} + </span> + )} + </td> + + <td className="thin text-middle nowrap"> + {(edit && !isDiffMetric) ? ( + <Checkbox + initiallyChecked={isLeakSelected} + onCheck={this.handlePeriodChange.bind(this)}/> + ) : this.renderPeriodValue()} + </td> + + <td className="thin text-middle nowrap"> + {edit ? ( + <Select + ref="operator" + className="input-medium" + name="operator" + value={this.state.op} + clearable={false} + searchable={false} + options={operatorOptions} + onChange={this.handleOperatorChange.bind(this)}/> + ) : translate('quality_gates.operator', condition.op)} + </td> + + <td className="thin text-middle nowrap"> + {edit ? ( + <input + ref="warning" + name="warning" + type="text" + className="input-tiny text-middle" + defaultValue={condition.warning} + data-type={metric.type} + placeholder={metric.placeholder} + onChange={this.handleChange}/> + ) : formatMeasure(condition.warning, metric.type)} + </td> + + <td className="thin text-middle nowrap"> + {edit ? ( + <input + ref="error" + name="error" + type="text" + className="input-tiny text-middle" + defaultValue={condition.error} + data-type={metric.type} + onChange={this.handleChange}/> + ) : formatMeasure(condition.error, metric.type)} + </td> + + {edit && ( + <td className="thin text-middle nowrap"> + {condition.id ? ( + <div className="button-group"> + <button + className="update-condition" + disabled={!this.state.changed} + onClick={this.handleUpdateClick.bind(this)}> + {translate('update_verb')} + </button> + <button + className="button-red delete-condition" + onClick={this.handleDeleteClick.bind(this)}> + {translate('delete')} + </button> + </div> + ) : ( + <div className="button-group"> + <button + className="add-condition" + onClick={this.handleSaveClick.bind(this)}> + {translate('add_verb')} + </button> + <a + className="action cancel-add-condition" + href="#" + onClick={this.handleCancelClick.bind(this)}> + {translate('cancel')} + </a> + </div> + )} + </td> + )} + + </tr> + ); + } +} 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 new file mode 100644 index 00000000000..fc7e90f4e88 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/Conditions.js @@ -0,0 +1,104 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 _ from 'underscore'; +import React from 'react'; + +import ConditionsAlert from './ConditionsAlert'; +import AddConditionForm from './AddConditionForm'; +import Condition from './Condition'; +import { translate } from '../../../helpers/l10n'; + +function getKey (condition, index) { + return condition.id ? condition.id : `new-${index}`; +} + +export default function Conditions ( + { + qualityGate, + conditions, + metrics, + periods, + edit, + onAddCondition, + onSaveCondition, + onDeleteCondition + } +) { + const sortedConditions = _.sortBy(conditions, condition => { + return metrics.find(metric => metric.key === condition.metric).name; + }); + + + return ( + <div id="quality-gate-conditions" className="quality-gate-section"> + <h3 className="spacer-bottom"> + {translate('quality_gates.conditions')} + </h3> + + <ConditionsAlert/> + + {sortedConditions.length ? ( + <table id="quality-gate-conditions" className="data zebra zebra-hover"> + <thead> + <tr> + <th className="nowrap"> + {translate('quality_gates.conditions.metric')} + </th> + <th className="thin nowrap"> + {translate('quality_gates.conditions.leak')} + </th> + <th className="thin nowrap"> + {translate('quality_gates.conditions.operator')} + </th> + <th className="thin nowrap"> + {translate('quality_gates.conditions.warning')} + </th> + <th className="thin nowrap"> + {translate('quality_gates.conditions.error')} + </th> + {edit && <th></th>} + </tr> + </thead> + <tbody> + {sortedConditions.map((condition, index) => ( + <Condition + key={getKey(condition, index)} + qualityGate={qualityGate} + condition={condition} + metrics={metrics} + periods={periods} + edit={edit} + onSaveCondition={onSaveCondition} + onDeleteCondition={onDeleteCondition}/> + ))} + </tbody> + </table> + ) : ( + <div className="big-spacer-top"> + {translate('quality_gates.no_conditions')} + </div> + )} + + {edit && ( + <AddConditionForm metrics={metrics} onSelect={onAddCondition}/> + )} + </div> + ); +} diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionsAlert.js b/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionsAlert.js new file mode 100644 index 00000000000..c2f8d3274a5 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionsAlert.js @@ -0,0 +1,73 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { translate } from '../../../helpers/l10n'; + +export default class ConditionsAlert extends Component { + state = { + expanded: false + }; + + handleMoreClick (e) { + e.preventDefault(); + this.setState({ expanded: true }); + } + + render () { + const { expanded } = this.state; + + return ( + <div className="big-spacer-bottom"> + {translate('quality_gates.introduction')} + {!expanded && ( + <a + className="spacer-left" + href="#" + onClick={this.handleMoreClick.bind(this)}> + {translate('more')} + </a> + )} + {expanded && ( + <div className="spacer-top"> + {translate('quality_gates.health_icons')} + <ul> + <li className="little-spacer-top"> + <i className="icon-alert-ok"></i> + {' '} + {translate('alerts.notes.ok')} + </li> + <li className="little-spacer-top"> + <i className="icon-alert-warn"></i> + {' '} + {translate('alerts.notes.warn')} + </li> + <li className="little-spacer-top"> + <i className="icon-alert-error"></i> + {' '} + {translate('alerts.notes.error')} + </li> + </ul> + </div> + )} + </div> + ); + } +} 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 new file mode 100644 index 00000000000..7c40c9f1f55 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/Details.js @@ -0,0 +1,138 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { fetchQualityGate, setQualityGateAsDefault, unsetQualityGateAsDefault } from '../../../api/quality-gates'; +import DetailsHeader from './DetailsHeader'; +import DetailsContent from './DetailsContent'; +import RenameView from '../views/rename-view'; +import CopyView from '../views/copy-view'; +import DeleteView from '../views/delete-view'; + +export default class Details extends Component { + componentDidMount () { + this.fetchDetails(); + } + + componentDidUpdate (nextProps) { + if (nextProps.params.id !== this.props.params.id) { + this.fetchDetails(); + } + } + + fetchDetails () { + const { id } = this.props.params; + const { onShow } = this.props; + + fetchQualityGate(id).then(qualityGate => { + onShow(qualityGate); + }); + } + + handleRenameClick () { + const { qualityGate, onRename } = this.props; + + new RenameView({ + qualityGate, + onRename: (qualityGate, newName) => { + onRename(qualityGate, newName); + } + }).render(); + } + + handleCopyClick () { + const { qualityGate, onCopy } = this.props; + const { router } = this.context; + + new CopyView({ + qualityGate, + onCopy: (newQualityGate) => { + onCopy(newQualityGate); + router.push(`/show/${newQualityGate.id}`); + } + }).render(); + } + + handleSetAsDefaultClick () { + const { qualityGate, onSetAsDefault, onUnsetAsDefault } = this.props; + + if (qualityGate.isDefault) { + unsetQualityGateAsDefault(qualityGate.id) + .then(() => onUnsetAsDefault(qualityGate)); + } else { + setQualityGateAsDefault(qualityGate.id) + .then(() => onSetAsDefault(qualityGate)); + } + } + + handleDeleteClick () { + const { qualityGate, onDelete } = this.props; + const { router } = this.context; + + new DeleteView({ + qualityGate, + onDelete: (qualityGate) => { + onDelete(qualityGate); + router.replace('/'); + } + }).render(); + } + + render () { + const { qualityGate, edit, metrics, periods } = this.props; + const { onAddCondition, onDeleteCondition, onSaveCondition } = this.props; + + if (!qualityGate) { + return ( + <div className="search-navigator-workspace"> + <div className="search-navigator-workspace-header" style={{ top: 30 }}> + <h2 className="search-navigator-header-component"> </h2> + </div> + <div className="search-navigator-workspace-details"></div> + </div> + ); + } + + return ( + <div className="search-navigator-workspace"> + <DetailsHeader + qualityGate={qualityGate} + edit={edit} + onRename={this.handleRenameClick.bind(this)} + onCopy={this.handleCopyClick.bind(this)} + onSetAsDefault={this.handleSetAsDefaultClick.bind(this)} + onDelete={this.handleDeleteClick.bind(this)}/> + + <DetailsContent + gate={qualityGate} + canEdit={edit} + metrics={metrics} + periods={periods} + onAddCondition={onAddCondition} + onSaveCondition={onSaveCondition} + onDeleteCondition={onDeleteCondition}/> + </div> + ); + } +} + +Details.contextTypes = { + router: React.PropTypes.object.isRequired +}; 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 new file mode 100644 index 00000000000..f53cfb36ae1 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsContent.js @@ -0,0 +1,62 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 Conditions from './Conditions'; +import Projects from './Projects'; +import { translate } from '../../../helpers/l10n'; + +export default class DetailsContent extends Component { + render () { + const { gate, canEdit, metrics, periods } = this.props; + const { onAddCondition, onDeleteCondition, onSaveCondition } = this.props; + const conditions = gate.conditions || []; + + return ( + <div + ref="container" + className="search-navigator-workspace-details"> + <Conditions + qualityGate={gate} + conditions={conditions} + metrics={metrics} + periods={periods} + edit={canEdit} + onAddCondition={onAddCondition} + onSaveCondition={onSaveCondition} + onDeleteCondition={onDeleteCondition}/> + + <div id="quality-gate-projects" className="quality-gate-section"> + <h3 className="spacer-bottom"> + {translate('quality_gates.projects')} + </h3> + {gate.isDefault ? ( + canEdit ? translate('quality_gates.projects_for_default.edit') : + translate('quality_gates.projects_for_default') + ) : ( + <Projects + qualityGate={gate} + edit={canEdit}/> + )} + </div> + </div> + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsHeader.js b/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsHeader.js new file mode 100644 index 00000000000..fc9675d28d8 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsHeader.js @@ -0,0 +1,77 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { translate } from '../../../helpers/l10n'; + +export default function DetailsHeader ({ qualityGate, edit, onRename, onCopy, onSetAsDefault, onDelete }) { + function handleRenameClick (e) { + e.preventDefault(); + onRename(); + } + + function handleCopyClick (e) { + e.preventDefault(); + onCopy(); + } + + function handleSetAsDefaultClick (e) { + e.preventDefault(); + onSetAsDefault(); + } + + function handleDeleteClick (e) { + e.preventDefault(); + onDelete(); + } + + return ( + <div className="search-navigator-workspace-header" style={{ top: 30 }}> + <h2 className="search-navigator-header-component">{qualityGate.name}</h2> + {edit && ( + <div className="search-navigator-header-actions"> + <div className="button-group"> + <button + id="quality-gate-rename" + onClick={handleRenameClick}> + {translate('rename')} + </button> + <button + id="quality-gate-copy" + onClick={handleCopyClick}> + {translate('copy')} + </button> + <button + id="quality-gate-toggle-default" + onClick={handleSetAsDefaultClick}> + {qualityGate.isDefault ? translate('unset_as_default') : translate('set_as_default')} + </button> + <button + id="quality-gate-delete" + className="button-red" + onClick={handleDeleteClick}> + {translate('delete')} + </button> + </div> + </div> + )} + </div> + ); +} diff --git a/server/sonar-web/src/main/js/apps/quality-gates/router.js b/server/sonar-web/src/main/js/apps/quality-gates/components/Intro.js index bfe973a5811..dbb43fbde68 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/router.js +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/Intro.js @@ -17,25 +17,17 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import Backbone from 'backbone'; - -export default Backbone.Router.extend({ - routes: { - '': 'index', - 'show/:id': 'show' - }, - - initialize (options) { - this.app = options.app; - }, - - index () { - this.app.controller.index(); - }, - - show (id) { - this.app.controller.show(id); - } -}); +import React from 'react'; +import { translate } from '../../../helpers/l10n'; +export default function Intro () { + return ( + <div className="search-navigator-workspace"> + <div className="search-navigator-intro markdown"> + <p>{translate('quality_gates.intro.1')}</p> + <p>{translate('quality_gates.intro.2')}</p> + </div> + </div> + ); +} diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/List.js b/server/sonar-web/src/main/js/apps/quality-gates/components/List.js new file mode 100644 index 00000000000..88a9a16f107 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/List.js @@ -0,0 +1,53 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { Link } from 'react-router'; + +import { translate } from '../../../helpers/l10n'; + +export default function List ({ qualityGates }) { + return ( + <div className="list-group"> + {qualityGates.map(qualityGate => ( + <Link + key={qualityGate.id} + to={`/show/${qualityGate.id}`} + activeClassName="active" + className="list-group-item" + data-id={qualityGate.id}> + <table> + <tbody> + <tr> + <td className="text-top"> + {qualityGate.name} + </td> + <td className="text-top thin nowrap spacer-left"> + {qualityGate.isDefault && ( + <span className="badge pull-right">{translate('default')}</span> + )} + </td> + </tr> + </tbody> + </table> + </Link> + ))} + </div> + ); +} diff --git a/server/sonar-web/src/main/js/apps/quality-gates/gate-view.js b/server/sonar-web/src/main/js/apps/quality-gates/components/ListHeader.js index 85f709cc324..0ecd1fc56da 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/gate-view.js +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/ListHeader.js @@ -17,31 +17,29 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import Marionette from 'backbone.marionette'; -import Template from './templates/quality-gates-gate.hbs'; +import React from 'react'; -export default Marionette.ItemView.extend({ - tagName: 'a', - className: 'list-group-item', - template: Template, +import CreateView from '../views/create-view'; +import { translate } from '../../../helpers/l10n'; - modelEvents: { - 'change': 'render' - }, - - events: { - 'click': 'onClick' - }, - - onRender () { - this.$el.toggleClass('active', this.options.highlighted); - this.$el.attr('data-id', this.model.id); - }, - - onClick (e) { +export default function ListHeader ({ canEdit, onAdd }) { + function handleAddClick (e) { e.preventDefault(); - this.model.trigger('select', this.model); + new CreateView({ onAdd }).render(); } -}); - + return ( + <div> + <h1 className="page-title">{translate('quality_gates.page')}</h1> + {canEdit && ( + <div className="page-actions"> + <div className="button-group"> + <button id="quality-gate-add" onClick={handleAddClick}> + {translate('create')} + </button> + </div> + </div> + )} + </div> + ); +} diff --git a/server/sonar-web/src/main/js/apps/quality-gates/gates.js b/server/sonar-web/src/main/js/apps/quality-gates/components/Projects.js index df7d2493c08..62b974bf76d 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/gates.js +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/Projects.js @@ -17,33 +17,47 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import _ from 'underscore'; -import Backbone from 'backbone'; -import Gate from './gate'; +import React, { Component } from 'react'; -export default Backbone.Collection.extend({ - model: Gate, +import ProjectsView from '../views/gate-projects-view'; - url () { - return '/api/qualitygates/list'; - }, +export default class Projects extends Component { + componentDidMount () { + this.renderView(); + } - parse (r) { - return r.qualitygates.map(function (gate) { - return _.extend(gate, { isDefault: gate.id === r.default }); - }); - }, + componentWillUpdate () { + this.destroyView(); + } - comparator (item) { - return item.get('name').toLowerCase(); - }, + componentDidUpdate () { + this.renderView(); + } - toggleDefault (gate) { - const isDefault = gate.isDefault(); - this.forEach(function (model) { - model.set({ isDefault: gate.id === model.id ? !isDefault : false }); + componentWillUnmount () { + this.destroyView(); + } + + renderView () { + const { qualityGate, edit } = this.props; + + this.projectsView = new ProjectsView({ + qualityGate, + edit, + container: this.refs.container }); + this.projectsView.render(); } -}); + destroyView () { + if (this.projectsView) { + this.projectsView.destroy(); + } + } + render () { + return ( + <div ref="container"></div> + ); + } +} 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 new file mode 100644 index 00000000000..8d3d4838a6c --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatesApp.js @@ -0,0 +1,81 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 ListHeader from './ListHeader'; +import List from './List'; + +import { + fetchQualityGatesAppDetails, + fetchQualityGates as fetchQualityGatesAPI +} from '../../../api/quality-gates'; + +export default class QualityGatesApp extends Component { + state = {}; + + componentDidMount () { + this.fetchQualityGates(); + } + + fetchQualityGates () { + Promise.all([ + fetchQualityGatesAppDetails(), + fetchQualityGatesAPI() + ]).then(responses => { + const [details, qualityGates] = responses; + const { updateStore } = this.props; + + updateStore({ ...details, qualityGates }); + }); + } + + handleAdd (qualityGate) { + const { addQualityGate } = this.props; + const { router } = this.context; + + addQualityGate(qualityGate); + router.push(`/show/${qualityGate.id}`); + } + + render () { + const { children, qualityGates, edit } = this.props; + + return ( + <div className="search-navigator sticky search-navigator-extended-view"> + <div className="search-navigator-side search-navigator-side-light" style={{ top: 30 }}> + <div className="search-navigator-filters"> + <ListHeader + canEdit={edit} + onAdd={this.handleAdd.bind(this)}/> + </div> + <div className="quality-gates-results panel"> + {qualityGates && <List qualityGates={qualityGates}/>} + </div> + </div> + + {!!qualityGates && children} + </div> + ); + } +} + +QualityGatesApp.contextTypes = { + router: React.PropTypes.object.isRequired +}; diff --git a/server/sonar-web/src/main/js/apps/quality-gates/condition.js b/server/sonar-web/src/main/js/apps/quality-gates/condition.js deleted file mode 100644 index 65bce51a82b..00000000000 --- a/server/sonar-web/src/main/js/apps/quality-gates/condition.js +++ /dev/null @@ -1,66 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact 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 Backbone from 'backbone'; - -export default Backbone.Model.extend({ - - defaults: { - period: 0 - }, - - url () { - return '/api/qualitygates'; - }, - - createUrl () { - return this.url() + '/create_condition'; - }, - - updateUrl () { - return this.url() + '/update_condition'; - }, - - deleteUrl () { - return this.url() + '/delete_condition'; - }, - - sync (method, model, options) { - const opts = options || {}; - opts.type = 'POST'; - if (method === 'create') { - opts.url = this.createUrl(); - opts.data = model.toJSON(); - } - if (method === 'update') { - opts.url = this.updateUrl(); - opts.data = model.toJSON(); - } - if (method === 'delete') { - opts.url = this.deleteUrl(); - opts.data = { id: model.id }; - } - if (opts.data.period === '0') { - delete opts.data.period; - } - return Backbone.ajax(opts); - } -}); - - diff --git a/server/sonar-web/src/main/js/apps/quality-gates/conditions.js b/server/sonar-web/src/main/js/apps/quality-gates/conditions.js deleted file mode 100644 index ae0569b966f..00000000000 --- a/server/sonar-web/src/main/js/apps/quality-gates/conditions.js +++ /dev/null @@ -1,27 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact 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 Backbone from 'backbone'; -import Condition from './condition'; - -export default Backbone.Collection.extend({ - model: Condition -}); - - diff --git a/server/sonar-web/src/main/js/apps/quality-gates/containers/DetailsContainer.js b/server/sonar-web/src/main/js/apps/quality-gates/containers/DetailsContainer.js new file mode 100644 index 00000000000..28a8dd342b9 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-gates/containers/DetailsContainer.js @@ -0,0 +1,56 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { connect } from 'react-redux'; + +import { + deleteQualityGate, + showQualityGate, + renameQualityGate, + copyQualityGate, + setQualityGateAsDefault, + unsetQualityGateAsDefault, + addCondition, + deleteCondition, + saveCondition +} from '../store/actions'; +import Details from '../components/Details'; + +function mapStateToProps (state) { + return state.rootReducer; +} + +function mapDispatchToProps (dispatch) { + return { + onShow: (qualityGate) => dispatch(showQualityGate(qualityGate)), + onDelete: (qualityGate) => dispatch(deleteQualityGate(qualityGate)), + onRename: (qualityGate, newName) => dispatch(renameQualityGate(qualityGate, newName)), + onCopy: (qualityGate) => dispatch(copyQualityGate(qualityGate)), + onSetAsDefault: (qualityGate) => dispatch(setQualityGateAsDefault(qualityGate)), + onUnsetAsDefault: (qualityGate) => dispatch(unsetQualityGateAsDefault(qualityGate)), + onAddCondition: (metric) => dispatch(addCondition(metric)), + onSaveCondition: (oldCondition, newCondition) => dispatch(saveCondition(oldCondition, newCondition)), + onDeleteCondition: (condition) => dispatch(deleteCondition(condition)) + }; +} + +export default connect( + mapStateToProps, + mapDispatchToProps +)(Details); diff --git a/server/sonar-web/src/main/js/apps/quality-gates/gates-view.js b/server/sonar-web/src/main/js/apps/quality-gates/containers/QualityGatesAppContainer.js index 0df6623ff16..d916ce1dd0a 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/gates-view.js +++ b/server/sonar-web/src/main/js/apps/quality-gates/containers/QualityGatesAppContainer.js @@ -17,27 +17,24 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import Marionette from 'backbone.marionette'; -import ItemView from './gate-view'; -import Template from './templates/quality-gates-gates.hbs'; +import { connect } from 'react-redux'; -export default Marionette.CompositeView.extend({ - className: 'list-group', - template: Template, - childView: ItemView, - childViewContainer: '.js-list', +import { setState, addQualityGate, deleteQualityGate } from '../store/actions'; +import QualityGateApp from '../components/QualityGatesApp'; - childViewOptions (model) { - return { - collectionView: this, - highlighted: model.id === this.highlighted - }; - }, - - highlight (id) { - this.highlighted = id; - this.render(); - } -}); +function mapStateToProps (state) { + return state.rootReducer; +} +function mapDispatchToProps (dispatch) { + return { + updateStore: (nextState) => dispatch(setState(nextState)), + addQualityGate: (qualityGate) => dispatch(addQualityGate(qualityGate)), + deleteQualityGate: (qualityGate) => dispatch(deleteQualityGate(qualityGate)) + }; +} +export default connect( + mapStateToProps, + mapDispatchToProps +)(QualityGateApp); diff --git a/server/sonar-web/src/main/js/apps/quality-gates/controller.js b/server/sonar-web/src/main/js/apps/quality-gates/controller.js deleted file mode 100644 index 826b5d57cca..00000000000 --- a/server/sonar-web/src/main/js/apps/quality-gates/controller.js +++ /dev/null @@ -1,83 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact 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 _ from 'underscore'; -import Marionette from 'backbone.marionette'; -import DetailsView from './details-view'; -import HeaderView from './header-view'; - -export default Marionette.Controller.extend({ - - initialize (options) { - this.app = options.app; - this.canEdit = this.app.canEdit; - this.listenTo(this.app.gates, 'select', this.onSelect); - this.listenTo(this.app.gates, 'destroy', this.onDestroy); - }, - - index () { - this.app.gates.fetch(); - }, - - show (id) { - const that = this; - this.app.gates.fetch().done(function () { - const gate = that.app.gates.get(id); - if (gate != null) { - gate.trigger('select', gate, { trigger: false }); - } - }); - }, - - onSelect (gate, options) { - const that = this; - const route = 'show/' + gate.id; - const opts = _.defaults(options || {}, { trigger: true }); - if (opts.trigger) { - this.app.router.navigate(route); - } - this.app.gatesView.highlight(gate.id); - gate.fetch().done(function () { - const headerView = new HeaderView({ - model: gate, - canEdit: that.canEdit - }); - that.app.layout.headerRegion.show(headerView); - - const detailsView = new DetailsView({ - model: gate, - canEdit: that.canEdit, - metrics: that.app.metrics, - periods: that.app.periods - }); - that.app.layout.detailsRegion.show(detailsView); - }); - }, - - onDestroy () { - this.app.router.navigate(''); - this.app.layout.headerRegion.reset(); - this.app.layout.detailsRegion.reset(); - this.app.layout.renderIntro(); - this.app.gatesView.highlight(null); - } - -}); - - diff --git a/server/sonar-web/src/main/js/apps/quality-gates/copy-view.js b/server/sonar-web/src/main/js/apps/quality-gates/copy-view.js deleted file mode 100644 index 638ca97eacc..00000000000 --- a/server/sonar-web/src/main/js/apps/quality-gates/copy-view.js +++ /dev/null @@ -1,41 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact 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 FormView from './form-view'; - -export default FormView.extend({ - method: 'copy', - - prepareRequest () { - const that = this; - const url = '/api/qualitygates/copy'; - const name = this.$('#quality-gate-form-name').val(); - const options = { - url, - data: { id: this.model.id, name } - }; - return this.sendRequest(options) - .done(function (r) { - const gate = that.addGate(r); - gate.trigger('select', gate); - }); - } -}); - - diff --git a/server/sonar-web/src/main/js/apps/quality-gates/details-view.js b/server/sonar-web/src/main/js/apps/quality-gates/details-view.js deleted file mode 100644 index ae49ae0bb9f..00000000000 --- a/server/sonar-web/src/main/js/apps/quality-gates/details-view.js +++ /dev/null @@ -1,72 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact 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 _ from 'underscore'; -import Marionette from 'backbone.marionette'; -import Conditions from './conditions'; -import DetailConditionsView from './gate-conditions-view'; -import ProjectsView from './gate-projects-view'; -import Template from './templates/quality-gate-detail.hbs'; - -export default Marionette.LayoutView.extend({ - template: Template, - - regions: { - conditionsRegion: '#quality-gate-conditions', - projectsRegion: '#quality-gate-projects' - }, - - modelEvents: { - 'change': 'render' - }, - - onRender () { - this.showConditions(); - this.showProjects(); - }, - - orderByName (conditions) { - const metrics = this.options.metrics; - return _.sortBy(conditions, (condition) => { - return _.findWhere(metrics, { key: condition.metric }).name; - }); - }, - - showConditions () { - const conditions = new Conditions(this.orderByName(this.model.get('conditions'))); - const view = new DetailConditionsView({ - canEdit: this.options.canEdit, - collection: conditions, - model: this.model, - metrics: this.options.metrics, - periods: this.options.periods - }); - this.conditionsRegion.show(view); - }, - - showProjects () { - const view = new ProjectsView({ - canEdit: this.options.canEdit, - model: this.model - }); - this.projectsRegion.show(view); - } -}); - - diff --git a/server/sonar-web/src/main/js/apps/quality-gates/form-view.js b/server/sonar-web/src/main/js/apps/quality-gates/form-view.js deleted file mode 100644 index d82e605c7b0..00000000000 --- a/server/sonar-web/src/main/js/apps/quality-gates/form-view.js +++ /dev/null @@ -1,66 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact 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 _ from 'underscore'; -import Backbone from 'backbone'; -import ModalForm from '../../components/common/modal-form'; -import Gate from './gate'; -import Template from './templates/quality-gate-form.hbs'; - -export default ModalForm.extend({ - template: Template, - - onFormSubmit () { - ModalForm.prototype.onFormSubmit.apply(this, arguments); - this.disableForm(); - this.prepareRequest(); - }, - - sendRequest (options) { - const that = this; - const opts = _.defaults(options || {}, { - type: 'POST', - statusCode: { - // do not show global error - 400: null - } - }); - return Backbone.ajax(opts) - .done(function () { - that.destroy(); - }).fail(function (jqXHR) { - that.enableForm(); - that.showErrors(jqXHR.responseJSON.errors, jqXHR.responseJSON.warnings); - }); - }, - - addGate (attrs) { - const gate = new Gate(attrs); - this.collection.add(gate, { merge: true }); - return gate; - }, - - serializeData () { - return _.extend(ModalForm.prototype.serializeData.apply(this, arguments), { - method: this.method - }); - } -}); - - diff --git a/server/sonar-web/src/main/js/apps/quality-gates/gate-condition-view.js b/server/sonar-web/src/main/js/apps/quality-gates/gate-condition-view.js deleted file mode 100644 index dab1a79e834..00000000000 --- a/server/sonar-web/src/main/js/apps/quality-gates/gate-condition-view.js +++ /dev/null @@ -1,118 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact 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 _ from 'underscore'; -import Marionette from 'backbone.marionette'; -import DeleteConditionView from './gate-conditions-delete-view'; -import Template from './templates/quality-gate-detail-condition.hbs'; -import { translate } from '../../helpers/l10n'; - -export default Marionette.ItemView.extend({ - tagName: 'tr', - template: Template, - - modelEvents: { - 'change': 'render' - }, - - ui: { - periodSelect: '[name=period]', - operatorSelect: '[name=operator]', - warningInput: '[name=warning]', - errorInput: '[name=error]', - actionsBox: '.quality-gate-condition-actions', - updateButton: '.update-condition', - deleteButton: '.delete-condition' - }, - - events: { - 'click @ui.updateButton': 'saveCondition', - 'click @ui.deleteButton': 'deleteCondition', - 'click .add-condition': 'saveCondition', - 'click .cancel-add-condition': 'cancelAddCondition', - 'keyup :input': 'enableUpdate', - 'change :input': 'enableUpdate' - }, - - onRender () { - this.ui.warningInput.val(this.model.get('warning')); - this.ui.errorInput.val(this.model.get('error')); - - this.ui.periodSelect.select2({ - allowClear: false, - minimumResultsForSearch: 999 - }); - - this.ui.operatorSelect.select2({ - allowClear: false, - minimumResultsForSearch: 999 - }); - - if (this.model.isNew()) { - this.ui.periodSelect.select2('open'); - } - }, - - saveCondition () { - const attrs = { - gateId: this.model.isNew() ? this.options.gate.id : void 0, - period: this.ui.periodSelect.val(), - op: this.ui.operatorSelect.val(), - warning: this.ui.warningInput.val(), - error: this.ui.errorInput.val() - }; - this.model.save(attrs, { wait: true }); - }, - - deleteCondition () { - new DeleteConditionView({ - model: this.model, - metric: this.getMetric() - }).render(); - }, - - cancelAddCondition () { - this.destroy(); - }, - - enableUpdate () { - this.ui.updateButton.prop('disabled', false); - }, - - getMetric () { - const key = this.model.get('metric'); - return _.findWhere(this.options.metrics, { key }); - }, - - isDiffMetric () { - const key = this.model.get('metric'); - return key.indexOf('new_') === 0; - }, - - serializeData () { - const period = _.findWhere(this.options.periods, { key: this.model.get('period') }); - return _.extend(Marionette.ItemView.prototype.serializeData.apply(this, arguments), { - canEdit: this.options.canEdit, - periods: this.options.periods, - periodText: period ? period.text : translate('value'), - metric: this.getMetric(), - isDiffMetric: this.isDiffMetric() - }); - } -}); diff --git a/server/sonar-web/src/main/js/apps/quality-gates/gate-conditions-empty-view.js b/server/sonar-web/src/main/js/apps/quality-gates/gate-conditions-empty-view.js deleted file mode 100644 index ee1e772273c..00000000000 --- a/server/sonar-web/src/main/js/apps/quality-gates/gate-conditions-empty-view.js +++ /dev/null @@ -1,35 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact 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 _ from 'underscore'; -import Marionette from 'backbone.marionette'; -import Template from './templates/quality-gate-detail-conditions-empty.hbs'; - -export default Marionette.ItemView.extend({ - tagName: 'tr', - template: Template, - - serializeData () { - return _.extend(Marionette.ItemView.prototype.serializeData.apply(this, arguments), { - canEdit: this.options.canEdit - }); - } -}); - - diff --git a/server/sonar-web/src/main/js/apps/quality-gates/gate-conditions-view.js b/server/sonar-web/src/main/js/apps/quality-gates/gate-conditions-view.js deleted file mode 100644 index 5d2396b3453..00000000000 --- a/server/sonar-web/src/main/js/apps/quality-gates/gate-conditions-view.js +++ /dev/null @@ -1,93 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact 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 _ from 'underscore'; -import Marionette from 'backbone.marionette'; -import Condition from './condition'; -import ConditionView from './gate-condition-view'; -import ConditionsEmptyView from './gate-conditions-empty-view'; -import Template from './templates/quality-gate-detail-conditions.hbs'; -import { translate } from '../../helpers/l10n'; - -export default Marionette.CompositeView.extend({ - template: Template, - childView: ConditionView, - emptyView: ConditionsEmptyView, - childViewContainer: '.js-conditions', - - ui: { - metricSelect: '#quality-gate-new-condition-metric' - }, - - events: { - 'click .js-show-more': 'showMoreIntroduction', - 'change @ui.metricSelect': 'addCondition' - }, - - childViewOptions () { - return { - canEdit: this.options.canEdit, - gate: this.model, - collectionView: this, - metrics: this.options.metrics, - periods: this.options.periods - }; - }, - - onRender () { - this.ui.metricSelect.select2({ - allowClear: false, - width: '250px', - placeholder: translate('alerts.select_metric') - }); - }, - - showMoreIntroduction () { - this.$('.js-show-more').addClass('hidden'); - this.$('.js-more').removeClass('hidden'); - }, - - addCondition () { - const metric = this.ui.metricSelect.val(); - this.ui.metricSelect.select2('val', ''); - const condition = new Condition({ metric }); - this.collection.add(condition); - }, - - groupedMetrics () { - let metrics = this.options.metrics.filter(function (metric) { - return !metric.hidden; - }); - metrics = _.groupBy(metrics, 'domain'); - metrics = _.map(metrics, function (list, domain) { - return { - domain, - metrics: _.sortBy(list, 'short_name') - }; - }); - return _.sortBy(metrics, 'domain'); - }, - - serializeData () { - return _.extend(Marionette.CompositeView.prototype.serializeData.apply(this, arguments), { - canEdit: this.options.canEdit, - metricGroups: this.groupedMetrics() - }); - } -}); diff --git a/server/sonar-web/src/main/js/apps/quality-gates/gate-projects-view.js b/server/sonar-web/src/main/js/apps/quality-gates/gate-projects-view.js deleted file mode 100644 index a8a7909351b..00000000000 --- a/server/sonar-web/src/main/js/apps/quality-gates/gate-projects-view.js +++ /dev/null @@ -1,66 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact 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 _ from 'underscore'; -import Marionette from 'backbone.marionette'; -import Template from './templates/quality-gate-detail-projects.hbs'; -import '../../components/SelectList'; -import { translate } from '../../helpers/l10n'; - -export default Marionette.ItemView.extend({ - template: Template, - - onRender () { - if (!this.model.isDefault()) { - new window.SelectList({ - el: this.$('#select-list-projects'), - width: '100%', - readOnly: !this.options.canEdit, - focusSearch: false, - format (item) { - return item.name; - }, - searchUrl: '/api/qualitygates/search?gateId=' + this.model.id, - selectUrl: '/api/qualitygates/select', - deselectUrl: '/api/qualitygates/deselect', - extra: { - gateId: this.model.id - }, - selectParameter: 'projectId', - selectParameterValue: 'id', - labels: { - selected: translate('quality_gates.projects.with'), - deselected: translate('quality_gates.projects.without'), - all: translate('quality_gates.projects.all'), - noResults: translate('quality_gates.projects.noResults') - }, - tooltips: { - select: translate('quality_gates.projects.select_hint'), - deselect: translate('quality_gates.projects.deselect_hint') - } - }); - } - }, - - serializeData () { - return _.extend(Marionette.ItemView.prototype.serializeData.apply(this, arguments), { - canEdit: this.options.canEdit - }); - } -}); diff --git a/server/sonar-web/src/main/js/apps/quality-gates/gate.js b/server/sonar-web/src/main/js/apps/quality-gates/gate.js deleted file mode 100644 index 00ff3718d44..00000000000 --- a/server/sonar-web/src/main/js/apps/quality-gates/gate.js +++ /dev/null @@ -1,73 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact 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 Backbone from 'backbone'; - -export default Backbone.Model.extend({ - - isDefault () { - return this.get('isDefault'); - }, - - url () { - return '/api/qualitygates'; - }, - - showUrl () { - return this.url() + '/show'; - }, - - deleteUrl () { - return this.url() + '/destroy'; - }, - - toggleDefaultUrl () { - const method = this.isDefault() ? 'unset_default' : 'set_as_default'; - return this.url() + '/' + method; - }, - - sync (method, model, options) { - const opts = options || {}; - opts.data = opts.data || {}; - opts.data.id = model.id; - if (method === 'read') { - opts.url = this.showUrl(); - } - if (method === 'delete') { - opts.url = this.deleteUrl(); - opts.type = 'POST'; - } - return Backbone.ajax(opts); - }, - - toggleDefault () { - const that = this; - const opts = { - type: 'POST', - url: this.toggleDefaultUrl(), - data: { id: this.id } - }; - return Backbone.ajax(opts).done(function () { - that.collection.toggleDefault(that); - }); - } - -}); - - diff --git a/server/sonar-web/src/main/js/apps/quality-gates/header-view.js b/server/sonar-web/src/main/js/apps/quality-gates/header-view.js deleted file mode 100644 index e01e161c9df..00000000000 --- a/server/sonar-web/src/main/js/apps/quality-gates/header-view.js +++ /dev/null @@ -1,71 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact 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 _ from 'underscore'; -import Marionette from 'backbone.marionette'; -import RenameView from './rename-view'; -import CopyView from './copy-view'; -import DeleteView from './delete-view'; -import Template from './templates/quality-gate-detail-header.hbs'; - -export default Marionette.ItemView.extend({ - template: Template, - - modelEvents: { - 'change': 'render' - }, - - events: { - 'click #quality-gate-rename': 'renameQualityGate', - 'click #quality-gate-copy': 'copyQualityGate', - 'click #quality-gate-delete': 'deleteQualityGate', - 'click #quality-gate-toggle-default': 'toggleDefault' - }, - - renameQualityGate () { - new RenameView({ - model: this.model - }).render(); - }, - - copyQualityGate () { - new CopyView({ - model: this.model, - collection: this.model.collection - }).render(); - }, - - deleteQualityGate () { - new DeleteView({ - model: this.model - }).render(); - }, - - toggleDefault () { - this.model.toggleDefault(); - }, - - serializeData () { - return _.extend(Marionette.ItemView.prototype.serializeData.apply(this, arguments), { - canEdit: this.options.canEdit - }); - } -}); - - diff --git a/server/sonar-web/src/main/js/apps/quality-gates/intro-view.js b/server/sonar-web/src/main/js/apps/quality-gates/intro-view.js deleted file mode 100644 index 083f83b82ae..00000000000 --- a/server/sonar-web/src/main/js/apps/quality-gates/intro-view.js +++ /dev/null @@ -1,27 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact 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 Marionette from 'backbone.marionette'; -import Template from './templates/quality-gates-intro.hbs'; - -export default Marionette.ItemView.extend({ - template: Template -}); - - diff --git a/server/sonar-web/src/main/js/apps/quality-gates/layout.js b/server/sonar-web/src/main/js/apps/quality-gates/layout.js deleted file mode 100644 index 351ca9ae43c..00000000000 --- a/server/sonar-web/src/main/js/apps/quality-gates/layout.js +++ /dev/null @@ -1,46 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact 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 Marionette from 'backbone.marionette'; -import IntroView from './intro-view'; -import Template from './templates/quality-gates-layout.hbs'; - -export default Marionette.LayoutView.extend({ - template: Template, - - regions: { - headerRegion: '.search-navigator-workspace-header', - actionsRegion: '.search-navigator-filters', - resultsRegion: '.quality-gates-results', - detailsRegion: '.search-navigator-workspace-details' - }, - - onRender () { - const top = this.$('.search-navigator').offset().top; - this.$('.search-navigator-workspace-header').css({ top }); - this.$('.search-navigator-side').css({ top }).isolatedScroll(); - this.renderIntro(); - }, - - renderIntro () { - this.detailsRegion.show(new IntroView()); - } -}); - - 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 new file mode 100644 index 00000000000..201fb4c4e6b --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-gates/store/actions.js @@ -0,0 +1,108 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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. + */ +export const SET_STATE = 'SET_STATE'; +export function setState (nextState) { + return { + type: SET_STATE, + nextState + }; +} + +export const ADD = 'ADD'; +export function addQualityGate (qualityGate) { + return { + type: 'ADD', + qualityGate + }; +} + +export const DELETE = 'DELETE'; +export function deleteQualityGate (qualityGate) { + return { + type: 'DELETE', + qualityGate + }; +} + +export const SHOW = 'SHOW'; +export function showQualityGate (qualityGate) { + return { + type: 'SHOW', + qualityGate + }; +} + +export const RENAME = 'RENAME'; +export function renameQualityGate (qualityGate, newName) { + return { + type: 'RENAME', + qualityGate, + newName + }; +} + +export const COPY = 'COPY'; +export function copyQualityGate (qualityGate) { + return { + type: COPY, + qualityGate + }; +} + +export const SET_AS_DEFAULT = 'SET_AS_DEFAULT'; +export function setQualityGateAsDefault (qualityGate) { + return { + type: SET_AS_DEFAULT, + qualityGate + }; +} + +export const UNSET_AS_DEFAULT = 'UNSET_AS_DEFAULT'; +export function unsetQualityGateAsDefault (qualityGate) { + return { + type: UNSET_AS_DEFAULT, + qualityGate + }; +} + +export const ADD_CONDITION = 'ADD_CONDITION'; +export function addCondition (metric) { + return { + type: ADD_CONDITION, + metric + }; +} + +export const SAVE_CONDITION = 'SAVE_CONDITION'; +export function saveCondition (oldCondition, newCondition) { + return { + type: SAVE_CONDITION, + oldCondition, + newCondition + }; +} + +export const DELETE_CONDITION = 'DELETE_CONDITION'; +export function deleteCondition (condition) { + return { + type: DELETE_CONDITION, + condition + }; +} diff --git a/server/sonar-web/src/main/js/apps/quality-gates/store/reducers.js b/server/sonar-web/src/main/js/apps/quality-gates/store/reducers.js new file mode 100644 index 00000000000..aadba7a2415 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-gates/store/reducers.js @@ -0,0 +1,93 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { + SET_STATE, + ADD, + DELETE, + SHOW, + RENAME, + COPY, + SET_AS_DEFAULT, + UNSET_AS_DEFAULT, + ADD_CONDITION, + DELETE_CONDITION, + SAVE_CONDITION +} from './actions'; +import { checkIfDefault, addCondition, deleteCondition, replaceCondition } from './utils'; + +const initialState = {}; + +export default function rootReducer (state = initialState, action) { + switch (action.type) { + case SET_STATE: + return { ...state, ...action.nextState }; + case ADD: + case COPY: + return { ...state, qualityGates: [...state.qualityGates, action.qualityGate] }; + case DELETE: + return { ...state, qualityGates: state.qualityGates.filter(candidate => candidate.id !== action.qualityGate.id) }; + case SHOW: + return { + ...state, + qualityGate: { ...action.qualityGate, isDefault: checkIfDefault(action.qualityGate, state.qualityGates) } + }; + case RENAME: + return { + ...state, + qualityGates: state.qualityGates.map(candidate => { + return candidate.id === action.qualityGate.id ? { ...candidate, name: action.newName } : candidate; + }), + qualityGate: { ...state.qualityGate, name: action.newName } + }; + case SET_AS_DEFAULT: + return { + ...state, + qualityGates: state.qualityGates.map(candidate => { + return { ...candidate, isDefault: candidate.id === action.qualityGate.id }; + }), + qualityGate: { ...state.qualityGate, isDefault: state.qualityGate.id === action.qualityGate.id } + }; + case UNSET_AS_DEFAULT: + return { + ...state, + qualityGates: state.qualityGates.map(candidate => { + return candidate.id === action.qualityGate.id ? { ...candidate, isDefault: false } : candidate; + }), + qualityGate: { ...state.qualityGate, isDefault: false } + }; + case ADD_CONDITION: + return { + ...state, + qualityGate: addCondition(state.qualityGate, action.metric) + }; + case DELETE_CONDITION: + return { + ...state, + qualityGate: deleteCondition(state.qualityGate, action.condition) + }; + case SAVE_CONDITION: + return { + ...state, + qualityGate: replaceCondition(state.qualityGate, action.oldCondition, action.newCondition) + }; + default: + return state; + } +} diff --git a/server/sonar-web/src/main/js/apps/quality-gates/store/utils.js b/server/sonar-web/src/main/js/apps/quality-gates/store/utils.js new file mode 100644 index 00000000000..81247f6393b --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-gates/store/utils.js @@ -0,0 +1,51 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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. + */ +export function checkIfDefault (qualityGate, list) { + const finding = list.find(candidate => candidate.id === qualityGate.id); + + return finding ? finding.isDefault : false; +} + +export function addCondition (qualityGate, metric) { + const condition = { + metric, + op: 'LT', + warning: '', + error: '' + }; + const conditions = [...qualityGate.conditions, condition]; + + return { ...qualityGate, conditions }; +} + +export function deleteCondition (qualityGate, condition) { + const conditions = qualityGate.conditions + .filter(candidate => candidate !== condition); + + return { ...qualityGate, conditions }; +} + +export function replaceCondition (qualityGate, oldCondition, newCondition) { + const conditions = qualityGate.conditions + .map(candidate => { + return candidate === oldCondition ? newCondition : candidate; + }); + return { ...qualityGate, conditions }; +} diff --git a/server/sonar-web/src/main/js/apps/quality-gates/templates/_quality-gate-intro.hbs b/server/sonar-web/src/main/js/apps/quality-gates/templates/_quality-gate-intro.hbs deleted file mode 100644 index 6f194fb7fbb..00000000000 --- a/server/sonar-web/src/main/js/apps/quality-gates/templates/_quality-gate-intro.hbs +++ /dev/null @@ -1,7 +0,0 @@ -<div class=""> - <p class="spacer-bottom">Quality Gates are collections of simple boolean thresholds set on project measures. A project - must pass each of the thresholds in order to pass the Quality Gate as a whole.</p> - - <p>It is possible to set a default Quality Gate, which will be applied to all projects not explicitly assigned to some - other gate.</p> -</div> diff --git a/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gate-actions.hbs b/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gate-actions.hbs deleted file mode 100644 index 6826aa94353..00000000000 --- a/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gate-actions.hbs +++ /dev/null @@ -1,8 +0,0 @@ -<h1 class="page-title">{{t 'quality_gates.page'}}</h1> -{{#if canEdit}} - <div class="page-actions"> - <div class="button-group"> - <button id="quality-gate-add">{{t 'create'}}</button> - </div> - </div> -{{/if}} diff --git a/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gate-detail-condition.hbs b/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gate-detail-condition.hbs deleted file mode 100644 index 206b7ceb283..00000000000 --- a/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gate-detail-condition.hbs +++ /dev/null @@ -1,69 +0,0 @@ -<td class="text-middle nowrap"> - {{metric.name}} - {{#if metric.hidden}} - <span class="text-danger little-spacer-left">{{t 'deprecated'}}</span> - {{/if}} -</td> - -<td class="text-right nowrap"> - {{#if canEdit}} - <select class="input-super-large" name="period"> - {{#unless isDiffMetric}} - <option value="0" {{#eq period 0}}selected{{/eq}}>{{t 'value'}}</option> - {{/unless}} - {{#each periods}} - <option value="{{key}}" {{#eq key ../period}}selected{{/eq}}>{{text}}</option> - {{/each}} - </select> - {{else}} - {{periodText}} - {{/if}} -</td> - -<td class="thin text-middle nowrap"> - {{#if canEdit}} - <select class="input-medium" name="operator"> - {{#operators}} - <option value="{{this}}" {{#eq this ../op}}selected{{/eq}}>{{t 'quality_gates.operator' this}}</option> - {{/operators}} - </select> - {{else}} - {{t 'quality_gates.operator' op}} - {{/if}} -</td> - -<td class="thin text-middle nowrap"> - <i class="icon-alert-warn" title="{{t 'quality_gates.warning_tooltip'}}"></i> - {{#if canEdit}} - <input name="warning" type="text" class="input-tiny text-middle" data-type="{{metric.type}}" - placeholder="{{metric.placeholder}}"> - {{else}} - {{formatMeasure warning metric.type}} - {{/if}} -</td> - -<td class="thin text-middle nowrap"> - <i class="icon-alert-error" title="{{t 'quality_gates.error_tooltip'}}"></i> - {{#if canEdit}} - <input name="error" type="text" class="input-tiny text-middle" data-type="{{metric.type}}" - placeholder="{{metric.placeholder}}"> - {{else}} - {{formatMeasure error metric.type}} - {{/if}} -</td> - -{{#if canEdit}} - <td class="thin text-middle nowrap"> - {{#if id}} - <div class="button-group"> - <button class="update-condition" disabled>{{t 'update_verb'}}</button> - <button class="button-red delete-condition">{{t 'delete'}}</button> - </div> - {{else}} - <div class="button-group"> - <button class="add-condition">{{t 'add_verb'}}</button> - <a class="action cancel-add-condition">{{t 'cancel'}}</a> - </div> - {{/if}} - </td> -{{/if}} diff --git a/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gate-detail-conditions-empty.hbs b/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gate-detail-conditions-empty.hbs deleted file mode 100644 index 80a12b45883..00000000000 --- a/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gate-detail-conditions-empty.hbs +++ /dev/null @@ -1,3 +0,0 @@ -<td colspan="{{#if canEdit}}6{{else}}5{{/if}}"> - {{t 'quality_gates.no_conditions'}} -</td> diff --git a/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gate-detail-conditions.hbs b/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gate-detail-conditions.hbs deleted file mode 100644 index 4f5a5126665..00000000000 --- a/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gate-detail-conditions.hbs +++ /dev/null @@ -1,41 +0,0 @@ -<h3 class="spacer-bottom">{{t 'quality_gates.conditions'}}</h3> - -<div class="alert alert-info"> - <div> - {{t 'quality_gates.introduction'}} - <a class="js-show-more pull-right spacer-left">{{t 'more'}} <i class="icon-dropdown"></i></a> - </div> - <div class="js-more spacer-top hidden"> - {{t 'quality_gates.health_icons'}} - <ul> - <li class="little-spacer-top"> - <i class="icon-alert-ok"></i> {{t 'alerts.notes.ok'}} - </li> - <li class="little-spacer-top"> - <i class="icon-alert-warn"></i> {{t 'alerts.notes.warn'}} - </li> - <li class="little-spacer-top"> - <i class="icon-alert-error"></i> {{t 'alerts.notes.error'}} - </li> - </ul> - </div> -</div> - -{{#if canEdit}} - <form class="big-spacer-top spacer-bottom"> - <label for="quality-gate-new-condition-metric" class="text-middle">{{t 'quality_gates.add_condition'}}</label> - <select id="quality-gate-new-condition-metric" class="text-middle"> - <option></option> - {{#each metricGroups}} - <optgroup label="{{this.domain}}"> - {{#each metrics}} - <option value="{{key}}">{{name}}</option>{{/each}} - </optgroup> - {{/each}} - </select> - </form> -{{/if}} - -<table class="data zebra width-100"> - <tbody class="js-conditions"></tbody> -</table> diff --git a/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gate-detail-header.hbs b/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gate-detail-header.hbs deleted file mode 100644 index c7fcf9a84d0..00000000000 --- a/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gate-detail-header.hbs +++ /dev/null @@ -1,14 +0,0 @@ -<h2 class="search-navigator-header-component">{{name}}</h2> - -{{#if canEdit}} - <div class="search-navigator-header-actions"> - <div class="button-group"> - <button id="quality-gate-rename">{{t 'rename'}}</button> - <button id="quality-gate-copy">{{t 'copy'}}</button> - <button id="quality-gate-toggle-default"> - {{#if isDefault}}{{t 'unset_as_default'}}{{else}}{{t 'set_as_default'}}{{/if}} - </button> - <button id="quality-gate-delete" class="button-red">{{t 'delete'}}</button> - </div> - </div> -{{/if}} diff --git a/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gate-detail.hbs b/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gate-detail.hbs deleted file mode 100644 index 0c5467b3476..00000000000 --- a/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gate-detail.hbs +++ /dev/null @@ -1,2 +0,0 @@ -<div id="quality-gate-conditions" class="quality-gate-section"></div> -<div id="quality-gate-projects" class="quality-gate-section"></div>
\ No newline at end of file diff --git a/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gates-gate.hbs b/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gates-gate.hbs deleted file mode 100644 index d39ea6809de..00000000000 --- a/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gates-gate.hbs +++ /dev/null @@ -1,13 +0,0 @@ -<table> - <tr> - <td class="text-top">{{name}}</td> - <td class="text-top thin nowrap spacer-left"> - {{#if isDefault}} - <span class="badge pull-right">{{t 'default'}}</span> - {{/if}} - </td> - </tr> -</table> - - - diff --git a/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gates-gates.hbs b/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gates-gates.hbs deleted file mode 100644 index 8022059ffad..00000000000 --- a/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gates-gates.hbs +++ /dev/null @@ -1 +0,0 @@ -<div class="js-list"></div> diff --git a/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gates-intro.hbs b/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gates-intro.hbs deleted file mode 100644 index a16e566c47e..00000000000 --- a/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gates-intro.hbs +++ /dev/null @@ -1,4 +0,0 @@ -<p class="spacer-bottom">Quality Gates are collections of simple boolean thresholds set on project measures. A project - must pass each of the thresholds in order to pass the Quality Gate as a whole.</p> -<p>It is possible to set a default Quality Gate, which will be applied to all projects not explicitly assigned to some - other gate.</p> diff --git a/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gates-layout.hbs b/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gates-layout.hbs deleted file mode 100644 index 25bd5742e32..00000000000 --- a/server/sonar-web/src/main/js/apps/quality-gates/templates/quality-gates-layout.hbs +++ /dev/null @@ -1,11 +0,0 @@ -<div class="search-navigator sticky search-navigator-extended-view"> - <div class="search-navigator-side search-navigator-side-light"> - <div class="search-navigator-filters"></div> - <div class="quality-gates-results panel"></div> - </div> - - <div class="search-navigator-workspace"> - <div class="search-navigator-workspace-header"></div> - <div class="search-navigator-workspace-details"></div> - </div> -</div> diff --git a/server/sonar-web/src/main/js/apps/quality-gates/gate-conditions-delete-view.js b/server/sonar-web/src/main/js/apps/quality-gates/views/copy-view.js index fb8fad5c333..bd20dbdfa8d 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/gate-conditions-delete-view.js +++ b/server/sonar-web/src/main/js/apps/quality-gates/views/copy-view.js @@ -17,10 +17,9 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import _ from 'underscore'; -import Marionette from 'backbone.marionette'; -import ModalForm from '../../components/common/modal-form'; -import Template from './templates/quality-gates-condition-delete.hbs'; +import ModalForm from '../../../components/common/modal-form'; +import Template from '../templates/quality-gate-form.hbs'; +import { copyQualityGate } from '../../../api/quality-gates'; export default ModalForm.extend({ template: Template, @@ -32,26 +31,17 @@ export default ModalForm.extend({ }, sendRequest () { - const that = this; - const options = { - statusCode: { - // do not show global error - 400: null - } - }; - return this.model.destroy(options) - .done(function () { - that.destroy(); - }).fail(function (jqXHR) { - that.enableForm(); - that.showErrors(jqXHR.responseJSON.errors, jqXHR.responseJSON.warnings); - }); + const { id } = this.options.qualityGate; + const name = this.$('#quality-gate-form-name').val(); + + copyQualityGate(id, name).then(qualityGate => { + this.destroy(); + this.options.onCopy(qualityGate); + }); }, serializeData () { - return _.extend(Marionette.ItemView.prototype.serializeData.apply(this, arguments), { - metric: this.options.metric - }); + return { method: 'copy', ...this.options.qualityGate }; } }); diff --git a/server/sonar-web/src/main/js/apps/quality-gates/create-view.js b/server/sonar-web/src/main/js/apps/quality-gates/views/create-view.js index 468b6711f81..3af7c15c118 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/create-view.js +++ b/server/sonar-web/src/main/js/apps/quality-gates/views/create-view.js @@ -17,24 +17,30 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import FormView from './form-view'; +import ModalForm from '../../../components/common/modal-form'; +import Template from '../templates/quality-gate-form.hbs'; +import { createQualityGate } from '../../../api/quality-gates'; -export default FormView.extend({ - method: 'create', +export default ModalForm.extend({ + template: Template, - prepareRequest () { - const that = this; - const url = '/api/qualitygates/create'; + onFormSubmit () { + ModalForm.prototype.onFormSubmit.apply(this, arguments); + this.disableForm(); + this.sendRequest(); + }, + + sendRequest () { const name = this.$('#quality-gate-form-name').val(); - const options = { - url, - data: { name } - }; - return this.sendRequest(options) - .done(function (r) { - const gate = that.addGate(r); - gate.trigger('select', gate); - }); + + createQualityGate(name).then(qualityGate => { + this.destroy(); + this.options.onAdd(qualityGate); + }); + }, + + serializeData () { + return { method: 'create' }; } }); diff --git a/server/sonar-web/src/main/js/apps/quality-gates/actions-view.js b/server/sonar-web/src/main/js/apps/quality-gates/views/delete-view.js index 8e22e0b9b8c..291e866c211 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/actions-view.js +++ b/server/sonar-web/src/main/js/apps/quality-gates/views/delete-view.js @@ -17,29 +17,30 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import _ from 'underscore'; -import Marionette from 'backbone.marionette'; -import CreateView from './create-view'; -import Template from './templates/quality-gate-actions.hbs'; +import ModalForm from '../../../components/common/modal-form'; +import Template from '../templates/quality-gates-delete.hbs'; +import { deleteQualityGate } from '../../../api/quality-gates'; -export default Marionette.ItemView.extend({ +export default ModalForm.extend({ template: Template, - events: { - 'click #quality-gate-add': 'add' + onFormSubmit () { + ModalForm.prototype.onFormSubmit.apply(this, arguments); + this.disableForm(); + this.sendRequest(); }, - add (e) { - e.preventDefault(); - new CreateView({ - collection: this.collection - }).render(); + sendRequest () { + const { id } = this.options.qualityGate; + + deleteQualityGate(id).then(() => { + this.destroy(); + this.options.onDelete(this.options.qualityGate); + }); }, serializeData () { - return _.extend(Marionette.ItemView.prototype.serializeData.apply(this, arguments), { - canEdit: this.options.canEdit - }); + return this.options.qualityGate; } }); diff --git a/server/sonar-web/src/main/js/apps/quality-gates/delete-view.js b/server/sonar-web/src/main/js/apps/quality-gates/views/gate-conditions-delete-view.js index 7efa270d9c1..51aa9aaa27f 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/delete-view.js +++ b/server/sonar-web/src/main/js/apps/quality-gates/views/gate-conditions-delete-view.js @@ -17,8 +17,9 @@ * 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-delete.hbs'; +import ModalForm from '../../../components/common/modal-form'; +import Template from '../templates/quality-gates-condition-delete.hbs'; +import { deleteCondition } from '../../../api/quality-gates'; export default ModalForm.extend({ template: Template, @@ -30,20 +31,17 @@ export default ModalForm.extend({ }, sendRequest () { - const that = this; - const options = { - statusCode: { - // do not show global error - 400: null - } - }; - return this.model.destroy(options) - .done(function () { - that.destroy(); - }).fail(function (jqXHR) { - that.enableForm(); - that.showErrors(jqXHR.responseJSON.errors, jqXHR.responseJSON.warnings); + return deleteCondition(this.options.condition.id) + .then(() => { + this.destroy(); + this.options.onDelete(); }); + }, + + serializeData () { + return { + metric: 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 new file mode 100644 index 00000000000..41d12eee7f7 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-gates/views/gate-projects-view.js @@ -0,0 +1,67 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 _ from 'underscore'; +import Marionette from 'backbone.marionette'; + +import Template from '../templates/quality-gate-detail-projects.hbs'; +import '../../../components/SelectList'; +import { translate } from '../../../helpers/l10n'; + +export default Marionette.ItemView.extend({ + template: Template, + + onRender () { + const { qualityGate } = this.options; + + new window.SelectList({ + el: this.options.container, + width: '100%', + readOnly: !this.options.edit, + focusSearch: false, + format (item) { + return item.name; + }, + searchUrl: '/api/qualitygates/search?gateId=' + qualityGate.id, + selectUrl: '/api/qualitygates/select', + deselectUrl: '/api/qualitygates/deselect', + extra: { + gateId: qualityGate.id + }, + selectParameter: 'projectId', + selectParameterValue: 'id', + labels: { + selected: translate('quality_gates.projects.with'), + deselected: translate('quality_gates.projects.without'), + all: translate('quality_gates.projects.all'), + noResults: translate('quality_gates.projects.noResults') + }, + tooltips: { + select: translate('quality_gates.projects.select_hint'), + deselect: translate('quality_gates.projects.deselect_hint') + } + }); + }, + + serializeData () { + return _.extend(Marionette.ItemView.prototype.serializeData.apply(this, arguments), { + canEdit: this.options.edit + }); + } +}); diff --git a/server/sonar-web/src/main/js/apps/quality-gates/rename-view.js b/server/sonar-web/src/main/js/apps/quality-gates/views/rename-view.js index 9c5d5b901b9..b3e0551bd5b 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/rename-view.js +++ b/server/sonar-web/src/main/js/apps/quality-gates/views/rename-view.js @@ -17,23 +17,31 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import FormView from './form-view'; +import ModalForm from '../../../components/common/modal-form'; +import Template from '../templates/quality-gate-form.hbs'; +import { renameQualityGate } from '../../../api/quality-gates'; -export default FormView.extend({ - method: 'rename', +export default ModalForm.extend({ + template: Template, - prepareRequest () { - const that = this; - const url = '/api/qualitygates/rename'; + onFormSubmit () { + ModalForm.prototype.onFormSubmit.apply(this, arguments); + this.disableForm(); + this.sendRequest(); + }, + + sendRequest () { + const { id } = this.options.qualityGate; const name = this.$('#quality-gate-form-name').val(); - const options = { - url, - data: { id: this.model.id, name } - }; - return this.sendRequest(options) - .done(function (r) { - that.model.set(r); - }); + + renameQualityGate(id, name).then(() => { + this.destroy(); + this.options.onRename(this.options.qualityGate, name); + }); + }, + + serializeData () { + return { method: 'rename', ...this.options.qualityGate }; } }); diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/templates/quality-profiles-intro.hbs b/server/sonar-web/src/main/js/apps/quality-profiles/templates/quality-profiles-intro.hbs index ae369f7f707..469fd1078a4 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/templates/quality-profiles-intro.hbs +++ b/server/sonar-web/src/main/js/apps/quality-profiles/templates/quality-profiles-intro.hbs @@ -1,3 +1,5 @@ -<p class="spacer-bottom">Quality Profiles are collections of rules to apply during an analysis.</p> -<p>For each language there is a default profile. All projects not explicitly assigned to some other profile will be - analyzed with the default.</p> +<div class="search-navigator-intro markdown"> + <p>Quality Profiles are collections of rules to apply during an analysis.</p> + <p>For each language there is a default profile. All projects not explicitly assigned to some other profile will be + analyzed with the default.</p> +</div> diff --git a/server/sonar-web/src/main/js/components/navigator/filters/ajax-select-filters.js b/server/sonar-web/src/main/js/components/navigator/filters/ajax-select-filters.js index 2d77c5d8849..a3402330ef8 100644 --- a/server/sonar-web/src/main/js/components/navigator/filters/ajax-select-filters.js +++ b/server/sonar-web/src/main/js/components/navigator/filters/ajax-select-filters.js @@ -234,7 +234,7 @@ const AjaxSelectFilterView = ChoiceFilters.ChoiceFilterView.extend({ initialize (options) { ChoiceFilters.ChoiceFilterView.prototype.initialize.call(this, { - detailsView: (options && options.detailsView) ? options.detailsView : AjaxSelectDetailsFilterView + projectsView: (options && options.projectsView) ? options.projectsView : AjaxSelectDetailsFilterView }); }, @@ -343,7 +343,7 @@ const AjaxSelectFilterView = ChoiceFilters.ChoiceFilterView.extend({ onRestore () { - this.detailsView.updateLists(); + this.projectsView.updateLists(); this.renderBase(); }, @@ -367,7 +367,7 @@ const ComponentFilterView = AjaxSelectFilterView.extend({ initialize () { AjaxSelectFilterView.prototype.initialize.call(this, { - detailsView: AjaxSelectDetailsFilterView + projectsView: AjaxSelectDetailsFilterView }); this.choices = new ComponentSuggestions(); }, @@ -396,7 +396,7 @@ const ProjectFilterView = AjaxSelectFilterView.extend({ initialize () { BaseFilters.BaseFilterView.prototype.initialize.call(this, { - detailsView: AjaxSelectDetailsFilterView + projectsView: AjaxSelectDetailsFilterView }); this.choices = new ProjectSuggestions(); @@ -427,7 +427,7 @@ const AssigneeFilterView = AjaxSelectFilterView.extend({ initialize () { BaseFilters.BaseFilterView.prototype.initialize.call(this, { - detailsView: AjaxSelectDetailsFilterView + projectsView: AjaxSelectDetailsFilterView }); this.choices = new UserSuggestions(); @@ -457,7 +457,7 @@ const ReporterFilterView = AjaxSelectFilterView.extend({ initialize () { BaseFilters.BaseFilterView.prototype.initialize.call(this, { - detailsView: AjaxSelectDetailsFilterView + projectsView: AjaxSelectDetailsFilterView }); this.selection = new UserSuggestions(); diff --git a/server/sonar-web/src/main/js/components/navigator/filters/base-filters.js b/server/sonar-web/src/main/js/components/navigator/filters/base-filters.js index 3918ef0dc09..44705c1de1b 100644 --- a/server/sonar-web/src/main/js/components/navigator/filters/base-filters.js +++ b/server/sonar-web/src/main/js/components/navigator/filters/base-filters.js @@ -87,8 +87,8 @@ const BaseFilterView = Marionette.ItemView.extend({ initialize (options) { Marionette.ItemView.prototype.initialize.apply(this, arguments); - const detailsView = (options && options.detailsView) || DetailsFilterView; - this.detailsView = new detailsView({ + const detailsView = (options && options.projectsView) || DetailsFilterView; + this.projectsView = new detailsView({ model: this.model, filterView: this }); @@ -98,7 +98,7 @@ const BaseFilterView = Marionette.ItemView.extend({ attachDetailsView () { - this.detailsView.$el.detach().appendTo($('body')); + this.projectsView.$el.detach().appendTo($('body')); }, @@ -106,7 +106,7 @@ const BaseFilterView = Marionette.ItemView.extend({ this.renderBase(); this.attachDetailsView(); - this.detailsView.render(); + this.projectsView.render(); this.$el.toggleClass( 'navigator-filter-disabled', @@ -156,9 +156,9 @@ const BaseFilterView = Marionette.ItemView.extend({ const top = this.$el.offset().top + this.$el.outerHeight() - 1; const left = this.$el.offset().left; - this.detailsView.$el.css({ top, left }).addClass('active'); + this.projectsView.$el.css({ top, left }).addClass('active'); this.$el.addClass('active'); - this.detailsView.onShow(); + this.projectsView.onShow(); }, @@ -169,9 +169,9 @@ const BaseFilterView = Marionette.ItemView.extend({ hideDetails () { - this.detailsView.$el.removeClass('active'); + this.projectsView.$el.removeClass('active'); this.$el.removeClass('active'); - this.detailsView.onHide(); + this.projectsView.onHide(); }, diff --git a/server/sonar-web/src/main/js/components/navigator/filters/choice-filters.js b/server/sonar-web/src/main/js/components/navigator/filters/choice-filters.js index 6a43959e55e..1bf7b2f4cc7 100644 --- a/server/sonar-web/src/main/js/components/navigator/filters/choice-filters.js +++ b/server/sonar-web/src/main/js/components/navigator/filters/choice-filters.js @@ -216,7 +216,7 @@ const ChoiceFilterView = BaseFilters.BaseFilterView.extend({ initialize (options) { BaseFilters.BaseFilterView.prototype.initialize.call(this, { - detailsView: (options && options.detailsView) ? options.detailsView : DetailsChoiceFilterView + projectsView: (options && options.projectsView) ? options.projectsView : DetailsChoiceFilterView }); let index = 0; @@ -367,9 +367,9 @@ const ChoiceFilterView = BaseFilters.BaseFilterView.extend({ }); } this.model.unset('value'); - this.detailsView.render(); - if (this.detailsView.updateCurrent) { - this.detailsView.updateCurrent(0); + this.projectsView.render(); + if (this.projectsView.updateCurrent) { + this.projectsView.updateCurrent(0); } }, diff --git a/server/sonar-web/src/main/js/components/navigator/filters/favorite-filters.js b/server/sonar-web/src/main/js/components/navigator/filters/favorite-filters.js index 3852d32983e..11b769a903a 100644 --- a/server/sonar-web/src/main/js/components/navigator/filters/favorite-filters.js +++ b/server/sonar-web/src/main/js/components/navigator/filters/favorite-filters.js @@ -69,7 +69,7 @@ const FavoriteFilterView = ChoiceFilters.ChoiceFilterView.extend({ initialize () { ChoiceFilters.ChoiceFilterView.prototype.initialize.call(this, { - detailsView: DetailsFavoriteFilterView + projectsView: DetailsFavoriteFilterView }); }, diff --git a/server/sonar-web/src/main/js/components/navigator/filters/metric-filters.js b/server/sonar-web/src/main/js/components/navigator/filters/metric-filters.js index f460810eee6..5a3be4e8f12 100644 --- a/server/sonar-web/src/main/js/components/navigator/filters/metric-filters.js +++ b/server/sonar-web/src/main/js/components/navigator/filters/metric-filters.js @@ -125,7 +125,7 @@ export default BaseFilters.BaseFilterView.extend({ initialize () { BaseFilters.BaseFilterView.prototype.initialize.call(this, { - detailsView: DetailsMetricFilterView + projectsView: DetailsMetricFilterView }); this.groupMetrics(); diff --git a/server/sonar-web/src/main/js/components/navigator/filters/more-criteria-filters.js b/server/sonar-web/src/main/js/components/navigator/filters/more-criteria-filters.js index 327f9f4d89c..0d148321b1a 100644 --- a/server/sonar-web/src/main/js/components/navigator/filters/more-criteria-filters.js +++ b/server/sonar-web/src/main/js/components/navigator/filters/more-criteria-filters.js @@ -82,7 +82,7 @@ const MoreCriteriaFilterView = ChoiceFilters.ChoiceFilterView.extend({ initialize () { ChoiceFilters.ChoiceFilterView.prototype.initialize.call(this, { - detailsView: DetailsMoreCriteriaFilterView + projectsView: DetailsMoreCriteriaFilterView }); }, diff --git a/server/sonar-web/src/main/js/components/navigator/filters/range-filters.js b/server/sonar-web/src/main/js/components/navigator/filters/range-filters.js index 3fc177c1a0a..964e73d10a7 100644 --- a/server/sonar-web/src/main/js/components/navigator/filters/range-filters.js +++ b/server/sonar-web/src/main/js/components/navigator/filters/range-filters.js @@ -72,7 +72,7 @@ const RangeFilterView = BaseFilters.BaseFilterView.extend({ initialize () { BaseFilters.BaseFilterView.prototype.initialize.call(this, { - detailsView: DetailsRangeFilterView + projectsView: DetailsRangeFilterView }); }, @@ -140,7 +140,7 @@ const RangeFilterView = BaseFilters.BaseFilterView.extend({ enabled: true }); - this.detailsView.populateInputs(); + this.projectsView.populateInputs(); } }, @@ -160,7 +160,7 @@ const RangeFilterView = BaseFilters.BaseFilterView.extend({ } }); - this.detailsView.updateLists(); + this.projectsView.updateLists(); this.model.set({ value, @@ -177,7 +177,7 @@ const RangeFilterView = BaseFilters.BaseFilterView.extend({ clear () { this.model.unset('value'); - this.detailsView.render(); + this.projectsView.render(); } }); @@ -187,7 +187,7 @@ const DateRangeFilterView = RangeFilterView.extend({ render () { RangeFilterView.prototype.render.apply(this, arguments); - this.detailsView.$('input') + this.projectsView.$('input') .prop('placeholder', '1970-01-31') .datepicker({ dateFormat: 'yy-mm-dd', diff --git a/server/sonar-web/src/main/js/components/navigator/filters/string-filters.js b/server/sonar-web/src/main/js/components/navigator/filters/string-filters.js index 5a1d11b11da..b6f6f8124db 100644 --- a/server/sonar-web/src/main/js/components/navigator/filters/string-filters.js +++ b/server/sonar-web/src/main/js/components/navigator/filters/string-filters.js @@ -55,7 +55,7 @@ export default BaseFilters.BaseFilterView.extend({ initialize () { BaseFilters.BaseFilterView.prototype.initialize.call(this, { - detailsView: DetailsStringFilterView + projectsView: DetailsStringFilterView }); }, @@ -90,7 +90,7 @@ export default BaseFilters.BaseFilterView.extend({ clear () { this.model.unset('value'); - this.detailsView.render(); + this.projectsView.render(); } }); diff --git a/server/sonar-web/src/main/js/components/store/configureStore.js b/server/sonar-web/src/main/js/components/store/configureStore.js index fee2f603bf2..d7d69d3ecea 100644 --- a/server/sonar-web/src/main/js/components/store/configureStore.js +++ b/server/sonar-web/src/main/js/components/store/configureStore.js @@ -17,18 +17,24 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { createStore, applyMiddleware } from 'redux'; +import { createStore, applyMiddleware, compose } from 'redux'; import thunk from 'redux-thunk'; const middlewares = [thunk]; +const composed = []; if (process.env.NODE_ENV !== 'production') { const createLogger = require('redux-logger'); middlewares.push(createLogger()); + + composed.push(window.devToolsExtension ? window.devToolsExtension() : f => f); } -const createStoreWithMiddleware = applyMiddleware(...middlewares)(createStore); +const finalCreateStore = compose( + applyMiddleware(...middlewares), + ...composed +)(createStore); export default function configureStore (rootReducer) { - return createStoreWithMiddleware(rootReducer); + return finalCreateStore(rootReducer); } diff --git a/server/sonar-web/src/main/less/components/react-select.less b/server/sonar-web/src/main/less/components/react-select.less index 2ef178979c5..05475d007e8 100644 --- a/server/sonar-web/src/main/less/components/react-select.less +++ b/server/sonar-web/src/main/less/components/react-select.less @@ -24,6 +24,7 @@ display: inline-block; vertical-align: middle; font-size: @smallFontSize; + text-align: left; } .Select, @@ -52,6 +53,7 @@ width: 100%; height: @formControlHeight; border: 1px solid @darkGrey; + border-collapse: separate; border-radius: 2px; background-color: #fff; color: @baseFontColor; @@ -258,7 +260,7 @@ position: absolute; top: 100%; width: 100%; - z-index: 1; + z-index: @dropdown-menu-z-index; -webkit-overflow-scrolling: touch; box-shadow: @defaultShadow; } diff --git a/server/sonar-web/src/main/less/components/search-navigator.less b/server/sonar-web/src/main/less/components/search-navigator.less index 9849cb77707..33a84b5f6a2 100644 --- a/server/sonar-web/src/main/less/components/search-navigator.less +++ b/server/sonar-web/src/main/less/components/search-navigator.less @@ -532,3 +532,9 @@ display: block; } } + +.search-navigator-intro { + width: 500px; + margin: 0 auto; + padding-top: 100px; +} diff --git a/server/sonar-web/src/main/less/pages/quality-gates.less b/server/sonar-web/src/main/less/pages/quality-gates.less index 05cb151e4c9..a4dfdd95d61 100644 --- a/server/sonar-web/src/main/less/pages/quality-gates.less +++ b/server/sonar-web/src/main/less/pages/quality-gates.less @@ -92,6 +92,9 @@ } } +.quality-gate-section { + max-width: 1440px; +} .quality-gate-section + .quality-gate-section { margin-top: @navigatorPadding; 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 13ed09f521f..fcf3c58a316 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -1861,7 +1861,16 @@ quality_gates.delete_condition.confirm.message=Are you sure you want to delete t quality_gates.project_period=over period {0} - defined at project level quality_gates.warning_tooltip=Warning Threshold quality_gates.error_tooltip=Error Threshold - +quality_gates.condition.leak.yes=Yes +quality_gates.condition.leak.no=No +quality_gates.condition.leak.unconditional=Always +quality_gates.conditions.metric=Metric +quality_gates.conditions.leak=Over Leak Period +quality_gates.conditions.operator=Operator +quality_gates.conditions.warning=Warning +quality_gates.conditions.error=Error +quality_gates.intro.1=Quality Gates are collections of simple boolean thresholds set on project measures. A project must pass each of the thresholds in order to pass the Quality Gate as a whole. +quality_gates.intro.2=It is possible to set a default Quality Gate, which will be applied to all projects not explicitly assigned to some other gate. #------------------------------------------------------------------------------ # |