@@ -272,7 +272,7 @@ | |||
*/ | |||
.overview-panel .activity-graph-legends { | |||
text-align: right; | |||
justify-content: right; | |||
margin-top: -30px; | |||
} | |||
@@ -41,7 +41,7 @@ export interface DataTableModalProps { | |||
type DataTableEntry = { date: Date } & { [x: string]: string | undefined }; | |||
const MAX_DATA_TABLE_ROWS = 100; | |||
export const MAX_DATA_TABLE_ROWS = 100; | |||
export default function DataTableModal(props: DataTableModalProps) { | |||
const { analyses, series, graphEndDate, graphStartDate } = props; |
@@ -32,36 +32,34 @@ export interface GraphsLegendCustomProps { | |||
export default function GraphsLegendCustom(props: GraphsLegendCustomProps) { | |||
const { series } = props; | |||
return ( | |||
<div className="activity-graph-legends display-flex-center"> | |||
<div className="flex-1"> | |||
{series.map((serie, idx) => { | |||
const hasData = hasDataValues(serie); | |||
const legendItem = ( | |||
<GraphsLegendItem | |||
index={idx} | |||
metric={serie.name} | |||
name={serie.translatedName} | |||
removeMetric={props.removeMetric} | |||
showWarning={!hasData} | |||
/> | |||
); | |||
if (!hasData) { | |||
return ( | |||
<Tooltip | |||
key={serie.name} | |||
overlay={translate('project_activity.graphs.custom.metric_no_history')} | |||
> | |||
<span className="spacer-left spacer-right">{legendItem}</span> | |||
</Tooltip> | |||
); | |||
} | |||
<ul className="activity-graph-legends"> | |||
{series.map((serie, idx) => { | |||
const hasData = hasDataValues(serie); | |||
const legendItem = ( | |||
<GraphsLegendItem | |||
index={idx} | |||
metric={serie.name} | |||
name={serie.translatedName} | |||
removeMetric={props.removeMetric} | |||
showWarning={!hasData} | |||
/> | |||
); | |||
if (!hasData) { | |||
return ( | |||
<span className="spacer-left spacer-right" key={serie.name}> | |||
{legendItem} | |||
</span> | |||
<Tooltip | |||
key={serie.name} | |||
overlay={translate('project_activity.graphs.custom.metric_no_history')} | |||
> | |||
<li className="spacer-left spacer-right">{legendItem}</li> | |||
</Tooltip> | |||
); | |||
})} | |||
</div> | |||
</div> | |||
} | |||
return ( | |||
<li className="spacer-left spacer-right" key={serie.name}> | |||
{legendItem} | |||
</li> | |||
); | |||
})} | |||
</ul> | |||
); | |||
} |
@@ -1,35 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2022 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 * as React from 'react'; | |||
import Tooltip from '../../components/controls/Tooltip'; | |||
import { translate } from '../../helpers/l10n'; | |||
export default function GraphsLegendNewCode() { | |||
return ( | |||
<Tooltip overlay={translate('project_activity.graphs.new_code_long')}> | |||
<span | |||
aria-label={translate('project_activity.graphs.new_code_long')} | |||
className="activity-graph-new-code-legend display-flex-center pull-right note" | |||
> | |||
{translate('project_activity.graphs.new_code')} | |||
</span> | |||
</Tooltip> | |||
); | |||
} |
@@ -27,16 +27,17 @@ export interface GraphsLegendStaticProps { | |||
export default function GraphsLegendStatic({ series }: GraphsLegendStaticProps) { | |||
return ( | |||
<div className="activity-graph-legends"> | |||
<ul className="activity-graph-legends"> | |||
{series.map((serie, idx) => ( | |||
<GraphsLegendItem | |||
className="big-spacer-left big-spacer-right" | |||
index={idx} | |||
key={serie.name} | |||
metric={serie.name} | |||
name={serie.translatedName} | |||
/> | |||
<li key={serie.name}> | |||
<GraphsLegendItem | |||
className="big-spacer-left big-spacer-right" | |||
index={idx} | |||
metric={serie.name} | |||
name={serie.translatedName} | |||
/> | |||
</li> | |||
))} | |||
</div> | |||
</ul> | |||
); | |||
} |
@@ -20,7 +20,7 @@ | |||
import * as React from 'react'; | |||
import { Popup, PopupPlacement } from '../../components/ui/popups'; | |||
import { isDefined } from '../../helpers/types'; | |||
import { AnalysisEvent, MeasureHistory, Serie } from '../../types/project-activity'; | |||
import { AnalysisEvent, GraphType, MeasureHistory, Serie } from '../../types/project-activity'; | |||
import DateTimeFormatter from '../intl/DateTimeFormatter'; | |||
import GraphsTooltipsContent from './GraphsTooltipsContent'; | |||
import GraphsTooltipsContentCoverage from './GraphsTooltipsContentCoverage'; | |||
@@ -42,53 +42,60 @@ interface Props { | |||
} | |||
const TOOLTIP_WIDTH = 250; | |||
const TOOLTIP_LEFT_MARGIN = 60; | |||
const TOOLTIP_LEFT_FLIP_THRESHOLD = 50; | |||
export default class GraphsTooltips extends React.PureComponent<Props> { | |||
renderContent() { | |||
const { tooltipIdx } = this.props; | |||
const { tooltipIdx, series, graph, measuresHistory } = this.props; | |||
return this.props.series.map((serie, idx) => { | |||
return series.map((serie, idx) => { | |||
const point = serie.data[tooltipIdx]; | |||
if (!point || (!point.y && point.y !== 0)) { | |||
return null; | |||
} | |||
if (this.props.graph === DEFAULT_GRAPH) { | |||
if (graph === DEFAULT_GRAPH) { | |||
return ( | |||
<GraphsTooltipsContentIssues | |||
index={idx} | |||
key={serie.name} | |||
measuresHistory={this.props.measuresHistory} | |||
measuresHistory={measuresHistory} | |||
name={serie.name} | |||
tooltipIdx={tooltipIdx} | |||
translatedName={serie.translatedName} | |||
value={this.props.formatValue(point.y)} | |||
/> | |||
); | |||
} else { | |||
return ( | |||
<GraphsTooltipsContent | |||
index={idx} | |||
key={serie.name} | |||
name={serie.name} | |||
translatedName={serie.translatedName} | |||
value={this.props.formatValue(point.y)} | |||
/> | |||
); | |||
} | |||
return ( | |||
<GraphsTooltipsContent | |||
index={idx} | |||
key={serie.name} | |||
name={serie.name} | |||
translatedName={serie.translatedName} | |||
value={this.props.formatValue(point.y)} | |||
/> | |||
); | |||
}); | |||
} | |||
render() { | |||
const { events, measuresHistory, tooltipIdx } = this.props; | |||
const { events, measuresHistory, tooltipIdx, tooltipPos, graph, graphWidth, selectedDate } = | |||
this.props; | |||
const top = 30; | |||
let left = this.props.tooltipPos + 60; | |||
let left = tooltipPos + TOOLTIP_LEFT_MARGIN; | |||
let placement = PopupPlacement.RightTop; | |||
if (left > this.props.graphWidth - TOOLTIP_WIDTH - 50) { | |||
if (left > graphWidth - TOOLTIP_WIDTH - TOOLTIP_LEFT_FLIP_THRESHOLD) { | |||
left -= TOOLTIP_WIDTH; | |||
placement = PopupPlacement.LeftTop; | |||
} | |||
const tooltipContent = this.renderContent().filter(isDefined); | |||
const addSeparator = tooltipContent.length > 0; | |||
return ( | |||
<Popup | |||
className="disabled-pointer-events" | |||
@@ -97,21 +104,21 @@ export default class GraphsTooltips extends React.PureComponent<Props> { | |||
> | |||
<div className="activity-graph-tooltip"> | |||
<div className="activity-graph-tooltip-title spacer-bottom"> | |||
<DateTimeFormatter date={this.props.selectedDate} /> | |||
<DateTimeFormatter date={selectedDate} /> | |||
</div> | |||
<table className="width-100"> | |||
{events && events.length > 0 && ( | |||
{events?.length > 0 && ( | |||
<GraphsTooltipsContentEvents addSeparator={addSeparator} events={events} /> | |||
)} | |||
<tbody>{tooltipContent}</tbody> | |||
{this.props.graph === 'coverage' && ( | |||
{graph === GraphType.coverage && ( | |||
<GraphsTooltipsContentCoverage | |||
addSeparator={addSeparator} | |||
measuresHistory={measuresHistory} | |||
tooltipIdx={tooltipIdx} | |||
/> | |||
)} | |||
{this.props.graph === 'duplications' && ( | |||
{graph === GraphType.duplications && ( | |||
<GraphsTooltipsContentDuplication | |||
addSeparator={addSeparator} | |||
measuresHistory={measuresHistory} |
@@ -20,6 +20,7 @@ | |||
import * as React from 'react'; | |||
import { translate } from '../../helpers/l10n'; | |||
import { formatMeasure } from '../../helpers/measures'; | |||
import { MetricKey } from '../../types/metrics'; | |||
import { MeasureHistory } from '../../types/project-activity'; | |||
export interface GraphsTooltipsContentCoverageProps { | |||
@@ -28,13 +29,10 @@ export interface GraphsTooltipsContentCoverageProps { | |||
tooltipIdx: number; | |||
} | |||
export default function GraphsTooltipsContentCoverage({ | |||
addSeparator, | |||
measuresHistory, | |||
tooltipIdx, | |||
}: GraphsTooltipsContentCoverageProps) { | |||
const uncovered = measuresHistory.find((measure) => measure.metric === 'uncovered_lines'); | |||
const coverage = measuresHistory.find((measure) => measure.metric === 'coverage'); | |||
export default function GraphsTooltipsContentCoverage(props: GraphsTooltipsContentCoverageProps) { | |||
const { addSeparator, measuresHistory, tooltipIdx } = props; | |||
const uncovered = measuresHistory.find((measure) => measure.metric === MetricKey.uncovered_lines); | |||
const coverage = measuresHistory.find((measure) => measure.metric === MetricKey.coverage); | |||
if (!uncovered || !uncovered.history[tooltipIdx] || !coverage || !coverage.history[tooltipIdx]) { | |||
return null; | |||
} |
@@ -20,6 +20,7 @@ | |||
import * as React from 'react'; | |||
import { translate } from '../../helpers/l10n'; | |||
import { formatMeasure } from '../../helpers/measures'; | |||
import { MetricKey } from '../../types/metrics'; | |||
import { MeasureHistory } from '../../types/project-activity'; | |||
export interface GraphsTooltipsContentDuplicationProps { | |||
@@ -28,13 +29,12 @@ export interface GraphsTooltipsContentDuplicationProps { | |||
tooltipIdx: number; | |||
} | |||
export default function GraphsTooltipsContentDuplication({ | |||
addSeparator, | |||
measuresHistory, | |||
tooltipIdx, | |||
}: GraphsTooltipsContentDuplicationProps) { | |||
export default function GraphsTooltipsContentDuplication( | |||
props: GraphsTooltipsContentDuplicationProps | |||
) { | |||
const { addSeparator, measuresHistory, tooltipIdx } = props; | |||
const duplicationDensity = measuresHistory.find( | |||
(measure) => measure.metric === 'duplicated_lines_density' | |||
(measure) => measure.metric === MetricKey.duplicated_lines_density | |||
); | |||
if (!duplicationDensity || !duplicationDensity.history[tooltipIdx]) { | |||
return null; |
@@ -20,6 +20,7 @@ | |||
import * as React from 'react'; | |||
import ChartLegendIcon from '../../components/icons/ChartLegendIcon'; | |||
import Rating from '../../components/ui/Rating'; | |||
import { MetricKey } from '../../types/metrics'; | |||
import { MeasureHistory } from '../../types/project-activity'; | |||
import { Dict } from '../../types/types'; | |||
@@ -33,29 +34,28 @@ export interface GraphsTooltipsContentIssuesProps { | |||
} | |||
const METRIC_RATING: Dict<string> = { | |||
bugs: 'reliability_rating', | |||
vulnerabilities: 'security_rating', | |||
code_smells: 'sqale_rating', | |||
[MetricKey.bugs]: MetricKey.reliability_rating, | |||
[MetricKey.vulnerabilities]: MetricKey.security_rating, | |||
[MetricKey.code_smells]: MetricKey.sqale_rating, | |||
}; | |||
export default function GraphsTooltipsContentIssues(props: GraphsTooltipsContentIssuesProps) { | |||
const rating = props.measuresHistory.find( | |||
(measure) => measure.metric === METRIC_RATING[props.name] | |||
); | |||
if (!rating || !rating.history[props.tooltipIdx]) { | |||
const { index, measuresHistory, name, tooltipIdx, translatedName, value } = props; | |||
const rating = measuresHistory.find((measure) => measure.metric === METRIC_RATING[name]); | |||
if (!rating || !rating.history[tooltipIdx]) { | |||
return null; | |||
} | |||
const ratingValue = rating.history[props.tooltipIdx].value; | |||
const ratingValue = rating.history[tooltipIdx].value; | |||
return ( | |||
<tr className="activity-graph-tooltip-issues-line" key={props.name}> | |||
<tr className="activity-graph-tooltip-issues-line" key={name}> | |||
<td className="thin"> | |||
<ChartLegendIcon className="spacer-right" index={props.index} /> | |||
<ChartLegendIcon className="spacer-right" index={index} /> | |||
</td> | |||
<td className="text-right spacer-right"> | |||
<span className="activity-graph-tooltip-value">{props.value}</span> | |||
<span className="activity-graph-tooltip-value">{value}</span> | |||
{ratingValue && <Rating className="spacer-left" small={true} value={ratingValue} />} | |||
</td> | |||
<td>{props.translatedName}</td> | |||
<td>{translatedName}</td> | |||
</tr> | |||
); | |||
} |
@@ -26,12 +26,7 @@ import * as React from 'react'; | |||
import selectEvent from 'react-select-event'; | |||
import { byLabelText, byPlaceholderText, byRole, byText } from 'testing-library-selector'; | |||
import { parseDate } from '../../../helpers/dates'; | |||
import { | |||
mockAnalysisEvent, | |||
mockHistoryItem, | |||
mockMeasureHistory, | |||
mockParsedAnalysis, | |||
} from '../../../helpers/mocks/project-activity'; | |||
import { mockHistoryItem, mockMeasureHistory } from '../../../helpers/mocks/project-activity'; | |||
import { mockMetric } from '../../../helpers/testMocks'; | |||
import { renderComponent } from '../../../helpers/testReactTestingUtils'; | |||
import { MetricKey } from '../../../types/metrics'; | |||
@@ -41,63 +36,65 @@ import GraphsHeader from '../GraphsHeader'; | |||
import GraphsHistory from '../GraphsHistory'; | |||
import { generateSeries, getDisplayedHistoryMetrics, splitSeriesInGraphs } from '../utils'; | |||
const ui = { | |||
// Graph types. | |||
graphTypeSelect: byLabelText('project_activity.graphs.choose_type'), | |||
// Add metrics. | |||
addMetricBtn: byRole('button', { name: 'project_activity.graphs.custom.add' }), | |||
bugsCheckbox: byRole('checkbox', { name: MetricKey.bugs }), | |||
newBugsCheckbox: byRole('checkbox', { name: MetricKey.new_bugs }), | |||
burnedBudgetCheckbox: byRole('checkbox', { name: MetricKey.burned_budget }), | |||
vulnerabilityCheckbox: byRole('checkbox', { name: MetricKey.vulnerabilities }), | |||
hiddenOptionsAlert: byText('project_activity.graphs.custom.type_x_message', { | |||
exact: false, | |||
}), | |||
maxOptionsAlert: byText('project_activity.graphs.custom.add_metric_info'), | |||
filterMetrics: byPlaceholderText('search.search_for_metrics'), | |||
// Graphs. | |||
graphs: byLabelText('project_activity.graphs.explanation_x', { exact: false }), | |||
noDataText: byText('project_activity.graphs.custom.no_history'), | |||
// Date filters. | |||
fromDateInput: byLabelText('from_date'), | |||
toDateInput: byLabelText('to_date'), | |||
submitDatesBtn: byRole('button', { name: 'Submit dates' }), | |||
// Data in table. | |||
openInTableBtn: byRole('button', { name: 'project_activity.graphs.open_in_table' }), | |||
closeDataTableBtn: byRole('button', { name: 'close' }), | |||
dataTable: byRole('table'), | |||
dataTableRows: byRole('row'), | |||
dataTableColHeaders: byRole('columnheader'), | |||
onlyFirst100Text: byText('project_activity.graphs.data_table.max_lines_warning.100'), | |||
noDataTableText: byText('project_activity.graphs.data_table.no_data_warning_check_dates_x', { | |||
exact: false, | |||
}), | |||
}; | |||
const MAX_GRAPHS = 2; | |||
const MAX_SERIES_PER_GRAPH = 3; | |||
const HISTORY_COUNT = 10; | |||
const START_DATE = '2016-01-01T00:00:00+0200'; | |||
it('should correctly handle adding/removing custom metrics', async () => { | |||
it('should render correctly when loading', async () => { | |||
renderActivityGraph({ loading: true }); | |||
expect(await screen.findByLabelText('loading')).toBeInTheDocument(); | |||
}); | |||
it('should show the correct legend items', async () => { | |||
const user = userEvent.setup(); | |||
const ui = getPageObject(user); | |||
renderActivityGraph(); | |||
// Static legend items, which aren't interactive. | |||
expect(ui.legendRemoveMetricBtn(MetricKey.bugs).query()).not.toBeInTheDocument(); | |||
expect(ui.getLegendItem(MetricKey.bugs)).toBeInTheDocument(); | |||
// Switch to custom graph. | |||
await ui.changeGraphType(GraphType.custom); | |||
await ui.openAddMetrics(); | |||
await ui.clickOnMetric(MetricKey.bugs); | |||
await ui.clickOnMetric(MetricKey.test_failures); | |||
await user.keyboard('{Escape}'); | |||
// These legend items are interactive (interaction tested below). | |||
expect(ui.legendRemoveMetricBtn(MetricKey.bugs).get()).toBeInTheDocument(); | |||
expect(ui.legendRemoveMetricBtn(MetricKey.test_failures).get()).toBeInTheDocument(); | |||
// Shows warning for metrics with no data. | |||
const li = ui.getLegendItem(MetricKey.test_failures); | |||
// eslint-disable-next-line jest/no-conditional-in-test | |||
if (li) { | |||
li.focus(); | |||
} | |||
expect(ui.noDataWarningTooltip.get()).toBeInTheDocument(); | |||
}); | |||
it('should correctly handle adding/removing custom metrics', async () => { | |||
const ui = getPageObject(userEvent.setup()); | |||
renderActivityGraph(); | |||
// Change graph type to "Custom". | |||
await changeGraphType(GraphType.custom); | |||
await ui.changeGraphType(GraphType.custom); | |||
// Open the "Add metrics" dropdown button; select some metrics. | |||
await toggleAddMetrics(user); | |||
await ui.openAddMetrics(); | |||
// We should not see DATA type or New Code metrics. | |||
expect(ui.newBugsCheckbox.query()).not.toBeInTheDocument(); | |||
expect(ui.burnedBudgetCheckbox.query()).not.toBeInTheDocument(); | |||
// Select 3 Int types. | |||
await clickOnMetric(user, MetricKey.bugs); | |||
await clickOnMetric(user, MetricKey.code_smells); | |||
await clickOnMetric(user, MetricKey.confirmed_issues); | |||
await ui.clickOnMetric(MetricKey.bugs); | |||
await ui.clickOnMetric(MetricKey.code_smells); | |||
await ui.clickOnMetric(MetricKey.confirmed_issues); | |||
// Select 1 Percent type. | |||
await clickOnMetric(user, MetricKey.coverage); | |||
await ui.clickOnMetric(MetricKey.coverage); | |||
// We should see 2 graphs, correctly labelled. | |||
expect(ui.graphs.getAll()).toHaveLength(2); | |||
@@ -107,8 +104,8 @@ it('should correctly handle adding/removing custom metrics', async () => { | |||
expect(ui.hiddenOptionsAlert.get()).toBeInTheDocument(); | |||
// Select 2 more Percent types. | |||
await clickOnMetric(user, MetricKey.duplicated_lines_density); | |||
await clickOnMetric(user, MetricKey.test_success_density); | |||
await ui.clickOnMetric(MetricKey.duplicated_lines_density); | |||
await ui.clickOnMetric(MetricKey.test_success_density); | |||
// We cannot select anymore options. It should disable all remaining options, and | |||
// show a different alert. | |||
@@ -118,103 +115,139 @@ it('should correctly handle adding/removing custom metrics', async () => { | |||
expect(ui.vulnerabilityCheckbox.get()).toHaveAttribute('aria-disabled', 'true'); | |||
// Disable a few options. | |||
await clickOnMetric(user, MetricKey.bugs); | |||
await clickOnMetric(user, MetricKey.code_smells); | |||
await clickOnMetric(user, MetricKey.coverage); | |||
await ui.clickOnMetric(MetricKey.bugs); | |||
await ui.clickOnMetric(MetricKey.code_smells); | |||
await ui.clickOnMetric(MetricKey.coverage); | |||
// Search for option. | |||
await searchForMetric(user, 'bug'); | |||
await ui.searchForMetric('bug'); | |||
expect(ui.bugsCheckbox.get()).toBeInTheDocument(); | |||
expect(ui.vulnerabilityCheckbox.query()).not.toBeInTheDocument(); | |||
toggleAddMetrics(user); | |||
// Disable final metrics by clicking on the legend items. | |||
await removeMetric(user, MetricKey.confirmed_issues); | |||
await removeMetric(user, MetricKey.duplicated_lines_density); | |||
await removeMetric(user, MetricKey.test_success_density); | |||
await ui.removeMetric(MetricKey.confirmed_issues); | |||
await ui.removeMetric(MetricKey.duplicated_lines_density); | |||
await ui.removeMetric(MetricKey.test_success_density); | |||
// Should show message that there's no data to be rendered. | |||
expect(ui.noDataText.get()).toBeInTheDocument(); | |||
}); | |||
it('should render correctly when loading', async () => { | |||
renderActivityGraph({ loading: true }); | |||
expect(await screen.findByLabelText('loading')).toBeInTheDocument(); | |||
}); | |||
describe('data table modal', () => { | |||
it('shows the same data in a table', async () => { | |||
const ui = getPageObject(userEvent.setup()); | |||
renderActivityGraph(); | |||
it('shows the same data in a table', async () => { | |||
const user = userEvent.setup(); | |||
renderActivityGraph(); | |||
await ui.openDataTable(); | |||
expect(ui.dataTable.get()).toBeInTheDocument(); | |||
expect(ui.dataTableColHeaders.getAll()).toHaveLength(5); | |||
expect(ui.dataTableRows.getAll()).toHaveLength(HISTORY_COUNT + 1); | |||
await user.click(ui.openInTableBtn.get()); | |||
expect(ui.dataTable.get()).toBeInTheDocument(); | |||
expect(ui.dataTableColHeaders.getAll()).toHaveLength(5); | |||
expect(ui.dataTableRows.getAll()).toHaveLength(101); | |||
expect(screen.getByText('event.category.QUALITY_GATE', { exact: false })).toBeInTheDocument(); | |||
expect(screen.getByText('event.category.VERSION', { exact: false })).toBeInTheDocument(); | |||
expect( | |||
screen.getByText('event.category.DEFINITION_CHANGE', { exact: false }) | |||
).toBeInTheDocument(); | |||
expect(ui.onlyFirst100Text.get()).toBeInTheDocument(); | |||
// Change graph type and dates, check table updates correctly. | |||
await user.click(ui.closeDataTableBtn.get()); | |||
await changeGraphType(GraphType.coverage); | |||
await user.click(ui.openInTableBtn.get()); | |||
expect(ui.dataTable.get()).toBeInTheDocument(); | |||
expect(ui.dataTableColHeaders.getAll()).toHaveLength(4); | |||
expect(ui.dataTableRows.getAll()).toHaveLength(101); | |||
}); | |||
// Change graph type and dates, check table updates correctly. | |||
await ui.closeDataTable(); | |||
await ui.changeGraphType(GraphType.coverage); | |||
it('shows the same data in a table when filtered by date', async () => { | |||
const user = userEvent.setup(); | |||
renderActivityGraph({ | |||
graphStartDate: parseDate('2017-01-01'), | |||
graphEndDate: parseDate('2019-01-01'), | |||
await ui.openDataTable(); | |||
expect(ui.dataTable.get()).toBeInTheDocument(); | |||
expect(ui.dataTableColHeaders.getAll()).toHaveLength(4); | |||
expect(ui.dataTableRows.getAll()).toHaveLength(HISTORY_COUNT + 1); | |||
}); | |||
await user.click(ui.openInTableBtn.get()); | |||
expect(ui.dataTable.get()).toBeInTheDocument(); | |||
expect(ui.dataTableColHeaders.getAll()).toHaveLength(5); | |||
expect(ui.dataTableRows.getAll()).toHaveLength(2); | |||
expect(ui.onlyFirst100Text.query()).not.toBeInTheDocument(); | |||
}); | |||
async function changeGraphType(type: GraphType) { | |||
await selectEvent.select(ui.graphTypeSelect.get(), [`project_activity.graphs.${type}`]); | |||
} | |||
async function toggleAddMetrics(user: UserEvent) { | |||
await user.click(ui.addMetricBtn.get()); | |||
} | |||
async function clickOnMetric(user: UserEvent, name: MetricKey) { | |||
await user.click(screen.getByRole('checkbox', { name })); | |||
} | |||
it('shows the same data in a table when filtered by date', async () => { | |||
const ui = getPageObject(userEvent.setup()); | |||
renderActivityGraph({ | |||
graphStartDate: parseDate('2017-01-01'), | |||
graphEndDate: parseDate('2019-01-01'), | |||
}); | |||
async function searchForMetric(user: UserEvent, text: string) { | |||
await user.type(ui.filterMetrics.get(), text); | |||
} | |||
await ui.openDataTable(); | |||
expect(ui.dataTable.get()).toBeInTheDocument(); | |||
expect(ui.dataTableColHeaders.getAll()).toHaveLength(5); | |||
expect(ui.dataTableRows.getAll()).toHaveLength(2); | |||
}); | |||
}); | |||
async function removeMetric(user: UserEvent, metric: MetricKey) { | |||
await user.click( | |||
screen.getByRole('button', { name: `project_activity.graphs.custom.remove_metric.${metric}` }) | |||
); | |||
function getPageObject(user: UserEvent) { | |||
const ui = { | |||
// Graph types. | |||
graphTypeSelect: byLabelText('project_activity.graphs.choose_type'), | |||
// Add/remove metrics. | |||
addMetricBtn: byRole('button', { name: 'project_activity.graphs.custom.add' }), | |||
bugsCheckbox: byRole('checkbox', { name: MetricKey.bugs }), | |||
newBugsCheckbox: byRole('checkbox', { name: MetricKey.new_bugs }), | |||
burnedBudgetCheckbox: byRole('checkbox', { name: MetricKey.burned_budget }), | |||
vulnerabilityCheckbox: byRole('checkbox', { name: MetricKey.vulnerabilities }), | |||
hiddenOptionsAlert: byText('project_activity.graphs.custom.type_x_message', { | |||
exact: false, | |||
}), | |||
maxOptionsAlert: byText('project_activity.graphs.custom.add_metric_info'), | |||
filterMetrics: byPlaceholderText('search.search_for_metrics'), | |||
legendRemoveMetricBtn: (key: string) => | |||
byRole('button', { name: `project_activity.graphs.custom.remove_metric.${key}` }), | |||
getLegendItem: (name: string) => { | |||
// This is due to a limitation in testing library, where we cannot get a listitem | |||
// role element by name. | |||
return screen.getAllByRole('listitem').find((item) => item.textContent === name); | |||
}, | |||
noDataWarningTooltip: byLabelText('project_activity.graphs.custom.metric_no_history'), | |||
// Graphs. | |||
graphs: byLabelText('project_activity.graphs.explanation_x', { exact: false }), | |||
noDataText: byText('project_activity.graphs.custom.no_history'), | |||
// Date filters. | |||
fromDateInput: byLabelText('from_date'), | |||
toDateInput: byLabelText('to_date'), | |||
submitDatesBtn: byRole('button', { name: 'Submit dates' }), | |||
// Data in table. | |||
openInTableBtn: byRole('button', { name: 'project_activity.graphs.open_in_table' }), | |||
closeDataTableBtn: byRole('button', { name: 'close' }), | |||
dataTable: byRole('table'), | |||
dataTableRows: byRole('row'), | |||
dataTableColHeaders: byRole('columnheader'), | |||
noDataTableText: byText('project_activity.graphs.data_table.no_data_warning_check_dates_x', { | |||
exact: false, | |||
}), | |||
}; | |||
return { | |||
...ui, | |||
async changeGraphType(type: GraphType) { | |||
await selectEvent.select(ui.graphTypeSelect.get(), [`project_activity.graphs.${type}`]); | |||
}, | |||
async openAddMetrics() { | |||
await user.click(ui.addMetricBtn.get()); | |||
}, | |||
async searchForMetric(text: string) { | |||
await user.type(ui.filterMetrics.get(), text); | |||
}, | |||
async clickOnMetric(name: MetricKey) { | |||
await user.click(screen.getByRole('checkbox', { name })); | |||
}, | |||
async removeMetric(metric: MetricKey) { | |||
await user.click(ui.legendRemoveMetricBtn(metric).get()); | |||
}, | |||
async openDataTable() { | |||
await user.click(ui.openInTableBtn.get()); | |||
}, | |||
async closeDataTable() { | |||
await user.click(ui.closeDataTableBtn.get()); | |||
}, | |||
}; | |||
} | |||
function renderActivityGraph( | |||
graphsHistoryProps: Partial<GraphsHistory['props']> = {}, | |||
graphsHeaderProps: Partial<GraphsHeader['props']> = {} | |||
) { | |||
const MAX_GRAPHS = 2; | |||
const MAX_SERIES_PER_GRAPH = 3; | |||
const HISTORY_COUNT = 100; | |||
function ActivityGraph() { | |||
const [selectedMetrics, setSelectedMetrics] = React.useState<string[]>([]); | |||
const [graph, setGraph] = React.useState(GraphType.issues); | |||
const [selectedDate, setSelectedDate] = React.useState<Date | undefined>(undefined); | |||
const [graph, setGraph] = React.useState(graphsHistoryProps.graph || GraphType.issues); | |||
const [selectedDate, setSelectedDate] = React.useState<Date | undefined>( | |||
graphsHistoryProps.selectedDate | |||
); | |||
const [fromDate, setFromDate] = React.useState<Date | undefined>(undefined); | |||
const [toDate, setToDate] = React.useState<Date | undefined>(undefined); | |||
@@ -232,8 +265,8 @@ function renderActivityGraph( | |||
MetricKey.duplicated_lines_density, | |||
MetricKey.test_success_density, | |||
].forEach((metric) => { | |||
const history = times(HISTORY_COUNT, (i) => { | |||
const date = parseDate('2016-01-01T00:00:00+0200'); | |||
const history = times(HISTORY_COUNT - 2, (i) => { | |||
const date = parseDate(START_DATE); | |||
date.setDate(date.getDate() + i); | |||
return mockHistoryItem({ date, value: i.toString() }); | |||
}); | |||
@@ -245,7 +278,6 @@ function renderActivityGraph( | |||
metrics.push( | |||
mockMetric({ | |||
key: metric, | |||
name: metric, | |||
type: metric.includes('_density') || metric === MetricKey.coverage ? 'PERCENT' : 'INT', | |||
}) | |||
); | |||
@@ -253,8 +285,21 @@ function renderActivityGraph( | |||
// The following should be filtered out, and not be suggested as options. | |||
metrics.push( | |||
mockMetric({ key: MetricKey.new_bugs, name: MetricKey.new_bugs, type: 'INT' }), | |||
mockMetric({ key: MetricKey.burned_budget, name: MetricKey.burned_budget, type: 'DATA' }) | |||
mockMetric({ key: MetricKey.new_bugs, type: 'INT' }), | |||
mockMetric({ key: MetricKey.burned_budget, type: 'DATA' }) | |||
); | |||
// The following will not be filtered out, but has no values. | |||
metrics.push(mockMetric({ key: MetricKey.test_failures, type: 'INT' })); | |||
measuresHistory.push( | |||
mockMeasureHistory({ | |||
metric: MetricKey.test_failures, | |||
history: times(HISTORY_COUNT, (i) => { | |||
const date = parseDate(START_DATE); | |||
date.setDate(date.getDate() + i); | |||
return mockHistoryItem({ date, value: undefined }); | |||
}), | |||
}) | |||
); | |||
const series = generateSeries( | |||
@@ -305,28 +350,7 @@ function renderActivityGraph( | |||
{...graphsHeaderProps} | |||
/> | |||
<GraphsHistory | |||
analyses={[ | |||
mockParsedAnalysis({ | |||
date: parseDate('2018-10-27T12:21:15+0200'), | |||
events: [ | |||
mockAnalysisEvent({ key: '1' }), | |||
mockAnalysisEvent({ | |||
key: '2', | |||
category: 'VERSION', | |||
description: undefined, | |||
qualityGate: undefined, | |||
}), | |||
mockAnalysisEvent({ | |||
key: '3', | |||
category: 'DEFINITION_CHANGE', | |||
definitionChange: { | |||
projects: [{ changeType: 'ADDED', key: 'foo', name: 'Foo' }], | |||
}, | |||
qualityGate: undefined, | |||
}), | |||
], | |||
}), | |||
]} | |||
analyses={[]} | |||
graph={graph} | |||
graphEndDate={toDate} | |||
graphStartDate={fromDate} |
@@ -22,13 +22,18 @@ import { screen } from '@testing-library/react'; | |||
import { times } from 'lodash'; | |||
import * as React from 'react'; | |||
import { parseDate } from '../../../helpers/dates'; | |||
import { mockHistoryItem, mockMeasureHistory } from '../../../helpers/mocks/project-activity'; | |||
import { | |||
mockAnalysisEvent, | |||
mockHistoryItem, | |||
mockMeasureHistory, | |||
mockParsedAnalysis, | |||
} from '../../../helpers/mocks/project-activity'; | |||
import { mockMetric } from '../../../helpers/testMocks'; | |||
import { renderComponent } from '../../../helpers/testReactTestingUtils'; | |||
import { MetricKey } from '../../../types/metrics'; | |||
import { GraphType, MeasureHistory } from '../../../types/project-activity'; | |||
import { Metric } from '../../../types/types'; | |||
import DataTableModal, { DataTableModalProps } from '../DataTableModal'; | |||
import DataTableModal, { DataTableModalProps, MAX_DATA_TABLE_ROWS } from '../DataTableModal'; | |||
import { generateSeries, getDisplayedHistoryMetrics } from '../utils'; | |||
it('should render correctly if there are no series', () => { | |||
@@ -38,10 +43,22 @@ it('should render correctly if there are no series', () => { | |||
).toBeInTheDocument(); | |||
}); | |||
it('should render correctly if there are events', () => { | |||
renderDataTableModal({ | |||
analyses: [ | |||
mockParsedAnalysis({ | |||
date: parseDate('2016-01-01T00:00:00+0200'), | |||
events: [mockAnalysisEvent({ key: '1', category: 'QUALITY_GATE' })], | |||
}), | |||
], | |||
}); | |||
expect(screen.getByText('event.category.QUALITY_GATE', { exact: false })).toBeInTheDocument(); | |||
}); | |||
it('should render correctly if there is too much data', () => { | |||
renderDataTableModal({ series: mockSeries(101) }); | |||
renderDataTableModal({ series: mockSeries(MAX_DATA_TABLE_ROWS + 1) }); | |||
expect( | |||
screen.getByText('project_activity.graphs.data_table.max_lines_warning.100') | |||
screen.getByText(`project_activity.graphs.data_table.max_lines_warning.${MAX_DATA_TABLE_ROWS}`) | |||
).toBeInTheDocument(); | |||
}); | |||
@@ -1,88 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2022 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 * as React from 'react'; | |||
import { mockBranch } from '../../../helpers/mocks/branch-like'; | |||
import { click } from '../../../helpers/testUtils'; | |||
import { DefinitionChangeEvent, DefinitionChangeEventInner } from '../DefinitionChangeEventInner'; | |||
it('should render', () => { | |||
const event: DefinitionChangeEvent = { | |||
category: 'DEFINITION_CHANGE', | |||
key: 'foo1234', | |||
name: '', | |||
definitionChange: { | |||
projects: [ | |||
{ changeType: 'ADDED', key: 'foo', name: 'Foo', branch: 'master' }, | |||
{ changeType: 'REMOVED', key: 'bar', name: 'Bar', branch: 'master' }, | |||
], | |||
}, | |||
}; | |||
const wrapper = shallow(<DefinitionChangeEventInner branchLike={undefined} event={event} />); | |||
expect(wrapper).toMatchSnapshot(); | |||
click(wrapper.find('.project-activity-event-inner-more-link')); | |||
wrapper.update(); | |||
expect(wrapper).toMatchSnapshot(); | |||
}); | |||
it('should render for a branch', () => { | |||
const branch = mockBranch({ name: 'feature-x' }); | |||
const event: DefinitionChangeEvent = { | |||
category: 'DEFINITION_CHANGE', | |||
key: 'foo1234', | |||
name: '', | |||
definitionChange: { | |||
projects: [ | |||
{ changeType: 'ADDED', key: 'foo', name: 'Foo', branch: 'feature-x' }, | |||
{ | |||
changeType: 'BRANCH_CHANGED', | |||
key: 'bar', | |||
name: 'Bar', | |||
oldBranch: 'master', | |||
newBranch: 'feature-y', | |||
}, | |||
], | |||
}, | |||
}; | |||
const wrapper = shallow(<DefinitionChangeEventInner branchLike={branch} event={event} />); | |||
click(wrapper.find('.project-activity-event-inner-more-link')); | |||
wrapper.update(); | |||
expect(wrapper).toMatchSnapshot(); | |||
}); | |||
it('should render when readonly', () => { | |||
const event: DefinitionChangeEvent = { | |||
category: 'DEFINITION_CHANGE', | |||
key: 'foo1234', | |||
name: '', | |||
definitionChange: { | |||
projects: [ | |||
{ changeType: 'ADDED', key: 'foo', name: 'Foo', branch: 'master' }, | |||
{ changeType: 'REMOVED', key: 'bar', name: 'Bar', branch: 'master' }, | |||
], | |||
}, | |||
}; | |||
const wrapper = shallow( | |||
<DefinitionChangeEventInner branchLike={undefined} event={event} readonly={true} /> | |||
); | |||
expect(wrapper).toMatchSnapshot(); | |||
expect(wrapper.find('.project-activity-event-inner-more-link').exists()).toBe(false); | |||
}); |
@@ -0,0 +1,165 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2022 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 { screen } from '@testing-library/react'; | |||
import userEvent from '@testing-library/user-event'; | |||
import * as React from 'react'; | |||
import { byRole, byText } from 'testing-library-selector'; | |||
import { mockAnalysisEvent } from '../../../helpers/mocks/project-activity'; | |||
import { renderComponent } from '../../../helpers/testReactTestingUtils'; | |||
import EventInner, { EventInnerProps } from '../EventInner'; | |||
const ui = { | |||
showMoreBtn: byRole('button', { name: 'more' }), | |||
showLessBtn: byRole('button', { name: 'hide' }), | |||
projectLink: (name: string) => byRole('link', { name }), | |||
definitionChangeLabel: byText('event.category.DEFINITION_CHANGE', { exact: false }), | |||
projectAddedTxt: byText('event.definition_change.added'), | |||
projectRemovedTxt: byText('event.definition_change.removed'), | |||
branchReplacedTxt: byText('event.definition_change.branch_replaced'), | |||
qualityGateLabel: byText('event.category.QUALITY_GATE', { exact: false }), | |||
stillFailingTxt: byText('event.quality_gate.still_x'), | |||
versionLabel: byText('event.category.VERSION', { exact: false }), | |||
}; | |||
describe('DEFINITION_CHANGE events', () => { | |||
it('should render correctly for "DEFINITION_CHANGE" events', async () => { | |||
const user = userEvent.setup(); | |||
renderEventInner({ | |||
event: mockAnalysisEvent({ | |||
category: 'DEFINITION_CHANGE', | |||
definitionChange: { | |||
projects: [ | |||
{ changeType: 'ADDED', key: 'foo', name: 'Foo', branch: 'master-foo' }, | |||
{ changeType: 'REMOVED', key: 'bar', name: 'Bar', branch: 'master-bar' }, | |||
{ | |||
changeType: 'BRANCH_CHANGED', | |||
key: 'baz', | |||
name: 'Baz', | |||
oldBranch: 'old-branch', | |||
newBranch: 'new-branch', | |||
}, | |||
], | |||
}, | |||
}), | |||
}); | |||
expect(ui.definitionChangeLabel.get()).toBeInTheDocument(); | |||
await user.click(ui.showMoreBtn.get()); | |||
// ADDED. | |||
expect(ui.projectAddedTxt.get()).toBeInTheDocument(); | |||
expect(ui.projectLink('Foo').get()).toBeInTheDocument(); | |||
expect(screen.getByText('master-foo')).toBeInTheDocument(); | |||
// REMOVED. | |||
expect(ui.projectRemovedTxt.get()).toBeInTheDocument(); | |||
expect(ui.projectLink('Bar').get()).toBeInTheDocument(); | |||
expect(screen.getByText('master-bar')).toBeInTheDocument(); | |||
// BRANCH_CHANGED | |||
expect(ui.branchReplacedTxt.get()).toBeInTheDocument(); | |||
expect(ui.projectLink('Baz').get()).toBeInTheDocument(); | |||
expect(screen.getByText('old-branch')).toBeInTheDocument(); | |||
expect(screen.getByText('new-branch')).toBeInTheDocument(); | |||
}); | |||
}); | |||
describe('QUALITY_GATE events', () => { | |||
it('should render correctly for simple "QUALITY_GATE" events', () => { | |||
renderEventInner({ | |||
event: mockAnalysisEvent({ | |||
category: 'QUALITY_GATE', | |||
qualityGate: { status: 'ERROR', stillFailing: false, failing: [] }, | |||
}), | |||
}); | |||
expect(ui.qualityGateLabel.get()).toBeInTheDocument(); | |||
}); | |||
it('should render correctly for "still failing" "QUALITY_GATE" events', () => { | |||
renderEventInner({ | |||
event: mockAnalysisEvent({ | |||
category: 'QUALITY_GATE', | |||
qualityGate: { status: 'ERROR', stillFailing: true, failing: [] }, | |||
}), | |||
}); | |||
expect(ui.qualityGateLabel.get()).toBeInTheDocument(); | |||
expect(ui.stillFailingTxt.get()).toBeInTheDocument(); | |||
}); | |||
it('should render correctly for application "QUALITY_GATE" events', async () => { | |||
const user = userEvent.setup(); | |||
renderEventInner({ | |||
event: mockAnalysisEvent({ | |||
category: 'QUALITY_GATE', | |||
qualityGate: { | |||
status: 'ERROR', | |||
stillFailing: true, | |||
failing: [ | |||
{ | |||
key: 'foo', | |||
name: 'Foo', | |||
branch: 'master', | |||
}, | |||
{ | |||
key: 'bar', | |||
name: 'Bar', | |||
branch: 'feature/bar', | |||
}, | |||
], | |||
}, | |||
}), | |||
}); | |||
expect(ui.qualityGateLabel.get()).toBeInTheDocument(); | |||
await user.click(ui.showMoreBtn.get()); | |||
expect(ui.projectLink('Foo').get()).toBeInTheDocument(); | |||
expect(ui.projectLink('Bar').get()).toBeInTheDocument(); | |||
await user.click(ui.showLessBtn.get()); | |||
expect(ui.projectLink('Foo').query()).not.toBeInTheDocument(); | |||
expect(ui.projectLink('Bar').query()).not.toBeInTheDocument(); | |||
}); | |||
}); | |||
describe('VERSION events', () => { | |||
it('should render correctly', () => { | |||
renderEventInner({ | |||
event: mockAnalysisEvent({ | |||
category: 'VERSION', | |||
name: '1.0', | |||
}), | |||
}); | |||
expect(ui.versionLabel.get()).toBeInTheDocument(); | |||
expect(screen.getByText('1.0')).toBeInTheDocument(); | |||
}); | |||
}); | |||
function renderEventInner(props: Partial<EventInnerProps> = {}) { | |||
return renderComponent(<EventInner event={mockAnalysisEvent()} {...props} />); | |||
} |
@@ -1,56 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2022 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 * as React from 'react'; | |||
import { parseDate } from '../../../helpers/dates'; | |||
import GraphsLegendCustom, { GraphsLegendCustomProps } from '../GraphsLegendCustom'; | |||
it('should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot('default'); | |||
}); | |||
function shallowRender(props: Partial<GraphsLegendCustomProps> = {}) { | |||
return shallow<GraphsLegendCustomProps>( | |||
<GraphsLegendCustom | |||
removeMetric={jest.fn()} | |||
series={[ | |||
{ | |||
name: 'bugs', | |||
translatedName: 'Bugs', | |||
data: [{ x: parseDate('2017-05-16T13:50:02+0200'), y: 1 }], | |||
type: 'INT', | |||
}, | |||
{ | |||
name: 'my_metric', | |||
translatedName: 'My Metric', | |||
data: [{ x: parseDate('2017-05-16T13:50:02+0200'), y: 1 }], | |||
type: 'INT', | |||
}, | |||
{ | |||
name: 'foo', | |||
translatedName: 'Foo', | |||
data: [], | |||
type: 'INT', | |||
}, | |||
]} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -1,51 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2022 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 * as React from 'react'; | |||
import { ClearButton } from '../../../components/controls/buttons'; | |||
import { click } from '../../../helpers/testUtils'; | |||
import GraphsLegendItem from '../GraphsLegendItem'; | |||
it('should render correctly a legend', () => { | |||
expect(shallowRender()).toMatchSnapshot('default'); | |||
expect( | |||
shallowRender({ | |||
className: 'myclass', | |||
index: 1, | |||
metric: 'foo', | |||
name: 'Foo', | |||
removeMetric: jest.fn(), | |||
}) | |||
).toMatchSnapshot('with legend'); | |||
expect(shallowRender({ showWarning: true })).toMatchSnapshot('with warning'); | |||
}); | |||
it('should correctly handle clicks', () => { | |||
const removeMetric = jest.fn(); | |||
const wrapper = shallowRender({ removeMetric }); | |||
click(wrapper.find(ClearButton)); | |||
expect(removeMetric).toHaveBeenCalledWith('bugs'); | |||
}); | |||
function shallowRender(props: Partial<GraphsLegendItem['props']> = {}) { | |||
return shallow<GraphsLegendItem>( | |||
<GraphsLegendItem index={2} metric="bugs" name="Bugs" {...props} /> | |||
); | |||
} |
@@ -1,30 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2022 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 * as React from 'react'; | |||
import GraphsLegendNewCode from '../GraphsLegendNewCode'; | |||
it('should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot('default'); | |||
}); | |||
function shallowRender() { | |||
return shallow(<GraphsLegendNewCode />); | |||
} |
@@ -1,38 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2022 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 * as React from 'react'; | |||
import GraphsLegendStatic, { GraphsLegendStaticProps } from '../GraphsLegendStatic'; | |||
it('should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot('default'); | |||
}); | |||
function shallowRender(props: Partial<GraphsLegendStaticProps> = {}) { | |||
return shallow<GraphsLegendStaticProps>( | |||
<GraphsLegendStatic | |||
series={[ | |||
{ name: 'bugs', translatedName: 'Bugs' }, | |||
{ name: 'code_smells', translatedName: 'Code Smells' }, | |||
]} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -0,0 +1,127 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2022 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 { screen } from '@testing-library/react'; | |||
import * as React from 'react'; | |||
import { parseDate } from '../../../helpers/dates'; | |||
import { | |||
mockAnalysisEvent, | |||
mockHistoryItem, | |||
mockMeasureHistory, | |||
} from '../../../helpers/mocks/project-activity'; | |||
import { mockMetric } from '../../../helpers/testMocks'; | |||
import { renderComponent } from '../../../helpers/testReactTestingUtils'; | |||
import { MetricKey } from '../../../types/metrics'; | |||
import { GraphType, MeasureHistory } from '../../../types/project-activity'; | |||
import { Metric } from '../../../types/types'; | |||
import GraphsTooltips from '../GraphsTooltips'; | |||
import { generateSeries, getDisplayedHistoryMetrics } from '../utils'; | |||
it.each([ | |||
[ | |||
GraphType.issues, | |||
[ | |||
[MetricKey.bugs, 1, 'C'], | |||
[MetricKey.code_smells, 0, 'A'], | |||
[MetricKey.vulnerabilities, 2, 'E'], | |||
], | |||
], | |||
[ | |||
GraphType.coverage, | |||
[ | |||
['metric.coverage.name', '75.0%'], | |||
['metric.uncovered_lines.name', 8], | |||
], | |||
], | |||
[GraphType.duplications, [['metric.duplicated_lines_density.name', '3.0%']]], | |||
[GraphType.custom, [[MetricKey.bugs, 1]]], | |||
])( | |||
'renders correctly for graph of type %s', | |||
(graph, metrics: Array<[string, number, string] | [string, number]>) => { | |||
renderGraphsTooltips({ graph }); | |||
// Render events. | |||
expect(screen.getByText('event.category.QUALITY_GATE', { exact: false })).toBeInTheDocument(); | |||
// Measures table. | |||
metrics.forEach(([key, n, rating]) => { | |||
expect( | |||
screen.getByRole('row', { | |||
// eslint-disable-next-line jest/no-conditional-in-test | |||
name: rating ? `${n} metric.has_rating_X.${rating} ${key}` : `${n} ${key}`, | |||
}) | |||
).toBeInTheDocument(); | |||
}); | |||
} | |||
); | |||
function renderGraphsTooltips(props: Partial<GraphsTooltips['props']> = {}) { | |||
const graph = (props.graph as GraphType) || GraphType.coverage; | |||
const measuresHistory: MeasureHistory[] = []; | |||
const date = props.selectedDate || parseDate('2016-01-01T00:00:00+0200'); | |||
const metrics: Metric[] = []; | |||
[ | |||
[MetricKey.bugs, '1'], | |||
[MetricKey.reliability_rating, '3'], | |||
[MetricKey.code_smells, '0'], | |||
[MetricKey.sqale_rating, '1'], | |||
[MetricKey.vulnerabilities, '2'], | |||
[MetricKey.security_rating, '5'], | |||
[MetricKey.lines_to_cover, '10'], | |||
[MetricKey.uncovered_lines, '8'], | |||
[MetricKey.coverage, '75'], | |||
[MetricKey.duplicated_lines_density, '3'], | |||
].forEach(([metric, value]) => { | |||
measuresHistory.push( | |||
mockMeasureHistory({ | |||
metric, | |||
history: [mockHistoryItem({ date, value })], | |||
}) | |||
); | |||
metrics.push( | |||
mockMetric({ | |||
key: metric, | |||
type: metric.includes('_density') || metric === MetricKey.coverage ? 'PERCENT' : 'INT', | |||
}) | |||
); | |||
}); | |||
const series = generateSeries( | |||
measuresHistory, | |||
graph, | |||
metrics, | |||
getDisplayedHistoryMetrics(graph, graph === GraphType.custom ? [MetricKey.bugs] : []) | |||
); | |||
return renderComponent( | |||
<GraphsTooltips | |||
events={[mockAnalysisEvent({ key: '1' })]} | |||
graph={graph} | |||
graphWidth={100} | |||
measuresHistory={measuresHistory} | |||
selectedDate={date} | |||
series={series} | |||
tooltipIdx={0} | |||
tooltipPos={0} | |||
formatValue={(n: number | string) => String(n)} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -1,111 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2022 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 * as React from 'react'; | |||
import { parseDate } from '../../../helpers/dates'; | |||
import { mockEvent } from '../../../helpers/testUtils'; | |||
import GraphsTooltips from '../GraphsTooltips'; | |||
import { DEFAULT_GRAPH } from '../utils'; | |||
const SERIES_ISSUES = [ | |||
{ | |||
name: 'bugs', | |||
translatedName: 'Bugs', | |||
data: [ | |||
{ | |||
x: parseDate('2011-10-01T22:01:00.000Z'), | |||
y: 3, | |||
}, | |||
{ | |||
x: parseDate('2011-10-25T10:27:41.000Z'), | |||
y: 0, | |||
}, | |||
], | |||
type: 'INT', | |||
}, | |||
{ | |||
name: 'code_smells', | |||
translatedName: 'Code Smells', | |||
data: [ | |||
{ | |||
x: parseDate('2011-10-01T22:01:00.000Z'), | |||
y: 18, | |||
}, | |||
{ | |||
x: parseDate('2011-10-25T10:27:41.000Z'), | |||
y: 15, | |||
}, | |||
], | |||
type: 'INT', | |||
}, | |||
{ | |||
name: 'vulnerabilities', | |||
translatedName: 'Vulnerabilities', | |||
data: [ | |||
{ | |||
x: parseDate('2011-10-01T22:01:00.000Z'), | |||
y: 0, | |||
}, | |||
{ | |||
x: parseDate('2011-10-25T10:27:41.000Z'), | |||
y: 1, | |||
}, | |||
], | |||
type: 'INT', | |||
}, | |||
]; | |||
const DEFAULT_PROPS: GraphsTooltips['props'] = { | |||
events: [], | |||
formatValue: (val) => 'Formated.' + val, | |||
graph: DEFAULT_GRAPH, | |||
graphWidth: 500, | |||
measuresHistory: [], | |||
selectedDate: parseDate('2011-10-01T22:01:00.000Z'), | |||
series: SERIES_ISSUES, | |||
tooltipIdx: 0, | |||
tooltipPos: 666, | |||
}; | |||
it('should render correctly for issues graphs', () => { | |||
expect(shallow(<GraphsTooltips {...DEFAULT_PROPS} />)).toMatchSnapshot(); | |||
expect(shallow(<GraphsTooltips {...DEFAULT_PROPS} events={[mockEvent()]} />)).toMatchSnapshot( | |||
'with events' | |||
); | |||
}); | |||
it('should render correctly for random graphs', () => { | |||
expect( | |||
shallow( | |||
<GraphsTooltips | |||
{...DEFAULT_PROPS} | |||
graph="random" | |||
selectedDate={parseDate('2011-10-25T10:27:41.000Z')} | |||
tooltipIdx={1} | |||
/> | |||
) | |||
).toMatchSnapshot(); | |||
}); | |||
it('should not add separators if not needed', () => { | |||
expect( | |||
shallow(<GraphsTooltips {...DEFAULT_PROPS} graph="coverage" series={[]} />) | |||
).toMatchSnapshot(); | |||
}); |
@@ -1,33 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2022 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 * as React from 'react'; | |||
import GraphsTooltipsContent from '../GraphsTooltipsContent'; | |||
const DEFAULT_PROPS = { | |||
index: 1, | |||
name: 'code_smells', | |||
translatedName: 'Code Smells', | |||
value: '1.2k', | |||
}; | |||
it('should render correctly', () => { | |||
expect(shallow(<GraphsTooltipsContent {...DEFAULT_PROPS} />)).toMatchSnapshot(); | |||
}); |
@@ -1,64 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2022 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 * as React from 'react'; | |||
import { parseDate } from '../../../helpers/dates'; | |||
import GraphsTooltipsContentCoverage, { | |||
GraphsTooltipsContentCoverageProps, | |||
} from '../GraphsTooltipsContentCoverage'; | |||
it('should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot('default'); | |||
expect(shallowRender({ addSeparator: true })).toMatchSnapshot('with separator'); | |||
expect(shallowRender({ tooltipIdx: -1 }).type()).toBeNull(); | |||
}); | |||
function shallowRender(props: Partial<GraphsTooltipsContentCoverageProps> = {}) { | |||
return shallow<GraphsTooltipsContentCoverageProps>( | |||
<GraphsTooltipsContentCoverage | |||
addSeparator={false} | |||
measuresHistory={[ | |||
{ | |||
metric: 'coverage', | |||
history: [ | |||
{ date: parseDate('2011-10-01T22:01:00.000Z') }, | |||
{ date: parseDate('2011-10-25T10:27:41.000Z'), value: '80.3' }, | |||
], | |||
}, | |||
{ | |||
metric: 'lines_to_cover', | |||
history: [ | |||
{ date: parseDate('2011-10-01T22:01:00.000Z'), value: '60545' }, | |||
{ date: parseDate('2011-10-25T10:27:41.000Z'), value: '65215' }, | |||
], | |||
}, | |||
{ | |||
metric: 'uncovered_lines', | |||
history: [ | |||
{ date: parseDate('2011-10-01T22:01:00.000Z'), value: '40564' }, | |||
{ date: parseDate('2011-10-25T10:27:41.000Z'), value: '10245' }, | |||
], | |||
}, | |||
]} | |||
tooltipIdx={1} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -1,51 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2022 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 * as React from 'react'; | |||
import { parseDate } from '../../../helpers/dates'; | |||
import GraphsTooltipsContentDuplication, { | |||
GraphsTooltipsContentDuplicationProps, | |||
} from '../GraphsTooltipsContentDuplication'; | |||
it('should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot('default'); | |||
expect(shallowRender({ addSeparator: true })).toMatchSnapshot('with separator'); | |||
expect(shallowRender({ tooltipIdx: -1 }).type()).toBeNull(); | |||
expect(shallowRender({ measuresHistory: [] }).type()).toBeNull(); | |||
}); | |||
function shallowRender(props: Partial<GraphsTooltipsContentDuplicationProps> = {}) { | |||
return shallow<GraphsTooltipsContentDuplicationProps>( | |||
<GraphsTooltipsContentDuplication | |||
addSeparator={false} | |||
measuresHistory={[ | |||
{ | |||
metric: 'duplicated_lines_density', | |||
history: [ | |||
{ date: parseDate('2011-10-01T22:01:00.000Z') }, | |||
{ date: parseDate('2011-10-25T10:27:41.000Z'), value: '10245' }, | |||
], | |||
}, | |||
]} | |||
tooltipIdx={1} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -1,33 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2022 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 * as React from 'react'; | |||
import GraphsTooltipsContentEvents from '../GraphsTooltipsContentEvents'; | |||
const EVENTS = [ | |||
{ key: '1', category: 'VERSION', name: '6.5' }, | |||
{ key: '2', category: 'OTHER', name: 'Foo' }, | |||
]; | |||
it('should render correctly', () => { | |||
expect( | |||
shallow(<GraphsTooltipsContentEvents addSeparator={true} events={EVENTS} />) | |||
).toMatchSnapshot(); | |||
}); |
@@ -1,59 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2022 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 * as React from 'react'; | |||
import { parseDate } from '../../../helpers/dates'; | |||
import GraphsTooltipsContentIssues, { | |||
GraphsTooltipsContentIssuesProps, | |||
} from '../GraphsTooltipsContentIssues'; | |||
it('should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot('default'); | |||
expect(shallowRender({ tooltipIdx: -1 }).type()).toBeNull(); | |||
}); | |||
function shallowRender(props: Partial<GraphsTooltipsContentIssuesProps> = {}) { | |||
return shallow<GraphsTooltipsContentIssuesProps>( | |||
<GraphsTooltipsContentIssues | |||
index={2} | |||
measuresHistory={[ | |||
{ | |||
metric: 'bugs', | |||
history: [ | |||
{ date: parseDate('2011-10-01T22:01:00.000Z'), value: '500' }, | |||
{ date: parseDate('2011-10-25T10:27:41.000Z'), value: '1.2k' }, | |||
], | |||
}, | |||
{ | |||
metric: 'reliability_rating', | |||
history: [ | |||
{ date: parseDate('2011-10-01T22:01:00.000Z') }, | |||
{ date: parseDate('2011-10-25T10:27:41.000Z'), value: '5.0' }, | |||
], | |||
}, | |||
]} | |||
name="bugs" | |||
tooltipIdx={1} | |||
translatedName="Bugs" | |||
value="1.2k" | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -1,60 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2022 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 * as React from 'react'; | |||
import { click } from '../../../helpers/testUtils'; | |||
import { RichQualityGateEvent, RichQualityGateEventInner } from '../RichQualityGateEventInner'; | |||
const event: RichQualityGateEvent = { | |||
category: 'QUALITY_GATE', | |||
key: 'foo1234', | |||
name: '', | |||
qualityGate: { | |||
failing: [ | |||
{ branch: 'master', key: 'foo', name: 'Foo' }, | |||
{ branch: 'master', key: 'bar', name: 'Bar' }, | |||
], | |||
status: 'ERROR', | |||
stillFailing: true, | |||
}, | |||
}; | |||
it('should render', () => { | |||
const wrapper = shallow(<RichQualityGateEventInner event={event} />); | |||
expect(wrapper).toMatchSnapshot(); | |||
click(wrapper.find('.project-activity-event-inner-more-link')); | |||
wrapper.update(); | |||
expect(wrapper).toMatchSnapshot(); | |||
}); | |||
it('should not expand', () => { | |||
const wrapper = shallow( | |||
<RichQualityGateEventInner | |||
event={{ ...event, qualityGate: { ...event.qualityGate, failing: [] } }} | |||
/> | |||
); | |||
expect(wrapper.find('.project-activity-event-inner-more-link').exists()).toBe(false); | |||
}); | |||
it('should not expand when readonly', () => { | |||
const wrapper = shallow(<RichQualityGateEventInner event={event} readonly={true} />); | |||
expect(wrapper.find('.project-activity-event-inner-more-link').exists()).toBe(false); | |||
}); |
@@ -1,248 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render 1`] = ` | |||
<Fragment> | |||
<span | |||
className="note" | |||
> | |||
event.category.DEFINITION_CHANGE | |||
: | |||
</span> | |||
<div> | |||
<ButtonLink | |||
className="project-activity-event-inner-more-link" | |||
onClick={[Function]} | |||
stopPropagation={true} | |||
> | |||
more | |||
<DropdownIcon | |||
className="little-spacer-left" | |||
turned={false} | |||
/> | |||
</ButtonLink> | |||
</div> | |||
</Fragment> | |||
`; | |||
exports[`should render 2`] = ` | |||
<Fragment> | |||
<span | |||
className="note" | |||
> | |||
event.category.DEFINITION_CHANGE | |||
: | |||
</span> | |||
<div> | |||
<ButtonLink | |||
className="project-activity-event-inner-more-link" | |||
onClick={[Function]} | |||
stopPropagation={true} | |||
> | |||
hide | |||
<DropdownIcon | |||
className="little-spacer-left" | |||
turned={true} | |||
/> | |||
</ButtonLink> | |||
</div> | |||
<ul | |||
className="spacer-left spacer-top" | |||
> | |||
<li | |||
className="display-flex-center spacer-top" | |||
key="foo" | |||
> | |||
<div | |||
className="text-ellipsis" | |||
> | |||
<FormattedMessage | |||
defaultMessage="event.definition_change.added" | |||
id="event.definition_change.added" | |||
values={ | |||
Object { | |||
"branch": <span | |||
className="nowrap" | |||
title="master" | |||
> | |||
<BranchIcon | |||
className="little-spacer-left text-text-top" | |||
/> | |||
master | |||
</span>, | |||
"project": <ForwardRef(Link) | |||
onClick={[Function]} | |||
title="Foo" | |||
to={ | |||
Object { | |||
"pathname": "/dashboard", | |||
"search": "?id=foo&branch=master", | |||
} | |||
} | |||
> | |||
Foo | |||
</ForwardRef(Link)>, | |||
} | |||
} | |||
/> | |||
</div> | |||
</li> | |||
<li | |||
className="display-flex-center spacer-top" | |||
key="bar" | |||
> | |||
<div | |||
className="text-ellipsis" | |||
> | |||
<FormattedMessage | |||
defaultMessage="event.definition_change.removed" | |||
id="event.definition_change.removed" | |||
values={ | |||
Object { | |||
"branch": <span | |||
className="nowrap" | |||
title="master" | |||
> | |||
<BranchIcon | |||
className="little-spacer-left text-text-top" | |||
/> | |||
master | |||
</span>, | |||
"project": <ForwardRef(Link) | |||
onClick={[Function]} | |||
title="Bar" | |||
to={ | |||
Object { | |||
"pathname": "/dashboard", | |||
"search": "?id=bar&branch=master", | |||
} | |||
} | |||
> | |||
Bar | |||
</ForwardRef(Link)>, | |||
} | |||
} | |||
/> | |||
</div> | |||
</li> | |||
</ul> | |||
</Fragment> | |||
`; | |||
exports[`should render for a branch 1`] = ` | |||
<Fragment> | |||
<span | |||
className="note" | |||
> | |||
event.category.DEFINITION_CHANGE | |||
: | |||
</span> | |||
<div> | |||
<ButtonLink | |||
className="project-activity-event-inner-more-link" | |||
onClick={[Function]} | |||
stopPropagation={true} | |||
> | |||
hide | |||
<DropdownIcon | |||
className="little-spacer-left" | |||
turned={true} | |||
/> | |||
</ButtonLink> | |||
</div> | |||
<ul | |||
className="spacer-left spacer-top" | |||
> | |||
<li | |||
className="display-flex-center spacer-top" | |||
key="foo" | |||
> | |||
<div | |||
className="text-ellipsis" | |||
> | |||
<FormattedMessage | |||
defaultMessage="event.definition_change.branch_added" | |||
id="event.definition_change.branch_added" | |||
values={ | |||
Object { | |||
"branch": <span | |||
className="nowrap" | |||
title="feature-x" | |||
> | |||
<BranchIcon | |||
className="little-spacer-left text-text-top" | |||
/> | |||
feature-x | |||
</span>, | |||
"project": <ForwardRef(Link) | |||
onClick={[Function]} | |||
title="Foo" | |||
to={ | |||
Object { | |||
"pathname": "/dashboard", | |||
"search": "?id=foo&branch=feature-x", | |||
} | |||
} | |||
> | |||
Foo | |||
</ForwardRef(Link)>, | |||
} | |||
} | |||
/> | |||
</div> | |||
</li> | |||
<li | |||
className="display-flex-center spacer-top" | |||
key="bar" | |||
> | |||
<FormattedMessage | |||
defaultMessage="event.definition_change.branch_replaced" | |||
id="event.definition_change.branch_replaced" | |||
values={ | |||
Object { | |||
"newBranch": <span | |||
className="nowrap" | |||
title="feature-y" | |||
> | |||
<BranchIcon | |||
className="little-spacer-left text-text-top" | |||
/> | |||
feature-y | |||
</span>, | |||
"oldBranch": <span | |||
className="nowrap" | |||
title="master" | |||
> | |||
<BranchIcon | |||
className="little-spacer-left text-text-top" | |||
/> | |||
master | |||
</span>, | |||
"project": <ForwardRef(Link) | |||
onClick={[Function]} | |||
title="Bar" | |||
to={ | |||
Object { | |||
"pathname": "/dashboard", | |||
"search": "?id=bar&branch=feature-y", | |||
} | |||
} | |||
> | |||
Bar | |||
</ForwardRef(Link)>, | |||
} | |||
} | |||
/> | |||
</li> | |||
</ul> | |||
</Fragment> | |||
`; | |||
exports[`should render when readonly 1`] = ` | |||
<Fragment> | |||
<span | |||
className="note" | |||
> | |||
event.category.DEFINITION_CHANGE | |||
</span> | |||
</Fragment> | |||
`; |
@@ -1,52 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly: default 1`] = ` | |||
<div | |||
className="activity-graph-legends display-flex-center" | |||
> | |||
<div | |||
className="flex-1" | |||
> | |||
<span | |||
className="spacer-left spacer-right" | |||
key="bugs" | |||
> | |||
<GraphsLegendItem | |||
index={0} | |||
metric="bugs" | |||
name="Bugs" | |||
removeMetric={[MockFunction]} | |||
showWarning={false} | |||
/> | |||
</span> | |||
<span | |||
className="spacer-left spacer-right" | |||
key="my_metric" | |||
> | |||
<GraphsLegendItem | |||
index={1} | |||
metric="my_metric" | |||
name="My Metric" | |||
removeMetric={[MockFunction]} | |||
showWarning={false} | |||
/> | |||
</span> | |||
<Tooltip | |||
key="foo" | |||
overlay="project_activity.graphs.custom.metric_no_history" | |||
> | |||
<span | |||
className="spacer-left spacer-right" | |||
> | |||
<GraphsLegendItem | |||
index={2} | |||
metric="foo" | |||
name="Foo" | |||
removeMetric={[MockFunction]} | |||
showWarning={true} | |||
/> | |||
</span> | |||
</Tooltip> | |||
</div> | |||
</div> | |||
`; |
@@ -1,58 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly a legend: default 1`] = ` | |||
<span | |||
className="" | |||
> | |||
<ChartLegendIcon | |||
className="text-middle spacer-right" | |||
index={2} | |||
/> | |||
<span | |||
className="text-middle" | |||
> | |||
Bugs | |||
</span> | |||
</span> | |||
`; | |||
exports[`should render correctly a legend: with legend 1`] = ` | |||
<span | |||
className="activity-graph-legend-actionable myclass" | |||
> | |||
<ChartLegendIcon | |||
className="text-middle spacer-right" | |||
index={1} | |||
/> | |||
<span | |||
className="text-middle" | |||
> | |||
Foo | |||
</span> | |||
<ClearButton | |||
aria-label="project_activity.graphs.custom.remove_metric.Foo" | |||
className="button-tiny spacer-left text-middle" | |||
iconProps={ | |||
Object { | |||
"size": 12, | |||
} | |||
} | |||
onClick={[Function]} | |||
/> | |||
</span> | |||
`; | |||
exports[`should render correctly a legend: with warning 1`] = ` | |||
<span | |||
className="" | |||
> | |||
<AlertWarnIcon | |||
className="spacer-right" | |||
/> | |||
<span | |||
className="text-middle" | |||
> | |||
Bugs | |||
</span> | |||
</span> | |||
`; |
@@ -1,14 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly: default 1`] = ` | |||
<Tooltip | |||
overlay="project_activity.graphs.new_code_long" | |||
> | |||
<span | |||
aria-label="project_activity.graphs.new_code_long" | |||
className="activity-graph-new-code-legend display-flex-center pull-right note" | |||
> | |||
project_activity.graphs.new_code | |||
</span> | |||
</Tooltip> | |||
`; |
@@ -1,22 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly: default 1`] = ` | |||
<div | |||
className="activity-graph-legends" | |||
> | |||
<GraphsLegendItem | |||
className="big-spacer-left big-spacer-right" | |||
index={0} | |||
key="bugs" | |||
metric="bugs" | |||
name="Bugs" | |||
/> | |||
<GraphsLegendItem | |||
className="big-spacer-left big-spacer-right" | |||
index={1} | |||
key="code_smells" | |||
metric="code_smells" | |||
name="Code Smells" | |||
/> | |||
</div> | |||
`; |
@@ -1,226 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should not add separators if not needed 1`] = ` | |||
<Popup | |||
className="disabled-pointer-events" | |||
placement="left-top" | |||
style={ | |||
Object { | |||
"left": 476, | |||
"top": 30, | |||
"width": 250, | |||
} | |||
} | |||
> | |||
<div | |||
className="activity-graph-tooltip" | |||
> | |||
<div | |||
className="activity-graph-tooltip-title spacer-bottom" | |||
> | |||
<DateTimeFormatter | |||
date={2011-10-01T22:01:00.000Z} | |||
/> | |||
</div> | |||
<table | |||
className="width-100" | |||
> | |||
<tbody /> | |||
<GraphsTooltipsContentCoverage | |||
addSeparator={false} | |||
measuresHistory={Array []} | |||
tooltipIdx={0} | |||
/> | |||
</table> | |||
</div> | |||
</Popup> | |||
`; | |||
exports[`should render correctly for issues graphs 1`] = ` | |||
<Popup | |||
className="disabled-pointer-events" | |||
placement="left-top" | |||
style={ | |||
Object { | |||
"left": 476, | |||
"top": 30, | |||
"width": 250, | |||
} | |||
} | |||
> | |||
<div | |||
className="activity-graph-tooltip" | |||
> | |||
<div | |||
className="activity-graph-tooltip-title spacer-bottom" | |||
> | |||
<DateTimeFormatter | |||
date={2011-10-01T22:01:00.000Z} | |||
/> | |||
</div> | |||
<table | |||
className="width-100" | |||
> | |||
<tbody> | |||
<GraphsTooltipsContentIssues | |||
index={0} | |||
key="bugs" | |||
measuresHistory={Array []} | |||
name="bugs" | |||
tooltipIdx={0} | |||
translatedName="Bugs" | |||
value="Formated.3" | |||
/> | |||
<GraphsTooltipsContentIssues | |||
index={1} | |||
key="code_smells" | |||
measuresHistory={Array []} | |||
name="code_smells" | |||
tooltipIdx={0} | |||
translatedName="Code Smells" | |||
value="Formated.18" | |||
/> | |||
<GraphsTooltipsContentIssues | |||
index={2} | |||
key="vulnerabilities" | |||
measuresHistory={Array []} | |||
name="vulnerabilities" | |||
tooltipIdx={0} | |||
translatedName="Vulnerabilities" | |||
value="Formated.0" | |||
/> | |||
</tbody> | |||
</table> | |||
</div> | |||
</Popup> | |||
`; | |||
exports[`should render correctly for issues graphs: with events 1`] = ` | |||
<Popup | |||
className="disabled-pointer-events" | |||
placement="left-top" | |||
style={ | |||
Object { | |||
"left": 476, | |||
"top": 30, | |||
"width": 250, | |||
} | |||
} | |||
> | |||
<div | |||
className="activity-graph-tooltip" | |||
> | |||
<div | |||
className="activity-graph-tooltip-title spacer-bottom" | |||
> | |||
<DateTimeFormatter | |||
date={2011-10-01T22:01:00.000Z} | |||
/> | |||
</div> | |||
<table | |||
className="width-100" | |||
> | |||
<GraphsTooltipsContentEvents | |||
addSeparator={true} | |||
events={ | |||
Array [ | |||
Object { | |||
"currentTarget": Object { | |||
"blur": [Function], | |||
}, | |||
"preventDefault": [Function], | |||
"stopImmediatePropagation": [Function], | |||
"stopPropagation": [Function], | |||
"target": Object { | |||
"blur": [Function], | |||
}, | |||
}, | |||
] | |||
} | |||
/> | |||
<tbody> | |||
<GraphsTooltipsContentIssues | |||
index={0} | |||
key="bugs" | |||
measuresHistory={Array []} | |||
name="bugs" | |||
tooltipIdx={0} | |||
translatedName="Bugs" | |||
value="Formated.3" | |||
/> | |||
<GraphsTooltipsContentIssues | |||
index={1} | |||
key="code_smells" | |||
measuresHistory={Array []} | |||
name="code_smells" | |||
tooltipIdx={0} | |||
translatedName="Code Smells" | |||
value="Formated.18" | |||
/> | |||
<GraphsTooltipsContentIssues | |||
index={2} | |||
key="vulnerabilities" | |||
measuresHistory={Array []} | |||
name="vulnerabilities" | |||
tooltipIdx={0} | |||
translatedName="Vulnerabilities" | |||
value="Formated.0" | |||
/> | |||
</tbody> | |||
</table> | |||
</div> | |||
</Popup> | |||
`; | |||
exports[`should render correctly for random graphs 1`] = ` | |||
<Popup | |||
className="disabled-pointer-events" | |||
placement="left-top" | |||
style={ | |||
Object { | |||
"left": 476, | |||
"top": 30, | |||
"width": 250, | |||
} | |||
} | |||
> | |||
<div | |||
className="activity-graph-tooltip" | |||
> | |||
<div | |||
className="activity-graph-tooltip-title spacer-bottom" | |||
> | |||
<DateTimeFormatter | |||
date={2011-10-25T10:27:41.000Z} | |||
/> | |||
</div> | |||
<table | |||
className="width-100" | |||
> | |||
<tbody> | |||
<GraphsTooltipsContent | |||
index={0} | |||
key="bugs" | |||
name="bugs" | |||
translatedName="Bugs" | |||
value="Formated.0" | |||
/> | |||
<GraphsTooltipsContent | |||
index={1} | |||
key="code_smells" | |||
name="code_smells" | |||
translatedName="Code Smells" | |||
value="Formated.15" | |||
/> | |||
<GraphsTooltipsContent | |||
index={2} | |||
key="vulnerabilities" | |||
name="vulnerabilities" | |||
translatedName="Vulnerabilities" | |||
value="Formated.1" | |||
/> | |||
</tbody> | |||
</table> | |||
</div> | |||
</Popup> | |||
`; |
@@ -1,25 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly 1`] = ` | |||
<tr | |||
className="activity-graph-tooltip-line" | |||
key="code_smells" | |||
> | |||
<td | |||
className="thin" | |||
> | |||
<ChartLegendIcon | |||
className="spacer-right" | |||
index={1} | |||
/> | |||
</td> | |||
<td | |||
className="activity-graph-tooltip-value text-right spacer-right thin" | |||
> | |||
1.2k | |||
</td> | |||
<td> | |||
Code Smells | |||
</td> | |||
</tr> | |||
`; |
@@ -1,71 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly: default 1`] = ` | |||
<tbody> | |||
<tr | |||
className="activity-graph-tooltip-line" | |||
> | |||
<td | |||
className="activity-graph-tooltip-value text-right spacer-right thin" | |||
colSpan={2} | |||
> | |||
10short_number_suffix.k | |||
</td> | |||
<td> | |||
metric.uncovered_lines.name | |||
</td> | |||
</tr> | |||
<tr | |||
className="activity-graph-tooltip-line" | |||
> | |||
<td | |||
className="activity-graph-tooltip-value text-right spacer-right thin" | |||
colSpan={2} | |||
> | |||
80.3% | |||
</td> | |||
<td> | |||
metric.coverage.name | |||
</td> | |||
</tr> | |||
</tbody> | |||
`; | |||
exports[`should render correctly: with separator 1`] = ` | |||
<tbody> | |||
<tr> | |||
<td | |||
className="activity-graph-tooltip-separator" | |||
colSpan={3} | |||
> | |||
<hr /> | |||
</td> | |||
</tr> | |||
<tr | |||
className="activity-graph-tooltip-line" | |||
> | |||
<td | |||
className="activity-graph-tooltip-value text-right spacer-right thin" | |||
colSpan={2} | |||
> | |||
10short_number_suffix.k | |||
</td> | |||
<td> | |||
metric.uncovered_lines.name | |||
</td> | |||
</tr> | |||
<tr | |||
className="activity-graph-tooltip-line" | |||
> | |||
<td | |||
className="activity-graph-tooltip-value text-right spacer-right thin" | |||
colSpan={2} | |||
> | |||
80.3% | |||
</td> | |||
<td> | |||
metric.coverage.name | |||
</td> | |||
</tr> | |||
</tbody> | |||
`; |
@@ -1,45 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly: default 1`] = ` | |||
<tbody> | |||
<tr | |||
className="activity-graph-tooltip-line" | |||
> | |||
<td | |||
className="activity-graph-tooltip-value text-right spacer-right thin" | |||
colSpan={2} | |||
> | |||
10,245.0% | |||
</td> | |||
<td> | |||
metric.duplicated_lines_density.name | |||
</td> | |||
</tr> | |||
</tbody> | |||
`; | |||
exports[`should render correctly: with separator 1`] = ` | |||
<tbody> | |||
<tr> | |||
<td | |||
className="activity-graph-tooltip-separator" | |||
colSpan={3} | |||
> | |||
<hr /> | |||
</td> | |||
</tr> | |||
<tr | |||
className="activity-graph-tooltip-line" | |||
> | |||
<td | |||
className="activity-graph-tooltip-value text-right spacer-right thin" | |||
colSpan={2} | |||
> | |||
10,245.0% | |||
</td> | |||
<td> | |||
metric.duplicated_lines_density.name | |||
</td> | |||
</tr> | |||
</tbody> | |||
`; |
@@ -1,60 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly 1`] = ` | |||
<tbody> | |||
<tr> | |||
<td | |||
className="activity-graph-tooltip-separator" | |||
colSpan={3} | |||
> | |||
<hr /> | |||
</td> | |||
</tr> | |||
<tr | |||
className="activity-graph-tooltip-line" | |||
> | |||
<td | |||
colSpan={3} | |||
> | |||
<div | |||
className="little-spacer-bottom" | |||
key="1" | |||
> | |||
<EventInner | |||
event={ | |||
Object { | |||
"category": "VERSION", | |||
"key": "1", | |||
"name": "6.5", | |||
} | |||
} | |||
readonly={true} | |||
/> | |||
</div> | |||
<div | |||
className="little-spacer-bottom" | |||
key="2" | |||
> | |||
<EventInner | |||
event={ | |||
Object { | |||
"category": "OTHER", | |||
"key": "2", | |||
"name": "Foo", | |||
} | |||
} | |||
readonly={true} | |||
/> | |||
</div> | |||
</td> | |||
</tr> | |||
<tr> | |||
<td | |||
className="activity-graph-tooltip-separator" | |||
colSpan={3} | |||
> | |||
<hr /> | |||
</td> | |||
</tr> | |||
</tbody> | |||
`; |
@@ -1,34 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly: default 1`] = ` | |||
<tr | |||
className="activity-graph-tooltip-issues-line" | |||
key="bugs" | |||
> | |||
<td | |||
className="thin" | |||
> | |||
<ChartLegendIcon | |||
className="spacer-right" | |||
index={2} | |||
/> | |||
</td> | |||
<td | |||
className="text-right spacer-right" | |||
> | |||
<span | |||
className="activity-graph-tooltip-value" | |||
> | |||
1.2k | |||
</span> | |||
<Rating | |||
className="spacer-left" | |||
small={true} | |||
value="5.0" | |||
/> | |||
</td> | |||
<td> | |||
Bugs | |||
</td> | |||
</tr> | |||
`; |
@@ -1,139 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render 1`] = ` | |||
<Fragment> | |||
<span | |||
className="note spacer-right" | |||
> | |||
event.category.QUALITY_GATE | |||
: | |||
</span> | |||
<FormattedMessage | |||
defaultMessage="event.quality_gate.still_x" | |||
id="event.quality_gate.still_x" | |||
values={ | |||
Object { | |||
"status": <Level | |||
level="ERROR" | |||
small={true} | |||
/>, | |||
} | |||
} | |||
/> | |||
<div> | |||
<ResetButtonLink | |||
className="project-activity-event-inner-more-link" | |||
onClick={[Function]} | |||
stopPropagation={true} | |||
> | |||
more | |||
<DropdownIcon | |||
className="little-spacer-left" | |||
turned={false} | |||
/> | |||
</ResetButtonLink> | |||
</div> | |||
</Fragment> | |||
`; | |||
exports[`should render 2`] = ` | |||
<Fragment> | |||
<span | |||
className="note spacer-right" | |||
> | |||
event.category.QUALITY_GATE | |||
: | |||
</span> | |||
<FormattedMessage | |||
defaultMessage="event.quality_gate.still_x" | |||
id="event.quality_gate.still_x" | |||
values={ | |||
Object { | |||
"status": <Level | |||
level="ERROR" | |||
small={true} | |||
/>, | |||
} | |||
} | |||
/> | |||
<div> | |||
<ResetButtonLink | |||
className="project-activity-event-inner-more-link" | |||
onClick={[Function]} | |||
stopPropagation={true} | |||
> | |||
hide | |||
<DropdownIcon | |||
className="little-spacer-left" | |||
turned={true} | |||
/> | |||
</ResetButtonLink> | |||
</div> | |||
<ul | |||
className="spacer-left spacer-top" | |||
> | |||
<li | |||
className="display-flex-center spacer-top" | |||
key="foo" | |||
> | |||
<Level | |||
aria-label="quality_gates.status" | |||
className="spacer-right" | |||
level="ERROR" | |||
small={true} | |||
/> | |||
<div | |||
className="flex-1 text-ellipsis" | |||
> | |||
<ForwardRef(Link) | |||
onClick={[Function]} | |||
title="Foo" | |||
to={ | |||
Object { | |||
"pathname": "/dashboard", | |||
"search": "?id=foo&branch=master", | |||
} | |||
} | |||
> | |||
<span | |||
aria-label="project_x.Foo" | |||
> | |||
Foo | |||
</span> | |||
</ForwardRef(Link)> | |||
</div> | |||
</li> | |||
<li | |||
className="display-flex-center spacer-top" | |||
key="bar" | |||
> | |||
<Level | |||
aria-label="quality_gates.status" | |||
className="spacer-right" | |||
level="ERROR" | |||
small={true} | |||
/> | |||
<div | |||
className="flex-1 text-ellipsis" | |||
> | |||
<ForwardRef(Link) | |||
onClick={[Function]} | |||
title="Bar" | |||
to={ | |||
Object { | |||
"pathname": "/dashboard", | |||
"search": "?id=bar&branch=master", | |||
} | |||
} | |||
> | |||
<span | |||
aria-label="project_x.Bar" | |||
> | |||
Bar | |||
</span> | |||
</ForwardRef(Link)> | |||
</div> | |||
</li> | |||
</ul> | |||
</Fragment> | |||
`; |
@@ -54,9 +54,9 @@ | |||
} | |||
.activity-graph-legends { | |||
flex-grow: 0; | |||
display: flex; | |||
justify-content: center; | |||
padding-bottom: calc(2 * var(--gridSize)); | |||
text-align: center; | |||
} | |||
.activity-graph-legend-actionable { |
@@ -1546,8 +1546,6 @@ project_activity.new_code_period_start.help=The analysis before this mark is the | |||
project_activity.graphs.choose_type=Choose graph type | |||
project_activity.graphs.explanation_x=This interactive graph shows data for the following project measures over time: {0} | |||
project_activity.graphs.new_code=New Code | |||
project_activity.graphs.new_code_long=New Code is indicated in yellow on the graph. | |||
project_activity.graphs.issues=Issues | |||
project_activity.graphs.coverage=Coverage | |||
project_activity.graphs.duplications=Duplications |