Browse Source

SONAR-21797 Show software qualites measure history in graph

tags/10.5.0.89998
stanislavh 1 month ago
parent
commit
7565e9a2e3

+ 0
- 2
server/sonar-web/src/main/js/apps/projectActivity/__tests__/utils-test.ts View File

describe('serializeQuery', () => { describe('serializeQuery', () => {
it('should serialize query for api request', () => { it('should serialize query for api request', () => {
expect(utils.serializeQuery(QUERY)).toEqual({ expect(utils.serializeQuery(QUERY)).toEqual({
from: '2017-04-27T08:21:32+0000',
project: 'foo', project: 'foo',
}); });
expect(utils.serializeQuery({ ...QUERY, graph: GraphType.coverage, category: 'test' })).toEqual( expect(utils.serializeQuery({ ...QUERY, graph: GraphType.coverage, category: 'test' })).toEqual(
{ {
from: '2017-04-27T08:21:32+0000',
project: 'foo', project: 'foo',
category: 'test', category: 'test',
}, },

+ 0
- 2
server/sonar-web/src/main/js/apps/projectActivity/utils.ts View File

export function serializeQuery(query: Query): RawQuery { export function serializeQuery(query: Query): RawQuery {
return cleanQuery({ return cleanQuery({
category: serializeString(query.category), category: serializeString(query.category),
from: serializeDate(query.from),
project: serializeString(query.project), project: serializeString(query.project),
to: serializeDate(query.to),
}); });
} }



+ 5
- 2
server/sonar-web/src/main/js/components/activity-graph/AddGraphMetric.tsx View File

import { ButtonSecondary, ChevronDownIcon, Dropdown, TextMuted } from 'design-system'; import { ButtonSecondary, ChevronDownIcon, Dropdown, TextMuted } from 'design-system';
import { sortBy } from 'lodash'; import { sortBy } from 'lodash';
import * as React from 'react'; import * as React from 'react';
import { HIDDEN_METRICS } from '../../helpers/constants';
import { CCT_SOFTWARE_QUALITY_METRICS, HIDDEN_METRICS } from '../../helpers/constants';
import { getLocalizedMetricName, translate } from '../../helpers/l10n'; import { getLocalizedMetricName, translate } from '../../helpers/l10n';
import { isDiffMetric } from '../../helpers/measures'; import { isDiffMetric } from '../../helpers/measures';
import { MetricKey, MetricType } from '../../types/metrics'; import { MetricKey, MetricType } from '../../types/metrics';
if (isDiffMetric(metric.key)) { if (isDiffMetric(metric.key)) {
return false; return false;
} }
if ([MetricType.Data, MetricType.Distribution].includes(metric.type as MetricType)) {
if (
[MetricType.Data, MetricType.Distribution].includes(metric.type as MetricType) &&
!CCT_SOFTWARE_QUALITY_METRICS.includes(metric.key as MetricKey)
) {
return false; return false;
} }
if (HIDDEN_METRICS.includes(metric.key as MetricKey)) { if (HIDDEN_METRICS.includes(metric.key as MetricKey)) {

+ 5
- 4
server/sonar-web/src/main/js/components/activity-graph/ChartLegend.tsx View File

import classNames from 'classnames'; import classNames from 'classnames';
import { Theme, themeColor } from 'design-system'; import { Theme, themeColor } from 'design-system';
import * as React from 'react'; import * as React from 'react';
import { LINE_CHART_DASHES } from './utils';


interface Props { interface Props {
className?: string; className?: string;
clipRule="evenodd" clipRule="evenodd"
fillRule="evenodd" fillRule="evenodd"
height={16} height={16}
strokeLinejoin="round"
strokeMiterlimit={1.41421} strokeMiterlimit={1.41421}
viewBox="0 0 16 16" viewBox="0 0 16 16"
width={16} width={16}
xmlnsXlink="http://www.w3.org/1999/xlink" xmlnsXlink="http://www.w3.org/1999/xlink"
> >
<path <path
className={classNames('line-chart-path line-chart-path-legend', `line-chart-path-${index}`)}
d="M14.325 7.143v1.714q0 0.357-0.25 0.607t-0.607 0.25h-10.857q-0.357 0-0.607-0.25t-0.25-0.607v-1.714q0-0.357 0.25-0.607t0.607-0.25h10.857q0.357 0 0.607 0.25t0.25 0.607z"
className={classNames('line-chart-path', `line-chart-path-${index}`)}
d="M0 8 L 16 8"
style={{ style={{
fill: themeColor(`graphLineColor.${index}` as Parameters<typeof themeColor>[0])({
stroke: themeColor(`graphLineColor.${index}` as Parameters<typeof themeColor>[0])({
theme, theme,
}), }),
strokeDasharray: LINE_CHART_DASHES[index],
}} }}
/> />
</svg> </svg>

+ 20
- 7
server/sonar-web/src/main/js/components/activity-graph/__tests__/ActivityGraph-it.tsx View File

import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import { times } from 'lodash'; import { times } from 'lodash';
import * as React from 'react'; import * as React from 'react';
import { CCT_SOFTWARE_QUALITY_METRICS } from '../../../helpers/constants';
import { parseDate } from '../../../helpers/dates'; import { parseDate } from '../../../helpers/dates';
import { mockHistoryItem, mockMeasureHistory } from '../../../helpers/mocks/project-activity'; import { mockHistoryItem, mockMeasureHistory } from '../../../helpers/mocks/project-activity';
import { mockMetric } from '../../../helpers/testMocks'; import { mockMetric } from '../../../helpers/testMocks';
await ui.clickOnMetric(MetricKey.code_smells); await ui.clickOnMetric(MetricKey.code_smells);
await ui.clickOnMetric(MetricKey.coverage); await ui.clickOnMetric(MetricKey.coverage);


// Search for option.
await ui.searchForMetric('bug');
expect(ui.bugsCheckbox.get()).toBeInTheDocument();
// Search for option and select it
await ui.searchForMetric('maintainability');
expect(ui.vulnerabilityCheckbox.query()).not.toBeInTheDocument(); expect(ui.vulnerabilityCheckbox.query()).not.toBeInTheDocument();
await ui.clickOnMetric(MetricKey.maintainability_issues);


// Disable final metrics by clicking on the legend items.
await ui.removeMetric(MetricKey.confirmed_issues);
// Disable percentage metrics by clicking on the legend items.
await ui.removeMetric(MetricKey.duplicated_lines_density); await ui.removeMetric(MetricKey.duplicated_lines_density);
await ui.removeMetric(MetricKey.test_success_density); await ui.removeMetric(MetricKey.test_success_density);


// We should see 1 graph
expect(ui.graphs.getAll()).toHaveLength(1);

// Disable final number metrics
await ui.removeMetric(MetricKey.confirmed_issues);
await ui.removeMetric(MetricKey.maintainability_issues);

// Should show message that there's no data to be rendered. // Should show message that there's no data to be rendered.
expect(ui.noDataText.get()).toBeInTheDocument(); expect(ui.noDataText.get()).toBeInTheDocument();
}); });
// Add/remove metrics. // Add/remove metrics.
addMetricBtn: byRole('button', { name: 'project_activity.graphs.custom.add' }), addMetricBtn: byRole('button', { name: 'project_activity.graphs.custom.add' }),
deprecatedBadge: byText('deprecated'), deprecatedBadge: byText('deprecated'),
bugsCheckbox: byRole('checkbox', { name: MetricKey.bugs }),
maintainabilityIssuesCheckbox: byRole('checkbox', { name: MetricKey.maintainability_issues }),
newBugsCheckbox: byRole('checkbox', { name: MetricKey.new_bugs }), newBugsCheckbox: byRole('checkbox', { name: MetricKey.new_bugs }),
burnedBudgetCheckbox: byRole('checkbox', { name: MetricKey.burned_budget }), burnedBudgetCheckbox: byRole('checkbox', { name: MetricKey.burned_budget }),
vulnerabilityCheckbox: byRole('checkbox', { name: MetricKey.vulnerabilities }), vulnerabilityCheckbox: byRole('checkbox', { name: MetricKey.vulnerabilities }),
MetricKey.bugs, MetricKey.bugs,
MetricKey.code_smells, MetricKey.code_smells,
MetricKey.confirmed_issues, MetricKey.confirmed_issues,
MetricKey.maintainability_issues,
MetricKey.vulnerabilities, MetricKey.vulnerabilities,
MetricKey.blocker_violations, MetricKey.blocker_violations,
MetricKey.lines_to_cover, MetricKey.lines_to_cover,
return mockHistoryItem({ date, value: i.toString() }); return mockHistoryItem({ date, value: i.toString() });
}); });
history.push( history.push(
mockHistoryItem({ date: parseDate('2018-10-27T12:21:15+0200') }),
mockHistoryItem({
date: parseDate('2018-10-27T12:21:15+0200'),
value: CCT_SOFTWARE_QUALITY_METRICS.includes(metric)
? JSON.stringify({ total: 2286 })
: '2286',
}),
mockHistoryItem({ date: parseDate('2020-10-27T16:33:50+0200') }), mockHistoryItem({ date: parseDate('2020-10-27T16:33:50+0200') }),
); );
measuresHistory.push(mockMeasureHistory({ metric, history })); measuresHistory.push(mockMeasureHistory({ metric, history }));

+ 18
- 5
server/sonar-web/src/main/js/components/activity-graph/utils.ts View File

* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
import { chunk, flatMap, groupBy, sortBy } from 'lodash'; import { chunk, flatMap, groupBy, sortBy } from 'lodash';
import { CCT_SOFTWARE_QUALITY_METRICS } from '../../helpers/constants';
import { getLocalizedMetricName, translate } from '../../helpers/l10n'; import { getLocalizedMetricName, translate } from '../../helpers/l10n';
import { localizeMetric } from '../../helpers/measures'; import { localizeMetric } from '../../helpers/measures';
import { get, save } from '../../helpers/storage'; import { get, save } from '../../helpers/storage';
], ],
}; };


export const LINE_CHART_DASHES = [0, 3, 7];

export function isCustomGraph(graph: GraphType) { export function isCustomGraph(graph: GraphType) {
return graph === GraphType.custom; return graph === GraphType.custom;
} }
return generateCoveredLinesMetric(measure, measuresHistory); return generateCoveredLinesMetric(measure, measuresHistory);
} }
const metric = findMetric(measure.metric, metrics); const metric = findMetric(measure.metric, metrics);
const isSoftwareQualityMetric = CCT_SOFTWARE_QUALITY_METRICS.includes(
metric?.key as MetricKey,
);
return { return {
data: measure.history.map((analysis) => ({
x: analysis.date,
y: metric && metric.type === MetricType.Level ? analysis.value : Number(analysis.value),
})),
data: measure.history.map((analysis) => {
let { value } = analysis;

if (value !== undefined && isSoftwareQualityMetric) {
value = JSON.parse(value).total;
}
return {
x: analysis.date,
y: metric?.type === MetricType.Level ? value : Number(value),
};
}),
name: measure.metric, name: measure.metric,
translatedName: metric ? getLocalizedMetricName(metric) : localizeMetric(measure.metric), translatedName: metric ? getLocalizedMetricName(metric) : localizeMetric(measure.metric),
type: metric ? metric.type : MetricType.Integer,
type: !metric || isSoftwareQualityMetric ? MetricType.Integer : metric.type,
}; };
}), }),
(serie) => (serie) =>

