Просмотр исходного кода

SONAR-15507 Add a prompt to warn when SonarQube need an update

tags/9.2.0.49834
Mathieu Suen 2 лет назад
Родитель
Сommit
667542fa30
30 измененных файлов: 795 добавлений и 216 удалений
  1. 1
    0
      server/sonar-web/src/main/js/api/system.ts
  2. 2
    0
      server/sonar-web/src/main/js/app/components/GlobalContainer.tsx
  3. 1
    0
      server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalContainer-test.tsx.snap
  4. 29
    0
      server/sonar-web/src/main/js/app/components/update-notification/UpdateNotification.css
  5. 241
    0
      server/sonar-web/src/main/js/app/components/update-notification/UpdateNotification.tsx
  6. 153
    0
      server/sonar-web/src/main/js/app/components/update-notification/__tests__/UpdateNotification-test.tsx
  7. 0
    49
      server/sonar-web/src/main/js/apps/system/__tests__/utils-test.ts
  8. 5
    24
      server/sonar-web/src/main/js/apps/system/components/system-upgrade/SystemUpgradeNotif.tsx
  9. 1
    4
      server/sonar-web/src/main/js/apps/system/components/system-upgrade/__tests__/SystemUpgradeNotif-test.tsx
  10. 18
    47
      server/sonar-web/src/main/js/apps/system/components/system-upgrade/__tests__/__snapshots__/SystemUpgradeNotif-test.tsx.snap
  11. 1
    16
      server/sonar-web/src/main/js/apps/system/utils.ts
  12. 1
    1
      server/sonar-web/src/main/js/components/ui/Alert.tsx
  13. 16
    16
      server/sonar-web/src/main/js/components/ui/DismissableAlert.tsx
  14. 48
    40
      server/sonar-web/src/main/js/components/ui/__tests__/__snapshots__/DismissableAlert-test.tsx.snap
  15. 63
    0
      server/sonar-web/src/main/js/components/upgrade/SystemUpgradeButton.tsx
  16. 6
    6
      server/sonar-web/src/main/js/components/upgrade/SystemUpgradeForm.tsx
  17. 5
    5
      server/sonar-web/src/main/js/components/upgrade/SystemUpgradeIntermediate.tsx
  18. 5
    5
      server/sonar-web/src/main/js/components/upgrade/SystemUpgradeItem.tsx
  19. 38
    0
      server/sonar-web/src/main/js/components/upgrade/__tests__/SystemUpgradeButton-test.tsx
  20. 1
    1
      server/sonar-web/src/main/js/components/upgrade/__tests__/SystemUpgradeForm-test.tsx
  21. 1
    1
      server/sonar-web/src/main/js/components/upgrade/__tests__/SystemUpgradeIntermediate-test.tsx
  22. 1
    1
      server/sonar-web/src/main/js/components/upgrade/__tests__/SystemUpgradeItem-test.tsx
  23. 12
    0
      server/sonar-web/src/main/js/components/upgrade/__tests__/__snapshots__/SystemUpgradeButton-test.tsx.snap
  24. 0
    0
      server/sonar-web/src/main/js/components/upgrade/__tests__/__snapshots__/SystemUpgradeForm-test.tsx.snap
  25. 0
    0
      server/sonar-web/src/main/js/components/upgrade/__tests__/__snapshots__/SystemUpgradeIntermediate-test.tsx.snap
  26. 0
    0
      server/sonar-web/src/main/js/components/upgrade/__tests__/__snapshots__/SystemUpgradeItem-test.tsx.snap
  27. 69
    0
      server/sonar-web/src/main/js/components/upgrade/__tests__/utils-test.ts
  28. 35
    0
      server/sonar-web/src/main/js/components/upgrade/utils.ts
  29. 31
    0
      server/sonar-web/src/main/js/helpers/mocks/system-upgrades.ts
  30. 11
    0
      sonar-core/src/main/resources/org/sonar/l10n/core.properties

+ 1
- 0
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');

+ 2
- 0
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>

+ 1
- 0
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>

+ 29
- 0
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);
}

+ 241
- 0
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));

+ 153
- 0
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} />
);
}

+ 0
- 49
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);

+ 5
- 24
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>
);
}

+ 1
- 4
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 () => {

+ 18
- 47
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>
`;

+ 1
- 16
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]);
}

+ 1
- 1
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;

+ 16
- 16
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>
);
}

+ 48
- 40
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>
`;

+ 63
- 0
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))}
/>
)}
</>
);
}
}

server/sonar-web/src/main/js/apps/system/components/system-upgrade/SystemUpgradeForm.tsx → 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 {

server/sonar-web/src/main/js/apps/system/components/system-upgrade/SystemUpgradeIntermediate.tsx → 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;

server/sonar-web/src/main/js/apps/system/components/system-upgrade/SystemUpgradeItem.tsx → 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 {

+ 38
- 0
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} />
);
}

server/sonar-web/src/main/js/apps/system/components/system-upgrade/__tests__/SystemUpgradeForm-test.tsx → 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 = [

server/sonar-web/src/main/js/apps/system/components/system-upgrade/__tests__/SystemUpgradeIntermediate-test.tsx → 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 = [

server/sonar-web/src/main/js/apps/system/components/system-upgrade/__tests__/SystemUpgradeItem-test.tsx → 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', () => {

+ 12
- 0
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>
`;

server/sonar-web/src/main/js/apps/system/components/system-upgrade/__tests__/__snapshots__/SystemUpgradeForm-test.tsx.snap → server/sonar-web/src/main/js/components/upgrade/__tests__/__snapshots__/SystemUpgradeForm-test.tsx.snap Просмотреть файл


server/sonar-web/src/main/js/apps/system/components/system-upgrade/__tests__/__snapshots__/SystemUpgradeIntermediate-test.tsx.snap → server/sonar-web/src/main/js/components/upgrade/__tests__/__snapshots__/SystemUpgradeIntermediate-test.tsx.snap Просмотреть файл


server/sonar-web/src/main/js/apps/system/components/system-upgrade/__tests__/__snapshots__/SystemUpgradeItem-test.tsx.snap → server/sonar-web/src/main/js/components/upgrade/__tests__/__snapshots__/SystemUpgradeItem-test.tsx.snap Просмотреть файл


+ 69
- 0
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' }]
]);
});
});

+ 35
- 0
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]);
}

+ 31
- 0
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
};
}

+ 11
- 0
sonar-core/src/main/resources/org/sonar/l10n/core.properties Просмотреть файл

@@ -451,6 +451,17 @@ qualifier.description.VW=Potentially multi-level, management-oriented overview a
qualifier.description.SVW=Potentially multi-level, management-oriented overview aggregation.
qualifier.description.APP=Single-level aggregation with a technical focus and a project-like homepage.


#------------------------------------------------------------------------------
#
# Admin notification
#
#------------------------------------------------------------------------------
admin_notification.update.new_minor_version=There’s a new version of SonarQube available. Update to enjoy the latest updates and features.
admin_notification.update.new_patch=There’s an update available for your SonarQube instance. Please update to make sure you benefit from the latest security and bug fixes.
admin_notification.update.pre_lts=You’re running a version of SonarQube that has reached end of life. Please upgrade to a supported version at your earliest convenience.
admin_notification.update.previous_lts=You’re running a version of SonarQube that is past end of life. Please upgrade to a supported version immediately.

#------------------------------------------------------------------------------
#
# PROJECT LINKS

Загрузка…
Отмена
Сохранить