diff options
author | Wouter Admiraal <wouter.admiraal@sonarsource.com> | 2021-08-24 10:44:41 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2021-08-26 20:04:29 +0000 |
commit | 3127e9e6821be46440f1ad89b5e56c47f10fd38b (patch) | |
tree | 5d73efed7536c5191b9163af35443e1906474f33 | |
parent | 9bd612f143a36272b6f2d06145e140b2d81bff90 (diff) | |
download | sonarqube-3127e9e6821be46440f1ad89b5e56c47f10fd38b.tar.gz sonarqube-3127e9e6821be46440f1ad89b5e56c47f10fd38b.zip |
SONAR-11538 Explain bubble charts only take into account 1st 500 files
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. |