aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main/js/apps
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/sonar-web/src/main/js/apps
parentc1f0c20f8115161814cea10814ba97826ab3bc1a (diff)
downloadsonarqube-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.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
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;
-}