aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorstanislavh <stanislav.honcharov@sonarsource.com>2023-06-01 13:31:17 +0200
committersonartech <sonartech@sonarsource.com>2023-06-05 20:02:47 +0000
commit76b6ef07b14ca6769d65cba7ee12c178427a60a9 (patch)
tree9bce14f396d11e1b6d296c9feae7694d68438178 /server
parentc1f0c20f8115161814cea10814ba97826ab3bc1a (diff)
downloadsonarqube-76b6ef07b14ca6769d65cba7ee12c178427a60a9.tar.gz
sonarqube-76b6ef07b14ca6769d65cba7ee12c178427a60a9.zip
SONAR-19391 Adopt bubble chart to new design
Diffstat (limited to 'server')
-rw-r--r--server/sonar-web/design-system/src/components/Checkbox.tsx4
-rw-r--r--server/sonar-web/design-system/src/components/ColorsLegend.tsx99
-rw-r--r--server/sonar-web/design-system/src/components/DeferredSpinner.tsx2
-rw-r--r--server/sonar-web/design-system/src/components/__tests__/ColorsLegend-test.tsx (renamed from server/sonar-web/src/main/js/components/charts/__tests__/ColorRatingsLegend-test.tsx)41
-rw-r--r--server/sonar-web/design-system/src/components/index.ts1
-rw-r--r--server/sonar-web/src/main/js/app/components/GlobalContainer.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/ComponentMeasuresApp.tsx3
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/MeasureContentHeader.tsx10
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverview.tsx41
-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.tsx75
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/sidebar/DomainSubnavigation.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/style.css70
-rw-r--r--server/sonar-web/src/main/js/components/charts/ColorRatingsLegend.tsx69
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>
- );
-}