}): Promise<ApplicationQualityGate> {
return getJSON('/api/qualitygates/application_status', data).catch(throwGlobalError);
}
+
+export function getQualityGateProjectStatus(
+ data: {
+ projectKey?: string;
+ projectId?: string;
+ } & T.BranchParameters
+): Promise<T.QualityGateProjectStatus> {
+ return getJSON('/api/qualitygates/project_status', data).catch(throwGlobalError);
+}
};
renderDashboardLink() {
- const { branchLike } = this.props;
-
- if (isShortLivingBranch(branchLike) || isPullRequest(branchLike)) {
- return null;
- }
-
const pathname = this.isPortfolio() ? '/portfolio' : '/dashboard';
return (
<li>
isMain: false,
mergeBranch: 'master',
name: 'foo',
- status: { bugs: 0, codeSmells: 0, qualityGateStatus: 'OK', vulnerabilities: 0 },
+ status: { qualityGateStatus: 'OK' },
type: 'SHORT'
};
const component = {} as T.Component;
isOrphan,
mergeBranch: 'master',
name,
- status: { bugs: 0, codeSmells: 0, qualityGateStatus: 'OK', vulnerabilities: 0 },
+ status: { qualityGateStatus: 'OK' },
type: 'SHORT'
};
}
base: 'master',
branch: 'feature',
key: '1234',
- status: { bugs: 0, codeSmells: 0, qualityGateStatus: 'OK', vulnerabilities: 0 },
+ status: { qualityGateStatus: 'OK' },
title
};
}
isMain: false,
mergeBranch: 'master',
name: 'foo',
- status: { bugs: 1, codeSmells: 2, qualityGateStatus: 'ERROR', vulnerabilities: 3 },
+ status: { qualityGateStatus: 'ERROR' },
type: 'SHORT'
};
expect(
shallowRender({
branchLike: mockPullRequest({
- status: { bugs: 0, codeSmells: 2, qualityGateStatus: 'ERROR', vulnerabilities: 3 },
url: 'https://example.com/pull/1234'
})
})
function shallowRender(props = {}) {
return shallow(
<ComponentNavMeta
- branchLike={mockShortLivingBranch({
- status: { bugs: 0, codeSmells: 2, qualityGateStatus: 'ERROR', vulnerabilities: 3 }
- })}
+ branchLike={mockShortLivingBranch()}
component={mockComponent({ analysisDate: '2017-01-02T00:00:00.000Z', version: '0.0.1' })}
currentUser={mockCurrentUser({ isLoggedIn: false })}
warnings={[]}
"mergeBranch": "master",
"name": "foo",
"status": Object {
- "bugs": 0,
- "codeSmells": 0,
"qualityGateStatus": "OK",
- "vulnerabilities": 0,
},
"type": "SHORT",
},
"mergeBranch": "master",
"name": "foo",
"status": Object {
- "bugs": 0,
- "codeSmells": 0,
"qualityGateStatus": "OK",
- "vulnerabilities": 0,
},
"type": "SHORT",
}
"mergeBranch": "master",
"name": "foo",
"status": Object {
- "bugs": 0,
- "codeSmells": 0,
"qualityGateStatus": "OK",
- "vulnerabilities": 0,
},
"type": "SHORT",
}
"branch": "feature",
"key": "1234",
"status": Object {
- "bugs": 0,
- "codeSmells": 0,
"qualityGateStatus": "OK",
- "vulnerabilities": 0,
},
"title": "qux",
}
"mergeBranch": "master",
"name": "baz",
"status": Object {
- "bugs": 0,
- "codeSmells": 0,
"qualityGateStatus": "OK",
- "vulnerabilities": 0,
},
"type": "SHORT",
}
"mergeBranch": "master",
"name": "foo",
"status": Object {
- "bugs": 0,
- "codeSmells": 0,
"qualityGateStatus": "OK",
- "vulnerabilities": 0,
},
"type": "SHORT",
}
"mergeBranch": "master",
"name": "baz",
"status": Object {
- "bugs": 0,
- "codeSmells": 0,
"qualityGateStatus": "OK",
- "vulnerabilities": 0,
},
"type": "SHORT",
}
"mergeBranch": "master",
"name": "foobar",
"status": Object {
- "bugs": 0,
- "codeSmells": 0,
"qualityGateStatus": "OK",
- "vulnerabilities": 0,
},
"type": "SHORT",
}
"query": Object {
"branch": "foo",
"id": "component",
- "resolved": "false",
},
}
}
"mergeBranch": "master",
"name": "foo",
"status": Object {
- "bugs": 1,
- "codeSmells": 2,
"qualityGateStatus": "ERROR",
- "vulnerabilities": 3,
},
"type": "SHORT",
}
"mergeBranch": "master",
"name": "foo",
"status": Object {
- "bugs": 1,
- "codeSmells": 2,
"qualityGateStatus": "ERROR",
- "vulnerabilities": 3,
},
"type": "SHORT",
}
"query": Object {
"branch": "foo",
"id": "component",
- "resolved": "false",
},
}
}
"mergeBranch": "master",
"name": "foo",
"status": Object {
- "bugs": 1,
- "codeSmells": 2,
"qualityGateStatus": "ERROR",
- "vulnerabilities": 3,
},
"type": "SHORT",
}
"mergeBranch": "master",
"name": "foo",
"status": Object {
- "bugs": 1,
- "codeSmells": 2,
"qualityGateStatus": "ERROR",
- "vulnerabilities": 3,
},
"type": "SHORT",
}
exports[`should work for short-living branches 1`] = `
<NavBarTabs>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/dashboard",
+ "query": Object {
+ "branch": "feature",
+ "id": "foo",
+ },
+ }
+ }
+ >
+ overview.page
+ </Link>
+ </li>
<li>
<Link
activeClassName="active"
"base": "master",
"branch": "feature/foo/bar",
"key": "1001",
- "status": Object {
- "bugs": 0,
- "codeSmells": 2,
- "qualityGateStatus": "ERROR",
- "vulnerabilities": 3,
- },
"title": "Foo Bar feature",
"url": "https://example.com/pull/1234",
}
"isMain": false,
"mergeBranch": "master",
"name": "release-1.0",
- "status": Object {
- "bugs": 0,
- "codeSmells": 2,
- "qualityGateStatus": "ERROR",
- "vulnerabilities": 3,
- },
"type": "SHORT",
}
}
green: '#00aa00',
lineCoverageGreen: '#b4dd78',
lightGreen: '#b0d513',
+ veryLightGreen: '#f5f9fc',
yellow: '#eabe06',
orange: '#ed7d20',
red: '#d4333f',
gray60: '#999',
gray40: '#404040',
+ transparentWhite: 'rgba(255,255,255,0.62)',
+
disableGrayText: '#bbb',
disableGrayBorder: '#ddd',
disableGrayBg: '#ebebeb',
branch: string;
key: string;
isOrphan?: true;
- status?: {
- bugs: number;
- codeSmells: number;
- qualityGateStatus: string;
- vulnerabilities: number;
- };
+ status?: { qualityGateStatus: string };
title: string;
url?: string;
}
name: string;
}
+ export interface QualityGateProjectStatus {
+ projectStatus: {
+ conditions?: Array<{
+ status: 'ERROR' | 'OK';
+ metricKey: string;
+ comparator: string;
+ periodIndex: number;
+ errorThreshold: string;
+ actualValue: string;
+ }>;
+ ignoredConditions: boolean;
+ status: string;
+ };
+ }
+
+ export interface QualityGateStatusCondition {
+ actual?: string;
+ error?: string;
+ level: string;
+ metric: string;
+ op: string;
+ period?: number;
+ warning?: string;
+ }
+
+ export interface QualityGateStatusConditionEnhanced extends QualityGateStatusCondition {
+ measure: T.MeasureEnhanced;
+ }
+
export interface Rule {
isTemplate?: boolean;
key: string;
isMain: false;
isOrphan?: true;
mergeBranch: string;
- status?: {
- bugs: number;
- codeSmells: number;
- qualityGateStatus: string;
- vulnerabilities: number;
- };
type: 'SHORT';
}
"query": Object {
"branch": "feature",
"id": "foo",
- "resolved": "false",
},
}
}
"query": Object {
"id": "foo",
"pullRequest": "pr-89",
- "resolved": "false",
},
}
}
>
<strong>
<Measure
- className="leak-box"
metricKey="new_reliability_rating"
metricType="RATING"
value="3.0"
import { Helmet } from 'react-helmet';
import OverviewApp from './OverviewApp';
import EmptyOverview from './EmptyOverview';
+import ReviewApp from '../pullRequests/ReviewApp';
import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
-import { isShortLivingBranch } from '../../../helpers/branches';
-import {
- getShortLivingBranchUrl,
- getProjectUrl,
- getBaseUrl,
- getPathUrlAsString
-} from '../../../helpers/urls';
-import { isSonarCloud } from '../../../helpers/system';
import { withRouter, Router } from '../../../components/hoc/withRouter';
+import { getProjectUrl, getBaseUrl, getPathUrlAsString } from '../../../helpers/urls';
+import { isSonarCloud } from '../../../helpers/system';
+import { isShortLivingBranch, isPullRequest } from '../../../helpers/branches';
interface Props {
branchLike?: T.BranchLike;
export class App extends React.PureComponent<Props> {
componentDidMount() {
- const { branchLike, component } = this.props;
+ const { component } = this.props;
if (this.isPortfolio()) {
this.props.router.replace({
pathname: '/portfolio',
query: { id: component.key }
});
- } else if (isShortLivingBranch(branchLike)) {
- this.props.router.replace(getShortLivingBranchUrl(component.key, branchLike.name));
}
}
- isPortfolio = () => ['VW', 'SVW'].includes(this.props.component.qualifier);
+ isPortfolio = () => {
+ return ['VW', 'SVW'].includes(this.props.component.qualifier);
+ };
render() {
const { branchLike, branchLikes, component } = this.props;
- if (this.isPortfolio() || isShortLivingBranch(branchLike)) {
+ if (this.isPortfolio()) {
return null;
}
/>
</Helmet>
)}
- <Suggestions suggestions="overview" />
- {!component.analysisDate && (
- <EmptyOverview
- branchLike={branchLike}
- branchLikes={branchLikes}
- component={component}
- hasAnalyses={this.props.isPending || this.props.isInProgress}
- onComponentChange={this.props.onComponentChange}
- />
- )}
- {component.analysisDate && (
- <OverviewApp
- branchLike={branchLike}
- component={component}
- onComponentChange={this.props.onComponentChange}
- />
+ {isShortLivingBranch(branchLike) || isPullRequest(branchLike) ? (
+ <ReviewApp branchLike={branchLike} component={component} />
+ ) : (
+ <>
+ <Suggestions suggestions="overview" />
+
+ {!component.analysisDate ? (
+ <EmptyOverview
+ branchLike={branchLike}
+ branchLikes={branchLikes}
+ component={component}
+ hasAnalyses={this.props.isPending || this.props.isInProgress}
+ onComponentChange={this.props.onComponentChange}
+ />
+ ) : (
+ <OverviewApp
+ branchLike={branchLike}
+ component={component}
+ onComponentChange={this.props.onComponentChange}
+ />
+ )}
+ </>
)}
</>
);
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 { formatMeasure, findMeasure } from '../../../helpers/measures';
+import { translate } from '../../../helpers/l10n';
+import { MEASUREMENTS_MAP, MeasurementType } from '../utils';
+
+interface Props {
+ measures: T.Measure[];
+ type: MeasurementType;
+}
+
+export default function AfterMergeEstimate({ measures, type }: Props) {
+ const { afterMergeMetric } = MEASUREMENTS_MAP[type];
+
+ const measure = findMeasure(measures, afterMergeMetric);
+
+ if (!measure || measure.value === undefined) {
+ return null;
+ }
+
+ return (
+ <>
+ <span className="huge">{formatMeasure(measure.value, 'PERCENT')}</span>
+ <span className="label flex-1">
+ {translate('component_measures.facet_category.overall_category.estimated')}
+ </span>
+ </>
+ );
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 'react-router';
+import * as classNames from 'classnames';
+import { getMetricName, ISSUETYPE_MAP, IssueType } from '../utils';
+import { getLeakValue } from '../../../components/measure/utils';
+import { getBranchLikeQuery } from '../../../helpers/branches';
+import { getComponentIssuesUrl } from '../../../helpers/urls';
+import { formatMeasure, findMeasure } from '../../../helpers/measures';
+
+interface Props {
+ branchLike?: T.ShortLivingBranch | T.PullRequest;
+ className?: string;
+ component: T.Component;
+ measures: T.Measure[];
+ type: IssueType;
+}
+
+export default function IssueLabel({ branchLike, className, component, measures, type }: Props) {
+ const { metric, iconClass } = ISSUETYPE_MAP[type];
+
+ const measure = findMeasure(measures, metric);
+
+ let value;
+ if (measure) {
+ value = getLeakValue(measure);
+ }
+
+ const params = {
+ ...getBranchLikeQuery(branchLike),
+ resolved: 'false',
+ types: type
+ };
+
+ return (
+ <>
+ {value === undefined ? (
+ <span className={classNames(className, 'measure-empty')}>—</span>
+ ) : (
+ <Link className={className} to={getComponentIssuesUrl(component.key, params)}>
+ {formatMeasure(value, 'SHORT_INT')}
+ </Link>
+ )}
+ {React.createElement(iconClass, { className: 'big-spacer-left little-spacer-right' })}
+ {getMetricName(metric)}
+ </>
+ );
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 DrilldownLink from '../../../components/shared/DrilldownLink';
+import Rating from '../../../components/ui/Rating';
+import Tooltip from '../../../components/controls/Tooltip';
+import { getRatingName, ISSUETYPE_MAP, IssueType } from '../utils';
+import { getLeakValue, getRatingTooltip } from '../../../components/measure/utils';
+import { findMeasure } from '../../../helpers/measures';
+
+interface Props {
+ branchLike?: T.ShortLivingBranch | T.PullRequest;
+ component: T.Component;
+ measures: T.Measure[];
+ type: IssueType;
+}
+
+export default function IssueRating({ branchLike, component, measures, type }: Props) {
+ const { rating } = ISSUETYPE_MAP[type];
+ const measure = findMeasure(measures, rating);
+
+ if (measure === undefined) {
+ return null;
+ }
+
+ const value = getLeakValue(measure);
+ const tooltip = value && getRatingTooltip(rating, Number(value));
+
+ return (
+ <>
+ <span className="big-spacer-right flex-1">{getRatingName(type)}</span>
+ <Tooltip overlay={tooltip}>
+ <span>
+ <DrilldownLink
+ branchLike={branchLike}
+ className="link-no-underline"
+ component={component.key}
+ metric={rating}>
+ <Rating value={value} />
+ </DrilldownLink>
+ </span>
+ </Tooltip>
+ </>
+ );
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 * as classNames from 'classnames';
+import { Link } from 'react-router';
+import { FormattedMessage } from 'react-intl';
+import HelpTooltip from '../../../components/controls/HelpTooltip';
+import HelpIcon from '../../../components/icons-components/HelpIcon';
+import { getQualityGateUrl, getQualityGatesUrl } from '../../../helpers/urls';
+import { isSonarCloud } from '../../../helpers/system';
+import { translate } from '../../../helpers/l10n';
+import { transparentWhite } from '../../../app/theme';
+
+interface Props {
+ component: T.Component;
+ level?: string;
+}
+
+export default function LargeQualityGateBadge({ component, level }: Props) {
+ const success = level !== 'ERROR';
+
+ let path;
+ if (isSonarCloud()) {
+ path =
+ component.qualityGate === undefined
+ ? getQualityGatesUrl(component.organization)
+ : getQualityGateUrl(component.qualityGate.key, component.organization);
+ } else {
+ path =
+ component.qualityGate === undefined
+ ? getQualityGatesUrl()
+ : getQualityGateUrl(component.qualityGate.key);
+ }
+
+ return (
+ <div
+ className={classNames('quality-gate-badge-large small', {
+ failed: !success,
+ success
+ })}>
+ <div className="display-flex-center">
+ <span>{translate('overview.on_new_code_long')}</span>
+
+ <HelpTooltip
+ className="little-spacer-left"
+ overlay={
+ <FormattedMessage
+ defaultMessage={translate('overview.quality_gate.conditions_on_new_code')}
+ id="overview.quality_gate.conditions_on_new_code"
+ values={{
+ link: <Link to={path}>{translate('overview.quality_gate')}</Link>
+ }}
+ />
+ }>
+ <HelpIcon fill={transparentWhite} size={12} />
+ </HelpTooltip>
+ </div>
+ {level !== undefined && (
+ <h4 className="huge-spacer-top huge">{translate('metric.level', level)}</h4>
+ )}
+ </div>
+ );
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 { FormattedMessage } from 'react-intl';
+import DrilldownLink from '../../../components/shared/DrilldownLink';
+import { translate } from '../../../helpers/l10n';
+import { formatMeasure, findMeasure } from '../../../helpers/measures';
+import { getLeakValue } from '../../../components/measure/utils';
+import { MEASUREMENTS_MAP, MeasurementType } from '../utils';
+
+interface Props {
+ branchLike?: T.BranchLike;
+ className?: string;
+ component: T.Component;
+ measures: T.Measure[];
+ type: MeasurementType;
+}
+
+export default class MeasurementLabel extends React.Component<Props> {
+ getLabelText = () => {
+ const { branchLike, component, measures, type } = this.props;
+ const { expandedLabelKey, linesMetric, labelKey } = MEASUREMENTS_MAP[type];
+
+ const measure = findMeasure(measures, linesMetric);
+ if (!measure) {
+ return translate(labelKey);
+ } else {
+ return (
+ <FormattedMessage
+ defaultMessage={translate(expandedLabelKey)}
+ id={expandedLabelKey}
+ values={{
+ count: (
+ <DrilldownLink branchLike={branchLike} component={component.key} metric={linesMetric}>
+ {formatMeasure(getLeakValue(measure), 'SHORT_INT')}
+ </DrilldownLink>
+ )
+ }}
+ />
+ );
+ }
+ };
+
+ render() {
+ const { branchLike, className, component, measures, type } = this.props;
+ const { iconClass, metric } = MEASUREMENTS_MAP[type];
+
+ const measure = findMeasure(measures, metric);
+
+ let value;
+ if (measure) {
+ value = getLeakValue(measure);
+ }
+
+ return (
+ <>
+ {value === undefined ? (
+ <span>—</span>
+ ) : (
+ <>
+ <span className="big-spacer-right">
+ {React.createElement(iconClass, { size: 'big', value: Number(value) })}
+ </span>
+ <DrilldownLink
+ branchLike={branchLike}
+ className={className}
+ component={component.key}
+ metric={metric}>
+ {formatMeasure(value, 'PERCENT')}
+ </DrilldownLink>
+ </>
+ )}
+ <span className="big-spacer-left">{this.getLabelText()}</span>
+ </>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 * as classNames from 'classnames';
+import AfterMergeEstimate from './AfterMergeEstimate';
+import LargeQualityGateBadge from './LargeQualityGateBadge';
+import IssueLabel from './IssueLabel';
+import IssueRating from './IssueRating';
+import MeasurementLabel from './MeasurementLabel';
+import DeferredSpinner from '../../../components/common/DeferredSpinner';
+import DocTooltip from '../../../components/docs/DocTooltip';
+import QualityGateConditions from '../qualityGate/QualityGateConditions';
+import { getMeasures } from '../../../api/measures';
+import { getQualityGateProjectStatus } from '../../../api/quality-gates';
+import { PR_METRICS, IssueType, MeasurementType } from '../utils';
+import { getBranchLikeQuery, isSameBranchLike } from '../../../helpers/branches';
+import { translate } from '../../../helpers/l10n';
+import { extractStatusConditionsFromProjectStatus } from '../../../helpers/qualityGates';
+import '../styles.css';
+
+interface Props {
+ branchLike?: T.PullRequest | T.ShortLivingBranch;
+ component: T.Component;
+}
+
+interface State {
+ conditions: T.QualityGateStatusCondition[];
+ loading: boolean;
+ measures: T.Measure[];
+ status?: string;
+}
+
+export default class ReviewApp extends React.Component<Props, State> {
+ mounted = false;
+
+ state: State = {
+ conditions: [],
+ loading: false,
+ measures: []
+ };
+
+ componentDidMount() {
+ this.mounted = true;
+ this.fetchBranchData();
+ }
+
+ componentDidUpdate(prevProps: Props) {
+ if (
+ this.props.component.key !== prevProps.component.key ||
+ !isSameBranchLike(this.props.branchLike, prevProps.branchLike)
+ ) {
+ this.fetchBranchData();
+ }
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ fetchBranchData = () => {
+ const { branchLike, component } = this.props;
+
+ this.setState({ loading: true });
+
+ const data = { projectKey: component.key, ...getBranchLikeQuery(branchLike) };
+
+ Promise.all([
+ getMeasures({
+ component: component.key,
+ metricKeys: PR_METRICS.join(),
+ ...getBranchLikeQuery(branchLike)
+ }),
+ getQualityGateProjectStatus(data)
+ ]).then(
+ ([measures, status]) => {
+ if (this.mounted && measures && status) {
+ this.setState({
+ conditions: extractStatusConditionsFromProjectStatus(status),
+ loading: false,
+ measures,
+ status: status.projectStatus.status
+ });
+ }
+ },
+ () => {
+ if (this.mounted) {
+ this.setState({ loading: false });
+ }
+ }
+ );
+ };
+
+ render() {
+ const { branchLike, component } = this.props;
+ const { loading, measures, conditions, status } = this.state;
+ const erroredConditions = conditions.filter(condition => condition.level === 'ERROR');
+
+ return (
+ <div className="page page-limited">
+ {loading ? (
+ <DeferredSpinner />
+ ) : (
+ <div
+ className={classNames('pr-overview', {
+ 'has-conditions': erroredConditions.length > 0
+ })}>
+ <div className="pr-overview-quality-gate big-spacer-right">
+ <h3 className="spacer-bottom small">
+ {translate('overview.quality_gate')}
+ <DocTooltip
+ className="spacer-left"
+ doc={import(/* webpackMode: "eager" */ 'Docs/tooltips/quality-gates/project-homepage-quality-gate.md')}
+ />
+ </h3>
+ <LargeQualityGateBadge component={component} level={status} />
+ </div>
+
+ {erroredConditions.length > 0 && (
+ <div className="pr-overview-failed-conditions big-spacer-right">
+ <h3 className="spacer-bottom small">{translate('overview.failed_conditions')}</h3>
+ <QualityGateConditions
+ branchLike={branchLike}
+ collapsible={true}
+ component={component}
+ conditions={erroredConditions}
+ />
+ </div>
+ )}
+
+ <div className="pr-overview-measurements flex-1">
+ <h3 className="spacer-bottom small">{translate('overview.metrics')}</h3>
+
+ {['BUG', 'VULNERABILITY', 'CODE_SMELL'].map((type: IssueType) => (
+ <div className="pr-overview-measurements-row display-flex-row" key={type}>
+ <div className="pr-overview-measurements-value flex-1 small display-flex-center">
+ <IssueLabel
+ branchLike={branchLike}
+ className="overview-domain-measure-value"
+ component={component}
+ measures={measures}
+ type={type}
+ />
+ </div>
+ <div className="pr-overview-measurements-rating display-flex-center">
+ <IssueRating
+ branchLike={branchLike}
+ component={component}
+ measures={measures}
+ type={type}
+ />
+ </div>
+ </div>
+ ))}
+
+ {['COVERAGE', 'DUPLICATION'].map((type: MeasurementType) => (
+ <div className="pr-overview-measurements-row display-flex-row" key={type}>
+ <div className="pr-overview-measurements-value flex-1 small display-flex-center">
+ <MeasurementLabel
+ branchLike={branchLike}
+ className="overview-domain-measure-value"
+ component={component}
+ measures={measures}
+ type={type}
+ />
+ </div>
+ <div className="pr-overview-measurements-estimate display-flex-center">
+ <AfterMergeEstimate measures={measures} type={type} />
+ </div>
+ </div>
+ ))}
+ </div>
+ </div>
+ )}
+ </div>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 { shallow } from 'enzyme';
+import AfterMergeEstimate from '../AfterMergeEstimate';
+import { mockMeasure } from '../../../../helpers/testMocks';
+
+it('should render correctly for coverage', () => {
+ expect(shallowRender({ measures: [mockMeasure({ metric: 'coverage' })] })).toMatchSnapshot();
+});
+
+it('should render correctly for duplications', () => {
+ expect(
+ shallowRender({
+ measures: [mockMeasure({ metric: 'duplicated_lines_density' })],
+ type: 'DUPLICATION'
+ })
+ ).toMatchSnapshot();
+});
+
+it('should render correctly with no value', () => {
+ expect(shallowRender()).toMatchSnapshot();
+});
+
+function shallowRender(props = {}) {
+ return shallow(<AfterMergeEstimate measures={[mockMeasure()]} type="COVERAGE" {...props} />);
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 { shallow } from 'enzyme';
+import IssueLabel from '../IssueLabel';
+import { mockComponent, mockPullRequest, mockMeasure } from '../../../../helpers/testMocks';
+
+it('should render correctly for bugs', () => {
+ expect(
+ shallowRender({
+ measures: [mockMeasure({ metric: 'new_bugs' })]
+ })
+ ).toMatchSnapshot();
+});
+
+it('should render correctly for code smells', () => {
+ expect(
+ shallowRender({
+ measures: [mockMeasure({ metric: 'new_code_smells' })],
+ type: 'CODE_SMELL'
+ })
+ ).toMatchSnapshot();
+});
+
+it('should render correctly for vulnerabilities', () => {
+ expect(
+ shallowRender({
+ measures: [mockMeasure({ metric: 'new_vulnerabilities' })],
+ type: 'VULNERABILITY'
+ })
+ ).toMatchSnapshot();
+});
+
+it('should render correctly if no values are present', () => {
+ expect(shallowRender()).toMatchSnapshot();
+});
+
+function shallowRender(props = {}) {
+ return shallow(
+ <IssueLabel
+ branchLike={mockPullRequest()}
+ component={mockComponent()}
+ measures={[]}
+ type="BUG"
+ {...props}
+ />
+ );
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 { shallow } from 'enzyme';
+import IssueRating from '../IssueRating';
+import { mockPullRequest, mockComponent, mockMeasure } from '../../../../helpers/testMocks';
+
+it('should render correctly for bugs', () => {
+ expect(shallowRender()).toMatchSnapshot();
+});
+
+it('should render correctly for code smells', () => {
+ expect(shallowRender({ type: 'CODE_SMELL' })).toMatchSnapshot();
+});
+
+it('should render correctly for vulnerabilities', () => {
+ expect(shallowRender({ type: 'VULNERABILITY' })).toMatchSnapshot();
+});
+
+it('should render correctly if no values are present', () => {
+ expect(shallowRender({ measures: [mockMeasure({ metric: 'NONE' })] })).toMatchSnapshot();
+});
+
+function shallowRender(props = {}) {
+ return shallow(
+ <IssueRating
+ branchLike={mockPullRequest()}
+ component={mockComponent()}
+ measures={[
+ mockMeasure({ metric: 'new_reliability_rating' }),
+ mockMeasure({ metric: 'new_maintainability_rating' }),
+ mockMeasure({ metric: 'new_security_rating' })
+ ]}
+ type="BUG"
+ {...props}
+ />
+ );
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 { shallow } from 'enzyme';
+import LargeQualityGateBadge from '../LargeQualityGateBadge';
+import { mockComponent } from '../../../../helpers/testMocks';
+import { isSonarCloud } from '../../../../helpers/system';
+
+jest.mock('../../../../helpers/system', () => ({
+ isSonarCloud: jest.fn()
+}));
+
+it('should render correctly for SQ', () => {
+ (isSonarCloud as jest.Mock).mockReturnValue(false);
+
+ expect(shallowRender()).toMatchSnapshot();
+ expect(shallowRender({ level: 'OK' })).toMatchSnapshot();
+});
+
+it('should render the link correctly for SC', () => {
+ (isSonarCloud as jest.Mock).mockReturnValue(true);
+ expect(shallowRender()).toMatchSnapshot();
+});
+
+function shallowRender(props = {}) {
+ return shallow(<LargeQualityGateBadge component={mockComponent()} level="ERROR" {...props} />);
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 { shallow } from 'enzyme';
+import MeasurementLabel from '../MeasurementLabel';
+import { mockShortLivingBranch, mockComponent, mockMeasure } from '../../../../helpers/testMocks';
+
+it('should render correctly for coverage', () => {
+ expect(shallowRender()).toMatchSnapshot();
+ expect(
+ shallowRender({
+ measures: [
+ mockMeasure({ metric: 'new_coverage' }),
+ mockMeasure({ metric: 'new_lines_to_cover' })
+ ]
+ })
+ ).toMatchSnapshot();
+});
+
+it('should render correctly for duplications', () => {
+ expect(
+ shallowRender({
+ measures: [mockMeasure({ metric: 'new_duplicated_lines_density' })],
+ type: 'DUPLICATION'
+ })
+ ).toMatchSnapshot();
+});
+
+it('should render correctly with no value', () => {
+ expect(shallowRender({ measures: [mockMeasure({ metric: 'NONE' })] })).toMatchSnapshot();
+});
+
+function shallowRender(props: Partial<MeasurementLabel['props']> = {}) {
+ return shallow(
+ <MeasurementLabel
+ branchLike={mockShortLivingBranch()}
+ component={mockComponent()}
+ measures={[mockMeasure({ metric: 'new_coverage' })]}
+ type="COVERAGE"
+ {...props}
+ />
+ );
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 { shallow } from 'enzyme';
+import ReviewApp from '../ReviewApp';
+import { getMeasures } from '../../../../api/measures';
+import { getQualityGateProjectStatus } from '../../../../api/quality-gates';
+import { mockComponent, mockPullRequest } from '../../../../helpers/testMocks';
+import { waitAndUpdate } from '../../../../helpers/testUtils';
+
+jest.mock('../../../../api/measures', () => {
+ const { mockMeasure } = getMockHelpers();
+ return {
+ getMeasures: jest
+ .fn()
+ .mockResolvedValue([
+ mockMeasure({ metric: 'new_bugs ' }),
+ mockMeasure({ metric: 'new_vulnerabilities' }),
+ mockMeasure({ metric: 'new_code_smells' })
+ ])
+ };
+});
+
+jest.mock('../../../../api/quality-gates', () => ({
+ getQualityGateProjectStatus: jest.fn()
+}));
+
+beforeEach(() => {
+ jest.clearAllMocks();
+});
+
+it('should render correctly for a passed QG', async () => {
+ const { mockQualityGateProjectStatus } = getMockHelpers();
+ (getQualityGateProjectStatus as jest.Mock).mockResolvedValue(mockQualityGateProjectStatus());
+
+ const wrapper = shallowRender();
+ await waitAndUpdate(wrapper);
+ expect(wrapper).toMatchSnapshot();
+
+ expect(wrapper.find('QualityGateConditions').exists()).toBe(false);
+
+ expect(getMeasures).toBeCalled();
+ expect(getQualityGateProjectStatus).toBeCalled();
+});
+
+it('should render correctly for a failed QG', async () => {
+ const { mockQualityGateProjectStatus } = getMockHelpers();
+ (getQualityGateProjectStatus as jest.Mock).mockResolvedValue(
+ mockQualityGateProjectStatus({
+ status: 'ERROR',
+ conditions: [
+ {
+ status: 'OK',
+ metricKey: 'new_bugs',
+ comparator: 'GT',
+ periodIndex: 1,
+ errorThreshold: '1.0',
+ actualValue: '0'
+ },
+ {
+ status: 'ERROR',
+ metricKey: 'new_code_smells',
+ comparator: 'GT',
+ periodIndex: 1,
+ errorThreshold: '1.0',
+ actualValue: '10'
+ }
+ ]
+ })
+ );
+
+ const wrapper = shallowRender();
+ await waitAndUpdate(wrapper);
+ expect(wrapper).toMatchSnapshot();
+
+ expect(wrapper.find('QualityGateConditions').exists()).toBe(true);
+});
+
+function getMockHelpers() {
+ // We use this little "force-requiring" instead of an import statement in
+ // order to prevent a hoisting race condition while mocking. If we want to use
+ // a mock helper in a Jest mock, we have to require it like this. Otherwise,
+ // we get errors like:
+ // ReferenceError: testMocks_1 is not defined
+ return require.requireActual('../../../../helpers/testMocks');
+}
+
+function shallowRender(props: Partial<ReviewApp['props']> = {}) {
+ return shallow(
+ <ReviewApp branchLike={mockPullRequest()} component={mockComponent()} {...props} />
+ );
+}
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly for coverage 1`] = `
+<Fragment>
+ <span
+ className="huge"
+ >
+ 1.0%
+ </span>
+ <span
+ className="label flex-1"
+ >
+ component_measures.facet_category.overall_category.estimated
+ </span>
+</Fragment>
+`;
+
+exports[`should render correctly for duplications 1`] = `
+<Fragment>
+ <span
+ className="huge"
+ >
+ 1.0%
+ </span>
+ <span
+ className="label flex-1"
+ >
+ component_measures.facet_category.overall_category.estimated
+ </span>
+</Fragment>
+`;
+
+exports[`should render correctly with no value 1`] = `""`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly for bugs 1`] = `
+<Fragment>
+ <Link
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/issues",
+ "query": Object {
+ "id": "my-project",
+ "pullRequest": "1001",
+ "resolved": "false",
+ "types": "BUG",
+ },
+ }
+ }
+ >
+ 1
+ </Link>
+ <BugIcon
+ className="big-spacer-left little-spacer-right"
+ />
+ overview.metric.new_bugs
+</Fragment>
+`;
+
+exports[`should render correctly for code smells 1`] = `
+<Fragment>
+ <Link
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/issues",
+ "query": Object {
+ "id": "my-project",
+ "pullRequest": "1001",
+ "resolved": "false",
+ "types": "CODE_SMELL",
+ },
+ }
+ }
+ >
+ 1
+ </Link>
+ <CodeSmellIcon
+ className="big-spacer-left little-spacer-right"
+ />
+ overview.metric.new_code_smells
+</Fragment>
+`;
+
+exports[`should render correctly for vulnerabilities 1`] = `
+<Fragment>
+ <Link
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/issues",
+ "query": Object {
+ "id": "my-project",
+ "pullRequest": "1001",
+ "resolved": "false",
+ "types": "VULNERABILITY",
+ },
+ }
+ }
+ >
+ 1
+ </Link>
+ <VulnerabilityIcon
+ className="big-spacer-left little-spacer-right"
+ />
+ overview.metric.new_vulnerabilities
+</Fragment>
+`;
+
+exports[`should render correctly if no values are present 1`] = `
+<Fragment>
+ <span
+ className="measure-empty"
+ >
+ —
+ </span>
+ <BugIcon
+ className="big-spacer-left little-spacer-right"
+ />
+ overview.metric.new_bugs
+</Fragment>
+`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly for bugs 1`] = `
+<Fragment>
+ <span
+ className="big-spacer-right flex-1"
+ >
+ metric_domain.Reliability
+ </span>
+ <Tooltip
+ overlay="metric.reliability_rating.tooltip.A"
+ >
+ <span>
+ <DrilldownLink
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "base": "master",
+ "branch": "feature/foo/bar",
+ "key": "1001",
+ "title": "Foo Bar feature",
+ }
+ }
+ className="link-no-underline"
+ component="my-project"
+ metric="new_reliability_rating"
+ >
+ <Rating
+ value="1.0"
+ />
+ </DrilldownLink>
+ </span>
+ </Tooltip>
+</Fragment>
+`;
+
+exports[`should render correctly for code smells 1`] = `
+<Fragment>
+ <span
+ className="big-spacer-right flex-1"
+ >
+ metric_domain.Maintainability
+ </span>
+ <Tooltip
+ overlay="metric.sqale_rating.tooltip.A.0.0%"
+ >
+ <span>
+ <DrilldownLink
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "base": "master",
+ "branch": "feature/foo/bar",
+ "key": "1001",
+ "title": "Foo Bar feature",
+ }
+ }
+ className="link-no-underline"
+ component="my-project"
+ metric="new_maintainability_rating"
+ >
+ <Rating
+ value="1.0"
+ />
+ </DrilldownLink>
+ </span>
+ </Tooltip>
+</Fragment>
+`;
+
+exports[`should render correctly for vulnerabilities 1`] = `
+<Fragment>
+ <span
+ className="big-spacer-right flex-1"
+ >
+ metric_domain.Security
+ </span>
+ <Tooltip
+ overlay="metric.security_rating.tooltip.A"
+ >
+ <span>
+ <DrilldownLink
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "base": "master",
+ "branch": "feature/foo/bar",
+ "key": "1001",
+ "title": "Foo Bar feature",
+ }
+ }
+ className="link-no-underline"
+ component="my-project"
+ metric="new_security_rating"
+ >
+ <Rating
+ value="1.0"
+ />
+ </DrilldownLink>
+ </span>
+ </Tooltip>
+</Fragment>
+`;
+
+exports[`should render correctly if no values are present 1`] = `""`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly for SQ 1`] = `
+<div
+ className="quality-gate-badge-large small failed"
+>
+ <div
+ className="display-flex-center"
+ >
+ <span>
+ overview.on_new_code_long
+ </span>
+ <HelpTooltip
+ className="little-spacer-left"
+ overlay={
+ <FormattedMessage
+ defaultMessage="overview.quality_gate.conditions_on_new_code"
+ id="overview.quality_gate.conditions_on_new_code"
+ values={
+ Object {
+ "link": <Link
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/quality_gates/show/30",
+ }
+ }
+ >
+ overview.quality_gate
+ </Link>,
+ }
+ }
+ />
+ }
+ >
+ <HelpIcon
+ fill="rgba(255,255,255,0.62)"
+ size={12}
+ />
+ </HelpTooltip>
+ </div>
+ <h4
+ className="huge-spacer-top huge"
+ >
+ metric.level.ERROR
+ </h4>
+</div>
+`;
+
+exports[`should render correctly for SQ 2`] = `
+<div
+ className="quality-gate-badge-large small success"
+>
+ <div
+ className="display-flex-center"
+ >
+ <span>
+ overview.on_new_code_long
+ </span>
+ <HelpTooltip
+ className="little-spacer-left"
+ overlay={
+ <FormattedMessage
+ defaultMessage="overview.quality_gate.conditions_on_new_code"
+ id="overview.quality_gate.conditions_on_new_code"
+ values={
+ Object {
+ "link": <Link
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/quality_gates/show/30",
+ }
+ }
+ >
+ overview.quality_gate
+ </Link>,
+ }
+ }
+ />
+ }
+ >
+ <HelpIcon
+ fill="rgba(255,255,255,0.62)"
+ size={12}
+ />
+ </HelpTooltip>
+ </div>
+ <h4
+ className="huge-spacer-top huge"
+ >
+ metric.level.OK
+ </h4>
+</div>
+`;
+
+exports[`should render the link correctly for SC 1`] = `
+<div
+ className="quality-gate-badge-large small failed"
+>
+ <div
+ className="display-flex-center"
+ >
+ <span>
+ overview.on_new_code_long
+ </span>
+ <HelpTooltip
+ className="little-spacer-left"
+ overlay={
+ <FormattedMessage
+ defaultMessage="overview.quality_gate.conditions_on_new_code"
+ id="overview.quality_gate.conditions_on_new_code"
+ values={
+ Object {
+ "link": <Link
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/organizations/foo/quality_gates/show/30",
+ }
+ }
+ >
+ overview.quality_gate
+ </Link>,
+ }
+ }
+ />
+ }
+ >
+ <HelpIcon
+ fill="rgba(255,255,255,0.62)"
+ size={12}
+ />
+ </HelpTooltip>
+ </div>
+ <h4
+ className="huge-spacer-top huge"
+ >
+ metric.level.ERROR
+ </h4>
+</div>
+`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly for coverage 1`] = `
+<Fragment>
+ <span
+ className="big-spacer-right"
+ >
+ <CoverageRating
+ size="big"
+ value={1}
+ />
+ </span>
+ <DrilldownLink
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "isMain": false,
+ "mergeBranch": "master",
+ "name": "release-1.0",
+ "type": "SHORT",
+ }
+ }
+ component="my-project"
+ metric="new_coverage"
+ >
+ 1.0%
+ </DrilldownLink>
+ <span
+ className="big-spacer-left"
+ >
+ overview.metric.coverage
+ </span>
+</Fragment>
+`;
+
+exports[`should render correctly for coverage 2`] = `
+<Fragment>
+ <span
+ className="big-spacer-right"
+ >
+ <CoverageRating
+ size="big"
+ value={1}
+ />
+ </span>
+ <DrilldownLink
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "isMain": false,
+ "mergeBranch": "master",
+ "name": "release-1.0",
+ "type": "SHORT",
+ }
+ }
+ component="my-project"
+ metric="new_coverage"
+ >
+ 1.0%
+ </DrilldownLink>
+ <span
+ className="big-spacer-left"
+ >
+ <FormattedMessage
+ defaultMessage="overview.coverage_on_X_lines"
+ id="overview.coverage_on_X_lines"
+ values={
+ Object {
+ "count": <DrilldownLink
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "isMain": false,
+ "mergeBranch": "master",
+ "name": "release-1.0",
+ "type": "SHORT",
+ }
+ }
+ component="my-project"
+ metric="new_lines_to_cover"
+ >
+ 1
+ </DrilldownLink>,
+ }
+ }
+ />
+ </span>
+</Fragment>
+`;
+
+exports[`should render correctly for duplications 1`] = `
+<Fragment>
+ <span
+ className="big-spacer-right"
+ >
+ <DuplicationsRating
+ size="big"
+ value={1}
+ />
+ </span>
+ <DrilldownLink
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "isMain": false,
+ "mergeBranch": "master",
+ "name": "release-1.0",
+ "type": "SHORT",
+ }
+ }
+ component="my-project"
+ metric="new_duplicated_lines_density"
+ >
+ 1.0%
+ </DrilldownLink>
+ <span
+ className="big-spacer-left"
+ >
+ overview.metric.duplications
+ </span>
+</Fragment>
+`;
+
+exports[`should render correctly with no value 1`] = `
+<Fragment>
+ <span>
+ —
+ </span>
+ <span
+ className="big-spacer-left"
+ >
+ overview.metric.coverage
+ </span>
+</Fragment>
+`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly for a failed QG 1`] = `
+<div
+ className="page page-limited"
+>
+ <div
+ className="pr-overview has-conditions"
+ >
+ <div
+ className="pr-overview-quality-gate big-spacer-right"
+ >
+ <h3
+ className="spacer-bottom small"
+ >
+ overview.quality_gate
+ <DocTooltip
+ className="spacer-left"
+ doc={Promise {}}
+ />
+ </h3>
+ <LargeQualityGateBadge
+ component={
+ Object {
+ "breadcrumbs": Array [],
+ "key": "my-project",
+ "name": "MyProject",
+ "organization": "foo",
+ "qualifier": "TRK",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ level="ERROR"
+ />
+ </div>
+ <div
+ className="pr-overview-failed-conditions big-spacer-right"
+ >
+ <h3
+ className="spacer-bottom small"
+ >
+ overview.failed_conditions
+ </h3>
+ <QualityGateConditions
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "base": "master",
+ "branch": "feature/foo/bar",
+ "key": "1001",
+ "title": "Foo Bar feature",
+ }
+ }
+ collapsible={true}
+ component={
+ Object {
+ "breadcrumbs": Array [],
+ "key": "my-project",
+ "name": "MyProject",
+ "organization": "foo",
+ "qualifier": "TRK",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ conditions={
+ Array [
+ Object {
+ "actual": "10",
+ "error": "1.0",
+ "level": "ERROR",
+ "metric": "new_code_smells",
+ "op": "GT",
+ "period": 1,
+ },
+ ]
+ }
+ />
+ </div>
+ <div
+ className="pr-overview-measurements flex-1"
+ >
+ <h3
+ className="spacer-bottom small"
+ >
+ overview.metrics
+ </h3>
+ <div
+ className="pr-overview-measurements-row display-flex-row"
+ key="BUG"
+ >
+ <div
+ className="pr-overview-measurements-value flex-1 small display-flex-center"
+ >
+ <IssueLabel
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "base": "master",
+ "branch": "feature/foo/bar",
+ "key": "1001",
+ "title": "Foo Bar feature",
+ }
+ }
+ className="overview-domain-measure-value"
+ component={
+ Object {
+ "breadcrumbs": Array [],
+ "key": "my-project",
+ "name": "MyProject",
+ "organization": "foo",
+ "qualifier": "TRK",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "metric": "new_bugs ",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "metric": "new_vulnerabilities",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "metric": "new_code_smells",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ ]
+ }
+ type="BUG"
+ />
+ </div>
+ <div
+ className="pr-overview-measurements-rating display-flex-center"
+ >
+ <IssueRating
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "base": "master",
+ "branch": "feature/foo/bar",
+ "key": "1001",
+ "title": "Foo Bar feature",
+ }
+ }
+ component={
+ Object {
+ "breadcrumbs": Array [],
+ "key": "my-project",
+ "name": "MyProject",
+ "organization": "foo",
+ "qualifier": "TRK",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "metric": "new_bugs ",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "metric": "new_vulnerabilities",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "metric": "new_code_smells",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ ]
+ }
+ type="BUG"
+ />
+ </div>
+ </div>
+ <div
+ className="pr-overview-measurements-row display-flex-row"
+ key="VULNERABILITY"
+ >
+ <div
+ className="pr-overview-measurements-value flex-1 small display-flex-center"
+ >
+ <IssueLabel
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "base": "master",
+ "branch": "feature/foo/bar",
+ "key": "1001",
+ "title": "Foo Bar feature",
+ }
+ }
+ className="overview-domain-measure-value"
+ component={
+ Object {
+ "breadcrumbs": Array [],
+ "key": "my-project",
+ "name": "MyProject",
+ "organization": "foo",
+ "qualifier": "TRK",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "metric": "new_bugs ",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "metric": "new_vulnerabilities",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "metric": "new_code_smells",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ ]
+ }
+ type="VULNERABILITY"
+ />
+ </div>
+ <div
+ className="pr-overview-measurements-rating display-flex-center"
+ >
+ <IssueRating
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "base": "master",
+ "branch": "feature/foo/bar",
+ "key": "1001",
+ "title": "Foo Bar feature",
+ }
+ }
+ component={
+ Object {
+ "breadcrumbs": Array [],
+ "key": "my-project",
+ "name": "MyProject",
+ "organization": "foo",
+ "qualifier": "TRK",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "metric": "new_bugs ",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "metric": "new_vulnerabilities",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "metric": "new_code_smells",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ ]
+ }
+ type="VULNERABILITY"
+ />
+ </div>
+ </div>
+ <div
+ className="pr-overview-measurements-row display-flex-row"
+ key="CODE_SMELL"
+ >
+ <div
+ className="pr-overview-measurements-value flex-1 small display-flex-center"
+ >
+ <IssueLabel
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "base": "master",
+ "branch": "feature/foo/bar",
+ "key": "1001",
+ "title": "Foo Bar feature",
+ }
+ }
+ className="overview-domain-measure-value"
+ component={
+ Object {
+ "breadcrumbs": Array [],
+ "key": "my-project",
+ "name": "MyProject",
+ "organization": "foo",
+ "qualifier": "TRK",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "metric": "new_bugs ",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "metric": "new_vulnerabilities",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "metric": "new_code_smells",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ ]
+ }
+ type="CODE_SMELL"
+ />
+ </div>
+ <div
+ className="pr-overview-measurements-rating display-flex-center"
+ >
+ <IssueRating
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "base": "master",
+ "branch": "feature/foo/bar",
+ "key": "1001",
+ "title": "Foo Bar feature",
+ }
+ }
+ component={
+ Object {
+ "breadcrumbs": Array [],
+ "key": "my-project",
+ "name": "MyProject",
+ "organization": "foo",
+ "qualifier": "TRK",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "metric": "new_bugs ",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "metric": "new_vulnerabilities",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "metric": "new_code_smells",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ ]
+ }
+ type="CODE_SMELL"
+ />
+ </div>
+ </div>
+ <div
+ className="pr-overview-measurements-row display-flex-row"
+ key="COVERAGE"
+ >
+ <div
+ className="pr-overview-measurements-value flex-1 small display-flex-center"
+ >
+ <MeasurementLabel
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "base": "master",
+ "branch": "feature/foo/bar",
+ "key": "1001",
+ "title": "Foo Bar feature",
+ }
+ }
+ className="overview-domain-measure-value"
+ component={
+ Object {
+ "breadcrumbs": Array [],
+ "key": "my-project",
+ "name": "MyProject",
+ "organization": "foo",
+ "qualifier": "TRK",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "metric": "new_bugs ",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "metric": "new_vulnerabilities",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "metric": "new_code_smells",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ ]
+ }
+ type="COVERAGE"
+ />
+ </div>
+ <div
+ className="pr-overview-measurements-estimate display-flex-center"
+ >
+ <AfterMergeEstimate
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "metric": "new_bugs ",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "metric": "new_vulnerabilities",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "metric": "new_code_smells",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ ]
+ }
+ type="COVERAGE"
+ />
+ </div>
+ </div>
+ <div
+ className="pr-overview-measurements-row display-flex-row"
+ key="DUPLICATION"
+ >
+ <div
+ className="pr-overview-measurements-value flex-1 small display-flex-center"
+ >
+ <MeasurementLabel
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "base": "master",
+ "branch": "feature/foo/bar",
+ "key": "1001",
+ "title": "Foo Bar feature",
+ }
+ }
+ className="overview-domain-measure-value"
+ component={
+ Object {
+ "breadcrumbs": Array [],
+ "key": "my-project",
+ "name": "MyProject",
+ "organization": "foo",
+ "qualifier": "TRK",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "metric": "new_bugs ",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "metric": "new_vulnerabilities",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "metric": "new_code_smells",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ ]
+ }
+ type="DUPLICATION"
+ />
+ </div>
+ <div
+ className="pr-overview-measurements-estimate display-flex-center"
+ >
+ <AfterMergeEstimate
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "metric": "new_bugs ",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "metric": "new_vulnerabilities",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "metric": "new_code_smells",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ ]
+ }
+ type="DUPLICATION"
+ />
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
+`;
+
+exports[`should render correctly for a passed QG 1`] = `
+<div
+ className="page page-limited"
+>
+ <div
+ className="pr-overview"
+ >
+ <div
+ className="pr-overview-quality-gate big-spacer-right"
+ >
+ <h3
+ className="spacer-bottom small"
+ >
+ overview.quality_gate
+ <DocTooltip
+ className="spacer-left"
+ doc={Promise {}}
+ />
+ </h3>
+ <LargeQualityGateBadge
+ component={
+ Object {
+ "breadcrumbs": Array [],
+ "key": "my-project",
+ "name": "MyProject",
+ "organization": "foo",
+ "qualifier": "TRK",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ level="OK"
+ />
+ </div>
+ <div
+ className="pr-overview-measurements flex-1"
+ >
+ <h3
+ className="spacer-bottom small"
+ >
+ overview.metrics
+ </h3>
+ <div
+ className="pr-overview-measurements-row display-flex-row"
+ key="BUG"
+ >
+ <div
+ className="pr-overview-measurements-value flex-1 small display-flex-center"
+ >
+ <IssueLabel
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "base": "master",
+ "branch": "feature/foo/bar",
+ "key": "1001",
+ "title": "Foo Bar feature",
+ }
+ }
+ className="overview-domain-measure-value"
+ component={
+ Object {
+ "breadcrumbs": Array [],
+ "key": "my-project",
+ "name": "MyProject",
+ "organization": "foo",
+ "qualifier": "TRK",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "metric": "new_bugs ",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "metric": "new_vulnerabilities",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "metric": "new_code_smells",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ ]
+ }
+ type="BUG"
+ />
+ </div>
+ <div
+ className="pr-overview-measurements-rating display-flex-center"
+ >
+ <IssueRating
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "base": "master",
+ "branch": "feature/foo/bar",
+ "key": "1001",
+ "title": "Foo Bar feature",
+ }
+ }
+ component={
+ Object {
+ "breadcrumbs": Array [],
+ "key": "my-project",
+ "name": "MyProject",
+ "organization": "foo",
+ "qualifier": "TRK",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "metric": "new_bugs ",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "metric": "new_vulnerabilities",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "metric": "new_code_smells",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ ]
+ }
+ type="BUG"
+ />
+ </div>
+ </div>
+ <div
+ className="pr-overview-measurements-row display-flex-row"
+ key="VULNERABILITY"
+ >
+ <div
+ className="pr-overview-measurements-value flex-1 small display-flex-center"
+ >
+ <IssueLabel
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "base": "master",
+ "branch": "feature/foo/bar",
+ "key": "1001",
+ "title": "Foo Bar feature",
+ }
+ }
+ className="overview-domain-measure-value"
+ component={
+ Object {
+ "breadcrumbs": Array [],
+ "key": "my-project",
+ "name": "MyProject",
+ "organization": "foo",
+ "qualifier": "TRK",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "metric": "new_bugs ",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "metric": "new_vulnerabilities",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "metric": "new_code_smells",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ ]
+ }
+ type="VULNERABILITY"
+ />
+ </div>
+ <div
+ className="pr-overview-measurements-rating display-flex-center"
+ >
+ <IssueRating
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "base": "master",
+ "branch": "feature/foo/bar",
+ "key": "1001",
+ "title": "Foo Bar feature",
+ }
+ }
+ component={
+ Object {
+ "breadcrumbs": Array [],
+ "key": "my-project",
+ "name": "MyProject",
+ "organization": "foo",
+ "qualifier": "TRK",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "metric": "new_bugs ",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "metric": "new_vulnerabilities",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "metric": "new_code_smells",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ ]
+ }
+ type="VULNERABILITY"
+ />
+ </div>
+ </div>
+ <div
+ className="pr-overview-measurements-row display-flex-row"
+ key="CODE_SMELL"
+ >
+ <div
+ className="pr-overview-measurements-value flex-1 small display-flex-center"
+ >
+ <IssueLabel
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "base": "master",
+ "branch": "feature/foo/bar",
+ "key": "1001",
+ "title": "Foo Bar feature",
+ }
+ }
+ className="overview-domain-measure-value"
+ component={
+ Object {
+ "breadcrumbs": Array [],
+ "key": "my-project",
+ "name": "MyProject",
+ "organization": "foo",
+ "qualifier": "TRK",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "metric": "new_bugs ",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "metric": "new_vulnerabilities",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "metric": "new_code_smells",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ ]
+ }
+ type="CODE_SMELL"
+ />
+ </div>
+ <div
+ className="pr-overview-measurements-rating display-flex-center"
+ >
+ <IssueRating
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "base": "master",
+ "branch": "feature/foo/bar",
+ "key": "1001",
+ "title": "Foo Bar feature",
+ }
+ }
+ component={
+ Object {
+ "breadcrumbs": Array [],
+ "key": "my-project",
+ "name": "MyProject",
+ "organization": "foo",
+ "qualifier": "TRK",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "metric": "new_bugs ",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "metric": "new_vulnerabilities",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "metric": "new_code_smells",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ ]
+ }
+ type="CODE_SMELL"
+ />
+ </div>
+ </div>
+ <div
+ className="pr-overview-measurements-row display-flex-row"
+ key="COVERAGE"
+ >
+ <div
+ className="pr-overview-measurements-value flex-1 small display-flex-center"
+ >
+ <MeasurementLabel
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "base": "master",
+ "branch": "feature/foo/bar",
+ "key": "1001",
+ "title": "Foo Bar feature",
+ }
+ }
+ className="overview-domain-measure-value"
+ component={
+ Object {
+ "breadcrumbs": Array [],
+ "key": "my-project",
+ "name": "MyProject",
+ "organization": "foo",
+ "qualifier": "TRK",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "metric": "new_bugs ",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "metric": "new_vulnerabilities",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "metric": "new_code_smells",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ ]
+ }
+ type="COVERAGE"
+ />
+ </div>
+ <div
+ className="pr-overview-measurements-estimate display-flex-center"
+ >
+ <AfterMergeEstimate
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "metric": "new_bugs ",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "metric": "new_vulnerabilities",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "metric": "new_code_smells",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ ]
+ }
+ type="COVERAGE"
+ />
+ </div>
+ </div>
+ <div
+ className="pr-overview-measurements-row display-flex-row"
+ key="DUPLICATION"
+ >
+ <div
+ className="pr-overview-measurements-value flex-1 small display-flex-center"
+ >
+ <MeasurementLabel
+ branchLike={
+ Object {
+ "analysisDate": "2018-01-01",
+ "base": "master",
+ "branch": "feature/foo/bar",
+ "key": "1001",
+ "title": "Foo Bar feature",
+ }
+ }
+ className="overview-domain-measure-value"
+ component={
+ Object {
+ "breadcrumbs": Array [],
+ "key": "my-project",
+ "name": "MyProject",
+ "organization": "foo",
+ "qualifier": "TRK",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "metric": "new_bugs ",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "metric": "new_vulnerabilities",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "metric": "new_code_smells",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ ]
+ }
+ type="DUPLICATION"
+ />
+ </div>
+ <div
+ className="pr-overview-measurements-estimate display-flex-center"
+ >
+ <AfterMergeEstimate
+ measures={
+ Array [
+ Object {
+ "bestValue": true,
+ "metric": "new_bugs ",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "metric": "new_vulnerabilities",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ Object {
+ "bestValue": true,
+ "metric": "new_code_smells",
+ "periods": Array [
+ Object {
+ "bestValue": true,
+ "index": 1,
+ "value": "1.0",
+ },
+ ],
+ "value": "1.0",
+ },
+ ]
+ }
+ type="DUPLICATION"
+ />
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
+`;
import * as React from 'react';
import * as classNames from 'classnames';
import { Link } from 'react-router';
-import { QualityGateStatusConditionEnhanced } from '../utils';
import DrilldownLink from '../../../components/shared/DrilldownLink';
import Measure from '../../../components/measure/Measure';
import IssueTypeIcon from '../../../components/ui/IssueTypeIcon';
import { getPeriodValue, isDiffMetric, formatMeasure } from '../../../helpers/measures';
import { translate } from '../../../helpers/l10n';
import { getComponentIssuesUrl } from '../../../helpers/urls';
-import { getBranchLikeQuery } from '../../../helpers/branches';
+import { getBranchLikeQuery, isPullRequest, isShortLivingBranch } from '../../../helpers/branches';
interface Props {
branchLike?: T.BranchLike;
component: Pick<T.Component, 'key'>;
- condition: QualityGateStatusConditionEnhanced;
+ condition: T.QualityGateStatusConditionEnhanced;
}
export default class QualityGateCondition extends React.PureComponent<Props> {
const className = classNames(
'overview-quality-gate-condition',
'overview-quality-gate-condition-' + condition.level.toLowerCase(),
- { 'overview-quality-gate-condition-leak': condition.period != null }
+ {
+ 'overview-quality-gate-condition-leak':
+ condition.period != null && !isPullRequest(branchLike) && !isShortLivingBranch(branchLike)
+ }
);
const metricKey = condition.measure.metric.key;
import * as React from 'react';
import { sortBy } from 'lodash';
import QualityGateCondition from './QualityGateCondition';
-import { QualityGateStatusCondition, QualityGateStatusConditionEnhanced } from '../utils';
+import ChevronDownIcon from '../../../components/icons-components/ChevronDownIcon';
+import { ButtonLink } from '../../../components/ui/buttons';
import { getMeasuresAndMeta } from '../../../api/measures';
import { enhanceMeasuresWithMetrics } from '../../../helpers/measures';
import { isSameBranchLike, getBranchLikeQuery } from '../../../helpers/branches';
+import { translateWithParameters } from '../../../helpers/l10n';
const LEVEL_ORDER = ['ERROR', 'WARN'];
interface Props {
branchLike?: T.BranchLike;
component: Pick<T.Component, 'key'>;
- conditions: QualityGateStatusCondition[];
+ collapsible?: boolean;
+ conditions: T.QualityGateStatusCondition[];
}
interface State {
- conditions?: QualityGateStatusConditionEnhanced[];
+ collapsed: boolean;
+ conditions?: T.QualityGateStatusConditionEnhanced[];
loading: boolean;
}
+const MAX_CONDITIONS = 5;
+
export default class QualityGateConditions extends React.PureComponent<Props, State> {
mounted = false;
- state: State = {
- loading: true
- };
+ state: State;
+
+ constructor(props: Props) {
+ super(props);
+ this.state = {
+ collapsed: Boolean(props.collapsible),
+ loading: true
+ };
+ }
componentDidMount() {
this.mounted = true;
this.mounted = false;
}
+ enhanceConditions = (
+ conditions: T.QualityGateStatusCondition[],
+ measures: T.MeasureEnhanced[]
+ ): T.QualityGateStatusConditionEnhanced[] => {
+ return conditions.map(condition => {
+ const measure = measures.find(measure => measure.metric.key === condition.metric)!;
+ return { ...condition, measure };
+ });
+ };
+
loadFailedMeasures() {
const { branchLike, component, conditions } = this.props;
const failedConditions = conditions.filter(c => c.level !== 'OK');
if (this.mounted) {
const measures = enhanceMeasuresWithMetrics(component.measures || [], metrics || []);
this.setState({
- conditions: enhanceConditions(failedConditions, measures),
+ conditions: this.enhanceConditions(failedConditions, measures),
loading: false
});
}
}
}
+ handleToggleCollapse = () => {
+ this.setState(state => ({
+ collapsed: !state.collapsed
+ }));
+ };
+
render() {
const { branchLike, component } = this.props;
- const { loading, conditions } = this.state;
+ const { loading, collapsed, conditions } = this.state;
if (loading || !conditions) {
return null;
}
- const sortedConditions = sortBy(
- conditions,
- condition => LEVEL_ORDER.indexOf(condition.level),
- condition => condition.measure.metric.name
- );
+ const sortedConditions = sortBy(conditions, condition => LEVEL_ORDER.indexOf(condition.level));
+
+ let renderConditions;
+ let renderCollapsed;
+ if (collapsed && sortedConditions.length > MAX_CONDITIONS) {
+ renderConditions = sortedConditions.slice(0, MAX_CONDITIONS);
+ renderCollapsed = true;
+ } else {
+ renderConditions = sortedConditions;
+ renderCollapsed = false;
+ }
return (
<div
className="overview-quality-gate-conditions-list clearfix"
id="overview-quality-gate-conditions-list">
- {sortedConditions.map(condition => (
+ {renderConditions.map(condition => (
<QualityGateCondition
branchLike={branchLike}
component={component}
key={condition.measure.metric.key}
/>
))}
+ {renderCollapsed && (
+ <ButtonLink
+ className="overview-quality-gate-conditions-list-collapse"
+ onClick={this.handleToggleCollapse}>
+ {translateWithParameters(
+ 'overview.X_more_failed_conditions',
+ sortedConditions.length - MAX_CONDITIONS
+ )}
+ <ChevronDownIcon className="little-spacer-left" />
+ </ButtonLink>
+ )}
</div>
);
}
}
-
-function enhanceConditions(
- conditions: QualityGateStatusCondition[],
- measures: T.MeasureEnhanced[]
-): QualityGateStatusConditionEnhanced[] {
- return conditions.map(condition => {
- const measure = measures.find(measure => measure.metric.key === condition.metric)!;
- return { ...condition, measure };
- });
-}
import * as React from 'react';
import { shallow } from 'enzyme';
import QualityGateCondition from '../QualityGateCondition';
-import { QualityGateStatusConditionEnhanced } from '../../utils';
-const mockRatingCondition = (metric: string): QualityGateStatusConditionEnhanced => ({
+const mockRatingCondition = (metric: string): T.QualityGateStatusConditionEnhanced => ({
error: '1',
level: 'ERROR',
measure: {
const periods = [{ value: '3', index: 1 }];
it('open_issues', () => {
- const condition: QualityGateStatusConditionEnhanced = {
+ const condition: T.QualityGateStatusConditionEnhanced = {
error: '0',
level: 'ERROR',
measure: {
});
it('new_open_issues', () => {
- const condition: QualityGateStatusConditionEnhanced = {
+ const condition: T.QualityGateStatusConditionEnhanced = {
error: '0',
level: 'ERROR',
measure: {
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 { shallow } from 'enzyme';
+import QualityGateConditions from '../QualityGateConditions';
+import { getMeasuresAndMeta } from '../../../../api/measures';
+import { mockComponent, mockQualityGateStatusCondition } from '../../../../helpers/testMocks';
+import { waitAndUpdate } from '../../../../helpers/testUtils';
+
+jest.mock('../../../../api/measures', () => {
+ return {
+ getMeasuresAndMeta: jest.fn().mockResolvedValue({
+ component: { measures: [{ metric: 'foo' }] },
+ metrics: [{ key: 'foo' }]
+ })
+ };
+});
+
+it('should render correctly', async () => {
+ const wrapper = shallowRender();
+ await waitAndUpdate(wrapper);
+ expect(getMeasuresAndMeta).toBeCalled();
+ expect(wrapper.find('QualityGateCondition').length).toBe(10);
+});
+
+it('should be collapsible', async () => {
+ const wrapper = shallowRender({ collapsible: true });
+ await waitAndUpdate(wrapper);
+ expect(wrapper.find('QualityGateCondition').length).toBe(5);
+ wrapper.setState({ collapsed: false });
+ await waitAndUpdate(wrapper);
+ expect(wrapper.find('QualityGateCondition').length).toBe(10);
+});
+
+function shallowRender(props: Partial<QualityGateConditions['props']> = {}) {
+ const conditions: T.QualityGateStatusCondition[] = [];
+ for (let i = 10; i > 0; --i) {
+ conditions.push(mockQualityGateStatusCondition());
+ }
+
+ return shallow(
+ <QualityGateConditions component={mockComponent()} conditions={conditions} {...props} />
+ );
+}
align-items: flex-start;
}
+.overview-quality-gate-conditions-list-collapse {
+ margin: calc(2 * var(--gridSize)) 0;
+}
+
.overview-quality-gate-condition {
display: block;
margin-top: 15px;
opacity: 1;
}
}
+
+/*
+ * PRs and SLBs
+ */
+.pr-overview {
+ display: flex;
+ max-width: 960px;
+ margin: 0 auto;
+}
+
+.pr-overview.has-conditions {
+ max-width: 1180px;
+}
+
+.pr-overview h3 {
+ text-transform: uppercase;
+}
+
+.pr-overview-failed-conditions {
+ flex: 0 0 240px;
+}
+
+.pr-overview .overview-quality-gate-condition:first-of-type {
+ margin-top: 0;
+}
+
+.pr-overview .overview-quality-gate-condition {
+ margin-right: 0;
+ margin-top: 12px;
+ box-sizing: border-box;
+ width: 100%;
+}
+
+.pr-overview-measurements-row {
+ min-height: 85px;
+ box-sizing: border-box;
+ background: white;
+ border: 1px solid var(--barBorderColor);
+}
+
+.pr-overview-measurements-row + .pr-overview-measurements-row {
+ border-top: 0;
+}
+
+.pr-overview-measurements-value,
+.pr-overview-measurements-rating,
+.pr-overview-measurements-estimate {
+ padding: calc(3 * var(--gridSize));
+}
+
+.pr-overview-measurements-value .measure-empty {
+ margin-top: -4px;
+}
+
+.pr-overview-measurements-rating,
+.pr-overview-measurements-estimate {
+ flex-basis: 270px;
+ text-align: right;
+ box-sizing: border-box;
+}
+
+@media (max-width: 1200px) {
+ .pr-overview-measurements-rating,
+ .pr-overview-measurements-estimate {
+ flex-basis: 220px;
+ }
+}
+
+.pr-overview-measurements-estimate {
+ background: var(--veryLightGreen);
+}
+
+.pr-overview-measurements-estimate .label {
+ margin-left: var(--gridSize);
+ text-align: right;
+}
+
+.quality-gate-badge-large {
+ width: 240px;
+ min-height: 160px;
+ padding: calc(2 * var(--gridSize));
+ color: var(--transparentWhite);
+ box-sizing: border-box;
+}
+
+.quality-gate-badge-large.failed {
+ background: var(--red);
+}
+
+.quality-gate-badge-large.success {
+ background: var(--green);
+}
+
+.quality-gate-badge-large h4 {
+ color: white;
+}
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { translate } from '../../helpers/l10n';
-
-export interface QualityGateStatusCondition {
- actual?: string;
- error?: string;
- level: string;
- metric: string;
- op: string;
- period?: number;
- warning?: string;
-}
-
-// long name to not mix with Condition from /app/types.ts
-export interface QualityGateStatusConditionEnhanced extends QualityGateStatusCondition {
- measure: T.MeasureEnhanced;
-}
+import CodeSmellIcon from '../../components/icons-components/CodeSmellIcon';
+import VulnerabilityIcon from '../../components/icons-components/VulnerabilityIcon';
+import BugIcon from '../../components/icons-components/BugIcon';
+import CoverageRating from '../../components/ui/CoverageRating';
+import DuplicationsRating from '../../components/ui/DuplicationsRating';
export const METRICS = [
// quality gate
'new_lines'
];
+export const PR_METRICS = [
+ 'coverage',
+ 'new_coverage',
+ 'new_lines_to_cover',
+
+ 'duplicated_lines_density',
+ 'new_duplicated_lines_density',
+ 'new_lines',
+ 'new_code_smells',
+ 'new_maintainability_rating',
+ 'new_bugs',
+ 'new_reliability_rating',
+ 'new_vulnerabilities',
+ 'new_security_rating'
+];
+
export const HISTORY_METRICS_LIST = [
'sqale_index',
'duplicated_lines_density',
'coverage'
];
+export type MeasurementType = 'COVERAGE' | 'DUPLICATION';
+
+export const MEASUREMENTS_MAP = {
+ COVERAGE: {
+ metric: 'new_coverage',
+ linesMetric: 'new_lines_to_cover',
+ afterMergeMetric: 'coverage',
+ labelKey: 'overview.metric.coverage',
+ expandedLabelKey: 'overview.coverage_on_X_lines',
+ iconClass: CoverageRating
+ },
+ DUPLICATION: {
+ metric: 'new_duplicated_lines_density',
+ linesMetric: 'new_lines',
+ afterMergeMetric: 'duplicated_lines_density',
+ labelKey: 'overview.metric.duplications',
+ expandedLabelKey: 'overview.duplications_on_X',
+ iconClass: DuplicationsRating
+ }
+};
+
+export type IssueType = 'CODE_SMELL' | 'VULNERABILITY' | 'BUG';
+
+export const ISSUETYPE_MAP = {
+ CODE_SMELL: {
+ metric: 'new_code_smells',
+ rating: 'new_maintainability_rating',
+ ratingName: 'Maintainability',
+ iconClass: CodeSmellIcon
+ },
+ VULNERABILITY: {
+ metric: 'new_vulnerabilities',
+ rating: 'new_security_rating',
+ ratingName: 'Security',
+ iconClass: VulnerabilityIcon
+ },
+ BUG: {
+ metric: 'new_bugs',
+ rating: 'new_reliability_rating',
+ ratingName: 'Reliability',
+ iconClass: BugIcon
+ }
+};
+
export function getMetricName(metricKey: string) {
return translate('overview.metric', metricKey);
}
+
+export function getRatingName(type: IssueType) {
+ return translate('metric_domain', ISSUETYPE_MAP[type].ratingName);
+}
"query": Object {
"branch": "feature",
"id": "project-key",
- "resolved": "false",
},
}
}
"query": Object {
"branch": "feature",
"id": "project-key",
- "resolved": "false",
},
}
}
"query": Object {
"branch": "feature",
"id": "project-key",
- "resolved": "false",
},
}
}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2019 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.
- */
-.branch-status {
- display: inline-flex;
- justify-content: flex-end;
- align-items: center;
- min-width: 64px;
- line-height: calc(2 * var(--gridSize));
- text-align: right;
-}
-
-.branch-status .status-indicator {
- margin: 0;
-}
*/
import * as React from 'react';
import Level from '../ui/Level';
-import './BranchStatus.css';
interface Props {
branchLike: T.BranchLike;
import { getRatingTooltip as nextGetRatingTooltip, isDiffMetric } from '../../helpers/measures';
import { getLeakPeriod } from '../../helpers/periods';
-const KNOWN_RATINGS = ['sqale_rating', 'reliability_rating', 'security_rating'];
+const KNOWN_RATINGS = [
+ 'sqale_rating',
+ 'maintainability_rating', // Needed to provide the label for "new_maintainability_rating"
+ 'reliability_rating',
+ 'security_rating'
+];
export function enhanceMeasure(
measure: T.Measure,
export function getDisplayMetrics(metrics: T.Metric[]) {
return metrics.filter(metric => !metric.hidden && !['DATA', 'DISTRIB'].includes(metric.type));
}
+
+export function findMeasure(measures: T.Measure[], metric: string) {
+ return measures.find(measure => measure.metric === metric);
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.
+ */
+export function extractStatusConditionsFromProjectStatus(
+ status: T.QualityGateProjectStatus
+): T.QualityGateStatusCondition[] {
+ const { conditions } = status.projectStatus;
+ return conditions
+ ? conditions.map(c => ({
+ actual: c.actualValue,
+ error: c.errorThreshold,
+ level: c.status,
+ metric: c.metricKey,
+ op: c.comparator,
+ period: c.periodIndex
+ }))
+ : [];
+}
};
}
+export function mockQualityGateStatusCondition(
+ overrides: Partial<T.QualityGateStatusCondition> = {}
+): T.QualityGateStatusCondition {
+ return {
+ actual: '10',
+ error: '0',
+ level: 'ERROR',
+ metric: 'foo',
+ op: 'GT',
+ ...overrides
+ };
+}
+
export function mockCurrentUser(overrides: Partial<T.LoggedInUser> = {}): T.LoggedInUser {
return {
groups: [],
};
}
+export function mockMeasure(overrides: Partial<T.Measure> = {}): T.Measure {
+ return {
+ bestValue: true,
+ metric: 'bugs',
+ periods: [
+ {
+ bestValue: true,
+ index: 1,
+ value: '1.0'
+ }
+ ],
+ value: '1.0',
+ ...overrides
+ };
+}
+
export function mockOrganization(overrides: Partial<T.Organization> = {}): T.Organization {
return { key: 'foo', name: 'Foo', ...overrides };
}
analysisDate: '2018-01-01',
base: 'master',
branch: 'feature/foo/bar',
- status: {
- bugs: 0,
- codeSmells: 0,
- qualityGateStatus: 'OK',
- vulnerabilities: 0
- },
key: '1001',
title: 'Foo Bar feature',
...overrides
};
}
+export function mockQualityGateProjectStatus(
+ overrides: Partial<T.QualityGateProjectStatus['projectStatus']> = {}
+): T.QualityGateProjectStatus {
+ return {
+ projectStatus: {
+ conditions: [
+ {
+ actualValue: '0',
+ comparator: 'GT',
+ errorThreshold: '1.0',
+ metricKey: 'new_bugs',
+ periodIndex: 1,
+ status: 'OK'
+ }
+ ],
+ ignoredConditions: false,
+ status: 'OK',
+ ...overrides
+ }
+ };
+}
+
export function mockRouter(overrides: { push?: Function; replace?: Function } = {}) {
return {
createHref: jest.fn(),
isMain: false,
name: 'release-1.0',
mergeBranch: 'master',
- status: {
- bugs: 0,
- codeSmells: 0,
- qualityGateStatus: 'OK',
- vulnerabilities: 0
- },
type: 'SHORT',
...overrides
};
analysisDate: '2018-01-01',
isMain: false,
name: 'master',
- status: {
- qualityGateStatus: 'OK'
- },
type: 'LONG',
...overrides
};
}
export function getShortLivingBranchUrl(project: string, branch: string): Location {
- return { pathname: '/dashboard', query: { branch, id: project, resolved: 'false' } };
+ return { pathname: '/dashboard', query: { branch, id: project } };
}
export function getPullRequestUrl(project: string, pullRequest: string): Location {
- return { pathname: '/dashboard', query: { id: project, pullRequest, resolved: 'false' } };
+ return { pathname: '/dashboard', query: { id: project, pullRequest } };
}
/**
# OVERVIEW
#
#------------------------------------------------------------------------------
+overview.failed_conditions=Failed conditions
+overview.X_more_failed_conditions={0} more failed conditions
+overview.metrics=Metrics
overview.quality_gate=Quality Gate
overview.quality_gate_x=Quality Gate: {0}
overview.quality_gate_failed_with_x=with {0} errors
overview.you_should_define_quality_gate=You should define a quality gate on this project.
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.
+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_profiles=Quality Profiles
overview.new_code_period_x=New code: {0}
overview.started_x=started {0}
overview.started_on_x=Started on {0}
overview.previous_analysis_on_x=Previous analysis on {0}
overview.on_new_code=On New Code
+overview.on_new_code_long=Conditions on New Code
overview.about_this_portfolio=About This Portfolio
overview.about_this_project.APP=About This Application
overview.about_this_project.TRK=About This Project
overview.metric.new_lines_to_cover=New Lines to Cover
overview.metric.files=Files
overview.coverage_on=Coverage on
+overview.coverage_on_X_lines=Coverage on {count} New Lines to cover
overview.duplications_on=Duplications on
+overview.duplications_on_X=Duplications on {count} New Lines
overview.period.previous_version=since {0}
overview.period.previous_version_only_date=since previous version