From: guillaume-peoch-sonarsource Date: Tue, 27 Dec 2022 15:34:19 +0000 (+0100) Subject: SONAR-16556 Do not open rule panel or issues under Project > Measures / Code tabs X-Git-Tag: 9.9.0.65466~115 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=ee69119351e6432bf221b2d74151ab99cf1c2546;p=sonarqube.git SONAR-16556 Do not open rule panel or issues under Project > Measures / Code tabs --- diff --git a/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesApp-it.tsx b/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesApp-it.tsx index f93a987b1c1..9c0ac32197a 100644 --- a/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesApp-it.tsx +++ b/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesApp-it.tsx @@ -50,6 +50,16 @@ beforeEach(() => { window.HTMLElement.prototype.scrollIntoView = jest.fn(); }); +it('should navigate to Why is this an issue tab', async () => { + renderProjectIssuesApp('project/issues?issues=issue2&open=issue2&id=myproject&why=1'); + expect( + await screen.findByRole('tab', { + name: `coding_rules.description_section.title.root_cause`, + selected: true, + }) + ).toBeInTheDocument(); +}); + //Improve this to include all the bulk change fonctionality it('should be able to bulk change', async () => { const user = userEvent.setup(); diff --git a/server/sonar-web/src/main/js/apps/issues/styles.css b/server/sonar-web/src/main/js/apps/issues/styles.css index e1d2e28f29e..bc12ffb35a6 100644 --- a/server/sonar-web/src/main/js/apps/issues/styles.css +++ b/server/sonar-web/src/main/js/apps/issues/styles.css @@ -169,18 +169,6 @@ width: 800px; } -.issues .issue { - border: 2px solid transparent; - cursor: pointer; -} - -.issues .issue:focus-within, -.issues .issue:hover { - border: 2px dashed var(--blue); - transition: all 0.3s ease; - outline: 0; -} - .issues .issue a:focus, .issues .issue button:focus { box-shadow: none; diff --git a/server/sonar-web/src/main/js/components/issue/Issue.css b/server/sonar-web/src/main/js/components/issue/Issue.css index 35a34901ecf..6a6731bb901 100644 --- a/server/sonar-web/src/main/js/components/issue/Issue.css +++ b/server/sonar-web/src/main/js/components/issue/Issue.css @@ -22,7 +22,8 @@ padding-top: var(--gridSize); padding-bottom: var(--gridSize); background-color: var(--issueBgColor); - transition: all 0.3s ease, border 0s ease; + transition: all 0.3s ease; + border: 2px solid transparent; cursor: pointer; } @@ -46,10 +47,6 @@ margin-top: 5px; } -.issue.selected + .issue { - border-top-color: transparent; -} - .issue-row { display: flex; margin-bottom: 5px; @@ -59,7 +56,6 @@ .issue-row-meta { padding-right: 5px; white-space: nowrap; - margin-top: 2px; } .issue-message { @@ -85,7 +81,7 @@ } .issue-meta { - line-height: 16px; + line-height: var(--smallFontSize); font-size: var(--smallFontSize); display: flex; } @@ -108,12 +104,6 @@ white-space: nowrap; } -.issue-see-rule { - border-bottom: none; - font-size: var(--smallFontSize); - margin-top: 5px; -} - .issue-changelog { width: 450px; max-height: 320px; @@ -275,7 +265,9 @@ background-color: var(--secondIssueBgColor); } -.issue-message-box.secondary-issue:hover { +.issue-message-box.secondary-issue:hover, +.issue:focus-within, +.issue:hover { border: 2px dashed var(--blue); outline: 0; cursor: pointer; 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 ec8aa593d90..d60dd94ec08 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,36 +18,34 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import { ButtonLink } from '../../../components/controls/buttons'; +import { Link } from 'react-router-dom'; +import { getBranchLikeQuery } from '../../../helpers/branch-like'; import { translate } from '../../../helpers/l10n'; -import { MessageFormatting } from '../../../types/issues'; +import { getComponentIssuesUrl } from '../../../helpers/urls'; +import { BranchLike } from '../../../types/branch-like'; import { RuleStatus } from '../../../types/rules'; -import { WorkspaceContext } from '../../workspace/context'; +import { Issue } from '../../../types/types'; import { IssueMessageHighlighting } from '../IssueMessageHighlighting'; import IssueMessageTags from './IssueMessageTags'; export interface IssueMessageProps { - engine?: string; - quickFixAvailable?: boolean; + issue: Issue; + branchLike?: BranchLike; displayWhyIsThisAnIssue?: boolean; - message: string; - messageFormattings?: MessageFormatting[]; - ruleKey: string; - ruleStatus?: RuleStatus; } export default function IssueMessage(props: IssueMessageProps) { - const { - engine, - quickFixAvailable, - message, - messageFormattings, - ruleKey, - ruleStatus, - displayWhyIsThisAnIssue, - } = props; + const { issue, branchLike, displayWhyIsThisAnIssue } = props; - const { openRule } = React.useContext(WorkspaceContext); + const { externalRuleEngine, quickFixAvailable, message, messageFormattings, ruleStatus } = issue; + + const whyIsThisAnIssueUrl = getComponentIssuesUrl(issue.project, { + ...getBranchLikeQuery(branchLike), + files: issue.componentLongName, + open: issue.key, + resolved: 'false', + why: '1', + }); return ( <> @@ -56,23 +54,20 @@ export default function IssueMessage(props: IssueMessageProps) { {displayWhyIsThisAnIssue && ( - - openRule({ - key: ruleKey, - }) - } + className="spacer-right" + target="_blank" + to={whyIsThisAnIssueUrl} > {translate('issue.why_this_issue')} - + )} ); diff --git a/server/sonar-web/src/main/js/components/issue/components/IssueTitleBar.tsx b/server/sonar-web/src/main/js/components/issue/components/IssueTitleBar.tsx index b8bfdecfefa..8e63e16a494 100644 --- a/server/sonar-web/src/main/js/components/issue/components/IssueTitleBar.tsx +++ b/server/sonar-web/src/main/js/components/issue/components/IssueTitleBar.tsx @@ -26,7 +26,6 @@ import { translate, translateWithParameters } from '../../../helpers/l10n'; import { formatMeasure } from '../../../helpers/measures'; import { getComponentIssuesUrl } from '../../../helpers/urls'; import { BranchLike } from '../../../types/branch-like'; -import { RuleStatus } from '../../../types/rules'; import { Issue } from '../../../types/types'; import LocationIndex from '../../common/LocationIndex'; import IssueChangelog from './IssueChangelog'; @@ -76,13 +75,9 @@ export default function IssueTitleBar(props: IssueTitleBarProps) { return (
diff --git a/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueMessage-test.tsx b/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueMessage-test.tsx index 10d353040a8..8cea8e54049 100644 --- a/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueMessage-test.tsx +++ b/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueMessage-test.tsx @@ -19,8 +19,9 @@ */ import { shallow } from 'enzyme'; import * as React from 'react'; +import { mockBranch } from '../../../../helpers/mocks/branch-like'; +import { mockIssue } from '../../../../helpers/testMocks'; import { RuleStatus } from '../../../../types/rules'; -import { ButtonLink } from '../../../controls/buttons'; import IssueMessage, { IssueMessageProps } from '../IssueMessage'; jest.mock('react', () => { @@ -34,35 +35,31 @@ jest.mock('react', () => { it('should render correctly', () => { expect(shallowRender()).toMatchSnapshot('default'); - expect(shallowRender({ engine: 'js' })).toMatchSnapshot('with engine info'); - expect(shallowRender({ quickFixAvailable: true })).toMatchSnapshot('with quick fix'); - expect(shallowRender({ ruleStatus: RuleStatus.Deprecated })).toMatchSnapshot( - 'is deprecated rule' + expect(shallowRender({ issue: mockIssue(false, { externalRuleEngine: 'js' }) })).toMatchSnapshot( + 'with engine info' ); - expect(shallowRender({ ruleStatus: RuleStatus.Removed })).toMatchSnapshot('is removed rule'); + expect(shallowRender({ issue: mockIssue(false, { quickFixAvailable: true }) })).toMatchSnapshot( + 'with quick fix' + ); + expect( + shallowRender({ issue: mockIssue(false, { ruleStatus: RuleStatus.Deprecated }) }) + ).toMatchSnapshot('is deprecated rule'); + expect( + shallowRender({ issue: mockIssue(false, { ruleStatus: RuleStatus.Removed }) }) + ).toMatchSnapshot('is removed rule'); expect(shallowRender({ displayWhyIsThisAnIssue: false })).toMatchSnapshot( 'hide why is it an issue' ); }); -it('should open why is this an issue workspace', () => { - const openRule = jest.fn(); - (React.useContext as jest.Mock).mockImplementationOnce(() => ({ - externalRulesRepoNames: {}, - openRule, - })); - const wrapper = shallowRender(); - wrapper.find(ButtonLink).simulate('click'); - - expect(openRule).toHaveBeenCalled(); -}); - function shallowRender(props: Partial = {}) { return shallow( ); 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 bc1d76c8876..bff0c5c5ab9 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 @@ -14,13 +14,20 @@ exports[`should render correctly: default 1`] = `
- issue.why_this_issue - + `; @@ -57,13 +64,20 @@ exports[`should render correctly: is deprecated rule 1`] = ` ruleStatus="DEPRECATED" />
- issue.why_this_issue - + `; @@ -83,13 +97,20 @@ exports[`should render correctly: is removed rule 1`] = ` ruleStatus="REMOVED" />
- issue.why_this_issue - + `; @@ -109,13 +130,20 @@ exports[`should render correctly: with engine info 1`] = ` engine="js" /> - issue.why_this_issue - + `; @@ -135,12 +163,19 @@ exports[`should render correctly: with quick fix 1`] = ` quickFixAvailable={true} /> - issue.why_this_issue - + `; diff --git a/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueTitleBar-test.tsx.snap b/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueTitleBar-test.tsx.snap index ecb2cf2fe3a..d628602e618 100644 --- a/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueTitleBar-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueTitleBar-test.tsx.snap @@ -5,9 +5,39 @@ exports[`should render correctly: default 1`] = ` className="issue-row" >
this.computeState(prevState)); this.attachScrollEvent(); + + const tabs = this.computeTabs(Boolean(this.state.displayEducationalPrinciplesNotification)); + + const query = new URLSearchParams(this.props.location.search); + if (query.has('why')) { + this.setState({ + selectedTab: tabs.find((tab) => tab.key === TabKeys.WhyIsThisAnIssue) || tabs[0], + }); + } } componentDidUpdate(prevProps: RuleTabViewerProps, prevState: State) { @@ -337,4 +349,4 @@ export class RuleTabViewer extends React.PureComponent; height: number; maximized?: boolean; - open: { component?: string; rule?: string }; + open: { component?: string }; rules: RuleDescriptor[]; } @@ -125,17 +124,6 @@ export default class Workspace extends React.PureComponent<{}, State> { this.setState({ open: { component: componentKey } }); }; - handleOpenRule = (rule: RuleDescriptor) => { - this.setState((state: State) => ({ - open: { rule: rule.key }, - rules: uniqBy([...state.rules, rule], (r) => r.key), - })); - }; - - handleRuleReopen = (ruleKey: string) => { - this.setState({ open: { rule: ruleKey } }); - }; - handleComponentClose = (componentKey: string) => { this.setState((state: State) => ({ components: state.components.filter((x) => x.key !== componentKey), @@ -146,16 +134,6 @@ export default class Workspace extends React.PureComponent<{}, State> { })); }; - handleRuleClose = (ruleKey: string) => { - this.setState((state: State) => ({ - rules: state.rules.filter((x) => x.key !== ruleKey), - open: { - ...state.open, - rule: state.open.rule === ruleKey ? undefined : state.open.rule, - }, - })); - }; - handleComponentLoad = (details: { key: string; name: string; qualifier: string }) => { if (this.mounted) { const { key, name, qualifier } = details; @@ -167,15 +145,6 @@ export default class Workspace extends React.PureComponent<{}, State> { } }; - handleRuleLoad = (details: { key: string; name: string }) => { - if (this.mounted) { - const { key, name } = details; - this.setState((state: State) => ({ - rules: state.rules.map((rule) => (rule.key === key ? { ...rule, name } : rule)), - })); - } - }; - handleCollapse = () => { this.setState({ open: {} }); }; @@ -200,7 +169,6 @@ export default class Workspace extends React.PureComponent<{}, State> { const { components, externalRulesRepoNames, height, maximized, open, rules } = this.state; const openComponent = open.component && components.find((x) => x.key === open.component); - const openRule = open.rule && rules.find((x) => x.key === open.rule); const actualHeight = maximized ? window.innerHeight * MAX_HEIGHT : height; @@ -209,7 +177,6 @@ export default class Workspace extends React.PureComponent<{}, State> { value={{ externalRulesRepoNames, openComponent: this.handleOpenComponent, - openRule: this.handleOpenRule, }} > {this.props.children} @@ -219,10 +186,7 @@ export default class Workspace extends React.PureComponent<{}, State> { components={components} onComponentClose={this.handleComponentClose} onComponentOpen={this.handleComponentReopen} - onRuleClose={this.handleRuleClose} - onRuleOpen={this.handleRuleReopen} open={open} - rules={rules} /> )} {openComponent && ( @@ -238,19 +202,6 @@ export default class Workspace extends React.PureComponent<{}, State> { onResize={this.handleResize} /> )} - {openRule && ( - - )} ); diff --git a/server/sonar-web/src/main/js/components/workspace/WorkspaceNav.tsx b/server/sonar-web/src/main/js/components/workspace/WorkspaceNav.tsx index 34d4fc5193a..bab413e61a5 100644 --- a/server/sonar-web/src/main/js/components/workspace/WorkspaceNav.tsx +++ b/server/sonar-web/src/main/js/components/workspace/WorkspaceNav.tsx @@ -18,24 +18,19 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import { ComponentDescriptor, RuleDescriptor } from './context'; +import { ComponentDescriptor } from './context'; import WorkspaceNavComponent from './WorkspaceNavComponent'; -import WorkspaceNavRule from './WorkspaceNavRule'; export interface Props { components: ComponentDescriptor[]; - rules: RuleDescriptor[]; onComponentClose: (componentKey: string) => void; onComponentOpen: (componentKey: string) => void; - onRuleClose: (ruleKey: string) => void; - onRuleOpen: (ruleKey: string) => void; open: { component?: string; rule?: string }; } export default function WorkspaceNav(props: Props) { // do not show a tab for the currently open component/rule const components = props.components.filter((x) => x.key !== props.open.component); - const rules = props.rules.filter((x) => x.key !== props.open.rule); return ( ); diff --git a/server/sonar-web/src/main/js/components/workspace/__tests__/Workspace-test.tsx b/server/sonar-web/src/main/js/components/workspace/__tests__/Workspace-test.tsx index 358d8193b43..f1dcd698eb1 100644 --- a/server/sonar-web/src/main/js/components/workspace/__tests__/Workspace-test.tsx +++ b/server/sonar-web/src/main/js/components/workspace/__tests__/Workspace-test.tsx @@ -23,13 +23,7 @@ import { mockBranch } from '../../../helpers/mocks/branch-like'; import { get, save } from '../../../helpers/storage'; import { waitAndUpdate } from '../../../helpers/testUtils'; import { ComponentQualifier } from '../../../types/component'; -import Workspace, { - INITIAL_HEIGHT, - MAX_HEIGHT, - MIN_HEIGHT, - TYPE_KEY, - WorkspaceTypes, -} from '../Workspace'; +import Workspace, { INITIAL_HEIGHT, MIN_HEIGHT, TYPE_KEY, WorkspaceTypes } from '../Workspace'; jest.mock('../../../helpers/storage', () => { return { @@ -76,12 +70,6 @@ it('should render correctly', () => { open: { component: 'foo' }, }) ).toMatchSnapshot('open component'); - expect( - shallowRender({ - rules: [{ key: 'foo' }], - open: { rule: 'foo' }, - }) - ).toMatchSnapshot('open rule'); }); it('should correctly load data from local storage', () => { @@ -134,17 +122,9 @@ it('should allow elements to be loaded and updated', () => { }); const instance = wrapper.instance(); - // Load an non-existent element won't do anything. - instance.handleRuleLoad({ key: 'baz', name: 'Baz' }); - expect(wrapper.state().rules).toEqual([rule]); - instance.handleComponentLoad({ key: 'baz', name: 'Baz', qualifier: ComponentQualifier.TestFile }); expect(wrapper.state().components).toEqual([component]); - // Load an existing element will update some of its properties. - instance.handleRuleLoad({ key: 'bar', name: 'Bar' }); - expect(wrapper.state().rules).toEqual([{ ...rule, name: 'Bar' }]); - instance.handleComponentLoad({ key: 'foo', name: 'Foo', qualifier: ComponentQualifier.File }); expect(wrapper.state().components).toEqual([ { ...component, name: 'Foo', qualifier: ComponentQualifier.File }, @@ -155,18 +135,14 @@ it('should be resizable', () => { (get as jest.Mock).mockReturnValue( JSON.stringify([{ [TYPE_KEY]: WorkspaceTypes.Rule, key: 'foo' }]) ); - const wrapper = shallowRender({ open: { rule: 'foo' } }); + const wrapper = shallowRender(); const instance = wrapper.instance(); instance.handleMaximize(); expect(wrapper.state().maximized).toBe(true); - // We cannot fetch by reference, as the viewer component is lazy loaded. Find - // by string instead. - expect(wrapper.find('WorkspaceRuleViewer').props().height).toBe(WINDOW_HEIGHT * MAX_HEIGHT); instance.handleMinimize(); expect(wrapper.state().maximized).toBe(false); - expect(wrapper.find('WorkspaceRuleViewer').props().height).toBe(INITIAL_HEIGHT); instance.handleResize(-200); expect(wrapper.state().height).toBe(INITIAL_HEIGHT + 200); @@ -176,10 +152,6 @@ it('should be resizable', () => { }); it('should be openable/collapsible', () => { - const rule = { - key: 'baz', - name: 'Baz', - }; const component = { branchLike: mockBranch(), key: 'foo', @@ -190,9 +162,6 @@ it('should be openable/collapsible', () => { instance.handleOpenComponent(component); expect(wrapper.state().open).toEqual({ component: 'foo' }); - instance.handleOpenRule(rule); - expect(wrapper.state().open).toEqual({ rule: 'baz' }); - instance.handleCollapse(); expect(wrapper.state().open).toEqual({}); @@ -203,14 +172,6 @@ it('should be openable/collapsible', () => { expect(wrapper.state().open).toEqual({ component: 'foo' }); instance.handleComponentClose('foo'); expect(wrapper.state().open).toEqual({}); - - instance.handleRuleReopen(rule.key); - expect(wrapper.state().open).toEqual({ rule: 'baz' }); - - instance.handleRuleClose('bar'); - expect(wrapper.state().open).toEqual({ rule: 'baz' }); - instance.handleRuleClose('baz'); - expect(wrapper.state().open).toEqual({}); }); function shallowRender(state?: Partial) { diff --git a/server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceNav-test.tsx b/server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceNav-test.tsx index fe57669a76f..954c742a1b3 100644 --- a/server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceNav-test.tsx +++ b/server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceNav-test.tsx @@ -29,25 +29,17 @@ it('should not render open component', () => { expect(shallowRender({ open: { component: 'bar' } })).toMatchSnapshot(); }); -it('should not render open rule', () => { - expect(shallowRender({ open: { rule: 'qux' } })).toMatchSnapshot(); -}); - function shallowRender(props?: Partial) { const components = [ { branchLike: undefined, key: 'foo' }, { branchLike: undefined, key: 'bar' }, ]; - const rules = [{ key: 'qux' }]; return shallow( ); diff --git a/server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/Workspace-test.tsx.snap b/server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/Workspace-test.tsx.snap index c6fc77fe21b..e7ef8687609 100644 --- a/server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/Workspace-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/Workspace-test.tsx.snap @@ -8,7 +8,6 @@ exports[`should render correctly: default 1`] = ` { "externalRulesRepoNames": {}, "openComponent": [Function], - "openRule": [Function], } } > @@ -25,7 +24,6 @@ exports[`should render correctly: open component 1`] = ` { "externalRulesRepoNames": {}, "openComponent": [Function], - "openRule": [Function], } } > @@ -49,14 +47,11 @@ exports[`should render correctly: open component 1`] = ` } onComponentClose={[Function]} onComponentOpen={[Function]} - onRuleClose={[Function]} - onRuleOpen={[Function]} open={ { "component": "foo", } } - rules={[]} /> `; - -exports[`should render correctly: open rule 1`] = ` - -
- - - - - -`; diff --git a/server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceNav-test.tsx.snap b/server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceNav-test.tsx.snap index 2712037667d..8a428d794ac 100644 --- a/server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceNav-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceNav-test.tsx.snap @@ -18,49 +18,6 @@ exports[`should not render open component 1`] = ` onClose={[MockFunction]} onOpen={[MockFunction]} /> - - - -`; - -exports[`should not render open rule 1`] = ` - `; @@ -94,16 +51,6 @@ exports[`should render 1`] = ` onClose={[MockFunction]} onOpen={[MockFunction]} /> - `; diff --git a/server/sonar-web/src/main/js/components/workspace/context.ts b/server/sonar-web/src/main/js/components/workspace/context.ts index 5b65951ed60..c4050243b4d 100644 --- a/server/sonar-web/src/main/js/components/workspace/context.ts +++ b/server/sonar-web/src/main/js/components/workspace/context.ts @@ -37,11 +37,9 @@ export interface RuleDescriptor { export interface WorkspaceContextShape { externalRulesRepoNames: Dict; openComponent: (component: ComponentDescriptor) => void; - openRule: (rule: RuleDescriptor) => void; } export const WorkspaceContext = createContext({ externalRulesRepoNames: {}, openComponent: () => {}, - openRule: () => {}, });