]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-16556 Do not open rule panel or issues under Project > Measures / Code tabs
authorguillaume-peoch-sonarsource <guillaume.peoch@sonarsource.com>
Tue, 27 Dec 2022 15:34:19 +0000 (16:34 +0100)
committersonartech <sonartech@sonarsource.com>
Wed, 4 Jan 2023 20:02:52 +0000 (20:02 +0000)
16 files changed:
server/sonar-web/src/main/js/apps/issues/__tests__/IssuesApp-it.tsx
server/sonar-web/src/main/js/apps/issues/styles.css
server/sonar-web/src/main/js/components/issue/Issue.css
server/sonar-web/src/main/js/components/issue/components/IssueMessage.tsx
server/sonar-web/src/main/js/components/issue/components/IssueTitleBar.tsx
server/sonar-web/src/main/js/components/issue/components/__tests__/IssueMessage-test.tsx
server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueMessage-test.tsx.snap
server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueTitleBar-test.tsx.snap
server/sonar-web/src/main/js/components/rules/RuleTabViewer.tsx
server/sonar-web/src/main/js/components/workspace/Workspace.tsx
server/sonar-web/src/main/js/components/workspace/WorkspaceNav.tsx
server/sonar-web/src/main/js/components/workspace/__tests__/Workspace-test.tsx
server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceNav-test.tsx
server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/Workspace-test.tsx.snap
server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceNav-test.tsx.snap
server/sonar-web/src/main/js/components/workspace/context.ts

index f93a987b1c1c6e830a5ef0378d2a84ed04fac74a..9c0ac32197ae7dfd8e5dc3dd3101004799e191a1 100644 (file)
@@ -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();
index e1d2e28f29e8e0146ec62396e8eddd7fc1ab6780..bc12ffb35a638505a305ba716a311029751581dd 100644 (file)
   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;
index 35a34901ecf4af8e51ea4467f950a7bde7088eb2..6a6731bb901b0cace7ebf4b0055db8947d4a4fc3 100644 (file)
@@ -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;
 }
 
   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;
 }
   white-space: nowrap;
 }
 
-.issue-see-rule {
-  border-bottom: none;
-  font-size: var(--smallFontSize);
-  margin-top: 5px;
-}
-
 .issue-changelog {
   width: 450px;
   max-height: 320px;
   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;
index ec8aa593d905399a897310ad17f0bb8d58e07921..d60dd94ec08dc7935bed73462cea9cc36a706aff 100644 (file)
  * 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) {
           <IssueMessageHighlighting message={message} messageFormattings={messageFormattings} />
         </span>
         <IssueMessageTags
-          engine={engine}
+          engine={externalRuleEngine}
           quickFixAvailable={quickFixAvailable}
-          ruleStatus={ruleStatus}
+          ruleStatus={ruleStatus as RuleStatus | undefined}
         />
       </div>
       {displayWhyIsThisAnIssue && (
-        <ButtonLink
+        <Link
           aria-label={translate('issue.why_this_issue.long')}
-          className="issue-see-rule spacer-right text-baseline"
-          onClick={() =>
-            openRule({
-              key: ruleKey,
-            })
-          }
+          className="spacer-right"
+          target="_blank"
+          to={whyIsThisAnIssueUrl}
         >
           {translate('issue.why_this_issue')}
-        </ButtonLink>
+        </Link>
       )}
     </>
   );
