From 667542fa30857ee17c1317e19d67f62e62e44b44 Mon Sep 17 00:00:00 2001 From: Mathieu Suen Date: Thu, 14 Oct 2021 18:06:41 +0200 Subject: [PATCH] SONAR-15507 Add a prompt to warn when SonarQube need an update --- server/sonar-web/src/main/js/api/system.ts | 1 + .../js/app/components/GlobalContainer.tsx | 2 + .../GlobalContainer-test.tsx.snap | 1 + .../UpdateNotification.css | 29 +++ .../UpdateNotification.tsx | 241 ++++++++++++++++++ .../__tests__/UpdateNotification-test.tsx | 153 +++++++++++ .../js/apps/system/__tests__/utils-test.ts | 49 ---- .../system-upgrade/SystemUpgradeNotif.tsx | 29 +-- .../__tests__/SystemUpgradeNotif-test.tsx | 5 +- .../SystemUpgradeNotif-test.tsx.snap | 65 ++--- .../src/main/js/apps/system/utils.ts | 17 +- .../src/main/js/components/ui/Alert.tsx | 2 +- .../js/components/ui/DismissableAlert.tsx | 32 +-- .../DismissableAlert-test.tsx.snap | 88 ++++--- .../upgrade/SystemUpgradeButton.tsx | 63 +++++ .../upgrade}/SystemUpgradeForm.tsx | 12 +- .../upgrade}/SystemUpgradeIntermediate.tsx | 10 +- .../upgrade}/SystemUpgradeItem.tsx | 10 +- .../__tests__/SystemUpgradeButton-test.tsx | 38 +++ .../__tests__/SystemUpgradeForm-test.tsx | 2 +- .../SystemUpgradeIntermediate-test.tsx | 2 +- .../__tests__/SystemUpgradeItem-test.tsx | 2 +- .../SystemUpgradeButton-test.tsx.snap | 12 + .../SystemUpgradeForm-test.tsx.snap | 0 .../SystemUpgradeIntermediate-test.tsx.snap | 0 .../SystemUpgradeItem-test.tsx.snap | 0 .../upgrade/__tests__/utils-test.ts | 69 +++++ .../src/main/js/components/upgrade/utils.ts | 35 +++ .../main/js/helpers/mocks/system-upgrades.ts | 31 +++ .../resources/org/sonar/l10n/core.properties | 11 + 30 files changed, 795 insertions(+), 216 deletions(-) create mode 100644 server/sonar-web/src/main/js/app/components/update-notification/UpdateNotification.css create mode 100644 server/sonar-web/src/main/js/app/components/update-notification/UpdateNotification.tsx create mode 100644 server/sonar-web/src/main/js/app/components/update-notification/__tests__/UpdateNotification-test.tsx create mode 100644 server/sonar-web/src/main/js/components/upgrade/SystemUpgradeButton.tsx rename server/sonar-web/src/main/js/{apps/system/components/system-upgrade => components/upgrade}/SystemUpgradeForm.tsx (87%) rename server/sonar-web/src/main/js/{apps/system/components/system-upgrade => components/upgrade}/SystemUpgradeIntermediate.tsx (89%) rename server/sonar-web/src/main/js/{apps/system/components/system-upgrade => components/upgrade}/SystemUpgradeItem.tsx (92%) create mode 100644 server/sonar-web/src/main/js/components/upgrade/__tests__/SystemUpgradeButton-test.tsx rename server/sonar-web/src/main/js/{apps/system/components/system-upgrade => components/upgrade}/__tests__/SystemUpgradeForm-test.tsx (97%) rename server/sonar-web/src/main/js/{apps/system/components/system-upgrade => components/upgrade}/__tests__/SystemUpgradeIntermediate-test.tsx (97%) rename server/sonar-web/src/main/js/{apps/system/components/system-upgrade => components/upgrade}/__tests__/SystemUpgradeItem-test.tsx (98%) create mode 100644 server/sonar-web/src/main/js/components/upgrade/__tests__/__snapshots__/SystemUpgradeButton-test.tsx.snap rename server/sonar-web/src/main/js/{apps/system/components/system-upgrade => components/upgrade}/__tests__/__snapshots__/SystemUpgradeForm-test.tsx.snap (100%) rename server/sonar-web/src/main/js/{apps/system/components/system-upgrade => components/upgrade}/__tests__/__snapshots__/SystemUpgradeIntermediate-test.tsx.snap (100%) rename server/sonar-web/src/main/js/{apps/system/components/system-upgrade => components/upgrade}/__tests__/__snapshots__/SystemUpgradeItem-test.tsx.snap (100%) create mode 100644 server/sonar-web/src/main/js/components/upgrade/__tests__/utils-test.ts create mode 100644 server/sonar-web/src/main/js/components/upgrade/utils.ts create mode 100644 server/sonar-web/src/main/js/helpers/mocks/system-upgrades.ts diff --git a/server/sonar-web/src/main/js/api/system.ts b/server/sonar-web/src/main/js/api/system.ts index 9adf55ea9dc..7a613374ec3 100644 --- a/server/sonar-web/src/main/js/api/system.ts +++ b/server/sonar-web/src/main/js/api/system.ts @@ -35,6 +35,7 @@ export function getSystemStatus(): Promise<{ id: string; version: string; status export function getSystemUpgrades(): Promise<{ upgrades: SystemUpgrade[]; + latestLTS: string; updateCenterRefresh: string; }> { return getJSON('/api/system/upgrades'); 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 68763f336ea..e31bf86e066 100644 --- a/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx +++ b/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx @@ -28,6 +28,7 @@ import IndexationContextProvider from './indexation/IndexationContextProvider'; import IndexationNotification from './indexation/IndexationNotification'; import GlobalNav from './nav/global/GlobalNav'; import StartupModal from './StartupModal'; +import UpdateNotification from './update-notification/UpdateNotification'; export interface Props { children: React.ReactNode; @@ -52,6 +53,7 @@ export default function GlobalContainer(props: Props) { + {props.children} diff --git a/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalContainer-test.tsx.snap b/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalContainer-test.tsx.snap index cf87ae0d4f2..7f04cba74c2 100644 --- a/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalContainer-test.tsx.snap +++ b/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalContainer-test.tsx.snap @@ -32,6 +32,7 @@ exports[`should render correctly 1`] = ` /> + diff --git a/server/sonar-web/src/main/js/app/components/update-notification/UpdateNotification.css b/server/sonar-web/src/main/js/app/components/update-notification/UpdateNotification.css new file mode 100644 index 00000000000..fc23a2cb9f8 --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/update-notification/UpdateNotification.css @@ -0,0 +1,29 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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. + */ +.promote-update-notification.dismissable-alert-wrapper { + height: 42px; +} + +.promote-update-notification .dismissable-alert-banner { + margin-bottom: 0 !important; + position: fixed; + width: 100%; + z-index: var(--globalBannerZIndex); +} diff --git a/server/sonar-web/src/main/js/app/components/update-notification/UpdateNotification.tsx b/server/sonar-web/src/main/js/app/components/update-notification/UpdateNotification.tsx new file mode 100644 index 00000000000..1a3a23b4f5f --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/update-notification/UpdateNotification.tsx @@ -0,0 +1,241 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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 { groupBy, isEmpty, mapValues } from 'lodash'; +import * as React from 'react'; +import { getSystemUpgrades } from '../../../api/system'; +import { withAppState } from '../../../components/hoc/withAppState'; +import { withCurrentUser } from '../../../components/hoc/withCurrentUser'; +import { AlertVariant } from '../../../components/ui/Alert'; +import DismissableAlert from '../../../components/ui/DismissableAlert'; +import SystemUpgradeButton from '../../../components/upgrade/SystemUpgradeButton'; +import { sortUpgrades } from '../../../components/upgrade/utils'; +import { translate } from '../../../helpers/l10n'; +import { hasGlobalPermission, isLoggedIn } from '../../../helpers/users'; +import { Permissions } from '../../../types/permissions'; +import { SystemUpgrade } from '../../../types/system'; +import './UpdateNotification.css'; + +const MONTH_BEFOR_PREVIOUS_LTS_NOTIFICATION = 6; + +type GroupedSystemUpdate = { + [x: string]: T.Dict; +}; + +enum UseCase { + NewMinorVersion = 'new_minor_version', + NewPatch = 'new_patch', + PreLTS = 'pre_lts', + PreviousLTS = 'previous_lts' +} + +const MAP_VARIANT: T.Dict = { + [UseCase.NewMinorVersion]: 'info', + [UseCase.NewPatch]: 'warning', + [UseCase.PreLTS]: 'warning', + [UseCase.PreviousLTS]: 'error' +}; + +interface Props { + appState: Pick; + currentUser: T.CurrentUser; +} + +interface State { + dismissKey: string; + useCase: UseCase; + systemUpgrades: SystemUpgrade[]; + canSeeNotification: boolean; +} + +export class UpdateNotification extends React.PureComponent { + mounted = false; + versionParser = /^(\d+)\.(\d+)(\.(\d+))?/; + + constructor(props: Props) { + super(props); + this.state = { + dismissKey: '', + systemUpgrades: [], + canSeeNotification: false, + useCase: UseCase.NewMinorVersion + }; + this.fetchSystemUpgradeInformation(); + } + + componentDidMount() { + this.mounted = true; + } + + componentDidUpdate(prevProps: Props) { + if ( + prevProps.currentUser !== this.props.currentUser || + this.props.appState.version !== prevProps.appState.version + ) { + this.fetchSystemUpgradeInformation(); + } + } + + componentWillUnmount() { + this.mounted = false; + } + + isPreLTSUpdate(parsedVersion: number[], latestLTS: string) { + const [currentMajor, currentMinor] = parsedVersion; + const [ltsMajor, ltsMinor] = latestLTS.split('.').map(Number); + return currentMajor < ltsMajor || (currentMajor === ltsMajor && currentMinor < ltsMinor); + } + + isPreviousLTSUpdate( + parsedVersion: number[], + latestLTS: string, + systemUpgrades: GroupedSystemUpdate + ) { + const [ltsMajor, ltsMinor] = latestLTS.split('.').map(Number); + let ltsOlderThan6Month = false; + const beforeLts = this.isPreLTSUpdate(parsedVersion, latestLTS); + if (beforeLts) { + const allLTS = sortUpgrades(systemUpgrades[ltsMajor][ltsMinor]); + const ltsReleaseDate = new Date(allLTS[allLTS.length - 1]?.releaseDate || ''); + if (isNaN(ltsReleaseDate.getTime())) { + // We can not parse the LTS date. + // It is unlikly that this could happen but consider LTS to be old. + return true; + } + ltsOlderThan6Month = + ltsReleaseDate.setMonth(ltsReleaseDate.getMonth() + MONTH_BEFOR_PREVIOUS_LTS_NOTIFICATION) - + Date.now() < + 0; + } + return ltsOlderThan6Month && beforeLts; + } + + isMinorUpdate(parsedVersion: number[], systemUpgrades: GroupedSystemUpdate) { + const [currentMajor, currentMinor] = parsedVersion; + const allMinor = systemUpgrades[currentMajor]; + return Object.keys(allMinor) + .map(Number) + .some(minor => minor > currentMinor); + } + + isPatchUpdate(parsedVersion: number[], systemUpgrades: GroupedSystemUpdate) { + const [currentMajor, currentMinor, currentPatch] = parsedVersion; + const allMinor = systemUpgrades[currentMajor]; + const allPatch = sortUpgrades(allMinor[currentMinor] || []); + + if (!isEmpty(allPatch)) { + const [, , latestPatch] = allPatch[0].version.split('.').map(Number); + const effectiveCurrentPatch = isNaN(currentPatch) ? 0 : currentPatch; + const effectiveLatestPatch = isNaN(latestPatch) ? 0 : latestPatch; + return effectiveCurrentPatch < effectiveLatestPatch; + } + return false; + } + + async fetchSystemUpgradeInformation() { + if ( + !isLoggedIn(this.props.currentUser) || + !hasGlobalPermission(this.props.currentUser, Permissions.Admin) + ) { + this.noPromptToShow(); + return; + } + + const regExpParsedVersion = this.versionParser.exec(this.props.appState.version); + if (regExpParsedVersion === null) { + this.noPromptToShow(); + return; + } + regExpParsedVersion.shift(); + const parsedVersion = regExpParsedVersion.map(Number).map(n => (isNaN(n) ? 0 : n)); + + const { upgrades, latestLTS } = await getSystemUpgrades(); + + if (isEmpty(upgrades)) { + // No new upgrades + this.noPromptToShow(); + return; + } + const systemUpgrades = mapValues( + groupBy(upgrades, upgrade => { + const [major] = upgrade.version.split('.'); + return major; + }), + upgrades => + groupBy(upgrades, upgrade => { + const [, minor] = upgrade.version.split('.'); + return minor; + }) + ); + + let useCase = UseCase.NewMinorVersion; + + if (this.isPreviousLTSUpdate(parsedVersion, latestLTS, systemUpgrades)) { + useCase = UseCase.PreviousLTS; + } else if (this.isPreLTSUpdate(parsedVersion, latestLTS)) { + useCase = UseCase.PreLTS; + } else if (this.isPatchUpdate(parsedVersion, systemUpgrades)) { + useCase = UseCase.NewPatch; + } else if (this.isMinorUpdate(parsedVersion, systemUpgrades)) { + useCase = UseCase.NewMinorVersion; + } + + const latest = [...upgrades].sort( + (upgrade1, upgrade2) => + new Date(upgrade2.releaseDate || '').getTime() - + new Date(upgrade1.releaseDate || '').getTime() + )[0]; + + const dismissKey = useCase + latest.version; + + if (this.mounted) { + this.setState({ + useCase, + dismissKey, + systemUpgrades: upgrades, + canSeeNotification: true + }); + } + } + + noPromptToShow() { + if (this.mounted) { + this.setState({ canSeeNotification: false }); + } + } + + render() { + const { systemUpgrades, canSeeNotification, useCase, dismissKey } = this.state; + if (!canSeeNotification) { + return null; + } + return ( + + {translate('admin_notification.update', useCase)} + + + ); + } +} + +export default withCurrentUser(withAppState(UpdateNotification)); diff --git a/server/sonar-web/src/main/js/app/components/update-notification/__tests__/UpdateNotification-test.tsx b/server/sonar-web/src/main/js/app/components/update-notification/__tests__/UpdateNotification-test.tsx new file mode 100644 index 00000000000..6c284a04610 --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/update-notification/__tests__/UpdateNotification-test.tsx @@ -0,0 +1,153 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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 { shallow } from 'enzyme'; +import * as React from 'react'; +import { getSystemUpgrades } from '../../../../api/system'; +import DismissableAlert from '../../../../components/ui/DismissableAlert'; +import { mockUpgrades } from '../../../../helpers/mocks/system-upgrades'; +import { mockAppState, mockCurrentUser, mockLoggedInUser } from '../../../../helpers/testMocks'; +import { waitAndUpdate } from '../../../../helpers/testUtils'; +import { Permissions } from '../../../../types/permissions'; +import { UpdateNotification } from '../UpdateNotification'; + +jest.mock('../../../../api/system', () => { + const { mockUpgrades } = jest.requireActual('../../../../helpers/mocks/system-upgrades'); + return { + getSystemUpgrades: jest.fn().mockResolvedValue({ upgrades: [mockUpgrades()], latestLTS: '8.9' }) + }; +}); + +function formatDate(date: Date): string { + return `${date.getFullYear()}-${date.getMonth()}-${date.getDay()}`; +} + +it('should not show prompt when not admin', async () => { + //As anonymous + const wrapper = shallowRender(); + await waitAndUpdate(wrapper); + expect(wrapper.type()).toBeNull(); + + // As non admin user + wrapper.setProps({ currentUser: mockLoggedInUser() }); + await waitAndUpdate(wrapper); + expect(wrapper.type()).toBeNull(); +}); + +it('should not show prompt when no current version', async () => { + const wrapper = shallowRender({ appState: mockAppState({ version: 'NOVERSION' }) }); + await waitAndUpdate(wrapper); + expect(wrapper.type()).toBeNull(); +}); + +it('should not show prompt when no upgrade', async () => { + (getSystemUpgrades as jest.Mock).mockResolvedValueOnce({ upgrades: [], latestLTS: '8.9' }); + const wrapper = shallowRender({ + appState: mockAppState({ version: '9.1' }), + currentUser: mockLoggedInUser({ permissions: { global: [Permissions.Admin] } }) + }); + await waitAndUpdate(wrapper); + expect(wrapper.type()).toBeNull(); +}); + +it('should show prompt when no lts date', async () => { + (getSystemUpgrades as jest.Mock).mockResolvedValueOnce({ + upgrades: [mockUpgrades({ version: '8.9', releaseDate: 'INVALID' })], + latestLTS: '8.9' + }); + const wrapper = shallowRender({ + appState: mockAppState({ version: '8.1' }), + currentUser: mockLoggedInUser({ permissions: { global: [Permissions.Admin] } }) + }); + await waitAndUpdate(wrapper); + expect(wrapper.find(DismissableAlert).props().alertKey).toBe('previous_lts8.9'); + expect(wrapper.contains('admin_notification.update.previous_lts')).toBe(true); +}); + +it('should show prompt when minor upgrade', async () => { + (getSystemUpgrades as jest.Mock).mockResolvedValueOnce({ + upgrades: [mockUpgrades({ version: '9.2' }), mockUpgrades({ version: '9.1' })], + latestLTS: '8.9' + }); + const wrapper = shallowRender({ + appState: mockAppState({ version: '9.1' }), + currentUser: mockLoggedInUser({ permissions: { global: [Permissions.Admin] } }) + }); + await waitAndUpdate(wrapper); + expect(wrapper.find(DismissableAlert).props().alertKey).toBe('new_minor_version9.2'); + expect(wrapper.contains('admin_notification.update.new_minor_version')).toBe(true); +}); + +it('should show prompt when patch upgrade', async () => { + (getSystemUpgrades as jest.Mock).mockResolvedValueOnce({ + upgrades: [mockUpgrades({ version: '9.2' }), mockUpgrades({ version: '9.1.1' })], + latestLTS: '8.9' + }); + const wrapper = shallowRender({ + appState: mockAppState({ version: '9.1' }), + currentUser: mockLoggedInUser({ permissions: { global: [Permissions.Admin] } }) + }); + await waitAndUpdate(wrapper); + expect(wrapper.find(DismissableAlert).props().alertKey).toBe('new_patch9.2'); + expect(wrapper.contains('admin_notification.update.new_patch')).toBe(true); +}); + +it('should show prompt when lts upgrade', async () => { + (getSystemUpgrades as jest.Mock).mockResolvedValueOnce({ + upgrades: [ + mockUpgrades({ version: '8.9', releaseDate: formatDate(new Date(Date.now())) }), + mockUpgrades({ version: '9.2' }), + mockUpgrades({ version: '9.1.1' }) + ], + latestLTS: '8.9' + }); + const wrapper = shallowRender({ + appState: mockAppState({ version: '8.8' }), + currentUser: mockLoggedInUser({ permissions: { global: [Permissions.Admin] } }) + }); + await waitAndUpdate(wrapper); + expect(wrapper.find(DismissableAlert).props().alertKey).toBe('pre_lts8.9'); + expect(wrapper.contains('admin_notification.update.pre_lts')).toBe(true); +}); + +it('should show prompt when lts upgrade is more than 6 month', async () => { + const ltsDate = new Date(Date.now()); + ltsDate.setMonth(ltsDate.getMonth() - 7); + (getSystemUpgrades as jest.Mock).mockResolvedValueOnce({ + upgrades: [ + mockUpgrades({ version: '8.9', releaseDate: formatDate(ltsDate) }), + mockUpgrades({ version: '9.2' }), + mockUpgrades({ version: '9.1.1' }) + ], + latestLTS: '8.9' + }); + const wrapper = shallowRender({ + appState: mockAppState({ version: '8.8' }), + currentUser: mockLoggedInUser({ permissions: { global: [Permissions.Admin] } }) + }); + await waitAndUpdate(wrapper); + expect(wrapper.find(DismissableAlert).props().alertKey).toBe('previous_lts8.9'); + expect(wrapper.contains('admin_notification.update.previous_lts')).toBe(true); +}); + +function shallowRender(props: Partial = {}) { + return shallow( + + ); +} diff --git a/server/sonar-web/src/main/js/apps/system/__tests__/utils-test.ts b/server/sonar-web/src/main/js/apps/system/__tests__/utils-test.ts index 331497b902d..3194894a6ab 100644 --- a/server/sonar-web/src/main/js/apps/system/__tests__/utils-test.ts +++ b/server/sonar-web/src/main/js/apps/system/__tests__/utils-test.ts @@ -19,7 +19,6 @@ */ import { mockClusterSysInfo, mockStandaloneSysInfo } from '../../../helpers/testMocks'; -import { SystemUpgrade } from '../../../types/system'; import * as u from '../utils'; describe('parseQuery', () => { @@ -73,54 +72,6 @@ describe('getSystemLogsLevel', () => { }); }); -describe('sortUpgrades', () => { - it('should sort correctly versions', () => { - expect( - u.sortUpgrades([ - { version: '5.4.2' }, - { version: '5.10' }, - { version: '5.1' }, - { version: '5.4' } - ] as SystemUpgrade[]) - ).toEqual([{ version: '5.10' }, { version: '5.4.2' }, { version: '5.4' }, { version: '5.1' }]); - expect( - u.sortUpgrades([ - { version: '5.10' }, - { version: '5.1.2' }, - { version: '6.0' }, - { version: '6.9' } - ] as SystemUpgrade[]) - ).toEqual([{ version: '6.9' }, { version: '6.0' }, { version: '5.10' }, { version: '5.1.2' }]); - }); -}); - -describe('groupUpgrades', () => { - it('should group correctly', () => { - expect( - u.groupUpgrades([ - { version: '5.10' }, - { version: '5.4.2' }, - { version: '5.4' }, - { version: '5.1' } - ] as SystemUpgrade[]) - ).toEqual([ - [{ version: '5.10' }, { version: '5.4.2' }, { version: '5.4' }, { version: '5.1' }] - ]); - expect( - u.groupUpgrades([ - { version: '6.9' }, - { version: '6.7' }, - { version: '6.0' }, - { version: '5.10' }, - { version: '5.4.2' } - ] as SystemUpgrade[]) - ).toEqual([ - [{ version: '6.9' }, { version: '6.7' }, { version: '6.0' }], - [{ version: '5.10' }, { version: '5.4.2' }] - ]); - }); -}); - describe('isCluster', () => { it('should return the correct information', () => { expect(u.isCluster(mockClusterSysInfo())).toBe(true); diff --git a/server/sonar-web/src/main/js/apps/system/components/system-upgrade/SystemUpgradeNotif.tsx b/server/sonar-web/src/main/js/apps/system/components/system-upgrade/SystemUpgradeNotif.tsx index 5655488d6d8..e6f7c4c0903 100644 --- a/server/sonar-web/src/main/js/apps/system/components/system-upgrade/SystemUpgradeNotif.tsx +++ b/server/sonar-web/src/main/js/apps/system/components/system-upgrade/SystemUpgradeNotif.tsx @@ -19,21 +19,18 @@ */ import * as React from 'react'; import { getSystemUpgrades } from '../../../../api/system'; -import { Button } from '../../../../components/controls/buttons'; import { Alert } from '../../../../components/ui/Alert'; +import SystemUpgradeButton from '../../../../components/upgrade/SystemUpgradeButton'; import { translate } from '../../../../helpers/l10n'; import { SystemUpgrade } from '../../../../types/system'; -import { groupUpgrades, sortUpgrades } from '../../utils'; -import SystemUpgradeForm from './SystemUpgradeForm'; interface State { - systemUpgrades: SystemUpgrade[][]; - openSystemUpgradeForm: boolean; + systemUpgrades: SystemUpgrade[]; } export default class SystemUpgradeNotif extends React.PureComponent<{}, State> { mounted = false; - state: State = { openSystemUpgradeForm: false, systemUpgrades: [] }; + state: State = { systemUpgrades: [] }; componentDidMount() { this.mounted = true; @@ -48,20 +45,12 @@ export default class SystemUpgradeNotif extends React.PureComponent<{}, State> { getSystemUpgrades().then( ({ upgrades }) => { if (this.mounted) { - this.setState({ systemUpgrades: groupUpgrades(sortUpgrades(upgrades)) }); + this.setState({ systemUpgrades: upgrades }); } }, () => {} ); - handleOpenSystemUpgradeForm = () => { - this.setState({ openSystemUpgradeForm: true }); - }; - - handleCloseSystemUpgradeForm = () => { - this.setState({ openSystemUpgradeForm: false }); - }; - render() { const { systemUpgrades } = this.state; @@ -73,16 +62,8 @@ export default class SystemUpgradeNotif extends React.PureComponent<{}, State> {
{translate('system.new_version_available')} - + - {this.state.openSystemUpgradeForm && ( - - )}
); } diff --git a/server/sonar-web/src/main/js/apps/system/components/system-upgrade/__tests__/SystemUpgradeNotif-test.tsx b/server/sonar-web/src/main/js/apps/system/components/system-upgrade/__tests__/SystemUpgradeNotif-test.tsx index 7ad1e031157..61673af52a2 100644 --- a/server/sonar-web/src/main/js/apps/system/components/system-upgrade/__tests__/SystemUpgradeNotif-test.tsx +++ b/server/sonar-web/src/main/js/apps/system/components/system-upgrade/__tests__/SystemUpgradeNotif-test.tsx @@ -20,7 +20,7 @@ import { shallow } from 'enzyme'; import * as React from 'react'; import { getSystemUpgrades } from '../../../../../api/system'; -import { click, waitAndUpdate } from '../../../../../helpers/testUtils'; +import { waitAndUpdate } from '../../../../../helpers/testUtils'; import SystemUpgradeNotif from '../SystemUpgradeNotif'; jest.mock('../../../../../api/system', () => ({ @@ -83,9 +83,6 @@ it('should render correctly', async () => { expect(getSystemUpgrades).toHaveBeenCalled(); expect(wrapper).toMatchSnapshot(); - - click(wrapper.find('Button')); - expect(wrapper).toMatchSnapshot(); }); it('should display nothing', async () => { diff --git a/server/sonar-web/src/main/js/apps/system/components/system-upgrade/__tests__/__snapshots__/SystemUpgradeNotif-test.tsx.snap b/server/sonar-web/src/main/js/apps/system/components/system-upgrade/__tests__/__snapshots__/SystemUpgradeNotif-test.tsx.snap index 88e3fa2d032..3e3d05a2f7b 100644 --- a/server/sonar-web/src/main/js/apps/system/components/system-upgrade/__tests__/__snapshots__/SystemUpgradeNotif-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/system/components/system-upgrade/__tests__/__snapshots__/SystemUpgradeNotif-test.tsx.snap @@ -8,61 +8,32 @@ exports[`should render correctly 1`] = ` variant="info" > system.new_version_available - - - -`; - -exports[`should render correctly 2`] = ` -
- - system.new_version_available - - - + ] + } + /> +
`; diff --git a/server/sonar-web/src/main/js/apps/system/utils.ts b/server/sonar-web/src/main/js/apps/system/utils.ts index 4a4c7f28242..a9857503449 100644 --- a/server/sonar-web/src/main/js/apps/system/utils.ts +++ b/server/sonar-web/src/main/js/apps/system/utils.ts @@ -17,10 +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 { each, groupBy, memoize, omit, omitBy, pickBy, sortBy } from 'lodash'; +import { each, memoize, omit, omitBy, pickBy, sortBy } from 'lodash'; import { formatMeasure } from '../../helpers/measures'; import { cleanQuery, parseAsArray, parseAsString, serializeStringArray } from '../../helpers/query'; -import { SystemUpgrade } from '../../types/system'; export interface Query { expandedCards: string[]; @@ -229,17 +228,3 @@ export const serializeQuery = memoize( expand: serializeStringArray(query.expandedCards) }) ); - -export function sortUpgrades(upgrades: SystemUpgrade[]): SystemUpgrade[] { - return sortBy(upgrades, [ - (upgrade: SystemUpgrade) => -Number(upgrade.version.split('.')[0]), - (upgrade: SystemUpgrade) => -Number(upgrade.version.split('.')[1] || 0), - (upgrade: SystemUpgrade) => -Number(upgrade.version.split('.')[2] || 0) - ]); -} - -export function groupUpgrades(upgrades: SystemUpgrade[]): SystemUpgrade[][] { - const groupedVersions = groupBy(upgrades, upgrade => upgrade.version.split('.')[0]); - const sortedMajor = sortBy(Object.keys(groupedVersions), key => -Number(key)); - return sortedMajor.map(key => groupedVersions[key]); -} diff --git a/server/sonar-web/src/main/js/components/ui/Alert.tsx b/server/sonar-web/src/main/js/components/ui/Alert.tsx index 0c7f2435632..5ed07a1d665 100644 --- a/server/sonar-web/src/main/js/components/ui/Alert.tsx +++ b/server/sonar-web/src/main/js/components/ui/Alert.tsx @@ -30,7 +30,7 @@ import InfoIcon from '../icons/InfoIcon'; import DeferredSpinner from './DeferredSpinner'; type AlertDisplay = 'banner' | 'inline' | 'block'; -type AlertVariant = 'error' | 'warning' | 'success' | 'info' | 'loading'; +export type AlertVariant = 'error' | 'warning' | 'success' | 'info' | 'loading'; export interface AlertProps { display?: AlertDisplay; diff --git a/server/sonar-web/src/main/js/components/ui/DismissableAlert.tsx b/server/sonar-web/src/main/js/components/ui/DismissableAlert.tsx index b6f5e08fb33..2d3ae9f0a30 100644 --- a/server/sonar-web/src/main/js/components/ui/DismissableAlert.tsx +++ b/server/sonar-web/src/main/js/components/ui/DismissableAlert.tsx @@ -45,25 +45,25 @@ export default function DismissableAlert(props: DismissableAlertProps) { }, [alertKey]); const hideAlert = () => { + window.dispatchEvent(new Event('resize')); save(DISMISSED_ALERT_STORAGE_KEY, 'true', alertKey); }; return !show ? null : ( - -
-
{children}
- { - hideAlert(); - setShow(false); - }}> - - -
-
+
+ +
+
{children}
+ { + hideAlert(); + setShow(false); + }}> + + +
+
+
); } diff --git a/server/sonar-web/src/main/js/components/ui/__tests__/__snapshots__/DismissableAlert-test.tsx.snap b/server/sonar-web/src/main/js/components/ui/__tests__/__snapshots__/DismissableAlert-test.tsx.snap index 7583076acd6..4c3e6bb89e7 100644 --- a/server/sonar-web/src/main/js/components/ui/__tests__/__snapshots__/DismissableAlert-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/ui/__tests__/__snapshots__/DismissableAlert-test.tsx.snap @@ -1,59 +1,67 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`should render correctly 1`] = ` - -
-
- My content +
+
+ My content +
+ + +
- - - -
- + +
`; exports[`should render correctly with a non-default display 1`] = ` - -
-
- My content +
+
+ My content +
+ + +
- - - -
- + +
`; diff --git a/server/sonar-web/src/main/js/components/upgrade/SystemUpgradeButton.tsx b/server/sonar-web/src/main/js/components/upgrade/SystemUpgradeButton.tsx new file mode 100644 index 00000000000..cd3a527ef9b --- /dev/null +++ b/server/sonar-web/src/main/js/components/upgrade/SystemUpgradeButton.tsx @@ -0,0 +1,63 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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'; +import { SystemUpgrade } from '../../types/system'; +import { Button } from '../controls/buttons'; +import SystemUpgradeForm from './SystemUpgradeForm'; +import { groupUpgrades, sortUpgrades } from './utils'; + +interface Props { + systemUpgrades: SystemUpgrade[]; +} + +interface State { + openSystemUpgradeForm: boolean; +} + +export default class SystemUpgradeButton extends React.PureComponent { + state: State = { openSystemUpgradeForm: false }; + + handleOpenSystemUpgradeForm = () => { + this.setState({ openSystemUpgradeForm: true }); + }; + + handleCloseSystemUpgradeForm = () => { + this.setState({ openSystemUpgradeForm: false }); + }; + + render() { + const { systemUpgrades } = this.props; + const { openSystemUpgradeForm } = this.state; + return ( + <> + + {openSystemUpgradeForm && ( + + )} + + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/system/components/system-upgrade/SystemUpgradeForm.tsx b/server/sonar-web/src/main/js/components/upgrade/SystemUpgradeForm.tsx similarity index 87% rename from server/sonar-web/src/main/js/apps/system/components/system-upgrade/SystemUpgradeForm.tsx rename to server/sonar-web/src/main/js/components/upgrade/SystemUpgradeForm.tsx index b4a25a71963..9a845dbc573 100644 --- a/server/sonar-web/src/main/js/apps/system/components/system-upgrade/SystemUpgradeForm.tsx +++ b/server/sonar-web/src/main/js/components/upgrade/SystemUpgradeForm.tsx @@ -18,12 +18,12 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import { ResetButtonLink } from '../../../../components/controls/buttons'; -import Modal from '../../../../components/controls/Modal'; -import { withAppState } from '../../../../components/hoc/withAppState'; -import { translate } from '../../../../helpers/l10n'; -import { EditionKey } from '../../../../types/editions'; -import { SystemUpgrade } from '../../../../types/system'; +import { translate } from '../../helpers/l10n'; +import { EditionKey } from '../../types/editions'; +import { SystemUpgrade } from '../../types/system'; +import { ResetButtonLink } from '../controls/buttons'; +import Modal from '../controls/Modal'; +import { withAppState } from '../hoc/withAppState'; import SystemUpgradeItem from './SystemUpgradeItem'; interface Props { diff --git a/server/sonar-web/src/main/js/apps/system/components/system-upgrade/SystemUpgradeIntermediate.tsx b/server/sonar-web/src/main/js/components/upgrade/SystemUpgradeIntermediate.tsx similarity index 89% rename from server/sonar-web/src/main/js/apps/system/components/system-upgrade/SystemUpgradeIntermediate.tsx rename to server/sonar-web/src/main/js/components/upgrade/SystemUpgradeIntermediate.tsx index b059fe41805..db09a7ef8ce 100644 --- a/server/sonar-web/src/main/js/apps/system/components/system-upgrade/SystemUpgradeIntermediate.tsx +++ b/server/sonar-web/src/main/js/components/upgrade/SystemUpgradeIntermediate.tsx @@ -18,11 +18,11 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import { ButtonLink } from '../../../../components/controls/buttons'; -import DropdownIcon from '../../../../components/icons/DropdownIcon'; -import DateFormatter from '../../../../components/intl/DateFormatter'; -import { translate } from '../../../../helpers/l10n'; -import { SystemUpgrade } from '../../../../types/system'; +import { translate } from '../../helpers/l10n'; +import { SystemUpgrade } from '../../types/system'; +import { ButtonLink } from '../controls/buttons'; +import DropdownIcon from '../icons/DropdownIcon'; +import DateFormatter from '../intl/DateFormatter'; interface Props { className?: string; diff --git a/server/sonar-web/src/main/js/apps/system/components/system-upgrade/SystemUpgradeItem.tsx b/server/sonar-web/src/main/js/components/upgrade/SystemUpgradeItem.tsx similarity index 92% rename from server/sonar-web/src/main/js/apps/system/components/system-upgrade/SystemUpgradeItem.tsx rename to server/sonar-web/src/main/js/components/upgrade/SystemUpgradeItem.tsx index d5c605cbc4b..756455472c6 100644 --- a/server/sonar-web/src/main/js/apps/system/components/system-upgrade/SystemUpgradeItem.tsx +++ b/server/sonar-web/src/main/js/components/upgrade/SystemUpgradeItem.tsx @@ -19,15 +19,15 @@ */ import * as React from 'react'; import { FormattedMessage } from 'react-intl'; -import DateFormatter from '../../../../components/intl/DateFormatter'; import { getEdition, getEditionDownloadFilename, getEditionDownloadUrl -} from '../../../../helpers/editions'; -import { translate, translateWithParameters } from '../../../../helpers/l10n'; -import { EditionKey } from '../../../../types/editions'; -import { SystemUpgrade } from '../../../../types/system'; +} from '../../helpers/editions'; +import { translate, translateWithParameters } from '../../helpers/l10n'; +import { EditionKey } from '../../types/editions'; +import { SystemUpgrade } from '../../types/system'; +import DateFormatter from '../intl/DateFormatter'; import SystemUpgradeIntermediate from './SystemUpgradeIntermediate'; export interface SystemUpgradeItemProps { diff --git a/server/sonar-web/src/main/js/components/upgrade/__tests__/SystemUpgradeButton-test.tsx b/server/sonar-web/src/main/js/components/upgrade/__tests__/SystemUpgradeButton-test.tsx new file mode 100644 index 00000000000..df853f7207c --- /dev/null +++ b/server/sonar-web/src/main/js/components/upgrade/__tests__/SystemUpgradeButton-test.tsx @@ -0,0 +1,38 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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 { shallow } from 'enzyme'; +import * as React from 'react'; +import { click } from '../../../helpers/testUtils'; +import { Button } from '../../controls/buttons'; +import SystemUpgradeButton from '../SystemUpgradeButton'; +import SystemUpgradeForm from '../SystemUpgradeForm'; + +it('should open modal correctly', () => { + const wrapper = shallowRender(); + expect(wrapper).toMatchSnapshot(); + click(wrapper.find(Button)); + expect(wrapper.find(SystemUpgradeForm)).toBeDefined(); +}); + +function shallowRender(props: Partial = {}) { + return shallow( + + ); +} diff --git a/server/sonar-web/src/main/js/apps/system/components/system-upgrade/__tests__/SystemUpgradeForm-test.tsx b/server/sonar-web/src/main/js/components/upgrade/__tests__/SystemUpgradeForm-test.tsx similarity index 97% rename from server/sonar-web/src/main/js/apps/system/components/system-upgrade/__tests__/SystemUpgradeForm-test.tsx rename to server/sonar-web/src/main/js/components/upgrade/__tests__/SystemUpgradeForm-test.tsx index 02bf8a136bb..7e33af49609 100644 --- a/server/sonar-web/src/main/js/apps/system/components/system-upgrade/__tests__/SystemUpgradeForm-test.tsx +++ b/server/sonar-web/src/main/js/components/upgrade/__tests__/SystemUpgradeForm-test.tsx @@ -19,7 +19,7 @@ */ import { shallow } from 'enzyme'; import * as React from 'react'; -import { EditionKey } from '../../../../../types/editions'; +import { EditionKey } from '../../../types/editions'; import { SystemUpgradeForm } from '../SystemUpgradeForm'; const UPGRADES = [ diff --git a/server/sonar-web/src/main/js/apps/system/components/system-upgrade/__tests__/SystemUpgradeIntermediate-test.tsx b/server/sonar-web/src/main/js/components/upgrade/__tests__/SystemUpgradeIntermediate-test.tsx similarity index 97% rename from server/sonar-web/src/main/js/apps/system/components/system-upgrade/__tests__/SystemUpgradeIntermediate-test.tsx rename to server/sonar-web/src/main/js/components/upgrade/__tests__/SystemUpgradeIntermediate-test.tsx index c2d05b1853f..23d79ea71d5 100644 --- a/server/sonar-web/src/main/js/apps/system/components/system-upgrade/__tests__/SystemUpgradeIntermediate-test.tsx +++ b/server/sonar-web/src/main/js/components/upgrade/__tests__/SystemUpgradeIntermediate-test.tsx @@ -19,7 +19,7 @@ */ import { shallow } from 'enzyme'; import * as React from 'react'; -import { click } from '../../../../../helpers/testUtils'; +import { click } from '../../../helpers/testUtils'; import SystemUpgradeIntermediate from '../SystemUpgradeIntermediate'; const UPGRADES = [ diff --git a/server/sonar-web/src/main/js/apps/system/components/system-upgrade/__tests__/SystemUpgradeItem-test.tsx b/server/sonar-web/src/main/js/components/upgrade/__tests__/SystemUpgradeItem-test.tsx similarity index 98% rename from server/sonar-web/src/main/js/apps/system/components/system-upgrade/__tests__/SystemUpgradeItem-test.tsx rename to server/sonar-web/src/main/js/components/upgrade/__tests__/SystemUpgradeItem-test.tsx index 6b775f034df..ba705ab5238 100644 --- a/server/sonar-web/src/main/js/apps/system/components/system-upgrade/__tests__/SystemUpgradeItem-test.tsx +++ b/server/sonar-web/src/main/js/components/upgrade/__tests__/SystemUpgradeItem-test.tsx @@ -19,7 +19,7 @@ */ import { shallow } from 'enzyme'; import * as React from 'react'; -import { EditionKey } from '../../../../../types/editions'; +import { EditionKey } from '../../../types/editions'; import SystemUpgradeItem, { SystemUpgradeItemProps } from '../SystemUpgradeItem'; it('should display correctly', () => { diff --git a/server/sonar-web/src/main/js/components/upgrade/__tests__/__snapshots__/SystemUpgradeButton-test.tsx.snap b/server/sonar-web/src/main/js/components/upgrade/__tests__/__snapshots__/SystemUpgradeButton-test.tsx.snap new file mode 100644 index 00000000000..037b14bb219 --- /dev/null +++ b/server/sonar-web/src/main/js/components/upgrade/__tests__/__snapshots__/SystemUpgradeButton-test.tsx.snap @@ -0,0 +1,12 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should open modal correctly 1`] = ` + + + +`; diff --git a/server/sonar-web/src/main/js/apps/system/components/system-upgrade/__tests__/__snapshots__/SystemUpgradeForm-test.tsx.snap b/server/sonar-web/src/main/js/components/upgrade/__tests__/__snapshots__/SystemUpgradeForm-test.tsx.snap similarity index 100% rename from server/sonar-web/src/main/js/apps/system/components/system-upgrade/__tests__/__snapshots__/SystemUpgradeForm-test.tsx.snap rename to server/sonar-web/src/main/js/components/upgrade/__tests__/__snapshots__/SystemUpgradeForm-test.tsx.snap diff --git a/server/sonar-web/src/main/js/apps/system/components/system-upgrade/__tests__/__snapshots__/SystemUpgradeIntermediate-test.tsx.snap b/server/sonar-web/src/main/js/components/upgrade/__tests__/__snapshots__/SystemUpgradeIntermediate-test.tsx.snap similarity index 100% rename from server/sonar-web/src/main/js/apps/system/components/system-upgrade/__tests__/__snapshots__/SystemUpgradeIntermediate-test.tsx.snap rename to server/sonar-web/src/main/js/components/upgrade/__tests__/__snapshots__/SystemUpgradeIntermediate-test.tsx.snap diff --git a/server/sonar-web/src/main/js/apps/system/components/system-upgrade/__tests__/__snapshots__/SystemUpgradeItem-test.tsx.snap b/server/sonar-web/src/main/js/components/upgrade/__tests__/__snapshots__/SystemUpgradeItem-test.tsx.snap similarity index 100% rename from server/sonar-web/src/main/js/apps/system/components/system-upgrade/__tests__/__snapshots__/SystemUpgradeItem-test.tsx.snap rename to server/sonar-web/src/main/js/components/upgrade/__tests__/__snapshots__/SystemUpgradeItem-test.tsx.snap diff --git a/server/sonar-web/src/main/js/components/upgrade/__tests__/utils-test.ts b/server/sonar-web/src/main/js/components/upgrade/__tests__/utils-test.ts new file mode 100644 index 00000000000..34d19105097 --- /dev/null +++ b/server/sonar-web/src/main/js/components/upgrade/__tests__/utils-test.ts @@ -0,0 +1,69 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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 { SystemUpgrade } from '../../../types/system'; +import * as u from '../utils'; + +describe('sortUpgrades', () => { + it('should sort correctly versions', () => { + expect( + u.sortUpgrades([ + { version: '5.4.2' }, + { version: '5.10' }, + { version: '5.1' }, + { version: '5.4' } + ] as SystemUpgrade[]) + ).toEqual([{ version: '5.10' }, { version: '5.4.2' }, { version: '5.4' }, { version: '5.1' }]); + expect( + u.sortUpgrades([ + { version: '5.10' }, + { version: '5.1.2' }, + { version: '6.0' }, + { version: '6.9' } + ] as SystemUpgrade[]) + ).toEqual([{ version: '6.9' }, { version: '6.0' }, { version: '5.10' }, { version: '5.1.2' }]); + }); +}); + +describe('groupUpgrades', () => { + it('should group correctly', () => { + expect( + u.groupUpgrades([ + { version: '5.10' }, + { version: '5.4.2' }, + { version: '5.4' }, + { version: '5.1' } + ] as SystemUpgrade[]) + ).toEqual([ + [{ version: '5.10' }, { version: '5.4.2' }, { version: '5.4' }, { version: '5.1' }] + ]); + expect( + u.groupUpgrades([ + { version: '6.9' }, + { version: '6.7' }, + { version: '6.0' }, + { version: '5.10' }, + { version: '5.4.2' } + ] as SystemUpgrade[]) + ).toEqual([ + [{ version: '6.9' }, { version: '6.7' }, { version: '6.0' }], + [{ version: '5.10' }, { version: '5.4.2' }] + ]); + }); +}); diff --git a/server/sonar-web/src/main/js/components/upgrade/utils.ts b/server/sonar-web/src/main/js/components/upgrade/utils.ts new file mode 100644 index 00000000000..e58b76a2bc0 --- /dev/null +++ b/server/sonar-web/src/main/js/components/upgrade/utils.ts @@ -0,0 +1,35 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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 { groupBy, sortBy } from 'lodash'; +import { SystemUpgrade } from '../../types/system'; + +export function sortUpgrades(upgrades: SystemUpgrade[]): SystemUpgrade[] { + return sortBy(upgrades, [ + (upgrade: SystemUpgrade) => -Number(upgrade.version.split('.')[0]), + (upgrade: SystemUpgrade) => -Number(upgrade.version.split('.')[1] || 0), + (upgrade: SystemUpgrade) => -Number(upgrade.version.split('.')[2] || 0) + ]); +} + +export function groupUpgrades(upgrades: SystemUpgrade[]): SystemUpgrade[][] { + const groupedVersions = groupBy(upgrades, upgrade => upgrade.version.split('.')[0]); + const sortedMajor = sortBy(Object.keys(groupedVersions), key => -Number(key)); + return sortedMajor.map(key => groupedVersions[key]); +} diff --git a/server/sonar-web/src/main/js/helpers/mocks/system-upgrades.ts b/server/sonar-web/src/main/js/helpers/mocks/system-upgrades.ts new file mode 100644 index 00000000000..ba1249c30cf --- /dev/null +++ b/server/sonar-web/src/main/js/helpers/mocks/system-upgrades.ts @@ -0,0 +1,31 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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 { SystemUpgrade } from '../../types/system'; + +export function mockUpgrades(override: Partial): SystemUpgrade { + return { + version: '5.6.7', + description: 'Version 5.6.7 description', + releaseDate: '2017-03-01', + changeLogUrl: 'changelogurl', + downloadUrl: 'downloadurl', + ...override + }; +} diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index 79260f4b39a..6d422fa42e7 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -451,6 +451,17 @@ qualifier.description.VW=Potentially multi-level, management-oriented overview a qualifier.description.SVW=Potentially multi-level, management-oriented overview aggregation. qualifier.description.APP=Single-level aggregation with a technical focus and a project-like homepage. + +#------------------------------------------------------------------------------ +# +# Admin notification +# +#------------------------------------------------------------------------------ +admin_notification.update.new_minor_version=There’s a new version of SonarQube available. Update to enjoy the latest updates and features. +admin_notification.update.new_patch=There’s an update available for your SonarQube instance. Please update to make sure you benefit from the latest security and bug fixes. +admin_notification.update.pre_lts=You’re running a version of SonarQube that has reached end of life. Please upgrade to a supported version at your earliest convenience. +admin_notification.update.previous_lts=You’re running a version of SonarQube that is past end of life. Please upgrade to a supported version immediately. + #------------------------------------------------------------------------------ # # PROJECT LINKS -- 2.39.5