aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main/js
diff options
context:
space:
mode:
authorRevanshu Paliwal <revanshu.paliwal@sonarsource.com>2024-12-04 13:05:01 +0100
committersonartech <sonartech@sonarsource.com>2024-12-06 20:03:27 +0000
commit5c2caf8c49a4373aabfdab93972efe453254582f (patch)
tree8d277634d483fd8754fc20c74cd2b8a18ee35434 /server/sonar-web/src/main/js
parent3521422cfd87bae127c4ccc6a72696ac0d1efae5 (diff)
downloadsonarqube-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')
-rw-r--r--server/sonar-web/src/main/js/apps/issues/__tests__/IssueApp-it.tsx14
-rw-r--r--server/sonar-web/src/main/js/components/issue/components/IssueTransitionItem.tsx13
-rw-r--r--server/sonar-web/src/main/js/components/issue/components/IssueTransitionOverlay.tsx124
-rw-r--r--server/sonar-web/src/main/js/components/issue/components/IssueTransitionOverlayHeader.tsx64
-rw-r--r--server/sonar-web/src/main/js/components/issue/components/SelectedTransitionItem.tsx56
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>
+ );
+}