From 940c3eb71b6c8fd6cb34c4ff93a1e661ec19a4c5 Mon Sep 17 00:00:00 2001 From: Stas Vilchik Date: Tue, 26 Jul 2016 16:21:19 +0200 Subject: [PATCH] SONAR-7922 Rewrite "Quality Profiles" project page (#1118) --- .../profile-admin.html | 8 +- .../src/main/js/api/quality-profiles.js | 16 ++- .../src/main/js/apps/project-admin/app.js | 19 +++- .../js/apps/project-admin/deletion/Form.js | 69 ++++++++++-- .../project-admin/quality-profiles/Header.js | 36 +++++++ .../quality-profiles/ProfileRow.js | 101 ++++++++++++++++++ .../quality-profiles/QualityProfiles.js | 75 +++++++++++++ .../project-admin/quality-profiles/Table.js | 71 ++++++++++++ .../js/apps/project-admin/store/actions.js | 70 ++++++++++++ .../js/apps/project-admin/store/profiles.js | 39 +++++++ .../project-admin/store/profilesByProject.js | 44 ++++++++ .../apps/project-admin/store/rootReducer.js | 42 ++++++++ .../main/nav/component/component-nav-menu.js | 6 +- .../app/controllers/project_controller.rb | 34 ++---- .../views/project/quality_profiles.html.erb | 3 + .../resources/org/sonar/l10n/core.properties | 1 + 16 files changed, 587 insertions(+), 47 deletions(-) create mode 100644 server/sonar-web/src/main/js/apps/project-admin/quality-profiles/Header.js create mode 100644 server/sonar-web/src/main/js/apps/project-admin/quality-profiles/ProfileRow.js create mode 100644 server/sonar-web/src/main/js/apps/project-admin/quality-profiles/QualityProfiles.js create mode 100644 server/sonar-web/src/main/js/apps/project-admin/quality-profiles/Table.js create mode 100644 server/sonar-web/src/main/js/apps/project-admin/store/actions.js create mode 100644 server/sonar-web/src/main/js/apps/project-admin/store/profiles.js create mode 100644 server/sonar-web/src/main/js/apps/project-admin/store/profilesByProject.js create mode 100644 server/sonar-web/src/main/js/apps/project-admin/store/rootReducer.js create mode 100644 server/sonar-web/src/main/webapp/WEB-INF/app/views/project/quality_profiles.html.erb diff --git a/it/it-tests/src/test/resources/authorisation/QualityProfileAdminPermissionTest/profile-admin.html b/it/it-tests/src/test/resources/authorisation/QualityProfileAdminPermissionTest/profile-admin.html index 083c7539e19..9c0efa135d0 100644 --- a/it/it-tests/src/test/resources/authorisation/QualityProfileAdminPermissionTest/profile-admin.html +++ b/it/it-tests/src/test/resources/authorisation/QualityProfileAdminPermissionTest/profile-admin.html @@ -65,7 +65,7 @@ open - /project/profile/sample + /project/quality_profiles?id=sample @@ -74,9 +74,9 @@ *Quality Profiles* - assertValue - id=submit-xoo - glob:*Update* + waitForText + id=content + *Xoo* diff --git a/server/sonar-web/src/main/js/api/quality-profiles.js b/server/sonar-web/src/main/js/api/quality-profiles.js index 4ddb455e9e6..924869837c0 100644 --- a/server/sonar-web/src/main/js/api/quality-profiles.js +++ b/server/sonar-web/src/main/js/api/quality-profiles.js @@ -26,9 +26,9 @@ import { postJSON } from '../helpers/request'; -export function getQualityProfiles () { +export function getQualityProfiles (data) { const url = '/api/qualityprofiles/search'; - return getJSON(url).then(r => r.profiles); + return getJSON(url, data).then(r => r.profiles); } export function createQualityProfile (data) { @@ -163,3 +163,15 @@ export function compareProfiles (leftKey, rightKey) { const data = { leftKey, rightKey }; return getJSON(url, data); } + +export function associateProject (profileKey, projectKey) { + const url = '/api/qualityprofiles/add_project'; + const data = { profileKey, projectKey }; + return post(url, data); +} + +export function dissociateProject (profileKey, projectKey) { + const url = '/api/qualityprofiles/remove_project'; + const data = { profileKey, projectKey }; + return post(url, data); +} diff --git a/server/sonar-web/src/main/js/apps/project-admin/app.js b/server/sonar-web/src/main/js/apps/project-admin/app.js index ee3493544d3..7bcb91f9e1c 100644 --- a/server/sonar-web/src/main/js/apps/project-admin/app.js +++ b/server/sonar-web/src/main/js/apps/project-admin/app.js @@ -19,9 +19,13 @@ */ import React from 'react'; import { render } from 'react-dom'; +import { Provider } from 'react-redux'; import { Router, Route, useRouterHistory } from 'react-router'; import { createHistory } from 'history'; import Deletion from './deletion/Deletion'; +import QualityProfiles from './quality-profiles/QualityProfiles'; +import rootReducer from './store/rootReducer'; +import configureStore from '../../components/store/configureStore'; window.sonarqube.appStarted.then(options => { const el = document.querySelector(options.el); @@ -30,12 +34,21 @@ window.sonarqube.appStarted.then(options => { basename: window.baseUrl + '/project' }); + const store = configureStore(rootReducer); + const withComponent = ComposedComponent => props => ; render(( - - - + + + + + + ), el); }); diff --git a/server/sonar-web/src/main/js/apps/project-admin/deletion/Form.js b/server/sonar-web/src/main/js/apps/project-admin/deletion/Form.js index 81d52fb3ba2..354935418fa 100644 --- a/server/sonar-web/src/main/js/apps/project-admin/deletion/Form.js +++ b/server/sonar-web/src/main/js/apps/project-admin/deletion/Form.js @@ -18,24 +18,39 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import React from 'react'; -import ConfirmationModal from './ConfirmationModal'; -import { translate } from '../../../helpers/l10n'; +import { translate, translateWithParameters } from '../../../helpers/l10n'; +import { deleteProject } from '../../../api/components'; export default class Form extends React.Component { static propTypes = { component: React.PropTypes.object.isRequired }; + state = { + confirmation: false, + loading: false + }; + handleDelete (e) { e.preventDefault(); - new ConfirmationModal({ project: this.props.component }) - .on('done', () => { - window.location = window.baseUrl + '/'; - }) - .render(); + this.setState({ confirmation: true }); } - render () { + confirmDeleteClick (e) { + e.preventDefault(); + this.setState({ loading: true }); + deleteProject(this.props.component.key).then(() => { + window.location = window.baseUrl + '/'; + }); + } + + cancelDeleteClick (e) { + e.preventDefault(); + e.target.blur(); + this.setState({ confirmation: false }); + } + + renderInitial () { return (
+ + {this.state.loading ? ( + + ) : ( + + {translate('cancel')} + + )} + + + ); + } + + render () { + return this.state.confirmation ? + this.renderConfirmation() : + this.renderInitial(); + } } diff --git a/server/sonar-web/src/main/js/apps/project-admin/quality-profiles/Header.js b/server/sonar-web/src/main/js/apps/project-admin/quality-profiles/Header.js new file mode 100644 index 00000000000..f93fb46e926 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/project-admin/quality-profiles/Header.js @@ -0,0 +1,36 @@ +/* + * 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 class Header extends React.Component { + render () { + return ( +
+

+ {translate('project_quality_profiles.page')} +

+
+ {translate('project_quality_profiles.page.description')} +
+
+ ); + } +} diff --git a/server/sonar-web/src/main/js/apps/project-admin/quality-profiles/ProfileRow.js b/server/sonar-web/src/main/js/apps/project-admin/quality-profiles/ProfileRow.js new file mode 100644 index 00000000000..50227fa19a9 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/project-admin/quality-profiles/ProfileRow.js @@ -0,0 +1,101 @@ +/* + * 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 shallowCompare from 'react-addons-shallow-compare'; +import Select from 'react-select'; +import { translate } from '../../../helpers/l10n'; + +export default class ProfileRow extends React.Component { + static propTypes = { + profile: React.PropTypes.object.isRequired, + possibleProfiles: React.PropTypes.array.isRequired, + onChangeProfile: React.PropTypes.func.isRequired + }; + + state = { + loading: false + }; + + shouldComponentUpdate (nextProps, nextState) { + return shallowCompare(this, nextProps, nextState); + } + + componentWillUpdate (nextProps) { + if (nextProps.profile !== this.props.profile) { + this.setState({ loading: false }); + } + } + + handleChange (option) { + if (this.props.profile.key !== option.value) { + this.setState({ loading: true }); + this.props.onChangeProfile(this.props.profile.key, option.value); + } + } + + renderProfileName (profileOption) { + if (profileOption.isDefault) { + return ( + + {translate('default')} + {': '} + {profileOption.label} + + ); + } + + return profileOption.label; + } + + renderProfileSelect () { + const { profile, possibleProfiles } = this.props; + + const options = possibleProfiles.map(profile => ({ + value: profile.key, + label: profile.name, + isDefault: profile.isDefault + })); + + return ( +