diff options
author | stanislavh <stanislav.honcharov@sonarsource.com> | 2023-06-01 13:31:17 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2023-06-05 20:02:47 +0000 |
commit | 76b6ef07b14ca6769d65cba7ee12c178427a60a9 (patch) | |
tree | 9bce14f396d11e1b6d296c9feae7694d68438178 /server/sonar-web/src/main/js/apps | |
parent | c1f0c20f8115161814cea10814ba97826ab3bc1a (diff) | |
download | sonarqube-76b6ef07b14ca6769d65cba7ee12c178427a60a9.tar.gz sonarqube-76b6ef07b14ca6769d65cba7ee12c178427a60a9.zip |
SONAR-19391 Adopt bubble chart to new design
Diffstat (limited to 'server/sonar-web/src/main/js/apps')
-rw-r--r-- | server/sonar-web/src/main/js/apps/component-measures/components/ComponentMeasuresApp.tsx | 3 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/apps/component-measures/components/MeasureContentHeader.tsx | 10 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverview.tsx | 41 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/apps/component-measures/drilldown/BubbleChartView.tsx (renamed from server/sonar-web/src/main/js/apps/component-measures/drilldown/BubbleChart.tsx) | 134 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/apps/component-measures/drilldown/ColorRatingsLegend.tsx | 75 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/apps/component-measures/sidebar/DomainSubnavigation.tsx | 2 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/apps/component-measures/style.css | 70 |
7 files changed, 187 insertions, 148 deletions
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 4cbf1d5d3f6..7a90f5734fd 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 @@ -218,7 +218,7 @@ class ComponentMeasuresApp extends React.PureComponent<Props, State> { if (displayOverview) { return ( - <StyledMain className="sw-rounded-1 sw-p-6 sw-mb-4"> + <StyledMain className="sw-rounded-1 sw-mb-4"> <MeasureOverviewContainer branchLike={branchLike} domain={query.metric} @@ -333,7 +333,6 @@ function AppWithComponentContext() { export default AppWithComponentContext; const StyledMain = withTheme(styled.main` - background-color: ${themeColor('filterbar')}; background-color: ${themeColor('pageBlock')}; border: ${themeBorder('default', 'pageBlockBorder')}; `); 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 8f061c91919..21af06f9157 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 @@ -17,6 +17,8 @@ * 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 { themeBorder } from 'design-system'; import * as React from 'react'; interface Props { @@ -26,9 +28,13 @@ interface Props { export default function MeasureContentHeader({ left, right }: Props) { return ( - <div> + <StyledHeader className="sw-py-3 sw-px-6 sw-flex sw-justify-between sw-items-center"> <div>{left}</div> <div>{right}</div> - </div> + </StyledHeader> ); } + +const StyledHeader = styled.div` + border-bottom: ${themeBorder('default', 'pageBlockBorder')}; +`; 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 2bac35e3559..855983957b9 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 @@ -17,11 +17,11 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { DeferredSpinner } from 'design-system'; import * as React from 'react'; import { getComponentLeaves } from '../../../api/components'; 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'; import { BranchLike } from '../../../types/branch-like'; @@ -36,7 +36,7 @@ import { Paging, Period, } from '../../../types/types'; -import BubbleChart from '../drilldown/BubbleChart'; +import BubbleChartView from '../drilldown/BubbleChartView'; import { BUBBLES_FETCH_LIMIT, enhanceComponent, getBubbleMetrics, hasFullMeasures } from '../utils'; import LeakPeriodLegend from './LeakPeriodLegend'; import MeasureContentHeader from './MeasureContentHeader'; @@ -121,11 +121,11 @@ export default class MeasureOverview extends React.PureComponent<Props, State> { ); }; - renderContent() { + renderContent(isFile: boolean) { const { branchLike, component, domain, metrics } = this.props; const { paging } = this.state; - if (isFile(component.qualifier)) { + if (isFile) { return ( <div className="measure-details-viewer"> <SourceViewer @@ -138,8 +138,8 @@ export default class MeasureOverview extends React.PureComponent<Props, State> { } return ( - <BubbleChart - componentKey={component.key} + <BubbleChartView + component={component} branchLike={branchLike} components={this.state.components} domain={domain} @@ -153,6 +153,8 @@ export default class MeasureOverview extends React.PureComponent<Props, State> { render() { const { branchLike, className, component, leakPeriod, loading, rootComponent } = this.props; const displayLeak = hasFullMeasures(branchLike); + const isFileComponent = isFile(component.qualifier); + return ( <div className={className}> <A11ySkipTarget anchor="measures_main" /> @@ -168,19 +170,26 @@ export default class MeasureOverview extends React.PureComponent<Props, State> { /> } right={ - <PageActions - componentQualifier={rootComponent.qualifier} - current={this.state.components.length} - /> + <> + <PageActions + componentQualifier={rootComponent.qualifier} + current={this.state.components.length} + /> + {leakPeriod && displayLeak && ( + <LeakPeriodLegend + className="pull-right" + component={component} + period={leakPeriod} + /> + )} + </> } /> - {leakPeriod && displayLeak && ( - <LeakPeriodLegend className="pull-right" component={component} period={leakPeriod} /> - )} - <DeferredSpinner loading={loading} /> - - {!loading && this.renderContent()} + <div className="sw-p-6"> + <DeferredSpinner loading={loading} /> + {!loading && this.renderContent(isFileComponent)} + </div> </div> ); } diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/BubbleChart.tsx b/server/sonar-web/src/main/js/apps/component-measures/drilldown/BubbleChartView.tsx index f7d36f2b5e8..9eb1d4ac818 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/drilldown/BubbleChart.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/BubbleChartView.tsx @@ -17,10 +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 { BubbleColorVal, BubbleChart as OriginalBubbleChart } from 'design-system'; +import styled from '@emotion/styled'; +import { + BubbleColorVal, + HelperHintIcon, + Highlight, + Link, + BubbleChart as OriginalBubbleChart, + themeColor, +} from 'design-system'; import * as React from 'react'; -import ColorRatingsLegend from '../../../components/charts/ColorRatingsLegend'; -import Link from '../../../components/common/Link'; import HelpTooltip from '../../../components/controls/HelpTooltip'; import { getLocalizedMetricDomain, @@ -32,10 +38,11 @@ import { formatMeasure, isDiffMetric } from '../../../helpers/measures'; import { isDefined } from '../../../helpers/types'; import { getComponentDrilldownUrl } from '../../../helpers/urls'; import { BranchLike } from '../../../types/branch-like'; -import { isProject } from '../../../types/component'; +import { isProject, isView } from '../../../types/component'; import { MetricKey } from '../../../types/metrics'; import { ComponentMeasureEnhanced, + ComponentMeasure as ComponentMeasureI, ComponentMeasureIntern, Dict, Metric, @@ -47,12 +54,13 @@ import { getBubbleYDomain, isProjectOverview, } from '../utils'; +import ColorRatingsLegend from './ColorRatingsLegend'; import EmptyResult from './EmptyResult'; const HEIGHT = 500; interface Props { - componentKey: string; + component: ComponentMeasureI; components: ComponentMeasureEnhanced[]; branchLike?: BranchLike; domain: string; @@ -65,7 +73,7 @@ interface State { ratingFilters: { [rating: number]: boolean }; } -export default class BubbleChart extends React.PureComponent<Props, State> { +export default class BubbleChartView extends React.PureComponent<Props, State> { state: State = { ratingFilters: {}, }; @@ -102,7 +110,7 @@ export default class BubbleChart extends React.PureComponent<Props, State> { }); } return ( - <div className="text-left"> + <div className="sw-text-left"> {inner.map((line, index) => ( <React.Fragment key={index}> {line} @@ -180,7 +188,7 @@ export default class BubbleChart extends React.PureComponent<Props, State> { height={HEIGHT} items={items} onBubbleClick={this.handleBubbleClick} - padding={[0, 4, 50, 60]} + padding={[0, 4, 50, 100]} yDomain={getBubbleYDomain(this.props.domain)} xDomain={xDomain} /> @@ -189,7 +197,8 @@ export default class BubbleChart extends React.PureComponent<Props, State> { renderChartHeader(domain: string, sizeMetric: Metric, colorsMetric?: Metric[]) { const { ratingFilters } = this.state; - const { paging } = this.props; + const { paging, component, branchLike, metrics: propsMetrics } = this.props; + const metrics = getBubbleMetrics(domain, propsMetrics); const title = isProjectOverview(domain) ? translate('component_measures.overview', domain, 'title') @@ -197,40 +206,58 @@ export default class BubbleChart extends React.PureComponent<Props, State> { 'component_measures.domain_x_overview', getLocalizedMetricDomain(domain) ); + return ( - <div className="measure-overview-bubble-chart-header"> - <span className="measure-overview-bubble-chart-title"> - <div className="display-flex-center"> - {title} - <HelpTooltip className="spacer-left" overlay={this.getDescription(domain)} /> + <div className="sw-flex sw-justify-between sw-gap-3"> + <div> + <div className="sw-flex sw-items-center sw-whitespace-nowrap"> + <Highlight className="it__measure-overview-bubble-chart-title">{title}</Highlight> + <HelpTooltip className="spacer-left" overlay={this.getDescription(domain)}> + <HelperHintIcon /> + </HelpTooltip> </div> {paging?.total && paging?.total > BUBBLES_FETCH_LIMIT && ( - <div className="note spacer-top"> + <div className="sw-mt-2"> ({translate('component_measures.legend.only_first_500_files')}) </div> )} - </span> - <span className="measure-overview-bubble-chart-legend"> - <span className="note"> + {(isView(component?.qualifier) || isProject(component?.qualifier)) && ( + <div className="sw-mt-2"> + <Link + to={getComponentDrilldownUrl({ + componentKey: component.key, + branchLike, + metric: isProjectOverview(domain) ? MetricKey.violations : metrics.size.key, + listView: true, + })} + > + {translate('component_measures.overview.see_data_as_list')} + </Link> + </div> + )} + </div> + + <div className="sw-flex sw-flex-col sw-items-end"> + <div className="sw-text-right"> {colorsMetric && ( - <span className="spacer-right"> - {translateWithParameters( - 'component_measures.legend.color_x', - colorsMetric.length > 1 - ? translateWithParameters( - 'component_measures.legend.worse_of_x_y', - ...colorsMetric.map((metric) => getLocalizedMetricName(metric)) - ) - : getLocalizedMetricName(colorsMetric[0]) - )} + <span className="sw-mr-3"> + <strong className="sw-body-sm-highlight"> + {translate('component_measures.legend.color')} + </strong>{' '} + {colorsMetric.length > 1 + ? translateWithParameters( + 'component_measures.legend.worse_of_x_y', + ...colorsMetric.map((metric) => getLocalizedMetricName(metric)) + ) + : getLocalizedMetricName(colorsMetric[0])} </span> )} - {translateWithParameters( - 'component_measures.legend.size_x', - getLocalizedMetricName(sizeMetric) - )} - </span> + <strong className="sw-body-sm-highlight"> + {translate('component_measures.legend.size')} + </strong>{' '} + {getLocalizedMetricName(sizeMetric)} + </div> {colorsMetric && ( <ColorRatingsLegend className="spacer-top" @@ -238,7 +265,7 @@ export default class BubbleChart extends React.PureComponent<Props, State> { onRatingClick={this.handleRatingFilterClick} /> )} - </span> + </div> </div> ); } @@ -247,34 +274,27 @@ export default class BubbleChart extends React.PureComponent<Props, State> { if (this.props.components.length <= 0) { return <EmptyResult />; } - const { domain, componentKey, branchLike } = this.props; + const { domain } = this.props; const metrics = getBubbleMetrics(domain, this.props.metrics); return ( - <div className="measure-overview-bubble-chart"> + <BubbleChartWrapper className="sw-relative sw-body-sm"> {this.renderChartHeader(domain, metrics.size, metrics.colors)} - <div className="measure-overview-bubble-chart-content"> - <div className="text-center small spacer-top spacer-bottom"> - <Link - to={getComponentDrilldownUrl({ - componentKey, - branchLike, - metric: isProjectOverview(domain) ? MetricKey.violations : metrics.size.key, - listView: true, - })} - > - {translate('component_measures.overview.see_data_as_list')} - </Link> - </div> - {this.renderBubbleChart(metrics)} - </div> - <div className="measure-overview-bubble-chart-axis x"> - {getLocalizedMetricName(metrics.x)} - </div> - <div className="measure-overview-bubble-chart-axis y"> + {this.renderBubbleChart(metrics)} + <div className="sw-text-center">{getLocalizedMetricName(metrics.x)}</div> + <YAxis className="sw-absolute sw-top-1/2 sw-left-3"> {getLocalizedMetricName(metrics.y)} - </div> - </div> + </YAxis> + </BubbleChartWrapper> ); } } + +const BubbleChartWrapper = styled.div` + color: ${themeColor('pageContentLight')}; +`; + +const YAxis = styled.div` + transform: rotate(-90deg) translateX(-50%); + transform-origin: left; +`; diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/ColorRatingsLegend.tsx b/server/sonar-web/src/main/js/apps/component-measures/drilldown/ColorRatingsLegend.tsx new file mode 100644 index 00000000000..63b303bc96b --- /dev/null +++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/ColorRatingsLegend.tsx @@ -0,0 +1,75 @@ +/* + * 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. + */ + +/* + * 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 { ColorFilterOption, ColorsLegend } from 'design-system'; +import * as React from 'react'; +import { translateWithParameters } from '../../../helpers/l10n'; +import { formatMeasure } from '../../../helpers/measures'; +import { MetricType } from '../../../types/metrics'; + +export interface ColorRatingsLegendProps { + className?: string; + filters: { [rating: number]: boolean }; + onRatingClick: (selection: number) => void; +} + +const RATINGS = [1, 2, 3, 4, 5]; + +export default function ColorRatingsLegend(props: ColorRatingsLegendProps) { + const { className, filters } = props; + + const ratingsColors = RATINGS.map((rating) => { + const formattedMeasure = formatMeasure(rating, MetricType.Rating); + return { + overlay: translateWithParameters('component_measures.legend.help_x', formattedMeasure), + ariaLabel: translateWithParameters('component_measures.legend.help_x', formattedMeasure), + label: formattedMeasure, + value: rating, + selected: !filters[rating], + }; + }); + + const handleColorClick = (color: ColorFilterOption) => { + props.onRatingClick(color.value as number); + }; + + return ( + <ColorsLegend className={className} colors={ratingsColors} onColorClick={handleColorClick} /> + ); +} diff --git a/server/sonar-web/src/main/js/apps/component-measures/sidebar/DomainSubnavigation.tsx b/server/sonar-web/src/main/js/apps/component-measures/sidebar/DomainSubnavigation.tsx index f308d9ae487..7828bbc1689 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/sidebar/DomainSubnavigation.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/sidebar/DomainSubnavigation.tsx @@ -92,7 +92,7 @@ export default function DomainSubnavigation(props: Props) { {sortedItems.map((item) => typeof item === 'string' ? ( showFullMeasures && ( - <SubnavigationSubheading> + <SubnavigationSubheading key={item}> {translate('component_measures.subnavigation_category', item)} </SubnavigationSubheading> ) diff --git a/server/sonar-web/src/main/js/apps/component-measures/style.css b/server/sonar-web/src/main/js/apps/component-measures/style.css index 251b014ba44..e4d8107cab4 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/style.css +++ b/server/sonar-web/src/main/js/apps/component-measures/style.css @@ -17,19 +17,6 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -.domain-measures-value { - margin-right: 4px; -} - -.domain-measures-value span { - line-height: 16px; -} - -.domain-measures-value .rating { - margin-left: -4px; - margin-right: -4px; -} - button.search-navigator-facet { text-align: start; } @@ -139,60 +126,3 @@ button.search-navigator-facet { .measure-favorite svg { vertical-align: middle; } - -.measure-overview-bubble-chart { - position: relative; - border: 1px solid var(--barBorderColor); - background-color: #fff; -} - -.measure-overview-bubble-chart-content { - padding: 0; - padding-left: 60px; -} - -.measure-overview-bubble-chart-header { - display: flex; - align-items: center; - padding: 16px; - border-bottom: 1px solid var(--barBorderColor); -} - -.measure-overview-bubble-chart-title { - position: absolute; -} - -.measure-overview-bubble-chart-legend { - display: flex; - flex-direction: column; - text-align: center; - flex-grow: 1; -} - -.measure-overview-bubble-chart-footer { - padding: 15px 60px; - border-top: 1px solid var(--barBorderColor); - text-align: center; - font-size: var(--smallFontSize); - line-height: 1.4; -} - -.measure-overview-bubble-chart-axis { - color: var(--secondFontColor); - font-size: var(--smallFontSize); -} - -.measure-overview-bubble-chart-axis.x { - position: relative; - top: -8px; - padding-bottom: 8px; - text-align: center; -} - -.measure-overview-bubble-chart-axis.y { - position: absolute; - top: 50%; - left: 30px; - transform: rotate(-90deg) translateX(-50%); - transform-origin: left; -} |