import { Edition, EditionStatus, getEditionsList, getEditionStatus } from '../../api/marketplace';
import { RawQuery } from '../../helpers/query';
import { translate } from '../../helpers/l10n';
-import { getEditionsForVersion, filterPlugins, parseQuery, Query, serializeQuery } from './utils';
+import {
+ getEditionsForLastVersion,
+ getEditionsForVersion,
+ filterPlugins,
+ parseQuery,
+ Query,
+ serializeQuery
+} from './utils';
export interface Props {
editionsUrl: string;
interface State {
editions?: Edition[];
+ editionsReadOnly: boolean;
editionStatus?: EditionStatus;
loadingEditions: boolean;
loadingPlugins: boolean;
constructor(props: Props) {
super(props);
this.state = {
+ editionsReadOnly: false,
loadingEditions: true,
loadingPlugins: true,
pending: {
fetchAllPlugins = () => {
this.setState({ loadingPlugins: true });
- Promise.all([getInstalledPluginsWithUpdates(), getAvailablePlugins()]).then(
- ([installed, available]) => {
- if (this.mounted) {
- this.setState({
- loadingPlugins: false,
- plugins: sortBy(uniqBy([...installed, ...available.plugins], 'key'), 'name')
- });
- }
- },
- () => {
- if (this.mounted) {
- this.setState({ loadingPlugins: false });
- }
+ Promise.all([
+ getInstalledPluginsWithUpdates(),
+ getAvailablePlugins()
+ ]).then(([installed, available]) => {
+ if (this.mounted) {
+ this.setState({
+ loadingPlugins: false,
+ plugins: sortBy(uniqBy([...installed, ...available.plugins], 'key'), 'name')
+ });
}
- );
+ }, this.stopLoadingPlugins);
};
fetchUpdatesOnly = () => {
this.setState({ loadingPlugins: true });
- getPluginUpdates().then(
- plugins => {
- if (this.mounted) {
- this.setState({ loadingPlugins: false, plugins });
- }
- },
- () => {
- if (this.mounted) {
- this.setState({ loadingPlugins: false });
- }
+ getPluginUpdates().then(plugins => {
+ if (this.mounted) {
+ this.setState({ loadingPlugins: false, plugins });
}
- );
+ }, this.stopLoadingPlugins);
};
fetchPendingPlugins = () =>
getEditionsList(this.props.editionsUrl).then(
editionsPerVersion => {
if (this.mounted) {
- this.setState({
+ const newState = {
editions: getEditionsForVersion(editionsPerVersion, this.props.sonarqubeVersion),
+ editionsReadOnly: false,
loadingEditions: false
- });
+ };
+ if (!newState.editions) {
+ newState.editions = getEditionsForLastVersion(editionsPerVersion);
+ newState.editionsReadOnly = true;
+ }
+ this.setState(newState);
}
},
() => {
this.context.router.push({ pathname: this.props.location.pathname, query });
};
+ stopLoadingPlugins = () => {
+ if (this.mounted) {
+ this.setState({ loadingPlugins: false });
+ }
+ };
+
render() {
const { standaloneMode } = this.props;
const { editions, editionStatus, loadingPlugins, plugins, pending } = this.state;
updateEditionStatus={this.updateEditionStatus}
/>
)}
- {!standaloneMode && (
+ {standaloneMode && (
<PendingActions refreshPending={this.fetchPendingPlugins} pending={pending} />
)}
</div>
loading={this.state.loadingEditions}
editionStatus={editionStatus}
editionsUrl={this.props.editionsUrl}
- readOnly={!standaloneMode}
+ readOnly={!standaloneMode || this.state.editionsReadOnly}
sonarqubeVersion={this.props.sonarqubeVersion}
updateCenterActive={this.props.updateCenterActive}
updateEditionStatus={this.updateEditionStatus}
/>
);
}
+ return null;
};
render() {
if (isInstalled) {
return (
<span className="marketplace-edition-badge badge badge-normal-size">
- <CheckIcon size={14} className="little-spacer-right text-text-top" />
+ <CheckIcon size={14} className="little-spacer-right text-bottom" />
{translate('marketplace.installed')}
</span>
);
render() {
const { edition, editionStatus, readOnly } = this.props;
const isInstalled = editionStatus && editionStatus.currentEditionKey === edition.key;
+ const uninstallInProgress =
+ editionStatus && editionStatus.installationStatus === 'UNINSTALL_IN_PROGRESS';
const installInProgress =
editionStatus &&
['AUTOMATIC_IN_PROGRESS', 'AUTOMATIC_READY'].includes(editionStatus.installationStatus);
{translate('marketplace.learn_more')}
</a>
{!readOnly &&
- !isInstalled && (
- <button disabled={installInProgress} onClick={this.handleInstall}>
- {translate('marketplace.install')}
- </button>
- )}
- {!readOnly &&
- isInstalled && (
- <button
- className="button-red"
- disabled={installInProgress}
- onClick={this.props.onUninstall}>
- {translate('marketplace.uninstall')}
- </button>
- )}
+ (isInstalled ? (
+ <button
+ className="button-red"
+ disabled={installInProgress || uninstallInProgress}
+ onClick={this.props.onUninstall}>
+ {translate('marketplace.uninstall')}
+ </button>
+ ) : (
+ <button
+ disabled={installInProgress || uninstallInProgress}
+ onClick={this.handleInstall}>
+ {translate('marketplace.install')}
+ </button>
+ ))}
</div>
</div>
);
);
};
- renderStatusAlert() {
+ renderRestartMsg(edition?: Edition) {
const { editionStatus, readOnly } = this.props;
+ return (
+ <div className="alert alert-success">
+ <span>
+ {edition ? (
+ translateWithParameters(
+ 'marketplace.status_x.' + editionStatus.installationStatus,
+ edition.name
+ )
+ ) : (
+ translate('marketplace.status', editionStatus.installationStatus)
+ )}
+ </span>
+ {!readOnly && (
+ <button className="js-restart spacer-left" onClick={this.handleOpenRestart}>
+ {translate('marketplace.restart')}
+ </button>
+ )}
+ {!readOnly && this.state.openRestart && <RestartForm onClose={this.hanleCloseRestart} />}
+ </div>
+ );
+ }
+
+ renderManualMsg(edition?: Edition) {
+ const { editionStatus } = this.props;
+ return (
+ <div className="alert alert-danger">
+ {edition ? (
+ translateWithParameters(
+ 'marketplace.status_x.' + editionStatus.installationStatus,
+ edition.name
+ )
+ ) : (
+ translate('marketplace.status', editionStatus.installationStatus)
+ )}
+ <p className="spacer-left">
+ {edition && (
+ <a
+ className="button spacer-right"
+ download={`sonarqube-${edition.name}.zip`}
+ href={edition.downloadUrl}
+ target="_blank">
+ {translate('marketplace.download_package')}
+ </a>
+ )}
+ <a
+ href="https://redirect.sonarsource.com/doc/how-to-install-an-edition.html"
+ target="_blank">
+ {translate('marketplace.how_to_install')}
+ </a>
+ </p>
+ <a className="little-spacer-left" href="https://www.sonarsource.com" target="_blank">
+ {translate('marketplace.how_to_install')}
+ </a>
+ </div>
+ );
+ }
+
+ renderStatusAlert() {
+ const { editionStatus } = this.props;
const { installationStatus, nextEditionKey } = editionStatus;
const nextEdition =
this.props.editions && this.props.editions.find(edition => edition.key === nextEditionKey);
);
case 'AUTOMATIC_READY':
case 'UNINSTALL_IN_PROGRESS':
- return (
- <div className="alert alert-success">
- <span>
- {nextEdition ? (
- translateWithParameters(
- 'marketplace.status_x.' + installationStatus,
- nextEdition.name
- )
- ) : (
- translate('marketplace.status', installationStatus)
- )}
- </span>
- {!readOnly && (
- <button className="js-restart spacer-left" onClick={this.handleOpenRestart}>
- {translate('marketplace.restart')}
- </button>
- )}
- {!readOnly &&
- this.state.openRestart && <RestartForm onClose={this.hanleCloseRestart} />}
- </div>
- );
+ return this.renderRestartMsg(nextEdition);
case 'MANUAL_IN_PROGRESS':
- return (
- <div className="alert alert-danger">
- {nextEdition ? (
- translateWithParameters(
- 'marketplace.status_x.' + installationStatus,
- nextEdition.name
- )
- ) : (
- translate('marketplace.status', installationStatus)
- )}
- <p className="spacer-left">
- {nextEdition && (
- <a
- className="button spacer-right"
- download={`sonarqube-${nextEdition.name}.zip`}
- href={nextEdition.downloadUrl}
- target="_blank">
- {translate('marketplace.download_package')}
- </a>
- )}
- <a
- href="https://redirect.sonarsource.com/doc/how-to-install-an-edition.html"
- target="_blank">
- {translate('marketplace.how_to_install')}
- </a>
- </p>
- <a className="little-spacer-left" href="https://www.sonarsource.com" target="_blank">
- {translate('marketplace.how_to_install')}
- </a>
- </div>
- );
+ return this.renderManualMsg(nextEdition);
}
return null;
}
return (
<div>
{installError && (
- <div className="alert alert-danger">
+ <div className="alert alert-danger alert-cancel">
{installError}
- <a
- className="pull-right button-link text-danger"
- href="#"
- onClick={this.handleDismissError}>
+ <a className="button-link text-danger" href="#" onClick={this.handleDismissError}>
<CloseIcon />
</a>
</div>
<textarea
autoFocus={true}
id="set-license"
- className="display-block"
- cols={62}
+ className="display-block input-super-large"
onChange={this.handleLicenseChange}
required={true}
- rows={6}
+ rows={8}
+ style={{ resize: 'none' }}
value={license}
/>
{previewStatus && (
handleUninstall = () => this.doPluginAction(uninstallPlugin);
handleTermsCheck = (checked: boolean) => this.setState({ acceptTerms: checked });
+ renderBundled() {
+ const { plugin } = this.props;
+
+ return (
+ <div className="js-actions">
+ {isPluginAvailable(plugin) && (
+ <div>
+ <p className="little-spacer-bottom">
+ {translate('marketplace.available_under_commercial_license')}
+ </p>
+ <a href={plugin.homepageUrl} target="_blank">
+ {translate('marketplace.learn_more')}
+ </a>
+ </div>
+ )}
+ {isPluginInstalled(plugin) && (
+ <p>
+ <CheckIcon className="little-spacer-right" />
+ {translate('marketplace.installed')}
+ </p>
+ )}
+ {isPluginInstalled(plugin) &&
+ plugin.updates &&
+ plugin.updates.length > 0 && (
+ <div className="spacer-top button-group">
+ {plugin.updates.map((update, idx) => (
+ <PluginUpdateButton
+ key={idx}
+ onClick={this.handleUpdate}
+ update={update}
+ disabled={this.state.loading}
+ />
+ ))}
+ </div>
+ )}
+ </div>
+ );
+ }
+
render() {
const { plugin } = this.props;
- const { loading } = this.state;
if (plugin.editionBundled) {
- return (
- <div className="js-actions">
- {isPluginAvailable(plugin) && (
- <div>
- <p className="little-spacer-bottom">
- {translate('marketplace.available_under_commercial_license')}
- </p>
- <a href={plugin.homepageUrl} target="_blank">
- {translate('marketplace.learn_more')}
- </a>
- </div>
- )}
- {isPluginInstalled(plugin) && (
- <p>
- <CheckIcon className="little-spacer-right" />
- {translate('marketplace.installed')}
- </p>
- )}
- {isPluginInstalled(plugin) &&
- plugin.updates &&
- plugin.updates.length > 0 && (
- <div className="spacer-top button-group">
- {plugin.updates.map((update, idx) => (
- <PluginUpdateButton
- key={idx}
- onClick={this.handleUpdate}
- update={update}
- disabled={loading}
- />
- ))}
- </div>
- )}
- </div>
- );
+ return this.renderBundled();
}
+ const { loading } = this.state;
return (
<div className="js-actions">
{isPluginAvailable(plugin) &&
.then(() => {
this.props.updateEditionStatus({
...this.props.editionStatus,
+ currentEditionKey: undefined,
installationStatus: 'UNINSTALL_IN_PROGRESS'
});
this.props.onClose();
expect(uninstallEdition).toHaveBeenCalled();
await new Promise(setImmediate);
expect(updateEditionStatus).toHaveBeenCalledWith({
- currentEditionKey: 'foo',
+ currentEditionKey: undefined,
installationStatus: 'UNINSTALL_IN_PROGRESS'
});
});
className="marketplace-edition-badge badge badge-normal-size"
>
<CheckIcon
- className="little-spacer-right text-text-top"
+ className="little-spacer-right text-bottom"
size={14}
/>
marketplace.installed
exports[`should display install errors 1`] = `
<div>
<div
- className="alert alert-danger"
+ className="alert alert-danger alert-cancel"
>
Foo error
<a
- className="pull-right button-link text-danger"
+ className="button-link text-danger"
href="#"
onClick={[Function]}
>
</label>
<textarea
autoFocus={true}
- className="display-block"
- cols={62}
+ className="display-block input-super-large"
id="set-license"
onChange={[Function]}
required={true}
- rows={6}
+ rows={8}
+ style={
+ Object {
+ "resize": "none",
+ }
+ }
value=""
/>
<a
* 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 } from 'lodash';
+import { memoize, sortBy } from 'lodash';
import { Plugin, PluginAvailable, PluginInstalled, PluginPending } from '../../api/plugins';
import { Edition, EditionsPerVersion } from '../../api/marketplace';
import { cleanQuery, parseAsString, RawQuery, serializeString } from '../../helpers/query';
});
}
+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
.alert-emphasis-variant(#3c763d, #dff0d8, #d6e9c6);
}
-.page-notifs .alert {
+.alert-cancel {
display: flex;
justify-content: space-between;
+}
+
+.page-notifs .alert {
padding: 8px 10px;
}
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: advanced feature packs brought to you by SonarSource on {url}
+marketplace.editions_unavailable=Explore our Commercial Editions on {url}: advanced feature packs brought to you by SonarSource
marketplace.release_notes=Release Notes
marketplace.status.AUTOMATIC_IN_PROGRESS=Updating your installation... Please wait...
-marketplace.status.AUTOMATIC_READY=Commercial Edition successfully installed. Please restart Server to activate your new features.
-marketplace.status.UNINSTALL_IN_PROGRESS=Commercial Edition successfully uninstalled. Please restart Server to remove the features.
+marketplace.status.AUTOMATIC_READY=Commercial Edition successfully installed. Please restart the server to activate your new features.
+marketplace.status.UNINSTALL_IN_PROGRESS=Commercial Edition successfully uninstalled. Please restart the server to remove the features.
marketplace.status.MANUAL_IN_PROGRESS=Can't install Commercial Edition because of internet access issue. Please manually install the package in your SonarQube's plugins folder.
-marketplace.status_x.AUTOMATIC_READY={0} successfully installed. Please restart Server to activate your new features.
-marketplace.status_X.UNINSTALL_IN_PROGRESS={0} successfully uninstalled. Please restart Server to remove the features.
+marketplace.status_x.AUTOMATIC_READY={0} successfully installed. Please resstart the server to activate your new features.
+marketplace.status_X.UNINSTALL_IN_PROGRESS={0} successfully uninstalled. Please restart the server to remove the features.
marketplace.status_x.MANUAL_IN_PROGRESS=Can't install {0} because of internet access issue. Please manually install the package in your SonarQube's plugins folder.
marketplace.how_to_install=How to install it?
marketplace.enter_license_for_x=Enter your license key for {0}