]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-18771 Fix Hotspot page test upon reassigning
authorMathieu Suen <mathieu.suen@sonarsource.com>
Tue, 5 Nov 2024 09:33:23 +0000 (10:33 +0100)
committersonartech <sonartech@sonarsource.com>
Mon, 11 Nov 2024 20:02:45 +0000 (20:02 +0000)
server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewer.tsx
server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerTabs.tsx
server/sonar-web/src/main/js/queries/hotspots.ts [new file with mode: 0644]

index 85e6a95853e89dee6721ded61aa5523d31f6c087..c7bfd7f1da7d92ad39e0e21412f220707cd95e82 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 
-import * as React from 'react';
-import { getRuleDetails } from '../../../api/rules';
-import { getSecurityHotspotDetails } from '../../../api/security-hotspots';
+import { useState } from 'react';
 import { get } from '../../../helpers/storage';
+import { useSecurityHotspotDetailsQuery } from '../../../queries/hotspots';
+import { useRuleDetailsQuery } from '../../../queries/rules';
 import { Standards } from '../../../types/security';
-import {
-  Hotspot,
-  HotspotStatusFilter,
-  HotspotStatusOption,
-} from '../../../types/security-hotspots';
+import { HotspotStatusFilter, HotspotStatusOption } from '../../../types/security-hotspots';
 import { Component } from '../../../types/types';
-import { RuleDescriptionSection } from '../../coding-rules/rule';
 import { SHOW_STATUS_DIALOG_STORAGE_KEY } from '../constants';
 import { getStatusFilterFromStatusOption } from '../utils';
 import HotspotViewerRenderer from './HotspotViewerRenderer';
@@ -46,118 +41,65 @@ interface Props {
   standards?: Standards;
 }
 
