From 329046215eb485837bf33161fa4bcccc24e96395 Mon Sep 17 00:00:00 2001 From: Viktor Vorona Date: Tue, 19 Nov 2024 13:40:26 +0100 Subject: [PATCH] SONAR-23653 Replace tours with Popovers --- .../issues/__tests__/IssuesAppGuides-it.tsx | 253 ------------------ .../js/apps/issues/components/IssueGuide.tsx | 183 ------------- .../js/apps/issues/components/IssueHeader.tsx | 6 - .../IssueNewStatusAndTransitionGuide.tsx | 194 -------------- .../js/apps/issues/components/IssuesApp.tsx | 11 +- ...IssuesNewStatusAndTransitionGuide-test.tsx | 166 ------------ .../issues/sidebar/AttributeCategoryFacet.tsx | 9 + .../main/js/apps/issues/sidebar/FacetHelp.tsx | 76 ++++++ .../apps/issues/sidebar/IssueStatusFacet.tsx | 4 +- .../issues/sidebar/QGMetricsMismatchHelp.tsx | 22 +- .../issues/sidebar/SoftwareQualityFacet.tsx | 10 +- .../issues/sidebar/__tests__/Sidebar-it.tsx | 30 ++- .../js/components/facets/SeverityFacet.tsx | 31 +-- .../issue/components/IssueTransition.tsx | 6 - .../components/IssueTransitionOverlay.tsx | 10 +- .../js/components/rules/IssueTabViewer.tsx | 5 - .../resources/org/sonar/l10n/core.properties | 38 +-- 17 files changed, 146 insertions(+), 908 deletions(-) delete mode 100644 server/sonar-web/src/main/js/apps/issues/__tests__/IssuesAppGuides-it.tsx delete mode 100644 server/sonar-web/src/main/js/apps/issues/components/IssueGuide.tsx delete mode 100644 server/sonar-web/src/main/js/apps/issues/components/IssueNewStatusAndTransitionGuide.tsx delete mode 100644 server/sonar-web/src/main/js/apps/issues/components/__tests__/IssuesNewStatusAndTransitionGuide-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/issues/sidebar/FacetHelp.tsx diff --git a/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesAppGuides-it.tsx b/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesAppGuides-it.tsx deleted file mode 100644 index f4694e7163c..00000000000 --- a/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesAppGuides-it.tsx +++ /dev/null @@ -1,253 +0,0 @@ -/* - * 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 { waitForElementToBeRemoved } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import React from 'react'; -import { mockCurrentUser } from '../../../helpers/testMocks'; -import { IssueTransition } from '../../../types/issues'; -import { NoticeType } from '../../../types/users'; -import { - branchHandler, - componentsHandler, - issuesHandler, - renderIssueApp, - renderProjectIssuesApp, - settingsHandler, - ui, - usersHandler, -} from '../test-utils'; - -jest.mock('../sidebar/Sidebar', () => { - const fakeSidebar = () => { - return
; - }; - return { - __esModule: true, - default: fakeSidebar, - Sidebar: fakeSidebar, - }; -}); - -jest.mock('../../../components/common/ScreenPositionHelper', () => { - const React = jest.requireActual('react'); - return { - __esModule: true, - default: class ScreenPositionHelper extends React.Component<{ - children: (args: { top: number }) => React.ReactNode; - }> { - render() { - // eslint-disable-next-line testing-library/no-node-access - return this.props.children({ top: 10 }); - } - }, - }; -}); - -describe('issue guides', () => { - beforeEach(() => { - issuesHandler.reset(); - componentsHandler.reset(); - branchHandler.reset(); - usersHandler.reset(); - settingsHandler.reset(); - window.scrollTo = jest.fn(); - window.HTMLElement.prototype.scrollTo = jest.fn(); - }); - - // The tests are skipped because it is not clear how guiding tours would work in MQR/Standard mode - // The tests will be revisited in the following MMF - // eslint-disable-next-line jest/no-disabled-tests - describe.skip('Issue Guide', () => { - it('should display guide', async () => { - const user = userEvent.setup(); - renderIssueApp( - mockCurrentUser({ - isLoggedIn: true, - dismissedNotices: { [NoticeType.ISSUE_NEW_STATUS_AND_TRANSITION_GUIDE]: true }, - }), - ); - - expect(await ui.guidePopup.find()).toBeInTheDocument(); - - expect(await ui.guidePopup.find()).toBeInTheDocument(); - expect(ui.guidePopup.get()).toHaveTextContent('guiding.issue_list.1.title'); - expect(ui.guidePopup.get()).toHaveTextContent('guiding.issue_list.1.content'); - expect(ui.guidePopup.get()).toHaveTextContent('guiding.step_x_of_y.1.5'); - - await user.click(ui.guidePopup.byRole('button', { name: 'next' }).get()); - - expect(ui.guidePopup.get()).toHaveTextContent('guiding.issue_list.2.title'); - expect(ui.guidePopup.get()).toHaveTextContent('guiding.issue_list.2.content'); - expect(ui.guidePopup.get()).toHaveTextContent('guiding.step_x_of_y.2.5'); - - await user.click(ui.guidePopup.byRole('button', { name: 'next' }).get()); - - expect(ui.guidePopup.get()).toHaveTextContent('guiding.issue_list.3.title'); - expect(ui.guidePopup.get()).toHaveTextContent('guiding.issue_list.3.content'); - expect(ui.guidePopup.get()).toHaveTextContent('guiding.step_x_of_y.3.5'); - - await user.click(ui.guidePopup.byRole('button', { name: 'next' }).get()); - - expect(ui.guidePopup.get()).toHaveTextContent('guiding.issue_list.4.title'); - expect(ui.guidePopup.get()).toHaveTextContent('guiding.issue_list.4.content'); - expect(ui.guidePopup.get()).toHaveTextContent('guiding.step_x_of_y.4.5'); - - await user.click(ui.guidePopup.byRole('button', { name: 'next' }).get()); - - expect(ui.guidePopup.get()).toHaveTextContent('guiding.issue_list.5.title'); - expect(ui.guidePopup.get()).toHaveTextContent('guiding.issue_list.5.content'); - expect(ui.guidePopup.get()).toHaveTextContent('guiding.step_x_of_y.5.5'); - - expect(ui.guidePopup.byRole('button', { name: 'Next' }).query()).not.toBeInTheDocument(); - - await user.click(ui.guidePopup.byRole('button', { name: 'close' }).get()); - - expect(ui.guidePopup.query()).not.toBeInTheDocument(); - }); - - it('should not show guide for those who dismissed it', async () => { - renderIssueApp( - mockCurrentUser({ - isLoggedIn: true, - dismissedNotices: { - [NoticeType.ISSUE_GUIDE]: true, - [NoticeType.ISSUE_NEW_STATUS_AND_TRANSITION_GUIDE]: true, - }, - }), - ); - - expect((await ui.issueItems.findAll()).length).toBeGreaterThan(0); - expect(ui.guidePopup.query()).not.toBeInTheDocument(); - }); - - it('should skip guide', async () => { - const user = userEvent.setup(); - renderIssueApp( - mockCurrentUser({ - isLoggedIn: true, - dismissedNotices: { [NoticeType.ISSUE_NEW_STATUS_AND_TRANSITION_GUIDE]: true }, - }), - ); - - expect(await ui.guidePopup.find()).toBeInTheDocument(); - expect(ui.guidePopup.get()).toHaveTextContent('guiding.issue_list.1.title'); - expect(ui.guidePopup.get()).toHaveTextContent('guiding.step_x_of_y.1.5'); - - await user.click(ui.guidePopup.byRole('button', { name: 'skip' }).get()); - - expect(ui.guidePopup.query()).not.toBeInTheDocument(); - }); - - it('should not show guide if issues need sync', async () => { - renderProjectIssuesApp( - undefined, - { needIssueSync: true }, - mockCurrentUser({ - isLoggedIn: true, - dismissedNotices: { [NoticeType.ISSUE_NEW_STATUS_AND_TRANSITION_GUIDE]: true }, - }), - ); - - expect((await ui.issueItems.findAll()).length).toBeGreaterThan(0); - expect(ui.guidePopup.query()).not.toBeInTheDocument(); - }); - - it('should not show guide if user is not logged in', async () => { - renderIssueApp(mockCurrentUser({ isLoggedIn: false })); - - expect((await ui.issueItems.findAll()).length).toBeGreaterThan(0); - expect(ui.guidePopup.query()).not.toBeInTheDocument(); - }); - - it('should not show guide if there are no issues', async () => { - issuesHandler.setIssueList([]); - renderIssueApp(mockCurrentUser({ isLoggedIn: true })); - - await waitForElementToBeRemoved(ui.loading.query()); - expect(ui.guidePopup.query()).not.toBeInTheDocument(); - }); - - it('should show guide on issue page and save its step on opened issue', async () => { - const user = userEvent.setup(); - renderProjectIssuesApp('project/issues', undefined, mockCurrentUser({ isLoggedIn: true })); - - expect(await ui.guidePopup.find()).toBeInTheDocument(); - expect(ui.guidePopup.get()).toHaveTextContent('guiding.step_x_of_y.1.5'); - - await user.click(ui.guidePopup.byRole('button', { name: 'next' }).get()); - - expect(ui.guidePopup.get()).toHaveTextContent('guiding.step_x_of_y.2.5'); - - await user.click(ui.guidePopup.byRole('button', { name: 'next' }).get()); - - expect(ui.guidePopup.get()).toHaveTextContent('guiding.step_x_of_y.3.5'); - - await user.click(ui.issueItemAction1.get()); - - expect(await ui.guidePopup.find()).toHaveTextContent('guiding.step_x_of_y.3.5'); - - await user.click(ui.guidePopup.byRole('button', { name: 'next' }).get()); - - expect(ui.guidePopup.get()).toHaveTextContent('guiding.step_x_of_y.4.5'); - - await user.click(ui.guidePopup.byRole('button', { name: 'next' }).get()); - - expect(ui.guidePopup.get()).toHaveTextContent('guiding.step_x_of_y.5.5'); - - expect(ui.guidePopup.byRole('button', { name: 'Next' }).query()).not.toBeInTheDocument(); - - await user.click(ui.guidePopup.byRole('button', { name: 'close' }).get()); - - expect(ui.guidePopup.query()).not.toBeInTheDocument(); - }); - }); - - describe('Issue new status and transition guide', () => { - it('should save transition guide step', async () => { - const user = userEvent.setup(); - issuesHandler.list[0].issue.transitions = [ - IssueTransition.Accept, - IssueTransition.Confirm, - IssueTransition.Resolve, - IssueTransition.FalsePositive, - IssueTransition.WontFix, - ]; - renderProjectIssuesApp( - 'project/issues', - undefined, - mockCurrentUser({ isLoggedIn: true, dismissedNotices: { [NoticeType.ISSUE_GUIDE]: true } }), - ); - - expect(await ui.guidePopup.find()).toBeInTheDocument(); - await user.click(ui.guidePopup.byRole('button', { name: 'next' }).get()); - expect(await ui.guidePopup.find()).toHaveTextContent('guiding.step_x_of_y.2.3'); - - await user.click(ui.issueItemAction1.get()); - - expect(await ui.guidePopup.find()).toHaveTextContent('guiding.step_x_of_y.2.3'); - - await user.click(ui.guidePopup.byRole('button', { name: 'next' }).get()); - await user.click(ui.guidePopup.byRole('button', { name: 'close' }).get()); - - expect(ui.guidePopup.query()).not.toBeInTheDocument(); - }); - }); -}); diff --git a/server/sonar-web/src/main/js/apps/issues/components/IssueGuide.tsx b/server/sonar-web/src/main/js/apps/issues/components/IssueGuide.tsx deleted file mode 100644 index 85cc72ff484..00000000000 --- a/server/sonar-web/src/main/js/apps/issues/components/IssueGuide.tsx +++ /dev/null @@ -1,183 +0,0 @@ -/* - * 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 React, { useState } from 'react'; -import { FormattedMessage } from 'react-intl'; -import { CallBackProps } from 'react-joyride'; -import { SpotlightTour, SpotlightTourStep } from '~design-system'; -import { dismissNotice } from '../../../api/users'; -import { CurrentUserContext } from '../../../app/components/current-user/CurrentUserContext'; -import DocumentationLink from '../../../components/common/DocumentationLink'; -import { SCREEN_POSITION_COMPUTE_DELAY } from '../../../components/common/ScreenPositionHelper'; -import { DocLink } from '../../../helpers/doc-links'; -import { translate, translateWithParameters } from '../../../helpers/l10n'; -import { NoticeType } from '../../../types/users'; - -interface Props { - run: boolean; -} - -const PLACEMENT_RIGHT = 'right'; -const SESSION_STORAGE_KEY = 'issueCleanCodeGuideStep'; - -const EXTRA_DELAY = 50; - -export default function IssueGuide({ run }: Props) { - const { currentUser, updateDismissedNotices } = React.useContext(CurrentUserContext); - const [step, setStep] = useState(+(sessionStorage.getItem(SESSION_STORAGE_KEY) ?? 0)); - const canRun = currentUser.isLoggedIn && !currentUser.dismissedNotices[NoticeType.ISSUE_GUIDE]; - - // IssueGuide can be called within context of a ScreenPositionHelper. When this happens, - // React Floater (a lib used by React Joyride, which in turn is what powers SpotlightTour) - // gets confused and cannot correctly position the first step. The only way around this is - // to delay the rendering of the SpotlightTour until *after* ScreenPositionHelper has - // recomputed its positioning. That's what this state + effect is about: if `run` is false, - // it means we are not in a state to start running. This could either be because we really don't - // want to start the tour at all, in which case `run` will remain false. OR, it means we are - // waiting on something else (like ScreenPositionHelper), in which case `run` will turn true - // later. We wait on the delay of ScreenPositionHelper + 50ms, and try again. If `run` is still - // false, we don't start the tour. If `run` is now true, we start the tour. - const [start, setStart] = React.useState(run); - React.useEffect(() => { - // Only trigger the timeout if start is false. - if (!start && canRun) { - setTimeout(() => { - setStart(run); - }, SCREEN_POSITION_COMPUTE_DELAY + EXTRA_DELAY); - } - }, [canRun, run, start]); - - React.useEffect(() => { - if (start && canRun) { - sessionStorage.setItem(SESSION_STORAGE_KEY, step.toString()); - } - }, [step, start, canRun]); - - if (!start || !canRun) { - return null; - } - - const onToggle = (props: CallBackProps) => { - switch (props.action) { - case 'close': - case 'skip': - case 'reset': - sessionStorage.removeItem(SESSION_STORAGE_KEY); - dismissNotice(NoticeType.ISSUE_GUIDE) - .then(() => { - updateDismissedNotices(NoticeType.ISSUE_GUIDE, true); - }) - .catch(() => { - /* noop */ - }); - break; - case 'next': - if (props.lifecycle === 'complete') { - setStep(step + 1); - } - break; - case 'prev': - if (props.lifecycle === 'complete') { - setStep(step - 1); - } - break; - default: - break; - } - }; - - const constructContent = ( - first: string, - second: string, - extraContent?: string | React.ReactNode, - ) => ( - <> - {translate(first)} -
-
- {translate(second)} - {extraContent ?? null} - - ); - - const steps: SpotlightTourStep[] = [ - { - target: '[data-guiding-id="issue-1"]', - content: constructContent('guiding.issue_list.1.content.1', 'guiding.issue_list.1.content.2'), - title: translate('guiding.issue_list.1.title'), - placement: PLACEMENT_RIGHT, - }, - { - target: '[data-guiding-id="issue-2"]', - content: constructContent('guiding.issue_list.2.content.1', 'guiding.issue_list.2.content.2'), - title: translate('guiding.issue_list.2.title'), - }, - { - target: '[data-guiding-id="issue-3"]', - content: constructContent('guiding.issue_list.3.content.1', 'guiding.issue_list.3.content.2'), - title: translate('guiding.issue_list.3.title'), - }, - { - target: '[data-guiding-id="issue-4"]', - content: constructContent( - 'guiding.issue_list.4.content.1', - 'guiding.issue_list.4.content.2', - , - ), - title: translate('guiding.issue_list.4.title'), - }, - { - target: '[data-guiding-id="issue-5"]', - content: ( - - {translate('documentation')} - - ), - }} - /> - ), - title: translate('guiding.issue_list.5.title'), - }, - ]; - - return ( - translateWithParameters('guiding.step_x_of_y', x, y)} - /> - ); -} diff --git a/server/sonar-web/src/main/js/apps/issues/components/IssueHeader.tsx b/server/sonar-web/src/main/js/apps/issues/components/IssueHeader.tsx index cc01e0de5f0..0cf4846b3a5 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/IssueHeader.tsx +++ b/server/sonar-web/src/main/js/apps/issues/components/IssueHeader.tsx @@ -47,7 +47,6 @@ import { IssueActions, IssueSeverity, IssueType } from '../../../types/issues'; import { Issue, RuleDetails } from '../../../types/types'; import IssueHeaderMeta from './IssueHeaderMeta'; import IssueHeaderSide from './IssueHeaderSide'; -import IssueNewStatusAndTransitionGuide from './IssueNewStatusAndTransitionGuide'; interface Props { branchLike?: BranchLike; @@ -243,11 +242,6 @@ export default class IssueHeader extends React.PureComponent { issue.actions.includes(IssueActions.SetSeverity) ? this.handleSeverityChange : undefined } /> - this.handleIssuePopupToggle(popup, show)} - /> ); } diff --git a/server/sonar-web/src/main/js/apps/issues/components/IssueNewStatusAndTransitionGuide.tsx b/server/sonar-web/src/main/js/apps/issues/components/IssueNewStatusAndTransitionGuide.tsx deleted file mode 100644 index 8a29d1854ae..00000000000 --- a/server/sonar-web/src/main/js/apps/issues/components/IssueNewStatusAndTransitionGuide.tsx +++ /dev/null @@ -1,194 +0,0 @@ -/* - * 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 React, { useState } from 'react'; -import { useIntl } from 'react-intl'; -import { CallBackProps } from 'react-joyride'; -import { SpotlightTour, SpotlightTourStep } from '~design-system'; -import { useCurrentUser } from '../../../app/components/current-user/CurrentUserContext'; -import DocumentationLink from '../../../components/common/DocumentationLink'; -import { SCREEN_POSITION_COMPUTE_DELAY } from '../../../components/common/ScreenPositionHelper'; -import { DocLink } from '../../../helpers/doc-links'; -import { useDismissNoticeMutation } from '../../../queries/users'; -import { IssueTransition } from '../../../types/issues'; -import { Issue } from '../../../types/types'; -import { NoticeType } from '../../../types/users'; - -interface Props { - issues: Issue[]; - run?: boolean; - togglePopup: (issue: string, popup: string, show?: boolean) => void; -} - -const PLACEMENT_RIGHT = 'right'; -export const SESSION_STORAGE_TRANSITION_GUIDE_KEY = 'issueNewStatusAndTransitionGuideStep'; -const EXTRA_DELAY = 100; -const GUIDE_WIDTH = 360; - -export default function IssueNewStatusAndTransitionGuide(props: Readonly) { - const { run, issues, togglePopup } = props; - const { currentUser, updateDismissedNotices } = useCurrentUser(); - const { mutateAsync: dismissNotice } = useDismissNoticeMutation(); - const intl = useIntl(); - const [step, setStep] = useState( - +(sessionStorage.getItem(SESSION_STORAGE_TRANSITION_GUIDE_KEY) ?? 0), - ); - const [start, setStart] = React.useState(false); - - const issueWithAcceptTransition = issues.find((issue) => - issue.transitions.includes(IssueTransition.Accept), - ); - - const userCompletedCCTGuide = - currentUser.isLoggedIn && currentUser.dismissedNotices[NoticeType.ISSUE_GUIDE]; - const userCompletedStatusGuide = - currentUser.isLoggedIn && - currentUser.dismissedNotices[NoticeType.ISSUE_NEW_STATUS_AND_TRANSITION_GUIDE]; - const canRun = - userCompletedCCTGuide && !userCompletedStatusGuide && run && issueWithAcceptTransition; - - // Wait for the issue list to be rendered, then scroll to the issue, wait for an extra delay - // to ensure proper positioning of the SpotlightTour in the context of ScreenPositionHelper, - // then start the tour. - React.useEffect(() => { - // Should start the tour if it is not started yet - if (!start && canRun) { - setTimeout(() => { - // Scroll to issue. This ensures proper rendering of the SpotlightTour. - document - .querySelector(`[data-guiding-id="issue-transition-${issueWithAcceptTransition.key}"]`) - ?.scrollIntoView({ behavior: 'instant', block: 'center' }); - // Start the tour - if (step !== 0) { - togglePopup(issueWithAcceptTransition.key, 'transition', true); - setTimeout(() => { - setStart(run); - }, 0); - } else { - setStart(run); - } - }, SCREEN_POSITION_COMPUTE_DELAY + EXTRA_DELAY); - } - }, [canRun, run, step, start, togglePopup, issueWithAcceptTransition]); - - React.useEffect(() => { - if (start && canRun) { - sessionStorage.setItem(SESSION_STORAGE_TRANSITION_GUIDE_KEY, step.toString()); - } - }, [step, start, canRun]); - - if (!canRun || !start) { - return null; - } - - const onToggle = (props: CallBackProps) => { - const { action, lifecycle, index } = props; - switch (action) { - case 'close': - case 'skip': - case 'reset': - togglePopup(issueWithAcceptTransition.key, 'transition', false); - sessionStorage.removeItem(SESSION_STORAGE_TRANSITION_GUIDE_KEY); - dismissNotice(NoticeType.ISSUE_NEW_STATUS_AND_TRANSITION_GUIDE) - .then(() => { - updateDismissedNotices(NoticeType.ISSUE_NEW_STATUS_AND_TRANSITION_GUIDE, true); - }) - .catch(() => { - /* noop */ - }); - break; - case 'next': - if (lifecycle === 'complete') { - if (index === 0) { - togglePopup(issueWithAcceptTransition.key, 'transition', true); - setTimeout(() => { - setStep(step + 1); - }, 0); - } else { - setStep(step + 1); - } - } - break; - case 'prev': - if (lifecycle === 'complete') { - if (index === 1) { - togglePopup(issueWithAcceptTransition.key, 'transition', false); - } - setStep(step - 1); - } - break; - default: - break; - } - }; - - const constructContent = (stepIndex: number) => { - return ( - <> -
- {intl.formatMessage({ id: `guiding.issue_accept.${stepIndex}.content.1` })} - {intl.formatMessage({ id: `guiding.issue_accept.${stepIndex}.content.2` })} -
- - {intl.formatMessage({ id: `guiding.issue_accept.${stepIndex}.content.link` })} - - - ); - }; - - const steps: SpotlightTourStep[] = [ - { - target: `[data-guiding-id="issue-transition-${issueWithAcceptTransition.key}"]`, - title: intl.formatMessage({ id: 'guiding.issue_accept.1.title' }), - content: intl.formatMessage({ id: 'guiding.issue_accept.1.content.1' }), - placement: PLACEMENT_RIGHT, - }, - { - target: '[data-guiding-id="issue-accept-transition"]', - title: intl.formatMessage({ id: 'guiding.issue_accept.2.title' }), - content: constructContent(2), - placement: PLACEMENT_RIGHT, - }, - { - target: '[data-guiding-id="issue-deprecated-transitions"]', - title: intl.formatMessage({ id: 'guiding.issue_accept.3.title' }), - content: constructContent(3), - placement: PLACEMENT_RIGHT, - }, - ]; - - return ( - - intl.formatMessage({ id: 'guiding.step_x_of_y' }, { '0': x, '1': y }) - } - /> - ); -} diff --git a/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx b/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx index 4e2ca45bcd6..b135db1a33c 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx +++ b/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx @@ -95,8 +95,6 @@ import { } from '../utils'; import BulkChangeModal, { MAX_PAGE_SIZE } from './BulkChangeModal'; import IssueDetails from './IssueDetails'; -import IssueGuide from './IssueGuide'; -import IssueNewStatusAndTransitionGuide from './IssueNewStatusAndTransitionGuide'; import IssuesList from './IssuesList'; import IssuesListTitle from './IssuesListTitle'; import NoIssues from './NoIssues'; @@ -1152,8 +1150,7 @@ export class App extends React.PureComponent { selected, locationsNavigator, } = this.state; - const { component, location } = this.props; - const open = getOpen(location.query); + const { component } = this.props; const { canBrowseAllChildProjects, qualifier = ComponentQualifier.Project } = this.props.component ?? {}; const warning = !canBrowseAllChildProjects && isPortfolioLike(qualifier) && ( @@ -1194,12 +1191,6 @@ export class App extends React.PureComponent {
- 0} /> - 0} - togglePopup={this.handlePopupToggle} - issues={issues} - />

