@@ -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', | |||
}, |
@@ -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), | |||
}); | |||
} | |||
@@ -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)) { |
@@ -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> |
@@ -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 })); |
@@ -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) => |
@@ -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> |
@@ -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; | |||
`; | |||
@@ -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> |
@@ -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, | |||
]; | |||