aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main
diff options
context:
space:
mode:
authorStas Vilchik <vilchiks@gmail.com>2016-11-29 17:03:24 +0100
committerStas Vilchik <vilchiks@gmail.com>2016-12-07 14:36:18 +0100
commitf1976a3f56f03c67ccbf6dca7ee5060b6a21a1da (patch)
treef8741034e51ae45b37201e90a305ebb3802da7c7 /server/sonar-web/src/main
parentb117943f3efa541d6c8cd8e62ad157c4f8194211 (diff)
downloadsonarqube-f1976a3f56f03c67ccbf6dca7ee5060b6a21a1da.tar.gz
sonarqube-f1976a3f56f03c67ccbf6dca7ee5060b6a21a1da.zip
SONAR-8451 Run js app outside of ruby container
Diffstat (limited to 'server/sonar-web/src/main')
-rw-r--r--server/sonar-web/src/main/js/api/auth.js7
-rw-r--r--server/sonar-web/src/main/js/api/settings.js5
-rw-r--r--server/sonar-web/src/main/js/api/system.js4
-rw-r--r--server/sonar-web/src/main/js/app/components/AdminContainer.js (renamed from server/sonar-web/src/main/js/apps/about/components/LoginSection.js)44
-rw-r--r--server/sonar-web/src/main/js/app/components/App.js42
-rw-r--r--server/sonar-web/src/main/js/app/components/GlobalContainer.js (renamed from server/sonar-web/src/main/js/app/components/nav/links-mixin.js)31
-rw-r--r--server/sonar-web/src/main/js/app/components/GlobalFooter.js81
-rw-r--r--server/sonar-web/src/main/js/app/components/GlobalLoading.css19
-rw-r--r--server/sonar-web/src/main/js/app/components/GlobalLoading.js33
-rw-r--r--server/sonar-web/src/main/js/app/components/Landing.js49
-rw-r--r--server/sonar-web/src/main/js/app/components/LocalizationContainer.js49
-rw-r--r--server/sonar-web/src/main/js/app/components/MigrationContainer.js52
-rw-r--r--server/sonar-web/src/main/js/app/components/NotFound.js33
-rw-r--r--server/sonar-web/src/main/js/app/components/ProjectContainer.js64
-rw-r--r--server/sonar-web/src/main/js/app/components/SimpleContainer.js55
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/app.js91
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.css16
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.js (renamed from server/sonar-web/src/main/js/app/components/nav/component/component-nav.js)45
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBreadcrumbs.js (renamed from server/sonar-web/src/main/js/app/components/nav/component/component-nav-breadcrumbs.js)12
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/ComponentNavFavorite.js (renamed from server/sonar-web/src/main/js/app/components/nav/component/component-nav-favorite.js)11
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.js (renamed from server/sonar-web/src/main/js/app/components/nav/component/component-nav-menu.js)120
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMeta.js (renamed from server/sonar-web/src/main/js/app/components/nav/component/component-nav-meta.js)0
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBreadcrumbs-test.js (renamed from server/sonar-web/src/main/js/app/components/nav/__tests__/nav-test.js)14
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/global/GlobalNav.js (renamed from server/sonar-web/src/main/js/app/components/nav/global/global-nav.js)66
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/global/GlobalNavBranding.js (renamed from server/sonar-web/src/main/js/app/components/nav/global/global-nav-branding.js)39
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMenu.js (renamed from server/sonar-web/src/main/js/app/components/nav/global/global-nav-menu.js)48
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/global/GlobalNavSearch.js (renamed from server/sonar-web/src/main/js/app/components/nav/global/global-nav-search.js)50
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/global/GlobalNavUser.js (renamed from server/sonar-web/src/main/js/app/components/nav/global/global-nav-user.js)46
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/global/SearchView.js (renamed from server/sonar-web/src/main/js/app/components/nav/global/search-view.js)5
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/global/ShortcutsHelpView.js (renamed from server/sonar-web/src/main/js/app/components/nav/global/shortcuts-help-view.js)0
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/settings/SettingsNav.js134
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/settings/settings-nav.js133
-rw-r--r--server/sonar-web/src/main/js/app/index.js14
-rw-r--r--server/sonar-web/src/main/js/app/store/appState/duck.js75
-rw-r--r--server/sonar-web/src/main/js/app/store/rootActions.js51
-rw-r--r--server/sonar-web/src/main/js/app/store/rootReducer.js10
-rw-r--r--server/sonar-web/src/main/js/app/store/users/actions.js6
-rw-r--r--server/sonar-web/src/main/js/app/store/users/reducer.js4
-rw-r--r--server/sonar-web/src/main/js/app/utils/getCurrentUserFromStore.js (renamed from server/sonar-web/src/main/js/app/components/nav/dashboard-name-mixin.js)20
-rw-r--r--server/sonar-web/src/main/js/app/utils/getHistory.js35
-rw-r--r--server/sonar-web/src/main/js/app/utils/getStore.js33
-rw-r--r--server/sonar-web/src/main/js/app/utils/handleRequiredAuthentication.js36
-rw-r--r--server/sonar-web/src/main/js/app/utils/handleRequiredAuthorization.js36
-rw-r--r--server/sonar-web/src/main/js/app/utils/handleRequiredMigration.js (renamed from server/sonar-web/src/main/js/app/components/NullComponent.js)7
-rw-r--r--server/sonar-web/src/main/js/app/utils/isCurrentPathKnown.js69
-rw-r--r--server/sonar-web/src/main/js/app/utils/startAjaxMonitoring.js7
-rw-r--r--server/sonar-web/src/main/js/app/utils/startApp.js69
-rw-r--r--server/sonar-web/src/main/js/app/utils/startReactApp.js125
-rw-r--r--server/sonar-web/src/main/js/apps/about/components/AboutApp.js44
-rw-r--r--server/sonar-web/src/main/js/apps/account/components/Account.js10
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/BackgroundTasksApp.js13
-rw-r--r--server/sonar-web/src/main/js/apps/code/components/App.js12
-rw-r--r--server/sonar-web/src/main/js/apps/component-issues/components/ComponentIssuesAppContainer.js13
-rw-r--r--server/sonar-web/src/main/js/apps/component-issues/init.js28
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/app/AppContainer.js11
-rw-r--r--server/sonar-web/src/main/js/apps/component/components/App.js (renamed from server/sonar-web/src/main/js/app/components/ComponentContainer.js)19
-rw-r--r--server/sonar-web/src/main/js/apps/component/routes.js28
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/components/CustomMeasuresAppContainer.js20
-rw-r--r--server/sonar-web/src/main/js/apps/issues/HeaderView.js2
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/IssuesAppContainer.js16
-rw-r--r--server/sonar-web/src/main/js/apps/issues/init.js8
-rw-r--r--server/sonar-web/src/main/js/apps/overview/components/App.js10
-rw-r--r--server/sonar-web/src/main/js/apps/overview/components/AppContainer.js32
-rw-r--r--server/sonar-web/src/main/js/apps/overview/meta/Meta.js10
-rw-r--r--server/sonar-web/src/main/js/apps/overview/meta/MetaQualityProfiles.js15
-rw-r--r--server/sonar-web/src/main/js/apps/permission-templates/components/AppContainer.js28
-rw-r--r--server/sonar-web/src/main/js/apps/permissions/project/components/App.js14
-rw-r--r--server/sonar-web/src/main/js/apps/permissions/project/components/PageHeader.js3
-rw-r--r--server/sonar-web/src/main/js/apps/project-admin/deletion/Deletion.js10
-rw-r--r--server/sonar-web/src/main/js/apps/project-admin/key/Key.js5
-rw-r--r--server/sonar-web/src/main/js/apps/project-admin/links/Links.js5
-rw-r--r--server/sonar-web/src/main/js/apps/project-admin/quality-gate/QualityGate.js5
-rw-r--r--server/sonar-web/src/main/js/apps/project-admin/quality-profiles/QualityProfiles.js9
-rw-r--r--server/sonar-web/src/main/js/apps/projects-admin/AppContainer.js20
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/AllProjects.js4
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/FavoriteFilter.js2
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/components/App.js14
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/components/AppContainer.js3
-rw-r--r--server/sonar-web/src/main/js/apps/sessions/components/LoginForm.js102
-rw-r--r--server/sonar-web/src/main/js/apps/sessions/components/LoginFormContainer.js78
-rw-r--r--server/sonar-web/src/main/js/apps/sessions/components/Logout.js42
-rw-r--r--server/sonar-web/src/main/js/apps/sessions/components/Unauthorized.js49
-rw-r--r--server/sonar-web/src/main/js/apps/sessions/routes.js31
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/AppContainer.js24
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/EmailForm.js12
-rw-r--r--server/sonar-web/src/main/js/apps/settings/store/values/reducer.js14
-rw-r--r--server/sonar-web/src/main/js/apps/settings/types.js1
-rw-r--r--server/sonar-web/src/main/js/apps/source-viewer/app.js49
-rw-r--r--server/sonar-web/src/main/js/apps/update-center/components/UpdateCenterAppContainer.js12
-rw-r--r--server/sonar-web/src/main/js/apps/update-center/init.js14
-rw-r--r--server/sonar-web/src/main/js/apps/users/change-password-view.js2
-rw-r--r--server/sonar-web/src/main/js/apps/users/components/UsersAppContainer.js16
-rw-r--r--server/sonar-web/src/main/js/apps/users/init.js12
-rw-r--r--server/sonar-web/src/main/js/apps/users/list-item-view.js3
-rw-r--r--server/sonar-web/src/main/js/apps/users/list-view.js5
-rw-r--r--server/sonar-web/src/main/js/components/issue/issue-view.js4
-rw-r--r--server/sonar-web/src/main/js/components/issue/views/assign-form-view.js4
-rw-r--r--server/sonar-web/src/main/js/components/navigator/filters/ajax-select-filters.js437
-rw-r--r--server/sonar-web/src/main/js/components/navigator/filters/base-filters.js227
-rw-r--r--server/sonar-web/src/main/js/components/navigator/filters/checkbox-filters.js65
-rw-r--r--server/sonar-web/src/main/js/components/navigator/filters/choice-filters.js383
-rw-r--r--server/sonar-web/src/main/js/components/navigator/filters/favorite-filters.js91
-rw-r--r--server/sonar-web/src/main/js/components/navigator/filters/filter-bar.js179
-rw-r--r--server/sonar-web/src/main/js/components/navigator/filters/metric-filters.js204
-rw-r--r--server/sonar-web/src/main/js/components/navigator/filters/more-criteria-filters.js107
-rw-r--r--server/sonar-web/src/main/js/components/navigator/filters/range-filters.js208
-rw-r--r--server/sonar-web/src/main/js/components/navigator/filters/string-filters.js87
-rw-r--r--server/sonar-web/src/main/js/components/store/globalMessages.js19
-rw-r--r--server/sonar-web/src/main/js/components/ui/Avatar.js20
-rw-r--r--server/sonar-web/src/main/js/components/ui/__tests__/Avatar-test.js24
-rw-r--r--server/sonar-web/src/main/js/helpers/__tests__/measures-test.js1
-rw-r--r--server/sonar-web/src/main/js/helpers/cookies.js3
-rw-r--r--server/sonar-web/src/main/js/helpers/csv.js3
-rw-r--r--server/sonar-web/src/main/js/helpers/handlebars/avatarHelper.js10
-rw-r--r--server/sonar-web/src/main/js/helpers/handlebars/ifShowAvatars.js11
-rw-r--r--server/sonar-web/src/main/js/helpers/l10n.js6
-rw-r--r--server/sonar-web/src/main/js/helpers/measures.js26
-rw-r--r--server/sonar-web/src/main/js/helpers/request.js65
-rw-r--r--server/sonar-web/src/main/js/helpers/users.js (renamed from server/sonar-web/src/main/js/helpers/handlebars/ifCanUseFilter.js)12
-rw-r--r--server/sonar-web/src/main/js/libs/sonar.js50
-rw-r--r--server/sonar-web/src/main/less/components/page.less2
121 files changed, 2080 insertions, 3042 deletions
diff --git a/server/sonar-web/src/main/js/api/auth.js b/server/sonar-web/src/main/js/api/auth.js
index 064ca296aaf..5781eb6c4fd 100644
--- a/server/sonar-web/src/main/js/api/auth.js
+++ b/server/sonar-web/src/main/js/api/auth.js
@@ -36,3 +36,10 @@ export const login = (login, password) => (
.submit()
.then(basicCheckStatus)
);
+
+export const logout = () => (
+ request('/api/authentication/logout')
+ .setMethod('POST')
+ .submit()
+ .then(basicCheckStatus)
+);
diff --git a/server/sonar-web/src/main/js/api/settings.js b/server/sonar-web/src/main/js/api/settings.js
index b35a877809e..50b3d8e1898 100644
--- a/server/sonar-web/src/main/js/api/settings.js
+++ b/server/sonar-web/src/main/js/api/settings.js
@@ -93,3 +93,8 @@ export function getServerId () {
export function generateServerId (organization, ip) {
return postJSON('/api/server_id/generate', { organization, ip });
}
+
+// TODO replace with /api/settings
+export const getSettingValue = key => (
+ getJSON(`/api/properties/${key}`).then(r => r[0] ? r[0].value : null)
+);
diff --git a/server/sonar-web/src/main/js/api/system.js b/server/sonar-web/src/main/js/api/system.js
index fcd5d791f9b..905c1eef85f 100644
--- a/server/sonar-web/src/main/js/api/system.js
+++ b/server/sonar-web/src/main/js/api/system.js
@@ -30,7 +30,7 @@ export function getSystemInfo () {
return getJSON(url);
}
-export function getStatus () {
+export function getSystemStatus () {
const url = '/api/system/status';
return getJSON(url);
}
@@ -44,7 +44,7 @@ const POLLING_INTERVAL = 2000;
function pollStatus (cb) {
setTimeout(() => {
- getStatus()
+ getSystemStatus()
.then(r => {
if (r.status === 'UP') {
cb();
diff --git a/server/sonar-web/src/main/js/apps/about/components/LoginSection.js b/server/sonar-web/src/main/js/app/components/AdminContainer.js
index 08773d996bd..7ecbb77b2cd 100644
--- a/server/sonar-web/src/main/js/apps/about/components/LoginSection.js
+++ b/server/sonar-web/src/main/js/app/components/AdminContainer.js
@@ -18,30 +18,36 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
-import { Link } from 'react-router';
-import OAuthProvider from './OAuthProvider';
-import IconLock from './IconLock';
+import { connect } from 'react-redux';
+import SettingsNav from './nav/settings/SettingsNav';
+import { getCurrentUser } from '../store/rootReducer';
+import { isUserAdmin } from '../../helpers/users';
-export default class LoginSection extends React.Component {
- render () {
- const { authProviders } = window.sonarqube;
+class AdminContainer extends React.Component {
+ componentDidMount () {
+ if (!isUserAdmin(this.props.currentUser)) {
+ // workaround cyclic dependencies
+ const handleRequiredAuthorization = require('../utils/handleRequiredAuthorization').default;
+ handleRequiredAuthorization();
+ }
+ }
- const loginWithSonarQubeLabel = authProviders.length ? 'Log in with SonarQube' : 'Log in';
+ render () {
+ if (!isUserAdmin(this.props.currentUser)) {
+ return null;
+ }
return (
- <div id="about-login">
- <div className="about-page-auth-providers big-spacer-top">
- {authProviders.map(provider => (
- <OAuthProvider key={provider.key} provider={provider}/>
- ))}
-
- <Link to={{ pathname: '/about', query: { login: null } }}
- className="oauth-provider oauth-provider-sonarqube">
- <IconLock/>
- <span>{loginWithSonarQubeLabel}</span>
- </Link>
- </div>
+ <div>
+ <SettingsNav/>
+ {this.props.children}
</div>
);
}
}
+
+const mapStateToProps = state => ({
+ currentUser: getCurrentUser(state)
+});
+
+export default connect(mapStateToProps)(AdminContainer);
diff --git a/server/sonar-web/src/main/js/app/components/App.js b/server/sonar-web/src/main/js/app/components/App.js
index c630a4fe275..4c13709deaf 100644
--- a/server/sonar-web/src/main/js/app/components/App.js
+++ b/server/sonar-web/src/main/js/app/components/App.js
@@ -17,27 +17,57 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+// @flow
import React from 'react';
import { connect } from 'react-redux';
+import GlobalLoading from './GlobalLoading';
import { fetchCurrentUser } from '../store/users/actions';
-import { fetchLanguages } from '../store/rootActions';
+import { fetchLanguages, fetchAppState } from '../store/rootActions';
class App extends React.Component {
+ mounted: bool;
+
static propTypes = {
- fetchCurrentUser: React.PropTypes.func.isRequired
+ fetchAppState: React.PropTypes.func.isRequired,
+ fetchCurrentUser: React.PropTypes.func.isRequired,
+ fetchLanguages: React.PropTypes.func.isRequired,
+ children: React.PropTypes.element.isRequired
+ };
+
+ state = {
+ loading: true
+ };
+
+ finishLoading = () => {
+ if (this.mounted) {
+ this.setState({ loading: false });
+ }
};
componentDidMount () {
- this.props.fetchCurrentUser();
- this.props.fetchLanguages();
+ this.mounted = true;
+
+ this.props.fetchCurrentUser()
+ .then(this.props.fetchAppState)
+ .then(this.finishLoading)
+ .then(this.props.fetchLanguages)
+ .catch(this.finishLoading);
+ }
+
+ componentWillUnmount () {
+ this.mounted = false;
}
render () {
+ if (this.state.loading) {
+ return <GlobalLoading/>;
+ }
+
return this.props.children;
}
}
export default connect(
- () => ({}),
- { fetchCurrentUser, fetchLanguages }
+ null,
+ { fetchAppState, fetchCurrentUser, fetchLanguages }
)(App);
diff --git a/server/sonar-web/src/main/js/app/components/nav/links-mixin.js b/server/sonar-web/src/main/js/app/components/GlobalContainer.js
index 0027e2250df..447e647ffec 100644
--- a/server/sonar-web/src/main/js/app/components/nav/links-mixin.js
+++ b/server/sonar-web/src/main/js/app/components/GlobalContainer.js
@@ -17,24 +17,23 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+// @flow
import React from 'react';
-import classNames from 'classnames';
-
-export default {
- activeLink(url) {
- return window.location.pathname.indexOf(window.baseUrl + url) === 0 ? 'active' : null;
- },
-
- renderLink(url, title, highlightUrl = url) {
- const fullUrl = window.baseUrl + url;
- const isActive = typeof highlightUrl === 'string' ?
- window.location.pathname.indexOf(window.baseUrl + highlightUrl) === 0 :
- highlightUrl(fullUrl);
+import GlobalNav from './nav/global/GlobalNav';
+import GlobalFooter from './GlobalFooter';
+import GlobalMessagesContainer from './GlobalMessagesContainer';
+export default class GlobalContainer extends React.Component {
+ render () {
return (
- <li key={url} className={classNames({ 'active': isActive })}>
- <a href={fullUrl}>{title}</a>
- </li>
+ <div className="global-container">
+ <div className="page-wrapper page-wrapper-global" id="container">
+ <GlobalNav/>
+ <GlobalMessagesContainer/>
+ {this.props.children}
+ </div>
+ <GlobalFooter/>
+ </div>
);
}
-};
+}
diff --git a/server/sonar-web/src/main/js/app/components/GlobalFooter.js b/server/sonar-web/src/main/js/app/components/GlobalFooter.js
new file mode 100644
index 00000000000..ac32f218810
--- /dev/null
+++ b/server/sonar-web/src/main/js/app/components/GlobalFooter.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.
+ */
+// @flow
+import React from 'react';
+import { connect } from 'react-redux';
+import { getAppState } from '../store/rootReducer';
+
+class GlobalFooter extends React.Component {
+ render () {
+ const { sonarqubeVersion, productionDatabase } = this.props;
+
+ return (
+ <div id="footer" className="page-footer page-container">
+ {!productionDatabase && (
+ <div className="alert alert-danger">
+ <p className="big" id="evaluation_warning">
+ Embedded database should be used for evaluation purpose only
+ </p>
+ <p>
+ The embedded database will not scale, it will not support upgrading to newer versions of SonarQube,
+ and there is no support for migrating your data out of it into a different database engine.
+ </p>
+ </div>
+ )}
+
+ <div>
+ This application is based on
+ {' '}
+ <a href="http://www.sonarqube.org/" title="SonarQube&trade;">SonarQube&trade;</a>
+ {' '}
+ but is <strong>not</strong> an official version provided by
+ {' '}
+ <a href="http://www.sonarsource.com" title="SonarSource SA">SonarSource SA</a>.
+ </div>
+
+
+ <div>
+ Version {sonarqubeVersion}
+ {' - '}
+ <a href="http://www.gnu.org/licenses/lgpl-3.0.txt">LGPL v3</a>
+ {' - '}
+ <a href="http://www.sonarqube.org">Community</a>
+ {' - '}
+ <a href="http://www.sonarqube.org/documentation">Documentation</a>
+ {' - '}
+ <a href="http://www.sonarqube.org/support">Get Support</a>
+ {' - '}
+ <a href="http://redirect.sonarsource.com/doc/plugin-library.html">Plugins</a>
+ {' - '}
+ <a href={window.baseUrl + '/web_api'}>Web API</a>
+ {' - '}
+ <a href={window.baseUrl + '/about'}>About</a>
+ </div>
+ </div>
+ );
+ }
+}
+
+const mapStateToProps = state => ({
+ sonarqubeVersion: getAppState(state).version,
+ productionDatabase: getAppState(state).productionDatabase
+});
+
+export default connect(mapStateToProps)(GlobalFooter);
diff --git a/server/sonar-web/src/main/js/app/components/GlobalLoading.css b/server/sonar-web/src/main/js/app/components/GlobalLoading.css
new file mode 100644
index 00000000000..5537f22c69a
--- /dev/null
+++ b/server/sonar-web/src/main/js/app/components/GlobalLoading.css
@@ -0,0 +1,19 @@
+.global-loading {
+ width: 300px;
+ margin: 200px auto 0;
+ white-space: nowrap;
+}
+
+.global-loading-spinner {
+ vertical-align: middle;
+ width: 80px;
+ height: 80px;
+}
+
+.global-loading-text {
+ display: inline-block;
+ vertical-align: middle;
+ margin-left: 30px;
+ font-size: 36px;
+ font-weight: 300;
+}
diff --git a/server/sonar-web/src/main/js/app/components/GlobalLoading.js b/server/sonar-web/src/main/js/app/components/GlobalLoading.js
new file mode 100644
index 00000000000..535a20739d7
--- /dev/null
+++ b/server/sonar-web/src/main/js/app/components/GlobalLoading.js
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+// @flow
+import React from 'react';
+import './GlobalLoading.css';
+
+export default class GlobalLoading extends React.Component {
+ render () {
+ return (
+ <div className="global-loading">
+ <i className="spinner global-loading-spinner"/>
+ <span className="global-loading-text">Loading...</span>
+ </div>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/app/components/Landing.js b/server/sonar-web/src/main/js/app/components/Landing.js
new file mode 100644
index 00000000000..17074c24945
--- /dev/null
+++ b/server/sonar-web/src/main/js/app/components/Landing.js
@@ -0,0 +1,49 @@
+/*
+ * 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.
+ */
+// @flow
+import React from 'react';
+import { withRouter } from 'react-router';
+import { connect } from 'react-redux';
+import { getCurrentUser } from '../store/rootReducer';
+
+class Landing extends React.Component {
+ static propTypes = {
+ currentUser: React.PropTypes.oneOfType([React.PropTypes.bool, React.PropTypes.object]).isRequired
+ };
+
+ componentDidMount () {
+ const { currentUser, router } = this.props;
+ if (currentUser.isLoggedIn) {
+ router.replace('/projects/favorite');
+ } else {
+ router.replace('/about');
+ }
+ }
+
+ render () {
+ return null;
+ }
+}
+
+const mapStateToProps = state => ({
+ currentUser: getCurrentUser(state)
+});
+
+export default connect(mapStateToProps)(withRouter(Landing));
diff --git a/server/sonar-web/src/main/js/app/components/LocalizationContainer.js b/server/sonar-web/src/main/js/app/components/LocalizationContainer.js
new file mode 100644
index 00000000000..1241ac8f20c
--- /dev/null
+++ b/server/sonar-web/src/main/js/app/components/LocalizationContainer.js
@@ -0,0 +1,49 @@
+/*
+ * 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.
+ */
+// @flow
+import React from 'react';
+import { requestMessages } from '../../helpers/l10n';
+
+export default class LocalizationContainer extends React.Component {
+ mounted: bool;
+
+ state = {
+ loading: true
+ };
+
+ finishLoading = () => {
+ if (this.mounted) {
+ this.setState({ loading: false });
+ }
+ };
+
+ componentDidMount () {
+ this.mounted = true;
+ requestMessages().then(this.finishLoading, this.finishLoading);
+ }
+
+ componentWillUnmount () {
+ this.mounted = false;
+ }
+
+ render () {
+ return this.state.loading ? null : this.props.children;
+ }
+}
diff --git a/server/sonar-web/src/main/js/app/components/MigrationContainer.js b/server/sonar-web/src/main/js/app/components/MigrationContainer.js
new file mode 100644
index 00000000000..ee6c7e49ad8
--- /dev/null
+++ b/server/sonar-web/src/main/js/app/components/MigrationContainer.js
@@ -0,0 +1,52 @@
+/*
+ * 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.
+ */
+// @flow
+import React from 'react';
+import { getSystemStatus } from '../../api/system';
+
+export default class MigrationContainer extends React.Component {
+ static propTypes = {
+ children: React.PropTypes.element.isRequired
+ };
+
+ state = {
+ loading: true
+ };
+
+ componentDidMount () {
+ getSystemStatus().then(r => {
+ if (r.status === 'UP') {
+ this.setState({ loading: false });
+ } else {
+ // workaround cyclic dependencies
+ const handleRequiredMigration = require('../utils/handleRequiredMigration').default;
+ handleRequiredMigration();
+ }
+ });
+ }
+
+ render () {
+ if (this.state.loading) {
+ return null;
+ }
+
+ return this.props.children;
+ }
+}
diff --git a/server/sonar-web/src/main/js/app/components/NotFound.js b/server/sonar-web/src/main/js/app/components/NotFound.js
new file mode 100644
index 00000000000..16b40224cef
--- /dev/null
+++ b/server/sonar-web/src/main/js/app/components/NotFound.js
@@ -0,0 +1,33 @@
+/*
+ * 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 SimpleContainer from './SimpleContainer';
+
+export default class NotFound extends React.Component {
+ render () {
+ return (
+ <SimpleContainer>
+ <h2 className="big-spacer-bottom">The page you were looking for does not exist.</h2>
+ <p className="spacer-bottom">You may have mistyped the address or the page may have moved.</p>
+ <p><a href={window.baseUrl + '/'}>Go back to the homepage</a></p>
+ </SimpleContainer>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/app/components/ProjectContainer.js b/server/sonar-web/src/main/js/app/components/ProjectContainer.js
new file mode 100644
index 00000000000..1a83c88e618
--- /dev/null
+++ b/server/sonar-web/src/main/js/app/components/ProjectContainer.js
@@ -0,0 +1,64 @@
+/*
+ * 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 ComponentNav from './nav/component/ComponentNav';
+import { fetchProject } from '../store/rootActions';
+import { getComponent } from '../store/rootReducer';
+
+class ProjectContainer extends React.Component {
+ static propTypes = {
+ project: React.PropTypes.object,
+ fetchProject: React.PropTypes.func.isRequired
+ };
+
+ componentDidMount () {
+ this.props.fetchProject();
+ }
+
+ render () {
+ if (!this.props.project) {
+ return null;
+ }
+
+ const isFile = ['FIL', 'UTS'].includes(this.props.project.qualifier);
+
+ const configuration = this.props.project.configuration || {};
+
+ return (
+ <div>
+ {!isFile && (
+ <ComponentNav component={this.props.project} conf={configuration}/>
+ )}
+ {this.props.children}
+ </div>
+ );
+ }
+}
+
+const mapStateToProps = (state, ownProps) => ({
+ project: getComponent(state, ownProps.location.query.id)
+});
+
+const mapDispatchToProps = (dispatch, ownProps) => ({
+ fetchProject: () => dispatch(fetchProject(ownProps.location.query.id))
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(ProjectContainer);
diff --git a/server/sonar-web/src/main/js/app/components/SimpleContainer.js b/server/sonar-web/src/main/js/app/components/SimpleContainer.js
new file mode 100644
index 00000000000..17d9c64cb69
--- /dev/null
+++ b/server/sonar-web/src/main/js/app/components/SimpleContainer.js
@@ -0,0 +1,55 @@
+/*
+ * 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.
+ */
+// @flow
+import React from 'react';
+import GlobalFooter from './GlobalFooter';
+
+export default class SimpleContainer extends React.Component {
+ static propTypes = {
+ children: React.PropTypes.element.isRequired
+ };
+
+ componentDidMount () {
+ document.querySelector('html').classList.add('dashboard-page');
+ }
+
+ componentWillUnmount () {
+ document.querySelector('html').classList.remove('dashboard-page');
+ }
+
+ render () {
+ return (
+ <div className="global-container">
+ <div className="page-wrapper page-wrapper-global" id="container">
+ <nav className="navbar navbar-global page-container" id="global-navigation">
+ <div className="navbar-header"></div>
+ </nav>
+
+ <div id="bd" className="page-wrapper-simple">
+ <div id="nonav" className="page-simple">
+ {this.props.children}
+ </div>
+ </div>
+ </div>
+ <GlobalFooter/>
+ </div>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/app/components/nav/app.js b/server/sonar-web/src/main/js/app/components/nav/app.js
deleted file mode 100644
index 2a5526bbbe3..00000000000
--- a/server/sonar-web/src/main/js/app/components/nav/app.js
+++ /dev/null
@@ -1,91 +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 React from 'react';
-import ReactDOM from 'react-dom';
-
-import GlobalNav from './global/global-nav';
-import ComponentNav from './component/component-nav';
-import SettingsNav from './settings/settings-nav';
-import { getGlobalNavigation, getComponentNavigation, getSettingsNavigation } from '../../../api/nav';
-
-export default class App {
- start () {
- const options = window.sonarqube;
-
- require('../../../components/workspace/main');
-
- return new Promise(resolve => {
- const response = {};
- const requests = [];
-
- requests.push(
- App.renderGlobalNav(options).then(r => response.global = r)
- );
-
- if (options.space === 'component') {
- requests.push(
- App.renderComponentNav(options).then(r => response.component = r)
- );
- } else if (options.space === 'settings') {
- requests.push(
- App.renderSettingsNav(options).then(r => response.settings = r)
- );
- }
-
- Promise.all(requests).then(() => resolve(response));
- });
- }
-
- static renderGlobalNav (options) {
- return getGlobalNavigation().then(r => {
- const el = document.getElementById('global-navigation');
- if (el) {
- ReactDOM.render(<GlobalNav {...options} {...r}/>, el);
- }
- return r;
- });
- }
-
- static renderComponentNav (options) {
- return getComponentNavigation(options.componentKey).then(component => {
- const el = document.getElementById('context-navigation');
- const nextComponent = {
- ...component,
- qualifier: _.last(component.breadcrumbs).qualifier
- };
- if (el) {
- ReactDOM.render(<ComponentNav component={nextComponent} conf={component.configuration || {}}/>, el);
- }
- return component;
- });
- }
-
- static renderSettingsNav (options) {
- return getSettingsNavigation().then(r => {
- const el = document.getElementById('context-navigation');
- const opts = _.extend(r, options);
- if (el) {
- ReactDOM.render(<SettingsNav {...opts}/>, el);
- }
- return r;
- });
- }
-}
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.css b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.css
new file mode 100644
index 00000000000..59d210c9534
--- /dev/null
+++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.css
@@ -0,0 +1,16 @@
+.navbar-context {
+ position: static;
+ padding: 0;
+ height: 65px;
+}
+
+.navbar-context-inner {
+ position: fixed;
+ z-index: 420;
+ left: 0;
+ right: 0;
+ height: 65px;
+ padding-top: 5px;
+ box-sizing: border-box;
+ background-color: #f3f3f3;
+}
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/component-nav.js b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.js
index bb511a169a5..b6dcd1c7359 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/component-nav.js
+++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.js
@@ -23,11 +23,12 @@ import React from 'react';
import ReactDOM from 'react-dom';
import { STATUSES } from '../../../../apps/background-tasks/constants';
import { getTasksForComponent } from '../../../../api/ce';
-import ComponentNavFavorite from './component-nav-favorite';
-import ComponentNavBreadcrumbs from './component-nav-breadcrumbs';
-import ComponentNavMeta from './component-nav-meta';
-import ComponentNavMenu from './component-nav-menu';
+import ComponentNavFavorite from './ComponentNavFavorite';
+import ComponentNavBreadcrumbs from './ComponentNavBreadcrumbs';
+import ComponentNavMeta from './ComponentNavMeta';
+import ComponentNavMenu from './ComponentNavMenu';
import RecentHistory from './RecentHistory';
+import './ComponentNav.css';
export default React.createClass({
componentDidMount() {
@@ -63,25 +64,29 @@ export default React.createClass({
render() {
return (
- <div className="container">
- <ComponentNavFavorite
- component={this.props.component.key}
- favorite={this.props.component.isFavorite}
- canBeFavorite={this.props.component.canBeFavorite}/>
+ <nav className="navbar navbar-context page-container" id="context-navigation">
+ <div className="navbar-context-inner">
+ <div className="container">
+ <ComponentNavFavorite
+ component={this.props.component.key}
+ favorite={this.props.component.isFavorite}
+ canBeFavorite={this.props.component.canBeFavorite}/>
- <ComponentNavBreadcrumbs
- breadcrumbs={this.props.component.breadcrumbs}/>
+ <ComponentNavBreadcrumbs
+ breadcrumbs={this.props.component.breadcrumbs}/>
- <ComponentNavMeta
- {...this.props}
- {...this.state}
- version={this.props.component.version}
- snapshotDate={this.props.component.snapshotDate}/>
+ <ComponentNavMeta
+ {...this.props}
+ {...this.state}
+ version={this.props.component.version}
+ snapshotDate={this.props.component.snapshotDate}/>
- <ComponentNavMenu
- component={this.props.component}
- conf={this.props.conf}/>
- </div>
+ <ComponentNavMenu
+ component={this.props.component}
+ conf={this.props.conf}/>
+ </div>
+ </div>
+ </nav>
);
}
});
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/component-nav-breadcrumbs.js b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBreadcrumbs.js
index 7458095814c..248d96d7258 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/component-nav-breadcrumbs.js
+++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBreadcrumbs.js
@@ -20,11 +20,16 @@
import React from 'react';
import QualifierIcon from '../../../../components/shared/qualifier-icon';
-export default React.createClass({
- render() {
+export default class ComponentNavBreadcrumbs extends React.Component {
+ static propTypes = {
+ breadcrumbs: React.PropTypes.array
+ };
+
+ render () {
if (!this.props.breadcrumbs) {
return null;
}
+
const items = this.props.breadcrumbs.map((item, index) => {
const url = `${window.baseUrl}/dashboard/index?id=${encodeURIComponent(item.key)}`;
return (
@@ -35,8 +40,9 @@ export default React.createClass({
</li>
);
});
+
return (
<ul className="nav navbar-nav nav-crumbs">{items}</ul>
);
}
-});
+}
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/component-nav-favorite.js b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavFavorite.js
index 72b590e48fb..9e9cb8bfe1d 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/component-nav-favorite.js
+++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavFavorite.js
@@ -20,11 +20,16 @@
import React from 'react';
import Favorite from '../../../../components/controls/Favorite';
-export default React.createClass({
- render() {
+export default class ComponentNavFavorite extends React.Component {
+ static propTypes = {
+ canBeFavorite: React.PropTypes.bool.isRequired
+ };
+
+ render () {
if (!this.props.canBeFavorite) {
return null;
}
+
return (
<div className="navbar-context-favorite">
<Favorite
@@ -33,4 +38,4 @@ export default React.createClass({
</div>
);
}
-});
+}
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/component-nav-menu.js b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.js
index e00f58efa03..dd16345bf43 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/component-nav-menu.js
+++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.js
@@ -17,10 +17,8 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import qs from 'querystring';
import classNames from 'classnames';
import React from 'react';
-import LinksMixin from '../links-mixin';
import { translate } from '../../../../helpers/l10n';
import { getComponentUrl } from '../../../../helpers/urls';
@@ -37,38 +35,44 @@ const SETTINGS_URLS = [
'/project/deletion'
];
-export default React.createClass({
- mixins: [LinksMixin],
+export default class ComponentNavMenu extends React.Component {
+ static propTypes = {
+ component: React.PropTypes.object.isRequired,
+ conf: React.PropTypes.object.isRequired
+ };
- isDeveloper() {
+ isDeveloper () {
return this.props.component.qualifier === 'DEV';
- },
+ }
- isView() {
+ isView () {
const { qualifier } = this.props.component;
return qualifier === 'VW' || qualifier === 'SVW';
- },
-
- periodParameter() {
- const params = qs.parse(window.location.search.substr(1));
- return params.period ? `&period=${params.period}` : '';
- },
-
- getPeriod() {
- const params = qs.parse(window.location.search.substr(1));
- return params.period;
- },
+ }
- isFixedDashboardActive() {
+ isFixedDashboardActive () {
const path = window.location.pathname;
return path.indexOf(window.baseUrl + '/dashboard') === 0 || path.indexOf(window.baseUrl + '/governance') === 0;
- },
+ }
- shouldShowAdministration() {
+ shouldShowAdministration () {
return Object.keys(this.props.conf).some(key => this.props.conf[key]);
- },
+ }
- renderDashboardLink() {
+ renderLink (url, title, highlightUrl = url) {
+ const fullUrl = window.baseUrl + url;
+ const isActive = typeof highlightUrl === 'string' ?
+ window.location.pathname.indexOf(window.baseUrl + highlightUrl) === 0 :
+ highlightUrl(fullUrl);
+
+ return (
+ <li key={url} className={classNames({ 'active': isActive })}>
+ <a href={fullUrl}>{title}</a>
+ </li>
+ );
+ }
+
+ renderDashboardLink () {
const url = getComponentUrl(this.props.component.key);
const name = <i className="icon-home"/>;
const className = classNames({ active: this.isFixedDashboardActive() });
@@ -77,9 +81,9 @@ export default React.createClass({
<a href={url}>{name}</a>
</li>
);
- },
+ }
- renderCodeLink() {
+ renderCodeLink () {
if (this.isDeveloper()) {
return null;
}
@@ -87,19 +91,19 @@ export default React.createClass({
const url = `/code/?id=${encodeURIComponent(this.props.component.key)}`;
const header = this.isView() ? translate('view_projects.page') : translate('code.page');
return this.renderLink(url, header, '/code');
- },
+ }
- renderComponentIssuesLink() {
+ renderComponentIssuesLink () {
const url = `/component_issues?id=${encodeURIComponent(this.props.component.key)}`;
return this.renderLink(url, translate('issues.page'), '/component_issues');
- },
+ }
- renderComponentMeasuresLink() {
+ renderComponentMeasuresLink () {
const url = `/component_measures/?id=${encodeURIComponent(this.props.component.key)}`;
return this.renderLink(url, translate('layout.measures'), '/component_measures');
- },
+ }
- renderAdministration() {
+ renderAdministration () {
if (!this.shouldShowAdministration()) {
return null;
}
@@ -126,81 +130,81 @@ export default React.createClass({
</ul>
</li>
);
- },
+ }
- renderSettingsLink() {
+ renderSettingsLink () {
if (!this.props.conf.showSettings) {
return null;
}
const url = `/project/settings?id=${encodeURIComponent(this.props.component.key)}`;
return this.renderLink(url, translate('project_settings.page'), '/project/settings');
- },
+ }
- renderProfilesLink() {
+ renderProfilesLink () {
if (!this.props.conf.showQualityProfiles) {
return null;
}
const url = `/project/quality_profiles?id=${encodeURIComponent(this.props.component.key)}`;
return this.renderLink(url, translate('project_quality_profiles.page'), '/project/quality_profiles');
- },
+ }
- renderQualityGateLink() {
+ renderQualityGateLink () {
if (!this.props.conf.showQualityGates) {
return null;
}
const url = `/project/quality_gate?id=${encodeURIComponent(this.props.component.key)}`;
return this.renderLink(url, translate('project_quality_gate.page'), '/project/quality_gate');
- },
+ }
- renderCustomMeasuresLink() {
+ renderCustomMeasuresLink () {
if (!this.props.conf.showManualMeasures) {
return null;
}
const url = `/custom_measures?id=${encodeURIComponent(this.props.component.key)}`;
return this.renderLink(url, translate('custom_measures.page'), '/custom_measures');
- },
+ }
- renderLinksLink() {
+ renderLinksLink () {
if (!this.props.conf.showLinks) {
return null;
}
const url = `/project/links?id=${encodeURIComponent(this.props.component.key)}`;
return this.renderLink(url, translate('project_links.page'), '/project/links');
- },
+ }
- renderPermissionsLink() {
+ renderPermissionsLink () {
if (!this.props.conf.showPermissions) {
return null;
}
const url = `/project_roles?id=${encodeURIComponent(this.props.component.key)}`;
return this.renderLink(url, translate('permissions.page'), '/project_roles');
- },
+ }
- renderHistoryLink() {
+ renderHistoryLink () {
if (!this.props.conf.showHistory) {
return null;
}
const url = `/project/history?id=${encodeURIComponent(this.props.component.key)}`;
return this.renderLink(url, translate('project_history.page'), '/project/history');
- },
+ }
- renderBackgroundTasksLink() {
+ renderBackgroundTasksLink () {
if (!this.props.conf.showBackgroundTasks) {
return null;
}
const url = `/project/background_tasks?id=${encodeURIComponent(this.props.component.key)}`;
return this.renderLink(url, translate('background_tasks.page'), '/project/background_tasks');
- },
+ }
- renderUpdateKeyLink() {
+ renderUpdateKeyLink () {
if (!this.props.conf.showUpdateKey) {
return null;
}
const url = `/project/key?id=${encodeURIComponent(this.props.component.key)}`;
return this.renderLink(url, translate('update_key.page'), '/project/key');
- },
+ }
- renderDeletionLink() {
+ renderDeletionLink () {
const { qualifier } = this.props.component;
if (qualifier !== 'TRK' && qualifier !== 'VW') {
@@ -209,14 +213,14 @@ export default React.createClass({
const url = `/project/deletion?id=${encodeURIComponent(this.props.component.key)}`;
return this.renderLink(url, translate('deletion.page'), '/project/deletion');
- },
+ }
- renderExtensions() {
+ renderExtensions () {
const extensions = this.props.conf.extensions || [];
return extensions.map(e => this.renderLink(e.url, e.name, e.url));
- },
+ }
- renderTools() {
+ renderTools () {
const extensions = this.props.component.extensions || [];
const withoutGovernance = extensions.filter(ext => ext.name !== 'Governance');
const tools = withoutGovernance
@@ -237,9 +241,9 @@ export default React.createClass({
</ul>
</li>
);
- },
+ }
- render() {
+ render () {
return (
<ul className="nav navbar-nav nav-tabs">
{this.renderDashboardLink()}
@@ -251,4 +255,4 @@ export default React.createClass({
</ul>
);
}
-});
+}
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/component-nav-meta.js b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMeta.js
index 393564cd8a3..393564cd8a3 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/component-nav-meta.js
+++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMeta.js
diff --git a/server/sonar-web/src/main/js/app/components/nav/__tests__/nav-test.js b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBreadcrumbs-test.js
index d5ddbab8afd..4790e90ca41 100644
--- a/server/sonar-web/src/main/js/app/components/nav/__tests__/nav-test.js
+++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBreadcrumbs-test.js
@@ -19,13 +19,11 @@
*/
import React from 'react';
import { shallow } from 'enzyme';
-import ComponentNavBreadcrumbs from '../component/component-nav-breadcrumbs';
+import ComponentNavBreadcrumbs from '../ComponentNavBreadcrumbs';
-describe('ComponentNavBreadcrumbs', () => {
- it('should not render breadcrumbs with one element', function () {
- const breadcrumbs = [{ key: 'my-project', name: 'My Project', qualifier: 'TRK' }];
- const result = shallow(<ComponentNavBreadcrumbs breadcrumbs={breadcrumbs}/>);
- expect(result.find('li').length).toBe(1);
- expect(result.find('a').length).toBe(1);
- });
+it('should not render breadcrumbs with one element', function () {
+ const breadcrumbs = [{ key: 'my-project', name: 'My Project', qualifier: 'TRK' }];
+ const result = shallow(<ComponentNavBreadcrumbs breadcrumbs={breadcrumbs}/>);
+ expect(result.find('li').length).toBe(1);
+ expect(result.find('a').length).toBe(1);
});
diff --git a/server/sonar-web/src/main/js/app/components/nav/global/global-nav.js b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNav.js
index 1c2916c56de..01735c65a5a 100644
--- a/server/sonar-web/src/main/js/app/components/nav/global/global-nav.js
+++ b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNav.js
@@ -18,22 +18,24 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
-import GlobalNavBranding from './global-nav-branding';
-import GlobalNavMenu from './global-nav-menu';
-import GlobalNavUser from './global-nav-user';
-import GlobalNavSearch from './global-nav-search';
-import ShortcutsHelpView from './shortcuts-help-view';
+import { connect } from 'react-redux';
+import GlobalNavBranding from './GlobalNavBranding';
+import GlobalNavMenu from './GlobalNavMenu';
+import GlobalNavUser from './GlobalNavUser';
+import GlobalNavSearch from './GlobalNavSearch';
+import ShortcutsHelpView from './ShortcutsHelpView';
+import { getCurrentUser } from '../../../store/rootReducer';
-export default React.createClass({
- componentDidMount() {
+class GlobalNav extends React.Component {
+ componentDidMount () {
window.addEventListener('keypress', this.onKeyPress);
- },
+ }
- componentWillUnmount() {
+ componentWillUnmount () {
window.removeEventListener('keypress', this.onKeyPress);
- },
+ }
- onKeyPress(e) {
+ onKeyPress = e => {
const tagName = e.target.tagName;
const code = e.keyCode || e.which;
const isInput = tagName === 'INPUT' || tagName === 'SELECT' || tagName === 'TEXTAREA';
@@ -42,32 +44,40 @@ export default React.createClass({
if (!isInput && !isModalOpen && isTriggerKey) {
this.openHelp();
}
- },
+ };
- openHelp(e) {
+ openHelp = e => {
if (e) {
e.preventDefault();
}
new ShortcutsHelpView().render();
- },
+ };
- render() {
+ render () {
return (
- <div className="container">
- <GlobalNavBranding {...this.props}/>
+ <nav className="navbar navbar-global page-container" id="global-navigation">
+ <div className="container">
+ <GlobalNavBranding/>
- <GlobalNavMenu {...this.props}/>
+ <GlobalNavMenu {...this.props}/>
- <ul className="nav navbar-nav navbar-right">
- <GlobalNavUser {...this.props}/>
- <GlobalNavSearch {...this.props}/>
- <li>
- <a onClick={this.openHelp} href="#">
- <i className="icon-help navbar-icon"/>
- </a>
- </li>
- </ul>
- </div>
+ <ul className="nav navbar-nav navbar-right">
+ <GlobalNavUser {...this.props}/>
+ <GlobalNavSearch {...this.props}/>
+ <li>
+ <a onClick={this.openHelp} href="#">
+ <i className="icon-help navbar-icon"/>
+ </a>
+ </li>
+ </ul>
+ </div>
+ </nav>
);
}
+}
+
+const mapStateToProps = state => ({
+ currentUser: getCurrentUser(state)
});
+
+export default connect(mapStateToProps)(GlobalNav);
diff --git a/server/sonar-web/src/main/js/app/components/nav/global/global-nav-branding.js b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavBranding.js
index 6241f7a4b63..6542a9fb20b 100644
--- a/server/sonar-web/src/main/js/app/components/nav/global/global-nav-branding.js
+++ b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavBranding.js
@@ -18,29 +18,42 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
+import { connect } from 'react-redux';
+import { getSettingValue, getCurrentUser } from '../../../store/rootReducer';
import { translate } from '../../../../helpers/l10n';
-export default React.createClass({
- renderLogo() {
- const url = this.props.logoUrl || `${window.baseUrl}/images/logo.svg`;
- const width = this.props.logoWidth || 100;
+class GlobalNavBranding extends React.Component {
+ static propTypes = {
+ customLogoUrl: React.PropTypes.string,
+ customLogoWidth: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.number])
+ };
+
+ renderLogo () {
+ const url = this.props.customLogoUrl || `${window.baseUrl}/images/logo.svg`;
+ const width = this.props.customLogoWidth || 100;
const height = 30;
const title = translate('layout.sonar.slogan');
- return <img src={url}
- width={width}
- height={height}
- alt={title}
- title={title}/>;
- },
+ return (
+ <img src={url} width={width} height={height} alt={title} title={title}/>
+ );
+ }
- render() {
- const homeController = window.SS.user ? '/projects/favorite' : '/about';
+ render () {
+ const homeController = this.props.currentUser.isLoggedIn ? '/projects/favorite' : '/about';
const homeUrl = window.baseUrl + homeController;
- const homeLinkClassName = 'navbar-brand' + (this.props.logoUrl ? ' navbar-brand-custom' : '');
+ const homeLinkClassName = 'navbar-brand' + (this.props.customLogoUrl ? ' navbar-brand-custom' : '');
return (
<div className="navbar-header">
<a className={homeLinkClassName} href={homeUrl}>{this.renderLogo()}</a>
</div>
);
}
+}
+
+const mapStateToProps = state => ({
+ currentUser: getCurrentUser(state),
+ customLogoUrl: (getSettingValue(state, 'sonar.lf.logoUrl') || {}).value,
+ customLogoWidth: (getSettingValue(state, 'sonar.lf.logoWidthPx') || {}).value
});
+
+export default connect(mapStateToProps)(GlobalNavBranding);
diff --git a/server/sonar-web/src/main/js/app/components/nav/global/global-nav-menu.js b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMenu.js
index 6295431ffb1..424ee074cef 100644
--- a/server/sonar-web/src/main/js/app/components/nav/global/global-nav-menu.js
+++ b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMenu.js
@@ -18,36 +18,42 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
-import DashboardNameMixin from '../dashboard-name-mixin';
-import LinksMixin from '../links-mixin';
import { translate } from '../../../../helpers/l10n';
+import { isUserAdmin } from '../../../../helpers/users';
-export default React.createClass({
- mixins: [DashboardNameMixin, LinksMixin],
+export default class GlobalNavMenu extends React.Component {
+ static propTypes = {
+ currentUser: React.PropTypes.object.isRequired
+ };
- getDefaultProps () {
- return { globalDashboards: [], globalPages: [] };
- },
+ static defaultProps = {
+ globalDashboards: [],
+ globalPages: []
+ };
+
+ activeLink (url) {
+ return window.location.pathname.indexOf(window.baseUrl + url) === 0 ? 'active' : null;
+ }
renderProjects () {
- const controller = window.SS.user ? '/projects/favorite' : '/projects';
+ const controller = this.props.currentUser.isLoggedIn ? '/projects/favorite' : '/projects';
const url = window.baseUrl + controller;
return (
<li className={this.activeLink('/projects')}>
<a href={url}>{translate('projects.page')}</a>
</li>
);
- },
+ }
renderIssuesLink () {
- const query = window.SS.user ? '#resolved=false|assigned_to_me=true' : '#resolved=false';
+ const query = this.props.currentUser.isLoggedIn ? '#resolved=false|assigned_to_me=true' : '#resolved=false';
const url = window.baseUrl + '/issues' + query;
return (
<li className={this.activeLink('/issues')}>
<a href={url}>{translate('issues.page')}</a>
</li>
);
- },
+ }
renderRulesLink () {
const url = window.baseUrl + '/coding_rules';
@@ -56,16 +62,16 @@ export default React.createClass({
<a href={url}>{translate('coding_rules.page')}</a>
</li>
);
- },
+ }
- renderProfilesLink() {
+ renderProfilesLink () {
const url = window.baseUrl + '/profiles';
return (
<li className={this.activeLink('/profiles')}>
<a href={url}>{translate('quality_profiles.page')}</a>
</li>
);
- },
+ }
renderQualityGatesLink () {
const url = window.baseUrl + '/quality_gates';
@@ -74,10 +80,10 @@ export default React.createClass({
<a href={url}>{translate('quality_gates.page')}</a>
</li>
);
- },
+ }
renderAdministrationLink () {
- if (!window.SS.isUserAdmin) {
+ if (!isUserAdmin(this.props.currentUser)) {
return null;
}
const url = window.baseUrl + '/settings';
@@ -86,7 +92,7 @@ export default React.createClass({
<a className="navbar-admin-link" href={url}>{translate('layout.settings')}</a>
</li>
);
- },
+ }
renderGlobalPageLink (globalPage, index) {
const url = window.baseUrl + globalPage.url;
@@ -95,13 +101,13 @@ export default React.createClass({
<a href={url}>{globalPage.name}</a>
</li>
);
- },
+ }
renderMore () {
if (this.props.globalPages.length === 0) {
return null;
}
- const globalPages = this.props.globalPages.map(this.renderGlobalPageLink);
+ const globalPages = this.props.globalPages.map((p, i) => this.renderGlobalPageLink(p, i));
return (
<li className="dropdown">
<a className="dropdown-toggle" data-toggle="dropdown" href="#">
@@ -113,7 +119,7 @@ export default React.createClass({
</ul>
</li>
);
- },
+ }
render () {
return (
@@ -128,4 +134,4 @@ export default React.createClass({
</ul>
);
}
-});
+}
diff --git a/server/sonar-web/src/main/js/app/components/nav/global/global-nav-search.js b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavSearch.js
index e84850bf365..c4d6f7c385c 100644
--- a/server/sonar-web/src/main/js/app/components/nav/global/global-nav-search.js
+++ b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavSearch.js
@@ -19,7 +19,9 @@
*/
import Backbone from 'backbone';
import React from 'react';
-import SearchView from './search-view';
+import { connect } from 'react-redux';
+import SearchView from './SearchView';
+import { getCurrentUser } from '../../../store/rootReducer';
function contains (root, node) {
while (node) {
@@ -31,12 +33,10 @@ function contains (root, node) {
return false;
}
-export default React.createClass({
- getInitialState() {
- return { open: false };
- },
+class GlobalNavSearch extends React.Component {
+ state = { open: false };
- componentDidMount() {
+ componentDidMount () {
key('s', () => {
const isModalOpen = document.querySelector('html').classList.contains('modal-open');
if (!isModalOpen) {
@@ -44,55 +44,55 @@ export default React.createClass({
}
return false;
});
- },
+ }
- componentWillUnmount() {
+ componentWillUnmount () {
this.closeSearch();
key.unbind('s');
- },
+ }
- openSearch() {
+ openSearch = () => {
document.addEventListener('click', this.onClickOutside);
this.setState({ open: true }, this.renderSearchView);
- },
+ };
- closeSearch() {
+ closeSearch = () => {
document.removeEventListener('click', this.onClickOutside);
this.resetSearchView();
this.setState({ open: false });
- },
+ };
- renderSearchView() {
+ renderSearchView = () => {
const searchContainer = this.refs.container;
this.searchView = new SearchView({
model: new Backbone.Model(this.props),
hide: this.closeSearch
});
this.searchView.render().$el.appendTo(searchContainer);
- },
+ };
- resetSearchView() {
+ resetSearchView = () => {
if (this.searchView) {
this.searchView.destroy();
}
- },
+ };
- onClick(e) {
+ onClick = e => {
e.preventDefault();
if (this.state.open) {
this.closeSearch();
} else {
this.openSearch();
}
- },
+ };
- onClickOutside(e) {
+ onClickOutside = e => {
if (!contains(this.refs.dropdown, e.target)) {
this.closeSearch();
}
- },
+ };
- render() {
+ render () {
const dropdownClassName = 'dropdown' + (this.state.open ? ' open' : '');
return (
<li ref="dropdown" className={dropdownClassName}>
@@ -103,4 +103,10 @@ export default React.createClass({
</li>
);
}
+}
+
+const mapStateToProps = state => ({
+ currentUser: getCurrentUser(state)
});
+
+export default connect(mapStateToProps)(GlobalNavSearch);
diff --git a/server/sonar-web/src/main/js/app/components/nav/global/global-nav-user.js b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavUser.js
index 8c2d0cc9949..11f633320c2 100644
--- a/server/sonar-web/src/main/js/app/components/nav/global/global-nav-user.js
+++ b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavUser.js
@@ -22,13 +22,26 @@ import Avatar from '../../../../components/ui/Avatar';
import RecentHistory from '../component/RecentHistory';
import { translate } from '../../../../helpers/l10n';
-export default React.createClass({
- renderAuthenticated() {
+export default class GlobalNavUser extends React.Component {
+ handleLogin = e => {
+ e.preventDefault();
+ const returnTo = window.location.pathname + window.location.search;
+ window.location = `${window.baseUrl}/sessions/new?return_to=${encodeURIComponent(returnTo)}${window.location.hash}`;
+ };
+
+ handleLogout = e => {
+ e.preventDefault();
+ RecentHistory.clear();
+ window.location = `${window.baseUrl}/sessions/logout`;
+ };
+
+ renderAuthenticated () {
+ const { currentUser } = this.props;
return (
<li className="dropdown js-user-authenticated">
<a className="dropdown-toggle" data-toggle="dropdown" href="#">
- <Avatar email={window.SS.userEmail} size={20}/>&nbsp;
- {window.SS.userName}&nbsp;<i className="icon-dropdown"/>
+ <Avatar email={currentUser.email} size={20}/>&nbsp;
+ {currentUser.name}&nbsp;<i className="icon-dropdown"/>
</a>
<ul className="dropdown-menu dropdown-menu-right">
<li>
@@ -40,30 +53,17 @@ export default React.createClass({
</ul>
</li>
);
- },
+ }
- renderAnonymous() {
+ renderAnonymous () {
return (
<li>
<a onClick={this.handleLogin} href="#">{translate('layout.login')}</a>
</li>
);
- },
-
- handleLogin(e) {
- e.preventDefault();
- const returnTo = window.location.pathname + window.location.search;
- window.location = `${window.baseUrl}/sessions/new?return_to=${encodeURIComponent(returnTo)}${window.location.hash}`;
- },
-
- handleLogout(e) {
- e.preventDefault();
- RecentHistory.clear();
- window.location = `${window.baseUrl}/sessions/logout`;
- },
+ }
- render() {
- const isUserAuthenticated = !!window.SS.user;
- return isUserAuthenticated ? this.renderAuthenticated() : this.renderAnonymous();
+ render () {
+ return this.props.currentUser.isLoggedIn ? this.renderAuthenticated() : this.renderAnonymous();
}
-});
+}
diff --git a/server/sonar-web/src/main/js/app/components/nav/global/search-view.js b/server/sonar-web/src/main/js/app/components/nav/global/SearchView.js
index 9974ddcc284..47d9fe6ae13 100644
--- a/server/sonar-web/src/main/js/app/components/nav/global/search-view.js
+++ b/server/sonar-web/src/main/js/app/components/nav/global/SearchView.js
@@ -28,6 +28,7 @@ import SearchTemplate from '../templates/nav-search.hbs';
import RecentHistory from '../component/RecentHistory';
import { translate } from '../../../../helpers/l10n';
import { collapsedDirFromPath, fileFromPath } from '../../../../helpers/path';
+import { isUserAdmin } from '../../../../helpers/users';
const SearchItemView = Marionette.ItemView.extend({
tagName: 'li',
@@ -96,7 +97,7 @@ export default Marionette.LayoutView.extend({
const that = this;
this.results = new Backbone.Collection();
this.favorite = [];
- if (window.SS.user) {
+ if (this.model.get('currentUser').isLoggedIn) {
this.fetchFavorite().always(function () {
that.resetResultsToDefault();
});
@@ -226,7 +227,7 @@ export default Marionette.LayoutView.extend({
{ name: translate('quality_gates.page'), url: window.baseUrl + '/quality_gates' }
];
const customItems = [];
- if (window.SS.isUserAdmin) {
+ if (isUserAdmin(this.model.get('currentUser'))) {
customItems.push({ name: translate('layout.settings'), url: window.baseUrl + '/settings' });
}
const findings = [].concat(DEFAULT_ITEMS, customItems).filter(function (f) {
diff --git a/server/sonar-web/src/main/js/app/components/nav/global/shortcuts-help-view.js b/server/sonar-web/src/main/js/app/components/nav/global/ShortcutsHelpView.js
index ca4f2f5ac97..ca4f2f5ac97 100644
--- a/server/sonar-web/src/main/js/app/components/nav/global/shortcuts-help-view.js
+++ b/server/sonar-web/src/main/js/app/components/nav/global/ShortcutsHelpView.js
diff --git a/server/sonar-web/src/main/js/app/components/nav/settings/SettingsNav.js b/server/sonar-web/src/main/js/app/components/nav/settings/SettingsNav.js
new file mode 100644
index 00000000000..afe8b25f1f2
--- /dev/null
+++ b/server/sonar-web/src/main/js/app/components/nav/settings/SettingsNav.js
@@ -0,0 +1,134 @@
+/*
+ * 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 classNames from 'classnames';
+import { translate } from '../../../../helpers/l10n';
+
+export default class SettingsNav extends React.Component {
+ static defaultProps = {
+ extensions: []
+ };
+
+ isSomethingActive (urls) {
+ const path = window.location.pathname;
+ return urls.some(url => path.indexOf(window.baseUrl + url) === 0);
+ }
+
+ isSecurityActive () {
+ const urls = ['/users', '/groups', '/roles/global', '/permission_templates'];
+ return this.isSomethingActive(urls);
+ }
+
+ isProjectsActive () {
+ const urls = ['/projects_admin', '/background_tasks'];
+ return this.isSomethingActive(urls);
+ }
+
+ isSystemActive () {
+ const urls = ['/updatecenter', '/system'];
+ return this.isSomethingActive(urls);
+ }
+
+ renderLink(url, title, highlightUrl = url) {
+ const fullUrl = window.baseUrl + url;
+ const isActive = typeof highlightUrl === 'string' ?
+ window.location.pathname.indexOf(window.baseUrl + highlightUrl) === 0 :
+ highlightUrl(fullUrl);
+
+ return (
+ <li key={url} className={classNames({ 'active': isActive })}>
+ <a href={fullUrl}>{title}</a>
+ </li>
+ );
+ }
+
+ render () {
+ const isSecurity = this.isSecurityActive();
+ const isProjects = this.isProjectsActive();
+ const isSystem = this.isSystemActive();
+
+ const securityClassName = classNames('dropdown', { active: isSecurity });
+ const projectsClassName = classNames('dropdown', { active: isProjects });
+ const systemClassName = classNames('dropdown', { active: isSystem });
+ const configurationClassNames = classNames('dropdown', {
+ active: !isSecurity && !isProjects && !isSystem
+ });
+
+ return (
+ <nav className="navbar navbar-context page-container" id="context-navigation">
+ <div className="navbar-context-inner">
+ <div className="container">
+ <ul className="nav navbar-nav nav-crumbs">
+ {this.renderLink('/settings', translate('layout.settings'))}
+ </ul>
+
+ <ul className="nav navbar-nav nav-tabs">
+ <li className={configurationClassNames}>
+ <a className="dropdown-toggle" data-toggle="dropdown" href="#">
+ {translate('sidebar.project_settings')} <i className="icon-dropdown"/>
+ </a>
+ <ul className="dropdown-menu">
+ {this.renderLink('/settings', translate('settings.page'), url => window.location.pathname === url)}
+ {this.renderLink('/settings/licenses', translate('property.category.licenses'))}
+ {this.renderLink('/settings/encryption', translate('property.category.security.encryption'))}
+ {this.renderLink('/settings/server_id', translate('property.category.server_id'))}
+ {this.renderLink('/metrics', 'Custom Metrics')}
+ {this.props.extensions.map(e => this.renderLink(e.url, e.name))}
+ </ul>
+ </li>
+
+ <li className={securityClassName}>
+ <a className="dropdown-toggle" data-toggle="dropdown" href="#">
+ {translate('sidebar.security')} <i className="icon-dropdown"/>
+ </a>
+ <ul className="dropdown-menu">
+ {this.renderLink('/users', translate('users.page'))}
+ {this.renderLink('/groups', translate('user_groups.page'))}
+ {this.renderLink('/roles/global', translate('global_permissions.page'))}
+ {this.renderLink('/permission_templates', translate('permission_templates'))}
+ </ul>
+ </li>
+
+ <li className={projectsClassName}>
+ <a className="dropdown-toggle" data-toggle="dropdown" href="#">
+ {translate('sidebar.projects')} <i className="icon-dropdown"/>
+ </a>
+ <ul className="dropdown-menu">
+ {this.renderLink('/projects_admin', 'Management')}
+ {this.renderLink('/background_tasks', translate('background_tasks.page'))}
+ </ul>
+ </li>
+
+ <li className={systemClassName}>
+ <a className="dropdown-toggle" data-toggle="dropdown" href="#">
+ {translate('sidebar.system')} <i className="icon-dropdown"/>
+ </a>
+ <ul className="dropdown-menu">
+ {this.renderLink('/updatecenter', translate('update_center.page'))}
+ {this.renderLink('/system', translate('system_info.page'))}
+ </ul>
+ </li>
+ </ul>
+ </div>
+ </div>
+ </nav>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/app/components/nav/settings/settings-nav.js b/server/sonar-web/src/main/js/app/components/nav/settings/settings-nav.js
deleted file mode 100644
index 087064ed84c..00000000000
--- a/server/sonar-web/src/main/js/app/components/nav/settings/settings-nav.js
+++ /dev/null
@@ -1,133 +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 React from 'react';
-import classNames from 'classnames';
-import some from 'lodash/some';
-import LinksMixin from '../links-mixin';
-import { translate } from '../../../../helpers/l10n';
-
-export default React.createClass({
- mixins: [LinksMixin],
-
- getDefaultProps() {
- return { extensions: [] };
- },
-
- isSomethingActive(urls) {
- const path = window.location.pathname;
- return some(urls, url => path.indexOf(window.baseUrl + url) === 0);
- },
-
- isSecurityActive() {
- const urls = ['/users', '/groups', '/roles/global', '/permission_templates'];
- return this.isSomethingActive(urls);
- },
-
- isProjectsActive() {
- const urls = ['/projects_admin', '/background_tasks'];
- return this.isSomethingActive(urls);
- },
-
- isSystemActive() {
- const urls = ['/updatecenter', '/system'];
- return this.isSomethingActive(urls);
- },
-
- render() {
- const isSecurity = this.isSecurityActive();
- const isProjects = this.isProjectsActive();
- const isSystem = this.isSystemActive();
-
- const securityClassName = classNames('dropdown', { active: isSecurity });
- const projectsClassName = classNames('dropdown', { active: isProjects });
- const systemClassName = classNames('dropdown', { active: isSystem });
- const configurationClassNames = classNames('dropdown', {
- active: !isSecurity && !isProjects && !isSystem
- });
-
- return (
- <div className="container">
- <ul className="nav navbar-nav nav-crumbs">
- {this.renderLink('/settings', translate('layout.settings'))}
- </ul>
-
- <ul className="nav navbar-nav nav-tabs">
- <li className={configurationClassNames}>
- <a className="dropdown-toggle" data-toggle="dropdown" href="#">
- {translate('sidebar.project_settings')}
- {' '}
- <i className="icon-dropdown"></i>
- </a>
- <ul className="dropdown-menu">
- {this.renderLink('/settings', translate('settings.page'), url => window.location.pathname === url)}
- {this.renderLink('/settings/licenses', translate('property.category.licenses'))}
- {this.renderLink('/settings/encryption', translate('property.category.security.encryption'))}
- {this.renderLink('/settings/server_id', translate('property.category.server_id'))}
- {this.renderLink('/metrics', 'Custom Metrics')}
- {this.props.extensions.map(e => this.renderLink(e.url, e.name))}
- </ul>
- </li>
-
- <li className={securityClassName}>
- <a className="dropdown-toggle" data-toggle="dropdown" href="#">
- {translate('sidebar.security')}
- {' '}
- <i className="icon-dropdown"></i>
- </a>
- <ul className="dropdown-menu">
- {this.renderLink('/users', translate('users.page'))}
- {this.renderLink('/groups', translate('user_groups.page'))}
- {this.renderLink('/roles/global',
- translate('global_permissions.page'))}
- {this.renderLink('/permission_templates',
- translate('permission_templates'))}
- </ul>
- </li>
-
- <li className={projectsClassName}>
- <a className="dropdown-toggle" data-toggle="dropdown" href="#">
- {translate('sidebar.projects')}
- {' '}
- <i className="icon-dropdown"></i>
- </a>
- <ul className="dropdown-menu">
- {this.renderLink('/projects_admin', 'Management')}
- {this.renderLink('/background_tasks',
- translate('background_tasks.page'))}
- </ul>
- </li>
-
- <li className={systemClassName}>
- <a className="dropdown-toggle" data-toggle="dropdown" href="#">
- {translate('sidebar.system')}
- {' '}
- <i className="icon-dropdown"></i>
- </a>
- <ul className="dropdown-menu">
- {this.renderLink('/updatecenter', translate('update_center.page'))}
- {this.renderLink('/system', translate('system_info.page'))}
- </ul>
- </li>
- </ul>
- </div>
-
- );
- }
-});
diff --git a/server/sonar-web/src/main/js/app/index.js b/server/sonar-web/src/main/js/app/index.js
index 65145e45906..edca4bb505c 100644
--- a/server/sonar-web/src/main/js/app/index.js
+++ b/server/sonar-web/src/main/js/app/index.js
@@ -20,12 +20,22 @@
import configureLocale from './utils/configureLocale';
import exposeLibraries from './utils/exposeLibraries';
import startAjaxMonitoring from './utils/startAjaxMonitoring';
-import startApp from './utils/startApp';
import startReactApp from './utils/startReactApp';
+import { installGlobal } from '../helpers/l10n';
import './styles/index';
+require('script!../libs/third-party/jquery-ui.js');
+require('script!../libs/third-party/select2.js');
+require('script!../libs/third-party/keymaster.js');
+require('script!../libs/third-party/bootstrap/tooltip.js');
+require('script!../libs/third-party/bootstrap/dropdown.js');
+require('script!../libs/select2-jquery-ui-fix.js');
+require('script!../libs/inputs.js');
+require('script!../libs/jquery-isolated-scroll.js');
+require('script!../libs/application.js');
+
configureLocale();
startAjaxMonitoring();
-startApp();
+installGlobal();
startReactApp();
exposeLibraries();
diff --git a/server/sonar-web/src/main/js/app/store/appState/duck.js b/server/sonar-web/src/main/js/app/store/appState/duck.js
new file mode 100644
index 00000000000..cd313870e19
--- /dev/null
+++ b/server/sonar-web/src/main/js/app/store/appState/duck.js
@@ -0,0 +1,75 @@
+/*
+ * 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.
+ */
+// @flow
+type AppState = {
+ authenticationError: boolean,
+ authorizationError: boolean,
+ qualifiers: ?Array<string>
+};
+
+export type Action = {
+ type: string,
+ appState: AppState
+}
+
+export const actions = {
+ SET_APP_STATE: 'SET_APP_STATE',
+ REQUIRE_AUTHENTICATION: 'REQUIRE_AUTHENTICATION',
+ REQUIRE_AUTHORIZATION: 'REQUIRE_AUTHORIZATION'
+};
+
+export const setAppState = (appState: AppState): Action => ({
+ type: actions.SET_APP_STATE,
+ appState
+});
+
+export const requireAuthentication = () => ({
+ type: actions.REQUIRE_AUTHENTICATION
+});
+
+export const requireAuthorization = () => ({
+ type: actions.REQUIRE_AUTHORIZATION
+});
+
+const defaultValue = {
+ authenticationError: false,
+ authorizationError: false,
+ qualifiers: null
+};
+
+export default (state: AppState = defaultValue, action: Action) => {
+ if (action.type === actions.SET_APP_STATE) {
+ return { ...state, ...action.appState };
+ }
+
+ if (action.type === actions.REQUIRE_AUTHENTICATION) {
+ return { ...state, authenticationError: true };
+ }
+
+ if (action.type === actions.REQUIRE_AUTHORIZATION) {
+ return { ...state, authorizationError: true };
+ }
+
+ return state;
+};
+
+export const getRootQualifiers = (state: AppState) => (
+ state.qualifiers
+);
diff --git a/server/sonar-web/src/main/js/app/store/rootActions.js b/server/sonar-web/src/main/js/app/store/rootActions.js
index 92f6f4734cf..717c52f4345 100644
--- a/server/sonar-web/src/main/js/app/store/rootActions.js
+++ b/server/sonar-web/src/main/js/app/store/rootActions.js
@@ -18,13 +18,24 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { getLanguages } from '../../api/languages';
+import { getGlobalNavigation, getComponentNavigation } from '../../api/nav';
+import * as auth from '../../api/auth';
import { receiveLanguages } from './languages/actions';
+import { receiveComponents } from './components/actions';
import { addGlobalErrorMessage } from '../../components/store/globalMessages';
import { parseError } from '../../apps/code/utils';
+import { setAppState } from './appState/duck';
-const onFail = dispatch => error => {
- parseError(error).then(message => dispatch(addGlobalErrorMessage(message)));
-};
+const onFail = dispatch => error => (
+ parseError(error).then(message => dispatch(addGlobalErrorMessage(message)))
+);
+
+export const fetchAppState = () => dispatch => (
+ getGlobalNavigation().then(
+ appState => dispatch(setAppState(appState)),
+ onFail(dispatch)
+ )
+);
export const fetchLanguages = () => dispatch => {
return getLanguages().then(
@@ -32,3 +43,37 @@ export const fetchLanguages = () => dispatch => {
onFail(dispatch)
);
};
+
+const mapUuidToId = project => ({ ...project, id: project.uuid });
+
+const addQualifier = project => ({
+ ...project,
+ qualifier: project.breadcrumbs[project.breadcrumbs.length - 1].qualifier
+});
+
+export const fetchProject = key => dispatch => (
+ getComponentNavigation(key).then(
+ component => dispatch(receiveComponents([mapUuidToId(addQualifier(component))])),
+ onFail(dispatch)
+ )
+);
+
+export const doLogin = (login, password) => dispatch => (
+ auth.login(login, password).then(
+ () => { /* everything is fine */ },
+ () => {
+ dispatch(addGlobalErrorMessage('Authentication failed'));
+ return Promise.reject();
+ }
+ )
+);
+
+export const doLogout = () => dispatch => (
+ auth.logout().then(
+ () => { /* everything is fine */ },
+ () => {
+ dispatch(addGlobalErrorMessage('Logout failed'));
+ return Promise.reject();
+ }
+ )
+);
diff --git a/server/sonar-web/src/main/js/app/store/rootReducer.js b/server/sonar-web/src/main/js/app/store/rootReducer.js
index 396c2b5c5f8..b5cba2ff9bf 100644
--- a/server/sonar-web/src/main/js/app/store/rootReducer.js
+++ b/server/sonar-web/src/main/js/app/store/rootReducer.js
@@ -18,6 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { combineReducers } from 'redux';
+import appState from './appState/duck';
import components, * as fromComponents from './components/reducer';
import users, * as fromUsers from './users/reducer';
import favorites, * as fromFavorites from './favorites/duck';
@@ -33,6 +34,7 @@ import qualityGatesApp from '../../apps/quality-gates/store/rootReducer';
import settingsApp, * as fromSettingsApp from '../../apps/settings/store/rootReducer';
export default combineReducers({
+ appState,
components,
globalMessages,
favorites,
@@ -49,6 +51,10 @@ export default combineReducers({
settingsApp
});
+export const getAppState = state => (
+ state.appState
+);
+
export const getComponent = (state, key) => (
fromComponents.getComponent(state.components, key)
);
@@ -125,6 +131,10 @@ export const getPermissionsAppError = state => (
fromPermissionsApp.getError(state.permissionsApp)
);
+export const getSettingValue = (state, key) => (
+ fromSettingsApp.getValue(state.settingsApp, key)
+);
+
export const getSettingsAppDefinition = (state, key) => (
fromSettingsApp.getDefinition(state.settingsApp, key)
);
diff --git a/server/sonar-web/src/main/js/app/store/users/actions.js b/server/sonar-web/src/main/js/app/store/users/actions.js
index 5ed397862c7..71e055d6526 100644
--- a/server/sonar-web/src/main/js/app/store/users/actions.js
+++ b/server/sonar-web/src/main/js/app/store/users/actions.js
@@ -26,6 +26,6 @@ export const receiveCurrentUser = user => ({
user
});
-export const fetchCurrentUser = () => dispatch => {
- getCurrentUser().then(user => dispatch(receiveCurrentUser(user)));
-};
+export const fetchCurrentUser = () => dispatch => (
+ getCurrentUser().then(user => dispatch(receiveCurrentUser(user)))
+);
diff --git a/server/sonar-web/src/main/js/app/store/users/reducer.js b/server/sonar-web/src/main/js/app/store/users/reducer.js
index d67d04bfdf7..03dd5f9df28 100644
--- a/server/sonar-web/src/main/js/app/store/users/reducer.js
+++ b/server/sonar-web/src/main/js/app/store/users/reducer.js
@@ -39,7 +39,7 @@ const userLogins = (state = [], action = {}) => {
const currentUser = (state = null, action = {}) => {
if (action.type === RECEIVE_CURRENT_USER) {
- return action.user.isLoggedIn ? action.user.login : false;
+ return action.user;
}
return state;
@@ -48,5 +48,5 @@ const currentUser = (state = null, action = {}) => {
export default combineReducers({ usersByLogin, userLogins, currentUser });
export const getCurrentUser = state => (
- state.currentUser ? state.usersByLogin[state.currentUser] : state.currentUser
+ state.currentUser
);
diff --git a/server/sonar-web/src/main/js/app/components/nav/dashboard-name-mixin.js b/server/sonar-web/src/main/js/app/utils/getCurrentUserFromStore.js
index 2bc0227b02a..b16ba492a00 100644
--- a/server/sonar-web/src/main/js/app/components/nav/dashboard-name-mixin.js
+++ b/server/sonar-web/src/main/js/app/utils/getCurrentUserFromStore.js
@@ -17,16 +17,14 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { translate } from '../../../helpers/l10n';
+// @flow
+import { getCurrentUser } from '../store/rootReducer';
-export default {
- getLocalizedDashboardName(baseName) {
- const l10nKey = `dashboard.${baseName}.name`;
- const l10nLabel = translate(l10nKey);
- if (l10nLabel !== l10nKey) {
- return l10nLabel;
- } else {
- return baseName;
- }
- }
+// TODO remove my usages
+const getCurrentUserFromStore = () => {
+ const getStore = require('./getStore').default;
+ const store = getStore();
+ return getCurrentUser(store.getState());
};
+
+export default getCurrentUserFromStore;
diff --git a/server/sonar-web/src/main/js/app/utils/getHistory.js b/server/sonar-web/src/main/js/app/utils/getHistory.js
new file mode 100644
index 00000000000..2f01de7566a
--- /dev/null
+++ b/server/sonar-web/src/main/js/app/utils/getHistory.js
@@ -0,0 +1,35 @@
+/*
+ * 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.
+ */
+// @flow
+import { useRouterHistory } from 'react-router';
+import { createHistory } from 'history';
+
+let history;
+
+const ensureHistory = () => {
+ history = useRouterHistory(createHistory)({
+ basename: window.baseUrl + '/'
+ });
+ return history;
+};
+
+export default () => (
+ history ? history : ensureHistory()
+);
diff --git a/server/sonar-web/src/main/js/app/utils/getStore.js b/server/sonar-web/src/main/js/app/utils/getStore.js
new file mode 100644
index 00000000000..1fb1cad4e4f
--- /dev/null
+++ b/server/sonar-web/src/main/js/app/utils/getStore.js
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+// @flow
+import configureStore from '../../components/store/configureStore';
+import rootReducer from '../store/rootReducer';
+
+let store;
+
+const createStore = () => {
+ store = configureStore(rootReducer);
+ return store;
+};
+
+export default () => (
+ store ? store : createStore()
+);
diff --git a/server/sonar-web/src/main/js/app/utils/handleRequiredAuthentication.js b/server/sonar-web/src/main/js/app/utils/handleRequiredAuthentication.js
new file mode 100644
index 00000000000..566434f196c
--- /dev/null
+++ b/server/sonar-web/src/main/js/app/utils/handleRequiredAuthentication.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.
+ */
+// @flow
+import getStore from './getStore';
+import getHistory from './getHistory';
+import { requireAuthentication } from '../store/appState/duck';
+
+export default () => {
+ const store = getStore();
+ const history = getHistory();
+
+ const returnTo = window.location.pathname + window.location.search + window.location.hash;
+
+ store.dispatch(requireAuthentication());
+ history.replace({
+ pathname: '/sessions/new',
+ query: { 'return_to': returnTo }
+ });
+};
diff --git a/server/sonar-web/src/main/js/app/utils/handleRequiredAuthorization.js b/server/sonar-web/src/main/js/app/utils/handleRequiredAuthorization.js
new file mode 100644
index 00000000000..b8664b312df
--- /dev/null
+++ b/server/sonar-web/src/main/js/app/utils/handleRequiredAuthorization.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.
+ */
+// @flow
+import getStore from './getStore';
+import getHistory from './getHistory';
+import { requireAuthorization } from '../store/appState/duck';
+
+export default () => {
+ const store = getStore();
+ const history = getHistory();
+
+ const returnTo = window.location.pathname + window.location.search + window.location.hash;
+
+ store.dispatch(requireAuthorization());
+ history.replace({
+ pathname: '/sessions/new',
+ query: { 'return_to': returnTo }
+ });
+};
diff --git a/server/sonar-web/src/main/js/app/components/NullComponent.js b/server/sonar-web/src/main/js/app/utils/handleRequiredMigration.js
index a3501dfbd16..1bed65c672e 100644
--- a/server/sonar-web/src/main/js/app/components/NullComponent.js
+++ b/server/sonar-web/src/main/js/app/utils/handleRequiredMigration.js
@@ -17,6 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-export default function () {
- return null;
-}
+// @flow
+export default () => {
+ window.location = window.baseUrl + '/maintenance';
+};
diff --git a/server/sonar-web/src/main/js/app/utils/isCurrentPathKnown.js b/server/sonar-web/src/main/js/app/utils/isCurrentPathKnown.js
deleted file mode 100644
index 37468ca94f4..00000000000
--- a/server/sonar-web/src/main/js/app/utils/isCurrentPathKnown.js
+++ /dev/null
@@ -1,69 +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.
- */
-const knownPaths = [
- 'about',
- 'account',
- 'background_tasks',
- 'coding_rules',
- 'dashboard',
- 'groups',
- 'issues',
- 'maintenance',
- 'metrics',
- 'permission_templates',
- 'projects',
- 'projects_admin',
- 'roles/global',
- 'settings',
- 'setup',
- 'system',
- 'quality_gates',
- 'profiles',
- 'updatecenter',
- 'users',
- 'web_api',
- 'code',
- 'component_issues',
- 'component_measures',
- 'custom_measures',
- 'project/background_tasks',
- 'project/settings',
- 'project/deletion',
- 'project/quality_profiles',
- 'project/quality_gate',
- 'project/links',
- 'project/key',
- 'project_roles'
-];
-
-const ignoredPaths = [
- 'users/new'
-];
-
-export default function () {
- const currentPath = window.location.pathname;
-
- const isIgnored = ignoredPaths.some(path => currentPath.indexOf(`${window.baseUrl}/${path}`) === 0);
- if (isIgnored) {
- return false;
- }
-
- return knownPaths.some(path => currentPath.indexOf(`${window.baseUrl}/${path}`) === 0);
-}
diff --git a/server/sonar-web/src/main/js/app/utils/startAjaxMonitoring.js b/server/sonar-web/src/main/js/app/utils/startAjaxMonitoring.js
index cc3f75823db..94525d04434 100644
--- a/server/sonar-web/src/main/js/app/utils/startAjaxMonitoring.js
+++ b/server/sonar-web/src/main/js/app/utils/startAjaxMonitoring.js
@@ -165,6 +165,12 @@ function handleAjaxError (jqXHR) {
}
}
+function handleNotAuthenticatedError () {
+ // workaround cyclic dependencies
+ const handleRequiredAuthentication = require('./handleRequiredAuthentication').default;
+ handleRequiredAuthentication();
+}
+
$.ajaxSetup({
beforeSend (jqXHR) {
jqXHR.setRequestHeader(getCSRFTokenName(), getCSRFTokenValue());
@@ -177,6 +183,7 @@ $.ajaxSetup({
},
statusCode: {
400: handleAjaxError,
+ 401: handleNotAuthenticatedError,
403: handleAjaxError,
500: handleAjaxError,
502: handleAjaxError,
diff --git a/server/sonar-web/src/main/js/app/utils/startApp.js b/server/sonar-web/src/main/js/app/utils/startApp.js
deleted file mode 100644
index 9229702f16d..00000000000
--- a/server/sonar-web/src/main/js/app/utils/startApp.js
+++ /dev/null
@@ -1,69 +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 Navigation from '../components/nav/app';
-import { installGlobal, requestMessages } from '../../helpers/l10n';
-
-function requestLocalizationBundle () {
- if (!window.sonarqube.bannedNavigation) {
- installGlobal();
- return requestMessages();
- } else {
- return Promise.resolve();
- }
-}
-
-function startNavigation () {
- if (!window.sonarqube.bannedNavigation) {
- return new Navigation().start();
- } else {
- return Promise.resolve();
- }
-}
-
-function prepareAppOptions (navResponse) {
- const appOptions = { el: '#content' };
- if (navResponse) {
- appOptions.rootQualifiers = navResponse.global.qualifiers;
- appOptions.logoUrl = navResponse.global.logoUrl;
- appOptions.logoWidth = navResponse.global.logoWidth;
- if (navResponse.component) {
- appOptions.component = {
- id: navResponse.component.uuid,
- key: navResponse.component.key,
- name: navResponse.component.name,
- qualifier: _.last(navResponse.component.breadcrumbs).qualifier,
- breadcrumbs: navResponse.component.breadcrumbs,
- snapshotDate: navResponse.component.snapshotDate
- };
- }
- }
- return appOptions;
-}
-
-const startApp = () => {
- window.sonarqube.appStarted = Promise.resolve()
- .then(requestLocalizationBundle)
- .then(startNavigation)
- .then(prepareAppOptions);
-};
-
-export default startApp;
-
diff --git a/server/sonar-web/src/main/js/app/utils/startReactApp.js b/server/sonar-web/src/main/js/app/utils/startReactApp.js
index 31b5aac4197..0d22ea04de8 100644
--- a/server/sonar-web/src/main/js/app/utils/startReactApp.js
+++ b/server/sonar-web/src/main/js/app/utils/startReactApp.js
@@ -19,17 +19,23 @@
*/
import React from 'react';
import { render } from 'react-dom';
-import { Router, Route, useRouterHistory } from 'react-router';
-import { createHistory } from 'history';
+import { Router, Route, IndexRoute } from 'react-router';
import { Provider } from 'react-redux';
+import LocalizationContainer from '../components/LocalizationContainer';
+import MigrationContainer from '../components/MigrationContainer';
import App from '../components/App';
-import ComponentContainer from '../components/ComponentContainer';
-import NullComponent from '../components/NullComponent';
+import GlobalContainer from '../components/GlobalContainer';
+import SimpleContainer from '../components/SimpleContainer';
+import Landing from '../components/Landing';
+import ProjectContainer from '../components/ProjectContainer';
+import AdminContainer from '../components/AdminContainer';
+import NotFound from '../components/NotFound';
import aboutRoutes from '../../apps/about/routes';
import accountRoutes from '../../apps/account/routes';
import backgroundTasksRoutes from '../../apps/background-tasks/routes';
import codeRoutes from '../../apps/code/routes';
import codingRulesRoutes from '../../apps/coding-rules/routes';
+import componentRoutes from '../../apps/component/routes';
import componentIssuesRoutes from '../../apps/component-issues/routes';
import componentMeasuresRoutes from '../../apps/component-measures/routes';
import customMeasuresRoutes from '../../apps/custom-measures/routes';
@@ -43,6 +49,7 @@ import projectsRoutes from '../../apps/projects/routes';
import projectsAdminRoutes from '../../apps/projects-admin/routes';
import qualityGatesRoutes from '../../apps/quality-gates/routes';
import qualityProfilesRoutes from '../../apps/quality-profiles/routes';
+import sessionsRoutes from '../../apps/sessions/routes';
import settingsRoutes from '../../apps/settings/routes';
import systemRoutes from '../../apps/system/routes';
import updateCenterRoutes from '../../apps/update-center/routes';
@@ -50,67 +57,79 @@ import usersRoutes from '../../apps/users/routes';
import webAPIRoutes from '../../apps/web-api/routes';
import { maintenanceRoutes, setupRoutes } from '../../apps/maintenance/routes';
import { globalPermissionsRoutes, projectPermissionsRoutes } from '../../apps/permissions/routes';
-import configureStore from '../../components/store/configureStore';
-import rootReducer from '../store/rootReducer';
-import isCurrentPathKnown from './isCurrentPathKnown';
+import getStore from './getStore';
+import getHistory from './getHistory';
const startReactApp = () => {
- if (isCurrentPathKnown()) {
- window.sonarqube.appStarted.then(options => {
- const el = document.querySelector(options.el);
+ const el = document.getElementById('content');
- const history = useRouterHistory(createHistory)({
- basename: window.baseUrl + '/'
- });
+ const history = getHistory();
+ const store = getStore();
- const store = configureStore(rootReducer);
+ render((
+ <Provider store={store}>
+ <Router history={history}>
+ <Route component={LocalizationContainer}>
+ <Route component={SimpleContainer}>
+ <Route path="maintenance">{maintenanceRoutes}</Route>
+ <Route path="setup">{setupRoutes}</Route>
+ </Route>
+
+ <Route component={MigrationContainer}>
+ <Route component={SimpleContainer}>
+ <Route path="/sessions">{sessionsRoutes}</Route>
+ </Route>
- render((
- <Provider store={store}>
- <Router history={history}>
<Route path="/" component={App}>
- <Route path="about">{aboutRoutes}</Route>
- <Route path="account">{accountRoutes}</Route>
- <Route path="background_tasks">{backgroundTasksRoutes}</Route>
- <Route path="coding_rules">{codingRulesRoutes}</Route>
- <Route path="dashboard">{overviewRoutes}</Route>
- <Route path="groups">{groupsRoutes}</Route>
- <Route path="issues">{issuesRoutes}</Route>
- <Route path="maintenance">{maintenanceRoutes}</Route>
- <Route path="metrics">{metricsRoutes}</Route>
- <Route path="permission_templates">{permissionTemplatesRoutes}</Route>
- <Route path="projects">{projectsRoutes}</Route>
- <Route path="projects_admin">{projectsAdminRoutes}</Route>
- <Route path="roles/global">{globalPermissionsRoutes}</Route>
- <Route path="settings">{settingsRoutes}</Route>
- <Route path="setup">{setupRoutes}</Route>
- <Route path="system">{systemRoutes}</Route>
- <Route path="quality_gates">{qualityGatesRoutes}</Route>
- <Route path="profiles">{qualityProfilesRoutes}</Route>
- <Route path="updatecenter">{updateCenterRoutes}</Route>
- <Route path="users">{usersRoutes}</Route>
- <Route path="web_api">{webAPIRoutes}</Route>
- <Route component={ComponentContainer}>
- <Route path="code">{codeRoutes}</Route>
- <Route path="component_issues">{componentIssuesRoutes}</Route>
- <Route path="component_measures">{componentMeasuresRoutes}</Route>
- <Route path="custom_measures">{customMeasuresRoutes}</Route>
- <Route path="project">
+ <IndexRoute component={Landing}/>
+
+ <Route component={GlobalContainer}>
+ <Route path="about">{aboutRoutes}</Route>
+ <Route path="account">{accountRoutes}</Route>
+ <Route path="coding_rules">{codingRulesRoutes}</Route>
+ <Route path="component">{componentRoutes}</Route>
+ <Route path="issues">{issuesRoutes}</Route>
+ <Route path="projects">{projectsRoutes}</Route>
+ <Route path="quality_gates">{qualityGatesRoutes}</Route>
+ <Route path="profiles">{qualityProfilesRoutes}</Route>
+ <Route path="web_api">{webAPIRoutes}</Route>
+
+ <Route component={ProjectContainer}>
+ <Route path="code">{codeRoutes}</Route>
+ <Route path="component_issues">{componentIssuesRoutes}</Route>
+ <Route path="component_measures">{componentMeasuresRoutes}</Route>
+ <Route path="custom_measures">{customMeasuresRoutes}</Route>
+ <Route path="dashboard">{overviewRoutes}</Route>
+ <Route path="project">
+ <Route path="background_tasks">{backgroundTasksRoutes}</Route>
+ <Route path="settings">{settingsRoutes}</Route>
+ {projectAdminRoutes}
+ </Route>
+ <Route path="project_roles">{projectPermissionsRoutes}</Route>
+ </Route>
+
+ <Route component={AdminContainer}>
<Route path="background_tasks">{backgroundTasksRoutes}</Route>
+ <Route path="groups">{groupsRoutes}</Route>
+ <Route path="metrics">{metricsRoutes}</Route>
+ <Route path="permission_templates">{permissionTemplatesRoutes}</Route>
+ <Route path="projects_admin">{projectsAdminRoutes}</Route>
+ <Route path="roles/global">{globalPermissionsRoutes}</Route>
<Route path="settings">{settingsRoutes}</Route>
- {projectAdminRoutes}
+ <Route path="system">{systemRoutes}</Route>
+ <Route path="updatecenter">{updateCenterRoutes}</Route>
+ <Route path="users">{usersRoutes}</Route>
</Route>
- <Route path="project_roles">{projectPermissionsRoutes}</Route>
</Route>
- </Route>
- <Route path="*" component={NullComponent}/>
- </Router>
- </Provider>
- ), el);
- });
- }
+ <Route path="*" component={NotFound}/>
+ </Route>
+ </Route>
+ </Route>
+ </Router>
+ </Provider>
+ ), el);
};
export default startReactApp;
diff --git a/server/sonar-web/src/main/js/apps/about/components/AboutApp.js b/server/sonar-web/src/main/js/apps/about/components/AboutApp.js
index dedff134191..dae2e6c8158 100644
--- a/server/sonar-web/src/main/js/apps/about/components/AboutApp.js
+++ b/server/sonar-web/src/main/js/apps/about/components/AboutApp.js
@@ -18,6 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
+import { connect } from 'react-redux';
import keyBy from 'lodash/keyBy';
import AboutProjects from './AboutProjects';
import EntryIssueTypes from './EntryIssueTypes';
@@ -28,11 +29,18 @@ import AboutLeakPeriod from './AboutLeakPeriod';
import AboutStandards from './AboutStandards';
import AboutScanners from './AboutScanners';
import { translate } from '../../../helpers/l10n';
-import '../styles.css';
import { searchProjects } from '../../../api/components';
import { getFacet } from '../../../api/issues';
+import { getSettingValue } from '../../../app/store/rootReducer';
+import * as settingsAPI from '../../../api/settings';
+import '../styles.css';
+
+class AboutApp extends React.Component {
+ static propTypes = {
+ customLogoUrl: React.PropTypes.string,
+ customLogoWidth: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.number])
+ };
-export default class AboutApp extends React.Component {
state = {
loading: true
};
@@ -56,20 +64,23 @@ export default class AboutApp extends React.Component {
return getFacet({ resolved: false }, 'types');
}
+ loadCustomText () {
+ return settingsAPI.getSettingValue('sonar.lf.aboutText');
+ }
+
loadData () {
Promise.all([
- window.sonarqube.appStarted,
this.loadProjects(),
- this.loadIssues()
+ this.loadIssues(),
+ this.loadCustomText()
]).then(responses => {
if (this.mounted) {
- const [options, projectsCount, issues] = responses;
+ const [projectsCount, issues, customText] = responses;
const issueTypes = keyBy(issues.facet, 'val');
this.setState({
projectsCount,
issueTypes,
- logoUrl: options.logoUrl,
- logoWidth: options.logoWidth,
+ customText,
loading: false
});
}
@@ -81,12 +92,12 @@ export default class AboutApp extends React.Component {
return null;
}
- const { landingText } = window.sonarqube;
+ const { customText } = this.state;
- const logoUrl = this.state.logoUrl || `${window.baseUrl}/images/logo.svg`;
- const logoWidth = this.state.logoWidth || 100;
+ const logoUrl = this.props.customLogoUrl || `${window.baseUrl}/images/logo.svg`;
+ const logoWidth = Number(this.props.customLogoWidth || 100);
const logoHeight = 30;
- const logoTitle = this.state.logoUrl ? '' : translate('layout.sonar.slogan');
+ const logoTitle = this.props.customLogoUrl ? '' : translate('layout.sonar.slogan');
return (
<div id="about-page" className="about-page">
@@ -113,8 +124,8 @@ export default class AboutApp extends React.Component {
<div className="about-page-container">
- {landingText.length > 0 && (
- <div className="about-page-section" dangerouslySetInnerHTML={{ __html: landingText }}/>
+ {customText != null && customText.length > 0 && (
+ <div className="about-page-section" dangerouslySetInnerHTML={{ __html: customText }}/>
)}
<div className="columns">
@@ -142,3 +153,10 @@ export default class AboutApp extends React.Component {
);
}
}
+
+const mapStateToProps = state => ({
+ customLogoUrl: (getSettingValue(state, 'sonar.lf.logoUrl') || {}).value,
+ customLogoWidth: (getSettingValue(state, 'sonar.lf.logoWidthPx') || {}).value
+});
+
+export default connect(mapStateToProps)(AboutApp);
diff --git a/server/sonar-web/src/main/js/apps/account/components/Account.js b/server/sonar-web/src/main/js/apps/account/components/Account.js
index 35e273a1d32..0606d1aef61 100644
--- a/server/sonar-web/src/main/js/apps/account/components/Account.js
+++ b/server/sonar-web/src/main/js/apps/account/components/Account.js
@@ -28,16 +28,6 @@ class Account extends React.Component {
render () {
const { currentUser, children } = this.props;
- if (currentUser == null) {
- return (
- <div id="account-page">
- <div className="text-center">
- <i className="spinner"/>
- </div>
- </div>
- );
- }
-
return (
<div id="account-page">
<header className="account-header">
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/BackgroundTasksApp.js b/server/sonar-web/src/main/js/apps/background-tasks/components/BackgroundTasksApp.js
index 0fa65014b61..94c5459e0fd 100644
--- a/server/sonar-web/src/main/js/apps/background-tasks/components/BackgroundTasksApp.js
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/BackgroundTasksApp.js
@@ -17,11 +17,11 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
- /* @flow */
+// @flow
import React from 'react';
import shallowCompare from 'react-addons-shallow-compare';
import debounce from 'lodash/debounce';
-
+import { connect } from 'react-redux';
import { DEFAULT_FILTERS, DEBOUNCE_DELAY, STATUSES, CURRENTS } from './../constants';
import Header from './Header';
import Footer from './Footer';
@@ -31,9 +31,10 @@ import Tasks from '../components/Tasks';
import { getTypes, getActivity, getStatus, cancelAllTasks, cancelTask as cancelTaskAPI } from '../../../api/ce';
import { updateTask, mapFiltersToParameters } from '../utils';
import { Task } from '../types';
+import { getComponent } from '../../../app/store/rootReducer';
import '../background-tasks.css';
-export default class BackgroundTasksApp extends React.Component {
+class BackgroundTasksApp extends React.Component {
static contextTypes = {
router: React.PropTypes.object.isRequired
};
@@ -229,3 +230,9 @@ export default class BackgroundTasksApp extends React.Component {
);
}
}
+
+const mapStateToProps = (state, ownProps) => ({
+ component: ownProps.location.query.id ? getComponent(state, ownProps.location.query.id) : undefined
+});
+
+export default connect(mapStateToProps)(BackgroundTasksApp);
diff --git a/server/sonar-web/src/main/js/apps/code/components/App.js b/server/sonar-web/src/main/js/apps/code/components/App.js
index 7dc6b7158b3..fadc222005c 100644
--- a/server/sonar-web/src/main/js/apps/code/components/App.js
+++ b/server/sonar-web/src/main/js/apps/code/components/App.js
@@ -19,7 +19,7 @@
*/
import classNames from 'classnames';
import React from 'react';
-
+import { connect } from 'react-redux';
import Components from './Components';
import Breadcrumbs from './Breadcrumbs';
import SourceViewer from './../../../components/source-viewer/SourceViewer';
@@ -27,10 +27,10 @@ import Search from './Search';
import ListFooter from '../../../components/controls/ListFooter';
import { retrieveComponentChildren, retrieveComponent, loadMoreChildren, parseError } from '../utils';
import { addComponent, addComponentBreadcrumbs } from '../bucket';
-
+import { getComponent } from '../../../app/store/rootReducer';
import '../code.css';
-export default class App extends React.Component {
+class App extends React.Component {
state = {
loading: true,
baseComponent: null,
@@ -210,3 +210,9 @@ export default class App extends React.Component {
);
}
}
+
+const mapStateToProps = (state, ownProps) => ({
+ component: getComponent(state, ownProps.location.query.id)
+});
+
+export default connect(mapStateToProps)(App);
diff --git a/server/sonar-web/src/main/js/apps/component-issues/components/ComponentIssuesAppContainer.js b/server/sonar-web/src/main/js/apps/component-issues/components/ComponentIssuesAppContainer.js
index 430ba7b0e79..0b3278c596e 100644
--- a/server/sonar-web/src/main/js/apps/component-issues/components/ComponentIssuesAppContainer.js
+++ b/server/sonar-web/src/main/js/apps/component-issues/components/ComponentIssuesAppContainer.js
@@ -19,13 +19,22 @@
*/
import React from 'react';
import init from '../init';
+import { connect } from 'react-redux';
+import { getComponent, getCurrentUser } from '../../../app/store/rootReducer';
-export default class ComponentIssuesAppContainer extends React.Component {
+class ComponentIssuesAppContainer extends React.Component {
componentDidMount () {
- init(this.refs.container);
+ init(this.refs.container, this.props.component, this.props.currentUser);
}
render () {
return <div ref="container"/>;
}
}
+
+const mapStateToProps = (state, ownProps) => ({
+ component: getComponent(state, ownProps.location.query.id),
+ currentUser: getCurrentUser(state)
+});
+
+export default connect(mapStateToProps)(ComponentIssuesAppContainer);
diff --git a/server/sonar-web/src/main/js/apps/component-issues/init.js b/server/sonar-web/src/main/js/apps/component-issues/init.js
index 95269406d71..9fd72073beb 100644
--- a/server/sonar-web/src/main/js/apps/component-issues/init.js
+++ b/server/sonar-web/src/main/js/apps/component-issues/init.js
@@ -33,17 +33,19 @@ import FacetsView from './../issues/facets-view';
import HeaderView from './../issues/HeaderView';
const App = new Marionette.Application();
-const init = function (el) {
- const options = window.sonarqube;
-
- this.config = options.config;
+const init = function ({ el, component, currentUser }) {
+ this.config = {
+ resource: component.id,
+ resourceName: component.name,
+ resourceQualifier: component.qualifier
+ };
this.state = new State({
- canBulkChange: !!window.SS.user,
+ canBulkChange: currentUser.isLoggedIn,
isContext: true,
- contextQuery: { componentUuids: options.config.resource },
- contextComponentUuid: options.config.resource,
- contextComponentName: options.config.resourceName,
- contextComponentQualifier: options.config.resourceQualifier
+ contextQuery: { componentUuids: this.config.resource },
+ contextComponentUuid: this.config.resource,
+ contextComponentName: this.config.resourceName,
+ contextComponentQualifier: this.config.resourceQualifier
});
this.updateContextFacets();
this.list = new Issues();
@@ -109,10 +111,10 @@ App.updateContextFacets = function () {
});
};
-App.on('start', function (el) {
- init.call(App, el);
+App.on('start', function (options) {
+ init.call(App, options);
});
-export default function (el) {
- App.start(el);
+export default function (el, component, currentUser) {
+ App.start({ el, component, currentUser });
}
diff --git a/server/sonar-web/src/main/js/apps/component-measures/app/AppContainer.js b/server/sonar-web/src/main/js/apps/component-measures/app/AppContainer.js
index c3e9e632d10..5c1f09eaf63 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/app/AppContainer.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/app/AppContainer.js
@@ -20,13 +20,12 @@
import { connect } from 'react-redux';
import App from './App';
import { fetchMetrics, setComponent } from './actions';
-import { getMeasuresAppAllMetrics } from '../../../app/store/rootReducer';
+import { getComponent, getMeasuresAppAllMetrics } from '../../../app/store/rootReducer';
-const mapStateToProps = state => {
- return {
- metrics: getMeasuresAppAllMetrics(state)
- };
-};
+const mapStateToProps = (state, ownProps) => ({
+ component: getComponent(state, ownProps.location.query.id),
+ metrics: getMeasuresAppAllMetrics(state)
+});
const mapDispatchToProps = dispatch => {
return {
diff --git a/server/sonar-web/src/main/js/app/components/ComponentContainer.js b/server/sonar-web/src/main/js/apps/component/components/App.js
index f31850c3122..fe7d0d59d80 100644
--- a/server/sonar-web/src/main/js/app/components/ComponentContainer.js
+++ b/server/sonar-web/src/main/js/apps/component/components/App.js
@@ -17,15 +17,18 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+// @flow
import React from 'react';
+import SourceViewer from '../../../components/source-viewer/SourceViewer';
+import { getComponentNavigation } from '../../../api/nav';
-export default class ComponentContainer extends React.Component {
+export default class App extends React.Component {
state = {};
componentDidMount () {
- window.sonarqube.appStarted.then(options => {
- this.setState({ component: options.component });
- });
+ getComponentNavigation(this.props.location.query.id).then(component => (
+ this.setState({ component })
+ ));
}
render () {
@@ -33,8 +36,10 @@ export default class ComponentContainer extends React.Component {
return null;
}
- return React.cloneElement(this.props.children, {
- component: this.state.component
- });
+ return (
+ <div className="page">
+ <SourceViewer component={{ id: this.state.component.uuid }}/>
+ </div>
+ );
}
}
diff --git a/server/sonar-web/src/main/js/apps/component/routes.js b/server/sonar-web/src/main/js/apps/component/routes.js
new file mode 100644
index 00000000000..cee8c941699
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/component/routes.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.
+ */
+// @flow
+import React from 'react';
+import { IndexRoute, Redirect } from 'react-router';
+import App from './components/App';
+
+export default [
+ <Redirect key="1" from="/component/index" to="/component"/>,
+ <IndexRoute key="2" component={App}/>
+];
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/components/CustomMeasuresAppContainer.js b/server/sonar-web/src/main/js/apps/custom-measures/components/CustomMeasuresAppContainer.js
index a88477e394a..ad4b91936c6 100644
--- a/server/sonar-web/src/main/js/apps/custom-measures/components/CustomMeasuresAppContainer.js
+++ b/server/sonar-web/src/main/js/apps/custom-measures/components/CustomMeasuresAppContainer.js
@@ -18,22 +18,22 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
+import { connect } from 'react-redux';
import init from '../init';
+import { getComponent } from '../../../app/store/rootReducer';
-export default class CustomMeasuresAppContainer extends React.Component {
+class CustomMeasuresAppContainer extends React.Component {
componentDidMount () {
- if (this.props.component) {
- init(this.refs.container, this.props.component);
- }
- }
-
- componentDidUpdate () {
- if (this.props.component) {
- init(this.refs.container, this.props.component);
- }
+ init(this.refs.container, this.props.component);
}
render () {
return <div ref="container"/>;
}
}
+
+const mapStateToProps = (state, ownProps) => ({
+ component: getComponent(state, ownProps.location.query.id)
+});
+
+export default connect(mapStateToProps)(CustomMeasuresAppContainer);
diff --git a/server/sonar-web/src/main/js/apps/issues/HeaderView.js b/server/sonar-web/src/main/js/apps/issues/HeaderView.js
index 01d6e6cc546..1cb30562e74 100644
--- a/server/sonar-web/src/main/js/apps/issues/HeaderView.js
+++ b/server/sonar-web/src/main/js/apps/issues/HeaderView.js
@@ -54,7 +54,7 @@ export default Marionette.ItemView.extend({
...Marionette.ItemView.prototype.serializeData.apply(this, arguments),
me,
isContext: this.options.app.state.get('isContext'),
- user: window.SS.user
+ user: this.options.app.state.get('user')
};
}
});
diff --git a/server/sonar-web/src/main/js/apps/issues/components/IssuesAppContainer.js b/server/sonar-web/src/main/js/apps/issues/components/IssuesAppContainer.js
index 33820285418..08cf0b30f15 100644
--- a/server/sonar-web/src/main/js/apps/issues/components/IssuesAppContainer.js
+++ b/server/sonar-web/src/main/js/apps/issues/components/IssuesAppContainer.js
@@ -18,14 +18,26 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
+import { connect } from 'react-redux';
import init from '../init';
+import { getCurrentUser } from '../../../app/store/rootReducer';
+
+class IssuesAppContainer extends React.Component {
+ static propTypes = {
+ currentUser: React.PropTypes.any.isRequired
+ };
-export default class IssuesAppContainer extends React.Component {
componentDidMount () {
- init(this.refs.container);
+ init(this.refs.container, this.props.currentUser);
}
render () {
return <div ref="container"/>;
}
}
+
+const mapStateToProps = state => ({
+ currentUser: getCurrentUser(state)
+});
+
+export default connect(mapStateToProps)(IssuesAppContainer);
diff --git a/server/sonar-web/src/main/js/apps/issues/init.js b/server/sonar-web/src/main/js/apps/issues/init.js
index 2ac5041b6ef..da9b2134c07 100644
--- a/server/sonar-web/src/main/js/apps/issues/init.js
+++ b/server/sonar-web/src/main/js/apps/issues/init.js
@@ -32,8 +32,8 @@ import FacetsView from './facets-view';
import HeaderView from './HeaderView';
const App = new Marionette.Application();
-const init = function (el) {
- this.state = new State({ canBulkChange: !!window.SS.user });
+const init = function ({ el, user }) {
+ this.state = new State({ user, canBulkChange: user.isLoggedIn });
this.list = new Issues();
this.facets = new Facets();
@@ -76,7 +76,7 @@ App.on('start', function (el) {
init.call(App, el);
});
-export default function (el) {
- App.start(el);
+export default function (el, user) {
+ App.start({ el, user });
}
diff --git a/server/sonar-web/src/main/js/apps/overview/components/App.js b/server/sonar-web/src/main/js/apps/overview/components/App.js
index d791a021882..2aefdf292f3 100644
--- a/server/sonar-web/src/main/js/apps/overview/components/App.js
+++ b/server/sonar-web/src/main/js/apps/overview/components/App.js
@@ -19,7 +19,6 @@
*/
import React from 'react';
import shallowCompare from 'react-addons-shallow-compare';
-
import OverviewApp from './OverviewApp';
import EmptyOverview from './EmptyOverview';
import { ComponentType } from '../propTypes';
@@ -36,6 +35,15 @@ export default class App extends React.Component {
render () {
const { component } = this.props;
+ if (['FIL', 'UTS'].includes(component.qualifier)) {
+ const SourceViewer = require('../../../components/source-viewer/SourceViewer').default;
+ return (
+ <div className="page">
+ <SourceViewer component={component}/>
+ </div>
+ );
+ }
+
if (!component.snapshotDate) {
return <EmptyOverview {...this.props}/>;
}
diff --git a/server/sonar-web/src/main/js/apps/overview/components/AppContainer.js b/server/sonar-web/src/main/js/apps/overview/components/AppContainer.js
index 0e7bde7cdbd..e697ff24c18 100644
--- a/server/sonar-web/src/main/js/apps/overview/components/AppContainer.js
+++ b/server/sonar-web/src/main/js/apps/overview/components/AppContainer.js
@@ -17,32 +17,12 @@
* 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 App from './App';
+import { getComponent } from '../../../app/store/rootReducer';
-export default class AppContainer extends React.Component {
- state = {};
+const mapStateToProps = (state, ownProps) => ({
+ component: getComponent(state, ownProps.location.query.id)
+});
- componentDidMount () {
- window.sonarqube.appStarted.then(options => {
- this.setState({ component: options.component });
- });
- }
-
- render () {
- // workaround for the case when a file is displayed
- if (window.sonarqube.file) {
- return null;
- }
-
- if (!this.state.component) {
- return null;
- }
-
- const component = { ...this.state.component, ...window.sonarqube.overview.component };
-
- return (
- <App component={component}/>
- );
- }
-}
+export default connect(mapStateToProps)(App);
diff --git a/server/sonar-web/src/main/js/apps/overview/meta/Meta.js b/server/sonar-web/src/main/js/apps/overview/meta/Meta.js
index 70d063d3101..c034da4452b 100644
--- a/server/sonar-web/src/main/js/apps/overview/meta/Meta.js
+++ b/server/sonar-web/src/main/js/apps/overview/meta/Meta.js
@@ -27,15 +27,15 @@ import EventsList from './../events/EventsList';
import MetaSize from './MetaSize';
const Meta = ({ component, measures }) => {
- const { qualifier, description, profiles, gate } = component;
+ const { qualifier, description, qualityProfiles, qualityGate } = component;
const isProject = qualifier === 'TRK';
const isView = qualifier === 'VW' || qualifier === 'SVW';
const isDeveloper = qualifier === 'DEV';
const hasDescription = !!description;
- const hasQualityProfiles = Array.isArray(profiles) && profiles.length > 0;
- const hasQualityGate = !!gate;
+ const hasQualityProfiles = Array.isArray(qualityProfiles) && qualityProfiles.length > 0;
+ const hasQualityGate = !!qualityGate;
const shouldShowQualityProfiles = !isView && !isDeveloper && hasQualityProfiles;
const shouldShowQualityGate = !isView && !isDeveloper && hasQualityGate;
@@ -53,11 +53,11 @@ const Meta = ({ component, measures }) => {
<MetaSize component={component} measures={measures}/>
{shouldShowQualityGate && (
- <MetaQualityGate gate={gate}/>
+ <MetaQualityGate gate={qualityGate}/>
)}
{shouldShowQualityProfiles && (
- <MetaQualityProfiles profiles={profiles}/>
+ <MetaQualityProfiles profiles={qualityProfiles}/>
)}
<MetaLinks component={component}/>
diff --git a/server/sonar-web/src/main/js/apps/overview/meta/MetaQualityProfiles.js b/server/sonar-web/src/main/js/apps/overview/meta/MetaQualityProfiles.js
index 9500b14e6bd..5709d077ec1 100644
--- a/server/sonar-web/src/main/js/apps/overview/meta/MetaQualityProfiles.js
+++ b/server/sonar-web/src/main/js/apps/overview/meta/MetaQualityProfiles.js
@@ -18,12 +18,14 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
+import { connect } from 'react-redux';
import { TooltipsContainer } from '../../../components/mixins/tooltips-mixin';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { getQualityProfileUrl } from '../../../helpers/urls';
import { searchRules } from '../../../api/rules';
+import { getLanguages } from '../../../app/store/rootReducer';
-export default class MetaQualityProfiles extends React.Component {
+class MetaQualityProfiles extends React.Component {
state = {
deprecatedByKey: {}
};
@@ -69,10 +71,13 @@ export default class MetaQualityProfiles extends React.Component {
}
renderProfile (profile) {
+ const languageFromStore = this.props.languages[profile.language];
+ const languageName = languageFromStore ? languageFromStore.name : profile.language;
+
const inner = (
<div className="text-ellipsis">
<span className="note spacer-right">
- {'(' + profile.language + ')'}
+ {'(' + languageName + ')'}
</span>
<a href={getQualityProfileUrl(profile.key)}>
{profile.name}
@@ -120,3 +125,9 @@ export default class MetaQualityProfiles extends React.Component {
);
}
}
+
+const mapStateToProps = state => ({
+ languages: getLanguages(state)
+});
+
+export default connect(mapStateToProps)(MetaQualityProfiles);
diff --git a/server/sonar-web/src/main/js/apps/permission-templates/components/AppContainer.js b/server/sonar-web/src/main/js/apps/permission-templates/components/AppContainer.js
index b6f4134f066..4fb4138e7d2 100644
--- a/server/sonar-web/src/main/js/apps/permission-templates/components/AppContainer.js
+++ b/server/sonar-web/src/main/js/apps/permission-templates/components/AppContainer.js
@@ -17,25 +17,15 @@
* 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 App from './App';
+import { getAppState } from '../../../app/store/rootReducer';
+import { getRootQualifiers } from '../../../app/store/appState/duck';
-export default class AppContainer extends React.Component {
- state = {};
+const mapStateToProps = state => ({
+ topQualifiers: getRootQualifiers(getAppState(state)),
+});
- componentDidMount () {
- window.sonarqube.appStarted.then(options => {
- this.setState({ rootQualifiers: options.rootQualifiers });
- });
- }
-
- render () {
- if (!this.state.rootQualifiers) {
- return null;
- }
-
- return (
- <App {...this.props} topQualifiers={this.state.rootQualifiers}/>
- );
- }
-}
+export default connect(
+ mapStateToProps
+)(App);
diff --git a/server/sonar-web/src/main/js/apps/permissions/project/components/App.js b/server/sonar-web/src/main/js/apps/permissions/project/components/App.js
index ba2b4e44e2e..c7657ddcc51 100644
--- a/server/sonar-web/src/main/js/apps/permissions/project/components/App.js
+++ b/server/sonar-web/src/main/js/apps/permissions/project/components/App.js
@@ -18,14 +18,16 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
+import { connect } from 'react-redux';
import PageHeader from './PageHeader';
import AllHoldersList from './AllHoldersList';
import PageError from '../../shared/components/PageError';
+import { getComponent, getCurrentUser } from '../../../../app/store/rootReducer';
import '../../styles.css';
// TODO helmet
-export default class App extends React.Component {
+class App extends React.Component {
static propTypes = {
component: React.PropTypes.object
};
@@ -37,10 +39,18 @@ export default class App extends React.Component {
return (
<div className="page page-limited">
- <PageHeader project={this.props.component}/>
+ <PageHeader project={this.props.component} currentUser={this.props.currentUser}/>
<PageError/>
<AllHoldersList project={this.props.component}/>
</div>
);
}
}
+
+const mapStateToProps = (state, ownProps) => ({
+ component: getComponent(state, ownProps.location.query.id),
+ currentUser: getCurrentUser(state)
+});
+
+export default connect(mapStateToProps)(App);
+
diff --git a/server/sonar-web/src/main/js/apps/permissions/project/components/PageHeader.js b/server/sonar-web/src/main/js/apps/permissions/project/components/PageHeader.js
index 14fcf947ee9..456a16cf7a3 100644
--- a/server/sonar-web/src/main/js/apps/permissions/project/components/PageHeader.js
+++ b/server/sonar-web/src/main/js/apps/permissions/project/components/PageHeader.js
@@ -23,6 +23,7 @@ import { translate } from '../../../../helpers/l10n';
import ApplyTemplateView from '../views/ApplyTemplateView';
import { loadHolders } from '../store/actions';
import { isPermissionsAppLoading } from '../../../../app/store/rootReducer';
+import { isUserAdmin } from '../../../../helpers/users';
class PageHeader extends React.Component {
static propTypes = {
@@ -59,7 +60,7 @@ class PageHeader extends React.Component {
<i className="spinner"/>
)}
- {!!window.SS.isUserAdmin && (
+ {isUserAdmin(this.props.currentUser) && (
<div className="page-actions">
<button className="js-apply-template" onClick={this.handleApplyTemplate}>
Apply Template
diff --git a/server/sonar-web/src/main/js/apps/project-admin/deletion/Deletion.js b/server/sonar-web/src/main/js/apps/project-admin/deletion/Deletion.js
index 52e4fbc1774..0fd141962aa 100644
--- a/server/sonar-web/src/main/js/apps/project-admin/deletion/Deletion.js
+++ b/server/sonar-web/src/main/js/apps/project-admin/deletion/Deletion.js
@@ -18,10 +18,12 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
+import { connect } from 'react-redux';
import Header from './Header';
import Form from './Form';
+import { getComponent } from '../../../app/store/rootReducer';
-export default class Deletion extends React.Component {
+class Deletion extends React.Component {
static propTypes = {
component: React.PropTypes.object
};
@@ -39,3 +41,9 @@ export default class Deletion extends React.Component {
);
}
}
+
+const mapStateToProps = (state, ownProps) => ({
+ component: getComponent(state, ownProps.location.query.id)
+});
+
+export default connect(mapStateToProps)(Deletion);
diff --git a/server/sonar-web/src/main/js/apps/project-admin/key/Key.js b/server/sonar-web/src/main/js/apps/project-admin/key/Key.js
index f4bff8b4a59..ff039076dd1 100644
--- a/server/sonar-web/src/main/js/apps/project-admin/key/Key.js
+++ b/server/sonar-web/src/main/js/apps/project-admin/key/Key.js
@@ -35,7 +35,7 @@ import {
import { parseError } from '../../code/utils';
import { reloadUpdateKeyPage } from './utils';
import RecentHistory from '../../../app/components/nav/component/RecentHistory';
-import { getProjectAdminProjectModules } from '../../../app/store/rootReducer';
+import { getProjectAdminProjectModules, getComponent } from '../../../app/store/rootReducer';
class Key extends React.Component {
static propTypes = {
@@ -152,7 +152,8 @@ class Key extends React.Component {
}
const mapStateToProps = (state, ownProps) => ({
- modules: getProjectAdminProjectModules(state, ownProps.component.key)
+ component: getComponent(state, ownProps.location.query.id),
+ modules: getProjectAdminProjectModules(state, ownProps.location.query.id)
});
export default connect(
diff --git a/server/sonar-web/src/main/js/apps/project-admin/links/Links.js b/server/sonar-web/src/main/js/apps/project-admin/links/Links.js
index 4494c52b5c9..d1e611c87c4 100644
--- a/server/sonar-web/src/main/js/apps/project-admin/links/Links.js
+++ b/server/sonar-web/src/main/js/apps/project-admin/links/Links.js
@@ -28,7 +28,7 @@ import {
deleteProjectLink,
createProjectLink
} from '../store/actions';
-import { getProjectAdminProjectLinks } from '../../../app/store/rootReducer';
+import { getProjectAdminProjectLinks, getComponent } from '../../../app/store/rootReducer';
class Links extends React.Component {
static propTypes = {
@@ -73,7 +73,8 @@ class Links extends React.Component {
}
const mapStateToProps = (state, ownProps) => ({
- links: getProjectAdminProjectLinks(state, ownProps.component.key)
+ component: getComponent(state, ownProps.location.query.id),
+ links: getProjectAdminProjectLinks(state, ownProps.location.query.id)
});
export default connect(
diff --git a/server/sonar-web/src/main/js/apps/project-admin/quality-gate/QualityGate.js b/server/sonar-web/src/main/js/apps/project-admin/quality-gate/QualityGate.js
index 0921f1f2060..7c75ab9479d 100644
--- a/server/sonar-web/src/main/js/apps/project-admin/quality-gate/QualityGate.js
+++ b/server/sonar-web/src/main/js/apps/project-admin/quality-gate/QualityGate.js
@@ -24,7 +24,7 @@ import Header from './Header';
import Form from './Form';
import GlobalMessagesContainer from '../components/GlobalMessagesContainer';
import { fetchProjectGate, setProjectGate } from '../store/actions';
-import { getProjectAdminAllGates, getProjectAdminProjectGate } from '../../../app/store/rootReducer';
+import { getProjectAdminAllGates, getProjectAdminProjectGate, getComponent } from '../../../app/store/rootReducer';
class QualityGate extends React.Component {
static propTypes = {
@@ -60,8 +60,9 @@ class QualityGate extends React.Component {
}
const mapStateToProps = (state, ownProps) => ({
+ component: getComponent(state, ownProps.location.query.id),
allGates: getProjectAdminAllGates(state),
- gate: getProjectAdminProjectGate(state, ownProps.component.key)
+ gate: getProjectAdminProjectGate(state, ownProps.location.query.id)
});
export default connect(
diff --git a/server/sonar-web/src/main/js/apps/project-admin/quality-profiles/QualityProfiles.js b/server/sonar-web/src/main/js/apps/project-admin/quality-profiles/QualityProfiles.js
index 9bfdab5c533..6eda6bd598a 100644
--- a/server/sonar-web/src/main/js/apps/project-admin/quality-profiles/QualityProfiles.js
+++ b/server/sonar-web/src/main/js/apps/project-admin/quality-profiles/QualityProfiles.js
@@ -24,7 +24,11 @@ import Header from './Header';
import Table from './Table';
import GlobalMessagesContainer from '../components/GlobalMessagesContainer';
import { fetchProjectProfiles, setProjectProfile } from '../store/actions';
-import { getProjectAdminAllProfiles, getProjectAdminProjectProfiles } from '../../../app/store/rootReducer';
+import {
+ getProjectAdminAllProfiles,
+ getProjectAdminProjectProfiles,
+ getComponent
+} from '../../../app/store/rootReducer';
class QualityProfiles extends React.Component {
static propTypes = {
@@ -68,8 +72,9 @@ class QualityProfiles extends React.Component {
}
const mapStateToProps = (state, ownProps) => ({
+ component: getComponent(state, ownProps.location.query.id),
allProfiles: getProjectAdminAllProfiles(state),
- profiles: getProjectAdminProjectProfiles(state, ownProps.component.key)
+ profiles: getProjectAdminProjectProfiles(state, ownProps.location.query.id)
});
export default connect(
diff --git a/server/sonar-web/src/main/js/apps/projects-admin/AppContainer.js b/server/sonar-web/src/main/js/apps/projects-admin/AppContainer.js
index e87d63f8ec6..11eb059ad02 100644
--- a/server/sonar-web/src/main/js/apps/projects-admin/AppContainer.js
+++ b/server/sonar-web/src/main/js/apps/projects-admin/AppContainer.js
@@ -17,36 +17,26 @@
* 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 React from 'react';
+import { connect } from 'react-redux';
import Main from './main';
-import { getCurrentUser } from '../../app/store/rootReducer';
+import { getCurrentUser, getAppState } from '../../app/store/rootReducer';
+import { getRootQualifiers } from '../../app/store/appState/duck';
class AppContainer extends React.Component {
- state = {};
-
- componentDidMount () {
- window.sonarqube.appStarted.then(options => {
- this.setState({ rootQualifiers: options.rootQualifiers });
- });
- }
-
render () {
- if (!this.props.user || !this.state.rootQualifiers) {
- return null;
- }
-
const hasProvisionPermission = this.props.user.permissions.global.indexOf('provisioning') !== -1;
return (
<Main
hasProvisionPermission={hasProvisionPermission}
- topLevelQualifiers={this.state.rootQualifiers}/>
+ topLevelQualifiers={this.props.rootQualifiers}/>
);
}
}
const mapStateToProps = state => ({
+ rootQualifiers: getRootQualifiers(getAppState(state)),
user: getCurrentUser(state)
});
diff --git a/server/sonar-web/src/main/js/apps/projects/components/AllProjects.js b/server/sonar-web/src/main/js/apps/projects/components/AllProjects.js
index bf1be5abe52..f7c3c04e7ef 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/AllProjects.js
+++ b/server/sonar-web/src/main/js/apps/projects/components/AllProjects.js
@@ -51,10 +51,6 @@ export default class AllProjects extends React.Component {
}
render () {
- if (this.props.user == null) {
- return null;
- }
-
const isFiltered = Object.keys(this.state.query).some(key => this.state.query[key] != null);
return (
diff --git a/server/sonar-web/src/main/js/apps/projects/components/FavoriteFilter.js b/server/sonar-web/src/main/js/apps/projects/components/FavoriteFilter.js
index 9c56f619d3c..d635b4a7f4c 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/FavoriteFilter.js
+++ b/server/sonar-web/src/main/js/apps/projects/components/FavoriteFilter.js
@@ -23,7 +23,7 @@ import { translate } from '../../../helpers/l10n';
export default class FavoriteFilter extends React.Component {
render () {
- if (!this.props.user) {
+ if (!this.props.user.isLoggedIn) {
return null;
}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/App.js b/server/sonar-web/src/main/js/apps/quality-profiles/components/App.js
index 75c7354fd06..5b794284285 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/components/App.js
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/components/App.js
@@ -19,9 +19,8 @@
*/
import React from 'react';
import { getQualityProfiles, getExporters } from '../../../api/quality-profiles';
-import { getCurrentUser } from '../../../api/users';
-import '../styles.css';
import { sortProfiles } from '../utils';
+import '../styles.css';
export default class App extends React.Component {
state = { loading: true };
@@ -44,16 +43,13 @@ export default class App extends React.Component {
loadData () {
this.setState({ loading: true });
Promise.all([
- getCurrentUser(),
getExporters(),
getQualityProfiles()
]).then(responses => {
if (this.mounted) {
- const [user, exporters, profiles] = responses;
- const canAdmin = user.permissions.global.includes('profileadmin');
+ const [exporters, profiles] = responses;
this.setState({
exporters,
- canAdmin,
profiles: sortProfiles(profiles),
loading: false
});
@@ -77,12 +73,14 @@ export default class App extends React.Component {
const finalLanguages = Object.values(this.props.languages);
+ const canAdmin = this.props.currentUser.permissions.global.includes('profileadmin');
+
return React.cloneElement(this.props.children, {
profiles: this.state.profiles,
languages: finalLanguages,
exporters: this.state.exporters,
- canAdmin: this.state.canAdmin,
- updateProfiles: this.updateProfiles
+ updateProfiles: this.updateProfiles,
+ canAdmin
});
}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/AppContainer.js b/server/sonar-web/src/main/js/apps/quality-profiles/components/AppContainer.js
index 3f164b4c0c2..8d0749f0fc8 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/components/AppContainer.js
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/components/AppContainer.js
@@ -19,10 +19,11 @@
*/
import { connect } from 'react-redux';
import App from './App';
-import { getLanguages } from '../../../app/store/rootReducer';
+import { getLanguages, getCurrentUser } from '../../../app/store/rootReducer';
export default connect(
state => ({
+ currentUser: getCurrentUser(state),
languages: getLanguages(state)
})
)(App);
diff --git a/server/sonar-web/src/main/js/apps/sessions/components/LoginForm.js b/server/sonar-web/src/main/js/apps/sessions/components/LoginForm.js
new file mode 100644
index 00000000000..12010610e3e
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/sessions/components/LoginForm.js
@@ -0,0 +1,102 @@
+/*
+ * 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.
+ */
+// @flow
+import React from 'react';
+import GlobalMessagesContainer from '../../../app/components/GlobalMessagesContainer';
+import { translate } from '../../../helpers/l10n';
+
+export default class LoginForm extends React.Component {
+ static propTypes = {
+ identityProviders: React.PropTypes.array.isRequired,
+ onSubmit: React.PropTypes.func.isRequired
+ };
+
+ state = {
+ login: '',
+ password: ''
+ };
+
+ handleSubmit = (e: any) => {
+ e.preventDefault();
+ this.props.onSubmit(this.state.login, this.state.password);
+ };
+
+ render () {
+ return (
+ <div>
+ <h1 className="maintenance-title text-center">Log In to SonarQube</h1>
+
+ {this.props.identityProviders.length > 0 && (
+ <section className="oauth-providers">
+ <ul>
+ {this.props.identityProviders.map(identityProvider => (
+ <li key={identityProvider.key}>
+ <a href={`${window.baseUrl}/sessions/init/${identityProvider.key}`}
+ style={{ backgroundColor: identityProvider.backgroundColor }}
+ title={`Log in with ${identityProvider.name}` }>
+ <img alt={identityProvider.name} width="20" height="20"
+ src={window.baseUrl + identityProvider.iconPath}/>
+ <span>Log in with {identityProvider.name}</span>
+ </a>
+ </li>
+ ))}
+ </ul>
+ </section>
+ )}
+
+ <form id="login_form" onSubmit={this.handleSubmit}>
+ <GlobalMessagesContainer/>
+
+ <div className="big-spacer-bottom">
+ <label htmlFor="login" className="login-label">{translate('login')}</label>
+ <input type="text"
+ id="login"
+ name="login"
+ className="login-input"
+ maxLength="255"
+ required={true}
+ placeholder={translate('login')}
+ value={this.state.login}
+ onChange={e => this.setState({ login: e.target.value })}/>
+ </div>
+
+ <div className="big-spacer-bottom">
+ <label htmlFor="password" className="login-label">{translate('password')}</label>
+ <input type="password"
+ id="password"
+ name="password"
+ className="login-input"
+ required={true}
+ placeholder={translate('password')}
+ value={this.state.password}
+ onChange={e => this.setState({ password: e.target.value })}/>
+ </div>
+
+ <div>
+ <div className="text-right overflow-hidden">
+ <button name="commit" type="submit">{translate('sessions.log_in')}</button>
+ <a className="spacer-left" href={window.baseUrl + '/'}>{translate('cancel')}</a>
+ </div>
+ </div>
+ </form>
+ </div>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/sessions/components/LoginFormContainer.js b/server/sonar-web/src/main/js/apps/sessions/components/LoginFormContainer.js
new file mode 100644
index 00000000000..f3171181aed
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/sessions/components/LoginFormContainer.js
@@ -0,0 +1,78 @@
+/*
+ * 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.
+ */
+// @flow
+import React from 'react';
+import { connect } from 'react-redux';
+import LoginForm from './LoginForm';
+import { doLogin } from '../../../app/store/rootActions';
+import { getAppState } from '../../../app/store/rootReducer';
+import { getIdentityProviders } from '../../../api/users';
+
+class LoginFormContainer extends React.Component {
+ mounted: bool;
+
+ static propTypes = {
+ location: React.PropTypes.object.isRequired
+ };
+
+ state = {};
+
+ componentDidMount () {
+ this.mounted = true;
+ getIdentityProviders().then(r => {
+ if (this.mounted) {
+ this.setState({ identityProviders: r.identityProviders });
+ }
+ });
+ }
+
+ componentWillUnmount () {
+ this.mounted = false;
+ }
+
+ handleSuccessfulLogin = () => {
+ window.location = this.props.location.query['return_to'] || (window.baseUrl + '/');
+ };
+
+ handleSubmit = (login: string, password: string) => {
+ this.props.doLogin(login, password).then(
+ this.handleSuccessfulLogin,
+ () => { /* do nothing */ }
+ );
+ };
+
+ render () {
+ if (!this.state.identityProviders) {
+ return null;
+ }
+
+ return (
+ <LoginForm identityProviders={this.state.identityProviders} onSubmit={this.handleSubmit}/>
+ );
+ }
+}
+
+const mapStateToProps = state => ({
+ appState: getAppState(state)
+});
+
+const mapDispatchToProps = { doLogin };
+
+export default connect(mapStateToProps, mapDispatchToProps)(LoginFormContainer);
diff --git a/server/sonar-web/src/main/js/apps/sessions/components/Logout.js b/server/sonar-web/src/main/js/apps/sessions/components/Logout.js
new file mode 100644
index 00000000000..409a099aa18
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/sessions/components/Logout.js
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+// @flow
+import React from 'react';
+import { connect } from 'react-redux';
+import GlobalMessagesContainer from '../../../app/components/GlobalMessagesContainer';
+import { doLogout } from '../../../app/store/rootActions';
+
+class Logout extends React.Component {
+ componentDidMount () {
+ this.props.doLogout()
+ .then(() => window.location = window.baseUrl + '/')
+ .catch(() => { /* do nothing */ });
+ }
+
+ render () {
+ return <GlobalMessagesContainer/>;
+ }
+}
+
+const mapStateToProps = () => ({});
+
+const mapDispatchToProps = { doLogout };
+
+export default connect(mapStateToProps, mapDispatchToProps)(Logout);
diff --git a/server/sonar-web/src/main/js/apps/sessions/components/Unauthorized.js b/server/sonar-web/src/main/js/apps/sessions/components/Unauthorized.js
new file mode 100644
index 00000000000..276c33809ec
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/sessions/components/Unauthorized.js
@@ -0,0 +1,49 @@
+/*
+ * 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.
+ */
+// @flow
+import React from 'react';
+
+export default class Unauthorized extends React.Component {
+ static propTypes = {
+ location: React.PropTypes.object.isRequired
+ };
+
+ render () {
+ const { message } = this.props.location.query;
+
+ return (
+ <div className="text-center">
+ <p id="unauthorized">
+ You're not authorized to access this page. Please contact the administrator.
+ </p>
+
+ {!!message && (
+ <p className="spacer-top">
+ Reason : {message}
+ </p>
+ )}
+
+ <div className="big-spacer-top">
+ <a href={window.baseUrl + '/'}>Home</a>
+ </div>
+ </div>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/sessions/routes.js b/server/sonar-web/src/main/js/apps/sessions/routes.js
new file mode 100644
index 00000000000..98204df9838
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/sessions/routes.js
@@ -0,0 +1,31 @@
+/*
+ * 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 { Route, Redirect } from 'react-router';
+import LoginFormContainer from './components/LoginFormContainer';
+import Logout from './components/Logout';
+import Unauthorized from './components/Unauthorized';
+
+export default [
+ <Redirect key="login" from="/sessions/login" to="/sessions/new"/>,
+ <Route key="new" path="new" component={LoginFormContainer}/>,
+ <Route key="logout" path="logout" component={Logout}/>,
+ <Route key="unauthorized" path="unauthorized" component={Unauthorized}/>,
+];
diff --git a/server/sonar-web/src/main/js/apps/settings/components/AppContainer.js b/server/sonar-web/src/main/js/apps/settings/components/AppContainer.js
index e4cd869edf9..288129ff5ec 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/AppContainer.js
+++ b/server/sonar-web/src/main/js/apps/settings/components/AppContainer.js
@@ -17,24 +17,12 @@
* 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 App from './App';
+import { getComponent } from '../../../app/store/rootReducer';
-export default class AppContainer extends React.Component {
- state = {};
+const mapStateToProps = (state, ownProps) => ({
+ component: ownProps.location.query.id ? getComponent(state, ownProps.location.query.id) : undefined
+});
- componentDidMount () {
- window.sonarqube.appStarted.then(options =>
- this.setState({ ready: true, component: options.component }));
- }
-
- render () {
- if (!this.state.ready) {
- return null;
- }
-
- return (
- <App {...this.props} component={this.state.component}/>
- );
- }
-}
+export default connect(mapStateToProps)(App);
diff --git a/server/sonar-web/src/main/js/apps/settings/components/EmailForm.js b/server/sonar-web/src/main/js/apps/settings/components/EmailForm.js
index d90c7fa0f28..3b236e0d52b 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/EmailForm.js
+++ b/server/sonar-web/src/main/js/apps/settings/components/EmailForm.js
@@ -18,15 +18,17 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
+import { connect } from 'react-redux';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { sendTestEmail } from '../../../api/settings';
import { parseError } from '../../code/utils';
+import { getCurrentUser } from '../../../app/store/rootReducer';
-export default class EmailForm extends React.Component {
+class EmailForm extends React.Component {
constructor (props) {
super(props);
this.state = {
- recipient: window.SS.userEmail,
+ recipient: this.props.currentUser.email,
subject: translate('email_configuration.test.subject'),
message: translate('email_configuration.test.message_text'),
loading: false,
@@ -114,3 +116,9 @@ export default class EmailForm extends React.Component {
);
}
}
+
+const mapStateToProps = state => ({
+ currentUser: getCurrentUser(state)
+});
+
+export default connect(mapStateToProps)(EmailForm);
diff --git a/server/sonar-web/src/main/js/apps/settings/store/values/reducer.js b/server/sonar-web/src/main/js/apps/settings/store/values/reducer.js
index 2369b75f3de..196687f245f 100644
--- a/server/sonar-web/src/main/js/apps/settings/store/values/reducer.js
+++ b/server/sonar-web/src/main/js/apps/settings/store/values/reducer.js
@@ -20,18 +20,24 @@
// @flow
import keyBy from 'lodash/keyBy';
import { RECEIVE_VALUES } from './actions';
-import type { SettingValue } from '../../types';
+import { actions as appStateActions } from '../../../../app/store/appState/duck';
type State = { [key: string]: {} };
-type Action = { type: string, settings: SettingValue[] };
-
-const reducer = (state: State = {}, action: Action) => {
+const reducer = (state: State = {}, action: any) => {
if (action.type === RECEIVE_VALUES) {
const settingsByKey = keyBy(action.settings, 'key');
return { ...state, ...settingsByKey };
}
+ if (action.type === appStateActions.SET_APP_STATE) {
+ const settingsByKey = {};
+ Object.keys(action.appState.settings).forEach(key => (
+ settingsByKey[key] = { value: action.appState.settings[key] }
+ ));
+ return { ...state, ...settingsByKey };
+ }
+
return state;
};
diff --git a/server/sonar-web/src/main/js/apps/settings/types.js b/server/sonar-web/src/main/js/apps/settings/types.js
index f82ae12a43b..65658d6987d 100644
--- a/server/sonar-web/src/main/js/apps/settings/types.js
+++ b/server/sonar-web/src/main/js/apps/settings/types.js
@@ -24,5 +24,6 @@ export type Definition = {
};
export type SettingValue = {
+ key: string,
value?: string
};
diff --git a/server/sonar-web/src/main/js/apps/source-viewer/app.js b/server/sonar-web/src/main/js/apps/source-viewer/app.js
deleted file mode 100644
index 700def699b4..00000000000
--- a/server/sonar-web/src/main/js/apps/source-viewer/app.js
+++ /dev/null
@@ -1,49 +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 SourceViewer from '../../components/source-viewer/main';
-
-const App = new Marionette.Application();
-const init = function ({ el }) {
- const options = window.sonarqube;
-
- this.addRegions({ mainRegion: window.sonarqube.el || el });
-
- const viewer = new SourceViewer();
- this.mainRegion.show(viewer);
-
- if (typeof options.file.line === 'number') {
- viewer.open(options.file.uuid, { aroundLine: options.file.line });
- viewer.on('loaded', function () {
- viewer
- .highlightLine(options.file.line)
- .scrollToLine(options.file.line);
- });
- } else {
- viewer.open(options.file.uuid);
- }
-};
-
-App.on('start', function (options) {
- init.call(App, options);
-});
-
-window.sonarqube.appStarted.then(options => App.start(options));
-
diff --git a/server/sonar-web/src/main/js/apps/update-center/components/UpdateCenterAppContainer.js b/server/sonar-web/src/main/js/apps/update-center/components/UpdateCenterAppContainer.js
index 4e40999fe81..500e4c052fa 100644
--- a/server/sonar-web/src/main/js/apps/update-center/components/UpdateCenterAppContainer.js
+++ b/server/sonar-web/src/main/js/apps/update-center/components/UpdateCenterAppContainer.js
@@ -18,14 +18,22 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
+import { connect } from 'react-redux';
import init from '../init';
+import { getSettingValue } from '../../../app/store/rootReducer';
-export default class UpdateCenterAppContainer extends React.Component {
+class UpdateCenterAppContainer extends React.Component {
componentDidMount () {
- init(this.refs.container);
+ init(this.refs.container, this.props.updateCenterActive);
}
render () {
return <div ref="container"/>;
}
}
+
+const mapStateToProps = state => ({
+ updateCenterActive: (getSettingValue(state, 'sonar.updatecenter.activate') || {}).value
+});
+
+export default connect(mapStateToProps)(UpdateCenterAppContainer);
diff --git a/server/sonar-web/src/main/js/apps/update-center/init.js b/server/sonar-web/src/main/js/apps/update-center/init.js
index 3c509e36232..830a3555822 100644
--- a/server/sonar-web/src/main/js/apps/update-center/init.js
+++ b/server/sonar-web/src/main/js/apps/update-center/init.js
@@ -29,11 +29,9 @@ import Router from './router';
import Plugins from './plugins';
const App = new Marionette.Application();
-const init = function (el) {
+const init = function ({ el, updateCenterActive }) {
// State
- this.state = new Backbone.Model({
- updateCenterActive: window.SS.updateCenterActive
- });
+ this.state = new Backbone.Model({ updateCenterActive });
// Layout
this.layout = new Layout({ el });
@@ -72,10 +70,10 @@ const init = function (el) {
});
};
-App.on('start', function (el) {
- init.call(App, el);
+App.on('start', function (options) {
+ init.call(App, options);
});
-export default function (el) {
- App.start(el);
+export default function (el, updateCenterActive) {
+ App.start({ el, updateCenterActive });
}
diff --git a/server/sonar-web/src/main/js/apps/users/change-password-view.js b/server/sonar-web/src/main/js/apps/users/change-password-view.js
index 120703690a1..fb8f29e3c04 100644
--- a/server/sonar-web/src/main/js/apps/users/change-password-view.js
+++ b/server/sonar-web/src/main/js/apps/users/change-password-view.js
@@ -53,7 +53,7 @@ export default ModalForm.extend({
serializeData () {
return Object.assign({}, ModalForm.prototype.serializeData.apply(this, arguments), {
- isOwnPassword: window.SS.user === this.model.id
+ isOwnPassword: this.options.currentUser.login === this.model.id
});
}
});
diff --git a/server/sonar-web/src/main/js/apps/users/components/UsersAppContainer.js b/server/sonar-web/src/main/js/apps/users/components/UsersAppContainer.js
index 8442a308327..8abe6fce357 100644
--- a/server/sonar-web/src/main/js/apps/users/components/UsersAppContainer.js
+++ b/server/sonar-web/src/main/js/apps/users/components/UsersAppContainer.js
@@ -18,14 +18,26 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
+import { connect } from 'react-redux';
import init from '../init';
+import { getCurrentUser } from '../../../app/store/rootReducer';
+
+class UsersAppContainer extends React.Component {
+ static propTypes = {
+ currentUser: React.PropTypes.object.isRequired
+ };
-export default class UsersAppContainer extends React.Component {
componentDidMount () {
- init(this.refs.container);
+ init(this.refs.container, this.props.currentUser);
}
render () {
return <div ref="container"/>;
}
}
+
+const mapStateToProps = state => ({
+ currentUser: getCurrentUser(state)
+});
+
+export default connect(mapStateToProps)(UsersAppContainer);
diff --git a/server/sonar-web/src/main/js/apps/users/init.js b/server/sonar-web/src/main/js/apps/users/init.js
index cd1dc8a6568..490ea231426 100644
--- a/server/sonar-web/src/main/js/apps/users/init.js
+++ b/server/sonar-web/src/main/js/apps/users/init.js
@@ -29,7 +29,7 @@ import { getIdentityProviders } from '../../api/users';
const App = new Marionette.Application();
-const init = function (el, providers) {
+const init = function ({ el, currentUser }, providers) {
// Layout
this.layout = new Layout({ el });
this.layout.render();
@@ -46,7 +46,7 @@ const init = function (el, providers) {
this.layout.searchRegion.show(this.searchView);
// List View
- this.listView = new ListView({ collection: this.users, providers });
+ this.listView = new ListView({ collection: this.users, currentUser, providers });
this.layout.listRegion.show(this.listView);
// List Footer View
@@ -57,10 +57,10 @@ const init = function (el, providers) {
this.users.fetch();
};
-App.on('start', function (el) {
- getIdentityProviders().then(r => init.call(App, el, r.identityProviders));
+App.on('start', function (options) {
+ getIdentityProviders().then(r => init.call(App, options, r.identityProviders));
});
-export default function (el) {
- App.start(el);
+export default function (el, currentUser) {
+ App.start({ el, currentUser });
}
diff --git a/server/sonar-web/src/main/js/apps/users/list-item-view.js b/server/sonar-web/src/main/js/apps/users/list-item-view.js
index f879001e909..7ababc08609 100644
--- a/server/sonar-web/src/main/js/apps/users/list-item-view.js
+++ b/server/sonar-web/src/main/js/apps/users/list-item-view.js
@@ -109,7 +109,8 @@ export default Marionette.ItemView.extend({
changePassword () {
new ChangePasswordView({
model: this.model,
- collection: this.model.collection
+ collection: this.model.collection,
+ currentUser: this.options.currentUser
}).render();
},
diff --git a/server/sonar-web/src/main/js/apps/users/list-view.js b/server/sonar-web/src/main/js/apps/users/list-view.js
index 6fb416fc812..3f557d76db8 100644
--- a/server/sonar-web/src/main/js/apps/users/list-view.js
+++ b/server/sonar-web/src/main/js/apps/users/list-view.js
@@ -33,7 +33,10 @@ export default Marionette.CompositeView.extend({
},
childViewOptions () {
- return { providers: this.options.providers };
+ return {
+ providers: this.options.providers,
+ currentUser: this.options.currentUser
+ };
},
showLoading () {
diff --git a/server/sonar-web/src/main/js/components/issue/issue-view.js b/server/sonar-web/src/main/js/components/issue/issue-view.js
index 8b0d7493d8a..dcab97f7e5c 100644
--- a/server/sonar-web/src/main/js/components/issue/issue-view.js
+++ b/server/sonar-web/src/main/js/components/issue/issue-view.js
@@ -32,6 +32,7 @@ import SetTypeFormView from './views/set-type-form-view';
import TagsFormView from './views/tags-form-view';
import Workspace from '../workspace/main';
import Template from './templates/issue.hbs';
+import getCurrentUserFromStore from '../../app/utils/getCurrentUserFromStore';
export default Marionette.ItemView.extend({
className: 'issue',
@@ -213,7 +214,8 @@ export default Marionette.ItemView.extend({
model: this.model,
triggerEl: $('body')
});
- view.submit(window.SS.user, window.SS.userName);
+ const currentUser = getCurrentUserFromStore();
+ view.submit(currentUser.login, currentUser.name);
view.destroy();
},
diff --git a/server/sonar-web/src/main/js/components/issue/views/assign-form-view.js b/server/sonar-web/src/main/js/components/issue/views/assign-form-view.js
index c947daedfe4..5a608cdecea 100644
--- a/server/sonar-web/src/main/js/components/issue/views/assign-form-view.js
+++ b/server/sonar-web/src/main/js/components/issue/views/assign-form-view.js
@@ -23,6 +23,7 @@ import ActionOptionsView from '../../common/action-options-view';
import Template from '../templates/issue-assign-form.hbs';
import OptionTemplate from '../templates/issue-assign-form-option.hbs';
import { translate } from '../../../helpers/l10n';
+import getCurrentUserFromStore from '../../../app/utils/getCurrentUserFromStore';
export default ActionOptionsView.extend({
template: Template,
@@ -142,8 +143,9 @@ export default ActionOptionsView.extend({
if (this.assignees) {
return this.assignees;
}
+ const currentUser = getCurrentUserFromStore();
const assignees = [
- { id: window.SS.user, text: window.SS.userName },
+ { id: currentUser.login, text: currentUser.name },
{ id: '', text: translate('unassigned') }
];
return this.makeUnique(assignees);
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
deleted file mode 100644
index cc4421cd3aa..00000000000
--- a/server/sonar-web/src/main/js/components/navigator/filters/ajax-select-filters.js
+++ /dev/null
@@ -1,437 +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 'jquery';
-import _ from 'underscore';
-import Backbone from 'backbone';
-import BaseFilters from './base-filters';
-import ChoiceFilters from './choice-filters';
-import Template from '../templates/ajax-select-filter.hbs';
-import ListTemplate from '../templates/choice-filter-item.hbs';
-
-const PAGE_SIZE = 100;
-
-const Suggestions = Backbone.Collection.extend({
- comparator: 'text',
-
- initialize () {
- this.more = false;
- this.page = 0;
- },
-
- parse (r) {
- this.more = r.more;
- return r.results;
- },
-
- fetch (options) {
- this.data = _.extend({
- p: 1,
- ps: PAGE_SIZE
- }, options.data || {});
-
- const settings = _.extend({}, options, { data: this.data });
- return Backbone.Collection.prototype.fetch.call(this, settings);
- },
-
- fetchNextPage (options) {
- if (this.more) {
- this.data.p += 1;
- const settings = _.extend({ remove: false }, options, { data: this.data });
- return this.fetch(settings);
- }
- return false;
- }
-
-});
-
-const UserSuggestions = Suggestions.extend({
-
- url () {
- return window.baseUrl + '/api/users/search';
- },
-
- parse (response) {
- const parsedResponse = window.usersToSelect2(response);
- this.more = parsedResponse.more;
- this.results = parsedResponse.results;
- }
-
-});
-
-const ProjectSuggestions = Suggestions.extend({
-
- url () {
- return window.baseUrl + '/api/resources/search?f=s2&q=TRK&display_key=true';
- }
-
-});
-
-const ComponentSuggestions = Suggestions.extend({
-
- url () {
- return window.baseUrl + '/api/resources/search?f=s2&qp=supportsGlobalDashboards&display_key=true';
- },
-
- parse (r) {
- this.more = r.more;
-
- // If results are divided into categories
- if (r.results.length > 0 && r.results[0].children) {
- const results = [];
- _.each(r.results, function (category) {
- _.each(category.children, function (child) {
- child.category = category.text;
- results.push(child);
- });
- });
- return results;
- } else {
- return r.results;
- }
- }
-
-});
-
-const AjaxSelectDetailsFilterView = ChoiceFilters.DetailsChoiceFilterView.extend({
- template: Template,
- listTemplate: ListTemplate,
- searchKey: 's',
-
- render () {
- ChoiceFilters.DetailsChoiceFilterView.prototype.render.apply(this, arguments);
-
- const that = this;
- const keyup = function (e) {
- if (e.keyCode !== 37 && e.keyCode !== 38 && e.keyCode !== 39 && e.keyCode !== 40) {
- that.search();
- }
- };
- const debouncedKeyup = _.debounce(keyup, 250);
- const scroll = function () {
- that.scroll();
- };
- const throttledScroll = _.throttle(scroll, 1000);
-
- this.$('.navigator-filter-search input')
- .off('keyup keydown')
- .on('keyup', debouncedKeyup)
- .on('keydown', this.keydown);
-
- this.$('.choices')
- .off('scroll')
- .on('scroll', throttledScroll);
- },
-
- search () {
- const that = this;
- this.query = this.$('.navigator-filter-search input').val();
- if (this.query.length > 1) {
- this.$el.addClass('fetching');
- const selected = that.options.filterView.getSelected();
- const data = { ps: PAGE_SIZE };
- data[this.searchKey] = this.query;
- this.options.filterView.choices.fetch({
- data,
- success () {
- selected.forEach(function (item) {
- that.options.filterView.choices.unshift(item);
- });
- _.each(that.model.get('choices'), function (v, k) {
- if (k[0] === '!') {
- that.options.filterView.choices.add(new Backbone.Model({ id: k, text: v }));
- }
- });
- that.updateLists();
- that.$el.removeClass('fetching');
- that.$('.navigator-filter-search').removeClass('fetching-error');
- },
- error () {
- that.showSearchError();
- }
- });
- } else {
- this.resetChoices();
- this.updateLists();
- }
- },
-
- showSearchError () {
- this.$el.removeClass('fetching');
- this.$('.navigator-filter-search').addClass('fetching-error');
- },
-
- scroll () {
- const that = this;
- const el = this.$('.choices');
- const scrollBottom = el.scrollTop() >= el[0].scrollHeight - el.outerHeight();
-
- if (scrollBottom) {
- this.options.filterView.choices.fetchNextPage().done(function () {
- that.updateLists();
- });
- }
- },
-
- keydown (e) {
- if (_([38, 40, 13]).indexOf(e.keyCode) !== -1) {
- e.preventDefault();
- }
- },
-
- resetChoices () {
- const that = this;
- this.options.filterView.choices.reset(this.options.filterView.choices.filter(function (item) {
- return item.get('checked');
- }));
- _.each(this.model.get('choices'), function (v, k) {
- that.options.filterView.choices.add(new Backbone.Model({ id: k, text: v }));
- });
- },
-
- onShow () {
- ChoiceFilters.DetailsChoiceFilterView.prototype.onShow.apply(this, arguments);
- this.resetChoices();
- this.render();
- this.$('.navigator-filter-search input').focus();
- }
-
-});
-
-const AjaxSelectFilterView = ChoiceFilters.ChoiceFilterView.extend({
-
- initialize (options) {
- ChoiceFilters.ChoiceFilterView.prototype.initialize.call(this, {
- projectsView: (options && options.projectsView) ? options.projectsView : AjaxSelectDetailsFilterView
- });
- },
-
- isDefaultValue () {
- return this.getSelected().length === 0;
- },
-
- renderInput () {
- const value = this.model.get('value') || [];
- const input = $('<input>')
- .prop('name', this.model.get('property'))
- .prop('type', 'hidden')
- .css('display', 'none')
- .val(value.join());
- input.appendTo(this.$el);
- },
-
- restoreFromQuery (q) {
- let param = _.findWhere(q, { key: this.model.get('property') });
-
- if (this.model.get('choices')) {
- _.each(this.model.get('choices'), function (v, k) {
- if (k[0] === '!') {
- const x = _.findWhere(q, { key: k.substr(1) });
- if (x == null) {
- return;
- }
- if (!param) {
- param = { value: k };
- } else {
- param.value += ',' + k;
- }
- }
- });
- }
-
- if (param && param.value) {
- this.model.set('enabled', true);
- this.restore(param.value, param);
- } else {
- this.clear();
- }
- },
-
- restore (value, param) {
- const that = this;
- if (_.isString(value)) {
- value = value.split(',');
- }
-
- if (this.choices && value.length > 0) {
- this.model.set({ value, enabled: true });
-
- const opposite = _.filter(value, function (item) {
- return item[0] === '!';
- });
- opposite.forEach(function (item) {
- that.choices.add(new Backbone.Model({
- id: item,
- text: that.model.get('choices')[item],
- checked: true
- }));
- });
-
- value = _.reject(value, function (item) {
- return item[0] === '!';
- });
- if (_.isArray(param.text) && param.text.length === value.length) {
- this.restoreFromText(value, param.text);
- } else {
- this.restoreByRequests(value);
- }
- } else {
- this.clear();
- }
- },
-
- restoreFromText (value, text) {
- const that = this;
- _.each(value, function (v, i) {
- that.choices.add(new Backbone.Model({
- id: v,
- text: text[i],
- checked: true
- }));
- });
- this.onRestore(value);
- },
-
- restoreByRequests (value) {
- const that = this;
- const requests = _.map(value, function (v) {
- return that.createRequest(v);
- });
-
- $.when.apply($, requests).done(function () {
- that.onRestore(value);
- });
- },
-
- onRestore () {
- this.projectsView.updateLists();
- this.renderBase();
- },
-
- clear () {
- this.model.unset('value');
- if (this.choices) {
- this.choices.reset([]);
- }
- this.render();
- },
-
- createRequest () {
- }
-
-});
-
-const ComponentFilterView = AjaxSelectFilterView.extend({
-
- initialize () {
- AjaxSelectFilterView.prototype.initialize.call(this, {
- projectsView: AjaxSelectDetailsFilterView
- });
- this.choices = new ComponentSuggestions();
- },
-
- createRequest (v) {
- const that = this;
- return $
- .ajax({
- url: window.baseUrl + '/api/resources',
- type: 'GET',
- data: { resource: v }
- })
- .done(function (r) {
- that.selection.add(new Backbone.Model({
- id: r[0].key,
- text: r[0].name
- }));
- });
- }
-
-});
-
-const ProjectFilterView = AjaxSelectFilterView.extend({
-
- initialize () {
- BaseFilters.BaseFilterView.prototype.initialize.call(this, {
- projectsView: AjaxSelectDetailsFilterView
- });
-
- this.choices = new ProjectSuggestions();
- },
-
- createRequest (v) {
- const that = this;
- return $
- .ajax({
- url: window.baseUrl + '/api/resources',
- type: 'GET',
- data: { resource: v }
- })
- .done(function (r) {
- that.choices.add(new Backbone.Model({
- id: r[0].key,
- text: r[0].name,
- checked: true
- }));
- });
- }
-
-});
-
-const AssigneeFilterView = AjaxSelectFilterView.extend({
-
- initialize () {
- BaseFilters.BaseFilterView.prototype.initialize.call(this, {
- projectsView: AjaxSelectDetailsFilterView
- });
-
- this.choices = new UserSuggestions();
- },
-
- createRequest (v) {
- const that = this;
- return $
- .ajax({
- url: window.baseUrl + '/api/users/search',
- type: 'GET',
- data: { q: v }
- })
- .done(function (r) {
- that.choices.add(new Backbone.Model({
- id: r.users[0].login,
- text: r.users[0].name + ' (' + r.users[0].login + ')',
- checked: true
- }));
- });
- }
-
-});
-
-/*
- * Export public classes
- */
-
-export default {
- Suggestions,
- AjaxSelectDetailsFilterView,
- AjaxSelectFilterView,
- ProjectFilterView,
- ComponentFilterView,
- AssigneeFilterView
-};
-
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
deleted file mode 100644
index ef9cb3c8315..00000000000
--- a/server/sonar-web/src/main/js/components/navigator/filters/base-filters.js
+++ /dev/null
@@ -1,227 +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 'jquery';
-import _ from 'underscore';
-import Backbone from 'backbone';
-import Marionette from 'backbone.marionette';
-import Template from '../templates/base-filter.hbs';
-import DetailsTemplate from '../templates/base-details-filter.hbs';
-
-const Filter = Backbone.Model.extend({
-
- defaults: {
- enabled: true,
- optional: false,
- multiple: true,
- placeholder: ''
- }
-
-});
-
-const Filters = Backbone.Collection.extend({
- model: Filter
-});
-
-const DetailsFilterView = Marionette.ItemView.extend({
- template: DetailsTemplate,
- className: 'navigator-filter-details',
-
- initialize () {
- this.$el.on('click', function (e) {
- e.stopPropagation();
- });
- this.$el.attr('id', 'filter-' + this.model.get('property'));
- },
-
- onShow () {
- },
-
- onHide () {
- }
-});
-
-const BaseFilterView = Marionette.ItemView.extend({
- template: Template,
- className: 'navigator-filter',
-
- events () {
- return {
- 'click': 'toggleDetails',
- 'click .navigator-filter-disable': 'disable'
- };
- },
-
- modelEvents: {
- 'change:enabled': 'focus',
- 'change:value': 'renderBase',
-
- // for more criteria filter
- 'change:filters': 'render'
- },
-
- initialize (options) {
- Marionette.ItemView.prototype.initialize.apply(this, arguments);
-
- const DetailsView = (options && options.projectsView) || DetailsFilterView;
- this.projectsView = new DetailsView({
- model: this.model,
- filterView: this
- });
-
- this.model.view = this;
- },
-
- attachDetailsView () {
- this.projectsView.$el.detach().appendTo($('body'));
- },
-
- render () {
- this.renderBase();
-
- this.attachDetailsView();
- this.projectsView.render();
-
- this.$el.toggleClass(
- 'navigator-filter-disabled',
- !this.model.get('enabled'));
-
- this.$el.toggleClass(
- 'navigator-filter-optional',
- this.model.get('optional'));
- },
-
- renderBase () {
- Marionette.ItemView.prototype.render.apply(this, arguments);
- this.renderInput();
-
- const title = this.model.get('name') + ': ' + this.renderValue();
- this.$el.prop('title', title);
- this.$el.attr('data-property', this.model.get('property'));
- },
-
- renderInput () {
- },
-
- focus () {
- this.render();
- },
-
- toggleDetails (e) {
- e.stopPropagation();
- this.options.filterBarView.selected = this.options.filterBarView.getEnabledFilters().index(this.$el);
- if (this.$el.hasClass('active')) {
- key.setScope('list');
- this.hideDetails();
- } else {
- key.setScope('filters');
- this.showDetails();
- }
- },
-
- showDetails () {
- this.registerShowedDetails();
-
- const top = this.$el.offset().top + this.$el.outerHeight() - 1;
- const left = this.$el.offset().left;
-
- this.projectsView.$el.css({ top, left }).addClass('active');
- this.$el.addClass('active');
- this.projectsView.onShow();
- },
-
- registerShowedDetails () {
- this.options.filterBarView.hideDetails();
- this.options.filterBarView.showedView = this;
- },
-
- hideDetails () {
- this.projectsView.$el.removeClass('active');
- this.$el.removeClass('active');
- this.projectsView.onHide();
- },
-
- isActive () {
- return this.$el.is('.active');
- },
-
- renderValue () {
- return this.model.get('value') || 'unset';
- },
-
- isDefaultValue () {
- return true;
- },
-
- restoreFromQuery (q) {
- const param = _.findWhere(q, { key: this.model.get('property') });
- if (param && param.value) {
- this.model.set('enabled', true);
- this.restore(param.value, param);
- } else {
- this.clear();
- }
- },
-
- restore (value) {
- this.model.set({ value }, { silent: true });
- this.renderBase();
- },
-
- clear () {
- this.model.unset('value');
- },
-
- disable (e) {
- e.stopPropagation();
- this.hideDetails();
- this.options.filterBarView.hideDetails();
- this.model.set({
- enabled: false,
- value: null
- });
- },
-
- formatValue () {
- const q = {};
- if (this.model.has('property') && this.model.has('value') && this.model.get('value')) {
- q[this.model.get('property')] = this.model.get('value');
- }
- return q;
- },
-
- serializeData () {
- return _.extend({}, this.model.toJSON(), {
- value: this.renderValue(),
- defaultValue: this.isDefaultValue()
- });
- }
-
-});
-
-/*
- * Export public classes
- */
-
-export default {
- Filter,
- Filters,
- BaseFilterView,
- DetailsFilterView
-};
diff --git a/server/sonar-web/src/main/js/components/navigator/filters/checkbox-filters.js b/server/sonar-web/src/main/js/components/navigator/filters/checkbox-filters.js
deleted file mode 100644
index 78b2e1c7fcc..00000000000
--- a/server/sonar-web/src/main/js/components/navigator/filters/checkbox-filters.js
+++ /dev/null
@@ -1,65 +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 'jquery';
-import BaseFilters from './base-filters';
-import Template from '../templates/checkbox-filter.hbs';
-
-export default BaseFilters.BaseFilterView.extend({
- template: Template,
- className: 'navigator-filter navigator-filter-inline',
-
- events () {
- return {
- 'click .navigator-filter-disable': 'disable'
- };
- },
-
- showDetails () {
- },
-
- renderInput () {
- if (this.model.get('enabled')) {
- $('<input>')
- .prop('name', this.model.get('property'))
- .prop('type', 'checkbox')
- .prop('value', 'true')
- .prop('checked', true)
- .css('display', 'none')
- .appendTo(this.$el);
- }
- },
-
- renderValue () {
- return this.model.get('value');
- },
-
- isDefaultValue () {
- return false;
- },
-
- restore (value) {
- this.model.set({
- value,
- enabled: true
- });
- }
-
-});
-
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
deleted file mode 100644
index 7f1b82d9b3a..00000000000
--- a/server/sonar-web/src/main/js/components/navigator/filters/choice-filters.js
+++ /dev/null
@@ -1,383 +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 'jquery';
-import _ from 'underscore';
-import Backbone from 'backbone';
-import BaseFilters from './base-filters';
-import Template from '../templates/choice-filter.hbs';
-import ItemTemplate from '../templates/choice-filter-item.hbs';
-import { translate } from '../../../helpers/l10n';
-
-const DetailsChoiceFilterView = BaseFilters.DetailsFilterView.extend({
- template: Template,
- itemTemplate: ItemTemplate,
-
- events () {
- return {
- 'click label': 'onCheck'
- };
- },
-
- render () {
- BaseFilters.DetailsFilterView.prototype.render.apply(this, arguments);
- this.updateLists();
- },
-
- renderList (collection, selector) {
- const that = this;
- const container = this.$(selector);
-
- container.empty().toggleClass('hidden', collection.length === 0);
- collection.each(function (item) {
- container.append(
- that.itemTemplate(_.extend(item.toJSON(), {
- multiple: that.model.get('multiple') && item.get('id')[0] !== '!'
- }))
- );
- });
- },
-
- updateLists () {
- const choices = new Backbone.Collection(this.options.filterView.choices.reject(function (item) {
- return item.get('id')[0] === '!';
- }));
- const opposite = new Backbone.Collection(this.options.filterView.choices.filter(function (item) {
- return item.get('id')[0] === '!';
- }));
-
- this.renderList(choices, '.choices');
- this.renderList(opposite, '.opposite');
-
- const current = this.currentChoice || 0;
- this.updateCurrent(current);
- },
-
- onCheck (e) {
- const checkbox = $(e.currentTarget);
- const id = checkbox.data('id');
- const checked = checkbox.find('.icon-checkbox-checked').length > 0;
-
- if (this.model.get('multiple')) {
- if (checkbox.closest('.opposite').length > 0) {
- this.options.filterView.choices.each(function (item) {
- item.set('checked', false);
- });
- } else {
- this.options.filterView.choices.filter(function (item) {
- return item.get('id')[0] === '!';
- }).forEach(function (item) {
- item.set('checked', false);
- });
- }
- } else {
- this.options.filterView.choices.each(function (item) {
- item.set('checked', false);
- });
- }
-
- this.options.filterView.choices.get(id).set('checked', !checked);
- this.updateValue();
- this.updateLists();
- },
-
- updateValue () {
- this.model.set('value', this.options.filterView.getSelected().map(function (m) {
- return m.get('id');
- }));
- },
-
- updateCurrent (index) {
- this.currentChoice = index;
- this.$('label').removeClass('current')
- .eq(this.currentChoice).addClass('current');
- },
-
- onShow () {
- this.bindedOnKeyDown = _.bind(this.onKeyDown, this);
- $('body').on('keydown', this.bindedOnKeyDown);
- },
-
- onHide () {
- $('body').off('keydown', this.bindedOnKeyDown);
- },
-
- onKeyDown (e) {
- switch (e.keyCode) {
- case 38:
- e.preventDefault();
- this.selectPrevChoice();
- break;
- case 40:
- e.preventDefault();
- this.selectNextChoice();
- break;
- case 13:
- e.preventDefault();
- this.selectCurrent();
- break;
- default:
-
- // Not a functional key - then skip
- break;
- }
- },
-
- selectNextChoice () {
- if (this.$('label').length > this.currentChoice + 1) {
- this.updateCurrent(this.currentChoice + 1);
- this.scrollNext();
- }
- },
-
- scrollNext () {
- const currentLabel = this.$('label').eq(this.currentChoice);
- if (currentLabel.length > 0) {
- const list = currentLabel.closest('ul');
- const labelPos = currentLabel.offset().top - list.offset().top + list.scrollTop();
- const deltaScroll = labelPos - list.height() + currentLabel.outerHeight();
-
- if (deltaScroll > 0) {
- list.scrollTop(deltaScroll);
- }
- }
- },
-
- selectPrevChoice () {
- if (this.currentChoice > 0) {
- this.updateCurrent(this.currentChoice - 1);
- this.scrollPrev();
- }
- },
-
- scrollPrev () {
- const currentLabel = this.$('label').eq(this.currentChoice);
- if (currentLabel.length > 0) {
- const list = currentLabel.closest('ul');
- const labelPos = currentLabel.offset().top - list.offset().top;
-
- if (labelPos < 0) {
- list.scrollTop(list.scrollTop() + labelPos);
- }
- }
- },
-
- selectCurrent () {
- const cb = this.$('label').eq(this.currentChoice);
- cb.click();
- },
-
- serializeData () {
- return _.extend({}, this.model.toJSON(), {
- choices: new Backbone.Collection(this.options.filterView.choices.reject(function (item) {
- return item.get('id')[0] === '!';
- })).toJSON(),
- opposite: new Backbone.Collection(this.options.filterView.choices.filter(function (item) {
- return item.get('id')[0] === '!';
- })).toJSON()
- });
- }
-
-});
-
-const ChoiceFilterView = BaseFilters.BaseFilterView.extend({
-
- initialize (options) {
- BaseFilters.BaseFilterView.prototype.initialize.call(this, {
- projectsView: (options && options.projectsView) ? options.projectsView : DetailsChoiceFilterView
- });
-
- let index = 0;
- const icons = this.model.get('choiceIcons');
-
- this.choices = new Backbone.Collection(
- _.map(this.model.get('choices'), function (value, key) {
- const model = new Backbone.Model({
- id: key,
- text: value,
- checked: false,
- index: index++
- });
-
- if (icons && icons[key]) {
- model.set('icon', icons[key]);
- }
-
- return model;
- }), { comparator: 'index' }
- );
- },
-
- getSelected () {
- return this.choices.filter(function (m) {
- return m.get('checked');
- });
- },
-
- renderInput () {
- const input = $('<select>')
- .prop('name', this.model.get('property'))
- .prop('multiple', true)
- .css('display', 'none');
- this.choices.each(function (item) {
- const option = $('<option>')
- .prop('value', item.get('id'))
- .prop('selected', item.get('checked'))
- .text(item.get('text'));
- option.appendTo(input);
- });
- input.appendTo(this.$el);
- },
-
- renderValue () {
- const value = this.getSelected().map(function (item) {
- return item.get('text');
- });
- const defaultValue = this.model.has('defaultValue') ?
- this.model.get('defaultValue') :
- this.model.get('multiple') ? translate('all') : translate('any');
-
- return this.isDefaultValue() ? defaultValue : value.join(', ');
- },
-
- isDefaultValue () {
- const selected = this.getSelected();
- return selected.length === 0;
- },
-
- disable () {
- this.choices.each(function (item) {
- item.set('checked', false);
- });
- BaseFilters.BaseFilterView.prototype.disable.apply(this, arguments);
- },
-
- restoreFromQuery (q) {
- let param = _.findWhere(q, { key: this.model.get('property') });
-
- if (this.choices) {
- this.choices.forEach(function (item) {
- if (item.get('id')[0] === '!') {
- let x = _.findWhere(q, { key: item.get('id').substr(1) });
- if (item.get('id').indexOf('=') >= 0) {
- const key = item.get('id').split('=')[0].substr(1);
- const value = item.get('id').split('=')[1];
- x = _.findWhere(q, { key, value });
- }
- if (x == null) {
- return;
- }
- if (!param) {
- param = { value: item.get('id') };
- } else {
- param.value += ',' + item.get('id');
- }
- }
- });
- }
-
- if (param && param.value) {
- this.model.set('enabled', true);
- this.restore(param.value, param);
- } else {
- this.clear();
- }
- },
-
- restore (value) {
- if (_.isString(value)) {
- value = value.split(',');
- }
-
- if (this.choices && value.length > 0) {
- const that = this;
-
- that.choices.each(function (item) {
- item.set('checked', false);
- });
-
- const unknownValues = [];
-
- _.each(value, function (v) {
- const cModel = that.choices.findWhere({ id: v });
- if (cModel) {
- cModel.set('checked', true);
- } else {
- unknownValues.push(v);
- }
- });
-
- value = _.difference(value, unknownValues);
-
- this.model.set({
- value,
- enabled: true
- });
-
- this.render();
- } else {
- this.clear();
- }
- },
-
- clear () {
- if (this.choices) {
- this.choices.each(function (item) {
- item.set('checked', false);
- });
- }
- this.model.unset('value');
- this.projectsView.render();
- if (this.projectsView.updateCurrent) {
- this.projectsView.updateCurrent(0);
- }
- },
-
- formatValue () {
- const q = {};
- if (this.model.has('property') && this.model.has('value') && this.model.get('value').length > 0) {
- const opposite = _.filter(this.model.get('value'), function (item) {
- return item[0] === '!';
- });
- if (opposite.length > 0) {
- opposite.forEach(function (item) {
- if (item.indexOf('=') >= 0) {
- const paramValue = item.split('=');
- q[paramValue[0].substr(1)] = paramValue[1];
- } else {
- q[item.substr(1)] = false;
- }
- });
- } else {
- q[this.model.get('property')] = this.model.get('value').join(',');
- }
- }
- return q;
- }
-
-});
-
-/*
- * Export public classes
- */
-
-export default {
- DetailsChoiceFilterView,
- ChoiceFilterView
-};
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
deleted file mode 100644
index 2ba0b86e5a6..00000000000
--- a/server/sonar-web/src/main/js/components/navigator/filters/favorite-filters.js
+++ /dev/null
@@ -1,91 +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 'jquery';
-import _ from 'underscore';
-import BaseFilters from './base-filters';
-import ChoiceFilters from './choice-filters';
-import Template from '../templates/favorite-filter.hbs';
-import DetailsTemplate from '../templates/favorite-details-filter.hbs';
-
-const DetailsFavoriteFilterView = BaseFilters.DetailsFilterView.extend({
- template: DetailsTemplate,
-
- events: {
- 'click label[data-id]': 'applyFavorite',
- 'click .manage label': 'manage'
- },
-
- applyFavorite (e) {
- const id = $(e.target).data('id');
- window.location = window.baseUrl + this.model.get('favoriteUrl') + '/' + id;
- },
-
- manage () {
- window.location = window.baseUrl + this.model.get('manageUrl');
- },
-
- serializeData () {
- const choices = this.model.get('choices');
- const choicesArray =
- _.sortBy(
- _.map(choices, function (v, k) {
- return { v, k };
- }),
- 'v');
-
- return _.extend({}, this.model.toJSON(), {
- choicesArray
- });
- }
-
-});
-
-const FavoriteFilterView = ChoiceFilters.ChoiceFilterView.extend({
- template: Template,
- className: 'navigator-filter navigator-filter-favorite',
-
- initialize () {
- ChoiceFilters.ChoiceFilterView.prototype.initialize.call(this, {
- projectsView: DetailsFavoriteFilterView
- });
- },
-
- renderValue () {
- return '';
- },
-
- renderInput () {
- },
-
- isDefaultValue () {
- return false;
- }
-
-});
-
-/*
- * Export public classes
- */
-
-export default {
- DetailsFavoriteFilterView,
- FavoriteFilterView
-};
-
diff --git a/server/sonar-web/src/main/js/components/navigator/filters/filter-bar.js b/server/sonar-web/src/main/js/components/navigator/filters/filter-bar.js
deleted file mode 100644
index 5182e73e415..00000000000
--- a/server/sonar-web/src/main/js/components/navigator/filters/filter-bar.js
+++ /dev/null
@@ -1,179 +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 'jquery';
-import _ from 'underscore';
-import Marionette from 'backbone.marionette';
-import BaseFilters from './base-filters';
-import MoreCriteriaFilters from './more-criteria-filters';
-
-export default Marionette.CompositeView.extend({
- childViewContainer: '.navigator-filters-list',
-
- collectionEvents: {
- 'change:enabled': 'changeEnabled'
- },
-
- getChildView (item) {
- return item.get('type') || BaseFilters.BaseFilterView;
- },
-
- childViewOptions () {
- return {
- filterBarView: this,
- app: this.options.app
- };
- },
-
- initialize () {
- Marionette.CompositeView.prototype.initialize.apply(this, arguments);
-
- const that = this;
- $('body').on('click', function () {
- that.hideDetails();
- });
- this.addMoreCriteriaFilter();
-
- key.filter = function (e) {
- let r = true;
- const el = $(e.target);
- const box = el.closest('.navigator-filter-details-inner');
- const tabbableSet = box.find(':tabbable');
- const isElFocusable = el.is(':input') || el.is('a');
- const isInsideDialog = el.closest('.ui-dialog').length > 0;
- if (isElFocusable) {
- if (!isInsideDialog && (e.keyCode === 9 || e.keyCode === 27)) {
- r = tabbableSet.index(el) >= tabbableSet.length - 1;
- } else {
- r = false;
- }
- }
- return r;
- };
- key('tab', 'list', function () {
- key.setScope('filters');
- that.selectFirst();
- return false;
- });
- key('shift+tab', 'filters', function () {
- that.selectPrev();
- return false;
- });
- key('tab', 'filters', function () {
- that.selectNext();
- return false;
- });
- key('escape', 'filters', function () {
- that.hideDetails();
- this.selected = -1;
- key.setScope('list');
- });
- },
-
- getEnabledFilters () {
- return this.$(this.childViewContainer).children()
- .not('.navigator-filter-disabled')
- .not('.navigator-filter-inactive')
- .not('.navigator-filter-favorite');
- },
-
- selectFirst () {
- this.selected = -1;
- this.selectNext();
- },
-
- selectPrev () {
- const filters = this.getEnabledFilters();
- if (this.selected > 0) {
- filters.eq(this.selected).blur();
- this.selected--;
- filters.eq(this.selected).click();
- this.$('.navigator-filter-submit').blur();
- }
- },
-
- selectNext () {
- const filters = this.getEnabledFilters();
- if (this.selected < filters.length - 1) {
- filters.eq(this.selected).blur();
- this.selected++;
- filters.eq(this.selected).click();
- } else {
- this.selected = filters.length;
- this.hideDetails();
- this.$('.navigator-filter-submit').focus();
- }
- },
-
- addMoreCriteriaFilter () {
- const disabledFilters = this.collection.where({ enabled: false });
- if (disabledFilters.length > 0) {
- this.moreCriteriaFilter = new BaseFilters.Filter({
- type: MoreCriteriaFilters.MoreCriteriaFilterView,
- enabled: true,
- optional: false,
- filters: disabledFilters
- });
- this.collection.add(this.moreCriteriaFilter);
- }
- },
-
- onAddChild (childView) {
- if (childView.model.get('type') === MoreCriteriaFilters.FavoriteFilterView) {
- $('.navigator-header').addClass('navigator-header-favorite');
- }
- },
-
- restoreFromQuery (q) {
- this.collection.each(function (item) {
- item.set('enabled', !item.get('optional'));
- item.view.clear();
- item.view.restoreFromQuery(q);
- });
- },
-
- hideDetails () {
- if (_.isObject(this.showedView)) {
- this.showedView.hideDetails();
- }
- },
-
- enableFilter (id) {
- const filter = this.collection.get(id);
- const filterView = filter.view;
-
- filterView.$el.detach().insertBefore(this.$('.navigator-filter-more-criteria'));
- filter.set('enabled', true);
- filterView.showDetails();
- },
-
- changeEnabled () {
- const disabledFilters = _.reject(this.collection.where({ enabled: false }), function (filter) {
- return filter.get('type') === MoreCriteriaFilters.MoreCriteriaFilterView;
- });
-
- if (disabledFilters.length === 0) {
- this.moreCriteriaFilter.set({ enabled: false }, { silent: true });
- } else {
- this.moreCriteriaFilter.set({ enabled: true }, { silent: true });
- }
- this.moreCriteriaFilter.set('filters', disabledFilters);
- }
-
-});
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
deleted file mode 100644
index ff48cbd760e..00000000000
--- a/server/sonar-web/src/main/js/components/navigator/filters/metric-filters.js
+++ /dev/null
@@ -1,204 +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 'jquery';
-import _ from 'underscore';
-import BaseFilters from './base-filters';
-import Template from '../templates/metric-filter.hbs';
-import { translate } from '../../../helpers/l10n';
-
-const DetailsMetricFilterView = BaseFilters.DetailsFilterView.extend({
- template: Template,
-
- events: {
- 'change :input': 'inputChanged'
- },
-
- inputChanged () {
- const metric = this.$('[name=metric]').val();
- const isDifferentialMetric = metric.indexOf('new_') === 0;
- const periodSelect = this.$('[name=period]');
- let period = periodSelect.val();
- const optionZero = periodSelect.children('[value="0"]');
- const value = {
- metric,
- period,
- metricText: this.$('[name=metric] option:selected').text(),
- periodText: this.$('[name=period] option:selected').text(),
- op: this.$('[name=op]').val(),
- opText: this.$('[name=op] option:selected').text(),
- val: this.$('[name=val]').val(),
- valText: this.$('[name=val]').originalVal()
- };
-
- if (isDifferentialMetric) {
- optionZero.remove();
- if (period === '0') {
- period = '1';
- }
- } else {
- if (optionZero.length === 0) {
- periodSelect.prepend(this.periodZeroOption);
- }
- }
- periodSelect.select2('destroy').val(period).select2({
- width: '100%',
- minimumResultsForSearch: 100
- });
-
- this.updateDataType(value);
- this.model.set('value', value);
- },
-
- updateDataType (value) {
- const metric = _.find(window.SS.metrics, function (m) {
- return m.metric.name === value.metric;
- });
- if (metric) {
- this.$('[name=val]').data('type', metric.metric.val_type);
- if (metric.metric.val_type === 'WORK_DUR') {
- this.$('[name=val]').prop('placeholder', '1d 7h 59min');
- }
- if (metric.metric.val_type === 'RATING') {
- this.$('[name=val]').prop('placeholder', 'A');
- }
- }
- },
-
- onRender () {
- const periodZeroLabel = this.$('[name=period]').children('[value="0"]').html();
- this.periodZeroOption = `<option value="0">${periodZeroLabel}</option>`;
-
- const value = this.model.get('value') || {};
- this.$('[name=metric]').val(value.metric).select2({
- width: '100%',
- placeholder: translate('measure_filter.criteria.metric')
- });
- this.$('[name=period]').val(value.period || 0).select2({
- width: '100%',
- minimumResultsForSearch: 100
- });
- this.$('[name=op]').val(value.op || 'eq').select2({
- width: '60px',
- placeholder: '=',
- minimumResultsForSearch: 100
- });
- this.updateDataType(value);
- this.$('[name=val]').val(value.val);
- this.inputChanged();
- },
-
- onShow () {
- const select = this.$('[name=metric]');
- if (this.model.get('value').metric === '') {
- select.select2('open');
- } else {
- select.select2('focus');
- }
- }
-
-});
-
-export default BaseFilters.BaseFilterView.extend({
-
- initialize () {
- BaseFilters.BaseFilterView.prototype.initialize.call(this, {
- projectsView: DetailsMetricFilterView
- });
-
- this.groupMetrics();
- },
-
- groupMetrics () {
- const metrics = _.map(this.model.get('metrics'), function (metric) {
- return metric.metric;
- });
- const groupedMetrics =
- _.sortBy(
- _.map(
- _.groupBy(metrics, 'domain'),
- function (metricList, domain) {
- return {
- domain,
- metrics: _.sortBy(metricList, 'short_name')
- };
- }),
- 'domain'
- );
- this.model.set('groupedMetrics', groupedMetrics);
- },
-
- renderValue () {
- return this.isDefaultValue() ?
- translate('measure_filter.criteria.metric.not_set') :
- this.model.get('value').metricText + ' ' + this.model.get('value').opText + ' ' +
- this.model.get('value').valText;
- },
-
- renderInput () {
- const that = this;
- const value = this.model.get('value');
-
- if (_.isObject(value) && value.metric && value.op && (value.val != null)) {
- _.each(['metric', 'period', 'op', 'val'], function (key) {
- let v = value[key];
- if (key === 'period' && v === '0') {
- v = '';
- }
-
- $('<input>')
- .prop('name', that.model.get('property') + '_' + key)
- .prop('type', 'hidden')
- .css('display', 'none')
- .val(v)
- .appendTo(that.$el);
- });
- }
- },
-
- isDefaultValue () {
- const value = this.model.get('value');
- if (!_.isObject(value)) {
- return true;
- }
- return !(value.metric && value.op && (value.val != null));
- },
-
- restoreFromQuery (q) {
- const that = this;
- const value = {};
- _.each(['metric', 'period', 'op', 'val'], function (p) {
- const property = that.model.get('property') + '_' + p;
- const pValue = _.findWhere(q, { key: property });
-
- if (pValue && pValue.value) {
- value[p] = pValue.value;
- }
- });
-
- if (value.metric && value.op && (value.val != null)) {
- this.model.set({
- value,
- enabled: true
- });
- }
- }
-
-});
-
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
deleted file mode 100644
index acf1f0b812a..00000000000
--- a/server/sonar-web/src/main/js/components/navigator/filters/more-criteria-filters.js
+++ /dev/null
@@ -1,107 +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 'jquery';
-import _ from 'underscore';
-import ChoiceFilters from './choice-filters';
-import Template from '../templates/more-criteria-filter.hbs';
-import DetailsTemplate from '../templates/more-criteria-details-filter.hbs';
-
-const DetailsMoreCriteriaFilterView = ChoiceFilters.DetailsChoiceFilterView.extend({
- template: DetailsTemplate,
-
- events: {
- 'click label[data-id]:not(.inactive)': 'enableFilter'
- },
-
- enableById (id) {
- this.model.view.options.filterBarView.enableFilter(id);
- this.model.view.hideDetails();
- },
-
- enableByProperty (property) {
- const filter = _.find(this.model.get('filters'), function (f) {
- return f.get('property') === property;
- });
- if (filter) {
- this.enableById(filter.cid);
- }
- },
-
- enableFilter (e) {
- const id = $(e.target).data('id');
- this.enableById(id);
- this.updateCurrent(0);
- },
-
- selectCurrent () {
- this.$('label').eq(this.currentChoice).click();
- },
-
- serializeData () {
- const filters = this.model.get('filters').map(function (filter) {
- return _.extend(filter.toJSON(), { id: filter.cid });
- });
- const getName = function (filter) {
- return filter.name;
- };
- const uniqueFilters = _.unique(filters, getName);
- const sortedFilters = _.sortBy(uniqueFilters, getName);
- return _.extend(this.model.toJSON(), { filters: sortedFilters });
- }
-
-});
-
-const MoreCriteriaFilterView = ChoiceFilters.ChoiceFilterView.extend({
- template: Template,
- className: 'navigator-filter navigator-filter-more-criteria',
-
- initialize () {
- ChoiceFilters.ChoiceFilterView.prototype.initialize.call(this, {
- projectsView: DetailsMoreCriteriaFilterView
- });
- },
-
- renderValue () {
- return '';
- },
-
- renderInput () {
- },
-
- renderBase () {
- ChoiceFilters.ChoiceFilterView.prototype.renderBase.call(this);
- this.$el.prop('title', '');
- },
-
- isDefaultValue () {
- return false;
- }
-
-});
-
-/*
- * Export public classes
- */
-
-export default {
- DetailsMoreCriteriaFilterView,
- MoreCriteriaFilterView
-};
-
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
deleted file mode 100644
index 3ae434f9e5e..00000000000
--- a/server/sonar-web/src/main/js/components/navigator/filters/range-filters.js
+++ /dev/null
@@ -1,208 +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 'jquery';
-import _ from 'underscore';
-import BaseFilters from './base-filters';
-import Template from '../templates/range-filter.hbs';
-import { translate } from '../../../helpers/l10n';
-
-const DetailsRangeFilterView = BaseFilters.DetailsFilterView.extend({
- template: Template,
-
- events: {
- 'change input': 'change'
- },
-
- change () {
- const value = {};
- const valueFrom = this.$('input').eq(0).val();
- const valueTo = this.$('input').eq(1).val();
-
- if (valueFrom.length > 0) {
- value[this.model.get('propertyFrom')] = valueFrom;
- }
-
- if (valueTo.length > 0) {
- value[this.model.get('propertyTo')] = valueTo;
- }
-
- this.model.set('value', value);
- },
-
- populateInputs () {
- const value = this.model.get('value');
- const propertyFrom = this.model.get('propertyFrom');
- const propertyTo = this.model.get('propertyTo');
- const valueFrom = _.isObject(value) && value[propertyFrom];
- const valueTo = _.isObject(value) && value[propertyTo];
-
- this.$('input').eq(0).val(valueFrom || '');
- this.$('input').eq(1).val(valueTo || '');
- },
-
- onShow () {
- this.$(':input:first').focus();
- }
-
-});
-
-const RangeFilterView = BaseFilters.BaseFilterView.extend({
-
- initialize () {
- BaseFilters.BaseFilterView.prototype.initialize.call(this, {
- projectsView: DetailsRangeFilterView
- });
- },
-
- renderValue () {
- if (!this.isDefaultValue()) {
- const value = _.values(this.model.get('value'));
- return value.join(' — ');
- } else {
- return translate('any');
- }
- },
-
- renderInput () {
- const value = this.model.get('value');
- const propertyFrom = this.model.get('propertyFrom');
- const propertyTo = this.model.get('propertyTo');
- const valueFrom = _.isObject(value) && value[propertyFrom];
- const valueTo = _.isObject(value) && value[propertyTo];
-
- $('<input>')
- .prop('name', propertyFrom)
- .prop('type', 'hidden')
- .css('display', 'none')
- .val(valueFrom || '')
- .appendTo(this.$el);
-
- $('<input>')
- .prop('name', propertyTo)
- .prop('type', 'hidden')
- .css('display', 'none')
- .val(valueTo || '')
- .appendTo(this.$el);
- },
-
- isDefaultValue () {
- const value = this.model.get('value');
- const propertyFrom = this.model.get('propertyFrom');
- const propertyTo = this.model.get('propertyTo');
- const valueFrom = _.isObject(value) && value[propertyFrom];
- const valueTo = _.isObject(value) && value[propertyTo];
-
- return !valueFrom && !valueTo;
- },
-
- restoreFromQuery (q) {
- const paramFrom = _.findWhere(q, { key: this.model.get('propertyFrom') });
- const paramTo = _.findWhere(q, { key: this.model.get('propertyTo') });
- const value = {};
-
- if ((paramFrom && paramFrom.value) || (paramTo && paramTo.value)) {
- if (paramFrom && paramFrom.value) {
- value[this.model.get('propertyFrom')] = paramFrom.value;
- }
-
- if (paramTo && paramTo.value) {
- value[this.model.get('propertyTo')] = paramTo.value;
- }
-
- this.model.set({
- value,
- enabled: true
- });
-
- this.projectsView.populateInputs();
- }
- },
-
- restore (value) {
- if (this.choices && this.selection && value.length > 0) {
- const that = this;
- this.choices.add(this.selection.models);
- this.selection.reset([]);
-
- _.each(value, function (v) {
- const cModel = that.choices.findWhere({ id: v });
-
- if (cModel) {
- that.selection.add(cModel);
- that.choices.remove(cModel);
- }
- });
-
- this.projectsView.updateLists();
-
- this.model.set({
- value,
- enabled: true
- });
- }
- },
-
- formatValue () {
- return this.model.get('value');
- },
-
- clear () {
- this.model.unset('value');
- this.projectsView.render();
- }
-
-});
-
-const DateRangeFilterView = RangeFilterView.extend({
-
- render () {
- RangeFilterView.prototype.render.apply(this, arguments);
- this.projectsView.$('input')
- .prop('placeholder', '1970-01-31')
- .datepicker({
- dateFormat: 'yy-mm-dd',
- changeMonth: true,
- changeYear: true
- })
- .on('change', function () {
- $(this).datepicker('setDate', $(this).val());
- });
- },
-
- renderValue () {
- if (!this.isDefaultValue()) {
- const value = _.values(this.model.get('value'));
- return value.join(' — ');
- } else {
- return translate('anytime');
- }
- }
-
-});
-
-/*
- * Export public classes
- */
-
-export default {
- RangeFilterView,
- DateRangeFilterView
-};
-
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
deleted file mode 100644
index b1ff255083e..00000000000
--- a/server/sonar-web/src/main/js/components/navigator/filters/string-filters.js
+++ /dev/null
@@ -1,87 +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 'jquery';
-import _ from 'underscore';
-import BaseFilters from './base-filters';
-import Template from '../templates/string-filter.hbs';
-
-const DetailsStringFilterView = BaseFilters.DetailsFilterView.extend({
- template: Template,
-
- events: {
- 'change input': 'change'
- },
-
- change (e) {
- this.model.set('value', $(e.target).val());
- },
-
- onShow () {
- BaseFilters.DetailsFilterView.prototype.onShow.apply(this, arguments);
- this.$(':input').focus();
- },
-
- serializeData () {
- return _.extend({}, this.model.toJSON(), {
- value: this.model.get('value') || ''
- });
- }
-
-});
-
-export default BaseFilters.BaseFilterView.extend({
-
- initialize () {
- BaseFilters.BaseFilterView.prototype.initialize.call(this, {
- projectsView: DetailsStringFilterView
- });
- },
-
- renderValue () {
- return this.isDefaultValue() ? '—' : this.model.get('value');
- },
-
- renderInput () {
- $('<input>')
- .prop('name', this.model.get('property'))
- .prop('type', 'hidden')
- .css('display', 'none')
- .val(this.model.get('value') || '')
- .appendTo(this.$el);
- },
-
- isDefaultValue () {
- return !this.model.get('value');
- },
-
- restore (value) {
- this.model.set({
- value,
- enabled: true
- });
- },
-
- clear () {
- this.model.unset('value');
- this.projectsView.render();
- }
-
-});
-
diff --git a/server/sonar-web/src/main/js/components/store/globalMessages.js b/server/sonar-web/src/main/js/components/store/globalMessages.js
index 038c74f0132..e813f6f59b8 100644
--- a/server/sonar-web/src/main/js/components/store/globalMessages.js
+++ b/server/sonar-web/src/main/js/components/store/globalMessages.js
@@ -18,6 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import uniqueId from 'lodash/uniqueId';
+import { actions } from '../../app/store/appState/duck';
export const ERROR = 'ERROR';
export const SUCCESS = 'SUCCESS';
@@ -54,6 +55,24 @@ const globalMessages = (state = [], action = {}) => {
}];
}
+ if (action.type === actions.REQUIRE_AUTHENTICATION) {
+ // FIXME l10n
+ return [{
+ id: uniqueId('global-message-'),
+ message: 'Authentication required to see this page.',
+ level: ERROR
+ }];
+ }
+
+ if (action.type === actions.REQUIRE_AUTHORIZATION) {
+ // FIXME l10n
+ return [{
+ id: uniqueId('global-message-'),
+ message: 'You are not authorized to access this page. Please log in with more privileges and try again.',
+ level: ERROR
+ }];
+ }
+
if (action.type === CLOSE_ALL_GLOBAL_MESSAGES) {
return [];
}
diff --git a/server/sonar-web/src/main/js/components/ui/Avatar.js b/server/sonar-web/src/main/js/components/ui/Avatar.js
index 53e4b90af11..8e96bb380ae 100644
--- a/server/sonar-web/src/main/js/components/ui/Avatar.js
+++ b/server/sonar-web/src/main/js/components/ui/Avatar.js
@@ -18,24 +18,27 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
+import { connect } from 'react-redux';
import md5 from 'blueimp-md5';
import classNames from 'classnames';
+import { getSettingValue } from '../../app/store/rootReducer';
-export default class Avatar extends React.Component {
+class Avatar extends React.Component {
static propTypes = {
+ enableGravatar: React.PropTypes.bool.isRequired,
+ gravatarServerUrl: React.PropTypes.string.isRequired,
email: React.PropTypes.string,
size: React.PropTypes.number.isRequired,
className: React.PropTypes.string
};
render () {
- const shouldShowAvatar = window.SS && window.SS.lf && window.SS.lf.enableGravatar;
- if (!shouldShowAvatar) {
+ if (!this.props.enableGravatar) {
return null;
}
const emailHash = md5.md5((this.props.email || '').toLowerCase()).trim();
- const url = ('' + window.SS.lf.gravatarServerUrl)
+ const url = this.props.gravatarServerUrl
.replace('{EMAIL_MD5}', emailHash)
.replace('{SIZE}', this.props.size * 2);
@@ -50,3 +53,12 @@ export default class Avatar extends React.Component {
);
}
}
+
+const mapStateToProps = state => ({
+ enableGravatar: (getSettingValue(state, 'sonar.lf.enableGravatar') || {}).value === 'true',
+ gravatarServerUrl: (getSettingValue(state, 'sonar.lf.gravatarServerUrl') || {}).value
+});
+
+export default connect(mapStateToProps)(Avatar);
+
+export const unconnectedAvatar = Avatar;
diff --git a/server/sonar-web/src/main/js/components/ui/__tests__/Avatar-test.js b/server/sonar-web/src/main/js/components/ui/__tests__/Avatar-test.js
index 1a5239d0727..a6cd4cf6115 100644
--- a/server/sonar-web/src/main/js/components/ui/__tests__/Avatar-test.js
+++ b/server/sonar-web/src/main/js/components/ui/__tests__/Avatar-test.js
@@ -19,23 +19,14 @@
*/
import { shallow } from 'enzyme';
import React from 'react';
-import Avatar from '../Avatar';
+import { unconnectedAvatar as Avatar } from '../Avatar';
-beforeEach(() => {
- window.SS = {
- lf: {
- enableGravatar: true,
- gravatarServerUrl: 'http://example.com/{EMAIL_MD5}.jpg?s={SIZE}'
- }
- };
-});
-
-afterEach(() => {
- window.SS = undefined;
-});
+const gravatarServerUrl = 'http://example.com/{EMAIL_MD5}.jpg?s={SIZE}';
it('should render', () => {
- const avatar = shallow(<Avatar email="mail@example.com" size={20}/>);
+ const avatar = shallow(
+ <Avatar enableGravatar={true} gravatarServerUrl={gravatarServerUrl} email="mail@example.com" size={20}/>
+ );
expect(avatar.is('img')).toBe(true);
expect(avatar.prop('width')).toBe(20);
expect(avatar.prop('height')).toBe(20);
@@ -44,7 +35,8 @@ it('should render', () => {
});
it('should not render', () => {
- window.SS.lf.enableGravatar = false;
- const avatar = shallow(<Avatar email="mail@example.com" size={20}/>);
+ const avatar = shallow(
+ <Avatar enableGravatar={false} gravatarServerUrl={gravatarServerUrl} email="mail@example.com" size={20}/>
+ );
expect(avatar.is('img')).toBe(false);
});
diff --git a/server/sonar-web/src/main/js/helpers/__tests__/measures-test.js b/server/sonar-web/src/main/js/helpers/__tests__/measures-test.js
index 19aa065750d..438fd223f90 100644
--- a/server/sonar-web/src/main/js/helpers/__tests__/measures-test.js
+++ b/server/sonar-web/src/main/js/helpers/__tests__/measures-test.js
@@ -35,7 +35,6 @@ beforeEach(function () {
'metric.level.WARN': 'Warning',
'metric.level.OK': 'Ok'
});
- window.SS = { hoursInDay: HOURS_IN_DAY };
});
describe('#formatMeasure()', function () {
diff --git a/server/sonar-web/src/main/js/helpers/cookies.js b/server/sonar-web/src/main/js/helpers/cookies.js
index b9b8062df7a..5ec17e2ef32 100644
--- a/server/sonar-web/src/main/js/helpers/cookies.js
+++ b/server/sonar-web/src/main/js/helpers/cookies.js
@@ -17,9 +17,10 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+// @flow
let cookies;
-export function getCookie (name) {
+export function getCookie (name: string) {
if (cookies) {
return cookies[name];
}
diff --git a/server/sonar-web/src/main/js/helpers/csv.js b/server/sonar-web/src/main/js/helpers/csv.js
index 57d372a6d2b..89f883ac93b 100644
--- a/server/sonar-web/src/main/js/helpers/csv.js
+++ b/server/sonar-web/src/main/js/helpers/csv.js
@@ -17,7 +17,8 @@
* 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 csvEscape (value) {
+// @flow
+export function csvEscape (value: string): string {
const escaped = value.replace(/"/g, '\\"');
return `"${escaped}"`;
}
diff --git a/server/sonar-web/src/main/js/helpers/handlebars/avatarHelper.js b/server/sonar-web/src/main/js/helpers/handlebars/avatarHelper.js
index 1e913674f90..c790508b1c0 100644
--- a/server/sonar-web/src/main/js/helpers/handlebars/avatarHelper.js
+++ b/server/sonar-web/src/main/js/helpers/handlebars/avatarHelper.js
@@ -20,10 +20,18 @@
import md5 from 'blueimp-md5';
import Handlebars from 'handlebars/runtime';
+function gravatarServer () {
+ const getStore = require('../../app/utils/getStore').default;
+ const { getSettingValue } = require('../../app/store/rootReducer');
+
+ const store = getStore();
+ return (getSettingValue(store.getState(), 'sonar.lf.gravatarServerUrl') || {}).value;
+}
+
module.exports = function (email, size) {
// double the size for high pixel density screens
const emailHash = md5.md5((email || '').trim());
- const url = ('' + window.SS.lf.gravatarServerUrl)
+ const url = gravatarServer()
.replace('{EMAIL_MD5}', emailHash)
.replace('{SIZE}', size * 2);
return new Handlebars.default.SafeString(
diff --git a/server/sonar-web/src/main/js/helpers/handlebars/ifShowAvatars.js b/server/sonar-web/src/main/js/helpers/handlebars/ifShowAvatars.js
index 0290074792f..87f038f1fa6 100644
--- a/server/sonar-web/src/main/js/helpers/handlebars/ifShowAvatars.js
+++ b/server/sonar-web/src/main/js/helpers/handlebars/ifShowAvatars.js
@@ -17,7 +17,14 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+function enableGravatar () {
+ const getStore = require('../../app/utils/getStore').default;
+ const { getSettingValue } = require('../../app/store/rootReducer');
+
+ const store = getStore();
+ return (getSettingValue(store.getState(), 'sonar.lf.enableGravatar') || {}).value === 'true';
+}
+
module.exports = function (options) {
- const cond = window.SS && window.SS.lf && window.SS.lf.enableGravatar;
- return cond ? options.fn(this) : options.inverse(this);
+ return enableGravatar() ? options.fn(this) : options.inverse(this);
};
diff --git a/server/sonar-web/src/main/js/helpers/l10n.js b/server/sonar-web/src/main/js/helpers/l10n.js
index 6b8f85ef4fc..d77ff416682 100644
--- a/server/sonar-web/src/main/js/helpers/l10n.js
+++ b/server/sonar-web/src/main/js/helpers/l10n.js
@@ -17,7 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
- /* @flow */
+/* @flow */
import moment from 'moment';
import { request } from './request';
@@ -63,7 +63,9 @@ function makeRequest (params) {
case 401:
window.location = window.baseUrl + '/sessions/new?return_to=' +
encodeURIComponent(window.location.pathname + window.location.search + window.location.hash);
- return {};
+ // return unresolved promise to stop the promise chain
+ // anyway the page will be reloaded
+ return new Promise(() => {});
default:
throw new Error('Unexpected status code: ' + response.status);
}
diff --git a/server/sonar-web/src/main/js/helpers/measures.js b/server/sonar-web/src/main/js/helpers/measures.js
index 14cbd630c3a..6a7cd4a2d0e 100644
--- a/server/sonar-web/src/main/js/helpers/measures.js
+++ b/server/sonar-web/src/main/js/helpers/measures.js
@@ -302,11 +302,21 @@ function formatDurationShort (isNegative, days, hours, minutes) {
return translateWithParameters('work_duration.x_minutes', formattedMinutes);
}
+function getHoursInDay () {
+ // workaround cyclic dependencies
+ const getStore = require('../app/utils/getStore').default;
+ const { getSettingValue } = require('../app/store/rootReducer');
+
+ const store = getStore();
+ const settingValue = getSettingValue(store.getState(), 'sonar.technicalDebt.hoursInDay');
+ return settingValue ? settingValue.value : 8;
+}
+
function durationFormatter (value) {
if (value === 0 || value === '0') {
return '0';
}
- const hoursInDay = window.SS.hoursInDay;
+ const hoursInDay = getHoursInDay();
const isNegative = value < 0;
const absValue = Math.abs(value);
const days = Math.floor(absValue / hoursInDay / 60);
@@ -321,7 +331,7 @@ function shortDurationFormatter (value) {
if (value === 0 || value === '0') {
return '0';
}
- const hoursInDay = window.SS.hoursInDay;
+ const hoursInDay = getHoursInDay();
const isNegative = value < 0;
const absValue = Math.abs(value);
const days = absValue / hoursInDay / 60;
@@ -347,13 +357,23 @@ function shortDurationVariationFormatter (value) {
return formatted[0] !== '-' ? '+' + formatted : formatted;
}
+function getRatingGrid () {
+ // workaround cyclic dependencies
+ const getStore = require('../app/utils/getStore').default;
+ const { getSettingValue } = require('../app/store/rootReducer');
+
+ const store = getStore();
+ const settingValue = getSettingValue(store.getState(), 'sonar.technicalDebt.ratingGrid');
+ return settingValue ? settingValue.value : '';
+}
+
let maintainabilityRatingGrid;
function getMaintainabilityRatingGrid () {
if (maintainabilityRatingGrid) {
return maintainabilityRatingGrid;
}
- const str = window.SS['sonar.technicalDebt.ratingGrid'];
+ const str = getRatingGrid();
const numbers = str.split(',')
.map(s => parseFloat(s))
.filter(n => !isNaN(n));
diff --git a/server/sonar-web/src/main/js/helpers/request.js b/server/sonar-web/src/main/js/helpers/request.js
index 349392af366..b58d1dda053 100644
--- a/server/sonar-web/src/main/js/helpers/request.js
+++ b/server/sonar-web/src/main/js/helpers/request.js
@@ -17,14 +17,20 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+// @flow
import { stringify } from 'querystring';
import { getCookie } from './cookies';
-export function getCSRFTokenName () {
+type Response = {
+ json: () => Promise<Object>,
+ status: number
+};
+
+export function getCSRFTokenName (): string {
return 'X-XSRF-TOKEN';
}
-export function getCSRFTokenValue () {
+export function getCSRFTokenValue (): string {
const cookieName = 'XSRF-TOKEN';
const cookieValue = getCookie(cookieName);
if (!cookieValue) {
@@ -37,7 +43,7 @@ export function getCSRFTokenValue () {
* Return an object containing a special http request header used to prevent CSRF attacks.
* @returns {Object}
*/
-export function getCSRFToken () {
+export function getCSRFToken (): Object {
// Fetch API in Edge doesn't work with empty header,
// so we ensure non-empty value
const value = getCSRFTokenValue();
@@ -47,7 +53,10 @@ export function getCSRFToken () {
/**
* Default options for any request
*/
-const DEFAULT_OPTIONS = {
+const DEFAULT_OPTIONS: {
+ credentials: string,
+ method: string
+} = {
method: 'GET',
credentials: 'same-origin'
};
@@ -55,7 +64,9 @@ const DEFAULT_OPTIONS = {
/**
* Default request headers
*/
-const DEFAULT_HEADERS = {
+const DEFAULT_HEADERS: {
+ 'Accept': string
+} = {
'Accept': 'application/json'
};
@@ -63,14 +74,21 @@ const DEFAULT_HEADERS = {
* Request
*/
class Request {
- constructor (url) {
+ url: string;
+ options: {
+ method?: string
+ };
+ headers: Object;
+ data: ?Object;
+
+ constructor (url: string): void {
this.url = url;
this.options = {};
this.headers = {};
}
submit () {
- let url = this.url;
+ let url: string = this.url;
const options = { ...DEFAULT_OPTIONS, ...this.options };
const customHeaders = {};
@@ -82,6 +100,7 @@ class Request {
url += '?' + stringify(this.data);
} else {
customHeaders['Content-Type'] = 'application/x-www-form-urlencoded';
+ // $FlowFixMe complains that `data` is nullable
options.body = stringify(this.data);
}
}
@@ -96,17 +115,17 @@ class Request {
return window.fetch(window.baseUrl + url, options);
}
- setMethod (method) {
+ setMethod (method: string): Request {
this.options.method = method;
return this;
}
- setData (data) {
+ setData (data?: Object): Request {
this.data = data;
return this;
}
- setHeader (name, value) {
+ setHeader (name: string, value: string): Request {
this.headers[name] = value;
return this;
}
@@ -117,7 +136,7 @@ class Request {
* @param {string} url
* @returns {Request}
*/
-export function request (url) {
+export function request (url: string): Request {
return new Request(url);
}
@@ -126,15 +145,17 @@ export function request (url) {
* @param response
* @returns {*}
*/
-export function checkStatus (response) {
+export function checkStatus (response: Response): Promise<Object> {
if (response.status === 401) {
- window.location = window.baseUrl + '/sessions/new?return_to=' +
- encodeURIComponent(window.location.pathname + window.location.search + window.location.hash);
- return {};
+ // workaround cyclic dependencies
+ const handleRequiredAuthentication = require('../app/utils/handleRequiredAuthentication').default;
+ handleRequiredAuthentication();
+ return Promise.reject();
} else if (response.status >= 200 && response.status < 300) {
- return response;
+ return Promise.resolve(response);
} else {
const error = new Error(response.status);
+ // $FlowFixMe complains that `response` is not found
error.response = response;
throw error;
}
@@ -145,7 +166,7 @@ export function checkStatus (response) {
* @param response
* @returns {object}
*/
-export function parseJSON (response) {
+export function parseJSON (response: Response): Promise<Object> {
return response.json();
}
@@ -154,7 +175,7 @@ export function parseJSON (response) {
* @param url
* @param data
*/
-export function getJSON (url, data) {
+export function getJSON (url: string, data?: Object): Promise<Object> {
return request(url)
.setData(data)
.submit()
@@ -167,7 +188,7 @@ export function getJSON (url, data) {
* @param url
* @param data
*/
-export function postJSON (url, data) {
+export function postJSON (url: string, data?: Object): Promise<Object> {
return request(url)
.setMethod('POST')
.setData(data)
@@ -181,7 +202,7 @@ export function postJSON (url, data) {
* @param url
* @param data
*/
-export function post (url, data) {
+export function post (url: string, data?: Object): Promise<Object> {
return request(url)
.setMethod('POST')
.setData(data)
@@ -194,7 +215,7 @@ export function post (url, data) {
* @param url
* @param data
*/
-export function requestDelete (url, data) {
+export function requestDelete (url: string, data?: Object): Promise<Object> {
return request(url)
.setMethod('DELETE')
.setData(data)
@@ -207,6 +228,6 @@ export function requestDelete (url, data) {
* @param response
* @returns {Promise}
*/
-export function delay (response) {
+export function delay (response: any): Promise<any> {
return new Promise(resolve => setTimeout(() => resolve(response), 1200));
}
diff --git a/server/sonar-web/src/main/js/helpers/handlebars/ifCanUseFilter.js b/server/sonar-web/src/main/js/helpers/users.js
index ee8ed1691ad..2c116694d52 100644
--- a/server/sonar-web/src/main/js/helpers/handlebars/ifCanUseFilter.js
+++ b/server/sonar-web/src/main/js/helpers/users.js
@@ -17,7 +17,13 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-module.exports = function (query, options) {
- const cond = window.SS.user || query.indexOf('__me__') === -1;
- return cond ? options.fn(this) : options.inverse(this);
+// @flow
+type User = {
+ permissions: {
+ global: Array<string>
+ }
};
+
+export const isUserAdmin = (user: User): boolean => (
+ user.permissions.global.includes('admin')
+);
diff --git a/server/sonar-web/src/main/js/libs/sonar.js b/server/sonar-web/src/main/js/libs/sonar.js
deleted file mode 100644
index 366925e71fd..00000000000
--- a/server/sonar-web/src/main/js/libs/sonar.js
+++ /dev/null
@@ -1,50 +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.
- */
-require('script!./third-party/jquery-ui.js');
-require('script!./third-party/select2.js');
-require('script!./third-party/keymaster.js');
-require('script!./third-party/bootstrap/tooltip.js');
-require('script!./third-party/bootstrap/dropdown.js');
-require('script!./select2-jquery-ui-fix.js');
-require('script!./inputs.js');
-require('script!./jquery-isolated-scroll.js');
-require('script!./application.js');
-var request = require('../helpers/request');
-
-window.$j = jQuery.noConflict();
-
-jQuery(function () {
- jQuery('.open-modal').modal();
-});
-
-jQuery.ajaxSetup({
- beforeSend: function (jqXHR) {
- jqXHR.setRequestHeader(request.getCSRFTokenName(), request.getCSRFTokenValue());
- },
- statusCode: {
- 401: function () {
- window.location = window.baseUrl + '/sessions/new?return_to=' +
- encodeURIComponent(window.location.pathname + window.location.search + window.location.hash);
- }
- }
-});
-
-window.sonarqube = {};
-window.sonarqube.el = '#content';
diff --git a/server/sonar-web/src/main/less/components/page.less b/server/sonar-web/src/main/less/components/page.less
index d6e25d68ac7..370464a0d77 100644
--- a/server/sonar-web/src/main/less/components/page.less
+++ b/server/sonar-web/src/main/less/components/page.less
@@ -21,7 +21,7 @@
@import (reference) "../mixins";
@import (reference) "../init/links";
-body {
+.global-container {
display: flex;
flex-direction: column;
height: 100%;