]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-10696 Display wrong edition error on license page
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>
Fri, 1 Jun 2018 09:04:05 +0000 (11:04 +0200)
committerSonarTech <sonartech@sonarsource.com>
Tue, 12 Jun 2018 18:21:00 +0000 (20:21 +0200)
* Handle optional "edition" field in api/navigation/global correctly
* Introduce enum of edition keys

18 files changed:
server/sonar-web/src/main/js/api/marketplace.ts
server/sonar-web/src/main/js/app/components/GlobalFooter.tsx
server/sonar-web/src/main/js/app/components/GlobalFooterContainer.tsx
server/sonar-web/src/main/js/app/components/StartupModal.tsx
server/sonar-web/src/main/js/app/components/__tests__/GlobalFooter-test.tsx
server/sonar-web/src/main/js/app/components/__tests__/StartupModal-test.tsx
server/sonar-web/src/main/js/apps/marketplace/App.tsx
server/sonar-web/src/main/js/apps/marketplace/AppContainer.tsx
server/sonar-web/src/main/js/apps/marketplace/EditionBoxes.tsx
server/sonar-web/src/main/js/apps/marketplace/Header.tsx
server/sonar-web/src/main/js/apps/marketplace/__tests__/EditionBoxes-test.tsx
server/sonar-web/src/main/js/apps/marketplace/__tests__/Header-test.tsx
server/sonar-web/src/main/js/apps/marketplace/__tests__/__snapshots__/EditionBoxes-test.tsx.snap
server/sonar-web/src/main/js/apps/marketplace/components/EditionBox.tsx
server/sonar-web/src/main/js/apps/marketplace/components/__tests__/EditionBox-test.tsx
server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/EditionBox-test.tsx.snap
server/sonar-web/src/main/js/apps/marketplace/utils.ts
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index 0681931e509d690f2eb9257c42484f1955fa201c..a90f4fcc24f6870341759fb80d24d50bb206ec0d 100644 (file)
@@ -28,6 +28,7 @@ export interface License {
   isExpired: boolean;
   isOfficialDistribution: boolean;
   isSupported: boolean;
+  isValidEdition: boolean;
   isValidServerId: boolean;
   loc: number;
   maxLoc: number;
index 363102ff65329872b1f7ea63486af0923bb2304b..d50fd613b4827d2945bf7f82e57f770801c0c3ea 100644 (file)
@@ -22,20 +22,21 @@ import { Link } from 'react-router';
 import GlobalFooterSonarCloud from './GlobalFooterSonarCloud';
 import GlobalFooterBranding from './GlobalFooterBranding';
 import InstanceMessage from '../../components/common/InstanceMessage';
-import { EDITIONS } from '../../apps/marketplace/utils';
+import { EDITIONS, EditionKey } from '../../apps/marketplace/utils';
 import { translate, translateWithParameters } from '../../helpers/l10n';
 import { isSonarCloud } from '../../helpers/system';
 
 interface Props {
   hideLoggedInInfo?: boolean;
   productionDatabase: boolean;
-  sonarqubeEdition: string;
+  sonarqubeEdition?: EditionKey;
   sonarqubeVersion?: string;
 }
 
 export default function GlobalFooter({
   hideLoggedInInfo,
   productionDatabase,
+  sonarqubeEdition,
   sonarqubeVersion
 }: Props) {
   if (isSonarCloud()) {
index 915fb155f2f0878451cc133c0bf6f054e3dd1447..4f079504d72c5724bd0a929578ea711ea228e255 100644 (file)
 import { connect } from 'react-redux';
 import GlobalFooter from './GlobalFooter';
 import { getAppState } from '../../store/rootReducer';
+import { EditionKey } from '../../apps/marketplace/utils';
 
 interface StateProps {
   productionDatabase: boolean;
-  sonarqubeEdition: string;
+  sonarqubeEdition?: EditionKey;
   sonarqubeVersion?: string;
 }
 
index f71ddd87badaa53593ed9cedc2cc1d4336475cf2..b2429cccd1955797ef4e52701a493f4da3b93980 100644 (file)
@@ -22,17 +22,18 @@ import * as PropTypes from 'prop-types';
 import { connect } from 'react-redux';
 import OnboardingModal from '../../apps/tutorials/onboarding/OnboardingModal';
 import LicensePromptModal from '../../apps/marketplace/components/LicensePromptModal';
-import { showLicense } from '../../api/marketplace';
+import { CurrentUser, isLoggedIn } from '../types';
 import { differenceInDays, parseDate, toShortNotSoISOString } from '../../helpers/dates';
-import { hasMessage } from '../../helpers/l10n';
-import { save, get } from '../../helpers/storage';
+import { EditionKey } from '../../apps/marketplace/utils';
 import { getCurrentUser, getAppState } from '../../store/rootReducer';
 import { skipOnboarding } from '../../store/users/actions';
-import { CurrentUser, isLoggedIn } from '../types';
+import { showLicense } from '../../api/marketplace';
+import { hasMessage } from '../../helpers/l10n';
+import { save, get } from '../../helpers/storage';
 
 interface StateProps {
   canAdmin: boolean;
-  currentEdition: string;
+  currentEdition?: EditionKey;
   currentUser: CurrentUser;
 }
 
@@ -77,16 +78,22 @@ export class StartupModal extends React.PureComponent<Props, State> {
   }
 
   closeOnboarding = () => {
-    this.setState(state => ({
-      modal: state.modal === ModalKey.onboarding ? undefined : state.modal
-    }));
-    this.props.skipOnboarding();
+    this.setState(state => {
+      if (state.modal === ModalKey.onboarding) {
+        this.props.skipOnboarding();
+        return { modal: undefined };
+      }
+      return undefined;
+    });
   };
 
   closeLicense = () => {
-    this.setState(state => ({
-      modal: state.modal === ModalKey.license ? undefined : state.modal
-    }));
+    this.setState(state => {
+      if (state.modal === ModalKey.license) {
+        return { modal: undefined };
+      }
+      return undefined;
+    });
   };
 
   openOnboarding = () => {
@@ -96,16 +103,14 @@ export class StartupModal extends React.PureComponent<Props, State> {
   tryAutoOpenLicense = () => {
     const { canAdmin, currentEdition, currentUser } = this.props;
     const hasLicenseManager = hasMessage('license.prompt.title');
-    if (
-      currentEdition !== 'community' &&
-      isLoggedIn(currentUser) &&
-      canAdmin &&
-      hasLicenseManager
-    ) {
+    const hasLicensedEdition = currentEdition && currentEdition !== EditionKey.community;
+
+    if (canAdmin && hasLicensedEdition && isLoggedIn(currentUser) && hasLicenseManager) {
       const lastPrompt = get(LICENSE_PROMPT, currentUser.login);
+
       if (!lastPrompt || differenceInDays(new Date(), parseDate(lastPrompt)) >= 1) {
         return showLicense().then(license => {
-          if (!license || license.edition !== currentEdition) {
+          if (!license || !license.isValidEdition) {
             save(LICENSE_PROMPT, toShortNotSoISOString(new Date()), currentUser.login);
             this.setState({ modal: ModalKey.license });
             return Promise.resolve();
index 47d63dd2ffc73be84afccb10397217be379e0f72..dad1ed27cab99d1720ec6572088ea877826dcc6d 100644 (file)
@@ -20,6 +20,7 @@
 import * as React from 'react';
 import { shallow } from 'enzyme';
 import GlobalFooter from '../GlobalFooter';
+import { EditionKey } from '../../../apps/marketplace/utils';
 import { isSonarCloud } from '../../../helpers/system';
 
 jest.mock('../../../helpers/system', () => ({ isSonarCloud: jest.fn() }));
@@ -40,7 +41,7 @@ it('should show the db warning message', () => {
 
 it('should display the sq version', () => {
   expect(
-    getWrapper({ sonarqubeEdition: 'enterprise', sonarqubeVersion: '6.4-SNAPSHOT' })
+    getWrapper({ sonarqubeEdition: EditionKey.enterprise, sonarqubeVersion: '6.4-SNAPSHOT' })
   ).toMatchSnapshot();
 });
 
@@ -50,5 +51,5 @@ it('should render SonarCloud footer', () => {
 
 function getWrapper(props = {}, onSonarCloud = false) {
   (isSonarCloud as jest.Mock).mockImplementation(() => onSonarCloud);
-  return shallow(<GlobalFooter productionDatabase={true} sonarqubeEdition="community" {...props} />);
+  return shallow(<GlobalFooter productionDatabase={true} sonarqubeEdition={EditionKey.community} {...props} />);
 }
index dee4a3bcd29eb959966459a534e40b23c392588f..0f8cbe181315fb0b6482d3252d5b07e1e17a9eb0 100644 (file)
@@ -26,6 +26,7 @@ import { hasMessage } from '../../../helpers/l10n';
 import { waitAndUpdate } from '../../../helpers/testUtils';
 import { differenceInDays, toShortNotSoISOString } from '../../../helpers/dates';
 import { LoggedInUser } from '../../types';
+import { EditionKey } from '../../../apps/marketplace/utils';
 
 jest.mock('../../../api/marketplace', () => ({
   showLicense: jest.fn().mockResolvedValue(undefined)
@@ -63,7 +64,7 @@ beforeEach(() => {
 });
 
 it('should render only the children', async () => {
-  const wrapper = getWrapper({ currentEdition: 'community' });
+  const wrapper = getWrapper({ currentEdition: EditionKey.community });
   await shouldNotHaveModals(wrapper);
   expect(showLicense).toHaveBeenCalledTimes(0);
   expect(wrapper.find('div').exists()).toBeTruthy();
@@ -73,7 +74,7 @@ it('should render only the children', async () => {
   (hasMessage as jest.Mock<any>).mockReturnValueOnce(false);
   await shouldNotHaveModals(getWrapper());
 
-  (showLicense as jest.Mock<any>).mockResolvedValueOnce({ edition: 'enterprise' });
+  (showLicense as jest.Mock<any>).mockResolvedValueOnce({ isValidEdition: true });
   await shouldNotHaveModals(getWrapper());
 
   (get as jest.Mock<any>).mockReturnValueOnce('date');
@@ -89,7 +90,7 @@ it('should render license prompt', async () => {
   (differenceInDays as jest.Mock<any>).mockReturnValueOnce(1);
   await shouldDisplayLicense(getWrapper());
 
-  (showLicense as jest.Mock<any>).mockResolvedValueOnce({ edition: 'developer' });
+  (showLicense as jest.Mock<any>).mockResolvedValueOnce({ isValidEdition: false });
   await shouldDisplayLicense(getWrapper());
 });
 
@@ -101,7 +102,7 @@ it('should render onboarding modal', async () => {
     })
   );
 
-  (showLicense as jest.Mock<any>).mockResolvedValueOnce({ edition: 'enterprise' });
+  (showLicense as jest.Mock<any>).mockResolvedValueOnce({ isValidEdition: true });
   await shouldDisplayOnboarding(
     getWrapper({ currentUser: { ...LOGGED_IN_USER, showOnboardingTutorial: true } })
   );
@@ -127,7 +128,7 @@ function getWrapper(props = {}) {
   return shallow(
     <StartupModal
       canAdmin={true}
-      currentEdition="enterprise"
+      currentEdition={EditionKey.enterprise}
       currentUser={LOGGED_IN_USER}
       skipOnboarding={jest.fn()}
       {...props}>
index 486f4e2bd1703874201c8eb6e35763257be4e35a..2e80802d8a9d702ab426999d851507eb6ba903d0 100644 (file)
@@ -26,7 +26,7 @@ import EditionBoxes from './EditionBoxes';
 import Footer from './Footer';
 import PluginsList from './PluginsList';
 import Search from './Search';
-import { filterPlugins, parseQuery, Query, serializeQuery } from './utils';
+import { filterPlugins, parseQuery, Query, serializeQuery, EditionKey } from './utils';
 import Suggestions from '../../app/components/embed-docs-modal/Suggestions';
 import {
   getAvailablePlugins,
@@ -41,7 +41,7 @@ import { translate } from '../../helpers/l10n';
 import './style.css';
 
 export interface Props {
-  currentEdition?: string;
+  currentEdition?: EditionKey;
   fetchPendingPlugins: () => void;
   location: { pathname: string; query: RawQuery };
   pendingPlugins: PluginPendingResult;
@@ -119,7 +119,7 @@ export default class App extends React.PureComponent<Props, State> {
   };
 
   render() {
-    const { currentEdition = 'community', standaloneMode, pendingPlugins } = this.props;
+    const { currentEdition, standaloneMode, pendingPlugins } = this.props;
     const { loadingPlugins, plugins } = this.state;
     const query = parseQuery(this.props.location.query);
     const filteredPlugins = query.search ? filterPlugins(plugins, query.search) : plugins;
index 78f4496d67aa050def40af78b436683e6afe1942..a5e2fbc24eb871f51dfee2029fc6258c7bde8044 100644 (file)
@@ -19,6 +19,7 @@
  */
 import { connect } from 'react-redux';
 import App from './App';
+import { EditionKey } from './utils';
 import {
   getAppState,
   getGlobalSettingValue,
@@ -33,7 +34,7 @@ interface OwnProps {
 }
 
 interface StateToProps {
-  currentEdition?: string;
+  currentEdition?: EditionKey;
   pendingPlugins: PluginPendingResult;
   standaloneMode: boolean;
   updateCenterActive: boolean;
index f79b349ec69aab354c7048173b964e473113654f..a9ee399fffc8a0b9198f170378d2d51024b57bab 100644 (file)
 
 import * as React from 'react';
 import EditionBox from './components/EditionBox';
-import { EDITIONS } from './utils';
+import { EDITIONS, EditionKey } from './utils';
 import { getFormData } from '../../api/marketplace';
 
 export interface Props {
-  currentEdition: string;
+  currentEdition?: EditionKey;
 }
 
 interface State {
index 2c17f03f8e7e10c034234d05ef31b0b9d1439db8..cfff7fe32ad4804b3c317a5e6af078dd7ff55712 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as React from 'react';
+import { EditionKey } from './utils';
 import { translate } from '../../helpers/l10n';
 
 interface Props {
-  currentEdition: string;
+  currentEdition?: EditionKey;
 }
 
 export default function Header({ currentEdition }: Props) {
   return (
     <header className="page-header" id="marketplace-header">
       <h1 className="page-title">{translate('marketplace.page')}</h1>
-      <h3 className="page-description">
-        {translate('marketplace.page.you_are_running', currentEdition)}
-      </h3>
+      {currentEdition && (
+        <h3 className="page-description">
+          {translate('marketplace.page.you_are_running', currentEdition)}
+        </h3>
+      )}
       <p className="page-description">
-        {currentEdition === 'datacenter'
+        {currentEdition === EditionKey.datacenter
           ? translate('marketplace.page.description_best_edition')
           : translate('marketplace.page.description')}
       </p>
index 9c35ee382d6d45f3a46d602f03c5dd342999abd8..ac0fac8a9f777077b4500359ffa934a5a3308921 100644 (file)
 import * as React from 'react';
 import { shallow } from 'enzyme';
 import EditionBoxes from '../EditionBoxes';
-
-jest.mock('../utils', () => ({
-  EDITIONS: [
-    { key: 'community', name: 'Community Edition', homeUrl: 'more_url' },
-    {
-      key: 'developer',
-      name: 'Developer Edition',
-      homeUrl: 'more_url'
-    },
-    {
-      key: 'enterprise',
-      name: 'Enterprise Edition',
-      homeUrl: 'more_url'
-    },
-    {
-      key: 'datacenter',
-      name: 'Data Center Edition',
-      homeUrl: 'more_url'
-    }
-  ]
-}));
+import { EditionKey } from '../utils';
 
 it('should display the available edition boxes correctly', () => {
   expect(getWrapper()).toMatchSnapshot();
 });
 
 it('should display the enterprise and datacenter edition boxes', () => {
-  expect(getWrapper({ currentEdition: 'developer' })).toMatchSnapshot();
+  expect(getWrapper({ currentEdition: EditionKey.developer })).toMatchSnapshot();
 });
 
 it('should display the datacenter edition box only', () => {
-  expect(getWrapper({ currentEdition: 'enterprise' })).toMatchSnapshot();
+  expect(getWrapper({ currentEdition: EditionKey.enterprise })).toMatchSnapshot();
 });
 
 it('should not display any edition box', () => {
-  expect(getWrapper({ currentEdition: 'datacenter' }).type()).toBeNull();
+  expect(getWrapper({ currentEdition: EditionKey.datacenter }).type()).toBeNull();
 });
 
 function getWrapper(props = {}) {
-  return shallow(<EditionBoxes currentEdition={'community'} {...props} />);
+  return shallow(<EditionBoxes currentEdition={EditionKey.community} {...props} />);
 }
index 3e0a609c78163a60057fc44ff23b85e610efafad..d09ca690c26a67d2782ed505f9c707024d137a58 100644 (file)
@@ -20,8 +20,9 @@
 import * as React from 'react';
 import { shallow } from 'enzyme';
 import Header from '../Header';
+import { EditionKey } from '../utils';
 
 it('should render with installed editions', () => {
-  expect(shallow(<Header currentEdition="community" />)).toMatchSnapshot();
-  expect(shallow(<Header currentEdition="datacenter" />)).toMatchSnapshot();
+  expect(shallow(<Header currentEdition={EditionKey.community} />)).toMatchSnapshot();
+  expect(shallow(<Header currentEdition={EditionKey.datacenter} />)).toMatchSnapshot();
 });
index d8abf481899cbd80093628bcf453a747c8d48356..b27aa61041aa316fafb9e714cfd3d7d708b90443 100644 (file)
@@ -8,7 +8,8 @@ exports[`should display the available edition boxes correctly 1`] = `
     currentEdition="community"
     edition={
       Object {
-        "homeUrl": "more_url",
+        "downloadUrl": "https://sonarsource.bintray.com/CommercialDistribution/editions/developer-edition-7.0.0.717.zip",
+        "homeUrl": "https://redirect.sonarsource.com/editions/developer.html",
         "key": "developer",
         "name": "Developer Edition",
       }
@@ -19,7 +20,8 @@ exports[`should display the available edition boxes correctly 1`] = `
     currentEdition="community"
     edition={
       Object {
-        "homeUrl": "more_url",
+        "downloadUrl": "https://sonarsource.bintray.com/CommercialDistribution/editions/enterprise-edition-7.0.0.717.zip",
+        "homeUrl": "https://redirect.sonarsource.com/editions/enterprise.html",
         "key": "enterprise",
         "name": "Enterprise Edition",
       }
@@ -30,7 +32,8 @@ exports[`should display the available edition boxes correctly 1`] = `
     currentEdition="community"
     edition={
       Object {
-        "homeUrl": "more_url",
+        "downloadUrl": "https://sonarsource.bintray.com/CommercialDistribution/editions/datacenter-edition-7.0.0.717.zip",
+        "homeUrl": "https://redirect.sonarsource.com/editions/datacenter.html",
         "key": "datacenter",
         "name": "Data Center Edition",
       }
@@ -48,7 +51,8 @@ exports[`should display the datacenter edition box only 1`] = `
     currentEdition="enterprise"
     edition={
       Object {
-        "homeUrl": "more_url",
+        "downloadUrl": "https://sonarsource.bintray.com/CommercialDistribution/editions/datacenter-edition-7.0.0.717.zip",
+        "homeUrl": "https://redirect.sonarsource.com/editions/datacenter.html",
         "key": "datacenter",
         "name": "Data Center Edition",
       }
@@ -66,7 +70,8 @@ exports[`should display the enterprise and datacenter edition boxes 1`] = `
     currentEdition="developer"
     edition={
       Object {
-        "homeUrl": "more_url",
+        "downloadUrl": "https://sonarsource.bintray.com/CommercialDistribution/editions/enterprise-edition-7.0.0.717.zip",
+        "homeUrl": "https://redirect.sonarsource.com/editions/enterprise.html",
         "key": "enterprise",
         "name": "Enterprise Edition",
       }
@@ -77,7 +82,8 @@ exports[`should display the enterprise and datacenter edition boxes 1`] = `
     currentEdition="developer"
     edition={
       Object {
-        "homeUrl": "more_url",
+        "downloadUrl": "https://sonarsource.bintray.com/CommercialDistribution/editions/datacenter-edition-7.0.0.717.zip",
+        "homeUrl": "https://redirect.sonarsource.com/editions/datacenter.html",
         "key": "datacenter",
         "name": "Data Center Edition",
       }
index 55309126c7e031a535e5ccae76a868d8ef5892a9..699d99a114c8c1a41dcbaaeae8c75eff94fb4732 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as React from 'react';
-import { Edition, getEditionUrl } from '../utils';
+import { Edition, getEditionUrl, EditionKey } from '../utils';
 import DocInclude from '../../../components/docs/DocInclude';
 import { translate } from '../../../helpers/l10n';
 
 interface Props {
-  currentEdition: string;
+  currentEdition?: EditionKey;
   edition: Edition;
   ncloc?: number;
   serverId?: string;
index f4398076daf29516a46d3342c91fe021484e57a6..7deb14298dd2be1ef3b4c0e376b94904adb21d33 100644 (file)
 import * as React from 'react';
 import { shallow } from 'enzyme';
 import EditionBox from '../EditionBox';
+import { EditionKey } from '../../utils';
 
 const DEFAULT_EDITION = {
-  key: 'foo',
-  name: 'Foo',
+  key: EditionKey.developer,
+  name: 'Developer',
   downloadUrl: 'download_url',
   homeUrl: 'more_url'
 };
@@ -32,7 +33,7 @@ it('should display the edition', () => {
   expect(
     shallow(
       <EditionBox
-        currentEdition="community"
+        currentEdition={EditionKey.community}
         edition={DEFAULT_EDITION}
         ncloc={1000}
         serverId="serverId"
index 69af9d6c1dba376b009ad5c4182d15011b57453c..acc639fed84e70bb69e4d8b80d7f24db02e966f0 100644 (file)
@@ -5,7 +5,7 @@ exports[`should display the edition 1`] = `
   className="boxed-group boxed-group-inner marketplace-edition"
 >
   <DocInclude
-    path="/tooltips/editions/foo"
+    path="/tooltips/editions/developer"
   />
   <div
     className="marketplace-edition-action spacer-top"
index 29ea02d891f9b4c1f9456c0156705352e421f8ff..58e77c269bd468c65f3d56ebcf2f6d9af62f25b8 100644 (file)
@@ -23,10 +23,17 @@ import { Plugin, PluginAvailable, PluginInstalled, PluginPending } from '../../a
 import { cleanQuery, parseAsString, RawQuery, serializeString } from '../../helpers/query';
 import { omitNil } from '../../helpers/request';
 
+export enum EditionKey {
+  community = 'community',
+  developer = 'developer',
+  enterprise = 'enterprise',
+  datacenter = 'datacenter'
+}
+
 export interface Edition {
   downloadUrl?: string;
   homeUrl: string;
-  key: string;
+  key: EditionKey;
   name: string;
 }
 
@@ -37,26 +44,26 @@ export interface Query {
 
 export const EDITIONS: Edition[] = [
   {
-    key: 'community',
+    key: EditionKey.community,
     name: 'Community Edition',
     homeUrl: 'https://redirect.sonarsource.com/editions/community.html'
   },
   {
-    key: 'developer',
+    key: EditionKey.developer,
     name: 'Developer Edition',
     homeUrl: 'https://redirect.sonarsource.com/editions/developer.html',
     downloadUrl:
       'https://sonarsource.bintray.com/CommercialDistribution/editions/developer-edition-7.0.0.717.zip'
   },
   {
-    key: 'enterprise',
+    key: EditionKey.enterprise,
     name: 'Enterprise Edition',
     homeUrl: 'https://redirect.sonarsource.com/editions/enterprise.html',
     downloadUrl:
       'https://sonarsource.bintray.com/CommercialDistribution/editions/enterprise-edition-7.0.0.717.zip'
   },
   {
-    key: 'datacenter',
+    key: EditionKey.datacenter,
     name: 'Data Center Edition',
     homeUrl: 'https://redirect.sonarsource.com/editions/datacenter.html',
     downloadUrl:
index f35e01ce87c49b4df1f1e0f4c341f585ecdb7290..edfbbb6ec9c030188774c0cd34bf8a6cab762257 100644 (file)
@@ -2133,13 +2133,13 @@ workspace.no_rule=The rule has been removed or never existed.
 #
 #------------------------------------------------------------------------------
 marketplace.page=Marketplace
-marketplace.page.description=Discover more features with the SonarSource Editions:
-marketplace.page.description_best_edition=This Edition gives you access to all features of the SonarQube-SonarLint ecosystem, including all SonarSource code analyzers !
+marketplace.page.description=Discover more features with SonarSource Editions:
+marketplace.page.description_best_edition=This Edition gives you access to all features of the SonarQube-SonarLint ecosystem, including all SonarSource code analyzers!
 marketplace.page.you_are_running.community=You are currently running a Community Edition.
 marketplace.page.you_are_running.developer=You are currently running a Developer Edition.
 marketplace.page.you_are_running.enterprise=You are currently running an Enterprise Edition.
 marketplace.page.you_are_running.datacenter=You are currently running a Data Center Edition.
-marketplace.page.open_source_plugins=Community plugins
+marketplace.page.open_source_plugins=Plugins
 marketplace.instance_needs_to_be_restarted_to={instance} needs to be restarted in order to
 marketplace.install_x_plugins=install {nb} plugins
 marketplace.update_x_plugins=update {nb} plugins