index b8bfdecfefa4a5b81e5953ddcf57dbc54535176e..8e63e16a49446331fd287541f0e97a7096f853f3 100644 (file)
@@ -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 (
     <div className="issue-row">
       <IssueMessage
-        engine={issue.externalRuleEngine}
-        quickFixAvailable={issue.quickFixAvailable}
+        issue={issue}
+        branchLike={props.branchLike}
         displayWhyIsThisAnIssue={displayWhyIsThisAnIssue}
-        message={issue.message}
-        messageFormattings={issue.messageFormattings}
-        ruleKey={issue.rule}
-        ruleStatus={issue.ruleStatus as RuleStatus | undefined}
       />
       <div className="issue-row-meta">
         <div className="issue-meta-list">
index 10d353040a8277806bb1bbf39e7c9de4206459ec..8cea8e54049a59adaa78bfe2c8e205f89415316b 100644 (file)
@@ -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<IssueMessageProps> = {}) {
   return shallow<IssueMessageProps>(
     <IssueMessage
-      message="Reduce the number of conditional operators (4) used in the expression"
+      issue={mockIssue(false, {
+        message: 'Reduce the number of conditional operators (4) used in the expression',
+      })}
       displayWhyIsThisAnIssue={true}
-      ruleKey="javascript:S1067"
+      branchLike={mockBranch()}
       {...props}
     />
   );
index bc1d76c887612fb62ac6893df4b6e7806d2857df..bff0c5c5ab9034246c6696135d95fea83e541ffa 100644 (file)
@@ -14,13 +14,20 @@ exports[`should render correctly: default 1`] = `
     </span>
     <IssueMessageTags />
   </div>
-  <ButtonLink
+  <Link
     aria-label="issue.why_this_issue.long"
-    className="issue-see-rule spacer-right text-baseline"
-    onClick={[Function]}
+    className="spacer-right"
+    target="_blank"
+    to={
+      {
+        "hash": "",
+        "pathname": "/project/issues",
+        "search": "?branch=branch-6.7&files=main.js&open=AVsae-CQS-9G3txfbFN2&resolved=false&why=1&id=myproject",
+      }
+    }
   >
     issue.why_this_issue
-  </ButtonLink>
+  </Link>
 </Fragment>
 `;
 
@@ -57,13 +64,20 @@ exports[`should render correctly: is deprecated rule 1`] = `
       ruleStatus="DEPRECATED"
     />
   </div>
-  <ButtonLink
+  <Link
     aria-label="issue.why_this_issue.long"
-    className="issue-see-rule spacer-right text-baseline"
-    onClick={[Function]}
+    className="spacer-right"
+    target="_blank"
+    to={
+      {
+        "hash": "",
+        "pathname": "/project/issues",
+        "search": "?branch=branch-6.7&files=main.js&open=AVsae-CQS-9G3txfbFN2&resolved=false&why=1&id=myproject",
+      }
+    }
   >
     issue.why_this_issue
-  </ButtonLink>
+  </Link>
 </Fragment>
 `;
 
@@ -83,13 +97,20 @@ exports[`should render correctly: is removed rule 1`] = `
       ruleStatus="REMOVED"
     />
   </div>
-  <ButtonLink
+  <Link
     aria-label="issue.why_this_issue.long"
-    className="issue-see-rule spacer-right text-baseline"
-    onClick={[Function]}
+    className="spacer-right"
+    target="_blank"
+    to={
+      {
+        "hash": "",
+        "pathname": "/project/issues",
+        "search": "?branch=branch-6.7&files=main.js&open=AVsae-CQS-9G3txfbFN2&resolved=false&why=1&id=myproject",
+      }
+    }
   >
     issue.why_this_issue
-  </ButtonLink>
+  </Link>
 </Fragment>
 `;
 
@@ -109,13 +130,20 @@ exports[`should render correctly: with engine info 1`] = `
       engine="js"
     />
   </div>
-  <ButtonLink
+  <Link
     aria-label="issue.why_this_issue.long"
-    className="issue-see-rule spacer-right text-baseline"
-    onClick={[Function]}
+    className="spacer-right"
+    target="_blank"
+    to={
+      {
+        "hash": "",
+        "pathname": "/project/issues",
+        "search": "?branch=branch-6.7&files=main.js&open=AVsae-CQS-9G3txfbFN2&resolved=false&why=1&id=myproject",
+      }
+    }
   >
     issue.why_this_issue
-  </ButtonLink>
+  </Link>
 </Fragment>
 `;
 
@@ -135,12 +163,19 @@ exports[`should render correctly: with quick fix 1`] = `
       quickFixAvailable={true}
     />
   </div>
-  <ButtonLink
+  <Link
     aria-label="issue.why_this_issue.long"
-    className="issue-see-rule spacer-right text-baseline"
-    onClick={[Function]}
+    className="spacer-right"
+    target="_blank"
+    to={
+      {
+        "hash": "",
+        "pathname": "/project/issues",
+        "search": "?branch=branch-6.7&files=main.js&open=AVsae-CQS-9G3txfbFN2&resolved=false&why=1&id=myproject",
+      }
+    }
   >
     issue.why_this_issue
-  </ButtonLink>
+  </Link>
 </Fragment>
 `;
index ecb2cf2fe3a8cd39dd98d424537513f7064bfe5f..d628602e61866388e6a32d7664fea110dfd181e3 100644 (file)
@@ -5,9 +5,39 @@ exports[`should render correctly: default 1`] = `
   className="issue-row"
 >
   <IssueMessage
-    engine="foo"
-    message="Reduce the number of conditional operators (4) used in the expression"
-    ruleKey="javascript:S1067"
+    issue={
+      {
+        "actions": [],
+        "component": "main.js",
+        "componentEnabled": true,
+        "componentLongName": "main.js",
+        "componentQualifier": "FIL",
+        "componentUuid": "foo1234",
+        "creationDate": "2017-03-01T09:36:01+0100",
+        "externalRuleEngine": "foo",
+        "flows": [],
+        "flowsWithType": [],
+        "key": "AVsae-CQS-9G3txfbFN2",
+        "line": 25,
+        "message": "Reduce the number of conditional operators (4) used in the expression",
+        "project": "myproject",
+        "projectKey": "foo",
+        "projectName": "Foo",
+        "rule": "javascript:S1067",
+        "ruleName": "foo",
+        "secondaryLocations": [],
+        "severity": "MAJOR",
+        "status": "OPEN",
+        "textRange": {
+          "endLine": 26,
+          "endOffset": 15,
+          "startLine": 25,
+          "startOffset": 0,
+        },
+        "transitions": [],
+        "type": "BUG",
+      }
+    }
   />
   <div
     className="issue-row-meta"
@@ -96,9 +126,39 @@ exports[`should render correctly: with filter 1`] = `
   className="issue-row"
 >
   <IssueMessage
-    engine="foo"
-    message="Reduce the number of conditional operators (4) used in the expression"
-    ruleKey="javascript:S1067"
+    issue={
+      {
+        "actions": [],
+        "component": "main.js",
+        "componentEnabled": true,
+        "componentLongName": "main.js",
+        "componentQualifier": "FIL",
+        "componentUuid": "foo1234",
+        "creationDate": "2017-03-01T09:36:01+0100",
+        "externalRuleEngine": "foo",
+        "flows": [],
+        "flowsWithType": [],
+        "key": "AVsae-CQS-9G3txfbFN2",
+        "line": 25,
+        "message": "Reduce the number of conditional operators (4) used in the expression",
+        "project": "myproject",
+        "projectKey": "foo",
+        "projectName": "Foo",
+        "rule": "javascript:S1067",
+        "ruleName": "foo",
+        "secondaryLocations": [],
+        "severity": "MAJOR",
+        "status": "OPEN",
+        "textRange": {
+          "endLine": 26,
+          "endOffset": 15,
+          "startLine": 25,
+          "startOffset": 0,
+        },
+        "transitions": [],
+        "type": "BUG",
+      }
+    }
   />
   <div
     className="issue-row-meta"
@@ -229,8 +289,107 @@ exports[`should render correctly: with multi locations 1`] = `
   className="issue-row"
 >
   <IssueMessage
-    message="Reduce the number of conditional operators (4) used in the expression"
-    ruleKey="javascript:S1067"
+    issue={
+      {
+        "actions": [],
+        "component": "main.js",
+        "componentEnabled": true,
+        "componentLongName": "main.js",
+        "componentQualifier": "FIL",
+        "componentUuid": "foo1234",
+        "creationDate": "2017-03-01T09:36:01+0100",
+        "flows": [
+          [
+            {
+              "component": "main.js",
+              "textRange": {
+                "endLine": 2,
+                "endOffset": 2,
+                "startLine": 1,
+                "startOffset": 1,
+              },
+            },
+            {
+              "component": "main.js",
+              "textRange": {
+                "endLine": 2,
+                "endOffset": 2,
+                "startLine": 1,
+                "startOffset": 1,
+              },
+            },
+            {
+              "component": "main.js",
+              "textRange": {
+                "endLine": 2,
+                "endOffset": 2,
+                "startLine": 1,
+                "startOffset": 1,
+              },
+            },
+          ],
+          [
+            {
+              "component": "main.js",
+              "textRange": {
+                "endLine": 2,
+                "endOffset": 2,
+                "startLine": 1,
+                "startOffset": 1,
+              },
+            },
+            {
+              "component": "main.js",
+              "textRange": {
+                "endLine": 2,
+                "endOffset": 2,
+                "startLine": 1,
+                "startOffset": 1,
+              },
+            },
+          ],
+        ],
+        "flowsWithType": [],
+        "key": "AVsae-CQS-9G3txfbFN2",
+        "line": 25,
+        "message": "Reduce the number of conditional operators (4) used in the expression",
+        "project": "myproject",
+        "projectKey": "foo",
+        "projectName": "Foo",
+        "rule": "javascript:S1067",
+        "ruleName": "foo",
+        "secondaryLocations": [
+          {
+            "component": "main.js",
+            "textRange": {
+              "endLine": 2,
+              "endOffset": 2,
+              "startLine": 1,
+              "startOffset": 1,
+            },
+          },
+          {
+            "component": "main.js",
+            "textRange": {
+              "endLine": 2,
+              "endOffset": 2,
+              "startLine": 1,
+              "startOffset": 1,
+            },
+          },
+        ],
+        "severity": "MAJOR",
+        "status": "OPEN",
+        "textRange": {
+          "endLine": 26,
+          "endOffset": 15,
+          "startLine": 25,
+          "startOffset": 0,
+        },
+        "transitions": [],
+        "type": "BUG",
+      }
+    }
   />
   <div
     className="issue-row-meta"
@@ -398,8 +557,115 @@ exports[`should render correctly: with multi locations and link 1`] = `
   className="issue-row"
 >
   <IssueMessage
-    message="Reduce the number of conditional operators (4) used in the expression"
-    ruleKey="javascript:S1067"
+    branchLike={
+      {
+        "analysisDate": "2018-01-01",
+        "excludedFromPurge": true,
+        "isMain": false,
+        "name": "branch-6.7",
+      }
+    }
+    issue={
+      {
+        "actions": [],
+        "component": "main.js",
+        "componentEnabled": true,
+        "componentLongName": "main.js",
+        "componentQualifier": "FIL",
+        "componentUuid": "foo1234",
+        "creationDate": "2017-03-01T09:36:01+0100",
+        "flows": [
+          [
+            {
+              "component": "main.js",
+              "textRange": {
+                "endLine": 2,
+                "endOffset": 2,
+                "startLine": 1,
+                "startOffset": 1,
+              },
+            },
+            {
+              "component": "main.js",
+              "textRange": {
+                "endLine": 2,
+                "endOffset": 2,
+                "startLine": 1,
+                "startOffset": 1,
+              },
+            },
+            {
+              "component": "main.js",
+              "textRange": {
+                "endLine": 2,
+                "endOffset": 2,
+                "startLine": 1,
+                "startOffset": 1,
+              },
+            },
+          ],
+          [
+            {
+              "component": "main.js",
+              "textRange": {
+                "endLine": 2,
+                "endOffset": 2,
+                "startLine": 1,
+                "startOffset": 1,
+              },
+            },
+            {
+              "component": "main.js",
+              "textRange": {
+                "endLine": 2,
+                "endOffset": 2,
+                "startLine": 1,
+                "startOffset": 1,
+              },
+            },
+          ],
+        ],
+        "flowsWithType": [],
+        "key": "AVsae-CQS-9G3txfbFN2",
+        "line": 25,
+        "message": "Reduce the number of conditional operators (4) used in the expression",
+        "project": "myproject",
+        "projectKey": "foo",
+        "projectName": "Foo",
+        "rule": "javascript:S1067",
+        "ruleName": "foo",
+        "secondaryLocations": [
+          {
+            "component": "main.js",
+            "textRange": {
+              "endLine": 2,
+              "endOffset": 2,
+              "startLine": 1,
+              "startOffset": 1,
+            },
+          },
+          {
+            "component": "main.js",
+            "textRange": {
+              "endLine": 2,
+              "endOffset": 2,
+              "startLine": 1,
+              "startOffset": 1,
+            },
+          },
+        ],
+        "severity": "MAJOR",
+        "status": "OPEN",
+        "textRange": {
+          "endLine": 26,
+          "endOffset": 15,
+          "startLine": 25,
+          "startOffset": 0,
+        },
+        "transitions": [],
+        "type": "BUG",
+      }
+    }
   />
   <div
     className="issue-row-meta"
index 2001941d1aea4da42db9e21b2ca574ece7d0cf32..bd0fbf2952e38387e6d89dfc7c090401e5b0a169 100644 (file)
@@ -20,6 +20,7 @@
 import classNames from 'classnames';
 import { cloneDeep, debounce, groupBy } from 'lodash';
 import * as React from 'react';
+import { Location } from 'react-router-dom';
 import { dismissNotice } from '../../api/users';
 import { CurrentUserContextInterface } from '../../app/components/current-user/CurrentUserContext';
 import withCurrentUserContext from '../../app/components/current-user/withCurrentUserContext';
@@ -29,6 +30,7 @@ import { RuleDetails } from '../../types/types';
 import { NoticeType } from '../../types/users';
 import ScreenPositionHelper from '../common/ScreenPositionHelper';
 import BoxedTabs, { getTabId, getTabPanelId } from '../controls/BoxedTabs';
+import withLocation from '../hoc/withLocation';
 import MoreInfoRuleDescription from './MoreInfoRuleDescription';
 import RuleDescription from './RuleDescription';
 import './style.css';
@@ -39,6 +41,7 @@ interface RuleTabViewerProps extends CurrentUserContextInterface {
   ruleDescriptionContextKey?: string;
   codeTabContent?: React.ReactNode;
   scrollInTab?: boolean;
+  location: Location;
 }
 
 interface State {
@@ -83,6 +86,15 @@ export class RuleTabViewer extends React.PureComponent<RuleTabViewerProps, State
   componentDidMount() {
     this.setState((prevState) => 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<RuleTabViewerProps, State
   }
 }
 
-export default withCurrentUserContext(RuleTabViewer);
+export default withCurrentUserContext(withLocation(RuleTabViewer));
index 073717572169c3507a6d4577c37494eef4415740..5bc8e4b96be1282914be2a1b5e8126e093ee5e08 100644 (file)
@@ -27,7 +27,6 @@ import './styles.css';
 import WorkspaceComponentViewer from './WorkspaceComponentViewer';
 import WorkspaceNav from './WorkspaceNav';
 import WorkspacePortal from './WorkspacePortal';
-import WorkspaceRuleViewer from './WorkspaceRuleViewer';
 
 const WORKSPACE = 'sonarqube-workspace';
 interface State {
@@ -35,7 +34,7 @@ interface State {
   externalRulesRepoNames: Dict<string>;
   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 && (
-            <WorkspaceRuleViewer
-              height={actualHeight}
-              maximized={maximized}
-              onClose={this.handleRuleClose}
-              onCollapse={this.handleCollapse}
-              onLoad={this.handleRuleLoad}
-              onMaximize={this.handleMaximize}
-              onMinimize={this.handleMinimize}
-              onResize={this.handleResize}
-              rule={openRule}
-            />
-          )}
         </WorkspacePortal>
       </WorkspaceContext.Provider>
     );
index 34d4fc5193ac7954f9d688af52b942855263a9c4..bab413e61a50f230f088af99b31e543da74d069a 100644 (file)
  * 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 (
     <nav className="workspace-nav">
@@ -48,15 +43,6 @@ export default function WorkspaceNav(props: Props) {
             onOpen={props.onComponentOpen}
           />
         ))}
-
-        {rules.map((rule) => (
-          <WorkspaceNavRule
-            key={`rule-${rule.key}`}
-            onClose={props.onRuleClose}
-            onOpen={props.onRuleOpen}
-            rule={rule}
-          />
-        ))}
       </ul>
     </nav>
   );
