jest.mocked(associateGateWithProject).mockImplementation(this.selectHandler);
jest.mocked(dissociateGateWithProject).mockImplementation(this.deSelectHandler);
jest.mocked(setQualityGateAsDefault).mockImplementation(this.setDefaultHandler);
- (getGateForProject as jest.Mock).mockImplementation(this.projectGateHandler);
+ jest.mocked(getGateForProject).mockImplementation(this.projectGateHandler);
jest.mocked(getQualityGateProjectStatus).mockImplementation(this.handleQualityGetProjectStatus);
jest.mocked(getApplicationQualityGate).mockImplementation(this.handleGetApplicationQualityGate);
return Promise.reject('unknown');
}
- return this.reply(this.list.find((qg) => qg.name === this.getGateForProjectGateName));
+ const qualityGate = this.list.find((qg) => qg.name === this.getGateForProjectGateName);
+
+ if (!qualityGate) {
+ return Promise.reject('Unable to find quality gate');
+ }
+
+ return this.reply({
+ name: qualityGate.name,
+ isDefault: qualityGate.isDefault,
+ });
};
handleGetApplicationQualityGate = () => {
QualityGateProjectStatus,
SearchPermissionsParameters,
} from '../types/quality-gates';
-import { Condition, Paging, QualityGate } from '../types/types';
+import { Condition, Paging, QualityGate, QualityGatePreview } from '../types/types';
import { UserBase } from '../types/users';
export function fetchQualityGates(): Promise<{
return post('/api/qualitygates/delete_condition', data);
}
-export function getGateForProject(data: { project: string }): Promise<QualityGate> {
+export function getGateForProject(data: { project: string }): Promise<QualityGatePreview> {
return getJSON('/api/qualitygates/get_by_project', data).then(
({ qualityGate }) => ({
...qualityGate,
import { BasicSeparator, CenteredLayout, PageContentFontWrapper, Spinner } from 'design-system';
import { uniq } from 'lodash';
import * as React from 'react';
-import { useEffect, useState } from 'react';
-import { getMeasuresWithMetrics } from '../../../api/measures';
-import { fetchQualityGate, getGateForProject } from '../../../api/quality-gates';
import { getBranchLikeQuery } from '../../../helpers/branch-like';
import { enhanceConditionWithMeasure, enhanceMeasuresWithMetrics } from '../../../helpers/measures';
import { isDefined } from '../../../helpers/types';
import { useBranchStatusQuery } from '../../../queries/branch';
+import { useComponentMeasuresWithMetricsQuery } from '../../../queries/component';
+import { useComponentQualityGateQuery } from '../../../queries/quality-gates';
import { PullRequest } from '../../../types/branch-like';
-import { Component, MeasureEnhanced, QualityGate } from '../../../types/types';
+import { Component } from '../../../types/types';
import BranchQualityGate from '../components/BranchQualityGate';
import IgnoredConditionWarning from '../components/IgnoredConditionWarning';
import MetaTopBar from '../components/MetaTopBar';
component: Component;
}
-export default function PullRequestOverview(props: Props) {
+export default function PullRequestOverview(props: Readonly<Props>) {
const { component, branchLike } = props;
- const [isLoadingMeasures, setIsLoadingMeasures] = useState(false);
- const [measures, setMeasures] = useState<MeasureEnhanced[]>([]);
+
const {
data: { conditions, ignoredConditions, status } = {},
isLoading: isLoadingBranchStatusesData,
} = useBranchStatusQuery(component);
- const [isLoadingQualityGate, setIsLoadingQualityGate] = useState(false);
- const [qualityGate, setQualityGate] = useState<QualityGate>();
- const isLoading = isLoadingBranchStatusesData || isLoadingMeasures || isLoadingQualityGate;
-
- useEffect(() => {
- setIsLoadingMeasures(true);
-
- const metricKeys =
- conditions !== undefined
- ? // Also load metrics that apply to QG conditions.
- uniq([...PR_METRICS, ...conditions.map((c) => c.metric)])
- : PR_METRICS;
-
- getMeasuresWithMetrics(component.key, metricKeys, getBranchLikeQuery(branchLike)).then(
- ({ component, metrics }) => {
- if (component.measures) {
- setIsLoadingMeasures(false);
- setMeasures(enhanceMeasuresWithMetrics(component.measures || [], metrics));
- }
- },
- () => {
- setIsLoadingMeasures(false);
- },
- );
- }, [branchLike, component.key, conditions]);
- useEffect(() => {
- async function fetchQualityGateDate() {
- setIsLoadingQualityGate(true);
+ const { data: qualityGate, isLoading: isLoadingQualityGate } = useComponentQualityGateQuery(
+ component.key,
+ );
- const qualityGate = await getGateForProject({ project: component.key });
- const qgDetails = await fetchQualityGate({ name: qualityGate.name });
+ const { data: componentMeasures, isLoading: isLoadingMeasures } =
+ useComponentMeasuresWithMetricsQuery(
+ component.key,
+ uniq([...PR_METRICS, ...(conditions?.map((c) => c.metric) ?? [])]),
+ getBranchLikeQuery(branchLike),
+ !isLoadingBranchStatusesData,
+ );
- setQualityGate(qgDetails);
- setIsLoadingQualityGate(false);
- }
+ const measures = componentMeasures
+ ? enhanceMeasuresWithMetrics(
+ componentMeasures.component.measures ?? [],
+ componentMeasures.metrics,
+ )
+ : [];
- fetchQualityGateDate();
- }, [component.key]);
+ const isLoading = isLoadingBranchStatusesData || isLoadingMeasures || isLoadingQualityGate;
if (isLoading) {
return (
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { useQuery } from '@tanstack/react-query';
+import { UseQueryResult, useQuery } from '@tanstack/react-query';
import { getTasksForComponent } from '../api/ce';
+import { getMeasuresWithMetrics } from '../api/measures';
+import { BranchParameters } from '../types/branch-like';
+import { MeasuresAndMetaWithMetrics } from '../types/measures';
import { Component } from '../types/types';
const TASK_RETRY = 10_000;
+type QueryKeyData = {
+ metricKeys: string[];
+ branchParameters: BranchParameters;
+};
+
+function getComponentQueryKey(key: string, type: 'tasks'): string[];
+function getComponentQueryKey(key: string, type: 'measures', data: QueryKeyData): string[];
+function getComponentQueryKey(key: string, type: string, data?: QueryKeyData): string[] {
+ return ['component', key, type, JSON.stringify(data)];
+}
+
+function extractQueryKeyData(queryKey: string[]): { key: string; data?: QueryKeyData } {
+ const [, key, , data] = queryKey;
+ return { key, data: JSON.parse(data ?? 'null') };
+}
+
export function useTaskForComponentQuery(component: Component) {
return useQuery({
- queryKey: ['component', component.key, 'tasks'] as const,
- queryFn: ({ queryKey: [_, key] }) => getTasksForComponent(key),
+ queryKey: getComponentQueryKey(component.key, 'tasks'),
+ queryFn: ({ queryKey }) => {
+ const { key } = extractQueryKeyData(queryKey);
+ return getTasksForComponent(key);
+ },
refetchInterval: TASK_RETRY,
});
}
+
+export function useComponentMeasuresWithMetricsQuery(
+ key: string,
+ metricKeys: string[],
+ branchParameters: BranchParameters,
+ enabled = true,
+): UseQueryResult<MeasuresAndMetaWithMetrics> {
+ return useQuery({
+ enabled,
+ queryKey: getComponentQueryKey(key, 'measures', {
+ metricKeys,
+ branchParameters,
+ }),
+ queryFn: ({ queryKey }) => {
+ const { key, data } = extractQueryKeyData(queryKey);
+ return data && getMeasuresWithMetrics(key, data.metricKeys, data.branchParameters);
+ },
+ });
+}
deleteQualityGate,
fetchQualityGate,
fetchQualityGates,
+ getGateForProject,
renameQualityGate,
setQualityGateAsDefault,
updateCondition,
import { translate } from '../helpers/l10n';
import { Condition, QualityGate } from '../types/types';
-const QUALITY_GATE_KEY = 'quality-gate';
-const QUALITY_GATES_KEY = 'quality-gates';
+function getQualityGateQueryKey(type: 'name', data: string): string[];
+function getQualityGateQueryKey(type: 'project', data: string): string[];
+function getQualityGateQueryKey(): string[];
+function getQualityGateQueryKey(type?: string, data?: string) {
+ return ['quality-gate', type ?? '', data ?? ''].filter(Boolean);
+}
export function useQualityGateQuery(name: string) {
return useQuery({
- queryKey: [QUALITY_GATE_KEY, name] as const,
- queryFn: ({ queryKey: [_, name] }) => {
+ queryKey: getQualityGateQueryKey('name', name),
+ queryFn: ({ queryKey: [, , name] }) => {
return fetchQualityGate({ name });
},
});
}
+export function useComponentQualityGateQuery(project: string) {
+ return useQuery({
+ queryKey: getQualityGateQueryKey('project', project),
+ queryFn: async ({ queryKey: [, , project] }) => {
+ const qualityGatePreview = await getGateForProject({ project });
+ return fetchQualityGate({ name: qualityGatePreview.name });
+ },
+ });
+}
+
export function useQualityGatesQuery() {
return useQuery({
- queryKey: [QUALITY_GATES_KEY] as const,
+ queryKey: getQualityGateQueryKey(),
queryFn: () => {
return fetchQualityGates();
},
return createQualityGate({ name });
},
onSuccess: () => {
- queryClient.invalidateQueries([QUALITY_GATES_KEY]);
+ queryClient.invalidateQueries(getQualityGateQueryKey());
},
});
}
return setQualityGateAsDefault({ name: qualityGate.name });
},
onSuccess: () => {
- queryClient.invalidateQueries([QUALITY_GATES_KEY]);
- queryClient.invalidateQueries([QUALITY_GATE_KEY, gateName]);
+ queryClient.invalidateQueries(getQualityGateQueryKey());
+ queryClient.invalidateQueries(getQualityGateQueryKey('name', gateName));
},
});
}
return renameQualityGate({ currentName, name: newName });
},
onSuccess: (_, newName: string) => {
- queryClient.invalidateQueries([QUALITY_GATES_KEY]);
- queryClient.invalidateQueries([QUALITY_GATE_KEY, newName]);
+ queryClient.invalidateQueries(getQualityGateQueryKey());
+ queryClient.invalidateQueries(getQualityGateQueryKey('name', newName));
},
});
}
return copyQualityGate({ sourceName, name: newName });
},
onSuccess: () => {
- queryClient.invalidateQueries([QUALITY_GATES_KEY]);
+ queryClient.invalidateQueries(getQualityGateQueryKey());
},
});
}
return deleteQualityGate({ name });
},
onSuccess: () => {
- queryClient.invalidateQueries([QUALITY_GATES_KEY]);
+ queryClient.invalidateQueries(getQualityGateQueryKey());
},
});
}
return Promise.all(promiseArr);
},
onSuccess: () => {
- queryClient.invalidateQueries([QUALITY_GATES_KEY]);
- queryClient.invalidateQueries([QUALITY_GATE_KEY, gateName]);
+ queryClient.invalidateQueries(getQualityGateQueryKey());
+ queryClient.invalidateQueries(getQualityGateQueryKey('name', gateName));
addGlobalSuccessMessage(translate('quality_gates.conditions_updated'));
},
});
return createCondition({ ...condition, gateName });
},
onSuccess: (_, condition) => {
- queryClient.invalidateQueries([QUALITY_GATES_KEY]);
- queryClient.setQueryData([QUALITY_GATE_KEY, gateName], (oldData?: QualityGate) => {
- return oldData?.conditions
- ? {
- ...oldData,
- conditions: [...oldData.conditions, condition],
- }
- : undefined;
- });
- queryClient.invalidateQueries([QUALITY_GATE_KEY, gateName]);
+ queryClient.invalidateQueries(getQualityGateQueryKey());
+ queryClient.setQueryData(
+ getQualityGateQueryKey('name', gateName),
+ (oldData?: QualityGate) => {
+ return oldData?.conditions
+ ? {
+ ...oldData,
+ conditions: [...oldData.conditions, condition],
+ }
+ : undefined;
+ },
+ );
+ queryClient.invalidateQueries(getQualityGateQueryKey('name', gateName));
addGlobalSuccessMessage(translate('quality_gates.condition_added'));
},
});
return updateCondition(condition);
},
onSuccess: () => {
- queryClient.invalidateQueries([QUALITY_GATES_KEY]);
- queryClient.invalidateQueries([QUALITY_GATE_KEY, gateName]);
+ queryClient.invalidateQueries(getQualityGateQueryKey());
+ queryClient.invalidateQueries(getQualityGateQueryKey('name', gateName));
addGlobalSuccessMessage(translate('quality_gates.condition_updated'));
},
});
});
},
onSuccess: () => {
- queryClient.invalidateQueries([QUALITY_GATES_KEY]);
- queryClient.invalidateQueries([QUALITY_GATE_KEY, gateName]);
+ queryClient.invalidateQueries(getQualityGateQueryKey());
+ queryClient.invalidateQueries(getQualityGateQueryKey('name', gateName));
addGlobalSuccessMessage(translate('quality_gates.condition_deleted'));
},
});
OverCompliant = 'over-compliant',
}
-export interface QualityGate {
+export interface QualityGatePreview {
+ isDefault?: boolean;
+ name: string;
+}
+
+export interface QualityGate extends QualityGatePreview {
actions?: {
associateProjects?: boolean;
copy?: boolean;
conditions?: Condition[];
isBuiltIn?: boolean;
caycStatus?: CaycStatus;
- isDefault?: boolean;
- name: string;
}
export type RawQuery = Dict<any>;