/* * SonarQube * Copyright (C) 2009-2023 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 { withTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { LAYOUT_FOOTER_HEIGHT, LAYOUT_GLOBAL_NAV_HEIGHT, LAYOUT_PROJECT_NAV_HEIGHT, LargeCenteredLayout, PageContentFontWrapper, Spinner, themeBorder, themeColor, } from 'design-system'; import * as React from 'react'; import { Helmet } from 'react-helmet-async'; import A11ySkipTarget from '../../components/a11y/A11ySkipTarget'; import Suggestions from '../../components/embed-docs-modal/Suggestions'; import { isBranch } from '../../helpers/branch-like'; import { translate } from '../../helpers/l10n'; import useFollowScroll from '../../hooks/useFollowScroll'; import { BranchLike } from '../../types/branch-like'; import { ComponentQualifier } from '../../types/component'; import { MetricKey } from '../../types/metrics'; import { SecurityStandard, Standards } from '../../types/security'; import { HotspotFilters, HotspotStatusFilter, RawHotspot } from '../../types/security-hotspots'; import { Component, StandardSecurityCategories } from '../../types/types'; import EmptyHotspotsPage from './components/EmptyHotspotsPage'; import HotspotList from './components/HotspotList'; import HotspotSidebarHeader from './components/HotspotSidebarHeader'; import HotspotSimpleList from './components/HotspotSimpleList'; import HotspotFilterByStatus from './components/HotspotStatusFilter'; import HotspotViewer from './components/HotspotViewer'; import './styles.css'; export interface SecurityHotspotsAppRendererProps { branchLike?: BranchLike; component: Component; filterByCategory?: { standard: SecurityStandard; category: string; }; filterByCWE?: string; filterByFile?: string; filters: HotspotFilters; hotspots: RawHotspot[]; hotspotsReviewedMeasure?: string; hotspotsTotal: number; isStaticListOfHotspots: boolean; loading: boolean; loadingMeasure: boolean; loadingMore: boolean; onChangeFilters: (filters: Partial) => void; onHotspotClick: (hotspot: RawHotspot) => void; onLoadMore: () => void; onLocationClick: (index?: number) => void; onShowAllHotspots: VoidFunction; onSwitchStatusFilter: (option: HotspotStatusFilter) => void; onUpdateHotspot: (hotspotKey: string) => Promise; securityCategories: StandardSecurityCategories; selectedHotspot?: RawHotspot; selectedHotspotLocation?: number; standards: Standards; } const STICKY_HEADER_HEIGHT = 73; export default function SecurityHotspotsAppRenderer(props: SecurityHotspotsAppRendererProps) { const { branchLike, component, filterByCategory, filterByCWE, filterByFile, filters, hotspots, hotspotsReviewedMeasure, hotspotsTotal, isStaticListOfHotspots, loading, loadingMeasure, loadingMore, onChangeFilters, onShowAllHotspots, securityCategories, selectedHotspot, selectedHotspotLocation, standards, } = props; const isProject = component.qualifier === ComponentQualifier.Project; const { top: topScroll } = useFollowScroll(); const distanceFromBottom = topScroll + window.innerHeight - document.body.clientHeight; const footerVisibleHeight = distanceFromBottom > -LAYOUT_FOOTER_HEIGHT ? LAYOUT_FOOTER_HEIGHT + distanceFromBottom : 0; return ( <>
{isProject && ( )} {hotspots.length > 0 && selectedHotspot && ( <> {filterByCategory || filterByCWE || filterByFile ? ( ) : ( )} )} {hotspots.length === 0 || !selectedHotspot ? ( ) : ( )}
); } const StyledSidebar = withTheme(styled.section` box-sizing: border-box; background-color: ${themeColor('filterbar')}; border-right: ${themeBorder('default', 'filterbarBorder')}; `); const StyledSidebarContent = styled.div` position: sticky; overflow-x: hidden; box-sizing: border-box; width: 100%; `; const StyledSidebarHeader = withTheme(styled.div` position: sticky; box-sizing: border-box; background-color: inherit; border-bottom: ${themeBorder('default')}; z-index: 1; height: ${STICKY_HEADER_HEIGHT}px; top: ${LAYOUT_GLOBAL_NAV_HEIGHT + LAYOUT_PROJECT_NAV_HEIGHT}px; `); const StyledMain = styled.main` flex-grow: 1; background-color: ${themeColor('backgroundSecondary')}; border-left: ${themeBorder('default')}; border-right: ${themeBorder('default')}; `;