From: Jeremy Davis Date: Mon, 22 Jan 2024 09:57:00 +0000 (+0100) Subject: SONAR-21445 migrate Marketplace page to the new UI X-Git-Tag: 10.4.0.87286~129 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=ee268b1caac7c777dbe612ef4cde8cbf15d00f26;p=sonarqube.git SONAR-21445 migrate Marketplace page to the new UI --- diff --git a/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx b/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx index f08f3ccb9b1..a28ac860a19 100644 --- a/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx +++ b/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx @@ -79,6 +79,7 @@ const TEMP_PAGELIST_WITH_NEW_BACKGROUND_WHITE = [ '/project/background_tasks', '/admin/background_tasks', '/admin/groups', + '/admin/marketplace', '/admin/system', '/admin/users', '/admin/settings/encryption', diff --git a/server/sonar-web/src/main/js/app/components/LicensePromptModal.tsx b/server/sonar-web/src/main/js/app/components/LicensePromptModal.tsx new file mode 100644 index 00000000000..3ed3a5f191e --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/LicensePromptModal.tsx @@ -0,0 +1,56 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 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 Link from '../../components/common/Link'; +import Modal from '../../components/controls/Modal'; +import { ResetButtonLink } from '../../components/controls/buttons'; +import { translate } from '../../helpers/l10n'; + +interface Props { + onClose: () => void; +} + +export default function LicensePromptModal({ onClose }: Readonly) { + const header = translate('license.prompt.title'); + return ( + +
+

{header}

+
+
+ + {translate('license.prompt.link')} + + ), + }} + /> +
+
+ {translate('cancel')} +
+
+ ); +} diff --git a/server/sonar-web/src/main/js/app/components/StartupModal.tsx b/server/sonar-web/src/main/js/app/components/StartupModal.tsx index 64077d76b91..192f4708c81 100644 --- a/server/sonar-web/src/main/js/app/components/StartupModal.tsx +++ b/server/sonar-web/src/main/js/app/components/StartupModal.tsx @@ -20,13 +20,13 @@ import { differenceInDays } from 'date-fns'; import * as React from 'react'; import { showLicense } from '../../api/editions'; -import LicensePromptModal from '../../apps/marketplace/components/LicensePromptModal'; import { parseDate, toShortISO8601String } from '../../helpers/dates'; import { hasMessage } from '../../helpers/l10n'; import { get, save } from '../../helpers/storage'; import { AppState } from '../../types/appstate'; import { EditionKey } from '../../types/editions'; import { CurrentUser, isLoggedIn } from '../../types/users'; +import LicensePromptModal from './LicensePromptModal'; import withAppStateContext from './app-state/withAppStateContext'; import withCurrentUserContext from './current-user/withCurrentUserContext'; diff --git a/server/sonar-web/src/main/js/apps/marketplace/App.tsx b/server/sonar-web/src/main/js/apps/marketplace/App.tsx index f5ac54516d9..3ed9b3b018b 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/App.tsx +++ b/server/sonar-web/src/main/js/apps/marketplace/App.tsx @@ -17,23 +17,29 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { + BasicSeparator, + FlagMessage, + LargeCenteredLayout, + PageContentFontWrapper, + Spinner, + SubTitle, +} from 'design-system'; import { sortBy, uniqBy } from 'lodash'; import * as React from 'react'; import { Helmet } from 'react-helmet-async'; import { FormattedMessage } from 'react-intl'; import { getAvailablePlugins, getInstalledPlugins } from '../../api/plugins'; import { getValue, setSimpleSettingValue } from '../../api/settings'; -import DocLink from '../../components/common/DocLink'; +import DocumentationLink from '../../components/common/DocumentationLink'; +import ListFooter from '../../components/controls/ListFooter'; import Suggestions from '../../components/embed-docs-modal/Suggestions'; import { Location, Router, withRouter } from '../../components/hoc/withRouter'; -import { Alert } from '../../components/ui/Alert'; -import Spinner from '../../components/ui/Spinner'; import { translate } from '../../helpers/l10n'; import { EditionKey } from '../../types/editions'; import { PendingPluginResult, Plugin, RiskConsent } from '../../types/plugins'; import { SettingsKey } from '../../types/settings'; import EditionBoxes from './EditionBoxes'; -import Footer from './Footer'; import Header from './Header'; import PluginsList from './PluginsList'; import Search from './Search'; @@ -158,60 +164,73 @@ class App extends React.PureComponent { riskConsent === RiskConsent.Accepted; return ( -
- - -
- -
-

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

-
-

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

- {currentEdition !== EditionKey.community && ( - - - {translate('marketplace.page.plugins.description2.link')} - - ), - }} - /> - - )} + + + + +
+ + + + +
+ {translate('marketplace.page.plugins')} +
+

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

