aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMathieu Suen <mathieu.suen@sonarsource.com>2024-01-26 11:59:54 +0100
committersonartech <sonartech@sonarsource.com>2024-01-30 15:02:03 +0000
commitd38fbd9b6915692d435355dac629ad660783912c (patch)
tree922733250ba964b9a2f36c5fac4584ef898b12ef
parentfafba5f6eb8a23d93beb58b37adc8631fcd5cf24 (diff)
downloadsonarqube-d38fbd9b6915692d435355dac629ad660783912c.tar.gz
sonarqube-d38fbd9b6915692d435355dac629ad660783912c.zip
SONAR-21178 Improve quality gates page accessiblity
-rw-r--r--server/sonar-web/src/main/js/apps/overview/components/QualityGateCondition.tsx7
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/AddConditionModal.tsx (renamed from server/sonar-web/src/main/js/apps/quality-gates/components/ConditionModal.tsx)93
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/Condition.tsx16
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/ConditionOperator.tsx12
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/Conditions.tsx9
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/EditConditionModal.tsx131
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGate-it.tsx57
-rw-r--r--server/sonar-web/src/main/js/helpers/qualityGates.ts10
8 files changed, 215 insertions, 120 deletions
diff --git a/server/sonar-web/src/main/js/apps/overview/components/QualityGateCondition.tsx b/server/sonar-web/src/main/js/apps/overview/components/QualityGateCondition.tsx
index 765b6cd79da..dda75f63084 100644
--- a/server/sonar-web/src/main/js/apps/overview/components/QualityGateCondition.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/components/QualityGateCondition.tsx
@@ -30,6 +30,7 @@ import {
import { getBranchLikeQuery } from '../../../helpers/branch-like';
import { translate } from '../../../helpers/l10n';
import { formatMeasure, isDiffMetric, localizeMetric } from '../../../helpers/measures';
+import { getOperatorLabel } from '../../../helpers/qualityGates';
import {
getComponentDrilldownUrl,
getComponentIssuesUrl,
@@ -155,11 +156,7 @@ export default class QualityGateCondition extends React.PureComponent<Props> {
const threshold = (condition.level === 'ERROR' ? condition.error : condition.warning) as string;
const actual = (condition.period ? measure.period?.value : measure.value) as string;
- let operator = translate('quality_gates.operator', condition.op);
-
- if (metric.type === MetricType.Rating) {
- operator = translate('quality_gates.operator', condition.op, 'rating');
- }
+ const operator = getOperatorLabel(condition.op, metric);
return this.wrapWithLink(
<div className="sw-flex sw-items-center sw-p-2">
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionModal.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/AddConditionModal.tsx
index 777a2d36459..61f7ae71dfc 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionModal.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/AddConditionModal.tsx
@@ -21,10 +21,7 @@ import { ButtonPrimary, FormField, Modal, RadioButton } from 'design-system';
import * as React from 'react';
import { getLocalizedMetricName, translate } from '../../../helpers/l10n';
import { isDiffMetric } from '../../../helpers/measures';
-import {
- useCreateConditionMutation,
- useUpdateConditionMutation,
-} from '../../../queries/quality-gates';
+import { useCreateConditionMutation } from '../../../queries/quality-gates';
import { Condition, Metric, QualityGate } from '../../../types/types';
import { getPossibleOperators } from '../utils';
import ConditionOperator from './ConditionOperator';
@@ -32,32 +29,19 @@ import MetricSelect from './MetricSelect';
import ThresholdInput from './ThresholdInput';
interface Props {
- condition?: Condition;
- metric?: Metric;
- metrics?: Metric[];
- header: string;
+ metrics: Metric[];
onClose: () => void;
qualityGate: QualityGate;
}
const ADD_CONDITION_MODAL_ID = 'add-condition-modal';
-export default function ConditionModal({
- condition,
- metric,
- metrics,
- header,
- onClose,
- qualityGate,
-}: Readonly<Props>) {
- const [errorThreshold, setErrorThreshold] = React.useState(condition ? condition.error : '');
+export default function AddConditionModal({ metrics, onClose, qualityGate }: Readonly<Props>) {
+ const [errorThreshold, setErrorThreshold] = React.useState('');
const [scope, setScope] = React.useState<'new' | 'overall'>('new');
- const [selectedMetric, setSelectedMetric] = React.useState<Metric | undefined>(metric);
- const [selectedOperator, setSelectedOperator] = React.useState<string | undefined>(
- condition ? condition.op : undefined,
- );
+ const [selectedMetric, setSelectedMetric] = React.useState<Metric | undefined>();
+ const [selectedOperator, setSelectedOperator] = React.useState<string | undefined>();
const { mutateAsync: createCondition } = useCreateConditionMutation(qualityGate.name);
- const { mutateAsync: updateCondition } = useUpdateConditionMutation(qualityGate.name);
const getSinglePossibleOperator = (metric: Metric) => {
const operators = getPossibleOperators(metric);
@@ -73,10 +57,7 @@ export default function ConditionModal({
op: getSinglePossibleOperator(selectedMetric) ?? selectedOperator,
error: errorThreshold,
};
- const submitPromise = condition
- ? updateCondition({ id: condition.id, ...newCondition })
- : createCondition(newCondition);
- await submitPromise;
+ await createCondition(newCondition);
onClose();
}
};
@@ -84,7 +65,7 @@ export default function ConditionModal({
const handleScopeChange = (scope: 'new' | 'overall') => {
let correspondingMetric;
- if (selectedMetric && metrics) {
+ if (selectedMetric) {
const correspondingMetricKey =
scope === 'new' ? `new_${selectedMetric.key}` : selectedMetric.key.replace(/^new_/, '');
correspondingMetric = metrics.find((m) => m.key === correspondingMetricKey);
@@ -110,42 +91,34 @@ export default function ConditionModal({
const renderBody = () => {
return (
- <form id={ADD_CONDITION_MODAL_ID} onSubmit={handleFormSubmit}>
- {metric === undefined && (
- <FormField label={translate('quality_gates.conditions.where')}>
- <div className="sw-flex sw-gap-4">
- <RadioButton checked={scope === 'new'} onCheck={handleScopeChange} value="new">
- <span data-test="quality-gates__condition-scope-new">
- {translate('quality_gates.conditions.new_code')}
- </span>
- </RadioButton>
- <RadioButton
- checked={scope === 'overall'}
- onCheck={handleScopeChange}
- value="overall"
- >
- <span data-test="quality-gates__condition-scope-overall">
- {translate('quality_gates.conditions.overall_code')}
- </span>
- </RadioButton>
- </div>
- </FormField>
- )}
+ <form onSubmit={handleFormSubmit} id={ADD_CONDITION_MODAL_ID}>
+ <FormField label={translate('quality_gates.conditions.where')}>
+ <div className="sw-flex sw-gap-4">
+ <RadioButton checked={scope === 'new'} onCheck={handleScopeChange} value="new">
+ <span data-test="quality-gates__condition-scope-new">
+ {translate('quality_gates.conditions.new_code')}
+ </span>
+ </RadioButton>
+ <RadioButton checked={scope === 'overall'} onCheck={handleScopeChange} value="overall">
+ <span data-test="quality-gates__condition-scope-overall">
+ {translate('quality_gates.conditions.overall_code')}
+ </span>
+ </RadioButton>
+ </div>
+ </FormField>
<FormField
- description={metric && getLocalizedMetricName(metric)}
+ description={selectedMetric && getLocalizedMetricName(selectedMetric)}
htmlFor="condition-metric"
label={translate('quality_gates.conditions.fails_when')}
>
- {metrics && (
- <MetricSelect
- metric={selectedMetric}
- metricsArray={metrics.filter((m) =>
- scope === 'new' ? isDiffMetric(m.key) : !isDiffMetric(m.key),
- )}
- onMetricChange={handleMetricChange}
- />
- )}
+ <MetricSelect
+ metric={selectedMetric}
+ metricsArray={metrics.filter((m) =>
+ scope === 'new' ? isDiffMetric(m.key) : !isDiffMetric(m.key),
+ )}
+ onMetricChange={handleMetricChange}
+ />
</FormField>
{selectedMetric && (
@@ -182,7 +155,7 @@ export default function ConditionModal({
<Modal
isScrollable={false}
isOverflowVisible
- headerTitle={header}
+ headerTitle={translate('quality_gates.add_condition')}
onClose={onClose}
body={renderBody()}
primaryButton={
@@ -193,7 +166,7 @@ export default function ConditionModal({
form={ADD_CONDITION_MODAL_ID}
type="submit"
>
- {header}
+ {translate('quality_gates.add_condition')}
</ButtonPrimary>
}
secondaryButtonLabel={translate('close')}
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/Condition.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/Condition.tsx
index 3d388f8afbc..e15de9c0759 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/Condition.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/Condition.tsx
@@ -33,12 +33,12 @@ import {
import * as React from 'react';
import { useMetrics } from '../../../app/components/metrics/withMetricsContext';
import { getLocalizedMetricName, translate, translateWithParameters } from '../../../helpers/l10n';
+import { getOperatorLabel } from '../../../helpers/qualityGates';
import { useDeleteConditionMutation } from '../../../queries/quality-gates';
-import { MetricType } from '../../../types/metrics';
import { CaycStatus, Condition as ConditionType, Metric, QualityGate } from '../../../types/types';
import { getLocalizedMetricNameNoDiffMetric, isConditionWithFixedValue } from '../utils';
-import ConditionModal from './ConditionModal';
import ConditionValue from './ConditionValue';
+import EditConditionModal from './EditConditionModal';
export enum ConditionChange {
Added = 'added',
@@ -67,6 +67,7 @@ export default function ConditionComponent({
const [modal, setModal] = React.useState(false);
const { mutateAsync: deleteCondition } = useDeleteConditionMutation(qualityGate.name);
const metrics = useMetrics();
+ const { op = 'GT' } = condition;
const handleOpenUpdate = () => {
setModal(true);
@@ -84,13 +85,6 @@ export default function ConditionComponent({
setDeleteFormOpen(false);
};
- const renderOperator = () => {
- const { op = 'GT' } = condition;
- return metric.type === MetricType.Rating
- ? translate('quality_gates.operator', op, 'rating')
- : translate('quality_gates.operator', op);
- };
-
const isCaycCompliantAndOverCompliant = qualityGate.caycStatus !== CaycStatus.NonCompliant;
return (
@@ -100,7 +94,7 @@ export default function ConditionComponent({
{metric.hidden && <TextError className="sw-ml-1" text={translate('deprecated')} />}
</ContentCell>
- <ContentCell className="sw-whitespace-nowrap">{renderOperator()}</ContentCell>
+ <ContentCell className="sw-whitespace-nowrap">{getOperatorLabel(op, metric)}</ContentCell>
<NumericalCell className="sw-whitespace-nowrap">
<ConditionValue
@@ -126,7 +120,7 @@ export default function ConditionComponent({
size="small"
/>
{modal && (
- <ConditionModal
+ <EditConditionModal
condition={condition}
header={translate('quality_gates.update_condition')}
metric={metric}
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionOperator.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionOperator.tsx
index 0278ccf11d3..4382b85ea3a 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionOperator.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionOperator.tsx
@@ -19,7 +19,7 @@
*/
import { InputSelect, Note } from 'design-system';
import * as React from 'react';
-import { translate } from '../../../helpers/l10n';
+import { getOperatorLabel } from '../../../helpers/qualityGates';
import { Metric } from '../../../types/types';
import { getPossibleOperators } from '../utils';
@@ -34,18 +34,12 @@ export default class ConditionOperator extends React.PureComponent<Props> {
this.props.onOperatorChange(value);
};
- getLabel(op: string, metric: Metric) {
- return metric.type === 'RATING'
- ? translate('quality_gates.operator', op, 'rating')
- : translate('quality_gates.operator', op);
- }
-
render() {
const operators = getPossibleOperators(this.props.metric);
if (Array.isArray(operators)) {
const operatorOptions = operators.map((op) => {
- const label = this.getLabel(op, this.props.metric);
+ const label = getOperatorLabel(op, this.props.metric);
return { label, value: op };
});
@@ -64,6 +58,6 @@ export default class ConditionOperator extends React.PureComponent<Props> {
);
}
- return <Note className="sw-w-abs-150">{this.getLabel(operators, this.props.metric)}</Note>;
+ return <Note className="sw-w-abs-150">{getOperatorLabel(operators, this.props.metric)}</Note>;
}
}
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/Conditions.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/Conditions.tsx
index 9267a9eff2c..aeba605928c 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/Conditions.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/Conditions.tsx
@@ -42,11 +42,11 @@ import { Feature } from '../../../types/features';
import { MetricKey } from '../../../types/metrics';
import { CaycStatus, Condition as ConditionType, QualityGate } from '../../../types/types';
import { groupAndSortByPriorityConditions, isQualityGateOptimized } from '../utils';
+import AddConditionModal from './AddConditionModal';
import CaYCConditionsSimplificationGuide from './CaYCConditionsSimplificationGuide';
import CaycCompliantBanner from './CaycCompliantBanner';
import CaycCondition from './CaycCondition';
import CaycFixOptimizeBanner from './CaycFixOptimizeBanner';
-import ConditionModal from './ConditionModal';
import CaycReviewUpdateConditionsModal from './ConditionReviewAndUpdateModal';
import ConditionsTable from './ConditionsTable';
@@ -110,12 +110,7 @@ export default function Conditions({ qualityGate, isFetching }: Readonly<Props>)
(metric, condition) => metric.key === condition.metric,
);
return (
- <ConditionModal
- header={translate('quality_gates.add_condition')}
- metrics={availableMetrics}
- onClose={onClose}
- qualityGate={qualityGate}
- />
+ <AddConditionModal metrics={availableMetrics} onClose={onClose} qualityGate={qualityGate} />
);
},
[metrics, qualityGate],
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/EditConditionModal.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/EditConditionModal.tsx
new file mode 100644
index 00000000000..24faa55b050
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/EditConditionModal.tsx
@@ -0,0 +1,131 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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 { ButtonPrimary, FormField, Highlight, Modal, Note } from 'design-system';
+import { isArray } from 'lodash';
+import * as React from 'react';
+import { getLocalizedMetricName, translate } from '../../../helpers/l10n';
+import { useUpdateConditionMutation } from '../../../queries/quality-gates';
+import { Condition, Metric, QualityGate } from '../../../types/types';
+import { getPossibleOperators } from '../utils';
+import ConditionOperator from './ConditionOperator';
+import ThresholdInput from './ThresholdInput';
+
+interface Props {
+ condition: Condition;
+ metric: Metric;
+ header: string;
+ onClose: () => void;
+ qualityGate: QualityGate;
+}
+
+const EDIT_CONDITION_MODAL_ID = 'edit-condition-modal';
+
+export default function EditConditionModal({
+ condition,
+ metric,
+ onClose,
+ qualityGate,
+}: Readonly<Props>) {
+ const [errorThreshold, setErrorThreshold] = React.useState(condition ? condition.error : '');
+
+ const [selectedOperator, setSelectedOperator] = React.useState<string | undefined>(
+ condition ? condition.op : undefined,
+ );
+ const { mutateAsync: updateCondition } = useUpdateConditionMutation(qualityGate.name);
+
+ const getSinglePossibleOperator = (metric: Metric) => {
+ const operators = getPossibleOperators(metric);
+ return isArray(operators) ? selectedOperator : operators;
+ };
+
+ const handleFormSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
+ event.preventDefault();
+
+ const newCondition: Omit<Condition, 'id'> = {
+ metric: metric.key,
+ op: getSinglePossibleOperator(metric),
+ error: errorThreshold,
+ };
+ await updateCondition({ id: condition.id, ...newCondition });
+ onClose();
+ };
+
+ const handleErrorChange = (error: string) => {
+ setErrorThreshold(error);
+ };
+
+ const handleOperatorChange = (op: string) => {
+ setSelectedOperator(op);
+ };
+
+ const renderBody = () => {
+ return (
+ <form onSubmit={handleFormSubmit} id={EDIT_CONDITION_MODAL_ID}>
+ <span className="sw-flex sw-flex-col sw-w-full sw-mb-6" aria-hidden="true">
+ <Highlight className="sw-mb-2 sw-flex sw-items-center sw-gap-2">
+ <span>{translate('quality_gates.conditions.fails_when')}</span>
+ </Highlight>
+ <Note className="sw-mt-2">{getLocalizedMetricName(metric)}</Note>
+ </span>
+
+ <div className="sw-flex sw-gap-2">
+ <FormField
+ className="sw-mb-0"
+ htmlFor="condition-operator"
+ label={translate('quality_gates.conditions.operator')}
+ >
+ <ConditionOperator
+ metric={metric}
+ onOperatorChange={handleOperatorChange}
+ op={selectedOperator}
+ />
+ </FormField>
+ <FormField
+ htmlFor="condition-threshold"
+ label={translate('quality_gates.conditions.value')}
+ >
+ <ThresholdInput
+ metric={metric}
+ name="error"
+ onChange={handleErrorChange}
+ value={errorThreshold}
+ />
+ </FormField>
+ </div>
+ </form>
+ );
+ };
+
+ return (
+ <Modal
+ isScrollable={false}
+ isOverflowVisible
+ headerTitle={translate('quality_gates.update_condition')}
+ onClose={onClose}
+ body={renderBody()}
+ primaryButton={
+ <ButtonPrimary form={EDIT_CONDITION_MODAL_ID} type="submit">
+ {translate('quality_gates.update_condition')}
+ </ButtonPrimary>
+ }
+ secondaryButtonLabel={translate('close')}
+ />
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGate-it.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGate-it.tsx
index eb9a67bd80c..f12283529bc 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGate-it.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGate-it.tsx
@@ -26,7 +26,7 @@ import { searchProjects, searchUsers } from '../../../../api/quality-gates';
import { dismissNotice } from '../../../../api/users';
import { mockLoggedInUser } from '../../../../helpers/testMocks';
import { RenderContext, renderAppRoutes } from '../../../../helpers/testReactTestingUtils';
-import { byRole } from '../../../../helpers/testSelector';
+import { byRole, byTestId } from '../../../../helpers/testSelector';
import { Feature } from '../../../../types/features';
import { CaycStatus } from '../../../../types/types';
import { NoticeType } from '../../../../types/users';
@@ -218,49 +218,52 @@ it('should be able to add a condition', async () => {
// On new code
await user.click(await screen.findByText('quality_gates.add_condition'));
- let dialog = within(screen.getByRole('dialog'));
+ const dialog = byRole('dialog');
- await user.click(dialog.getByRole('radio', { name: 'quality_gates.conditions.new_code' }));
- await selectEvent.select(dialog.getByRole('combobox'), ['Issues']);
- await user.click(dialog.getByRole('textbox', { name: 'quality_gates.conditions.value' }));
+ await user.click(dialog.byRole('radio', { name: 'quality_gates.conditions.new_code' }).get());
+
+ await selectEvent.select(dialog.byRole('combobox').get(), 'Issues');
+ await user.click(
+ await dialog.byRole('textbox', { name: 'quality_gates.conditions.value' }).find(),
+ );
await user.keyboard('12');
- await user.click(dialog.getByRole('button', { name: 'quality_gates.add_condition' }));
- const newConditions = within(await screen.findByTestId('quality-gates__conditions-new'));
- expect(await newConditions.findByRole('cell', { name: 'Issues' })).toBeInTheDocument();
- expect(await newConditions.findByRole('cell', { name: '12' })).toBeInTheDocument();
+ await user.click(dialog.byRole('button', { name: 'quality_gates.add_condition' }).get());
+ const newConditions = byTestId('quality-gates__conditions-new');
+ expect(await newConditions.byRole('cell', { name: 'Issues' }).find()).toBeInTheDocument();
+ expect(await newConditions.byRole('cell', { name: '12' }).find()).toBeInTheDocument();
// On overall code
await user.click(await screen.findByText('quality_gates.add_condition'));
- dialog = within(screen.getByRole('dialog'));
- await selectEvent.select(dialog.getByRole('combobox'), ['Info Issues']);
- await user.click(dialog.getByRole('radio', { name: 'quality_gates.conditions.overall_code' }));
- await user.click(dialog.getByLabelText('quality_gates.conditions.operator'));
+ await selectEvent.select(dialog.byRole('combobox').get(), ['Info Issues']);
+ await user.click(dialog.byRole('radio', { name: 'quality_gates.conditions.overall_code' }).get());
+ await user.click(dialog.byLabelText('quality_gates.conditions.operator').get());
- await user.click(dialog.getByText('quality_gates.operator.LT'));
- await user.click(dialog.getByRole('textbox', { name: 'quality_gates.conditions.value' }));
+ await user.click(dialog.byText('quality_gates.operator.LT').get());
+ await user.click(dialog.byRole('textbox', { name: 'quality_gates.conditions.value' }).get());
await user.keyboard('42');
- await user.click(dialog.getByRole('button', { name: 'quality_gates.add_condition' }));
+ await user.click(dialog.byRole('button', { name: 'quality_gates.add_condition' }).get());
- const overallConditions = within(await screen.findByTestId('quality-gates__conditions-overall'));
+ const overallConditions = byTestId('quality-gates__conditions-overall');
- expect(await overallConditions.findByRole('cell', { name: 'Info Issues' })).toBeInTheDocument();
- expect(await overallConditions.findByRole('cell', { name: '42' })).toBeInTheDocument();
+ expect(
+ await overallConditions.byRole('cell', { name: 'Info Issues' }).find(),
+ ).toBeInTheDocument();
+ expect(await overallConditions.byRole('cell', { name: '42' }).find()).toBeInTheDocument();
// Select a rating
await user.click(await screen.findByText('quality_gates.add_condition'));
- dialog = within(screen.getByRole('dialog'));
- await user.click(dialog.getByRole('radio', { name: 'quality_gates.conditions.overall_code' }));
- await selectEvent.select(dialog.getByRole('combobox'), ['Maintainability Rating']);
- await user.click(dialog.getByLabelText('quality_gates.conditions.value'));
- await user.click(dialog.getByText('B'));
- await user.click(dialog.getByRole('button', { name: 'quality_gates.add_condition' }));
+ await user.click(dialog.byRole('radio', { name: 'quality_gates.conditions.overall_code' }).get());
+ await selectEvent.select(dialog.byRole('combobox').get(), ['Maintainability Rating']);
+ await user.click(dialog.byLabelText('quality_gates.conditions.value').get());
+ await user.click(dialog.byText('B').get());
+ await user.click(dialog.byRole('button', { name: 'quality_gates.add_condition' }).get());
expect(
- await overallConditions.findByRole('cell', { name: 'Maintainability Rating' }),
+ await overallConditions.byRole('cell', { name: 'Maintainability Rating' }).find(),
).toBeInTheDocument();
- expect(await overallConditions.findByRole('cell', { name: 'B' })).toBeInTheDocument();
+ expect(await overallConditions.byRole('cell', { name: 'B' }).find()).toBeInTheDocument();
});
it('should be able to edit a condition', async () => {
diff --git a/server/sonar-web/src/main/js/helpers/qualityGates.ts b/server/sonar-web/src/main/js/helpers/qualityGates.ts
index abba1d81c7a..19c8e161ed7 100644
--- a/server/sonar-web/src/main/js/helpers/qualityGates.ts
+++ b/server/sonar-web/src/main/js/helpers/qualityGates.ts
@@ -17,12 +17,20 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { MetricKey } from '../types/metrics';
+import { MetricKey, MetricType } from '../types/metrics';
import {
QualityGateApplicationStatusChildProject,
QualityGateProjectStatus,
QualityGateStatusCondition,
} from '../types/quality-gates';
+import { Metric } from '../types/types';
+import { translate } from './l10n';
+
+export function getOperatorLabel(op: string, metric: Metric) {
+ return metric.type === MetricType.Rating
+ ? translate('quality_gates.operator', op, 'rating')
+ : translate('quality_gates.operator', op);
+}
export function extractStatusConditionsFromProjectStatus(
projectStatus: QualityGateProjectStatus,