Sfoglia il codice sorgente

SONAR-21909 Update Banner and Dialog with new LTA wording

tags/10.5.0.89998
stanislavh 2 mesi fa
parent
commit
fde088bc4d

+ 2
- 1
server/sonar-web/src/main/js/api/system.ts Vedi File



export function getSystemUpgrades(): Promise<{ export function getSystemUpgrades(): Promise<{
upgrades: SystemUpgrade[]; upgrades: SystemUpgrade[];
latestLTS: string;
latestLTA: string;
installedVersionActive: boolean;
updateCenterRefresh: string; updateCenterRefresh: string;
}> { }> {
return getJSON('/api/system/upgrades'); return getJSON('/api/system/upgrades');

+ 4
- 0
server/sonar-web/src/main/js/app/components/app-state/withAppStateContext.tsx Vedi File

} }
}; };
} }

export function useAppState() {
return React.useContext(AppStateContext);
}

+ 24
- 33
server/sonar-web/src/main/js/app/components/update-notification/UpdateNotification.tsx Vedi File

* along with this program; if not, write to the Free Software Foundation, * along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
import { Banner, Variant } from 'design-system';
import { Banner } from 'design-system';
import { groupBy, isEmpty, mapValues } from 'lodash'; import { groupBy, isEmpty, mapValues } from 'lodash';
import * as React from 'react'; import * as React from 'react';
import DismissableAlert from '../../../components/ui/DismissableAlert'; import DismissableAlert from '../../../components/ui/DismissableAlert';
import { translate } from '../../../helpers/l10n'; import { translate } from '../../../helpers/l10n';
import { hasGlobalPermission } from '../../../helpers/users'; import { hasGlobalPermission } from '../../../helpers/users';
import { useSystemUpgrades } from '../../../queries/system'; import { useSystemUpgrades } from '../../../queries/system';
import { AppState } from '../../../types/appstate';
import { Permissions } from '../../../types/permissions'; import { Permissions } from '../../../types/permissions';
import { Dict } from '../../../types/types';
import { CurrentUser, isLoggedIn } from '../../../types/users';
import withAppStateContext from '../app-state/withAppStateContext';
import withCurrentUserContext from '../current-user/withCurrentUserContext';
import { isMinorUpdate, isPatchUpdate, isPreLTSUpdate, isPreviousLTSUpdate } from './helpers';

const MAP_VARIANT: Dict<Variant> = {
[UpdateUseCase.NewMinorVersion]: 'info',
[UpdateUseCase.NewPatch]: 'warning',
[UpdateUseCase.PreLTS]: 'warning',
[UpdateUseCase.PreviousLTS]: 'error',
};
import { isLoggedIn } from '../../../types/users';
import { useAppState } from '../app-state/withAppStateContext';
import { useCurrentUser } from '../current-user/CurrentUserContext';
import { BANNER_VARIANT, isCurrentVersionLTA, isMinorUpdate, isPatchUpdate } from './helpers';


interface Props { interface Props {
dismissable: boolean;
appState: AppState;
currentUser: CurrentUser;
dismissable?: boolean;
} }


const VERSION_PARSER = /^(\d+)\.(\d+)(\.(\d+))?/; const VERSION_PARSER = /^(\d+)\.(\d+)(\.(\d+))?/;


export function UpdateNotification({ dismissable, appState, currentUser }: Readonly<Props>) {
export default function UpdateNotification({ dismissable }: Readonly<Props>) {
const appState = useAppState();
const { currentUser } = useCurrentUser();

const canUserSeeNotification = const canUserSeeNotification =
isLoggedIn(currentUser) && hasGlobalPermission(currentUser, Permissions.Admin); isLoggedIn(currentUser) && hasGlobalPermission(currentUser, Permissions.Admin);
const regExpParsedVersion = VERSION_PARSER.exec(appState.version); const regExpParsedVersion = VERSION_PARSER.exec(appState.version);

const { data } = useSystemUpgrades({ const { data } = useSystemUpgrades({
enabled: canUserSeeNotification && regExpParsedVersion !== null, enabled: canUserSeeNotification && regExpParsedVersion !== null,
}); });
return null; return null;
} }


const { upgrades, latestLTS } = data;
const { upgrades, installedVersionActive, latestLTA } = data;

const parsedVersion = regExpParsedVersion const parsedVersion = regExpParsedVersion
.slice(1) .slice(1)
.map(Number) .map(Number)
}), }),
); );


let useCase = UpdateUseCase.NewMinorVersion;
let useCase = UpdateUseCase.NewVersion;


