@@ -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'); |
@@ -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> |
@@ -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> |
@@ -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); | |||
} |
@@ -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)); |
@@ -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} /> | |||
); | |||
} |
@@ -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); |
@@ -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> | |||
); | |||
} |
@@ -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 () => { |
@@ -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> | |||
`; |
@@ -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]); | |||
} |
@@ -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; |
@@ -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> | |||
); | |||
} |
@@ -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> | |||
`; |
@@ -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))} | |||
/> | |||
)} | |||
</> | |||
); | |||
} | |||
} |
@@ -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 { |
@@ -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; |
@@ -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 { |
@@ -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} /> | |||
); | |||
} |
@@ -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 = [ |
@@ -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 = [ |
@@ -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', () => { |
@@ -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> | |||
`; |
@@ -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' }] | |||
]); | |||
}); | |||
}); |
@@ -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]); | |||
} |
@@ -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 | |||
}; | |||
} |
@@ -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 |