diff options
author | Revanshu Paliwal <revanshu.paliwal@sonarsource.com> | 2024-12-04 13:05:01 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2024-12-06 20:03:27 +0000 |
commit | 5c2caf8c49a4373aabfdab93972efe453254582f (patch) | |
tree | 8d277634d483fd8754fc20c74cd2b8a18ee35434 /server/sonar-web/src/main/js | |
parent | 3521422cfd87bae127c4ccc6a72696ac0d1efae5 (diff) | |
download | sonarqube-5c2caf8c49a4373aabfdab93972efe453254582f.tar.gz sonarqube-5c2caf8c49a4373aabfdab93972efe453254582f.zip |
SONAR-21224 Improving status change drop-down inside issues page
Diffstat (limited to 'server/sonar-web/src/main/js')
5 files changed, 198 insertions, 73 deletions
diff --git a/server/sonar-web/src/main/js/apps/issues/__tests__/IssueApp-it.tsx b/server/sonar-web/src/main/js/apps/issues/__tests__/IssueApp-it.tsx index fb2315539f1..760e791a189 100644 --- a/server/sonar-web/src/main/js/apps/issues/__tests__/IssueApp-it.tsx +++ b/server/sonar-web/src/main/js/apps/issues/__tests__/IssueApp-it.tsx @@ -336,15 +336,25 @@ describe('issue app', () => { await user.click(listItem.getByText('issue.issue_status.OPEN')); + expect(screen.getByText('issue.transition.status_change')).toBeInTheDocument(); expect(listItem.getByText('issue.transition.accept')).toBeInTheDocument(); expect(listItem.getByText('issue.transition.confirm')).toBeInTheDocument(); - await user.click(listItem.getByText('issue.transition.accept')); + // test go back + await user.click(listItem.getByText('issue.transition.falsepositive')); + expect(listItem.getByText('issue.transition.falsepositive')).toBeInTheDocument(); + expect(listItem.queryByText('issue.transition.accept')).not.toBeInTheDocument(); + expect(listItem.queryByText('issue.transition.confirm')).not.toBeInTheDocument(); + await user.click(screen.getByLabelText('go_back')); + expect(listItem.getByText('issue.transition.accept')).toBeInTheDocument(); + // select accept + await user.click(listItem.getByText('issue.transition.accept')); + expect(screen.getByText('issue.transition.go_back_change_status')).toBeInTheDocument(); expect(listItem.getByRole('textbox')).toBeInTheDocument(); await user.type(listItem.getByRole('textbox'), 'test'); - await user.click(listItem.getByText('resolve')); + await user.click(listItem.getByText('issue.transition.change_status')); expect( listItem.getByLabelText( diff --git a/server/sonar-web/src/main/js/components/issue/components/IssueTransitionItem.tsx b/server/sonar-web/src/main/js/components/issue/components/IssueTransitionItem.tsx index bd08b87178d..604fa6f5c3c 100644 --- a/server/sonar-web/src/main/js/components/issue/components/IssueTransitionItem.tsx +++ b/server/sonar-web/src/main/js/components/issue/components/IssueTransitionItem.tsx @@ -18,7 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { IconQuestionMark } from '@sonarsource/echoes-react'; +import { IconArrowRight, IconQuestionMark } from '@sonarsource/echoes-react'; import * as React from 'react'; import { useIntl } from 'react-intl'; import { ItemButton, PageContentFontWrapper, TextBold, TextMuted } from '~design-system'; @@ -27,12 +27,18 @@ import HelpTooltip from '../../../sonar-aligned/components/controls/HelpTooltip' import { IssueTransition } from '../../../types/issues'; type Props = { + hasCommentAction?: boolean; onSelectTransition: (transition: IssueTransition) => void; selected: boolean; transition: IssueTransition; }; -export function IssueTransitionItem({ transition, selected, onSelectTransition }: Readonly<Props>) { +export function IssueTransitionItem({ + transition, + selected, + onSelectTransition, + hasCommentAction = false, +}: Readonly<Props>) { const intl = useIntl(); const tooltips: Record<string, React.JSX.Element> = { @@ -57,7 +63,7 @@ export function IssueTransitionItem({ transition, selected, onSelectTransition } key={transition} onClick={() => onSelectTransition(transition)} selected={selected} - className="sw-px-4" + className="sw-flex sw-items-center sw-justify-between sw-px-4" > <div className="it__issue-transition-option sw-flex sw-flex-col"> <PageContentFontWrapper className="sw-font-semibold sw-flex sw-gap-1 sw-items-center"> @@ -70,6 +76,7 @@ export function IssueTransitionItem({ transition, selected, onSelectTransition } </PageContentFontWrapper> <TextMuted text={translate('issue.transition', transition, 'description')} /> </div> + {hasCommentAction && <IconArrowRight />} </ItemButton> ); } diff --git a/server/sonar-web/src/main/js/components/issue/components/IssueTransitionOverlay.tsx b/server/sonar-web/src/main/js/components/issue/components/IssueTransitionOverlay.tsx index f4a5dd81630..ddd3899df97 100644 --- a/server/sonar-web/src/main/js/components/issue/components/IssueTransitionOverlay.tsx +++ b/server/sonar-web/src/main/js/components/issue/components/IssueTransitionOverlay.tsx @@ -18,21 +18,16 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { Button, ButtonVariety, DropdownMenu, Spinner } from '@sonarsource/echoes-react'; import { useState } from 'react'; -import { useIntl } from 'react-intl'; -import { - ButtonPrimary, - ButtonSecondary, - InputTextArea, - ItemDivider, - PageContentFontWrapper, - Spinner, -} from '~design-system'; +import { InputTextArea } from '~design-system'; import { translate } from '../../../helpers/l10n'; import { IssueActions, IssueTransition } from '../../../types/issues'; import { Issue } from '../../../types/types'; -import { isTransitionDeprecated, isTransitionHidden, transitionRequiresComment } from '../helpers'; +import { isTransitionHidden, transitionRequiresComment } from '../helpers'; import { IssueTransitionItem } from './IssueTransitionItem'; +import IssueTransitionOverlayHeader from './IssueTransitionOverlayHeader'; +import { SelectedTransitionItem } from './SelectedTransitionItem'; export type Props = { issue: Pick<Issue, 'transitions' | 'actions'>; @@ -43,7 +38,6 @@ export type Props = { export function IssueTransitionOverlay(props: Readonly<Props>) { const { issue, onClose, onSetTransition, loading } = props; - const intl = useIntl(); const [comment, setComment] = useState(''); const [selectedTransition, setSelectedTransition] = useState<IssueTransition>(); @@ -68,67 +62,61 @@ export function IssueTransitionOverlay(props: Readonly<Props>) { const filteredTransitions = issue.transitions.filter( (transition) => !isTransitionHidden(transition), ); - const filteredTransitionsRecommended = filteredTransitions.filter( - (t) => !isTransitionDeprecated(t), - ); - const filteredTransitionsDeprecated = filteredTransitions.filter(isTransitionDeprecated); return ( - <ul className="sw-flex sw-flex-col"> - {filteredTransitionsRecommended.map((transition) => ( - <IssueTransitionItem - key={transition} - transition={transition} - selected={selectedTransition === transition} - onSelectTransition={selectTransition} - /> - ))} - {filteredTransitionsRecommended.length > 0 && filteredTransitionsDeprecated.length > 0 && ( - <ItemDivider /> - )} - {filteredTransitionsDeprecated.map((transition) => ( - <IssueTransitionItem - key={transition} - transition={transition} - selected={selectedTransition === transition} - onSelectTransition={selectTransition} - /> - ))} + <Spinner isLoading={!selectedTransition && loading} className="sw-ml-4"> + <IssueTransitionOverlayHeader + onBack={() => setSelectedTransition(undefined)} + onClose={onClose} + selected={Boolean(selectedTransition)} + /> + <DropdownMenu.Separator /> + <ul className="sw-flex sw-flex-col"> + {!selectedTransition && + filteredTransitions.map((transition, index) => ( + <div key={transition}> + <IssueTransitionItem + transition={transition} + selected={selectedTransition === transition} + hasCommentAction={transitionRequiresComment(transition)} + onSelectTransition={selectTransition} + /> + {index !== filteredTransitions.length - 1 && <DropdownMenu.Separator />} + </div> + ))} - {selectedTransition && ( - <> - <ItemDivider /> - <div className="sw-mx-4 sw-mt-2"> - <PageContentFontWrapper className="sw-font-semibold"> - {intl.formatMessage({ id: 'issue.transition.comment' })} - </PageContentFontWrapper> - <InputTextArea - autoFocus - onChange={(event) => setComment(event.currentTarget.value)} - placeholder={translate( - 'issue.transition.comment.placeholder', - selectedTransition ?? '', - )} - rows={5} - value={comment} - size="large" - className="sw-mt-2" - /> - <Spinner loading={loading} className="sw-float-right sw-m-2"> + {selectedTransition && ( + <> + <SelectedTransitionItem transition={selectedTransition} /> + <DropdownMenu.Separator /> + <div className="sw-mx-3 sw-mt-2"> + <div className="sw-font-semibold">{translate('issue.transition.comment')}</div> + <div className="sw-flex sw-flex-col"> + <InputTextArea + autoFocus + className="sw-mt-2 sw-resize" + onChange={(event) => setComment(event.currentTarget.value)} + placeholder={translate( + 'issue.transition.comment.placeholder', + selectedTransition ?? '', + )} + rows={3} + size="large" + value={comment} + /> + </div> <div className="sw-mt-2 sw-flex sw-gap-3 sw-justify-end"> - <ButtonPrimary onClick={handleResolve}>{translate('resolve')}</ButtonPrimary> - <ButtonSecondary onClick={onClose}>{translate('cancel')}</ButtonSecondary> + <Button variety={ButtonVariety.Primary} onClick={handleResolve}> + {translate('issue.transition.change_status')} + </Button> + <Button variety={ButtonVariety.Default} onClick={onClose}> + {translate('cancel')} + </Button> </div> - </Spinner> - </div> - </> - )} - - {!selectedTransition && loading && ( - <div className="sw-flex sw-justify-center sw-m-2"> - <Spinner loading className="sw-float-right sw-2" /> - </div> - )} - </ul> + </div> + </> + )} + </ul> + </Spinner> ); } diff --git a/server/sonar-web/src/main/js/components/issue/components/IssueTransitionOverlayHeader.tsx b/server/sonar-web/src/main/js/components/issue/components/IssueTransitionOverlayHeader.tsx new file mode 100644 index 00000000000..f28cbd4c2a9 --- /dev/null +++ b/server/sonar-web/src/main/js/components/issue/components/IssueTransitionOverlayHeader.tsx @@ -0,0 +1,64 @@ +/* + * 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 { ButtonIcon, ButtonVariety, IconArrowLeft, IconX } from '@sonarsource/echoes-react'; +import { translate } from '../../../helpers/l10n'; + +interface Props { + onBack: VoidFunction; + onClose: VoidFunction; + selected: boolean; +} + +export default function IssueTransitionOverlayHeader({ + onBack, + onClose, + selected, +}: Readonly<Props>) { + if (selected) { + return ( + <div className="sw-flex sw-items-center sw-gap-2 sw-px-3 sw-mb-1"> + <ButtonIcon + Icon={IconArrowLeft} + ariaLabel={translate('go_back')} + className="sw-flex sw-justify-center sw-p-0" + onClick={onBack} + variety={ButtonVariety.DefaultGhost} + /> + <span className="sw-font-semibold"> + {translate('issue.transition.go_back_change_status')} + </span> + </div> + ); + } + + return ( + <div className="sw-flex sw-justify-between sw-items-center sw-px-3 sw-mb-1"> + <span className="sw-font-semibold">{translate('issue.transition.status_change')}</span> + <ButtonIcon + Icon={IconX} + ariaLabel={translate('close')} + className="sw-flex sw-justify-center sw-p-0" + onClick={onClose} + variety={ButtonVariety.DefaultGhost} + /> + </div> + ); +} diff --git a/server/sonar-web/src/main/js/components/issue/components/SelectedTransitionItem.tsx b/server/sonar-web/src/main/js/components/issue/components/SelectedTransitionItem.tsx new file mode 100644 index 00000000000..d5d9e9ab8c5 --- /dev/null +++ b/server/sonar-web/src/main/js/components/issue/components/SelectedTransitionItem.tsx @@ -0,0 +1,56 @@ +/* + * 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 { IconCheck } from '@sonarsource/echoes-react'; +import classNames from 'classnames'; +import { noop } from 'lodash'; +import { ItemButton, Note } from '../../../design-system'; +import { translate } from '../../../helpers/l10n'; +import DocHelpTooltip from '../../../sonar-aligned/components/controls/DocHelpTooltip'; +import { IssueTransition } from '../../../types/issues'; +import { isTransitionDeprecated } from '../helpers'; + +interface Props { + transition: IssueTransition; +} + +export function SelectedTransitionItem({ transition }: Readonly<Props>) { + return ( + <ItemButton className={classNames('sw-px-3 selected')} key={transition} onClick={noop}> + <IconCheck className="sw-mr-2" /> + <div className="sw-flex"> + <div className="sw-flex sw-flex-col"> + <div className="sw-font-semibold sw-flex sw-gap-1 sw-items-center"> + {translate('issue.transition', transition)} + {isTransitionDeprecated(transition) && ( + <DocHelpTooltip + className="sw-ml-1" + content={translate('issue.transition', transition, 'deprecated_tooltip')} + /> + )} + </div> + <Note className="sw-whitespace-break-spaces"> + {translate('issue.transition', transition, 'description')} + </Note> + </div> + </div> + </ItemButton> + ); +} |