aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorMathieu Suen <mathieu.suen@sonarsource.com>2021-10-14 18:06:41 +0200
committersonartech <sonartech@sonarsource.com>2021-10-21 20:04:01 +0000
commit667542fa30857ee17c1317e19d67f62e62e44b44 (patch)
tree921b5837c2e68b86c2c4a5f9e35662bb007fc755 /server
parent8ce408e17dd34e2401bd34394d5b21e8b790f836 (diff)
downloadsonarqube-667542fa30857ee17c1317e19d67f62e62e44b44.tar.gz
sonarqube-667542fa30857ee17c1317e19d67f62e62e44b44.zip
SONAR-15507 Add a prompt to warn when SonarQube need an update
Diffstat (limited to 'server')
-rw-r--r--server/sonar-web/src/main/js/api/system.ts1
-rw-r--r--server/sonar-web/src/main/js/app/components/GlobalContainer.tsx2
-rw-r--r--server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalContainer-test.tsx.snap1
-rw-r--r--server/sonar-web/src/main/js/app/components/update-notification/UpdateNotification.css29
-rw-r--r--server/sonar-web/src/main/js/app/components/update-notification/UpdateNotification.tsx241
-rw-r--r--server/sonar-web/src/main/js/app/components/update-notification/__tests__/UpdateNotification-test.tsx153
-rw-r--r--server/sonar-web/src/main/js/apps/system/__tests__/utils-test.ts49
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/system-upgrade/SystemUpgradeNotif.tsx29
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/system-upgrade/__tests__/SystemUpgradeNotif-test.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/system-upgrade/__tests__/__snapshots__/SystemUpgradeNotif-test.tsx.snap65
-rw-r--r--server/sonar-web/src/main/js/apps/system/utils.ts17
-rw-r--r--server/sonar-web/src/main/js/components/ui/Alert.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/ui/DismissableAlert.tsx32
-rw-r--r--server/sonar-web/src/main/js/components/ui/__tests__/__snapshots__/DismissableAlert-test.tsx.snap88
-rw-r--r--server/sonar-web/src/main/js/components/upgrade/SystemUpgradeButton.tsx63
-rw-r--r--server/sonar-web/src/main/js/components/upgrade/SystemUpgradeForm.tsx (renamed from server/sonar-web/src/main/js/apps/system/components/system-upgrade/SystemUpgradeForm.tsx)12
-rw-r--r--server/sonar-web/src/main/js/components/upgrade/SystemUpgradeIntermediate.tsx (renamed from server/sonar-web/src/main/js/apps/system/components/system-upgrade/SystemUpgradeIntermediate.tsx)10
-rw-r--r--server/sonar-web/src/main/js/components/upgrade/SystemUpgradeItem.tsx (renamed from server/sonar-web/src/main/js/apps/system/components/system-upgrade/SystemUpgradeItem.tsx)10
-rw-r--r--server/sonar-web/src/main/js/components/upgrade/__tests__/SystemUpgradeButton-test.tsx38
-rw-r--r--server/sonar-web/src/main/js/components/upgrade/__tests__/SystemUpgradeForm-test.tsx (renamed from server/sonar-web/src/main/js/apps/system/components/system-upgrade/__tests__/SystemUpgradeForm-test.tsx)2
-rw-r--r--server/sonar-web/src/main/js/components/upgrade/__tests__/SystemUpgradeIntermediate-test.tsx (renamed from server/sonar-web/src/main/js/apps/system/components/system-upgrade/__tests__/SystemUpgradeIntermediate-test.tsx)2
-rw-r--r--server/sonar-web/src/main/js/components/upgrade/__tests__/SystemUpgradeItem-test.tsx (renamed from server/sonar-web/src/main/js/apps/system/components/system-upgrade/__tests__/SystemUpgradeItem-test.tsx)2
-rw-r--r--server/sonar-web/src/main/js/components/upgrade/__tests__/__snapshots__/SystemUpgradeButton-test.tsx.snap12
-rw-r--r--server/sonar-web/src/main/js/components/upgrade/__tests__/__snapshots__/SystemUpgradeForm-test.tsx.snap (renamed from server/sonar-web/src/main/js/apps/system/components/system-upgrade/__tests__/__snapshots__/SystemUpgradeForm-test.tsx.snap)0
-rw-r--r--server/sonar-web/src/main/js/components/upgrade/__tests__/__snapshots__/SystemUpgradeIntermediate-test.tsx.snap (renamed from server/sonar-web/src/main/js/apps/system/components/system-upgrade/__tests__/__snapshots__/SystemUpgradeIntermediate-test.tsx.snap)0
-rw-r--r--server/sonar-web/src/main/js/components/upgrade/__tests__/__snapshots__/SystemUpgradeItem-test.tsx.snap (renamed from server/sonar-web/src/main/js/apps/system/components/system-upgrade/__tests__/__snapshots__/SystemUpgradeItem-test.tsx.snap)0
-rw-r--r--server/sonar-web/src/main/js/components/upgrade/__tests__/utils-test.ts69
-rw-r--r--server/sonar-web/src/main/js/components/upgrade/utils.ts35
-rw-r--r--server/sonar-web/src/main/js/helpers/mocks/system-upgrades.ts31
29 files changed, 784 insertions, 216 deletions
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) {
<GlobalNav location={props.location} />
<GlobalMessagesContainer />
<IndexationNotification />
+ <UpdateNotification />
{props.children}
</IndexationContextProvider>
</Workspace>
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`] = `
/>
<Connect(GlobalMessages) />
<Connect(withCurrentUser(withIndexationContext(IndexationNotification))) />
+ <Connect(withCurrentUser(Connect(withAppState(UpdateNotification)))) />
<ChildComponent />
</Connect(withAppState(IndexationContextProvider))>
</Workspace>
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<SystemUpgrade[]>;
+};
+
+enum UseCase {
+ NewMinorVersion = 'new_minor_version',
+ NewPatch = 'new_patch',
+ PreLTS = 'pre_lts',
+ PreviousLTS = 'previous_lts'
+}
+
+const MAP_VARIANT: T.Dict<AlertVariant> = {
+ [UseCase.NewMinorVersion]: 'info',
+ [UseCase.NewPatch]: 'warning',
+ [UseCase.PreLTS]: 'warning',
+ [UseCase.PreviousLTS]: 'error'
+};
+
+interface Props {
+ appState: Pick<T.AppState, 'version'>;
+ currentUser: T.CurrentUser;
+}
+
+interface State {
+ dismissKey: string;
+ useCase: UseCase;
+ systemUpgrades: SystemUpgrade[];
+ canSeeNotification: boolean;
+}
+
+export class UpdateNotification extends React.PureComponent<Props, State> {
+ 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 (
+ <DismissableAlert
+ alertKey={dismissKey}
+ variant={MAP_VARIANT[useCase]}
+ className="promote-update-notification">
+ {translate('admin_notification.update', useCase)}
+ <SystemUpgradeButton systemUpgrades={systemUpgrades} />
+ </DismissableAlert>
+ );
+ }
+}
+
+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<UpdateNotification['props']> = {}) {
+ return shallow(
+ <UpdateNotification appState={mockAppState()} currentUser={mockCurrentUser()} {...props} />
+ );
+}
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> {
<div className="page-notifs">
<Alert variant="info">
{translate('system.new_version_available')}
- <Button className="spacer-left" onClick={this.handleOpenSystemUpgradeForm}>
- {translate('learn_more')}
- </Button>
+ <SystemUpgradeButton systemUpgrades={systemUpgrades} />
</Alert>
- {this.state.openSystemUpgradeForm && (
- <SystemUpgradeForm
- onClose={this.handleCloseSystemUpgradeForm}
- systemUpgrades={systemUpgrades}
- />
- )}
</div>
);
}
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
- <Button
- className="spacer-left"
- onClick={[Function]}
- >
- learn_more
- </Button>
- </Alert>
-</div>
-`;
-
-exports[`should render correctly 2`] = `
-<div
- className="page-notifs"
->
- <Alert
- variant="info"
- >
- system.new_version_available
- <Button
- className="spacer-left"
- onClick={[Function]}
- >
- learn_more
- </Button>
- </Alert>
- <Connect(withAppState(SystemUpgradeForm))
- onClose={[Function]}
- systemUpgrades={
- Array [
+ <SystemUpgradeButton
+ systemUpgrades={
Array [
Object {
"changeLogUrl": "changelogurl",
- "description": "Version 6.4 description",
+ "description": "Version 5.6.7 description",
"downloadUrl": "downloadurl",
"plugins": Object {},
- "releaseDate": "2017-06-02",
- "version": "6.4",
+ "releaseDate": "2017-03-01",
+ "version": "5.6.7",
},
Object {
"changeLogUrl": "changelogurl",
- "description": "Version 6.3 description",
+ "description": "Version 5.6.5 description",
"downloadUrl": "downloadurl",
"plugins": Object {},
- "releaseDate": "2017-05-02",
- "version": "6.3",
+ "releaseDate": "2017-03-01",
+ "version": "5.6.5",
},
- ],
- Array [
Object {
"changeLogUrl": "changelogurl",
- "description": "Version 5.6.7 description",
+ "description": "Version 6.3 description",
"downloadUrl": "downloadurl",
"plugins": Object {},
- "releaseDate": "2017-03-01",
- "version": "5.6.7",
+ "releaseDate": "2017-05-02",
+ "version": "6.3",
},
Object {
"changeLogUrl": "changelogurl",
@@ -74,15 +45,15 @@ exports[`should render correctly 2`] = `
},
Object {
"changeLogUrl": "changelogurl",
- "description": "Version 5.6.5 description",
+ "description": "Version 6.4 description",
"downloadUrl": "downloadurl",
"plugins": Object {},
- "releaseDate": "2017-03-01",
- "version": "5.6.5",
+ "releaseDate": "2017-06-02",
+ "version": "6.4",
},
- ],
- ]
- }
- />
+ ]
+ }
+ />
+ </Alert>
</div>
`;
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 : (
- <Alert
- className={classNames(`dismissable-alert-${display}`, className)}
- display={display}
- variant={variant}>
- <div className="display-flex-center dismissable-alert-content">
- <div className="flex-1">{children}</div>
- <ButtonIcon
- aria-label={translate('alert.dismiss')}
- onClick={() => {
- hideAlert();
- setShow(false);
- }}>
- <ClearIcon size={12} thin={true} />
- </ButtonIcon>
- </div>
- </Alert>
+ <div className={classNames('dismissable-alert-wrapper', className)}>
+ <Alert className={`dismissable-alert-${display}`} display={display} variant={variant}>
+ <div className="display-flex-center dismissable-alert-content">
+ <div className="flex-1">{children}</div>
+ <ButtonIcon
+ aria-label={translate('alert.dismiss')}
+ onClick={() => {
+ hideAlert();
+ setShow(false);
+ }}>
+ <ClearIcon size={12} thin={true} />
+ </ButtonIcon>
+ </div>
+ </Alert>
+ </div>
);
}
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`] = `
-<Alert
- className="dismissable-alert-banner"
- display="banner"
- variant="info"
+<div
+ className="dismissable-alert-wrapper"
>
- <div
- className="display-flex-center dismissable-alert-content"
+ <Alert
+ className="dismissable-alert-banner"
+ display="banner"
+ variant="info"
>
<div
- className="flex-1"
+ className="display-flex-center dismissable-alert-content"
>
- <div>
- My content
+ <div
+ className="flex-1"
+ >
+ <div>
+ My content
+ </div>
</div>
+ <ButtonIcon
+ aria-label="alert.dismiss"
+ onClick={[Function]}
+ >
+ <ClearIcon
+ size={12}
+ thin={true}
+ />
+ </ButtonIcon>
</div>
- <ButtonIcon
- aria-label="alert.dismiss"
- onClick={[Function]}
- >
- <ClearIcon
- size={12}
- thin={true}
- />
- </ButtonIcon>
- </div>
-</Alert>
+ </Alert>
+</div>
`;
exports[`should render correctly with a non-default display 1`] = `
-<Alert
- className="dismissable-alert-block"
- display="block"
- variant="info"
+<div
+ className="dismissable-alert-wrapper"
>
- <div
- className="display-flex-center dismissable-alert-content"
+ <Alert
+ className="dismissable-alert-block"
+ display="block"
+ variant="info"
>
<div
- className="flex-1"
+ className="display-flex-center dismissable-alert-content"
>
- <div>
- My content
+ <div
+ className="flex-1"
+ >
+ <div>
+ My content
+ </div>
</div>
+ <ButtonIcon
+ aria-label="alert.dismiss"
+ onClick={[Function]}
+ >
+ <ClearIcon
+ size={12}
+ thin={true}
+ />
+ </ButtonIcon>
</div>
- <ButtonIcon
- aria-label="alert.dismiss"
- onClick={[Function]}
- >
- <ClearIcon
- size={12}
- thin={true}
- />
- </ButtonIcon>
- </div>
-</Alert>
+ </Alert>
+</div>
`;
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<Props, State> {
+ state: State = { openSystemUpgradeForm: false };
+
+ handleOpenSystemUpgradeForm = () => {
+ this.setState({ openSystemUpgradeForm: true });
+ };
+
+ handleCloseSystemUpgradeForm = () => {
+ this.setState({ openSystemUpgradeForm: false });
+ };
+
+ render() {
+ const { systemUpgrades } = this.props;
+ const { openSystemUpgradeForm } = this.state;
+ return (
+ <>
+ <Button className="spacer-left" onClick={this.handleOpenSystemUpgradeForm}>
+ {translate('learn_more')}
+ </Button>
+ {openSystemUpgradeForm && (
+ <SystemUpgradeForm
+ onClose={this.handleCloseSystemUpgradeForm}
+ systemUpgrades={groupUpgrades(sortUpgrades(systemUpgrades))}
+ />
+ )}
+ </>
+ );
+ }
+}
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
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
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
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<SystemUpgradeButton['props']> = {}) {
+ return shallow<SystemUpgradeButton['props']>(
+ <SystemUpgradeButton systemUpgrades={[]} {...props} />
+ );
+}
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
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
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
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`] = `
+<Fragment>
+ <Button
+ className="spacer-left"
+ onClick={[Function]}
+ >
+ learn_more
+ </Button>
+</Fragment>
+`;
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
index e1c80419910..e1c80419910 100644
--- 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
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
index 763ab35301a..763ab35301a 100644
--- 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
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
index 51d35d94dc6..51d35d94dc6 100644
--- 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
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>): SystemUpgrade {
+ return {
+ version: '5.6.7',
+ description: 'Version 5.6.7 description',
+ releaseDate: '2017-03-01',
+ changeLogUrl: 'changelogurl',
+ downloadUrl: 'downloadurl',
+ ...override
+ };
+}