From 04638c2a1b32c882b26791abc9277f40ae599d65 Mon Sep 17 00:00:00 2001 From: Mathieu Suen Date: Tue, 5 Nov 2024 10:33:23 +0100 Subject: [PATCH] SONAR-18771 Fix Hotspot page test upon reassigning --- .../components/HotspotViewer.tsx | 166 ++++++------------ .../components/HotspotViewerTabs.tsx | 3 +- .../sonar-web/src/main/js/queries/hotspots.ts | 32 ++++ 3 files changed, 87 insertions(+), 114 deletions(-) create mode 100644 server/sonar-web/src/main/js/queries/hotspots.ts diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewer.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewer.tsx index 85e6a95853e..c7bfd7f1da7 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewer.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewer.tsx @@ -18,18 +18,13 @@ * 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 { - 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) { + const { + hotspotKey, + component, + cveId, + hotspotsReviewedMeasure, + selectedHotspotLocation, + standards, + } = props; + + const [lastStatusChangedTo, setLastStatusChangedTo] = useState(); + 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 ( - - ); - } + return ( + + ); } diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerTabs.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerTabs.tsx index 1199d17b067..2116ccfa0ff 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerTabs.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerTabs.tsx @@ -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 index 00000000000..9a7d2ce02c2 --- /dev/null +++ b/server/sonar-web/src/main/js/queries/hotspots.ts @@ -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, + }), +); -- 2.39.5