Explorar el Código

SONAR-8554 Load and display page extensions (#1482)

tags/6.3-RC1
Stas Vilchik hace 7 años
padre
commit
ed2fff193e
Se han modificado 38 ficheros con 677 adiciones y 82 borrados
  1. 1
    1
      it/it-plugins/ui-extensions-plugin/src/main/java/GlobalPage.java
  2. 1
    1
      it/it-plugins/ui-extensions-plugin/src/main/java/ProjectPage.java
  3. 1
    1
      it/it-plugins/ui-extensions-plugin/src/main/java/ResourceConfigurationPage.java
  4. 1
    1
      it/it-plugins/ui-extensions-plugin/src/main/java/SettingsPage.java
  5. 4
    0
      it/it-plugins/ui-extensions-plugin/src/main/resources/static/global_page.js
  6. 4
    0
      it/it-plugins/ui-extensions-plugin/src/main/resources/static/project_page.js
  7. 4
    0
      it/it-plugins/ui-extensions-plugin/src/main/resources/static/resource_configuration_sample.js
  8. 4
    0
      it/it-plugins/ui-extensions-plugin/src/main/resources/static/settings_page.js
  9. 0
    1
      server/sonar-web/.eslintrc
  10. 9
    17
      server/sonar-web/src/main/js/app/components/AdminContainer.js
  11. 66
    0
      server/sonar-web/src/main/js/app/components/ProjectAdminContainer.js
  12. 98
    0
      server/sonar-web/src/main/js/app/components/extensions/Extension.js
  13. 44
    0
      server/sonar-web/src/main/js/app/components/extensions/ExtensionNotFound.js
  14. 51
    0
      server/sonar-web/src/main/js/app/components/extensions/GlobalAdminPageExtension.js
  15. 51
    0
      server/sonar-web/src/main/js/app/components/extensions/GlobalPageExtension.js
  16. 63
    0
      server/sonar-web/src/main/js/app/components/extensions/ProjectAdminPageExtension.js
  17. 60
    0
      server/sonar-web/src/main/js/app/components/extensions/ProjectPageExtension.js
  18. 32
    0
      server/sonar-web/src/main/js/app/components/extensions/ViewDashboard.js
  19. 48
    0
      server/sonar-web/src/main/js/app/components/extensions/utils.js
  20. 13
    5
      server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.js
  21. 11
    19
      server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.js
  22. 1
    1
      server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMenu-test.js.snap
  23. 2
    0
      server/sonar-web/src/main/js/app/index.js
  24. 33
    0
      server/sonar-web/src/main/js/app/utils/installExtensionsHandler.js
  25. 16
    1
      server/sonar-web/src/main/js/app/utils/startReactApp.js
  26. 29
    7
      server/sonar-web/src/main/js/apps/overview/components/App.js
  27. 4
    4
      server/sonar-web/src/main/js/apps/overview/components/__tests__/App-test.js
  28. 1
    3
      server/sonar-web/src/main/js/apps/overview/meta/Meta.js
  29. 0
    1
      server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.js
  30. 0
    1
      server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileDetails.js
  31. 0
    1
      server/sonar-web/src/main/js/apps/settings/components/inputs/Input.js
  32. 1
    2
      server/sonar-web/src/main/js/apps/settings/store/values/reducer.js
  33. 0
    1
      server/sonar-web/src/main/js/components/controls/DateInput.js
  34. 0
    1
      server/sonar-web/src/main/js/components/select-list/list.js
  35. 0
    1
      server/sonar-web/src/main/js/components/select-list/main.js
  36. 22
    10
      server/sonar-web/src/main/js/store/appState/duck.js
  37. 1
    2
      server/sonar-web/src/main/js/store/globalMessages/duck.js
  38. 1
    0
      sonar-core/src/main/resources/org/sonar/l10n/core.properties

+ 1
- 1
it/it-plugins/ui-extensions-plugin/src/main/java/GlobalPage.java Ver fichero

@@ -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() {

+ 1
- 1
it/it-plugins/ui-extensions-plugin/src/main/java/ProjectPage.java Ver fichero

@@ -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() {

+ 1
- 1
it/it-plugins/ui-extensions-plugin/src/main/java/ResourceConfigurationPage.java Ver fichero

@@ -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() {

+ 1
- 1
it/it-plugins/ui-extensions-plugin/src/main/java/SettingsPage.java Ver fichero

@@ -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() {

+ 4
- 0
it/it-plugins/ui-extensions-plugin/src/main/resources/static/global_page.js Ver fichero

@@ -0,0 +1,4 @@
window.registerExtension('uiextensionsplugin/global_page', function (options) {
options.el.textContent = 'uiextensionsplugin/global_page';
return function () {};
});

+ 4
- 0
it/it-plugins/ui-extensions-plugin/src/main/resources/static/project_page.js Ver fichero

@@ -0,0 +1,4 @@
window.registerExtension('uiextensionsplugin/project_page', function (options) {
options.el.textContent = 'uiextensionsplugin/project_page';
return function () {};
});

+ 4
- 0
it/it-plugins/ui-extensions-plugin/src/main/resources/static/resource_configuration_sample.js Ver fichero

@@ -0,0 +1,4 @@
window.registerExtension('uiextensionsplugin/resource_configuration_sample', function (options) {
options.el.textContent = 'uiextensionsplugin/resource_configuration_sample';
return function () {};
});

+ 4
- 0
it/it-plugins/ui-extensions-plugin/src/main/resources/static/settings_page.js Ver fichero

@@ -0,0 +1,4 @@
window.registerExtension('uiextensionsplugin/settings_page', function (options) {
options.el.textContent = 'uiextensionsplugin/settings_page';
return function () {};
});

+ 0
- 1
server/sonar-web/.eslintrc Ver fichero

@@ -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

+ 9
- 17
server/sonar-web/src/main/js/app/components/AdminContainer.js Ver fichero

@@ -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);

+ 66
- 0
server/sonar-web/src/main/js/app/components/ProjectAdminContainer.js Ver fichero

@@ -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);

+ 98
- 0
server/sonar-web/src/main/js/app/components/extensions/Extension.js Ver fichero

@@ -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));

+ 44
- 0
server/sonar-web/src/main/js/app/components/extensions/ExtensionNotFound.js Ver fichero

@@ -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>
);
}
}

