* SONAR-10699 Remove upgrade/downgrade buttons * SONAR-10697 Drop edition.json support in the marketplace * SONAR-10717 Drop 'sonar.editions.jsonUrl' property * SONAR-10702 Edition's "Learn more" links redirect to the form page with arguments * SONAR-10698 Get Edition data from the doc * SONAR-10700 Remove LicenseEditionForm and LicenseEditionSet from Marketplacetags/7.5
@@ -121,7 +121,7 @@ public class ComputeEngineContainerImplTest { | |||
+ 27 // level 1 | |||
+ 55 // content of DaoModule | |||
+ 3 // content of EsModule | |||
+ 59 // content of CorePropertyDefinitions | |||
+ 58 // content of CorePropertyDefinitions | |||
+ 1 // StopFlagContainer | |||
); | |||
assertThat( |
@@ -0,0 +1,3 @@ | |||
### Community Edition | |||
Comes with support for 9 programming languages, numerous plugins, integration with DevOps tool chains, and ability to connect to SonarLint in the IDE. |
@@ -0,0 +1,3 @@ | |||
### Data Center Edition | |||
Enterprise Edition + component redundancy and data integrity |
@@ -0,0 +1,3 @@ | |||
### Developer Edition | |||
Community Edition + branch analysis, SonarLint push notifications, and 16 languages. |
@@ -0,0 +1,3 @@ | |||
### Enterprise Edition | |||
Developer Edition + portfolio management, executive reporting, parallel processing of analysis reports and 20 languages. |
@@ -45,7 +45,6 @@ import org.sonar.server.ui.VersionFormatter; | |||
import org.sonar.server.user.UserSession; | |||
import static org.sonar.api.CoreProperties.RATING_GRID; | |||
import static org.sonar.core.config.CorePropertyDefinitions.EDITIONS_CONFIG_URL; | |||
import static org.sonar.core.config.WebConstants.SONAR_LF_ENABLE_GRAVATAR; | |||
import static org.sonar.core.config.WebConstants.SONAR_LF_GRAVATAR_SERVER_URL; | |||
import static org.sonar.core.config.WebConstants.SONAR_LF_LOGO_URL; | |||
@@ -59,7 +58,6 @@ public class GlobalAction implements NavigationWsAction, Startable { | |||
SONAR_LF_LOGO_WIDTH_PX, | |||
SONAR_LF_ENABLE_GRAVATAR, | |||
SONAR_LF_GRAVATAR_SERVER_URL, | |||
EDITIONS_CONFIG_URL, | |||
RATING_GRID); | |||
private final Map<String, String> systemSettingValuesByKey; |
@@ -105,7 +105,6 @@ public class GlobalActionTest { | |||
settings.setProperty("sonar.lf.gravatarServerUrl", "https://secure.gravatar.com/avatar/{EMAIL_MD5}.jpg?s={SIZE}&d=identicon"); | |||
settings.setProperty("sonar.lf.enableGravatar", true); | |||
settings.setProperty("sonar.updatecenter.activate", false); | |||
settings.setProperty("sonar.editions.jsonUrl", "https://foo.bar/editions.json"); | |||
settings.setProperty("sonar.technicalDebt.ratingGrid", "0.05,0.1,0.2,0.5"); | |||
// This setting should be ignored as it's not needed | |||
settings.setProperty("sonar.defaultGroup", "sonar-users"); | |||
@@ -117,7 +116,6 @@ public class GlobalActionTest { | |||
" \"sonar.lf.logoWidthPx\": \"135\"," + | |||
" \"sonar.lf.gravatarServerUrl\": \"https://secure.gravatar.com/avatar/{EMAIL_MD5}.jpg?s={SIZE}&d=identicon\"," + | |||
" \"sonar.lf.enableGravatar\": \"true\"," + | |||
" \"sonar.editions.jsonUrl\": \"https://foo.bar/editions.json\"," + | |||
" \"sonar.updatecenter.activate\": \"false\"," + | |||
" \"sonar.technicalDebt.ratingGrid\": \"0.05,0.1,0.2,0.5\"" + | |||
" }" + |
@@ -17,45 +17,17 @@ | |||
* 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, post, postJSON } from '../helpers/request'; | |||
import { getJSON, postJSON } from '../helpers/request'; | |||
import throwGlobalError from '../app/utils/throwGlobalError'; | |||
export interface Edition { | |||
key: string; | |||
name: string; | |||
textDescription: string; | |||
homeUrl: string; | |||
licenseRequestUrl: string; | |||
downloadUrl: string; | |||
} | |||
export interface EditionsPerVersion { | |||
[version: string]: Edition[]; | |||
} | |||
export interface EditionStatus { | |||
currentEditionKey?: string; | |||
nextEditionKey?: string; | |||
installError?: string; | |||
installationStatus: | |||
| 'NONE' | |||
| 'AUTOMATIC_IN_PROGRESS' | |||
| 'MANUAL_IN_PROGRESS' | |||
| 'AUTOMATIC_READY' | |||
| 'UNINSTALL_IN_PROGRESS'; | |||
} | |||
export function getEditionStatus(): Promise<EditionStatus> { | |||
return getJSON('/api/editions/status'); | |||
} | |||
export function getEditionsList(url: string): Promise<EditionsPerVersion> { | |||
return corsRequest(url) | |||
.submit() | |||
.then(checkStatus) | |||
.then(parseJSON); | |||
} | |||
export function getLicensePreview(data: { | |||
license: string; | |||
}): Promise<{ | |||
@@ -72,11 +44,3 @@ export function getFormData(): Promise<{ serverId: string; ncloc: number }> { | |||
export function applyLicense(data: { license: string }): Promise<EditionStatus> { | |||
return postJSON('/api/editions/apply_license', data).catch(throwGlobalError); | |||
} | |||
export function uninstallEdition(): Promise<void | Response> { | |||
return post('/api/editions/uninstall').catch(throwGlobalError); | |||
} | |||
export function dismissErrorMessage(): Promise<void | Response> { | |||
return post('/api/editions/clear_error_message').catch(throwGlobalError); | |||
} |
@@ -22,20 +22,10 @@ import * as PropTypes from 'prop-types'; | |||
import Helmet from 'react-helmet'; | |||
import { connect } from 'react-redux'; | |||
import SettingsNav from './nav/settings/SettingsNav'; | |||
import { | |||
getAppState, | |||
getGlobalSettingValue, | |||
getMarketplaceEditionStatus, | |||
getMarketplacePendingPlugins | |||
} from '../../store/rootReducer'; | |||
import { getAppState, getMarketplacePendingPlugins } from '../../store/rootReducer'; | |||
import { getSettingsNavigation } from '../../api/nav'; | |||
import { EditionStatus, getEditionStatus } from '../../api/marketplace'; | |||
import { setAdminPages } from '../../store/appState/duck'; | |||
import { | |||
fetchEditions, | |||
setEditionStatus, | |||
fetchPendingPlugins | |||
} from '../../store/marketplace/actions'; | |||
import { fetchCurrentEdition, fetchPendingPlugins } from '../../store/marketplace/actions'; | |||
import { translate } from '../../helpers/l10n'; | |||
import { Extension } from '../types'; | |||
import { PluginPendingResult } from '../../api/plugins'; | |||
@@ -47,23 +37,22 @@ interface StateProps { | |||
organizationsEnabled: boolean; | |||
version: string; | |||
}; | |||
editionStatus?: EditionStatus; | |||
editionsUrl: string; | |||
pendingPlugins: PluginPendingResult; | |||
} | |||
interface DispatchProps { | |||
fetchEditions: (url: string, version: string) => void; | |||
interface DispatchToProps { | |||
fetchPendingPlugins: () => void; | |||
fetchCurrentEdition: () => void; | |||
setAdminPages: (adminPages: Extension[]) => void; | |||
setEditionStatus: (editionStatus: EditionStatus) => void; | |||
} | |||
interface OwnProps { | |||
location: {}; | |||
} | |||
class AdminContainer extends React.PureComponent<StateProps & DispatchProps & OwnProps> { | |||
type Props = StateProps & DispatchToProps & OwnProps; | |||
class AdminContainer extends React.PureComponent<Props> { | |||
static contextTypes = { | |||
canAdmin: PropTypes.bool.isRequired | |||
}; | |||
@@ -73,17 +62,13 @@ class AdminContainer extends React.PureComponent<StateProps & DispatchProps & Ow | |||
handleRequiredAuthorization(); | |||
} else { | |||
this.fetchNavigationSettings(); | |||
this.props.fetchEditions(this.props.editionsUrl, this.props.appState.version); | |||
this.fetchEditionStatus(); | |||
this.props.fetchCurrentEdition(); | |||
} | |||
} | |||
fetchNavigationSettings = () => | |||
getSettingsNavigation().then(r => this.props.setAdminPages(r.extensions), () => {}); | |||
fetchEditionStatus = () => | |||
getEditionStatus().then(editionStatus => this.props.setEditionStatus(editionStatus), () => {}); | |||
render() { | |||
const { adminPages, organizationsEnabled } = this.props.appState; | |||
@@ -98,7 +83,6 @@ class AdminContainer extends React.PureComponent<StateProps & DispatchProps & Ow | |||
<div> | |||
<Helmet defaultTitle={defaultTitle} titleTemplate={'%s - ' + defaultTitle} /> | |||
<SettingsNav | |||
editionStatus={this.props.editionStatus} | |||
extensions={adminPages} | |||
fetchPendingPlugins={this.props.fetchPendingPlugins} | |||
location={this.props.location} | |||
@@ -113,16 +97,15 @@ class AdminContainer extends React.PureComponent<StateProps & DispatchProps & Ow | |||
const mapStateToProps = (state: any): StateProps => ({ | |||
appState: getAppState(state), | |||
editionStatus: getMarketplaceEditionStatus(state), | |||
editionsUrl: (getGlobalSettingValue(state, 'sonar.editions.jsonUrl') || {}).value, | |||
pendingPlugins: getMarketplacePendingPlugins(state) | |||
}); | |||
const mapDispatchToProps: DispatchProps = { | |||
setAdminPages, | |||
setEditionStatus, | |||
fetchEditions, | |||
fetchPendingPlugins | |||
const mapDispatchToProps: DispatchToProps = { | |||
fetchCurrentEdition, | |||
fetchPendingPlugins, | |||
setAdminPages | |||
}; | |||
export default connect(mapStateToProps, mapDispatchToProps)(AdminContainer); | |||
export default connect<StateProps, DispatchToProps, OwnProps>(mapStateToProps, mapDispatchToProps)( | |||
AdminContainer | |||
); |
@@ -81,7 +81,7 @@ export default class ComponentNav extends React.PureComponent<Props> { | |||
} | |||
return ( | |||
<ContextNavBar | |||
height={notifComponent ? theme.contextNavHeightRaw + 20 : theme.contextNavHeightRaw} | |||
height={notifComponent ? theme.contextNavHeightRaw + 30 : theme.contextNavHeightRaw} | |||
id="context-navigation" | |||
notif={notifComponent}> | |||
<div className="navbar-context-justified"> |
@@ -1,179 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 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 NavBarNotif from '../../../../components/nav/NavBarNotif'; | |||
import RestartForm from '../../../../components/common/RestartForm'; | |||
import { Button } from '../../../../components/ui/buttons'; | |||
import { dismissErrorMessage, Edition, EditionStatus } from '../../../../api/marketplace'; | |||
import { translate, translateWithParameters } from '../../../../helpers/l10n'; | |||
interface Props { | |||
editions?: Edition[]; | |||
editionStatus: EditionStatus; | |||
preventRestart: boolean; | |||
setEditionStatus: (editionStatus: EditionStatus) => void; | |||
} | |||
interface State { | |||
openRestart: boolean; | |||
} | |||
export default class SettingsEditionsNotif extends React.PureComponent<Props, State> { | |||
state: State = { openRestart: false }; | |||
handleOpenRestart = () => this.setState({ openRestart: true }); | |||
hanleCloseRestart = () => this.setState({ openRestart: false }); | |||
handleDismissError = () => | |||
dismissErrorMessage().then( | |||
() => this.props.setEditionStatus({ ...this.props.editionStatus, installError: undefined }), | |||
() => {} | |||
); | |||
renderStatusMsg(edition?: Edition) { | |||
const { editionStatus } = this.props; | |||
return ( | |||
<NavBarNotif className="alert alert-info"> | |||
<i className="spinner spacer-right" /> | |||
<span> | |||
{edition | |||
? translateWithParameters( | |||
'marketplace.edition_status_x.' + editionStatus.installationStatus, | |||
edition.name | |||
) | |||
: translate('marketplace.edition_status', editionStatus.installationStatus)} | |||
</span> | |||
</NavBarNotif> | |||
); | |||
} | |||
renderRestartMsg(edition?: Edition) { | |||
const { editionStatus, preventRestart } = this.props; | |||
return ( | |||
<NavBarNotif className="alert alert-success"> | |||
<span> | |||
{edition | |||
? translateWithParameters( | |||
'marketplace.edition_status_x.' + editionStatus.installationStatus, | |||
edition.name | |||
) | |||
: translate('marketplace.edition_status', editionStatus.installationStatus)} | |||
</span> | |||
{edition && | |||
edition.key === 'datacenter' && ( | |||
<span className="little-spacer-left"> | |||
<FormattedMessage | |||
defaultMessage={translate('marketplace.see_documentation_to_enable_cluster')} | |||
id="marketplace.see_documentation_to_enable_cluster" | |||
values={{ | |||
url: ( | |||
<a | |||
href="https://redirect.sonarsource.com/doc/data-center-edition.html" | |||
rel="noopener noreferrer" | |||
target="_blank"> | |||
{edition.name} | |||
</a> | |||
) | |||
}} | |||
/> | |||
</span> | |||
)} | |||
{!preventRestart && ( | |||
<Button className="js-restart spacer-left" onClick={this.handleOpenRestart}> | |||
{translate('marketplace.restart')} | |||
</Button> | |||
)} | |||
{!preventRestart && | |||
this.state.openRestart && <RestartForm onClose={this.hanleCloseRestart} />} | |||
</NavBarNotif> | |||
); | |||
} | |||
renderManualMsg(edition?: Edition) { | |||
const { editionStatus } = this.props; | |||
return ( | |||
<NavBarNotif className="alert alert-danger"> | |||
{edition | |||
? translateWithParameters( | |||
'marketplace.edition_status_x.' + editionStatus.installationStatus, | |||
edition.name | |||
) | |||
: translate('marketplace.edition_status', editionStatus.installationStatus)} | |||
<a | |||
className="spacer-left" | |||
href={ | |||
edition && edition.key === 'datacenter' | |||
? 'https://redirect.sonarsource.com/doc/data-center-edition.html' | |||
: 'https://redirect.sonarsource.com/doc/how-to-install-an-edition.html' | |||
} | |||
target="_blank"> | |||
{translate('marketplace.how_to_install')} | |||
</a> | |||
{edition && ( | |||
<a | |||
className="button spacer-left" | |||
download={`sonarqube-${edition.name}.zip`} | |||
href={edition.downloadUrl} | |||
target="_blank"> | |||
{translate('marketplace.download_package')} | |||
</a> | |||
)} | |||
</NavBarNotif> | |||
); | |||
} | |||
renderStatusAlert() { | |||
const { currentEditionKey, installationStatus, nextEditionKey } = this.props.editionStatus; | |||
const nextEdition = | |||
this.props.editions && this.props.editions.find(edition => edition.key === nextEditionKey); | |||
const currentEdition = | |||
this.props.editions && | |||
this.props.editions.find( | |||
edition => | |||
edition.key === currentEditionKey || (!currentEditionKey && edition.key === 'community') | |||
); | |||
switch (installationStatus) { | |||
case 'AUTOMATIC_IN_PROGRESS': | |||
return this.renderStatusMsg(nextEdition); | |||
case 'AUTOMATIC_READY': | |||
return this.renderRestartMsg(nextEdition); | |||
case 'UNINSTALL_IN_PROGRESS': | |||
return this.renderRestartMsg(currentEdition); | |||
case 'MANUAL_IN_PROGRESS': | |||
return this.renderManualMsg(nextEdition); | |||
} | |||
return null; | |||
} | |||
render() { | |||
const { installError } = this.props.editionStatus; | |||
if (installError) { | |||
return ( | |||
<NavBarNotif className="alert alert-danger" onCancel={this.handleDismissError}> | |||
{installError} | |||
</NavBarNotif> | |||
); | |||
} | |||
return this.renderStatusAlert(); | |||
} | |||
} |
@@ -1,49 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 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 { connect } from 'react-redux'; | |||
import SettingsEditionsNotif from './SettingsEditionsNotif'; | |||
import { getAppState, getMarketplaceEditions } from '../../../../store/rootReducer'; | |||
import { Edition, EditionStatus } from '../../../../api/marketplace'; | |||
import { setEditionStatus } from '../../../../store/marketplace/actions'; | |||
interface OwnProps { | |||
editionStatus: EditionStatus; | |||
} | |||
interface StateToProps { | |||
editions?: Edition[]; | |||
preventRestart: boolean; | |||
} | |||
interface DispatchToProps { | |||
setEditionStatus: (editionStatus: EditionStatus) => void; | |||
} | |||
const mapStateToProps = (state: any): StateToProps => ({ | |||
editions: getMarketplaceEditions(state), | |||
preventRestart: !getAppState(state).standalone | |||
}); | |||
const mapDispatchToProps = { setEditionStatus }; | |||
export default connect<StateToProps, DispatchToProps, OwnProps>( | |||
mapStateToProps, | |||
mapDispatchToProps | |||
)(SettingsEditionsNotif); |
@@ -20,20 +20,17 @@ | |||
import * as React from 'react'; | |||
import * as classNames from 'classnames'; | |||
import { IndexLink, Link } from 'react-router'; | |||
import SettingsEditionsNotifContainer from './SettingsEditionsNotifContainer'; | |||
import PendingPluginsActionNotif from './PendingPluginsActionNotif'; | |||
import * as theme from '../../../../app/theme'; | |||
import ContextNavBar from '../../../../components/nav/ContextNavBar'; | |||
import Dropdown from '../../../../components/controls/Dropdown'; | |||
import NavBarTabs from '../../../../components/nav/NavBarTabs'; | |||
import { EditionStatus } from '../../../../api/marketplace'; | |||
import { Extension } from '../../../types'; | |||
import { translate } from '../../../../helpers/l10n'; | |||
import Dropdown from '../../../../components/controls/Dropdown'; | |||
import { PluginPendingResult } from '../../../../api/plugins'; | |||
import DropdownIcon from '../../../../components/icons-components/DropdownIcon'; | |||
import { translate } from '../../../../helpers/l10n'; | |||
interface Props { | |||
editionStatus?: EditionStatus; | |||
extensions: Extension[]; | |||
fetchPendingPlugins: () => void; | |||
location: {}; | |||
@@ -232,23 +229,16 @@ export default class SettingsNav extends React.PureComponent<Props> { | |||
} | |||
render() { | |||
const { editionStatus, extensions, pendingPlugins } = this.props; | |||
const { extensions, pendingPlugins } = this.props; | |||
const hasSupportExtension = extensions.find(extension => extension.key === 'license/support'); | |||
const totalPendingPlugins = | |||
pendingPlugins.installing.length + | |||
pendingPlugins.removing.length + | |||
pendingPlugins.updating.length; | |||
const notifComponents = []; | |||
if ( | |||
editionStatus && | |||
(editionStatus.installError || editionStatus.installationStatus !== 'NONE') | |||
) { | |||
notifComponents.push(<SettingsEditionsNotifContainer editionStatus={editionStatus} />); | |||
} | |||
if ( | |||
pendingPlugins.installing.length > 0 || | |||
pendingPlugins.removing.length > 0 || | |||
pendingPlugins.updating.length > 0 | |||
) { | |||
notifComponents.push( | |||
let notifComponent; | |||
if (totalPendingPlugins > 0) { | |||
notifComponent = ( | |||
<PendingPluginsActionNotif | |||
pending={pendingPlugins} | |||
refreshPending={this.props.fetchPendingPlugins} | |||
@@ -256,18 +246,11 @@ export default class SettingsNav extends React.PureComponent<Props> { | |||
); | |||
} | |||
const notifContainer = | |||
notifComponents.length > 0 ? ( | |||
<div className="alert-container"> | |||
{notifComponents.map((element, index) => <div key={index}>{element}</div>)} | |||
</div> | |||
) : null; | |||
return ( | |||
<ContextNavBar | |||
height={theme.contextNavHeightRaw + 38 * notifComponents.length} | |||
height={notifComponent ? theme.contextNavHeightRaw + 30 : theme.contextNavHeightRaw} | |||
id="context-navigation" | |||
notif={notifContainer}> | |||
notif={notifComponent}> | |||
<header className="navbar-context-header"> | |||
<h1>{translate('layout.settings')}</h1> | |||
</header> |
@@ -1,130 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 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. | |||
*/ | |||
/* eslint-disable import/order */ | |||
import * as React from 'react'; | |||
import { mount, shallow } from 'enzyme'; | |||
import { click } from '../../../../../helpers/testUtils'; | |||
import SettingsEditionsNotif from '../SettingsEditionsNotif'; | |||
jest.mock('../../../../../api/marketplace', () => ({ | |||
dismissErrorMessage: jest.fn(() => Promise.resolve()) | |||
})); | |||
const dismissMsg = require('../../../../../api/marketplace').dismissErrorMessage as jest.Mock<any>; | |||
beforeEach(() => { | |||
dismissMsg.mockClear(); | |||
}); | |||
it('should display an in progress notif', () => { | |||
const wrapper = shallow( | |||
<SettingsEditionsNotif | |||
editionStatus={{ installationStatus: 'AUTOMATIC_IN_PROGRESS' }} | |||
preventRestart={false} | |||
setEditionStatus={jest.fn()} | |||
/> | |||
); | |||
expect(wrapper).toMatchSnapshot(); | |||
}); | |||
it('should display a ready notification', () => { | |||
const wrapper = shallow( | |||
<SettingsEditionsNotif | |||
editionStatus={{ installationStatus: 'AUTOMATIC_READY' }} | |||
preventRestart={false} | |||
setEditionStatus={jest.fn()} | |||
/> | |||
); | |||
expect(wrapper).toMatchSnapshot(); | |||
}); | |||
it('should display a manual installation notification', () => { | |||
const wrapper = shallow( | |||
<SettingsEditionsNotif | |||
editionStatus={{ installationStatus: 'MANUAL_IN_PROGRESS', nextEditionKey: 'foo' }} | |||
editions={[ | |||
{ | |||
key: 'foo', | |||
name: 'Foo', | |||
textDescription: 'Foo desc', | |||
downloadUrl: 'download_url', | |||
homeUrl: 'more_url', | |||
licenseRequestUrl: 'license_url' | |||
} | |||
]} | |||
preventRestart={false} | |||
setEditionStatus={jest.fn()} | |||
/> | |||
); | |||
expect(wrapper).toMatchSnapshot(); | |||
}); | |||
it('should display install errors', () => { | |||
const wrapper = shallow( | |||
<SettingsEditionsNotif | |||
editionStatus={{ installationStatus: 'AUTOMATIC_IN_PROGRESS', installError: 'Foo error' }} | |||
preventRestart={false} | |||
setEditionStatus={jest.fn()} | |||
/> | |||
); | |||
expect(wrapper).toMatchSnapshot(); | |||
}); | |||
it('should allow to dismiss install errors', async () => { | |||
const setEditionStatus = jest.fn(); | |||
const wrapper = mount( | |||
<SettingsEditionsNotif | |||
editionStatus={{ installationStatus: 'NONE', installError: 'Foo error' }} | |||
preventRestart={false} | |||
setEditionStatus={setEditionStatus} | |||
/> | |||
); | |||
click(wrapper.find('button')); | |||
expect(dismissMsg).toHaveBeenCalled(); | |||
await new Promise(setImmediate); | |||
expect(setEditionStatus).toHaveBeenCalledWith({ | |||
installationStatus: 'NONE', | |||
installError: undefined | |||
}); | |||
}); | |||
it('should not display the restart button', () => { | |||
const wrapper = shallow( | |||
<SettingsEditionsNotif | |||
editionStatus={{ installationStatus: 'AUTOMATIC_READY' }} | |||
preventRestart={true} | |||
setEditionStatus={jest.fn()} | |||
/> | |||
); | |||
expect(wrapper.find('button.js-restart').exists()).toBeFalsy(); | |||
}); | |||
it('should have a link to cluster documentation for datacenter edition', () => { | |||
const editions = [{ key: 'datacenter' }] as any; | |||
const wrapper = shallow( | |||
<SettingsEditionsNotif | |||
editions={editions} | |||
editionStatus={{ installationStatus: 'AUTOMATIC_READY', nextEditionKey: 'datacenter' }} | |||
preventRestart={false} | |||
setEditionStatus={jest.fn()} | |||
/> | |||
); | |||
expect(wrapper.find('FormattedMessage').exists()).toBeTruthy(); | |||
}); |
@@ -35,3 +35,28 @@ it('should work with extensions', () => { | |||
expect(wrapper).toMatchSnapshot(); | |||
expect(wrapper.find('Dropdown')).toMatchSnapshot(); | |||
}); | |||
it('should display a pending plugin notif', () => { | |||
const extensions = [{ key: 'foo', name: 'Foo' }]; | |||
const wrapper = shallow( | |||
<SettingsNav | |||
extensions={extensions} | |||
fetchPendingPlugins={() => {}} | |||
location={{}} | |||
organizationsEnabled={false} | |||
pendingPlugins={{ | |||
installing: [ | |||
{ | |||
key: 'foo', | |||
name: 'Foo', | |||
version: '1.0', | |||
implementationBuild: '1' | |||
} | |||
], | |||
removing: [], | |||
updating: [] | |||
}} | |||
/> | |||
); | |||
expect(wrapper.find('ContextNavBar').prop('notif')).toMatchSnapshot(); | |||
}); |
@@ -1,62 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should display a manual installation notification 1`] = ` | |||
<NavBarNotif | |||
className="alert alert-danger" | |||
> | |||
marketplace.edition_status_x.MANUAL_IN_PROGRESS.Foo | |||
<a | |||
className="spacer-left" | |||
href="https://redirect.sonarsource.com/doc/how-to-install-an-edition.html" | |||
target="_blank" | |||
> | |||
marketplace.how_to_install | |||
</a> | |||
<a | |||
className="button spacer-left" | |||
download="sonarqube-Foo.zip" | |||
href="download_url" | |||
target="_blank" | |||
> | |||
marketplace.download_package | |||
</a> | |||
</NavBarNotif> | |||
`; | |||
exports[`should display a ready notification 1`] = ` | |||
<NavBarNotif | |||
className="alert alert-success" | |||
> | |||
<span> | |||
marketplace.edition_status.AUTOMATIC_READY | |||
</span> | |||
<Button | |||
className="js-restart spacer-left" | |||
onClick={[Function]} | |||
> | |||
marketplace.restart | |||
</Button> | |||
</NavBarNotif> | |||
`; | |||
exports[`should display an in progress notif 1`] = ` | |||
<NavBarNotif | |||
className="alert alert-info" | |||
> | |||
<i | |||
className="spinner spacer-right" | |||
/> | |||
<span> | |||
marketplace.edition_status.AUTOMATIC_IN_PROGRESS | |||
</span> | |||
</NavBarNotif> | |||
`; | |||
exports[`should display install errors 1`] = ` | |||
<NavBarNotif | |||
className="alert alert-danger" | |||
onCancel={[Function]} | |||
> | |||
Foo error | |||
</NavBarNotif> | |||
`; |
@@ -1,10 +1,29 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should display a pending plugin notif 1`] = ` | |||
<PendingPluginsActionNotif | |||
pending={ | |||
Object { | |||
"installing": Array [ | |||
Object { | |||
"implementationBuild": "1", | |||
"key": "foo", | |||
"name": "Foo", | |||
"version": "1.0", | |||
}, | |||
], | |||
"removing": Array [], | |||
"updating": Array [], | |||
} | |||
} | |||
refreshPending={[Function]} | |||
/> | |||
`; | |||
exports[`should work with extensions 1`] = ` | |||
<ContextNavBar | |||
height={72} | |||
id="context-navigation" | |||
notif={null} | |||
> | |||
<header | |||
className="navbar-context-header" |
@@ -17,12 +17,6 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
.alert-container .alert { | |||
margin-bottom: 0px; | |||
padding: 0 8px; | |||
line-height: 38px; | |||
} | |||
.alert { | |||
display: block; | |||
margin-bottom: 8px; |
@@ -27,7 +27,6 @@ import DateFromNow from '../../components/intl/DateFromNow'; | |||
import DateFormatter from '../../components/intl/DateFormatter'; | |||
import DateTimeFormatter from '../../components/intl/DateTimeFormatter'; | |||
import FavoriteContainer from '../../components/controls/FavoriteContainer'; | |||
import LicenseEditionSet from '../../apps/marketplace/components/LicenseEditionSet'; | |||
import HomePageSelect from '../../components/controls/HomePageSelect'; | |||
import ListFooter from '../../components/controls/ListFooter'; | |||
import Modal from '../../components/controls/Modal'; | |||
@@ -82,7 +81,6 @@ const exposeLibraries = () => { | |||
HelpTooltip, | |||
HomePageSelect, | |||
Level, | |||
LicenseEditionSet, | |||
ListFooter, | |||
LockIcon, | |||
Modal, |
@@ -36,21 +36,16 @@ import { | |||
PluginPendingResult, | |||
getInstalledPlugins | |||
} from '../../api/plugins'; | |||
import { Edition, EditionStatus } from '../../api/marketplace'; | |||
import { RawQuery } from '../../helpers/query'; | |||
import { translate } from '../../helpers/l10n'; | |||
import './style.css'; | |||
export interface Props { | |||
editions?: Edition[]; | |||
editionsReadOnly: boolean; | |||
editionStatus?: EditionStatus; | |||
currentEdition?: string; | |||
fetchPendingPlugins: () => void; | |||
loadingEditions: boolean; | |||
location: { pathname: string; query: RawQuery }; | |||
pendingPlugins: PluginPendingResult; | |||
standaloneMode: boolean; | |||
setEditionStatus: (editionStatus: EditionStatus) => void; | |||
updateCenterActive: boolean; | |||
} | |||
@@ -66,13 +61,7 @@ export default class App extends React.PureComponent<Props, State> { | |||
router: PropTypes.object.isRequired | |||
}; | |||
constructor(props: Props) { | |||
super(props); | |||
this.state = { | |||
loadingPlugins: true, | |||
plugins: [] | |||
}; | |||
} | |||
state: State = { loadingPlugins: true, plugins: [] }; | |||
componentDidMount() { | |||
this.mounted = true; | |||
@@ -130,7 +119,7 @@ export default class App extends React.PureComponent<Props, State> { | |||
}; | |||
render() { | |||
const { editions, editionStatus, standaloneMode, pendingPlugins } = this.props; | |||
const { currentEdition, standaloneMode, pendingPlugins } = this.props; | |||
const { loadingPlugins, plugins } = this.state; | |||
const query = parseQuery(this.props.location.query); | |||
const filteredPlugins = query.search ? filterPlugins(plugins, query.search) : plugins; | |||
@@ -140,15 +129,7 @@ export default class App extends React.PureComponent<Props, State> { | |||
<Suggestions suggestions="marketplace" /> | |||
<Helmet title={translate('marketplace.page')} /> | |||
<Header /> | |||
<EditionBoxes | |||
canInstall={standaloneMode && !this.props.editionsReadOnly} | |||
canUninstall={standaloneMode} | |||
editionStatus={editionStatus} | |||
editions={editions} | |||
loading={this.props.loadingEditions} | |||
updateCenterActive={this.props.updateCenterActive} | |||
updateEditionStatus={this.props.setEditionStatus} | |||
/> | |||
<EditionBoxes currentEdition={currentEdition} /> | |||
<Search | |||
query={query} | |||
updateCenterActive={this.props.updateCenterActive} |
@@ -22,13 +22,10 @@ import App from './App'; | |||
import { | |||
getAppState, | |||
getGlobalSettingValue, | |||
getMarketplaceState, | |||
getMarketplaceEditions, | |||
getMarketplaceEditionStatus, | |||
getMarketplaceCurrentEdition, | |||
getMarketplacePendingPlugins | |||
} from '../../store/rootReducer'; | |||
import { Edition, EditionStatus } from '../../api/marketplace'; | |||
import { setEditionStatus, fetchPendingPlugins } from '../../store/marketplace/actions'; | |||
import { fetchPendingPlugins } from '../../store/marketplace/actions'; | |||
import { RawQuery } from '../../helpers/query'; | |||
import { PluginPendingResult } from '../../api/plugins'; | |||
@@ -37,32 +34,27 @@ interface OwnProps { | |||
} | |||
interface StateToProps { | |||
editions?: Edition[]; | |||
editionsReadOnly: boolean; | |||
editionStatus?: EditionStatus; | |||
loadingEditions: boolean; | |||
currentEdition?: string; | |||
pendingPlugins: PluginPendingResult; | |||
standaloneMode: boolean; | |||
updateCenterActive: boolean; | |||
} | |||
interface DispatchToProps { | |||
setEditionStatus: (editionStatus: EditionStatus) => void; | |||
fetchPendingPlugins: () => void; | |||
} | |||
const mapStateToProps = (state: any) => ({ | |||
editions: getMarketplaceEditions(state), | |||
editionsReadOnly: getMarketplaceState(state).readOnly, | |||
editionStatus: getMarketplaceEditionStatus(state), | |||
loadingEditions: getMarketplaceState(state).loading, | |||
pendingPlugins: getMarketplacePendingPlugins(state), | |||
standaloneMode: getAppState(state).standalone, | |||
updateCenterActive: | |||
(getGlobalSettingValue(state, 'sonar.updatecenter.activate') || {}).value === 'true' | |||
}); | |||
const mapStateToProps = (state: any) => { | |||
return { | |||
currentEdition: getMarketplaceCurrentEdition(state), | |||
pendingPlugins: getMarketplacePendingPlugins(state), | |||
standaloneMode: getAppState(state).standalone, | |||
updateCenterActive: | |||
(getGlobalSettingValue(state, 'sonar.updatecenter.activate') || {}).value === 'true' | |||
}; | |||
}; | |||
const mapDispatchToProps = { setEditionStatus, fetchPendingPlugins }; | |||
const mapDispatchToProps = { fetchPendingPlugins }; | |||
export default connect<StateToProps, DispatchToProps, OwnProps>( | |||
mapStateToProps, |
@@ -17,144 +17,59 @@ | |||
* 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 LicenseEditionForm from './components/LicenseEditionForm'; | |||
import UninstallEditionForm from './components/UninstallEditionForm'; | |||
import { sortEditions } from './utils'; | |||
import { Edition, EditionStatus } from '../../api/marketplace'; | |||
import { translate } from '../../helpers/l10n'; | |||
import { EDITIONS } from './utils'; | |||
import { getFormData } from '../../api/marketplace'; | |||
export interface Props { | |||
canInstall: boolean; | |||
canUninstall: boolean; | |||
editions?: Edition[]; | |||
editionStatus?: EditionStatus; | |||
loading: boolean; | |||
updateCenterActive: boolean; | |||
updateEditionStatus: (editionStatus: EditionStatus) => void; | |||
currentEdition?: string; | |||
} | |||
interface State { | |||
installEdition?: Edition; | |||
openUninstallForm: boolean; | |||
serverId?: string; | |||
ncloc?: number; | |||
} | |||
export default class EditionBoxes extends React.PureComponent<Props, State> { | |||
state: State = { openUninstallForm: false }; | |||
handleOpenLicenseForm = (edition: Edition) => this.setState({ installEdition: edition }); | |||
handleCloseLicenseForm = () => this.setState({ installEdition: undefined }); | |||
handleOpenUninstallForm = () => this.setState({ openUninstallForm: true }); | |||
handleCloseUninstallForm = () => this.setState({ openUninstallForm: false }); | |||
renderForms(sortedEditions: Edition[], installedIdx?: number) { | |||
const { canInstall, canUninstall, editionStatus } = this.props; | |||
const { installEdition, openUninstallForm } = this.state; | |||
const installEditionIdx = | |||
installEdition && sortedEditions.findIndex(edition => edition.key === installEdition.key); | |||
mounted = false; | |||
state: State = {}; | |||
if (canInstall && installEdition) { | |||
return ( | |||
<LicenseEditionForm | |||
edition={installEdition} | |||
editions={sortedEditions} | |||
isDowngrade={ | |||
installedIdx !== undefined && | |||
installEditionIdx !== undefined && | |||
installEditionIdx < installedIdx | |||
} | |||
onClose={this.handleCloseLicenseForm} | |||
updateEditionStatus={this.props.updateEditionStatus} | |||
/> | |||
); | |||
} | |||
if (canUninstall && openUninstallForm && editionStatus && editionStatus.currentEditionKey) { | |||
return ( | |||
<UninstallEditionForm | |||
edition={sortedEditions.find(edition => edition.key === editionStatus.currentEditionKey)} | |||
editionStatus={editionStatus} | |||
onClose={this.handleCloseUninstallForm} | |||
updateEditionStatus={this.props.updateEditionStatus} | |||
/> | |||
); | |||
} | |||
return null; | |||
componentDidMount() { | |||
this.mounted = true; | |||
this.fetchFormData(); | |||
} | |||
render() { | |||
const { canInstall, canUninstall, editions, loading } = this.props; | |||
if (loading) { | |||
return <i className="big-spacer-bottom spinner" />; | |||
} | |||
if (!editions) { | |||
return ( | |||
<div className="spacer-bottom marketplace-editions"> | |||
<span className="alert alert-info"> | |||
<FormattedMessage | |||
defaultMessage={translate('marketplace.editions_unavailable')} | |||
id="marketplace.editions_unavailable" | |||
values={{ | |||
url: ( | |||
<a href="https://redirect.sonarsource.com/editions/editions.html" target="_blank"> | |||
SonarSource.com | |||
</a> | |||
) | |||
}} | |||
/> | |||
</span> | |||
</div> | |||
); | |||
} | |||
componentWillUnmount() { | |||
this.mounted = false; | |||
} | |||
const sortedEditions = sortEditions(editions); | |||
const status = this.props.editionStatus || { installationStatus: 'NONE' }; | |||
const inProgressStatus = [ | |||
'AUTOMATIC_IN_PROGRESS', | |||
'AUTOMATIC_READY', | |||
'UNINSTALL_IN_PROGRESS' | |||
].includes(status.installationStatus); | |||
const installedIdx = sortedEditions.findIndex( | |||
edition => edition.key === status.currentEditionKey | |||
fetchFormData = () => { | |||
getFormData().then( | |||
formData => { | |||
if (this.mounted) { | |||
this.setState({ ...formData }); | |||
} | |||
}, | |||
() => {} | |||
); | |||
const nextIdx = sortedEditions.findIndex(edition => edition.key === status.nextEditionKey); | |||
const currentIdx = inProgressStatus ? nextIdx : installedIdx; | |||
}; | |||
render() { | |||
const { currentEdition } = this.props; | |||
const { serverId, ncloc } = this.state; | |||
return ( | |||
<div className="spacer-bottom marketplace-editions"> | |||
<EditionBox | |||
actionLabel={translate('marketplace.downgrade')} | |||
disableAction={inProgressStatus} | |||
displayAction={canUninstall && currentIdx > 0} | |||
edition={sortedEditions[0]} | |||
editionStatus={status} | |||
key={sortedEditions[0].key} | |||
onAction={this.handleOpenUninstallForm} | |||
/> | |||
{sortedEditions | |||
.slice(1) | |||
.map((edition, idx) => ( | |||
<EditionBox | |||
actionLabel={ | |||
currentIdx > idx + 1 | |||
? translate('marketplace.downgrade') | |||
: translate('marketplace.upgrade') | |||
} | |||
disableAction={inProgressStatus} | |||
displayAction={canInstall && currentIdx !== idx + 1} | |||
edition={edition} | |||
editionStatus={status} | |||
key={edition.key} | |||
onAction={this.handleOpenLicenseForm} | |||
/> | |||
))} | |||
{this.renderForms(sortedEditions, installedIdx)} | |||
{EDITIONS.map(edition => ( | |||
<EditionBox | |||
currentEdition={currentEdition || 'community'} | |||
edition={edition} | |||
key={edition.key} | |||
ncloc={ncloc} | |||
serverId={serverId} | |||
/> | |||
))} | |||
</div> | |||
); | |||
} |
@@ -20,110 +20,22 @@ | |||
import * as React from 'react'; | |||
import { shallow } from 'enzyme'; | |||
import EditionBoxes from '../EditionBoxes'; | |||
import { EditionStatus } from '../../../api/marketplace'; | |||
const DEFAULT_STATUS: EditionStatus = { | |||
currentEditionKey: 'developer', | |||
nextEditionKey: '', | |||
installationStatus: 'NONE' | |||
}; | |||
const DEFAULT_EDITIONS = [ | |||
{ | |||
key: 'developer', | |||
name: 'Developer Edition', | |||
textDescription: 'foo', | |||
downloadUrl: 'download_url', | |||
homeUrl: 'more_url', | |||
licenseRequestUrl: 'license_url' | |||
}, | |||
{ | |||
key: 'comunity', | |||
name: 'Comunity Edition', | |||
textDescription: 'bar', | |||
downloadUrl: 'download_url', | |||
homeUrl: 'more_url', | |||
licenseRequestUrl: 'license_url' | |||
} | |||
]; | |||
jest.mock('../utils', () => ({ | |||
EDITIONS: [ | |||
{ key: 'comunity', homeUrl: 'more_url' }, | |||
{ key: 'developer', downloadUrl: 'download_url', homeUrl: 'more_url' } | |||
] | |||
})); | |||
it('should display the edition boxes correctly', () => { | |||
const wrapper = getWrapper({ editions: DEFAULT_EDITIONS, loading: true }); | |||
expect(wrapper).toMatchSnapshot(); | |||
wrapper.setProps({ loading: false }); | |||
expect(wrapper).toMatchSnapshot(); | |||
}); | |||
it('should display an error message', () => { | |||
const wrapper = getWrapper(); | |||
expect(wrapper).toMatchSnapshot(); | |||
}); | |||
it('should display community without the downgrade button', () => { | |||
const communityBox = getWrapper({ | |||
editions: DEFAULT_EDITIONS, | |||
editionStatus: { | |||
currentEditionKey: '', | |||
installationStatus: 'NONE' | |||
}, | |||
loading: false | |||
}) | |||
.find('EditionBox') | |||
.first(); | |||
expect(communityBox.prop('displayAction')).toBeFalsy(); | |||
}); | |||
it('should not display action buttons', () => { | |||
const wrapper = getWrapper({ | |||
editions: DEFAULT_EDITIONS, | |||
editionStatus: { | |||
currentEditionKey: '', | |||
installationStatus: 'NONE' | |||
}, | |||
loading: false, | |||
canInstall: false, | |||
canUninstall: false | |||
}); | |||
wrapper.find('EditionBox').forEach(box => expect(box.prop('displayAction')).toBeFalsy()); | |||
}); | |||
it('should display disabled action buttons', () => { | |||
const wrapper = getWrapper({ | |||
editions: DEFAULT_EDITIONS, | |||
editionStatus: { installationStatus: 'AUTOMATIC_IN_PROGRESS', nextEditionKey: 'developer' }, | |||
loading: false | |||
}); | |||
wrapper.find('EditionBox').forEach(box => expect(box.prop('disableAction')).toBeTruthy()); | |||
expect(wrapper.find('EditionBox').map(box => box.prop('displayAction'))).toEqual([true, false]); | |||
wrapper.setProps({ | |||
editionStatus: { currentEditionKey: 'developer', installationStatus: 'UNINSTALL_IN_PROGRESS' } | |||
}); | |||
wrapper.find('EditionBox').forEach(box => expect(box.prop('disableAction')).toBeTruthy()); | |||
expect(wrapper.find('EditionBox').map(box => box.prop('displayAction'))).toEqual([false, true]); | |||
wrapper.setProps({ editionStatus: { installationStatus: 'AUTOMATIC_READY' } }); | |||
wrapper.find('EditionBox').forEach(box => expect(box.prop('disableAction')).toBeTruthy()); | |||
expect(getWrapper()).toMatchSnapshot(); | |||
}); | |||
it('should open the license form', () => { | |||
const wrapper = getWrapper({ editions: DEFAULT_EDITIONS }); | |||
(wrapper.instance() as EditionBoxes).handleOpenLicenseForm(DEFAULT_EDITIONS[0]); | |||
wrapper.update(); | |||
expect(wrapper.find('LicenseEditionForm').exists()).toBeTruthy(); | |||
it('should display the developer edition as installed', () => { | |||
expect(getWrapper({ currentEdition: 'developer' })).toMatchSnapshot(); | |||
}); | |||
function getWrapper(props = {}) { | |||
return shallow( | |||
<EditionBoxes | |||
canInstall={true} | |||
canUninstall={true} | |||
loading={false} | |||
editionStatus={DEFAULT_STATUS} | |||
updateCenterActive={true} | |||
updateEditionStatus={jest.fn()} | |||
{...props} | |||
/> | |||
); | |||
return shallow(<EditionBoxes currentEdition={undefined} {...props} />); | |||
} |
@@ -1,87 +1,57 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should display an error message 1`] = ` | |||
exports[`should display the developer edition as installed 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://redirect.sonarsource.com/editions/editions.html" | |||
target="_blank" | |||
> | |||
SonarSource.com | |||
</a>, | |||
} | |||
<EditionBox | |||
currentEdition="developer" | |||
edition={ | |||
Object { | |||
"homeUrl": "more_url", | |||
"key": "comunity", | |||
} | |||
/> | |||
</span> | |||
} | |||
key="comunity" | |||
/> | |||
<EditionBox | |||
currentEdition="developer" | |||
edition={ | |||
Object { | |||
"downloadUrl": "download_url", | |||
"homeUrl": "more_url", | |||
"key": "developer", | |||
} | |||
} | |||
key="developer" | |||
/> | |||
</div> | |||
`; | |||
exports[`should display the edition boxes correctly 1`] = ` | |||
<i | |||
className="big-spacer-bottom spinner" | |||
/> | |||
`; | |||
exports[`should display the edition boxes correctly 2`] = ` | |||
<div | |||
className="spacer-bottom marketplace-editions" | |||
> | |||
<EditionBox | |||
actionLabel="marketplace.downgrade" | |||
disableAction={false} | |||
displayAction={true} | |||
currentEdition="community" | |||
edition={ | |||
Object { | |||
"downloadUrl": "download_url", | |||
"homeUrl": "more_url", | |||
"key": "comunity", | |||
"licenseRequestUrl": "license_url", | |||
"name": "Comunity Edition", | |||
"textDescription": "bar", | |||
} | |||
} | |||
editionStatus={ | |||
Object { | |||
"currentEditionKey": "developer", | |||
"installationStatus": "NONE", | |||
"nextEditionKey": "", | |||
} | |||
} | |||
key="comunity" | |||
onAction={[Function]} | |||
/> | |||
<EditionBox | |||
actionLabel="marketplace.upgrade" | |||
disableAction={false} | |||
displayAction={false} | |||
currentEdition="community" | |||
edition={ | |||
Object { | |||
"downloadUrl": "download_url", | |||
"homeUrl": "more_url", | |||
"key": "developer", | |||
"licenseRequestUrl": "license_url", | |||
"name": "Developer Edition", | |||
"textDescription": "foo", | |||
} | |||
} | |||
editionStatus={ | |||
Object { | |||
"currentEditionKey": "developer", | |||
"installationStatus": "NONE", | |||
"nextEditionKey": "", | |||
} | |||
} | |||
key="developer" | |||
onAction={[Function]} | |||
/> | |||
</div> | |||
`; |
@@ -18,45 +18,37 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import EditionBoxBadge from './EditionBoxBadge'; | |||
import { Edition, EditionStatus } from '../../../api/marketplace'; | |||
import { Button } from '../../../components/ui/buttons'; | |||
import CheckIcon from '../../../components/icons-components/CheckIcon'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import DocInclude from '../../../components/docs/DocInclude'; | |||
import { Edition, getEditionUrl } from '../utils'; | |||
interface Props { | |||
actionLabel: string; | |||
disableAction: boolean; | |||
displayAction: boolean; | |||
currentEdition: string; | |||
edition: Edition; | |||
editionStatus?: EditionStatus; | |||
onAction: (edition: Edition) => void; | |||
ncloc?: number; | |||
serverId?: string; | |||
} | |||
export default class EditionBox extends React.PureComponent<Props> { | |||
handleAction = () => { | |||
this.props.onAction(this.props.edition); | |||
}; | |||
render() { | |||
const { disableAction, displayAction, edition, editionStatus } = this.props; | |||
return ( | |||
<div className="boxed-group boxed-group-inner marketplace-edition"> | |||
{editionStatus && <EditionBoxBadge editionKey={edition.key} status={editionStatus} />} | |||
<div> | |||
<h3 className="spacer-bottom">{edition.name}</h3> | |||
<p>{edition.textDescription}</p> | |||
</div> | |||
<div className="marketplace-edition-action spacer-top"> | |||
<a href={edition.homeUrl} target="_blank"> | |||
{translate('marketplace.learn_more')} | |||
</a> | |||
{displayAction && ( | |||
<Button disabled={disableAction} onClick={this.handleAction}> | |||
{this.props.actionLabel} | |||
</Button> | |||
)} | |||
</div> | |||
export default function EditionBox({ currentEdition, edition, ncloc, serverId }: Props) { | |||
const isInstalled = currentEdition === edition.key; | |||
const url = getEditionUrl(edition, { ncloc, serverId, sourceEdition: currentEdition }); | |||
return ( | |||
<div className="boxed-group boxed-group-inner marketplace-edition"> | |||
{isInstalled && ( | |||
<span className="marketplace-edition-badge badge badge-normal-size display-flex-center"> | |||
<CheckIcon className="little-spacer-right" size={14} /> | |||
{translate('marketplace.installed')} | |||
</span> | |||
)} | |||
<div> | |||
<DocInclude path={'/tooltips/editions/' + edition.key} /> | |||
</div> | |||
<div className="marketplace-edition-action spacer-top"> | |||
<a href={url} target="_blank"> | |||
{translate('marketplace.learn_more')} | |||
</a> | |||
</div> | |||
); | |||
} | |||
</div> | |||
); | |||
} |
@@ -1,62 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 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 { EditionStatus } from '../../../api/marketplace'; | |||
import { translate } from '../../../helpers/l10n'; | |||
interface Props { | |||
editionKey: string; | |||
status: EditionStatus; | |||
} | |||
export default function EditionBoxBadge({ editionKey, status }: Props) { | |||
const isInstalled = | |||
status.currentEditionKey === editionKey || | |||
(!status.currentEditionKey && editionKey === 'community'); | |||
const isProgressing = | |||
status.nextEditionKey === editionKey || (!status.nextEditionKey && editionKey === 'community'); | |||
const inProgressStatus = [ | |||
'AUTOMATIC_READY', | |||
'AUTOMATIC_IN_PROGRESS', | |||
'UNINSTALL_IN_PROGRESS' | |||
].includes(status.installationStatus); | |||
if (inProgressStatus) { | |||
if (isProgressing) { | |||
return ( | |||
<span className="marketplace-edition-badge badge badge-normal-size"> | |||
{status.installationStatus === 'AUTOMATIC_IN_PROGRESS' | |||
? translate('marketplace.installing') | |||
: translate('marketplace.pending')} | |||
</span> | |||
); | |||
} | |||
} else if (isInstalled) { | |||
return ( | |||
<span className="marketplace-edition-badge badge badge-normal-size display-flex-center"> | |||
<CheckIcon size={14} className="little-spacer-right" /> | |||
{translate('marketplace.installed')} | |||
</span> | |||
); | |||
} | |||
return null; | |||
} |
@@ -1,114 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 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 LicenseEditionSet from './LicenseEditionSet'; | |||
import { Edition, EditionStatus, applyLicense } from '../../../api/marketplace'; | |||
import Modal from '../../../components/controls/Modal'; | |||
import { Button, ResetButtonLink } from '../../../components/ui/buttons'; | |||
import { translate, translateWithParameters } from '../../../helpers/l10n'; | |||
export interface Props { | |||
edition: Edition; | |||
editions: Edition[]; | |||
isDowngrade: boolean; | |||
onClose: () => void; | |||
updateEditionStatus: (editionStatus: EditionStatus) => void; | |||
} | |||
interface State { | |||
license: string; | |||
status?: string; | |||
submitting: boolean; | |||
} | |||
export default class LicenseEditionForm extends React.PureComponent<Props, State> { | |||
mounted = false; | |||
state: State = { license: '', submitting: false }; | |||
componentDidMount() { | |||
this.mounted = true; | |||
} | |||
componentWillUnmount() { | |||
this.mounted = false; | |||
} | |||
handleLicenseChange = (license: string, status?: string) => { | |||
if (this.mounted) { | |||
this.setState({ license, status }); | |||
} | |||
}; | |||
handleConfirmClick = () => { | |||
const { license, status } = this.state; | |||
if (license && status) { | |||
this.setState({ submitting: true }); | |||
applyLicense({ license }).then( | |||
editionStatus => { | |||
this.props.updateEditionStatus(editionStatus); | |||
this.props.onClose(); | |||
}, | |||
() => { | |||
if (this.mounted) { | |||
this.setState({ submitting: false }); | |||
} | |||
} | |||
); | |||
} | |||
}; | |||
render() { | |||
const { edition, isDowngrade } = this.props; | |||
const { license, submitting, status } = this.state; | |||
const header = isDowngrade | |||
? translateWithParameters('marketplace.downgrade_to_x', edition.name) | |||
: translateWithParameters('marketplace.upgrade_to_x', edition.name); | |||
return ( | |||
<Modal contentLabel={header} onRequestClose={this.props.onClose}> | |||
<header className="modal-head"> | |||
<h2>{header}</h2> | |||
</header> | |||
<LicenseEditionSet | |||
className="modal-body" | |||
edition={edition} | |||
editions={this.props.editions} | |||
updateLicense={this.handleLicenseChange} | |||
/> | |||
<footer className="modal-foot"> | |||
{submitting && <i className="spinner spacer-right" />} | |||
{status && ( | |||
<Button | |||
className="js-confirm" | |||
disabled={!license || submitting} | |||
onClick={this.handleConfirmClick}> | |||
{status === 'AUTOMATIC_INSTALL' | |||
? translate('marketplace.install') | |||
: translate('save')} | |||
</Button> | |||
)} | |||
<ResetButtonLink onClick={this.props.onClose}>{translate('cancel')}</ResetButtonLink> | |||
</footer> | |||
</Modal> | |||
); | |||
} | |||
} |
@@ -1,269 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 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 { stringify } from 'querystring'; | |||
import * as React from 'react'; | |||
import * as classNames from 'classnames'; | |||
import { FormattedMessage } from 'react-intl'; | |||
import { debounce } from 'lodash'; | |||
import Checkbox from '../../../components/controls/Checkbox'; | |||
import DeferredSpinner from '../../../components/common/DeferredSpinner'; | |||
import { omitNil } from '../../../helpers/request'; | |||
import { Edition, getFormData, getLicensePreview } from '../../../api/marketplace'; | |||
import { translate, translateWithParameters } from '../../../helpers/l10n'; | |||
export interface Props { | |||
className?: string; | |||
edition?: Edition; | |||
editions: Edition[]; | |||
updateLicense: (license?: string, status?: string) => void; | |||
} | |||
interface State { | |||
acceptTerms: boolean; | |||
formData?: { | |||
serverId?: string; | |||
ncloc?: number; | |||
}; | |||
license: string; | |||
licenseEdition?: Edition; | |||
loading: boolean; | |||
previewStatus?: string; | |||
wrongEdition: boolean; | |||
} | |||
export default class LicenseEditionSet extends React.PureComponent<Props, State> { | |||
mounted = false; | |||
constructor(props: Props) { | |||
super(props); | |||
this.state = { acceptTerms: false, license: '', loading: false, wrongEdition: false }; | |||
this.fetchLicensePreview = debounce(this.fetchLicensePreview, 100); | |||
} | |||
componentDidMount() { | |||
this.mounted = true; | |||
this.fetchFormData(); | |||
} | |||
componentWillUnmount() { | |||
this.mounted = false; | |||
} | |||
fetchLicensePreview = (license: string) => { | |||
this.setState({ loading: true }); | |||
getLicensePreview({ license }).then( | |||
({ previewStatus, nextEditionKey }) => { | |||
if (this.mounted) { | |||
const { edition } = this.props; | |||
const licenseEdition = this.props.editions.find( | |||
edition => edition.key === nextEditionKey | |||
); | |||
const wrongEdition = Boolean( | |||
!licenseEdition || (edition && edition.key !== nextEditionKey) | |||
); | |||
this.setLicense({ license, loading: false, licenseEdition, previewStatus, wrongEdition }); | |||
} | |||
}, | |||
() => { | |||
if (this.mounted) { | |||
this.resetLicense({ license, loading: false }); | |||
} | |||
} | |||
); | |||
}; | |||
fetchFormData = () => { | |||
getFormData().then( | |||
formData => { | |||
if (this.mounted) { | |||
this.setState({ formData }); | |||
} | |||
}, | |||
() => {} | |||
); | |||
}; | |||
getLicenseFormUrl = (edition: Edition) => { | |||
let url = edition.licenseRequestUrl; | |||
if (this.state.formData) { | |||
const query = stringify(omitNil(this.state.formData)); | |||
if (query) { | |||
url += '?' + query; | |||
} | |||
} | |||
return url; | |||
}; | |||
handleLicenseChange = (event: React.SyntheticEvent<HTMLTextAreaElement>) => { | |||
const license = event.currentTarget.value; | |||
if (license) { | |||
this.fetchLicensePreview(license); | |||
this.setState({ license }); | |||
} else { | |||
this.resetLicense({}); | |||
} | |||
}; | |||
handleTermsCheck = (checked: boolean) => { | |||
this.setLicense({ acceptTerms: checked }); | |||
}; | |||
resetLicense<K extends keyof State>(state: Pick<State, K>) { | |||
this.setLicense( | |||
Object.assign( | |||
{ | |||
license: '', | |||
licenseEdition: undefined, | |||
previewStatus: undefined, | |||
wrongEdition: false | |||
}, | |||
state | |||
) | |||
); | |||
} | |||
setLicense<K extends keyof State>(state: Pick<State, K>) { | |||
this.setState(state, this.updateParentLicense); | |||
} | |||
updateParentLicense = () => { | |||
const { acceptTerms, license, previewStatus, wrongEdition } = this.state; | |||
this.props.updateLicense( | |||
previewStatus !== 'NO_INSTALL' && !acceptTerms ? undefined : license, | |||
wrongEdition ? undefined : previewStatus | |||
); | |||
}; | |||
renderAlert() { | |||
const { licenseEdition, previewStatus, wrongEdition } = this.state; | |||
if (!previewStatus || wrongEdition) { | |||
const { edition } = this.props; | |||
return ( | |||
<div className="spacer-top"> | |||
{wrongEdition && ( | |||
<p className="alert alert-danger"> | |||
{edition | |||
? translateWithParameters('marketplace.wrong_license_type_x', edition.name) | |||
: translate('marketplace.wrong_license_type')} | |||
</p> | |||
)} | |||
{edition && ( | |||
<a href={this.getLicenseFormUrl(edition)} target="_blank"> | |||
{translate('marketplace.i_need_a_license')} | |||
</a> | |||
)} | |||
</div> | |||
); | |||
} | |||
return ( | |||
<div className="spacer-top"> | |||
<p | |||
className={classNames('alert', { | |||
'alert-warning': previewStatus === 'AUTOMATIC_INSTALL', | |||
'alert-success': previewStatus === 'NO_INSTALL', | |||
'alert-danger': previewStatus === 'MANUAL_INSTALL' | |||
})}> | |||
{translateWithParameters( | |||
'marketplace.license_preview_status.' + previewStatus, | |||
licenseEdition ? licenseEdition.name : translate('marketplace.commercial_edition') | |||
)} | |||
{licenseEdition && | |||
licenseEdition.key === 'datacenter' && | |||
previewStatus !== 'NO_INSTALL' && ( | |||
<span className="little-spacer-left"> | |||
<FormattedMessage | |||
defaultMessage={translate('marketplace.how_to_setup_cluster_url')} | |||
id="marketplace.how_to_setup_cluster_url" | |||
values={{ | |||
url: ( | |||
<a | |||
href="https://redirect.sonarsource.com/doc/data-center-edition.html" | |||
rel="noopener noreferrer" | |||
target="_blank"> | |||
{licenseEdition.name} | |||
</a> | |||
) | |||
}} | |||
/> | |||
</span> | |||
)} | |||
</p> | |||
{previewStatus !== 'NO_INSTALL' && ( | |||
<span className="js-edition-tos"> | |||
<Checkbox | |||
checked={this.state.acceptTerms} | |||
id="edition-terms" | |||
onCheck={this.handleTermsCheck}> | |||
<label className="little-spacer-left" htmlFor="edition-terms"> | |||
{translate('marketplace.i_accept_the')} | |||
</label> | |||
</Checkbox> | |||
<a | |||
className="nowrap little-spacer-left" | |||
href="http://dist.sonarsource.com/SonarSource_Terms_And_Conditions.pdf" | |||
rel="noopener noreferrer" | |||
target="_blank"> | |||
{translate('marketplace.terms_and_conditions')} | |||
</a> | |||
</span> | |||
)} | |||
</div> | |||
); | |||
} | |||
render() { | |||
const { className, edition } = this.props; | |||
const { license, loading } = this.state; | |||
return ( | |||
<div className={className}> | |||
{edition && ( | |||
<label className="display-inline-block spacer-bottom" htmlFor="set-license"> | |||
{translateWithParameters('marketplace.enter_license_for_x', edition.name)} | |||
<em className="mandatory">*</em> | |||
</label> | |||
)} | |||
<textarea | |||
autoFocus={true} | |||
className="display-block input-super-large" | |||
id="set-license" | |||
onChange={this.handleLicenseChange} | |||
required={true} | |||
rows={8} | |||
style={{ resize: 'none' }} | |||
value={license} | |||
/> | |||
<DeferredSpinner | |||
customSpinner={ | |||
<p className="spacer-top"> | |||
<i className="spinner spacer-right text-bottom" /> | |||
{translate('marketplace.checking_license')} | |||
</p> | |||
} | |||
loading={loading}> | |||
{this.renderAlert()} | |||
</DeferredSpinner> | |||
</div> | |||
); | |||
} | |||
} |
@@ -1,94 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 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 { Edition, EditionStatus, uninstallEdition } from '../../../api/marketplace'; | |||
import Modal from '../../../components/controls/Modal'; | |||
import { Button, ResetButtonLink } from '../../../components/ui/buttons'; | |||
import { translate, translateWithParameters } from '../../../helpers/l10n'; | |||
export interface Props { | |||
edition?: Edition; | |||
editionStatus: EditionStatus; | |||
onClose: () => void; | |||
updateEditionStatus: (editionStatus: EditionStatus) => void; | |||
} | |||
interface State { | |||
loading: boolean; | |||
} | |||
export default class UninstallEditionForm extends React.PureComponent<Props, State> { | |||
mounted = false; | |||
state: State = { loading: false }; | |||
componentDidMount() { | |||
this.mounted = true; | |||
} | |||
componentWillUnmount() { | |||
this.mounted = false; | |||
} | |||
handleConfirmClick = () => { | |||
this.setState({ loading: true }); | |||
uninstallEdition() | |||
.then(() => { | |||
this.props.updateEditionStatus({ | |||
...this.props.editionStatus, | |||
currentEditionKey: undefined, | |||
installationStatus: 'UNINSTALL_IN_PROGRESS' | |||
}); | |||
this.props.onClose(); | |||
}) | |||
.catch(() => { | |||
if (this.mounted) { | |||
this.setState({ loading: false }); | |||
} | |||
}); | |||
}; | |||
render() { | |||
const { edition } = this.props; | |||
const { loading } = this.state; | |||
const currentEdition = edition ? edition.name : translate('marketplace.commercial_edition'); | |||
const header = translateWithParameters('marketplace.downgrade_to_community_edition'); | |||
return ( | |||
<Modal contentLabel={header} onRequestClose={this.props.onClose}> | |||
<header className="modal-head"> | |||
<h2>{header}</h2> | |||
</header> | |||
<div className="modal-body"> | |||
<p>{translateWithParameters('marketplace.uninstall_x_confirmation', currentEdition)}</p> | |||
</div> | |||
<footer className="modal-foot"> | |||
{loading && <i className="spinner spacer-right" />} | |||
<Button disabled={loading} onClick={this.handleConfirmClick}> | |||
{translate('marketplace.downgrade')} | |||
</Button> | |||
<ResetButtonLink className="js-modal-close" onClick={this.props.onClose}> | |||
{translate('cancel')} | |||
</ResetButtonLink> | |||
</footer> | |||
</Modal> | |||
); | |||
} | |||
} |
@@ -19,46 +19,22 @@ | |||
*/ | |||
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 = { | |||
const DEFAULT_EDITION = { | |||
key: 'foo', | |||
name: 'Foo', | |||
textDescription: 'Foo desc', | |||
downloadUrl: 'download_url', | |||
homeUrl: 'more_url', | |||
licenseRequestUrl: 'license_url' | |||
homeUrl: 'more_url' | |||
}; | |||
it('should display the edition', () => { | |||
expect(getWrapper()).toMatchSnapshot(); | |||
}); | |||
it('should disable action button', () => { | |||
expect(getWrapper({ disableAction: true })).toMatchSnapshot(); | |||
expect(getWrapper({ ncloc: 1000, serverId: 'serverId' })).toMatchSnapshot(); | |||
}); | |||
it('should correctly hide action buttons', () => { | |||
expect(getWrapper({ displayAction: false })).toMatchSnapshot(); | |||
it('should show insalled badge', () => { | |||
expect(getWrapper({ currentEdition: 'foo' })).toMatchSnapshot(); | |||
}); | |||
function getWrapper(props = {}) { | |||
return shallow( | |||
<EditionBox | |||
actionLabel="action" | |||
disableAction={false} | |||
displayAction={true} | |||
edition={DEFAULT_EDITION} | |||
editionStatus={DEFAULT_STATUS} | |||
onAction={jest.fn()} | |||
{...props} | |||
/> | |||
); | |||
return shallow(<EditionBox currentEdition="community" edition={DEFAULT_EDITION} {...props} />); | |||
} |
@@ -1,81 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 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 { EditionStatus } from '../../../../api/marketplace'; | |||
import EditionBoxBadge from '../EditionBoxBadge'; | |||
const DEFAULT_STATUS: EditionStatus = { | |||
currentEditionKey: '', | |||
nextEditionKey: '', | |||
installationStatus: 'NONE' | |||
}; | |||
it('should display installed badge', () => { | |||
expect( | |||
getWrapper({ | |||
status: { | |||
currentEditionKey: 'foo', | |||
nextEditionKey: '', | |||
installationStatus: 'NONE' | |||
} | |||
}) | |||
).toMatchSnapshot(); | |||
}); | |||
it('should display installing badge', () => { | |||
expect( | |||
getWrapper({ | |||
status: { | |||
currentEditionKey: 'foo', | |||
nextEditionKey: 'foo', | |||
installationStatus: 'AUTOMATIC_IN_PROGRESS' | |||
} | |||
}) | |||
).toMatchSnapshot(); | |||
}); | |||
it('should display pending badge', () => { | |||
expect( | |||
getWrapper({ | |||
status: { | |||
currentEditionKey: '', | |||
nextEditionKey: 'foo', | |||
installationStatus: 'AUTOMATIC_READY' | |||
} | |||
}) | |||
).toMatchSnapshot(); | |||
}); | |||
it('should not display a badge', () => { | |||
expect( | |||
getWrapper({ | |||
status: { | |||
currentEditionKey: '', | |||
nextEditionKey: 'bar', | |||
installationStatus: 'AUTOMATIC_READY' | |||
} | |||
}).type() | |||
).toBeNull(); | |||
}); | |||
function getWrapper(props = {}) { | |||
return shallow(<EditionBoxBadge editionKey="foo" status={DEFAULT_STATUS} {...props} />); | |||
} |
@@ -1,98 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 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. | |||
*/ | |||
/* eslint-disable import/order */ | |||
import * as React from 'react'; | |||
import { shallow } from 'enzyme'; | |||
import { click } from '../../../../helpers/testUtils'; | |||
import LicenseEditionForm from '../LicenseEditionForm'; | |||
jest.mock('../../../../api/marketplace', () => ({ | |||
applyLicense: jest.fn(() => | |||
Promise.resolve({ nextEditionKey: 'foo', installationStatus: 'AUTOMATIC_IN_PROGRESS' }) | |||
) | |||
})); | |||
const applyLicense = require('../../../../api/marketplace').applyLicense as jest.Mock<any>; | |||
const DEFAULT_EDITION = { | |||
key: 'foo', | |||
name: 'Foo', | |||
textDescription: 'Foo desc', | |||
downloadUrl: 'download_url', | |||
homeUrl: 'more_url', | |||
licenseRequestUrl: 'license_url' | |||
}; | |||
beforeEach(() => { | |||
applyLicense.mockClear(); | |||
}); | |||
it('should display correctly', () => { | |||
expect(getWrapper()).toMatchSnapshot(); | |||
}); | |||
it('should correctly change the button based on the status and license', () => { | |||
const wrapper = getWrapper(); | |||
let button; | |||
(wrapper.instance() as LicenseEditionForm).mounted = true; | |||
wrapper.setState({ license: 'mylicense', status: 'NO_INSTALL' }); | |||
button = wrapper.find('Button'); | |||
expect(button).toMatchSnapshot(); | |||
wrapper.setState({ license: undefined, status: 'MANUAL_INSTALL' }); | |||
button = wrapper.find('Button'); | |||
expect(button).toMatchSnapshot(); | |||
wrapper.setState({ status: 'AUTOMATIC_INSTALL' }); | |||
button = wrapper.find('Button'); | |||
expect(button).toMatchSnapshot(); | |||
wrapper.setState({ license: 'mylicense' }); | |||
expect(wrapper.find('Button').prop('disabled')).toBeFalsy(); | |||
}); | |||
it('should update the edition status after install', async () => { | |||
const updateEditionStatus = jest.fn(); | |||
const wrapper = getWrapper({ updateEditionStatus }); | |||
const form = wrapper.instance() as LicenseEditionForm; | |||
form.handleLicenseChange('mylicense', 'AUTOMATIC_INSTALL'); | |||
wrapper.update(); | |||
click(wrapper.find('Button')); | |||
expect(applyLicense).toHaveBeenCalledWith({ license: 'mylicense' }); | |||
await new Promise(setImmediate); | |||
expect(updateEditionStatus).toHaveBeenCalledWith({ | |||
nextEditionKey: 'foo', | |||
installationStatus: 'AUTOMATIC_IN_PROGRESS' | |||
}); | |||
}); | |||
function getWrapper(props = {}) { | |||
return shallow( | |||
<LicenseEditionForm | |||
edition={DEFAULT_EDITION} | |||
editions={[DEFAULT_EDITION]} | |||
isDowngrade={false} | |||
onClose={jest.fn()} | |||
updateEditionStatus={jest.fn()} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -1,126 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 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. | |||
*/ | |||
/* eslint-disable import/order */ | |||
import * as React from 'react'; | |||
import { shallow } from 'enzyme'; | |||
import { change, waitAndUpdate } from '../../../../helpers/testUtils'; | |||
import LicenseEditionSet from '../LicenseEditionSet'; | |||
jest.mock('../../../../api/marketplace', () => ({ | |||
getLicensePreview: jest.fn(() => | |||
Promise.resolve({ nextEditionKey: 'foo', previewStatus: 'NO_INSTALL' }) | |||
), | |||
getFormData: jest.fn(() => Promise.resolve({ serverId: 'foo', ncloc: 1000 })) | |||
})); | |||
jest.mock('lodash', () => { | |||
const lodash = require.requireActual('lodash'); | |||
lodash.debounce = (fn: Function) => (...args: any[]) => fn(...args); | |||
return lodash; | |||
}); | |||
const getLicensePreview = require('../../../../api/marketplace').getLicensePreview as jest.Mock< | |||
any | |||
>; | |||
const DEFAULT_EDITION = { | |||
key: 'foo', | |||
name: 'Foo', | |||
textDescription: 'Foo desc', | |||
downloadUrl: 'download_url', | |||
homeUrl: 'more_url', | |||
licenseRequestUrl: 'license_url' | |||
}; | |||
beforeEach(() => { | |||
getLicensePreview.mockClear(); | |||
}); | |||
it('should display correctly', () => { | |||
expect(getWrapper()).toMatchSnapshot(); | |||
}); | |||
it('should display the get license link with parameters', async () => { | |||
const wrapper = getWrapper(); | |||
await waitAndUpdate(wrapper); | |||
expect(wrapper.find('a')).toMatchSnapshot(); | |||
}); | |||
it('should correctly display status message after checking license', async () => { | |||
let wrapper = await testLicenseStatus('NO_INSTALL'); | |||
expect(wrapper.find('p.alert')).toMatchSnapshot(); | |||
wrapper = await testLicenseStatus('AUTOMATIC_INSTALL'); | |||
expect(wrapper.find('p.alert')).toMatchSnapshot(); | |||
wrapper = await testLicenseStatus('MANUAL_INSTALL'); | |||
expect(wrapper.find('p.alert')).toMatchSnapshot(); | |||
wrapper = await testLicenseStatus('AUTOMATIC_INSTALL', jest.fn(), 'bar'); | |||
expect(wrapper.find('p.alert')).toMatchSnapshot(); | |||
wrapper = await testLicenseStatus('AUTOMATIC_INSTALL', jest.fn(), 'bar', { edition: undefined }); | |||
expect(wrapper.find('p.alert')).toMatchSnapshot(); | |||
}); | |||
it('should display terms of license checkbox', async () => { | |||
let updateLicense = jest.fn(); | |||
let wrapper = await testLicenseStatus('NO_INSTALL', updateLicense); | |||
expect(wrapper.find('.js-edition-tos').exists()).toBeFalsy(); | |||
expect(updateLicense).toHaveBeenCalledWith('mylicense', 'NO_INSTALL'); | |||
updateLicense = jest.fn(); | |||
wrapper = await testLicenseStatus('AUTOMATIC_INSTALL', updateLicense); | |||
const tosCheckbox = wrapper.find('.js-edition-tos'); | |||
expect(tosCheckbox.find('a').exists()).toBeTruthy(); | |||
expect(updateLicense).toHaveBeenLastCalledWith(undefined, 'AUTOMATIC_INSTALL'); | |||
(tosCheckbox.find('Checkbox').prop('onCheck') as Function)(true); | |||
expect(updateLicense).toHaveBeenLastCalledWith('mylicense', 'AUTOMATIC_INSTALL'); | |||
updateLicense = jest.fn(); | |||
wrapper = await testLicenseStatus('MANUAL_INSTALL', updateLicense); | |||
expect(wrapper.find('.js-edition-tos').exists()).toBeTruthy(); | |||
expect(updateLicense).toHaveBeenLastCalledWith(undefined, 'MANUAL_INSTALL'); | |||
}); | |||
function getWrapper(props = {}) { | |||
return shallow( | |||
<LicenseEditionSet | |||
edition={DEFAULT_EDITION} | |||
editions={[DEFAULT_EDITION]} | |||
updateLicense={jest.fn()} | |||
{...props} | |||
/> | |||
); | |||
} | |||
async function testLicenseStatus( | |||
status: string, | |||
updateLicense = jest.fn(), | |||
nextEditionKey = 'foo', | |||
props = {} | |||
) { | |||
getLicensePreview.mockImplementation(() => | |||
Promise.resolve({ nextEditionKey, previewStatus: status }) | |||
); | |||
const wrapper = getWrapper({ updateLicense, ...props }); | |||
change(wrapper.find('textarea'), 'mylicense'); | |||
expect(getLicensePreview).toHaveBeenCalled(); | |||
await new Promise(setImmediate); | |||
expect(updateLicense).toHaveBeenCalled(); | |||
wrapper.update(); | |||
return wrapper; | |||
} |
@@ -1,72 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 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. | |||
*/ | |||
/* eslint-disable import/order */ | |||
import * as React from 'react'; | |||
import { shallow } from 'enzyme'; | |||
import { click } from '../../../../helpers/testUtils'; | |||
import UninstallEditionForm from '../UninstallEditionForm'; | |||
jest.mock('../../../../api/marketplace', () => ({ | |||
uninstallEdition: jest.fn(() => Promise.resolve()) | |||
})); | |||
const uninstallEdition = require('../../../../api/marketplace').uninstallEdition as jest.Mock<any>; | |||
const DEFAULT_EDITION = { | |||
key: 'foo', | |||
name: 'Foo', | |||
textDescription: 'Foo desc', | |||
downloadUrl: 'download_url', | |||
homeUrl: 'more_url', | |||
licenseRequestUrl: 'license_url' | |||
}; | |||
beforeEach(() => { | |||
uninstallEdition.mockClear(); | |||
}); | |||
it('should display correctly', () => { | |||
expect(getWrapper()).toMatchSnapshot(); | |||
}); | |||
it('should update the edition status after uninstall', async () => { | |||
const updateEditionStatus = jest.fn(); | |||
const wrapper = getWrapper({ updateEditionStatus }); | |||
(wrapper.instance() as UninstallEditionForm).mounted = true; | |||
click(wrapper.find('Button')); | |||
expect(uninstallEdition).toHaveBeenCalled(); | |||
await new Promise(setImmediate); | |||
expect(updateEditionStatus).toHaveBeenCalledWith({ | |||
currentEditionKey: undefined, | |||
installationStatus: 'UNINSTALL_IN_PROGRESS' | |||
}); | |||
}); | |||
function getWrapper(props = {}) { | |||
return shallow( | |||
<UninstallEditionForm | |||
edition={DEFAULT_EDITION} | |||
editionStatus={{ currentEditionKey: 'foo', installationStatus: 'NONE' }} | |||
onClose={jest.fn()} | |||
updateEditionStatus={jest.fn()} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -1,34 +1,19 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should correctly hide action buttons 1`] = ` | |||
exports[`should display the edition 1`] = ` | |||
<div | |||
className="boxed-group boxed-group-inner marketplace-edition" | |||
> | |||
<EditionBoxBadge | |||
editionKey="foo" | |||
status={ | |||
Object { | |||
"currentEditionKey": "", | |||
"installationStatus": "NONE", | |||
"nextEditionKey": "", | |||
} | |||
} | |||
/> | |||
<div> | |||
<h3 | |||
className="spacer-bottom" | |||
> | |||
Foo | |||
</h3> | |||
<p> | |||
Foo desc | |||
</p> | |||
<DocInclude | |||
path="/tooltips/editions/foo" | |||
/> | |||
</div> | |||
<div | |||
className="marketplace-edition-action spacer-top" | |||
> | |||
<a | |||
href="more_url" | |||
href="more_url?ncloc=1000&serverId=serverId&sourceEdition=community" | |||
target="_blank" | |||
> | |||
marketplace.learn_more | |||
@@ -37,88 +22,33 @@ exports[`should correctly hide action buttons 1`] = ` | |||
</div> | |||
`; | |||
exports[`should disable action button 1`] = ` | |||
exports[`should show insalled badge 1`] = ` | |||
<div | |||
className="boxed-group boxed-group-inner marketplace-edition" | |||
> | |||
<EditionBoxBadge | |||
editionKey="foo" | |||
status={ | |||
Object { | |||
"currentEditionKey": "", | |||
"installationStatus": "NONE", | |||
"nextEditionKey": "", | |||
} | |||
} | |||
/> | |||
<div> | |||
<h3 | |||
className="spacer-bottom" | |||
> | |||
Foo | |||
</h3> | |||
<p> | |||
Foo desc | |||
</p> | |||
</div> | |||
<div | |||
className="marketplace-edition-action spacer-top" | |||
<span | |||
className="marketplace-edition-badge badge badge-normal-size display-flex-center" | |||
> | |||
<a | |||
href="more_url" | |||
target="_blank" | |||
> | |||
marketplace.learn_more | |||
</a> | |||
<Button | |||
disabled={true} | |||
onClick={[Function]} | |||
> | |||
action | |||
</Button> | |||
</div> | |||
</div> | |||
`; | |||
exports[`should display the edition 1`] = ` | |||
<div | |||
className="boxed-group boxed-group-inner marketplace-edition" | |||
> | |||
<EditionBoxBadge | |||
editionKey="foo" | |||
status={ | |||
Object { | |||
"currentEditionKey": "", | |||
"installationStatus": "NONE", | |||
"nextEditionKey": "", | |||
} | |||
} | |||
/> | |||
<CheckIcon | |||
className="little-spacer-right" | |||
size={14} | |||
/> | |||
marketplace.installed | |||
</span> | |||
<div> | |||
<h3 | |||
className="spacer-bottom" | |||
> | |||
Foo | |||
</h3> | |||
<p> | |||
Foo desc | |||
</p> | |||
<DocInclude | |||
path="/tooltips/editions/foo" | |||
/> | |||
</div> | |||
<div | |||
className="marketplace-edition-action spacer-top" | |||
> | |||
<a | |||
href="more_url" | |||
href="more_url?sourceEdition=foo" | |||
target="_blank" | |||
> | |||
marketplace.learn_more | |||
</a> | |||
<Button | |||
disabled={false} | |||
onClick={[Function]} | |||
> | |||
action | |||
</Button> | |||
</div> | |||
</div> | |||
`; |
@@ -1,29 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should display installed badge 1`] = ` | |||
<span | |||
className="marketplace-edition-badge badge badge-normal-size display-flex-center" | |||
> | |||
<CheckIcon | |||
className="little-spacer-right" | |||
size={14} | |||
/> | |||
marketplace.installed | |||
</span> | |||
`; | |||
exports[`should display installing badge 1`] = ` | |||
<span | |||
className="marketplace-edition-badge badge badge-normal-size" | |||
> | |||
marketplace.installing | |||
</span> | |||
`; | |||
exports[`should display pending badge 1`] = ` | |||
<span | |||
className="marketplace-edition-badge badge badge-normal-size" | |||
> | |||
marketplace.pending | |||
</span> | |||
`; |
@@ -1,81 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should correctly change the button based on the status and license 1`] = ` | |||
<Button | |||
className="js-confirm" | |||
disabled={false} | |||
onClick={[Function]} | |||
> | |||
save | |||
</Button> | |||
`; | |||
exports[`should correctly change the button based on the status and license 2`] = ` | |||
<Button | |||
className="js-confirm" | |||
disabled={true} | |||
onClick={[Function]} | |||
> | |||
save | |||
</Button> | |||
`; | |||
exports[`should correctly change the button based on the status and license 3`] = ` | |||
<Button | |||
className="js-confirm" | |||
disabled={true} | |||
onClick={[Function]} | |||
> | |||
marketplace.install | |||
</Button> | |||
`; | |||
exports[`should display correctly 1`] = ` | |||
<Modal | |||
contentLabel="marketplace.upgrade_to_x.Foo" | |||
onRequestClose={[MockFunction]} | |||
> | |||
<header | |||
className="modal-head" | |||
> | |||
<h2> | |||
marketplace.upgrade_to_x.Foo | |||
</h2> | |||
</header> | |||
<LicenseEditionSet | |||
className="modal-body" | |||
edition={ | |||
Object { | |||
"downloadUrl": "download_url", | |||
"homeUrl": "more_url", | |||
"key": "foo", | |||
"licenseRequestUrl": "license_url", | |||
"name": "Foo", | |||
"textDescription": "Foo desc", | |||
} | |||
} | |||
editions={ | |||
Array [ | |||
Object { | |||
"downloadUrl": "download_url", | |||
"homeUrl": "more_url", | |||
"key": "foo", | |||
"licenseRequestUrl": "license_url", | |||
"name": "Foo", | |||
"textDescription": "Foo desc", | |||
}, | |||
] | |||
} | |||
updateLicense={[Function]} | |||
/> | |||
<footer | |||
className="modal-foot" | |||
> | |||
<ResetButtonLink | |||
onClick={[MockFunction]} | |||
> | |||
cancel | |||
</ResetButtonLink> | |||
</footer> | |||
</Modal> | |||
`; |
@@ -1,105 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should correctly display status message after checking license 1`] = ` | |||
<p | |||
className="alert alert-success" | |||
> | |||
marketplace.license_preview_status.NO_INSTALL.Foo | |||
</p> | |||
`; | |||
exports[`should correctly display status message after checking license 2`] = ` | |||
<p | |||
className="alert alert-warning" | |||
> | |||
marketplace.license_preview_status.AUTOMATIC_INSTALL.Foo | |||
</p> | |||
`; | |||
exports[`should correctly display status message after checking license 3`] = ` | |||
<p | |||
className="alert alert-danger" | |||
> | |||
marketplace.license_preview_status.MANUAL_INSTALL.Foo | |||
</p> | |||
`; | |||
exports[`should correctly display status message after checking license 4`] = ` | |||
<p | |||
className="alert alert-danger" | |||
> | |||
marketplace.wrong_license_type_x.Foo | |||
</p> | |||
`; | |||
exports[`should correctly display status message after checking license 5`] = ` | |||
<p | |||
className="alert alert-danger" | |||
> | |||
marketplace.wrong_license_type | |||
</p> | |||
`; | |||
exports[`should display correctly 1`] = ` | |||
<div> | |||
<label | |||
className="display-inline-block spacer-bottom" | |||
htmlFor="set-license" | |||
> | |||
marketplace.enter_license_for_x.Foo | |||
<em | |||
className="mandatory" | |||
> | |||
* | |||
</em> | |||
</label> | |||
<textarea | |||
autoFocus={true} | |||
className="display-block input-super-large" | |||
id="set-license" | |||
onChange={[Function]} | |||
required={true} | |||
rows={8} | |||
style={ | |||
Object { | |||
"resize": "none", | |||
} | |||
} | |||
value="" | |||
/> | |||
<DeferredSpinner | |||
customSpinner={ | |||
<p | |||
className="spacer-top" | |||
> | |||
<i | |||
className="spinner spacer-right text-bottom" | |||
/> | |||
marketplace.checking_license | |||
</p> | |||
} | |||
loading={false} | |||
timeout={100} | |||
> | |||
<div | |||
className="spacer-top" | |||
> | |||
<a | |||
href="license_url" | |||
target="_blank" | |||
> | |||
marketplace.i_need_a_license | |||
</a> | |||
</div> | |||
</DeferredSpinner> | |||
</div> | |||
`; | |||
exports[`should display the get license link with parameters 1`] = ` | |||
<a | |||
href="license_url?serverId=foo&ncloc=1000" | |||
target="_blank" | |||
> | |||
marketplace.i_need_a_license | |||
</a> | |||
`; |
@@ -1,39 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should display correctly 1`] = ` | |||
<Modal | |||
contentLabel="marketplace.downgrade_to_community_edition." | |||
onRequestClose={[MockFunction]} | |||
> | |||
<header | |||
className="modal-head" | |||
> | |||
<h2> | |||
marketplace.downgrade_to_community_edition. | |||
</h2> | |||
</header> | |||
<div | |||
className="modal-body" | |||
> | |||
<p> | |||
marketplace.uninstall_x_confirmation.Foo | |||
</p> | |||
</div> | |||
<footer | |||
className="modal-foot" | |||
> | |||
<Button | |||
disabled={false} | |||
onClick={[Function]} | |||
> | |||
marketplace.downgrade | |||
</Button> | |||
<ResetButtonLink | |||
className="js-modal-close" | |||
onClick={[MockFunction]} | |||
> | |||
cancel | |||
</ResetButtonLink> | |||
</footer> | |||
</Modal> | |||
`; |
@@ -35,6 +35,11 @@ | |||
margin-right: 8px; | |||
} | |||
.marketplace-edition .markdown h3 { | |||
font-size: 14px; | |||
margin-top: 0; | |||
} | |||
.marketplace-edition-badge { | |||
position: absolute; | |||
right: -1px; |
@@ -17,26 +17,58 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { memoize, sortBy } from 'lodash'; | |||
import { stringify } from 'querystring'; | |||
import { memoize } from 'lodash'; | |||
import { Plugin, PluginAvailable, PluginInstalled, PluginPending } from '../../api/plugins'; | |||
import { cleanQuery, parseAsString, RawQuery, serializeString } from '../../helpers/query'; | |||
import { Edition } from '../../api/marketplace'; | |||
import { omitNil } from '../../helpers/request'; | |||
export interface Edition { | |||
downloadUrl?: string; | |||
homeUrl: string; | |||
key: string; | |||
} | |||
export interface Query { | |||
filter: string; | |||
search?: string; | |||
} | |||
export function isPluginAvailable(plugin: Plugin): plugin is PluginAvailable { | |||
return (plugin as any).release !== undefined; | |||
} | |||
export const EDITIONS: Edition[] = [ | |||
{ | |||
key: 'community', | |||
homeUrl: 'https://redirect.sonarsource.com/editions/community.html' | |||
}, | |||
{ | |||
key: 'developer', | |||
homeUrl: 'https://redirect.sonarsource.com/editions/developer.html', | |||
downloadUrl: | |||
'https://sonarsource.bintray.com/CommercialDistribution/editions/developer-edition-7.0.0.717.zip' | |||
}, | |||
{ | |||
key: 'enterprise', | |||
homeUrl: 'https://redirect.sonarsource.com/editions/enterprise.html', | |||
downloadUrl: | |||
'https://sonarsource.bintray.com/CommercialDistribution/editions/enterprise-edition-7.0.0.717.zip' | |||
}, | |||
{ | |||
key: 'datacenter', | |||
homeUrl: 'https://redirect.sonarsource.com/editions/datacenter.html', | |||
downloadUrl: | |||
'https://sonarsource.bintray.com/CommercialDistribution/editions/datacenter-edition-7.0.0.717.zip' | |||
} | |||
]; | |||
export function isPluginInstalled(plugin: Plugin): plugin is PluginInstalled { | |||
return isPluginPending(plugin) && (plugin as any).updatedAt !== undefined; | |||
} | |||
export function isPluginPending(plugin: Plugin): plugin is PluginPending { | |||
return (plugin as any).version !== undefined; | |||
export function getEditionUrl( | |||
edition: Edition, | |||
data: { serverId?: string; ncloc?: number; sourceEdition: string } | |||
) { | |||
let url = edition.homeUrl; | |||
const query = stringify(omitNil(data)); | |||
if (query) { | |||
url += '?' + query; | |||
} | |||
return url; | |||
} | |||
export function filterPlugins(plugins: Plugin[], search: string): Plugin[] { | |||
@@ -50,9 +82,16 @@ export function filterPlugins(plugins: Plugin[], search: string): Plugin[] { | |||
}); | |||
} | |||
const EDITIONS_ORDER = ['community', 'developer', 'enterprise', 'datacenter']; | |||
export function sortEditions(editions: Edition[]): Edition[] { | |||
return sortBy(editions, edition => EDITIONS_ORDER.indexOf(edition.key)); | |||
export function isPluginAvailable(plugin: Plugin): plugin is PluginAvailable { | |||
return (plugin as any).release !== undefined; | |||
} | |||
export function isPluginInstalled(plugin: Plugin): plugin is PluginInstalled { | |||
return isPluginPending(plugin) && (plugin as any).updatedAt !== undefined; | |||
} | |||
export function isPluginPending(plugin: Plugin): plugin is PluginPending { | |||
return (plugin as any).version !== undefined; | |||
} | |||
export const DEFAULT_FILTER = 'all'; |
@@ -18,62 +18,34 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { Dispatch } from 'react-redux'; | |||
import { getEditionsForVersion, getEditionsForLastVersion } from './utils'; | |||
import { Edition, EditionStatus, getEditionStatus, getEditionsList } from '../../api/marketplace'; | |||
import { getEditionStatus } from '../../api/marketplace'; | |||
import { getPendingPlugins, PluginPendingResult } from '../../api/plugins'; | |||
interface LoadEditionsAction { | |||
type: 'LOAD_EDITIONS'; | |||
loading: boolean; | |||
} | |||
interface SetPendingPluginsAction { | |||
type: 'SET_PENDING_PLUGINS'; | |||
pending: PluginPendingResult; | |||
} | |||
interface SetEditionsAction { | |||
type: 'SET_EDITIONS'; | |||
editions: Edition[]; | |||
readOnly: boolean; | |||
} | |||
interface SetEditionStatusAction { | |||
type: 'SET_EDITION_STATUS'; | |||
status: EditionStatus; | |||
interface SetCurrentEditionAction { | |||
type: 'SET_CURRENT_EDITION'; | |||
currentEdition?: string; | |||
} | |||
export type Action = | |||
| LoadEditionsAction | |||
| SetEditionsAction | |||
| SetEditionStatusAction | |||
| SetPendingPluginsAction; | |||
export function loadEditions(loading = true): LoadEditionsAction { | |||
return { type: 'LOAD_EDITIONS', loading }; | |||
} | |||
export type Action = SetCurrentEditionAction | SetPendingPluginsAction; | |||
export function setPendingPlugins(pending: PluginPendingResult): SetPendingPluginsAction { | |||
return { type: 'SET_PENDING_PLUGINS', pending }; | |||
} | |||
export function setEditions(editions: Edition[], readOnly?: boolean): SetEditionsAction { | |||
return { type: 'SET_EDITIONS', editions, readOnly: !!readOnly }; | |||
} | |||
export const setCurrentEdition = (currentEdition?: string) => (dispatch: Dispatch<Action>) => { | |||
dispatch({ type: 'SET_CURRENT_EDITION', currentEdition }); | |||
}; | |||
let editionTimer: number | undefined; | |||
export const setEditionStatus = (status: EditionStatus) => (dispatch: Dispatch<Action>) => { | |||
dispatch({ type: 'SET_EDITION_STATUS', status }); | |||
if (editionTimer) { | |||
window.clearTimeout(editionTimer); | |||
editionTimer = undefined; | |||
} | |||
if (status.installationStatus === 'AUTOMATIC_IN_PROGRESS') { | |||
editionTimer = window.setTimeout(() => { | |||
getEditionStatus().then(status => setEditionStatus(status)(dispatch), () => {}); | |||
editionTimer = undefined; | |||
}, 2000); | |||
} | |||
export const fetchCurrentEdition = () => (dispatch: Dispatch<Action>) => { | |||
getEditionStatus().then( | |||
editionStatus => dispatch(setCurrentEdition(editionStatus.currentEditionKey)), | |||
() => {} | |||
); | |||
}; | |||
export const fetchPendingPlugins = () => (dispatch: Dispatch<Action>) => { | |||
@@ -84,18 +56,3 @@ export const fetchPendingPlugins = () => (dispatch: Dispatch<Action>) => { | |||
() => {} | |||
); | |||
}; | |||
export const fetchEditions = (url: string, version: string) => (dispatch: Dispatch<Action>) => { | |||
dispatch(loadEditions(true)); | |||
getEditionsList(url).then( | |||
editionsPerVersion => { | |||
const editions = getEditionsForVersion(editionsPerVersion, version); | |||
if (editions) { | |||
dispatch(setEditions(editions)); | |||
} else { | |||
dispatch(setEditions(getEditionsForLastVersion(editionsPerVersion), true)); | |||
} | |||
}, | |||
() => dispatch(loadEditions(false)) | |||
); | |||
}; |
@@ -18,48 +18,30 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { Action } from './actions'; | |||
import { Edition, EditionStatus } from '../../api/marketplace'; | |||
import { PluginPendingResult } from '../../api/plugins'; | |||
interface State { | |||
editions?: Edition[]; | |||
loading: boolean; | |||
status?: EditionStatus; | |||
readOnly: boolean; | |||
currentEdition?: string; | |||
pending: PluginPendingResult; | |||
} | |||
const defaultState: State = { | |||
loading: true, | |||
readOnly: false, | |||
pending: { installing: [], removing: [], updating: [] } | |||
}; | |||
const defaultState: State = { pending: { installing: [], removing: [], updating: [] } }; | |||
export default function(state: State = defaultState, action: Action): State { | |||
if (action.type === 'SET_EDITIONS') { | |||
return { ...state, editions: action.editions, readOnly: action.readOnly, loading: false }; | |||
} | |||
if (action.type === 'LOAD_EDITIONS') { | |||
return { ...state, loading: action.loading }; | |||
} | |||
if (action.type === 'SET_PENDING_PLUGINS') { | |||
return { | |||
...state, | |||
pending: action.pending | |||
}; | |||
} | |||
if (action.type === 'SET_EDITION_STATUS') { | |||
const hasChanged = Object.keys(action.status).some( | |||
(key: keyof EditionStatus) => !state.status || state.status[key] !== action.status[key] | |||
); | |||
// Prevent from rerendering the whole admin if the status didn't change | |||
if (hasChanged) { | |||
return { ...state, status: action.status }; | |||
} | |||
if (action.type === 'SET_CURRENT_EDITION') { | |||
return { | |||
...state, | |||
currentEdition: action.currentEdition | |||
}; | |||
} | |||
return state; | |||
} | |||
export const getEditions = (state: State) => state.editions; | |||
export const getEditionStatus = (state: State) => state.status; | |||
export const getCurrentEdition = (state: State) => state.currentEdition; | |||
export const getPendingPlugins = (state: State) => state.pending; |
@@ -1,49 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 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 { sortBy } from 'lodash'; | |||
import { Edition, EditionsPerVersion } from '../../api/marketplace'; | |||
export function getEditionsForLastVersion(editions: EditionsPerVersion): Edition[] { | |||
const sortedVersion = sortBy(Object.keys(editions), [ | |||
(version: string) => -Number(version.split('.')[0]), | |||
(version: string) => -Number(version.split('.')[1] || 0), | |||
(version: string) => -Number(version.split('.')[2] || 0) | |||
]); | |||
return editions[sortedVersion[0]]; | |||
} | |||
export function getEditionsForVersion( | |||
editions: EditionsPerVersion, | |||
version: string | |||
): Edition[] | undefined { | |||
const minorVersion = version.match(/\d+\.\d+.\d+/); | |||
if (minorVersion) { | |||
if (editions[minorVersion[0]]) { | |||
return editions[minorVersion[0]]; | |||
} | |||
} | |||
const majorVersion = version.match(/\d+\.\d+/); | |||
if (majorVersion) { | |||
if (editions[majorVersion[0]]) { | |||
return editions[majorVersion[0]]; | |||
} | |||
} | |||
return undefined; | |||
} |
@@ -73,10 +73,8 @@ export const isFavorite = (state, componentKey) => | |||
export const getMarketplaceState = state => state.marketplace; | |||
export const getMarketplaceEditions = state => fromMarketplace.getEditions(state.marketplace); | |||
export const getMarketplaceEditionStatus = state => | |||
fromMarketplace.getEditionStatus(state.marketplace); | |||
export const getMarketplaceCurrentEdition = state => | |||
fromMarketplace.getCurrentEdition(state.marketplace); | |||
export const getMarketplacePendingPlugins = state => | |||
fromMarketplace.getPendingPlugins(state.marketplace); |
@@ -47,7 +47,6 @@ public class CorePropertyDefinitions { | |||
public static final String ORGANIZATIONS_CREATE_PERSONAL_ORG = "sonar.organizations.createPersonalOrg"; | |||
public static final String ONBOARDING_TUTORIAL_SHOW_TO_NEW_USERS = "sonar.onboardingTutorial.showToNewUsers"; | |||
public static final String DISABLE_NOTIFICATION_ON_BUILT_IN_QPROFILES = "sonar.builtInQualityProfiles.disableNotificationOnUpdate"; | |||
public static final String EDITIONS_CONFIG_URL = "sonar.editions.jsonUrl"; | |||
private CorePropertyDefinitions() { | |||
// only static stuff | |||
@@ -232,15 +231,6 @@ public class CorePropertyDefinitions { | |||
.category(CATEGORY_ORGANIZATIONS) | |||
.type(BOOLEAN) | |||
.hidden() | |||
.build(), | |||
// EDITIONS | |||
PropertyDefinition.builder(EDITIONS_CONFIG_URL) | |||
.name("Defines URL of JSON file with the definitions of SonarSource editions.") | |||
.defaultValue("https://update.sonarsource.org/editions.json") | |||
.category(CATEGORY_ORGANIZATIONS) | |||
.type(BOOLEAN) | |||
.hidden() | |||
.build())); | |||
return defs; | |||
} |
@@ -2150,9 +2150,6 @@ marketplace.installed=Installed | |||
marketplace.installing=Installing... | |||
marketplace.upgrade=Upgrade | |||
marketplace.downgrade=Downgrade | |||
marketplace.downgrade_to_community_edition=Downgrade to Community Edition | |||
marketplace.uninstall_x_confirmation=Are you sure you want to uninstall {0} and get back to the Community Edition? | |||
marketplace.pending=Pending... | |||
marketplace.checking_license=Checking your license... | |||
marketplace._installed=installed | |||
marketplace.available_under_commercial_license=Available under our commercial editions | |||
@@ -2178,24 +2175,12 @@ marketplace.uninstall=Uninstall | |||
marketplace.i_accept_the=I accept the | |||
marketplace.commercial_edition=Commercial Edition | |||
marketplace.terms_and_conditions=Terms and Conditions | |||
marketplace.editions_unavailable=Explore our Commercial Editions on {url}: advanced feature packs brought to you by SonarSource | |||
marketplace.edition_status.AUTOMATIC_IN_PROGRESS=Installing your new Commercial Edition... Please wait... | |||
marketplace.edition_status.AUTOMATIC_READY=Commercial Edition successfully installed. Please restart the server to activate your new edition. | |||
marketplace.edition_status.UNINSTALL_IN_PROGRESS=Commercial Edition successfully downgraded. Please restart the server to remove the features. | |||
marketplace.edition_status.MANUAL_IN_PROGRESS=Commercial Edition can't automatically be installed because of internet access issues. Please manually install the package. | |||
marketplace.edition_status_x.AUTOMATIC_IN_PROGRESS=Installing your new {0}... Please wait... | |||
marketplace.edition_status_x.AUTOMATIC_READY={0} successfully installed. Please restart the server to activate your new edition. | |||
marketplace.edition_status_x.UNINSTALL_IN_PROGRESS=Successfully downgraded to {0}. Please restart the server to remove the features. | |||
marketplace.edition_status_x.MANUAL_IN_PROGRESS={0} can't automatically be installed because of internet access issues. Please manually install the package. | |||
marketplace.release_notes=Release Notes | |||
marketplace.how_to_install=How to install it? | |||
marketplace.see_documentation_to_enable_cluster=See {url} documentation to set up a cluster. | |||
marketplace.how_to_setup_cluster_url=Further configuration is required to set up a cluster. See {url} documentation. | |||
marketplace.enter_license_for_x=Enter your license key for {0} | |||
marketplace.wrong_license_type=Your license is not compatible with any existing editions. Please provide a valid license. | |||
marketplace.wrong_license_type_x=Your license is not compatible with the selected edition. Please provide a valid license for {0}. | |||
marketplace.i_need_a_license=I need a license key | |||
marketplace.download_package=Download package | |||
marketplace.search=Search by features, tags, or categories... | |||
@@ -30,7 +30,7 @@ public class CorePropertyDefinitionsTest { | |||
@Test | |||
public void all() { | |||
List<PropertyDefinition> defs = CorePropertyDefinitions.all(); | |||
assertThat(defs).hasSize(59); | |||
assertThat(defs).hasSize(58); | |||
} | |||
@Test |