exports[`getCodeMetrics should return the right metrics for portfolios 1`] = `
[
"releasability_rating",
- "releasability_rating_new",
+ "software_quality_releasability_rating",
"new_security_rating",
- "new_security_rating_new",
+ "new_software_quality_security_rating",
"new_reliability_rating",
- "new_reliability_rating_new",
+ "new_software_quality_reliability_rating",
"new_maintainability_rating",
- "new_maintainability_rating_new",
+ "new_software_quality_maintainability_rating",
"new_security_review_rating",
- "new_security_review_rating_new",
+ "new_software_quality_security_review_rating",
"new_lines",
"releasability_rating",
- "releasability_rating_new",
+ "software_quality_releasability_rating",
"security_rating",
- "security_rating_new",
+ "software_quality_security_rating",
"reliability_rating",
- "reliability_rating_new",
+ "software_quality_reliability_rating",
"sqale_rating",
- "sqale_rating_new",
+ "software_quality_maintainability_rating",
"security_review_rating",
- "security_review_rating_new",
+ "software_quality_security_review_rating",
"ncloc",
]
`;
exports[`getCodeMetrics should return the right metrics for portfolios 2`] = `
[
"releasability_rating",
- "releasability_rating_new",
+ "software_quality_releasability_rating",
"new_security_rating",
- "new_security_rating_new",
+ "new_software_quality_security_rating",
"new_reliability_rating",
- "new_reliability_rating_new",
+ "new_software_quality_reliability_rating",
"new_maintainability_rating",
- "new_maintainability_rating_new",
+ "new_software_quality_maintainability_rating",
"new_security_review_rating",
- "new_security_review_rating_new",
+ "new_software_quality_security_review_rating",
"new_lines",
"releasability_rating",
- "releasability_rating_new",
+ "software_quality_releasability_rating",
"security_rating",
- "security_rating_new",
+ "software_quality_security_rating",
"reliability_rating",
- "reliability_rating_new",
+ "software_quality_reliability_rating",
"sqale_rating",
- "sqale_rating_new",
+ "software_quality_maintainability_rating",
"security_review_rating",
- "security_review_rating_new",
+ "software_quality_security_review_rating",
"ncloc",
"alert_status",
]
exports[`getCodeMetrics should return the right metrics for portfolios 3`] = `
[
"releasability_rating",
- "releasability_rating_new",
+ "software_quality_releasability_rating",
"new_security_rating",
- "new_security_rating_new",
+ "new_software_quality_security_rating",
"new_reliability_rating",
- "new_reliability_rating_new",
+ "new_software_quality_reliability_rating",
"new_maintainability_rating",
- "new_maintainability_rating_new",
+ "new_software_quality_maintainability_rating",
"new_security_review_rating",
- "new_security_review_rating_new",
+ "new_software_quality_security_review_rating",
"new_lines",
"alert_status",
]
exports[`getCodeMetrics should return the right metrics for portfolios 4`] = `
[
"releasability_rating",
- "releasability_rating_new",
+ "software_quality_releasability_rating",
"security_rating",
- "security_rating_new",
+ "software_quality_security_rating",
"reliability_rating",
- "reliability_rating_new",
+ "software_quality_reliability_rating",
"sqale_rating",
- "sqale_rating_new",
+ "software_quality_maintainability_rating",
"security_review_rating",
- "security_review_rating_new",
+ "software_quality_security_review_rating",
"ncloc",
"alert_status",
]
const PORTFOLIO_METRICS = [
MetricKey.releasability_rating,
- MetricKey.releasability_rating_new,
+ MetricKey.software_quality_releasability_rating,
MetricKey.security_rating,
- MetricKey.security_rating_new,
+ MetricKey.software_quality_security_rating,
MetricKey.reliability_rating,
- MetricKey.reliability_rating_new,
+ MetricKey.software_quality_reliability_rating,
MetricKey.sqale_rating,
- MetricKey.sqale_rating_new,
+ MetricKey.software_quality_maintainability_rating,
MetricKey.security_review_rating,
- MetricKey.security_review_rating_new,
+ MetricKey.software_quality_security_review_rating,
MetricKey.ncloc,
];
const NEW_PORTFOLIO_METRICS = [
MetricKey.releasability_rating,
- MetricKey.releasability_rating_new,
+ MetricKey.software_quality_releasability_rating,
MetricKey.new_security_rating,
- MetricKey.new_security_rating_new,
+ MetricKey.new_software_quality_security_rating,
MetricKey.new_reliability_rating,
- MetricKey.new_reliability_rating_new,
+ MetricKey.new_software_quality_reliability_rating,
MetricKey.new_maintainability_rating,
- MetricKey.new_maintainability_rating_new,
+ MetricKey.new_software_quality_maintainability_rating,
MetricKey.new_security_review_rating,
- MetricKey.new_security_review_rating_new,
+ MetricKey.new_software_quality_security_review_rating,
MetricKey.new_lines,
];
import { getApplicationDetails, getApplicationLeak } from '../../../api/application';
import { getMeasuresWithPeriodAndMetrics } from '../../../api/measures';
import { getProjectActivity } from '../../../api/projectActivity';
-import {
- fetchQualityGate,
- getApplicationQualityGate,
- getGateForProject,
- getQualityGateProjectStatus,
-} from '../../../api/quality-gates';
+import { fetchQualityGate, getGateForProject } from '../../../api/quality-gates';
import { getAllTimeMachineData } from '../../../api/time-machine';
import {
getActivityGraph,
extractStatusConditionsFromProjectStatus,
} from '../../../helpers/qualityGates';
import { isDefined } from '../../../helpers/types';
+import { useMeasuresAndLeakQuery } from '../../../queries/measures';
+import {
+ useApplicationQualityGateStatus,
+ useProjectQualityGateStatus,
+} from '../../../queries/quality-gates';
import { ApplicationPeriod } from '../../../types/application';
import { Branch, BranchLike } from '../../../types/branch-like';
import { Analysis, GraphType, MeasureHistory } from '../../../types/project-activity';
import { BRANCH_OVERVIEW_METRICS, HISTORY_METRICS_LIST, Status } from '../utils';
import BranchOverviewRenderer from './BranchOverviewRenderer';
+// TODO: remove this once backend ready
+const NEW_METRICS = [
+ MetricKey.software_quality_maintainability_rating,
+ MetricKey.software_quality_security_rating,
+ MetricKey.new_software_quality_security_rating,
+ MetricKey.software_quality_reliability_rating,
+ MetricKey.new_software_quality_reliability_rating,
+ MetricKey.software_quality_security_review_rating,
+ MetricKey.new_software_quality_security_review_rating,
+ MetricKey.new_software_quality_maintainability_rating,
+];
+
interface Props {
branch?: Branch;
branchesEnabled?: boolean;
component: Component;
}
-interface State {
- analyses?: Analysis[];
- appLeak?: ApplicationPeriod;
- detectedCIOnLastAnalysis?: boolean;
- graph: GraphType;
- loadingHistory?: boolean;
- loadingStatus?: boolean;
- measures?: MeasureEnhanced[];
- measuresHistory?: MeasureHistory[];
- metrics?: Metric[];
- period?: Period;
- qgStatuses?: QualityGateStatus[];
- qualityGate?: QualityGate;
-}
-
export const BRANCH_OVERVIEW_ACTIVITY_GRAPH = 'sonar_branch_overview.graph';
export const NO_CI_DETECTED = 'undetected';
// Get all history data over the past year.
const FROM_DATE = toISO8601WithOffsetString(new Date().setFullYear(new Date().getFullYear() - 1));
-export default class BranchOverview extends React.PureComponent<Props, State> {
- mounted = false;
- state: State;
-
- constructor(props: Props) {
- super(props);
-
- const { graph } = getActivityGraph(BRANCH_OVERVIEW_ACTIVITY_GRAPH, props.component.key);
- this.state = { graph };
- }
-
- componentDidMount() {
- this.mounted = true;
- this.loadStatus();
- this.loadHistory();
- }
-
- componentDidUpdate(prevProps: Props) {
- if (prevProps.branch !== this.props.branch) {
- this.loadStatus();
- this.loadHistory();
- }
- }
-
- componentWillUnmount() {
- this.mounted = false;
- }
-
- loadStatus = () => {
- if (this.props.component.qualifier === ComponentQualifier.Application) {
- this.loadApplicationStatus();
- } else {
- this.loadProjectStatus();
- this.loadProjectQualityGate();
- }
+export default function BranchOverview(props: Readonly<Props>) {
+ const { component, branch, branchesEnabled } = props;
+ const { graph: initialGraph } = getActivityGraph(
+ BRANCH_OVERVIEW_ACTIVITY_GRAPH,
+ props.component.key,
+ );
+ const [graph, setGraph] = React.useState<GraphType>(initialGraph);
+ const [loadingStatus, setLoadingStatus] = React.useState<boolean>(true);
+ const [appLeak, setAppLeak] = React.useState<ApplicationPeriod | undefined>(undefined);
+ const [measures, setMeasures] = React.useState<MeasureEnhanced[] | undefined>(undefined);
+ const [metrics, setMetrics] = React.useState<Metric[] | undefined>(undefined);
+ const [period, setPeriod] = React.useState<Period | undefined>(undefined);
+ const [qgStatuses, setQgStatuses] = React.useState<QualityGateStatus[] | undefined>(undefined);
+ const [loadingHistory, setLoadingHistory] = React.useState<boolean>(true);
+ const [analyses, setAnalyses] = React.useState<Analysis[] | undefined>(undefined);
+ const [detectedCIOnLastAnalysis, setDetectedCIOnLastAnalysis] = React.useState<
+ boolean | undefined
+ >(undefined);
+ const [qualityGate, setQualityGate] = React.useState<QualityGate | undefined>(undefined);
+ const [measuresHistory, setMeasuresHistory] = React.useState<MeasureHistory[] | undefined>(
+ undefined,
+ );
+
+ const { data: projectQualityGateStatus } = useProjectQualityGateStatus(
+ {
+ projectKey: component.key,
+ branchParameters: getBranchLikeQuery(branch),
+ },
+ { enabled: component.qualifier === ComponentQualifier.Project },
+ );
+
+ const { data: applicationQualityGateStatus } = useApplicationQualityGateStatus(
+ { application: component.key, ...getBranchLikeQuery(branch) },
+ { enabled: component.qualifier === ComponentQualifier.Application },
+ );
+
+ const { data: measuresAndLeak } = useMeasuresAndLeakQuery({
+ componentKey: component.key,
+ metricKeys:
+ component.qualifier === ComponentQualifier.Project
+ ? projectQualityGateStatus?.conditions !== undefined
+ ? uniq([
+ ...BRANCH_OVERVIEW_METRICS,
+ ...projectQualityGateStatus.conditions.map((c) => c.metricKey),
+ ])
+ : BRANCH_OVERVIEW_METRICS
+ : BRANCH_OVERVIEW_METRICS,
+ branchParameters: getBranchLikeQuery(branch),
+ });
+
+ const getEnhancedConditions = (
+ conditions: QualityGateStatusCondition[],
+ measures: MeasureEnhanced[],
+ ) => {
+ return (
+ conditions
+ // Enhance them with Metric information, which will be needed
+ // to render the conditions properly.
+ .map((c) => enhanceConditionWithMeasure(c, measures))
+ // The enhancement will return undefined if it cannot find the
+ // appropriate measure. Make sure we filter them out.
+ .filter(isDefined)
+ );
};
- loadApplicationStatus = async () => {
- const { branch, component } = this.props;
- this.setState({ loadingStatus: true });
- // Start by loading the application quality gate info, as well as the meta
- // data for the application as a whole.
- const appStatus = await getApplicationQualityGate({
- application: component.key,
- ...getBranchLikeQuery(branch),
- });
- const {
- measures: appMeasures,
- metrics,
- period,
- } = await this.loadMeasuresAndMeta(component.key, branch);
+ const loadApplicationStatus = React.useCallback(async () => {
+ if (!measuresAndLeak || !applicationQualityGateStatus) {
+ return;
+ }
+ const { component: componentMeasures, metrics, period } = measuresAndLeak;
+ const appMeasures = componentMeasures.measures
+ ? enhanceMeasuresWithMetrics(componentMeasures.measures, metrics)
+ : [];
const appBranchName =
(branch && !isMainBranch(branch) && getBranchLikeDisplayName(branch)) || undefined;
const appDetails = await getApplicationDetails(component.key, appBranchName);
+ setLoadingStatus(true);
// We also need to load the application leak periods separately.
getApplicationLeak(component.key, appBranchName).then(
(leaks) => {
- if (this.mounted && leaks && leaks.length) {
+ if (leaks && leaks.length) {
const sortedLeaks = sortBy(leaks, (leak) => {
return new Date(leak.date);
});
- this.setState({
- appLeak: sortedLeaks[0],
- });
+ setAppLeak(sortedLeaks[0]);
}
},
() => {
- if (this.mounted) {
- this.setState({ appLeak: undefined });
- }
+ setAppLeak(undefined);
},
);
// them at the parent application level will not get all the necessary
// information, unfortunately, as they are aggregated.
Promise.all(
- appStatus.projects.map((project) => {
+ applicationQualityGateStatus.projects.map((project) => {
const projectDetails = appDetails.projects.find((p) => p.key === project.key);
const projectBranchLike = projectDetails
? { isMain: projectDetails.isMain, name: projectDetails.branch, excludedFromPurge: false }
: undefined;
- return this.loadMeasuresAndMeta(
+ return loadMeasuresAndMeta(
project.key,
projectBranchLike,
// Only load metrics that apply to failing QG conditions; we don't
}),
).then(
(results) => {
- if (this.mounted) {
- const qgStatuses = results
- .map(({ measures = [], project, projectBranchLike }): QualityGateStatus => {
- const { key, name, status, caycStatus } = project;
- const conditions = extractStatusConditionsFromApplicationStatusChildProject(project);
- const enhancedConditions = this.getEnhancedConditions(conditions, measures);
- const failedConditions = enhancedConditions.filter((c) => c.level !== Status.OK);
-
- return {
- conditions: enhancedConditions,
- failedConditions,
- caycStatus,
- key,
- name,
- status,
- branchLike: projectBranchLike,
- };
- })
- .sort((a, b) => Math.sign(b.failedConditions.length - a.failedConditions.length));
-
- this.setState({
- loadingStatus: false,
- measures: appMeasures,
- metrics,
- period,
- qgStatuses,
- });
- }
+ const qgStatuses = results
+ .map(({ measures = [], project, projectBranchLike }): QualityGateStatus => {
+ const { key, name, status, caycStatus } = project;
+ const conditions = extractStatusConditionsFromApplicationStatusChildProject(project);
+ const enhancedConditions = getEnhancedConditions(conditions, measures);
+ const failedConditions = enhancedConditions.filter((c) => c.level !== Status.OK);
+
+ return {
+ conditions: enhancedConditions,
+ failedConditions,
+ caycStatus,
+ key,
+ name,
+ status,
+ branchLike: projectBranchLike,
+ };
+ })
+ .sort((a, b) => Math.sign(b.failedConditions.length - a.failedConditions.length));
+
+ setQgStatuses(qgStatuses);
+ setPeriod(period);
+ setMetrics(metrics);
+ setMeasures(appMeasures);
+ setLoadingStatus(false);
},
() => {
- if (this.mounted) {
- this.setState({ loadingStatus: false, qgStatuses: undefined });
- }
+ setQgStatuses(undefined);
+ setLoadingStatus(false);
},
);
- };
+ }, [applicationQualityGateStatus, branch, component.key, measuresAndLeak]);
- loadProjectStatus = async () => {
- const {
- branch,
- component: { key, name },
- } = this.props;
- this.setState({ loadingStatus: true });
+ const loadProjectStatus = React.useCallback(() => {
+ const { key, name } = component;
- const projectStatus = await getQualityGateProjectStatus({
- projectKey: key,
- ...getBranchLikeQuery(branch),
- });
+ if (!measuresAndLeak || !projectQualityGateStatus) {
+ return;
+ }
+ setLoadingStatus(true);
+ const { component: componentMeasures, metrics, period } = measuresAndLeak;
+ const projectMeasures = componentMeasures.measures
+ ? enhanceMeasuresWithMetrics(componentMeasures.measures, metrics)
+ : [];
+
+ if (projectMeasures) {
+ const { ignoredConditions, caycStatus, status } = projectQualityGateStatus;
+ const conditions = extractStatusConditionsFromProjectStatus(projectQualityGateStatus);
+ const enhancedConditions = getEnhancedConditions(conditions, projectMeasures);
+ const failedConditions = enhancedConditions.filter((c) => c.level !== Status.OK);
+
+ const qgStatus: QualityGateStatus = {
+ ignoredConditions,
+ caycStatus,
+ conditions: enhancedConditions,
+ failedConditions,
+ key,
+ name,
+ status,
+ branchLike: branch,
+ };
- // Get failing condition metric keys. We need measures for them as well to
- // render them.
- const metricKeys =
- projectStatus.conditions !== undefined
- ? uniq([...BRANCH_OVERVIEW_METRICS, ...projectStatus.conditions.map((c) => c.metricKey)])
- : BRANCH_OVERVIEW_METRICS;
-
- this.loadMeasuresAndMeta(key, branch, metricKeys).then(
- ({ measures, metrics, period }) => {
- if (this.mounted && measures) {
- const { ignoredConditions, caycStatus, status } = projectStatus;
- const conditions = extractStatusConditionsFromProjectStatus(projectStatus);
- const enhancedConditions = this.getEnhancedConditions(conditions, measures);
- const failedConditions = enhancedConditions.filter((c) => c.level !== Status.OK);
-
- const qgStatus: QualityGateStatus = {
- ignoredConditions,
- caycStatus,
- conditions: enhancedConditions,
- failedConditions,
- key,
- name,
- status,
- branchLike: branch,
- };
-
- this.setState({
- loadingStatus: false,
- measures,
- metrics,
- period,
- qgStatuses: [qgStatus],
- });
- } else if (this.mounted) {
- this.setState({ loadingStatus: false, qgStatuses: undefined });
- }
- },
- () => {
- if (this.mounted) {
- this.setState({ loadingStatus: false, qgStatuses: undefined });
- }
- },
- );
- };
+ setMeasures(projectMeasures);
+ setMetrics(metrics);
+ setPeriod(period);
+ setQgStatuses([qgStatus]);
+ } else {
+ setQgStatuses(undefined);
+ }
+ setLoadingStatus(false);
+ }, [branch, component, measuresAndLeak, projectQualityGateStatus]);
- loadProjectQualityGate = async () => {
- const { component } = this.props;
+ const loadProjectQualityGate = React.useCallback(async () => {
const qualityGate = await getGateForProject({ project: component.key });
const qgDetails = await fetchQualityGate({ name: qualityGate.name });
- this.setState({ qualityGate: qgDetails });
- };
+ setQualityGate(qgDetails);
+ }, [component.key]);
- loadMeasuresAndMeta = (
+ const loadMeasuresAndMeta = (
componentKey: string,
branchLike?: BranchLike,
metricKeys: string[] = [],
) => {
return getMeasuresWithPeriodAndMetrics(
componentKey,
- metricKeys.length > 0 ? metricKeys : BRANCH_OVERVIEW_METRICS,
+ metricKeys.length > 0
+ ? metricKeys
+ : BRANCH_OVERVIEW_METRICS.filter(
+ (metricKey) => !NEW_METRICS.includes(metricKey as MetricKey),
+ ),
getBranchLikeQuery(branchLike),
).then(({ component: { measures }, metrics, period }) => {
return {
});
};
- loadHistory = () => {
- this.setState({ loadingHistory: true });
-
- return Promise.all([this.loadHistoryMeasures(), this.loadAnalyses()]).then(
- this.doneLoadingHistory,
- this.doneLoadingHistory,
- );
- };
-
- loadHistoryMeasures = () => {
- const { branch, component } = this.props;
- const { graph } = this.state;
-
+ const loadHistoryMeasures = React.useCallback(() => {
const graphMetrics = getHistoryMetrics(graph, []);
const metrics = uniq([...HISTORY_METRICS_LIST, ...graphMetrics]);
metrics: metrics.join(),
}).then(
({ measures }) => {
- if (this.mounted) {
- this.setState({
- measuresHistory: measures.map((measure) => ({
- metric: measure.metric,
- history: measure.history.map((analysis) => ({
- date: parseDate(analysis.date),
- value: analysis.value,
- })),
+ setMeasuresHistory(
+ measures.map((measure) => ({
+ metric: measure.metric,
+ history: measure.history.map((analysis) => ({
+ date: parseDate(analysis.date),
+ value: analysis.value,
})),
- });
- }
- },
- () => {},
- );
- };
-
- loadAnalyses = () => {
- const { branch } = this.props;
-
- return getProjectActivity({
- ...getBranchLikeQuery(branch),
- project: this.getTopLevelComponent(),
- from: FROM_DATE,
- }).then(
- ({ analyses }) => {
- if (this.mounted) {
- this.setState({
- detectedCIOnLastAnalysis:
- analyses.length > 0
- ? analyses[0].detectedCI !== undefined && analyses[0].detectedCI !== NO_CI_DETECTED
- : undefined,
- analyses,
- });
- }
+ })),
+ );
},
() => {},
);
- };
-
- getEnhancedConditions = (
- conditions: QualityGateStatusCondition[],
- measures: MeasureEnhanced[],
- ) => {
- return (
- conditions
- // Enhance them with Metric information, which will be needed
- // to render the conditions properly.
- .map((c) => enhanceConditionWithMeasure(c, measures))
- // The enhancement will return undefined if it cannot find the
- // appropriate measure. Make sure we filter them out.
- .filter(isDefined)
- );
- };
+ }, [branch, component.key, graph]);
- getTopLevelComponent = () => {
- const { component } = this.props;
+ const getTopLevelComponent = React.useCallback(() => {
let current = component.breadcrumbs.length - 1;
while (
current > 0 &&
current--;
}
return component.breadcrumbs[current].key;
- };
+ }, [component.breadcrumbs]);
- doneLoadingHistory = () => {
- if (this.mounted) {
- this.setState({
- loadingHistory: false,
- });
- }
+ const loadAnalyses = React.useCallback(() => {
+ return getProjectActivity({
+ ...getBranchLikeQuery(branch),
+ project: getTopLevelComponent(),
+ from: FROM_DATE,
+ }).then(
+ ({ analyses }) => {
+ setAnalyses(analyses);
+ setDetectedCIOnLastAnalysis(
+ analyses.length > 0
+ ? analyses[0].detectedCI !== undefined && analyses[0].detectedCI !== NO_CI_DETECTED
+ : undefined,
+ );
+ },
+ () => {},
+ );
+ }, [branch, getTopLevelComponent]);
+
+ const loadHistory = React.useCallback(() => {
+ setLoadingHistory(true);
+
+ return Promise.all([loadHistoryMeasures(), loadAnalyses()]).then(
+ doneLoadingHistory,
+ doneLoadingHistory,
+ );
+ }, [loadAnalyses, loadHistoryMeasures]);
+
+ const doneLoadingHistory = () => {
+ setLoadingHistory(false);
};
- handleGraphChange = (graph: GraphType) => {
- const { component } = this.props;
+ const handleGraphChange = (graph: GraphType) => {
+ setGraph(graph);
saveActivityGraph(BRANCH_OVERVIEW_ACTIVITY_GRAPH, component.key, graph);
- this.setState({ graph, loadingHistory: true }, () => {
- this.loadHistoryMeasures().then(this.doneLoadingHistory, this.doneLoadingHistory);
- });
+ setLoadingHistory(true);
+ loadHistoryMeasures().then(doneLoadingHistory, doneLoadingHistory);
};
- render() {
- const { branch, branchesEnabled, component } = this.props;
- const {
- analyses,
- appLeak,
- detectedCIOnLastAnalysis,
- graph,
- loadingStatus,
- loadingHistory,
- measures,
- measuresHistory,
- metrics,
- period,
- qgStatuses,
- qualityGate,
- } = this.state;
-
- const projectIsEmpty =
- loadingStatus === false &&
- (measures === undefined ||
- measures.find((measure) =>
- ([MetricKey.lines, MetricKey.new_lines] as string[]).includes(measure.metric.key),
- ) === undefined);
-
- return (
- <BranchOverviewRenderer
- analyses={analyses}
- appLeak={appLeak}
- branch={branch}
- branchesEnabled={branchesEnabled}
- component={component}
- detectedCIOnLastAnalysis={detectedCIOnLastAnalysis}
- graph={graph}
- loadingHistory={loadingHistory}
- loadingStatus={loadingStatus}
- measures={measures}
- measuresHistory={measuresHistory}
- metrics={metrics}
- onGraphChange={this.handleGraphChange}
- period={period}
- projectIsEmpty={projectIsEmpty}
- qgStatuses={qgStatuses}
- qualityGate={qualityGate}
- />
- );
- }
+ const loadStatus = React.useCallback(() => {
+ if (component.qualifier === ComponentQualifier.Application) {
+ loadApplicationStatus();
+ } else {
+ loadProjectStatus();
+ loadProjectQualityGate();
+ }
+ }, [component.qualifier, loadApplicationStatus, loadProjectQualityGate, loadProjectStatus]);
+
+ React.useEffect(() => {
+ loadStatus();
+ loadHistory();
+ }, [branch, loadHistory, loadStatus]);
+
+ const projectIsEmpty =
+ loadingStatus === false &&
+ (measures === undefined ||
+ measures.find((measure) =>
+ ([MetricKey.lines, MetricKey.new_lines] as string[]).includes(measure.metric.key),
+ ) === undefined);
+
+ return (
+ <BranchOverviewRenderer
+ analyses={analyses}
+ appLeak={appLeak}
+ branch={branch}
+ branchesEnabled={branchesEnabled}
+ component={component}
+ detectedCIOnLastAnalysis={detectedCIOnLastAnalysis}
+ graph={graph}
+ loadingHistory={loadingHistory}
+ loadingStatus={loadingStatus}
+ measures={measures}
+ measuresHistory={measuresHistory}
+ metrics={metrics}
+ onGraphChange={handleGraphChange}
+ period={period}
+ projectIsEmpty={projectIsEmpty}
+ qgStatuses={qgStatuses}
+ qualityGate={qualityGate}
+ />
+ );
}
import { MeasuresServiceMock } from '../../../../api/mocks/MeasuresServiceMock';
import { ProjectActivityServiceMock } from '../../../../api/mocks/ProjectActivityServiceMock';
import { QualityGatesServiceMock } from '../../../../api/mocks/QualityGatesServiceMock';
+import SettingsServiceMock from '../../../../api/mocks/SettingsServiceMock';
import { TimeMachineServiceMock } from '../../../../api/mocks/TimeMachineServiceMock';
import UsersServiceMock from '../../../../api/mocks/UsersServiceMock';
import { PARENT_COMPONENT_KEY } from '../../../../api/mocks/data/ids';
import { mockQualityGateProjectStatus } from '../../../../helpers/mocks/quality-gates';
import { mockLoggedInUser, mockMeasure, mockPaging } from '../../../../helpers/testMocks';
import { renderComponent } from '../../../../helpers/testReactTestingUtils';
+import { ComponentPropsType } from '../../../../helpers/testUtils';
import { SoftwareImpactSeverity, SoftwareQuality } from '../../../../types/clean-code-taxonomy';
import { ProjectAnalysisEventCategory } from '../../../../types/project-activity';
import { CaycStatus } from '../../../../types/types';
import { getPageObjects } from '../test-utils';
const almHandler = new AlmSettingsServiceMock();
+const settingsHandler = new SettingsServiceMock();
let branchesHandler: BranchesServiceMock;
let measuresHandler: MeasuresServiceMock;
let applicationHandler: ApplicationServiceMock;
timeMarchineHandler.reset();
qualityGatesHandler.reset();
almHandler.reset();
+ settingsHandler.reset();
});
describe('project overview', () => {
])(
"should correctly flag a project that wasn't analyzed using a CI (%s)",
async (_, analyses, expected) => {
- jest.mocked(getProjectActivity).mockResolvedValueOnce({ analyses, paging: mockPaging() });
+ jest.mocked(getProjectActivity).mockResolvedValue({ analyses, paging: mockPaging() });
renderBranchOverview();
now: new Date('2023-04-25T12:00:00+0200'),
});
- jest.mocked(getProjectActivity).mockResolvedValueOnce({
+ jest.mocked(getProjectActivity).mockResolvedValue({
analyses,
paging: mockPaging(),
});
},
);
-function renderBranchOverview(props: Partial<BranchOverview['props']> = {}) {
+function renderBranchOverview(props: Partial<ComponentPropsType<typeof BranchOverview>> = {}) {
const user = mockLoggedInUser();
const component = mockComponent({
breadcrumbs: [mockComponent({ key: 'foo' })],
MetricKey.bugs,
MetricKey.new_bugs,
MetricKey.reliability_rating,
+ MetricKey.software_quality_reliability_rating,
MetricKey.new_reliability_rating,
+ MetricKey.new_software_quality_reliability_rating,
// vulnerabilities
MetricKey.vulnerabilities,
MetricKey.new_vulnerabilities,
MetricKey.security_rating,
+ MetricKey.software_quality_security_rating,
MetricKey.new_security_rating,
+ MetricKey.new_software_quality_security_rating,
// hotspots
MetricKey.security_hotspots,
MetricKey.security_hotspots_reviewed,
MetricKey.new_security_hotspots_reviewed,
MetricKey.security_review_rating,
+ MetricKey.software_quality_security_review_rating,
MetricKey.new_security_review_rating,
+ MetricKey.new_software_quality_security_review_rating,
// code smells
MetricKey.code_smells,
MetricKey.new_code_smells,
MetricKey.sqale_rating,
+ MetricKey.software_quality_maintainability_rating,
MetricKey.new_maintainability_rating,
+ MetricKey.new_software_quality_maintainability_rating,
MetricKey.sqale_index,
MetricKey.new_technical_debt,
MetricKey.reliability_issues,
MetricKey.bugs,
MetricKey.reliability_rating,
- MetricKey.reliability_rating_new,
+ MetricKey.software_quality_reliability_rating,
MetricKey.security_issues,
MetricKey.vulnerabilities,
MetricKey.security_rating,
- MetricKey.security_rating_new,
+ MetricKey.software_quality_security_rating,
MetricKey.maintainability_issues,
MetricKey.code_smells,
MetricKey.sqale_rating,
- MetricKey.sqale_rating_new,
+ MetricKey.software_quality_maintainability_rating,
MetricKey.security_hotspots_reviewed,
MetricKey.security_review_rating,
- MetricKey.security_review_rating_new,
+ MetricKey.software_quality_security_review_rating,
MetricKey.duplicated_lines_density,
MetricKey.coverage,
MetricKey.ncloc,
import { StaleTime, createInfiniteQueryHook, createQueryHook } from './common';
const NEW_METRICS = [
- MetricKey.sqale_rating_new,
- MetricKey.security_rating_new,
- MetricKey.reliability_rating_new,
- MetricKey.security_review_rating_new,
- MetricKey.releasability_rating_new,
- MetricKey.new_security_rating_new,
- MetricKey.new_reliability_rating_new,
- MetricKey.new_maintainability_rating_new,
- MetricKey.new_security_review_rating_new,
+ MetricKey.software_quality_maintainability_rating,
+ MetricKey.software_quality_security_rating,
+ MetricKey.software_quality_reliability_rating,
+ MetricKey.software_quality_security_review_rating,
+ MetricKey.software_quality_releasability_rating,
+ MetricKey.new_software_quality_security_rating,
+ MetricKey.new_software_quality_reliability_rating,
+ MetricKey.new_software_quality_maintainability_rating,
+ MetricKey.new_software_quality_security_review_rating,
];
const TASK_RETRY = 10_000;
import { queryOptions, useQuery, useQueryClient } from '@tanstack/react-query';
import { groupBy } from 'lodash';
import { BranchParameters } from '~sonar-aligned/types/branch-like';
-import { getMeasures, getMeasuresForProjects } from '../api/measures';
+import {
+ getMeasures,
+ getMeasuresForProjects,
+ getMeasuresWithPeriodAndMetrics,
+} from '../api/measures';
import { getAllTimeMachineData } from '../api/time-machine';
import { MetricKey } from '../sonar-aligned/types/metrics';
import { Measure } from '../types/types';
import { createQueryHook } from './common';
const NEW_METRICS = [
- MetricKey.sqale_rating_new,
- MetricKey.security_rating_new,
- MetricKey.reliability_rating_new,
- MetricKey.security_review_rating_new,
+ MetricKey.software_quality_maintainability_rating,
+ MetricKey.software_quality_security_rating,
+ MetricKey.new_software_quality_security_rating,
+ MetricKey.software_quality_reliability_rating,
+ MetricKey.new_software_quality_reliability_rating,
+ MetricKey.software_quality_security_review_rating,
+ MetricKey.new_software_quality_security_review_rating,
+ MetricKey.new_software_quality_maintainability_rating,
];
export function useAllMeasuresHistoryQuery(
},
);
+export const useMeasuresAndLeakQuery = createQueryHook(
+ ({
+ componentKey,
+ metricKeys,
+ branchParameters,
+ }: {
+ branchParameters?: BranchParameters;
+ componentKey: string;
+ metricKeys: string[];
+ }) => {
+ const queryClient = useQueryClient();
+ return queryOptions({
+ queryKey: ['measures', 'details', 'component', componentKey, metricKeys, branchParameters],
+ queryFn: async () => {
+ // TODO remove this once all metrics are supported
+ const filteredMetricKeys = metricKeys.filter(
+ (metricKey) => !NEW_METRICS.includes(metricKey as MetricKey),
+ );
+ const { component, metrics, period } = await getMeasuresWithPeriodAndMetrics(
+ componentKey,
+ filteredMetricKeys,
+ branchParameters,
+ );
+ const measuresMapByMetricKey = groupBy(component.measures, 'metric');
+ metricKeys.forEach((metricKey) => {
+ const measure = measuresMapByMetricKey[metricKey]?.[0] ?? null;
+ queryClient.setQueryData<Measure>(
+ ['measures', 'details', componentKey, metricKey],
+ measure,
+ );
+ });
+ return { component, metrics, period };
+ },
+ });
+ },
+);
+
export const useMeasureQuery = createQueryHook(
({ componentKey, metricKey }: { componentKey: string; metricKey: string }) => {
return queryOptions({
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
+import { queryOptions, useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { addGlobalSuccessMessage } from 'design-system';
+import { BranchParameters } from '~sonar-aligned/types/branch-like';
import {
copyQualityGate,
createCondition,
deleteQualityGate,
fetchQualityGate,
fetchQualityGates,
+ getApplicationQualityGate,
getGateForProject,
+ getQualityGateProjectStatus,
renameQualityGate,
setQualityGateAsDefault,
updateCondition,
import { getCorrectCaycCondition } from '../apps/quality-gates/utils';
import { translate } from '../helpers/l10n';
import { Condition, QualityGate } from '../types/types';
+import { createQueryHook } from './common';
const QUERY_STALE_TIME = 5 * 60 * 1000;
},
});
}
+
+export const useProjectQualityGateStatus = createQueryHook(
+ ({
+ projectId,
+ projectKey,
+ branchParameters,
+ }: {
+ branchParameters?: BranchParameters;
+ projectId?: string;
+ projectKey?: string;
+ }) => {
+ return queryOptions({
+ queryKey: ['quality-gate', 'status', 'project', projectId, projectKey, branchParameters],
+ queryFn: () => getQualityGateProjectStatus({ projectId, projectKey, ...branchParameters }),
+ });
+ },
+);
+
+export const useApplicationQualityGateStatus = createQueryHook(
+ ({ application, branch }: { application: string; branch?: string }) => {
+ return queryOptions({
+ queryKey: ['quality-gate', 'status', 'application', application, branch],
+ queryFn: () => getApplicationQualityGate({ application, branch }),
+ });
+ },
+);
duplicated_lines_density = 'duplicated_lines_density',
duplications_data = 'duplications_data',
effort_to_reach_maintainability_rating_a = 'effort_to_reach_maintainability_rating_a',
- effort_to_reach_maintainability_rating_a_new = 'effort_to_reach_maintainability_rating_a_new',
+ effort_to_reach_software_quality_maintainability_rating_a = 'effort_to_reach_software_quality_maintainability_rating_a',
executable_lines_data = 'executable_lines_data',
false_positive_issues = 'false_positive_issues',
file_complexity = 'file_complexity',
lines_to_cover = 'lines_to_cover',
maintainability_issues = 'maintainability_issues',
maintainability_rating_distribution = 'maintainability_rating_distribution',
- maintainability_rating_distribution_new = 'maintainability_rating_distribution_new',
+ software_quality_maintainability_rating_distribution = 'software_quality_maintainability_rating_distribution',
maintainability_rating_effort = 'maintainability_rating_effort',
major_violations = 'major_violations',
minor_violations = 'minor_violations',
new_lines_to_cover = 'new_lines_to_cover',
new_maintainability_issues = 'new_maintainability_issues',
new_maintainability_rating = 'new_maintainability_rating',
- new_maintainability_rating_new = 'new_maintainability_rating_new',
+ new_software_quality_maintainability_rating = 'new_software_quality_maintainability_rating',
new_maintainability_rating_distribution = 'new_maintainability_rating_distribution',
- new_maintainability_rating_distribution_new = 'new_maintainability_rating_distribution_new',
+ new_software_quality_maintainability_rating_distribution = 'new_software_quality_maintainability_rating_distribution',
new_major_violations = 'new_major_violations',
new_minor_violations = 'new_minor_violations',
new_reliability_issues = 'new_reliability_issues',
new_reliability_rating = 'new_reliability_rating',
- new_reliability_rating_new = 'new_reliability_rating_new',
+ new_software_quality_reliability_rating = 'new_software_quality_reliability_rating',
new_reliability_rating_distribution = 'new_reliability_rating_distribution',
- new_reliability_rating_distribution_new = 'new_reliability_rating_distribution_new',
+ new_software_quality_reliability_rating_distribution = 'new_software_quality_reliability_rating_distribution',
new_reliability_remediation_effort = 'new_reliability_remediation_effort',
new_security_hotspots = 'new_security_hotspots',
new_security_hotspots_reviewed = 'new_security_hotspots_reviewed',
new_security_issues = 'new_security_issues',
new_security_rating = 'new_security_rating',
- new_security_rating_new = 'new_security_rating_new',
+ new_software_quality_security_rating = 'new_software_quality_security_rating',
new_security_rating_distribution = 'new_security_rating_distribution',
- new_security_rating_distribution_new = 'new_security_rating_distribution_new',
+ new_software_quality_security_rating_distribution = 'new_software_quality_security_rating_distribution',
new_security_remediation_effort = 'new_security_remediation_effort',
new_security_review_rating = 'new_security_review_rating',
- new_security_review_rating_new = 'new_security_review_rating_new',
+ new_software_quality_security_review_rating = 'new_software_quality_security_review_rating',
new_security_review_rating_distribution = 'new_security_review_rating_distribution',
- new_security_review_rating_distribution_new = 'new_security_review_rating_distribution_new',
+ new_software_quality_security_review_rating_distribution = 'new_software_quality_security_review_rating_distribution',
new_sqale_debt_ratio = 'new_sqale_debt_ratio',
new_technical_debt = 'new_technical_debt',
new_uncovered_conditions = 'new_uncovered_conditions',
quality_profiles = 'quality_profiles',
releasability_effort = 'releasability_effort',
releasability_rating = 'releasability_rating',
- releasability_rating_new = 'releasability_rating_new',
+ software_quality_releasability_rating = 'software_quality_releasability_rating',
releasability_rating_distribution = 'releasability_rating_distribution',
- releasability_rating_distribution_new = 'releasability_rating_distribution_new',
+ software_quality_releasability_rating_distribution = 'software_quality_releasability_rating_distribution',
reliability_issues = 'reliability_issues',
reliability_rating = 'reliability_rating',
- reliability_rating_new = 'reliability_rating_new',
+ software_quality_reliability_rating = 'software_quality_reliability_rating',
reliability_rating_distribution = 'reliability_rating_distribution',
- reliability_rating_distribution_new = 'reliability_rating_distribution_new',
+ software_quality_reliability_rating_distribution = 'software_quality_reliability_rating_distribution',
reliability_rating_effort = 'reliability_rating_effort',
reliability_remediation_effort = 'reliability_remediation_effort',
reopened_issues = 'reopened_issues',
security_hotspots_reviewed = 'security_hotspots_reviewed',
security_issues = 'security_issues',
security_rating = 'security_rating',
- security_rating_new = 'security_rating_new',
+ software_quality_security_rating = 'software_quality_security_rating',
security_rating_distribution = 'security_rating_distribution',
- security_rating_distribution_new = 'security_rating_distribution_new',
+ software_quality_security_rating_distribution = 'software_quality_security_rating_distribution',
security_rating_effort = 'security_rating_effort',
security_remediation_effort = 'security_remediation_effort',
security_review_rating = 'security_review_rating',
- security_review_rating_new = 'security_review_rating_new',
+ software_quality_security_review_rating = 'software_quality_security_review_rating',
security_review_rating_distribution = 'security_review_rating_distribution',
- security_review_rating_distribution_new = 'security_review_rating_distribution_new',
+ software_quality_security_review_rating_distribution = 'software_quality_security_review_rating_distribution',
security_review_rating_effort = 'security_review_rating_effort',
skipped_tests = 'skipped_tests',
sonarjava_feedback = 'sonarjava_feedback',
sqale_debt_ratio = 'sqale_debt_ratio',
sqale_index = 'sqale_index',
sqale_rating = 'sqale_rating',
- sqale_rating_new = 'sqale_rating_new',
+ software_quality_maintainability_rating = 'software_quality_maintainability_rating',
statements = 'statements',
team_at_sonarsource = 'team_at_sonarsource',
team_size = 'team_size',