+ 51
- 0
server/sonar-web/src/main/js/app/components/extensions/GlobalAdminPageExtension.js Ver fichero

@@ -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);

+ 51
- 0
server/sonar-web/src/main/js/app/components/extensions/GlobalPageExtension.js Ver fichero

@@ -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);

+ 63
- 0
server/sonar-web/src/main/js/app/components/extensions/ProjectAdminPageExtension.js Ver fichero

@@ -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);

+ 60
- 0
server/sonar-web/src/main/js/app/components/extensions/ProjectPageExtension.js Ver fichero

@@ -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);

+ 32
- 0
server/sonar-web/src/main/js/app/components/extensions/ViewDashboard.js Ver fichero

@@ -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' }}/>
);
}
}

+ 48
- 0
server/sonar-web/src/main/js/app/components/extensions/utils.js Ver fichero

@@ -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();
}
});
})
);

+ 13
- 5
server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.js Ver fichero

@@ -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
});
}
});
},


+ 11
- 19
server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.js Ver fichero

@@ -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 () {

+ 1
- 1
server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMenu-test.js.snap Ver fichero

@@ -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",
},

+ 2
- 0
server/sonar-web/src/main/js/app/index.js Ver fichero

@@ -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();

+ 33
- 0
server/sonar-web/src/main/js/app/utils/installExtensionsHandler.js Ver fichero

@@ -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];
};

+ 16
- 1
server/sonar-web/src/main/js/app/utils/startReactApp.js Ver fichero

@@ -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>

+ 29
- 7
server/sonar-web/src/main/js/apps/overview/components/App.js Ver fichero

@@ -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;

+ 4
- 4
server/sonar-web/src/main/js/apps/overview/components/__tests__/App-test.js Ver fichero

@@ -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');
});

+ 1
- 3
server/sonar-web/src/main/js/apps/overview/meta/Meta.js Ver fichero

@@ -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>

+ 0
- 1
server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.js Ver fichero

@@ -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 } }
};


+ 0
- 1
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileDetails.js Ver fichero

@@ -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

+ 0
- 1
server/sonar-web/src/main/js/apps/settings/components/inputs/Input.js Ver fichero

@@ -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

+ 1
- 2
server/sonar-web/src/main/js/apps/settings/store/values/reducer.js Ver fichero

@@ -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] }

+ 0
- 1
server/sonar-web/src/main/js/components/controls/DateInput.js Ver fichero

@@ -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,

+ 0
- 1
server/sonar-web/src/main/js/components/select-list/list.js Ver fichero

@@ -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,

+ 0
- 1
server/sonar-web/src/main/js/components/select-list/main.js Ver fichero

@@ -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,

+ 22
- 10
server/sonar-web/src/main/js/store/appState/duck.js Ver fichero

@@ -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 };
}


+ 1
- 2
server/sonar-web/src/main/js/store/globalMessages/duck.js Ver fichero

@@ -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-'),

+ 1
- 0
sonar-core/src/main/resources/org/sonar/l10n/core.properties Ver fichero

@@ -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

Cargando…
Cancelar
Guardar