aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWouter Admiraal <wouter.admiraal@sonarsource.com>2021-08-24 10:44:41 +0200
committersonartech <sonartech@sonarsource.com>2021-08-26 20:04:29 +0000
commit3127e9e6821be46440f1ad89b5e56c47f10fd38b (patch)
tree5d73efed7536c5191b9163af35443e1906474f33
parent9bd612f143a36272b6f2d06145e140b2d81bff90 (diff)
downloadsonarqube-3127e9e6821be46440f1ad89b5e56c47f10fd38b.tar.gz
sonarqube-3127e9e6821be46440f1ad89b5e56c47f10fd38b.zip
SONAR-11538 Explain bubble charts only take into account 1st 500 files
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverview.tsx37
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureOverview-test.tsx132
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureOverview-test.tsx.snap428
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/drilldown/BubbleChart.tsx21
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/BubbleChart-test.tsx17
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/__snapshots__/BubbleChart-test.tsx.snap231
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/utils.ts1
-rw-r--r--server/sonar-web/src/main/js/helpers/mocks/component.ts32
-rw-r--r--server/sonar-web/src/main/js/helpers/testMocks.ts9
-rw-r--r--sonar-core/src/main/resources/org/sonar/l10n/core.properties1
10 files changed, 867 insertions, 42 deletions
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 9df6b91c619..abc99393977 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
@@ -26,7 +26,13 @@ import PageActions from '../../../components/ui/PageActions';
import { getBranchLikeQuery, isSameBranchLike } from '../../../helpers/branch-like';
import { BranchLike } from '../../../types/branch-like';
import BubbleChart from '../drilldown/BubbleChart';
-import { enhanceComponent, getBubbleMetrics, hasFullMeasures, isFileType } from '../utils';
+import {
+ BUBBLES_FETCH_LIMIT,
+ enhanceComponent,
+ getBubbleMetrics,
+ hasFullMeasures,
+ isFileType
+} from '../utils';
import Breadcrumbs from './Breadcrumbs';
import LeakPeriodLegend from './LeakPeriodLegend';
import MeasureContentHeader from './MeasureContentHeader';
@@ -50,8 +56,6 @@ interface State {
paging?: T.Paging;
}
-const BUBBLES_LIMIT = 500;
-
export default class MeasureOverview extends React.PureComponent<Props, State> {
mounted = false;
state: State = { components: [] };
@@ -92,7 +96,7 @@ export default class MeasureOverview extends React.PureComponent<Props, State> {
s: 'metric',
metricSort: size.key,
asc: false,
- ps: BUBBLES_LIMIT
+ ps: BUBBLES_FETCH_LIMIT
};
this.props.updateLoading({ bubbles: true });
@@ -101,9 +105,7 @@ export default class MeasureOverview extends React.PureComponent<Props, State> {
if (domain === this.props.domain) {
if (this.mounted) {
this.setState({
- components: r.components.map(component =>
- enhanceComponent(component, undefined, metrics)
- ),
+ components: r.components.map(c => enhanceComponent(c, undefined, metrics)),
paging: r.paging
});
}
@@ -115,7 +117,9 @@ export default class MeasureOverview extends React.PureComponent<Props, State> {
};
renderContent() {
- const { branchLike, component } = this.props;
+ const { branchLike, component, domain, metrics } = this.props;
+ const { paging } = this.state;
+
if (isFileType(component)) {
return (
<div className="measure-details-viewer">
@@ -130,21 +134,21 @@ export default class MeasureOverview extends React.PureComponent<Props, State> {
return (
<BubbleChart
- component={this.props.component}
+ component={component}
components={this.state.components}
- domain={this.props.domain}
- metrics={this.props.metrics}
+ domain={domain}
+ metrics={metrics}
+ paging={paging}
updateSelected={this.props.updateSelected}
/>
);
}
render() {
- const { branchLike, component, leakPeriod, rootComponent } = this.props;
- const { paging } = this.state;
+ const { branchLike, className, component, leakPeriod, loading, rootComponent } = this.props;
const displayLeak = hasFullMeasures(branchLike);
return (
- <div className={this.props.className}>
+ <div className={className}>
<div className="layout-page-header-panel layout-page-main-header">
<A11ySkipTarget anchor="measures_main" />
@@ -165,7 +169,6 @@ export default class MeasureOverview extends React.PureComponent<Props, State> {
<PageActions
componentQualifier={rootComponent.qualifier}
current={this.state.components.length}
- total={paging && paging.total}
/>
}
/>
@@ -178,8 +181,8 @@ export default class MeasureOverview extends React.PureComponent<Props, State> {
<LeakPeriodLegend className="pull-right" component={component} period={leakPeriod} />
)}
</div>
- <DeferredSpinner loading={this.props.loading} />
- {!this.props.loading && this.renderContent()}
+ <DeferredSpinner loading={loading} />
+ {!loading && this.renderContent()}
</div>
</div>
);
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureOverview-test.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureOverview-test.tsx
new file mode 100644
index 00000000000..77923e3f982
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureOverview-test.tsx
@@ -0,0 +1,132 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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 { shallow } from 'enzyme';
+import { keyBy } from 'lodash';
+import * as React from 'react';
+import { getComponentLeaves } from '../../../../api/components';
+import { mockBranch } from '../../../../helpers/mocks/branch-like';
+import { mockComponentMeasureEnhanced } from '../../../../helpers/mocks/component';
+import {
+ mockComponentMeasure,
+ mockMeasure,
+ mockMeasureEnhanced,
+ mockMetric,
+ mockPeriod
+} from '../../../../helpers/testMocks';
+import { waitAndUpdate } from '../../../../helpers/testUtils';
+import { MetricKey } from '../../../../types/metrics';
+import { BUBBLES_FETCH_LIMIT } from '../../utils';
+import MeasureOverview from '../MeasureOverview';
+
+jest.mock('../../../../api/components', () => ({
+ getComponentLeaves: jest
+ .fn()
+ .mockResolvedValue({ components: [], paging: { total: 200, pageIndex: 1, pageSize: 100 } })
+}));
+
+beforeEach(jest.clearAllMocks);
+
+it('should render correctly', () => {
+ expect(shallowRender()).toMatchSnapshot('default');
+ expect(shallowRender({ loading: true })).toMatchSnapshot('loading');
+ expect(shallowRender({ leakPeriod: mockPeriod(), branchLike: mockBranch() })).toMatchSnapshot(
+ 'has leak period'
+ );
+ expect(shallowRender({ component: mockComponentMeasure(true) })).toMatchSnapshot('is file');
+});
+
+it('should correctly enhance leaf components', async () => {
+ (getComponentLeaves as jest.Mock).mockResolvedValueOnce({
+ components: [
+ mockComponentMeasure(false, { measures: [mockMeasure({ metric: MetricKey.bugs })] })
+ ]
+ });
+ const updateLoading = jest.fn();
+ const wrapper = shallowRender({ updateLoading });
+
+ expect(updateLoading).toBeCalledWith({ bubbles: true });
+ expect(getComponentLeaves).toHaveBeenCalledWith(
+ 'foo',
+ [
+ MetricKey.ncloc,
+ MetricKey.reliability_remediation_effort,
+ MetricKey.bugs,
+ MetricKey.reliability_rating
+ ],
+ expect.objectContaining({ metricSort: MetricKey.bugs, s: 'metric', ps: BUBBLES_FETCH_LIMIT })
+ );
+
+ await waitAndUpdate(wrapper);
+
+ expect(wrapper.state().components).toEqual([
+ mockComponentMeasureEnhanced({
+ measures: [
+ mockMeasureEnhanced({
+ leak: '1.0',
+ metric: mockMetric({ key: MetricKey.bugs, type: 'INT' })
+ })
+ ]
+ })
+ ]);
+ expect(updateLoading).toHaveBeenLastCalledWith({ bubbles: false });
+});
+
+it('should not enhance file components', () => {
+ shallowRender({ component: mockComponentMeasure(true) });
+ expect(getComponentLeaves).not.toHaveBeenCalled();
+});
+
+it('should correctly flag itself as (un)mounted', () => {
+ const wrapper = shallowRender();
+ const instance = wrapper.instance();
+ expect(instance.mounted).toBe(true);
+ wrapper.unmount();
+ expect(instance.mounted).toBe(false);
+});
+
+function shallowRender(props: Partial<MeasureOverview['props']> = {}) {
+ return shallow<MeasureOverview>(
+ <MeasureOverview
+ component={mockComponentMeasure()}
+ domain="Reliability"
+ loading={false}
+ metrics={keyBy(
+ [
+ mockMetric({ key: MetricKey.ncloc, type: 'INT' }),
+ mockMetric({
+ key: MetricKey.reliability_remediation_effort,
+ type: 'INT'
+ }),
+ mockMetric({ key: MetricKey.bugs, type: 'INT' }),
+ mockMetric({
+ key: MetricKey.reliability_rating,
+ type: 'DATA'
+ })
+ ],
+ m => m.key
+ )}
+ updateLoading={jest.fn()}
+ updateSelected={jest.fn()}
+ rootComponent={mockComponentMeasure()}
+ {...props}
+ />
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureOverview-test.tsx.snap b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureOverview-test.tsx.snap
new file mode 100644
index 00000000000..170556dc4d0
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureOverview-test.tsx.snap
@@ -0,0 +1,428 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly: default 1`] = `
+<div>
+ <div
+ className="layout-page-header-panel layout-page-main-header"
+ >
+ <A11ySkipTarget
+ anchor="measures_main"
+ />
+ <div
+ className="layout-page-header-panel-inner layout-page-main-header-inner"
+ >
+ <div
+ className="layout-page-main-inner"
+ >
+ <MeasureContentHeader
+ left={
+ <Breadcrumbs
+ backToFirst={true}
+ className="text-ellipsis"
+ component={
+ Object {
+ "key": "foo",
+ "measures": Array [
+ Object {
+ "bestValue": false,
+ "metric": "bugs",
+ "value": "12",
+ },
+ ],
+ "name": "Foo",
+ "qualifier": "TRK",
+ }
+ }
+ handleSelect={[MockFunction]}
+ rootComponent={
+ Object {
+ "key": "foo",
+ "measures": Array [
+ Object {
+ "bestValue": false,
+ "metric": "bugs",
+ "value": "12",
+ },
+ ],
+ "name": "Foo",
+ "qualifier": "TRK",
+ }
+ }
+ />
+ }
+ right={
+ <PageActions
+ componentQualifier="TRK"
+ current={0}
+ />
+ }
+ />
+ </div>
+ </div>
+ </div>
+ <div
+ className="layout-page-main-inner measure-details-content"
+ >
+ <div
+ className="clearfix big-spacer-bottom"
+ />
+ <DeferredSpinner
+ loading={false}
+ />
+ <BubbleChart
+ component={
+ Object {
+ "key": "foo",
+ "measures": Array [
+ Object {
+ "bestValue": false,
+ "metric": "bugs",
+ "value": "12",
+ },
+ ],
+ "name": "Foo",
+ "qualifier": "TRK",
+ }
+ }
+ components={Array []}
+ domain="Reliability"
+ metrics={
+ Object {
+ "bugs": Object {
+ "id": "bugs",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "INT",
+ },
+ "ncloc": Object {
+ "id": "ncloc",
+ "key": "ncloc",
+ "name": "Ncloc",
+ "type": "INT",
+ },
+ "reliability_rating": Object {
+ "id": "reliability_rating",
+ "key": "reliability_rating",
+ "name": "Reliability_rating",
+ "type": "DATA",
+ },
+ "reliability_remediation_effort": Object {
+ "id": "reliability_remediation_effort",
+ "key": "reliability_remediation_effort",
+ "name": "Reliability_remediation_effort",
+ "type": "INT",
+ },
+ }
+ }
+ updateSelected={[MockFunction]}
+ />
+ </div>
+</div>
+`;
+
+exports[`should render correctly: has leak period 1`] = `
+<div>
+ <div
+ className="layout-page-header-panel layout-page-main-header"
+ >
+ <A11ySkipTarget
+ anchor="measures_main"
+ />
+ <div
+ className="layout-page-header-panel-inner layout-page-main-header-inner"
+ >
+ <div
+ className="layout-page-main-inner"
+ >
+ <MeasureContentHeader
+ left={
+ <Breadcrumbs
+ backToFirst={true}
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": false,
+ "name": "branch-6.7",
+ }
+ }
+ className="text-ellipsis"
+ component={
+ Object {
+ "key": "foo",
+ "measures": Array [
+ Object {
+ "bestValue": false,
+ "metric": "bugs",
+ "value": "12",
+ },
+ ],
+ "name": "Foo",
+ "qualifier": "TRK",
+ }
+ }
+ handleSelect={[MockFunction]}
+ rootComponent={
+ Object {
+ "key": "foo",
+ "measures": Array [
+ Object {
+ "bestValue": false,
+ "metric": "bugs",
+ "value": "12",
+ },
+ ],
+ "name": "Foo",
+ "qualifier": "TRK",
+ }
+ }
+ />
+ }
+ right={
+ <PageActions
+ componentQualifier="TRK"
+ current={0}
+ />
+ }
+ />
+ </div>
+ </div>
+ </div>
+ <div
+ className="layout-page-main-inner measure-details-content"
+ >
+ <div
+ className="clearfix big-spacer-bottom"
+ >
+ <InjectIntl(LeakPeriodLegend)
+ className="pull-right"
+ component={
+ Object {
+ "key": "foo",
+ "measures": Array [
+ Object {
+ "bestValue": false,
+ "metric": "bugs",
+ "value": "12",
+ },
+ ],
+ "name": "Foo",
+ "qualifier": "TRK",
+ }
+ }
+ period={
+ Object {
+ "date": "2019-04-23T02:12:32+0100",
+ "index": 0,
+ "mode": "previous_version",
+ }
+ }
+ />
+ </div>
+ <DeferredSpinner
+ loading={false}
+ />
+ <BubbleChart
+ component={
+ Object {
+ "key": "foo",
+ "measures": Array [
+ Object {
+ "bestValue": false,
+ "metric": "bugs",
+ "value": "12",
+ },
+ ],
+ "name": "Foo",
+ "qualifier": "TRK",
+ }
+ }
+ components={Array []}
+ domain="Reliability"
+ metrics={
+ Object {
+ "bugs": Object {
+ "id": "bugs",
+ "key": "bugs",
+ "name": "Bugs",
+ "type": "INT",
+ },
+ "ncloc": Object {
+ "id": "ncloc",
+ "key": "ncloc",
+ "name": "Ncloc",
+ "type": "INT",
+ },
+ "reliability_rating": Object {
+ "id": "reliability_rating",
+ "key": "reliability_rating",
+ "name": "Reliability_rating",
+ "type": "DATA",
+ },
+ "reliability_remediation_effort": Object {
+ "id": "reliability_remediation_effort",
+ "key": "reliability_remediation_effort",
+ "name": "Reliability_remediation_effort",
+ "type": "INT",
+ },
+ }
+ }
+ updateSelected={[MockFunction]}
+ />
+ </div>
+</div>
+`;
+
+exports[`should render correctly: is file 1`] = `
+<div>
+ <div
+ className="layout-page-header-panel layout-page-main-header"
+ >
+ <A11ySkipTarget
+ anchor="measures_main"
+ />
+ <div
+ className="layout-page-header-panel-inner layout-page-main-header-inner"
+ >
+ <div
+ className="layout-page-main-inner"
+ >
+ <MeasureContentHeader
+ left={
+ <Breadcrumbs
+ backToFirst={true}
+ className="text-ellipsis"
+ component={
+ Object {
+ "key": "foo:src/index.tsx",
+ "measures": Array [
+ Object {
+ "bestValue": false,
+ "metric": "bugs",
+ "value": "1",
+ },
+ ],
+ "name": "index.tsx",
+ "path": "src/index.tsx",
+ "qualifier": "FIL",
+ }
+ }
+ handleSelect={[MockFunction]}
+ rootComponent={
+ Object {
+ "key": "foo",
+ "measures": Array [
+ Object {
+ "bestValue": false,
+ "metric": "bugs",
+ "value": "12",
+ },
+ ],
+ "name": "Foo",
+ "qualifier": "TRK",
+ }
+ }
+ />
+ }
+ right={
+ <PageActions
+ componentQualifier="TRK"
+ current={0}
+ />
+ }
+ />
+ </div>
+ </div>
+ </div>
+ <div
+ className="layout-page-main-inner measure-details-content"
+ >
+ <div
+ className="clearfix big-spacer-bottom"
+ />
+ <DeferredSpinner
+ loading={false}
+ />
+ <div
+ className="measure-details-viewer"
+ >
+ <SourceViewer
+ component="foo:src/index.tsx"
+ />
+ </div>
+ </div>
+</div>
+`;
+
+exports[`should render correctly: loading 1`] = `
+<div>
+ <div
+ className="layout-page-header-panel layout-page-main-header"
+ >
+ <A11ySkipTarget
+ anchor="measures_main"
+ />
+ <div
+ className="layout-page-header-panel-inner layout-page-main-header-inner"
+ >
+ <div
+ className="layout-page-main-inner"
+ >
+ <MeasureContentHeader
+ left={
+ <Breadcrumbs
+ backToFirst={true}
+ className="text-ellipsis"
+ component={
+ Object {
+ "key": "foo",
+ "measures": Array [
+ Object {
+ "bestValue": false,
+ "metric": "bugs",
+ "value": "12",
+ },
+ ],
+ "name": "Foo",
+ "qualifier": "TRK",
+ }
+ }
+ handleSelect={[MockFunction]}
+ rootComponent={
+ Object {
+ "key": "foo",
+ "measures": Array [
+ Object {
+ "bestValue": false,
+ "metric": "bugs",
+ "value": "12",
+ },
+ ],
+ "name": "Foo",
+ "qualifier": "TRK",
+ }
+ }
+ />
+ }
+ right={
+ <PageActions
+ componentQualifier="TRK"
+ current={0}
+ />
+ }
+ />
+ </div>
+ </div>
+ </div>
+ <div
+ className="layout-page-main-inner measure-details-content"
+ >
+ <div
+ className="clearfix big-spacer-bottom"
+ />
+ <DeferredSpinner
+ loading={true}
+ />
+ </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/BubbleChart.tsx
index 14997bd80e2..2fb770d4722 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/BubbleChart.tsx
@@ -30,7 +30,12 @@ import {
} from '../../../helpers/l10n';
import { formatMeasure, isDiffMetric } from '../../../helpers/measures';
import { isDefined } from '../../../helpers/types';
-import { getBubbleMetrics, getBubbleYDomain, isProjectOverview } from '../utils';
+import {
+ BUBBLES_FETCH_LIMIT,
+ getBubbleMetrics,
+ getBubbleYDomain,
+ isProjectOverview
+} from '../utils';
import EmptyResult from './EmptyResult';
const HEIGHT = 500;
@@ -40,6 +45,7 @@ interface Props {
components: T.ComponentMeasureEnhanced[];
domain: string;
metrics: T.Dict<T.Metric>;
+ paging?: T.Paging;
updateSelected: (component: string) => void;
}
@@ -170,6 +176,7 @@ export default class BubbleChart extends React.PureComponent<Props, State> {
renderChartHeader(domain: string, sizeMetric: T.Metric, colorsMetric?: T.Metric[]) {
const { ratingFilters } = this.state;
+ const { paging } = this.props;
const title = isProjectOverview(domain)
? translate('component_measures.overview', domain, 'title')
@@ -180,8 +187,16 @@ export default class BubbleChart extends React.PureComponent<Props, State> {
return (
<div className="measure-overview-bubble-chart-header">
<span className="measure-overview-bubble-chart-title">
- <span className="text-middle">{title}</span>
- <HelpTooltip className="spacer-left" overlay={this.getDescription(domain)} />
+ <div className="display-flex-center">
+ {title}
+ <HelpTooltip className="spacer-left" overlay={this.getDescription(domain)} />
+ </div>
+
+ {paging?.total && paging?.total > BUBBLES_FETCH_LIMIT && (
+ <div className="note spacer-top">
+ ({translate('component_measures.legend.only_first_500_files')})
+ </div>
+ )}
</span>
<span className="measure-overview-bubble-chart-legend">
<span className="note">
diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/BubbleChart-test.tsx b/server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/BubbleChart-test.tsx
index 7c1eca4cf52..4673a98f943 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/BubbleChart-test.tsx
+++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/BubbleChart-test.tsx
@@ -21,16 +21,21 @@ import { shallow } from 'enzyme';
import { keyBy } from 'lodash';
import * as React from 'react';
import OriginalBubbleChart from '../../../../components/charts/BubbleChart';
-import { mockComponentMeasure, mockMeasure, mockMetric } from '../../../../helpers/testMocks';
+import {
+ mockComponentMeasure,
+ mockMeasure,
+ mockMetric,
+ mockPaging
+} from '../../../../helpers/testMocks';
import { MetricKey } from '../../../../types/metrics';
import { enhanceComponent } from '../../utils';
import BubbleChart from '../BubbleChart';
const metrics = keyBy(
[
- mockMetric({ key: MetricKey.ncloc, type: 'NUMBER' }),
- mockMetric({ key: MetricKey.security_remediation_effort, type: 'NUMBER' }),
- mockMetric({ key: MetricKey.vulnerabilities, type: 'NUMBER' }),
+ mockMetric({ key: MetricKey.ncloc, type: 'INT' }),
+ mockMetric({ key: MetricKey.security_remediation_effort, type: 'DATA' }),
+ mockMetric({ key: MetricKey.vulnerabilities, type: 'INT' }),
mockMetric({ key: MetricKey.security_rating, type: 'RATING' })
],
m => m.key
@@ -38,6 +43,9 @@ const metrics = keyBy(
it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot('default');
+ expect(shallowRender({ paging: mockPaging({ total: 1000 }) })).toMatchSnapshot(
+ 'only showing first 500 files'
+ );
expect(
shallowRender({
components: [
@@ -88,6 +96,7 @@ function shallowRender(overrides: Partial<BubbleChart['props']> = {}) {
]}
domain="Security"
metrics={metrics}
+ paging={mockPaging({ total: 100 })}
updateSelected={jest.fn()}
{...overrides}
/>
diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/__snapshots__/BubbleChart-test.tsx.snap b/server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/__snapshots__/BubbleChart-test.tsx.snap
index cadea7ed64d..e8c8d464e90 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/__snapshots__/BubbleChart-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/__snapshots__/BubbleChart-test.tsx.snap
@@ -10,15 +10,15 @@ exports[`should render correctly: all on x=0 1`] = `
<span
className="measure-overview-bubble-chart-title"
>
- <span
- className="text-middle"
+ <div
+ className="display-flex-center"
>
component_measures.domain_x_overview.Security
- </span>
- <HelpTooltip
- className="spacer-left"
- overlay={null}
- />
+ <HelpTooltip
+ className="spacer-left"
+ overlay={null}
+ />
+ </div>
</span>
<span
className="measure-overview-bubble-chart-legend"
@@ -66,7 +66,7 @@ exports[`should render correctly: all on x=0 1`] = `
"id": "ncloc",
"key": "ncloc",
"name": "Ncloc",
- "type": "NUMBER",
+ "type": "INT",
},
"period": Object {
"bestValue": true,
@@ -82,7 +82,7 @@ exports[`should render correctly: all on x=0 1`] = `
"id": "security_remediation_effort",
"key": "security_remediation_effort",
"name": "Security_remediation_effort",
- "type": "NUMBER",
+ "type": "DATA",
},
"period": Object {
"bestValue": true,
@@ -98,7 +98,7 @@ exports[`should render correctly: all on x=0 1`] = `
"id": "vulnerabilities",
"key": "vulnerabilities",
"name": "Vulnerabilities",
- "type": "NUMBER",
+ "type": "INT",
},
"period": Object {
"bestValue": true,
@@ -204,16 +204,211 @@ exports[`should render correctly: default 1`] = `
<span
className="measure-overview-bubble-chart-title"
>
- <span
- className="text-middle"
+ <div
+ className="display-flex-center"
>
component_measures.domain_x_overview.Security
+ <HelpTooltip
+ className="spacer-left"
+ overlay={null}
+ />
+ </div>
+ </span>
+ <span
+ className="measure-overview-bubble-chart-legend"
+ >
+ <span
+ className="note"
+ >
+ <span
+ className="spacer-right"
+ >
+ component_measures.legend.color_x.Security_rating
+ </span>
+ component_measures.legend.size_x.Vulnerabilities
</span>
- <HelpTooltip
- className="spacer-left"
- overlay={null}
+ <ColorRatingsLegend
+ className="spacer-top"
+ filters={Object {}}
+ onRatingClick={[Function]}
/>
</span>
+ </div>
+ <div
+ className="measure-overview-bubble-chart-content"
+ >
+ <BubbleChart
+ displayXGrid={true}
+ displayXTicks={true}
+ displayYGrid={true}
+ displayYTicks={true}
+ formatXTick={[Function]}
+ formatYTick={[Function]}
+ height={500}
+ items={
+ Array [
+ Object {
+ "color": "#b0d513",
+ "data": Object {
+ "key": "foo:src/index.tsx",
+ "leak": "1.0",
+ "measures": Array [
+ Object {
+ "bestValue": true,
+ "leak": "1.0",
+ "metric": Object {
+ "id": "ncloc",
+ "key": "ncloc",
+ "name": "Ncloc",
+ "type": "INT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "236",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1.0",
+ "metric": Object {
+ "id": "security_remediation_effort",
+ "key": "security_remediation_effort",
+ "name": "Security_remediation_effort",
+ "type": "DATA",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "10",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1.0",
+ "metric": Object {
+ "id": "vulnerabilities",
+ "key": "vulnerabilities",
+ "name": "Vulnerabilities",
+ "type": "INT",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "3",
+ },
+ Object {
+ "bestValue": true,
+ "leak": "1.0",
+ "metric": Object {
+ "id": "security_rating",
+ "key": "security_rating",
+ "name": "Security_rating",
+ "type": "RATING",
+ },
+ "period": Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ "value": "2",
+ },
+ ],
+ "name": "index.tsx",
+ "path": "src/index.tsx",
+ "qualifier": "FIL",
+ "value": "3",
+ },
+ "size": 3,
+ "tooltip": <div
+ className="text-left"
+ >
+ <React.Fragment>
+ index.tsx
+ <br />
+ </React.Fragment>
+ <React.Fragment>
+ Ncloc: 236
+ <br />
+ </React.Fragment>
+ <React.Fragment>
+ Security_remediation_effort: 10
+ <br />
+ </React.Fragment>
+ <React.Fragment>
+ Vulnerabilities: 3
+ <br />
+ </React.Fragment>
+ <React.Fragment>
+ Security_rating: B
+ </React.Fragment>
+ </div>,
+ "x": 236,
+ "y": 10,
+ },
+ ]
+ }
+ onBubbleClick={[Function]}
+ padding={
+ Array [
+ 25,
+ 60,
+ 50,
+ 60,
+ ]
+ }
+ sizeRange={
+ Array [
+ 5,
+ 45,
+ ]
+ }
+ />
+ </div>
+ <div
+ className="measure-overview-bubble-chart-axis x"
+ >
+ Ncloc
+ </div>
+ <div
+ className="measure-overview-bubble-chart-axis y"
+ >
+ Security_remediation_effort
+ </div>
+</div>
+`;
+
+exports[`should render correctly: only showing first 500 files 1`] = `
+<div
+ className="measure-overview-bubble-chart"
+>
+ <div
+ className="measure-overview-bubble-chart-header"
+ >
+ <span
+ className="measure-overview-bubble-chart-title"
+ >
+ <div
+ className="display-flex-center"
+ >
+ component_measures.domain_x_overview.Security
+ <HelpTooltip
+ className="spacer-left"
+ overlay={null}
+ />
+ </div>
+ <div
+ className="note spacer-top"
+ >
+ (
+ component_measures.legend.only_first_500_files
+ )
+ </div>
+ </span>
<span
className="measure-overview-bubble-chart-legend"
>
@@ -260,7 +455,7 @@ exports[`should render correctly: default 1`] = `
"id": "ncloc",
"key": "ncloc",
"name": "Ncloc",
- "type": "NUMBER",
+ "type": "INT",
},
"period": Object {
"bestValue": true,
@@ -276,7 +471,7 @@ exports[`should render correctly: default 1`] = `
"id": "security_remediation_effort",
"key": "security_remediation_effort",
"name": "Security_remediation_effort",
- "type": "NUMBER",
+ "type": "DATA",
},
"period": Object {
"bestValue": true,
@@ -292,7 +487,7 @@ exports[`should render correctly: default 1`] = `
"id": "vulnerabilities",
"key": "vulnerabilities",
"name": "Vulnerabilities",
- "type": "NUMBER",
+ "type": "INT",
},
"period": Object {
"bestValue": true,
diff --git a/server/sonar-web/src/main/js/apps/component-measures/utils.ts b/server/sonar-web/src/main/js/apps/component-measures/utils.ts
index d2e7763e86b..a36aaf2a613 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/utils.ts
+++ b/server/sonar-web/src/main/js/apps/component-measures/utils.ts
@@ -31,6 +31,7 @@ import { domains } from './config/domains';
export type View = 'list' | 'tree' | 'treemap';
+export const BUBBLES_FETCH_LIMIT = 500;
export const PROJECT_OVERVEW = 'project_overview';
export const DEFAULT_VIEW: View = 'tree';
export const DEFAULT_METRIC = PROJECT_OVERVEW;
diff --git a/server/sonar-web/src/main/js/helpers/mocks/component.ts b/server/sonar-web/src/main/js/helpers/mocks/component.ts
new file mode 100644
index 00000000000..4bcbde0cb11
--- /dev/null
+++ b/server/sonar-web/src/main/js/helpers/mocks/component.ts
@@ -0,0 +1,32 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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 { mockComponentMeasure, mockMeasureEnhanced } from '../testMocks';
+
+export function mockComponentMeasureEnhanced(
+ overrides: Partial<T.ComponentMeasureEnhanced> = {}
+): T.ComponentMeasureEnhanced {
+ return {
+ ...mockComponentMeasure(false, overrides as T.ComponentMeasure),
+ leak: undefined,
+ measures: [mockMeasureEnhanced()],
+ value: undefined,
+ ...overrides
+ };
+}
diff --git a/server/sonar-web/src/main/js/helpers/testMocks.ts b/server/sonar-web/src/main/js/helpers/testMocks.ts
index 5dbbc378f68..d4bdd520184 100644
--- a/server/sonar-web/src/main/js/helpers/testMocks.ts
+++ b/server/sonar-web/src/main/js/helpers/testMocks.ts
@@ -790,3 +790,12 @@ export function mockRef(
}
} as React.RefObject<HTMLElement>;
}
+
+export function mockPaging(overrides: Partial<T.Paging> = {}): T.Paging {
+ return {
+ pageIndex: 1,
+ pageSize: 100,
+ total: 1000,
+ ...overrides
+ };
+}
diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
index 9b9fd4368d5..90bcc63c696 100644
--- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties
+++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
@@ -3169,6 +3169,7 @@ component_measures.legend.color_x=Color: {0}
component_measures.legend.size_x=Size: {0}
component_measures.legend.worse_of_x_y=Worse of {0} and {1}
component_measures.legend.help=Click to toggle visibility.
+component_measures.legend.only_first_500_files=Only showing data for the first 500 files
component_measures.no_history=There isn't enough data to generate an activity graph.
component_measures.not_found=The requested measure was not found.
component_measures.empty=No measures.