diff options
author | Viktor Vorona <viktor.vorona@sonarsource.com> | 2024-03-27 10:36:18 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2024-04-03 20:02:41 +0000 |
commit | 4817d91f001bfd970c3c778956d1bab507089eda (patch) | |
tree | 8e0fa1ca20a2be02ce3c6900a2ab3e86a7def95f /server/sonar-web/src/main | |
parent | aef564fbc7aa8b50109abee00bb62944e791a6af (diff) | |
download | sonarqube-4817d91f001bfd970c3c778956d1bab507089eda.tar.gz sonarqube-4817d91f001bfd970c3c778956d1bab507089eda.zip |
SONAR-21909 migrate api/system/upgrades to react-query
Diffstat (limited to 'server/sonar-web/src/main')
3 files changed, 192 insertions, 190 deletions
diff --git a/server/sonar-web/src/main/js/app/components/update-notification/UpdateNotification.tsx b/server/sonar-web/src/main/js/app/components/update-notification/UpdateNotification.tsx index 4ed1ad44c54..03cb3bbc19c 100644 --- a/server/sonar-web/src/main/js/app/components/update-notification/UpdateNotification.tsx +++ b/server/sonar-web/src/main/js/app/components/update-notification/UpdateNotification.tsx @@ -20,25 +20,19 @@ import { Banner, Variant } from 'design-system'; import { groupBy, isEmpty, mapValues } from 'lodash'; import * as React from 'react'; -import { getSystemUpgrades } from '../../../api/system'; import DismissableAlert from '../../../components/ui/DismissableAlert'; import SystemUpgradeButton from '../../../components/upgrade/SystemUpgradeButton'; -import { UpdateUseCase, sortUpgrades } from '../../../components/upgrade/utils'; +import { UpdateUseCase } from '../../../components/upgrade/utils'; import { translate } from '../../../helpers/l10n'; import { hasGlobalPermission } from '../../../helpers/users'; +import { useSystemUpgrades } from '../../../queries/system'; import { AppState } from '../../../types/appstate'; import { Permissions } from '../../../types/permissions'; -import { SystemUpgrade } from '../../../types/system'; import { Dict } from '../../../types/types'; import { CurrentUser, isLoggedIn } from '../../../types/users'; import withAppStateContext from '../app-state/withAppStateContext'; import withCurrentUserContext from '../current-user/withCurrentUserContext'; - -const MONTH_BEFOR_PREVIOUS_LTS_NOTIFICATION = 6; - -type GroupedSystemUpdate = { - [x: string]: Dict<SystemUpgrade[]>; -}; +import { isMinorUpdate, isPatchUpdate, isPreLTSUpdate, isPreviousLTSUpdate } from './helpers'; const MAP_VARIANT: Dict<Variant> = { [UpdateUseCase.NewMinorVersion]: 'info', @@ -53,194 +47,86 @@ interface Props { currentUser: CurrentUser; } -interface State { - dismissKey: string; - useCase: UpdateUseCase; - latestLTS: string; - 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: [], - latestLTS: '', - canSeeNotification: false, - useCase: UpdateUseCase.NewMinorVersion, - }; - } - - componentDidMount() { - this.mounted = true; - - 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 VERSION_PARSER = /^(\d+)\.(\d+)(\.(\d+))?/; + +export function UpdateNotification({ dismissable, appState, currentUser }: Readonly<Props>) { + const canUserSeeNotification = + isLoggedIn(currentUser) && hasGlobalPermission(currentUser, Permissions.Admin); + const regExpParsedVersion = VERSION_PARSER.exec(appState.version); + const { data } = useSystemUpgrades({ + enabled: canUserSeeNotification && regExpParsedVersion !== null, + }); + + if ( + !canUserSeeNotification || + regExpParsedVersion === null || + data === undefined || + isEmpty(data.upgrades) ) { - 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; + return null; } - 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( + const { upgrades, latestLTS } = data; + const parsedVersion = regExpParsedVersion + .slice(1) + .map(Number) + .map((n) => (isNaN(n) ? 0 : n)); + + const systemUpgrades = mapValues( + groupBy(upgrades, (upgrade) => { + const [major] = upgrade.version.split('.'); + return major; + }), + (upgrades) => groupBy(upgrades, (upgrade) => { - const [major] = upgrade.version.split('.'); - return major; + const [, minor] = upgrade.version.split('.'); + return minor; }), - (upgrades) => - groupBy(upgrades, (upgrade) => { - const [, minor] = upgrade.version.split('.'); - return minor; - }), - ); - - let useCase = UpdateUseCase.NewMinorVersion; - - if (this.isPreviousLTSUpdate(parsedVersion, latestLTS, systemUpgrades)) { - useCase = UpdateUseCase.PreviousLTS; - } else if (this.isPreLTSUpdate(parsedVersion, latestLTS)) { - useCase = UpdateUseCase.PreLTS; - } else if (this.isPatchUpdate(parsedVersion, systemUpgrades)) { - useCase = UpdateUseCase.NewPatch; - } else if (this.isMinorUpdate(parsedVersion, systemUpgrades)) { - useCase = UpdateUseCase.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({ - latestLTS, - useCase, - dismissKey, - systemUpgrades: upgrades, - canSeeNotification: true, - }); - } + ); + + let useCase = UpdateUseCase.NewMinorVersion; + + if (isPreviousLTSUpdate(parsedVersion, latestLTS, systemUpgrades)) { + useCase = UpdateUseCase.PreviousLTS; + } else if (isPreLTSUpdate(parsedVersion, latestLTS)) { + useCase = UpdateUseCase.PreLTS; + } else if (isPatchUpdate(parsedVersion, systemUpgrades)) { + useCase = UpdateUseCase.NewPatch; + } else if (isMinorUpdate(parsedVersion, systemUpgrades)) { + useCase = UpdateUseCase.NewMinorVersion; } - noPromptToShow() { - if (this.mounted) { - this.setState({ canSeeNotification: false }); - } - } - - render() { - const { dismissable } = this.props; - const { latestLTS, systemUpgrades, canSeeNotification, useCase, dismissKey } = this.state; - if (!canSeeNotification) { - return null; - } - return dismissable ? ( - <DismissableAlert - alertKey={dismissKey} - variant={MAP_VARIANT[useCase]} - className={`it__promote-update-notification it__upgrade-prompt-${useCase}`} - > - {translate('admin_notification.update', useCase)} - <SystemUpgradeButton - systemUpgrades={systemUpgrades} - updateUseCase={useCase} - latestLTS={latestLTS} - /> - </DismissableAlert> - ) : ( - <Banner variant={MAP_VARIANT[useCase]} className={`it__upgrade-prompt-${useCase}`}> - {translate('admin_notification.update', useCase)} - <SystemUpgradeButton - systemUpgrades={systemUpgrades} - updateUseCase={useCase} - latestLTS={latestLTS} - /> - </Banner> - ); - } + const latest = [...upgrades].sort( + (upgrade1, upgrade2) => + new Date(upgrade2.releaseDate ?? '').getTime() - + new Date(upgrade1.releaseDate ?? '').getTime(), + )[0]; + + const dismissKey = useCase + latest.version; + + return dismissable ? ( + <DismissableAlert + alertKey={dismissKey} + variant={MAP_VARIANT[useCase]} + className={`it__promote-update-notification it__upgrade-prompt-${useCase}`} + > + {translate('admin_notification.update', useCase)} + <SystemUpgradeButton + systemUpgrades={upgrades} + updateUseCase={useCase} + latestLTS={latestLTS} + /> + </DismissableAlert> + ) : ( + <Banner variant={MAP_VARIANT[useCase]} className={`it__upgrade-prompt-${useCase}`}> + {translate('admin_notification.update', useCase)} + <SystemUpgradeButton + systemUpgrades={upgrades} + updateUseCase={useCase} + latestLTS={latestLTS} + /> + </Banner> + ); } export default withCurrentUserContext(withAppStateContext(UpdateNotification)); diff --git a/server/sonar-web/src/main/js/app/components/update-notification/helpers.ts b/server/sonar-web/src/main/js/app/components/update-notification/helpers.ts new file mode 100644 index 00000000000..0442139d263 --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/update-notification/helpers.ts @@ -0,0 +1,81 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import { isEmpty } from 'lodash'; +import { sortUpgrades } from '../../../components/upgrade/utils'; +import { SystemUpgrade } from '../../../types/system'; + +const MONTH_BEFOR_PREVIOUS_LTS_NOTIFICATION = 6; + +type GroupedSystemUpdate = { + [x: string]: Record<string, SystemUpgrade[]>; +}; + +export const isPreLTSUpdate = (parsedVersion: number[], latestLTS: string) => { + const [currentMajor, currentMinor] = parsedVersion; + 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; +}; + +export const isMinorUpdate = (parsedVersion: number[], systemUpgrades: GroupedSystemUpdate) => { + const [currentMajor, currentMinor] = parsedVersion; + const allMinor = systemUpgrades[currentMajor]; + return Object.keys(allMinor) + .map(Number) + .some((minor) => minor > currentMinor); +}; + +export const 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; +}; diff --git a/server/sonar-web/src/main/js/queries/system.ts b/server/sonar-web/src/main/js/queries/system.ts new file mode 100644 index 00000000000..1f1f41c406e --- /dev/null +++ b/server/sonar-web/src/main/js/queries/system.ts @@ -0,0 +1,35 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import { UseQueryOptions, useQuery } from '@tanstack/react-query'; +import { getSystemUpgrades } from '../api/system'; + +export function useSystemUpgrades<T = Awaited<ReturnType<typeof getSystemUpgrades>>>( + options?: Omit< + UseQueryOptions<Awaited<ReturnType<typeof getSystemUpgrades>>, Error, T>, + 'queryKey' | 'queryFn' + >, +) { + return useQuery({ + queryKey: ['system', 'upgrades'], + queryFn: () => getSystemUpgrades(), + staleTime: Infinity, + ...options, + }); +} |