@@ -76,7 +76,7 @@ export default function ProjectCard({ project }: Props) { | |||
overlay={translate('quality_gates.conditions.warning.tooltip')} | |||
/> | |||
)} | |||
<Level level={project.qualityGate} /> | |||
<Level aria-label={translate('quality_gates.status')} level={project.qualityGate} /> | |||
</div> | |||
)} | |||
</aside> |
@@ -31,13 +31,13 @@ import MainRating from './MainRating'; | |||
import MeasuresButtonLink from './MeasuresButtonLink'; | |||
import RatingFreshness from './RatingFreshness'; | |||
interface Props { | |||
export interface MetricBoxProps { | |||
component: string; | |||
measures: T.Dict<string | undefined>; | |||
metricKey: string; | |||
} | |||
export default function MetricBox({ component, measures, metricKey }: Props) { | |||
export default function MetricBox({ component, measures, metricKey }: MetricBoxProps) { | |||
const keys = METRICS_PER_TYPE[metricKey]; | |||
const rating = measures[keys.rating]; | |||
const lastReliabilityChange = measures[keys.last_change]; | |||
@@ -90,8 +90,17 @@ export default function MetricBox({ component, measures, metricKey }: Props) { | |||
? translate('project_singular') | |||
: translate('project_plural')} | |||
</span> | |||
</Link>{' '} | |||
<Level level="ERROR" small={true} /> | |||
</Link> | |||
<Level | |||
aria-label={ | |||
Number(effort) === 1 | |||
? translate('portfolio.has_qg_status') | |||
: translate('portfolio.have_qg_status') | |||
} | |||
className="little-spacer-left" | |||
level="ERROR" | |||
small={true} | |||
/> | |||
</div> | |||
</> | |||
) |
@@ -17,19 +17,13 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
/* eslint-disable sonarjs/no-duplicate-string */ | |||
import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import MetricBox from '../MetricBox'; | |||
import MetricBox, { MetricBoxProps } from '../MetricBox'; | |||
it('should render correctly', () => { | |||
const measures = { | |||
reliability_rating: '3', | |||
last_change_on_reliability_rating: '{"date":"2017-01-02T00:00:00.000Z","value":2}', | |||
reliability_rating_effort: '{"rating":3,"projects":1}' | |||
}; | |||
expect( | |||
shallow(<MetricBox component="foo" measures={measures} metricKey="reliability" />) | |||
).toMatchSnapshot(); | |||
expect(shallowRender({ metricKey: 'reliability' })).toMatchSnapshot(); | |||
}); | |||
it('should render correctly for releasability', () => { | |||
@@ -38,19 +32,34 @@ it('should render correctly for releasability', () => { | |||
last_change_on_releasability_rating: '{"date":"2017-01-02T00:00:00.000Z","value":2}', | |||
releasability_effort: '5' | |||
}; | |||
expect( | |||
shallow(<MetricBox component="foo" measures={measures} metricKey="releasability" />) | |||
).toMatchSnapshot(); | |||
expect(shallowRender({ measures })).toMatchSnapshot(); | |||
expect(shallowRender({ measures: { ...measures, releasability_effort: '1' } })).toMatchSnapshot(); | |||
}); | |||
it('should render correctly when no effort', () => { | |||
const measures = { | |||
releasability_rating: '2', | |||
last_change_on_releasability_rating: '{"date":"2017-01-02T00:00:00.000Z","value":2}', | |||
releasability_effort: '0' | |||
}; | |||
expect( | |||
shallow(<MetricBox component="foo" measures={measures} metricKey="releasability" />) | |||
shallowRender({ | |||
measures: { | |||
releasability_rating: '2', | |||
last_change_on_releasability_rating: '{"date":"2017-01-02T00:00:00.000Z","value":2}', | |||
releasability_effort: '0' | |||
} | |||
}) | |||
).toMatchSnapshot(); | |||
}); | |||
function shallowRender(props: Partial<MetricBoxProps> = {}) { | |||
return shallow( | |||
<MetricBox | |||
component="foo" | |||
measures={{ | |||
reliability_rating: '3', | |||
last_change_on_reliability_rating: '{"date":"2017-01-02T00:00:00.000Z","value":2}', | |||
reliability_rating_effort: '{"rating":3,"projects":1}' | |||
}} | |||
metricKey="releasability" | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -111,8 +111,89 @@ exports[`should render correctly for releasability 1`] = ` | |||
project_plural | |||
</span> | |||
</Link> | |||
<Level | |||
aria-label="portfolio.have_qg_status" | |||
className="little-spacer-left" | |||
level="ERROR" | |||
small={true} | |||
/> | |||
</div> | |||
<div | |||
className="portfolio-box-links" | |||
> | |||
<div> | |||
<MeasuresButtonLink | |||
component="foo" | |||
metric="Releasability" | |||
/> | |||
</div> | |||
<div> | |||
<HistoryButtonLink | |||
component="foo" | |||
metric="releasability_rating" | |||
/> | |||
</div> | |||
</div> | |||
</div> | |||
`; | |||
exports[`should render correctly for releasability 2`] = ` | |||
<div | |||
className="portfolio-box" | |||
> | |||
<h2 | |||
className="portfolio-box-title" | |||
> | |||
metric_domain.Releasability | |||
<HelpTooltip | |||
className="little-spacer-left" | |||
overlay="portfolio.metric_domain.releasability.help" | |||
/> | |||
</h2> | |||
<MainRating | |||
component="foo" | |||
metric="releasability_rating" | |||
value="2" | |||
/> | |||
<h3> | |||
portfolio.metric_trend | |||
</h3> | |||
<RatingFreshness | |||
lastChange="{\\"date\\":\\"2017-01-02T00:00:00.000Z\\",\\"value\\":2}" | |||
rating="2" | |||
/> | |||
<h3> | |||
portfolio.lowest_rated_projects | |||
</h3> | |||
<div | |||
className="portfolio-effort" | |||
> | |||
<Link | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/component_measures", | |||
"query": Object { | |||
"id": "foo", | |||
"metric": "alert_status", | |||
}, | |||
} | |||
} | |||
> | |||
<span> | |||
<Measure | |||
className="little-spacer-right" | |||
metricKey="projects" | |||
metricType="SHORT_INT" | |||
value={1} | |||
/> | |||
project_singular | |||
</span> | |||
</Link> | |||
<Level | |||
aria-label="portfolio.has_qg_status" | |||
className="little-spacer-left" | |||
level="ERROR" | |||
small={true} | |||
/> |
@@ -23,7 +23,7 @@ import { Link } from 'react-router'; | |||
import { ResetButtonLink } from 'sonar-ui-common/components/controls/buttons'; | |||
import DropdownIcon from 'sonar-ui-common/components/icons/DropdownIcon'; | |||
import Level from 'sonar-ui-common/components/ui/Level'; | |||
import { translate } from 'sonar-ui-common/helpers/l10n'; | |||
import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; | |||
import { getProjectUrl } from '../../../helpers/urls'; | |||
export type RichQualityGateEvent = T.AnalysisEvent & Required<Pick<T.AnalysisEvent, 'qualityGate'>>; | |||
@@ -83,13 +83,20 @@ export class RichQualityGateEventInner extends React.PureComponent<Props, State> | |||
<ul className="spacer-left spacer-top"> | |||
{event.qualityGate.failing.map(project => ( | |||
<li className="display-flex-center spacer-top" key={project.key}> | |||
<Level className="spacer-right" level={event.qualityGate.status} small={true} /> | |||
<Level | |||
aria-label={translate('quality_gates.status')} | |||
className="spacer-right" | |||
level={event.qualityGate.status} | |||
small={true} | |||
/> | |||
<div className="flex-1 text-ellipsis"> | |||
<Link | |||
onClick={this.stopPropagation} | |||
title={project.name} | |||
to={getProjectUrl(project.key, project.branch)}> | |||
{project.name} | |||
<span aria-label={translateWithParameters('project_x', project.name)}> | |||
{project.name} | |||
</span> | |||
</Link> | |||
</div> | |||
</li> |
@@ -77,6 +77,7 @@ exports[`should render 2`] = ` | |||
key="foo" | |||
> | |||
<Level | |||
aria-label="quality_gates.status" | |||
className="spacer-right" | |||
level="ERROR" | |||
small={true} | |||
@@ -99,7 +100,11 @@ exports[`should render 2`] = ` | |||
} | |||
} | |||
> | |||
Foo | |||
<span | |||
aria-label="project_x.Foo" | |||
> | |||
Foo | |||
</span> | |||
</Link> | |||
</div> | |||
</li> | |||
@@ -108,6 +113,7 @@ exports[`should render 2`] = ` | |||
key="bar" | |||
> | |||
<Level | |||
aria-label="quality_gates.status" | |||
className="spacer-right" | |||
level="ERROR" | |||
small={true} | |||
@@ -130,7 +136,11 @@ exports[`should render 2`] = ` | |||
} | |||
} | |||
> | |||
Bar | |||
<span | |||
aria-label="project_x.Bar" | |||
> | |||
Bar | |||
</span> | |||
</Link> | |||
</div> | |||
</li> |
@@ -42,7 +42,7 @@ export default function ProjectCardQualityGate({ status }: Props) { | |||
<div className="project-card-quality-gate big-spacer-left"> | |||
<Tooltip overlay={tooltip}> | |||
<div className="project-card-measure-inner"> | |||
<Level level={status} small={true} /> | |||
<Level aria-label={translate('quality_gates.status')} level={status} small={true} /> | |||
{status === 'WARN' && ( | |||
<HelpTooltip | |||
className="little-spacer-left" |
@@ -11,6 +11,7 @@ exports[`renders 1`] = ` | |||
className="project-card-measure-inner" | |||
> | |||
<Level | |||
aria-label="quality_gates.status" | |||
level="ERROR" | |||
small={true} | |||
/> |
@@ -137,6 +137,7 @@ path=Path | |||
permalink=Permanent Link | |||
plugin=Plugin | |||
project=Project | |||
project_x=Project: {0} | |||
projects=Projects | |||
projects_=project(s) | |||
project_singular=project | |||
@@ -1399,6 +1400,7 @@ quality_gates.intro.2=It is possible to set a default Quality Gate, which will b | |||
quality_gates.built_in=Built-in | |||
quality_gates.built_in.description.1=This quality gate is provided by default. | |||
quality_gates.built_in.description.2=It will automatically be updated with the latest recommendations. | |||
quality_gates.status=Quality Gate status | |||
#------------------------------------------------------------------------------ | |||
@@ -3227,6 +3229,8 @@ branch_like_navigation.for_merge_into_x_from_y=for merge into {target} from {bra | |||
portfolio.has_always_been_x=has always been {rating} | |||
portfolio.was_x_y=was {rating} {date} | |||
portfolio.x_in_y={projects} in {rating} | |||
portfolio.has_qg_status=Has Quality Gate Status | |||
portfolio.have_qg_status=Have Quality Gate Status | |||
portfolio.empty=This portfolio is empty. | |||
portfolio.no_lines_of_code=All projects in this portfolio are empty | |||
portfolio.not_computed=This portfolio is not yet computed. |