From d419d9392f7274dacfefc619bd0cee2fb71c4e7c Mon Sep 17 00:00:00 2001 From: Wouter Admiraal Date: Fri, 26 May 2023 13:12:24 +0200 Subject: [PATCH] SONAR-19391 Prepare new layout structure --- .../components/Breadcrumb.tsx | 62 ------ .../components/ComponentMeasuresApp.tsx | 187 ++++++++---------- .../components/MeasureContent.tsx | 133 ++++++------- .../components/MeasureContentHeader.tsx | 6 +- .../components/MeasureOverview.tsx | 67 +++---- ...readcrumbs.tsx => MeasuresBreadcrumbs.tsx} | 36 ++-- .../components/MeasuresEmpty.tsx | 7 +- .../component-measures/sidebar/Sidebar.tsx | 121 ++++++++---- .../main/js/apps/component-measures/style.css | 24 --- .../resources/org/sonar/l10n/core.properties | 3 +- 10 files changed, 291 insertions(+), 355 deletions(-) delete mode 100644 server/sonar-web/src/main/js/apps/component-measures/components/Breadcrumb.tsx rename server/sonar-web/src/main/js/apps/component-measures/components/{Breadcrumbs.tsx => MeasuresBreadcrumbs.tsx} (76%) diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/Breadcrumb.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/Breadcrumb.tsx deleted file mode 100644 index d1d3c14888a..00000000000 --- a/server/sonar-web/src/main/js/apps/component-measures/components/Breadcrumb.tsx +++ /dev/null @@ -1,62 +0,0 @@ -/* - * 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 * as React from 'react'; -import Tooltip from '../../../components/controls/Tooltip'; -import { collapsePath, limitComponentName } from '../../../helpers/path'; -import { ComponentMeasure, ComponentMeasureIntern } from '../../../types/types'; - -interface Props { - canBrowse: boolean; - component: ComponentMeasure; - isLast: boolean; - handleSelect: (component: ComponentMeasureIntern) => void; -} - -export default class Breadcrumb extends React.PureComponent { - handleClick = (event: React.MouseEvent) => { - event.preventDefault(); - event.currentTarget.blur(); - this.props.handleSelect(this.props.component); - }; - - render() { - const { canBrowse, component, isLast } = this.props; - const isPath = component.qualifier === 'DIR'; - const componentName = isPath - ? collapsePath(component.name, 15) - : limitComponentName(component.name); - const breadcrumbItem = canBrowse ? ( - - {componentName} - - ) : ( - {componentName} - ); - - return ( - - - {breadcrumbItem} - - {!isLast && } - - ); - } -} diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/ComponentMeasuresApp.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/ComponentMeasuresApp.tsx index a99b3684ad6..2aba7aba066 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/ComponentMeasuresApp.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/components/ComponentMeasuresApp.tsx @@ -17,7 +17,16 @@ * 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 { + DeferredSpinner, + LargeCenteredLayout, + Note, + PageContentFontWrapper, + themeBorder, + themeColor, +} from 'design-system'; import { debounce, keyBy } from 'lodash'; import * as React from 'react'; import { Helmet } from 'react-helmet-async'; @@ -25,23 +34,14 @@ import { getMeasuresWithPeriod } from '../../../api/measures'; import { getAllMetrics } from '../../../api/metrics'; import withBranchStatusActions from '../../../app/components/branch-status/withBranchStatusActions'; import { ComponentContext } from '../../../app/components/componentContext/ComponentContext'; -import ScreenPositionHelper from '../../../components/common/ScreenPositionHelper'; -import HelpTooltip from '../../../components/controls/HelpTooltip'; import Suggestions from '../../../components/embed-docs-modal/Suggestions'; import { Location, Router, withRouter } from '../../../components/hoc/withRouter'; import { enhanceMeasure } from '../../../components/measure/utils'; import '../../../components/search-navigator.css'; -import { Alert } from '../../../components/ui/Alert'; import { getBranchLikeQuery, isPullRequest, isSameBranchLike } from '../../../helpers/branch-like'; import { translate } from '../../../helpers/l10n'; -import { - addSideBarClass, - addWhitePageClass, - removeSideBarClass, - removeWhitePageClass, -} from '../../../helpers/pages'; import { BranchLike } from '../../../types/branch-like'; -import { ComponentQualifier, isPortfolioLike } from '../../../types/component'; +import { ComponentQualifier } from '../../../types/component'; import { ComponentMeasure, Dict, @@ -53,6 +53,7 @@ import { import Sidebar from '../sidebar/Sidebar'; import '../style.css'; import { + Query, banQualityGateMeasure, getMeasuresPageMetricKeys, groupByDomains, @@ -61,7 +62,6 @@ import { hasTree, hasTreemap, parseQuery, - Query, serializeQuery, sortMeasures, } from '../utils'; @@ -111,7 +111,7 @@ class ComponentMeasuresApp extends React.PureComponent { ); } - componentDidUpdate(prevProps: Props, prevState: State) { + componentDidUpdate(prevProps: Props) { const prevQuery = parseQuery(prevProps.location.query); const query = parseQuery(this.props.location.query); @@ -122,17 +122,10 @@ class ComponentMeasuresApp extends React.PureComponent { ) { this.fetchMeasures(this.state.metrics); } - - if (prevState.measures.length === 0 && this.state.measures.length > 0) { - addWhitePageClass(); - addSideBarClass(); - } } componentWillUnmount() { this.mounted = false; - removeWhitePageClass(); - removeSideBarClass(); } fetchMeasures(metrics: State['metrics']) { @@ -221,25 +214,31 @@ class ComponentMeasuresApp extends React.PureComponent { renderContent = (displayOverview: boolean, query: Query, metric?: Metric) => { const { branchLike, component } = this.props; const { leakPeriod } = this.state; + if (displayOverview) { return ( - + + + ); } if (!metric) { - return ; + return ( + + + + ); } const hideDrilldown = @@ -248,40 +247,32 @@ class ComponentMeasuresApp extends React.PureComponent { if (hideDrilldown) { return ( -
-
-
{translate('component_measures.details_are_not_available')}
-
-
+ + {translate('component_measures.details_are_not_available')} + ); } return ( - + + + ); }; render() { - if (this.state.loading) { - return ( -
- -
- ); - } - const { branchLike } = this.props; const { measures } = this.state; const { canBrowseAllChildProjects, qualifier } = this.props.component; @@ -291,58 +282,40 @@ class ComponentMeasuresApp extends React.PureComponent { const metric = this.getSelectedMetric(query, displayOverview); return ( -
+ - {measures.length > 0 ? ( -
- - {({ top }) => ( -
-
- {!canBrowseAllChildProjects && isPortfolioLike(qualifier) && ( - - - {translate('component_measures.not_all_measures_are_shown')} - - - - )} -
- -
-
-
- )} -
- {this.renderContent(displayOverview, query, metric)} -
- ) : ( - - )} -
+ + + + {measures.length > 0 ? ( +
+ +
+ {this.renderContent(displayOverview, query, metric)} +
+
+ ) : ( + + + + )} +
+ ); } } -const AlertContent = styled.div` - display: flex; - align-items: center; -`; - /* * This needs to be refactored: the issue * is that we can't use the usual withComponentContext HOC, because the type @@ -357,3 +330,9 @@ function AppWithComponentContext() { } export default AppWithComponentContext; + +const StyledMain = withTheme(styled.main` + background-color: ${themeColor('filterbar')}; + background-color: ${themeColor('pageBlock')}; + border: ${themeBorder('default', 'pageBlockBorder')}l; +`); diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.tsx index 304be402e5e..5f241e9e2c4 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.tsx @@ -20,9 +20,9 @@ import * as React from 'react'; import { getComponentTree } from '../../../api/components'; import { getMeasures } from '../../../api/measures'; +import SourceViewer from '../../../components/SourceViewer/SourceViewer'; import A11ySkipTarget from '../../../components/a11y/A11ySkipTarget'; import { Router } from '../../../components/hoc/withRouter'; -import SourceViewer from '../../../components/SourceViewer/SourceViewer'; import PageActions from '../../../components/ui/PageActions'; import { getBranchLikeQuery, isSameBranchLike } from '../../../helpers/branch-like'; import { getComponentMeasureUniqueKey } from '../../../helpers/component'; @@ -48,11 +48,11 @@ import { import { complementary } from '../config/complementary'; import FilesView from '../drilldown/FilesView'; import TreeMapView from '../drilldown/TreeMapView'; -import { enhanceComponent, Query } from '../utils'; -import Breadcrumbs from './Breadcrumbs'; +import { Query, enhanceComponent } from '../utils'; import MeasureContentHeader from './MeasureContentHeader'; import MeasureHeader from './MeasureHeader'; import MeasureViewSelect from './MeasureViewSelect'; +import MeasuresBreadcrumbs from './MeasuresBreadcrumbs'; interface Props { branchLike?: BranchLike; @@ -352,81 +352,68 @@ export default class MeasureContent extends React.PureComponent { const selectedIdx = this.getSelectedIndex(); return ( -
(this.container = container)} - > +
(this.container = container)}> -
-
-
- - } - right={ -
- {!isFileComponent && metric && ( - <> -
- {translate('component_measures.view_as')} -
- - - - - )} + + } + right={ +
+ {!isFileComponent && metric && ( + <> +
+ {translate('component_measures.view_as')}
- } - /> + + + + + )}
+ } + /> + + + {isFileComponent ? ( +
+
-
- -
- - {isFileComponent ? ( -
- -
- ) : ( - this.renderMeasure() - )} -
+ ) : ( + this.renderMeasure() + )}
); } diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContentHeader.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContentHeader.tsx index 55e74cd8421..8f061c91919 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContentHeader.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContentHeader.tsx @@ -26,9 +26,9 @@ interface Props { export default function MeasureContentHeader({ left, right }: Props) { return ( -
-
{left}
-
{right}
+
+
{left}
+
{right}
); } diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverview.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverview.tsx index 501ec9fac2e..2bac35e3559 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverview.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverview.tsx @@ -19,8 +19,8 @@ */ import * as React from 'react'; import { getComponentLeaves } from '../../../api/components'; -import A11ySkipTarget from '../../../components/a11y/A11ySkipTarget'; import SourceViewer from '../../../components/SourceViewer/SourceViewer'; +import A11ySkipTarget from '../../../components/a11y/A11ySkipTarget'; import DeferredSpinner from '../../../components/ui/DeferredSpinner'; import PageActions from '../../../components/ui/PageActions'; import { getBranchLikeQuery, isSameBranchLike } from '../../../helpers/branch-like'; @@ -38,9 +38,9 @@ import { } from '../../../types/types'; import BubbleChart from '../drilldown/BubbleChart'; import { BUBBLES_FETCH_LIMIT, enhanceComponent, getBubbleMetrics, hasFullMeasures } from '../utils'; -import Breadcrumbs from './Breadcrumbs'; import LeakPeriodLegend from './LeakPeriodLegend'; import MeasureContentHeader from './MeasureContentHeader'; +import MeasuresBreadcrumbs from './MeasuresBreadcrumbs'; interface Props { branchLike?: BranchLike; @@ -154,43 +154,34 @@ export default class MeasureOverview extends React.PureComponent { const { branchLike, className, component, leakPeriod, loading, rootComponent } = this.props; const displayLeak = hasFullMeasures(branchLike); return ( -
-
- +
+ -
-
- - } - right={ - - } - /> -
-
-
-
-
- {leakPeriod && displayLeak && ( - - )} -
- - {!loading && this.renderContent()} -
-
+ + } + right={ + + } + /> + {leakPeriod && displayLeak && ( + + )} + + + + {!loading && this.renderContent()} +
); } } diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/Breadcrumbs.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/MeasuresBreadcrumbs.tsx similarity index 76% rename from server/sonar-web/src/main/js/apps/component-measures/components/Breadcrumbs.tsx rename to server/sonar-web/src/main/js/apps/component-measures/components/MeasuresBreadcrumbs.tsx index d86ac9cae16..b5c578c07e8 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/Breadcrumbs.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/components/MeasuresBreadcrumbs.tsx @@ -17,13 +17,16 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { Breadcrumbs, HoverLink } from 'design-system'; import * as React from 'react'; import { getBreadcrumbs } from '../../../api/components'; import { getBranchLikeQuery, isSameBranchLike } from '../../../helpers/branch-like'; import { KeyboardKeys } from '../../../helpers/keycodes'; +import { translate } from '../../../helpers/l10n'; +import { collapsePath, limitComponentName } from '../../../helpers/path'; import { BranchLike } from '../../../types/branch-like'; +import { ComponentQualifier } from '../../../types/component'; import { ComponentMeasure, ComponentMeasureIntern } from '../../../types/types'; -import Breadcrumb from './Breadcrumb'; interface Props { backToFirst: boolean; @@ -38,7 +41,7 @@ interface State { breadcrumbs: ComponentMeasure[]; } -export default class Breadcrumbs extends React.PureComponent { +export default class MeasuresBreadcrumbs extends React.PureComponent { mounted = false; state: State = { breadcrumbs: [] }; @@ -94,22 +97,33 @@ export default class Breadcrumbs extends React.PureComponent { render() { const { breadcrumbs } = this.state; + if (breadcrumbs.length <= 0) { return null; } - const lastItem = breadcrumbs[breadcrumbs.length - 1]; + return ( -
+ {breadcrumbs.map((component) => ( - + to="#" + onClick={(event: React.MouseEvent) => { + event.preventDefault(); + event.currentTarget.blur(); + this.props.handleSelect(component); + }} + > + {component.qualifier === ComponentQualifier.Directory + ? collapsePath(component.name, 15) + : limitComponentName(component.name)} + ))} -
+ ); } } diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasuresEmpty.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/MeasuresEmpty.tsx index 4bfc270d36d..72bcaca5599 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/MeasuresEmpty.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/components/MeasuresEmpty.tsx @@ -17,13 +17,10 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { Note } from 'design-system/lib'; import * as React from 'react'; import { translate } from '../../../helpers/l10n'; export default function MeasuresEmpty() { - return ( -
-
{translate('component_measures.empty')}
-
- ); + return {translate('component_measures.empty')}; } diff --git a/server/sonar-web/src/main/js/apps/component-measures/sidebar/Sidebar.tsx b/server/sonar-web/src/main/js/apps/component-measures/sidebar/Sidebar.tsx index 7ffcfb03c8d..0b786954998 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/sidebar/Sidebar.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/sidebar/Sidebar.tsx @@ -17,72 +17,117 @@ * 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 { + FlagMessage, + LAYOUT_FOOTER_HEIGHT, + LAYOUT_GLOBAL_NAV_HEIGHT, + LAYOUT_PROJECT_NAV_HEIGHT, + themeBorder, + themeColor, +} from 'design-system/lib'; import * as React from 'react'; import A11ySkipTarget from '../../../components/a11y/A11ySkipTarget'; +import HelpTooltip from '../../../components/controls/HelpTooltip'; import { translate } from '../../../helpers/l10n'; +import useFollowScroll from '../../../hooks/useFollowScroll'; +import { isPortfolioLike } from '../../../types/component'; import { Dict, MeasureEnhanced } from '../../../types/types'; -import { groupByDomains, KNOWN_DOMAINS, PROJECT_OVERVEW, Query } from '../utils'; +import { KNOWN_DOMAINS, PROJECT_OVERVEW, Query, groupByDomains } from '../utils'; import DomainFacet from './DomainFacet'; import ProjectOverviewFacet from './ProjectOverviewFacet'; interface Props { + canBrowseAllChildProjects: boolean; measures: MeasureEnhanced[]; + qualifier: string; selectedMetric: string; showFullMeasures: boolean; updateQuery: (query: Partial) => void; } -interface State { - openFacets: Dict; -} - -export default class Sidebar extends React.PureComponent { - static getDerivedStateFromProps(props: Props, state: State) { - return { openFacets: getOpenFacets(state.openFacets, props) }; - } +export default function Sidebar(props: Props) { + const { + showFullMeasures, + canBrowseAllChildProjects, + qualifier, + updateQuery, + selectedMetric, + measures, + } = props; + const [openFacets, setOpenFacets] = React.useState(getOpenFacets({}, props)); + const { top: topScroll } = useFollowScroll(); - state: State = { - openFacets: {}, - }; + const handleToggleFacet = React.useCallback( + (name: string) => { + setOpenFacets((openFacets) => ({ ...openFacets, [name]: !openFacets[name] })); + }, + [setOpenFacets] + ); - toggleFacet = (name: string) => { - this.setState(({ openFacets }) => ({ - openFacets: { ...openFacets, [name]: !openFacets[name] }, - })); - }; + const handleChangeMetric = React.useCallback( + (metric: string) => { + updateQuery({ metric }); + }, + [updateQuery] + ); - changeMetric = (metric: string) => { - this.props.updateQuery({ metric }); - }; + const distanceFromBottom = topScroll + window.innerHeight - document.body.clientHeight; + const footerVisibleHeight = + distanceFromBottom > -LAYOUT_FOOTER_HEIGHT ? LAYOUT_FOOTER_HEIGHT + distanceFromBottom : 0; - render() { - const { showFullMeasures } = this.props; - return ( -