* 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';
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}
+ />
+ );
}
--- /dev/null
+/*
+ * 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,
+ }),
+);