From: Grégoire Aubert Date: Wed, 11 Oct 2017 15:32:30 +0000 (+0200) Subject: SONAR-9935 Migrate Update center to Marketplace X-Git-Tag: 6.7-RC1~116 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=847b1833f8f86657e9ddc206fab04e29ec5dbdc9;p=sonarqube.git SONAR-9935 Migrate Update center to Marketplace --- diff --git a/server/sonar-web/src/main/js/api/plugins.ts b/server/sonar-web/src/main/js/api/plugins.ts new file mode 100644 index 00000000000..75004b3fc1d --- /dev/null +++ b/server/sonar-web/src/main/js/api/plugins.ts @@ -0,0 +1,168 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import { getJSON, post } from '../helpers/request'; +import { findLastIndex } from 'lodash'; +import throwGlobalError from '../app/utils/throwGlobalError'; + +export interface Plugin { + key: string; + name: string; + description: string; + category?: string; + license?: string; + organizationName?: string; + organizationUrl?: string; + homepageUrl?: string; + issueTrackerUrl?: string; + termsAndConditionsUrl?: string; +} + +export interface Release { + version: string; + date: string; + description?: string; + changeLogUrl?: string; +} + +export interface Update { + status: string; + release?: Release; + requires: Plugin[]; + previousUpdates?: Update[]; +} + +export interface PluginAvailable extends Plugin { + release: Release; + update: Update; +} + +export interface PluginPending extends Plugin { + version: string; + implementationBuild: string; +} + +export interface PluginInstalled extends PluginPending { + filename: string; + hash: string; + sonarLintSupported: boolean; + updatedAt: number; + updates?: Update[]; +} + +export function getAvailablePlugins(): Promise<{ + plugins: PluginAvailable[]; + updateCenterRefresh: string; +}> { + return getJSON('/api/plugins/available').catch(throwGlobalError); +} + +export function getPendingPlugins(): Promise<{ + installing: PluginPending[]; + updating: PluginPending[]; + removing: PluginPending[]; +}> { + return getJSON('/api/plugins/pending').catch(throwGlobalError); +} + +function getLastUpdates(updates: undefined | Update[]): Update[] { + if (!updates) { + return []; + } + const lastUpdate = [ + 'INCOMPATIBLE', + 'REQUIRES_SYSTEM_UPGRADE', + 'DEPS_REQUIRE_SYSTEM_UPGRADE' + ].map(status => { + const index = findLastIndex(updates, update => update.status === status); + return index > -1 ? updates[index] : undefined; + }); + return lastUpdate.filter(Boolean) as Update[]; +} + +function addChangelog(update: Update, updates?: Update[]) { + if (!updates) { + return update; + } + const index = updates.indexOf(update); + const previousUpdates = index > 0 ? updates.slice(0, index) : []; + return { ...update, previousUpdates }; +} + +export function getInstalledPluginsWithUpdates(): Promise { + return Promise.all([ + getJSON('/api/plugins/installed', { f: 'category' }), + getJSON('/api/plugins/updates') + ]) + .then(([installed, updates]) => + installed.plugins.map((plugin: PluginInstalled) => { + const updatePlugin: PluginInstalled = updates.plugins.find( + (p: PluginInstalled) => p.key === plugin.key + ); + if (updatePlugin) { + return { + ...updatePlugin, + ...plugin, + updates: getLastUpdates(updatePlugin.updates).map(update => + addChangelog(update, updatePlugin.updates) + ) + }; + } + return plugin; + }) + ) + .catch(throwGlobalError); +} + +export function getPluginUpdates(): Promise { + return Promise.all([getJSON('/api/plugins/updates'), getJSON('/api/plugins/installed')]) + .then(([updates, installed]) => + updates.plugins.map((updatePlugin: PluginInstalled) => { + const updates = getLastUpdates(updatePlugin.updates).map(update => + addChangelog(update, updatePlugin.updates) + ); + const plugin = installed.plugins.find((p: PluginInstalled) => p.key === updatePlugin.key); + if (plugin) { + return { + ...plugin, + ...updatePlugin, + updates + }; + } + return { ...updatePlugin, updates }; + }) + ) + .catch(throwGlobalError); +} + +export function installPlugin(data: { key: string }): Promise { + return post('/api/plugins/install', data).catch(throwGlobalError); +} + +export function uninstallPlugin(data: { key: string }): Promise { + return post('/api/plugins/uninstall', data).catch(throwGlobalError); +} + +export function updatePlugin(data: { key: string }): Promise { + return post('/api/plugins/update', data).catch(throwGlobalError); +} + +export function cancelPendingPlugins(): Promise { + return post('/api/plugins/cancel_all').catch(throwGlobalError); +} diff --git a/server/sonar-web/src/main/js/app/components/nav/settings/SettingsNav.js b/server/sonar-web/src/main/js/app/components/nav/settings/SettingsNav.js index 69cbec3e6dd..e307778eec4 100644 --- a/server/sonar-web/src/main/js/app/components/nav/settings/SettingsNav.js +++ b/server/sonar-web/src/main/js/app/components/nav/settings/SettingsNav.js @@ -192,6 +192,12 @@ class SettingsNav extends React.PureComponent { +
  • + + {translate('marketplace.page')} + +
  • + {hasSupportExtension && (
  • diff --git a/server/sonar-web/src/main/js/app/components/nav/settings/__tests__/__snapshots__/SettingsNav-test.js.snap b/server/sonar-web/src/main/js/app/components/nav/settings/__tests__/__snapshots__/SettingsNav-test.js.snap index d70451a6f1d..62c113f24bb 100644 --- a/server/sonar-web/src/main/js/app/components/nav/settings/__tests__/__snapshots__/SettingsNav-test.js.snap +++ b/server/sonar-web/src/main/js/app/components/nav/settings/__tests__/__snapshots__/SettingsNav-test.js.snap @@ -188,6 +188,14 @@ exports[`should work with extensions 1`] = `
  • +
  • + + marketplace.page + +
  • `; diff --git a/server/sonar-web/src/main/js/app/utils/startReactApp.js b/server/sonar-web/src/main/js/app/utils/startReactApp.js index 06dc6283e04..b71489f5e86 100644 --- a/server/sonar-web/src/main/js/app/utils/startReactApp.js +++ b/server/sonar-web/src/main/js/app/utils/startReactApp.js @@ -48,6 +48,7 @@ import componentMeasuresRoutes from '../../apps/component-measures/routes'; import customMeasuresRoutes from '../../apps/custom-measures/routes'; import groupsRoutes from '../../apps/groups/routes'; import issuesRoutes from '../../apps/issues/routes'; +import marketplaceRoutes from '../../apps/marketplace/routes'; import metricsRoutes from '../../apps/metrics/routes'; import overviewRoutes from '../../apps/overview/routes'; import organizationsRoutes from '../../apps/organizations/routes'; @@ -227,6 +228,7 @@ const startReactApp = () => { + diff --git a/server/sonar-web/src/main/js/apps/marketplace/App.tsx b/server/sonar-web/src/main/js/apps/marketplace/App.tsx new file mode 100644 index 00000000000..0cb9096adc3 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/marketplace/App.tsx @@ -0,0 +1,173 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import * as PropTypes from 'prop-types'; +import { sortBy, uniqBy } from 'lodash'; +import Helmet from 'react-helmet'; +import Header from './Header'; +import Footer from './Footer'; +import PendingActions from './PendingActions'; +import PluginsList from './PluginsList'; +import Search from './Search'; +import { + getAvailablePlugins, + getInstalledPluginsWithUpdates, + getPendingPlugins, + getPluginUpdates, + Plugin, + PluginPending +} from '../../api/plugins'; +import { RawQuery } from '../../helpers/query'; +import { translate } from '../../helpers/l10n'; +import { filterPlugins, parseQuery, Query, serializeQuery } from './utils'; + +export interface Props { + location: { pathname: string; query: RawQuery }; + updateCenterActive: boolean; +} + +interface State { + loading: boolean; + pending: { + installing: PluginPending[]; + updating: PluginPending[]; + removing: PluginPending[]; + }; + plugins: Plugin[]; +} + +export default class App extends React.PureComponent { + mounted: boolean; + + static contextTypes = { + router: PropTypes.object.isRequired + }; + + constructor(props: Props) { + super(props); + this.state = { + loading: true, + pending: { + installing: [], + updating: [], + removing: [] + }, + plugins: [] + }; + } + + componentDidMount() { + this.mounted = true; + this.fetchPendingPlugins(); + this.fetchQueryPlugins(); + } + + componentDidUpdate(prevProps: Props) { + if (prevProps.location.query.filter !== this.props.location.query.filter) { + this.fetchQueryPlugins(); + } + } + + componentWillUnmount() { + this.mounted = false; + } + + fetchQueryPlugins = () => { + const query = parseQuery(this.props.location.query); + if (query.filter === 'updates') { + this.fetchUpdatesOnly(); + } else { + this.fetchAllPlugins(); + } + }; + + fetchAllPlugins = () => { + this.setState({ loading: true }); + Promise.all([getInstalledPluginsWithUpdates(), getAvailablePlugins()]).then( + ([installed, available]) => { + if (this.mounted) { + this.setState({ + loading: false, + plugins: sortBy(uniqBy([...installed, ...available.plugins], 'key'), 'name') + }); + } + }, + () => {} + ); + }; + + fetchUpdatesOnly = () => { + this.setState({ loading: true }); + getPluginUpdates().then( + plugins => { + if (this.mounted) { + this.setState({ loading: false, plugins }); + } + }, + () => {} + ); + }; + + fetchPendingPlugins = () => + getPendingPlugins().then( + pending => { + if (this.mounted) { + this.setState({ pending }); + } + }, + () => {} + ); + + updateQuery = (newQuery: Partial) => { + const query = serializeQuery({ + ...parseQuery(this.props.location.query), + ...newQuery + }); + this.context.router.push({ + pathname: this.props.location.pathname, + query + }); + }; + + render() { + const { plugins, pending } = this.state; + const query = parseQuery(this.props.location.query); + const filteredPlugins = query.search ? filterPlugins(plugins, query.search) : plugins; + return ( +
    + +
    + + + +
    +
    + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/marketplace/AppContainer.tsx b/server/sonar-web/src/main/js/apps/marketplace/AppContainer.tsx new file mode 100644 index 00000000000..4319d2b1890 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/marketplace/AppContainer.tsx @@ -0,0 +1,28 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import { connect } from 'react-redux'; +import App from './App'; +import { getGlobalSettingValue } from '../../store/rootReducer'; + +const mapStateToProps = (state: any) => ({ + updateCenterActive: (getGlobalSettingValue(state, 'sonar.updatecenter.activate') || {}).value +}); + +export default connect(mapStateToProps)(App as any); diff --git a/server/sonar-web/src/main/js/apps/marketplace/Footer.tsx b/server/sonar-web/src/main/js/apps/marketplace/Footer.tsx new file mode 100644 index 00000000000..d5b950ddc6c --- /dev/null +++ b/server/sonar-web/src/main/js/apps/marketplace/Footer.tsx @@ -0,0 +1,33 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { translateWithParameters } from '../../helpers/l10n'; + +interface Props { + total: number; +} + +export default function Footer({ total }: Props) { + return ( +
    + {translateWithParameters('x_show', total)} +
    + ); +} diff --git a/server/sonar-web/src/main/js/apps/marketplace/Header.tsx b/server/sonar-web/src/main/js/apps/marketplace/Header.tsx new file mode 100644 index 00000000000..8dbfd3777a9 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/marketplace/Header.tsx @@ -0,0 +1,30 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { translate } from '../../helpers/l10n'; + +export default function Header() { + return ( +
    +

    {translate('marketplace.page')}

    +

    {translate('marketplace.page.description')}

    +
    + ); +} diff --git a/server/sonar-web/src/main/js/apps/marketplace/PendingActions.tsx b/server/sonar-web/src/main/js/apps/marketplace/PendingActions.tsx new file mode 100644 index 00000000000..921afeef3da --- /dev/null +++ b/server/sonar-web/src/main/js/apps/marketplace/PendingActions.tsx @@ -0,0 +1,120 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { FormattedMessage } from 'react-intl'; +import { translate } from '../../helpers/l10n'; +import { cancelPendingPlugins, PluginPending } from '../../api/plugins'; +import RestartForm from '../../components/common/RestartForm'; + +interface Props { + pending: { + installing: PluginPending[]; + updating: PluginPending[]; + removing: PluginPending[]; + }; + refreshPending: () => void; +} + +interface State { + openRestart: boolean; +} + +export default class PendingActions extends React.PureComponent { + state: State = { openRestart: false }; + + handleOpenRestart = () => this.setState({ openRestart: true }); + + hanleCloseRestart = () => this.setState({ openRestart: false }); + + handleRevert = () => { + cancelPendingPlugins().then(this.props.refreshPending, () => {}); + }; + + render() { + const { installing, updating, removing } = this.props.pending; + const hasPendingActions = installing.length || updating.length || removing.length; + if (!hasPendingActions) { + return null; + } + + const nbPluginsClass = 'big little-spacer-left little-spacer-right'; + return ( +
    +
    +

    {translate('marketplace.sonarqube_needs_to_be_restarted_to')}

    +
      + {installing.length > 0 && ( +
    • + + {installing.length} + + ) + }} + /> +
    • + )} + {updating.length > 0 && ( +
    • + + {updating.length} + + ) + }} + /> +
    • + )} + {removing.length > 0 && ( +
    • + {removing.length} + ) + }} + /> +
    • + )} +
    +
    +
    + + +
    + {this.state.openRestart && } +
    + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/marketplace/PluginActions.tsx b/server/sonar-web/src/main/js/apps/marketplace/PluginActions.tsx new file mode 100644 index 00000000000..d14cc83cd94 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/marketplace/PluginActions.tsx @@ -0,0 +1,127 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import Checkbox from '../../components/controls/Checkbox'; +import PluginUpdateButton from './PluginUpdateButton'; +import { Plugin, installPlugin, updatePlugin, uninstallPlugin } from '../../api/plugins'; +import { isPluginAvailable, isPluginInstalled } from './utils'; +import { translate } from '../../helpers/l10n'; + +interface Props { + plugin: Plugin; + refreshPending: () => void; +} + +interface State { + acceptTerms: boolean; + loading: boolean; +} + +export default class PluginActions extends React.PureComponent { + mounted: boolean; + state: State = { acceptTerms: false, loading: false }; + + componentDidMount() { + this.mounted = true; + } + + componentWillUnmount() { + this.mounted = false; + } + + doPluginAction = (apiAction: (data: { key: string }) => Promise) => { + this.setState({ loading: true }); + apiAction({ key: this.props.plugin.key }).then( + () => { + this.props.refreshPending(); + if (this.mounted) { + this.setState({ loading: false }); + } + }, + () => { + if (this.mounted) { + this.setState({ loading: false }); + } + } + ); + }; + + handleInstall = () => this.doPluginAction(installPlugin); + handleUpdate = () => this.doPluginAction(updatePlugin); + handleUninstall = () => this.doPluginAction(uninstallPlugin); + handleTermsCheck = (checked: boolean) => this.setState({ acceptTerms: checked }); + + render() { + const { plugin } = this.props; + const { loading } = this.state; + return ( +
    + {isPluginAvailable(plugin) && + plugin.termsAndConditionsUrl && ( +

    + + + + + {translate('marketplace.terms_and_conditions')} + +

    + )} + {loading && } + {isPluginInstalled(plugin) && ( +
    + {plugin.updates && + plugin.updates.map((update, idx) => ( + + ))} + +
    + )} + {isPluginAvailable(plugin) && ( + + )} +
    + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/marketplace/PluginAvailable.tsx b/server/sonar-web/src/main/js/apps/marketplace/PluginAvailable.tsx new file mode 100644 index 00000000000..26c12116a49 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/marketplace/PluginAvailable.tsx @@ -0,0 +1,75 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import PluginDescription from './PluginDescription'; +import PluginLicense from './PluginLicense'; +import PluginOrganization from './PluginOrganization'; +import PluginStatus from './PluginStatus'; +import PluginChangeLogButton from './PluginChangeLogButton'; +import { PluginAvailable } from '../../api/plugins'; +import { translateWithParameters } from '../../helpers/l10n'; +import { Query } from './utils'; + +interface Props { + plugin: PluginAvailable; + refreshPending: () => void; + status?: string; + updateQuery: (newQuery: Partial) => void; +} + +export default function PluginAvailable({ plugin, refreshPending, status, updateQuery }: Props) { + return ( + + + +
      +
    • +
      + {plugin.release.version} +
      +
      + {plugin.release.description} + + {plugin.update.requires.length > 0 && ( +

      + + {translateWithParameters( + 'marketplace.installing_this_plugin_will_also_install_x', + plugin.update.requires.map(requiredPlugin => requiredPlugin.name).join(', ') + )} + +

      + )} +
      +
    • +
    + + + +
      + + +
    + + + + + ); +} diff --git a/server/sonar-web/src/main/js/apps/marketplace/PluginChangeLog.tsx b/server/sonar-web/src/main/js/apps/marketplace/PluginChangeLog.tsx new file mode 100644 index 00000000000..21690e4e136 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/marketplace/PluginChangeLog.tsx @@ -0,0 +1,54 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import BubblePopup from '../../components/common/BubblePopup'; +import PluginChangeLogItem from './PluginChangeLogItem'; +import { Release, Update } from '../../api/plugins'; +import { translate } from '../../helpers/l10n'; + +interface Props { + popupPosition?: any; + release: Release; + update: Update; +} + +export default function PluginChangeLog({ popupPosition, release, update }: Props) { + return ( + +
    +
    {translate('changelog')}
    +
      + {update.previousUpdates && + update.previousUpdates.map( + previousUpdate => + previousUpdate.release ? ( + + ) : null + )} + +
    +
    +
    + ); +} diff --git a/server/sonar-web/src/main/js/apps/marketplace/PluginChangeLogButton.tsx b/server/sonar-web/src/main/js/apps/marketplace/PluginChangeLogButton.tsx new file mode 100644 index 00000000000..ec87c9ea26c --- /dev/null +++ b/server/sonar-web/src/main/js/apps/marketplace/PluginChangeLogButton.tsx @@ -0,0 +1,67 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import BubblePopupHelper from '../../components/common/BubblePopupHelper'; +import PluginChangeLog from './PluginChangeLog'; +import { Release, Update } from '../../api/plugins'; + +interface Props { + release: Release; + update: Update; +} + +interface State { + changelogOpen: boolean; +} + +export default class PluginChangeLogButton extends React.PureComponent { + state: State = { changelogOpen: false }; + + handleChangelogClick = (event: React.SyntheticEvent) => { + event.preventDefault(); + event.stopPropagation(); + this.toggleChangelog(); + }; + + toggleChangelog = (show?: boolean) => { + if (show != undefined) { + this.setState({ changelogOpen: show }); + } else { + this.setState(state => ({ changelogOpen: !state.changelogOpen })); + } + }; + + render() { + return ( +
    +
    + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/marketplace/PluginChangeLogItem.tsx b/server/sonar-web/src/main/js/apps/marketplace/PluginChangeLogItem.tsx new file mode 100644 index 00000000000..163c8165ced --- /dev/null +++ b/server/sonar-web/src/main/js/apps/marketplace/PluginChangeLogItem.tsx @@ -0,0 +1,58 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import DateFormatter from '../../components/intl/DateFormatter'; +import Tooltip from '../../components/controls/Tooltip'; +import { Release, Update } from '../../api/plugins'; +import { translate, translateWithParameters } from '../../helpers/l10n'; + +interface Props { + release: Release; + update: Update; +} + +export default function PluginChangeLogItem({ release, update }: Props) { + return ( +
  • +
    + {update.status === 'COMPATIBLE' || !update.status ? ( + + {release.version} + + ) : ( + + + {release.version} + + + )} + + + + {release.changeLogUrl && ( + + {translate('update_center.release_notes')} + + )} +
    +
    {release.description}
    +
  • + ); +} diff --git a/server/sonar-web/src/main/js/apps/marketplace/PluginDescription.tsx b/server/sonar-web/src/main/js/apps/marketplace/PluginDescription.tsx new file mode 100644 index 00000000000..8de1f3b0476 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/marketplace/PluginDescription.tsx @@ -0,0 +1,54 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { Plugin } from '../../api/plugins'; +import { Query } from './utils'; + +interface Props { + plugin: Plugin; + updateQuery: (newQuery: Partial) => void; +} + +export default class PluginDescription extends React.PureComponent { + handleCategoryClick = (e: React.SyntheticEvent) => { + e.preventDefault(); + this.props.updateQuery({ search: this.props.plugin.category }); + }; + + render() { + const { plugin } = this.props; + return ( + +
    + {plugin.name} + {plugin.category && ( + + {plugin.category} + + )} +
    +
    {plugin.description}
    + + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/marketplace/PluginInstalled.tsx b/server/sonar-web/src/main/js/apps/marketplace/PluginInstalled.tsx new file mode 100644 index 00000000000..85202411f18 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/marketplace/PluginInstalled.tsx @@ -0,0 +1,83 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import PluginDescription from './PluginDescription'; +import PluginLicense from './PluginLicense'; +import PluginStatus from './PluginStatus'; +import PluginOrganization from './PluginOrganization'; +import PluginUpdates from './PluginUpdates'; +import { PluginInstalled } from '../../api/plugins'; +import { translate } from '../../helpers/l10n'; +import { Query } from './utils'; + +interface Props { + plugin: PluginInstalled; + refreshPending: () => void; + status?: string; + updateQuery: (newQuery: Partial) => void; +} + +export default function PluginInstalled({ plugin, refreshPending, status, updateQuery }: Props) { + return ( + + + +
      +
    • + + {plugin.version} + + {translate('marketplace._installed')} +
    • + +
    + + + + + + + + + ); +} diff --git a/server/sonar-web/src/main/js/apps/marketplace/PluginLicense.tsx b/server/sonar-web/src/main/js/apps/marketplace/PluginLicense.tsx new file mode 100644 index 00000000000..550ba9249f3 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/marketplace/PluginLicense.tsx @@ -0,0 +1,43 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { FormattedMessage } from 'react-intl'; +import { translate } from '../../helpers/l10n'; + +interface Props { + license?: string; +} + +export default function PluginLicense({ license }: Props) { + if (!license) { + return null; + } + return ( +
  • + {license} + }} + /> +
  • + ); +} diff --git a/server/sonar-web/src/main/js/apps/marketplace/PluginOrganization.tsx b/server/sonar-web/src/main/js/apps/marketplace/PluginOrganization.tsx new file mode 100644 index 00000000000..505ebd16f28 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/marketplace/PluginOrganization.tsx @@ -0,0 +1,50 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { FormattedMessage } from 'react-intl'; +import { translate } from '../../helpers/l10n'; +import { Plugin } from '../../api/plugins'; + +interface Props { + plugin: Plugin; +} + +export default function PluginOrganization({ plugin }: Props) { + if (!plugin.organizationName) { + return null; + } + return ( +
  • + + {plugin.organizationName} + + ) : ( + {plugin.organizationName} + ) + }} + /> +
  • + ); +} diff --git a/server/sonar-web/src/main/js/apps/marketplace/PluginStatus.tsx b/server/sonar-web/src/main/js/apps/marketplace/PluginStatus.tsx new file mode 100644 index 00000000000..298206f2e92 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/marketplace/PluginStatus.tsx @@ -0,0 +1,54 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { Plugin } from '../../api/plugins'; +import PluginActions from './PluginActions'; +import { translate } from '../../helpers/l10n'; + +interface Props { + plugin: Plugin; + refreshPending: () => void; + status?: string; +} + +export default function PluginStatus({ plugin, refreshPending, status }: Props) { + return ( + + {status === 'installing' && ( +

    {translate('marketplace.install_pending')}

    + )} + + {status === 'updating' && ( +

    {translate('marketplace.update_pending')}

    + )} + + {status === 'removing' && ( +

    {translate('marketplace.uninstall_pending')}

    + )} + + {status == null && ( +
    + + +
    + )} + + ); +} diff --git a/server/sonar-web/src/main/js/apps/marketplace/PluginUpdateButton.tsx b/server/sonar-web/src/main/js/apps/marketplace/PluginUpdateButton.tsx new file mode 100644 index 00000000000..53c154e8fa6 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/marketplace/PluginUpdateButton.tsx @@ -0,0 +1,44 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { Update } from '../../api/plugins'; +import { translateWithParameters } from '../../helpers/l10n'; + +interface Props { + disabled: boolean; + onClick: (update: Update) => void; + update: Update; +} + +export default class PluginUpdateButton extends React.PureComponent { + handleClick = () => this.props.onClick(this.props.update); + + render() { + const { disabled, update } = this.props; + if (update.status !== 'COMPATIBLE' || !update.release) { + return null; + } + return ( + + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/marketplace/PluginUpdateItem.tsx b/server/sonar-web/src/main/js/apps/marketplace/PluginUpdateItem.tsx new file mode 100644 index 00000000000..f4d9d19d1c8 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/marketplace/PluginUpdateItem.tsx @@ -0,0 +1,72 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import PluginChangeLogButton from './PluginChangeLogButton'; +import Tooltip from '../../components/controls/Tooltip'; +import { Release, Update } from '../../api/plugins'; +import { translate } from '../../helpers/l10n'; + +interface Props { + update: Update; + release: Release; +} + +interface State { + changelogOpen: boolean; +} + +export default class PluginUpdateItem extends React.PureComponent { + state: State = { changelogOpen: false }; + + handleChangelogClick = (event: React.SyntheticEvent) => { + event.preventDefault(); + event.stopPropagation(); + this.toggleChangelog(); + }; + + toggleChangelog = (show?: boolean) => { + if (show != undefined) { + this.setState({ changelogOpen: show }); + } else { + this.setState(state => ({ changelogOpen: !state.changelogOpen })); + } + }; + + render() { + const { release, update } = this.props; + return ( +
  • +
    + {update.status === 'COMPATIBLE' ? ( + {release.version} + ) : ( + + {release.version} + + )} +
    +
    + {release.description} + +
    +
  • + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/marketplace/PluginUpdates.tsx b/server/sonar-web/src/main/js/apps/marketplace/PluginUpdates.tsx new file mode 100644 index 00000000000..b662651f1dc --- /dev/null +++ b/server/sonar-web/src/main/js/apps/marketplace/PluginUpdates.tsx @@ -0,0 +1,50 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import PluginUpdateItem from './PluginUpdateItem'; +import { Update } from '../../api/plugins'; +import { translate } from '../../helpers/l10n'; + +interface Props { + updates?: Update[]; +} + +export default function PluginUpdates({ updates }: Props) { + if (!updates || updates.length <= 0) { + return null; + } + return ( +
  • + {translate('marketplace.updates')}: +
      + {updates.map( + update => + update.release ? ( + + ) : null + )} +
    +
  • + ); +} diff --git a/server/sonar-web/src/main/js/apps/marketplace/PluginsList.tsx b/server/sonar-web/src/main/js/apps/marketplace/PluginsList.tsx new file mode 100644 index 00000000000..0e51d154342 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/marketplace/PluginsList.tsx @@ -0,0 +1,91 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import PluginAvailable from './PluginAvailable'; +import PluginInstalled from './PluginInstalled'; +import { isPluginAvailable, isPluginInstalled, Query } from './utils'; +import { Plugin, PluginPending } from '../../api/plugins'; + +interface Props { + plugins: Plugin[]; + pending: { + installing: PluginPending[]; + updating: PluginPending[]; + removing: PluginPending[]; + }; + refreshPending: () => void; + updateQuery: (newQuery: Partial) => void; +} + +export default class PluginsList extends React.PureComponent { + getPluginStatus = (plugin: Plugin): string | undefined => { + const { installing, updating, removing } = this.props.pending; + if (installing.find(p => p.key === plugin.key)) { + return 'installing'; + } + if (updating.find(p => p.key === plugin.key)) { + return 'updating'; + } + if (removing.find(p => p.key === plugin.key)) { + return 'removing'; + } + return undefined; + }; + + renderPlugin = (plugin: Plugin) => { + const status = this.getPluginStatus(plugin); + if (isPluginInstalled(plugin)) { + return ( + + ); + } + if (isPluginAvailable(plugin)) { + return ( + + ); + } + }; + + render() { + return ( +
    +
      + {this.props.plugins.map(plugin => ( +
    • + + {this.renderPlugin(plugin)} +
      +
    • + ))} +
    +
    + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/marketplace/Search.tsx b/server/sonar-web/src/main/js/apps/marketplace/Search.tsx new file mode 100644 index 00000000000..194427a709e --- /dev/null +++ b/server/sonar-web/src/main/js/apps/marketplace/Search.tsx @@ -0,0 +1,98 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { debounce } from 'lodash'; +import RadioToggle from '../../components/controls/RadioToggle'; +import { Query } from './utils'; +import { translate } from '../../helpers/l10n'; + +interface Props { + query: Query; + updateCenterActive: boolean; + updateQuery: (newQuery: Partial) => void; +} + +interface State { + search?: string; +} + +export default class Search extends React.PureComponent { + constructor(props: Props) { + super(props); + this.state = { search: props.query.search }; + this.updateSearch = debounce(this.updateSearch, 250); + } + + componentWillReceiveProps(nextProps: Props) { + if (nextProps.query.search !== this.state.search) { + this.setState({ search: nextProps.query.search }); + } + } + + handleSearch = (e: React.SyntheticEvent) => { + const search = e.currentTarget.value; + this.setState({ search }); + this.updateSearch(search); + }; + + handleFilterChange = (filter: string) => this.props.updateQuery({ filter }); + + updateSearch = (search: string) => this.props.updateQuery({ search }); + + render() { + const { query, updateCenterActive } = this.props; + const radioOptions = [ + { label: translate('marketplace.all'), value: 'all' }, + { + disabled: !updateCenterActive, + label: translate('marketplace.updates_only'), + tooltip: !updateCenterActive ? translate('marketplace.not_activated') : undefined, + value: 'updates' + } + ]; + return ( + + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/marketplace/routes.ts b/server/sonar-web/src/main/js/apps/marketplace/routes.ts new file mode 100644 index 00000000000..e066c3a2ef1 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/marketplace/routes.ts @@ -0,0 +1,30 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import { RouterState, IndexRouteProps } from 'react-router'; + +const routes = [ + { + getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) { + import('./AppContainer').then(i => callback(null, { component: i.default })); + } + } +]; + +export default routes; diff --git a/server/sonar-web/src/main/js/apps/marketplace/utils.ts b/server/sonar-web/src/main/js/apps/marketplace/utils.ts new file mode 100644 index 00000000000..780fafb3af8 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/marketplace/utils.ts @@ -0,0 +1,64 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import { memoize } from 'lodash'; +import { Plugin, PluginAvailable, PluginInstalled, PluginPending } from '../../api/plugins'; +import { cleanQuery, parseAsString, RawQuery, serializeString } from '../../helpers/query'; + +export interface Query { + filter: string; + search?: string; +} + +export const DEFAULT_FILTER = 'all'; + +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 function filterPlugins(plugins: Plugin[], search: string): Plugin[] { + const s = search.toLowerCase(); + return plugins.filter(plugin => { + return ( + plugin.name.toLowerCase().includes(s) || + plugin.description.toLowerCase().includes(s) || + (plugin.category || '').toLowerCase().includes(s) + ); + }); +} + +export const parseQuery = memoize((urlQuery: RawQuery): Query => ({ + filter: parseAsString(urlQuery['filter']) || DEFAULT_FILTER, + search: parseAsString(urlQuery['search']) +})); + +export const serializeQuery = memoize((query: Query): RawQuery => + cleanQuery({ + filter: query.filter === DEFAULT_FILTER ? undefined : serializeString(query.filter), + search: query.search ? serializeString(query.search) : undefined + }) +); diff --git a/server/sonar-web/src/main/js/components/controls/Checkbox.tsx b/server/sonar-web/src/main/js/components/controls/Checkbox.tsx index 1e5afe85195..f0986772213 100644 --- a/server/sonar-web/src/main/js/components/controls/Checkbox.tsx +++ b/server/sonar-web/src/main/js/components/controls/Checkbox.tsx @@ -22,7 +22,7 @@ import * as classNames from 'classnames'; interface Props { checked: boolean; - children?: React.ReactElement; + children?: React.ReactNode; className?: string; id?: string; onCheck: (checked: boolean, id?: string) => void; diff --git a/server/sonar-web/src/main/js/components/controls/RadioToggle.tsx b/server/sonar-web/src/main/js/components/controls/RadioToggle.tsx index bfb7c4cfee1..8c4fe83ff20 100644 --- a/server/sonar-web/src/main/js/components/controls/RadioToggle.tsx +++ b/server/sonar-web/src/main/js/components/controls/RadioToggle.tsx @@ -18,11 +18,19 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; +import Tooltip from './Tooltip'; + +interface Option { + disabled?: boolean; + label: string; + tooltip?: string; + value: string; +} interface Props { name: string; onCheck: (value: string) => void; - options: Array<{ label: string; value: string }>; + options: Option[]; value?: string; } @@ -37,21 +45,27 @@ export default class RadioToggle extends React.PureComponent { this.props.onCheck(newValue); }; - renderOption = (option: { label: string; value: string }) => { + renderOption = (option: Option) => { const checked = option.value === this.props.value; const htmlId = this.props.name + '__' + option.value; return (
  • - - + {option.tooltip ? ( + + + + ) : ( + + )}
  • ); }; diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/RadioToggle-test.tsx b/server/sonar-web/src/main/js/components/controls/__tests__/RadioToggle-test.tsx index f1a231b564c..90b5659e124 100644 --- a/server/sonar-web/src/main/js/components/controls/__tests__/RadioToggle-test.tsx +++ b/server/sonar-web/src/main/js/components/controls/__tests__/RadioToggle-test.tsx @@ -33,6 +33,19 @@ it('calls onCheck', () => { expect(onCheck).toBeCalledWith('two'); }); +it('accepts advanced options fields', () => { + expect( + shallow( + getSample({ + options: [ + { value: 'one', label: 'first', tooltip: 'foo' }, + { value: 'two', label: 'second', tooltip: 'bar', disabled: true } + ] + }) + ) + ).toMatchSnapshot(); +}); + function getSample(props?: any) { const options = [{ value: 'one', label: 'first' }, { value: 'two', label: 'second' }]; return true} {...props} />; diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/RadioToggle-test.tsx.snap b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/RadioToggle-test.tsx.snap index df9579888bd..c0aa0d1ab89 100644 --- a/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/RadioToggle-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/RadioToggle-test.tsx.snap @@ -1,5 +1,53 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`accepts advanced options fields 1`] = ` +
      +
    • + + + + +
    • +
    • + + + + +
    • +
    +`; + exports[`renders 1`] = `