if (isPreviousLTSUpdate(parsedVersion, latestLTS, systemUpgrades)) {
useCase = UpdateUseCase.PreviousLTS;
} else if (isPreLTSUpdate(parsedVersion, latestLTS)) {
useCase = UpdateUseCase.PreLTS;
} else if (isPatchUpdate(parsedVersion, systemUpgrades)) {
if (!installedVersionActive) {
useCase = UpdateUseCase.CurrentVersionInactive;
} else if (
isPatchUpdate(parsedVersion, systemUpgrades) &&
(isCurrentVersionLTA(parsedVersion, latestLTA) || !isMinorUpdate(parsedVersion, systemUpgrades))
) {
useCase = UpdateUseCase.NewPatch; useCase = UpdateUseCase.NewPatch;
} else if (isMinorUpdate(parsedVersion, systemUpgrades)) {
useCase = UpdateUseCase.NewMinorVersion;
} }


const latest = [...upgrades].sort( const latest = [...upgrades].sort(
return dismissable ? ( return dismissable ? (
<DismissableAlert <DismissableAlert
alertKey={dismissKey} alertKey={dismissKey}
variant={MAP_VARIANT[useCase]}
variant={BANNER_VARIANT[useCase]}
className={`it__promote-update-notification it__upgrade-prompt-${useCase}`} className={`it__promote-update-notification it__upgrade-prompt-${useCase}`}
> >
{translate('admin_notification.update', useCase)} {translate('admin_notification.update', useCase)}
<SystemUpgradeButton <SystemUpgradeButton
systemUpgrades={upgrades} systemUpgrades={upgrades}
updateUseCase={useCase} updateUseCase={useCase}
latestLTS={latestLTS}
latestLTA={latestLTA}
/> />
</DismissableAlert> </DismissableAlert>
) : ( ) : (
<Banner variant={MAP_VARIANT[useCase]} className={`it__upgrade-prompt-${useCase}`}>
<Banner variant={BANNER_VARIANT[useCase]} className={`it__upgrade-prompt-${useCase}`}>
{translate('admin_notification.update', useCase)} {translate('admin_notification.update', useCase)}
<SystemUpgradeButton <SystemUpgradeButton
systemUpgrades={upgrades} systemUpgrades={upgrades}
updateUseCase={useCase} updateUseCase={useCase}
latestLTS={latestLTS}
latestLTA={latestLTA}
/> />
</Banner> </Banner>
); );
} }

export default withCurrentUserContext(withAppStateContext(UpdateNotification));

+ 12
- 30
server/sonar-web/src/main/js/app/components/update-notification/helpers.ts Vedi File

* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */


import { Variant } from 'design-system';
import { isEmpty } from 'lodash'; import { isEmpty } from 'lodash';
import { sortUpgrades } from '../../../components/upgrade/utils';
import { UpdateUseCase, sortUpgrades } from '../../../components/upgrade/utils';
import { SystemUpgrade } from '../../../types/system'; import { SystemUpgrade } from '../../../types/system';

const MONTH_BEFOR_PREVIOUS_LTS_NOTIFICATION = 6;
import { Dict } from '../../../types/types';


type GroupedSystemUpdate = { type GroupedSystemUpdate = {
[x: string]: Record<string, SystemUpgrade[]>; [x: string]: Record<string, SystemUpgrade[]>;
}; };


export const isPreLTSUpdate = (parsedVersion: number[], latestLTS: string) => {
export const isCurrentVersionLTA = (parsedVersion: number[], latestLTS: string) => {
const [currentMajor, currentMinor] = parsedVersion; const [currentMajor, currentMinor] = parsedVersion;
const [ltsMajor, ltsMinor] = latestLTS.split('.').map(Number); const [ltsMajor, ltsMinor] = latestLTS.split('.').map(Number);
return currentMajor < ltsMajor || (currentMajor === ltsMajor && currentMinor < ltsMinor);
};

export const isPreviousLTSUpdate = (
parsedVersion: number[],
latestLTS: string,
systemUpgrades: GroupedSystemUpdate,
) => {
const [ltsMajor, ltsMinor] = latestLTS.split('.').map(Number);
let ltsOlderThan6Month = false;
const beforeLts = 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;
return currentMajor === ltsMajor && currentMinor === ltsMinor;
}; };