+ {currentEdition !== EditionKey.community && ( + +

+ + {translate('marketplace.page.plugins.description2.link')} + + ), + }} + /> +

+
+ )} +
+
+ + + + +
+ + {filteredPlugins.length === 0 && + translate('marketplace.plugin_list.no_plugins', query.filter)} + {filteredPlugins.length > 0 && ( + <> + + + + )} +
-
- - - - - - {filteredPlugins.length === 0 && - translate('marketplace.plugin_list.no_plugins', query.filter)} - {filteredPlugins.length > 0 && ( - <> - -
- - )} - -
+ + ); } } diff --git a/server/sonar-web/src/main/js/apps/marketplace/EditionBoxes.tsx b/server/sonar-web/src/main/js/apps/marketplace/EditionBoxes.tsx index 135550963b0..a22a23c2932 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/EditionBoxes.tsx +++ b/server/sonar-web/src/main/js/apps/marketplace/EditionBoxes.tsx @@ -17,9 +17,11 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { Card, Link } from 'design-system'; import * as React from 'react'; import { getMarketplaceNavigation } from '../../api/navigation'; -import { getAllEditionsAbove } from '../../helpers/editions'; +import { getAllEditionsAbove, getEditionUrl } from '../../helpers/editions'; +import { translate } from '../../helpers/l10n'; import { EditionKey } from '../../types/editions'; import EditionBox from './components/EditionBox'; @@ -66,15 +68,20 @@ export default class EditionBoxes extends React.PureComponent { } return ( -
+
{visibleEditions.map((edition) => ( - + className="sw-max-w-1/2 sw-flex-1 sw-flex sw-flex-col sw-justify-between" + > + + +
+ + {translate('marketplace.request_free_trial')} + +
+ ))}
); diff --git a/server/sonar-web/src/main/js/apps/marketplace/Footer.tsx b/server/sonar-web/src/main/js/apps/marketplace/Footer.tsx deleted file mode 100644 index 6546decb0cb..00000000000 --- a/server/sonar-web/src/main/js/apps/marketplace/Footer.tsx +++ /dev/null @@ -1,33 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 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 index 1bf06a2d94c..9e02ad24d8d 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/Header.tsx +++ b/server/sonar-web/src/main/js/apps/marketplace/Header.tsx @@ -17,6 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { Title } from 'design-system'; import * as React from 'react'; import { translate } from '../../helpers/l10n'; import { EditionKey } from '../../types/editions'; @@ -25,16 +26,16 @@ interface Props { currentEdition?: EditionKey; } -export default function Header({ currentEdition }: Props) { +export default function Header({ currentEdition }: Readonly) { return ( -
-

{translate('marketplace.page')}

+
+ {translate('marketplace.page')} {currentEdition && ( -

+
{translate('marketplace.page.you_are_running', currentEdition)} -

+
)} -

+

{currentEdition === 'datacenter' ? translate('marketplace.page.description_best_edition') : translate('marketplace.page.description')} diff --git a/server/sonar-web/src/main/js/apps/marketplace/PluginsList.tsx b/server/sonar-web/src/main/js/apps/marketplace/PluginsList.tsx index 08f380c25e7..8decba91433 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/PluginsList.tsx +++ b/server/sonar-web/src/main/js/apps/marketplace/PluginsList.tsx @@ -17,10 +17,11 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { Card, Table, TableRow } from 'design-system'; import { sortBy } from 'lodash'; import * as React from 'react'; import { translate } from '../../helpers/l10n'; -import { isAvailablePlugin, isInstalledPlugin, PendingPlugin, Plugin } from '../../types/plugins'; +import { PendingPlugin, Plugin, isAvailablePlugin, isInstalledPlugin } from '../../types/plugins'; import PluginAvailable from './components/PluginAvailable'; import PluginInstalled from './components/PluginInstalled'; @@ -49,39 +50,42 @@ function getPluginStatus(plugin: Plugin, pending: PluginsListProps['pending']): return undefined; } -export default function PluginsList(props: PluginsListProps) { +export default function PluginsList(props: Readonly) { const { pending, plugins, readOnly } = props; const installedPlugins = plugins.filter(isInstalledPlugin); + + const columns = readOnly ? ['25%', 'auto', '20%'] : ['25%', 'auto', '20%', '20%']; + return ( -

-
    + + {sortBy(plugins, ({ name }) => name).map((plugin) => ( -
  • -
  • - - {isInstalledPlugin(plugin) && ( - - )} + + {isInstalledPlugin(plugin) && ( + + )} - {isAvailablePlugin(plugin) && ( - - )} - -
    - + {isAvailablePlugin(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 index b9402cbda32..8afebdcf298 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/Search.tsx +++ b/server/sonar-web/src/main/js/apps/marketplace/Search.tsx @@ -17,9 +17,8 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { InputSearch, ToggleButton } from 'design-system'; import * as React from 'react'; -import ButtonToggle from '../../components/controls/ButtonToggle'; -import SearchBox from '../../components/controls/SearchBox'; import { translate } from '../../helpers/l10n'; import { Query } from './utils'; @@ -49,17 +48,16 @@ export default class Search extends React.PureComponent { }, ]; return ( - diff --git a/server/sonar-web/src/main/js/apps/marketplace/__tests__/MarketplaceApp-it.tsx b/server/sonar-web/src/main/js/apps/marketplace/__tests__/MarketplaceApp-it.tsx index 4fdf0bf577e..54c5f8fdf5b 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/__tests__/MarketplaceApp-it.tsx +++ b/server/sonar-web/src/main/js/apps/marketplace/__tests__/MarketplaceApp-it.tsx @@ -42,10 +42,10 @@ const ui = { deTitle: byRole('heading', { name: 'SonarQube logo Developer Edition' }), eeTitle: byRole('heading', { name: 'SonarQube logo Enterprise Edition' }), dceTitle: byRole('heading', { name: 'SonarQube logo Data Center Edition' }), - pluginRow: byRole('list', { name: 'marketplace.page.plugins' }).byRole('table'), - filterAll: byRole('button', { name: 'marketplace.all' }), - filterInstalled: byRole('button', { name: 'marketplace.installed' }), - filterWithUpdates: byRole('button', { name: 'marketplace.updates_only' }), + pluginRow: byRole('table', { name: 'marketplace.page.plugins' }).byRole('row'), + filterAll: byRole('radio', { name: 'marketplace.all' }), + filterInstalled: byRole('radio', { name: 'marketplace.installed' }), + filterWithUpdates: byRole('radio', { name: 'marketplace.updates_only' }), search: byRole('searchbox', { name: 'marketplace.search' }), clearSearch: byRole('button', { name: 'clear' }), noPluginsText: byText('marketplace.plugin_list.no_plugins', { exact: false }), @@ -115,7 +115,7 @@ it('should install, uninstall, update', async () => { expect(ui.updateButton.query()).not.toBeInTheDocument(); expect(ui.riskConsentMessage.get()).toBeInTheDocument(); expect(ui.riskConsentButton.get()).toBeInTheDocument(); - await act(() => user.click(ui.riskConsentButton.get())); + await user.click(ui.riskConsentButton.get()); expect(ui.riskConsentMessage.query()).not.toBeInTheDocument(); expect(rows[0]).toHaveTextContent('ATest_install'); @@ -123,7 +123,7 @@ it('should install, uninstall, update', async () => { expect(ui.installButton.query(rows[0])).not.toBeInTheDocument(); expect(ui.updateButton.query(rows[0])).not.toBeInTheDocument(); expect(ui.uninstallPending.query(rows[0])).not.toBeInTheDocument(); - await act(() => user.click(ui.uninstallButton.get(rows[0]))); + await user.click(ui.uninstallButton.get(rows[0])); expect(await ui.uninstallPending.find(rows[0])).toBeInTheDocument(); expect(ui.uninstallButton.query(rows[0])).not.toBeInTheDocument(); diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/EditionBox.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/EditionBox.tsx index 58a338754bc..f845f0fd429 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/components/EditionBox.tsx +++ b/server/sonar-web/src/main/js/apps/marketplace/components/EditionBox.tsx @@ -17,135 +17,123 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { SubHeading, UnorderedList } from 'design-system'; import * as React from 'react'; -import Link from '../../../components/common/Link'; -import { getEditionUrl } from '../../../helpers/editions'; -import { translate } from '../../../helpers/l10n'; import { getBaseUrl } from '../../../helpers/system'; import { Edition, EditionKey } from '../../../types/editions'; interface Props { - currentEdition?: EditionKey; edition: Edition; - ncloc?: number; - serverId?: string; } -export default function EditionBox({ edition, ncloc, serverId, currentEdition }: Props) { - return ( -
- {edition.key === EditionKey.datacenter && ( -
-
-
-

- SonarQube logo - Data Center Edition -

-

- Designed for High Availability and Scalability -

-

Enterprise Edition functionality plus:

-
    -
  • Component redundancy
  • -
  • Data resiliency
  • -
  • Horizontal scalability
  • -
-
-
+export default function EditionBox({ edition }: Readonly) { + switch (edition.key) { + case EditionKey.datacenter: + return ( +
+ + SonarQube logo + Data Center Edition + +

+ Designed for High Availability and Scalability +

+

Enterprise Edition functionality plus:

+ +
  • Component redundancy
  • +
  • Data resiliency
  • +
  • Horizontal scalability
  • +
    - )} - {edition.key === EditionKey.developer && ( -
    -
    -
    -

    - SonarQube logo - Developer Edition -

    -

    - Built for Developers by Developers -

    -

    Community Edition functionality plus:

    -
      -
    • - PR / MR decoration & Quality Gate - GitHub - GitLab - Azure DevOps - Bitbucket -
    • -
    • - Taint analysis / Injection flaw detection for Java, C#, PHP, Python, JS & TS -
    • -
    • Branch analysis
    • -
    • Project aggregation
    • -
    • Additional languages: C, C++, Obj-C, PL/SQL, ABAP, TSQL & Swift
    • -
    -
    -
    + ); + + case EditionKey.enterprise: + return ( +
    + + SonarQube logo + Enterprise Edition + +

    + Designed to Meet Enterprise Requirements +

    +

    Developer Edition functionality plus:

    + +
  • Faster analysis with parallel processing
  • +
  • OWASP/CWE security reports
  • +
  • Portfolio management
  • +
  • Executive reporting
  • +
  • Project transfer
  • +
  • Additional languages: Apex, COBOL, PL/I, RPG & VB6
  • +
    - )} - {edition.key === EditionKey.enterprise && ( -
    -
    -
    -

    - SonarQube logo{' '} - Enterprise Edition -

    -

    - Designed to Meet Enterprise Requirements -

    -

    Developer Edition functionality plus:

    -
      -
    • Faster analysis with parallel processing
    • -
    • OWASP/CWE security reports
    • -
    • Portfolio management
    • -
    • Executive reporting
    • -
    • Project transfer
    • -
    • Additional languages: Apex, COBOL, PL/I, RPG & VB6
    • -
    -
    -
    + ); + + case EditionKey.developer: + return ( +
    + + SonarQube logo + Developer Edition + +

    + Built for Developers by Developers +

    +

    Community Edition functionality plus:

    + +
  • + PR / MR decoration & Quality Gate + GitHub + GitLab + Azure DevOps + Bitbucket +
  • +
  • + Taint analysis / Injection flaw detection for Java, C#, PHP, Python, JS & TS +
  • +
  • Branch analysis
  • +
  • Project aggregation
  • +
  • Additional languages: C, C++, Obj-C, PL/SQL, ABAP, TSQL & Swift
  • +
    - )} -
    - - {translate('marketplace.request_free_trial')} - -
    -
    - ); + ); + + default: + return null; + } } diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/LicensePromptModal.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/LicensePromptModal.tsx deleted file mode 100644 index 4deefed6c05..00000000000 --- a/server/sonar-web/src/main/js/apps/marketplace/components/LicensePromptModal.tsx +++ /dev/null @@ -1,56 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 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 Link from '../../../components/common/Link'; -import { ResetButtonLink } from '../../../components/controls/buttons'; -import Modal from '../../../components/controls/Modal'; -import { translate } from '../../../helpers/l10n'; - -interface Props { - onClose: () => void; -} - -export default function LicensePromptModal({ onClose }: Props) { - const header = translate('license.prompt.title'); - return ( - -
    -

    {header}

    -
    -
    - - {translate('license.prompt.link')} - - ), - }} - /> -
    -
    - {translate('cancel')} -
    -
    - ); -} diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/PluginActions.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/PluginActions.tsx index 74d719fc36a..9f1344bd1a5 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/components/PluginActions.tsx +++ b/server/sonar-web/src/main/js/apps/marketplace/components/PluginActions.tsx @@ -17,13 +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 { + ButtonSecondary, + CheckIcon, + Checkbox, + DangerButtonSecondary, + Link, + Spinner, +} from 'design-system'; import * as React from 'react'; import { installPlugin, uninstallPlugin, updatePlugin } from '../../../api/plugins'; -import Link from '../../../components/common/Link'; -import Checkbox from '../../../components/controls/Checkbox'; import Tooltip from '../../../components/controls/Tooltip'; -import { Button } from '../../../components/controls/buttons'; -import CheckIcon from '../../../components/icons/CheckIcon'; import { translate } from '../../../helpers/l10n'; import { Plugin, isAvailablePlugin, isInstalledPlugin } from '../../../types/plugins'; import PluginUpdateButton from './PluginUpdateButton'; @@ -122,61 +126,48 @@ export default class PluginActions extends React.PureComponent { return (
    {isAvailablePlugin(plugin) && plugin.termsAndConditionsUrl && ( -
    +
    - + {translate('marketplace.i_accept_the')} - + {translate('marketplace.terms_and_conditions')} - +
    )} - {loading && } + {isInstalledPlugin(plugin) && ( <> - {plugin.updates && - plugin.updates.map((update, idx) => ( + {plugin.updates?.map((update, idx) => ( +
    - ))} +
    + ))} - + )} {isAvailablePlugin(plugin) && ( - + )}
    diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/PluginAvailable.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/PluginAvailable.tsx index 09e99894de6..1e0a0c7522e 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/components/PluginAvailable.tsx +++ b/server/sonar-web/src/main/js/apps/marketplace/components/PluginAvailable.tsx @@ -17,6 +17,8 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import styled from '@emotion/styled'; +import { Badge, ContentCell, UnorderedList } from 'design-system'; import * as React from 'react'; import { translateWithParameters } from '../../../helpers/l10n'; import { AvailablePlugin, InstalledPlugin } from '../../../types/plugins'; @@ -35,54 +37,58 @@ export interface PluginAvailableProps { status?: string; } -export default function PluginAvailable(props: PluginAvailableProps) { +export default function PluginAvailable(props: Readonly) { const { installedPlugins, plugin, readOnly, status } = props; const installedPluginKeys = installedPlugins.map(({ key }) => key); return ( - + <> - -
      -
    • -
      - {plugin.release.version} -
      -
      - {plugin.release.description} - - {plugin.update.requires.length > 0 && ( -

      - - {translateWithParameters( - 'marketplace.installing_this_plugin_will_also_install_x', - plugin.update.requires - .filter(({ key }) => !installedPluginKeys.includes(key)) - .map((requiredPlugin) => requiredPlugin.name) - .join(', '), - )} - -

      + +
      + {plugin.release.version} +
      +
      {plugin.release.description}
      + + {plugin.update.requires.length > 0 && ( +

      + + {translateWithParameters( + 'marketplace.installing_this_plugin_will_also_install_x', + plugin.update.requires + .filter(({ key }) => !installedPluginKeys.includes(key)) + .map((requiredPlugin) => requiredPlugin.name) + .join(', '), )} -

      -
    • -
    - + +

    + )} + - -
      + + -
    - + + {!readOnly && ( - + + + )} - + ); } + +const StyledUnorderedList = styled(UnorderedList)` + margin-top: 0; + + & li:first { + margin-top: 0; + } +`; diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/PluginChangeLog.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/PluginChangeLog.tsx index eb520c2ec13..2985475d641 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/components/PluginChangeLog.tsx +++ b/server/sonar-web/src/main/js/apps/marketplace/components/PluginChangeLog.tsx @@ -17,6 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { UnorderedList } from 'design-system'; import { sortBy } from 'lodash'; import * as React from 'react'; import { translate } from '../../../helpers/l10n'; @@ -30,9 +31,9 @@ export interface Props { export default function PluginChangeLog({ release, update }: Props) { return ( -
    - {translate('changelog')} -
      +
      + {translate('changelog')} + {update.previousUpdates && sortBy(update.previousUpdates, (prevUpdate) => prevUpdate.release?.date).map( (previousUpdate) => @@ -45,7 +46,7 @@ export default function PluginChangeLog({ release, update }: Props) { ) : null, )} -
    +
    ); } diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/PluginChangeLogButton.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/PluginChangeLogButton.tsx index 474146b4aea..d2caa3a3c70 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/components/PluginChangeLogButton.tsx +++ b/server/sonar-web/src/main/js/apps/marketplace/components/PluginChangeLogButton.tsx @@ -17,11 +17,9 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { ButtonSecondary, DropdownToggler } from 'design-system'; import * as React from 'react'; -import { ButtonLink } from '../../../components/controls/buttons'; -import Dropdown from '../../../components/controls/Dropdown'; -import EllipsisIcon from '../../../components/icons/EllipsisIcon'; -import { translateWithParameters } from '../../../helpers/l10n'; +import { translate, translateWithParameters } from '../../../helpers/l10n'; import { Release, Update } from '../../../types/plugins'; import PluginChangeLog from './PluginChangeLog'; @@ -31,22 +29,27 @@ interface Props { update: Update; } -export default function PluginChangeLogButton({ pluginName, release, update }: Props) { +export default function PluginChangeLogButton({ pluginName, release, update }: Readonly) { + const [open, setOpen] = React.useState(false); + return ( - setOpen(false)} + open={open} + id={`plugin-changelog-${pluginName}`} overlay={} > - setOpen((open) => !open)} > - - - + {translate('see_changelog')} + + ); } diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/PluginChangeLogItem.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/PluginChangeLogItem.tsx index d7abc8698ff..1775cf6aebc 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/components/PluginChangeLogItem.tsx +++ b/server/sonar-web/src/main/js/apps/marketplace/components/PluginChangeLogItem.tsx @@ -17,6 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { Badge, Link, ListItem, Note } from 'design-system'; import * as React from 'react'; import Tooltip from '../../../components/controls/Tooltip'; import DateFormatter from '../../../components/intl/DateFormatter'; @@ -30,34 +31,27 @@ interface Props { export default function PluginChangeLogItem({ release, update }: Props) { return ( -
  • -
    + +
    {update.status === 'COMPATIBLE' || !update.status ? ( - + {release.version} - + ) : ( - - {release.version} + + {release.version} )} - + - + {release.changeLogUrl && ( - - {translate('marketplace.release_notes')} - + {translate('marketplace.release_notes')} )}
    -
    {release.description}
    -
  • +

    {release.description}

    + ); } diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/PluginDescription.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/PluginDescription.tsx index 6858f2ad783..e436531f096 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/components/PluginDescription.tsx +++ b/server/sonar-web/src/main/js/apps/marketplace/components/PluginDescription.tsx @@ -17,6 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { Badge, CellComponent } from 'design-system'; import * as React from 'react'; import { Plugin } from '../../../types/plugins'; @@ -24,18 +25,12 @@ interface Props { plugin: Plugin; } -const PluginDescription = (props: Props) => { +export default function PluginDescription(props: Readonly) { return ( - -
    - {props.plugin.name} - {props.plugin.category && ( - {props.plugin.category} - )} -
    -
    {props.plugin.description}
    - + + {props.plugin.name} + {props.plugin.category && {props.plugin.category}} + {props.plugin.description &&
    {props.plugin.description}
    } +
    ); -}; - -export default PluginDescription; +} diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/PluginInstalled.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/PluginInstalled.tsx index 51ce958bfca..7e1692ce31b 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/components/PluginInstalled.tsx +++ b/server/sonar-web/src/main/js/apps/marketplace/components/PluginInstalled.tsx @@ -17,6 +17,8 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import styled from '@emotion/styled'; +import { ContentCell, ListItem, UnorderedList } from 'design-system'; import * as React from 'react'; import { translate } from '../../../helpers/l10n'; import { InstalledPlugin } from '../../../types/plugins'; @@ -34,33 +36,46 @@ interface Props { status?: string; } -export default function PluginInstalled({ plugin, readOnly, refreshPending, status }: Props) { +export default function PluginInstalled({ + plugin, + readOnly, + refreshPending, + status, +}: Readonly) { return ( - + <> - -
      -
    • - - {plugin.version} - + + + + {plugin.version} {translate('marketplace._installed')} -
    • + -
    - + + - -
      + + -
    - + + {!readOnly && ( - + + + )} - + ); } + +const StyledUnorderedList = styled(UnorderedList)` + margin-top: 0; + + & li:first { + margin-top: 0; + } +`; diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/PluginLicense.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/PluginLicense.tsx index 0ce80143502..aa5ac6ccacb 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/components/PluginLicense.tsx +++ b/server/sonar-web/src/main/js/apps/marketplace/components/PluginLicense.tsx @@ -17,30 +17,32 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { ListItem } from 'design-system'; +import { isEmpty } from 'lodash'; import * as React from 'react'; import { FormattedMessage } from 'react-intl'; import Tooltip from '../../../components/controls/Tooltip'; -import { translate } from '../../../helpers/l10n'; interface Props { license?: string; } -export default function PluginLicense({ license }: Props) { - if (!license) { +export default function PluginLicense({ license }: Readonly) { + if (isEmpty(license)) { return null; } return ( - -
  • - {license}, - }} - /> -
  • -
    + + +
    + +
    +
    +
    ); } diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/PluginOrganization.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/PluginOrganization.tsx index fa387126b4e..ac97e348544 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/components/PluginOrganization.tsx +++ b/server/sonar-web/src/main/js/apps/marketplace/components/PluginOrganization.tsx @@ -17,39 +17,31 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { Link, ListItem } from 'design-system'; import * as React from 'react'; import { FormattedMessage } from 'react-intl'; -import { translate } from '../../../helpers/l10n'; import { Plugin } from '../../../types/plugins'; export interface PluginOrganizationProps { plugin: Plugin; } -export default function PluginOrganization({ plugin }: PluginOrganizationProps) { +export default function PluginOrganization({ plugin }: Readonly) { if (!plugin.organizationName) { return null; } return ( -
  • + - {plugin.organizationName} - + {plugin.organizationName} ) : ( - {plugin.organizationName} + {plugin.organizationName} ), }} /> -
  • + ); } diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/PluginRiskConsentBox.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/PluginRiskConsentBox.tsx index ae01172fd74..ade15440de3 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/components/PluginRiskConsentBox.tsx +++ b/server/sonar-web/src/main/js/apps/marketplace/components/PluginRiskConsentBox.tsx @@ -17,8 +17,8 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { ButtonPrimary, Card, DarkLabel } from 'design-system'; import * as React from 'react'; -import { Button } from '../../../components/controls/buttons'; import { translate } from '../../../helpers/l10n'; import { EditionKey } from '../../../types/editions'; import { RiskConsent } from '../../../types/plugins'; @@ -29,7 +29,7 @@ export interface PluginRiskConsentBoxProps { riskConsent?: RiskConsent; } -export default function PluginRiskConsentBox(props: PluginRiskConsentBoxProps) { +export default function PluginRiskConsentBox(props: Readonly) { const { currentEdition, riskConsent } = props; if (riskConsent === RiskConsent.Accepted) { @@ -37,20 +37,16 @@ export default function PluginRiskConsentBox(props: PluginRiskConsentBoxProps) { } return ( -
    -

    {translate('marketplace.risk_consent.title')}

    -
    -

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

    - {currentEdition === EditionKey.community && ( -

    {translate('marketplace.risk_consent.installation')}

    - )} - -
    -
    + + {translate('marketplace.risk_consent.title')} + +

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

    + {currentEdition === EditionKey.community && ( +

    {translate('marketplace.risk_consent.installation')}

    + )} + + {translate('marketplace.risk_consent.action')} + +
    ); } diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/PluginStatus.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/PluginStatus.tsx index 2160961678b..e543fa1421a 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/components/PluginStatus.tsx +++ b/server/sonar-web/src/main/js/apps/marketplace/components/PluginStatus.tsx @@ -17,6 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { FlagMessage } from 'design-system'; import * as React from 'react'; import { translate } from '../../../helpers/l10n'; import { Plugin } from '../../../types/plugins'; @@ -28,22 +29,18 @@ interface Props { status?: string; } -export default function PluginStatus({ plugin, refreshPending, status }: Props) { - return ( - - {status === 'installing' && ( -

    {translate('marketplace.install_pending')}

    - )} +export default function PluginStatus({ plugin, refreshPending, status }: Readonly) { + switch (status) { + case 'installing': + return {translate('marketplace.install_pending')}; - {status === 'updating' && ( -

    {translate('marketplace.update_pending')}

    - )} + case 'updating': + return {translate('marketplace.update_pending')}; - {status === 'removing' && ( -

    {translate('marketplace.uninstall_pending')}

    - )} + case 'removing': + return {translate('marketplace.uninstall_pending')}; - {status == null && } - - ); + default: + return ; + } } diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/PluginUpdateButton.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/PluginUpdateButton.tsx index f36f2acc954..bfa202e0daf 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/components/PluginUpdateButton.tsx +++ b/server/sonar-web/src/main/js/apps/marketplace/components/PluginUpdateButton.tsx @@ -17,8 +17,8 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { ButtonSecondary } from 'design-system'; import * as React from 'react'; -import { Button } from '../../../components/controls/buttons'; import Tooltip from '../../../components/controls/Tooltip'; import { translate, translateWithParameters } from '../../../helpers/l10n'; import { Update } from '../../../types/plugins'; @@ -29,26 +29,21 @@ interface Props { update: Update; } -export default class PluginUpdateButton extends React.PureComponent { - handleClick = () => { - this.props.onClick(this.props.update); - }; +export default function PluginUpdateButton(props: Readonly) { + const { disabled, onClick, update } = props; - render() { - const { disabled, update } = this.props; - if (update.status !== 'COMPATIBLE' || !update.release) { - return null; - } - return ( - - - - ); + const handleClick = React.useCallback(() => { + onClick(update); + }, [onClick, update]); + + if (update.status !== 'COMPATIBLE' || !update.release) { + return null; } + return ( + + + {translateWithParameters('marketplace.update_to_x', update.release.version)} + + + ); } diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/PluginUpdateItem.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/PluginUpdateItem.tsx index 5b26c1cf62e..9c6923f8bb2 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/components/PluginUpdateItem.tsx +++ b/server/sonar-web/src/main/js/apps/marketplace/components/PluginUpdateItem.tsx @@ -17,6 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { Badge, ListItem } from 'design-system'; import * as React from 'react'; import Tooltip from '../../../components/controls/Tooltip'; import { translate } from '../../../helpers/l10n'; @@ -29,15 +30,17 @@ interface Props { release: Release; } -export default function PluginUpdateItem({ release, update, pluginName }: Props) { +export default function PluginUpdateItem({ release, update, pluginName }: Readonly) { return ( -
  • -
    + +
    {update.status === 'COMPATIBLE' ? ( - {release.version} + {release.version} ) : ( - {release.version} + + {release.version} + )}
    @@ -45,6 +48,6 @@ export default function PluginUpdateItem({ release, update, pluginName }: Props) {release.description}
    -
  • + ); } diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/PluginUpdates.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/PluginUpdates.tsx index 1c9bc33e814..5457c590c68 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/components/PluginUpdates.tsx +++ b/server/sonar-web/src/main/js/apps/marketplace/components/PluginUpdates.tsx @@ -17,6 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { ListItem, UnorderedList } from 'design-system'; import * as React from 'react'; import { translate } from '../../../helpers/l10n'; import { Update } from '../../../types/plugins'; @@ -27,14 +28,14 @@ export interface PluginUpdatesProps { updates?: Update[]; } -export default function PluginUpdates({ pluginName, updates }: PluginUpdatesProps) { +export default function PluginUpdates({ pluginName, updates }: Readonly) { if (!updates || updates.length <= 0) { return null; } return ( -
  • - {translate('marketplace.updates')}: -
      + + {translate('marketplace.updates')}: + {updates.map((update) => update.release ? ( ) : null, )} -
    -
  • + + ); } diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/PluginUrls.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/PluginUrls.tsx index c6a69cb5957..a17788db2d6 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/components/PluginUrls.tsx +++ b/server/sonar-web/src/main/js/apps/marketplace/components/PluginUrls.tsx @@ -17,6 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { Link, ListItem } from 'design-system'; import * as React from 'react'; import { translate } from '../../../helpers/l10n'; import { Plugin } from '../../../types/plugins'; @@ -25,38 +26,22 @@ interface Props { plugin: Plugin; } -export default function PluginUrls({ plugin }: Props) { +export default function PluginUrls({ plugin }: Readonly) { if (!plugin.homepageUrl && !plugin.issueTrackerUrl) { return null; } return ( -
  • - -
  • + + {plugin.homepageUrl && ( + + {translate('marketplace.homepage')} + + )} + {plugin.issueTrackerUrl && ( + + {translate('marketplace.issue_tracker')} + + )} + ); } diff --git a/server/sonar-web/src/main/js/components/controls/SearchBox.tsx b/server/sonar-web/src/main/js/components/controls/SearchBox.tsx index 36d14bd7f48..d5ce452ffa4 100644 --- a/server/sonar-web/src/main/js/components/controls/SearchBox.tsx +++ b/server/sonar-web/src/main/js/components/controls/SearchBox.tsx @@ -56,7 +56,7 @@ export default class SearchBox extends React.PureComponent { constructor(props: Props) { super(props); - this.state = { value: props.value || '' }; + this.state = { value: props.value ?? '' }; this.debouncedOnChange = debounce(this.props.onChange, DEBOUNCE_DELAY); }