--- /dev/null
+/*
+ * 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.
+ */
+
+/**
+ * Mock Global from emotion which doesn't like jsdom
+ * (Throws `TypeError: node.setAttribute is not a function`)
+ */
+function Global() {
+ return null;
+}
+
+module.exports = {
+ ...jest.requireActual('@emotion/react'),
+ Global,
+};
"@babel/preset-typescript": "7.24.7",
"@emotion/babel-plugin": "11.11.0",
"@emotion/babel-plugin-jsx-pragmatic": "0.2.1",
- "@sonarsource/echoes-react": "0.5.0",
+ "@sonarsource/echoes-react": "0.6.0",
"@testing-library/dom": "10.2.0",
"@testing-library/jest-dom": "6.4.6",
"@testing-library/react": "16.0.0",
return (props as PropsWithChildren).children === undefined;
}
+/** @deprecated Use either Modal or ModalAlert from Echoes instead.
+ *
+ * The props have changed significantly:
+ * - `headerTitle` is now `title`
+ * - `headerDescription` is now `description` and is announced to screen readers.
+ * - `body` is replaced with `content`
+ * - `isLarge` is replaced with `size` (ModalSize.Default or ModalSize.Wide)
+ * - `isScrollable` and `isOverflowVisible` have been removed and the behavior is automatic!
+ * - `closeOnOverlayClick` has been removed and is either
+ * - always false for ModalAlert (it requires an action)
+ * or
+ * - always true for Modal
+ *
+ * By default, the Modal will be controlled automatically by its Trigger (child element).
+ * This is the preferred way.
+ *
+ * If you need to control the Modal (e.g. open as a side effect, close after async action):
+ * - `onClose` has been removed. Instead, use:
+ * - `onOpenChange`: callback for `isOpen` value changes.
+ * - `IsOpen`: controls the display of the Modal (conditional rendering isn't necessary anymore)
+ *
+ * See the {@link https://xtranet-sonarsource.atlassian.net/wiki/spaces/Platform/pages/3465543707/Modals | Migration Guide} for more
+ */
export function Modal({
closeOnOverlayClick = true,
isLarge,
"@primer/octicons-react": "19.10.0",
"@react-spring/rafz": "9.7.3",
"@react-spring/web": "9.7.3",
- "@sonarsource/echoes-react": "0.5.0",
+ "@sonarsource/echoes-react": "0.6.0",
"@tanstack/react-query": "5.18.1",
"axios": "1.7.2",
"classnames": "2.5.1",
)}
</ActionsDropdown>
- {this.state.cancelTaskOpen && (
- <ConfirmModal
- cancelButtonText={translate('close')}
- confirmButtonText={translate('background_tasks.cancel_task')}
- header={translate('background_tasks.cancel_task')}
- isDestructive
- onClose={this.closeCancelTask}
- onConfirm={this.handleCancelTask}
- >
- {translate('background_tasks.cancel_task.text')}
- </ConfirmModal>
- )}
+ <ConfirmModal
+ cancelButtonText={translate('close')}
+ confirmButtonText={translate('background_tasks.cancel_task')}
+ header={translate('background_tasks.cancel_task')}
+ isDestructive
+ isOpen={this.state.cancelTaskOpen}
+ onClose={this.closeCancelTask}
+ onConfirm={this.handleCancelTask}
+ >
+ {translate('background_tasks.cancel_task.text')}
+ </ConfirmModal>
{this.state.scannerContextOpen && (
<ScannerContext onClose={this.closeScannerContext} task={task} />
// Custom rule form
createCustomRuleDialog: byRole('dialog', { name: 'coding_rules.create_custom_rule' }),
updateCustomRuleDialog: byRole('dialog', { name: 'coding_rules.update_custom_rule' }),
- deleteCustomRuleDialog: byRole('dialog', { name: 'coding_rules.delete_rule' }),
+ deleteCustomRuleDialog: byRole('alertdialog', { name: 'coding_rules.delete_rule' }),
ruleNameTextbox: byRole('textbox', { name: 'name' }),
keyTextbox: byRole('textbox', { name: 'key' }),
cleanCodeCategorySelect: byRole('combobox', { name: 'category' }),
isLoading={loading}
/>
- {disclaimer && (
- <PublicProjectDisclaimer
- component={component}
- onClose={this.handleCloseDisclaimer}
- onConfirm={this.handleTurnProjectToPublic}
- />
- )}
+ <PublicProjectDisclaimer
+ component={component}
+ onClose={this.handleCloseDisclaimer}
+ onConfirm={this.handleTurnProjectToPublic}
+ isOpen={disclaimer}
+ />
</div>
<AllHoldersList
name: string;
qualifier: string;
};
+ isOpen: boolean;
onClose: () => void;
onConfirm: () => void;
}
-export default function PublicProjectDisclaimer({ component, onClose, onConfirm }: Props) {
+export default function PublicProjectDisclaimer({ component, isOpen, onClose, onConfirm }: Props) {
const { qualifier } = component;
return (
<ConfirmModal
header={translateWithParameters('projects_role.turn_x_to_public', component.name)}
onClose={onClose}
onConfirm={onConfirm}
+ isOpen={isOpen}
>
<FlagMessage className="sw-mb-4" variant="warning">
{translate('projects_role.are_you_sure_to_turn_project_to_public.warning', qualifier)}
githubExplanations: byText('roles.page.description.github'),
gitlabLogo: byRole('img', { name: 'project_permission.managed.alm.gitlab' }),
gitlabExplanations: byText('roles.page.description.gitlab'),
- confirmRemovePermissionDialog: byRole('dialog', {
+ confirmRemovePermissionDialog: byRole('alertdialog', {
name: 'project_permission.remove_only_confirmation_title',
}),
nonGHProjectWarning: byText('project_permission.local_project_with_github_provisioning'),
expect(byText('deletion.page').get()).toBeInTheDocument();
expect(byText('project_deletion.page.description').get()).toBeInTheDocument();
- await user.click(byRole('button', { name: 'delete' }).get());
- expect(await byRole('dialog', { name: 'qualifier.delete.TRK' }).find()).toBeInTheDocument();
+ await user.click(ui.deleteButton.get());
+ expect(await ui.confirmationModal(ComponentQualifier.Project).find()).toBeInTheDocument();
+
await user.click(
- byRole('dialog', { name: 'qualifier.delete.TRK' }).byRole('button', { name: 'delete' }).get(),
+ ui.confirmationModal(ComponentQualifier.Project).byRole('button', { name: 'delete' }).get(),
);
expect(await byText(/project_deletion.resource_dele/).find()).toBeInTheDocument();
expect(byText('deletion.page').get()).toBeInTheDocument();
expect(byText('portfolio_deletion.page.description').get()).toBeInTheDocument();
- await user.click(byRole('button', { name: 'delete' }).get());
+ await user.click(ui.deleteButton.get());
- expect(await byRole('dialog', { name: 'qualifier.delete.VW' }).find()).toBeInTheDocument();
+ expect(await ui.confirmationModal(ComponentQualifier.Portfolio).find()).toBeInTheDocument();
await user.click(
- byRole('dialog', { name: 'qualifier.delete.VW' }).byRole('button', { name: 'delete' }).get(),
+ ui.confirmationModal(ComponentQualifier.Portfolio).byRole('button', { name: 'delete' }).get(),
);
expect(await byText(/project_deletion.resource_dele/).find()).toBeInTheDocument();
expect(byText('deletion.page').get()).toBeInTheDocument();
expect(byText('application_deletion.page.description').get()).toBeInTheDocument();
- await user.click(byRole('button', { name: 'delete' }).get());
- expect(await byRole('dialog', { name: 'qualifier.delete.APP' }).find()).toBeInTheDocument();
+ await user.click(ui.deleteButton.get());
+ expect(await ui.confirmationModal(ComponentQualifier.Application).find()).toBeInTheDocument();
await user.click(
- byRole('dialog', { name: 'qualifier.delete.APP' }).byRole('button', { name: 'delete' }).get(),
+ ui.confirmationModal(ComponentQualifier.Application).byRole('button', { name: 'delete' }).get(),
);
expect(await byText(/project_deletion.resource_dele/).find()).toBeInTheDocument();
</ComponentContext.Provider>,
);
}
+
+const ui = {
+ confirmationModal: (qualifier: ComponentQualifier) =>
+ byRole('alertdialog', { name: `qualifier.delete.${qualifier}` }),
+ deleteButton: byRole('button', { name: 'delete' }),
+};
const ui = {
pageTitle: byRole('heading', { name: 'update_key.page' }),
- updateKeyDialog: byRole('dialog'),
+ updateKeyDialog: byRole('alertdialog'),
newKeyInput: byRole('textbox'),
updateInputButton: byRole('button', { name: 'update_verb' }),
resetInputButton: byRole('button', { name: 'reset_verb' }),
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { Button, ButtonVariety, RadioButtonGroup } from '@sonarsource/echoes-react';
-import { FormField, Modal } from 'design-system';
+import { Button, ButtonVariety, Modal, RadioButtonGroup } from '@sonarsource/echoes-react';
+import { FormField } from 'design-system';
+import { differenceWith, map } from 'lodash';
import * as React from 'react';
+import { useAvailableFeatures } from '../../../app/components/available-features/withAvailableFeatures';
+import { useMetrics } from '../../../app/components/metrics/withMetricsContext';
import { translate } from '../../../helpers/l10n';
import { isDiffMetric } from '../../../helpers/measures';
import { useCreateConditionMutation } from '../../../queries/quality-gates';
-import { MetricKey } from '../../../sonar-aligned/types/metrics';
+import { MetricKey, MetricType } from '../../../sonar-aligned/types/metrics';
+import { Feature } from '../../../types/features';
import { Condition, Metric, QualityGate } from '../../../types/types';
import { getPossibleOperators, isNonEditableMetric } from '../utils';
import ConditionOperator from './ConditionOperator';
import ThresholdInput from './ThresholdInput';
interface Props {
- metrics: Metric[];
- onClose: () => void;
qualityGate: QualityGate;
}
+const FORBIDDEN_METRIC_TYPES = [MetricType.Data, MetricType.Distribution, 'STRING', 'BOOL'];
+const FORBIDDEN_METRICS: string[] = [
+ MetricKey.alert_status,
+ MetricKey.releasability_rating,
+ MetricKey.security_hotspots,
+ MetricKey.new_security_hotspots,
+ MetricKey.software_quality_maintainability_rating,
+ MetricKey.new_software_quality_maintainability_rating,
+ MetricKey.software_quality_reliability_rating,
+ MetricKey.new_software_quality_reliability_rating,
+ MetricKey.software_quality_security_rating,
+ MetricKey.new_software_quality_security_rating,
+ MetricKey.software_quality_security_review_rating,
+ MetricKey.new_software_quality_security_review_rating,
+ MetricKey.effort_to_reach_software_quality_maintainability_rating_a,
+ MetricKey.software_quality_maintainability_remediation_effort,
+ MetricKey.new_software_quality_maintainability_remediation_effort,
+ MetricKey.software_quality_security_remediation_effort,
+ MetricKey.new_software_quality_security_remediation_effort,
+ MetricKey.software_quality_reliability_remediation_effort,
+ MetricKey.new_software_quality_reliability_remediation_effort,
+ MetricKey.software_quality_maintainability_debt_ratio,
+ MetricKey.new_software_quality_maintainability_debt_ratio,
+];
+
const ADD_CONDITION_MODAL_ID = 'add-condition-modal';
-export default function AddConditionModal({ metrics, onClose, qualityGate }: Readonly<Props>) {
+export default function AddConditionModal({ qualityGate }: Readonly<Props>) {
+ const [open, setOpen] = React.useState(false);
+ const closeModal = React.useCallback(() => setOpen(false), []);
+
const [errorThreshold, setErrorThreshold] = React.useState('');
const [scope, setScope] = React.useState<'new' | 'overall'>('new');
const [selectedMetric, setSelectedMetric] = React.useState<Metric | undefined>();
const [selectedOperator, setSelectedOperator] = React.useState<string | undefined>();
const { mutateAsync: createCondition } = useCreateConditionMutation(qualityGate.name);
+ const { hasFeature } = useAvailableFeatures();
+ const metrics = useMetrics();
const getSinglePossibleOperator = (metric: Metric) => {
const operators = getPossibleOperators(metric);
return Array.isArray(operators) ? undefined : operators;
};
- const handleFormSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
- event.preventDefault();
-
- if (selectedMetric) {
- const newCondition: Omit<Condition, 'id'> = {
- metric: selectedMetric.key,
- op: getSinglePossibleOperator(selectedMetric) ?? selectedOperator,
- error: errorThreshold,
- };
- await createCondition(newCondition);
- onClose();
- }
- };
+ const { conditions = [] } = qualityGate;
+
+ const availableMetrics = React.useMemo(() => {
+ return differenceWith(
+ map(metrics, (metric) => metric).filter(
+ (metric) =>
+ !metric.hidden &&
+ !FORBIDDEN_METRIC_TYPES.includes(metric.type) &&
+ !FORBIDDEN_METRICS.includes(metric.key) &&
+ !(
+ metric.key === MetricKey.prioritized_rule_issues &&
+ !hasFeature(Feature.PrioritizedRules)
+ ),
+ ),
+ conditions,
+ (metric, condition) => metric.key === condition.metric,
+ );
+ }, [conditions, hasFeature, metrics]);
+
+ const handleFormSubmit = React.useCallback(
+ async (event: React.FormEvent<HTMLFormElement>) => {
+ event.preventDefault();
+
+ if (selectedMetric) {
+ const newCondition: Omit<Condition, 'id'> = {
+ metric: selectedMetric.key,
+ op: getSinglePossibleOperator(selectedMetric) ?? selectedOperator,
+ error: errorThreshold,
+ };
+ await createCondition(newCondition);
+ closeModal();
+ }
+ },
+ [closeModal, createCondition, errorThreshold, selectedMetric, selectedOperator],
+ );
const handleScopeChange = (scope: 'new' | 'overall') => {
let correspondingMetric;
if (selectedMetric) {
const correspondingMetricKey =
scope === 'new' ? `new_${selectedMetric.key}` : selectedMetric.key.replace(/^new_/, '');
- correspondingMetric = metrics.find((m) => m.key === correspondingMetricKey);
+ correspondingMetric = availableMetrics.find((m) => m.key === correspondingMetricKey);
}
setScope(scope);
label={translate('quality_gates.conditions.fails_when')}
>
<MetricSelect
- metric={selectedMetric}
- metricsArray={metrics.filter((m) =>
+ selectedMetric={selectedMetric}
+ metricsArray={availableMetrics.filter((m) =>
scope === 'new' ? isDiffMetric(m.key) : !isDiffMetric(m.key),
)}
onMetricChange={handleMetricChange}
return (
<Modal
- isScrollable={false}
- isOverflowVisible
- headerTitle={translate('quality_gates.add_condition')}
- onClose={onClose}
- body={renderBody()}
+ title={translate('quality_gates.add_condition')}
+ content={renderBody()}
+ isOpen={open}
+ onOpenChange={setOpen}
primaryButton={
<Button
- hasAutoFocus
isDisabled={selectedMetric === undefined}
id="add-condition-button"
form={ADD_CONDITION_MODAL_ID}
{translate('quality_gates.add_condition')}
</Button>
}
- secondaryButtonLabel={translate('close')}
- />
+ secondaryButton={<Button onClick={closeModal}>{translate('close')}</Button>}
+ >
+ <Button data-test="quality-gates__add-condition">
+ {translate('quality_gates.add_condition')}
+ </Button>
+ </Modal>
);
}
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import {
- ActionCell,
- ContentCell,
- DangerButtonPrimary,
- DestructiveIcon,
- InteractiveIcon,
- Modal,
- NumericalCell,
- PencilIcon,
- TableRow,
- TextError,
- TrashIcon,
-} from 'design-system';
+ Button,
+ ButtonIcon,
+ ButtonSize,
+ ButtonVariety,
+ IconDelete,
+ ModalAlert,
+} from '@sonarsource/echoes-react';
+import { ActionCell, ContentCell, NumericalCell, TableRow, TextError } from 'design-system';
import * as React from 'react';
import { useMetrics } from '../../../app/components/metrics/withMetricsContext';
import { getLocalizedMetricName, translate, translateWithParameters } from '../../../helpers/l10n';
showEdit,
isCaycModal,
}: Readonly<Props>) {
- const [deleteFormOpen, setDeleteFormOpen] = React.useState(false);
- const [modal, setModal] = React.useState(false);
const { mutateAsync: deleteCondition } = useDeleteConditionMutation(qualityGate.name);
const metrics = useMetrics();
const { op = 'GT' } = condition;
- const handleOpenUpdate = () => {
- setModal(true);
- };
-
- const handleUpdateClose = () => {
- setModal(false);
- };
-
- const handleDeleteClick = () => {
- setDeleteFormOpen(true);
- };
-
- const closeDeleteForm = () => {
- setDeleteFormOpen(false);
- };
-
const isCaycCompliantAndOverCompliant = qualityGate.caycStatus !== CaycStatus.NonCompliant;
return (
!isConditionWithFixedValue(condition) ||
(isCaycCompliantAndOverCompliant && showEdit)) &&
!isNonEditableMetric(condition.metric as MetricKey) && (
- <>
- <InteractiveIcon
- Icon={PencilIcon}
- aria-label={translateWithParameters(
- 'quality_gates.condition.edit',
- metric.name,
- )}
- data-test="quality-gates__condition-update"
- onClick={handleOpenUpdate}
- className="sw-mr-4"
- size="small"
- />
- {modal && (
- <EditConditionModal
- condition={condition}
- header={translate('quality_gates.update_condition')}
- metric={metric}
- onClose={handleUpdateClose}
- qualityGate={qualityGate}
- />
- )}
- </>
+ <EditConditionModal
+ condition={condition}
+ header={translate('quality_gates.update_condition')}
+ metric={metric}
+ qualityGate={qualityGate}
+ />
)}
{(!isCaycCompliantAndOverCompliant ||
!condition.isCaycCondition ||
(isCaycCompliantAndOverCompliant && showEdit)) && (
- <>
- <DestructiveIcon
- Icon={TrashIcon}
- aria-label={translateWithParameters(
- 'quality_gates.condition.delete',
- metric.name,
- )}
- onClick={handleDeleteClick}
- size="small"
- />
- {deleteFormOpen && (
- <Modal
- headerTitle={translate('quality_gates.delete_condition')}
- onClose={closeDeleteForm}
- body={translateWithParameters(
- 'quality_gates.delete_condition.confirm.message',
- getLocalizedMetricName(metric),
- )}
- primaryButton={
- <DangerButtonPrimary
- autoFocus
- type="submit"
- onClick={() => deleteCondition(condition)}
- >
- {translate('delete')}
- </DangerButtonPrimary>
- }
- secondaryButtonLabel={translate('close')}
- />
+ <ModalAlert
+ title={translate('quality_gates.delete_condition')}
+ description={translateWithParameters(
+ 'quality_gates.delete_condition.confirm.message',
+ getLocalizedMetricName(metric),
)}
- </>
+ primaryButton={
+ <Button variety={ButtonVariety.Danger} onClick={() => deleteCondition(condition)}>
+ {translate('delete')}
+ </Button>
+ }
+ secondaryButtonLabel={translate('close')}
+ >
+ <ButtonIcon
+ Icon={IconDelete}
+ ariaLabel={translateWithParameters('quality_gates.condition.delete', metric.name)}
+ size={ButtonSize.Medium}
+ variety={ButtonVariety.DangerGhost}
+ />
+ </ModalAlert>
)}
</>
)}
Spinner,
SubHeading,
} from 'design-system';
-import { differenceWith, map, uniqBy } from 'lodash';
+import { uniqBy } from 'lodash';
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
import DocHelpTooltip from '~sonar-aligned/components/controls/DocHelpTooltip';
-import { MetricKey } from '~sonar-aligned/types/metrics';
import { useAvailableFeatures } from '../../../app/components/available-features/withAvailableFeatures';
import { useMetrics } from '../../../app/components/metrics/withMetricsContext';
import DocumentationLink from '../../../components/common/DocumentationLink';
-import ModalButton, { ModalProps } from '../../../components/controls/ModalButton';
+import { ModalProps } from '../../../components/controls/ModalButton';
import { DocLink } from '../../../helpers/doc-links';
import { useDocUrl } from '../../../helpers/docs';
import { getLocalizedMetricName, translate } from '../../../helpers/l10n';
qualityGate: QualityGate;
}
-const FORBIDDEN_METRIC_TYPES = ['DATA', 'DISTRIB', 'STRING', 'BOOL'];
-const FORBIDDEN_METRICS: string[] = [
- MetricKey.alert_status,
- MetricKey.releasability_rating,
- MetricKey.security_hotspots,
- MetricKey.new_security_hotspots,
- MetricKey.software_quality_maintainability_rating,
- MetricKey.new_software_quality_maintainability_rating,
- MetricKey.software_quality_reliability_rating,
- MetricKey.new_software_quality_reliability_rating,
- MetricKey.software_quality_security_rating,
- MetricKey.new_software_quality_security_rating,
- MetricKey.software_quality_security_review_rating,
- MetricKey.new_software_quality_security_review_rating,
- MetricKey.effort_to_reach_software_quality_maintainability_rating_a,
- MetricKey.software_quality_maintainability_remediation_effort,
- MetricKey.new_software_quality_maintainability_remediation_effort,
- MetricKey.software_quality_security_remediation_effort,
- MetricKey.new_software_quality_security_remediation_effort,
- MetricKey.software_quality_reliability_remediation_effort,
- MetricKey.new_software_quality_reliability_remediation_effort,
- MetricKey.software_quality_maintainability_debt_ratio,
- MetricKey.new_software_quality_maintainability_debt_ratio,
-];
-
export default function Conditions({ qualityGate, isFetching }: Readonly<Props>) {
const [editing, setEditing] = React.useState<boolean>(
qualityGate.caycStatus === CaycStatus.NonCompliant,
setEditing(qualityGate.caycStatus === CaycStatus.NonCompliant);
}, [name]); // eslint-disable-line react-hooks/exhaustive-deps
- const renderConditionModal = React.useCallback(
- ({ onClose }: ModalProps) => {
- const { conditions = [] } = qualityGate;
- const availableMetrics = differenceWith(
- map(metrics, (metric) => metric).filter(
- (metric) =>
- !metric.hidden &&
- !FORBIDDEN_METRIC_TYPES.includes(metric.type) &&
- !FORBIDDEN_METRICS.includes(metric.key) &&
- !(
- metric.key === MetricKey.prioritized_rule_issues &&
- !hasFeature(Feature.PrioritizedRules)
- ),
- ),
- conditions,
- (metric, condition) => metric.key === condition.metric,
- );
- return (
- <AddConditionModal metrics={availableMetrics} onClose={onClose} qualityGate={qualityGate} />
- );
- },
- [metrics, qualityGate],
- );
-
const docUrl = useDocUrl(DocLink.CaYC);
const isCompliantCustomQualityGate =
qualityGate.caycStatus !== CaycStatus.NonCompliant && !qualityGate.isBuiltIn;
</div>
<div>
{(qualityGate.caycStatus === CaycStatus.NonCompliant || editing) && canEdit && (
- <ModalButton modal={renderConditionModal}>
- {({ onClick }) => (
- <ButtonSecondary data-test="quality-gates__add-condition" onClick={onClick}>
- {translate('quality_gates.add_condition')}
- </ButtonSecondary>
- )}
- </ModalButton>
+ <AddConditionModal qualityGate={qualityGate} />
)}
</div>
</header>
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { Button, ButtonVariety } from '@sonarsource/echoes-react';
-import { FormField, Highlight, Modal, Note } from 'design-system';
+import {
+ Button,
+ ButtonIcon,
+ ButtonSize,
+ ButtonVariety,
+ IconEdit,
+ Modal,
+} from '@sonarsource/echoes-react';
+import { FormField, Highlight, Note } from 'design-system';
import { isArray } from 'lodash';
import * as React from 'react';
-import { getLocalizedMetricName, translate } from '../../../helpers/l10n';
+import { getLocalizedMetricName, translate, translateWithParameters } from '../../../helpers/l10n';
import { useUpdateConditionMutation } from '../../../queries/quality-gates';
import { Condition, Metric, QualityGate } from '../../../types/types';
import { getPossibleOperators } from '../utils';
condition: Condition;
header: string;
metric: Metric;
- onClose: () => void;
qualityGate: QualityGate;
}
const EDIT_CONDITION_MODAL_ID = 'edit-condition-modal';
-export default function EditConditionModal({
- condition,
- metric,
- onClose,
- qualityGate,
-}: Readonly<Props>) {
+export default function EditConditionModal({ condition, metric, qualityGate }: Readonly<Props>) {
+ const [open, setOpen] = React.useState(false);
+ const [submitting, setSubmitting] = React.useState(false);
+
const [errorThreshold, setErrorThreshold] = React.useState(condition ? condition.error : '');
const [selectedOperator, setSelectedOperator] = React.useState<string | undefined>(
const handleFormSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
+ setSubmitting(true);
+
const newCondition: Omit<Condition, 'id'> = {
metric: metric.key,
op: getSinglePossibleOperator(metric),
error: errorThreshold,
};
- await updateCondition({ id: condition.id, ...newCondition });
- onClose();
+ try {
+ await updateCondition({ id: condition.id, ...newCondition });
+ setOpen(false);
+ } catch (_) {
+ /* Error already handled */
+ }
+
+ setSubmitting(false);
};
const handleErrorChange = (error: string) => {
return (
<Modal
- isScrollable={false}
- isOverflowVisible
- headerTitle={translate('quality_gates.update_condition')}
- onClose={onClose}
- body={renderBody()}
+ title={translate('quality_gates.update_condition')}
+ content={renderBody()}
primaryButton={
- <Button form={EDIT_CONDITION_MODAL_ID} type="submit" variety={ButtonVariety.Primary}>
+ <Button
+ form={EDIT_CONDITION_MODAL_ID}
+ isLoading={submitting}
+ type="submit"
+ variety={ButtonVariety.Primary}
+ >
{translate('quality_gates.update_condition')}
</Button>
}
- secondaryButtonLabel={translate('close')}
- />
+ secondaryButton={
+ <Button variety={ButtonVariety.Default} onClick={() => setOpen(false)}>
+ {translate('close')}
+ </Button>
+ }
+ isOpen={open}
+ onOpenChange={setOpen}
+ >
+ <ButtonIcon
+ Icon={IconEdit}
+ ariaLabel={translateWithParameters('quality_gates.condition.edit', metric.name)}
+ data-test="quality-gates__condition-update"
+ className="sw-mr-4"
+ size={ButtonSize.Medium}
+ variety={ButtonVariety.PrimaryGhost}
+ />
+ </Modal>
);
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { LabelValueSelectOption, SearchSelectDropdown } from 'design-system';
-import { sortBy } from 'lodash';
+import { Select, SelectOption } from '@sonarsource/echoes-react';
import * as React from 'react';
-import { Options } from 'react-select';
import withMetricsContext from '../../../app/components/metrics/withMetricsContext';
-import { getLocalizedMetricDomain, translate } from '../../../helpers/l10n';
+import { translate } from '../../../helpers/l10n';
+import { isDefined } from '../../../helpers/types';
import { Dict, Metric } from '../../../types/types';
import { getLocalizedMetricNameNoDiffMetric } from '../utils';
interface Props {
- metric?: Metric;
metrics: Dict<Metric>;
metricsArray: Metric[];
onMetricChange: (metric: Metric) => void;
+ selectedMetric?: Metric;
}
-interface Option {
- isDisabled?: boolean;
- label: string;
- value: string;
-}
-
-export function MetricSelect({ metric, metricsArray, metrics, onMetricChange }: Readonly<Props>) {
- const handleChange = (option: Option | null) => {
- if (option) {
- const selectedMetric = metricsArray.find((metric) => metric.key === option.value);
+export function MetricSelect({
+ selectedMetric,
+ metricsArray,
+ metrics,
+ onMetricChange,
+}: Readonly<Props>) {
+ const handleChange = (key: string | null) => {
+ if (isDefined(key)) {
+ const selectedMetric = metricsArray.find((metric) => metric.key === key);
if (selectedMetric) {
onMetricChange(selectedMetric);
}
}
};
- const options: Array<Option & { domain?: string }> = sortBy(
- metricsArray.map((m) => ({
- value: m.key,
- label: getLocalizedMetricNameNoDiffMetric(m, metrics),
- domain: m.domain,
- })),
- 'domain',
- );
-
- // Use "disabled" property to emulate optgroups.
- const optionsWithDomains: Option[] = [];
- options.forEach((option, index, options) => {
- const previous = index > 0 ? options[index - 1] : null;
- if (option.domain && (!previous || previous.domain !== option.domain)) {
- optionsWithDomains.push({
- value: '<domain>',
- label: getLocalizedMetricDomain(option.domain),
- isDisabled: true,
- });
- }
- optionsWithDomains.push(option);
- });
-
- const handleMetricsSearch = React.useCallback(
- (query: string, resolve: (options: Options<LabelValueSelectOption<string>>) => void) => {
- resolve(options.filter((opt) => opt.label.toLowerCase().includes(query.toLowerCase())));
- },
- [options],
- );
+ const options: SelectOption[] = metricsArray.map((m) => ({
+ value: m.key,
+ label: getLocalizedMetricNameNoDiffMetric(m, metrics),
+ group: m.domain,
+ }));
return (
- <SearchSelectDropdown
- aria-label={translate('search.search_for_metrics')}
- size="large"
- controlSize="full"
- inputId="condition-metric"
- defaultOptions={optionsWithDomains}
- loadOptions={handleMetricsSearch}
+ <Select
+ data={options}
+ value={selectedMetric?.key}
onChange={handleChange}
- placeholder={translate('search.search_for_metrics')}
- controlLabel={
- optionsWithDomains.find((o) => o.value === metric?.key)?.label ?? translate('select_verb')
- }
+ ariaLabel={translate('quality_gates.conditions.fails_when')}
+ isSearchable
+ isNotClearable
/>
);
}
await user.click(dialog.byRole('radio', { name: 'quality_gates.conditions.new_code' }).get());
- await selectEvent.select(dialog.byRole('combobox').get(), 'Issues');
+ await user.click(dialog.byLabelText('quality_gates.conditions.fails_when').get());
+ await user.click(dialog.byRole('option', { name: 'Issues' }).get());
+
await user.click(
await dialog.byRole('textbox', { name: 'quality_gates.conditions.value' }).find(),
);
const dialog = byRole('dialog');
- 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.fails_when').get());
+
// In real app there are no metrics with selectable condition operator
// so we manually changed direction for Info Issues to 0 to test this behavior
- await user.click(dialog.byRole('radio', { name: 'quality_gates.conditions.overall_code' }).get());
+ await user.click(await dialog.byRole('option', { name: 'Info Issues' }).find());
+
await user.click(dialog.byLabelText('quality_gates.conditions.operator').get());
await user.click(dialog.byText('quality_gates.operator.LT').get());
const dialog = byRole('dialog');
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.fails_when').get());
+ await user.click(dialog.byRole('option', { name: 'Maintainability Rating' }).get());
+
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());
newConditions.getByLabelText('quality_gates.condition.delete.Coverage on New Code'),
);
- const dialog = within(screen.getByRole('dialog'));
+ const dialog = within(screen.getByRole('alertdialog'));
await user.click(dialog.getByRole('button', { name: 'delete' }));
await waitFor(() => {
const dialog = byRole('dialog');
await user.click(dialog.byRole('radio', { name: 'quality_gates.conditions.overall_code' }).get());
- await selectEvent.select(dialog.byRole('combobox').get(), ['Issues from prioritized rules']);
+ await user.click(dialog.byLabelText('quality_gates.conditions.fails_when').get());
+ await user.click(dialog.byRole('option', { name: 'Issues from prioritized rules' }).get());
expect(dialog.byRole('textbox', { name: 'quality_gates.conditions.value' }).get()).toBeDisabled();
expect(dialog.byRole('textbox', { name: 'quality_gates.conditions.value' }).get()).toHaveValue(
compareDropdown: byRole('combobox', { name: 'quality_profiles.compare_with' }),
changelogLink: byRole('link', { name: 'changelog' }),
popup: byRole('dialog'),
+ confirmationModal: byRole('alertdialog'),
restoreProfileDialog: byRole('dialog', { name: 'quality_profiles.restore_profile' }),
copyRadio: byRole('radio', {
name: 'quality_profiles.creation_from_copy quality_profiles.creation_from_copy_description_1 quality_profiles.creation_from_copy_description_2',
// Deactivate
await user.click(await ui.deactivateRuleButton('java quality profile #2').find());
- expect(ui.popup.get()).toBeInTheDocument();
+ expect(ui.confirmationModal.get()).toBeInTheDocument();
await user.click(ui.deactivateConfirmButton.get());
expect(ui.summaryAdditionalRules(1).query()).not.toBeInTheDocument();
});
onUpdateDefinitions={props.onUpdateDefinitions}
/>
- {isDefined(definitionKeyForDeletion) && (
- <DeleteModal
- id={definitionKeyForDeletion}
- onCancel={props.onCancelDelete}
- onDelete={props.onConfirmDelete}
- projectCount={projectCount}
- />
- )}
+ <DeleteModal
+ id={definitionKeyForDeletion}
+ isOpen={isDefined(definitionKeyForDeletion)}
+ onCancel={props.onCancelDelete}
+ onDelete={props.onConfirmDelete}
+ projectCount={projectCount}
+ />
</>
);
}
import { translate, translateWithParameters } from '../../../../helpers/l10n';
export interface DeleteModalProps {
- id: string;
+ id?: string;
+ isOpen: boolean;
onCancel: () => void;
onDelete: (id: string) => void;
projectCount?: number;
) : null;
}
-export default function DeleteModal({ id, onDelete, onCancel, projectCount }: DeleteModalProps) {
+export default function DeleteModal({
+ id,
+ isOpen,
+ onDelete,
+ onCancel,
+ projectCount,
+}: DeleteModalProps) {
return (
<ConfirmModal
confirmButtonText={translate('delete')}
confirmData={id}
header={translate('settings.almintegration.delete.header')}
isDestructive
+ isOpen={isOpen}
onClose={onCancel}
onConfirm={onDelete}
>
allowUsersToSignUp?: boolean;
hasProvisioningTypeChange?: boolean;
isAllowListEmpty: boolean;
+ isOpen: boolean;
onClose: VoidFunction;
onConfirm: VoidFunction;
provider: Provider.Github | Provider.Gitlab;
allowUsersToSignUp,
hasProvisioningTypeChange,
isAllowListEmpty,
+ isOpen,
onConfirm,
onClose,
provider,
return (
<ConfirmModal
+ isOpen={isOpen}
onConfirm={onConfirm}
header={intl.formatMessage({
id: `settings.authentication.${provider}.confirm.${hasProvisioningTypeChange ? provisioningStatus : 'insecure'}`,
provisioningType={provisioningType ?? ProvisioningType.jit}
synchronizationDetails={<GitHubSynchronisationWarning />}
/>
- {isConfirmProvisioningModalOpen && provisioningType && (
+
+ {provisioningType && (
<ConfirmProvisioningModal
allowUsersToSignUp={allowUsersToSignUp}
hasProvisioningTypeChange={changes?.provisioningType !== undefined}
isAllowListEmpty={isEmpty(gitHubConfiguration.allowedOrganizations)}
+ isOpen={isConfirmProvisioningModalOpen}
onClose={() => setIsConfirmProvisioningModalOpen(false)}
onConfirm={onUpdateProvisioning}
provider={Provider.Github}
provisioningStatus={provisioningType}
/>
)}
+
{isMappingModalOpen && (
<GitHubMappingModal
mapping={rolesMapping}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { Button, ButtonVariety, Spinner } from '@sonarsource/echoes-react';
-import { FlagMessage, Modal } from 'design-system';
+import { Button, ButtonVariety, Modal } from '@sonarsource/echoes-react';
+import { FlagMessage } from 'design-system';
import { isEmpty, keyBy } from 'lodash';
import React, { useEffect, useState } from 'react';
import { FormattedMessage } from 'react-intl';
return (
<>
<Modal
- body={formBody}
- headerTitle={header}
- isScrollable
- onClose={onClose}
+ content={formBody}
+ title={header}
+ isOpen
+ onOpenChange={onClose}
primaryButton={
- <>
- <Spinner className="sw-ml-2" isLoading={isCreating || isUpdating} />
- <Button
- form={FORM_ID}
- type="submit"
- hasAutoFocus
- isDisabled={!isFormValid}
- variety={ButtonVariety.Primary}
- >
- <FormattedMessage id="settings.almintegration.form.save" />
- </Button>
- </>
+ <Button
+ form={FORM_ID}
+ type="submit"
+ hasAutoFocus
+ isDisabled={!isFormValid}
+ isLoading={isCreating || isUpdating}
+ variety={ButtonVariety.Primary}
+ >
+ <FormattedMessage id="settings.almintegration.form.save" />
+ </Button>
}
+ secondaryButton={<Button onClick={onClose}>{translate('close')}</Button>}
+ />
+
+ <ConfirmProvisioningModal
+ allowUsersToSignUp={gitHubConfiguration?.allowUsersToSignUp}
+ isAllowListEmpty={isEmpty(gitHubConfiguration?.allowedOrganizations)}
+ isOpen={isConfirmModalOpen}
+ onClose={() => setIsConfirmModalOpen(false)}
+ onConfirm={onSave}
+ provider={Provider.Github}
+ provisioningStatus={gitHubConfiguration?.provisioningType ?? ProvisioningType.jit}
/>
- {isConfirmModalOpen && (
- <ConfirmProvisioningModal
- allowUsersToSignUp={gitHubConfiguration?.allowUsersToSignUp}
- isAllowListEmpty={isEmpty(gitHubConfiguration?.allowedOrganizations)}
- onClose={() => setIsConfirmModalOpen(false)}
- onConfirm={onSave}
- provider={Provider.Github}
- provisioningStatus={gitHubConfiguration?.provisioningType ?? ProvisioningType.jit}
- />
- )}
</>
);
}
</>
)}
</div>
- {showConfirmProvisioningModal && provisioningType && (
+ {provisioningType && (
<ConfirmProvisioningModal
allowUsersToSignUp={allowUsersToSignUp}
hasProvisioningTypeChange={Boolean(changes?.provisioningType)}
isAllowListEmpty={isEmpty(allowedGroups)}
+ isOpen={showConfirmProvisioningModal}
onClose={() => setShowConfirmProvisioningModal(false)}
onConfirm={updateProvisioning}
provider={Provider.Gitlab}
</>
}
/>
- {showConfirmProvisioningModal && (
- <ConfirmModal
- onConfirm={() => handleConfirmChangeProvisioning()}
- header={translate(
- 'settings.authentication.saml.confirm',
- newScimStatus ? 'scim' : 'jit',
- )}
- onClose={() => setShowConfirmProvisioningModal(false)}
- isDestructive={!newScimStatus}
- confirmButtonText={translate('yes')}
- >
- {translate(
- 'settings.authentication.saml.confirm',
- newScimStatus ? 'scim' : 'jit',
- 'description',
- )}
- </ConfirmModal>
- )}
+ <ConfirmModal
+ onConfirm={() => handleConfirmChangeProvisioning()}
+ header={translate(
+ 'settings.authentication.saml.confirm',
+ newScimStatus ? 'scim' : 'jit',
+ )}
+ onClose={() => setShowConfirmProvisioningModal(false)}
+ isDestructive={!newScimStatus}
+ isOpen={showConfirmProvisioningModal}
+ confirmButtonText={translate('yes')}
+ >
+ {translate(
+ 'settings.authentication.saml.confirm',
+ newScimStatus ? 'scim' : 'jit',
+ 'description',
+ )}
+ </ConfirmModal>
</>
)}
{showEditModal && (
textbox1: byRole('textbox', { name: 'test1' }),
textbox2: byRole('textbox', { name: 'test2' }),
tab: byRole('tab', { name: 'github GitHub' }),
- cancelDialogButton: byRole('dialog').byRole('button', { name: 'cancel' }),
+ cancelDialogButton: byRole('alertdialog').byRole('button', { name: 'cancel' }),
noGithubConfiguration: byText('settings.authentication.github.form.not_configured'),
createConfigButton: ghContainer.byRole('button', {
name: 'settings.authentication.form.create',
name: `settings.definition.delete_value.property.allowedOrganizations.name.${org}`,
}),
enableFirstMessage: ghContainer.byText('settings.authentication.github.enable_first'),
- insecureConfigWarning: byRole('dialog').byText(
+ insecureConfigWarning: byRole('alertdialog').byText(
'settings.authentication.github.provisioning_change.insecure_config',
),
jitProvisioningButton: ghContainer.byRole('radio', {
await user.click(ui.deleteOrg('organization1').get());
await user.click(ui.saveConfigButton.get());
+ await user.click(ui.confirmProvisioningButton.get());
await user.click(await ui.editConfigButton.find());
await user.click(ui.saveGithubProvisioning.get());
expect(ui.insecureConfigWarning.get()).toBeInTheDocument();
- await user.click(ui.confirmProvisioningButton.get());
+ await user.click(await ui.confirmProvisioningButton.find());
await user.click(ui.projectVisibility.get());
await user.click(ui.saveGithubProvisioning.get());
confirmAutoProvisioningDialog: byRole('dialog', {
name: 'settings.authentication.gitlab.confirm.AUTO_PROVISIONING',
}),
- confirmJitProvisioningDialog: byRole('dialog', {
+ confirmJitProvisioningDialog: byRole('alertdialog', {
name: 'settings.authentication.gitlab.confirm.JIT',
}),
- confirmInsecureProvisioningDialog: byRole('dialog', {
+ confirmInsecureProvisioningDialog: byRole('alertdialog', {
name: 'settings.authentication.gitlab.confirm.insecure',
}),
confirmProvisioningChange: byRole('button', {
* 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 Echoes from '@sonarsource/echoes-react';
import * as React from 'react';
import ConfirmModal, { ConfirmModalProps } from './ConfirmModal';
import ModalButton, { ChildrenProps, ModalProps } from './ModalButton';
-interface Props<T> extends Omit<ConfirmModalProps<T>, 'children'> {
+interface Props<T> extends Omit<ConfirmModalProps<T>, 'children' | 'isOpen'> {
children: (props: ChildrenProps) => React.ReactNode;
modalBody: React.ReactNode;
modalHeader: string;
modal: boolean;
}
+/** @deprecated Use {@link Echoes.ModalAlert | ModalAlert} from Echoes instead.
+ * See the {@link https://xtranet-sonarsource.atlassian.net/wiki/spaces/Platform/pages/3465543707/Modals | Migration Guide}
+ */
export default class ConfirmButton<T> extends React.PureComponent<Props<T>, State> {
renderConfirmModal = ({ onClose }: ModalProps) => {
const { children, modalBody, modalHeader, modalHeaderDescription, ...confirmModalProps } =
header={modalHeader}
headerDescription={modalHeaderDescription}
onClose={onClose}
+ isOpen
{...confirmModalProps}
>
{modalBody}
* 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, DangerButtonPrimary, Modal } from 'design-system';
+import * as Echoes from '@sonarsource/echoes-react';
+import { Button, ButtonVariety, ModalAlert } from '@sonarsource/echoes-react';
import React from 'react';
import { translate } from '../../helpers/l10n';
-import ClickEventBoundary from './ClickEventBoundary';
export interface ConfirmModalProps<T> {
cancelButtonText?: string;
confirmData?: T;
confirmDisable?: boolean;
isDestructive?: boolean;
+ isOpen: boolean;
onConfirm: (data?: T) => void | Promise<void | Response>;
}
onClose: () => void;
}
+/** @deprecated Use {@link Echoes.ModalAlert | ModalAlert} from Echoes instead.
+ * See the {@link https://xtranet-sonarsource.atlassian.net/wiki/spaces/Platform/pages/3465543707/Modals | Migration Guide}
+ */
export default function ConfirmModal<T = string>(props: Readonly<Props<T>>) {
const {
header,
confirmDisable,
headerDescription,
isDestructive,
+ isOpen,
cancelButtonText = translate('cancel'),
} = props;
if (result) {
return result.then(
() => {
+ setSubmitting(false);
onClose();
},
() => {
- /* noop */
+ setSubmitting(false);
},
);
}
+ setSubmitting(false);
onClose();
return undefined;
}, [confirmData, onClose, onConfirm, setSubmitting]);
- const Button = isDestructive ? DangerButtonPrimary : ButtonPrimary;
-
return (
- <Modal
- headerTitle={header}
- headerDescription={headerDescription}
- body={
- <ClickEventBoundary>
- <>{children}</>
- </ClickEventBoundary>
- }
+ <ModalAlert
+ title={header}
+ description={headerDescription}
+ isOpen={isOpen}
+ onOpenChange={onClose}
+ content={children}
primaryButton={
- <Button autoFocus disabled={submitting || confirmDisable} onClick={handleSubmit}>
+ <Button
+ variety={isDestructive ? ButtonVariety.Danger : ButtonVariety.Primary}
+ isDisabled={submitting || confirmDisable}
+ isLoading={submitting}
+ onClick={handleSubmit}
+ >
{confirmButtonText}
</Button>
}
secondaryButtonLabel={cancelButtonText}
- loading={submitting}
- onClose={onClose}
/>
);
}
* 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 Echoes from '@sonarsource/echoes-react';
import * as React from 'react';
export interface ChildrenProps {
modal: boolean;
}
+/** @deprecated Use either {@link Echoes.Modal | Modal} or {@link Echoes.ModalAlert | ModalAlert} from Echoes instead.
+ * See the {@link https://xtranet-sonarsource.atlassian.net/wiki/spaces/Platform/pages/3465543707/Modals | Migration Guide}
+ */
export default class ModalButton extends React.PureComponent<Props, State> {
mounted = false;
state: State = { modal: false };
confirmButtonText={translate('confirm')}
header={translate('project_permission.remove_only_confirmation_title')}
isDestructive
+ isOpen
onClose={() => setConfirmPermission(null)}
onConfirm={() => handleChangePermission(confirmPermission.key)}
>
languageName: node
linkType: hard
-"@mantine/core@patch:@mantine/core@6.0.21#./config/patches/@mantine-core-npm-6.0.21-4d202d6649.patch::locator=%40sonarsource%2Fechoes-react%40npm%3A0.5.0":
+"@mantine/core@patch:@mantine/core@6.0.21#./config/patches/@mantine-core-npm-6.0.21-4d202d6649.patch::locator=%40sonarsource%2Fechoes-react%40npm%3A0.6.0":
version: 6.0.21
- resolution: "@mantine/core@patch:@mantine/core@npm%3A6.0.21#./config/patches/@mantine-core-npm-6.0.21-4d202d6649.patch::version=6.0.21&hash=070d0f&locator=%40sonarsource%2Fechoes-react%40npm%3A0.5.0"
+ resolution: "@mantine/core@patch:@mantine/core@npm%3A6.0.21#./config/patches/@mantine-core-npm-6.0.21-4d202d6649.patch::version=6.0.21&hash=070d0f&locator=%40sonarsource%2Fechoes-react%40npm%3A0.6.0"
dependencies:
"@floating-ui/react": "npm:^0.19.1"
"@mantine/styles": "npm:6.0.21"
languageName: node
linkType: hard
+"@radix-ui/react-alert-dialog@npm:1.1.1":
+ version: 1.1.1
+ resolution: "@radix-ui/react-alert-dialog@npm:1.1.1"
+ dependencies:
+ "@radix-ui/primitive": "npm:1.1.0"
+ "@radix-ui/react-compose-refs": "npm:1.1.0"
+ "@radix-ui/react-context": "npm:1.1.0"
+ "@radix-ui/react-dialog": "npm:1.1.1"
+ "@radix-ui/react-primitive": "npm:2.0.0"
+ "@radix-ui/react-slot": "npm:1.1.0"
+ peerDependencies:
+ "@types/react": "*"
+ "@types/react-dom": "*"
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ "@types/react-dom":
+ optional: true
+ checksum: 10/5049255a4b31b2d65c36235c32016c54234f7580778639b416c3c169af545a2522f9f452d6eb1d9d31c0d2e4d9ca2ae36cac4876bec31f5cc422e92acba5557c
+ languageName: node
+ linkType: hard
+
"@radix-ui/react-arrow@npm:1.1.0":
version: 1.1.0
resolution: "@radix-ui/react-arrow@npm:1.1.0"
languageName: node
linkType: hard
+"@radix-ui/react-dialog@npm:1.1.1":
+ version: 1.1.1
+ resolution: "@radix-ui/react-dialog@npm:1.1.1"
+ dependencies:
+ "@radix-ui/primitive": "npm:1.1.0"
+ "@radix-ui/react-compose-refs": "npm:1.1.0"
+ "@radix-ui/react-context": "npm:1.1.0"
+ "@radix-ui/react-dismissable-layer": "npm:1.1.0"
+ "@radix-ui/react-focus-guards": "npm:1.1.0"
+ "@radix-ui/react-focus-scope": "npm:1.1.0"
+ "@radix-ui/react-id": "npm:1.1.0"
+ "@radix-ui/react-portal": "npm:1.1.1"
+ "@radix-ui/react-presence": "npm:1.1.0"
+ "@radix-ui/react-primitive": "npm:2.0.0"
+ "@radix-ui/react-slot": "npm:1.1.0"
+ "@radix-ui/react-use-controllable-state": "npm:1.1.0"
+ aria-hidden: "npm:^1.1.1"
+ react-remove-scroll: "npm:2.5.7"
+ peerDependencies:
+ "@types/react": "*"
+ "@types/react-dom": "*"
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ "@types/react-dom":
+ optional: true
+ checksum: 10/8c4b4af680e306db4fe113e9e19eb173f633b40b267030906167f6f62adfaa02aa2cb8ee97775ad0c701242a5ef6d0ce3528e1b13e640cbc6ea356eb27836189
+ languageName: node
+ linkType: hard
+
"@radix-ui/react-direction@npm:1.0.0":
version: 1.0.0
resolution: "@radix-ui/react-direction@npm:1.0.0"
languageName: node
linkType: hard
-"@sonarsource/echoes-react@npm:0.5.0":
- version: 0.5.0
- resolution: "@sonarsource/echoes-react@npm:0.5.0"
+"@sonarsource/echoes-react@npm:0.6.0":
+ version: 0.6.0
+ resolution: "@sonarsource/echoes-react@npm:0.6.0"
dependencies:
"@mantine/core": "patch:@mantine/core@6.0.21#./config/patches/@mantine-core-npm-6.0.21-4d202d6649.patch"
"@mantine/hooks": "npm:6.0.21"
"@material-symbols/font-400": "npm:0.21.1"
+ "@radix-ui/react-alert-dialog": "npm:1.1.1"
"@radix-ui/react-checkbox": "npm:1.1.1"
+ "@radix-ui/react-dialog": "npm:1.1.1"
"@radix-ui/react-dropdown-menu": "npm:2.1.1"
"@radix-ui/react-popover": "npm:1.1.1"
"@radix-ui/react-radio-group": "npm:1.2.0"
react-dom: ^17.0.0 || ^18.0.0
react-intl: ^6.0.0
react-router-dom: ^6.0.0
- checksum: 10/ffe4b12cce1cf8a66ce89c8fd255d288292a73542de87806d2283553aa44804a90e37a559230d224a6b5fc20a245463969fc350cc742e13bab35355c9853f4bf
+ checksum: 10/bfeca900430708f94e7b8f8fb9e8aef2c55028b1e621eb6df6d192fdeffd63d67f929715ca600cb36936af81b6df1738502a065ba2947fc23755d8780988db62
languageName: node
linkType: hard
"@primer/octicons-react": "npm:19.10.0"
"@react-spring/rafz": "npm:9.7.3"
"@react-spring/web": "npm:9.7.3"
- "@sonarsource/echoes-react": "npm:0.5.0"
+ "@sonarsource/echoes-react": "npm:0.6.0"
"@swc/core": "npm:1.6.6"
"@swc/jest": "npm:0.2.36"
"@tanstack/react-query": "npm:5.18.1"
"@babel/preset-typescript": "npm:7.24.7"
"@emotion/babel-plugin": "npm:11.11.0"
"@emotion/babel-plugin-jsx-pragmatic": "npm:0.2.1"
- "@sonarsource/echoes-react": "npm:0.5.0"
+ "@sonarsource/echoes-react": "npm:0.6.0"
"@testing-library/dom": "npm:10.2.0"
"@testing-library/jest-dom": "npm:6.4.6"
"@testing-library/react": "npm:16.0.0"