+ 2
- 0
server/sonar-web/src/main/js/components/charts/AdvancedTimeline.tsx View File

import { isDefined } from '../../helpers/types'; import { isDefined } from '../../helpers/types';
import { MetricType } from '../../types/metrics'; import { MetricType } from '../../types/metrics';
import { Chart } from '../../types/types'; import { Chart } from '../../types/types';
import { LINE_CHART_DASHES } from '../activity-graph/utils';
import './AdvancedTimeline.css'; import './AdvancedTimeline.css';
import './LineChart.css'; import './LineChart.css';


stroke={themeColor(`graphLineColor.${idx}` as Parameters<typeof themeColor>[0])({ stroke={themeColor(`graphLineColor.${idx}` as Parameters<typeof themeColor>[0])({
theme, theme,
})} })}
strokeDasharray={LINE_CHART_DASHES[idx]}
/> />
))} ))}
</g> </g>

+ 2
- 0
server/sonar-web/src/main/js/components/charts/ZoomTimeLine.tsx View File

import Draggable, { DraggableBounds, DraggableCore, DraggableData } from 'react-draggable'; import Draggable, { DraggableBounds, DraggableCore, DraggableData } from 'react-draggable';
import { MetricType } from '../../types/metrics'; import { MetricType } from '../../types/metrics';
import { Chart } from '../../types/types'; import { Chart } from '../../types/types';
import { LINE_CHART_DASHES } from '../activity-graph/utils';


