From d837860e6673b3f7a5ee1a251d63d0a13bb58780 Mon Sep 17 00:00:00 2001 From: Jay Date: Mon, 5 Jun 2023 13:22:27 +0200 Subject: [PATCH] SONAR-19472 New layout for Code page --- .../js/app/components/GlobalContainer.tsx | 1 + .../main/js/apps/code/components/CodeApp.tsx | 194 ++------------- .../apps/code/components/CodeAppRenderer.tsx | 231 ++++++++++++++++++ 3 files changed, 249 insertions(+), 177 deletions(-) create mode 100644 server/sonar-web/src/main/js/apps/code/components/CodeAppRenderer.tsx diff --git a/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx b/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx index f0ca8055c1f..4a425746158 100644 --- a/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx +++ b/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx @@ -43,6 +43,7 @@ const TEMP_PAGELIST_WITH_NEW_BACKGROUND = [ '/security_hotspots', '/component_measures', '/project/issues', + '/code', ]; export default function GlobalContainer() { diff --git a/server/sonar-web/src/main/js/apps/code/components/CodeApp.tsx b/server/sonar-web/src/main/js/apps/code/components/CodeApp.tsx index 1efd31d8c59..af5d7b5255f 100644 --- a/server/sonar-web/src/main/js/apps/code/components/CodeApp.tsx +++ b/server/sonar-web/src/main/js/apps/code/components/CodeApp.tsx @@ -17,39 +17,21 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import styled from '@emotion/styled'; -import classNames from 'classnames'; -import { debounce, intersection } from 'lodash'; +import { debounce, noop } from 'lodash'; import * as React from 'react'; -import { Helmet } from 'react-helmet-async'; import withBranchStatusActions from '../../../app/components/branch-status/withBranchStatusActions'; import withComponentContext from '../../../app/components/componentContext/withComponentContext'; import withMetricsContext from '../../../app/components/metrics/withMetricsContext'; -import A11ySkipTarget from '../../../components/a11y/A11ySkipTarget'; -import HelpTooltip from '../../../components/controls/HelpTooltip'; -import ListFooter from '../../../components/controls/ListFooter'; -import Suggestions from '../../../components/embed-docs-modal/Suggestions'; import { Location, Router, withRouter } from '../../../components/hoc/withRouter'; -import { Alert } from '../../../components/ui/Alert'; import { isPullRequest } from '../../../helpers/branch-like'; -import { translate } from '../../../helpers/l10n'; import { CodeScope, getCodeUrl, getProjectUrl } from '../../../helpers/urls'; import { BranchLike } from '../../../types/branch-like'; -import { ComponentQualifier, isApplication, isPortfolioLike } from '../../../types/component'; +import { ComponentQualifier } from '../../../types/component'; import { Breadcrumb, Component, ComponentMeasure, Dict, Issue, Metric } from '../../../types/types'; import { addComponent, addComponentBreadcrumbs, clearBucket } from '../bucket'; import '../code.css'; -import { - getCodeMetrics, - loadMoreChildren, - retrieveComponent, - retrieveComponentChildren, -} from '../utils'; -import Breadcrumbs from './Breadcrumbs'; -import Components from './Components'; -import Search from './Search'; -import SearchResults from './SearchResults'; -import SourceViewerWrapper from './SourceViewerWrapper'; +import { loadMoreChildren, retrieveComponent, retrieveComponentChildren } from '../utils'; +import CodeAppRenderer from './CodeAppRenderer'; interface Props { branchLike?: BranchLike; @@ -244,170 +226,28 @@ class CodeApp extends React.Component { refreshBranchStatus = () => { const { branchLike, component } = this.props; if (branchLike && component && isPullRequest(branchLike)) { - this.props.fetchBranchStatus(branchLike, component.key); + this.props.fetchBranchStatus(branchLike, component.key).catch(noop); } }; render() { - const { branchLike, component, location } = this.props; - const { - baseComponent, - breadcrumbs, - components = [], - highlighted, - loading, - newCodeSelected, - total, - searchResults, - sourceViewer, - } = this.state; - const { canBrowseAllChildProjects, qualifier } = component; - - const showSearch = searchResults !== undefined; - - const hasComponents = components.length > 0 || searchResults !== undefined; - - const showBreadcrumbs = breadcrumbs.length > 1 && !showSearch; - - const showComponentList = sourceViewer === undefined && components.length > 0 && !showSearch; - - const componentsClassName = classNames('boxed-group', 'spacer-top', { - 'new-loading': loading, - 'search-results': showSearch, - }); - - const metricKeys = intersection( - getCodeMetrics(component.qualifier, branchLike, { newCode: newCodeSelected }), - Object.keys(this.props.metrics) - ); - const metrics = metricKeys.map((metric) => this.props.metrics[metric]); - - let defaultTitle = translate('code.page'); - if (isApplication(baseComponent?.qualifier)) { - defaultTitle = translate('projects.page'); - } else if (isPortfolioLike(baseComponent?.qualifier)) { - defaultTitle = translate('portfolio_breakdown.page'); - } - - const isPortfolio = isPortfolioLike(qualifier); - return ( -
- - {!canBrowseAllChildProjects && isPortfolio && ( - - - {translate('code_viewer.not_all_measures_are_shown')} - - - - )} - - - {hasComponents && ( - - )} - -
- {!hasComponents && sourceViewer === undefined && ( -
- - {translate( - 'code_viewer.no_source_code_displayed_due_to_empty_analysis', - component.qualifier - )} - -
- )} - {showBreadcrumbs && ( - - )} - -
- {showComponentList && ( - - )} - - {showSearch && ( - - )} - -
- {searchResults?.length === 0 && translate('no_results')} -
-
- - {showComponentList && ( - - )} - - {sourceViewer !== undefined && !showSearch && ( -
- -
- )} -
-
+ ); } } -const StyledAlert = styled(Alert)` - display: inline-flex; - margin-bottom: 15px; -`; - -const AlertContent = styled.div` - display: flex; - align-items: center; -`; - export default withRouter( withComponentContext(withBranchStatusActions(withMetricsContext(CodeApp))) ); diff --git a/server/sonar-web/src/main/js/apps/code/components/CodeAppRenderer.tsx b/server/sonar-web/src/main/js/apps/code/components/CodeAppRenderer.tsx new file mode 100644 index 00000000000..919958d1653 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/code/components/CodeAppRenderer.tsx @@ -0,0 +1,231 @@ +/* + * 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 styled from '@emotion/styled'; +import classNames from 'classnames'; +import { LargeCenteredLayout } from 'design-system'; +import { intersection } from 'lodash'; +import * as React from 'react'; +import { Helmet } from 'react-helmet-async'; +import A11ySkipTarget from '../../../components/a11y/A11ySkipTarget'; +import HelpTooltip from '../../../components/controls/HelpTooltip'; +import ListFooter from '../../../components/controls/ListFooter'; +import Suggestions from '../../../components/embed-docs-modal/Suggestions'; +import { Location } from '../../../components/hoc/withRouter'; +import { Alert } from '../../../components/ui/Alert'; +import { translate } from '../../../helpers/l10n'; +import { BranchLike } from '../../../types/branch-like'; +import { isApplication, isPortfolioLike } from '../../../types/component'; +import { Breadcrumb, Component, ComponentMeasure, Dict, Issue, Metric } from '../../../types/types'; +import '../code.css'; +import { getCodeMetrics } from '../utils'; +import Breadcrumbs from './Breadcrumbs'; +import Components from './Components'; +import Search from './Search'; +import SearchResults from './SearchResults'; +import SourceViewerWrapper from './SourceViewerWrapper'; + +interface Props { + branchLike?: BranchLike; + component: Component; + location: Location; + metrics: Dict; + baseComponent?: ComponentMeasure; + breadcrumbs: Breadcrumb[]; + components?: ComponentMeasure[]; + highlighted?: ComponentMeasure; + loading: boolean; + searchResults?: ComponentMeasure[]; + sourceViewer?: ComponentMeasure; + total: number; + newCodeSelected: boolean; + + handleGoToParent: () => void; + handleHighlight: (highlighted: ComponentMeasure) => void; + handleIssueChange: (issue: Issue) => void; + handleLoadMore: () => void; + handleSearchClear: () => void; + handleSearchResults: (searchResults: ComponentMeasure[]) => void; + handleSelect: (component: ComponentMeasure) => void; + handleSelectNewCode: (newCodeSelected: boolean) => void; +} + +export default function CodeAppRenderer(props: Props) { + const { + branchLike, + component, + location, + baseComponent, + breadcrumbs, + components = [], + highlighted, + loading, + metrics, + newCodeSelected, + total, + searchResults, + sourceViewer, + } = props; + const { canBrowseAllChildProjects, qualifier } = component; + + const showSearch = searchResults !== undefined; + + const hasComponents = components.length > 0 || searchResults !== undefined; + + const showBreadcrumbs = breadcrumbs.length > 1 && !showSearch; + + const showComponentList = sourceViewer === undefined && components.length > 0 && !showSearch; + + const componentsClassName = classNames('boxed-group', 'spacer-top', { + 'new-loading': loading, + 'search-results': showSearch, + }); + + const metricKeys = intersection( + getCodeMetrics(component.qualifier, branchLike, { newCode: newCodeSelected }), + Object.keys(metrics) + ); + const filteredMetrics = metricKeys.map((metric) => metrics[metric]); + + let defaultTitle = translate('code.page'); + if (isApplication(baseComponent?.qualifier)) { + defaultTitle = translate('projects.page'); + } else if (isPortfolioLike(baseComponent?.qualifier)) { + defaultTitle = translate('portfolio_breakdown.page'); + } + + const isPortfolio = isPortfolioLike(qualifier); + + return ( + + + + {!canBrowseAllChildProjects && isPortfolio && ( + + + {translate('code_viewer.not_all_measures_are_shown')} + + + + )} + + + + + + {hasComponents && ( + + )} + +
+ {!hasComponents && sourceViewer === undefined && ( +
+ + {translate( + 'code_viewer.no_source_code_displayed_due_to_empty_analysis', + component.qualifier + )} + +
+ )} + + {showBreadcrumbs && ( + + )} + +
+ {showComponentList && ( + + )} + + {showSearch && ( + + )} + +
+ {searchResults?.length === 0 && translate('no_results')} +
+
+ + {showComponentList && ( + + )} + + {sourceViewer !== undefined && !showSearch && ( +
+ +
+ )} +
+
+ ); +} + +const StyledAlert = styled(Alert)` + display: inline-flex; + margin-bottom: 15px; +`; + +const AlertContent = styled.div` + display: flex; + align-items: center; +`; -- 2.39.5