* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { BasicSeparator, Card, Spinner } from 'design-system';
+import { Spinner } from '@sonarsource/echoes-react';
+import { BasicSeparator, Card } from 'design-system';
import * as React from 'react';
import { MetricKey } from '~sonar-aligned/types/metrics';
import GraphsHeader from '../../../components/activity-graph/GraphsHeader';
import { parseDate } from '../../../helpers/dates';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { localizeMetric } from '../../../helpers/measures';
+import { useIsLegacyCCTMode } from '../../../queries/settings';
import { BranchLike } from '../../../types/branch-like';
import {
Analysis as AnalysisType,
metrics,
} = props;
- const displayedMetrics = getDisplayedHistoryMetrics(graph, []);
+ const { data: isLegacy = false } = useIsLegacyCCTMode();
+ const displayedMetrics = getDisplayedHistoryMetrics(graph, [], isLegacy);
const series = generateSeries(measuresHistory, graph, metrics, displayedMetrics);
const graphs = splitSeriesInGraphs(series, MAX_GRAPH_NB, MAX_SERIES_PER_GRAPH);
let shownLeakPeriodDate;
<BasicSeparator className="sw-mb-4 sw-mt-16" />
- <Spinner loading={loading}>
+ <Spinner isLoading={loading}>
{displayedAnalyses.length === 0 ? (
<p>{translate('no_results')}</p>
) : (
useApplicationQualityGateStatus,
useProjectQualityGateStatus,
} from '../../../queries/quality-gates';
+import { useIsLegacyCCTMode } from '../../../queries/settings';
import { ApplicationPeriod } from '../../../types/application';
import { Branch, BranchLike } from '../../../types/branch-like';
import { Analysis, GraphType, MeasureHistory } from '../../../types/project-activity';
export default function BranchOverview(props: Readonly<Props>) {
const { component, branch, branchesEnabled } = props;
+ const { data: isLegacy = false } = useIsLegacyCCTMode();
const { graph: initialGraph } = getActivityGraph(
BRANCH_OVERVIEW_ACTIVITY_GRAPH,
props.component.key,
};
const loadHistoryMeasures = React.useCallback(() => {
- const graphMetrics = getHistoryMetrics(graph, []);
+ const graphMetrics = getHistoryMetrics(graph, [], isLegacy);
const metrics = uniq([...HISTORY_METRICS_LIST, ...graphMetrics]);
return getAllTimeMachineData({
}, [branch, loadHistory, loadStatus]);
const projectIsEmpty =
- loadingStatus === false &&
- (measures === undefined ||
- measures.find((measure) =>
- ([MetricKey.lines, MetricKey.new_lines] as string[]).includes(measure.metric.key),
- ) === undefined);
+ !loadingStatus &&
+ measures?.find((measure) =>
+ ([MetricKey.lines, MetricKey.new_lines] as string[]).includes(measure.metric.key),
+ ) === undefined;
return (
<BranchOverviewRenderer
import { useLocation, useRouter } from '~sonar-aligned/components/hoc/withRouter';
import { isPortfolioLike } from '~sonar-aligned/helpers/component';
import { ComponentQualifier } from '~sonar-aligned/types/component';
+import { MetricKey } from '~sonar-aligned/types/metrics';
import { useAvailableFeatures } from '../../../app/components/available-features/withAvailableFeatures';
import { CurrentUserContext } from '../../../app/components/current-user/CurrentUserContext';
import AnalysisMissingInfoMessage from '../../../components/shared/AnalysisMissingInfoMessage';
import { CodeScope } from '../../../helpers/urls';
import { useProjectAiCodeAssuredQuery } from '../../../queries/ai-code-assurance';
import { useDismissNoticeMutation } from '../../../queries/users';
-import { MetricKey } from '../../../sonar-aligned/types/metrics';
import { ApplicationPeriod } from '../../../types/application';
import { Branch } from '../../../types/branch-like';
import { isProject } from '../../../types/component';
qualityGate?: QualityGate;
}
-export default function BranchOverviewRenderer(props: BranchOverviewRendererProps) {
+export default function BranchOverviewRenderer(props: Readonly<BranchOverviewRendererProps>) {
const {
analyses,
appLeak,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import styled from '@emotion/styled';
-import { LinkHighlight, LinkStandalone, Tooltip } from '@sonarsource/echoes-react';
-import { Badge, TextBold, TextSubdued, themeColor } from 'design-system';
+import { LinkHighlight, LinkStandalone, Text, Tooltip } from '@sonarsource/echoes-react';
+import { Badge, themeColor } from 'design-system';
import * as React from 'react';
import { FormattedMessage, useIntl } from 'react-intl';
import { formatMeasure } from '~sonar-aligned/helpers/measures';
>
<div className="sw-flex sw-items-center">
<ColorBold className="sw-typo-semibold">
- {intl.formatMessage({ id: `software_quality.${softwareQuality}` })}
+ {!isLegacy && intl.formatMessage({ id: `software_quality.${softwareQuality}` })}
+ {alternativeMeasure && isLegacy && alternativeMeasure.metric.name}
</ColorBold>
{failed && (
<Badge className="sw-h-fit sw-ml-2" variant="deleted">
<div className="sw-flex sw-mt-4">
<div className="sw-flex sw-gap-1 sw-items-center">
{count ? (
- <Tooltip content={countTooltipOverlay}>
+ <Tooltip content={countTooltipOverlay} isOpen={isLegacy ? false : undefined}>
<LinkStandalone
data-testid={`overview__software-impact-${softwareQuality}`}
aria-label={intl.formatMessage(
</LinkStandalone>
</Tooltip>
) : (
- <StyledDash className="sw-font-bold" name="-" />
+ <StyledDash isHighlighted>-</StyledDash>
)}
- <TextSubdued className="sw-self-end sw-typo-default sw-pb-1">
+ <Text isSubdued className="sw-self-end sw-typo-default sw-pb-1">
{intl.formatMessage({ id: 'overview.measures.software_impact.total_open_issues' })}
- </TextSubdued>
+ </Text>
</div>
<div className="sw-flex-grow sw-flex sw-justify-end">
);
}
-const StyledDash = styled(TextBold)`
+const StyledDash = styled(Text)`
font-size: 36px;
`;
const ColorBold = styled.h2`
import * as React from 'react';
import { useCallback } from 'react';
import { useIntl } from 'react-intl';
+import { MetricKey } from '~sonar-aligned/types/metrics';
import RatingComponent from '../../../app/components/metrics/RatingComponent';
-import { MetricKey } from '../../../sonar-aligned/types/metrics';
+import RatingTooltipContent from '../../../components/measure/RatingTooltipContent';
+import { useIsLegacyCCTMode } from '../../../queries/settings';
import { Branch } from '../../../types/branch-like';
import { SoftwareImpactSeverity, SoftwareQuality } from '../../../types/clean-code-taxonomy';
export function SoftwareImpactMeasureRating(props: Readonly<SoftwareImpactMeasureRatingProps>) {
const { ratingMetricKey, componentKey, softwareQuality, branch } = props;
+ const { data: isLegacy = false } = useIsLegacyCCTMode();
const intl = useIntl();
const getSoftwareImpactRatingTooltip = useCallback(
- (rating: RatingEnum) => {
+ (rating: RatingEnum, value: string | undefined) => {
if (rating === undefined) {
return null;
}
+ if (isLegacy && value !== undefined) {
+ return <RatingTooltipContent metricKey={ratingMetricKey} value={value} />;
+ }
+
function ratingToWorseSeverity(rating: string): SoftwareImpactSeverity {
return (
{
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { screen } from '@testing-library/react';
+import { UserEvent } from '@testing-library/user-event/dist/types/setup/setup';
import * as React from 'react';
+import SettingsServiceMock from '../../../../api/mocks/SettingsServiceMock';
import { mockComponent } from '../../../../helpers/mocks/component';
import {
mockAnalysis,
DefinitionChangeType,
ProjectAnalysisEventCategory,
} from '../../../../types/project-activity';
+import { SettingsKey } from '../../../../types/settings';
import ActivityPanel, { ActivityPanelProps } from '../ActivityPanel';
-it('should render correctly', async () => {
- const user = userEvent.setup();
+const settingsHandler = new SettingsServiceMock();
+
+afterEach(() => {
+ settingsHandler.reset();
+});
+
+async function expectGraphs(user: UserEvent) {
renderActivityPanel();
expect(await screen.findAllByText('metric.level.ERROR')).toHaveLength(2);
expect(screen.getByText('event.sqUpgrade10.2')).toBeInTheDocument();
// Checking measures variations
- expect(screen.getAllByText(/% project_activity\.graphs\.coverage$/)).toHaveLength(3);
+ expect(await screen.findAllByText(/% project_activity\.graphs\.coverage$/)).toHaveLength(3);
expect(screen.getAllByText(/% project_activity\.graphs\.duplications$/)).toHaveLength(3);
// Analysis 1 (latest)
expect(screen.getByText(/^-5 project_activity\.graphs\.issues$/)).toBeInTheDocument();
),
).toBeInTheDocument();
+ return user;
+}
+
+async function expectQualityGate(user: UserEvent) {
await user.click(
screen.getByRole('link', {
name: 'quality_profiles.page_title_changelog_x.QP-test: 1 new rule, 2 modified rules, and 3 removed rules',
);
expect(await screen.findByText('QP-test java')).toBeInTheDocument();
+}
+
+it('should render correctly', async () => {
+ const user = userEvent.setup();
+ await expectGraphs(user);
+
+ expect(screen.queryByText('metric.code_smells.name')).not.toBeInTheDocument();
+ expect(screen.queryByText('metric.vulnerabilities.name')).not.toBeInTheDocument();
+ expect(screen.queryByText('metric.bugs.name')).not.toBeInTheDocument();
+
+ await expectQualityGate(user);
+});
+
+it('should render correctly for legacy mode', async () => {
+ settingsHandler.set(SettingsKey.MQRMode, 'false');
+ const user = userEvent.setup();
+ await expectGraphs(user);
+
+ expect(screen.getByText('metric.code_smells.name')).toBeInTheDocument();
+ expect(screen.getByText('metric.vulnerabilities.name')).toBeInTheDocument();
+ expect(screen.getByText('metric.bugs.name')).toBeInTheDocument();
+
+ await expectQualityGate(user);
});
function renderActivityPanel() {
const mockedMeasureHistory = [
+ mockMeasureHistory({
+ metric: MetricKey.vulnerabilities,
+ history: [
+ mockHistoryItem({ date: parseDate('2018-10-27T10:21:15+0200'), value: '200' }),
+ mockHistoryItem({ date: parseDate('2018-10-27T12:21:15+0200'), value: '200' }),
+ mockHistoryItem({ date: parseDate('2020-10-27T16:33:50+0200'), value: '100' }),
+ mockHistoryItem({ date: parseDate('2020-10-27T18:33:50+0200'), value: '95' }),
+ ],
+ }),
+ mockMeasureHistory({
+ metric: MetricKey.code_smells,
+ history: [
+ mockHistoryItem({ date: parseDate('2018-10-27T10:21:15+0200'), value: '200' }),
+ mockHistoryItem({ date: parseDate('2018-10-27T12:21:15+0200'), value: '200' }),
+ mockHistoryItem({ date: parseDate('2020-10-27T16:33:50+0200'), value: '100' }),
+ mockHistoryItem({ date: parseDate('2020-10-27T18:33:50+0200'), value: '95' }),
+ ],
+ }),
+ mockMeasureHistory({
+ metric: MetricKey.bugs,
+ history: [
+ mockHistoryItem({ date: parseDate('2018-10-27T10:21:15+0200'), value: '200' }),
+ mockHistoryItem({ date: parseDate('2018-10-27T12:21:15+0200'), value: '200' }),
+ mockHistoryItem({ date: parseDate('2020-10-27T16:33:50+0200'), value: '100' }),
+ mockHistoryItem({ date: parseDate('2020-10-27T18:33:50+0200'), value: '95' }),
+ ],
+ }),
mockMeasureHistory({
metric: MetricKey.violations,
history: [
selectedMetrics?: string[];
}
-export default function GraphsHeader(props: Props) {
+export default function GraphsHeader(props: Readonly<Props>) {
const {
className,
graph,
customMetrics,
);
});
+ it('should return Legacy graphs', () => {
+ expect(utils.getDisplayedHistoryMetrics(GraphType.issues, [], true)).toEqual([
+ MetricKey.bugs,
+ MetricKey.code_smells,
+ MetricKey.vulnerabilities,
+ ]);
+ });
});
describe('getHistoryMetrics', () => {
]);
expect(utils.getHistoryMetrics(GraphType.custom, customMetrics)).toEqual(customMetrics);
});
+ it('should return legacy metrics', () => {
+ expect(utils.getHistoryMetrics(utils.DEFAULT_GRAPH, [], true)).toEqual([
+ MetricKey.bugs,
+ MetricKey.code_smells,
+ MetricKey.vulnerabilities,
+ MetricKey.reliability_rating,
+ MetricKey.security_rating,
+ MetricKey.sqale_rating,
+ ]);
+ });
});
describe('hasHistoryData', () => {
[GraphType.duplications]: [MetricKey.ncloc, MetricKey.duplicated_lines],
};
+const LEGACY_GRAPHS_METRICS_DISPLAYED: Dict<string[]> = {
+ ...GRAPHS_METRICS_DISPLAYED,
+ [GraphType.issues]: [MetricKey.bugs, MetricKey.code_smells, MetricKey.vulnerabilities],
+};
+
const GRAPHS_METRICS: Dict<string[]> = {
[GraphType.issues]: GRAPHS_METRICS_DISPLAYED[GraphType.issues].concat([
MetricKey.reliability_rating,
],
};
+const LEGACY_GRAPHS_METRICS: Dict<string[]> = {
+ ...GRAPHS_METRICS,
+ [GraphType.issues]: LEGACY_GRAPHS_METRICS_DISPLAYED[GraphType.issues].concat([
+ MetricKey.reliability_rating,
+ MetricKey.security_rating,
+ MetricKey.sqale_rating,
+ ]),
+};
+
export const LINE_CHART_DASHES = [0, 3, 7];
export function isCustomGraph(graph: GraphType) {
return series.length > 0 ? series[0].type : MetricType.Integer;
}
-export function getDisplayedHistoryMetrics(graph: GraphType, customMetrics: string[]) {
- return isCustomGraph(graph) ? customMetrics : GRAPHS_METRICS_DISPLAYED[graph];
+export function getDisplayedHistoryMetrics(
+ graph: GraphType,
+ customMetrics: string[],
+ isLegacy = false,
+) {
+ if (isCustomGraph(graph)) {
+ return customMetrics;
+ }
+
+ return isLegacy ? LEGACY_GRAPHS_METRICS_DISPLAYED[graph] : GRAPHS_METRICS_DISPLAYED[graph];
}
-export function getHistoryMetrics(graph: GraphType, customMetrics: string[]) {
- return isCustomGraph(graph) ? customMetrics : GRAPHS_METRICS[graph];
+export function getHistoryMetrics(graph: GraphType, customMetrics: string[], isLegacy = false) {
+ if (isCustomGraph(graph)) {
+ return customMetrics;
+ }
+ return isLegacy ? LEGACY_GRAPHS_METRICS[graph] : GRAPHS_METRICS[graph];
}
export function hasHistoryDataValue(series: Serie[]) {
@Override
public Optional<Boolean> getValue() {
PropertyDto property = dbClient.propertiesDao().selectGlobalProperty(MULTI_QUALITY_MODE_ENABLED);
- return Optional.of(property != null && Boolean.parseBoolean(property.getValue()));
+ return property == null ? Optional.of(true) : Optional.of(Boolean.valueOf(property.getValue()));
}
}
public static List<PropertyDefinition> all() {
return Collections.singletonList(
PropertyDefinition.builder(MULTI_QUALITY_MODE_ENABLED)
- .defaultValue(Boolean.FALSE.toString())
+ .defaultValue(Boolean.TRUE.toString())
.name("Enable Multi-Quality Rule Mode")
.description("Aims to more accurately represent the impact software has on all software qualities. " +
"It does this by mapping rules to every software quality they can impact, not just the one " +