export const isMinorUpdate = (parsedVersion: number[], systemUpgrades: GroupedSystemUpdate) => { export const isMinorUpdate = (parsedVersion: number[], systemUpgrades: GroupedSystemUpdate) => {
export const isPatchUpdate = (parsedVersion: number[], systemUpgrades: GroupedSystemUpdate) => { export const isPatchUpdate = (parsedVersion: number[], systemUpgrades: GroupedSystemUpdate) => {
const [currentMajor, currentMinor, currentPatch] = parsedVersion; const [currentMajor, currentMinor, currentPatch] = parsedVersion;
const allMinor = systemUpgrades[currentMajor]; const allMinor = systemUpgrades[currentMajor];
const allPatch = sortUpgrades(allMinor[currentMinor] || []);
const allPatch = sortUpgrades(allMinor?.[currentMinor] ?? []);


if (!isEmpty(allPatch)) { if (!isEmpty(allPatch)) {
const [, , latestPatch] = allPatch[0].version.split('.').map(Number); const [, , latestPatch] = allPatch[0].version.split('.').map(Number);
} }
return false; return false;
}; };

export const BANNER_VARIANT: Dict<Variant> = {
[UpdateUseCase.NewVersion]: 'info',
[UpdateUseCase.CurrentVersionInactive]: 'error',
[UpdateUseCase.NewPatch]: 'warning',
};

+ 1
- 1
server/sonar-web/src/main/js/apps/system/components/SystemApp.tsx Vedi File

<Helmet defer={false} title={translate('system_info.page')} /> <Helmet defer={false} title={translate('system_info.page')} />
<PageContentFontWrapper className="sw-body-sm sw-pb-8"> <PageContentFontWrapper className="sw-body-sm sw-pb-8">
<div> <div>
<UpdateNotification dismissable={false} />
<UpdateNotification />
</div> </div>
{sysInfoData && ( {sysInfoData && (
<PageHeader <PageHeader

+ 4
- 4
server/sonar-web/src/main/js/components/upgrade/SystemUpgradeButton.tsx Vedi File

import { groupUpgrades, sortUpgrades, UpdateUseCase } from './utils'; import { groupUpgrades, sortUpgrades, UpdateUseCase } from './utils';


interface Props { interface Props {
latestLTS: string;
latestLTA: string;
systemUpgrades: SystemUpgrade[]; systemUpgrades: SystemUpgrade[];
updateUseCase?: UpdateUseCase;
updateUseCase: UpdateUseCase;
} }


export default function SystemUpgradeButton(props: Readonly<Props>) { export default function SystemUpgradeButton(props: Readonly<Props>) {
const { latestLTS, systemUpgrades, updateUseCase } = props;
const { latestLTA, systemUpgrades, updateUseCase } = props;


const [isSystemUpgradeFormOpen, setSystemUpgradeFormOpen] = React.useState(false); const [isSystemUpgradeFormOpen, setSystemUpgradeFormOpen] = React.useState(false);


<SystemUpgradeForm <SystemUpgradeForm
onClose={closeSystemUpgradeForm} onClose={closeSystemUpgradeForm}
systemUpgrades={groupUpgrades(sortUpgrades(systemUpgrades))} systemUpgrades={groupUpgrades(sortUpgrades(systemUpgrades))}
latestLTS={latestLTS}
latestLTA={latestLTA}
updateUseCase={updateUseCase} updateUseCase={updateUseCase}
/> />
)} )}

+ 15
- 22
server/sonar-web/src/main/js/components/upgrade/SystemUpgradeForm.tsx Vedi File

* along with this program; if not, write to the Free Software Foundation, * along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
import { FlagMessage, Link, Modal, Variant } from 'design-system';
import { FlagMessage, Link, Modal } from 'design-system';
import { filter, flatMap, isEmpty, negate } from 'lodash'; import { filter, flatMap, isEmpty, negate } from 'lodash';
import * as React from 'react'; import * as React from 'react';
import withAppStateContext from '../../app/components/app-state/withAppStateContext';
import { useAppState } from '../../app/components/app-state/withAppStateContext';
import { BANNER_VARIANT } from '../../app/components/update-notification/helpers';
import { translate } from '../../helpers/l10n'; import { translate } from '../../helpers/l10n';
import { AppState } from '../../types/appstate';
import { SystemUpgrade } from '../../types/system'; import { SystemUpgrade } from '../../types/system';
import SystemUpgradeItem from './SystemUpgradeItem'; import SystemUpgradeItem from './SystemUpgradeItem';
import { SYSTEM_VERSION_REGEXP, UpdateUseCase } from './utils'; import { SYSTEM_VERSION_REGEXP, UpdateUseCase } from './utils';


interface Props { interface Props {
appState: AppState;
onClose: () => void; onClose: () => void;
systemUpgrades: SystemUpgrade[][]; systemUpgrades: SystemUpgrade[][];
latestLTS: string;
updateUseCase?: UpdateUseCase;
latestLTA: string;
updateUseCase: UpdateUseCase;
} }


const MAP_ALERT: { [key in UpdateUseCase]?: Variant } = {
[UpdateUseCase.NewPatch]: 'warning',
[UpdateUseCase.PreLTS]: 'warning',
[UpdateUseCase.PreviousLTS]: 'error',
};

export function SystemUpgradeForm(props: Readonly<Props>) {
const { appState, latestLTS, onClose, updateUseCase, systemUpgrades } = props;
export default function SystemUpgradeForm(props: Readonly<Props>) {
const appState = useAppState();
const { latestLTA, onClose, updateUseCase, systemUpgrades } = props;


let systemUpgradesWithPatch: SystemUpgrade[][] = []; let systemUpgradesWithPatch: SystemUpgrade[][] = [];
const alertVariant = updateUseCase ? MAP_ALERT[updateUseCase] : undefined;
const alertVariant =
updateUseCase !== UpdateUseCase.NewVersion ? BANNER_VARIANT[updateUseCase] : undefined;
const header = translate('system.system_upgrade'); const header = translate('system.system_upgrade');
const parsedVersion = SYSTEM_VERSION_REGEXP.exec(appState.version); const parsedVersion = SYSTEM_VERSION_REGEXP.exec(appState.version);
let patches: SystemUpgrade[] = []; let patches: SystemUpgrade[] = [];


systemUpgradesWithPatch.push(patches); systemUpgradesWithPatch.push(patches);
} else { } else {
let untilLTS = false;
let untilLTA = false;


for (const upgrades of systemUpgrades) { for (const upgrades of systemUpgrades) {
if (untilLTS === false) {
if (untilLTA === false) {
systemUpgradesWithPatch.push(upgrades); systemUpgradesWithPatch.push(upgrades);
untilLTS = upgrades.some((upgrade) => upgrade.version.startsWith(latestLTS));
untilLTA = upgrades.some((upgrade) => upgrade.version.startsWith(latestLTA));
} }
} }
} }
onClose={onClose} onClose={onClose}
body={ body={
<> <>
{alertVariant && updateUseCase && (
{alertVariant && (
<FlagMessage variant={alertVariant} className={`it__upgrade-alert-${updateUseCase}`}> <FlagMessage variant={alertVariant} className={`it__upgrade-alert-${updateUseCase}`}>
{translate('admin_notification.update', updateUseCase)} {translate('admin_notification.update', updateUseCase)}
</FlagMessage> </FlagMessage>
key={upgrades[upgrades.length - 1].version} key={upgrades[upgrades.length - 1].version}
systemUpgrades={upgrades} systemUpgrades={upgrades}
isPatch={upgrades === patches} isPatch={upgrades === patches}
isLTSVersion={upgrades.some((upgrade) => upgrade.version.startsWith(latestLTS))}
isLTAVersion={upgrades.some((upgrade) => upgrade.version.startsWith(latestLTA))}
/> />
))} ))}
</> </>
/> />
); );
} }

