diff options
7 files changed, 167 insertions, 168 deletions
diff --git a/server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts b/server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts index d0e9abd2661..ac260fff216 100644 --- a/server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts +++ b/server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts @@ -214,6 +214,35 @@ export default class IssuesServiceMock { ], 'component.key' ) + }, + { + issue: mockRawIssue(false, { + actions: ['set_type', 'set_tags', 'comment', 'set_severity', 'assign'], + transitions: ['confirm', 'resolve', 'falsepositive', 'wontfix'], + key: 'issue4', + component: 'project:file.bar', + message: 'Issue with tags', + rule: 'external_eslint_repo:no-div-regex', + textRange: { + startLine: 25, + endLine: 25, + startOffset: 0, + endOffset: 1 + }, + ruleDescriptionContextKey: 'spring', + ruleStatus: 'DEPRECATED', + quickFixAvailable: true + }), + snippets: keyBy( + [ + mockSnippetsByComponent( + 'file.bar', + 'project', + times(40, i => i + 20) + ) + ], + 'component.key' + ) } ]; (searchIssues as jest.Mock).mockImplementation(this.handleSearchIssues); 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 2746ea8e931..0d3a43a18b8 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 @@ -446,6 +446,21 @@ it('should show code tabs when any secondary location is selected', async () => ).toBeInTheDocument(); }); +it('should show issue tags if applicable', async () => { + const user = userEvent.setup(); + handler.setIsAdmin(true); + renderIssueApp(); + + // Select an issue with an advanced rule + await user.click(await screen.findByRole('region', { name: 'Issue with tags' })); + + expect( + screen.getByRole('heading', { + name: 'Issue with tags sonar-lint-icon issue.resolution.badge.DEPRECATED' + }) + ).toBeInTheDocument(); +}); + describe('redirects', () => { it('should work for hotspots', () => { renderProjectIssuesApp(`project/issues?types=${IssueType.SecurityHotspot}`); 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 61eda47277e..630c044e89f 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 @@ -18,20 +18,17 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import { FormattedMessage } from 'react-intl'; import { Link } from 'react-router-dom'; import { setIssueAssignee } from '../../../api/issues'; -import DocumentationTooltip from '../../../components/common/DocumentationTooltip'; -import Tooltip from '../../../components/controls/Tooltip'; import LinkIcon from '../../../components/icons/LinkIcon'; -import SonarLintIcon from '../../../components/icons/SonarLintIcon'; import { updateIssue } from '../../../components/issue/actions'; import IssueActionsBar from '../../../components/issue/components/IssueActionsBar'; import IssueChangelog from '../../../components/issue/components/IssueChangelog'; +import IssueMessageTags from '../../../components/issue/components/IssueMessageTags'; import { getBranchLikeQuery } from '../../../helpers/branch-like'; import { isInput, isShortcut } from '../../../helpers/keyboardEventHelpers'; import { KeyboardKeys } from '../../../helpers/keycodes'; -import { translate, translateWithParameters } from '../../../helpers/l10n'; +import { translate } from '../../../helpers/l10n'; import { getComponentIssuesUrl, getRuleUrl } from '../../../helpers/urls'; import { BranchLike } from '../../../types/branch-like'; import { RuleStatus } from '../../../types/rules'; @@ -132,56 +129,18 @@ export default class IssueHeader extends React.PureComponent<Props, State> { types: issue.type === 'SECURITY_HOTSPOT' ? issue.type : undefined }); const ruleStatus = issue.ruleStatus as RuleStatus | undefined; - const ruleEngine = issue.externalRuleEngine; const { quickFixAvailable } = issue; return ( <> <div className="display-flex-center display-flex-space-between big-padded-top"> <h1 className="text-bold spacer-right"> - {issue.message} - {quickFixAvailable && ( - <Tooltip - overlay={ - <FormattedMessage - id="issue.quick_fix_available_with_sonarlint" - defaultMessage={translate('issue.quick_fix_available_with_sonarlint')} - values={{ - link: ( - <a - href="https://www.sonarqube.org/sonarlint/?referrer=sonarqube-quick-fix" - rel="noopener noreferrer" - target="_blank"> - SonarLint - </a> - ) - }} - /> - } - mouseLeaveDelay={0.5}> - <SonarLintIcon className="it__issues-sonarlint-quick-fix spacer-left" size={20} /> - </Tooltip> - )} - {(ruleStatus === RuleStatus.Deprecated || ruleStatus === RuleStatus.Removed) && ( - <DocumentationTooltip - content={translate('rules.status', ruleStatus, 'help')} - links={[ - { - href: '/documentation/user-guide/rules/', - label: translateWithParameters('see_x', translate('rules')) - } - ]}> - <span className="badge spacer-left badge-error"> - {translate('issue.resolution.badge', ruleStatus)} - </span> - </DocumentationTooltip> - )} - {ruleEngine && ( - <Tooltip - overlay={translateWithParameters('issue.from_external_rule_engine', ruleEngine)}> - <div className="badge spacer-left text-baseline">{ruleEngine}</div> - </Tooltip> - )} + <span className="spacer-right">{issue.message}</span> + <IssueMessageTags + engine={issue.externalRuleEngine} + quickFixAvailable={quickFixAvailable} + ruleStatus={ruleStatus} + /> </h1> <div className="issue-meta issue-get-perma-link"> <Link diff --git a/server/sonar-web/src/main/js/components/issue/components/IssueMessage.tsx b/server/sonar-web/src/main/js/components/issue/components/IssueMessage.tsx index 0a605f6546b..df6c0798b3c 100644 --- a/server/sonar-web/src/main/js/components/issue/components/IssueMessage.tsx +++ b/server/sonar-web/src/main/js/components/issue/components/IssueMessage.tsx @@ -18,14 +18,11 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import { FormattedMessage } from 'react-intl'; import { ButtonLink } from '../../../components/controls/buttons'; -import Tooltip from '../../../components/controls/Tooltip'; -import { translate, translateWithParameters } from '../../../helpers/l10n'; +import { translate } from '../../../helpers/l10n'; import { RuleStatus } from '../../../types/rules'; -import DocumentationTooltip from '../../common/DocumentationTooltip'; -import SonarLintIcon from '../../icons/SonarLintIcon'; import { WorkspaceContext } from '../../workspace/context'; +import IssueMessageTags from './IssueMessageTags'; export interface IssueMessageProps { engine?: string; @@ -46,55 +43,17 @@ export default function IssueMessage(props: IssueMessageProps) { displayWhyIsThisAnIssue } = props; - const { externalRulesRepoNames, openRule } = React.useContext(WorkspaceContext); - const ruleEngine = (engine && externalRulesRepoNames && externalRulesRepoNames[engine]) || engine; + const { openRule } = React.useContext(WorkspaceContext); return ( <> <div className="display-inline-flex-center issue-message break-word"> <span className="spacer-right">{message}</span> - {quickFixAvailable && ( - <Tooltip - overlay={ - <FormattedMessage - id="issue.quick_fix_available_with_sonarlint" - defaultMessage={translate('issue.quick_fix_available_with_sonarlint')} - values={{ - link: ( - <a - href="https://www.sonarqube.org/sonarlint/?referrer=sonarqube-quick-fix" - rel="noopener noreferrer" - target="_blank"> - SonarLint - </a> - ) - }} - /> - } - mouseLeaveDelay={0.5}> - <SonarLintIcon className="it__issues-sonarlint-quick-fix spacer-right" size={15} /> - </Tooltip> - )} - {ruleStatus && (ruleStatus === RuleStatus.Deprecated || ruleStatus === RuleStatus.Removed) && ( - <DocumentationTooltip - className="spacer-left" - content={translate('rules.status', ruleStatus, 'help')} - links={[ - { - href: '/documentation/user-guide/rules/', - label: translateWithParameters('see_x', translate('rules')) - } - ]}> - <span className="spacer-right badge badge-error"> - {translate('issue.resolution.badge', ruleStatus)} - </span> - </DocumentationTooltip> - )} - {ruleEngine && ( - <Tooltip overlay={translateWithParameters('issue.from_external_rule_engine', ruleEngine)}> - <div className="badge spacer-right text-baseline">{ruleEngine}</div> - </Tooltip> - )} + <IssueMessageTags + engine={engine} + quickFixAvailable={quickFixAvailable} + ruleStatus={ruleStatus} + /> </div> {displayWhyIsThisAnIssue && ( <ButtonLink diff --git a/server/sonar-web/src/main/js/components/issue/components/IssueMessageTags.tsx b/server/sonar-web/src/main/js/components/issue/components/IssueMessageTags.tsx new file mode 100644 index 00000000000..f681a078916 --- /dev/null +++ b/server/sonar-web/src/main/js/components/issue/components/IssueMessageTags.tsx @@ -0,0 +1,91 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 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 * as React from 'react'; +import { FormattedMessage } from 'react-intl'; +import Tooltip from '../../../components/controls/Tooltip'; +import { translate, translateWithParameters } from '../../../helpers/l10n'; +import { RuleStatus } from '../../../types/rules'; +import DocumentationTooltip from '../../common/DocumentationTooltip'; +import SonarLintIcon from '../../icons/SonarLintIcon'; +import { WorkspaceContext } from '../../workspace/context'; + +export interface IssueMessageTagsProps { + engine?: string; + quickFixAvailable?: boolean; + ruleStatus?: RuleStatus; +} + +export default function IssueMessageTags(props: IssueMessageTagsProps) { + const { engine, quickFixAvailable, ruleStatus } = props; + + const { externalRulesRepoNames } = React.useContext(WorkspaceContext); + const ruleEngine = (engine && externalRulesRepoNames && externalRulesRepoNames[engine]) || engine; + + return ( + <> + {quickFixAvailable && ( + <Tooltip + overlay={ + <FormattedMessage + id="issue.quick_fix_available_with_sonarlint" + defaultMessage={translate('issue.quick_fix_available_with_sonarlint')} + values={{ + link: ( + <a + href="https://www.sonarqube.org/sonarlint/?referrer=sonarqube-quick-fix" + rel="noopener noreferrer" + target="_blank"> + SonarLint + </a> + ) + }} + /> + } + mouseLeaveDelay={0.5}> + <SonarLintIcon + className="it__issues-sonarlint-quick-fix spacer-right" + size={15} + ariaLabel="sonar-lint-icon" + /> + </Tooltip> + )} + {ruleStatus && (ruleStatus === RuleStatus.Deprecated || ruleStatus === RuleStatus.Removed) && ( + <DocumentationTooltip + className="spacer-left" + content={translate('rules.status', ruleStatus, 'help')} + links={[ + { + href: '/documentation/user-guide/rules/', + label: translateWithParameters('see_x', translate('rules')) + } + ]}> + <span className="spacer-right badge badge-error"> + {translate('issue.resolution.badge', ruleStatus)} + </span> + </DocumentationTooltip> + )} + {ruleEngine && ( + <Tooltip overlay={translateWithParameters('issue.from_external_rule_engine', ruleEngine)}> + <div className="badge spacer-right text-baseline">{ruleEngine}</div> + </Tooltip> + )} + </> + ); +} diff --git a/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueMessage-test.tsx.snap b/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueMessage-test.tsx.snap index eb1c297404f..3a8e73822e2 100644 --- a/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueMessage-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueMessage-test.tsx.snap @@ -10,6 +10,7 @@ exports[`should render correctly: default 1`] = ` > Reduce the number of conditional operators (4) used in the expression </span> + <IssueMessageTags /> </div> <ButtonLink aria-label="issue.why_this_issue.long" @@ -31,6 +32,7 @@ exports[`should render correctly: hide why is it an issue 1`] = ` > Reduce the number of conditional operators (4) used in the expression </span> + <IssueMessageTags /> </div> </Fragment> `; @@ -45,24 +47,9 @@ exports[`should render correctly: is deprecated rule 1`] = ` > Reduce the number of conditional operators (4) used in the expression </span> - <DocumentationTooltip - className="spacer-left" - content="rules.status.DEPRECATED.help" - links={ - Array [ - Object { - "href": "/documentation/user-guide/rules/", - "label": "see_x.rules", - }, - ] - } - > - <span - className="spacer-right badge badge-error" - > - issue.resolution.badge.DEPRECATED - </span> - </DocumentationTooltip> + <IssueMessageTags + ruleStatus="DEPRECATED" + /> </div> <ButtonLink aria-label="issue.why_this_issue.long" @@ -84,24 +71,9 @@ exports[`should render correctly: is removed rule 1`] = ` > Reduce the number of conditional operators (4) used in the expression </span> - <DocumentationTooltip - className="spacer-left" - content="rules.status.REMOVED.help" - links={ - Array [ - Object { - "href": "/documentation/user-guide/rules/", - "label": "see_x.rules", - }, - ] - } - > - <span - className="spacer-right badge badge-error" - > - issue.resolution.badge.REMOVED - </span> - </DocumentationTooltip> + <IssueMessageTags + ruleStatus="REMOVED" + /> </div> <ButtonLink aria-label="issue.why_this_issue.long" @@ -123,15 +95,9 @@ exports[`should render correctly: with engine info 1`] = ` > Reduce the number of conditional operators (4) used in the expression </span> - <Tooltip - overlay="issue.from_external_rule_engine.js" - > - <div - className="badge spacer-right text-baseline" - > - js - </div> - </Tooltip> + <IssueMessageTags + engine="js" + /> </div> <ButtonLink aria-label="issue.why_this_issue.long" @@ -153,31 +119,9 @@ exports[`should render correctly: with quick fix 1`] = ` > Reduce the number of conditional operators (4) used in the expression </span> - <Tooltip - mouseLeaveDelay={0.5} - overlay={ - <FormattedMessage - defaultMessage="issue.quick_fix_available_with_sonarlint" - id="issue.quick_fix_available_with_sonarlint" - values={ - Object { - "link": <a - href="https://www.sonarqube.org/sonarlint/?referrer=sonarqube-quick-fix" - rel="noopener noreferrer" - target="_blank" - > - SonarLint - </a>, - } - } - /> - } - > - <SonarLintIcon - className="it__issues-sonarlint-quick-fix spacer-right" - size={15} - /> - </Tooltip> + <IssueMessageTags + quickFixAvailable={true} + /> </div> <ButtonLink aria-label="issue.why_this_issue.long" diff --git a/server/sonar-web/src/main/js/types/issues.ts b/server/sonar-web/src/main/js/types/issues.ts index 2e12d798318..2c106f1ba60 100644 --- a/server/sonar-web/src/main/js/types/issues.ts +++ b/server/sonar-web/src/main/js/types/issues.ts @@ -64,6 +64,8 @@ export interface RawIssue { textRange?: TextRange; type: IssueType; ruleDescriptionContextKey?: string; + ruleStatus?: string; + quickFixAvailable?: boolean; } export interface IssueResponse { |