From d2da7f30d512284c943b82abc135483b59b85536 Mon Sep 17 00:00:00 2001 From: Stas Vilchik Date: Thu, 1 Sep 2016 17:30:34 +0200 Subject: SONAR-5856 Rewrite Settings page (#1163) --- .../config/webpack/webpack.config.base.js | 1 + server/sonar-web/scripts/start.js | 6 +- server/sonar-web/src/main/js/api/settings.js | 69 +++++++ server/sonar-web/src/main/js/api/users.js | 6 + .../main/js/apps/settings/__tests__/utils-test.js | 46 +++++ server/sonar-web/src/main/js/apps/settings/app.js | 50 +++++ .../apps/settings/components/AllCategoriesList.js | 37 ++++ .../src/main/js/apps/settings/components/App.js | 101 ++++++++++ .../js/apps/settings/components/CategoriesList.js | 71 +++++++ .../settings/components/CategoryDefinitionsList.js | 37 ++++ .../main/js/apps/settings/components/Definition.js | 195 +++++++++++++++++++ .../apps/settings/components/DefinitionChanges.js | 59 ++++++ .../apps/settings/components/DefinitionDefaults.js | 60 ++++++ .../js/apps/settings/components/DefinitionsList.js | 45 +++++ .../settings/components/GlobalMessagesContainer.js | 28 +++ .../main/js/apps/settings/components/PageHeader.js | 44 +++++ .../components/SubCategoryDefinitionsList.js | 62 ++++++ .../js/apps/settings/components/inputs/Input.js | 51 +++++ .../settings/components/inputs/InputForBoolean.js | 48 +++++ .../settings/components/inputs/InputForNumber.js | 29 +++ .../settings/components/inputs/InputForPassword.js | 89 +++++++++ .../components/inputs/InputForSingleSelectList.js | 50 +++++ .../settings/components/inputs/InputForString.js | 29 +++ .../settings/components/inputs/InputForText.js | 40 ++++ .../settings/components/inputs/MultiValueInput.js | 90 +++++++++ .../settings/components/inputs/PrimitiveInput.js | 75 +++++++ .../settings/components/inputs/PropertySetInput.js | 110 +++++++++++ .../apps/settings/components/inputs/SimpleInput.js | 45 +++++ .../components/inputs/__tests__/Input-test.js | 60 ++++++ .../inputs/__tests__/InputForBoolean-test.js | 77 ++++++++ .../inputs/__tests__/InputForNumber-test.js | 43 ++++ .../inputs/__tests__/InputForPassword-test.js | 97 +++++++++ .../__tests__/InputForSingleSelectList-test.js | 66 +++++++ .../inputs/__tests__/InputForString-test.js | 43 ++++ .../inputs/__tests__/InputForText-test.js | 60 ++++++ .../inputs/__tests__/MultiValueInput-test.js | 103 ++++++++++ .../inputs/__tests__/SimpleInput-test.js | 66 +++++++ .../src/main/js/apps/settings/constants.js | 28 +++ .../src/main/js/apps/settings/propTypes.js | 27 +++ .../src/main/js/apps/settings/store/actions.js | 85 ++++++++ .../js/apps/settings/store/definitions/actions.js | 30 +++ .../js/apps/settings/store/definitions/reducer.js | 56 ++++++ .../src/main/js/apps/settings/store/rootReducer.js | 55 ++++++ .../store/settingsPage/changedValues/actions.js | 33 ++++ .../store/settingsPage/changedValues/reducer.js | 37 ++++ .../settings/store/settingsPage/loading/actions.js | 31 +++ .../settings/store/settingsPage/loading/reducer.js | 36 ++++ .../js/apps/settings/store/settingsPage/reducer.js | 38 ++++ .../settingsPage/validationMessages/actions.js | 33 ++++ .../settingsPage/validationMessages/reducer.js | 36 ++++ .../main/js/apps/settings/store/values/actions.js | 30 +++ .../main/js/apps/settings/store/values/reducer.js | 34 ++++ .../sonar-web/src/main/js/apps/settings/styles.css | 157 +++++++++++++++ .../sonar-web/src/main/js/apps/settings/utils.js | 140 +++++++++++++ .../src/main/js/components/controls/Toggle.js | 51 +++++ .../components/controls/__tests__/Toggle-test.js | 53 +++++ .../src/main/js/components/controls/styles.css | 51 +++++ server/sonar-web/src/main/js/helpers/l10n.js | 5 + server/sonar-web/src/main/js/helpers/request.js | 2 +- .../src/main/less/components/react-select.less | 2 +- server/sonar-web/src/main/less/init/forms.less | 22 ++- server/sonar-web/src/main/less/init/tables.less | 20 ++ .../WEB-INF/app/controllers/project_controller.rb | 16 +- .../WEB-INF/app/controllers/settings_controller.rb | 88 +-------- .../WEB-INF/app/views/project/settings.html.erb | 8 +- .../WEB-INF/app/views/settings/_error.html.erb | 5 - .../app/views/settings/_multi_value.html.erb | 5 - .../app/views/settings/_properties.html.erb | 216 --------------------- .../app/views/settings/_set_instance.html.erb | 42 ---- .../WEB-INF/app/views/settings/_settings.html.erb | 42 ---- .../app/views/settings/_single_value.html.erb | 1 - .../WEB-INF/app/views/settings/_special.html.erb | 1 - .../app/views/settings/_type_BOOLEAN.html.erb | 1 - .../app/views/settings/_type_FLOAT.html.erb | 5 - .../app/views/settings/_type_INTEGER.html.erb | 5 - .../app/views/settings/_type_LICENSE.html.erb | 62 ------ .../app/views/settings/_type_METRIC.html.erb | 24 --- .../app/views/settings/_type_PASSWORD.html.erb | 7 - .../app/views/settings/_type_PROPERTY_SET.html.erb | 11 -- .../_type_PROPERTY_SET_DEFINITION.html.erb | 38 ---- .../settings/_type_REGULAR_EXPRESSION.html.erb | 5 - .../settings/_type_SINGLE_SELECT_LIST.html.erb | 2 - .../app/views/settings/_type_STRING.html.erb | 5 - .../WEB-INF/app/views/settings/_type_TEXT.html.erb | 5 - .../app/views/settings/_type_USER_LOGIN.html.erb | 5 - .../WEB-INF/app/views/settings/index.html.erb | 6 +- server/sonar-web/tests/utils.js | 38 ++++ 87 files changed, 3321 insertions(+), 602 deletions(-) create mode 100644 server/sonar-web/src/main/js/api/settings.js create mode 100644 server/sonar-web/src/main/js/apps/settings/__tests__/utils-test.js create mode 100644 server/sonar-web/src/main/js/apps/settings/app.js create mode 100644 server/sonar-web/src/main/js/apps/settings/components/AllCategoriesList.js create mode 100644 server/sonar-web/src/main/js/apps/settings/components/App.js create mode 100644 server/sonar-web/src/main/js/apps/settings/components/CategoriesList.js create mode 100644 server/sonar-web/src/main/js/apps/settings/components/CategoryDefinitionsList.js create mode 100644 server/sonar-web/src/main/js/apps/settings/components/Definition.js create mode 100644 server/sonar-web/src/main/js/apps/settings/components/DefinitionChanges.js create mode 100644 server/sonar-web/src/main/js/apps/settings/components/DefinitionDefaults.js create mode 100644 server/sonar-web/src/main/js/apps/settings/components/DefinitionsList.js create mode 100644 server/sonar-web/src/main/js/apps/settings/components/GlobalMessagesContainer.js create mode 100644 server/sonar-web/src/main/js/apps/settings/components/PageHeader.js create mode 100644 server/sonar-web/src/main/js/apps/settings/components/SubCategoryDefinitionsList.js create mode 100644 server/sonar-web/src/main/js/apps/settings/components/inputs/Input.js create mode 100644 server/sonar-web/src/main/js/apps/settings/components/inputs/InputForBoolean.js create mode 100644 server/sonar-web/src/main/js/apps/settings/components/inputs/InputForNumber.js create mode 100644 server/sonar-web/src/main/js/apps/settings/components/inputs/InputForPassword.js create mode 100644 server/sonar-web/src/main/js/apps/settings/components/inputs/InputForSingleSelectList.js create mode 100644 server/sonar-web/src/main/js/apps/settings/components/inputs/InputForString.js create mode 100644 server/sonar-web/src/main/js/apps/settings/components/inputs/InputForText.js create mode 100644 server/sonar-web/src/main/js/apps/settings/components/inputs/MultiValueInput.js create mode 100644 server/sonar-web/src/main/js/apps/settings/components/inputs/PrimitiveInput.js create mode 100644 server/sonar-web/src/main/js/apps/settings/components/inputs/PropertySetInput.js create mode 100644 server/sonar-web/src/main/js/apps/settings/components/inputs/SimpleInput.js create mode 100644 server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/Input-test.js create mode 100644 server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForBoolean-test.js create mode 100644 server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForNumber-test.js create mode 100644 server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForPassword-test.js create mode 100644 server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForSingleSelectList-test.js create mode 100644 server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForString-test.js create mode 100644 server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForText-test.js create mode 100644 server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/MultiValueInput-test.js create mode 100644 server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/SimpleInput-test.js create mode 100644 server/sonar-web/src/main/js/apps/settings/constants.js create mode 100644 server/sonar-web/src/main/js/apps/settings/propTypes.js create mode 100644 server/sonar-web/src/main/js/apps/settings/store/actions.js create mode 100644 server/sonar-web/src/main/js/apps/settings/store/definitions/actions.js create mode 100644 server/sonar-web/src/main/js/apps/settings/store/definitions/reducer.js create mode 100644 server/sonar-web/src/main/js/apps/settings/store/rootReducer.js create mode 100644 server/sonar-web/src/main/js/apps/settings/store/settingsPage/changedValues/actions.js create mode 100644 server/sonar-web/src/main/js/apps/settings/store/settingsPage/changedValues/reducer.js create mode 100644 server/sonar-web/src/main/js/apps/settings/store/settingsPage/loading/actions.js create mode 100644 server/sonar-web/src/main/js/apps/settings/store/settingsPage/loading/reducer.js create mode 100644 server/sonar-web/src/main/js/apps/settings/store/settingsPage/reducer.js create mode 100644 server/sonar-web/src/main/js/apps/settings/store/settingsPage/validationMessages/actions.js create mode 100644 server/sonar-web/src/main/js/apps/settings/store/settingsPage/validationMessages/reducer.js create mode 100644 server/sonar-web/src/main/js/apps/settings/store/values/actions.js create mode 100644 server/sonar-web/src/main/js/apps/settings/store/values/reducer.js create mode 100644 server/sonar-web/src/main/js/apps/settings/styles.css create mode 100644 server/sonar-web/src/main/js/apps/settings/utils.js create mode 100644 server/sonar-web/src/main/js/components/controls/Toggle.js create mode 100644 server/sonar-web/src/main/js/components/controls/__tests__/Toggle-test.js delete mode 100644 server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_error.html.erb delete mode 100644 server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_multi_value.html.erb delete mode 100644 server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_properties.html.erb delete mode 100644 server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_set_instance.html.erb delete mode 100644 server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_settings.html.erb delete mode 100644 server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_single_value.html.erb delete mode 100644 server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_special.html.erb delete mode 100644 server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_BOOLEAN.html.erb delete mode 100644 server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_FLOAT.html.erb delete mode 100644 server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_INTEGER.html.erb delete mode 100644 server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_LICENSE.html.erb delete mode 100644 server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_METRIC.html.erb delete mode 100644 server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_PASSWORD.html.erb delete mode 100644 server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_PROPERTY_SET.html.erb delete mode 100644 server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_PROPERTY_SET_DEFINITION.html.erb delete mode 100644 server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_REGULAR_EXPRESSION.html.erb delete mode 100644 server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_SINGLE_SELECT_LIST.html.erb delete mode 100644 server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_STRING.html.erb delete mode 100644 server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_TEXT.html.erb delete mode 100644 server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_USER_LOGIN.html.erb create mode 100644 server/sonar-web/tests/utils.js (limited to 'server') diff --git a/server/sonar-web/config/webpack/webpack.config.base.js b/server/sonar-web/config/webpack/webpack.config.base.js index 0533884f245..2e0dcc48236 100644 --- a/server/sonar-web/config/webpack/webpack.config.base.js +++ b/server/sonar-web/config/webpack/webpack.config.base.js @@ -45,6 +45,7 @@ module.exports = { 'projects': './src/main/js/apps/projects/app.js', 'quality-gates': './src/main/js/apps/quality-gates/app.js', 'quality-profiles': './src/main/js/apps/quality-profiles/app.js', + 'settings': './src/main/js/apps/settings/app.js', 'source-viewer': './src/main/js/apps/source-viewer/app.js', 'system': './src/main/js/apps/system/app.js', 'update-center': './src/main/js/apps/update-center/app.js', diff --git a/server/sonar-web/scripts/start.js b/server/sonar-web/scripts/start.js index ceb9d61928a..905793318e2 100644 --- a/server/sonar-web/scripts/start.js +++ b/server/sonar-web/scripts/start.js @@ -121,14 +121,10 @@ function setupCompiler () { function runDevServer (port) { app.use(require('webpack-dev-middleware')(compiler, { noInfo: true, - quiet: true, publicPath: config.output.publicPath })); - app.use(require('webpack-hot-middleware')(compiler, { - noInfo: true, - quiet: true - })); + app.use(require('webpack-hot-middleware')(compiler)); app.all('*', proxy(API_HOST, { forwardPath: function (req) { diff --git a/server/sonar-web/src/main/js/api/settings.js b/server/sonar-web/src/main/js/api/settings.js new file mode 100644 index 00000000000..14396243a2f --- /dev/null +++ b/server/sonar-web/src/main/js/api/settings.js @@ -0,0 +1,69 @@ +/* + * 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 omitBy from 'lodash/omitBy'; +import { getJSON, post } from '../helpers/request'; +import { TYPE_PROPERTY_SET } from '../apps/settings/constants'; + +export function getDefinitions (componentKey) { + const url = '/api/settings/list_definitions'; + const data = componentKey ? { componentKey } : {}; + return getJSON(url, data).then(r => r.definitions); +} + +export function getValues (keys, componentKey) { + const url = '/api/settings/values'; + const data = { keys }; + if (componentKey) { + data.componentKey = componentKey; + } + return getJSON(url, data).then(r => r.settings); +} + +export function setSettingValue (definition, value, componentKey) { + const url = '/api/settings/set'; + + const { key } = definition; + const data = { key }; + + if (definition.multiValues) { + data.values = value; + } else if (definition.type === TYPE_PROPERTY_SET) { + data.fieldValues = value + .map(fields => omitBy(fields, value => value == null)) + .map(JSON.stringify); + } else { + data.value = value; + } + + if (componentKey) { + data.componentKey = componentKey; + } + + return post(url, data); +} + +export function resetSettingValue (key, componentKey) { + const url = '/api/settings/reset'; + const data = { key }; + if (componentKey) { + data.componentKey = componentKey; + } + return post(url, data); +} diff --git a/server/sonar-web/src/main/js/api/users.js b/server/sonar-web/src/main/js/api/users.js index 80865674d80..e5d5d15f7bc 100644 --- a/server/sonar-web/src/main/js/api/users.js +++ b/server/sonar-web/src/main/js/api/users.js @@ -39,3 +39,9 @@ export function getIdentityProviders () { const url = '/api/users/identity_providers'; return getJSON(url); } + +export function searchUsers (query) { + const url = '/api/users/search'; + const data = { q: query }; + return getJSON(url, data); +} diff --git a/server/sonar-web/src/main/js/apps/settings/__tests__/utils-test.js b/server/sonar-web/src/main/js/apps/settings/__tests__/utils-test.js new file mode 100644 index 00000000000..16b709d99ab --- /dev/null +++ b/server/sonar-web/src/main/js/apps/settings/__tests__/utils-test.js @@ -0,0 +1,46 @@ +/* + * 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 { expect } from 'chai'; +import { getEmptyValue } from '../utils'; +import { TYPE_PROPERTY_SET, TYPE_STRING, TYPE_SINGLE_SELECT_LIST, TYPE_BOOLEAN } from '../constants'; + +const fields = [ + { key: 'foo', type: TYPE_STRING }, + { key: 'bar', type: TYPE_SINGLE_SELECT_LIST } +]; + +describe('Settings :: Utils', () => { + describe('#getEmptyValue()', () => { + it('should work for property sets', () => { + const setting = { type: TYPE_PROPERTY_SET, fields }; + expect(getEmptyValue(setting)).to.deep.equal([{ foo: '', bar: null }]); + }); + + it('should work for multi values string', () => { + const setting = { type: TYPE_STRING, multiValues: true }; + expect(getEmptyValue(setting)).to.deep.equal(['']); + }); + + it('should work for multi values boolean', () => { + const setting = { type: TYPE_BOOLEAN, multiValues: true }; + expect(getEmptyValue(setting)).to.deep.equal([null]); + }); + }); +}); diff --git a/server/sonar-web/src/main/js/apps/settings/app.js b/server/sonar-web/src/main/js/apps/settings/app.js new file mode 100644 index 00000000000..cf9dfcf07ad --- /dev/null +++ b/server/sonar-web/src/main/js/apps/settings/app.js @@ -0,0 +1,50 @@ +/* + * 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 { render } from 'react-dom'; +import { Provider } from 'react-redux'; +import { Router, Route, Redirect, useRouterHistory } from 'react-router'; +import { createHistory } from 'history'; +import App from './components/App'; +import rootReducer from './store/rootReducer'; +import configureStore from '../../components/store/configureStore'; + +window.sonarqube.appStarted.then(options => { + const el = document.querySelector(options.el); + + const controller = options.component ? '/project/settings' : '/settings'; + const history = useRouterHistory(createHistory)({ + basename: window.baseUrl + controller + }); + + const store = configureStore(rootReducer); + + const withComponent = ComposedComponent => props => + ; + + render(( + + + + + + + ), el); +}); diff --git a/server/sonar-web/src/main/js/apps/settings/components/AllCategoriesList.js b/server/sonar-web/src/main/js/apps/settings/components/AllCategoriesList.js new file mode 100644 index 00000000000..44d16ad6bdc --- /dev/null +++ b/server/sonar-web/src/main/js/apps/settings/components/AllCategoriesList.js @@ -0,0 +1,37 @@ +/* + * 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 { connect } from 'react-redux'; +import CategoriesList from './CategoriesList'; +import { getAllCategories } from '../store/rootReducer'; + +class AllCategoriesList extends React.Component { + render () { + return ; + } +} + +const mapStateToProps = state => ({ + categories: getAllCategories(state) +}); + +export default connect( + mapStateToProps +)(AllCategoriesList); diff --git a/server/sonar-web/src/main/js/apps/settings/components/App.js b/server/sonar-web/src/main/js/apps/settings/components/App.js new file mode 100644 index 00000000000..424675ba502 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/settings/components/App.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 { connect } from 'react-redux'; +import PageHeader from './PageHeader'; +import CategoryDefinitionsList from './CategoryDefinitionsList'; +import AllCategoriesList from './AllCategoriesList'; +import GlobalMessagesContainer from './GlobalMessagesContainer'; +import { fetchSettings } from '../store/actions'; +import { getDefaultCategory } from '../store/rootReducer'; +import '../styles.css'; + +class App extends React.Component { + static propTypes = { + component: React.PropTypes.object, + fetchSettings: React.PropTypes.func.isRequired, + defaultCategory: React.PropTypes.string + }; + + state = { loaded: false }; + + componentDidMount () { + document.querySelector('html').classList.add('dashboard-page'); + const componentKey = this.props.component ? this.props.component.key : null; + this.props.fetchSettings(componentKey).then(() => { + this.setState({ loaded: true }); + }); + } + + shouldComponentUpdate (nextProps, nextState) { + return shallowCompare(this, nextProps, nextState); + } + + componentDidUpdate (prevProps) { + if (prevProps.component !== this.props.component) { + const componentKey = this.props.component ? this.props.component.key : null; + this.props.fetchSettings(componentKey); + } + } + + componentWillUnmount () { + document.querySelector('html').classList.remove('dashboard-page'); + } + + render () { + if (!this.state.loaded) { + return null; + } + + const { query } = this.props.location; + const selectedCategory = query.category || this.props.defaultCategory; + + return ( +
+ + +
+
+ +
+
+ +
+
+
+ ); + } +} + +const mapStateToProps = state => ({ + defaultCategory: getDefaultCategory(state) +}); + +export default connect( + mapStateToProps, + { fetchSettings } +)(App); + diff --git a/server/sonar-web/src/main/js/apps/settings/components/CategoriesList.js b/server/sonar-web/src/main/js/apps/settings/components/CategoriesList.js new file mode 100644 index 00000000000..aa833d5ce90 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/settings/components/CategoriesList.js @@ -0,0 +1,71 @@ +/* + * 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 sortBy from 'lodash/sortBy'; +import { IndexLink } from 'react-router'; +import { getCategoryName } from '../utils'; + +export default class CategoriesList extends React.Component { + static propTypes = { + categories: React.PropTypes.array.isRequired, + selectedCategory: React.PropTypes.string.isRequired, + defaultCategory: React.PropTypes.string.isRequired + }; + + shouldComponentUpdate (nextProps, nextState) { + return shallowCompare(this, nextProps, nextState); + } + + renderLink (category) { + const query = {}; + + if (category.key !== this.props.defaultCategory) { + query.category = category.key.toLowerCase(); + } + + if (this.props.component) { + query.id = this.props.component.key; + } + + const className = category.key.toLowerCase() === this.props.selectedCategory.toLowerCase() ? 'active' : ''; + + return ( + + {category.name} + + ); + } + + render () { + const categoriesWithName = this.props.categories.map(key => ({ key, name: getCategoryName(key) })); + const sortedCategories = sortBy(categoriesWithName, category => category.name.toLowerCase()); + + return ( +
    + {sortedCategories.map(category => ( +
  • + {this.renderLink(category)} +
  • + ))} +
+ ); + } +} diff --git a/server/sonar-web/src/main/js/apps/settings/components/CategoryDefinitionsList.js b/server/sonar-web/src/main/js/apps/settings/components/CategoryDefinitionsList.js new file mode 100644 index 00000000000..cc2e6d8c24e --- /dev/null +++ b/server/sonar-web/src/main/js/apps/settings/components/CategoryDefinitionsList.js @@ -0,0 +1,37 @@ +/* + * 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 { connect } from 'react-redux'; +import SubCategoryDefinitionsList from './SubCategoryDefinitionsList'; +import { getSettingsForCategory } from '../store/rootReducer'; + +class CategoryDefinitionsList extends React.Component { + render () { + return ; + } +} + +const mapStateToProps = (state, ownProps) => ({ + settings: getSettingsForCategory(state, ownProps.category) +}); + +export default connect( + mapStateToProps +)(CategoryDefinitionsList); diff --git a/server/sonar-web/src/main/js/apps/settings/components/Definition.js b/server/sonar-web/src/main/js/apps/settings/components/Definition.js new file mode 100644 index 00000000000..66b19fd023a --- /dev/null +++ b/server/sonar-web/src/main/js/apps/settings/components/Definition.js @@ -0,0 +1,195 @@ +/* + * 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 { connect } from 'react-redux'; +import shallowCompare from 'react-addons-shallow-compare'; +import classNames from 'classnames'; +import Input from './inputs/Input'; +import DefinitionDefaults from './DefinitionDefaults'; +import DefinitionChanges from './DefinitionChanges'; +import { getPropertyName, getPropertyDescription, isEmptyValue, getSettingValue, isDefaultOrInherited } from '../utils'; +import { translateWithParameters, translate } from '../../../helpers/l10n'; +import { resetValue, saveValue } from '../store/actions'; +import { isLoading, getValidationMessage, getChangedValue } from '../store/rootReducer'; +import { failValidation, passValidation } from '../store/settingsPage/validationMessages/actions'; +import { cancelChange, changeValue } from '../store/settingsPage/changedValues/actions'; + +class Definition extends React.Component { + static propTypes = { + component: React.PropTypes.object, + setting: React.PropTypes.object.isRequired, + changedValue: React.PropTypes.any, + loading: React.PropTypes.bool.isRequired, + validationMessage: React.PropTypes.string, + + changeValue: React.PropTypes.func.isRequired, + cancelChange: React.PropTypes.func.isRequired, + saveValue: React.PropTypes.func.isRequired, + resetValue: React.PropTypes.func.isRequired, + failValidation: React.PropTypes.func.isRequired, + passValidation: React.PropTypes.func.isRequired + }; + + state = { + success: false + }; + + componentDidMount () { + this.mounted = true; + } + + shouldComponentUpdate (nextProps, nextState) { + return shallowCompare(this, nextProps, nextState); + } + + componentWillUnmount () { + this.mounted = false; + } + + safeSetState (changes) { + if (this.mounted) { + this.setState(changes); + } + } + + handleChange (value) { + clearTimeout(this.timeout); + return this.props.changeValue(this.props.setting.definition.key, value); + } + + handleReset () { + const componentKey = this.props.component ? this.props.component.key : null; + const { definition } = this.props.setting; + return this.props.resetValue(definition.key, componentKey).then(() => { + this.safeSetState({ success: true }); + this.timeout = setTimeout(() => this.safeSetState({ success: false }), 3000); + }).catch(() => { /* do nothing */ }); + } + + handleCancel () { + this.props.cancelChange(this.props.setting.definition.key); + this.props.passValidation(this.props.setting.definition.key); + } + + handleSave () { + this.safeSetState({ success: false }); + const { definition } = this.props.setting; + if (isEmptyValue(definition, this.props.changedValue)) { + this.props.failValidation(definition.key, translate('settings.state.value_cant_be_empty')); + return; + } + + const componentKey = this.props.component ? this.props.component.key : null; + this.props.saveValue(this.props.setting.definition.key, componentKey).then(() => { + this.safeSetState({ success: true }); + this.timeout = setTimeout(() => this.safeSetState({ success: false }), 3000); + }).catch(() => { /* do nothing */ }); + } + + render () { + const { setting, changedValue, loading } = this.props; + const { definition } = setting; + const propertyName = getPropertyName(definition); + + const hasValueChanged = changedValue != null; + + const className = classNames('settings-definition', { + 'settings-definition-changed': hasValueChanged + }); + + const effectiveValue = hasValueChanged ? changedValue : getSettingValue(setting); + + const isDefault = isDefaultOrInherited(setting) && !hasValueChanged; + + return ( +
+
+

+ {propertyName} +

+ +
+ +
+ {translateWithParameters('settings.key_x', definition.key)} +
+
+ +
+ + + {!hasValueChanged && ( + this.handleReset()}/> + )} + + {hasValueChanged && ( + + )} + +
+ {loading && ( + + + + + {translate('settings.state.saving')} + + )} + + {!loading && (this.props.validationMessage != null) && ( + + + + + {translateWithParameters('settings.state.validation_failed', this.props.validationMessage)} + + )} + + {!loading && this.state.success && ( + + + + + {translate('settings.state.saved')} + + )} +
+
+
+ ); + } +} + +const mapStateToProps = (state, ownProps) => ({ + changedValue: getChangedValue(state, ownProps.setting.definition.key), + loading: isLoading(state, ownProps.setting.definition.key), + validationMessage: getValidationMessage(state, ownProps.setting.definition.key) +}); + +export default connect( + mapStateToProps, + { changeValue, saveValue, resetValue, failValidation, passValidation, cancelChange } +)(Definition); diff --git a/server/sonar-web/src/main/js/apps/settings/components/DefinitionChanges.js b/server/sonar-web/src/main/js/apps/settings/components/DefinitionChanges.js new file mode 100644 index 00000000000..edbeafecf9e --- /dev/null +++ b/server/sonar-web/src/main/js/apps/settings/components/DefinitionChanges.js @@ -0,0 +1,59 @@ +/* + * 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 { translate } from '../../../helpers/l10n'; + +export default class DefinitionChanges extends React.Component { + static propTypes = { + onSave: React.PropTypes.func.isRequired, + onCancel: React.PropTypes.func.isRequired + }; + + shouldComponentUpdate (nextProps, nextState) { + return shallowCompare(this, nextProps, nextState); + } + + handleSaveClick (e) { + e.preventDefault(); + e.target.blur(); + this.props.onSave(); + } + + handleCancelChange (e) { + e.preventDefault(); + e.target.blur(); + this.props.onCancel(); + } + + render () { + return ( +
+ + + +
+ ); + } +} diff --git a/server/sonar-web/src/main/js/apps/settings/components/DefinitionDefaults.js b/server/sonar-web/src/main/js/apps/settings/components/DefinitionDefaults.js new file mode 100644 index 00000000000..452b24c5ea5 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/settings/components/DefinitionDefaults.js @@ -0,0 +1,60 @@ +/* + * 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 { getSettingValue, isEmptyValue, getDefaultValue } from '../utils'; +import { translate } from '../../../helpers/l10n'; + +export default class DefinitionDefaults extends React.Component { + static propTypes = { + setting: React.PropTypes.object.isRequired, + isDefault: React.PropTypes.bool.isRequired, + onReset: React.PropTypes.func.isRequired + }; + + handleReset (e) { + e.preventDefault(); + e.target.blur(); + this.props.onReset(); + } + + render () { + const { setting, isDefault } = this.props; + const { definition } = setting; + + const isExplicitlySet = !isDefault && !isEmptyValue(definition, getSettingValue(setting)); + + return ( +
+ {isDefault && ( +
+ {translate('settings._default')} +
+ )} + + {isExplicitlySet && ( +
+ + {translate('default')}{': '}{getDefaultValue(setting)} +
+ )} +
+ ); + } +} diff --git a/server/sonar-web/src/main/js/apps/settings/components/DefinitionsList.js b/server/sonar-web/src/main/js/apps/settings/components/DefinitionsList.js new file mode 100644 index 00000000000..77d0d7fa95b --- /dev/null +++ b/server/sonar-web/src/main/js/apps/settings/components/DefinitionsList.js @@ -0,0 +1,45 @@ +/* + * 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 Definition from './Definition'; + +export default class DefinitionsList extends React.Component { + static propTypes = { + component: React.PropTypes.object, + settings: React.PropTypes.array.isRequired + }; + + shouldComponentUpdate (nextProps, nextState) { + return shallowCompare(this, nextProps, nextState); + } + + render () { + return ( +
    + {this.props.settings.map(setting => ( +
  • + +
  • + ))} +
+ ); + } +} diff --git a/server/sonar-web/src/main/js/apps/settings/components/GlobalMessagesContainer.js b/server/sonar-web/src/main/js/apps/settings/components/GlobalMessagesContainer.js new file mode 100644 index 00000000000..f64ef91664b --- /dev/null +++ b/server/sonar-web/src/main/js/apps/settings/components/GlobalMessagesContainer.js @@ -0,0 +1,28 @@ +/* + * 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 GlobalMessages from '../../../components/controls/GlobalMessages'; +import { getGlobalMessages } from '../store/rootReducer'; + +const mapStateToProps = state => ({ + messages: getGlobalMessages(state) +}); + +export default connect(mapStateToProps)(GlobalMessages); diff --git a/server/sonar-web/src/main/js/apps/settings/components/PageHeader.js b/server/sonar-web/src/main/js/apps/settings/components/PageHeader.js new file mode 100644 index 00000000000..34e402122cb --- /dev/null +++ b/server/sonar-web/src/main/js/apps/settings/components/PageHeader.js @@ -0,0 +1,44 @@ +/* + * 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 PageHeader extends React.Component { + static propTypes = { + component: React.PropTypes.object + }; + + render () { + const title = this.props.component != null ? + translate('project_settings.page') : + translate('settings.page'); + + const description = this.props.component != null ? + translate('project_settings.page.description') : + translate('settings.page.description'); + + return ( +
+

{title}

+
{description}
+
+ ); + } +} diff --git a/server/sonar-web/src/main/js/apps/settings/components/SubCategoryDefinitionsList.js b/server/sonar-web/src/main/js/apps/settings/components/SubCategoryDefinitionsList.js new file mode 100644 index 00000000000..f61d6b2aa7a --- /dev/null +++ b/server/sonar-web/src/main/js/apps/settings/components/SubCategoryDefinitionsList.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 from 'react'; +import shallowCompare from 'react-addons-shallow-compare'; +import groupBy from 'lodash/groupBy'; +import sortBy from 'lodash/sortBy'; +import DefinitionsList from './DefinitionsList'; +import { getSubCategoryName, getSubCategoryDescription } from '../utils'; + +export default class SubCategoryDefinitionsList extends React.Component { + static propTypes = { + component: React.PropTypes.object, + settings: React.PropTypes.array.isRequired + }; + + shouldComponentUpdate (nextProps, nextState) { + return shallowCompare(this, nextProps, nextState); + } + + render () { + const bySubCategory = groupBy(this.props.settings, setting => setting.definition.subCategory); + const subCategories = Object.keys(bySubCategory).map(key => ({ + key, + name: getSubCategoryName(bySubCategory[key][0].definition.category, key), + description: getSubCategoryDescription(bySubCategory[key][0].definition.category, key) + })); + const sortedSubCategories = sortBy(subCategories, subCategory => subCategory.name.toLowerCase()); + + return ( +
    + {sortedSubCategories.map(subCategory => ( +
  • +

    {subCategory.name}

    + {subCategory.description != null && ( +
    + {subCategory.description} +
    + )} + +
  • + ))} +
+ ); + } +} diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/Input.js b/server/sonar-web/src/main/js/apps/settings/components/inputs/Input.js new file mode 100644 index 00000000000..02fcbfacef2 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/settings/components/inputs/Input.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. + */ +import React from 'react'; +import shallowCompare from 'react-addons-shallow-compare'; +import PropertySetInput from './PropertySetInput'; +import MultiValueInput from './MultiValueInput'; +import PrimitiveInput from './PrimitiveInput'; +import { TYPE_PROPERTY_SET } from '../../constants'; + +export default class Input extends React.Component { + static propTypes = { + setting: React.PropTypes.object.isRequired, + value: React.PropTypes.any, + onChange: React.PropTypes.func.isRequired + }; + + shouldComponentUpdate (nextProps, nextState) { + return shallowCompare(this, nextProps, nextState); + } + + render () { + const { definition } = this.props.setting; + + if (definition.multiValues) { + return ; + } + + if (definition.type === TYPE_PROPERTY_SET) { + return ; + } + + return ; + } +} diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForBoolean.js b/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForBoolean.js new file mode 100644 index 00000000000..9f640591835 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForBoolean.js @@ -0,0 +1,48 @@ +/* + * 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 Toggle from '../../../../components/controls/Toggle'; +import { defaultInputPropTypes } from '../../propTypes'; +import { translate } from '../../../../helpers/l10n'; + +export default class InputForBoolean extends React.Component { + static propTypes = { + ...defaultInputPropTypes, + value: React.PropTypes.oneOfType([React.PropTypes.bool, React.PropTypes.string]) + }; + + render () { + const hasValue = this.props.value != null; + const displayedValue = hasValue ? this.props.value : false; + + return ( +
+ + + {!hasValue && ( + {translate('settings.not_set')} + )} +
+ ); + } +} diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForNumber.js b/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForNumber.js new file mode 100644 index 00000000000..6f741da3730 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForNumber.js @@ -0,0 +1,29 @@ +/* + * 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 SimpleInput from './SimpleInput'; + +export default class InputForNumber extends React.Component { + render () { + return ( + + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForPassword.js b/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForPassword.js new file mode 100644 index 00000000000..4e2a783ce11 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForPassword.js @@ -0,0 +1,89 @@ +/* + * 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'; +import { defaultInputPropTypes } from '../../propTypes'; + +export default class InputForPassword extends React.Component { + static propTypes = defaultInputPropTypes; + + state = { + changing: false + }; + + handleChangeClick (e) { + e.preventDefault(); + e.target.blur(); + this.setState({ changing: true }); + } + + handleCancelChangeClick (e) { + e.preventDefault(); + e.target.blur(); + this.setState({ changing: false }); + } + + handleFormSubmit (e) { + e.preventDefault(); + this.props.onChange(this.refs.input.value); + this.setState({ changing: false }); + } + + renderInput () { + return ( +
+
this.handleFormSubmit(e)}> + + + + this.handleCancelChangeClick(e)}> + {translate('cancel')} + +
+
+ ); + } + + render () { + if (this.state.changing) { + return this.renderInput(); + } + + const hasValue = !!this.props.value; + + return ( +
+ {hasValue && ( + + )} + + +
+ ); + } +} diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForSingleSelectList.js b/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForSingleSelectList.js new file mode 100644 index 00000000000..a0c704623d9 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForSingleSelectList.js @@ -0,0 +1,50 @@ +/* + * 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 Select from 'react-select'; +import { defaultInputPropTypes } from '../../propTypes'; + +export default class InputForSingleSelectList extends React.Component { + static propTypes = { + ...defaultInputPropTypes, + options: React.PropTypes.arrayOf(React.PropTypes.string).isRequired + }; + + handleInputChange (option) { + this.props.onChange(option.value); + } + + render () { + const options = this.props.options.map(option => ({ + label: option, + value: option + })); + + return ( + -<% - else - license = controller.java_facade.parseLicense(value) - product = license.getProduct() || '-' - # super-hack here - # should be avoided in the future - does_product_match = property.key.include? product -%> -
- - -
- - - - - - - - - - - - - - - - - - - - - - <% license.additionalProperties().each do |k,v| -%> - - - - - <% end %> -
Product:<%= product -%>
Organization:<%= license.getOrganization() || '-' -%>
Expiration: - <% if license.getExpirationDate() - formatted_date = l(Date.parse(license.getExpirationDateAsString())) - %> - <%= license.isExpired() ? "#{formatted_date}" : formatted_date -%> - <% else %> - - - <% end %> - -
Type:<%= license.getType() || '-' -%>
Server: - <% if license.getServer() && - license.getServer() != "*" && - controller.java_facade.getConfigurationValue("sonar.server_id") != license.getServer() %> - <%= license.getServer() -%> - <% else %> - <%= license.getServer() || '-' -%> - <% end %> -
<%= k -%>:<%= v || '-' -%>
-
-
-<% end %> diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_METRIC.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_METRIC.html.erb deleted file mode 100644 index 18e706b7f26..00000000000 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_METRIC.html.erb +++ /dev/null @@ -1,24 +0,0 @@ -<% - defaultValue = (defined? property.defaultValue) ? property.defaultValue : nil -%> - \ No newline at end of file diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_PASSWORD.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_PASSWORD.html.erb deleted file mode 100644 index d8c1556936c..00000000000 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_PASSWORD.html.erb +++ /dev/null @@ -1,7 +0,0 @@ -<% - options = {:id => id} - options[:size] = (defined? size) ? size : nil -%> - -<% value = Property::EXISTING_PASSWORD unless value.blank? %> -<%= property_input_field(name, PropertyType::TYPE_PASSWORD, value, PropertiesHelper::SCREEN_SETTINGS, options) %> diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_PROPERTY_SET.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_PROPERTY_SET.html.erb deleted file mode 100644 index ae8159a9df4..00000000000 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_PROPERTY_SET.html.erb +++ /dev/null @@ -1,11 +0,0 @@ -<% choices = Property.values(property.propertySetKey).reject(&:blank?) -%> -<% prompt = [[message('default'), '']] -%> - -<% if !value.blank? && (choices.exclude? value) -%> - <%= image_tag 'exclamation.png' -%> - <% missing = [[h(value + ' <' + message('deleted') + '>'), value]] -%> -<% else -%> - <% missing = [] -%> -<% end -%> - -<%= select_tag name, options_for_select(prompt + choices + missing, value), :id => id -%> \ No newline at end of file diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_PROPERTY_SET_DEFINITION.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_PROPERTY_SET_DEFINITION.html.erb deleted file mode 100644 index beec593636c..00000000000 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_PROPERTY_SET_DEFINITION.html.erb +++ /dev/null @@ -1,38 +0,0 @@ -<% resource_id = @resource.id if @resource -%> - - - - - <% unless key_field(property) -%> - <%= hidden_field_tag "auto_generate[#{property.key}]", true -%> - <% end -%> - <% property.fields.each do |field| -%> - - <% end -%> - - - - - - <% set_keys = Property.values(property.key, resource_id) -%> - <% set_keys = [''] if set_keys.all?(&:blank?) -%> - <% set_keys.each_with_index do |set_key, index| -%> - <%= render 'settings/set_instance', :property => property, :set_key => set_key, :resource_id => resource_id, :hide_delete => (index == 0) %> - <% end -%> - <%= render 'settings/set_instance', :property => property, :set_key => nil, :resource_id => resource_id, :hide_delete => false %> - - - - - - - -
- <%= field_name(property, field) -%> - <% desc = field_description(property, field) -%> - <% unless desc.blank? %> -

<%= desc -%>

- <% end -%> -
- -
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_REGULAR_EXPRESSION.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_REGULAR_EXPRESSION.html.erb deleted file mode 100644 index 8274fd8ef8f..00000000000 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_REGULAR_EXPRESSION.html.erb +++ /dev/null @@ -1,5 +0,0 @@ -<% - options = {:id => id} - options[:size] = (defined? size) ? size : nil -%> -<%= property_input_field(name, PropertyType::TYPE_REGULAR_EXPRESSION, value, PropertiesHelper::SCREEN_SETTINGS, options) %> \ No newline at end of file diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_SINGLE_SELECT_LIST.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_SINGLE_SELECT_LIST.html.erb deleted file mode 100644 index 4153c332d13..00000000000 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_SINGLE_SELECT_LIST.html.erb +++ /dev/null @@ -1,2 +0,0 @@ -<%= property_input_field(name, PropertyType::TYPE_SINGLE_SELECT_LIST, value, PropertiesHelper::SCREEN_SETTINGS, - {:id => id, :default => (defined? property.defaultValue) ? property.defaultValue : nil, :values => property.options, :extra_values => {:property => property, :field => field}}) %> \ No newline at end of file diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_STRING.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_STRING.html.erb deleted file mode 100644 index b28c75604a7..00000000000 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_STRING.html.erb +++ /dev/null @@ -1,5 +0,0 @@ -<% - options = {:id => id} - options[:size] = (defined? size) ? size : nil -%> -<%= property_input_field(name, PropertyType::TYPE_STRING, value, PropertiesHelper::SCREEN_SETTINGS, options) %> \ No newline at end of file diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_TEXT.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_TEXT.html.erb deleted file mode 100644 index af46cf40082..00000000000 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_TEXT.html.erb +++ /dev/null @@ -1,5 +0,0 @@ -<% - options = {:id => id} - options[:size] = (defined? size) ? size : nil -%> -<%= property_input_field(name, PropertyType::TYPE_TEXT, value, PropertiesHelper::SCREEN_SETTINGS, options) %> diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_USER_LOGIN.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_USER_LOGIN.html.erb deleted file mode 100644 index 6f94fa6a651..00000000000 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_USER_LOGIN.html.erb +++ /dev/null @@ -1,5 +0,0 @@ -<% - options = {:id => id} - options[:size] = (defined? size) ? size : nil -%> -<%= property_input_field(name, PropertyType::TYPE_USER_LOGIN, value, PropertiesHelper::SCREEN_SETTINGS, options) %> diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/index.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/index.html.erb index 55ae2ad65ec..6c42ad1d4b7 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/index.html.erb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/index.html.erb @@ -1,3 +1,3 @@ -
- <%= render 'settings', :project => nil %> -
+<% content_for :extra_script do %> + +<% end %> diff --git a/server/sonar-web/tests/utils.js b/server/sonar-web/tests/utils.js new file mode 100644 index 00000000000..afa4f53d0d4 --- /dev/null +++ b/server/sonar-web/tests/utils.js @@ -0,0 +1,38 @@ +/* + * 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 click = element => { + return element.simulate('click', { + target: { blur () {} }, + preventDefault () {} + }); +}; + +export const submit = element => { + return element.simulate('submit', { + preventDefault () {} + }); +}; + +export const change = (element, value) => { + return element.simulate('change', { + target: { value }, + currentTarget: { value } + }); +}; -- cgit v1.2.3