]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-21909 migrate api/system/upgrades to react-query
authorViktor Vorona <viktor.vorona@sonarsource.com>
Wed, 27 Mar 2024 09:36:18 +0000 (10:36 +0100)
committersonartech <sonartech@sonarsource.com>
Wed, 3 Apr 2024 20:02:41 +0000 (20:02 +0000)
server/sonar-web/src/main/js/app/components/update-notification/UpdateNotification.tsx
server/sonar-web/src/main/js/app/components/update-notification/helpers.ts [new file with mode: 0644]
server/sonar-web/src/main/js/queries/system.ts [new file with mode: 0644]

index 4ed1ad44c5458516f0de5396c4bd96f5d8ee20df..03cb3bbc19c87a75b687cd00bcc2f6e086d7246d 100644 (file)
 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 (file)
index 0000000..0442139
--- /dev/null
@@ -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 (file)
index 0000000..1f1f41c
--- /dev/null
@@ -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,
+  });
+}