export interface Props { export interface Props {
basisCurve?: boolean; basisCurve?: boolean;
clip-path: url(#chart-clip); clip-path: url(#chart-clip);
fill: none; fill: none;
stroke: ${({ index }) => themeColor(`graphLineColor.${index}` as CSSColor)}; stroke: ${({ index }) => themeColor(`graphLineColor.${index}` as CSSColor)};
stroke-dasharray: ${({ index }) => LINE_CHART_DASHES[index]};
stroke-width: 2px; stroke-width: 2px;
`; `;



+ 20
- 0
server/sonar-web/src/main/js/components/charts/__tests__/__snapshots__/AdvancedTimeline-test.tsx.snap View File

class="line-chart-path line-chart-path-0" class="line-chart-path line-chart-path-0"
d="M0,16L20,8" d="M0,16L20,8"
stroke="rgb(85,170,223)" stroke="rgb(85,170,223)"
stroke-dasharray="0"
/> />
<path <path
class="line-chart-path line-chart-path-1" class="line-chart-path line-chart-path-1"
d="M40,0Z" d="M40,0Z"
stroke="rgb(58,127,173)" stroke="rgb(58,127,173)"
stroke-dasharray="3"
/> />
</g> </g>
<g> <g>
class="line-chart-path line-chart-path-0" class="line-chart-path line-chart-path-0"
d="M0,16L20,8" d="M0,16L20,8"
stroke="rgb(85,170,223)" stroke="rgb(85,170,223)"
stroke-dasharray="0"
/> />
<path <path
class="line-chart-path line-chart-path-1" class="line-chart-path line-chart-path-1"
d="M40,0Z" d="M40,0Z"
stroke="rgb(58,127,173)" stroke="rgb(58,127,173)"
stroke-dasharray="3"
/> />
</g> </g>
<g> <g>
class="line-chart-path line-chart-path-0" class="line-chart-path line-chart-path-0"
d="M0,16L20,8" d="M0,16L20,8"
stroke="rgb(85,170,223)" stroke="rgb(85,170,223)"
stroke-dasharray="0"
/> />
<path <path
class="line-chart-path line-chart-path-1" class="line-chart-path line-chart-path-1"
d="M40,0Z" d="M40,0Z"
stroke="rgb(58,127,173)" stroke="rgb(58,127,173)"
stroke-dasharray="3"
/> />
</g> </g>
<g> <g>
class="line-chart-path line-chart-path-0" class="line-chart-path line-chart-path-0"
d="M0,16L20,8" d="M0,16L20,8"
stroke="rgb(85,170,223)" stroke="rgb(85,170,223)"
stroke-dasharray="0"
/> />
<path <path
class="line-chart-path line-chart-path-1" class="line-chart-path line-chart-path-1"
d="M40,0Z" d="M40,0Z"
stroke="rgb(58,127,173)" stroke="rgb(58,127,173)"
stroke-dasharray="3"
/> />
</g> </g>
<g> <g>
class="line-chart-path line-chart-path-0" class="line-chart-path line-chart-path-0"
d="M0,16L20,8" d="M0,16L20,8"
stroke="rgb(85,170,223)" stroke="rgb(85,170,223)"
stroke-dasharray="0"
/> />
<path <path
class="line-chart-path line-chart-path-1" class="line-chart-path line-chart-path-1"
d="M40,0Z" d="M40,0Z"
stroke="rgb(58,127,173)" stroke="rgb(58,127,173)"
stroke-dasharray="3"
/> />
</g> </g>
<g> <g>
class="line-chart-path line-chart-path-0" class="line-chart-path line-chart-path-0"
d="M0,NaNL20,NaN" d="M0,NaNL20,NaN"
stroke="rgb(85,170,223)" stroke="rgb(85,170,223)"
stroke-dasharray="0"
/> />
<path <path
class="line-chart-path line-chart-path-1" class="line-chart-path line-chart-path-1"
d="M40,NaNZ" d="M40,NaNZ"
stroke="rgb(58,127,173)" stroke="rgb(58,127,173)"
stroke-dasharray="3"
/> />
</g> </g>
<g> <g>
class="line-chart-path line-chart-path-0" class="line-chart-path line-chart-path-0"
d="M0,16L20,8" d="M0,16L20,8"
stroke="rgb(85,170,223)" stroke="rgb(85,170,223)"
stroke-dasharray="0"
/> />
<path <path
class="line-chart-path line-chart-path-1" class="line-chart-path line-chart-path-1"
d="M40,0Z" d="M40,0Z"
stroke="rgb(58,127,173)" stroke="rgb(58,127,173)"
stroke-dasharray="3"
/> />
</g> </g>
<g> <g>
class="line-chart-path line-chart-path-0" class="line-chart-path line-chart-path-0"
d="M0,0L20,6" d="M0,0L20,6"
stroke="rgb(85,170,223)" stroke="rgb(85,170,223)"
stroke-dasharray="0"
/> />
<path <path
class="line-chart-path line-chart-path-1" class="line-chart-path line-chart-path-1"
d="M40,12Z" d="M40,12Z"
stroke="rgb(58,127,173)" stroke="rgb(58,127,173)"
stroke-dasharray="3"
/> />
</g> </g>
<g> <g>
class="line-chart-path line-chart-path-0" class="line-chart-path line-chart-path-0"
d="M0,16L20,8" d="M0,16L20,8"
stroke="rgb(85,170,223)" stroke="rgb(85,170,223)"
stroke-dasharray="0"
/> />
<path <path
class="line-chart-path line-chart-path-1" class="line-chart-path line-chart-path-1"
d="M40,0Z" d="M40,0Z"
stroke="rgb(58,127,173)" stroke="rgb(58,127,173)"
stroke-dasharray="3"
/> />
</g> </g>
<g> <g>
class="line-chart-path line-chart-path-0" class="line-chart-path line-chart-path-0"
d="M0,16L20,8" d="M0,16L20,8"
stroke="rgb(85,170,223)" stroke="rgb(85,170,223)"
stroke-dasharray="0"
/> />
<path <path
class="line-chart-path line-chart-path-1" class="line-chart-path line-chart-path-1"
d="M40,0Z" d="M40,0Z"
stroke="rgb(58,127,173)" stroke="rgb(58,127,173)"
stroke-dasharray="3"
/> />
</g> </g>
<g> <g>

+ 1
- 3
server/sonar-web/src/main/js/helpers/constants.ts View File

]; ];


export const DEPRECATED_ACTIVITY_METRICS = [ export const DEPRECATED_ACTIVITY_METRICS = [
...OLD_TAXONOMY_METRICS,
MetricKey.blocker_violations, MetricKey.blocker_violations,
MetricKey.critical_violations, MetricKey.critical_violations,
MetricKey.major_violations, MetricKey.major_violations,
MetricKey.minor_violations, MetricKey.minor_violations,
MetricKey.info_violations, MetricKey.info_violations,
MetricKey.code_smells,
MetricKey.bugs,
MetricKey.vulnerabilities,
MetricKey.confirmed_issues, MetricKey.confirmed_issues,
]; ];



Loading…
Cancel
Save