aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStas Vilchik <vilchiks@gmail.com>2016-12-23 16:19:54 +0100
committerGitHub <noreply@github.com>2016-12-23 16:19:54 +0100
commited2fff193eca9e5407e42a32e54eef02daca0fb7 (patch)
tree29093fd442888ffa5446dba6142ab93d3290ab7a
parentcc65eb695394d533c0124c53742a5bb36f948a70 (diff)
downloadsonarqube-ed2fff193eca9e5407e42a32e54eef02daca0fb7.tar.gz
sonarqube-ed2fff193eca9e5407e42a32e54eef02daca0fb7.zip
SONAR-8554 Load and display page extensions (#1482)
-rw-r--r--it/it-plugins/ui-extensions-plugin/src/main/java/GlobalPage.java2
-rw-r--r--it/it-plugins/ui-extensions-plugin/src/main/java/ProjectPage.java2
-rw-r--r--it/it-plugins/ui-extensions-plugin/src/main/java/ResourceConfigurationPage.java2
-rw-r--r--it/it-plugins/ui-extensions-plugin/src/main/java/SettingsPage.java2
-rw-r--r--it/it-plugins/ui-extensions-plugin/src/main/resources/static/global_page.js4
-rw-r--r--it/it-plugins/ui-extensions-plugin/src/main/resources/static/project_page.js4
-rw-r--r--it/it-plugins/ui-extensions-plugin/src/main/resources/static/resource_configuration_sample.js4
-rw-r--r--it/it-plugins/ui-extensions-plugin/src/main/resources/static/settings_page.js4
-rw-r--r--server/sonar-web/.eslintrc1
-rw-r--r--server/sonar-web/src/main/js/app/components/AdminContainer.js26
-rw-r--r--server/sonar-web/src/main/js/app/components/ProjectAdminContainer.js66
-rw-r--r--server/sonar-web/src/main/js/app/components/extensions/Extension.js98
-rw-r--r--server/sonar-web/src/main/js/app/components/extensions/ExtensionNotFound.js44
-rw-r--r--server/sonar-web/src/main/js/app/components/extensions/GlobalAdminPageExtension.js51
-rw-r--r--server/sonar-web/src/main/js/app/components/extensions/GlobalPageExtension.js51
-rw-r--r--server/sonar-web/src/main/js/app/components/extensions/ProjectAdminPageExtension.js63
-rw-r--r--server/sonar-web/src/main/js/app/components/extensions/ProjectPageExtension.js60
-rw-r--r--server/sonar-web/src/main/js/app/components/extensions/ViewDashboard.js32
-rw-r--r--server/sonar-web/src/main/js/app/components/extensions/utils.js48
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.js18
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.js30
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMenu-test.js.snap2
-rw-r--r--server/sonar-web/src/main/js/app/index.js2
-rw-r--r--server/sonar-web/src/main/js/app/utils/installExtensionsHandler.js33
-rw-r--r--server/sonar-web/src/main/js/app/utils/startReactApp.js17
-rw-r--r--server/sonar-web/src/main/js/apps/overview/components/App.js36
-rw-r--r--server/sonar-web/src/main/js/apps/overview/components/__tests__/App-test.js8
-rw-r--r--server/sonar-web/src/main/js/apps/overview/meta/Meta.js4
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.js1
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileDetails.js1
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/inputs/Input.js1
-rw-r--r--server/sonar-web/src/main/js/apps/settings/store/values/reducer.js3
-rw-r--r--server/sonar-web/src/main/js/components/controls/DateInput.js1
-rw-r--r--server/sonar-web/src/main/js/components/select-list/list.js1
-rw-r--r--server/sonar-web/src/main/js/components/select-list/main.js1
-rw-r--r--server/sonar-web/src/main/js/store/appState/duck.js32
-rw-r--r--server/sonar-web/src/main/js/store/globalMessages/duck.js3
-rw-r--r--sonar-core/src/main/resources/org/sonar/l10n/core.properties1
38 files changed, 677 insertions, 82 deletions
diff --git a/it/it-plugins/ui-extensions-plugin/src/main/java/GlobalPage.java b/it/it-plugins/ui-extensions-plugin/src/main/java/GlobalPage.java
index d243da0828d..2dcbd264181 100644
--- a/it/it-plugins/ui-extensions-plugin/src/main/java/GlobalPage.java
+++ b/it/it-plugins/ui-extensions-plugin/src/main/java/GlobalPage.java
@@ -24,7 +24,7 @@ import org.sonar.api.web.Page;
public class GlobalPage implements Page {
public String getId() {
- return "global_page";
+ return "uiextensionsplugin/global_page";
}
public String getTitle() {
diff --git a/it/it-plugins/ui-extensions-plugin/src/main/java/ProjectPage.java b/it/it-plugins/ui-extensions-plugin/src/main/java/ProjectPage.java
index db08565862a..66da8e6ad33 100644
--- a/it/it-plugins/ui-extensions-plugin/src/main/java/ProjectPage.java
+++ b/it/it-plugins/ui-extensions-plugin/src/main/java/ProjectPage.java
@@ -27,7 +27,7 @@ import org.sonar.api.web.ResourceQualifier;
public class ProjectPage implements Page {
public String getId() {
- return "/project_page";
+ return "uiextensionsplugin/project_page";
}
public String getTitle() {
diff --git a/it/it-plugins/ui-extensions-plugin/src/main/java/ResourceConfigurationPage.java b/it/it-plugins/ui-extensions-plugin/src/main/java/ResourceConfigurationPage.java
index b804359642d..953b1f9df15 100644
--- a/it/it-plugins/ui-extensions-plugin/src/main/java/ResourceConfigurationPage.java
+++ b/it/it-plugins/ui-extensions-plugin/src/main/java/ResourceConfigurationPage.java
@@ -29,7 +29,7 @@ import org.sonar.api.web.UserRole;
public class ResourceConfigurationPage implements Page {
public String getId() {
- return "/resource_configuration_sample";
+ return "uiextensionsplugin/resource_configuration_sample";
}
public String getTitle() {
diff --git a/it/it-plugins/ui-extensions-plugin/src/main/java/SettingsPage.java b/it/it-plugins/ui-extensions-plugin/src/main/java/SettingsPage.java
index 9c3fd26c990..c5b801a6c90 100644
--- a/it/it-plugins/ui-extensions-plugin/src/main/java/SettingsPage.java
+++ b/it/it-plugins/ui-extensions-plugin/src/main/java/SettingsPage.java
@@ -27,7 +27,7 @@ import org.sonar.api.web.UserRole;
public class SettingsPage implements Page {
public String getId() {
- return "settings_page";
+ return "uiextensionsplugin/settings_page";
}
public String getTitle() {
diff --git a/it/it-plugins/ui-extensions-plugin/src/main/resources/static/global_page.js b/it/it-plugins/ui-extensions-plugin/src/main/resources/static/global_page.js
new file mode 100644
index 00000000000..02369e084e1
--- /dev/null
+++ b/it/it-plugins/ui-extensions-plugin/src/main/resources/static/global_page.js
@@ -0,0 +1,4 @@
+window.registerExtension('uiextensionsplugin/global_page', function (options) {
+ options.el.textContent = 'uiextensionsplugin/global_page';
+ return function () {};
+}); \ No newline at end of file
diff --git a/it/it-plugins/ui-extensions-plugin/src/main/resources/static/project_page.js b/it/it-plugins/ui-extensions-plugin/src/main/resources/static/project_page.js
new file mode 100644
index 00000000000..1e28dace449
--- /dev/null
+++ b/it/it-plugins/ui-extensions-plugin/src/main/resources/static/project_page.js
@@ -0,0 +1,4 @@
+window.registerExtension('uiextensionsplugin/project_page', function (options) {
+ options.el.textContent = 'uiextensionsplugin/project_page';
+ return function () {};
+}); \ No newline at end of file
diff --git a/it/it-plugins/ui-extensions-plugin/src/main/resources/static/resource_configuration_sample.js b/it/it-plugins/ui-extensions-plugin/src/main/resources/static/resource_configuration_sample.js
new file mode 100644
index 00000000000..6d84892378a
--- /dev/null
+++ b/it/it-plugins/ui-extensions-plugin/src/main/resources/static/resource_configuration_sample.js
@@ -0,0 +1,4 @@
+window.registerExtension('uiextensionsplugin/resource_configuration_sample', function (options) {
+ options.el.textContent = 'uiextensionsplugin/resource_configuration_sample';
+ return function () {};
+}); \ No newline at end of file
diff --git a/it/it-plugins/ui-extensions-plugin/src/main/resources/static/settings_page.js b/it/it-plugins/ui-extensions-plugin/src/main/resources/static/settings_page.js
new file mode 100644
index 00000000000..d3a69f44aeb
--- /dev/null
+++ b/it/it-plugins/ui-extensions-plugin/src/main/resources/static/settings_page.js
@@ -0,0 +1,4 @@
+window.registerExtension('uiextensionsplugin/settings_page', function (options) {
+ options.el.textContent = 'uiextensionsplugin/settings_page';
+ return function () {};
+}); \ No newline at end of file
diff --git a/server/sonar-web/.eslintrc b/server/sonar-web/.eslintrc
index df5d5291501..461c8bbc32e 100644
--- a/server/sonar-web/.eslintrc
+++ b/server/sonar-web/.eslintrc
@@ -160,7 +160,6 @@
"react/no-render-return-value": 2,
"react/no-unescaped-entities": 2,
"react/no-unknown-property": 2,
- "react/no-unused-prop-types": 2,
"react/react-in-jsx-scope": 2,
"react/require-render-return": 2,
"react/self-closing-comp": 2
diff --git a/server/sonar-web/src/main/js/app/components/AdminContainer.js b/server/sonar-web/src/main/js/app/components/AdminContainer.js
index 26da1f1be44..0b85f97fb71 100644
--- a/server/sonar-web/src/main/js/app/components/AdminContainer.js
+++ b/server/sonar-web/src/main/js/app/components/AdminContainer.js
@@ -20,48 +20,37 @@
import React from 'react';
import { connect } from 'react-redux';
import SettingsNav from './nav/settings/SettingsNav';
-import { getCurrentUser } from '../../store/rootReducer';
+import { getCurrentUser, getAppState } from '../../store/rootReducer';
import { isUserAdmin } from '../../helpers/users';
import { onFail } from '../../store/rootActions';
import { getSettingsNavigation } from '../../api/nav';
+import { setAdminPages } from '../../store/appState/duck';
class AdminContainer extends React.Component {
- state = {
- loading: true
- };
-
componentDidMount () {
if (!isUserAdmin(this.props.currentUser)) {
// workaround cyclic dependencies
const handleRequiredAuthorization = require('../utils/handleRequiredAuthorization').default;
handleRequiredAuthorization();
}
-
- this.mounted = true;
this.loadData();
}
- componentWillUnmount () {
- this.mounted = false;
- }
-
loadData () {
getSettingsNavigation().then(
- r => this.setState({ extensions: r.extensions, loading: false }),
+ r => this.props.setAdminPages(r.extensions),
onFail(this.props.dispatch)
);
}
render () {
- if (!isUserAdmin(this.props.currentUser) || this.state.loading) {
+ if (!isUserAdmin(this.props.currentUser) || !this.props.adminPages) {
return null;
}
return (
<div>
- <SettingsNav
- location={this.props.location}
- extensions={this.state.extensions}/>
+ <SettingsNav location={this.props.location} extensions={this.props.adminPages}/>
{this.props.children}
</div>
);
@@ -69,7 +58,10 @@ class AdminContainer extends React.Component {
}
const mapStateToProps = state => ({
+ adminPages: getAppState(state).adminPages,
currentUser: getCurrentUser(state)
});
-export default connect(mapStateToProps)(AdminContainer);
+const mapDispatchToProps = { setAdminPages };
+
+export default connect(mapStateToProps, mapDispatchToProps)(AdminContainer);
diff --git a/server/sonar-web/src/main/js/app/components/ProjectAdminContainer.js b/server/sonar-web/src/main/js/app/components/ProjectAdminContainer.js
new file mode 100644
index 00000000000..5ffef1f5908
--- /dev/null
+++ b/server/sonar-web/src/main/js/app/components/ProjectAdminContainer.js
@@ -0,0 +1,66 @@
+/*
+ * 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 { getComponent } from '../../store/rootReducer';
+import handleRequiredAuthorization from '../utils/handleRequiredAuthorization';
+
+class ProjectAdminContainer extends React.Component {
+ props: {
+ project: {
+ configuration?: {
+ showSettings: boolean
+ }
+ }
+ };
+
+ componentDidMount () {
+ this.checkPermissions();
+ }
+
+ componentDidUpdate () {
+ this.checkPermissions();
+ }
+
+ isProjectAdmin () {
+ const { configuration } = this.props.project;
+ return configuration != null && configuration.showSettings;
+ }
+
+ checkPermissions () {
+ if (!this.isProjectAdmin()) {
+ handleRequiredAuthorization();
+ }
+ }
+
+ render () {
+ if (!this.isProjectAdmin()) {
+ return null;
+ }
+
+ return this.props.children;
+ }
+}
+
+const mapStateToProps = (state, ownProps) => ({
+ project: getComponent(state, ownProps.location.query.id)
+});
+
+export default connect(mapStateToProps)(ProjectAdminContainer);
diff --git a/server/sonar-web/src/main/js/app/components/extensions/Extension.js b/server/sonar-web/src/main/js/app/components/extensions/Extension.js
new file mode 100644
index 00000000000..5b642937860
--- /dev/null
+++ b/server/sonar-web/src/main/js/app/components/extensions/Extension.js
@@ -0,0 +1,98 @@
+/*
+ * 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 { withRouter } from 'react-router';
+import { addGlobalErrorMessage } from '../../../store/globalMessages/duck';
+import { getCurrentUser } from '../../../store/rootReducer';
+import { translate } from '../../../helpers/l10n';
+import { getExtensionStart } from './utils';
+
+type Props = {
+ currentUser: Object,
+ extension: {
+ id: string,
+ title: string
+ },
+ onFail: (string) => void,
+ options?: {},
+ router: Object
+};
+
+class Extension extends React.Component {
+ container: Object;
+ props: Props;
+ stop: ?Function;
+
+ componentDidMount () {
+ this.startExtension();
+ }
+
+ componentDidUpdate (prevProps: Props) {
+ if (prevProps.extension !== this.props.extension) {
+ this.stopExtension();
+ this.startExtension();
+ }
+ }
+
+ componentWillUnmount () {
+ this.stopExtension();
+ }
+
+ handleStart = (start: Function) => {
+ this.stop = start({
+ el: this.container,
+ currentUser: this.props.currentUser,
+ router: this.props.router,
+ ...this.props.options
+ });
+ };
+
+ handleFailure = () => {
+ this.props.onFail(translate('page_extension_failed'));
+ };
+
+ startExtension () {
+ const { extension } = this.props;
+ getExtensionStart(extension.id).then(this.handleStart, this.handleFailure);
+ }
+
+ stopExtension () {
+ this.stop && this.stop();
+ this.stop = null;
+ }
+
+ render () {
+ return (
+ <div>
+ <div ref={container => this.container = container}/>
+ </div>
+ );
+ }
+}
+
+const mapStateToProps = state => ({
+ currentUser: getCurrentUser(state)
+});
+
+const mapDispatchToProps = { onFail: addGlobalErrorMessage };
+
+export default connect(mapStateToProps, mapDispatchToProps)(withRouter(Extension));
diff --git a/server/sonar-web/src/main/js/app/components/extensions/ExtensionNotFound.js b/server/sonar-web/src/main/js/app/components/extensions/ExtensionNotFound.js
new file mode 100644
index 00000000000..e2311b1fde4
--- /dev/null
+++ b/server/sonar-web/src/main/js/app/components/extensions/ExtensionNotFound.js
@@ -0,0 +1,44 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+// @flow
+import React from 'react';
+import { Link } from 'react-router';
+
+export default class ExtensionNotFound extends React.Component {
+ componentDidMount () {
+ document.querySelector('html').classList.add('dashboard-page');
+ }
+
+ componentWillUnmount () {
+ document.querySelector('html').classList.remove('dashboard-page');
+ }
+
+ render () {
+ return (
+ <div id="bd" className="page-wrapper-simple">
+ <div id="nonav" className="page-simple">
+ <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><Link to="/">Go back to the homepage</Link></p>
+ </div>
+ </div>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/app/components/extensions/GlobalAdminPageExtension.js b/server/sonar-web/src/main/js/app/components/extensions/GlobalAdminPageExtension.js
new file mode 100644
index 00000000000..e79e25e4052
--- /dev/null
+++ b/server/sonar-web/src/main/js/app/components/extensions/GlobalAdminPageExtension.js
@@ -0,0 +1,51 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+// @flow
+import React from 'react';
+import { connect } from 'react-redux';
+import Extension from './Extension';
+import ExtensionNotFound from './ExtensionNotFound';
+import { getAppState } from '../../../store/rootReducer';
+
+class GlobalAdminPageExtension extends React.Component {
+ props: {
+ adminPages: Array<{ id: string }>,
+ params: {
+ extensionKey: string,
+ pluginKey: string
+ }
+ }
+
+ render () {
+ const { extensionKey, pluginKey } = this.props.params;
+ const extension = this.props.adminPages.find(p => p.id === `${pluginKey}/${extensionKey}`);
+ return extension ? (
+ <Extension extension={extension}/>
+ ) : (
+ <ExtensionNotFound/>
+ );
+ }
+}
+
+const mapStateToProps = state => ({
+ adminPages: getAppState(state).adminPages
+});
+
+export default connect(mapStateToProps)(GlobalAdminPageExtension);
diff --git a/server/sonar-web/src/main/js/app/components/extensions/GlobalPageExtension.js b/server/sonar-web/src/main/js/app/components/extensions/GlobalPageExtension.js
new file mode 100644
index 00000000000..22e4eeeca8e
--- /dev/null
+++ b/server/sonar-web/src/main/js/app/components/extensions/GlobalPageExtension.js
@@ -0,0 +1,51 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+// @flow
+import React from 'react';
+import { connect } from 'react-redux';
+import Extension from './Extension';
+import ExtensionNotFound from './ExtensionNotFound';
+import { getAppState } from '../../../store/rootReducer';
+
+class GlobalPageExtension extends React.Component {
+ props: {
+ globalPages: Array<{ id: string }>,
+ params: {
+ extensionKey: string,
+ pluginKey: string
+ }
+ }
+
+ render () {
+ const { extensionKey, pluginKey } = this.props.params;
+ const extension = this.props.globalPages.find(p => p.id === `${pluginKey}/${extensionKey}`);
+ return extension ? (
+ <Extension extension={extension}/>
+ ) : (
+ <ExtensionNotFound/>
+ );
+ }
+}
+
+const mapStateToProps = state => ({
+ globalPages: getAppState(state).globalPages
+});
+
+export default connect(mapStateToProps)(GlobalPageExtension);
diff --git a/server/sonar-web/src/main/js/app/components/extensions/ProjectAdminPageExtension.js b/server/sonar-web/src/main/js/app/components/extensions/ProjectAdminPageExtension.js
new file mode 100644
index 00000000000..7c9f36b73f4
--- /dev/null
+++ b/server/sonar-web/src/main/js/app/components/extensions/ProjectAdminPageExtension.js
@@ -0,0 +1,63 @@
+/*
+ * 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 Extension from './Extension';
+import ExtensionNotFound from './ExtensionNotFound';
+import { getComponent } from '../../../store/rootReducer';
+import { addGlobalErrorMessage } from '../../../store/globalMessages/duck';
+
+type Props = {
+ component: {
+ configuration?: {
+ extensions: Array<{ id: string }>
+ }
+ },
+ location: { query: { id: string } },
+ params: {
+ extensionKey: string,
+ pluginKey: string
+ }
+};
+
+class ProjectAdminPageExtension extends React.Component {
+ props: Props;
+
+ render () {
+ const { extensionKey, pluginKey } = this.props.params;
+ const { component } = this.props;
+ const extension = component.configuration &&
+ component.configuration.extensions.find(p => p.id === `${pluginKey}/${extensionKey}`);
+ return extension ? (
+ <Extension extension={extension} options={{ component }}/>
+ ) : (
+ <ExtensionNotFound/>
+ );
+ }
+}
+
+const mapStateToProps = (state, ownProps: Props) => ({
+ component: getComponent(state, ownProps.location.query.id)
+});
+
+const mapDispatchToProps = { onFail: addGlobalErrorMessage };
+
+export default connect(mapStateToProps, mapDispatchToProps)(ProjectAdminPageExtension);
diff --git a/server/sonar-web/src/main/js/app/components/extensions/ProjectPageExtension.js b/server/sonar-web/src/main/js/app/components/extensions/ProjectPageExtension.js
new file mode 100644
index 00000000000..c249b94b1c7
--- /dev/null
+++ b/server/sonar-web/src/main/js/app/components/extensions/ProjectPageExtension.js
@@ -0,0 +1,60 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+// @flow
+import React from 'react';
+import { connect } from 'react-redux';
+import Extension from './Extension';
+import ExtensionNotFound from './ExtensionNotFound';
+import { getComponent } from '../../../store/rootReducer';
+import { addGlobalErrorMessage } from '../../../store/globalMessages/duck';
+
+type Props = {
+ component: {
+ extensions: Array<{ id: string }>
+ },
+ location: { query: { id: string } },
+ params: {
+ extensionKey: string,
+ pluginKey: string
+ }
+};
+
+class ProjectPageExtension extends React.Component {
+ props: Props;
+
+ render () {
+ const { extensionKey, pluginKey } = this.props.params;
+ const { component } = this.props;
+ const extension = component.extensions.find(p => p.id === `${pluginKey}/${extensionKey}`);
+ return extension ? (
+ <Extension extension={extension} options={{ component }}/>
+ ) : (
+ <ExtensionNotFound/>
+ );
+ }
+}
+
+const mapStateToProps = (state, ownProps: Props) => ({
+ component: getComponent(state, ownProps.location.query.id)
+});
+
+const mapDispatchToProps = { onFail: addGlobalErrorMessage };
+
+export default connect(mapStateToProps, mapDispatchToProps)(ProjectPageExtension);
diff --git a/server/sonar-web/src/main/js/app/components/extensions/ViewDashboard.js b/server/sonar-web/src/main/js/app/components/extensions/ViewDashboard.js
new file mode 100644
index 00000000000..8b57e56f570
--- /dev/null
+++ b/server/sonar-web/src/main/js/app/components/extensions/ViewDashboard.js
@@ -0,0 +1,32 @@
+/*
+ * 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 ProjectPageExtension from './ProjectPageExtension';
+
+export default class ViewDashboard extends React.Component {
+ render () {
+ return (
+ <ProjectPageExtension
+ location={this.props.location}
+ params={{ pluginKey: 'governance', extensionKey: 'governance' }}/>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/app/components/extensions/utils.js b/server/sonar-web/src/main/js/app/components/extensions/utils.js
new file mode 100644
index 00000000000..b9500c38073
--- /dev/null
+++ b/server/sonar-web/src/main/js/app/components/extensions/utils.js
@@ -0,0 +1,48 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+// @flow
+import { getExtensionFromCache } from '../../utils/installExtensionsHandler';
+
+const installScript = (key: string) => {
+ return new Promise(resolve => {
+ const scriptTag = document.createElement('script');
+ scriptTag.src = `${window.baseUrl}/static/${key}.js`;
+ scriptTag.onload = resolve;
+ document.getElementsByTagName('body')[0].appendChild(scriptTag);
+ });
+};
+
+export const getExtensionStart = (key: string) => (
+ new Promise((resolve, reject) => {
+ const fromCache = getExtensionFromCache(key);
+ if (fromCache) {
+ return resolve(fromCache);
+ }
+
+ installScript(key).then(() => {
+ const start = getExtensionFromCache(key);
+ if (start) {
+ resolve(start);
+ } else {
+ reject();
+ }
+ });
+ })
+);
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.js b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.js
index c2dfa20660c..73ceb41f446 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.js
+++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.js
@@ -30,17 +30,25 @@ import './ComponentNav.css';
export default React.createClass({
componentDidMount () {
+ this.mounted = true;
+
this.loadStatus();
this.populateRecentHistory();
},
+ componentWillUnmount () {
+ this.mounted = false;
+ },
+
loadStatus () {
getTasksForComponent(this.props.component.id).then(r => {
- this.setState({
- isPending: r.queue.some(task => task.status === STATUSES.PENDING),
- isInProgress: r.queue.some(task => task.status === STATUSES.IN_PROGRESS),
- isFailed: r.current && r.current.status === STATUSES.FAILED
- });
+ if (this.mounted) {
+ this.setState({
+ isPending: r.queue.some(task => task.status === STATUSES.PENDING),
+ isInProgress: r.queue.some(task => task.status === STATUSES.IN_PROGRESS),
+ isFailed: r.current && r.current.status === STATUSES.FAILED
+ });
+ }
});
},
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.js b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.js
index 9c5d53f63e1..c41a461f35b 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.js
+++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.js
@@ -17,12 +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 classNames from 'classnames';
import React from 'react';
import { Link } from 'react-router';
import { translate } from '../../../../helpers/l10n';
const SETTINGS_URLS = [
+ '/project/admin',
'/project/settings',
'/project/quality_profiles',
'/project/quality_gate',
@@ -54,24 +54,12 @@ export default class ComponentNavMenu extends React.Component {
return Object.keys(this.props.conf).some(key => this.props.conf[key]);
}
- 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 pathname = this.isView() ? '/view' : '/dashboard';
return (
<li>
<Link
- to={{ pathname: '/dashboard', query: { id: this.props.component.key } }}
+ to={{ pathname, query: { id: this.props.component.key } }}
activeClassName="active">
<i className="icon-home"/>
</Link>
@@ -96,6 +84,10 @@ export default class ComponentNavMenu extends React.Component {
}
renderActivityLink () {
+ if (this.isView() || this.isDeveloper()) {
+ return null;
+ }
+
return (
<li>
<Link to={{ pathname: '/project/activity', query: { id: this.props.component.key } }}
@@ -296,11 +288,11 @@ export default class ComponentNavMenu extends React.Component {
);
}
- renderExtension = ({ id, name }) => {
+ renderExtension = ({ id, name }, isAdmin = false) => {
+ const pathname = isAdmin ? `/project/admin/extension/${id}` : `/project/extension/${id}`;
return (
<li key={id}>
- <Link to={{ pathname: `/project/extension/${id}`, query: { id: this.props.component.key } }}
- activeClassName="active">
+ <Link to={{ pathname, query: { id: this.props.component.key } }} activeClassName="active">
{name}
</Link>
</li>
@@ -309,7 +301,7 @@ export default class ComponentNavMenu extends React.Component {
renderExtensions () {
const extensions = this.props.conf.extensions || [];
- return extensions.map(this.renderExtension);
+ return extensions.map(e => this.renderExtension(e, true));
}
renderTools () {
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMenu-test.js.snap b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMenu-test.js.snap
index c76ff04de72..b2013614907 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMenu-test.js.snap
+++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMenu-test.js.snap
@@ -118,7 +118,7 @@ exports[`test should work with extensions 1`] = `
style={Object {}}
to={
Object {
- "pathname": "/project/extension/foo",
+ "pathname": "/project/admin/extension/foo",
"query": Object {
"id": "foo",
},
diff --git a/server/sonar-web/src/main/js/app/index.js b/server/sonar-web/src/main/js/app/index.js
index edca4bb505c..33ec72df68c 100644
--- a/server/sonar-web/src/main/js/app/index.js
+++ b/server/sonar-web/src/main/js/app/index.js
@@ -21,6 +21,7 @@ import configureLocale from './utils/configureLocale';
import exposeLibraries from './utils/exposeLibraries';
import startAjaxMonitoring from './utils/startAjaxMonitoring';
import startReactApp from './utils/startReactApp';
+import installExtensionsHandler from './utils/installExtensionsHandler';
import { installGlobal } from '../helpers/l10n';
import './styles/index';
@@ -39,3 +40,4 @@ startAjaxMonitoring();
installGlobal();
startReactApp();
exposeLibraries();
+installExtensionsHandler();
diff --git a/server/sonar-web/src/main/js/app/utils/installExtensionsHandler.js b/server/sonar-web/src/main/js/app/utils/installExtensionsHandler.js
new file mode 100644
index 00000000000..f8cd418260b
--- /dev/null
+++ b/server/sonar-web/src/main/js/app/utils/installExtensionsHandler.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
+const extensions = {};
+
+const registerExtension = (key: string, start: Function) => {
+ extensions[key] = start;
+};
+
+export default () => {
+ window.registerExtension = registerExtension;
+};
+
+export const getExtensionFromCache = (key: string) => {
+ return extensions[key];
+};
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 2c04125c885..ae1411b2643 100644
--- a/server/sonar-web/src/main/js/app/utils/startReactApp.js
+++ b/server/sonar-web/src/main/js/app/utils/startReactApp.js
@@ -19,7 +19,7 @@
*/
import React from 'react';
import { render } from 'react-dom';
-import { Router, Route, IndexRoute } from 'react-router';
+import { Router, Route, IndexRoute, Redirect } from 'react-router';
import { Provider } from 'react-redux';
import LocalizationContainer from '../components/LocalizationContainer';
import MigrationContainer from '../components/MigrationContainer';
@@ -28,7 +28,13 @@ import GlobalContainer from '../components/GlobalContainer';
import SimpleContainer from '../components/SimpleContainer';
import Landing from '../components/Landing';
import ProjectContainer from '../components/ProjectContainer';
+import ProjectAdminContainer from '../components/ProjectAdminContainer';
+import ProjectPageExtension from '../components/extensions/ProjectPageExtension';
+import ProjectAdminPageExtension from '../components/extensions/ProjectAdminPageExtension';
+import ViewDashboard from '../components/extensions/ViewDashboard';
import AdminContainer from '../components/AdminContainer';
+import GlobalPageExtension from '../components/extensions/GlobalPageExtension';
+import GlobalAdminPageExtension from '../components/extensions/GlobalAdminPageExtension';
import MarkdownHelp from '../components/MarkdownHelp';
import NotFound from '../components/NotFound';
import aboutRoutes from '../../apps/about/routes';
@@ -97,6 +103,7 @@ const startReactApp = () => {
<Route path="account">{accountRoutes}</Route>
<Route path="coding_rules">{codingRulesRoutes}</Route>
<Route path="component">{componentRoutes}</Route>
+ <Route path="extension/:pluginKey/:extensionKey" component={GlobalPageExtension}/>
<Route path="issues">{issuesRoutes}</Route>
<Route path="projects">{projectsRoutes}</Route>
<Route path="quality_gates">{qualityGatesRoutes}</Route>
@@ -109,16 +116,24 @@ const startReactApp = () => {
<Route path="component_measures">{componentMeasuresRoutes}</Route>
<Route path="custom_measures">{customMeasuresRoutes}</Route>
<Route path="dashboard">{overviewRoutes}</Route>
+ <Redirect from="governance" to="/view"/>
<Route path="project">
<Route path="activity">{projectActivityRoutes}</Route>
+ <Route path="admin" component={ProjectAdminContainer}>
+ <Route path="extension/:pluginKey/:extensionKey" component={ProjectAdminPageExtension}/>
+ </Route>
+ <Redirect from="extension/governance/governance" to="/view"/>
+ <Route path="extension/:pluginKey/:extensionKey" component={ProjectPageExtension}/>
<Route path="background_tasks">{backgroundTasksRoutes}</Route>
<Route path="settings">{settingsRoutes}</Route>
{projectAdminRoutes}
</Route>
<Route path="project_roles">{projectPermissionsRoutes}</Route>
+ <Route path="view" component={ViewDashboard}/>
</Route>
<Route component={AdminContainer}>
+ <Route path="admin/extension/:pluginKey/:extensionKey" component={GlobalAdminPageExtension}/>
<Route path="background_tasks">{backgroundTasksRoutes}</Route>
<Route path="groups">{groupsRoutes}</Route>
<Route path="metrics">{metricsRoutes}</Route>
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 2aefdf292f3..dab6387f782 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
@@ -17,19 +17,37 @@
* 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 shallowCompare from 'react-addons-shallow-compare';
+import { withRouter } from 'react-router';
import OverviewApp from './OverviewApp';
import EmptyOverview from './EmptyOverview';
-import { ComponentType } from '../propTypes';
-export default class App extends React.Component {
- static propTypes = {
- component: ComponentType.isRequired
- };
+type Props = {
+ component: {
+ id: string,
+ key: string,
+ qualifier: string
+ },
+ router: Object
+};
- shouldComponentUpdate (nextProps, nextState) {
- return shallowCompare(this, nextProps, nextState);
+class App extends React.Component {
+ props: Props;
+ state: Object;
+
+ componentDidMount () {
+ if (['VW', 'SVW'].includes(this.props.component.qualifier)) {
+ this.props.router.replace({
+ pathname: '/view',
+ query: { id: this.props.component.key }
+ });
+ }
+ }
+
+ shouldComponentUpdate (nextProps: Props) {
+ return shallowCompare(this, nextProps);
}
render () {
@@ -55,3 +73,7 @@ export default class App extends React.Component {
);
}
}
+
+export default withRouter(App);
+
+export const UnconnectedApp = App;
diff --git a/server/sonar-web/src/main/js/apps/overview/components/__tests__/App-test.js b/server/sonar-web/src/main/js/apps/overview/components/__tests__/App-test.js
index 520688a4eb7..8f6f25c0f2b 100644
--- a/server/sonar-web/src/main/js/apps/overview/components/__tests__/App-test.js
+++ b/server/sonar-web/src/main/js/apps/overview/components/__tests__/App-test.js
@@ -19,24 +19,24 @@
*/
import React from 'react';
import { shallow } from 'enzyme';
-import App from '../App';
+import { UnconnectedApp } from '../App';
import OverviewApp from '../OverviewApp';
import EmptyOverview from '../EmptyOverview';
it('should render OverviewApp', () => {
const component = { id: 'id', snapshotDate: '2016-01-01' };
- const output = shallow(<App component={component}/>);
+ const output = shallow(<UnconnectedApp component={component}/>);
expect(output.type()).toBe(OverviewApp);
});
it('should render EmptyOverview', () => {
const component = { id: 'id' };
- const output = shallow(<App component={component}/>);
+ const output = shallow(<UnconnectedApp component={component}/>);
expect(output.type()).toBe(EmptyOverview);
});
it('should pass leakPeriodIndex', () => {
const component = { id: 'id', snapshotDate: '2016-01-01' };
- const output = shallow(<App component={component}/>);
+ const output = shallow(<UnconnectedApp component={component}/>);
expect(output.prop('leakPeriodIndex')).toBe('1');
});
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 61e26530eee..a0b76bf0690 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
@@ -39,8 +39,6 @@ const Meta = ({ component, measures }) => {
const shouldShowQualityProfiles = !isView && !isDeveloper && hasQualityProfiles;
const shouldShowQualityGate = !isView && !isDeveloper && hasQualityGate;
- const showShowAnalyses = isProject || isView || isDeveloper;
-
return (
<div className="overview-meta">
{hasDescription && (
@@ -63,7 +61,7 @@ const Meta = ({ component, measures }) => {
<MetaKey component={component}/>
- {showShowAnalyses && (
+ {isProject && (
<AnalysesList project={component.key}/>
)}
</div>
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.js b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.js
index 57d8475bf83..26dc82ece68 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.js
@@ -30,7 +30,6 @@ import './projectActivity.css';
type Props = {
location: { query: { id: string } },
fetchProjectActivity: (project: string) => void,
- /* eslint-disable react/no-unused-prop-types */
project: { configuration?: { showHistory: boolean } }
};
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileDetails.js b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileDetails.js
index 46c0b395ae2..93020e2dff7 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileDetails.js
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileDetails.js
@@ -26,7 +26,6 @@ import { ProfileType } from '../propTypes';
export default class ProfileDetails extends React.Component {
static propTypes = {
- /* eslint-disable react/no-unused-prop-types */
profile: ProfileType,
canAdmin: React.PropTypes.bool,
updateProfiles: React.PropTypes.func
diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/Input.js b/server/sonar-web/src/main/js/apps/settings/components/inputs/Input.js
index b2ddc8e6959..02fcbfacef2 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/inputs/Input.js
+++ b/server/sonar-web/src/main/js/apps/settings/components/inputs/Input.js
@@ -26,7 +26,6 @@ import { TYPE_PROPERTY_SET } from '../../constants';
export default class Input extends React.Component {
static propTypes = {
- /* eslint-disable react/no-unused-prop-types */
setting: React.PropTypes.object.isRequired,
value: React.PropTypes.any,
onChange: React.PropTypes.func.isRequired
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 55f2182af2c..8b7ef942c37 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,7 +20,6 @@
// @flow
import keyBy from 'lodash/keyBy';
import { RECEIVE_VALUES } from './actions';
-import { actions as appStateActions } from '../../../../store/appState/duck';
type State = { [key: string]: {} };
@@ -30,7 +29,7 @@ const reducer = (state: State = {}, action: Object) => {
return { ...state, ...settingsByKey };
}
- if (action.type === appStateActions.SET_APP_STATE) {
+ if (action.type === 'SET_APP_STATE') {
const settingsByKey = {};
Object.keys(action.appState.settings).forEach(key => (
settingsByKey[key] = { value: action.appState.settings[key] }
diff --git a/server/sonar-web/src/main/js/components/controls/DateInput.js b/server/sonar-web/src/main/js/components/controls/DateInput.js
index 4f011cef19e..2d15e9130c7 100644
--- a/server/sonar-web/src/main/js/components/controls/DateInput.js
+++ b/server/sonar-web/src/main/js/components/controls/DateInput.js
@@ -24,7 +24,6 @@ import './styles.css';
export default class DateInput extends React.Component {
static propTypes = {
- /* eslint-disable react/no-unused-prop-types */
value: React.PropTypes.string,
format: React.PropTypes.string,
name: React.PropTypes.string,
diff --git a/server/sonar-web/src/main/js/components/select-list/list.js b/server/sonar-web/src/main/js/components/select-list/list.js
index 92604b288a3..476346880e2 100644
--- a/server/sonar-web/src/main/js/components/select-list/list.js
+++ b/server/sonar-web/src/main/js/components/select-list/list.js
@@ -22,7 +22,6 @@ import Item from './item';
export default React.createClass({
propTypes: {
- /* eslint-disable react/no-unused-prop-types */
items: React.PropTypes.array.isRequired,
renderItem: React.PropTypes.func.isRequired,
getItemKey: React.PropTypes.func.isRequired,
diff --git a/server/sonar-web/src/main/js/components/select-list/main.js b/server/sonar-web/src/main/js/components/select-list/main.js
index b763e6463d6..6547746ebe4 100644
--- a/server/sonar-web/src/main/js/components/select-list/main.js
+++ b/server/sonar-web/src/main/js/components/select-list/main.js
@@ -24,7 +24,6 @@ import Footer from './footer';
export default React.createClass({
propTypes: {
- /* eslint-disable react/no-unused-prop-types */
loadItems: React.PropTypes.func.isRequired,
renderItem: React.PropTypes.func.isRequired,
getItemKey: React.PropTypes.func.isRequired,
diff --git a/server/sonar-web/src/main/js/store/appState/duck.js b/server/sonar-web/src/main/js/store/appState/duck.js
index 1fe430c990a..912ceaa85c2 100644
--- a/server/sonar-web/src/main/js/store/appState/duck.js
+++ b/server/sonar-web/src/main/js/store/appState/duck.js
@@ -19,28 +19,36 @@
*/
// @flow
type AppState = {
+ adminPages?: Array<*>,
authenticationError: boolean,
authorizationError: boolean,
qualifiers: ?Array<string>
};
-export type Action = {
- type: string,
+type SetAppStateAction = {
+ type: 'SET_APP_STATE',
appState: AppState
};
-export const actions = {
- SET_APP_STATE: 'SET_APP_STATE',
- REQUIRE_AUTHORIZATION: 'REQUIRE_AUTHORIZATION'
+type SetAdminPagesAction = {
+ type: 'SET_ADMIN_PAGES',
+ adminPages: Array<*>
};
-export const setAppState = (appState: AppState): Action => ({
- type: actions.SET_APP_STATE,
+export type Action = SetAppStateAction | SetAdminPagesAction;
+
+export const setAppState = (appState: AppState): SetAppStateAction => ({
+ type: 'SET_APP_STATE',
appState
});
+export const setAdminPages = (adminPages: Array<*>): SetAdminPagesAction => ({
+ type: 'SET_ADMIN_PAGES',
+ adminPages
+});
+
export const requireAuthorization = () => ({
- type: actions.REQUIRE_AUTHORIZATION
+ type: 'REQUIRE_AUTHORIZATION'
});
const defaultValue = {
@@ -50,11 +58,15 @@ const defaultValue = {
};
export default (state: AppState = defaultValue, action: Action) => {
- if (action.type === actions.SET_APP_STATE) {
+ if (action.type === 'SET_APP_STATE') {
return { ...state, ...action.appState };
}
- if (action.type === actions.REQUIRE_AUTHORIZATION) {
+ if (action.type === 'SET_ADMIN_PAGES') {
+ return { ...state, adminPages: action.adminPages };
+ }
+
+ if (action.type === 'REQUIRE_AUTHORIZATION') {
return { ...state, authorizationError: true };
}
diff --git a/server/sonar-web/src/main/js/store/globalMessages/duck.js b/server/sonar-web/src/main/js/store/globalMessages/duck.js
index 57abed0977b..f3a5d1cdf58 100644
--- a/server/sonar-web/src/main/js/store/globalMessages/duck.js
+++ b/server/sonar-web/src/main/js/store/globalMessages/duck.js
@@ -19,7 +19,6 @@
*/
// @flow
import uniqueId from 'lodash/uniqueId';
-import { actions } from '../appState/duck';
type Level = 'ERROR' | 'SUCCESS';
@@ -80,7 +79,7 @@ const globalMessages = (state: State = [], action: Action = {}) => {
level: action.level
}];
- case actions.REQUIRE_AUTHORIZATION:
+ case 'REQUIRE_AUTHORIZATION':
// FIXME l10n
return [{
id: uniqueId('global-message-'),
diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
index 8ec7fda9c47..0ca4ee2690e 100644
--- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties
+++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
@@ -256,6 +256,7 @@ over_x_days.short={0} days
over_x_days_detailed=over {0} days ({1})
over_x_days_detailed.short={0} days ({1})
page_size=Page size
+page_extension_failed=Page extension failed.
paging_first=First
paging_last=Last
paging_next=Next