index 358d8193b43be68c12318f02925055faf85572c8..f1dcd698eb1dd2cd63846724285ec922a9371f8c 100644 (file)
@@ -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<Workspace['state']>) {
index fe57669a76f7815c2d2ab7986ab83f240048334f..954c742a1b3e6810d20a373550afd9746a0245b1 100644 (file)
@@ -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<Props>) {
   const components = [
     { branchLike: undefined, key: 'foo' },
     { branchLike: undefined, key: 'bar' },
   ];
-  const rules = [{ key: 'qux' }];
   return shallow(
     <WorkspaceNav
       components={components}
       onComponentClose={jest.fn()}
       onComponentOpen={jest.fn()}
-      onRuleClose={jest.fn()}
-      onRuleOpen={jest.fn()}
       open={{}}
-      rules={rules}
       {...props}
     />
   );
index c6fc77fe21bd95a77292a07c6dee77e669d7da5f..e7ef86876090172b5b8c2497946324ab0a532ee0 100644 (file)
@@ -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={[]}
     />
     <withBranchStatusActions(WorkspaceComponentViewer)
       component={
@@ -81,54 +76,3 @@ exports[`should render correctly: open component 1`] = `
   </WorkspacePortal>
 </ContextProvider>
 `;
-
-exports[`should render correctly: open rule 1`] = `
-<ContextProvider
-  value={
-    {
-      "externalRulesRepoNames": {},
-      "openComponent": [Function],
-      "openRule": [Function],
-    }
-  }
->
-  <div
-    className="child"
-  />
-  <WorkspacePortal>
-    <WorkspaceNav
-      components={[]}
-      onComponentClose={[Function]}
-      onComponentOpen={[Function]}
-      onRuleClose={[Function]}
-      onRuleOpen={[Function]}
-      open={
-        {
-          "rule": "foo",
-        }
-      }
-      rules={
-        [
-          {
-            "key": "foo",
-          },
-        ]
-      }
-    />
-    <WorkspaceRuleViewer
-      height={300}
-      onClose={[Function]}
-      onCollapse={[Function]}
-      onLoad={[Function]}
-      onMaximize={[Function]}
-      onMinimize={[Function]}
-      onResize={[Function]}
-      rule={
-        {
-          "key": "foo",
-        }
-      }
-    />
-  </WorkspacePortal>
-</ContextProvider>
-`;
index 2712037667df9f039e0e75073ca9907924f73d4f..8a428d794ac1e8c27607aabaf58a33d9a56b858a 100644 (file)
@@ -18,49 +18,6 @@ exports[`should not render open component 1`] = `
       onClose={[MockFunction]}
       onOpen={[MockFunction]}
     />
-    <WorkspaceNavRule
-      key="rule-qux"
-      onClose={[MockFunction]}
-      onOpen={[MockFunction]}
-      rule={
-        {
-          "key": "qux",
-        }
-      }
-    />
-  </ul>
-</nav>
-`;
-
-exports[`should not render open rule 1`] = `
-<nav
-  className="workspace-nav"
->
-  <ul
-    className="workspace-nav-list"
-  >
-    <WorkspaceNavComponent
-      component={
-        {
-          "branchLike": undefined,
-          "key": "foo",
-        }
-      }
-      key="component-foo"
-      onClose={[MockFunction]}
-      onOpen={[MockFunction]}
-    />
-    <WorkspaceNavComponent
-      component={
-        {
-          "branchLike": undefined,
-          "key": "bar",
-        }
-      }
-      key="component-bar"
-      onClose={[MockFunction]}
-      onOpen={[MockFunction]}
-    />
   </ul>
 </nav>
 `;
@@ -94,16 +51,6 @@ exports[`should render 1`] = `
       onClose={[MockFunction]}
       onOpen={[MockFunction]}
     />
-    <WorkspaceNavRule
-      key="rule-qux"
-      onClose={[MockFunction]}
-      onOpen={[MockFunction]}
-      rule={
-        {
-          "key": "qux",
-        }
-      }
-    />
   </ul>
 </nav>
 `;
index 5b65951ed60de7a2b50d3f2495ac92b212609f0b..c4050243b4def3d7ca9806b18c9640f0900a9f6a 100644 (file)
@@ -37,11 +37,9 @@ export interface RuleDescriptor {
 export interface WorkspaceContextShape {
   externalRulesRepoNames: Dict<string>;
   openComponent: (component: ComponentDescriptor) => void;
-  openRule: (rule: RuleDescriptor) => void;
 }
 
 export const WorkspaceContext = createContext<WorkspaceContextShape>({
   externalRulesRepoNames: {},
   openComponent: () => {},
-  openRule: () => {},
 });