export default withAppStateContext(SystemUpgradeForm);

+ 4
- 4
server/sonar-web/src/main/js/components/upgrade/SystemUpgradeItem.tsx Vedi File



export interface SystemUpgradeItemProps { export interface SystemUpgradeItemProps {
edition: EditionKey | undefined; edition: EditionKey | undefined;
isLTSVersion: boolean;
isLTAVersion: boolean;
isPatch: boolean; isPatch: boolean;
systemUpgrades: SystemUpgrade[]; systemUpgrades: SystemUpgrade[];
} }


export default function SystemUpgradeItem(props: SystemUpgradeItemProps) { export default function SystemUpgradeItem(props: SystemUpgradeItemProps) {
const { edition, isPatch, isLTSVersion, systemUpgrades } = props;
const { edition, isPatch, isLTAVersion, systemUpgrades } = props;
const lastUpgrade = systemUpgrades[0]; const lastUpgrade = systemUpgrades[0];
const downloadUrl = getEditionDownloadUrl( const downloadUrl = getEditionDownloadUrl(
getEdition(edition || EditionKey.community), getEdition(edition || EditionKey.community),
lastUpgrade, lastUpgrade,
); );
let header = translate('system.latest_version'); let header = translate('system.latest_version');
if (isLTSVersion) {
header = translate('system.lts_version');
if (isLTAVersion) {
header = translate('system.lta_version');
} else if (isPatch) { } else if (isPatch) {
header = translate('system.latest_patch'); header = translate('system.latest_patch');
} }

+ 6
- 5
server/sonar-web/src/main/js/components/upgrade/__tests__/SystemUpgrade-test.tsx Vedi File

header: byRole('heading', { name: 'system.system_upgrade' }), header: byRole('heading', { name: 'system.system_upgrade' }),
downloadLink: byRole('link', { name: /system.see_sonarqube_downloads/ }), downloadLink: byRole('link', { name: /system.see_sonarqube_downloads/ }),


ltsVersionHeader: byRole('heading', { name: /system.lts_version/ }),
ltaVersionHeader: byRole('heading', { name: /system.lta_version/ }),


newPatchWarning: byText(/admin_notification.update/), newPatchWarning: byText(/admin_notification.update/),
}; };
await user.click(ui.learnMoreButton.get()); await user.click(ui.learnMoreButton.get());