{translate('issues.page')}

diff --git a/server/sonar-web/src/main/js/apps/issues/components/__tests__/IssuesNewStatusAndTransitionGuide-test.tsx b/server/sonar-web/src/main/js/apps/issues/components/__tests__/IssuesNewStatusAndTransitionGuide-test.tsx deleted file mode 100644 index 7c31426c1c2..00000000000 --- a/server/sonar-web/src/main/js/apps/issues/components/__tests__/IssuesNewStatusAndTransitionGuide-test.tsx +++ /dev/null @@ -1,166 +0,0 @@ -/* - * 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 userEvent from '@testing-library/user-event'; -import React from 'react'; -import IssuesServiceMock from '../../../../api/mocks/IssuesServiceMock'; -import UsersServiceMock from '../../../../api/mocks/UsersServiceMock'; -import CurrentUserContextProvider from '../../../../app/components/current-user/CurrentUserContextProvider'; -import IssueTransitionComponent from '../../../../components/issue/components/IssueTransition'; -import { mockCurrentUser, mockIssue } from '../../../../helpers/testMocks'; -import { renderComponent } from '../../../../helpers/testReactTestingUtils'; -import { IssueTransition } from '../../../../types/issues'; -import { Issue } from '../../../../types/types'; -import { NoticeType } from '../../../../types/users'; -import { ui } from '../../test-utils'; -import IssueNewStatusAndTransitionGuide from '../IssueNewStatusAndTransitionGuide'; - -const usersHandler = new UsersServiceMock(); -const issuesHandler = new IssuesServiceMock(usersHandler); - -beforeEach(() => { - usersHandler.reset(); - issuesHandler.reset(); -}); - -it('should display status guide', async () => { - const user = userEvent.setup(); - renderIssueNewStatusGuide(); - - expect(await ui.guidePopup.find()).toBeInTheDocument(); - expect(ui.guidePopup.get()).toHaveTextContent(/guiding.issue_accept.1.title/); - - await user.click(ui.guidePopup.byRole('button', { name: 'next' }).get()); - - expect(await ui.guidePopup.find()).toHaveTextContent(/guiding.issue_accept.2.title/); - - await user.click(ui.guidePopup.byRole('button', { name: 'go_back' }).get()); - expect(await ui.guidePopup.find()).toHaveTextContent(/guiding.issue_accept.1.title/); - - await user.click(ui.guidePopup.byRole('button', { name: 'next' }).get()); - await user.click(await ui.guidePopup.byRole('button', { name: 'next' }).find()); - expect(await ui.guidePopup.find()).toHaveTextContent(/guiding.issue_accept.3.title/); - expect(ui.guidePopup.byRole('button', { name: 'Next' }).query()).not.toBeInTheDocument(); - - await user.click(ui.guidePopup.byRole('button', { name: 'close' }).get()); - - expect(ui.guidePopup.query()).not.toBeInTheDocument(); -}); - -it('should not show guide for those who dismissed it', () => { - renderIssueNewStatusGuide( - mockCurrentUser({ - isLoggedIn: true, - dismissedNotices: { - [NoticeType.ISSUE_GUIDE]: true, - [NoticeType.ISSUE_NEW_STATUS_AND_TRANSITION_GUIDE]: true, - }, - }), - ); - - expect(ui.guidePopup.query()).not.toBeInTheDocument(); -}); - -it('should skip guide', async () => { - const user = userEvent.setup(); - renderIssueNewStatusGuide(); - - expect(await ui.guidePopup.find()).toBeInTheDocument(); - expect(ui.guidePopup.get()).toHaveTextContent(/guiding.issue_accept.1.title/); - - await user.click(ui.guidePopup.byRole('button', { name: 'skip' }).get()); - - expect(ui.guidePopup.query()).not.toBeInTheDocument(); -}); - -it('should not show guide if user is not logged in', () => { - renderIssueNewStatusGuide(mockCurrentUser({ isLoggedIn: false })); - - expect(ui.guidePopup.query()).not.toBeInTheDocument(); -}); - -it('should not show guide if there are no issues', () => { - renderIssueNewStatusGuide(mockCurrentUser({ isLoggedIn: true }), []); - - expect(ui.guidePopup.query()).not.toBeInTheDocument(); -}); - -it('should not show guide if CCT guide is shown', () => { - renderIssueNewStatusGuide( - mockCurrentUser({ isLoggedIn: true, dismissedNotices: { [NoticeType.ISSUE_GUIDE]: false } }), - [], - ); - - expect(ui.guidePopup.query()).not.toBeInTheDocument(); -}); - -function IssueNewStatusGuide({ issues }: { issues: Issue[] }) { - const [open, setOpen] = React.useState(false); - const issue = mockIssue(false, { - transitions: [ - IssueTransition.Accept, - IssueTransition.Confirm, - IssueTransition.Resolve, - IssueTransition.FalsePositive, - IssueTransition.WontFix, - ], - }); - - return ( -
-
/
- setOpen(!open)} - issue={issue} - onChange={jest.fn()} - /> - setOpen(Boolean(show))} - run - issues={issues} - /> -
- ); -} - -function renderIssueNewStatusGuide( - currentUser = mockCurrentUser({ - isLoggedIn: true, - dismissedNotices: { [NoticeType.ISSUE_GUIDE]: true }, - }), - issues = [ - mockIssue(false, { - transitions: [ - IssueTransition.Accept, - IssueTransition.Confirm, - IssueTransition.Resolve, - IssueTransition.FalsePositive, - IssueTransition.WontFix, - ], - }), - ], -) { - return renderComponent( - - - , - ); -} diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/AttributeCategoryFacet.tsx b/server/sonar-web/src/main/js/apps/issues/sidebar/AttributeCategoryFacet.tsx index 8582e8784b3..86e95895302 100644 --- a/server/sonar-web/src/main/js/apps/issues/sidebar/AttributeCategoryFacet.tsx +++ b/server/sonar-web/src/main/js/apps/issues/sidebar/AttributeCategoryFacet.tsx @@ -19,7 +19,9 @@ */ import { CLEAN_CODE_CATEGORIES } from '../../../helpers/constants'; +import { DocLink } from '../../../helpers/doc-links'; import { CleanCodeAttributeCategory } from '../../../types/clean-code-taxonomy'; +import { FacetHelp } from './FacetHelp'; import { CommonProps, SimpleListStyleFacet } from './SimpleListStyleFacet'; interface Props extends CommonProps { @@ -35,6 +37,13 @@ export function AttributeCategoryFacet(props: Props) { itemNamePrefix="issue.clean_code_attribute_category" listItems={CLEAN_CODE_CATEGORIES} selectedItems={categories} + help={ + + } {...rest} /> ); diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/FacetHelp.tsx b/server/sonar-web/src/main/js/apps/issues/sidebar/FacetHelp.tsx new file mode 100644 index 00000000000..a11726248c2 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/sidebar/FacetHelp.tsx @@ -0,0 +1,76 @@ +/* + * 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 { Button, ButtonVariety, IconQuestionMark, Popover } from '@sonarsource/echoes-react'; +import { useIntl } from 'react-intl'; +import DocumentationLink from '../../../components/common/DocumentationLink'; +import { DocLink } from '../../../helpers/doc-links'; + +type Props = + | { + description?: never; + link: DocLink; + linkText?: never; + noDescription?: boolean; + property: string; + title?: never; + } + | { + description?: string | React.ReactNode; + link: DocLink; + linkText: string; + noDescription?: never; + property?: never; + title: string; + }; + +export function FacetHelp({ property, title, description, noDescription, link, linkText }: Props) { + const intl = useIntl(); + return ( +

{text}

, p: (text) =>

{text}

}, + ) + : description + } + footer={ + + {property ? intl.formatMessage({ id: `issues.facet.${property}.help.link` }) : linkText} + + } + > + +
+ ); +} diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/IssueStatusFacet.tsx b/server/sonar-web/src/main/js/apps/issues/sidebar/IssueStatusFacet.tsx index ce359af8b08..a887cc8e3d8 100644 --- a/server/sonar-web/src/main/js/apps/issues/sidebar/IssueStatusFacet.tsx +++ b/server/sonar-web/src/main/js/apps/issues/sidebar/IssueStatusFacet.tsx @@ -23,8 +23,10 @@ import { useIntl } from 'react-intl'; import { FacetBox, FacetItem } from '~design-system'; import { DEFAULT_ISSUES_QUERY } from '../../../components/shared/utils'; import { ISSUE_STATUSES } from '../../../helpers/constants'; +import { DocLink } from '../../../helpers/doc-links'; import { IssueStatus } from '../../../types/issues'; import { formatFacetStat } from '../utils'; +import { FacetHelp } from './FacetHelp'; import { FacetItemsList } from './FacetItemsList'; import { MultipleSelectionHint } from './MultipleSelectionHint'; import { CommonProps } from './SimpleListStyleFacet'; @@ -65,7 +67,7 @@ export function IssueStatusFacet(props: Readonly) { } onClick={() => props.onToggle(property)} open={open} - help={help} + help={help ?? } > {ISSUE_STATUSES.map((item) => { diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/QGMetricsMismatchHelp.tsx b/server/sonar-web/src/main/js/apps/issues/sidebar/QGMetricsMismatchHelp.tsx index f2e99fd3c77..3e63d850e95 100644 --- a/server/sonar-web/src/main/js/apps/issues/sidebar/QGMetricsMismatchHelp.tsx +++ b/server/sonar-web/src/main/js/apps/issues/sidebar/QGMetricsMismatchHelp.tsx @@ -18,32 +18,20 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { Button, ButtonVariety, IconQuestionMark, Popover } from '@sonarsource/echoes-react'; import { useIntl } from 'react-intl'; -import DocumentationLink from '../../../components/common/DocumentationLink'; import { DocLink } from '../../../helpers/doc-links'; import { useStandardExperienceMode } from '../../../queries/settings'; +import { FacetHelp } from './FacetHelp'; export default function QGMetricsMismatchHelp() { const intl = useIntl(); const { data: isStandardMode } = useStandardExperienceMode(); return ( - - {intl.formatMessage({ id: 'issues.qg_mismatch.link' })} - - } - > - - + linkText={intl.formatMessage({ id: 'issues.qg_mismatch.link' })} + link={DocLink.QualityGates} + /> ); } diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/SoftwareQualityFacet.tsx b/server/sonar-web/src/main/js/apps/issues/sidebar/SoftwareQualityFacet.tsx index 310729c94c9..b16f8a028f0 100644 --- a/server/sonar-web/src/main/js/apps/issues/sidebar/SoftwareQualityFacet.tsx +++ b/server/sonar-web/src/main/js/apps/issues/sidebar/SoftwareQualityFacet.tsx @@ -19,7 +19,9 @@ */ import { SOFTWARE_QUALITIES } from '../../../helpers/constants'; +import { DocLink } from '../../../helpers/doc-links'; import { SoftwareQuality } from '../../../types/clean-code-taxonomy'; +import { FacetHelp } from './FacetHelp'; import QGMetricsMismatchHelp from './QGMetricsMismatchHelp'; import { CommonProps, SimpleListStyleFacet } from './SimpleListStyleFacet'; @@ -36,7 +38,13 @@ export function SoftwareQualityFacet(props: Props) { itemNamePrefix="software_quality" listItems={SOFTWARE_QUALITIES} selectedItems={qualities} - help={Boolean(props.secondLine) && } + help={ + props.secondLine ? ( + + ) : ( + + ) + } {...rest} /> ); diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/Sidebar-it.tsx b/server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/Sidebar-it.tsx index 730b7b8f42d..218900239bf 100644 --- a/server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/Sidebar-it.tsx +++ b/server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/Sidebar-it.tsx @@ -59,12 +59,15 @@ describe('MQR mode', () => { expect(screen.getAllByRole('button').map((button) => button.textContent)).toStrictEqual([ 'issues.facet.impactSoftwareQualities', + '', 'coding_rules.facet.impactSeverities', // help icon - '', + '', 'issues.facet.cleanCodeAttributeCategories', + '', 'issues.facet.scopes', 'issues.facet.issueStatuses', + '', 'issues.facet.standards', 'issues.facet.createdAt', 'issues.facet.languages', @@ -96,8 +99,7 @@ describe('MQR mode', () => { .get(), ).toBeInTheDocument(); // help icon - expect(byRole('button', { name: 'help' }).get()).toBeInTheDocument(); - await user.click(byRole('button', { name: 'help' }).get()); + await user.click(byRole('button', { name: 'help' }).getAt(2)); expect(screen.getByText('issues.qg_mismatch.title')).toBeInTheDocument(); expect( @@ -144,12 +146,15 @@ describe('MQR mode', () => { expect(screen.getAllByRole('button').map((button) => button.textContent)).toStrictEqual([ 'issues.facet.impactSoftwareQualities', + '', 'coding_rules.facet.impactSeverities', // help icon - '', + '', 'issues.facet.cleanCodeAttributeCategories', + '', 'issues.facet.scopes', 'issues.facet.issueStatuses', + '', 'issues.facet.standards', 'issues.facet.createdAt', 'issues.facet.languages', @@ -167,12 +172,15 @@ describe('MQR mode', () => { expect(screen.getAllByRole('button').map((button) => button.textContent)).toStrictEqual([ 'issues.facet.impactSoftwareQualities', + '', 'coding_rules.facet.impactSeverities', // help icon - '', + '', 'issues.facet.cleanCodeAttributeCategories', + '', 'issues.facet.scopes', 'issues.facet.issueStatuses', + '', 'issues.facet.standards', 'issues.facet.createdAt', 'issues.facet.languages', @@ -190,12 +198,15 @@ describe('MQR mode', () => { expect(screen.getAllByRole('button').map((button) => button.textContent)).toStrictEqual([ 'issues.facet.impactSoftwareQualities', + '', 'coding_rules.facet.impactSeverities', // help icon - '', + '', 'issues.facet.cleanCodeAttributeCategories', + '', 'issues.facet.scopes', 'issues.facet.issueStatuses', + '', 'issues.facet.standards', 'issues.facet.createdAt', 'issues.facet.languages', @@ -229,6 +240,7 @@ describe('Standard mode', () => { 'issues.facet.severities', 'issues.facet.scopes', 'issues.facet.issueStatuses', + '', 'issues.facet.standards', 'issues.facet.createdAt', 'issues.facet.languages', @@ -261,8 +273,7 @@ describe('Standard mode', () => { ).toBeInTheDocument(); // help icon - expect(byRole('button', { name: 'help' }).get()).toBeInTheDocument(); - await user.click(byRole('button', { name: 'help' }).get()); + await user.click(byRole('button', { name: 'help' }).getAt(0)); expect(screen.getByText('issues.qg_mismatch.title')).toBeInTheDocument(); expect( @@ -318,6 +329,7 @@ describe('Standard mode', () => { 'issues.facet.severities', 'issues.facet.scopes', 'issues.facet.issueStatuses', + '', 'issues.facet.standards', 'issues.facet.createdAt', 'issues.facet.languages', @@ -340,6 +352,7 @@ describe('Standard mode', () => { 'issues.facet.severities', 'issues.facet.scopes', 'issues.facet.issueStatuses', + '', 'issues.facet.standards', 'issues.facet.createdAt', 'issues.facet.languages', @@ -362,6 +375,7 @@ describe('Standard mode', () => { 'issues.facet.severities', 'issues.facet.scopes', 'issues.facet.issueStatuses', + '', 'issues.facet.standards', 'issues.facet.createdAt', 'issues.facet.languages', diff --git a/server/sonar-web/src/main/js/components/facets/SeverityFacet.tsx b/server/sonar-web/src/main/js/components/facets/SeverityFacet.tsx index 23784daa404..6b3b6ac7182 100644 --- a/server/sonar-web/src/main/js/components/facets/SeverityFacet.tsx +++ b/server/sonar-web/src/main/js/components/facets/SeverityFacet.tsx @@ -18,15 +18,13 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { Popover } from '@sonarsource/echoes-react'; import * as React from 'react'; import { useIntl } from 'react-intl'; -import { BareButton, HelperHintIcon } from '~design-system'; +import { FacetHelp } from '../../apps/issues/sidebar/FacetHelp'; import QGMetricsMismatchHelp from '../../apps/issues/sidebar/QGMetricsMismatchHelp'; import { IMPACT_SEVERITIES } from '../../helpers/constants'; import { DocLink } from '../../helpers/doc-links'; import { translate } from '../../helpers/l10n'; -import DocumentationLink from '../common/DocumentationLink'; import SoftwareImpactSeverityIcon from '../icon-mappers/SoftwareImpactSeverityIcon'; import Facet, { BasicProps } from './Facet'; @@ -58,26 +56,15 @@ export default function SeverityFacet(props: Readonly) { props.secondLine ? ( ) : ( - -

{intl.formatMessage({ id: 'severity_impact.help.line1' })}

-

- {intl.formatMessage({ id: 'severity_impact.help.line2' })} -

- - } - footer={ - - {intl.formatMessage({ id: 'learn_more' })} - - } - > - - - -
+ description={intl.formatMessage( + { id: `severity_impact.help.description` }, + { p1: (text) =>

{text}

, p: (text) =>

{text}

}, + )} + link={DocLink.CleanCodeIntroduction} + linkText={intl.formatMessage({ id: 'learn_more' })} + /> ) } /> diff --git a/server/sonar-web/src/main/js/components/issue/components/IssueTransition.tsx b/server/sonar-web/src/main/js/components/issue/components/IssueTransition.tsx index bfb03626961..01bb1ad3841 100644 --- a/server/sonar-web/src/main/js/components/issue/components/IssueTransition.tsx +++ b/server/sonar-web/src/main/js/components/issue/components/IssueTransition.tsx @@ -28,7 +28,6 @@ import { PopupZLevel, SearchSelectDropdownControl, } from '~design-system'; -import { SESSION_STORAGE_TRANSITION_GUIDE_KEY } from '../../../apps/issues/components/IssueNewStatusAndTransitionGuide'; import { translate, translateWithParameters } from '../../../helpers/l10n'; import { useIssueCommentMutation, useIssueTransitionMutation } from '../../../queries/issues'; import { Issue } from '../../../types/types'; @@ -46,8 +45,6 @@ interface Props { export default function IssueTransition(props: Readonly) { const { isOpen, issue, onChange, togglePopup } = props; - const guideStepIndex = +(sessionStorage.getItem(SESSION_STORAGE_TRANSITION_GUIDE_KEY) ?? 0); - const guideIsRunning = sessionStorage.getItem(SESSION_STORAGE_TRANSITION_GUIDE_KEY) !== null; const [transitioning, setTransitioning] = React.useState(false); const { mutateAsync: setIssueTransition } = useIssueTransitionMutation(); const { mutateAsync: addIssueComment } = useIssueCommentMutation(); @@ -84,15 +81,12 @@ export default function IssueTransition(props: Readonly) { id="issue-transition" onClose={handleClose} openDropdown={isOpen} - withClickOutHandler={!guideIsRunning} - withFocusOutHandler={!guideIsRunning} overlay={ } placement={PopupPlacement.Bottom} 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 bb2d212cfe3..7fb8b00af2e 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 @@ -36,7 +36,6 @@ import { isTransitionDeprecated, isTransitionHidden, transitionRequiresComment } import { IssueTransitionItem } from './IssueTransitionItem'; export type Props = { - guideStepIndex: number; issue: Pick; loading?: boolean; onClose: () => void; @@ -44,7 +43,7 @@ export type Props = { }; export function IssueTransitionOverlay(props: Readonly) { - const { issue, onClose, onSetTransition, loading, guideStepIndex } = props; + const { issue, onClose, onSetTransition, loading } = props; const intl = useIntl(); const [comment, setComment] = useState(''); @@ -84,10 +83,7 @@ export function IssueTransitionOverlay(props: Readonly) { > @@ -100,7 +96,7 @@ export function IssueTransitionOverlay(props: Readonly) { ))} diff --git a/server/sonar-web/src/main/js/components/rules/IssueTabViewer.tsx b/server/sonar-web/src/main/js/components/rules/IssueTabViewer.tsx index cf742fc25b6..a404df9802a 100644 --- a/server/sonar-web/src/main/js/components/rules/IssueTabViewer.tsx +++ b/server/sonar-web/src/main/js/components/rules/IssueTabViewer.tsx @@ -27,7 +27,6 @@ import { dismissNotice } from '../../api/users'; import { CurrentUserContextInterface } from '../../app/components/current-user/CurrentUserContext'; import withCurrentUserContext from '../../app/components/current-user/withCurrentUserContext'; import { RuleDescriptionSections } from '../../apps/coding-rules/rule'; -import IssueGuide from '../../apps/issues/components/IssueGuide'; import IssueHeader from '../../apps/issues/components/IssueHeader'; import StyledHeader from '../../apps/issues/components/StyledHeader'; import { fillBranchLike } from '../../helpers/branch-like'; @@ -381,10 +380,6 @@ export class IssueTabViewer extends React.PureComponent - 0. - run={top > 0} - /> Issue Resolution and Issue Status were merged and there are now five possible statuses in the issue lifecycle.

Won't Fix was replaced with Accept. Marking issues as Confirmed and Fixed was deprecated.

+issues.facet.issueStatuses.help.link=Learn more about new issue lifecycle issues.facet.hotspotStatuses=Hotspot Status issues.facet.assignees=Assignee issues.facet.files=File @@ -1245,7 +1248,12 @@ issues.facet.tags=Tag issues.facet.rules=Rule issues.facet.languages=Language issues.facet.cleanCodeAttributeCategories=Clean Code Attribute +issues.facet.cleanCodeAttributeCategories.help.title=Attributes group together conceptually similar issues. +issues.facet.cleanCodeAttributeCategories.help.link=Learn more about clean code attributes issues.facet.impactSoftwareQualities=Software Quality +issues.facet.impactSoftwareQualities.help.title=What are software qualities? +issues.facet.impactSoftwareQualities.help.description=Security is the protection of your software from unauthorized access, use, or destruction.

Reliability is a measure of how your software is capable of maintaining its level of performance under stated conditions for a stated period of time.

Maintainability refers to the ease with which you can repair, improve and understand software code.

+issues.facet.impactSoftwareQualities.help.link=Learn more about software qualities issues.facet.codeVariants=Code Variant issues.facet.createdAt=Creation Date issues.facet.createdAt.all=All @@ -3121,8 +3129,7 @@ severity_impact.LOW=Low severity_impact.LOW.description=Potential for moderate to minor impact. severity_impact.INFO=Info severity_impact.INFO.description=Neither a bug nor a quality flaw. Just a finding. -severity_impact.help.line1=Severities are now directly tied to the software quality impacted. This means that one software quality impacted has one severity. -severity_impact.help.line2=They can be changed with sufficient permissions. +severity_impact.help.description=Severities are now directly tied to the software quality impacted. This means that one software quality impacted has one severity.

They can be changed with sufficient permissions.

#------------------------------------------------------------------------------ # @@ -5886,31 +5893,4 @@ component_report.unsubscribe_success=Subscription successfully canceled. You won # GUIDING # #------------------------------------------------------------------------------ -guiding.issue_list.1.title=Introducing Clean Code Attributes -guiding.issue_list.1.content.1=Clean Code attributes are the characteristics that your code must have to be considered Clean Code. -guiding.issue_list.1.content.2=You can now filter by these attributes to evaluate why your code is breaking away from being clean. -guiding.issue_list.2.title=Introducing Software Qualities -guiding.issue_list.2.content.1=A software quality is a characteristic of software that contributes to its lasting value. -guiding.issue_list.2.content.2=You can now filter by these qualities to evaluate the areas in your software that are impacted by the introduction of code that isn't clean. -guiding.issue_list.3.title=Severity and Software Qualities -guiding.issue_list.3.content.1=Severities are now directly tied to the software quality impacted. This means that one software quality impacted has one severity. -guiding.issue_list.3.content.2=There are five levels: blocker, high, medium, low and info. -guiding.issue_list.4.title=Type and old severity deprecated -guiding.issue_list.4.content.1=Issue types and the old severities are deprecated and can no longer be modified. -guiding.issue_list.4.content.2=You can now filter issues by: -guiding.issue_list.4.content.list.1=Clean Code Attributes -guiding.issue_list.4.content.list.2=Software Qualities -guiding.issue_list.4.content.list.3=The severity of the software quality -guiding.issue_list.5.title=Learn more -guiding.issue_list.5.content=You can learn more about the approach to Clean Code in the {link} -guiding.issue_accept.1.title=Simplified issue lifecycle -guiding.issue_accept.1.content.1=Issue resolution and Issue status are now merged and there are only 5 possible statuses: Open, Accepted, False Positive, Confirmed and Fixed. -guiding.issue_accept.2.title=Won't Fix becomes Accept -guiding.issue_accept.2.content.1=Won't Fix is now called Accept and it keeps the same behaviour for now. -guiding.issue_accept.2.content.2=In the future, Accepting issues will be counted as technical debt. -guiding.issue_accept.2.content.link=Learn more about this status -guiding.issue_accept.3.title=Confirm and Fixed deprecated -guiding.issue_accept.3.content.1=Marking issues as Confirmed and Fixed is now deprecated. -guiding.issue_accept.3.content.2=Consider Accepting issues, assigning the issue or using comments and tags instead. -guiding.issue_accept.3.content.link=Learn more about issues statuses guiding.step_x_of_y={0} of {1} -- 2.39.5