diff options
author | David Cho-Lerat <david.cho-lerat@sonarsource.com> | 2024-11-27 09:11:58 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2024-11-29 20:03:09 +0000 |
commit | 5f497026206e3f7b47389d1bac0abc89a1d2c6ca (patch) | |
tree | 4a8bb7898aa027a0f3873719890b51d0d6857500 /server | |
parent | 692dcd4fe7c59fed8d379d1aec50430fac4139e5 (diff) | |
download | sonarqube-5f497026206e3f7b47389d1bac0abc89a1d2c6ca.tar.gz sonarqube-5f497026206e3f7b47389d1bac0abc89a1d2c6ca.zip |
SONAR-23803 show upgrade banner(s) and modals for SQCB / SQS
Diffstat (limited to 'server')
18 files changed, 978 insertions, 528 deletions
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 bb12f43c094..ef1e27b7946 100644 --- a/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx +++ b/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx @@ -38,7 +38,7 @@ import GlobalNav from './nav/global/GlobalNav'; import PromotionNotification from './promotion-notification/PromotionNotification'; import StartupModal from './StartupModal'; import SystemAnnouncement from './SystemAnnouncement'; -import UpdateNotification from './update-notification/UpdateNotification'; +import { UpdateNotification } from './update-notification/UpdateNotification'; /* * These pages need a white background (aka 'secondary', rather than the default 'primary') diff --git a/server/sonar-web/src/main/js/app/components/__tests__/UpdateNotification-it.tsx b/server/sonar-web/src/main/js/app/components/__tests__/UpdateNotification-it.tsx index 9082fd8791f..6ef0cf9ece1 100644 --- a/server/sonar-web/src/main/js/app/components/__tests__/UpdateNotification-it.tsx +++ b/server/sonar-web/src/main/js/app/components/__tests__/UpdateNotification-it.tsx @@ -18,6 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { addDays, formatISO, subDays } from 'date-fns'; import { byRole } from '~sonar-aligned/helpers/testSelector'; @@ -26,445 +27,587 @@ import { UpdateUseCase } from '../../../components/upgrade/utils'; import { mockAppState, mockCurrentUser } from '../../../helpers/testMocks'; import { renderComponent } from '../../../helpers/testReactTestingUtils'; import { AppState } from '../../../types/appstate'; +import { EditionKey } from '../../../types/editions'; import { Permissions } from '../../../types/permissions'; +import { ProductNameForUpgrade } from '../../../types/system'; import { CurrentUser } from '../../../types/users'; import { AppStateContext } from '../app-state/AppStateContext'; import { CurrentUserContext } from '../current-user/CurrentUserContext'; -import UpdateNotification from '../update-notification/UpdateNotification'; +import { UpdateNotification } from '../update-notification/UpdateNotification'; jest.mock('../../../api/system', () => ({ getSystemUpgrades: jest.fn(), })); const ui = { - updateMessage: byRole('alert'), - openDialogBtn: byRole('button', { name: 'learn_more' }), - learnMoreLink: byRole('link', { name: /learn_more/ }), + closeDialogBtn: byRole('button', { name: 'cancel' }), dialog: byRole('dialog', { name: 'system.system_upgrade' }), + dialogErrorMessage: byRole('dialog').byText('admin_notification.update.current_version_inactive'), + dialogWarningMessage: byRole('dialog').byText('admin_notification.update.new_patch'), + hideIntermediateBtn: byRole('button', { name: 'system.hide_intermediate_versions' }), + intermediateRegion: byRole('region', { name: 'system.hide_intermediate_versions' }), latestHeader: byRole('heading', { name: /system.latest_version/ }), latestLTAHeader: byRole('heading', { name: /system.lta_version/ }), + learnMoreLink: byRole('link', { name: /learn_more/ }), + openDialogBtn: byRole('button', { name: 'learn_more' }), patchHeader: byRole('heading', { name: /system.latest_patch/ }), showIntermediateBtn: byRole('button', { name: 'system.show_intermediate_versions' }), - hideIntermediateBtn: byRole('button', { name: 'system.hide_intermediate_versions' }), - intermediateRegion: byRole('region', { name: 'system.hide_intermediate_versions' }), - dialogWarningMessage: byRole('dialog').byText('admin_notification.update.new_patch'), - dialogErrorMessage: byRole('dialog').byText('admin_notification.update.current_version_inactive'), + updateMessage: byRole('alert'), }; -it('should not render update notification if user is not logged in', () => { - renderUpdateNotification(undefined, { isLoggedIn: false }); - expect(getSystemUpgrades).not.toHaveBeenCalled(); - expect(ui.updateMessage.query()).not.toBeInTheDocument(); -}); +const SQCBUpgrade = { downloadUrl: '', product: ProductNameForUpgrade.SonarQubeCommunityBuild }; +const SQSUpgrade = { downloadUrl: '', product: ProductNameForUpgrade.SonarQubeServer }; -it('should not render update notification if user is not admin', () => { - renderUpdateNotification(undefined, { permissions: { global: [] } }); - expect(getSystemUpgrades).not.toHaveBeenCalled(); - expect(ui.updateMessage.query()).not.toBeInTheDocument(); -}); +describe('when running SQS', () => { + it('should not render update notification if user is not logged in', () => { + renderUpdateNotification(undefined, { isLoggedIn: false }); + expect(getSystemUpgrades).not.toHaveBeenCalled(); + expect(ui.updateMessage.query()).not.toBeInTheDocument(); + }); -it('should not render update notification if no upgrades', () => { - jest.mocked(getSystemUpgrades).mockResolvedValue({ - upgrades: [], - latestLTA: '9.9', - updateCenterRefresh: '', - installedVersionActive: true, + it('should not render update notification if user is not admin', () => { + renderUpdateNotification(undefined, { permissions: { global: [] } }); + expect(getSystemUpgrades).not.toHaveBeenCalled(); + expect(ui.updateMessage.query()).not.toBeInTheDocument(); }); - renderUpdateNotification(); - expect(getSystemUpgrades).toHaveBeenCalled(); - expect(ui.updateMessage.query()).not.toBeInTheDocument(); -}); -it('should show error message if upgrades call failed and the version has reached eol', async () => { - jest.mocked(getSystemUpgrades).mockReturnValue(Promise.reject(new Error('error'))); - renderUpdateNotification(undefined, undefined, { - versionEOL: formatISO(subDays(new Date(), 1), { representation: 'date' }), + it('should not render update notification if no upgrades', () => { + jest.mocked(getSystemUpgrades).mockResolvedValue({ + upgrades: [], + latestLTA: '9.9', + updateCenterRefresh: '', + installedVersionActive: true, + }); + renderUpdateNotification(); + expect(getSystemUpgrades).toHaveBeenCalled(); + expect(ui.updateMessage.query()).not.toBeInTheDocument(); }); - expect(await ui.updateMessage.find()).toHaveTextContent( - `admin_notification.update.${UpdateUseCase.CurrentVersionInactive}`, - ); - expect(ui.openDialogBtn.query()).not.toBeInTheDocument(); - expect(ui.learnMoreLink.get()).toBeInTheDocument(); -}); -it('should not show the notification banner if there is no network connection and version has not reached the eol', () => { - jest.mocked(getSystemUpgrades).mockResolvedValue({ - upgrades: [], + it('should show error message if upgrades call failed and the version has reached eol', async () => { + jest.mocked(getSystemUpgrades).mockReturnValue(Promise.reject(new Error('error'))); + renderUpdateNotification(undefined, undefined, { + versionEOL: formatISO(subDays(new Date(), 1), { representation: 'date' }), + }); + expect(await ui.updateMessage.find()).toHaveTextContent( + `admin_notification.update.${UpdateUseCase.CurrentVersionInactive}`, + ); + expect(ui.openDialogBtn.query()).not.toBeInTheDocument(); + expect(ui.learnMoreLink.get()).toBeInTheDocument(); }); - renderUpdateNotification(undefined, undefined, { - versionEOL: formatISO(addDays(new Date(), 1), { representation: 'date' }), + + it('should not show the notification banner if there is no network connection and version has not reached the eol', () => { + jest.mocked(getSystemUpgrades).mockResolvedValue({ + upgrades: [], + }); + renderUpdateNotification(undefined, undefined, { + versionEOL: formatISO(addDays(new Date(), 1), { representation: 'date' }), + }); + expect(ui.updateMessage.query()).not.toBeInTheDocument(); }); - expect(ui.updateMessage.query()).not.toBeInTheDocument(); -}); -it('should show the error banner if there is no network connection and version has reached the eol', async () => { - jest.mocked(getSystemUpgrades).mockResolvedValue({ - upgrades: [], + it('should show the error banner if there is no network connection and version has reached the eol', async () => { + jest.mocked(getSystemUpgrades).mockResolvedValue({ + upgrades: [], + }); + renderUpdateNotification(undefined, undefined, { + versionEOL: formatISO(subDays(new Date(), 1), { representation: 'date' }), + }); + expect(await ui.updateMessage.find()).toHaveTextContent( + `admin_notification.update.${UpdateUseCase.CurrentVersionInactive}`, + ); + expect(ui.openDialogBtn.query()).not.toBeInTheDocument(); + expect(ui.learnMoreLink.get()).toBeInTheDocument(); }); - renderUpdateNotification(undefined, undefined, { - versionEOL: formatISO(subDays(new Date(), 1), { representation: 'date' }), + + it('active / latest / patch', async () => { + jest.mocked(getSystemUpgrades).mockResolvedValue({ + upgrades: [{ ...SQSUpgrade, version: '10.5.1' }], + latestLTA: '9.9', + updateCenterRefresh: '', + installedVersionActive: true, + }); + const user = userEvent.setup(); + renderUpdateNotification(); + expect(await ui.updateMessage.find()).toHaveTextContent( + `admin_notification.update.${UpdateUseCase.NewPatch}`, + ); + await user.click(ui.openDialogBtn.get()); + expect(ui.dialogWarningMessage.get()).toBeInTheDocument(); + expect(ui.dialog.get()).toHaveTextContent('10.5.1'); + expect(ui.patchHeader.get()).toBeInTheDocument(); + expect(ui.showIntermediateBtn.query()).not.toBeInTheDocument(); }); - expect(await ui.updateMessage.find()).toHaveTextContent( - `admin_notification.update.${UpdateUseCase.CurrentVersionInactive}`, - ); - expect(ui.openDialogBtn.query()).not.toBeInTheDocument(); - expect(ui.learnMoreLink.get()).toBeInTheDocument(); -}); -it('active / latest / patch', async () => { - jest.mocked(getSystemUpgrades).mockResolvedValue({ - upgrades: [{ downloadUrl: '', version: '10.5.1' }], - latestLTA: '9.9', - updateCenterRefresh: '', - installedVersionActive: true, + it('active / latest / several patches', async () => { + jest.mocked(getSystemUpgrades).mockResolvedValue({ + upgrades: [ + { ...SQSUpgrade, version: '10.5.2', releaseDate: '2021-08-02' }, + { ...SQSUpgrade, version: '10.5.1', releaseDate: '2021-08-01' }, + ], + latestLTA: '9.9', + updateCenterRefresh: '', + installedVersionActive: true, + }); + const user = userEvent.setup(); + renderUpdateNotification(); + expect(await ui.updateMessage.find()).toHaveTextContent( + `admin_notification.update.${UpdateUseCase.NewPatch}`, + ); + await user.click(ui.openDialogBtn.get()); + expect(ui.dialogWarningMessage.get()).toBeInTheDocument(); + expect(ui.dialog.get()).toHaveTextContent('10.5.2'); + expect(ui.dialog.get()).not.toHaveTextContent('10.5.1'); + expect(ui.patchHeader.get()).toBeInTheDocument(); + expect(ui.showIntermediateBtn.get()).toBeInTheDocument(); + await user.click(ui.showIntermediateBtn.get()); + expect(ui.showIntermediateBtn.query()).not.toBeInTheDocument(); + expect(ui.hideIntermediateBtn.get()).toBeInTheDocument(); + expect(ui.intermediateRegion.get()).toHaveTextContent('10.5.1August 1, 2021'); }); - const user = userEvent.setup(); - renderUpdateNotification(); - expect(await ui.updateMessage.find()).toHaveTextContent( - `admin_notification.update.${UpdateUseCase.NewPatch}`, - ); - await user.click(ui.openDialogBtn.get()); - expect(ui.dialogWarningMessage.get()).toBeInTheDocument(); - expect(ui.dialog.get()).toHaveTextContent('10.5.1'); - expect(ui.patchHeader.get()).toBeInTheDocument(); - expect(ui.showIntermediateBtn.query()).not.toBeInTheDocument(); -}); -it('active / latest / several patches', async () => { - jest.mocked(getSystemUpgrades).mockResolvedValue({ - upgrades: [ - { downloadUrl: '', version: '10.5.2', releaseDate: '2021-08-02' }, - { downloadUrl: '', version: '10.5.1', releaseDate: '2021-08-01' }, - ], - latestLTA: '9.9', - updateCenterRefresh: '', - installedVersionActive: true, + it('active / latest / new minor', async () => { + jest.mocked(getSystemUpgrades).mockResolvedValue({ + upgrades: [{ ...SQSUpgrade, version: '10.6.0' }], + latestLTA: '9.9', + updateCenterRefresh: '', + installedVersionActive: true, + }); + const user = userEvent.setup(); + renderUpdateNotification(); + expect(await ui.updateMessage.find()).toHaveTextContent( + `admin_notification.update.${UpdateUseCase.NewVersion}`, + ); + await user.click(ui.openDialogBtn.get()); + expect(ui.latestHeader.get()).toBeInTheDocument(); + expect(ui.showIntermediateBtn.query()).not.toBeInTheDocument(); }); - const user = userEvent.setup(); - renderUpdateNotification(); - expect(await ui.updateMessage.find()).toHaveTextContent( - `admin_notification.update.${UpdateUseCase.NewPatch}`, - ); - await user.click(ui.openDialogBtn.get()); - expect(ui.dialogWarningMessage.get()).toBeInTheDocument(); - expect(ui.dialog.get()).toHaveTextContent('10.5.2'); - expect(ui.dialog.get()).not.toHaveTextContent('10.5.1'); - expect(ui.patchHeader.get()).toBeInTheDocument(); - expect(ui.showIntermediateBtn.get()).toBeInTheDocument(); - await user.click(ui.showIntermediateBtn.get()); - expect(ui.showIntermediateBtn.query()).not.toBeInTheDocument(); - expect(ui.hideIntermediateBtn.get()).toBeInTheDocument(); - expect(ui.intermediateRegion.get()).toHaveTextContent('10.5.1August 1, 2021'); -}); -it('active / latest / new minor', async () => { - jest.mocked(getSystemUpgrades).mockResolvedValue({ - upgrades: [{ downloadUrl: '', version: '10.6.0' }], - latestLTA: '9.9', - updateCenterRefresh: '', - installedVersionActive: true, + it('active / latest / new minor + patch', async () => { + jest.mocked(getSystemUpgrades).mockResolvedValue({ + upgrades: [ + { ...SQSUpgrade, version: '10.5.1', releaseDate: '2021-08-01' }, + { ...SQSUpgrade, version: '10.6.0', releaseDate: '2021-08-02' }, + ], + latestLTA: '9.9', + updateCenterRefresh: '', + installedVersionActive: true, + }); + const user = userEvent.setup(); + renderUpdateNotification(); + expect(await ui.updateMessage.find()).toHaveTextContent( + `admin_notification.update.${UpdateUseCase.NewVersion}`, + ); + await user.click(ui.openDialogBtn.get()); + expect(ui.dialogWarningMessage.query()).not.toBeInTheDocument(); + expect(ui.dialog.get()).toHaveTextContent('10.6.0'); + expect(ui.dialog.get()).not.toHaveTextContent('10.5.1'); + expect(ui.latestHeader.get()).toBeInTheDocument(); + expect(ui.patchHeader.query()).not.toBeInTheDocument(); + expect(ui.showIntermediateBtn.get()).toBeInTheDocument(); + await user.click(ui.showIntermediateBtn.get()); + expect(ui.intermediateRegion.get()).toHaveTextContent('10.5.1August 1, 2021'); }); - const user = userEvent.setup(); - renderUpdateNotification(); - expect(await ui.updateMessage.find()).toHaveTextContent( - `admin_notification.update.${UpdateUseCase.NewVersion}`, - ); - await user.click(ui.openDialogBtn.get()); - expect(ui.latestHeader.get()).toBeInTheDocument(); - expect(ui.showIntermediateBtn.query()).not.toBeInTheDocument(); -}); -it('active / latest / new minor + patch', async () => { - jest.mocked(getSystemUpgrades).mockResolvedValue({ - upgrades: [ - { downloadUrl: '', version: '10.5.1', releaseDate: '2021-08-01' }, - { downloadUrl: '', version: '10.6.0', releaseDate: '2021-08-02' }, - ], - latestLTA: '9.9', - updateCenterRefresh: '', - installedVersionActive: true, + it('no longer active / latest / new minor', async () => { + jest.mocked(getSystemUpgrades).mockResolvedValue({ + upgrades: [ + { ...SQSUpgrade, version: '10.6.0', releaseDate: '2021-08-02' }, + { ...SQSUpgrade, version: '10.7.0', releaseDate: '2021-08-03' }, + ], + latestLTA: '9.9', + updateCenterRefresh: '', + installedVersionActive: false, + }); + const user = userEvent.setup(); + renderUpdateNotification(); + expect(await ui.updateMessage.find()).toHaveTextContent( + `admin_notification.update.${UpdateUseCase.CurrentVersionInactive}`, + ); + await user.click(ui.openDialogBtn.get()); + expect(ui.dialogErrorMessage.get()).toBeInTheDocument(); + expect(ui.dialog.get()).toHaveTextContent('10.7.0'); + expect(ui.dialog.get()).not.toHaveTextContent('10.6.0'); + expect(ui.latestHeader.get()).toBeInTheDocument(); + expect(ui.showIntermediateBtn.get()).toBeInTheDocument(); + await user.click(ui.showIntermediateBtn.get()); + expect(ui.intermediateRegion.get()).toHaveTextContent('10.6.0August 2, 2021'); }); - const user = userEvent.setup(); - renderUpdateNotification(); - expect(await ui.updateMessage.find()).toHaveTextContent( - `admin_notification.update.${UpdateUseCase.NewVersion}`, - ); - await user.click(ui.openDialogBtn.get()); - expect(ui.dialogWarningMessage.query()).not.toBeInTheDocument(); - expect(ui.dialog.get()).toHaveTextContent('10.6.0'); - expect(ui.dialog.get()).not.toHaveTextContent('10.5.1'); - expect(ui.latestHeader.get()).toBeInTheDocument(); - expect(ui.patchHeader.query()).not.toBeInTheDocument(); - expect(ui.showIntermediateBtn.get()).toBeInTheDocument(); - await user.click(ui.showIntermediateBtn.get()); - expect(ui.intermediateRegion.get()).toHaveTextContent('10.5.1August 1, 2021'); -}); -it('no longer active / latest / new minor', async () => { - jest.mocked(getSystemUpgrades).mockResolvedValue({ - upgrades: [ - { downloadUrl: '', version: '10.6.0', releaseDate: '2021-08-02' }, - { downloadUrl: '', version: '10.7.0', releaseDate: '2021-08-03' }, - ], - latestLTA: '9.9', - updateCenterRefresh: '', - installedVersionActive: false, + it('no longer active / latest / new minor + patch', async () => { + jest.mocked(getSystemUpgrades).mockResolvedValue({ + upgrades: [ + { ...SQSUpgrade, version: '10.5.1', releaseDate: '2021-08-01' }, + { ...SQSUpgrade, version: '10.6.0', releaseDate: '2021-08-02' }, + { ...SQSUpgrade, version: '10.7.0', releaseDate: '2021-08-03' }, + ], + latestLTA: '9.9', + updateCenterRefresh: '', + installedVersionActive: false, + }); + const user = userEvent.setup(); + renderUpdateNotification(); + expect(await ui.updateMessage.find()).toHaveTextContent( + `admin_notification.update.${UpdateUseCase.CurrentVersionInactive}`, + ); + await user.click(ui.openDialogBtn.get()); + expect(ui.dialogErrorMessage.get()).toBeInTheDocument(); + expect(ui.dialogWarningMessage.query()).not.toBeInTheDocument(); + expect(ui.dialog.get()).toHaveTextContent('10.7.0'); + expect(ui.dialog.get()).not.toHaveTextContent('10.6.0'); + expect(ui.dialog.get()).not.toHaveTextContent('10.5.1'); + expect(ui.latestHeader.get()).toBeInTheDocument(); + expect(ui.patchHeader.query()).not.toBeInTheDocument(); + expect(ui.showIntermediateBtn.get()).toBeInTheDocument(); + await user.click(ui.showIntermediateBtn.get()); + expect(ui.intermediateRegion.get()).toHaveTextContent('10.6.0August 2, 2021'); + expect(ui.intermediateRegion.get()).toHaveTextContent('10.5.1August 1, 2021'); }); - const user = userEvent.setup(); - renderUpdateNotification(); - expect(await ui.updateMessage.find()).toHaveTextContent( - `admin_notification.update.${UpdateUseCase.CurrentVersionInactive}`, - ); - await user.click(ui.openDialogBtn.get()); - expect(ui.dialogErrorMessage.get()).toBeInTheDocument(); - expect(ui.dialog.get()).toHaveTextContent('10.7.0'); - expect(ui.dialog.get()).not.toHaveTextContent('10.6.0'); - expect(ui.latestHeader.get()).toBeInTheDocument(); - expect(ui.showIntermediateBtn.get()).toBeInTheDocument(); - await user.click(ui.showIntermediateBtn.get()); - expect(ui.intermediateRegion.get()).toHaveTextContent('10.6.0August 2, 2021'); -}); -it('no longer active / latest / new minor + patch', async () => { - jest.mocked(getSystemUpgrades).mockResolvedValue({ - upgrades: [ - { downloadUrl: '', version: '10.5.1', releaseDate: '2021-08-01' }, - { downloadUrl: '', version: '10.6.0', releaseDate: '2021-08-02' }, - { downloadUrl: '', version: '10.7.0', releaseDate: '2021-08-03' }, - ], - latestLTA: '9.9', - updateCenterRefresh: '', - installedVersionActive: false, + it('active / lta / patch', async () => { + jest.mocked(getSystemUpgrades).mockResolvedValue({ + upgrades: [{ ...SQSUpgrade, version: '9.9.1' }], + latestLTA: '9.9', + updateCenterRefresh: '', + installedVersionActive: true, + }); + const user = userEvent.setup(); + renderUpdateNotification(undefined, undefined, { version: '9.9.0' }); + expect(await ui.updateMessage.find()).toHaveTextContent( + `admin_notification.update.${UpdateUseCase.NewPatch}`, + ); + await user.click(ui.openDialogBtn.get()); + expect(ui.dialogWarningMessage.get()).toBeInTheDocument(); + expect(ui.dialog.get()).toHaveTextContent('9.9.1'); + expect(ui.latestLTAHeader.get()).toBeInTheDocument(); + // If the current version is an LTA version, we don't show Patch header, we show Latest LTA header + expect(ui.patchHeader.query()).not.toBeInTheDocument(); + expect(ui.showIntermediateBtn.query()).not.toBeInTheDocument(); }); - const user = userEvent.setup(); - renderUpdateNotification(); - expect(await ui.updateMessage.find()).toHaveTextContent( - `admin_notification.update.${UpdateUseCase.CurrentVersionInactive}`, - ); - await user.click(ui.openDialogBtn.get()); - expect(ui.dialogErrorMessage.get()).toBeInTheDocument(); - expect(ui.dialogWarningMessage.query()).not.toBeInTheDocument(); - expect(ui.dialog.get()).toHaveTextContent('10.7.0'); - expect(ui.dialog.get()).not.toHaveTextContent('10.6.0'); - expect(ui.dialog.get()).not.toHaveTextContent('10.5.1'); - expect(ui.latestHeader.get()).toBeInTheDocument(); - expect(ui.patchHeader.query()).not.toBeInTheDocument(); - expect(ui.showIntermediateBtn.get()).toBeInTheDocument(); - await user.click(ui.showIntermediateBtn.get()); - expect(ui.intermediateRegion.get()).toHaveTextContent('10.6.0August 2, 2021'); - expect(ui.intermediateRegion.get()).toHaveTextContent('10.5.1August 1, 2021'); -}); -it('active / lta / patch', async () => { - jest.mocked(getSystemUpgrades).mockResolvedValue({ - upgrades: [{ downloadUrl: '', version: '9.9.1' }], - latestLTA: '9.9', - updateCenterRefresh: '', - installedVersionActive: true, + it('active / lta / new minor', async () => { + jest.mocked(getSystemUpgrades).mockResolvedValue({ + upgrades: [{ ...SQSUpgrade, version: '10.0.0' }], + latestLTA: '9.9', + updateCenterRefresh: '', + installedVersionActive: true, + }); + const user = userEvent.setup(); + renderUpdateNotification(undefined, undefined, { version: '9.9.0' }); + expect(await ui.updateMessage.find()).toHaveTextContent( + `admin_notification.update.${UpdateUseCase.NewVersion}`, + ); + await user.click(ui.openDialogBtn.get()); + expect(ui.dialogWarningMessage.query()).not.toBeInTheDocument(); + expect(ui.dialog.get()).toHaveTextContent('10.0.0'); + expect(ui.latestHeader.get()).toBeInTheDocument(); + expect(ui.latestLTAHeader.query()).not.toBeInTheDocument(); + expect(ui.patchHeader.query()).not.toBeInTheDocument(); + expect(ui.showIntermediateBtn.query()).not.toBeInTheDocument(); }); - const user = userEvent.setup(); - renderUpdateNotification(undefined, undefined, { version: '9.9.0' }); - expect(await ui.updateMessage.find()).toHaveTextContent( - `admin_notification.update.${UpdateUseCase.NewPatch}`, - ); - await user.click(ui.openDialogBtn.get()); - expect(ui.dialogWarningMessage.get()).toBeInTheDocument(); - expect(ui.dialog.get()).toHaveTextContent('9.9.1'); - expect(ui.latestLTAHeader.get()).toBeInTheDocument(); - // If the current version is an LTA version, we don't show Patch header, we show Latest LTA header - expect(ui.patchHeader.query()).not.toBeInTheDocument(); - expect(ui.showIntermediateBtn.query()).not.toBeInTheDocument(); -}); -it('active / lta / new minor', async () => { - jest.mocked(getSystemUpgrades).mockResolvedValue({ - upgrades: [{ downloadUrl: '', version: '10.0.0' }], - latestLTA: '9.9', - updateCenterRefresh: '', - installedVersionActive: true, + it('active / lta / new minor + patch', async () => { + jest.mocked(getSystemUpgrades).mockResolvedValue({ + upgrades: [ + { ...SQSUpgrade, version: '9.9.1' }, + { ...SQSUpgrade, version: '10.0.0' }, + ], + latestLTA: '9.9', + updateCenterRefresh: '', + installedVersionActive: true, + }); + const user = userEvent.setup(); + renderUpdateNotification(undefined, undefined, { version: '9.9.0' }); + expect(await ui.updateMessage.find()).toHaveTextContent( + `admin_notification.update.${UpdateUseCase.NewPatch}`, + ); + await user.click(ui.openDialogBtn.get()); + expect(ui.dialogWarningMessage.get()).toBeInTheDocument(); + expect(ui.dialog.get()).toHaveTextContent('10.0.0'); + expect(ui.dialog.get()).toHaveTextContent('9.9.1'); + expect(ui.latestHeader.get()).toBeInTheDocument(); + expect(ui.latestLTAHeader.get()).toBeInTheDocument(); + // If the current version is an LTA version, we don't show Patch header, we show Latest LTA header + expect(ui.patchHeader.query()).not.toBeInTheDocument(); + expect(ui.showIntermediateBtn.query()).not.toBeInTheDocument(); }); - const user = userEvent.setup(); - renderUpdateNotification(undefined, undefined, { version: '9.9.0' }); - expect(await ui.updateMessage.find()).toHaveTextContent( - `admin_notification.update.${UpdateUseCase.NewVersion}`, - ); - await user.click(ui.openDialogBtn.get()); - expect(ui.dialogWarningMessage.query()).not.toBeInTheDocument(); - expect(ui.dialog.get()).toHaveTextContent('10.0.0'); - expect(ui.latestHeader.get()).toBeInTheDocument(); - expect(ui.latestLTAHeader.query()).not.toBeInTheDocument(); - expect(ui.patchHeader.query()).not.toBeInTheDocument(); - expect(ui.showIntermediateBtn.query()).not.toBeInTheDocument(); -}); -it('active / lta / new minor + patch', async () => { - jest.mocked(getSystemUpgrades).mockResolvedValue({ - upgrades: [ - { downloadUrl: '', version: '9.9.1' }, - { downloadUrl: '', version: '10.0.0' }, - ], - latestLTA: '9.9', - updateCenterRefresh: '', - installedVersionActive: true, + it('active / prev lta / new lta + patch', async () => { + jest.mocked(getSystemUpgrades).mockResolvedValue({ + upgrades: [ + { ...SQSUpgrade, version: '8.9.1' }, + { ...SQSUpgrade, version: '9.9.0' }, + ], + latestLTA: '9.9', + updateCenterRefresh: '', + installedVersionActive: true, + }); + const user = userEvent.setup(); + renderUpdateNotification(undefined, undefined, { version: '8.9.0' }); + expect(await ui.updateMessage.find()).toHaveTextContent( + `admin_notification.update.${UpdateUseCase.NewPatch}`, + ); + await user.click(ui.openDialogBtn.get()); + expect(ui.dialogWarningMessage.get()).toBeInTheDocument(); + expect(ui.dialog.get()).toHaveTextContent('8.9.1'); + expect(ui.dialog.get()).toHaveTextContent('9.9.0'); + expect(ui.latestHeader.query()).not.toBeInTheDocument(); + expect(ui.latestLTAHeader.get()).toBeInTheDocument(); + expect(ui.patchHeader.get()).toBeInTheDocument(); + expect(ui.showIntermediateBtn.query()).not.toBeInTheDocument(); }); - const user = userEvent.setup(); - renderUpdateNotification(undefined, undefined, { version: '9.9.0' }); - expect(await ui.updateMessage.find()).toHaveTextContent( - `admin_notification.update.${UpdateUseCase.NewPatch}`, - ); - await user.click(ui.openDialogBtn.get()); - expect(ui.dialogWarningMessage.get()).toBeInTheDocument(); - expect(ui.dialog.get()).toHaveTextContent('10.0.0'); - expect(ui.dialog.get()).toHaveTextContent('9.9.1'); - expect(ui.latestHeader.get()).toBeInTheDocument(); - expect(ui.latestLTAHeader.get()).toBeInTheDocument(); - // If the current version is an LTA version, we don't show Patch header, we show Latest LTA header - expect(ui.patchHeader.query()).not.toBeInTheDocument(); - expect(ui.showIntermediateBtn.query()).not.toBeInTheDocument(); -}); -it('active / prev lta / new lta + patch', async () => { - jest.mocked(getSystemUpgrades).mockResolvedValue({ - upgrades: [ - { downloadUrl: '', version: '8.9.1' }, - { downloadUrl: '', version: '9.9.0' }, - ], - latestLTA: '9.9', - updateCenterRefresh: '', - installedVersionActive: true, + it('active / prev lta / new lta + new minor + patch', async () => { + jest.mocked(getSystemUpgrades).mockResolvedValue({ + upgrades: [ + { ...SQSUpgrade, version: '8.9.1' }, + { ...SQSUpgrade, version: '9.9.0' }, + { ...SQSUpgrade, version: '10.0.0' }, + ], + latestLTA: '9.9', + updateCenterRefresh: '', + installedVersionActive: true, + }); + const user = userEvent.setup(); + renderUpdateNotification(undefined, undefined, { version: '8.9.0' }); + expect(await ui.updateMessage.find()).toHaveTextContent( + `admin_notification.update.${UpdateUseCase.NewPatch}`, + ); + await user.click(ui.openDialogBtn.get()); + expect(ui.dialogWarningMessage.get()).toBeInTheDocument(); + expect(ui.dialog.get()).toHaveTextContent('8.9.1'); + expect(ui.dialog.get()).toHaveTextContent('9.9.0'); + expect(ui.dialog.get()).toHaveTextContent('10.0.0'); + expect(ui.latestHeader.get()).toBeInTheDocument(); + expect(ui.latestLTAHeader.get()).toBeInTheDocument(); + expect(ui.patchHeader.get()).toBeInTheDocument(); + expect(ui.showIntermediateBtn.query()).not.toBeInTheDocument(); }); - const user = userEvent.setup(); - renderUpdateNotification(undefined, undefined, { version: '8.9.0' }); - expect(await ui.updateMessage.find()).toHaveTextContent( - `admin_notification.update.${UpdateUseCase.NewPatch}`, - ); - await user.click(ui.openDialogBtn.get()); - expect(ui.dialogWarningMessage.get()).toBeInTheDocument(); - expect(ui.dialog.get()).toHaveTextContent('8.9.1'); - expect(ui.dialog.get()).toHaveTextContent('9.9.0'); - expect(ui.latestHeader.query()).not.toBeInTheDocument(); - expect(ui.latestLTAHeader.get()).toBeInTheDocument(); - expect(ui.patchHeader.get()).toBeInTheDocument(); - expect(ui.showIntermediateBtn.query()).not.toBeInTheDocument(); -}); -it('active / prev lta / new lta + new minor + patch', async () => { - jest.mocked(getSystemUpgrades).mockResolvedValue({ - upgrades: [ - { downloadUrl: '', version: '8.9.1' }, - { downloadUrl: '', version: '9.9.0' }, - { downloadUrl: '', version: '10.0.0' }, - ], - latestLTA: '9.9', - updateCenterRefresh: '', - installedVersionActive: true, + it('active / no uogrades', () => { + jest.mocked(getSystemUpgrades).mockResolvedValue({ + upgrades: [], + latestLTA: '9.9', + updateCenterRefresh: '', + installedVersionActive: true, + }); + + renderUpdateNotification(undefined, undefined, { version: '8.9.0' }); + + expect(screen.queryByText('admin_notification.update')).not.toBeInTheDocument(); }); - const user = userEvent.setup(); - renderUpdateNotification(undefined, undefined, { version: '8.9.0' }); - expect(await ui.updateMessage.find()).toHaveTextContent( - `admin_notification.update.${UpdateUseCase.NewPatch}`, - ); - await user.click(ui.openDialogBtn.get()); - expect(ui.dialogWarningMessage.get()).toBeInTheDocument(); - expect(ui.dialog.get()).toHaveTextContent('8.9.1'); - expect(ui.dialog.get()).toHaveTextContent('9.9.0'); - expect(ui.dialog.get()).toHaveTextContent('10.0.0'); - expect(ui.latestHeader.get()).toBeInTheDocument(); - expect(ui.latestLTAHeader.get()).toBeInTheDocument(); - expect(ui.patchHeader.get()).toBeInTheDocument(); - expect(ui.showIntermediateBtn.query()).not.toBeInTheDocument(); -}); -it('no longer active / prev lta / new lta', async () => { - jest.mocked(getSystemUpgrades).mockResolvedValue({ - upgrades: [{ downloadUrl: '', version: '9.9.0' }], - latestLTA: '9.9', - updateCenterRefresh: '', - installedVersionActive: false, + it('no longer active / prev lta / new lta', async () => { + jest.mocked(getSystemUpgrades).mockResolvedValue({ + upgrades: [{ ...SQSUpgrade, version: '9.9.0' }], + latestLTA: '9.9', + updateCenterRefresh: '', + installedVersionActive: false, + }); + const user = userEvent.setup(); + renderUpdateNotification(undefined, undefined, { version: '8.9.0' }); + expect(await ui.updateMessage.find()).toHaveTextContent( + `admin_notification.update.${UpdateUseCase.CurrentVersionInactive}`, + ); + await user.click(ui.openDialogBtn.get()); + expect(ui.dialogErrorMessage.get()).toBeInTheDocument(); + expect(ui.dialog.get()).toHaveTextContent('9.9.0'); + expect(ui.latestLTAHeader.get()).toBeInTheDocument(); + expect(ui.showIntermediateBtn.query()).not.toBeInTheDocument(); }); - const user = userEvent.setup(); - renderUpdateNotification(undefined, undefined, { version: '8.9.0' }); - expect(await ui.updateMessage.find()).toHaveTextContent( - `admin_notification.update.${UpdateUseCase.CurrentVersionInactive}`, - ); - await user.click(ui.openDialogBtn.get()); - expect(ui.dialogErrorMessage.get()).toBeInTheDocument(); - expect(ui.dialog.get()).toHaveTextContent('9.9.0'); - expect(ui.latestLTAHeader.get()).toBeInTheDocument(); - expect(ui.showIntermediateBtn.query()).not.toBeInTheDocument(); -}); -it('no longer active / prev lta / new lta + patch', async () => { - jest.mocked(getSystemUpgrades).mockResolvedValue({ - upgrades: [ - { downloadUrl: '', version: '8.9.1' }, - { downloadUrl: '', version: '9.9.0' }, - ], - latestLTA: '9.9', - updateCenterRefresh: '', - installedVersionActive: false, + it('no longer active / prev lta / new lta + patch', async () => { + jest.mocked(getSystemUpgrades).mockResolvedValue({ + upgrades: [ + { ...SQSUpgrade, version: '8.9.1' }, + { ...SQSUpgrade, version: '9.9.0' }, + ], + latestLTA: '9.9', + updateCenterRefresh: '', + installedVersionActive: false, + }); + const user = userEvent.setup(); + renderUpdateNotification(undefined, undefined, { version: '8.9.0' }); + expect(await ui.updateMessage.find()).toHaveTextContent( + `admin_notification.update.${UpdateUseCase.CurrentVersionInactive}`, + ); + await user.click(ui.openDialogBtn.get()); + expect(ui.dialogErrorMessage.get()).toBeInTheDocument(); + expect(ui.dialog.get()).toHaveTextContent('9.9.0'); + expect(ui.dialog.get()).not.toHaveTextContent('8.9.1'); + expect(ui.latestLTAHeader.get()).toBeInTheDocument(); + expect(ui.patchHeader.query()).not.toBeInTheDocument(); + expect(ui.showIntermediateBtn.query()).not.toBeInTheDocument(); + }); + + it('no longer active / prev lta / new lta + patch + new minors', async () => { + jest.mocked(getSystemUpgrades).mockResolvedValue({ + upgrades: [ + { ...SQSUpgrade, version: '8.9.1', releaseDate: '2021-08-01' }, + { ...SQSUpgrade, version: '9.9.0', releaseDate: '2021-08-02' }, + { ...SQSUpgrade, version: '9.9.1', releaseDate: '2021-08-03' }, + { ...SQSUpgrade, version: '10.0.0', releaseDate: '2021-08-04' }, + { ...SQSUpgrade, version: '10.1.0', releaseDate: '2021-08-05' }, + { ...SQSUpgrade, version: '10.1.1', releaseDate: '2021-08-06' }, + ], + latestLTA: '9.9', + updateCenterRefresh: '', + installedVersionActive: false, + }); + const user = userEvent.setup(); + renderUpdateNotification(true, undefined, { version: '8.9.0' }); + expect(await ui.updateMessage.find()).toHaveTextContent( + `admin_notification.update.${UpdateUseCase.CurrentVersionInactive}`, + ); + await user.click(ui.openDialogBtn.get()); + expect(ui.dialogErrorMessage.get()).toBeInTheDocument(); + expect(ui.dialog.get()).toHaveTextContent('9.9.1'); + expect(ui.dialog.get()).not.toHaveTextContent('9.9.0'); + expect(ui.dialog.get()).toHaveTextContent('10.1.1'); + expect(ui.dialog.get()).not.toHaveTextContent('10.1.0'); + expect(ui.dialog.get()).not.toHaveTextContent('10.0.0'); + expect(ui.dialog.get()).not.toHaveTextContent('8.9.1'); + expect(ui.latestHeader.get()).toBeInTheDocument(); + expect(ui.latestLTAHeader.get()).toBeInTheDocument(); + expect(ui.patchHeader.query()).not.toBeInTheDocument(); + expect(ui.showIntermediateBtn.getAll()).toHaveLength(2); + await user.click(ui.showIntermediateBtn.getAt(0)); + expect(ui.intermediateRegion.get()).toHaveTextContent('10.1.0August 5, 2021'); + expect(ui.intermediateRegion.get()).toHaveTextContent('10.0.0August 4, 2021'); + expect(ui.intermediateRegion.get()).not.toHaveTextContent('9.9.0'); + await user.click(ui.hideIntermediateBtn.get()); + await user.click(ui.showIntermediateBtn.getAt(1)); + expect(ui.intermediateRegion.get()).toHaveTextContent('9.9.0August 2, 2021'); + expect(ui.intermediateRegion.get()).not.toHaveTextContent('10.1.0'); + expect(ui.intermediateRegion.get()).not.toHaveTextContent('10.0.0'); }); - const user = userEvent.setup(); - renderUpdateNotification(undefined, undefined, { version: '8.9.0' }); - expect(await ui.updateMessage.find()).toHaveTextContent( - `admin_notification.update.${UpdateUseCase.CurrentVersionInactive}`, - ); - await user.click(ui.openDialogBtn.get()); - expect(ui.dialogErrorMessage.get()).toBeInTheDocument(); - expect(ui.dialog.get()).toHaveTextContent('9.9.0'); - expect(ui.dialog.get()).not.toHaveTextContent('8.9.1'); - expect(ui.latestLTAHeader.get()).toBeInTheDocument(); - expect(ui.patchHeader.query()).not.toBeInTheDocument(); - expect(ui.showIntermediateBtn.query()).not.toBeInTheDocument(); }); -it('no longer active / prev lta / new lta + patch + new minors', async () => { - jest.mocked(getSystemUpgrades).mockResolvedValue({ - upgrades: [ - { downloadUrl: '', version: '8.9.1', releaseDate: '2021-08-01' }, - { downloadUrl: '', version: '9.9.0', releaseDate: '2021-08-02' }, - { downloadUrl: '', version: '9.9.1', releaseDate: '2021-08-03' }, - { downloadUrl: '', version: '10.0.0', releaseDate: '2021-08-04' }, - { downloadUrl: '', version: '10.1.0', releaseDate: '2021-08-05' }, - { downloadUrl: '', version: '10.1.1', releaseDate: '2021-08-06' }, - ], - latestLTA: '9.9', - updateCenterRefresh: '', - installedVersionActive: false, +describe('when running SQCB', () => { + it('should not render update notification if no upgrades', () => { + jest.mocked(getSystemUpgrades).mockResolvedValue({ + upgrades: [], + latestLTA: '9.9', + updateCenterRefresh: '', + installedVersionActive: true, + }); + + renderUpdateNotification(undefined, undefined, { edition: EditionKey.community }); + + expect(getSystemUpgrades).toHaveBeenCalled(); + + expect(ui.updateMessage.query()).not.toBeInTheDocument(); + }); + + it('SQCB upgrade available, no SQS upgrade', async () => { + jest.mocked(getSystemUpgrades).mockResolvedValue({ + upgrades: [{ ...SQCBUpgrade, version: '25.2' }], + latestLTA: '9.9', + updateCenterRefresh: '', + installedVersionActive: true, + }); + + renderUpdateNotification(undefined, undefined, { edition: EditionKey.community }); + + expect(await ui.updateMessage.find()).toHaveTextContent( + 'admin_notification.update.new_sqcb_version', + ); + + expect( + screen.queryByText('admin_notification.update.new_sqs_version_when_running_sqcb.banner'), + ).not.toBeInTheDocument(); + }); + + it('non-patch SQS upgrade available, no SQCB upgrade', async () => { + jest.mocked(getSystemUpgrades).mockResolvedValue({ + upgrades: [{ ...SQSUpgrade, version: '2025.2' }], + latestLTA: '9.9', + updateCenterRefresh: '', + installedVersionActive: true, + }); + + const user = userEvent.setup(); + + renderUpdateNotification(undefined, undefined, { edition: EditionKey.community }); + + expect( + screen.queryByText('admin_notification.update.new_sqcb_version'), + ).not.toBeInTheDocument(); + + expect(await ui.updateMessage.find()).toHaveTextContent( + 'admin_notification.update.new_sqs_version_when_running_sqcb.banner', + ); + + await user.click(ui.openDialogBtn.get()); + + expect( + await screen.findByText( + 'admin_notification.update.new_sqs_version_when_running_sqcb.upgrade', + ), + ).toBeInTheDocument(); + + await user.click(ui.closeDialogBtn.get()); + + expect( + screen.queryByText('admin_notification.update.new_sqs_version_when_running_sqcb.upgrade'), + ).not.toBeInTheDocument(); + }); + + it('only patch SQS upgrade available, no SQCB upgrade', () => { + jest.mocked(getSystemUpgrades).mockResolvedValue({ + upgrades: [{ ...SQSUpgrade, version: '2025.2.3' }], + latestLTA: '9.9', + updateCenterRefresh: '', + installedVersionActive: true, + }); + + renderUpdateNotification(undefined, undefined, { edition: EditionKey.community }); + + expect( + screen.queryByText('admin_notification.update.new_sqcb_version'), + ).not.toBeInTheDocument(); + + expect( + screen.queryByText('admin_notification.update.new_sqs_version_when_running_sqcb.banner'), + ).not.toBeInTheDocument(); + }); + + it('both SQCB and non-patch-SQS upgrades available', async () => { + jest.mocked(getSystemUpgrades).mockResolvedValue({ + upgrades: [ + { ...SQCBUpgrade, version: '25.4' }, + { ...SQSUpgrade, version: '2025.4' }, + ], + latestLTA: '9.9', + updateCenterRefresh: '', + installedVersionActive: true, + }); + + const user = userEvent.setup(); + + renderUpdateNotification(true, undefined, { edition: EditionKey.community }); + + expect(await ui.updateMessage.findAll()).toHaveLength(2); + + await user.click(ui.openDialogBtn.get()); + + expect( + await screen.findByText( + 'admin_notification.update.new_sqs_version_when_running_sqcb.upgrade', + ), + ).toBeInTheDocument(); + + await user.keyboard('{Escape}'); + + expect( + screen.queryByText('admin_notification.update.new_sqs_version_when_running_sqcb.upgrade'), + ).not.toBeInTheDocument(); }); - const user = userEvent.setup(); - renderUpdateNotification(undefined, undefined, { version: '8.9.0' }); - expect(await ui.updateMessage.find()).toHaveTextContent( - `admin_notification.update.${UpdateUseCase.CurrentVersionInactive}`, - ); - await user.click(ui.openDialogBtn.get()); - expect(ui.dialogErrorMessage.get()).toBeInTheDocument(); - expect(ui.dialog.get()).toHaveTextContent('9.9.1'); - expect(ui.dialog.get()).not.toHaveTextContent('9.9.0'); - expect(ui.dialog.get()).toHaveTextContent('10.1.1'); - expect(ui.dialog.get()).not.toHaveTextContent('10.1.0'); - expect(ui.dialog.get()).not.toHaveTextContent('10.0.0'); - expect(ui.dialog.get()).not.toHaveTextContent('8.9.1'); - expect(ui.latestHeader.get()).toBeInTheDocument(); - expect(ui.latestLTAHeader.get()).toBeInTheDocument(); - expect(ui.patchHeader.query()).not.toBeInTheDocument(); - expect(ui.showIntermediateBtn.getAll()).toHaveLength(2); - await user.click(ui.showIntermediateBtn.getAt(0)); - expect(ui.intermediateRegion.get()).toHaveTextContent('10.1.0August 5, 2021'); - expect(ui.intermediateRegion.get()).toHaveTextContent('10.0.0August 4, 2021'); - expect(ui.intermediateRegion.get()).not.toHaveTextContent('9.9.0'); - await user.click(ui.hideIntermediateBtn.get()); - await user.click(ui.showIntermediateBtn.getAt(1)); - expect(ui.intermediateRegion.get()).toHaveTextContent('9.9.0August 2, 2021'); - expect(ui.intermediateRegion.get()).not.toHaveTextContent('10.1.0'); - expect(ui.intermediateRegion.get()).not.toHaveTextContent('10.0.0'); }); function renderUpdateNotification( - dissmissable: boolean = false, + dissmissable = false, user?: Partial<CurrentUser>, - // versionEOL is a date in the past to be sure that it is not used when we have data from upgrades endpoint - appState: Partial<AppState> = { version: '10.5.0', versionEOL: '2020-01-01' }, + appState: Partial<AppState> = {}, ) { return renderComponent( <CurrentUserContext.Provider @@ -478,7 +621,15 @@ function renderUpdateNotification( updateDismissedNotices: () => {}, }} > - <AppStateContext.Provider value={mockAppState(appState)}> + <AppStateContext.Provider + value={mockAppState({ + edition: EditionKey.developer, + version: '10.5.0', + // versionEOL is a date in the past to be sure that it is not used when we have data from upgrades endpoint + versionEOL: '2020-01-01', + ...appState, + })} + > <UpdateNotification dismissable={dissmissable} /> </AppStateContext.Provider> </CurrentUserContext.Provider>, diff --git a/server/sonar-web/src/main/js/app/components/calculation-notification/CalculationChangeMessage.tsx b/server/sonar-web/src/main/js/app/components/calculation-notification/CalculationChangeMessage.tsx index 1499bc7f3c9..3252f4890e0 100644 --- a/server/sonar-web/src/main/js/app/components/calculation-notification/CalculationChangeMessage.tsx +++ b/server/sonar-web/src/main/js/app/components/calculation-notification/CalculationChangeMessage.tsx @@ -22,7 +22,7 @@ import { FormattedMessage } from 'react-intl'; import { useLocation } from '~sonar-aligned/components/hoc/withRouter'; import { ComponentQualifier } from '~sonar-aligned/types/component'; import DocumentationLink from '../../../components/common/DocumentationLink'; -import DismissableAlert from '../../../components/ui/DismissableAlert'; +import { DismissableAlert } from '../../../components/ui/DismissableAlert'; import { DocLink } from '../../../helpers/doc-links'; import { translate } from '../../../helpers/l10n'; import { useStandardExperienceModeQuery } from '../../../queries/mode'; @@ -49,7 +49,7 @@ export default function CalculationChangeMessage() { id={`notification.calculation_change.message.${SHOW_MESSAGE_PATHS[location.pathname]}`} values={{ link: ( - <DocumentationLink to={DocLink.MetricDefinitions}> + <DocumentationLink className="sw-ml-1" to={DocLink.MetricDefinitions}> {translate('learn_more')} </DocumentationLink> ), diff --git a/server/sonar-web/src/main/js/app/components/update-notification/SQCBUpdateBanners.tsx b/server/sonar-web/src/main/js/app/components/update-notification/SQCBUpdateBanners.tsx new file mode 100644 index 00000000000..db9b26758ee --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/update-notification/SQCBUpdateBanners.tsx @@ -0,0 +1,128 @@ +/* + * 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 { LinkStandalone } from '@sonarsource/echoes-react'; +import { isEmpty } from 'lodash'; +import { FormattedMessage } from 'react-intl'; +import { Banner } from '~design-system'; +import { getSystemUpgrades } from '../../../api/system'; +import { DismissableAlert } from '../../../components/ui/DismissableAlert'; +import { SystemUpgradeButton } from '../../../components/upgrade/SystemUpgradeButton'; +import { UpdateUseCase } from '../../../components/upgrade/utils'; +import { translate } from '../../../helpers/l10n'; +import { ProductNameForUpgrade } from '../../../types/system'; +import { useAppState } from '../app-state/withAppStateContext'; +import { analyzeUpgrades, isVersionAPatchUpdate, parseVersion } from './helpers'; + +interface Props { + data?: Awaited<ReturnType<typeof getSystemUpgrades>>; + dismissable?: boolean; +} + +export function SQCBUpdateBanners({ data, dismissable }: Readonly<Props>) { + const appState = useAppState(); + + const parsedVersion = parseVersion(appState.version); + const { upgrades = [], latestLTA } = data ?? {}; + + const SQSUpgrades = upgrades.filter( + (upgrade) => + upgrade.product === ProductNameForUpgrade.SonarQubeServer && + !isVersionAPatchUpdate(upgrade.version), + ); + + const SQCBUpgrades = upgrades.filter( + (upgrade) => upgrade.product === ProductNameForUpgrade.SonarQubeCommunityBuild, + ); + + const banners = []; + + if (!isEmpty(SQCBUpgrades)) { + const contents = ( + <FormattedMessage + id="admin_notification.update.new_sqcb_version" + values={{ + link: ( + <LinkStandalone + className="sw-ml-1" + to="https://www.sonarsource.com/open-source-editions/sonarqube-community-edition/" + > + {translate('admin_notification.update.latest')} + </LinkStandalone> + ), + }} + /> + ); + + const { latest } = analyzeUpgrades({ + parsedVersion, + upgrades: SQCBUpgrades, + }); + + const dismissKey = latest?.version ?? appState.version; + + banners.push( + dismissable ? ( + <DismissableAlert alertKey={dismissKey} key="SQCB" variant="info"> + {contents} + </DismissableAlert> + ) : ( + <Banner key="SQCB" variant="info"> + {contents} + </Banner> + ), + ); + } + + if (!isEmpty(SQSUpgrades)) { + const { latest } = analyzeUpgrades({ + parsedVersion, + upgrades: SQSUpgrades, + }); + + const contents = ( + <> + {translate('admin_notification.update.new_sqs_version_when_running_sqcb.banner')}{' '} + {translate('admin_notification.update.new_sqs_version_when_running_sqcb.upgrade')}. + <SystemUpgradeButton + systemUpgrades={[latest]} + updateUseCase={UpdateUseCase.NewVersion} + latestLTA={latestLTA} + /> + </> + ); + + const dismissKey = latest?.version ?? appState.version; + + banners.push( + dismissable ? ( + <DismissableAlert alertKey={dismissKey} key="SQS" variant="info"> + {contents} + </DismissableAlert> + ) : ( + <Banner key="SQS" variant="info"> + {contents} + </Banner> + ), + ); + } + + return banners; +} diff --git a/server/sonar-web/src/main/js/app/components/update-notification/SQSUpdateBanner.tsx b/server/sonar-web/src/main/js/app/components/update-notification/SQSUpdateBanner.tsx new file mode 100644 index 00000000000..f85348642c3 --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/update-notification/SQSUpdateBanner.tsx @@ -0,0 +1,99 @@ +/* + * 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 { isEmpty } from 'lodash'; +import { Banner } from '~design-system'; +import { getSystemUpgrades } from '../../../api/system'; +import { DismissableAlert } from '../../../components/ui/DismissableAlert'; +import { SystemUpgradeButton } from '../../../components/upgrade/SystemUpgradeButton'; +import { UpdateUseCase } from '../../../components/upgrade/utils'; +import { translate } from '../../../helpers/l10n'; +import { isCurrentVersionEOLActive } from '../../../helpers/system'; +import { ProductNameForUpgrade } from '../../../types/system'; +import { useAppState } from '../app-state/withAppStateContext'; +import { analyzeUpgrades, BANNER_VARIANT, isCurrentVersionLTA, parseVersion } from './helpers'; + +interface Props { + data?: Awaited<ReturnType<typeof getSystemUpgrades>>; + dismissable?: boolean; +} + +export function SQSUpdateBanner({ data, dismissable }: Readonly<Props>) { + const appState = useAppState(); + + // below: undefined already tested upstream in UpdateNotification, ?? [] is just to make TS happy + const parsedVersion = parseVersion(appState.version) ?? []; + const { upgrades = [], installedVersionActive, latestLTA } = data ?? {}; + + const SQSUpgrades = upgrades.filter( + (upgrade) => upgrade.product === ProductNameForUpgrade.SonarQubeServer, + ); + + const active = installedVersionActive ?? isCurrentVersionEOLActive(appState.versionEOL); + + if (active && isEmpty(SQSUpgrades)) { + return null; + } + + const { isMinorUpdate, isPatchUpdate, latest } = analyzeUpgrades({ + parsedVersion, + upgrades: SQSUpgrades, + }); + + let useCase = UpdateUseCase.NewVersion; + + if (!active) { + useCase = UpdateUseCase.CurrentVersionInactive; + } else if ( + isPatchUpdate && + // if the latest update is a patch and either we're running latest LTA, or there's no minor update + ((latestLTA !== undefined && isCurrentVersionLTA(parsedVersion, latestLTA)) || !isMinorUpdate) + ) { + useCase = UpdateUseCase.NewPatch; + } + + const dismissKey = useCase + (latest?.version ?? appState.version); + + const contents = ( + <> + {translate('admin_notification.update', useCase)} + + <SystemUpgradeButton + systemUpgrades={SQSUpgrades} + updateUseCase={useCase} + latestLTA={latestLTA} + /> + </> + ); + + return dismissable ? ( + <DismissableAlert + alertKey={dismissKey} + variant={BANNER_VARIANT[useCase]} + className={`it__promote-update-notification it__upgrade-prompt-${useCase}`} + > + {contents} + </DismissableAlert> + ) : ( + <Banner variant={BANNER_VARIANT[useCase]} className={`it__upgrade-prompt-${useCase}`}> + {contents} + </Banner> + ); +} 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 index 40fdb8b963a..0d13adce1be 100644 --- 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 @@ -18,113 +18,48 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { groupBy, isEmpty, mapValues } from 'lodash'; -import { Banner } from '~design-system'; -import DismissableAlert from '../../../components/ui/DismissableAlert'; -import SystemUpgradeButton from '../../../components/upgrade/SystemUpgradeButton'; -import { UpdateUseCase } from '../../../components/upgrade/utils'; -import { translate } from '../../../helpers/l10n'; -import { isCurrentVersionEOLActive } from '../../../helpers/system'; +import { isEmpty } from 'lodash'; import { hasGlobalPermission } from '../../../helpers/users'; import { useSystemUpgrades } from '../../../queries/system'; +import { EditionKey } from '../../../types/editions'; import { Permissions } from '../../../types/permissions'; import { isLoggedIn } from '../../../types/users'; import { useAppState } from '../app-state/withAppStateContext'; import { useCurrentUser } from '../current-user/CurrentUserContext'; -import { BANNER_VARIANT, isCurrentVersionLTA, isMinorUpdate, isPatchUpdate } from './helpers'; +import { parseVersion } from './helpers'; +import { SQCBUpdateBanners } from './SQCBUpdateBanners'; +import { SQSUpdateBanner } from './SQSUpdateBanner'; interface Props { dismissable?: boolean; } -const VERSION_PARSER = /^(\d+)\.(\d+)(\.(\d+))?/; - -export default function UpdateNotification({ dismissable }: Readonly<Props>) { +export function UpdateNotification({ dismissable }: Readonly<Props>) { const appState = useAppState(); const { currentUser } = useCurrentUser(); const canUserSeeNotification = isLoggedIn(currentUser) && hasGlobalPermission(currentUser, Permissions.Admin); - const regExpParsedVersion = VERSION_PARSER.exec(appState.version); + + const parsedVersion = parseVersion(appState.version); const { data, isLoading } = useSystemUpgrades({ - enabled: canUserSeeNotification && regExpParsedVersion !== null, + enabled: canUserSeeNotification && parsedVersion !== undefined, }); - if (!canUserSeeNotification || regExpParsedVersion === null || isLoading) { + if (!canUserSeeNotification || parsedVersion === undefined || isLoading) { return null; } - const { upgrades = [], installedVersionActive, latestLTA } = data ?? {}; - - let active = installedVersionActive; + const isCommunityBuildRunning = appState.edition === EditionKey.community; - if (installedVersionActive === undefined) { - active = isCurrentVersionEOLActive(appState.versionEOL); - } + if (isCommunityBuildRunning && !isEmpty(data?.upgrades)) { + // We're running SQCB, show SQCB update banner & SQS update banner if applicable - if (active && isEmpty(upgrades)) { - return null; + return <SQCBUpdateBanners data={data} dismissable={dismissable} />; } - const parsedVersion = regExpParsedVersion - .slice(1) - .map(Number) - .map((n) => (isNaN(n) ? 0 : n)); - - 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 = UpdateUseCase.NewVersion; - - if (!active) { - useCase = UpdateUseCase.CurrentVersionInactive; - } else if ( - isPatchUpdate(parsedVersion, systemUpgrades) && - ((latestLTA !== undefined && isCurrentVersionLTA(parsedVersion, latestLTA)) || - !isMinorUpdate(parsedVersion, systemUpgrades)) - ) { - useCase = UpdateUseCase.NewPatch; - } - - const latest = [...upgrades].sort( - (upgrade1, upgrade2) => - new Date(upgrade2.releaseDate ?? '').getTime() - - new Date(upgrade1.releaseDate ?? '').getTime(), - )[0]; - - const dismissKey = useCase + (latest?.version ?? appState.version); + // We're running SQS (or old SQ), only show SQS update banner if applicable - return dismissable ? ( - <DismissableAlert - alertKey={dismissKey} - variant={BANNER_VARIANT[useCase]} - className={`it__promote-update-notification it__upgrade-prompt-${useCase}`} - > - {translate('admin_notification.update', useCase)} - <SystemUpgradeButton - systemUpgrades={upgrades} - updateUseCase={useCase} - latestLTA={latestLTA} - /> - </DismissableAlert> - ) : ( - <Banner variant={BANNER_VARIANT[useCase]} className={`it__upgrade-prompt-${useCase}`}> - {translate('admin_notification.update', useCase)} - <SystemUpgradeButton - systemUpgrades={upgrades} - updateUseCase={useCase} - latestLTA={latestLTA} - /> - </Banner> - ); + return <SQSUpdateBanner data={data} dismissable={dismissable} />; } diff --git a/server/sonar-web/src/main/js/app/components/update-notification/helpers.ts b/server/sonar-web/src/main/js/app/components/update-notification/helpers.ts index 0eb3b558b84..5092aa7bda9 100644 --- a/server/sonar-web/src/main/js/app/components/update-notification/helpers.ts +++ b/server/sonar-web/src/main/js/app/components/update-notification/helpers.ts @@ -18,7 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { isEmpty } from 'lodash'; +import { groupBy, isEmpty, mapValues } from 'lodash'; import { Variant } from '~design-system'; import { UpdateUseCase, sortUpgrades } from '../../../components/upgrade/utils'; import { SystemUpgrade } from '../../../types/system'; @@ -28,6 +28,38 @@ type GroupedSystemUpdate = { [x: string]: Record<string, SystemUpgrade[]>; }; +export const analyzeUpgrades = ({ + parsedVersion = [], + upgrades, +}: { + parsedVersion: number[] | undefined; + upgrades: SystemUpgrade[]; +}) => { + const systemUpgrades = mapValues( + groupBy(upgrades, (upgrade: SystemUpgrade) => { + const [major] = upgrade.version.split('.'); + return major; + }), + (upgrades) => + groupBy(upgrades, (upgrade: SystemUpgrade) => { + const [, minor] = upgrade.version.split('.'); + return minor; + }), + ); + + const latest = [...upgrades].sort( + (upgrade1, upgrade2) => + new Date(upgrade2.releaseDate ?? '').getTime() - + new Date(upgrade1.releaseDate ?? '').getTime(), + )[0]; + + return { + isMinorUpdate: isMinorUpdate(parsedVersion, systemUpgrades), + isPatchUpdate: isLatestUpdatedAPatchUpdate(parsedVersion, systemUpgrades), + latest, + }; +}; + export const isCurrentVersionLTA = (parsedVersion: number[], latestLTS: string) => { const [currentMajor, currentMinor] = parsedVersion; const [ltsMajor, ltsMinor] = latestLTS.split('.').map(Number); @@ -36,13 +68,17 @@ export const isCurrentVersionLTA = (parsedVersion: number[], latestLTS: string) export const isMinorUpdate = (parsedVersion: number[], systemUpgrades: GroupedSystemUpdate) => { const [currentMajor, currentMinor] = parsedVersion; - const allMinor = systemUpgrades[currentMajor]; + const allMinor = systemUpgrades[currentMajor] ?? {}; + return Object.keys(allMinor) .map(Number) .some((minor) => minor > currentMinor); }; -export const isPatchUpdate = (parsedVersion: number[], systemUpgrades: GroupedSystemUpdate) => { +export const isLatestUpdatedAPatchUpdate = ( + parsedVersion: number[], + systemUpgrades: GroupedSystemUpdate, +) => { const [currentMajor, currentMinor, currentPatch] = parsedVersion; const allMinor = systemUpgrades[currentMajor]; const allPatch = sortUpgrades(allMinor?.[currentMinor] ?? []); @@ -51,11 +87,26 @@ export const isPatchUpdate = (parsedVersion: number[], systemUpgrades: GroupedSy 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; }; +export const parseVersion = (version: string) => { + const VERSION_PARSER = /^(\d+)\.(\d+)(\.(\d+))?/; + const regExpParsedVersion = VERSION_PARSER.exec(version); + + return regExpParsedVersion + ?.slice(1) + .map(Number) + .map((n) => (isNaN(n) ? 0 : n)); +}; + +export const isVersionAPatchUpdate = (version: string) => + ((parseVersion(version) ?? [])[2] ?? 0) !== 0; + export const BANNER_VARIANT: Dict<Variant> = { [UpdateUseCase.NewVersion]: 'info', [UpdateUseCase.CurrentVersionInactive]: 'error', diff --git a/server/sonar-web/src/main/js/apps/overview/branches/FirstAnalysisNextStepsNotif.tsx b/server/sonar-web/src/main/js/apps/overview/branches/FirstAnalysisNextStepsNotif.tsx index 7a095b6652b..9784787bb1e 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/FirstAnalysisNextStepsNotif.tsx +++ b/server/sonar-web/src/main/js/apps/overview/branches/FirstAnalysisNextStepsNotif.tsx @@ -23,7 +23,7 @@ import { Link } from '~design-system'; import { queryToSearchString } from '~sonar-aligned/helpers/urls'; import { ComponentQualifier } from '~sonar-aligned/types/component'; import withCurrentUserContext from '../../../app/components/current-user/withCurrentUserContext'; -import DismissableAlert from '../../../components/ui/DismissableAlert'; +import { DismissableAlert } from '../../../components/ui/DismissableAlert'; import { translate } from '../../../helpers/l10n'; import { useProjectBindingQuery } from '../../../queries/devops-integration'; import { Component } from '../../../types/types'; diff --git a/server/sonar-web/src/main/js/apps/system/components/SystemApp.tsx b/server/sonar-web/src/main/js/apps/system/components/SystemApp.tsx index 0d142ef634e..141515a8397 100644 --- a/server/sonar-web/src/main/js/apps/system/components/SystemApp.tsx +++ b/server/sonar-web/src/main/js/apps/system/components/SystemApp.tsx @@ -24,7 +24,7 @@ import { LargeCenteredLayout, PageContentFontWrapper } from '~design-system'; import { withRouter } from '~sonar-aligned/components/hoc/withRouter'; import { Location, Router } from '~sonar-aligned/types/router'; import { getSystemInfo } from '../../../api/system'; -import UpdateNotification from '../../../app/components/update-notification/UpdateNotification'; +import { UpdateNotification } from '../../../app/components/update-notification/UpdateNotification'; import { translate } from '../../../helpers/l10n'; import { SysInfoCluster, SysInfoStandalone } from '../../../types/types'; import '../styles.css'; 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 34bed88ab14..98a6bb91a17 100644 --- a/server/sonar-web/src/main/js/components/ui/DismissableAlert.tsx +++ b/server/sonar-web/src/main/js/components/ui/DismissableAlert.tsx @@ -32,7 +32,7 @@ export interface DismissableAlertProps { export const DISMISSED_ALERT_STORAGE_KEY = 'sonarqube.dismissed_alert'; -export default function DismissableAlert(props: DismissableAlertProps) { +export function DismissableAlert(props: DismissableAlertProps) { const { alertKey, children, className, variant } = props; const [show, setShow] = React.useState(false); diff --git a/server/sonar-web/src/main/js/components/upgrade/SystemUpgradeButton.tsx b/server/sonar-web/src/main/js/components/upgrade/SystemUpgradeButton.tsx index d3f89677405..f29ce936bf0 100644 --- a/server/sonar-web/src/main/js/components/upgrade/SystemUpgradeButton.tsx +++ b/server/sonar-web/src/main/js/components/upgrade/SystemUpgradeButton.tsx @@ -18,12 +18,11 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { Link } from '@sonarsource/echoes-react'; -import * as React from 'react'; -import { ButtonSecondary } from '~design-system'; +import { Button, Link } from '@sonarsource/echoes-react'; +import React from 'react'; import { translate } from '../../helpers/l10n'; import { SystemUpgrade } from '../../types/system'; -import SystemUpgradeForm from './SystemUpgradeForm'; +import { SystemUpgradeForm } from './SystemUpgradeForm'; import { groupUpgrades, sortUpgrades, UpdateUseCase } from './utils'; interface Props { @@ -32,18 +31,18 @@ interface Props { updateUseCase: UpdateUseCase; } -export default function SystemUpgradeButton(props: Readonly<Props>) { +export function SystemUpgradeButton(props: Readonly<Props>) { const { latestLTA, systemUpgrades, updateUseCase } = props; - const [isSystemUpgradeFormOpen, setSystemUpgradeFormOpen] = React.useState(false); + const [isSystemUpgradeFormOpen, setIsSystemUpgradeFormOpen] = React.useState(false); const openSystemUpgradeForm = React.useCallback(() => { - setSystemUpgradeFormOpen(true); - }, [setSystemUpgradeFormOpen]); + setIsSystemUpgradeFormOpen(true); + }, [setIsSystemUpgradeFormOpen]); const closeSystemUpgradeForm = React.useCallback(() => { - setSystemUpgradeFormOpen(false); - }, [setSystemUpgradeFormOpen]); + setIsSystemUpgradeFormOpen(false); + }, [setIsSystemUpgradeFormOpen]); if (systemUpgrades.length === 0) { return ( @@ -59,9 +58,10 @@ export default function SystemUpgradeButton(props: Readonly<Props>) { return ( <> - <ButtonSecondary className="sw-ml-2" onClick={openSystemUpgradeForm}> + <Button className="sw-ml-2" onClick={openSystemUpgradeForm}> {translate('learn_more')} - </ButtonSecondary> + </Button> + {isSystemUpgradeFormOpen && ( <SystemUpgradeForm onClose={closeSystemUpgradeForm} diff --git a/server/sonar-web/src/main/js/components/upgrade/SystemUpgradeForm.tsx b/server/sonar-web/src/main/js/components/upgrade/SystemUpgradeForm.tsx index 73739e28939..f6f9dafa673 100644 --- a/server/sonar-web/src/main/js/components/upgrade/SystemUpgradeForm.tsx +++ b/server/sonar-web/src/main/js/components/upgrade/SystemUpgradeForm.tsx @@ -18,13 +18,15 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { Button, ButtonVariety, LinkStandalone, Modal } from '@sonarsource/echoes-react'; import { filter, flatMap, isEmpty, negate } from 'lodash'; -import { FlagMessage, Link, Modal } from '~design-system'; +import { FlagMessage } from '~design-system'; import { useAppState } from '../../app/components/app-state/withAppStateContext'; import { BANNER_VARIANT } from '../../app/components/update-notification/helpers'; import { translate } from '../../helpers/l10n'; +import { EditionKey } from '../../types/editions'; import { SystemUpgrade } from '../../types/system'; -import SystemUpgradeItem from './SystemUpgradeItem'; +import { SystemUpgradeItem } from './SystemUpgradeItem'; import { SYSTEM_VERSION_REGEXP, UpdateUseCase } from './utils'; interface Props { @@ -34,15 +36,20 @@ interface Props { updateUseCase: UpdateUseCase; } -export default function SystemUpgradeForm(props: Readonly<Props>) { +export function SystemUpgradeForm(props: Readonly<Props>) { const appState = useAppState(); + const { latestLTA, onClose, updateUseCase, systemUpgrades } = props; + const isCommunityBuildRunning = appState.edition === EditionKey.community; + let systemUpgradesWithPatch: SystemUpgrade[][] = []; + const alertVariant = updateUseCase !== UpdateUseCase.NewVersion ? BANNER_VARIANT[updateUseCase] : undefined; - const header = translate('system.system_upgrade'); + const parsedVersion = SYSTEM_VERSION_REGEXP.exec(appState.version); + let patches: SystemUpgrade[] = []; if (updateUseCase === UpdateUseCase.NewPatch && parsedVersion !== null) { @@ -52,6 +59,7 @@ export default function SystemUpgradeForm(props: Readonly<Props>) { patches = flatMap(systemUpgrades, (upgrades) => filter(upgrades, (upgrade) => upgrade.version.startsWith(majoMinorVersion)), ); + systemUpgradesWithPatch = systemUpgrades .map((upgrades) => upgrades.filter((upgrade) => !upgrade.version.startsWith(majoMinorVersion)), @@ -65,6 +73,7 @@ export default function SystemUpgradeForm(props: Readonly<Props>) { for (const upgrades of systemUpgrades) { if (untilLTA === false) { systemUpgradesWithPatch.push(upgrades); + untilLTA = upgrades.some( (upgrade) => latestLTA !== undefined && upgrade.version.startsWith(latestLTA), ); @@ -74,15 +83,14 @@ export default function SystemUpgradeForm(props: Readonly<Props>) { return ( <Modal - headerTitle={header} - onClose={onClose} - body={ - <> + content={ + <div className="sw-mt-4"> {alertVariant && ( <FlagMessage variant={alertVariant} className={`it__upgrade-alert-${updateUseCase}`}> {translate('admin_notification.update', updateUseCase)} </FlagMessage> )} + {systemUpgradesWithPatch.map((upgrades) => ( <SystemUpgradeItem edition={appState.edition} @@ -94,14 +102,39 @@ export default function SystemUpgradeForm(props: Readonly<Props>) { )} /> ))} - </> + </div> } + {...(isCommunityBuildRunning && { + description: translate( + 'admin_notification.update.new_sqs_version_when_running_sqcb.upgrade', + ), + })} + isOpen + onOpenChange={(isOpen) => { + if (!isOpen) { + onClose(); + } + }} primaryButton={ - <Link to="https://www.sonarsource.com/products/sonarqube/downloads/?referrer=sonarqube"> - {translate('system.see_sonarqube_downloads')} - </Link> + !isCommunityBuildRunning && ( + <LinkStandalone + className="sw-mr-8" + to="https://www.sonarsource.com/products/sonarqube/downloads/?referrer=sonarqube" + > + {translate('system.see_sonarqube_downloads')} + </LinkStandalone> + ) + } + secondaryButton={ + <Button onClick={onClose} variety={ButtonVariety.Default}> + {translate('cancel')} + </Button> } - secondaryButtonLabel={translate('cancel')} + title={translate( + isCommunityBuildRunning + ? 'admin_notification.update.new_sqs_version_when_running_sqcb.modal' + : 'system.system_upgrade', + )} /> ); } diff --git a/server/sonar-web/src/main/js/components/upgrade/SystemUpgradeItem.tsx b/server/sonar-web/src/main/js/components/upgrade/SystemUpgradeItem.tsx index bc187305969..566a8177b6c 100644 --- a/server/sonar-web/src/main/js/components/upgrade/SystemUpgradeItem.tsx +++ b/server/sonar-web/src/main/js/components/upgrade/SystemUpgradeItem.tsx @@ -18,8 +18,10 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { Button, ButtonVariety, LinkStandalone } from '@sonarsource/echoes-react'; import { FormattedMessage } from 'react-intl'; -import { DownloadButton, Link, SubHeading } from '~design-system'; +import { DownloadButton, SubHeading } from '~design-system'; +import { useAppState } from '../../app/components/app-state/withAppStateContext'; import { DocLink } from '../../helpers/doc-links'; import { getEdition, @@ -28,7 +30,7 @@ import { } from '../../helpers/editions'; import { translate, translateWithParameters } from '../../helpers/l10n'; import { EditionKey } from '../../types/editions'; -import { SystemUpgrade } from '../../types/system'; +import { ProductName, SystemUpgrade } from '../../types/system'; import DocumentationLink from '../common/DocumentationLink'; import DateFormatter from '../intl/DateFormatter'; import SystemUpgradeIntermediate from './SystemUpgradeIntermediate'; @@ -40,14 +42,20 @@ export interface SystemUpgradeItemProps { systemUpgrades: SystemUpgrade[]; } -export default function SystemUpgradeItem(props: SystemUpgradeItemProps) { +export function SystemUpgradeItem(props: Readonly<SystemUpgradeItemProps>) { + const appState = useAppState(); + const { edition, isPatch, isLTAVersion, systemUpgrades } = props; + + const isCommunityBuildRunning = appState.edition === EditionKey.community; + const lastUpgrade = systemUpgrades[0]; - const downloadUrl = getEditionDownloadUrl( - getEdition(edition || EditionKey.community), - lastUpgrade, - ); + + const downloadUrl = + getEditionDownloadUrl(getEdition(edition ?? EditionKey.community), lastUpgrade) ?? ''; + let header = translate('system.latest_version'); + if (isLTAVersion) { header = translate('system.lta_version'); } else if (isPatch) { @@ -58,46 +66,78 @@ export default function SystemUpgradeItem(props: SystemUpgradeItemProps) { <div className="system-upgrade-version it__upgrade-list-item"> <SubHeading as="h3"> <strong>{header}</strong> + {!isPatch && ( - <Link + <LinkStandalone className="sw-ml-2" to="https://www.sonarsource.com/products/sonarqube/whats-new/?referrer=sonarqube" > {translate('system.see_whats_new')} - </Link> + </LinkStandalone> )} </SubHeading> + <p> <FormattedMessage - defaultMessage={translate('system.version_is_availble')} id="system.version_is_availble" - values={{ version: <b>SonarQube {lastUpgrade.version}</b> }} + values={{ + version: ( + <b> + {`${ProductName.SonarQubeServer} ${ + isCommunityBuildRunning + ? lastUpgrade.version.split('.').join(' Release ') + : lastUpgrade.version + }`} + </b> + ), + }} /> </p> + <p className="sw-mt-2">{lastUpgrade.description}</p> + <div className="sw-mt-4"> - {lastUpgrade.releaseDate && ( + {lastUpgrade.releaseDate !== undefined && ( <DateFormatter date={lastUpgrade.releaseDate} long> {(formattedDate) => ( <span>{translateWithParameters('system.released_x', formattedDate)}</span> )} </DateFormatter> )} - {lastUpgrade.changeLogUrl && ( - <Link className="sw-ml-2" to={lastUpgrade.changeLogUrl}> + + {lastUpgrade.changeLogUrl !== undefined && ( + <LinkStandalone className="sw-ml-2" to={lastUpgrade.changeLogUrl}> {translate('system.release_notes')} - </Link> + </LinkStandalone> )} </div> + <SystemUpgradeIntermediate className="sw-mt-2" upgrades={systemUpgrades.slice(1)} /> + <div className="sw-mt-4"> - <DownloadButton download={getEditionDownloadFilename(downloadUrl)} href={downloadUrl}> - {translateWithParameters('system.download_x', lastUpgrade.version)} - </DownloadButton> + {isCommunityBuildRunning ? ( + <Button + // WARNING! A button acting as a link is bad a11y. We should replace this with a + // Call To Action (CTA) component from Echoes once it becomes available. + onClick={() => { + window.location.href = 'https://www.sonarsource.com/plans-and-pricing/sonarqube/'; + }} + // + variety={ButtonVariety.Primary} + > + {translate('learn_more')} + </Button> + ) : ( + <> + <DownloadButton download={getEditionDownloadFilename(downloadUrl)} href={downloadUrl}> + {translateWithParameters('system.download_x', lastUpgrade.version)} + </DownloadButton> - <DocumentationLink className="sw-ml-2" to={DocLink.ServerUpgradeRoadmap}> - {translate('system.how_to_upgrade')} - </DocumentationLink> + <DocumentationLink className="sw-ml-4" to={DocLink.ServerUpgradeRoadmap}> + {translate('system.how_to_upgrade')} + </DocumentationLink> + </> + )} </div> </div> ); diff --git a/server/sonar-web/src/main/js/components/upgrade/__tests__/SystemUpgrade-test.tsx b/server/sonar-web/src/main/js/components/upgrade/__tests__/SystemUpgrade-test.tsx index dd19f764342..cd8e8d52ada 100644 --- a/server/sonar-web/src/main/js/components/upgrade/__tests__/SystemUpgrade-test.tsx +++ b/server/sonar-web/src/main/js/components/upgrade/__tests__/SystemUpgrade-test.tsx @@ -23,7 +23,8 @@ import React from 'react'; import { byRole, byText } from '~sonar-aligned/helpers/testSelector'; import { mockAppState } from '../../../helpers/testMocks'; import { renderComponent } from '../../../helpers/testReactTestingUtils'; -import SystemUpgradeButton from '../SystemUpgradeButton'; +import { EditionKey } from '../../../types/editions'; +import { SystemUpgradeButton } from '../SystemUpgradeButton'; import { UpdateUseCase } from '../utils'; const ui = { @@ -85,6 +86,6 @@ function renderSystemUpgradeButton( {...props} />, '', - { appState: mockAppState({ version }) }, + { appState: mockAppState({ edition: EditionKey.developer, version }) }, ); } diff --git a/server/sonar-web/src/main/js/design-system/sonar-aligned/components/buttons/ButtonSecondary.tsx b/server/sonar-web/src/main/js/design-system/sonar-aligned/components/buttons/ButtonSecondary.tsx index 0a9945f10f0..971f3554e98 100644 --- a/server/sonar-web/src/main/js/design-system/sonar-aligned/components/buttons/ButtonSecondary.tsx +++ b/server/sonar-web/src/main/js/design-system/sonar-aligned/components/buttons/ButtonSecondary.tsx @@ -24,7 +24,7 @@ import { Button } from './Button'; /** * @deprecated Use Button from Echoes instead without the `variety` prop set, - * this is a the default look and feel of the button. + * this is the default look and feel of the button. * * Some of the props have changed or been renamed: * - `blurAfterClick` is now `shouldBlurAfterClick` diff --git a/server/sonar-web/src/main/js/helpers/l10nBundle.ts b/server/sonar-web/src/main/js/helpers/l10nBundle.ts index d34b1f479bb..8c1ea4b2a1f 100644 --- a/server/sonar-web/src/main/js/helpers/l10nBundle.ts +++ b/server/sonar-web/src/main/js/helpers/l10nBundle.ts @@ -23,6 +23,7 @@ import { fetchL10nBundle } from '../api/l10n'; import { AppState } from '../types/appstate'; import { EditionKey } from '../types/editions'; import { L10nBundle, L10nBundleRequestParams } from '../types/l10nBundle'; +import { ProductName } from '../types/system'; import { Dict } from '../types/types'; import { toISO8601WithOffsetString } from './dates'; import { isDefined } from './types'; @@ -127,8 +128,8 @@ function persistL10nBundleInCache(bundle: L10nBundle) { function getProductName(appState?: AppState) { if (isDefined(appState?.edition)) { return appState?.edition === EditionKey.community - ? 'SonarQube Community Build' - : 'SonarQube Server'; + ? ProductName.SonarQubeCommunityBuild + : ProductName.SonarQubeServer; } return 'SonarQube'; 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 index 554245568a1..80466d15fce 100644 --- a/server/sonar-web/src/main/js/helpers/mocks/system-upgrades.ts +++ b/server/sonar-web/src/main/js/helpers/mocks/system-upgrades.ts @@ -18,15 +18,15 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { SystemUpgrade } from '../../types/system'; +import { ProductNameForUpgrade, SystemUpgrade } from '../../types/system'; -export function mockSystemUpgrade(override: Partial<SystemUpgrade> = {}): SystemUpgrade { - return { - version: '5.6.7', - description: 'Version 5.6.7 description', - releaseDate: '2017-03-01', +export const mockSystemUpgrade = (override: Partial<SystemUpgrade> = {}) => + ({ changeLogUrl: 'changelogurl', + description: 'Version 5.6.7 description', downloadUrl: 'downloadurl', + product: ProductNameForUpgrade.SonarQubeServer, + releaseDate: '2017-03-01', + version: '5.6.7', ...override, - }; -} + }) as SystemUpgrade; diff --git a/server/sonar-web/src/main/js/types/system.ts b/server/sonar-web/src/main/js/types/system.ts index a91b1bfe4a6..64f57531325 100644 --- a/server/sonar-web/src/main/js/types/system.ts +++ b/server/sonar-web/src/main/js/types/system.ts @@ -25,9 +25,20 @@ export interface SystemUpgradeDownloadUrls { downloadUrl: string; } +export enum ProductName { + SonarQubeCommunityBuild = 'SonarQube Community Build', + SonarQubeServer = 'SonarQube Server', +} + +export enum ProductNameForUpgrade { + SonarQubeCommunityBuild = 'SONARQUBE_COMMUNITY_BUILD', + SonarQubeServer = 'SONARQUBE_SERVER', +} + export interface SystemUpgrade extends SystemUpgradeDownloadUrls { changeLogUrl?: string; description?: string; + product?: ProductNameForUpgrade; releaseDate?: string; version: string; } |