]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-21797 Show software qualites measure history in graph
authorstanislavh <stanislav.honcharov@sonarsource.com>
Tue, 19 Mar 2024 09:22:10 +0000 (10:22 +0100)
committersonartech <sonartech@sonarsource.com>
Mon, 25 Mar 2024 20:02:42 +0000 (20:02 +0000)
server/sonar-web/src/main/js/apps/projectActivity/__tests__/utils-test.ts
server/sonar-web/src/main/js/apps/projectActivity/utils.ts
server/sonar-web/src/main/js/components/activity-graph/AddGraphMetric.tsx
server/sonar-web/src/main/js/components/activity-graph/ChartLegend.tsx
server/sonar-web/src/main/js/components/activity-graph/__tests__/ActivityGraph-it.tsx
server/sonar-web/src/main/js/components/activity-graph/utils.ts
server/sonar-web/src/main/js/components/charts/AdvancedTimeline.tsx
server/sonar-web/src/main/js/components/charts/ZoomTimeLine.tsx
server/sonar-web/src/main/js/components/charts/__tests__/__snapshots__/AdvancedTimeline-test.tsx.snap
server/sonar-web/src/main/js/helpers/constants.ts

index 6e4d19e73792a4942f826d169a7847687c3f3e29..2d940fa9691ac0f1e2e0092fb82225a7c03ecd0c 100644 (file)
@@ -162,12 +162,10 @@ describe('parseQuery', () => {
 describe('serializeQuery', () => {
   it('should serialize query for api request', () => {
     expect(utils.serializeQuery(QUERY)).toEqual({
-      from: '2017-04-27T08:21:32+0000',
       project: 'foo',
     });
     expect(utils.serializeQuery({ ...QUERY, graph: GraphType.coverage, category: 'test' })).toEqual(
       {
-        from: '2017-04-27T08:21:32+0000',
         project: 'foo',
         category: 'test',
       },
index cc0a319a41a8b54aab8630343e49eef62747c380..171b70d1efec3e1dec1616a972ab2c316bb85753 100644 (file)
@@ -128,9 +128,7 @@ export function parseQuery(urlQuery: RawQuery): Query {
 export function serializeQuery(query: Query): RawQuery {
   return cleanQuery({
     category: serializeString(query.category),
-    from: serializeDate(query.from),
     project: serializeString(query.project),
-    to: serializeDate(query.to),
   });
 }
 
index 7a224c9d3f7e9d1af9f6cbe6fadca300626041de..6f26ed427352191e3bcc2d2bb879d0dc34aae9d3 100644 (file)
@@ -20,7 +20,7 @@
 import { ButtonSecondary, ChevronDownIcon, Dropdown, TextMuted } from 'design-system';
 import { sortBy } from 'lodash';
 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 { isDiffMetric } from '../../helpers/measures';
 import { MetricKey, MetricType } from '../../types/metrics';
@@ -66,7 +66,10 @@ export default class AddGraphMetric extends React.PureComponent<Props, State> {
         if (isDiffMetric(metric.key)) {
           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;
         }
         if (HIDDEN_METRICS.includes(metric.key as MetricKey)) {
index b07e5c0d84d14d2d28722ddf174c2bd3cf3e1766..b4ac04e5b24307d0c7d4c8774d8fc7e1136c1f21 100644 (file)
@@ -21,6 +21,7 @@ import { useTheme } from '@emotion/react';
 import classNames from 'classnames';
 import { Theme, themeColor } from 'design-system';
 import * as React from 'react';
+import { LINE_CHART_DASHES } from './utils';
 
 interface Props {
   className?: string;
@@ -36,7 +37,6 @@ export function ChartLegend({ index, className }: Readonly<Props>) {
       clipRule="evenodd"
       fillRule="evenodd"
       height={16}
-      strokeLinejoin="round"
       strokeMiterlimit={1.41421}
       viewBox="0 0 16 16"
       width={16}
@@ -44,12 +44,13 @@ export function ChartLegend({ index, className }: Readonly<Props>) {
       xmlnsXlink="http://www.w3.org/1999/xlink"
     >
       <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={{
-          fill: themeColor(`graphLineColor.${index}` as Parameters<typeof themeColor>[0])({
+          stroke: themeColor(`graphLineColor.${index}` as Parameters<typeof themeColor>[0])({
             theme,
           }),
+          strokeDasharray: LINE_CHART_DASHES[index],
         }}
       />
     </svg>
index 88131755a6868b2bcc19ebf87b7d47ebbbee4092..06eb0fa41af367923046f60f7e332edf46bfdd35 100644 (file)
@@ -21,6 +21,7 @@ import { screen } from '@testing-library/react';
 import userEvent from '@testing-library/user-event';
 import { times } from 'lodash';
 import * as React from 'react';
+import { CCT_SOFTWARE_QUALITY_METRICS } from '../../../helpers/constants';
 import { parseDate } from '../../../helpers/dates';
 import { mockHistoryItem, mockMeasureHistory } from '../../../helpers/mocks/project-activity';
 import { mockMetric } from '../../../helpers/testMocks';
@@ -153,16 +154,22 @@ it('should correctly handle adding/removing custom metrics', async () => {
   await ui.clickOnMetric(MetricKey.code_smells);
   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();
+  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.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.
   expect(ui.noDataText.get()).toBeInTheDocument();
 });
@@ -176,7 +183,7 @@ function getPageObject() {
     // Add/remove metrics.
     addMetricBtn: byRole('button', { name: 'project_activity.graphs.custom.add' }),
     deprecatedBadge: byText('deprecated'),
-    bugsCheckbox: byRole('checkbox', { name: MetricKey.bugs }),
+    maintainabilityIssuesCheckbox: byRole('checkbox', { name: MetricKey.maintainability_issues }),
     newBugsCheckbox: byRole('checkbox', { name: MetricKey.new_bugs }),
     burnedBudgetCheckbox: byRole('checkbox', { name: MetricKey.burned_budget }),
     vulnerabilityCheckbox: byRole('checkbox', { name: MetricKey.vulnerabilities }),
@@ -255,6 +262,7 @@ function renderActivityGraph(
       MetricKey.bugs,
       MetricKey.code_smells,
       MetricKey.confirmed_issues,
+      MetricKey.maintainability_issues,
       MetricKey.vulnerabilities,
       MetricKey.blocker_violations,
       MetricKey.lines_to_cover,
@@ -269,7 +277,12 @@ function renderActivityGraph(
         return mockHistoryItem({ date, value: i.toString() });
       });
       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') }),
       );
       measuresHistory.push(mockMeasureHistory({ metric, history }));
index 74de8cc41165d72e1ffbb2265824ec1cb4881bac..a4bc18da96d5322acb49487f2ae770a8e8cd3aa6 100644 (file)
@@ -18,6 +18,7 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import { chunk, flatMap, groupBy, sortBy } from 'lodash';
+import { CCT_SOFTWARE_QUALITY_METRICS } from '../../helpers/constants';
 import { getLocalizedMetricName, translate } from '../../helpers/l10n';
 import { localizeMetric } from '../../helpers/measures';
 import { get, save } from '../../helpers/storage';
@@ -46,6 +47,8 @@ const GRAPHS_METRICS: Dict<string[]> = {
   ],
 };
 
+export const LINE_CHART_DASHES = [0, 3, 7];
+
 export function isCustomGraph(graph: GraphType) {
   return graph === GraphType.custom;
 }
@@ -126,14 +129,24 @@ export function generateSeries(
           return generateCoveredLinesMetric(measure, measuresHistory);
         }
         const metric = findMetric(measure.metric, metrics);
+        const isSoftwareQualityMetric = CCT_SOFTWARE_QUALITY_METRICS.includes(
+          metric?.key as MetricKey,
+        );
         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,
           translatedName: metric ? getLocalizedMetricName(metric) : localizeMetric(measure.metric),
-          type: metric ? metric.type : MetricType.Integer,
+          type: !metric || isSoftwareQualityMetric ? MetricType.Integer : metric.type,
         };
       }),
     (serie) =>
index 79cd2a7bb291db219df2962508d0e0844ee590c1..6f60e1fc7945a58c6eda3e2e544faf8d827f1c5a 100644 (file)
@@ -36,6 +36,7 @@ import * as React from 'react';
 import { isDefined } from '../../helpers/types';
 import { MetricType } from '../../types/metrics';
 import { Chart } from '../../types/types';
+import { LINE_CHART_DASHES } from '../activity-graph/utils';
 import './AdvancedTimeline.css';
 import './LineChart.css';
 
@@ -450,6 +451,7 @@ export class AdvancedTimelineClass extends React.PureComponent<Props, State> {
             stroke={themeColor(`graphLineColor.${idx}` as Parameters<typeof themeColor>[0])({
               theme,
             })}
+            strokeDasharray={LINE_CHART_DASHES[idx]}
           />
         ))}
       </g>
index 0fac846503af1614c79f15de14c28b0ab9aefec4..5e63ae9a97abe2d0be2cc8f7a9ae44fcfd27ce65 100644 (file)
@@ -27,6 +27,7 @@ import * as React from 'react';
 import Draggable, { DraggableBounds, DraggableCore, DraggableData } from 'react-draggable';
 import { MetricType } from '../../types/metrics';
 import { Chart } from '../../types/types';
+import { LINE_CHART_DASHES } from '../activity-graph/utils';
 
 export interface Props {
   basisCurve?: boolean;
@@ -412,6 +413,7 @@ const StyledPath = styled.path<{ index: number }>`
   clip-path: url(#chart-clip);
   fill: none;
   stroke: ${({ index }) => themeColor(`graphLineColor.${index}` as CSSColor)};
+  stroke-dasharray: ${({ index }) => LINE_CHART_DASHES[index]};
   stroke-width: 2px;
 `;
 
index 78d53f223552ed9bb8404cce3578e9b5e4a8482b..65b69b04b6cf29451f4cc8729cbdf6a9ab9e2ca8 100644 (file)
@@ -247,11 +247,13 @@ exports[`should render correctly: Zoom enabled 1`] = `
         class="line-chart-path line-chart-path-0"
         d="M0,16L20,8"
         stroke="rgb(85,170,223)"
+        stroke-dasharray="0"
       />
       <path
         class="line-chart-path line-chart-path-1"
         d="M40,0Z"
         stroke="rgb(58,127,173)"
+        stroke-dasharray="3"
       />
     </g>
     <g>
@@ -509,11 +511,13 @@ exports[`should render correctly: basisCurve 1`] = `
         class="line-chart-path line-chart-path-0"
         d="M0,16L20,8"
         stroke="rgb(85,170,223)"
+        stroke-dasharray="0"
       />
       <path
         class="line-chart-path line-chart-path-1"
         d="M40,0Z"
         stroke="rgb(58,127,173)"
+        stroke-dasharray="3"
       />
     </g>
     <g>
@@ -771,11 +775,13 @@ exports[`should render correctly: default 1`] = `
         class="line-chart-path line-chart-path-0"
         d="M0,16L20,8"
         stroke="rgb(85,170,223)"
+        stroke-dasharray="0"
       />
       <path
         class="line-chart-path line-chart-path-1"
         d="M40,0Z"
         stroke="rgb(58,127,173)"
+        stroke-dasharray="3"
       />
     </g>
     <g>
@@ -1193,11 +1199,13 @@ exports[`should render correctly: format y tick 1`] = `
         class="line-chart-path line-chart-path-0"
         d="M0,16L20,8"
         stroke="rgb(85,170,223)"
+        stroke-dasharray="0"
       />
       <path
         class="line-chart-path line-chart-path-1"
         d="M40,0Z"
         stroke="rgb(58,127,173)"
+        stroke-dasharray="3"
       />
     </g>
     <g>
@@ -1463,11 +1471,13 @@ exports[`should render correctly: leakPeriodDate 1`] = `
         class="line-chart-path line-chart-path-0"
         d="M0,16L20,8"
         stroke="rgb(85,170,223)"
+        stroke-dasharray="0"
       />
       <path
         class="line-chart-path line-chart-path-1"
         d="M40,0Z"
         stroke="rgb(58,127,173)"
+        stroke-dasharray="3"
       />
     </g>
     <g>
@@ -1608,11 +1618,13 @@ exports[`should render correctly: level metric 1`] = `
         class="line-chart-path line-chart-path-0"
         d="M0,NaNL20,NaN"
         stroke="rgb(85,170,223)"
+        stroke-dasharray="0"
       />
       <path
         class="line-chart-path line-chart-path-1"
         d="M40,NaNZ"
         stroke="rgb(58,127,173)"
+        stroke-dasharray="3"
       />
     </g>
     <g>
@@ -1869,11 +1881,13 @@ exports[`should render correctly: no areas 1`] = `
         class="line-chart-path line-chart-path-0"
         d="M0,16L20,8"
         stroke="rgb(85,170,223)"
+        stroke-dasharray="0"
       />
       <path
         class="line-chart-path line-chart-path-1"
         d="M40,0Z"
         stroke="rgb(58,127,173)"
+        stroke-dasharray="3"
       />
     </g>
     <g>
@@ -2036,11 +2050,13 @@ exports[`should render correctly: rating metric 1`] = `
         class="line-chart-path line-chart-path-0"
         d="M0,0L20,6"
         stroke="rgb(85,170,223)"
+        stroke-dasharray="0"
       />
       <path
         class="line-chart-path line-chart-path-1"
         d="M40,12Z"
         stroke="rgb(58,127,173)"
+        stroke-dasharray="3"
       />
     </g>
     <g>
@@ -2298,11 +2314,13 @@ exports[`should render correctly: selected date 1`] = `
         class="line-chart-path line-chart-path-0"
         d="M0,16L20,8"
         stroke="rgb(85,170,223)"
+        stroke-dasharray="0"
       />
       <path
         class="line-chart-path line-chart-path-1"
         d="M40,0Z"
         stroke="rgb(58,127,173)"
+        stroke-dasharray="3"
       />
     </g>
     <g>
@@ -2585,11 +2603,13 @@ exports[`should render correctly: zoomSpeed 1`] = `
         class="line-chart-path line-chart-path-0"
         d="M0,16L20,8"
         stroke="rgb(85,170,223)"
+        stroke-dasharray="0"
       />
       <path
         class="line-chart-path line-chart-path-1"
         d="M40,0Z"
         stroke="rgb(58,127,173)"
+        stroke-dasharray="3"
       />
     </g>
     <g>
index f5b42b3adce344628b923583f51fe9911f178b47..809bc472756477002fda16d01298fe2e758628d5 100644 (file)
@@ -144,14 +144,12 @@ export const HIDDEN_METRICS = [
 ];
 
 export const DEPRECATED_ACTIVITY_METRICS = [
+  ...OLD_TAXONOMY_METRICS,
   MetricKey.blocker_violations,
   MetricKey.critical_violations,
   MetricKey.major_violations,
   MetricKey.minor_violations,
   MetricKey.info_violations,
-  MetricKey.code_smells,
-  MetricKey.bugs,
-  MetricKey.vulnerabilities,
   MetricKey.confirmed_issues,
 ];