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 | |
parent | c1f0c20f8115161814cea10814ba97826ab3bc1a (diff) | |
download | sonarqube-76b6ef07b14ca6769d65cba7ee12c178427a60a9.tar.gz sonarqube-76b6ef07b14ca6769d65cba7ee12c178427a60a9.zip |
SONAR-19391 Adopt bubble chart to new design
Diffstat (limited to 'server')
14 files changed, 322 insertions, 235 deletions
diff --git a/server/sonar-web/design-system/src/components/Checkbox.tsx b/server/sonar-web/design-system/src/components/Checkbox.tsx index 648fd6af48c..328205b2deb 100644 --- a/server/sonar-web/design-system/src/components/Checkbox.tsx +++ b/server/sonar-web/design-system/src/components/Checkbox.tsx @@ -27,6 +27,7 @@ import { CheckIcon } from './icons/CheckIcon'; import { CustomIcon } from './icons/Icon'; interface Props { + ariaLabel?: string; checked: boolean; children?: React.ReactNode; className?: string; @@ -42,6 +43,7 @@ interface Props { } export function Checkbox({ + ariaLabel, checked, disabled, children, @@ -65,7 +67,7 @@ export function Checkbox({ <CheckboxContainer className={className} disabled={disabled}> {right && children} <AccessibleCheckbox - aria-label={title} + aria-label={ariaLabel ?? title} checked={checked} disabled={disabled ?? loading} id={id} diff --git a/server/sonar-web/design-system/src/components/ColorsLegend.tsx b/server/sonar-web/design-system/src/components/ColorsLegend.tsx new file mode 100644 index 00000000000..74edf8025a0 --- /dev/null +++ b/server/sonar-web/design-system/src/components/ColorsLegend.tsx @@ -0,0 +1,99 @@ +/* + * 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 { useTheme } from '@emotion/react'; +import styled from '@emotion/styled'; +import tw from 'twin.macro'; +import { BubbleColorVal } from '../types/charts'; +import { Checkbox } from './Checkbox'; +import Tooltip from './Tooltip'; + +import { themeBorder, themeColor, themeContrast } from '../helpers'; + +export interface ColorFilterOption { + ariaLabel?: string; + backgroundColor?: string; + borderColor?: string; + label: React.ReactNode; + overlay?: React.ReactNode; + selected: boolean; + value: string | number; +} + +interface ColorLegendProps { + className?: string; + colors: ColorFilterOption[]; + onColorClick: (color: ColorFilterOption) => void; +} + +export function ColorsLegend(props: ColorLegendProps) { + const { className, colors } = props; + const theme = useTheme(); + + return ( + <ColorsLegendWrapper className={className}> + {colors.map((color, idx) => ( + <li className="sw-ml-4" key={color.value}> + <Tooltip overlay={color.overlay}> + <div> + <Checkbox + ariaLabel={color.ariaLabel} + checked={color.selected} + onCheck={() => { + props.onColorClick(color); + }} + > + <ColorRating + style={ + color.selected + ? { + backgroundColor: + color.borderColor ?? + themeColor(`bubble.${(idx + 1) as BubbleColorVal}`)({ theme }), + borderColor: + color.backgroundColor ?? + themeContrast(`bubble.${(idx + 1) as BubbleColorVal}`)({ theme }), + } + : {} + } + > + {color.label} + </ColorRating> + </Checkbox> + </div> + </Tooltip> + </li> + ))} + </ColorsLegendWrapper> + ); +} + +const ColorsLegendWrapper = styled.ul` + ${tw`sw-flex`} +`; + +const ColorRating = styled.div` + width: 20px; + height: 20px; + line-height: 20px; + border-radius: 50%; + border: ${themeBorder()}; + ${tw`sw-flex sw-justify-center`} + ${tw`sw-ml-1`} +`; diff --git a/server/sonar-web/design-system/src/components/DeferredSpinner.tsx b/server/sonar-web/design-system/src/components/DeferredSpinner.tsx index 711214f2a2e..09c432d6873 100644 --- a/server/sonar-web/design-system/src/components/DeferredSpinner.tsx +++ b/server/sonar-web/design-system/src/components/DeferredSpinner.tsx @@ -41,7 +41,7 @@ const DEFAULT_TIMEOUT = 100; export class DeferredSpinner extends React.PureComponent<Props, State> { timer?: number; - + static displayName = 'DeferredSpinner'; state: State = { showSpinner: false }; componentDidMount() { diff --git a/server/sonar-web/src/main/js/components/charts/__tests__/ColorRatingsLegend-test.tsx b/server/sonar-web/design-system/src/components/__tests__/ColorsLegend-test.tsx index 6073e4d2eee..cb70e5fe760 100644 --- a/server/sonar-web/src/main/js/components/charts/__tests__/ColorRatingsLegend-test.tsx +++ b/server/sonar-web/design-system/src/components/__tests__/ColorsLegend-test.tsx @@ -18,29 +18,40 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import { screen } from '@testing-library/react'; -import * as React from 'react'; -import { renderComponent } from '../../../helpers/testReactTestingUtils'; -import ColorRatingsLegend, { ColorRatingsLegendProps } from '../ColorRatingsLegend'; + +import { render } from '../../helpers/testUtils'; +import { FCProps } from '../../types/misc'; +import { ColorsLegend } from '../ColorsLegend'; + +const colors = [ + { + selected: true, + overlay: 'Overlay A', + label: 'A', + value: '1', + }, + { + selected: true, + overlay: 'Overlay B', + label: 'B', + value: '2', + }, +]; it('should render correctly', () => { - renderColorRatingsLegend(); + renderColorLegend(); expect(screen.getByRole('checkbox', { name: 'A' })).toBeInTheDocument(); expect(screen.getByRole('checkbox', { name: 'B' })).toBeInTheDocument(); - expect(screen.getByRole('checkbox', { name: 'C' })).toBeInTheDocument(); - expect(screen.getByRole('checkbox', { name: 'D' })).toBeInTheDocument(); - expect(screen.getByRole('checkbox', { name: 'E' })).toBeInTheDocument(); }); it('should react when a rating is clicked', () => { - const onRatingClick = jest.fn(); - renderColorRatingsLegend({ onRatingClick }); + const onColorClick = jest.fn(); + renderColorLegend({ onColorClick }); - screen.getByRole('checkbox', { name: 'D' }).click(); - expect(onRatingClick).toHaveBeenCalledWith(4); + screen.getByRole('checkbox', { name: 'A' }).click(); + expect(onColorClick).toHaveBeenCalledWith(colors[0]); }); -function renderColorRatingsLegend(props: Partial<ColorRatingsLegendProps> = {}) { - return renderComponent( - <ColorRatingsLegend filters={{ 2: true }} onRatingClick={jest.fn()} {...props} /> - ); +function renderColorLegend(props: Partial<FCProps<typeof ColorsLegend>> = {}) { + return render(<ColorsLegend colors={colors} onColorClick={jest.fn()} {...props} />); } diff --git a/server/sonar-web/design-system/src/components/index.ts b/server/sonar-web/design-system/src/components/index.ts index 0d41427aa9c..27126e7a328 100644 --- a/server/sonar-web/design-system/src/components/index.ts +++ b/server/sonar-web/design-system/src/components/index.ts @@ -27,6 +27,7 @@ export * from './BubbleChart'; export * from './Card'; export * from './Checkbox'; export * from './CodeSnippet'; +export * from './ColorsLegend'; export * from './CoverageIndicator'; export * from './DatePicker'; export * from './DateRangePicker'; 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 9468d011da4..ed2ace43cdf 100644 --- a/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx +++ b/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx @@ -38,7 +38,11 @@ import GlobalNav from './nav/global/GlobalNav'; import PromotionNotification from './promotion-notification/PromotionNotification'; import UpdateNotification from './update-notification/UpdateNotification'; -const TEMP_PAGELIST_WITH_NEW_BACKGROUND = ['/dashboard', '/security_hotspots']; +const TEMP_PAGELIST_WITH_NEW_BACKGROUND = [ + '/dashboard', + '/security_hotspots', + '/component_measures', +]; export default function GlobalContainer() { // it is important to pass `location` down to `GlobalNav` to trigger render on url change 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; -} diff --git a/server/sonar-web/src/main/js/components/charts/ColorRatingsLegend.tsx b/server/sonar-web/src/main/js/components/charts/ColorRatingsLegend.tsx deleted file mode 100644 index c3706bde87a..00000000000 --- a/server/sonar-web/src/main/js/components/charts/ColorRatingsLegend.tsx +++ /dev/null @@ -1,69 +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 classNames from 'classnames'; -import * as React from 'react'; -import Tooltip from '../../components/controls/Tooltip'; -import { RATING_COLORS } from '../../helpers/constants'; -import { translateWithParameters } from '../../helpers/l10n'; -import { formatMeasure } from '../../helpers/measures'; -import Checkbox from '../controls/Checkbox'; -import './ColorBoxLegend.css'; - -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; - return ( - <ul className={classNames('color-box-legend', className)}> - {RATINGS.map((rating) => ( - <li key={rating}> - <Tooltip - overlay={translateWithParameters( - 'component_measures.legend.help_x', - formatMeasure(rating, 'RATING') - )} - > - <Checkbox - className="display-flex-center" - checked={!filters[rating]} - onCheck={() => props.onRatingClick(rating)} - > - <span - className="color-box-legend-rating little-spacer-left" - style={{ - borderColor: RATING_COLORS[rating - 1].stroke, - backgroundColor: RATING_COLORS[rating - 1].fillTransparent, - }} - > - {formatMeasure(rating, 'RATING')} - </span> - </Checkbox> - </Tooltip> - </li> - ))} - </ul> - ); -} |