-interface State {
-  hotspot?: Hotspot;
-  lastStatusChangedTo?: HotspotStatusOption;
-  loading: boolean;
-  ruleDescriptionSections?: RuleDescriptionSection[];
-  ruleLanguage?: string;
-  showStatusUpdateSuccessModal: boolean;
-}
-
-export default class HotspotViewer extends React.PureComponent<Props, State> {
-  mounted = false;
-  state: State;
-
-  constructor(props: Props) {
-    super(props);
-    this.state = { loading: false, showStatusUpdateSuccessModal: false };
-  }
-
-  componentDidMount() {
-    this.mounted = true;
-    this.fetchHotspot();
-  }
-
-  componentDidUpdate(prevProps: Props) {
-    if (prevProps.hotspotKey !== this.props.hotspotKey) {
-      this.fetchHotspot();
-    }
-  }
-
-  componentWillUnmount() {
-    this.mounted = false;
-  }
-
-  fetchHotspot = async () => {
-    this.setState({ loading: true });
-
-    try {
-      const hotspot = await getSecurityHotspotDetails(this.props.hotspotKey);
-      const ruleDetails = await getRuleDetails({ key: hotspot.rule.key }).then((r) => r.rule);
-
-      if (this.mounted) {
-        this.setState({
-          hotspot,
-          loading: false,
-          ruleLanguage: ruleDetails.lang,
-          ruleDescriptionSections: ruleDetails.descriptionSections,
-        });
-      }
-    } catch (error) {
-      if (this.mounted) {
-        this.setState({ loading: false });
-      }
-    }
-  };
-
-  handleHotspotUpdate = async (statusUpdate = false, statusOption?: HotspotStatusOption) => {
-    const { hotspotKey } = this.props;
-
+export default function HotspotViewer(props: Readonly<Props>) {
+  const {
+    hotspotKey,
+    component,
+    cveId,
+    hotspotsReviewedMeasure,
+    selectedHotspotLocation,
+    standards,
+  } = props;
+
+  const [lastStatusChangedTo, setLastStatusChangedTo] = useState<HotspotStatusOption>();
+  const [showStatusUpdateSuccessModal, setShowStatusUpdateSuccessModal] = useState(false);
+
+  const { data: hotspot, refetch } = useSecurityHotspotDetailsQuery({ key: hotspotKey });
+  const { data: rule, isLoading } = useRuleDetailsQuery(
+    { key: hotspot?.rule.key! },
+    { enabled: hotspot !== undefined },
+  );
+
+  const ruleLanguage = rule?.rule.lang;
+  const ruleDescriptionSections = rule?.rule.descriptionSections;
+
+  const handleHotspotUpdate = async (statusUpdate = false, statusOption?: HotspotStatusOption) => {
     if (statusUpdate) {
-      this.setState({
-        lastStatusChangedTo: statusOption,
-        showStatusUpdateSuccessModal: get(SHOW_STATUS_DIALOG_STORAGE_KEY) !== 'false',
-      });
-      await this.props.onUpdateHotspot(hotspotKey);
+      setLastStatusChangedTo(statusOption);
+      setShowStatusUpdateSuccessModal(get(SHOW_STATUS_DIALOG_STORAGE_KEY) !== 'false');
+      await props.onUpdateHotspot(hotspotKey);
     } else {
-      await this.fetchHotspot();
+      refetch();
     }
   };
 
-  handleSwitchFilterToStatusOfUpdatedHotspot = () => {
-    const { lastStatusChangedTo } = this.state;
-
+  const handleSwitchFilterToStatusOfUpdatedHotspot = () => {
     if (lastStatusChangedTo) {
-      this.props.onSwitchStatusFilter(getStatusFilterFromStatusOption(lastStatusChangedTo));
+      props.onSwitchStatusFilter(getStatusFilterFromStatusOption(lastStatusChangedTo));
     }
   };
 
-  handleCloseStatusUpdateSuccessModal = () => {
-    this.setState({ showStatusUpdateSuccessModal: false });
+  const handleCloseStatusUpdateSuccessModal = () => {
+    setShowStatusUpdateSuccessModal(false);
   };
 
-  render() {
-    const { component, cveId, hotspotsReviewedMeasure, selectedHotspotLocation, standards } =
-      this.props;
-
-    const {
-      hotspot,
-      ruleDescriptionSections,
-      ruleLanguage,
-      loading,
-      showStatusUpdateSuccessModal,
-      lastStatusChangedTo,
-    } = this.state;
-
-    return (
-      <HotspotViewerRenderer
-        component={component}
-        hotspot={hotspot}
-        hotspotsReviewedMeasure={hotspotsReviewedMeasure}
-        lastStatusChangedTo={lastStatusChangedTo}
-        loading={loading}
-        onCloseStatusUpdateSuccessModal={this.handleCloseStatusUpdateSuccessModal}
-        onLocationClick={this.props.onLocationClick}
-        onSwitchFilterToStatusOfUpdatedHotspot={this.handleSwitchFilterToStatusOfUpdatedHotspot}
-        onUpdateHotspot={this.handleHotspotUpdate}
-        ruleDescriptionSections={ruleDescriptionSections}
-        ruleLanguage={ruleLanguage}
-        cveId={cveId}
-        selectedHotspotLocation={selectedHotspotLocation}
-        showStatusUpdateSuccessModal={showStatusUpdateSuccessModal}
-        standards={standards}
-      />
-    );
-  }
+  return (
+    <HotspotViewerRenderer
+      component={component}
+      hotspot={hotspot}
+      hotspotsReviewedMeasure={hotspotsReviewedMeasure}
+      lastStatusChangedTo={lastStatusChangedTo}
+      loading={isLoading}
+      onCloseStatusUpdateSuccessModal={handleCloseStatusUpdateSuccessModal}
+      onLocationClick={props.onLocationClick}
+      onSwitchFilterToStatusOfUpdatedHotspot={handleSwitchFilterToStatusOfUpdatedHotspot}
+      onUpdateHotspot={handleHotspotUpdate}
+      ruleDescriptionSections={ruleDescriptionSections}
+      ruleLanguage={ruleLanguage}
+      cveId={cveId}
+      selectedHotspotLocation={selectedHotspotLocation}
+      showStatusUpdateSuccessModal={showStatusUpdateSuccessModal}
+      standards={standards}
+    />
+  );
 }
index 1199d17b067c4685c5e9a6d08f95a68b8ccbbc6c..2116ccfa0ffeb7c89a7fbf7b8e13eb0f5d84592e 100644 (file)
@@ -167,8 +167,7 @@ export default function HotspotViewerTabs(props: Props) {
     document.addEventListener('keydown', handleKeyboardNavigation);
 
     return () => document.removeEventListener('keydown', handleKeyboardNavigation);
-    // eslint-disable-next-line react-hooks/exhaustive-deps
-  }, []);
+  }, [handleKeyboardNavigation]);
 
   React.useEffect(() => {
     setCurrentTab(tabs[0]);
diff --git a/server/sonar-web/src/main/js/queries/hotspots.ts b/server/sonar-web/src/main/js/queries/hotspots.ts
new file mode 100644 (file)
index 0000000..9a7d2ce
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * 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 { queryOptions } from '@tanstack/react-query';
+import { getSecurityHotspotDetails } from '../api/security-hotspots';
+import { createQueryHook, StaleTime } from './common';
+
+export const useSecurityHotspotDetailsQuery = createQueryHook((param: { key: string }) =>
+  queryOptions({
+    queryKey: ['hotspot', 'details', param.key],
+    queryFn: () => getSecurityHotspotDetails(param.key),
+    // For now no mutation is migrate, later it can be set to never
+    staleTime: StaleTime.LIVE,
+  }),
+);