* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import classNames from 'classnames';
+
+import { Badge } from 'design-system';
import * as React from 'react';
import Tooltip from '../../../components/controls/Tooltip';
import { translate } from '../../../helpers/l10n';
export default function BuiltInQualityGateBadge({ className }: Props) {
return (
<Tooltip overlay={translate('quality_gates.built_in.help')}>
- <div className={classNames('badge', className)}>{translate('quality_gates.built_in')}</div>
+ <Badge className={className}>{translate('quality_gates.built_in')}</Badge>
</Tooltip>
);
}
* 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, InputField, Modal } from 'design-system';
import * as React from 'react';
import { copyQualityGate } from '../../../api/quality-gates';
-import ConfirmModal from '../../../components/controls/ConfirmModal';
import { Router, withRouter } from '../../../components/hoc/withRouter';
-import MandatoryFieldMarker from '../../../components/ui/MandatoryFieldMarker';
import MandatoryFieldsExplanation from '../../../components/ui/MandatoryFieldsExplanation';
import { translate } from '../../../helpers/l10n';
import { getQualityGateUrl } from '../../../helpers/urls';
name: string;
}
+const FORM_ID = 'rename-quality-gate';
+
export class CopyQualityGateForm extends React.PureComponent<Props, State> {
constructor(props: Props) {
super(props);
this.setState({ name: event.currentTarget.value });
};
- handleCopy = () => {
+ handleCopy = (event: React.FormEvent) => {
+ event.preventDefault();
+
const { qualityGate } = this.props;
const { name } = this.state;
render() {
const { qualityGate } = this.props;
const { name } = this.state;
- const confirmDisable = !name || (qualityGate && qualityGate.name === name);
+ const buttonDisabled = !name || (qualityGate && qualityGate.name === name);
return (
- <ConfirmModal
- confirmButtonText={translate('copy')}
- confirmDisable={confirmDisable}
- header={translate('quality_gates.copy')}
+ <Modal
+ headerTitle={translate('quality_gates.copy')}
onClose={this.props.onClose}
- onConfirm={this.handleCopy}
- size="small"
- >
- <MandatoryFieldsExplanation className="modal-field" />
- <div className="modal-field">
- <label htmlFor="quality-gate-form-name">
- {translate('name')}
- <MandatoryFieldMarker />
- </label>
- <input
- autoFocus
- id="quality-gate-form-name"
- maxLength={100}
- onChange={this.handleNameChange}
- required
- size={50}
- type="text"
- value={name}
- />
- </div>
- </ConfirmModal>
+ body={
+ <form id={FORM_ID} onSubmit={this.handleCopy}>
+ <MandatoryFieldsExplanation />
+ <FormField
+ label={translate('name')}
+ htmlFor="quality-gate-form-name"
+ required
+ className="sw-my-2"
+ >
+ <InputField
+ autoFocus
+ id="quality-gate-form-name"
+ maxLength={100}
+ onChange={this.handleNameChange}
+ size="auto"
+ type="text"
+ value={name}
+ />
+ </FormField>
+ </form>
+ }
+ primaryButton={
+ <ButtonPrimary autoFocus type="submit" disabled={buttonDisabled} form={FORM_ID}>
+ {translate('copy')}
+ </ButtonPrimary>
+ }
+ secondaryButtonLabel={translate('cancel')}
+ />
);
}
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { DangerButtonPrimary, Modal } from 'design-system';
import * as React from 'react';
import { deleteQualityGate } from '../../../api/quality-gates';
-import { Button } from '../../../components/controls/buttons';
-import ConfirmButton from '../../../components/controls/ConfirmButton';
import { Router, withRouter } from '../../../components/hoc/withRouter';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { getQualityGatesUrl } from '../../../helpers/urls';
import { QualityGate } from '../../../types/types';
interface Props {
+ readonly onClose: () => void;
onDelete: () => Promise<void>;
qualityGate: QualityGate;
router: Router;
const { qualityGate } = this.props;
return (
- <ConfirmButton
- confirmButtonText={translate('delete')}
- isDestructive
- modalBody={translateWithParameters(
- 'quality_gates.delete.confirm.message',
- qualityGate.name,
- )}
- modalHeader={translate('quality_gates.delete')}
- onConfirm={this.onDelete}
- >
- {({ onClick }) => (
- <Button
- className="little-spacer-left button-red"
- id="quality-gate-delete"
- onClick={onClick}
- >
+ <Modal
+ headerTitle={translate('quality_gates.delete')}
+ onClose={this.props.onClose}
+ body={translateWithParameters('quality_gates.delete.confirm.message', qualityGate.name)}
+ primaryButton={
+ <DangerButtonPrimary autoFocus type="submit" onClick={this.onDelete}>
{translate('delete')}
- </Button>
- )}
- </ConfirmButton>
+ </DangerButtonPrimary>
+ }
+ secondaryButtonLabel={translate('cancel')}
+ />
);
}
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
+import {
+ ActionsDropdown,
+ Badge,
+ ButtonSecondary,
+ DangerButtonPrimary,
+ FlagWarningIcon,
+ ItemButton,
+ ItemDangerButton,
+ ItemDivider,
+ SubTitle,
+} from 'design-system';
+import { countBy } from 'lodash';
import * as React from 'react';
import { setQualityGateAsDefault } from '../../../api/quality-gates';
-import ModalButton from '../../../components/controls/ModalButton';
import Tooltip from '../../../components/controls/Tooltip';
-import { Button } from '../../../components/controls/buttons';
-import AlertWarnIcon from '../../../components/icons/AlertWarnIcon';
import { translate } from '../../../helpers/l10n';
import { CaycStatus, QualityGate } from '../../../types/types';
import BuiltInQualityGateBadge from './BuiltInQualityGateBadge';
const TOOLTIP_MOUSE_LEAVE_DELAY = 0.3;
-export default class DetailsHeader extends React.PureComponent<Props> {
- handleActionRefresh = () => {
- const { refreshItem, refreshList } = this.props;
+export default function DetailsHeader({
+ refreshItem,
+ refreshList,
+ onSetDefault,
+ qualityGate,
+}: Props) {
+ const [isRenameFormOpen, setIsRenameFormOpen] = React.useState(false);
+ const [isCopyFormOpen, setIsCopyFormOpen] = React.useState(false);
+ const [isRemoveFormOpen, setIsRemoveFormOpen] = React.useState(false);
+ const actions = qualityGate.actions ?? {};
+ const actionsCount = countBy([
+ actions.rename,
+ actions.copy,
+ actions.delete,
+ actions.setAsDefault,
+ ])['true'];
+ const canEdit = Boolean(actions?.manageConditions);
+
+ const handleActionRefresh = () => {
return Promise.all([refreshItem(), refreshList()]).then(
() => {},
() => {},
);
};
- handleSetAsDefaultClick = () => {
- const { qualityGate } = this.props;
+ const handleSetAsDefaultClick = () => {
if (!qualityGate.isDefault) {
// Optimistic update
- this.props.onSetDefault();
+ onSetDefault();
setQualityGateAsDefault({ name: qualityGate.name }).then(
- this.handleActionRefresh,
- this.handleActionRefresh,
+ handleActionRefresh,
+ handleActionRefresh,
);
}
};
- render() {
- const { qualityGate } = this.props;
- const actions = qualityGate.actions || ({} as any);
- const canEdit = Boolean(actions?.manageConditions);
-
- return (
- <div className="layout-page-header-panel layout-page-main-header issues-main-header">
- <div className="layout-page-header-panel-inner layout-page-main-header-inner">
- <div className="layout-page-main-inner">
- <div className="pull-left display-flex-center">
- <h2>{qualityGate.name}</h2>
- {qualityGate.isBuiltIn && <BuiltInQualityGateBadge className="spacer-left" />}
- {qualityGate.caycStatus === CaycStatus.NonCompliant && canEdit && (
- <Tooltip overlay={<CaycBadgeTooltip />} mouseLeaveDelay={TOOLTIP_MOUSE_LEAVE_DELAY}>
- <AlertWarnIcon className="spacer-left" description={<CaycBadgeTooltip />} />
- </Tooltip>
- )}
- </div>
-
- <div className="pull-right">
- {actions.rename && (
- <ModalButton
- modal={({ onClose }) => (
- <RenameQualityGateForm
- onClose={onClose}
- onRename={this.props.refreshList}
- qualityGate={qualityGate}
- />
- )}
+ return (
+ <>
+ <div className="it__layout-page-main-header sw-flex sw-items-center sw-justify-between sw-mb-9">
+ <div className="sw-flex sw-flex-col">
+ <div className="sw-flex sw-items-center">
+ <SubTitle className="sw-m-0">{qualityGate.name}</SubTitle>
+ {qualityGate.caycStatus === CaycStatus.NonCompliant && canEdit && (
+ <Tooltip overlay={<CaycBadgeTooltip />} mouseLeaveDelay={TOOLTIP_MOUSE_LEAVE_DELAY}>
+ <FlagWarningIcon className="sw-ml-2" description={<CaycBadgeTooltip />} />
+ </Tooltip>
+ )}
+ </div>
+ <div className="sw-flex sw-gap-2 sw-mt-4">
+ {qualityGate.isDefault && <Badge>{translate('default')}</Badge>}
+ {qualityGate.isBuiltIn && <BuiltInQualityGateBadge />}
+ </div>
+ </div>
+ {actionsCount === 1 && (
+ <>
+ {actions.rename && (
+ <ButtonSecondary onClick={() => setIsRenameFormOpen(true)}>
+ {translate('rename')}
+ </ButtonSecondary>
+ )}
+ {actions.copy && (
+ <Tooltip
+ overlay={
+ qualityGate.caycStatus === CaycStatus.NonCompliant
+ ? translate('quality_gates.cannot_copy_no_cayc')
+ : null
+ }
+ >
+ <ButtonSecondary
+ disabled={qualityGate.caycStatus === CaycStatus.NonCompliant}
+ onClick={() => setIsCopyFormOpen(true)}
>
- {({ onClick }) => (
- <Button id="quality-gate-rename" onClick={onClick}>
- {translate('rename')}
- </Button>
- )}
- </ModalButton>
- )}
- {actions.copy && (
- <ModalButton
- modal={({ onClose }) => (
- <CopyQualityGateForm
- onClose={onClose}
- onCopy={this.handleActionRefresh}
- qualityGate={qualityGate}
- />
- )}
+ {translate('copy')}
+ </ButtonSecondary>
+ </Tooltip>
+ )}
+ {actions.setAsDefault && (
+ <Tooltip
+ overlay={
+ qualityGate.caycStatus === CaycStatus.NonCompliant
+ ? translate('quality_gates.cannot_set_default_no_cayc')
+ : null
+ }
+ >
+ <ButtonSecondary
+ disabled={qualityGate.caycStatus === CaycStatus.NonCompliant}
+ onClick={handleSetAsDefaultClick}
>
- {({ onClick }) => (
- <Tooltip
- overlay={
- qualityGate.caycStatus === CaycStatus.NonCompliant
- ? translate('quality_gates.cannot_copy_no_cayc')
- : null
- }
- >
- <Button
- className="little-spacer-left"
- id="quality-gate-copy"
- onClick={onClick}
- disabled={qualityGate.caycStatus === CaycStatus.NonCompliant}
- >
- {translate('copy')}
- </Button>
- </Tooltip>
- )}
- </ModalButton>
- )}
- {actions.setAsDefault && (
- <Tooltip
- overlay={
- qualityGate.caycStatus === CaycStatus.NonCompliant
- ? translate('quality_gates.cannot_set_default_no_cayc')
- : null
- }
+ {translate('set_as_default')}
+ </ButtonSecondary>
+ </Tooltip>
+ )}
+ {actions.delete && (
+ <DangerButtonPrimary onClick={() => setIsRemoveFormOpen(true)}>
+ {translate('delete')}
+ </DangerButtonPrimary>
+ )}
+ </>
+ )}
+
+ {actionsCount > 1 && (
+ <ActionsDropdown allowResizing id="quality-gate-actions">
+ {actions.rename && (
+ <ItemButton onClick={() => setIsRenameFormOpen(true)}>
+ {translate('rename')}
+ </ItemButton>
+ )}
+ {actions.copy && (
+ <Tooltip
+ overlay={
+ qualityGate.caycStatus === CaycStatus.NonCompliant
+ ? translate('quality_gates.cannot_copy_no_cayc')
+ : null
+ }
+ >
+ <ItemButton
+ disabled={qualityGate.caycStatus === CaycStatus.NonCompliant}
+ onClick={() => setIsCopyFormOpen(true)}
>
- <Button
- className="little-spacer-left"
- disabled={qualityGate.caycStatus === CaycStatus.NonCompliant}
- id="quality-gate-toggle-default"
- onClick={this.handleSetAsDefaultClick}
- >
- {translate('set_as_default')}
- </Button>
- </Tooltip>
- )}
- {actions.delete && (
- <DeleteQualityGateForm
- onDelete={this.props.refreshList}
- qualityGate={qualityGate}
- />
- )}
- </div>
- </div>
- </div>
+ {translate('copy')}
+ </ItemButton>
+ </Tooltip>
+ )}
+ {actions.setAsDefault && (
+ <Tooltip
+ overlay={
+ qualityGate.caycStatus === CaycStatus.NonCompliant
+ ? translate('quality_gates.cannot_set_default_no_cayc')
+ : null
+ }
+ >
+ <ItemButton
+ disabled={qualityGate.caycStatus === CaycStatus.NonCompliant}
+ onClick={handleSetAsDefaultClick}
+ >
+ {translate('set_as_default')}
+ </ItemButton>
+ </Tooltip>
+ )}
+ {actions.delete && (
+ <>
+ <ItemDivider />
+ <ItemDangerButton onClick={() => setIsRemoveFormOpen(true)}>
+ {translate('delete')}
+ </ItemDangerButton>
+ </>
+ )}
+ </ActionsDropdown>
+ )}
</div>
- );
- }
+
+ {isRenameFormOpen && (
+ <RenameQualityGateForm
+ onClose={() => setIsRenameFormOpen(false)}
+ onRename={handleActionRefresh}
+ qualityGate={qualityGate}
+ />
+ )}
+
+ {isCopyFormOpen && (
+ <CopyQualityGateForm
+ onClose={() => setIsCopyFormOpen(false)}
+ onCopy={handleActionRefresh}
+ qualityGate={qualityGate}
+ />
+ )}
+
+ {isRemoveFormOpen && (
+ <DeleteQualityGateForm
+ onClose={() => setIsRemoveFormOpen(false)}
+ onDelete={refreshList}
+ qualityGate={qualityGate}
+ />
+ )}
+ </>
+ );
}
* 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, InputField, Modal } from 'design-system/lib';
import * as React from 'react';
import { renameQualityGate } from '../../../api/quality-gates';
-import ConfirmModal from '../../../components/controls/ConfirmModal';
-import { withRouter, WithRouterProps } from '../../../components/hoc/withRouter';
-import MandatoryFieldMarker from '../../../components/ui/MandatoryFieldMarker';
+import { WithRouterProps, withRouter } from '../../../components/hoc/withRouter';
import MandatoryFieldsExplanation from '../../../components/ui/MandatoryFieldsExplanation';
import { translate } from '../../../helpers/l10n';
import { getQualityGateUrl } from '../../../helpers/urls';
name: string;
}
+const FORM_ID = 'rename-quality-gate';
+
class RenameQualityGateForm extends React.PureComponent<Props, State> {
constructor(props: Props) {
super(props);
this.setState({ name: event.currentTarget.value });
};
- handleRename = () => {
+ handleRename = (event: React.FormEvent) => {
+ event.preventDefault();
+
const { qualityGate, router } = this.props;
const { name } = this.state;
return renameQualityGate({ currentName: qualityGate.name, name }).then(() => {
- this.props.onRename();
router.push(getQualityGateUrl(name));
+ this.props.onRename();
});
};
const confirmDisable = !name || (qualityGate && qualityGate.name === name);
return (
- <ConfirmModal
- confirmButtonText={translate('rename')}
- confirmDisable={confirmDisable}
- header={translate('quality_gates.rename')}
+ <Modal
+ headerTitle={translate('quality_gates.rename')}
onClose={this.props.onClose}
- onConfirm={this.handleRename}
- size="small"
- >
- <MandatoryFieldsExplanation className="modal-field" />
- <div className="modal-field">
- <label htmlFor="quality-gate-form-name">
- {translate('name')}
- <MandatoryFieldMarker />
- </label>
- <input
- autoFocus
- id="quality-gate-form-name"
- maxLength={100}
- onChange={this.handleNameChange}
- required
- size={50}
- type="text"
- value={name}
- />
- </div>
- </ConfirmModal>
+ body={
+ <form id={FORM_ID} onSubmit={this.handleRename}>
+ <MandatoryFieldsExplanation />
+ <FormField
+ label={translate('name')}
+ htmlFor="quality-gate-form-name"
+ required
+ className="sw-my-2"
+ >
+ <InputField
+ autoFocus
+ id="quality-gate-form-name"
+ maxLength={100}
+ onChange={this.handleNameChange}
+ size="auto"
+ type="text"
+ value={name}
+ />
+ </FormField>
+ </form>
+ }
+ primaryButton={
+ <ButtonPrimary autoFocus type="submit" disabled={confirmDisable} form={FORM_ID}>
+ {translate('rename')}
+ </ButtonPrimary>
+ }
+ secondaryButtonLabel={translate('cancel')}
+ />
);
}
}
// Delete the quality gate
await user.click(newQG);
- const deleteButton = await screen.findByRole('button', { name: 'delete' });
+
+ await user.click(screen.getByLabelText('menu'));
+ const deleteButton = screen.getByRole('menuitem', { name: 'delete' });
await user.click(deleteButton);
const popup = screen.getByRole('dialog');
const dialogDeleteButton = within(popup).getByRole('button', { name: 'delete' });
const notDefaultQualityGate = await screen.findByText('Sonar way');
await user.click(notDefaultQualityGate);
- const copyButton = await screen.findByRole('button', { name: 'copy' });
+ await user.click(screen.getByLabelText('menu'));
+ const copyButton = screen.getByRole('menuitem', { name: 'copy' });
await user.click(copyButton);
const nameInput = screen.getByRole('textbox', { name: /name.*/ });
const notDefaultQualityGate = await screen.findByText('SonarSource way - CFamily');
await user.click(notDefaultQualityGate);
-
- const copyButton = await screen.findByRole('button', { name: 'copy' });
+ await user.click(screen.getByLabelText('menu'));
+ const copyButton = screen.getByRole('menuitem', { name: 'copy' });
expect(copyButton).toBeDisabled();
});
const user = userEvent.setup();
handler.setIsAdmin(true);
renderQualityGateApp();
-
- const renameButton = await screen.findByRole('button', { name: 'rename' });
+ await user.click(await screen.findByLabelText('menu'));
+ const renameButton = screen.getByRole('menuitem', { name: 'rename' });
await user.click(renameButton);
const nameInput = screen.getByRole('textbox', { name: /name.*/ });
const notDefaultQualityGate = await screen.findByText('SonarSource way - CFamily');
await user.click(notDefaultQualityGate);
- const setAsDefaultButton = screen.getByRole('button', { name: 'set_as_default' });
+ await user.click(screen.getByLabelText('menu'));
+ const setAsDefaultButton = screen.getByRole('menuitem', { name: 'set_as_default' });
expect(setAsDefaultButton).toBeDisabled();
});
const notDefaultQualityGate = await screen.findByRole('button', { name: /Sonar way/ });
await user.click(notDefaultQualityGate);
- const setAsDefaultButton = screen.getByRole('button', { name: 'set_as_default' });
+ await user.click(screen.getByLabelText('menu'));
+ const setAsDefaultButton = screen.getByRole('menuitem', { name: 'set_as_default' });
await user.click(setAsDefaultButton);
expect(screen.getByRole('button', { name: /Sonar way default/ })).toBeInTheDocument();
});