@@ -22,7 +22,7 @@ import * as React from 'react'; | |||
import { getQualityGateProjectStatus } from '../../../../api/quality-gates'; | |||
import { mockBranch } from '../../../../helpers/mocks/branch-like'; | |||
import { waitAndUpdate } from '../../../../helpers/testUtils'; | |||
import { QualityGateProjectStatus } from '../../../../types/quality-gates'; | |||
import { BranchStatusData } from '../../../../types/branch-like'; | |||
import BranchStatusContextProvider from '../BranchStatusContextProvider'; | |||
jest.mock('../../../../api/quality-gates', () => ({ | |||
@@ -33,7 +33,7 @@ describe('fetchBranchStatus', () => { | |||
it('should get the branch status', async () => { | |||
const projectKey = 'projectKey'; | |||
const branchName = 'branch-6.7'; | |||
const status: QualityGateProjectStatus = { | |||
const status: BranchStatusData = { | |||
status: 'OK', | |||
conditions: [], | |||
ignoredConditions: false, |
@@ -186,12 +186,13 @@ export default class BranchOverview extends React.PureComponent<Props, State> { | |||
(results) => { | |||
if (this.mounted) { | |||
const qgStatuses = results.map(({ measures = [], project, projectBranchLike }) => { | |||
const { key, name, status } = project; | |||
const { key, name, status, isCaycCompliant } = project; | |||
const conditions = extractStatusConditionsFromApplicationStatusChildProject(project); | |||
const failedConditions = this.getFailedConditions(conditions, measures); | |||
return { | |||
failedConditions, | |||
isCaycCompliant, | |||
key, | |||
name, | |||
status, | |||
@@ -238,12 +239,13 @@ export default class BranchOverview extends React.PureComponent<Props, State> { | |||
this.loadMeasuresAndMeta(key, branch, metricKeys).then( | |||
({ measures, metrics, period }) => { | |||
if (this.mounted && measures) { | |||
const { ignoredConditions, status } = projectStatus; | |||
const { ignoredConditions, isCaycCompliant, status } = projectStatus; | |||
const conditions = extractStatusConditionsFromProjectStatus(projectStatus); | |||
const failedConditions = this.getFailedConditions(conditions, measures); | |||
const qgStatus = { | |||
ignoredConditions, | |||
isCaycCompliant, | |||
failedConditions, | |||
key, | |||
name, |
@@ -53,7 +53,7 @@ export interface BranchOverviewRendererProps { | |||
qgStatuses?: QualityGateStatus[]; | |||
} | |||
export function BranchOverviewRenderer(props: BranchOverviewRendererProps) { | |||
export default function BranchOverviewRenderer(props: BranchOverviewRendererProps) { | |||
const { | |||
analyses, | |||
appLeak, | |||
@@ -131,5 +131,3 @@ export function BranchOverviewRenderer(props: BranchOverviewRendererProps) { | |||
</> | |||
); | |||
} | |||
export default React.memo(BranchOverviewRenderer); |
@@ -0,0 +1,40 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2023 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 Link from '../../../components/common/Link'; | |||
import { Alert } from '../../../components/ui/Alert'; | |||
import { translate } from '../../../helpers/l10n'; | |||
export default function CleanAsYouCodeWarning() { | |||
return ( | |||
<> | |||
<Alert variant="warning">{translate('overview.quality_gate.conditions.cayc.warning')}</Alert> | |||
<p className="big-spacer-top big-spacer-bottom"> | |||
{translate('overview.quality_gate.conditions.cayc.details')} | |||
</p> | |||
<Link | |||
target="_blank" | |||
to="https://docs.sonarqube.org/latest/user-guide/clean-as-you-code/#quality-gate" | |||
> | |||
{translate('overview.quality_gate.conditions.cayc.link')} | |||
</Link> | |||
</> | |||
); | |||
} |
@@ -24,6 +24,7 @@ import HelpTooltip from '../../../components/controls/HelpTooltip'; | |||
import { Alert } from '../../../components/ui/Alert'; | |||
import DeferredSpinner from '../../../components/ui/DeferredSpinner'; | |||
import { translate, translateWithParameters } from '../../../helpers/l10n'; | |||
import { ComponentQualifier } from '../../../types/component'; | |||
import { QualityGateStatus } from '../../../types/quality-gates'; | |||
import { Component } from '../../../types/types'; | |||
import SonarLintPromotion from '../components/SonarLintPromotion'; | |||
@@ -51,8 +52,7 @@ export function QualityGatePanel(props: QualityGatePanelProps) { | |||
); | |||
const showIgnoredConditionWarning = | |||
component.qualifier === 'TRK' && | |||
qgStatuses !== undefined && | |||
component.qualifier === ComponentQualifier.Project && | |||
qgStatuses.some((p) => Boolean(p.ignoredConditions)); | |||
return ( | |||
@@ -82,7 +82,7 @@ export function QualityGatePanel(props: QualityGatePanelProps) { | |||
</Alert> | |||
)} | |||
<div className="overview-panel-content"> | |||
<div> | |||
{loading ? ( | |||
<div className="overview-panel-big-padded"> | |||
<DeferredSpinner loading={loading} /> | |||
@@ -107,7 +107,8 @@ export function QualityGatePanel(props: QualityGatePanelProps) { | |||
</span> | |||
</div> | |||
{overallFailedConditionsCount > 0 && ( | |||
{(overallFailedConditionsCount > 0 || | |||
qgStatuses.some(({ isCaycCompliant }) => !isCaycCompliant)) && ( | |||
<div data-test="overview__quality-gate-conditions"> | |||
{qgStatuses.map((qgStatus) => ( | |||
<QualityGatePanelSection |
@@ -18,13 +18,22 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { ButtonPlain } from '../../../components/controls/buttons'; | |||
import ChevronDownIcon from '../../../components/icons/ChevronDownIcon'; | |||
import ChevronRightIcon from '../../../components/icons/ChevronRightIcon'; | |||
import { Alert } from '../../../components/ui/Alert'; | |||
import { translate, translateWithParameters } from '../../../helpers/l10n'; | |||
import { isDiffMetric } from '../../../helpers/measures'; | |||
import { BranchLike } from '../../../types/branch-like'; | |||
import { ComponentQualifier } from '../../../types/component'; | |||
import { QualityGateStatus } from '../../../types/quality-gates'; | |||
import { | |||
QualityGateStatus, | |||
QualityGateStatusConditionEnhanced, | |||
} from '../../../types/quality-gates'; | |||
import { Component } from '../../../types/types'; | |||
import QualityGateConditions from '../components/QualityGateConditions'; | |||
import { CAYC_METRICS } from '../utils'; | |||
import CleanAsYouCodeWarning from './CleanAsYouCodeWarning'; | |||
export interface QualityGatePanelSectionProps { | |||
branchLike?: BranchLike; | |||
@@ -32,46 +41,161 @@ export interface QualityGatePanelSectionProps { | |||
qgStatus: QualityGateStatus; | |||
} | |||
function splitConditions(conditions: QualityGateStatusConditionEnhanced[]) { | |||
const caycConditions = []; | |||
const newCodeFailedConditions = []; | |||
const overallFailedConditions = []; | |||
for (const condition of conditions) { | |||
if (CAYC_METRICS.includes(condition.metric)) { | |||
caycConditions.push(condition); | |||
} else if (isDiffMetric(condition.metric)) { | |||
newCodeFailedConditions.push(condition); | |||
} else { | |||
overallFailedConditions.push(condition); | |||
} | |||
} | |||
return [caycConditions, newCodeFailedConditions, overallFailedConditions]; | |||
} | |||
function displayConditions(conditions: number) { | |||
if (conditions === 0) { | |||
return null; | |||
} | |||
const text = | |||
conditions === 1 | |||
? translate('overview.1_condition_failed') | |||
: translateWithParameters('overview.X_conditions_failed', conditions); | |||
return <span className="text-muted big-spacer-left">{text}</span>; | |||
} | |||
export function QualityGatePanelSection(props: QualityGatePanelSectionProps) { | |||
const { component, qgStatus } = props; | |||
const newCodeFailedConditions = qgStatus.failedConditions.filter((c) => isDiffMetric(c.metric)); | |||
const overallFailedConditions = qgStatus.failedConditions.filter((c) => !isDiffMetric(c.metric)); | |||
const [collapsed, setCollapsed] = React.useState(false); | |||
const toggle = React.useCallback(() => { | |||
setCollapsed(!collapsed); | |||
}, [collapsed]); | |||
if (newCodeFailedConditions.length === 0 && overallFailedConditions.length === 0) { | |||
if (qgStatus.failedConditions.length === 0 && qgStatus.isCaycCompliant) { | |||
return null; | |||
} | |||
const [caycConditions, newCodeFailedConditions, overallFailedConditions] = splitConditions( | |||
qgStatus.failedConditions | |||
); | |||
/* | |||
* Show Clean as You Code if: | |||
* - The QG is not CAYC-compliant | |||
* - There are *any* failing conditions, we either show: | |||
* - the cayc-specific failures | |||
* - that cayc is passing and only other conditions are failing | |||
*/ | |||
const showCayc = !qgStatus.isCaycCompliant || qgStatus.failedConditions.length > 0; | |||
const showSuccessfulCayc = caycConditions.length === 0 && qgStatus.isCaycCompliant; | |||
const hasOtherConditions = newCodeFailedConditions.length + overallFailedConditions.length > 0; | |||
const showName = component.qualifier === ComponentQualifier.Application; | |||
const toggleLabel = collapsed | |||
? translateWithParameters('overview.quality_gate.show_project_conditions_x', qgStatus.name) | |||
: translateWithParameters('overview.quality_gate.hide_project_conditions_x', qgStatus.name); | |||
return ( | |||
<div className="overview-quality-gate-conditions"> | |||
{showName && ( | |||
<h3 className="overview-quality-gate-conditions-project-name">{qgStatus.name}</h3> | |||
<ButtonPlain | |||
aria-label={toggleLabel} | |||
aria-expanded={!collapsed} | |||
className="width-100 text-left" | |||
onClick={toggle} | |||
> | |||
<div className="display-flex-center"> | |||
<h3 | |||
className="overview-quality-gate-conditions-project-name text-ellipsis" | |||
title={qgStatus.name} | |||
> | |||
{collapsed ? <ChevronRightIcon /> : <ChevronDownIcon />} | |||
<span className="spacer-left">{qgStatus.name}</span> | |||
</h3> | |||
{collapsed && displayConditions(qgStatus.failedConditions.length)} | |||
</div> | |||
</ButtonPlain> | |||
)} | |||
{newCodeFailedConditions.length > 0 && ( | |||
{!collapsed && ( | |||
<> | |||
<h4 className="overview-quality-gate-conditions-section-title"> | |||
{translate('quality_gates.conditions.new_code')} | |||
</h4> | |||
<QualityGateConditions | |||
component={qgStatus} | |||
branchLike={qgStatus.branchLike} | |||
failedConditions={newCodeFailedConditions} | |||
/> | |||
</> | |||
)} | |||
{showCayc && ( | |||
<> | |||
<div className="display-flex-center overview-quality-gate-conditions-section-title"> | |||
<h4 className="padded">{translate('quality_gates.conditions.cayc')}</h4> | |||
{displayConditions(caycConditions.length)} | |||
</div> | |||
{overallFailedConditions.length > 0 && ( | |||
<> | |||
<h4 className="overview-quality-gate-conditions-section-title"> | |||
{translate('quality_gates.conditions.overall_code')} | |||
</h4> | |||
<QualityGateConditions | |||
component={qgStatus} | |||
branchLike={qgStatus.branchLike} | |||
failedConditions={overallFailedConditions} | |||
/> | |||
{!qgStatus.isCaycCompliant && ( | |||
<div className="big-padded bordered-bottom overview-quality-gate-conditions-list"> | |||
<CleanAsYouCodeWarning /> | |||
</div> | |||
)} | |||
{showSuccessfulCayc && ( | |||
<div className="big-padded bordered-bottom overview-quality-gate-conditions-list"> | |||
<Alert variant="success" className="no-margin-bottom"> | |||
{translate('overview.quality_gate.conditions.cayc.passed')} | |||
</Alert> | |||
</div> | |||
)} | |||
{caycConditions.length > 0 && ( | |||
<QualityGateConditions | |||
component={qgStatus} | |||
branchLike={qgStatus.branchLike} | |||
failedConditions={caycConditions} | |||
/> | |||
)} | |||
</> | |||
)} | |||
{hasOtherConditions && ( | |||
<> | |||
<div className="display-flex-center overview-quality-gate-conditions-section-title"> | |||
<h4 className="padded">{translate('quality_gates.conditions.other_conditions')}</h4> | |||
{displayConditions(newCodeFailedConditions.length + overallFailedConditions.length)} | |||
</div> | |||
{newCodeFailedConditions.length > 0 && ( | |||
<> | |||
<h5 className="big-padded overview-quality-gate-conditions-subsection-title"> | |||
{translate('quality_gates.conditions.new_code')} | |||
</h5> | |||
<QualityGateConditions | |||
component={qgStatus} | |||
branchLike={qgStatus.branchLike} | |||
failedConditions={newCodeFailedConditions} | |||
/> | |||
</> | |||
)} | |||
{overallFailedConditions.length > 0 && ( | |||
<> | |||
<h5 className="big-padded overview-quality-gate-conditions-subsection-title"> | |||
{translate('quality_gates.conditions.overall_code')} | |||
</h5> | |||
<QualityGateConditions | |||
component={qgStatus} | |||
branchLike={qgStatus.branchLike} | |||
failedConditions={overallFailedConditions} | |||
/> | |||
</> | |||
)} | |||
</> | |||
)} | |||
</> | |||
)} | |||
</div> |
@@ -17,33 +17,27 @@ | |||
* 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 { screen } from '@testing-library/react'; | |||
import userEvent from '@testing-library/user-event'; | |||
import * as React from 'react'; | |||
import { getApplicationDetails, getApplicationLeak } from '../../../../api/application'; | |||
import selectEvent from 'react-select-event'; | |||
import { getMeasuresWithPeriodAndMetrics } from '../../../../api/measures'; | |||
import { getProjectActivity } from '../../../../api/projectActivity'; | |||
import { | |||
getApplicationQualityGate, | |||
getQualityGateProjectStatus, | |||
} from '../../../../api/quality-gates'; | |||
import { getAllTimeMachineData } from '../../../../api/time-machine'; | |||
import { getQualityGateProjectStatus } from '../../../../api/quality-gates'; | |||
import CurrentUserContextProvider from '../../../../app/components/current-user/CurrentUserContextProvider'; | |||
import { getActivityGraph, saveActivityGraph } from '../../../../components/activity-graph/utils'; | |||
import { isDiffMetric } from '../../../../helpers/measures'; | |||
import { mockBranch, mockMainBranch } from '../../../../helpers/mocks/branch-like'; | |||
import { mockMainBranch } from '../../../../helpers/mocks/branch-like'; | |||
import { mockComponent } from '../../../../helpers/mocks/component'; | |||
import { mockAnalysis } from '../../../../helpers/mocks/project-activity'; | |||
import { waitAndUpdate } from '../../../../helpers/testUtils'; | |||
import { mockQualityGateProjectStatus } from '../../../../helpers/mocks/quality-gates'; | |||
import { mockLoggedInUser, mockPeriod } from '../../../../helpers/testMocks'; | |||
import { renderComponent } from '../../../../helpers/testReactTestingUtils'; | |||
import { ComponentQualifier } from '../../../../types/component'; | |||
import { MetricKey } from '../../../../types/metrics'; | |||
import { GraphType } from '../../../../types/project-activity'; | |||
import { Measure, Metric } from '../../../../types/types'; | |||
import BranchOverview, { BRANCH_OVERVIEW_ACTIVITY_GRAPH, NO_CI_DETECTED } from '../BranchOverview'; | |||
import BranchOverviewRenderer from '../BranchOverviewRenderer'; | |||
jest.mock('../../../../helpers/dates', () => ({ | |||
parseDate: jest.fn((date) => `PARSED:${date}`), | |||
toNotSoISOString: jest.fn((date) => date), | |||
})); | |||
jest.mock('../../../../api/measures', () => { | |||
const { mockMeasure, mockMetric } = jest.requireActual('../../../../helpers/testMocks'); | |||
@@ -96,8 +90,8 @@ jest.mock('../../../../api/quality-gates', () => { | |||
{ | |||
actualValue: '2', | |||
comparator: 'GT', | |||
errorThreshold: '1.0', | |||
metricKey: MetricKey.new_bugs, | |||
errorThreshold: '1', | |||
metricKey: MetricKey.new_reliability_rating, | |||
periodIndex: 1, | |||
status: 'ERROR', | |||
}, | |||
@@ -184,7 +178,9 @@ jest.mock('../../../../api/application', () => ({ | |||
jest.mock('../../../../components/activity-graph/utils', () => { | |||
const { MetricKey } = jest.requireActual('../../../../types/metrics'); | |||
const { GraphType } = jest.requireActual('../../../../types/project-activity'); | |||
const original = jest.requireActual('../../../../components/activity-graph/utils'); | |||
return { | |||
...original, | |||
getActivityGraph: jest.fn(() => ({ graph: GraphType.coverage })), | |||
saveActivityGraph: jest.fn(), | |||
getHistoryMetrics: jest.fn(() => [MetricKey.lines_to_cover, MetricKey.uncovered_lines]), | |||
@@ -194,57 +190,85 @@ jest.mock('../../../../components/activity-graph/utils', () => { | |||
beforeEach(jest.clearAllMocks); | |||
describe('project overview', () => { | |||
it('should render correctly', async () => { | |||
const wrapper = shallowRender(); | |||
await waitAndUpdate(wrapper); | |||
expect(wrapper).toMatchSnapshot(); | |||
it('should show a successful QG', async () => { | |||
const user = userEvent.setup(); | |||
jest | |||
.mocked(getQualityGateProjectStatus) | |||
.mockResolvedValueOnce(mockQualityGateProjectStatus({ status: 'OK' })); | |||
renderBranchOverview(); | |||
// QG panel | |||
expect(await screen.findByText('metric.level.OK')).toBeInTheDocument(); | |||
expect(screen.getByText('overview.quality_gate_all_conditions_passed')).toBeInTheDocument(); | |||
expect( | |||
screen.queryByText('overview.quality_gate.conditions.cayc.warning') | |||
).not.toBeInTheDocument(); | |||
//Measures panel | |||
expect(screen.getByText('metric.new_vulnerabilities.name')).toBeInTheDocument(); | |||
// go to overall | |||
await user.click(screen.getByText('overview.overall_code')); | |||
expect(screen.getByText('metric.vulnerabilities.name')).toBeInTheDocument(); | |||
}); | |||
it('should show a successful non-compliant QG', async () => { | |||
jest | |||
.mocked(getQualityGateProjectStatus) | |||
.mockResolvedValueOnce( | |||
mockQualityGateProjectStatus({ status: 'OK', isCaycCompliant: false }) | |||
); | |||
renderBranchOverview(); | |||
expect(await screen.findByText('metric.level.OK')).toBeInTheDocument(); | |||
expect(screen.getByText('overview.quality_gate.conditions.cayc.warning')).toBeInTheDocument(); | |||
}); | |||
it('should show a failed QG', async () => { | |||
renderBranchOverview(); | |||
expect(await screen.findByText('metric.level.ERROR')).toBeInTheDocument(); | |||
expect(screen.getByText('overview.X_conditions_failed.2')).toBeInTheDocument(); | |||
expect( | |||
screen.queryByText('overview.quality_gate.conditions.cayc.passed') | |||
).not.toBeInTheDocument(); | |||
}); | |||
it("should correctly load a project's status", async () => { | |||
const wrapper = shallowRender(); | |||
await waitAndUpdate(wrapper); | |||
expect(getQualityGateProjectStatus).toHaveBeenCalled(); | |||
expect(getMeasuresWithPeriodAndMetrics).toHaveBeenCalled(); | |||
// Check the conditions got correctly enhanced with measure meta data. | |||
const { qgStatuses } = wrapper.state(); | |||
expect(qgStatuses).toHaveLength(1); | |||
const [qgStatus] = qgStatuses!; | |||
expect(qgStatus).toEqual( | |||
expect.objectContaining({ | |||
name: 'Foo', | |||
key: 'foo', | |||
it('should show a failed QG with passing CAYC conditions', async () => { | |||
jest.mocked(getQualityGateProjectStatus).mockResolvedValueOnce( | |||
mockQualityGateProjectStatus({ | |||
status: 'ERROR', | |||
conditions: [ | |||
{ | |||
actualValue: '12', | |||
comparator: 'GT', | |||
errorThreshold: '10', | |||
metricKey: MetricKey.new_bugs, | |||
periodIndex: 1, | |||
status: 'ERROR', | |||
}, | |||
], | |||
}) | |||
); | |||
renderBranchOverview(); | |||
const { failedConditions } = qgStatus; | |||
expect(failedConditions).toHaveLength(2); | |||
expect(failedConditions[0]).toMatchObject({ | |||
actual: '2', | |||
level: 'ERROR', | |||
metric: MetricKey.new_bugs, | |||
measure: expect.objectContaining({ | |||
metric: expect.objectContaining({ key: MetricKey.new_bugs }), | |||
}), | |||
}); | |||
expect(failedConditions[1]).toMatchObject({ | |||
actual: '5', | |||
level: 'ERROR', | |||
metric: MetricKey.bugs, | |||
measure: expect.objectContaining({ | |||
metric: expect.objectContaining({ key: MetricKey.bugs }), | |||
}), | |||
}); | |||
expect(await screen.findByText('metric.level.ERROR')).toBeInTheDocument(); | |||
expect(screen.getByText('overview.quality_gate.conditions.cayc.passed')).toBeInTheDocument(); | |||
}); | |||
it('should correctly flag a project as empty', async () => { | |||
(getMeasuresWithPeriodAndMetrics as jest.Mock).mockResolvedValueOnce({ component: {} }); | |||
it('should correctly show a project as empty', async () => { | |||
jest.mocked(getMeasuresWithPeriodAndMetrics).mockResolvedValueOnce({ | |||
component: { key: '', name: '', qualifier: ComponentQualifier.Project, measures: [] }, | |||
metrics: [], | |||
period: mockPeriod(), | |||
}); | |||
renderBranchOverview(); | |||
const wrapper = shallowRender(); | |||
await waitAndUpdate(wrapper); | |||
expect(wrapper.find(BranchOverviewRenderer).props().projectIsEmpty).toBe(true); | |||
expect(await screen.findByText('overview.project.main_branch_empty')).toBeInTheDocument(); | |||
}); | |||
}); | |||
@@ -254,106 +278,27 @@ describe('application overview', () => { | |||
qualifier: ComponentQualifier.Application, | |||
}); | |||
it('should render correctly', async () => { | |||
const wrapper = shallowRender({ component }); | |||
await waitAndUpdate(wrapper); | |||
expect(wrapper).toMatchSnapshot(); | |||
}); | |||
it('should fetch correctly other branch', async () => { | |||
const wrapper = shallowRender({ branch: mockBranch(), component }); | |||
await waitAndUpdate(wrapper); | |||
expect(getApplicationDetails).toHaveBeenCalled(); | |||
expect(wrapper).toMatchSnapshot(); | |||
it('should show failed conditions for every project', async () => { | |||
renderBranchOverview({ component }); | |||
expect(await screen.findByText('Foo')).toBeInTheDocument(); | |||
expect(screen.getByText('Bar')).toBeInTheDocument(); | |||
}); | |||
it("should correctly load an application's status", async () => { | |||
const wrapper = shallowRender({ component }); | |||
await waitAndUpdate(wrapper); | |||
expect(getApplicationQualityGate).toHaveBeenCalled(); | |||
expect(getApplicationLeak).toHaveBeenCalled(); | |||
expect(getMeasuresWithPeriodAndMetrics).toHaveBeenCalled(); | |||
// Check the conditions got correctly enhanced with measure meta data. | |||
const { qgStatuses } = wrapper.state(); | |||
expect(qgStatuses).toHaveLength(2); | |||
const [qgStatus1, qgStatus2] = qgStatuses!; | |||
expect(qgStatus1).toEqual( | |||
expect.objectContaining({ | |||
name: 'Foo', | |||
key: 'foo', | |||
status: 'ERROR', | |||
}) | |||
); | |||
const { failedConditions: failedConditions1 } = qgStatus1; | |||
expect(failedConditions1).toHaveLength(2); | |||
expect(failedConditions1[0]).toMatchObject({ | |||
actual: '10', | |||
level: 'ERROR', | |||
metric: MetricKey.coverage, | |||
measure: expect.objectContaining({ | |||
metric: expect.objectContaining({ key: MetricKey.coverage }), | |||
}), | |||
}); | |||
expect(failedConditions1[1]).toMatchObject({ | |||
actual: '5', | |||
level: 'ERROR', | |||
metric: MetricKey.new_bugs, | |||
measure: expect.objectContaining({ | |||
metric: expect.objectContaining({ key: MetricKey.new_bugs }), | |||
}), | |||
it('should correctly show an app as empty', async () => { | |||
jest.mocked(getMeasuresWithPeriodAndMetrics).mockResolvedValueOnce({ | |||
component: { key: '', name: '', qualifier: ComponentQualifier.Application, measures: [] }, | |||
metrics: [], | |||
period: mockPeriod(), | |||
}); | |||
expect(qgStatus1).toEqual( | |||
expect.objectContaining({ | |||
name: 'Foo', | |||
key: 'foo', | |||
status: 'ERROR', | |||
}) | |||
); | |||
renderBranchOverview({ component }); | |||
const { failedConditions: failedConditions2 } = qgStatus2; | |||
expect(failedConditions2).toHaveLength(1); | |||
expect(failedConditions2[0]).toMatchObject({ | |||
actual: '15', | |||
level: 'ERROR', | |||
metric: MetricKey.new_bugs, | |||
measure: expect.objectContaining({ | |||
metric: expect.objectContaining({ key: MetricKey.new_bugs }), | |||
}), | |||
}); | |||
}); | |||
it('should correctly flag an application as empty', async () => { | |||
(getMeasuresWithPeriodAndMetrics as jest.Mock).mockResolvedValueOnce({ component: {} }); | |||
const wrapper = shallowRender({ component }); | |||
await waitAndUpdate(wrapper); | |||
expect(wrapper.find(BranchOverviewRenderer).props().projectIsEmpty).toBe(true); | |||
expect(await screen.findByText('portfolio.app.empty')).toBeInTheDocument(); | |||
}); | |||
}); | |||
it("should correctly load a component's history", async () => { | |||
const wrapper = shallowRender(); | |||
await waitAndUpdate(wrapper); | |||
expect(getProjectActivity).toHaveBeenCalled(); | |||
expect(getAllTimeMachineData).toHaveBeenCalled(); | |||
const { measuresHistory } = wrapper.state(); | |||
expect(measuresHistory).toHaveLength(6); | |||
expect(measuresHistory![0]).toEqual( | |||
expect.objectContaining({ | |||
metric: MetricKey.bugs, | |||
history: [{ date: 'PARSED:2019-01-05', value: '2.0' }], | |||
}) | |||
); | |||
}); | |||
it.each([ | |||
['no analysis', [], undefined], | |||
['no analysis', [], true], | |||
['1 analysis, no CI data', [mockAnalysis()], false], | |||
['1 analysis, no CI detected', [mockAnalysis({ detectedCI: NO_CI_DETECTED })], false], | |||
['1 analysis, CI detected', [mockAnalysis({ detectedCI: 'Cirrus CI' })], true], | |||
@@ -362,36 +307,41 @@ it.each([ | |||
async (_, analyses, expected) => { | |||
(getProjectActivity as jest.Mock).mockResolvedValueOnce({ analyses }); | |||
const wrapper = shallowRender(); | |||
await waitAndUpdate(wrapper); | |||
expect(wrapper.state().detectedCIOnLastAnalysis).toBe(expected); | |||
renderBranchOverview(); | |||
// wait for loading | |||
await screen.findByText('overview.quality_gate'); | |||
expect(screen.queryByText('overview.project.next_steps.set_up_ci') === null).toBe(expected); | |||
} | |||
); | |||
it('should correctly handle graph type storage', () => { | |||
const wrapper = shallowRender(); | |||
it('should correctly handle graph type storage', async () => { | |||
renderBranchOverview(); | |||
expect(getActivityGraph).toHaveBeenCalledWith(BRANCH_OVERVIEW_ACTIVITY_GRAPH, 'foo'); | |||
expect(wrapper.state().graph).toBe(GraphType.coverage); | |||
wrapper.instance().handleGraphChange(GraphType.issues); | |||
const select = await screen.findByLabelText('project_activity.graphs.choose_type'); | |||
await selectEvent.select(select, `project_activity.graphs.${GraphType.issues}`); | |||
expect(saveActivityGraph).toHaveBeenCalledWith( | |||
BRANCH_OVERVIEW_ACTIVITY_GRAPH, | |||
'foo', | |||
GraphType.issues | |||
); | |||
expect(wrapper.state().graph).toBe(GraphType.issues); | |||
}); | |||
function shallowRender(props: Partial<BranchOverview['props']> = {}) { | |||
return shallow<BranchOverview>( | |||
<BranchOverview | |||
branch={mockMainBranch()} | |||
component={mockComponent({ | |||
breadcrumbs: [mockComponent({ key: 'foo' })], | |||
key: 'foo', | |||
name: 'Foo', | |||
})} | |||
{...props} | |||
/> | |||
function renderBranchOverview(props: Partial<BranchOverview['props']> = {}) { | |||
renderComponent( | |||
<CurrentUserContextProvider currentUser={mockLoggedInUser()}> | |||
<BranchOverview | |||
branch={mockMainBranch()} | |||
component={mockComponent({ | |||
breadcrumbs: [mockComponent({ key: 'foo' })], | |||
key: 'foo', | |||
name: 'Foo', | |||
})} | |||
{...props} | |||
/> | |||
</CurrentUserContextProvider> | |||
); | |||
} |
@@ -1,47 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2023 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 { mockMainBranch } from '../../../../helpers/mocks/branch-like'; | |||
import { mockComponent } from '../../../../helpers/mocks/component'; | |||
import { mockMeasureEnhanced } from '../../../../helpers/testMocks'; | |||
import { GraphType } from '../../../../types/project-activity'; | |||
import { BranchOverviewRenderer, BranchOverviewRendererProps } from '../BranchOverviewRenderer'; | |||
it('should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot('default'); | |||
expect(shallowRender({ projectIsEmpty: true })).toMatchSnapshot('empty project'); | |||
expect(shallowRender({ loadingHistory: true, loadingStatus: true })).toMatchSnapshot('loading'); | |||
}); | |||
function shallowRender(props: Partial<BranchOverviewRendererProps> = {}) { | |||
return shallow( | |||
<BranchOverviewRenderer | |||
branch={mockMainBranch()} | |||
component={mockComponent()} | |||
graph={GraphType.issues} | |||
loadingHistory={false} | |||
loadingStatus={false} | |||
measures={[mockMeasureEnhanced()]} | |||
onGraphChange={jest.fn()} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -17,22 +17,48 @@ | |||
* 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 { screen } from '@testing-library/react'; | |||
import * as React from 'react'; | |||
import { mockMainBranch } from '../../../../helpers/mocks/branch-like'; | |||
import { mockComponent } from '../../../../helpers/mocks/component'; | |||
import { mockMeasureEnhanced, mockMetric } from '../../../../helpers/testMocks'; | |||
import { renderComponent } from '../../../../helpers/testReactTestingUtils'; | |||
import { MetricKey } from '../../../../types/metrics'; | |||
import { DebtValue, DebtValueProps } from '../DebtValue'; | |||
it('should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot(); | |||
expect(shallowRender({ useDiffMetric: true })).toMatchSnapshot(); | |||
expect(shallowRender({ measures: [] })).toMatchSnapshot(); | |||
renderDebtValue(); | |||
expect( | |||
screen.getByLabelText( | |||
'overview.see_more_details_on_x_of_y.work_duration.x_minutes.1.sqale_index' | |||
) | |||
).toBeInTheDocument(); | |||
expect(screen.getByText('sqale_index')).toBeInTheDocument(); | |||
}); | |||
it('should render diff metric correctly', () => { | |||
renderDebtValue({ useDiffMetric: true }); | |||
expect( | |||
screen.getByLabelText( | |||
'overview.see_more_details_on_x_of_y.work_duration.x_minutes.1.new_technical_debt' | |||
) | |||
).toBeInTheDocument(); | |||
expect(screen.getByText('new_technical_debt')).toBeInTheDocument(); | |||
}); | |||
it('should handle missing measure', () => { | |||
renderDebtValue({ measures: [] }); | |||
expect(screen.getByLabelText('no_data')).toBeInTheDocument(); | |||
expect(screen.getByText('metric.sqale_index.name')).toBeInTheDocument(); | |||
}); | |||
function shallowRender(props: Partial<DebtValueProps> = {}) { | |||
return shallow( | |||
function renderDebtValue(props: Partial<DebtValueProps> = {}) { | |||
return renderComponent( | |||
<DebtValue | |||
branchLike={mockMainBranch()} | |||
component={mockComponent()} |
@@ -1,63 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2023 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 { mockMainBranch } from '../../../../helpers/mocks/branch-like'; | |||
import { mockComponent } from '../../../../helpers/mocks/component'; | |||
import { mockMeasureEnhanced, mockMetric } from '../../../../helpers/testMocks'; | |||
import { ComponentQualifier } from '../../../../types/component'; | |||
import { IssueType } from '../../../../types/issues'; | |||
import { MetricKey } from '../../../../types/metrics'; | |||
import MeasuresPanelIssueMeasureRow, { | |||
MeasuresPanelIssueMeasureRowProps, | |||
} from '../MeasuresPanelIssueMeasureRow'; | |||
it('should render correctly for projects', () => { | |||
expect(shallowRender({ type: IssueType.Bug })).toMatchSnapshot('Bug'); | |||
expect(shallowRender({ type: IssueType.CodeSmell })).toMatchSnapshot('Code Smell'); | |||
expect(shallowRender({ type: IssueType.SecurityHotspot })).toMatchSnapshot('Hotspot'); | |||
expect(shallowRender({ type: IssueType.Vulnerability })).toMatchSnapshot('Vulnerabilty'); | |||
expect(shallowRender({ isNewCodeTab: false })).toMatchSnapshot('Overview'); | |||
}); | |||
it('should render correctly for apps', () => { | |||
const app = mockComponent({ qualifier: ComponentQualifier.Application }); | |||
expect(shallowRender({ component: app })).toMatchSnapshot('new code'); | |||
expect(shallowRender({ component: app, isNewCodeTab: false })).toMatchSnapshot('overview'); | |||
}); | |||
function shallowRender(props: Partial<MeasuresPanelIssueMeasureRowProps> = {}) { | |||
return shallow<MeasuresPanelIssueMeasureRowProps>( | |||
<MeasuresPanelIssueMeasureRow | |||
branchLike={mockMainBranch()} | |||
component={mockComponent()} | |||
isNewCodeTab={true} | |||
measures={[ | |||
mockMeasureEnhanced({ metric: mockMetric({ key: MetricKey.coverage }) }), | |||
mockMeasureEnhanced({ metric: mockMetric({ key: MetricKey.new_coverage }) }), | |||
mockMeasureEnhanced({ metric: mockMetric({ key: MetricKey.bugs }) }), | |||
mockMeasureEnhanced({ metric: mockMetric({ key: MetricKey.new_bugs }) }), | |||
]} | |||
type={IssueType.Bug} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -1,110 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2023 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 { mockMainBranch } from '../../../../helpers/mocks/branch-like'; | |||
import { mockComponent } from '../../../../helpers/mocks/component'; | |||
import { mockPeriod } from '../../../../helpers/testMocks'; | |||
import { ComponentQualifier } from '../../../../types/component'; | |||
import MeasuresPanelNoNewCode, { MeasuresPanelNoNewCodeProps } from '../MeasuresPanelNoNewCode'; | |||
it('should render the default message', () => { | |||
const defaultMessage = ` | |||
<div | |||
className="display-flex-center display-flex-justify-center" | |||
style={ | |||
{ | |||
"height": 500, | |||
} | |||
} | |||
> | |||
<img | |||
alt="" | |||
className="spacer-right" | |||
height={52} | |||
src="/images/source-code.svg" | |||
/> | |||
<div | |||
className="big-spacer-left text-muted" | |||
style={ | |||
{ | |||
"maxWidth": 500, | |||
} | |||
} | |||
> | |||
<p | |||
className="spacer-bottom big-spacer-top big" | |||
> | |||
overview.measures.empty_explanation | |||
</p> | |||
<p> | |||
<FormattedMessage | |||
defaultMessage="overview.measures.empty_link" | |||
id="overview.measures.empty_link" | |||
values={ | |||
{ | |||
"learn_more_link": <withAppStateContext(DocLink) | |||
to="/user-guide/clean-as-you-code/" | |||
> | |||
learn_more | |||
</withAppStateContext(DocLink)>, | |||
} | |||
} | |||
/> | |||
</p> | |||
</div> | |||
</div> | |||
`; | |||
expect(shallowRender()).toMatchInlineSnapshot(defaultMessage); | |||
expect( | |||
shallowRender({ component: mockComponent({ qualifier: ComponentQualifier.Application }) }) | |||
).toMatchInlineSnapshot(defaultMessage); | |||
expect( | |||
shallowRender({ period: mockPeriod({ date: '2018-05-23', mode: 'REFERENCE_BRANCH' }) }) | |||
).toMatchInlineSnapshot(defaultMessage); | |||
expect( | |||
shallowRender({ period: mockPeriod({ date: '2018-05-23', mode: 'PREVIOUS_VERSION' }) }) | |||
).toMatchInlineSnapshot(defaultMessage); | |||
expect( | |||
shallowRender({ | |||
period: mockPeriod({ date: undefined, mode: 'REFERENCE_BRANCH', parameter: 'master' }), | |||
}) | |||
).toMatchSnapshot(); | |||
expect( | |||
shallowRender({ | |||
period: mockPeriod({ date: undefined, mode: 'REFERENCE_BRANCH', parameter: 'notsame' }), | |||
}) | |||
).toMatchSnapshot(); | |||
}); | |||
it('should render "bad code setting" explanation', () => { | |||
const period = mockPeriod({ date: undefined, mode: 'REFERENCE_BRANCH' }); | |||
expect(shallowRender({ period })).toMatchSnapshot('no link'); | |||
expect( | |||
shallowRender({ component: mockComponent({ configuration: { showSettings: true } }), period }) | |||
).toMatchSnapshot('with link'); | |||
}); | |||
function shallowRender(props: Partial<MeasuresPanelNoNewCodeProps> = {}) { | |||
return shallow<MeasuresPanelNoNewCodeProps>( | |||
<MeasuresPanelNoNewCode branch={mockMainBranch()} component={mockComponent()} {...props} /> | |||
); | |||
} |
@@ -1,46 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2023 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 { mockMeasureEnhanced, mockMetric } from '../../../../helpers/testMocks'; | |||
import { MetricKey } from '../../../../types/metrics'; | |||
import SecurityHotspotsReviewed, { | |||
SecurityHotspotsReviewedProps, | |||
} from '../SecurityHotspotsReviewed'; | |||
it('should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot(); | |||
expect(shallowRender({ useDiffMetric: true })).toMatchSnapshot('on new code'); | |||
expect(shallowRender({ measures: [] })).toMatchSnapshot('no measures'); | |||
}); | |||
function shallowRender(props: Partial<SecurityHotspotsReviewedProps> = {}) { | |||
return shallow( | |||
<SecurityHotspotsReviewed | |||
measures={[ | |||
mockMeasureEnhanced({ metric: mockMetric({ key: MetricKey.security_hotspots_reviewed }) }), | |||
mockMeasureEnhanced({ | |||
metric: mockMetric({ key: MetricKey.new_security_hotspots_reviewed }), | |||
}), | |||
]} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -1,435 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly: default 1`] = ` | |||
<Fragment> | |||
<withCurrentUserContext(FirstAnalysisNextStepsNotif) | |||
component={ | |||
{ | |||
"breadcrumbs": [], | |||
"key": "my-project", | |||
"name": "MyProject", | |||
"qualifier": "TRK", | |||
"qualityGate": { | |||
"isDefault": true, | |||
"key": "30", | |||
"name": "Sonar way", | |||
}, | |||
"qualityProfiles": [ | |||
{ | |||
"deleted": false, | |||
"key": "my-qp", | |||
"language": "ts", | |||
"name": "Sonar way", | |||
}, | |||
], | |||
"tags": [], | |||
} | |||
} | |||
/> | |||
<div | |||
className="page page-limited" | |||
> | |||
<div | |||
className="overview" | |||
> | |||
<A11ySkipTarget | |||
anchor="overview_main" | |||
/> | |||
<div | |||
className="display-flex-row" | |||
> | |||
<div | |||
className="width-25 big-spacer-right" | |||
> | |||
<Memo(QualityGatePanel) | |||
component={ | |||
{ | |||
"breadcrumbs": [], | |||
"key": "my-project", | |||
"name": "MyProject", | |||
"qualifier": "TRK", | |||
"qualityGate": { | |||
"isDefault": true, | |||
"key": "30", | |||
"name": "Sonar way", | |||
}, | |||
"qualityProfiles": [ | |||
{ | |||
"deleted": false, | |||
"key": "my-qp", | |||
"language": "ts", | |||
"name": "Sonar way", | |||
}, | |||
], | |||
"tags": [], | |||
} | |||
} | |||
loading={false} | |||
/> | |||
</div> | |||
<div | |||
className="flex-1" | |||
> | |||
<div | |||
className="display-flex-column" | |||
> | |||
<withRouter(Component) | |||
branch={ | |||
{ | |||
"analysisDate": "2018-01-01", | |||
"excludedFromPurge": true, | |||
"isMain": true, | |||
"name": "master", | |||
} | |||
} | |||
component={ | |||
{ | |||
"breadcrumbs": [], | |||
"key": "my-project", | |||
"name": "MyProject", | |||
"qualifier": "TRK", | |||
"qualityGate": { | |||
"isDefault": true, | |||
"key": "30", | |||
"name": "Sonar way", | |||
}, | |||
"qualityProfiles": [ | |||
{ | |||
"deleted": false, | |||
"key": "my-qp", | |||
"language": "ts", | |||
"name": "Sonar way", | |||
}, | |||
], | |||
"tags": [], | |||
} | |||
} | |||
loading={false} | |||
measures={ | |||
[ | |||
{ | |||
"bestValue": true, | |||
"leak": "1", | |||
"metric": { | |||
"id": "coverage", | |||
"key": "coverage", | |||
"name": "coverage", | |||
"type": "PERCENT", | |||
}, | |||
"period": { | |||
"bestValue": true, | |||
"index": 1, | |||
"value": "1.0", | |||
}, | |||
"value": "1.0", | |||
}, | |||
] | |||
} | |||
/> | |||
<Memo(ActivityPanel) | |||
branchLike={ | |||
{ | |||
"analysisDate": "2018-01-01", | |||
"excludedFromPurge": true, | |||
"isMain": true, | |||
"name": "master", | |||
} | |||
} | |||
component={ | |||
{ | |||
"breadcrumbs": [], | |||
"key": "my-project", | |||
"name": "MyProject", | |||
"qualifier": "TRK", | |||
"qualityGate": { | |||
"isDefault": true, | |||
"key": "30", | |||
"name": "Sonar way", | |||
}, | |||
"qualityProfiles": [ | |||
{ | |||
"deleted": false, | |||
"key": "my-qp", | |||
"language": "ts", | |||
"name": "Sonar way", | |||
}, | |||
], | |||
"tags": [], | |||
} | |||
} | |||
graph="issues" | |||
loading={false} | |||
measuresHistory={[]} | |||
metrics={[]} | |||
onGraphChange={[MockFunction]} | |||
/> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</Fragment> | |||
`; | |||
exports[`should render correctly: empty project 1`] = ` | |||
<Fragment> | |||
<withCurrentUserContext(FirstAnalysisNextStepsNotif) | |||
component={ | |||
{ | |||
"breadcrumbs": [], | |||
"key": "my-project", | |||
"name": "MyProject", | |||
"qualifier": "TRK", | |||
"qualityGate": { | |||
"isDefault": true, | |||
"key": "30", | |||
"name": "Sonar way", | |||
}, | |||
"qualityProfiles": [ | |||
{ | |||
"deleted": false, | |||
"key": "my-qp", | |||
"language": "ts", | |||
"name": "Sonar way", | |||
}, | |||
], | |||
"tags": [], | |||
} | |||
} | |||
/> | |||
<div | |||
className="page page-limited" | |||
> | |||
<div | |||
className="overview" | |||
> | |||
<A11ySkipTarget | |||
anchor="overview_main" | |||
/> | |||
<Memo(NoCodeWarning) | |||
branchLike={ | |||
{ | |||
"analysisDate": "2018-01-01", | |||
"excludedFromPurge": true, | |||
"isMain": true, | |||
"name": "master", | |||
} | |||
} | |||
component={ | |||
{ | |||
"breadcrumbs": [], | |||
"key": "my-project", | |||
"name": "MyProject", | |||
"qualifier": "TRK", | |||
"qualityGate": { | |||
"isDefault": true, | |||
"key": "30", | |||
"name": "Sonar way", | |||
}, | |||
"qualityProfiles": [ | |||
{ | |||
"deleted": false, | |||
"key": "my-qp", | |||
"language": "ts", | |||
"name": "Sonar way", | |||
}, | |||
], | |||
"tags": [], | |||
} | |||
} | |||
measures={ | |||
[ | |||
{ | |||
"bestValue": true, | |||
"leak": "1", | |||
"metric": { | |||
"id": "coverage", | |||
"key": "coverage", | |||
"name": "coverage", | |||
"type": "PERCENT", | |||
}, | |||
"period": { | |||
"bestValue": true, | |||
"index": 1, | |||
"value": "1.0", | |||
}, | |||
"value": "1.0", | |||
}, | |||
] | |||
} | |||
/> | |||
</div> | |||
</div> | |||
</Fragment> | |||
`; | |||
exports[`should render correctly: loading 1`] = ` | |||
<Fragment> | |||
<withCurrentUserContext(FirstAnalysisNextStepsNotif) | |||
component={ | |||
{ | |||
"breadcrumbs": [], | |||
"key": "my-project", | |||
"name": "MyProject", | |||
"qualifier": "TRK", | |||
"qualityGate": { | |||
"isDefault": true, | |||
"key": "30", | |||
"name": "Sonar way", | |||
}, | |||
"qualityProfiles": [ | |||
{ | |||
"deleted": false, | |||
"key": "my-qp", | |||
"language": "ts", | |||
"name": "Sonar way", | |||
}, | |||
], | |||
"tags": [], | |||
} | |||
} | |||
/> | |||
<div | |||
className="page page-limited" | |||
> | |||
<div | |||
className="overview" | |||
> | |||
<A11ySkipTarget | |||
anchor="overview_main" | |||
/> | |||
<div | |||
className="display-flex-row" | |||
> | |||
<div | |||
className="width-25 big-spacer-right" | |||
> | |||
<Memo(QualityGatePanel) | |||
component={ | |||
{ | |||
"breadcrumbs": [], | |||
"key": "my-project", | |||
"name": "MyProject", | |||
"qualifier": "TRK", | |||
"qualityGate": { | |||
"isDefault": true, | |||
"key": "30", | |||
"name": "Sonar way", | |||
}, | |||
"qualityProfiles": [ | |||
{ | |||
"deleted": false, | |||
"key": "my-qp", | |||
"language": "ts", | |||
"name": "Sonar way", | |||
}, | |||
], | |||
"tags": [], | |||
} | |||
} | |||
loading={true} | |||
/> | |||
</div> | |||
<div | |||
className="flex-1" | |||
> | |||
<div | |||
className="display-flex-column" | |||
> | |||
<withRouter(Component) | |||
branch={ | |||
{ | |||
"analysisDate": "2018-01-01", | |||
"excludedFromPurge": true, | |||
"isMain": true, | |||
"name": "master", | |||
} | |||
} | |||
component={ | |||
{ | |||
"breadcrumbs": [], | |||
"key": "my-project", | |||
"name": "MyProject", | |||
"qualifier": "TRK", | |||
"qualityGate": { | |||
"isDefault": true, | |||
"key": "30", | |||
"name": "Sonar way", | |||
}, | |||
"qualityProfiles": [ | |||
{ | |||
"deleted": false, | |||
"key": "my-qp", | |||
"language": "ts", | |||
"name": "Sonar way", | |||
}, | |||
], | |||
"tags": [], | |||
} | |||
} | |||
loading={true} | |||
measures={ | |||
[ | |||
{ | |||
"bestValue": true, | |||
"leak": "1", | |||
"metric": { | |||
"id": "coverage", | |||
"key": "coverage", | |||
"name": "coverage", | |||
"type": "PERCENT", | |||
}, | |||
"period": { | |||
"bestValue": true, | |||
"index": 1, | |||
"value": "1.0", | |||
}, | |||
"value": "1.0", | |||
}, | |||
] | |||
} | |||
/> | |||
<Memo(ActivityPanel) | |||
branchLike={ | |||
{ | |||
"analysisDate": "2018-01-01", | |||
"excludedFromPurge": true, | |||
"isMain": true, | |||
"name": "master", | |||
} | |||
} | |||
component={ | |||
{ | |||
"breadcrumbs": [], | |||
"key": "my-project", | |||
"name": "MyProject", | |||
"qualifier": "TRK", | |||
"qualityGate": { | |||
"isDefault": true, | |||
"key": "30", | |||
"name": "Sonar way", | |||
}, | |||
"qualityProfiles": [ | |||
{ | |||
"deleted": false, | |||
"key": "my-qp", | |||
"language": "ts", | |||
"name": "Sonar way", | |||
}, | |||
], | |||
"tags": [], | |||
} | |||
} | |||
graph="issues" | |||
loading={true} | |||
measuresHistory={[]} | |||
metrics={[]} | |||
onGraphChange={[MockFunction]} | |||
/> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</Fragment> | |||
`; |
@@ -1,67 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly 1`] = ` | |||
<Fragment> | |||
<DrilldownLink | |||
ariaLabel="overview.see_more_details_on_x_of_y.work_duration.x_minutes.1.sqale_index" | |||
branchLike={ | |||
{ | |||
"analysisDate": "2018-01-01", | |||
"excludedFromPurge": true, | |||
"isMain": true, | |||
"name": "master", | |||
} | |||
} | |||
className="overview-measures-value text-light" | |||
component="my-project" | |||
metric="sqale_index" | |||
> | |||
work_duration.x_minutes.1 | |||
</DrilldownLink> | |||
<span | |||
className="big-spacer-left" | |||
> | |||
sqale_index | |||
</span> | |||
</Fragment> | |||
`; | |||
exports[`should render correctly 2`] = ` | |||
<Fragment> | |||
<DrilldownLink | |||
ariaLabel="overview.see_more_details_on_x_of_y.work_duration.x_minutes.1.new_technical_debt" | |||
branchLike={ | |||
{ | |||
"analysisDate": "2018-01-01", | |||
"excludedFromPurge": true, | |||
"isMain": true, | |||
"name": "master", | |||
} | |||
} | |||
className="overview-measures-value text-light" | |||
component="my-project" | |||
metric="new_technical_debt" | |||
> | |||
work_duration.x_minutes.1 | |||
</DrilldownLink> | |||
<span | |||
className="big-spacer-left" | |||
> | |||
new_technical_debt | |||
</span> | |||
</Fragment> | |||
`; | |||
exports[`should render correctly 3`] = ` | |||
<Fragment> | |||
<span | |||
aria-label="no_data" | |||
className="overview-measures-empty-value" | |||
/> | |||
<span | |||
className="big-spacer-left" | |||
> | |||
metric.sqale_index.name | |||
</span> | |||
</Fragment> | |||
`; |
@@ -1,174 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render "bad code setting" explanation: no link 1`] = ` | |||
<div | |||
className="display-flex-center display-flex-justify-center" | |||
style={ | |||
{ | |||
"height": 500, | |||
} | |||
} | |||
> | |||
<img | |||
alt="" | |||
className="spacer-right" | |||
height={52} | |||
src="/images/source-code.svg" | |||
/> | |||
<div | |||
className="big-spacer-left text-muted" | |||
style={ | |||
{ | |||
"maxWidth": 500, | |||
} | |||
} | |||
> | |||
<p | |||
className="spacer-bottom big-spacer-top big" | |||
> | |||
overview.measures.bad_reference.explanation | |||
</p> | |||
<p> | |||
<FormattedMessage | |||
defaultMessage="overview.measures.empty_link" | |||
id="overview.measures.empty_link" | |||
values={ | |||
{ | |||
"learn_more_link": <withAppStateContext(DocLink) | |||
to="/user-guide/clean-as-you-code/" | |||
> | |||
learn_more | |||
</withAppStateContext(DocLink)>, | |||
} | |||
} | |||
/> | |||
</p> | |||
</div> | |||
</div> | |||
`; | |||
exports[`should render "bad code setting" explanation: with link 1`] = ` | |||
<div | |||
className="display-flex-center display-flex-justify-center" | |||
style={ | |||
{ | |||
"height": 500, | |||
} | |||
} | |||
> | |||
<img | |||
alt="" | |||
className="spacer-right" | |||
height={52} | |||
src="/images/source-code.svg" | |||
/> | |||
<div | |||
className="big-spacer-left text-muted" | |||
style={ | |||
{ | |||
"maxWidth": 500, | |||
} | |||
} | |||
> | |||
<p | |||
className="spacer-bottom big-spacer-top big" | |||
> | |||
overview.measures.bad_reference.explanation | |||
</p> | |||
<p> | |||
<FormattedMessage | |||
defaultMessage="overview.measures.empty_link" | |||
id="overview.measures.empty_link" | |||
values={ | |||
{ | |||
"learn_more_link": <withAppStateContext(DocLink) | |||
to="/user-guide/clean-as-you-code/" | |||
> | |||
learn_more | |||
</withAppStateContext(DocLink)>, | |||
} | |||
} | |||
/> | |||
</p> | |||
</div> | |||
</div> | |||
`; | |||
exports[`should render the default message 5`] = ` | |||
<div | |||
className="display-flex-center display-flex-justify-center" | |||
style={ | |||
{ | |||
"height": 500, | |||
} | |||
} | |||
> | |||
<img | |||
alt="" | |||
className="spacer-right" | |||
height={52} | |||
src="/images/source-code.svg" | |||
/> | |||
<div | |||
className="big-spacer-left text-muted" | |||
style={ | |||
{ | |||
"maxWidth": 500, | |||
} | |||
} | |||
> | |||
<p | |||
className="spacer-bottom big-spacer-top big" | |||
> | |||
overview.measures.same_reference.explanation | |||
</p> | |||
</div> | |||
</div> | |||
`; | |||
exports[`should render the default message 6`] = ` | |||
<div | |||
className="display-flex-center display-flex-justify-center" | |||
style={ | |||
{ | |||
"height": 500, | |||
} | |||
} | |||
> | |||
<img | |||
alt="" | |||
className="spacer-right" | |||
height={52} | |||
src="/images/source-code.svg" | |||
/> | |||
<div | |||
className="big-spacer-left text-muted" | |||
style={ | |||
{ | |||
"maxWidth": 500, | |||
} | |||
} | |||
> | |||
<p | |||
className="spacer-bottom big-spacer-top big" | |||
> | |||
overview.measures.bad_reference.explanation | |||
</p> | |||
<p> | |||
<FormattedMessage | |||
defaultMessage="overview.measures.empty_link" | |||
id="overview.measures.empty_link" | |||
values={ | |||
{ | |||
"learn_more_link": <withAppStateContext(DocLink) | |||
to="/user-guide/clean-as-you-code/" | |||
> | |||
learn_more | |||
</withAppStateContext(DocLink)>, | |||
} | |||
} | |||
/> | |||
</p> | |||
</div> | |||
</div> | |||
`; |
@@ -25,9 +25,7 @@ exports[`should render correctly for applications 1`] = ` | |||
} | |||
/> | |||
</div> | |||
<div | |||
className="overview-panel-content" | |||
> | |||
<div> | |||
<div | |||
className="overview-quality-gate-badge-large failed" | |||
> | |||
@@ -97,6 +95,7 @@ exports[`should render correctly for applications 1`] = ` | |||
}, | |||
], | |||
"ignoredConditions": false, | |||
"isCaycCompliant": true, | |||
"key": "foo", | |||
"name": "Foo", | |||
"status": "ERROR", | |||
@@ -178,6 +177,7 @@ exports[`should render correctly for applications 1`] = ` | |||
}, | |||
], | |||
"ignoredConditions": false, | |||
"isCaycCompliant": true, | |||
"key": "foo", | |||
"name": "Foo", | |||
"status": "ERROR", | |||
@@ -289,9 +289,7 @@ exports[`should render correctly for applications 2`] = ` | |||
} | |||
/> | |||
</div> | |||
<div | |||
className="overview-panel-content" | |||
> | |||
<div> | |||
<div | |||
className="overview-quality-gate-badge-large failed" | |||
> | |||
@@ -361,6 +359,7 @@ exports[`should render correctly for applications 2`] = ` | |||
}, | |||
], | |||
"ignoredConditions": false, | |||
"isCaycCompliant": true, | |||
"key": "foo", | |||
"name": "Foo", | |||
"status": "ERROR", | |||
@@ -395,6 +394,7 @@ exports[`should render correctly for applications 2`] = ` | |||
{ | |||
"failedConditions": [], | |||
"ignoredConditions": false, | |||
"isCaycCompliant": true, | |||
"key": "foo", | |||
"name": "Foo", | |||
"status": "OK", | |||
@@ -460,9 +460,7 @@ exports[`should render correctly for projects 1`] = ` | |||
} | |||
/> | |||
</div> | |||
<div | |||
className="overview-panel-content" | |||
> | |||
<div> | |||
<div | |||
className="overview-quality-gate-badge-large failed" | |||
> | |||
@@ -532,6 +530,7 @@ exports[`should render correctly for projects 1`] = ` | |||
}, | |||
], | |||
"ignoredConditions": false, | |||
"isCaycCompliant": true, | |||
"key": "foo", | |||
"name": "Foo", | |||
"status": "ERROR", | |||
@@ -597,9 +596,7 @@ exports[`should render correctly for projects 2`] = ` | |||
} | |||
/> | |||
</div> | |||
<div | |||
className="overview-panel-content" | |||
> | |||
<div> | |||
<div | |||
className="overview-quality-gate-badge-large success" | |||
> | |||
@@ -661,9 +658,7 @@ exports[`should render correctly for projects 3`] = ` | |||
overlay="overview.quality_gate.ignored_conditions.tooltip" | |||
/> | |||
</Alert> | |||
<div | |||
className="overview-panel-content" | |||
> | |||
<div> | |||
<div | |||
className="overview-quality-gate-badge-large failed" | |||
> | |||
@@ -733,6 +728,7 @@ exports[`should render correctly for projects 3`] = ` | |||
}, | |||
], | |||
"ignoredConditions": true, | |||
"isCaycCompliant": true, | |||
"key": "foo", | |||
"name": "Foo", | |||
"status": "ERROR", |
@@ -4,11 +4,44 @@ exports[`should render correctly 1`] = ` | |||
<div | |||
className="overview-quality-gate-conditions" | |||
> | |||
<h4 | |||
className="overview-quality-gate-conditions-section-title" | |||
<div | |||
className="display-flex-center overview-quality-gate-conditions-section-title" | |||
> | |||
<h4 | |||
className="padded" | |||
> | |||
quality_gates.conditions.cayc | |||
</h4> | |||
</div> | |||
<div | |||
className="big-padded bordered-bottom overview-quality-gate-conditions-list" | |||
> | |||
<Alert | |||
className="no-margin-bottom" | |||
variant="success" | |||
> | |||
overview.quality_gate.conditions.cayc.passed | |||
</Alert> | |||
</div> | |||
<div | |||
className="display-flex-center overview-quality-gate-conditions-section-title" | |||
> | |||
<h4 | |||
className="padded" | |||
> | |||
quality_gates.conditions.other_conditions | |||
</h4> | |||
<span | |||
className="text-muted big-spacer-left" | |||
> | |||
overview.X_conditions_failed.2 | |||
</span> | |||
</div> | |||
<h5 | |||
className="big-padded overview-quality-gate-conditions-subsection-title" | |||
> | |||
quality_gates.conditions.new_code | |||
</h4> | |||
</h5> | |||
<Memo(QualityGateConditions) | |||
component={ | |||
{ | |||
@@ -61,6 +94,7 @@ exports[`should render correctly 1`] = ` | |||
}, | |||
], | |||
"ignoredConditions": false, | |||
"isCaycCompliant": true, | |||
"key": "foo", | |||
"name": "Foo", | |||
"status": "ERROR", | |||
@@ -94,11 +128,11 @@ exports[`should render correctly 1`] = ` | |||
] | |||
} | |||
/> | |||
<h4 | |||
className="overview-quality-gate-conditions-section-title" | |||
<h5 | |||
className="big-padded overview-quality-gate-conditions-subsection-title" | |||
> | |||
quality_gates.conditions.overall_code | |||
</h4> | |||
</h5> | |||
<Memo(QualityGateConditions) | |||
component={ | |||
{ | |||
@@ -151,6 +185,7 @@ exports[`should render correctly 1`] = ` | |||
}, | |||
], | |||
"ignoredConditions": false, | |||
"isCaycCompliant": true, | |||
"key": "foo", | |||
"name": "Foo", | |||
"status": "ERROR", | |||
@@ -191,16 +226,66 @@ exports[`should render correctly 2`] = ` | |||
<div | |||
className="overview-quality-gate-conditions" | |||
> | |||
<h3 | |||
className="overview-quality-gate-conditions-project-name" | |||
<ButtonPlain | |||
aria-expanded={true} | |||
aria-label="overview.quality_gate.hide_project_conditions_x.Foo" | |||
className="width-100 text-left" | |||
onClick={[Function]} | |||
> | |||
<div | |||
className="display-flex-center" | |||
> | |||
<h3 | |||
className="overview-quality-gate-conditions-project-name text-ellipsis" | |||
title="Foo" | |||
> | |||
<ChevronDownIcon /> | |||
<span | |||
className="spacer-left" | |||
> | |||
Foo | |||
</span> | |||
</h3> | |||
</div> | |||
</ButtonPlain> | |||
<div | |||
className="display-flex-center overview-quality-gate-conditions-section-title" | |||
> | |||
<h4 | |||
className="padded" | |||
> | |||
quality_gates.conditions.cayc | |||
</h4> | |||
</div> | |||
<div | |||
className="big-padded bordered-bottom overview-quality-gate-conditions-list" | |||
> | |||
<Alert | |||
className="no-margin-bottom" | |||
variant="success" | |||
> | |||
overview.quality_gate.conditions.cayc.passed | |||
</Alert> | |||
</div> | |||
<div | |||
className="display-flex-center overview-quality-gate-conditions-section-title" | |||
> | |||
Foo | |||
</h3> | |||
<h4 | |||
className="overview-quality-gate-conditions-section-title" | |||
<h4 | |||
className="padded" | |||
> | |||
quality_gates.conditions.other_conditions | |||
</h4> | |||
<span | |||
className="text-muted big-spacer-left" | |||
> | |||
overview.X_conditions_failed.2 | |||
</span> | |||
</div> | |||
<h5 | |||
className="big-padded overview-quality-gate-conditions-subsection-title" | |||
> | |||
quality_gates.conditions.new_code | |||
</h4> | |||
</h5> | |||
<Memo(QualityGateConditions) | |||
component={ | |||
{ | |||
@@ -253,6 +338,7 @@ exports[`should render correctly 2`] = ` | |||
}, | |||
], | |||
"ignoredConditions": false, | |||
"isCaycCompliant": true, | |||
"key": "foo", | |||
"name": "Foo", | |||
"status": "ERROR", | |||
@@ -286,11 +372,11 @@ exports[`should render correctly 2`] = ` | |||
] | |||
} | |||
/> | |||
<h4 | |||
className="overview-quality-gate-conditions-section-title" | |||
<h5 | |||
className="big-padded overview-quality-gate-conditions-subsection-title" | |||
> | |||
quality_gates.conditions.overall_code | |||
</h4> | |||
</h5> | |||
<Memo(QualityGateConditions) | |||
component={ | |||
{ | |||
@@ -343,6 +429,7 @@ exports[`should render correctly 2`] = ` | |||
}, | |||
], | |||
"ignoredConditions": false, | |||
"isCaycCompliant": true, | |||
"key": "foo", | |||
"name": "Foo", | |||
"status": "ERROR", |
@@ -1,51 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly 1`] = ` | |||
<Fragment> | |||
<CoverageRating | |||
value="1.0" | |||
/> | |||
<span | |||
className="huge spacer-left" | |||
> | |||
1.0% | |||
</span> | |||
<span | |||
className="big-spacer-left" | |||
> | |||
overview.measures.security_hotspots_reviewed | |||
</span> | |||
</Fragment> | |||
`; | |||
exports[`should render correctly: no measures 1`] = ` | |||
<Fragment> | |||
<span | |||
aria-label="no_data" | |||
className="overview-measures-empty-value" | |||
/> | |||
<span | |||
className="big-spacer-left" | |||
> | |||
overview.measures.security_hotspots_reviewed | |||
</span> | |||
</Fragment> | |||
`; | |||
exports[`should render correctly: on new code 1`] = ` | |||
<Fragment> | |||
<CoverageRating | |||
value="1.0" | |||
/> | |||
<span | |||
className="huge spacer-left" | |||
> | |||
1.0% | |||
</span> | |||
<span | |||
className="big-spacer-left" | |||
> | |||
overview.measures.security_hotspots_reviewed | |||
</span> | |||
</Fragment> | |||
`; |
@@ -42,9 +42,7 @@ export function QualityGateConditions(props: QualityGateConditionsProps) { | |||
const { branchLike, collapsible, component, failedConditions } = props; | |||
const [collapsed, toggleCollapsed] = React.useState(Boolean(collapsible)); | |||
if (failedConditions.length === 0) { | |||
return null; | |||
} | |||
const handleToggleCollapsed = React.useCallback(() => toggleCollapsed(!collapsed), [collapsed]); | |||
const sortedConditions = sortBy(failedConditions, (condition) => | |||
LEVEL_ORDER.indexOf(condition.level) | |||
@@ -77,7 +75,7 @@ export function QualityGateConditions(props: QualityGateConditionsProps) { | |||
<li> | |||
<ButtonLink | |||
className="overview-quality-gate-conditions-list-collapse" | |||
onClick={() => toggleCollapsed(!collapsed)} | |||
onClick={handleToggleCollapsed} | |||
> | |||
{translateWithParameters( | |||
'overview.X_more_failed_conditions', |
@@ -28,7 +28,6 @@ import { QualityGateConditions, QualityGateConditionsProps } from '../QualityGat | |||
it('should render correctly', () => { | |||
const wrapper = shallowRender(); | |||
expect(wrapper.find('QualityGateCondition').length).toBe(10); | |||
expect(shallowRender({ failedConditions: [] }).type()).toBeNull(); | |||
}); | |||
it('should be collapsible', () => { |
@@ -115,26 +115,27 @@ | |||
color: white; | |||
} | |||
.overview-quality-gate-conditions { | |||
padding-bottom: calc(2 * var(--gridSize)); | |||
.overview-quality-gate-conditions-list { | |||
background-color: white; | |||
} | |||
.overview-quality-gate-conditions-project-name { | |||
padding: var(--gridSize) 0 var(--gridSize) calc(2 * var(--gridSize)); | |||
padding: calc(2 * var(--gridSize)) 0 calc(2 * var(--gridSize)) calc(2 * var(--gridSize)); | |||
font-size: var(--bigFontSize); | |||
background: var(--barBorderColor); | |||
} | |||
.overview-quality-gate-conditions-section-title { | |||
padding: calc(2 * var(--gridSize)) calc(2 * var(--gridSize)) var(--gridSize) | |||
calc(2 * var(--gridSize)); | |||
border-bottom: 1px solid var(--barBorderColor); | |||
margin: 0; | |||
font-size: var(--baseFontSize); | |||
background: var(--barBorderColor); | |||
} | |||
.overview-quality-gate-conditions-list { | |||
margin-bottom: calc(2 * var(--gridSize)); | |||
.overview-quality-gate-conditions-subsection-title { | |||
background-color: white; | |||
border-bottom: 1px solid var(--barBorderColor); | |||
margin: 0; | |||
font-size: var(--baseFontSize); | |||
} | |||
.overview-quality-gate-conditions-list-collapse { |
@@ -112,6 +112,16 @@ export enum MeasurementType { | |||
Duplication = 'DUPLICATION', | |||
} | |||
/* | |||
* Metrics part of Clean As You Code | |||
*/ | |||
export const CAYC_METRICS: string[] = [ | |||
MetricKey.new_maintainability_rating, | |||
MetricKey.new_reliability_rating, | |||
MetricKey.new_security_hotspots_reviewed, | |||
MetricKey.new_security_rating, | |||
]; | |||
const MEASUREMENTS_MAP = { | |||
[MeasurementType.Coverage]: { | |||
metric: MetricKey.coverage, |
@@ -40,6 +40,7 @@ export function mockQualityGateStatus( | |||
): QualityGateStatus { | |||
return { | |||
ignoredConditions: false, | |||
isCaycCompliant: true, | |||
failedConditions: [mockQualityGateStatusConditionEnhanced()], | |||
key: 'foo', | |||
name: 'Foo', | |||
@@ -90,6 +91,7 @@ export function mockQualityGateProjectStatus( | |||
}, | |||
], | |||
ignoredConditions: false, | |||
isCaycCompliant: true, | |||
status: 'OK', | |||
...overrides, | |||
}; | |||
@@ -121,6 +123,7 @@ export function mockQualityGateApplicationStatus( | |||
value: '5', | |||
}, | |||
], | |||
isCaycCompliant: true, | |||
status: 'ERROR', | |||
}, | |||
{ | |||
@@ -136,6 +139,7 @@ export function mockQualityGateApplicationStatus( | |||
value: '15', | |||
}, | |||
], | |||
isCaycCompliant: true, | |||
status: 'ERROR', | |||
}, | |||
], |
@@ -25,6 +25,7 @@ export interface QualityGateProjectStatus { | |||
conditions?: QualityGateProjectStatusCondition[]; | |||
ignoredConditions: boolean; | |||
status: Status; | |||
isCaycCompliant: boolean; | |||
} | |||
export interface QualityGateProjectStatusCondition { | |||
@@ -58,11 +59,13 @@ export interface QualityGateApplicationStatusChildProject { | |||
key: string; | |||
name: string; | |||
status: Status; | |||
isCaycCompliant: boolean; | |||
} | |||
export interface QualityGateStatus { | |||
failedConditions: QualityGateStatusConditionEnhanced[]; | |||
ignoredConditions?: boolean; | |||
isCaycCompliant: boolean; | |||
key: string; | |||
name: string; | |||
status: Status; |
@@ -1810,6 +1810,8 @@ quality_gates.condition_deleted=Successfully deleted condition | |||
quality_gates.delete_condition.confirm.message=Are you sure you want to delete the "{0}" condition? | |||
quality_gates.conditions.fails_when=Quality Gate fails when | |||
quality_gates.conditions.metric=Metric | |||
quality_gates.conditions.cayc=Clean as You Code | |||
quality_gates.conditions.other_conditions=Other conditions | |||
quality_gates.conditions.new_code=On New Code | |||
quality_gates.conditions.new_code.long=Conditions on New Code | |||
quality_gates.conditions.new_code.description=Conditions on New Code apply to all branches and to Pull Requests. | |||
@@ -3254,6 +3256,7 @@ system.version_is_availble={version} is available | |||
#------------------------------------------------------------------------------ | |||
overview.failed_conditions=Failed conditions | |||
overview.X_more_failed_conditions={0} more failed conditions | |||
overview.1_condition_failed=1 condition failed | |||
overview.X_conditions_failed={0} conditions failed | |||
overview.fix_failed_conditions_with_sonarlint=Fix issues before they fail your Quality Gate with {link} in your IDE. Power up with connected mode! | |||
overview.quality_gate=Quality Gate Status | |||
@@ -3265,6 +3268,12 @@ overview.you_should_define_quality_gate=You should define a quality gate on this | |||
overview.quality_gate.ignored_conditions=Some Quality Gate conditions on New Code were ignored because of the small number of New Lines | |||
overview.quality_gate.ignored_conditions.tooltip=At the start of a new code period, if very few lines have been added or modified, it might be difficult to reach the desired level of code coverage or duplications. To prevent Quality Gate failure when there's little that can be done about it, Quality Gate conditions about duplications in new code and coverage on new code are ignored until the number of new lines is at least 20. An administrator can disable this in the general settings. | |||
overview.quality_gate.conditions_on_new_code=Only conditions on new code that are defined in the Quality Gate are checked. See the {link} associated to the project for details. | |||
overview.quality_gate.conditions.cayc.warning=Some Clean as You Code conditions are missing or are too permissive. | |||
overview.quality_gate.conditions.cayc.details=Clean as You Code conditions ensure that only Clean Code passes the gate. | |||
overview.quality_gate.conditions.cayc.link=What is Clean as You Code | |||
overview.quality_gate.conditions.cayc.passed=All conditions passed | |||
overview.quality_gate.show_project_conditions_x=Show failed conditions for project {0} | |||
overview.quality_gate.hide_project_conditions_x=Hide failed conditions for project {0} | |||
overview.quality_profiles=Quality Profiles used | |||
overview.new_code_period_x=New Code: {0} | |||
overview.max_new_code_period_from_x=Max New Code from: {0} |