/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2017 SonarSource SA | |||||
* mailto:info 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 { checkStatus, corsRequest, getJSON, parseJSON } from '../helpers/request'; | |||||
import throwGlobalError from '../app/utils/throwGlobalError'; | |||||
export interface Edition { | |||||
name: string; | |||||
desc: string; | |||||
more_link: string; | |||||
request_license_link: string; | |||||
download_link: string; | |||||
} | |||||
export interface Editions { | |||||
[key: string]: Edition; | |||||
} | |||||
export interface EditionStatus { | |||||
currentEditionKey?: string; | |||||
nextEditionKey?: string; | |||||
installationStatus: | |||||
| 'NONE' | |||||
| 'AUTOMATIC_IN_PROGRESS' | |||||
| 'MANUAL_IN_PROGRESS' | |||||
| 'AUTOMATIC_READY' | |||||
| 'AUTOMATIC_FAILURE'; | |||||
} | |||||
export function getEditionStatus(): Promise<EditionStatus> { | |||||
return getJSON('/api/editions/status').catch(throwGlobalError); | |||||
} | |||||
export function getEditionsList(): Promise<Editions> { | |||||
// TODO Replace with real url | |||||
const url = | |||||
'https://gist.githubusercontent.com/gregaubert/e34535494f8a94bec7cbc4d750ae7d06/raw/ba8670a28d4bc6fbac18f92e450ec42029cc5dcb/editions.json'; | |||||
return corsRequest(url) | |||||
.submit() | |||||
.then(checkStatus) | |||||
.then(parseJSON); | |||||
} |
* along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
import React from 'react'; | |||||
import * as React from 'react'; | |||||
import * as PropTypes from 'prop-types'; | |||||
import Helmet from 'react-helmet'; | import Helmet from 'react-helmet'; | ||||
import { connect } from 'react-redux'; | import { connect } from 'react-redux'; | ||||
import SettingsNav from './nav/settings/SettingsNav'; | import SettingsNav from './nav/settings/SettingsNav'; | ||||
import { getAppState } from '../../store/rootReducer'; | import { getAppState } from '../../store/rootReducer'; | ||||
import { onFail } from '../../store/rootActions'; | |||||
import { getSettingsNavigation } from '../../api/nav'; | import { getSettingsNavigation } from '../../api/nav'; | ||||
import { setAdminPages } from '../../store/appState/duck'; | |||||
import { EditionStatus, getEditionStatus } from '../../api/marketplace'; | |||||
import { setAdminPages, setEditionStatus } from '../../store/appState/duck'; | |||||
import { translate } from '../../helpers/l10n'; | import { translate } from '../../helpers/l10n'; | ||||
import { Extension } from '../types'; | |||||
interface Props { | |||||
appState: { | |||||
adminPages: Extension[]; | |||||
editionStatus?: EditionStatus; | |||||
organizationsEnabled: boolean; | |||||
}; | |||||
location: {}; | |||||
setAdminPages: (adminPages: Extension[]) => void; | |||||
setEditionStatus: (editionStatus: EditionStatus) => void; | |||||
} | |||||
class AdminContainer extends React.PureComponent<Props> { | |||||
static contextTypes = { | |||||
canAdmin: PropTypes.bool.isRequired | |||||
}; | |||||
class AdminContainer extends React.PureComponent { | |||||
componentDidMount() { | componentDidMount() { | ||||
if (!this.props.appState.canAdmin) { | |||||
if (!this.context.canAdmin) { | |||||
// workaround cyclic dependencies | // workaround cyclic dependencies | ||||
const handleRequiredAuthorization = require('../utils/handleRequiredAuthorization').default; | const handleRequiredAuthorization = require('../utils/handleRequiredAuthorization').default; | ||||
handleRequiredAuthorization(); | handleRequiredAuthorization(); | ||||
} else { | |||||
this.loadData(); | |||||
} | } | ||||
this.loadData(); | |||||
} | } | ||||
loadData() { | loadData() { | ||||
getSettingsNavigation().then( | |||||
r => this.props.setAdminPages(r.extensions), | |||||
onFail(this.props.dispatch) | |||||
Promise.all([getSettingsNavigation(), getEditionStatus()]).then( | |||||
([r, editionStatus]) => { | |||||
this.props.setAdminPages(r.extensions); | |||||
this.props.setEditionStatus(editionStatus); | |||||
}, | |||||
() => {} | |||||
); | ); | ||||
} | } | ||||
render() { | render() { | ||||
const { adminPages } = this.props.appState; | |||||
const { adminPages, editionStatus, organizationsEnabled } = this.props.appState; | |||||
// Check that the adminPages are loaded | // Check that the adminPages are loaded | ||||
if (!adminPages) { | if (!adminPages) { | ||||
return ( | return ( | ||||
<div> | <div> | ||||
<Helmet defaultTitle={defaultTitle} titleTemplate={'%s - ' + defaultTitle} /> | <Helmet defaultTitle={defaultTitle} titleTemplate={'%s - ' + defaultTitle} /> | ||||
<SettingsNav location={this.props.location} extensions={adminPages} /> | |||||
<SettingsNav | |||||
customOrganizations={organizationsEnabled} | |||||
editionStatus={editionStatus} | |||||
extensions={adminPages} | |||||
location={this.props.location} | |||||
/> | |||||
{this.props.children} | {this.props.children} | ||||
</div> | </div> | ||||
); | ); | ||||
} | } | ||||
} | } | ||||
const mapStateToProps = state => ({ | |||||
const mapStateToProps = (state: any) => ({ | |||||
appState: getAppState(state) | appState: getAppState(state) | ||||
}); | }); | ||||
const mapDispatchToProps = { setAdminPages }; | |||||
const mapDispatchToProps = { setAdminPages, setEditionStatus }; | |||||
export default connect(mapStateToProps, mapDispatchToProps)(AdminContainer); | |||||
export default connect(mapStateToProps, mapDispatchToProps)(AdminContainer as any); |
import { Link } from 'react-router'; | import { Link } from 'react-router'; | ||||
import * as classNames from 'classnames'; | import * as classNames from 'classnames'; | ||||
import * as PropTypes from 'prop-types'; | import * as PropTypes from 'prop-types'; | ||||
import { Branch, Component, ComponentExtension } from '../../../types'; | |||||
import { Branch, Component, Extension } from '../../../types'; | |||||
import NavBarTabs from '../../../../components/nav/NavBarTabs'; | import NavBarTabs from '../../../../components/nav/NavBarTabs'; | ||||
import { | import { | ||||
isShortLivingBranch, | isShortLivingBranch, | ||||
); | ); | ||||
} | } | ||||
renderExtension = ({ key, name }: ComponentExtension, isAdmin: boolean) => { | |||||
renderExtension = ({ key, name }: Extension, isAdmin: boolean) => { | |||||
const pathname = isAdmin ? `/project/admin/extension/${key}` : `/project/extension/${key}`; | const pathname = isAdmin ? `/project/admin/extension/${key}` : `/project/extension/${key}`; | ||||
return ( | return ( | ||||
<li key={key}> | <li key={key}> |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2017 SonarSource SA | |||||
* mailto:info 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 * as React from 'react'; | |||||
import NavBarNotif from '../../../../components/nav/NavBarNotif'; | |||||
import { EditionStatus } from '../../../../api/marketplace'; | |||||
import { translate } from '../../../../helpers/l10n'; | |||||
interface Props { | |||||
editionStatus: EditionStatus; | |||||
} | |||||
export default class SettingsEditionsNotif extends React.PureComponent<Props> { | |||||
render() { | |||||
const { editionStatus } = this.props; | |||||
if (editionStatus.installationStatus === 'AUTOMATIC_IN_PROGRESS') { | |||||
return ( | |||||
<NavBarNotif className="alert alert-info"> | |||||
<i className="spinner spacer-right text-bottom" /> | |||||
<span>{translate('marketplace.status.AUTOMATIC_IN_PROGRESS')}</span> | |||||
</NavBarNotif> | |||||
); | |||||
} else if (editionStatus.installationStatus === 'AUTOMATIC_READY') { | |||||
return ( | |||||
<NavBarNotif className="alert alert-success"> | |||||
<span>{translate('marketplace.status.AUTOMATIC_READY')}</span> | |||||
</NavBarNotif> | |||||
); | |||||
} else if ( | |||||
['MANUAL_IN_PROGRESS', 'AUTOMATIC_FAILURE'].includes(editionStatus.installationStatus) | |||||
) { | |||||
return ( | |||||
<NavBarNotif className="alert alert-danger"> | |||||
{translate('marketplace.status', editionStatus.installationStatus)} | |||||
<a className="little-spacer-left" href="https://www.sonarsource.com" target="_blank"> | |||||
{translate('marketplace.how_to_install')} | |||||
</a> | |||||
</NavBarNotif> | |||||
); | |||||
} | |||||
return null; | |||||
} | |||||
} |
* along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
import React from 'react'; | |||||
import classNames from 'classnames'; | |||||
import * as React from 'react'; | |||||
import * as classNames from 'classnames'; | |||||
import { IndexLink, Link } from 'react-router'; | import { IndexLink, Link } from 'react-router'; | ||||
import { connect } from 'react-redux'; | |||||
import ContextNavBar from '../../../../components/nav/ContextNavBar'; | import ContextNavBar from '../../../../components/nav/ContextNavBar'; | ||||
import SettingsEditionsNotif from './SettingsEditionsNotif'; | |||||
import NavBarTabs from '../../../../components/nav/NavBarTabs'; | import NavBarTabs from '../../../../components/nav/NavBarTabs'; | ||||
import { EditionStatus } from '../../../../api/marketplace'; | |||||
import { Extension } from '../../../types'; | |||||
import { translate } from '../../../../helpers/l10n'; | import { translate } from '../../../../helpers/l10n'; | ||||
import { areThereCustomOrganizations } from '../../../../store/rootReducer'; | |||||
class SettingsNav extends React.PureComponent { | |||||
interface Props { | |||||
editionStatus?: EditionStatus; | |||||
extensions: Extension[]; | |||||
customOrganizations: boolean; | |||||
location: {}; | |||||
} | |||||
export default class SettingsNav extends React.PureComponent<Props> { | |||||
static defaultProps = { | static defaultProps = { | ||||
extensions: [] | extensions: [] | ||||
}; | }; | ||||
isSomethingActive(urls) { | |||||
isSomethingActive(urls: string[]): boolean { | |||||
const path = window.location.pathname; | const path = window.location.pathname; | ||||
return urls.some(url => path.indexOf(window.baseUrl + url) === 0); | |||||
return urls.some((url: string) => path.indexOf((window as any).baseUrl + url) === 0); | |||||
} | } | ||||
isSecurityActive() { | isSecurityActive() { | ||||
return this.isSomethingActive(urls); | return this.isSomethingActive(urls); | ||||
} | } | ||||
renderExtension = ({ key, name }) => { | |||||
renderExtension = ({ key, name }: Extension) => { | |||||
return ( | return ( | ||||
<li key={key}> | <li key={key}> | ||||
<Link to={`/admin/extension/${key}`} activeClassName="active"> | <Link to={`/admin/extension/${key}`} activeClassName="active"> | ||||
}; | }; | ||||
render() { | render() { | ||||
const { customOrganizations, editionStatus, extensions } = this.props; | |||||
const isSecurity = this.isSecurityActive(); | const isSecurity = this.isSecurityActive(); | ||||
const isProjects = this.isProjectsActive(); | const isProjects = this.isProjectsActive(); | ||||
const isSystem = this.isSystemActive(); | const isSystem = this.isSystemActive(); | ||||
active: !isSecurity && !isProjects && !isSystem && !isSupport | active: !isSecurity && !isProjects && !isSystem && !isSupport | ||||
}); | }); | ||||
const extensionsWithoutSupport = this.props.extensions.filter( | |||||
const extensionsWithoutSupport = extensions.filter( | |||||
extension => extension.key !== 'license/support' | extension => extension.key !== 'license/support' | ||||
); | ); | ||||
const hasSupportExtension = extensionsWithoutSupport.length < this.props.extensions.length; | |||||
const hasSupportExtension = extensionsWithoutSupport.length < extensions.length; | |||||
let notifComponent; | |||||
if (editionStatus && editionStatus.installationStatus !== 'NONE') { | |||||
notifComponent = <SettingsEditionsNotif editionStatus={editionStatus} />; | |||||
} | |||||
return ( | return ( | ||||
<ContextNavBar id="context-navigation" height={65}> | |||||
<ContextNavBar | |||||
id="context-navigation" | |||||
height={notifComponent ? 95 : 65} | |||||
notif={notifComponent}> | |||||
<h1 className="navbar-context-header"> | <h1 className="navbar-context-header"> | ||||
<strong>{translate('layout.settings')}</strong> | <strong>{translate('layout.settings')}</strong> | ||||
</h1> | </h1> | ||||
{translate('users.page')} | {translate('users.page')} | ||||
</IndexLink> | </IndexLink> | ||||
</li> | </li> | ||||
{!this.props.customOrganizations && ( | |||||
{!customOrganizations && ( | |||||
<li> | <li> | ||||
<IndexLink to="/admin/groups" activeClassName="active"> | <IndexLink to="/admin/groups" activeClassName="active"> | ||||
{translate('user_groups.page')} | {translate('user_groups.page')} | ||||
</IndexLink> | </IndexLink> | ||||
</li> | </li> | ||||
)} | )} | ||||
{!this.props.customOrganizations && ( | |||||
{!customOrganizations && ( | |||||
<li> | <li> | ||||
<IndexLink to="/admin/permissions" activeClassName="active"> | <IndexLink to="/admin/permissions" activeClassName="active"> | ||||
{translate('global_permissions.page')} | {translate('global_permissions.page')} | ||||
</IndexLink> | </IndexLink> | ||||
</li> | </li> | ||||
)} | )} | ||||
{!this.props.customOrganizations && ( | |||||
{!customOrganizations && ( | |||||
<li> | <li> | ||||
<IndexLink to="/admin/permission_templates" activeClassName="active"> | <IndexLink to="/admin/permission_templates" activeClassName="active"> | ||||
{translate('permission_templates')} | {translate('permission_templates')} | ||||
{translate('sidebar.projects')} <i className="icon-dropdown" /> | {translate('sidebar.projects')} <i className="icon-dropdown" /> | ||||
</a> | </a> | ||||
<ul className="dropdown-menu"> | <ul className="dropdown-menu"> | ||||
{!this.props.customOrganizations && ( | |||||
{!customOrganizations && ( | |||||
<li> | <li> | ||||
<IndexLink to="/admin/projects_management" activeClassName="active"> | <IndexLink to="/admin/projects_management" activeClassName="active"> | ||||
{translate('management')} | {translate('management')} | ||||
); | ); | ||||
} | } | ||||
} | } | ||||
const mapStateToProps = state => ({ | |||||
customOrganizations: areThereCustomOrganizations(state) | |||||
}); | |||||
export default connect(mapStateToProps)(SettingsNav); | |||||
export const UnconnectedSettingsNav = SettingsNav; |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2017 SonarSource SA | |||||
* mailto:info 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 * as React from 'react'; | |||||
import { shallow } from 'enzyme'; | |||||
import SettingsEditionsNotif from '../SettingsEditionsNotif'; | |||||
it('should display an in progress notif', () => { | |||||
const wrapper = shallow( | |||||
<SettingsEditionsNotif editionStatus={{ installationStatus: 'AUTOMATIC_IN_PROGRESS' }} /> | |||||
); | |||||
expect(wrapper).toMatchSnapshot(); | |||||
}); | |||||
it('should display an error notification', () => { | |||||
const wrapper = shallow( | |||||
<SettingsEditionsNotif editionStatus={{ installationStatus: 'AUTOMATIC_FAILURE' }} /> | |||||
); | |||||
expect(wrapper).toMatchSnapshot(); | |||||
}); | |||||
it('should display a ready notification', () => { | |||||
const wrapper = shallow( | |||||
<SettingsEditionsNotif editionStatus={{ installationStatus: 'AUTOMATIC_READY' }} /> | |||||
); | |||||
expect(wrapper).toMatchSnapshot(); | |||||
}); |
* along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
import React from 'react'; | |||||
import * as React from 'react'; | |||||
import { shallow } from 'enzyme'; | import { shallow } from 'enzyme'; | ||||
import { UnconnectedSettingsNav } from '../SettingsNav'; | |||||
import SettingsNav from '../SettingsNav'; | |||||
it('should work with extensions', () => { | it('should work with extensions', () => { | ||||
const extensions = [{ key: 'foo', name: 'Foo' }]; | const extensions = [{ key: 'foo', name: 'Foo' }]; | ||||
const wrapper = shallow(<UnconnectedSettingsNav extensions={extensions} />); | |||||
const wrapper = shallow( | |||||
<SettingsNav | |||||
customOrganizations={false} | |||||
editionStatus={{ installationStatus: 'NONE' }} | |||||
extensions={extensions} | |||||
location={{}} | |||||
/> | |||||
); | |||||
expect(wrapper).toMatchSnapshot(); | expect(wrapper).toMatchSnapshot(); | ||||
}); | }); | ||||
it('should display an edition notification', () => { | |||||
const wrapper = shallow( | |||||
<SettingsNav | |||||
customOrganizations={false} | |||||
editionStatus={{ installationStatus: 'AUTOMATIC_IN_PROGRESS' }} | |||||
extensions={[]} | |||||
location={{}} | |||||
/> | |||||
); | |||||
expect({ ...wrapper.find('ContextNavBar').props(), children: [] }).toMatchSnapshot(); | |||||
}); |
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||||
exports[`should display a ready notification 1`] = ` | |||||
<NavBarNotif | |||||
className="alert alert-success" | |||||
> | |||||
<span> | |||||
marketplace.status.AUTOMATIC_READY | |||||
</span> | |||||
</NavBarNotif> | |||||
`; | |||||
exports[`should display an error notification 1`] = ` | |||||
<NavBarNotif | |||||
className="alert alert-danger" | |||||
> | |||||
marketplace.status.AUTOMATIC_FAILURE | |||||
<a | |||||
className="little-spacer-left" | |||||
href="https://www.sonarsource.com" | |||||
target="_blank" | |||||
> | |||||
marketplace.how_to_install | |||||
</a> | |||||
</NavBarNotif> | |||||
`; | |||||
exports[`should display an in progress notif 1`] = ` | |||||
<NavBarNotif | |||||
className="alert alert-info" | |||||
> | |||||
<i | |||||
className="spinner spacer-right text-bottom" | |||||
/> | |||||
<span> | |||||
marketplace.status.AUTOMATIC_IN_PROGRESS | |||||
</span> | |||||
</NavBarNotif> | |||||
`; |
// Jest Snapshot v1, https://goo.gl/fbAQLP | // Jest Snapshot v1, https://goo.gl/fbAQLP | ||||
exports[`should display an edition notification 1`] = ` | |||||
Object { | |||||
"children": Array [], | |||||
"height": 95, | |||||
"id": "context-navigation", | |||||
"notif": <SettingsEditionsNotif | |||||
editionStatus={ | |||||
Object { | |||||
"installationStatus": "AUTOMATIC_IN_PROGRESS", | |||||
} | |||||
} | |||||
/>, | |||||
} | |||||
`; | |||||
exports[`should work with extensions 1`] = ` | exports[`should work with extensions 1`] = ` | ||||
<ContextNavBar | <ContextNavBar | ||||
height={65} | height={65} |
export type Branch = MainBranch | LongLivingBranch | ShortLivingBranch; | export type Branch = MainBranch | LongLivingBranch | ShortLivingBranch; | ||||
export interface ComponentExtension { | |||||
export interface Extension { | |||||
key: string; | key: string; | ||||
name: string; | name: string; | ||||
} | } | ||||
}>; | }>; | ||||
configuration?: ComponentConfiguration; | configuration?: ComponentConfiguration; | ||||
description?: string; | description?: string; | ||||
extensions?: ComponentExtension[]; | |||||
extensions?: Extension[]; | |||||
isFavorite?: boolean; | isFavorite?: boolean; | ||||
key: string; | key: string; | ||||
name: string; | name: string; | ||||
} | } | ||||
interface ComponentConfiguration { | interface ComponentConfiguration { | ||||
extensions?: ComponentExtension[]; | |||||
extensions?: Extension[]; | |||||
showBackgroundTasks?: boolean; | showBackgroundTasks?: boolean; | ||||
showLinks?: boolean; | showLinks?: boolean; | ||||
showManualMeasures?: boolean; | showManualMeasures?: boolean; |
import { sortBy, uniqBy } from 'lodash'; | import { sortBy, uniqBy } from 'lodash'; | ||||
import Helmet from 'react-helmet'; | import Helmet from 'react-helmet'; | ||||
import Header from './Header'; | import Header from './Header'; | ||||
import EditionBoxes from './EditionBoxes'; | |||||
import Footer from './Footer'; | import Footer from './Footer'; | ||||
import PendingActions from './PendingActions'; | import PendingActions from './PendingActions'; | ||||
import PluginsList from './PluginsList'; | import PluginsList from './PluginsList'; | ||||
Plugin, | Plugin, | ||||
PluginPending | PluginPending | ||||
} from '../../api/plugins'; | } from '../../api/plugins'; | ||||
import { EditionStatus } from '../../api/marketplace'; | |||||
import { RawQuery } from '../../helpers/query'; | import { RawQuery } from '../../helpers/query'; | ||||
import { translate } from '../../helpers/l10n'; | import { translate } from '../../helpers/l10n'; | ||||
import { filterPlugins, parseQuery, Query, serializeQuery } from './utils'; | import { filterPlugins, parseQuery, Query, serializeQuery } from './utils'; | ||||
export interface Props { | export interface Props { | ||||
editionStatus?: EditionStatus; | |||||
location: { pathname: string; query: RawQuery }; | location: { pathname: string; query: RawQuery }; | ||||
updateCenterActive: boolean; | updateCenterActive: boolean; | ||||
} | } | ||||
}); | }); | ||||
} | } | ||||
}, | }, | ||||
() => {} | |||||
() => { | |||||
if (this.mounted) { | |||||
this.setState({ loading: false }); | |||||
} | |||||
} | |||||
); | ); | ||||
}; | }; | ||||
this.setState({ loading: false, plugins }); | this.setState({ loading: false, plugins }); | ||||
} | } | ||||
}, | }, | ||||
() => {} | |||||
() => { | |||||
if (this.mounted) { | |||||
this.setState({ loading: false }); | |||||
} | |||||
} | |||||
); | ); | ||||
}; | }; | ||||
<div className="page page-limited" id="marketplace-page"> | <div className="page page-limited" id="marketplace-page"> | ||||
<Helmet title={translate('marketplace.page')} /> | <Helmet title={translate('marketplace.page')} /> | ||||
<Header /> | <Header /> | ||||
<EditionBoxes | |||||
editionStatus={this.props.editionStatus} | |||||
updateCenterActive={this.props.updateCenterActive} | |||||
/> | |||||
<PendingActions refreshPending={this.fetchPendingPlugins} pending={pending} /> | <PendingActions refreshPending={this.fetchPendingPlugins} pending={pending} /> | ||||
<Search | <Search | ||||
query={query} | query={query} |
*/ | */ | ||||
import { connect } from 'react-redux'; | import { connect } from 'react-redux'; | ||||
import App from './App'; | import App from './App'; | ||||
import { getGlobalSettingValue } from '../../store/rootReducer'; | |||||
import { getAppState, getGlobalSettingValue } from '../../store/rootReducer'; | |||||
import './style.css'; | |||||
const mapStateToProps = (state: any) => ({ | const mapStateToProps = (state: any) => ({ | ||||
editionStatus: getAppState(state).editionStatus, | |||||
updateCenterActive: (getGlobalSettingValue(state, 'sonar.updatecenter.activate') || {}).value | updateCenterActive: (getGlobalSettingValue(state, 'sonar.updatecenter.activate') || {}).value | ||||
}); | }); | ||||
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2017 SonarSource SA | |||||
* mailto:info 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 * as React from 'react'; | |||||
import { FormattedMessage } from 'react-intl'; | |||||
import EditionBox from './components/EditionBox'; | |||||
import { Editions, EditionStatus, getEditionsList } from '../../api/marketplace'; | |||||
import { translate } from '../../helpers/l10n'; | |||||
export interface Props { | |||||
editionStatus?: EditionStatus; | |||||
updateCenterActive: boolean; | |||||
} | |||||
interface State { | |||||
editions: Editions; | |||||
editionsError: boolean; | |||||
loading: boolean; | |||||
} | |||||
export default class EditionBoxes extends React.PureComponent<Props, State> { | |||||
mounted: boolean; | |||||
state: State = { editions: {}, editionsError: false, loading: true }; | |||||
componentDidMount() { | |||||
this.mounted = true; | |||||
this.fetchEditions(); | |||||
} | |||||
componentWillUnmount() { | |||||
this.mounted = false; | |||||
} | |||||
fetchEditions = () => { | |||||
this.setState({ loading: true }); | |||||
getEditionsList().then( | |||||
editions => { | |||||
if (this.mounted) { | |||||
this.setState({ | |||||
loading: false, | |||||
editions, | |||||
editionsError: false | |||||
}); | |||||
} | |||||
}, | |||||
() => { | |||||
if (this.mounted) { | |||||
this.setState({ editionsError: true, loading: false }); | |||||
} | |||||
} | |||||
); | |||||
}; | |||||
render() { | |||||
const { editions, loading } = this.state; | |||||
if (loading) { | |||||
return null; | |||||
} | |||||
return ( | |||||
<div className="spacer-bottom marketplace-editions"> | |||||
{this.state.editionsError ? ( | |||||
<span className="alert alert-info"> | |||||
<FormattedMessage | |||||
defaultMessage={translate('marketplace.editions_unavailable')} | |||||
id="marketplace.editions_unavailable" | |||||
values={{ | |||||
url: ( | |||||
<a href="https://www.sonarsource.com" target="_blank"> | |||||
SonarSource.com | |||||
</a> | |||||
) | |||||
}} | |||||
/> | |||||
</span> | |||||
) : ( | |||||
Object.keys(editions).map(key => ( | |||||
<EditionBox | |||||
edition={editions[key]} | |||||
editionKey={key} | |||||
editionStatus={this.props.editionStatus} | |||||
key={key} | |||||
/> | |||||
)) | |||||
)} | |||||
</div> | |||||
); | |||||
} | |||||
} |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2017 SonarSource SA | |||||
* mailto:info 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 * as React from 'react'; | |||||
import { shallow } from 'enzyme'; | |||||
import EditionBoxes from '../EditionBoxes'; | |||||
import { EditionStatus } from '../../../api/marketplace'; | |||||
const DEFAULT_STATUS: EditionStatus = { | |||||
currentEditionKey: 'foo', | |||||
nextEditionKey: '', | |||||
installationStatus: 'NONE' | |||||
}; | |||||
it('should display the edition boxes', () => { | |||||
const wrapper = getWrapper(); | |||||
expect(wrapper).toMatchSnapshot(); | |||||
wrapper.setState({ | |||||
editions: { | |||||
foo: { | |||||
name: 'Foo', | |||||
desc: 'Foo desc', | |||||
download_link: 'download_url', | |||||
more_link: 'more_url', | |||||
request_license_link: 'license_url' | |||||
}, | |||||
bar: { | |||||
name: 'Bar', | |||||
desc: 'Bar desc', | |||||
download_link: 'download_url', | |||||
more_link: 'more_url', | |||||
request_license_link: 'license_url' | |||||
} | |||||
}, | |||||
loading: false | |||||
}); | |||||
expect(wrapper).toMatchSnapshot(); | |||||
}); | |||||
it('should display an error message', () => { | |||||
const wrapper = getWrapper(); | |||||
wrapper.setState({ loading: false, editionsError: true }); | |||||
expect(wrapper).toMatchSnapshot(); | |||||
}); | |||||
function getWrapper(props = {}) { | |||||
return shallow( | |||||
<EditionBoxes editionStatus={DEFAULT_STATUS} updateCenterActive={true} {...props} /> | |||||
); | |||||
} |
expect(getWrapper()).toMatchSnapshot(); | expect(getWrapper()).toMatchSnapshot(); | ||||
}); | }); | ||||
it('should not display nothing', () => { | |||||
it('should not display anything', () => { | |||||
expect(getWrapper({ pending: { installing: [], updating: [], removing: [] } })).toMatchSnapshot(); | expect(getWrapper({ pending: { installing: [], updating: [], removing: [] } })).toMatchSnapshot(); | ||||
}); | }); | ||||
it('should open the restart form', () => { | it('should open the restart form', () => { | ||||
const wrapper = getWrapper(); | const wrapper = getWrapper(); | ||||
click(wrapper.find('.js-restart')); | click(wrapper.find('.js-restart')); | ||||
expect(wrapper.find('RestartForm')).toHaveLength(1); | |||||
expect(wrapper.find('RestartForm').exists()).toBeTruthy(); | |||||
}); | }); | ||||
it('should cancel all pending and refresh them', async () => { | it('should cancel all pending and refresh them', async () => { |
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||||
exports[`should display an error message 1`] = ` | |||||
<div | |||||
className="spacer-bottom marketplace-editions" | |||||
> | |||||
<span | |||||
className="alert alert-info" | |||||
> | |||||
<FormattedMessage | |||||
defaultMessage="marketplace.editions_unavailable" | |||||
id="marketplace.editions_unavailable" | |||||
values={ | |||||
Object { | |||||
"url": <a | |||||
href="https://www.sonarsource.com" | |||||
target="_blank" | |||||
> | |||||
SonarSource.com | |||||
</a>, | |||||
} | |||||
} | |||||
/> | |||||
</span> | |||||
</div> | |||||
`; | |||||
exports[`should display the edition boxes 1`] = `null`; | |||||
exports[`should display the edition boxes 2`] = ` | |||||
<div | |||||
className="spacer-bottom marketplace-editions" | |||||
> | |||||
<EditionBox | |||||
edition={ | |||||
Object { | |||||
"desc": "Foo desc", | |||||
"download_link": "download_url", | |||||
"more_link": "more_url", | |||||
"name": "Foo", | |||||
"request_license_link": "license_url", | |||||
} | |||||
} | |||||
editionKey="foo" | |||||
editionStatus={ | |||||
Object { | |||||
"currentEditionKey": "foo", | |||||
"installationStatus": "NONE", | |||||
"nextEditionKey": "", | |||||
} | |||||
} | |||||
/> | |||||
<EditionBox | |||||
edition={ | |||||
Object { | |||||
"desc": "Bar desc", | |||||
"download_link": "download_url", | |||||
"more_link": "more_url", | |||||
"name": "Bar", | |||||
"request_license_link": "license_url", | |||||
} | |||||
} | |||||
editionKey="bar" | |||||
editionStatus={ | |||||
Object { | |||||
"currentEditionKey": "foo", | |||||
"installationStatus": "NONE", | |||||
"nextEditionKey": "", | |||||
} | |||||
} | |||||
/> | |||||
</div> | |||||
`; |
</div> | </div> | ||||
`; | `; | ||||
exports[`should not display nothing 1`] = `null`; | |||||
exports[`should not display anything 1`] = `null`; |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2017 SonarSource SA | |||||
* mailto:info 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 * as React from 'react'; | |||||
import CheckIcon from '../../../components/icons-components/CheckIcon'; | |||||
import { Edition, EditionStatus } from '../../../api/marketplace'; | |||||
import { translate } from '../../../helpers/l10n'; | |||||
export interface Props { | |||||
edition: Edition; | |||||
editionKey: string; | |||||
editionStatus?: EditionStatus; | |||||
} | |||||
export default class EditionBox extends React.PureComponent<Props> { | |||||
render() { | |||||
const { edition, editionKey, editionStatus } = this.props; | |||||
const isInstalled = editionStatus && editionStatus.currentEditionKey === editionKey; | |||||
const isInstalling = editionStatus && editionStatus.nextEditionKey === editionKey; | |||||
const installInProgress = | |||||
editionStatus && editionStatus.installationStatus === 'AUTOMATIC_IN_PROGRESS'; | |||||
return ( | |||||
<div className="boxed-group boxed-group-inner marketplace-edition"> | |||||
{isInstalled && | |||||
!isInstalling && ( | |||||
<span className="marketplace-edition-badge badge badge-normal-size"> | |||||
<CheckIcon size={14} className="little-spacer-right text-text-top" /> | |||||
{translate('marketplace.installed')} | |||||
</span> | |||||
)} | |||||
{isInstalling && ( | |||||
<span className="marketplace-edition-badge badge badge-normal-size"> | |||||
{translate('marketplace.installing')} | |||||
</span> | |||||
)} | |||||
<div> | |||||
<h3 className="spacer-bottom">{edition.name}</h3> | |||||
<p>{edition.desc}</p> | |||||
</div> | |||||
<div className="marketplace-edition-action spacer-top"> | |||||
<a href={edition.more_link} target="_blank"> | |||||
{translate('marketplace.learn_more')} | |||||
</a> | |||||
{!isInstalled && ( | |||||
<button disabled={installInProgress}>{translate('marketplace.install')}</button> | |||||
)} | |||||
{isInstalled && ( | |||||
<button className="button-red" disabled={installInProgress}> | |||||
{translate('marketplace.uninstall')} | |||||
</button> | |||||
)} | |||||
</div> | |||||
</div> | |||||
); | |||||
} | |||||
} |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2017 SonarSource SA | |||||
* mailto:info 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 * as React from 'react'; | |||||
import { shallow } from 'enzyme'; | |||||
import { Edition, EditionStatus } from '../../../../api/marketplace'; | |||||
import EditionBox from '../EditionBox'; | |||||
const DEFAULT_STATUS: EditionStatus = { | |||||
currentEditionKey: '', | |||||
nextEditionKey: '', | |||||
installationStatus: 'NONE' | |||||
}; | |||||
const DEFAULT_EDITION: Edition = { | |||||
name: 'Foo', | |||||
desc: 'Foo desc', | |||||
download_link: 'download_url', | |||||
more_link: 'more_url', | |||||
request_license_link: 'license_url' | |||||
}; | |||||
it('should display the edition', () => { | |||||
expect(getWrapper()).toMatchSnapshot(); | |||||
}); | |||||
it('should display installed badge', () => { | |||||
expect( | |||||
getWrapper({ | |||||
editionStatus: { | |||||
currentEditionKey: 'foo', | |||||
nextEditionKey: '', | |||||
installationStatus: 'NONE' | |||||
} | |||||
}) | |||||
).toMatchSnapshot(); | |||||
}); | |||||
it('should display installing badge', () => { | |||||
expect( | |||||
getWrapper({ | |||||
editionStatus: { | |||||
currentEditionKey: 'foo', | |||||
nextEditionKey: 'foo', | |||||
installationStatus: 'NONE' | |||||
} | |||||
}) | |||||
).toMatchSnapshot(); | |||||
}); | |||||
it('should disable install button', () => { | |||||
expect( | |||||
getWrapper({ | |||||
editionStatus: { | |||||
currentEditionKey: 'foo', | |||||
nextEditionKey: '', | |||||
installationStatus: 'AUTOMATIC_IN_PROGRESS' | |||||
} | |||||
}) | |||||
).toMatchSnapshot(); | |||||
}); | |||||
it('should disable uninstall button', () => { | |||||
expect( | |||||
getWrapper({ | |||||
editionStatus: { | |||||
currentEditionKey: '', | |||||
nextEditionKey: 'foo', | |||||
installationStatus: 'AUTOMATIC_IN_PROGRESS' | |||||
} | |||||
}) | |||||
).toMatchSnapshot(); | |||||
}); | |||||
function getWrapper(props = {}) { | |||||
return shallow( | |||||
<EditionBox | |||||
edition={DEFAULT_EDITION} | |||||
editionKey="foo" | |||||
editionStatus={DEFAULT_STATUS} | |||||
{...props} | |||||
/> | |||||
); | |||||
} |
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||||
exports[`should disable install button 1`] = ` | |||||
<div | |||||
className="boxed-group boxed-group-inner marketplace-edition" | |||||
> | |||||
<span | |||||
className="marketplace-edition-badge badge badge-normal-size" | |||||
> | |||||
<CheckIcon | |||||
className="little-spacer-right text-text-top" | |||||
size={14} | |||||
/> | |||||
marketplace.installed | |||||
</span> | |||||
<div> | |||||
<h3 | |||||
className="spacer-bottom" | |||||
> | |||||
Foo | |||||
</h3> | |||||
<p> | |||||
Foo desc | |||||
</p> | |||||
</div> | |||||
<div | |||||
className="marketplace-edition-action spacer-top" | |||||
> | |||||
<a | |||||
href="more_url" | |||||
target="_blank" | |||||
> | |||||
marketplace.learn_more | |||||
</a> | |||||
<button | |||||
className="button-red" | |||||
disabled={true} | |||||
> | |||||
marketplace.uninstall | |||||
</button> | |||||
</div> | |||||
</div> | |||||
`; | |||||
exports[`should disable uninstall button 1`] = ` | |||||
<div | |||||
className="boxed-group boxed-group-inner marketplace-edition" | |||||
> | |||||
<span | |||||
className="marketplace-edition-badge badge badge-normal-size" | |||||
> | |||||
marketplace.installing | |||||
</span> | |||||
<div> | |||||
<h3 | |||||
className="spacer-bottom" | |||||
> | |||||
Foo | |||||
</h3> | |||||
<p> | |||||
Foo desc | |||||
</p> | |||||
</div> | |||||
<div | |||||
className="marketplace-edition-action spacer-top" | |||||
> | |||||
<a | |||||
href="more_url" | |||||
target="_blank" | |||||
> | |||||
marketplace.learn_more | |||||
</a> | |||||
<button | |||||
disabled={true} | |||||
> | |||||
marketplace.install | |||||
</button> | |||||
</div> | |||||
</div> | |||||
`; | |||||
exports[`should display installed badge 1`] = ` | |||||
<div | |||||
className="boxed-group boxed-group-inner marketplace-edition" | |||||
> | |||||
<span | |||||
className="marketplace-edition-badge badge badge-normal-size" | |||||
> | |||||
<CheckIcon | |||||
className="little-spacer-right text-text-top" | |||||
size={14} | |||||
/> | |||||
marketplace.installed | |||||
</span> | |||||
<div> | |||||
<h3 | |||||
className="spacer-bottom" | |||||
> | |||||
Foo | |||||
</h3> | |||||
<p> | |||||
Foo desc | |||||
</p> | |||||
</div> | |||||
<div | |||||
className="marketplace-edition-action spacer-top" | |||||
> | |||||
<a | |||||
href="more_url" | |||||
target="_blank" | |||||
> | |||||
marketplace.learn_more | |||||
</a> | |||||
<button | |||||
className="button-red" | |||||
disabled={false} | |||||
> | |||||
marketplace.uninstall | |||||
</button> | |||||
</div> | |||||
</div> | |||||
`; | |||||
exports[`should display installing badge 1`] = ` | |||||
<div | |||||
className="boxed-group boxed-group-inner marketplace-edition" | |||||
> | |||||
<span | |||||
className="marketplace-edition-badge badge badge-normal-size" | |||||
> | |||||
marketplace.installing | |||||
</span> | |||||
<div> | |||||
<h3 | |||||
className="spacer-bottom" | |||||
> | |||||
Foo | |||||
</h3> | |||||
<p> | |||||
Foo desc | |||||
</p> | |||||
</div> | |||||
<div | |||||
className="marketplace-edition-action spacer-top" | |||||
> | |||||
<a | |||||
href="more_url" | |||||
target="_blank" | |||||
> | |||||
marketplace.learn_more | |||||
</a> | |||||
<button | |||||
className="button-red" | |||||
disabled={false} | |||||
> | |||||
marketplace.uninstall | |||||
</button> | |||||
</div> | |||||
</div> | |||||
`; | |||||
exports[`should display the edition 1`] = ` | |||||
<div | |||||
className="boxed-group boxed-group-inner marketplace-edition" | |||||
> | |||||
<div> | |||||
<h3 | |||||
className="spacer-bottom" | |||||
> | |||||
Foo | |||||
</h3> | |||||
<p> | |||||
Foo desc | |||||
</p> | |||||
</div> | |||||
<div | |||||
className="marketplace-edition-action spacer-top" | |||||
> | |||||
<a | |||||
href="more_url" | |||||
target="_blank" | |||||
> | |||||
marketplace.learn_more | |||||
</a> | |||||
<button | |||||
disabled={false} | |||||
> | |||||
marketplace.install | |||||
</button> | |||||
</div> | |||||
</div> | |||||
`; |
.marketplace-editions { | |||||
display: flex; | |||||
flex-direction: row; | |||||
justify-content: space-between; | |||||
margin-left: -8px; | |||||
margin-right: -8px; | |||||
} | |||||
.marketplace-edition { | |||||
position: relative; | |||||
flex: 1; | |||||
display: flex; | |||||
flex-direction: column; | |||||
justify-content: space-between; | |||||
background-color: #f3f3f3; | |||||
margin-left: 8px; | |||||
margin-right: 8px; | |||||
} | |||||
.marketplace-edition-badge { | |||||
position: absolute; | |||||
right: -1px; | |||||
top: 16px; | |||||
padding: 4px 8px; | |||||
border-radius: 2px 0 0 2px; | |||||
} | |||||
.marketplace-edition-action { | |||||
display: flex; | |||||
align-items: baseline; | |||||
justify-content: space-between; | |||||
} |
constructor(private url: string, private options: { method?: string } = {}) {} | constructor(private url: string, private options: { method?: string } = {}) {} | ||||
submit(): Promise<Response> { | |||||
getSubmitData(customHeaders: any = {}): { url: string; options: RequestInit } { | |||||
let url = this.url; | let url = this.url; | ||||
const options: RequestInit = { ...DEFAULT_OPTIONS, ...this.options }; | const options: RequestInit = { ...DEFAULT_OPTIONS, ...this.options }; | ||||
const customHeaders: any = {}; | |||||
if (this.data) { | if (this.data) { | ||||
if (this.data instanceof FormData) { | if (this.data instanceof FormData) { | ||||
options.headers = { | options.headers = { | ||||
...DEFAULT_HEADERS, | ...DEFAULT_HEADERS, | ||||
...customHeaders, | |||||
...getCSRFToken() | |||||
...customHeaders | |||||
}; | }; | ||||
return { url, options }; | |||||
} | |||||
submit(): Promise<Response> { | |||||
const { url, options } = this.getSubmitData({ ...getCSRFToken() }); | |||||
return window.fetch((window as any).baseUrl + url, options); | return window.fetch((window as any).baseUrl + url, options); | ||||
} | } | ||||
return new Request(url); | return new Request(url); | ||||
} | } | ||||
/** | |||||
* Make a cors request | |||||
*/ | |||||
export function corsRequest(url: string, mode: RequestMode = 'cors'): Request { | |||||
const options: RequestInit = { mode }; | |||||
const request = new Request(url, options); | |||||
request.submit = function() { | |||||
const { url, options } = this.getSubmitData(); | |||||
return window.fetch(url, options); | |||||
}; | |||||
return request; | |||||
} | |||||
/** | /** | ||||
* Check that response status is ok | * Check that response status is ok | ||||
*/ | */ |
* along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
import { Extension } from '../../app/types'; | |||||
import { EditionStatus } from '../../api/marketplace'; | |||||
interface AppState { | interface AppState { | ||||
adminPages?: any[]; | |||||
adminPages?: Extension[]; | |||||
authenticationError: boolean; | authenticationError: boolean; | ||||
authorizationError: boolean; | authorizationError: boolean; | ||||
editionStatus?: EditionStatus; | |||||
organizationsEnabled: boolean; | organizationsEnabled: boolean; | ||||
qualifiers?: string[]; | qualifiers?: string[]; | ||||
} | } | ||||
interface SetAdminPagesAction { | interface SetAdminPagesAction { | ||||
type: 'SET_ADMIN_PAGES'; | type: 'SET_ADMIN_PAGES'; | ||||
adminPages: any[]; | |||||
adminPages: Extension[]; | |||||
} | |||||
interface SetEditionStatusAction { | |||||
type: 'SET_EDITION_STATUS'; | |||||
editionStatus: EditionStatus; | |||||
} | } | ||||
interface RequireAuthorizationAction { | interface RequireAuthorizationAction { | ||||
type: 'REQUIRE_AUTHORIZATION'; | type: 'REQUIRE_AUTHORIZATION'; | ||||
} | } | ||||
export type Action = SetAppStateAction | SetAdminPagesAction | RequireAuthorizationAction; | |||||
export type Action = | |||||
| SetAppStateAction | |||||
| SetAdminPagesAction | |||||
| SetEditionStatusAction | |||||
| RequireAuthorizationAction; | |||||
export function setAppState(appState: AppState): SetAppStateAction { | export function setAppState(appState: AppState): SetAppStateAction { | ||||
return { | return { | ||||
}; | }; | ||||
} | } | ||||
export function setAdminPages(adminPages: any[]): SetAdminPagesAction { | |||||
export function setAdminPages(adminPages: Extension[]): SetAdminPagesAction { | |||||
return { type: 'SET_ADMIN_PAGES', adminPages }; | return { type: 'SET_ADMIN_PAGES', adminPages }; | ||||
} | } | ||||
return { type: 'REQUIRE_AUTHORIZATION' }; | return { type: 'REQUIRE_AUTHORIZATION' }; | ||||
} | } | ||||
export function setEditionStatus(editionStatus: EditionStatus): SetEditionStatusAction { | |||||
return { type: 'SET_EDITION_STATUS', editionStatus }; | |||||
} | |||||
const defaultValue: AppState = { | const defaultValue: AppState = { | ||||
authenticationError: false, | authenticationError: false, | ||||
authorizationError: false, | authorizationError: false, | ||||
return { ...state, adminPages: action.adminPages }; | return { ...state, adminPages: action.adminPages }; | ||||
} | } | ||||
if (action.type === 'SET_EDITION_STATUS') { | |||||
return { ...state, editionStatus: action.editionStatus }; | |||||
} | |||||
if (action.type === 'REQUIRE_AUTHORIZATION') { | if (action.type === 'REQUIRE_AUTHORIZATION') { | ||||
return { ...state, authorizationError: true }; | return { ...state, authorizationError: true }; | ||||
} | } |
marketplace.system_upgrades=System Upgrades | marketplace.system_upgrades=System Upgrades | ||||
marketplace.install=Install | marketplace.install=Install | ||||
marketplace.installed=Installed | marketplace.installed=Installed | ||||
marketplace.installing=Installing... | |||||
marketplace._installed=installed | marketplace._installed=installed | ||||
marketplace.available_under_commercial_license=Available under our commercial editions | marketplace.available_under_commercial_license=Available under our commercial editions | ||||
marketplace.learn_more=Learn more | marketplace.learn_more=Learn more | ||||
marketplace.uninstall=Uninstall | marketplace.uninstall=Uninstall | ||||
marketplace.i_accept_the=I accept the | marketplace.i_accept_the=I accept the | ||||
marketplace.terms_and_conditions=Terms and Conditions | marketplace.terms_and_conditions=Terms and Conditions | ||||
marketplace.editions_unavailable=Explore our Editions: advanced feature packs brought to you by SonarSource on {url} | |||||
marketplace.status.AUTOMATIC_IN_PROGRESS=Updating your installation... Please wait... | |||||
marketplace.status.AUTOMATIC_READY=New installation complete. Please restart Server to benefit from it. | |||||
marketplace.status.MANUAL_IN_PROGRESS=Can't install Developer Edition because of internet access issue. Please manually install the package in your SonarQube's plugins folder. | |||||
marketplace.status.AUTOMATIC_FAILURE=Can't install Developer Edition. Please manually install the package in your SonarQube's plugins folder. | |||||
marketplace.how_to_install=How to install it? | |||||
#------------------------------------------------------------------------------ | #------------------------------------------------------------------------------ | ||||
# | # |