expect(ui.header.get()).toBeInTheDocument(); expect(ui.header.get()).toBeInTheDocument();
expect(ui.ltsVersionHeader.get()).toBeInTheDocument();
expect(ui.ltaVersionHeader.get()).toBeInTheDocument();
expect(ui.downloadLink.get()).toBeInTheDocument(); expect(ui.downloadLink.get()).toBeInTheDocument();
}); });


renderSystemUpgradeButton( renderSystemUpgradeButton(
{ {
updateUseCase: UpdateUseCase.NewPatch, updateUseCase: UpdateUseCase.NewPatch,
latestLTS: '9.9',
latestLTA: '9.9',
systemUpgrades: [{ downloadUrl: '', version: '9.9.1' }], systemUpgrades: [{ downloadUrl: '', version: '9.9.1' }],
}, },
'9.9', '9.9',


expect(ui.header.get()).toBeInTheDocument(); expect(ui.header.get()).toBeInTheDocument();
expect(ui.newPatchWarning.get()).toBeInTheDocument(); expect(ui.newPatchWarning.get()).toBeInTheDocument();
expect(ui.ltsVersionHeader.get()).toBeInTheDocument();
expect(ui.ltaVersionHeader.get()).toBeInTheDocument();
expect(ui.downloadLink.get()).toBeInTheDocument(); expect(ui.downloadLink.get()).toBeInTheDocument();
}); });


) { ) {
renderComponent( renderComponent(
<SystemUpgradeButton <SystemUpgradeButton
latestLTS="9.9"
updateUseCase={UpdateUseCase.NewVersion}
latestLTA="9.9"
systemUpgrades={[ systemUpgrades={[
{ downloadUrl: 'eight', version: '9.8' }, { downloadUrl: 'eight', version: '9.8' },
{ downloadUrl: 'lts', version: '9.9' }, { downloadUrl: 'lts', version: '9.9' },

+ 2
- 3
server/sonar-web/src/main/js/components/upgrade/utils.ts Vedi File

import { SystemUpgrade } from '../../types/system'; import { SystemUpgrade } from '../../types/system';


export enum UpdateUseCase { export enum UpdateUseCase {
NewMinorVersion = 'new_minor_version',
NewVersion = 'new_version',
CurrentVersionInactive = 'current_version_inactive',
NewPatch = 'new_patch', NewPatch = 'new_patch',
PreLTS = 'pre_lts',
PreviousLTS = 'previous_lts',
} }


export const SYSTEM_VERSION_REGEXP = /^(\d+)\.(\d+)(\.(\d+))?/; export const SYSTEM_VERSION_REGEXP = /^(\d+)\.(\d+)(\.(\d+))?/;

+ 3
- 4
sonar-core/src/main/resources/org/sonar/l10n/core.properties Vedi File

# Admin notification # 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.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.
admin_notification.update.new_version=There’s a new version of SonarQube available. Upgrade to the latest active version to access new updates and features.
admin_notification.update.current_version_inactive=You’re running a version of SonarQube that is no longer active. Please upgrade to an active version immediately.


#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
# #
system.how_to_upgrade=How to upgrade? system.how_to_upgrade=How to upgrade?
system.latest_version=Latest Version system.latest_version=Latest Version
system.latest_patch=Patch Release system.latest_patch=Patch Release
system.lts_version=Latest LTS Version
system.lta_version=Latest LTA Version
system.log_level.warning=This level has performance impacts, please make sure to get back to INFO level once your investigation is done. Please note that when the server is restarted, logging will revert to the level configured in sonar.properties. system.log_level.warning=This level has performance impacts, please make sure to get back to INFO level once your investigation is done. Please note that when the server is restarted, logging will revert to the level configured in sonar.properties.
system.log_level.warning.short=Current logs level has performance impacts, get back to INFO level. system.log_level.warning.short=Current logs level has performance impacts, get back to INFO level.
system.log_level.info=Your selection does not affect the Search Engine. system.log_level.info=Your selection does not affect the Search Engine.

Loading…
Annulla
Salva