]> source.dussan.org Git - sonarqube.git/commitdiff
rewrite workspace in react (#3140)
authorStas Vilchik <stas.vilchik@sonarsource.com>
Wed, 14 Mar 2018 11:47:17 +0000 (12:47 +0100)
committerGitHub <noreply@github.com>
Wed, 14 Mar 2018 11:47:17 +0000 (12:47 +0100)
60 files changed:
server/sonar-web/src/main/js/app/components/GlobalContainer.tsx
server/sonar-web/src/main/js/apps/code/components/ComponentPin.tsx
server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsMeta.tsx
server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeader.tsx
server/sonar-web/src/main/js/components/SourceViewer/components/CoveragePopup.tsx
server/sonar-web/src/main/js/components/SourceViewer/components/DuplicationPopup.tsx
server/sonar-web/src/main/js/components/icons-components/CollapseIcon.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/icons-components/ExpandIcon.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/icons-components/MinimizeIcon.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/issue/components/IssueMessage.js
server/sonar-web/src/main/js/components/issue/components/__tests__/IssueMessage-test.js
server/sonar-web/src/main/js/components/workspace/Workspace.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/workspace/WorkspaceComponentTitle.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/workspace/WorkspaceComponentViewer.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/workspace/WorkspaceHeader.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/workspace/WorkspaceNav.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/workspace/WorkspaceNavComponent.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/workspace/WorkspaceNavItem.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/workspace/WorkspaceNavRule.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/workspace/WorkspacePortal.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/workspace/WorkspaceRuleDetails.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/workspace/WorkspaceRuleTitle.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/workspace/WorkspaceRuleViewer.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceComponentTitle-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceComponentViewer-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceHeader-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceNav-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceNavComponent-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceNavItem-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceNavRule-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/workspace/__tests__/WorkspacePortal-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceRuleDetails-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceRuleTitle-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceRuleViewer-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceComponentTitle-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceComponentViewer-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceHeader-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceNav-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceNavComponent-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceNavItem-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceNavRule-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceRuleDetails-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceRuleTitle-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceRuleViewer-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/components/workspace/context.ts [new file with mode: 0644]
server/sonar-web/src/main/js/components/workspace/main.js [deleted file]
server/sonar-web/src/main/js/components/workspace/models/item.js [deleted file]
server/sonar-web/src/main/js/components/workspace/models/items.js [deleted file]
server/sonar-web/src/main/js/components/workspace/styles.css
server/sonar-web/src/main/js/components/workspace/templates/workspace-item.hbs [deleted file]
server/sonar-web/src/main/js/components/workspace/templates/workspace-items.hbs [deleted file]
server/sonar-web/src/main/js/components/workspace/templates/workspace-rule.hbs [deleted file]
server/sonar-web/src/main/js/components/workspace/templates/workspace-viewer-header.hbs [deleted file]
server/sonar-web/src/main/js/components/workspace/templates/workspace-viewer.hbs [deleted file]
server/sonar-web/src/main/js/components/workspace/views/base-viewer-view.js [deleted file]
server/sonar-web/src/main/js/components/workspace/views/item-view.js [deleted file]
server/sonar-web/src/main/js/components/workspace/views/items-view.js [deleted file]
server/sonar-web/src/main/js/components/workspace/views/rule-view.js [deleted file]
server/sonar-web/src/main/js/components/workspace/views/viewer-header-view.js [deleted file]
server/sonar-web/src/main/js/components/workspace/views/viewer-view.js [deleted file]

index d4c6fb3a4268e3f5db4e5200dc512ff16a1e26f7..a89319b690af2b8e0767cea5a91627f40c761132 100644 (file)
@@ -22,6 +22,7 @@ import * as PropTypes from 'prop-types';
 import GlobalNav from './nav/global/GlobalNav';
 import GlobalFooterContainer from './GlobalFooterContainer';
 import GlobalMessagesContainer from './GlobalMessagesContainer';
+import Workspace from '../../components/workspace/Workspace';
 
 interface Props {
   children: React.ReactNode;
@@ -61,14 +62,16 @@ export default class GlobalContainer extends React.PureComponent<Props, State> {
       <div className="global-container">
         <div className="page-wrapper" id="container">
           <div className="page-container">
-            <GlobalNav
-              closeOnboardingTutorial={this.closeOnboardingTutorial}
-              isOnboardingTutorialOpen={this.state.isOnboardingTutorialOpen}
-              location={this.props.location}
-              openOnboardingTutorial={this.openOnboardingTutorial}
-            />
-            <GlobalMessagesContainer />
-            {this.props.children}
+            <Workspace>
+              <GlobalNav
+                closeOnboardingTutorial={this.closeOnboardingTutorial}
+                isOnboardingTutorialOpen={this.state.isOnboardingTutorialOpen}
+                location={this.props.location}
+                openOnboardingTutorial={this.openOnboardingTutorial}
+              />
+              <GlobalMessagesContainer />
+              {this.props.children}
+            </Workspace>
           </div>
         </div>
         <GlobalFooterContainer />
index 867f4bd476f1efb826e1bf19f5a6c060eb673de5..c9b3fb86af688a25627e12f92a6b2cf4efc8aed3 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as React from 'react';
+import * as PropTypes from 'prop-types';
 import { Component } from '../types';
 import { BranchLike } from '../../../app/types';
 import PinIcon from '../../../components/shared/pin-icon';
-import Workspace from '../../../components/workspace/main';
+import { WorkspaceContext } from '../../../components/workspace/context';
 import { translate } from '../../../helpers/l10n';
 
 interface Props {
@@ -29,19 +30,34 @@ interface Props {
   component: Component;
 }
 
-export default function ComponentPin({ branchLike, component }: Props) {
-  const handleClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
+export default class ComponentPin extends React.PureComponent<Props> {
+  // prettier-ignore
+  context!: { workspace: WorkspaceContext };
+
+  static contextTypes = {
+    workspace: PropTypes.object.isRequired
+  };
+
+  handleClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
     event.preventDefault();
-    Workspace.openComponent({ branchLike, key: component.key });
+    event.currentTarget.blur();
+    this.context.workspace.openComponent({
+      branchLike: this.props.branchLike,
+      key: this.props.component.key,
+      name: this.props.component.path,
+      qualifier: this.props.component.qualifier
+    });
   };
 
-  return (
-    <a
-      className="link-no-underline"
-      onClick={handleClick}
-      title={translate('component_viewer.open_in_workspace')}
-      href="#">
-      <PinIcon />
-    </a>
-  );
+  render() {
+    return (
+      <a
+        className="link-no-underline"
+        href="#"
+        onClick={this.handleClick}
+        title={translate('component_viewer.open_in_workspace')}>
+        <PinIcon />
+      </a>
+    );
+  }
 }
index 1a272610065449b99e92c803f982260252732561..97b99759784c5e3c597262b60821a920ee8b2e23 100644 (file)
@@ -37,6 +37,7 @@ import { Button } from '../../../components/ui/buttons';
 
 interface Props {
   canWrite: boolean | undefined;
+  hideSimilarRulesFilter?: boolean;
   onFilterChange: (changes: Partial<Query>) => void;
   onTagsChange: (tags: string[]) => void;
   organization: string | undefined;
@@ -228,7 +229,9 @@ export default class RuleDetailsMeta extends React.PureComponent<Props, State> {
               to={getRuleUrl(ruleDetails.key, this.props.organization)}>
               <LinkIcon />
             </Link>
-            <SimilarRulesFilter onFilterChange={this.props.onFilterChange} rule={ruleDetails} />
+            {!this.props.hideSimilarRulesFilter && (
+              <SimilarRulesFilter onFilterChange={this.props.onFilterChange} rule={ruleDetails} />
+            )}
           </div>
           <h3 className="page-title coding-rules-detail-header">
             <big>{ruleDetails.name}</big>
index 0ba3cd6ec40dc105e327ede8b1acc3afa67062eb..72ac6fa5bcd5ca82151c0d54f348af33a3fcf675 100644 (file)
 import { stringify } from 'querystring';
 import * as React from 'react';
 import { Link } from 'react-router';
+import * as PropTypes from 'prop-types';
 import MeasuresOverlay from './components/MeasuresOverlay';
 import { SourceViewerFile, BranchLike } from '../../app/types';
 import QualifierIcon from '../shared/QualifierIcon';
 import FavoriteContainer from '../controls/FavoriteContainer';
+import { WorkspaceContext } from '../workspace/context';
 import {
   getPathUrlAsString,
   getBranchLikeUrl,
@@ -46,6 +48,13 @@ interface State {
 }
 
 export default class SourceViewerHeader extends React.PureComponent<Props, State> {
+  // prettier-ignore
+  context!: { workspace: WorkspaceContext };
+
+  static contextTypes = {
+    workspace: PropTypes.object.isRequired
+  };
+
   state: State = { measuresOverlay: false };
 
   handleShowMeasuresClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
@@ -60,8 +69,7 @@ export default class SourceViewerHeader extends React.PureComponent<Props, State
   openInWorkspace = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
     event.preventDefault();
     const { key } = this.props.sourceViewerFile;
-    const Workspace = require('../workspace/main').default;
-    Workspace.openComponent({ key, branchLike: this.props.branchLike });
+    this.context.workspace.openComponent({ branchLike: this.props.branchLike, key });
   };
 
   render() {
index ef06788347e367521808ea8604469e8266faa19c..4d60126638e113b045286b86ebd44546e98dc0f1 100644 (file)
  */
 import * as React from 'react';
 import { groupBy } from 'lodash';
+import * as PropTypes from 'prop-types';
 import { getTests } from '../../../api/components';
 import { BranchLike, SourceLine, TestCase } from '../../../app/types';
 import BubblePopup from '../../common/BubblePopup';
 import TestStatusIcon from '../../shared/TestStatusIcon';
+import { WorkspaceContext } from '../../workspace/context';
 import { isSameBranchLike, getBranchLikeQuery } from '../../../helpers/branches';
 import { translate } from '../../../helpers/l10n';
 import { collapsePath } from '../../../helpers/path';
@@ -41,7 +43,14 @@ interface State {
 }
 
 export default class CoveragePopup extends React.PureComponent<Props, State> {
+  // prettier-ignore
+  context!: { workspace: WorkspaceContext };
   mounted = false;
+
+  static contextTypes = {
+    workspace: PropTypes.object.isRequired
+  };
+
   state: State = { loading: true, testCases: [] };
 
   componentDidMount() {
@@ -87,8 +96,9 @@ export default class CoveragePopup extends React.PureComponent<Props, State> {
     event.preventDefault();
     event.currentTarget.blur();
     const { key } = event.currentTarget.dataset;
-    const Workspace = require('../../workspace/main').default;
-    Workspace.openComponent({ key, branchLike: this.props.branchLike });
+    if (key) {
+      this.context.workspace.openComponent({ branchLike: this.props.branchLike, key });
+    }
     this.props.onClose();
   };
 
index d4b75a93b286f51a461abfa2a0008f70dac5cd39..445b750f807345bba699d20561e108b98d995ab6 100644 (file)
  */
 import * as React from 'react';
 import { Link } from 'react-router';
+import * as PropTypes from 'prop-types';
 import { groupBy, sortBy } from 'lodash';
 import { BranchLike, DuplicatedFile, DuplicationBlock, SourceViewerFile } from '../../../app/types';
 import BubblePopup from '../../common/BubblePopup';
 import QualifierIcon from '../../shared/QualifierIcon';
+import { WorkspaceContext } from '../../workspace/context';
 import { translate } from '../../../helpers/l10n';
 import { collapsedDirFromPath, fileFromPath } from '../../../helpers/path';
 import { getProjectUrl } from '../../../helpers/urls';
@@ -38,6 +40,13 @@ interface Props {
 }
 
 export default class DuplicationPopup extends React.PureComponent<Props> {
+  // prettier-ignore
+  context!: { workspace: WorkspaceContext };
+
+  static contextTypes = {
+    workspace: PropTypes.object.isRequired
+  };
+
   isDifferentComponent = (
     a: { project: string; subProject?: string },
     b: { project: string; subProject?: string }
@@ -48,9 +57,14 @@ export default class DuplicationPopup extends React.PureComponent<Props> {
   handleFileClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
     event.preventDefault();
     event.currentTarget.blur();
-    const Workspace = require('../../workspace/main').default;
     const { key, line } = event.currentTarget.dataset;
-    Workspace.openComponent({ key, line, branchLike: this.props.branchLike });
+    if (key) {
+      this.context.workspace.openComponent({
+        branchLike: this.props.branchLike,
+        key,
+        line: line ? Number(line) : undefined
+      });
+    }
     this.props.onClose();
   };
 
diff --git a/server/sonar-web/src/main/js/components/icons-components/CollapseIcon.tsx b/server/sonar-web/src/main/js/components/icons-components/CollapseIcon.tsx
new file mode 100644 (file)
index 0000000..f1070ac
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 { IconProps } from './types';
+
+export default function CollapseIcon({ className, fill = 'currentColor', size = 16 }: IconProps) {
+  return (
+    <svg
+      className={className}
+      height={size}
+      version="1.1"
+      viewBox="0 0 16 16"
+      width={size}
+      xmlSpace="preserve"
+      xmlnsXlink="http://www.w3.org/1999/xlink">
+      <path
+        d="M8 8.509v3.56c0 .138-.05.257-.151.357-.1.101-.22.151-.358.151a.489.489 0 0 1-.357-.15l-1.145-1.145-2.638 2.639a.251.251 0 0 1-.366 0l-.906-.906a.251.251 0 0 1 0-.366l2.639-2.638-1.144-1.145a.489.489 0 0 1-.151-.357c0-.138.05-.257.15-.358.101-.1.22-.151.358-.151h3.56c.138 0 .257.05.358.151.1.1.151.22.151.358zm6-5.34c0 .068-.026.129-.08.182l-2.638 2.638 1.144 1.145c.101.1.151.22.151.357 0 .138-.05.257-.15.358-.101.1-.22.151-.358.151h-3.56a.489.489 0 0 1-.358-.151A.489.489 0 0 1 8 7.491v-3.56c0-.138.05-.257.151-.357.1-.101.22-.151.358-.151.137 0 .257.05.357.15l1.145 1.145 2.638-2.639a.251.251 0 0 1 .366 0l.906.906c.053.053.079.114.079.183z"
+        style={{ fill }}
+      />
+    </svg>
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/icons-components/ExpandIcon.tsx b/server/sonar-web/src/main/js/components/icons-components/ExpandIcon.tsx
new file mode 100644 (file)
index 0000000..1105b78
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 { IconProps } from './types';
+
+export default function ExpandIcon({ className, fill = 'currentColor', size = 16 }: IconProps) {
+  return (
+    <svg
+      className={className}
+      height={size}
+      version="1.1"
+      viewBox="0 0 16 16"
+      width={size}
+      xmlSpace="preserve"
+      xmlnsXlink="http://www.w3.org/1999/xlink">
+      <path
+        d="M7.898 9.25a.247.247 0 0 1-.078.18l-2.593 2.593 1.125 1.125a.48.48 0 0 1 .148.352.48.48 0 0 1-.148.352A.48.48 0 0 1 6 14H2.5a.48.48 0 0 1-.352-.148A.48.48 0 0 1 2 13.5V10a.48.48 0 0 1 .148-.352A.48.48 0 0 1 2.5 9.5a.48.48 0 0 1 .352.148l1.125 1.125L6.57 8.18a.247.247 0 0 1 .36 0l.89.89a.247.247 0 0 1 .078.18zM14 2.5V6a.48.48 0 0 1-.148.352.48.48 0 0 1-.352.148.48.48 0 0 1-.352-.148l-1.125-1.125L9.43 7.82a.247.247 0 0 1-.36 0l-.89-.89a.247.247 0 0 1 0-.36l2.593-2.593-1.125-1.125A.48.48 0 0 1 9.5 2.5a.48.48 0 0 1 .148-.352A.48.48 0 0 1 10 2h3.5a.48.48 0 0 1 .352.148A.48.48 0 0 1 14 2.5z"
+        style={{ fill }}
+      />
+    </svg>
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/icons-components/MinimizeIcon.tsx b/server/sonar-web/src/main/js/components/icons-components/MinimizeIcon.tsx
new file mode 100644 (file)
index 0000000..4478239
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 { IconProps } from './types';
+
+export default function MinimizeIcon({ className, fill = 'currentColor', size = 16 }: IconProps) {
+  return (
+    <svg
+      className={className}
+      height={size}
+      version="1.1"
+      viewBox="0 0 16 16"
+      width={size}
+      xmlSpace="preserve"
+      xmlnsXlink="http://www.w3.org/1999/xlink">
+      <path
+        d="M14 12.1v1.267c0 .176-.08.325-.239.448a.918.918 0 0 1-.58.185H2.819a.918.918 0 0 1-.58-.185C2.08 13.692 2 13.543 2 13.367V12.1c0-.176.08-.326.239-.449a.918.918 0 0 1 .58-.185h10.363c.227 0 .42.062.58.185.158.123.238.273.238.449z"
+        style={{ fill }}
+      />
+    </svg>
+  );
+}
index df26092b98336d5ec68f3bdea90570306c1b0d78..515edd9b3babaf45979aa7d9b1a6bae2c500b868 100644 (file)
@@ -19,6 +19,7 @@
  */
 // @flow
 import React from 'react';
+import PropTypes from 'prop-types';
 import { translate } from '../../../helpers/l10n';
 
 export default class IssueMessage extends React.PureComponent {
@@ -27,13 +28,16 @@ export default class IssueMessage extends React.PureComponent {
     rule: string,
     organization: string
   };
-*/
+  */
+
+  static contextTypes = {
+    workspace: PropTypes.object.isRequired
+  };
 
   handleClick = (e /*: MouseEvent */) => {
     e.preventDefault();
     e.stopPropagation();
-    const Workspace = require('../../workspace/main').default;
-    Workspace.openRule({
+    this.context.workspace.openRule({
       key: this.props.rule,
       organization: this.props.organization
     });
index eafe943c67a235a0216c38d60694b74bf18d050d..785bc2a21f1ff652211f38bd5999727acc14d969 100644 (file)
@@ -27,7 +27,8 @@ it('should render with the message and a link to open the rule', () => {
       rule="javascript:S1067"
       message="Reduce the number of conditional operators (4) used in the expression"
       organization="myorg"
-    />
+    />,
+    { context: { workspace: {} } }
   );
   expect(element).toMatchSnapshot();
 });
diff --git a/server/sonar-web/src/main/js/components/workspace/Workspace.tsx b/server/sonar-web/src/main/js/components/workspace/Workspace.tsx
new file mode 100644 (file)
index 0000000..a5dcce0
--- /dev/null
@@ -0,0 +1,235 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 * as PropTypes from 'prop-types';
+import { omit, uniqBy } from 'lodash';
+import { WorkspaceContext, ComponentDescriptor, RuleDescriptor } from './context';
+import WorkspaceNav from './WorkspaceNav';
+import WorkspacePortal from './WorkspacePortal';
+import { lazyLoad } from '../lazyLoad';
+import './styles.css';
+
+const WorkspaceRuleViewer = lazyLoad(() => import('./WorkspaceRuleViewer'));
+const WorkspaceComponentViewer = lazyLoad(() => import('./WorkspaceComponentViewer'));
+
+interface State {
+  components: ComponentDescriptor[];
+  height: number;
+  maximized?: boolean;
+  open: { component?: string; rule?: string };
+  rules: RuleDescriptor[];
+}
+
+const MIN_HEIGHT = 0.05;
+const MAX_HEIGHT = 0.85;
+const INITIAL_HEIGHT = 300;
+
+const STORAGE_KEY = 'sonarqube-workspace';
+const TYPE_KEY = '__type__';
+
+export default class Workspace extends React.PureComponent<{}, State> {
+  mounted = false;
+
+  static childContextTypes = {
+    workspace: PropTypes.object
+  };
+
+  constructor(props: {}) {
+    super(props);
+    this.state = { height: INITIAL_HEIGHT, open: {}, ...this.loadWorkspace() };
+  }
+
+  getChildContext = (): { workspace: WorkspaceContext } => {
+    return { workspace: { openComponent: this.openComponent, openRule: this.openRule } };
+  };
+
+  componentDidMount() {
+    this.mounted = true;
+  }
+
+  componentDidUpdate(_: {}, prevState: State) {
+    if (prevState.components !== this.state.components || prevState.rules !== this.state.rules) {
+      this.saveWorkspace();
+    }
+  }
+
+  componentWillUnmount() {
+    this.mounted = false;
+  }
+
+  loadWorkspace = () => {
+    try {
+      const data: any[] = JSON.parse(window.localStorage.getItem(STORAGE_KEY) || '');
+      const components: ComponentDescriptor[] = data.filter(x => x[TYPE_KEY] === 'component');
+      const rules: RuleDescriptor[] = data.filter(x => x[TYPE_KEY] === 'rule');
+      return { components, rules };
+    } catch {
+      // fail silently
+      return { components: [], rules: [] };
+    }
+  };
+
+  saveWorkspace = () => {
+    const data = [
+      // do not save line number, next time the file is open, it should be open on the first line
+      ...this.state.components.map(x => omit({ ...x, [TYPE_KEY]: 'component' }, 'line')),
+      ...this.state.rules.map(x => ({ ...x, [TYPE_KEY]: 'rule' }))
+    ];
+    try {
+      window.localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
+    } catch {
+      // fail silently
+    }
+  };
+
+  openComponent = (component: ComponentDescriptor) => {
+    this.setState((state: State): Partial<State> => ({
+      components: uniqBy([...state.components, component], component => component.key),
+      open: { component: component.key }
+    }));
+  };
+
+  reopenComponent = (componentKey: string) => {
+    this.setState({ open: { component: componentKey } });
+  };
+
+  openRule = (rule: RuleDescriptor) => {
+    this.setState((state: State): Partial<State> => ({
+      open: { rule: rule.key },
+      rules: uniqBy([...state.rules, rule], rule => rule.key)
+    }));
+  };
+
+  reopenRule = (ruleKey: string) => {
+    this.setState({ open: { rule: ruleKey } });
+  };
+
+  closeComponent = (componentKey: string) => {
+    this.setState((state: State): Partial<State> => ({
+      components: state.components.filter(x => x.key !== componentKey),
+      open: {
+        ...state.open,
+        component: state.open.component === componentKey ? undefined : state.open.component
+      }
+    }));
+  };
+
+  closeRule = (ruleKey: string) => {
+    this.setState((state: State): Partial<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;
+      this.setState((state: State): Partial<State> => ({
+        components: state.components.map(
+          component => (component.key === key ? { ...component, name, qualifier } : component)
+        )
+      }));
+    }
+  };
+
+  handleRuleLoad = (details: { key: string; name: string }) => {
+    if (this.mounted) {
+      const { key, name } = details;
+      this.setState((state: State): Partial<State> => ({
+        rules: state.rules.map(rule => (rule.key === key ? { ...rule, name } : rule))
+      }));
+    }
+  };
+
+  collapse = () => {
+    this.setState({ open: {} });
+  };
+
+  maximize = () => {
+    this.setState({ maximized: true });
+  };
+
+  minimize = () => {
+    this.setState({ maximized: false });
+  };
+
+  resize = (deltaY: number) => {
+    const minHeight = window.innerHeight * MIN_HEIGHT;
+    const maxHeight = window.innerHeight * MAX_HEIGHT;
+    this.setState((state: State): Partial<State> => ({
+      height: Math.min(maxHeight, Math.max(minHeight, state.height - deltaY))
+    }));
+  };
+
+  render() {
+    const { components, 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 height = this.state.maximized ? window.innerHeight * MAX_HEIGHT : this.state.height;
+
+    return (
+      <>
+        {this.props.children}
+        <WorkspacePortal>
+          <WorkspaceNav
+            components={components}
+            onComponentClose={this.closeComponent}
+            onComponentOpen={this.reopenComponent}
+            onRuleClose={this.closeRule}
+            onRuleOpen={this.reopenRule}
+            open={this.state.open}
+            rules={this.state.rules}
+          />
+          {openComponent && (
+            <WorkspaceComponentViewer
+              component={openComponent}
+              height={height}
+              maximized={this.state.maximized}
+              onClose={this.closeComponent}
+              onCollapse={this.collapse}
+              onLoad={this.handleComponentLoad}
+              onMaximize={this.maximize}
+              onMinimize={this.minimize}
+              onResize={this.resize}
+            />
+          )}
+          {openRule && (
+            <WorkspaceRuleViewer
+              height={height}
+              maximized={this.state.maximized}
+              onClose={this.closeRule}
+              onCollapse={this.collapse}
+              onLoad={this.handleRuleLoad}
+              onMaximize={this.maximize}
+              onMinimize={this.minimize}
+              onResize={this.resize}
+              rule={openRule}
+            />
+          )}
+        </WorkspacePortal>
+      </>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/components/workspace/WorkspaceComponentTitle.tsx b/server/sonar-web/src/main/js/components/workspace/WorkspaceComponentTitle.tsx
new file mode 100644 (file)
index 0000000..8679e16
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 { ComponentDescriptor } from './context';
+import QualifierIcon from '../shared/QualifierIcon';
+import { collapsePath } from '../../helpers/path';
+
+interface Props {
+  component: ComponentDescriptor;
+  limited?: boolean;
+}
+
+export default function WorkspaceComponentTitle({ component, limited }: Props) {
+  const { name = '—' } = component;
+  return (
+    <>
+      {component.qualifier && (
+        <QualifierIcon className="little-spacer-right" qualifier={component.qualifier} />
+      )}
+      {limited ? collapsePath(name, 15) : name}
+    </>
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/workspace/WorkspaceComponentViewer.tsx b/server/sonar-web/src/main/js/components/workspace/WorkspaceComponentViewer.tsx
new file mode 100644 (file)
index 0000000..eb2140e
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 { ComponentDescriptor } from './context';
+import WorkspaceHeader, { Props as WorkspaceHeaderProps } from './WorkspaceHeader';
+import WorkspaceComponentTitle from './WorkspaceComponentTitle';
+import SourceViewer from '../SourceViewer/SourceViewer';
+import { SourceViewerFile, Omit } from '../../app/types';
+import { scrollToElement } from '../../helpers/scrolling';
+
+export interface Props extends Omit<WorkspaceHeaderProps, 'children' | 'onClose'> {
+  component: ComponentDescriptor;
+  height: number;
+  onClose: (componentKey: string) => void;
+  onLoad: (details: { key: string; name: string; qualifier: string }) => void;
+}
+
+export default class WorkspaceComponentViewer extends React.PureComponent<Props> {
+  container?: HTMLElement | null;
+
+  componentDidMount() {
+    document.documentElement.classList.add('with-workspace');
+  }
+
+  componentWillUnmount() {
+    document.documentElement.classList.remove('with-workspace');
+  }
+
+  handleClose = () => {
+    this.props.onClose(this.props.component.key);
+  };
+
+  handleLoaded = (component: SourceViewerFile) => {
+    this.props.onLoad({
+      key: this.props.component.key,
+      name: component.path,
+      qualifier: component.q
+    });
+
+    if (this.container && this.props.component.line) {
+      const row = this.container.querySelector(
+        `.source-line[data-line-number="${this.props.component.line}"]`
+      );
+      if (row) {
+        scrollToElement(row, {
+          smooth: false,
+          parent: this.container,
+          topOffset: 50,
+          bottomOffset: 50
+        });
+      }
+    }
+  };
+
+  render() {
+    const { component } = this.props;
+
+    return (
+      <div className="workspace-viewer">
+        <WorkspaceHeader
+          maximized={this.props.maximized}
+          onClose={this.handleClose}
+          onCollapse={this.props.onCollapse}
+          onMaximize={this.props.onMaximize}
+          onMinimize={this.props.onMinimize}
+          onResize={this.props.onResize}>
+          <WorkspaceComponentTitle component={component} />
+        </WorkspaceHeader>
+
+        <div
+          className="workspace-viewer-container"
+          ref={node => (this.container = node)}
+          style={{ height: this.props.height }}>
+          <SourceViewer
+            aroundLine={component.line}
+            branchLike={component.branchLike}
+            component={component.key}
+            highlightedLine={component.line}
+            onLoaded={this.handleLoaded}
+          />
+        </div>
+      </div>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/components/workspace/WorkspaceHeader.tsx b/server/sonar-web/src/main/js/components/workspace/WorkspaceHeader.tsx
new file mode 100644 (file)
index 0000000..011991d
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 { DraggableCore, DraggableData } from 'react-draggable';
+import { translate } from '../../helpers/l10n';
+import { ButtonIcon } from '../ui/buttons';
+import ClearIcon from '../icons-components/ClearIcon';
+import CollapseIcon from '../icons-components/CollapseIcon';
+import ExpandIcon from '../icons-components/ExpandIcon';
+import MinimizeIcon from '../icons-components/MinimizeIcon';
+import { IconProps } from '../icons-components/types';
+
+export interface Props {
+  children: React.ReactNode;
+  maximized?: boolean;
+  onClose: () => void;
+  onCollapse: () => void;
+  onMaximize: () => void;
+  onMinimize: () => void;
+  onResize: (deltaY: number) => void;
+}
+
+export default class WorkspaceHeader extends React.PureComponent<Props> {
+  handleDrag = (_event: MouseEvent, data: DraggableData) => {
+    this.props.onResize(data.deltaY);
+  };
+
+  render() {
+    return (
+      <header className="workspace-viewer-header">
+        <h6 className="workspace-viewer-name">{this.props.children}</h6>
+
+        <DraggableCore offsetParent={document.body} onDrag={this.handleDrag}>
+          <div className="workspace-viewer-resize js-resize" />
+        </DraggableCore>
+
+        <div className="workspace-viewer-actions">
+          <WorkspaceHeaderButton
+            icon={MinimizeIcon}
+            onClick={this.props.onCollapse}
+            tooltip="workspace.minimize"
+          />
+
+          {this.props.maximized ? (
+            <WorkspaceHeaderButton
+              icon={CollapseIcon}
+              onClick={this.props.onMinimize}
+              tooltip="workspace.normal_size"
+            />
+          ) : (
+            <WorkspaceHeaderButton
+              icon={ExpandIcon}
+              onClick={this.props.onMaximize}
+              tooltip="workspace.full_window"
+            />
+          )}
+
+          <WorkspaceHeaderButton
+            icon={ClearIcon}
+            onClick={this.props.onClose}
+            tooltip="workspace.close"
+          />
+        </div>
+      </header>
+    );
+  }
+}
+
+interface WorkspaceHeaderButtonProps {
+  icon: React.SFC<IconProps>;
+  onClick: () => void;
+  tooltip: string;
+}
+
+function WorkspaceHeaderButton({ icon: Icon, onClick, tooltip }: WorkspaceHeaderButtonProps) {
+  return (
+    <ButtonIcon
+      className="workspace-header-icon"
+      color="#fff"
+      onClick={onClick}
+      tooltip={translate(tooltip)}>
+      <Icon fill={undefined} />
+    </ButtonIcon>
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/workspace/WorkspaceNav.tsx b/server/sonar-web/src/main/js/components/workspace/WorkspaceNav.tsx
new file mode 100644 (file)
index 0000000..52471f7
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 { ComponentDescriptor, RuleDescriptor } 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">
+      <ul className="workspace-nav-list">
+        {components.map(component => (
+          <WorkspaceNavComponent
+            component={component}
+            key={`component-${component.key}`}
+            onClose={props.onComponentClose}
+            onOpen={props.onComponentOpen}
+          />
+        ))}
+
+        {rules.map(rule => (
+          <WorkspaceNavRule
+            key={`rule-${rule.key}`}
+            onClose={props.onRuleClose}
+            onOpen={props.onRuleOpen}
+            rule={rule}
+          />
+        ))}
+      </ul>
+    </nav>
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/workspace/WorkspaceNavComponent.tsx b/server/sonar-web/src/main/js/components/workspace/WorkspaceNavComponent.tsx
new file mode 100644 (file)
index 0000000..15a5c11
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 { ComponentDescriptor } from './context';
+import WorkspaceComponentTitle from './WorkspaceComponentTitle';
+import WorkspaceNavItem from './WorkspaceNavItem';
+
+export interface Props {
+  component: ComponentDescriptor;
+  onClose: (componentKey: string) => void;
+  onOpen: (componentKey: string) => void;
+}
+
+export default class WorkspaceNavComponent extends React.PureComponent<Props> {
+  handleClose = () => {
+    this.props.onClose(this.props.component.key);
+  };
+
+  handleOpen = () => {
+    this.props.onOpen(this.props.component.key);
+  };
+
+  render() {
+    return (
+      <WorkspaceNavItem onClose={this.handleClose} onOpen={this.handleOpen}>
+        <WorkspaceComponentTitle component={this.props.component} limited={true} />
+      </WorkspaceNavItem>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/components/workspace/WorkspaceNavItem.tsx b/server/sonar-web/src/main/js/components/workspace/WorkspaceNavItem.tsx
new file mode 100644 (file)
index 0000000..6f29f4c
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 ClearIcon from '../icons-components/ClearIcon';
+import { ButtonIcon } from '../ui/buttons';
+
+export interface Props {
+  children: React.ReactNode;
+  onClose: () => void;
+  onOpen: () => void;
+}
+
+export default class WorkspaceNavItem extends React.PureComponent<Props> {
+  handleNameClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
+    event.preventDefault();
+    event.currentTarget.blur();
+    this.props.onOpen();
+  };
+
+  render() {
+    return (
+      <li className="workspace-nav-item">
+        <a className="workspace-nav-item-link" href="#" onClick={this.handleNameClick}>
+          {this.props.children}
+        </a>
+        <ButtonIcon
+          className="js-close workspace-nav-item-close workspace-header-icon button-small little-spacer-left"
+          color="#fff"
+          onClick={this.props.onClose}>
+          <ClearIcon fill={undefined} size={12} />
+        </ButtonIcon>
+      </li>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/components/workspace/WorkspaceNavRule.tsx b/server/sonar-web/src/main/js/components/workspace/WorkspaceNavRule.tsx
new file mode 100644 (file)
index 0000000..1dea871
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 { RuleDescriptor } from './context';
+import WorkspaceNavItem from './WorkspaceNavItem';
+import WorkspaceRuleTitle from './WorkspaceRuleTitle';
+
+export interface Props {
+  rule: RuleDescriptor;
+  onClose: (ruleKey: string) => void;
+  onOpen: (ruleKey: string) => void;
+}
+
+export default class WorkspaceNavRule extends React.PureComponent<Props> {
+  handleClose = () => {
+    this.props.onClose(this.props.rule.key);
+  };
+
+  handleOpen = () => {
+    this.props.onOpen(this.props.rule.key);
+  };
+
+  render() {
+    return (
+      <WorkspaceNavItem onClose={this.handleClose} onOpen={this.handleOpen}>
+        <WorkspaceRuleTitle limited={true} rule={this.props.rule} />
+      </WorkspaceNavItem>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/components/workspace/WorkspacePortal.tsx b/server/sonar-web/src/main/js/components/workspace/WorkspacePortal.tsx
new file mode 100644 (file)
index 0000000..63af9fe
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 { createPortal } from 'react-dom';
+
+export default class WorkspacePortal extends React.PureComponent {
+  el: HTMLElement;
+
+  constructor(props: {}) {
+    super(props);
+    this.el = document.createElement('div');
+    this.el.classList.add('workspace');
+  }
+
+  componentDidMount() {
+    document.body.appendChild(this.el);
+  }
+
+  componentWillUnmount() {
+    document.body.removeChild(this.el);
+  }
+
+  render() {
+    return createPortal(this.props.children, this.el);
+  }
+}
diff --git a/server/sonar-web/src/main/js/components/workspace/WorkspaceRuleDetails.tsx b/server/sonar-web/src/main/js/components/workspace/WorkspaceRuleDetails.tsx
new file mode 100644 (file)
index 0000000..e154a14
--- /dev/null
@@ -0,0 +1,114 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 { keyBy } from 'lodash';
+import { getRuleDetails, getRulesApp } from '../../api/rules';
+import { RuleDetails } from '../../app/types';
+import DeferredSpinner from '../common/DeferredSpinner';
+import RuleDetailsMeta from '../../apps/coding-rules/components/RuleDetailsMeta';
+import RuleDetailsDescription from '../../apps/coding-rules/components/RuleDetailsDescription';
+import '../../apps/coding-rules/styles.css';
+
+interface Props {
+  onLoad: (details: { name: string }) => void;
+  organization: string | undefined;
+  ruleKey: string;
+}
+
+interface State {
+  loading: boolean;
+  referencedRepositories: { [repository: string]: { key: string; language: string; name: string } };
+  ruleDetails?: RuleDetails;
+}
+
+export default class WorkspaceRuleDetails extends React.PureComponent<Props, State> {
+  mounted = false;
+  state: State = { loading: true, referencedRepositories: {} };
+
+  componentDidMount() {
+    this.mounted = true;
+    this.fetchRuleDetails();
+  }
+
+  componentDidUpdate(prevProps: Props) {
+    if (
+      prevProps.ruleKey !== this.props.ruleKey ||
+      prevProps.organization !== this.props.organization
+    ) {
+      this.fetchRuleDetails();
+    }
+  }
+
+  componentWillUnmount() {
+    this.mounted = false;
+  }
+
+  fetchRuleDetails = () => {
+    this.setState({ loading: true });
+    Promise.all([
+      getRulesApp({ organization: this.props.organization }),
+      getRuleDetails({ key: this.props.ruleKey, organization: this.props.organization })
+    ]).then(
+      ([{ repositories }, { rule }]) => {
+        if (this.mounted) {
+          this.setState({
+            loading: false,
+            referencedRepositories: keyBy(repositories, 'key'),
+            ruleDetails: rule
+          });
+          this.props.onLoad({ name: rule.name });
+        }
+      },
+      () => {
+        if (this.mounted) {
+          this.setState({ loading: false });
+        }
+      }
+    );
+  };
+
+  noOp = () => {};
+
+  render() {
+    return (
+      <DeferredSpinner loading={this.state.loading}>
+        {this.state.ruleDetails && (
+          <>
+            <RuleDetailsMeta
+              canWrite={false}
+              hideSimilarRulesFilter={true}
+              onFilterChange={this.noOp}
+              onTagsChange={this.noOp}
+              organization={this.props.organization}
+              referencedRepositories={this.state.referencedRepositories}
+              ruleDetails={this.state.ruleDetails}
+            />
+            <RuleDetailsDescription
+              canWrite={false}
+              onChange={this.noOp}
+              organization={this.props.organization}
+              ruleDetails={this.state.ruleDetails}
+            />
+          </>
+        )}
+      </DeferredSpinner>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/components/workspace/WorkspaceRuleTitle.tsx b/server/sonar-web/src/main/js/components/workspace/WorkspaceRuleTitle.tsx
new file mode 100644 (file)
index 0000000..ac7387c
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 { RuleDescriptor } from './context';
+
+interface Props {
+  limited?: boolean;
+  rule: RuleDescriptor;
+}
+
+export default function WorkspaceRuleTitle({ limited, rule }: Props) {
+  const { name = '—' } = rule;
+  return (
+    <>
+      <i className="icon-workspace-doc little-spacer-right" />
+      {limited ? <span className="text-limited">{name}</span> : name}
+    </>
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/workspace/WorkspaceRuleViewer.tsx b/server/sonar-web/src/main/js/components/workspace/WorkspaceRuleViewer.tsx
new file mode 100644 (file)
index 0000000..513f2ca
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 { RuleDescriptor } from './context';
+import WorkspaceHeader, { Props as WorkspaceHeaderProps } from './WorkspaceHeader';
+import WorkspaceRuleDetails from './WorkspaceRuleDetails';
+import WorkspaceRuleTitle from './WorkspaceRuleTitle';
+import { Omit } from '../../app/types';
+
+export interface Props extends Omit<WorkspaceHeaderProps, 'children' | 'onClose'> {
+  rule: RuleDescriptor;
+  height: number;
+  onClose: (componentKey: string) => void;
+  onLoad: (details: { key: string; name: string }) => void;
+}
+
+export default class WorkspaceRuleViewer extends React.PureComponent<Props> {
+  handleClose = () => {
+    this.props.onClose(this.props.rule.key);
+  };
+
+  handleLoaded = (rule: { name: string }) => {
+    this.props.onLoad({ key: this.props.rule.key, name: rule.name });
+  };
+
+  render() {
+    const { rule } = this.props;
+
+    return (
+      <div className="workspace-viewer">
+        <WorkspaceHeader
+          maximized={this.props.maximized}
+          onClose={this.handleClose}
+          onCollapse={this.props.onCollapse}
+          onMaximize={this.props.onMaximize}
+          onMinimize={this.props.onMinimize}
+          onResize={this.props.onResize}>
+          <WorkspaceRuleTitle rule={rule} />
+        </WorkspaceHeader>
+
+        <div className="workspace-viewer-container" style={{ height: this.props.height }}>
+          <WorkspaceRuleDetails
+            onLoad={this.handleLoaded}
+            organization={rule.organization}
+            ruleKey={rule.key}
+          />
+        </div>
+      </div>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceComponentTitle-test.tsx b/server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceComponentTitle-test.tsx
new file mode 100644 (file)
index 0000000..2db4089
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 { shallow } from 'enzyme';
+import WorkspaceComponentTitle from '../WorkspaceComponentTitle';
+
+it('should render component', () => {
+  const component = { branchLike: undefined, key: 'foo' };
+  expect(shallow(<WorkspaceComponentTitle component={component} />)).toMatchSnapshot();
+});
+
+it('should render loaded component', () => {
+  const component = { branchLike: undefined, key: 'foo', name: 'src/foo.js', qualifier: 'FIL' };
+  expect(shallow(<WorkspaceComponentTitle component={component} />)).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceComponentViewer-test.tsx b/server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceComponentViewer-test.tsx
new file mode 100644 (file)
index 0000000..b3cab1c
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 { shallow } from 'enzyme';
+import WorkspaceComponentViewer, { Props } from '../WorkspaceComponentViewer';
+
+it('should render', () => {
+  expect(shallowRender()).toMatchSnapshot();
+});
+
+it('should close', () => {
+  const onClose = jest.fn();
+  const wrapper = shallowRender({ onClose });
+  wrapper.find('WorkspaceHeader').prop<Function>('onClose')();
+  expect(onClose).toBeCalledWith('foo');
+});
+
+it('should call back after load', () => {
+  const onLoad = jest.fn();
+  const wrapper = shallowRender({ onLoad });
+  wrapper.find('[onLoaded]').prop<Function>('onLoaded')({
+    key: 'foo',
+    path: 'src/foo.js',
+    q: 'FIL'
+  });
+  expect(onLoad).toBeCalledWith({ key: 'foo', name: 'src/foo.js', qualifier: 'FIL' });
+});
+
+function shallowRender(props?: Partial<Props>) {
+  const component = { branchLike: undefined, key: 'foo' };
+  return shallow(
+    <WorkspaceComponentViewer
+      component={component}
+      height={300}
+      onClose={jest.fn()}
+      onCollapse={jest.fn()}
+      onLoad={jest.fn()}
+      onMaximize={jest.fn()}
+      onMinimize={jest.fn()}
+      onResize={jest.fn()}
+      {...props}
+    />
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceHeader-test.tsx b/server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceHeader-test.tsx
new file mode 100644 (file)
index 0000000..a8be1a8
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 { shallow } from 'enzyme';
+import WorkspaceHeader, { Props } from '../WorkspaceHeader';
+
+it('should render', () => {
+  expect(shallowRender()).toMatchSnapshot();
+});
+
+it('should resize', () => {
+  const onResize = jest.fn();
+  const wrapper = shallowRender({ onResize });
+  wrapper.find('DraggableCore').prop<Function>('onDrag')({}, { deltaY: 15 });
+  expect(onResize).toBeCalledWith(15);
+});
+
+function shallowRender(props?: Partial<Props>) {
+  return shallow(
+    <WorkspaceHeader
+      onClose={jest.fn()}
+      onCollapse={jest.fn()}
+      onMaximize={jest.fn()}
+      onMinimize={jest.fn()}
+      onResize={jest.fn()}
+      {...props}>
+      <div id="workspace-header-children" />
+    </WorkspaceHeader>
+  );
+}
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
new file mode 100644 (file)
index 0000000..487e94a
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 { shallow } from 'enzyme';
+import WorkspaceNav, { Props } from '../WorkspaceNav';
+
+it('should render', () => {
+  expect(shallowRender()).toMatchSnapshot();
+});
+
+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', organization: 'org' }];
+  return shallow(
+    <WorkspaceNav
+      components={components}
+      onComponentClose={jest.fn()}
+      onComponentOpen={jest.fn()}
+      onRuleClose={jest.fn()}
+      onRuleOpen={jest.fn()}
+      open={{}}
+      rules={rules}
+      {...props}
+    />
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceNavComponent-test.tsx b/server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceNavComponent-test.tsx
new file mode 100644 (file)
index 0000000..e14ddd0
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 { shallow } from 'enzyme';
+import WorkspaceNavComponent, { Props } from '../WorkspaceNavComponent';
+
+it('should render', () => {
+  expect(shallowRender()).toMatchSnapshot();
+});
+
+it('should close', () => {
+  const onClose = jest.fn();
+  const wrapper = shallowRender({ onClose });
+  wrapper.find('WorkspaceNavItem').prop<Function>('onClose')();
+  expect(onClose).toBeCalledWith('foo');
+});
+
+it('should open', () => {
+  const onOpen = jest.fn();
+  const wrapper = shallowRender({ onOpen });
+  wrapper.find('WorkspaceNavItem').prop<Function>('onOpen')();
+  expect(onOpen).toBeCalledWith('foo');
+});
+
+function shallowRender(props?: Partial<Props>) {
+  const component = { branchLike: undefined, key: 'foo' };
+  return shallow(
+    <WorkspaceNavComponent
+      component={component}
+      onClose={jest.fn()}
+      onOpen={jest.fn()}
+      {...props}
+    />
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceNavItem-test.tsx b/server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceNavItem-test.tsx
new file mode 100644 (file)
index 0000000..3f81b90
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 { shallow } from 'enzyme';
+import WorkspaceNavItem, { Props } from '../WorkspaceNavItem';
+import { click } from '../../../helpers/testUtils';
+
+it('should render', () => {
+  expect(shallowRender()).toMatchSnapshot();
+});
+
+it('should close', () => {
+  const onClose = jest.fn();
+  const wrapper = shallowRender({ onClose });
+  click(wrapper.find('ButtonIcon'));
+  expect(onClose).toBeCalled();
+});
+
+it('should open', () => {
+  const onOpen = jest.fn();
+  const wrapper = shallowRender({ onOpen });
+  click(wrapper.find('.workspace-nav-item-link'));
+  expect(onOpen).toBeCalled();
+});
+
+function shallowRender(props?: Partial<Props>) {
+  return shallow(
+    <WorkspaceNavItem onClose={jest.fn()} onOpen={jest.fn()} {...props}>
+      <div id="workspace-nav-item" />
+    </WorkspaceNavItem>
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceNavRule-test.tsx b/server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceNavRule-test.tsx
new file mode 100644 (file)
index 0000000..15be679
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 { shallow } from 'enzyme';
+import WorkspaceNavRule, { Props } from '../WorkspaceNavRule';
+
+it('should render', () => {
+  expect(shallowRender()).toMatchSnapshot();
+});
+
+it('should close', () => {
+  const onClose = jest.fn();
+  const wrapper = shallowRender({ onClose });
+  wrapper.find('WorkspaceNavItem').prop<Function>('onClose')();
+  expect(onClose).toBeCalledWith('foo');
+});
+
+it('should open', () => {
+  const onOpen = jest.fn();
+  const wrapper = shallowRender({ onOpen });
+  wrapper.find('WorkspaceNavItem').prop<Function>('onOpen')();
+  expect(onOpen).toBeCalledWith('foo');
+});
+
+function shallowRender(props?: Partial<Props>) {
+  const rule = { key: 'foo', organization: 'org' };
+  return shallow(
+    <WorkspaceNavRule onClose={jest.fn()} onOpen={jest.fn()} rule={rule} {...props} />
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/workspace/__tests__/WorkspacePortal-test.tsx b/server/sonar-web/src/main/js/components/workspace/__tests__/WorkspacePortal-test.tsx
new file mode 100644 (file)
index 0000000..35b0909
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 { shallow } from 'enzyme';
+import WorkspacePortal from '../WorkspacePortal';
+
+it('should create portal element', () => {
+  const wrapper = shallow(<WorkspacePortal />);
+  const { el } = wrapper.instance() as WorkspacePortal;
+  expect(el.tagName.toLowerCase()).toBe('div');
+  expect(el.className).toBe('workspace');
+});
diff --git a/server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceRuleDetails-test.tsx b/server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceRuleDetails-test.tsx
new file mode 100644 (file)
index 0000000..e1b5f68
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 { shallow } from 'enzyme';
+import WorkspaceRuleDetails from '../WorkspaceRuleDetails';
+import { waitAndUpdate } from '../../../helpers/testUtils';
+
+jest.mock('../../../api/rules', () => ({
+  getRulesApp: jest.fn(() =>
+    Promise.resolve({ repositories: [{ key: 'repo', language: 'xoo', name: 'Xoo Repository' }] })
+  ),
+  getRuleDetails: jest.fn(() => Promise.resolve({ rule: { key: 'foo', name: 'Foo' } }))
+}));
+
+it('should render', async () => {
+  const wrapper = shallow(
+    <WorkspaceRuleDetails onLoad={jest.fn()} organization="org" ruleKey="foo" />
+  );
+  expect(wrapper).toMatchSnapshot();
+
+  await waitAndUpdate(wrapper);
+  expect(wrapper).toMatchSnapshot();
+});
+
+it('should call back on load', async () => {
+  const onLoad = jest.fn();
+  const wrapper = shallow(
+    <WorkspaceRuleDetails onLoad={onLoad} organization="org" ruleKey="foo" />
+  );
+  await waitAndUpdate(wrapper);
+  expect(onLoad).toBeCalledWith({ name: 'Foo' });
+});
diff --git a/server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceRuleTitle-test.tsx b/server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceRuleTitle-test.tsx
new file mode 100644 (file)
index 0000000..b12796f
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 { shallow } from 'enzyme';
+import WorkspaceRuleTitle from '../WorkspaceRuleTitle';
+
+it('should render rule', () => {
+  const rule = { key: 'foo', organization: 'org' };
+  expect(shallow(<WorkspaceRuleTitle rule={rule} />)).toMatchSnapshot();
+});
+
+it('should render loaded rule', () => {
+  const rule = { key: 'foo', name: 'Foo', organization: 'org' };
+  expect(shallow(<WorkspaceRuleTitle rule={rule} />)).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceRuleViewer-test.tsx b/server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceRuleViewer-test.tsx
new file mode 100644 (file)
index 0000000..978b877
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 { shallow } from 'enzyme';
+import WorkspaceRuleViewer, { Props } from '../WorkspaceRuleViewer';
+
+it('should render', () => {
+  expect(shallowRender()).toMatchSnapshot();
+});
+
+it('should close', () => {
+  const onClose = jest.fn();
+  const wrapper = shallowRender({ onClose });
+  wrapper.find('WorkspaceHeader').prop<Function>('onClose')();
+  expect(onClose).toBeCalledWith('foo');
+});
+
+it('should call back after load', () => {
+  const onLoad = jest.fn();
+  const wrapper = shallowRender({ onLoad });
+  wrapper.find('WorkspaceRuleDetails').prop<Function>('onLoad')({ name: 'Foo' });
+  expect(onLoad).toBeCalledWith({ key: 'foo', name: 'Foo' });
+});
+
+function shallowRender(props?: Partial<Props>) {
+  const rule = { key: 'foo', organization: 'org' };
+  return shallow(
+    <WorkspaceRuleViewer
+      height={300}
+      onClose={jest.fn()}
+      onCollapse={jest.fn()}
+      onLoad={jest.fn()}
+      onMaximize={jest.fn()}
+      onMinimize={jest.fn()}
+      onResize={jest.fn()}
+      rule={rule}
+      {...props}
+    />
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceComponentTitle-test.tsx.snap b/server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceComponentTitle-test.tsx.snap
new file mode 100644 (file)
index 0000000..ed1cae4
--- /dev/null
@@ -0,0 +1,17 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render component 1`] = `
+<React.Fragment>
+  â€”
+</React.Fragment>
+`;
+
+exports[`should render loaded component 1`] = `
+<React.Fragment>
+  <QualifierIcon
+    className="little-spacer-right"
+    qualifier="FIL"
+  />
+  src/foo.js
+</React.Fragment>
+`;
diff --git a/server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceComponentViewer-test.tsx.snap b/server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceComponentViewer-test.tsx.snap
new file mode 100644 (file)
index 0000000..221000a
--- /dev/null
@@ -0,0 +1,37 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render 1`] = `
+<div
+  className="workspace-viewer"
+>
+  <WorkspaceHeader
+    onClose={[Function]}
+    onCollapse={[MockFunction]}
+    onMaximize={[MockFunction]}
+    onMinimize={[MockFunction]}
+    onResize={[MockFunction]}
+  >
+    <WorkspaceComponentTitle
+      component={
+        Object {
+          "branchLike": undefined,
+          "key": "foo",
+        }
+      }
+    />
+  </WorkspaceHeader>
+  <div
+    className="workspace-viewer-container"
+    style={
+      Object {
+        "height": 300,
+      }
+    }
+  >
+    <Connect(SourceViewerBase)
+      component="foo"
+      onLoaded={[Function]}
+    />
+  </div>
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceHeader-test.tsx.snap b/server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceHeader-test.tsx.snap
new file mode 100644 (file)
index 0000000..11212c4
--- /dev/null
@@ -0,0 +1,52 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render 1`] = `
+<header
+  className="workspace-viewer-header"
+>
+  <h6
+    className="workspace-viewer-name"
+  >
+    <div
+      id="workspace-header-children"
+    />
+  </h6>
+  <DraggableCore
+    allowAnyClick={false}
+    cancel={null}
+    disabled={false}
+    enableUserSelectHack={true}
+    grid={null}
+    handle={null}
+    offsetParent={<body />}
+    onDrag={[Function]}
+    onMouseDown={[Function]}
+    onStart={[Function]}
+    onStop={[Function]}
+    transform={null}
+  >
+    <div
+      className="workspace-viewer-resize js-resize"
+    />
+  </DraggableCore>
+  <div
+    className="workspace-viewer-actions"
+  >
+    <WorkspaceHeaderButton
+      icon={[Function]}
+      onClick={[MockFunction]}
+      tooltip="workspace.minimize"
+    />
+    <WorkspaceHeaderButton
+      icon={[Function]}
+      onClick={[MockFunction]}
+      tooltip="workspace.full_window"
+    />
+    <WorkspaceHeaderButton
+      icon={[Function]}
+      onClick={[MockFunction]}
+      tooltip="workspace.close"
+    />
+  </div>
+</header>
+`;
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
new file mode 100644 (file)
index 0000000..28dfed9
--- /dev/null
@@ -0,0 +1,111 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should not render open component 1`] = `
+<nav
+  className="workspace-nav"
+>
+  <ul
+    className="workspace-nav-list"
+  >
+    <WorkspaceNavComponent
+      component={
+        Object {
+          "branchLike": undefined,
+          "key": "foo",
+        }
+      }
+      key="component-foo"
+      onClose={[MockFunction]}
+      onOpen={[MockFunction]}
+    />
+    <WorkspaceNavRule
+      key="rule-qux"
+      onClose={[MockFunction]}
+      onOpen={[MockFunction]}
+      rule={
+        Object {
+          "key": "qux",
+          "organization": "org",
+        }
+      }
+    />
+  </ul>
+</nav>
+`;
+
+exports[`should not render open rule 1`] = `
+<nav
+  className="workspace-nav"
+>
+  <ul
+    className="workspace-nav-list"
+  >
+    <WorkspaceNavComponent
+      component={
+        Object {
+          "branchLike": undefined,
+          "key": "foo",
+        }
+      }
+      key="component-foo"
+      onClose={[MockFunction]}
+      onOpen={[MockFunction]}
+    />
+    <WorkspaceNavComponent
+      component={
+        Object {
+          "branchLike": undefined,
+          "key": "bar",
+        }
+      }
+      key="component-bar"
+      onClose={[MockFunction]}
+      onOpen={[MockFunction]}
+    />
+  </ul>
+</nav>
+`;
+
+exports[`should render 1`] = `
+<nav
+  className="workspace-nav"
+>
+  <ul
+    className="workspace-nav-list"
+  >
+    <WorkspaceNavComponent
+      component={
+        Object {
+          "branchLike": undefined,
+          "key": "foo",
+        }
+      }
+      key="component-foo"
+      onClose={[MockFunction]}
+      onOpen={[MockFunction]}
+    />
+    <WorkspaceNavComponent
+      component={
+        Object {
+          "branchLike": undefined,
+          "key": "bar",
+        }
+      }
+      key="component-bar"
+      onClose={[MockFunction]}
+      onOpen={[MockFunction]}
+    />
+    <WorkspaceNavRule
+      key="rule-qux"
+      onClose={[MockFunction]}
+      onOpen={[MockFunction]}
+      rule={
+        Object {
+          "key": "qux",
+          "organization": "org",
+        }
+      }
+    />
+  </ul>
+</nav>
+`;
diff --git a/server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceNavComponent-test.tsx.snap b/server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceNavComponent-test.tsx.snap
new file mode 100644 (file)
index 0000000..0f6abcb
--- /dev/null
@@ -0,0 +1,18 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render 1`] = `
+<WorkspaceNavItem
+  onClose={[Function]}
+  onOpen={[Function]}
+>
+  <WorkspaceComponentTitle
+    component={
+      Object {
+        "branchLike": undefined,
+        "key": "foo",
+      }
+    }
+    limited={true}
+  />
+</WorkspaceNavItem>
+`;
diff --git a/server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceNavItem-test.tsx.snap b/server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceNavItem-test.tsx.snap
new file mode 100644 (file)
index 0000000..3834b94
--- /dev/null
@@ -0,0 +1,26 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render 1`] = `
+<li
+  className="workspace-nav-item"
+>
+  <a
+    className="workspace-nav-item-link"
+    href="#"
+    onClick={[Function]}
+  >
+    <div
+      id="workspace-nav-item"
+    />
+  </a>
+  <ButtonIcon
+    className="js-close workspace-nav-item-close workspace-header-icon button-small little-spacer-left"
+    color="#fff"
+    onClick={[MockFunction]}
+  >
+    <ClearIcon
+      size={12}
+    />
+  </ButtonIcon>
+</li>
+`;
diff --git a/server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceNavRule-test.tsx.snap b/server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceNavRule-test.tsx.snap
new file mode 100644 (file)
index 0000000..2855436
--- /dev/null
@@ -0,0 +1,18 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render 1`] = `
+<WorkspaceNavItem
+  onClose={[Function]}
+  onOpen={[Function]}
+>
+  <WorkspaceRuleTitle
+    limited={true}
+    rule={
+      Object {
+        "key": "foo",
+        "organization": "org",
+      }
+    }
+  />
+</WorkspaceNavItem>
+`;
diff --git a/server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceRuleDetails-test.tsx.snap b/server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceRuleDetails-test.tsx.snap
new file mode 100644 (file)
index 0000000..682c1d5
--- /dev/null
@@ -0,0 +1,51 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render 1`] = `
+<DeferredSpinner
+  loading={true}
+  timeout={100}
+/>
+`;
+
+exports[`should render 2`] = `
+<DeferredSpinner
+  loading={false}
+  timeout={100}
+>
+  <React.Fragment>
+    <RuleDetailsMeta
+      canWrite={false}
+      hideSimilarRulesFilter={true}
+      onFilterChange={[Function]}
+      onTagsChange={[Function]}
+      organization="org"
+      referencedRepositories={
+        Object {
+          "repo": Object {
+            "key": "repo",
+            "language": "xoo",
+            "name": "Xoo Repository",
+          },
+        }
+      }
+      ruleDetails={
+        Object {
+          "key": "foo",
+          "name": "Foo",
+        }
+      }
+    />
+    <RuleDetailsDescription
+      canWrite={false}
+      onChange={[Function]}
+      organization="org"
+      ruleDetails={
+        Object {
+          "key": "foo",
+          "name": "Foo",
+        }
+      }
+    />
+  </React.Fragment>
+</DeferredSpinner>
+`;
diff --git a/server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceRuleTitle-test.tsx.snap b/server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceRuleTitle-test.tsx.snap
new file mode 100644 (file)
index 0000000..5ba5fc9
--- /dev/null
@@ -0,0 +1,19 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render loaded rule 1`] = `
+<React.Fragment>
+  <i
+    className="icon-workspace-doc little-spacer-right"
+  />
+  Foo
+</React.Fragment>
+`;
+
+exports[`should render rule 1`] = `
+<React.Fragment>
+  <i
+    className="icon-workspace-doc little-spacer-right"
+  />
+  â€”
+</React.Fragment>
+`;
diff --git a/server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceRuleViewer-test.tsx.snap b/server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceRuleViewer-test.tsx.snap
new file mode 100644 (file)
index 0000000..f8bc63a
--- /dev/null
@@ -0,0 +1,38 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render 1`] = `
+<div
+  className="workspace-viewer"
+>
+  <WorkspaceHeader
+    onClose={[Function]}
+    onCollapse={[MockFunction]}
+    onMaximize={[MockFunction]}
+    onMinimize={[MockFunction]}
+    onResize={[MockFunction]}
+  >
+    <WorkspaceRuleTitle
+      rule={
+        Object {
+          "key": "foo",
+          "organization": "org",
+        }
+      }
+    />
+  </WorkspaceHeader>
+  <div
+    className="workspace-viewer-container"
+    style={
+      Object {
+        "height": 300,
+      }
+    }
+  >
+    <WorkspaceRuleDetails
+      onLoad={[Function]}
+      organization="org"
+      ruleKey="foo"
+    />
+  </div>
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/components/workspace/context.ts b/server/sonar-web/src/main/js/components/workspace/context.ts
new file mode 100644 (file)
index 0000000..07dde02
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+* SonarQube
+* Copyright (C) 2009-2018 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 { BranchLike } from '../../app/types';
+
+export interface ComponentDescriptor {
+  branchLike: BranchLike | undefined;
+  key: string;
+  line?: number;
+  name?: string;
+  qualifier?: string;
+}
+
+export interface RuleDescriptor {
+  key: string;
+  name?: string;
+  organization: string;
+}
+
+export interface WorkspaceContext {
+  openComponent: (component: ComponentDescriptor) => void;
+  openRule: (rule: RuleDescriptor) => void;
+}
diff --git a/server/sonar-web/src/main/js/components/workspace/main.js b/server/sonar-web/src/main/js/components/workspace/main.js
deleted file mode 100644 (file)
index b2fd794..0000000
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.
- */
-// @flow
-import $ from 'jquery';
-import Item from './models/item';
-import Items from './models/items';
-import ItemsView from './views/items-view';
-import ViewerView from './views/viewer-view';
-import RuleView from './views/rule-view';
-import { getRuleDetails } from '../../api/rules';
-import './styles.css';
-import '../../apps/coding-rules/styles.css';
-
-let instance = null;
-const Workspace = function() {
-  if (instance != null) {
-    throw new Error('Cannot instantiate more than one Workspace, use Workspace.getInstance()');
-  }
-  this.initialize();
-};
-
-Workspace.prototype = {
-  initialize() {
-    const that = this;
-
-    this.items = new Items();
-    this.items.load();
-    this.items.on('change', () => {
-      that.save();
-    });
-
-    this.itemsView = new ItemsView({ collection: this.items });
-    this.itemsView.render().$el.appendTo(document.body);
-    this.itemsView.on('click', model => {
-      that.open(model);
-    });
-  },
-
-  save() {
-    this.items.save();
-  },
-
-  addComponent(model) {
-    const m = this.items.add2(model);
-    this.save();
-    return m;
-  },
-
-  open(options) {
-    const model = typeof options.toJSON === 'function' ? options : new Item(options);
-    if (!model.isValid()) {
-      throw new Error(model.validationError);
-    }
-    const m = this.addComponent(model);
-    if (m.isComponent()) {
-      this.showComponentViewer(m);
-    }
-    if (m.isRule()) {
-      this.showRule(m);
-    }
-  },
-
-  openComponent(options) {
-    return this.open({ ...options, __type__: 'component' });
-  },
-
-  openRule(options /*: { key: string, organization: string } */) {
-    return this.open({ ...options, __type__: 'rule' });
-  },
-
-  showViewer(Viewer, model) {
-    const that = this;
-    if (this.viewerView != null) {
-      this.viewerView.model.trigger('hideViewer');
-      this.viewerView.destroy();
-    }
-    $('html').addClass('with-workspace');
-    model.trigger('showViewer');
-    this.viewerView = new Viewer({ model });
-    this.viewerView
-      .on('viewerMinimize', () => {
-        model.trigger('hideViewer');
-        that.closeComponentViewer();
-      })
-      .on('viewerClose', m => {
-        that.closeComponentViewer();
-        m.destroy();
-      });
-    this.viewerView.$el.appendTo(document.body);
-    this.viewerView.render();
-  },
-
-  showComponentViewer(model) {
-    this.showViewer(ViewerView, model);
-  },
-
-  closeComponentViewer() {
-    if (this.viewerView != null) {
-      this.viewerView.destroy();
-      $('.with-workspace').removeClass('with-workspace');
-    }
-  },
-
-  showRule(model) {
-    const that = this;
-    getRuleDetails({ key: model.get('key') }).then(
-      r => {
-        model.set({ ...r.rule, exist: true });
-        that.showViewer(RuleView, model);
-      },
-      () => {
-        model.set({ exist: false });
-        that.showViewer(RuleView, model);
-      }
-    );
-  }
-};
-
-Workspace.getInstance = function() {
-  if (instance == null) {
-    instance = new Workspace();
-  }
-  return instance;
-};
-
-export default Workspace.getInstance();
diff --git a/server/sonar-web/src/main/js/components/workspace/models/item.js b/server/sonar-web/src/main/js/components/workspace/models/item.js
deleted file mode 100644 (file)
index 08ad8fe..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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 Backbone from 'backbone';
-
-export default Backbone.Model.extend({
-  validate() {
-    if (!this.has('__type__')) {
-      return 'type is missing';
-    }
-    if (this.get('__type__') === 'component' && !this.has('key')) {
-      return 'key is missing';
-    }
-    if (this.get('__type__') === 'rule' && !this.has('key')) {
-      return 'key is missing';
-    }
-  },
-
-  isComponent() {
-    return this.get('__type__') === 'component';
-  },
-
-  isRule() {
-    return this.get('__type__') === 'rule';
-  },
-
-  destroy(options) {
-    this.stopListening();
-    this.trigger('destroy', this, this.collection, options);
-  }
-});
diff --git a/server/sonar-web/src/main/js/components/workspace/models/items.js b/server/sonar-web/src/main/js/components/workspace/models/items.js
deleted file mode 100644 (file)
index c191ca9..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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 Backbone from 'backbone';
-import Item from './item';
-
-const STORAGE_KEY = 'sonarqube-workspace';
-
-export default Backbone.Collection.extend({
-  model: Item,
-
-  initialize() {
-    this.on('remove', this.save);
-  },
-
-  save() {
-    const dump = JSON.stringify(this.toJSON());
-    window.localStorage.setItem(STORAGE_KEY, dump);
-  },
-
-  load() {
-    const dump = window.localStorage.getItem(STORAGE_KEY);
-    if (dump != null) {
-      try {
-        const parsed = JSON.parse(dump);
-        this.reset(parsed);
-      } catch (err) {
-        // fail silently
-      }
-    }
-  },
-
-  has(model) {
-    const forComponent = model.isComponent() && this.findWhere({ key: model.get('key') }) != null;
-    const forRule = model.isRule() && this.findWhere({ key: model.get('key') }) != null;
-    return forComponent || forRule;
-  },
-
-  add2(model) {
-    const tryModel = this.findWhere({ key: model.get('key') });
-    return tryModel != null ? tryModel : this.add(model);
-  }
-});
index 170a4e2c986dc4809e79a045422686721598d063..229b996cdea11b7477a93cfbd4238f69cff5223e 100644 (file)
@@ -22,7 +22,7 @@
   z-index: 451;
   bottom: 0;
   right: 0;
-  height: 30px;
+  height: 28px;
 }
 
 .workspace-nav-list {
 }
 
 .workspace-nav-item {
-  float: left;
-  vertical-align: middle;
-  margin-right: 10px;
-  padding: 3px 10px;
+  position: relative;
+  display: inline-flex;
+  align-items: center;
+  margin-right: var(--gridSize);
+}
+
+.workspace-nav-item-link {
+  display: inline-flex;
+  align-items: center;
+  height: 28px;
+  padding: 0 calc(3.5 * var(--gridSize)) 0 var(--gridSize);
+  border: none;
   background-color: var(--gray40);
   color: #fff;
   font-size: var(--smallFontSize);
-  font-weight: 300;
-  cursor: pointer;
 }
 
-.workspace-nav-item > i {
-  position: relative;
-  top: -2px;
-  vertical-align: middle;
+.workspace-nav-item-link:hover,
+.workspace-nav-item-link:focus {
+  color: #fff;
+  opacity: 0.9;
+}
+
+.workspace-nav-item-close {
+  position: absolute;
+  right: 4px;
+  top: 4px;
 }
 
 .workspace-viewer {
@@ -72,7 +84,7 @@
   float: left;
   line-height: var(--controlHeight);
   color: #fff;
-  font-weight: 300;
+  font-weight: 400;
 }
 
 .workspace-viewer-name i {
 
 .workspace-viewer-actions {
   float: right;
-  line-height: var(--controlHeight);
 }
 
 .workspace-viewer-actions a {
   color: inherit;
 }
 
+.workspace-header-icon:hover path,
+.workspace-header-icon:focus path {
+  color: var(--gray40);
+}
+
 .workspace-viewer-container {
   height: calc(40vh - 30px);
   min-height: 100px;
diff --git a/server/sonar-web/src/main/js/components/workspace/templates/workspace-item.hbs b/server/sonar-web/src/main/js/components/workspace/templates/workspace-item.hbs
deleted file mode 100644 (file)
index a98380c..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-{{#if q}}{{qualifierIcon q}}&nbsp;{{/if}}{{#eq type 'rule'}}<i class="icon-workspace-doc"></i>&nbsp;{{/eq}}{{limitString name}}
-
-<button class="js-close button-clean" style="color: #fff;">&times;</button>
diff --git a/server/sonar-web/src/main/js/components/workspace/templates/workspace-items.hbs b/server/sonar-web/src/main/js/components/workspace/templates/workspace-items.hbs
deleted file mode 100644 (file)
index 6fe99c0..0000000
+++ /dev/null
@@ -1 +0,0 @@
-<ul class="workspace-nav-list"></ul>
diff --git a/server/sonar-web/src/main/js/components/workspace/templates/workspace-rule.hbs b/server/sonar-web/src/main/js/components/workspace/templates/workspace-rule.hbs
deleted file mode 100644 (file)
index 304daea..0000000
+++ /dev/null
@@ -1,76 +0,0 @@
-<div class="workspace-viewer-header"></div>
-
-<div class="workspace-viewer-container">
-
-  {{#if exist}}
-
-    <ul class="coding-rules-detail-properties">
-      {{#if severity}}
-        <li class="coding-rules-detail-property"
-            data-toggle="tooltip" data-placement="bottom" title="{{t 'coding_rules.type.tooltip' this.type}}">
-          <span class="little-spacer-right">{{issueTypeIcon this.type}}</span>{{issueType this.type}}
-        </li>
-
-        <li class="coding-rules-detail-property"
-            data-toggle="tooltip" data-placement="bottom" title="Default rule severity">
-          {{severityIcon severity}}&nbsp;{{t "severity" severity}}
-        </li>
-      {{/if}}
-
-      {{#notEq status 'READY'}}
-        <li class="coding-rules-detail-property"
-            data-toggle="tooltip" data-placement="bottom" title="Rule status">{{status}}</li>
-      {{/notEq}}
-
-      <li class="coding-rules-detail-property coding-rules-detail-tag-list {{#if canWrite}}coding-rules-detail-tags-change{{/if}}"
-          data-toggle="tooltip" data-placement="bottom" title="Rule tags">
-        <i class="icon-tags little-spacer-right"></i>
-        <span>{{#if allTags}}{{join allTags ', '}}{{else}}{{t 'coding_rules.no_tags'}}{{/if}}</span>
-      </li>
-
-      <li class="coding-rules-detail-property">{{t 'coding_rules.available_since'}} {{d createdAt}}</li>
-
-      {{#if debtRemFnType}}
-        <li class="coding-rules-detail-property"
-            data-toggle="tooltip" data-placement="bottom" title="{{t 'coding_rules.remediation_function'}}">
-          {{t 'coding_rules.remediation_function' debtRemFnType}}:
-
-          {{#if debtRemFnOffset}}{{debtRemFnOffset}}{{/if}}
-          {{#if debtRemFnCoeff}}{{#if debtRemFnOffset}}+{{/if}}{{debtRemFnCoeff}}{{/if}}
-          {{#if effortToFixDescription}}{{effortToFixDescription}}{{/if}}
-        </li>
-      {{/if}}
-
-      <li class="coding-rules-detail-property spacer-left">
-        <a class="link-no-underline" target="_blank" href="{{permalink}}">
-          <svg
-            xmlns="http://www.w3.org/2000/svg"
-            height=14
-            width=14
-            viewBox="0 0 16 16">
-            <path
-              fill="currentColor"
-              d="M13.501 11.429q0-0.357-0.25-0.607l-1.857-1.857q-0.25-0.25-0.607-0.25-0.375 0-0.643 0.286 0.027 0.027 0.17 0.165t0.192 0.192 0.134 0.17 0.116 0.228 0.031 0.246q0 0.357-0.25 0.607t-0.607 0.25q-0.134 0-0.246-0.031t-0.228-0.116-0.17-0.134-0.192-0.192-0.165-0.17q-0.295 0.277-0.295 0.652 0 0.357 0.25 0.607l1.839 1.848q0.241 0.241 0.607 0.241 0.357 0 0.607-0.232l1.313-1.304q0.25-0.25 0.25-0.598zM7.224 5.134q0-0.357-0.25-0.607l-1.839-1.848q-0.25-0.25-0.607-0.25-0.348 0-0.607 0.241l-1.313 1.304q-0.25 0.25-0.25 0.598 0 0.357 0.25 0.607l1.857 1.857q0.241 0.241 0.607 0.241 0.375 0 0.643-0.277-0.027-0.027-0.17-0.165t-0.192-0.192-0.134-0.17-0.116-0.228-0.031-0.246q0-0.357 0.25-0.607t0.607-0.25q0.134 0 0.246 0.031t0.228 0.116 0.17 0.134 0.192 0.192 0.165 0.17q0.295-0.277 0.295-0.652zM15.215 11.429q0 1.071-0.759 1.813l-1.313 1.304q-0.741 0.741-1.813 0.741-1.080 0-1.821-0.759l-1.839-1.848q-0.741-0.741-0.741-1.813 0-1.098 0.786-1.866l-0.786-0.786q-0.768 0.786-1.857 0.786-1.071 0-1.821-0.75l-1.857-1.857q-0.75-0.75-0.75-1.821t0.759-1.813l1.313-1.304q0.741-0.741 1.813-0.741 1.080 0 1.821 0.759l1.839 1.848q0.741 0.741 0.741 1.813 0 1.098-0.786 1.866l0.786 0.786q0.768-0.786 1.857-0.786 1.071 0 1.821 0.75l1.857 1.857q0.75 0.75 0.75 1.821z"
-            />
-          </svg>
-        </a>
-        <span class="note little-spacer-left">{{key}}</span>
-      </li>
-    </ul>
-
-    <div class="coding-rules-detail-description rule-desc markdown">{{{htmlDesc}}}</div>
-
-    {{#if htmlNote}}
-      <div id="coding-rules-detail-description-extra">
-        <div class="rule-desc markdown">{{{htmlNote}}}</div>
-      </div>
-    {{/if}}
-
-  {{else}}
-
-  {{! does not exist}}
-    <div class="alert alert-warning">{{t 'workspace.no_rule'}}</div>
-
-  {{/if}}
-
-</div>
diff --git a/server/sonar-web/src/main/js/components/workspace/templates/workspace-viewer-header.hbs b/server/sonar-web/src/main/js/components/workspace/templates/workspace-viewer-header.hbs
deleted file mode 100644 (file)
index 409dcd3..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-<h6 class="workspace-viewer-name">{{#if q}}{{qualifierIcon q}}&nbsp;{{/if}}{{#eq type 'rule'}}<i class="icon-workspace-doc"></i>&nbsp;{{/eq}}{{name}}</h6>
-
-<div class="workspace-viewer-resize js-resize"></div>
-
-<div class="workspace-viewer-actions">
-  <a href="#" class="js-minimize icon-minimize spacer-right"
-     title="{{t 'workspace.minimize'}}" data-placement="bottom" data-toggle="tooltip"></a>
-
-  <a href="#" class="js-full-screen icon-bigger-size spacer-right"
-     title="{{t 'workspace.full_window'}}" data-placement="bottom" data-toggle="tooltip"></a>
-
-  <a href="#" class="js-normal-size icon-smaller-size spacer-right"
-     title="{{t 'workspace.normal_size'}}" data-placement="bottom" data-toggle="tooltip"></a>
-
-  <a href="#" class="js-close icon-close"
-     title="{{t 'workspace.close'}}" data-placement="bottom" data-toggle="tooltip"></a>
-</div>
diff --git a/server/sonar-web/src/main/js/components/workspace/templates/workspace-viewer.hbs b/server/sonar-web/src/main/js/components/workspace/templates/workspace-viewer.hbs
deleted file mode 100644 (file)
index 45515fb..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-<div class="workspace-viewer-header"></div>
-
-<div class="workspace-viewer-container"></div>
diff --git a/server/sonar-web/src/main/js/components/workspace/views/base-viewer-view.js b/server/sonar-web/src/main/js/components/workspace/views/base-viewer-view.js
deleted file mode 100644 (file)
index f5883d5..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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 Marionette from 'backbone.marionette';
-import HeaderView from './viewer-header-view';
-
-export default Marionette.LayoutView.extend({
-  className: 'workspace-viewer',
-
-  modelEvents: {
-    destroy: 'destroy'
-  },
-
-  regions: {
-    headerRegion: '.workspace-viewer-header',
-    viewerRegion: '.workspace-viewer-container'
-  },
-
-  onRender() {
-    this.showHeader();
-    this.$('.workspace-viewer-container').isolatedScroll();
-  },
-
-  onViewerMinimize() {
-    this.trigger('viewerMinimize');
-  },
-
-  onViewerClose() {
-    this.trigger('viewerClose', this.model);
-  },
-
-  showHeader() {
-    const headerView = new HeaderView({ model: this.model });
-    this.listenTo(headerView, 'viewerMinimize', this.onViewerMinimize);
-    this.listenTo(headerView, 'viewerClose', this.onViewerClose);
-    this.headerRegion.show(headerView);
-  }
-});
diff --git a/server/sonar-web/src/main/js/components/workspace/views/item-view.js b/server/sonar-web/src/main/js/components/workspace/views/item-view.js
deleted file mode 100644 (file)
index b007f55..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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 Marionette from 'backbone.marionette';
-import Template from '../templates/workspace-item.hbs';
-
-export default Marionette.ItemView.extend({
-  tagName: 'li',
-  className: 'workspace-nav-item',
-  template: Template,
-
-  modelEvents: {
-    change: 'render',
-    showViewer: 'onViewerShow',
-    hideViewer: 'onViewerHide'
-  },
-
-  events: {
-    click: 'onClick',
-    'click .js-close': 'onCloseClick'
-  },
-
-  onClick(e) {
-    e.preventDefault();
-    this.options.collectionView.trigger('click', this.model);
-  },
-
-  onCloseClick(e) {
-    e.preventDefault();
-    e.stopPropagation();
-    this.model.destroy();
-  },
-
-  onViewerShow() {
-    this.$el.addClass('hidden');
-  },
-
-  onViewerHide() {
-    this.$el.removeClass('hidden');
-  }
-});
diff --git a/server/sonar-web/src/main/js/components/workspace/views/items-view.js b/server/sonar-web/src/main/js/components/workspace/views/items-view.js
deleted file mode 100644 (file)
index 06f3f93..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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 Marionette from 'backbone.marionette';
-import ItemView from './item-view';
-import Template from '../templates/workspace-items.hbs';
-
-export default Marionette.CompositeView.extend({
-  className: 'workspace-nav',
-  template: Template,
-  childViewContainer: '.workspace-nav-list',
-  childView: ItemView,
-
-  childViewOptions() {
-    return { collectionView: this };
-  }
-});
diff --git a/server/sonar-web/src/main/js/components/workspace/views/rule-view.js b/server/sonar-web/src/main/js/components/workspace/views/rule-view.js
deleted file mode 100644 (file)
index 88c2dbb..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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 { union } from 'lodash';
-import Marionette from 'backbone.marionette';
-import BaseView from './base-viewer-view';
-import Template from '../templates/workspace-rule.hbs';
-import { getPathUrlAsString, getRulesUrl } from '../../../helpers/urls';
-import { areThereCustomOrganizations } from '../../../store/organizations/utils';
-
-export default BaseView.extend({
-  template: Template,
-
-  onRender() {
-    BaseView.prototype.onRender.apply(this, arguments);
-    this.$('[data-toggle="tooltip"]').tooltip({ container: 'body' });
-  },
-
-  onDestroy() {
-    this.$('[data-toggle="tooltip"]').tooltip('destroy');
-  },
-
-  serializeData() {
-    const query = { rule_key: this.model.get('key') };
-    const permalink = getPathUrlAsString(
-      areThereCustomOrganizations()
-        ? getRulesUrl(query, this.model.get('organization'))
-        : getRulesUrl(query, undefined)
-    );
-
-    return {
-      ...Marionette.LayoutView.prototype.serializeData.apply(this, arguments),
-      allTags: union(this.model.get('sysTags'), this.model.get('tags')),
-      permalink
-    };
-  }
-});
diff --git a/server/sonar-web/src/main/js/components/workspace/views/viewer-header-view.js b/server/sonar-web/src/main/js/components/workspace/views/viewer-header-view.js
deleted file mode 100644 (file)
index 756ede7..0000000
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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 $ from 'jquery';
-import Marionette from 'backbone.marionette';
-import Template from '../templates/workspace-viewer-header.hbs';
-
-export default Marionette.ItemView.extend({
-  template: Template,
-
-  modelEvents: {
-    change: 'render'
-  },
-
-  events: {
-    'mousedown .js-resize': 'onResizeClick',
-
-    'click .js-minimize': 'onMinimizeClick',
-    'click .js-full-screen': 'onFullScreenClick',
-    'click .js-normal-size': 'onNormalSizeClick',
-    'click .js-close': 'onCloseClick'
-  },
-
-  onRender() {
-    this.$('[data-toggle="tooltip"]').tooltip({ container: 'body' });
-    this.$('.js-normal-size').addClass('hidden');
-  },
-
-  onDestroy() {
-    this.$('[data-toggle="tooltip"]').tooltip('destroy');
-    $('.tooltip').remove();
-  },
-
-  onResizeClick(e) {
-    e.preventDefault();
-    this.startResizing(e);
-  },
-
-  onMinimizeClick(e) {
-    e.preventDefault();
-    this.trigger('viewerMinimize');
-  },
-
-  onFullScreenClick(e) {
-    e.preventDefault();
-    this.toFullScreen();
-  },
-
-  onNormalSizeClick(e) {
-    e.preventDefault();
-    this.toNormalSize();
-  },
-
-  onCloseClick(e) {
-    e.preventDefault();
-    this.trigger('viewerClose');
-  },
-
-  startResizing(e) {
-    this.initialResizePosition = e.clientY;
-    this.initialResizeHeight = $('.workspace-viewer-container').height();
-    const processResizing = this.processResizing.bind(this);
-    const stopResizing = this.stopResizing.bind(this);
-    $('body')
-      .on('mousemove.workspace', processResizing)
-      .on('mouseup.workspace', stopResizing);
-  },
-
-  processResizing(e) {
-    const currentResizePosition = e.clientY;
-    const resizeDelta = this.initialResizePosition - currentResizePosition;
-    const height = this.initialResizeHeight + resizeDelta;
-    $('.workspace-viewer-container').height(height);
-  },
-
-  stopResizing() {
-    $('body')
-      .off('mousemove.workspace')
-      .off('mouseup.workspace');
-  },
-
-  toFullScreen() {
-    this.$('.js-normal-size').removeClass('hidden');
-    this.$('.js-full-screen').addClass('hidden');
-    this.initialResizeHeight = $('.workspace-viewer-container').height();
-    $('.workspace-viewer-container').height('9999px');
-  },
-
-  toNormalSize() {
-    this.$('.js-normal-size').addClass('hidden');
-    this.$('.js-full-screen').removeClass('hidden');
-    $('.workspace-viewer-container').height(this.initialResizeHeight);
-  }
-});
diff --git a/server/sonar-web/src/main/js/components/workspace/views/viewer-view.js b/server/sonar-web/src/main/js/components/workspace/views/viewer-view.js
deleted file mode 100644 (file)
index eafcac4..0000000
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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 $ from 'jquery';
-import React from 'react';
-import { render } from 'react-dom';
-import BaseView from './base-viewer-view';
-import SourceViewer from '../../SourceViewer/SourceViewer';
-import Template from '../templates/workspace-viewer.hbs';
-import WithStore from '../../shared/WithStore';
-
-export default BaseView.extend({
-  template: Template,
-
-  onRender() {
-    BaseView.prototype.onRender.apply(this, arguments);
-    this.showViewer();
-  },
-
-  scrollToLine(line) {
-    const row = this.$el.find(`.source-line[data-line-number="${line}"]`);
-    if (row.length > 0) {
-      const sourceViewer = this.$el.find('.source-viewer');
-      let p = sourceViewer.scrollParent();
-      if (p.is(document) || p.is('body')) {
-        p = $(window);
-      }
-      const pTopOffset = p.offset() != null ? p.offset().top : 0;
-      const pHeight = p.height();
-      const goal = row.offset().top - pHeight / 3 - pTopOffset;
-      p.scrollTop(goal);
-    }
-  },
-
-  showViewer() {
-    const { branchLike, key, line } = this.model.toJSON();
-
-    const el = document.querySelector(this.viewerRegion.el);
-
-    render(
-      <WithStore>
-        <SourceViewer
-          aroundLine={line}
-          branchLike={branchLike}
-          component={key}
-          fromWorkspace={true}
-          highlightedLine={line}
-          onLoaded={component => {
-            this.model.set({ name: component.name, q: component.q });
-            if (line) {
-              this.scrollToLine(line);
-            }
-          }}
-        />
-      </WithStore>,
-      el
-    );
-  }
-});