From ea198cf0fad05628dd00f84d8b780130a9e2f3f2 Mon Sep 17 00:00:00 2001 From: Revanshu Paliwal Date: Thu, 14 Sep 2023 10:52:57 +0200 Subject: [PATCH] SONAR-20337 Migrating quality gate header to MIUI --- .../components/BuiltInQualityGateBadge.tsx | 5 +- .../components/CopyQualityGateForm.tsx | 66 +++-- .../components/DeleteQualityGateForm.tsx | 33 +-- .../components/DetailsHeader.tsx | 276 +++++++++++------- .../components/RenameQualityGateForm.tsx | 68 +++-- .../components/__tests__/QualityGate-it.tsx | 21 +- 6 files changed, 275 insertions(+), 194 deletions(-) diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/BuiltInQualityGateBadge.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/BuiltInQualityGateBadge.tsx index 341704ba7d3..b2c5203171b 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/BuiltInQualityGateBadge.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/BuiltInQualityGateBadge.tsx @@ -17,7 +17,8 @@ * 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'; @@ -29,7 +30,7 @@ interface Props { export default function BuiltInQualityGateBadge({ className }: Props) { return ( -
{translate('quality_gates.built_in')}
+ {translate('quality_gates.built_in')}
); } diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/CopyQualityGateForm.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/CopyQualityGateForm.tsx index f14b9ee6ea6..13193152b68 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/CopyQualityGateForm.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/CopyQualityGateForm.tsx @@ -17,11 +17,10 @@ * 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'; @@ -38,6 +37,8 @@ interface State { name: string; } +const FORM_ID = 'rename-quality-gate'; + export class CopyQualityGateForm extends React.PureComponent { constructor(props: Props) { super(props); @@ -48,7 +49,9 @@ export class CopyQualityGateForm extends React.PureComponent { this.setState({ name: event.currentTarget.value }); }; - handleCopy = () => { + handleCopy = (event: React.FormEvent) => { + event.preventDefault(); + const { qualityGate } = this.props; const { name } = this.state; @@ -61,35 +64,40 @@ export class CopyQualityGateForm extends React.PureComponent { render() { const { qualityGate } = this.props; const { name } = this.state; - const confirmDisable = !name || (qualityGate && qualityGate.name === name); + const buttonDisabled = !name || (qualityGate && qualityGate.name === name); return ( - - -
- - -
-
+ body={ +
+ + + + + + } + primaryButton={ + + {translate('copy')} + + } + secondaryButtonLabel={translate('cancel')} + /> ); } } diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/DeleteQualityGateForm.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/DeleteQualityGateForm.tsx index b58782e8634..a022e111554 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/DeleteQualityGateForm.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/DeleteQualityGateForm.tsx @@ -17,16 +17,16 @@ * 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; qualityGate: QualityGate; router: Router; @@ -46,26 +46,17 @@ export class DeleteQualityGateForm extends React.PureComponent { const { qualityGate } = this.props; return ( - - {({ onClick }) => ( - - )} - + + } + secondaryButtonLabel={translate('cancel')} + /> ); } } diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsHeader.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsHeader.tsx index 4aff608825c..ed02c326faa 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsHeader.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsHeader.tsx @@ -17,12 +17,22 @@ * 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'; @@ -40,122 +50,180 @@ interface Props { const TOOLTIP_MOUSE_LEAVE_DELAY = 0.3; -export default class DetailsHeader extends React.PureComponent { - 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 ( -
-
-
-
-

{qualityGate.name}

- {qualityGate.isBuiltIn && } - {qualityGate.caycStatus === CaycStatus.NonCompliant && canEdit && ( - } mouseLeaveDelay={TOOLTIP_MOUSE_LEAVE_DELAY}> - } /> - - )} -
- -
- {actions.rename && ( - ( - - )} + return ( + <> +
+
+
+ {qualityGate.name} + {qualityGate.caycStatus === CaycStatus.NonCompliant && canEdit && ( + } mouseLeaveDelay={TOOLTIP_MOUSE_LEAVE_DELAY}> + } /> + + )} +
+
+ {qualityGate.isDefault && {translate('default')}} + {qualityGate.isBuiltIn && } +
+
+ {actionsCount === 1 && ( + <> + {actions.rename && ( + setIsRenameFormOpen(true)}> + {translate('rename')} + + )} + {actions.copy && ( + + setIsCopyFormOpen(true)} > - {({ onClick }) => ( - - )} - - )} - {actions.copy && ( - ( - - )} + {translate('copy')} + + + )} + {actions.setAsDefault && ( + + - {({ onClick }) => ( - - - - )} - - )} - {actions.setAsDefault && ( - + + )} + {actions.delete && ( + setIsRemoveFormOpen(true)}> + {translate('delete')} + + )} + + )} + + {actionsCount > 1 && ( + + {actions.rename && ( + setIsRenameFormOpen(true)}> + {translate('rename')} + + )} + {actions.copy && ( + + setIsCopyFormOpen(true)} > - - - )} - {actions.delete && ( - - )} -
-
-
+ {translate('copy')} + + + )} + {actions.setAsDefault && ( + + + {translate('set_as_default')} + + + )} + {actions.delete && ( + <> + + setIsRemoveFormOpen(true)}> + {translate('delete')} + + + )} + + )}
- ); - } + + {isRenameFormOpen && ( + setIsRenameFormOpen(false)} + onRename={handleActionRefresh} + qualityGate={qualityGate} + /> + )} + + {isCopyFormOpen && ( + setIsCopyFormOpen(false)} + onCopy={handleActionRefresh} + qualityGate={qualityGate} + /> + )} + + {isRemoveFormOpen && ( + setIsRemoveFormOpen(false)} + onDelete={refreshList} + qualityGate={qualityGate} + /> + )} + + ); } diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/RenameQualityGateForm.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/RenameQualityGateForm.tsx index f1dc6dc79fa..9d2c26bf662 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/RenameQualityGateForm.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/RenameQualityGateForm.tsx @@ -17,11 +17,10 @@ * 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'; @@ -37,6 +36,8 @@ interface State { name: string; } +const FORM_ID = 'rename-quality-gate'; + class RenameQualityGateForm extends React.PureComponent { constructor(props: Props) { super(props); @@ -47,13 +48,15 @@ class RenameQualityGateForm extends React.PureComponent { 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(); }); }; @@ -63,32 +66,37 @@ class RenameQualityGateForm extends React.PureComponent { const confirmDisable = !name || (qualityGate && qualityGate.name === name); return ( - - -
- - -
-
+ body={ +
+ + + + + + } + primaryButton={ + + {translate('rename')} + + } + secondaryButtonLabel={translate('cancel')} + /> ); } } 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 8522ed7a488..ec15cdda312 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 @@ -96,7 +96,9 @@ it('should be able to create a quality gate then delete it', async () => { // 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' }); @@ -114,7 +116,8 @@ it('should be able to copy a quality gate which is CAYC compliant', async () => 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.*/ }); @@ -133,8 +136,8 @@ it('should not be able to copy a quality gate which is not CAYC compliant', asyn 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(); }); @@ -143,8 +146,8 @@ it('should be able to rename a quality gate', async () => { 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.*/ }); @@ -162,7 +165,8 @@ it('should not be able to set as default a quality gate which is not CAYC compli 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(); }); @@ -173,7 +177,8 @@ it('should be able to set as default a quality gate which is CAYC compliant', as 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(); }); -- 2.39.5