From 6f189a7c95ee207e02c7c9321ed37d0c6ca4afe6 Mon Sep 17 00:00:00 2001 From: Stas Vilchik Date: Wed, 14 Mar 2018 12:47:17 +0100 Subject: [PATCH] rewrite workspace in react (#3140) --- .../js/app/components/GlobalContainer.tsx | 19 +- .../js/apps/code/components/ComponentPin.tsx | 42 +++- .../components/RuleDetailsMeta.tsx | 5 +- .../SourceViewer/SourceViewerHeader.tsx | 12 +- .../SourceViewer/components/CoveragePopup.tsx | 14 +- .../components/DuplicationPopup.tsx | 18 +- .../icons-components/CollapseIcon.tsx | 39 +++ .../icons-components/ExpandIcon.tsx | 39 +++ .../icons-components/MinimizeIcon.tsx | 39 +++ .../issue/components/IssueMessage.js | 10 +- .../components/__tests__/IssueMessage-test.js | 3 +- .../js/components/workspace/Workspace.tsx | 235 ++++++++++++++++++ .../workspace/WorkspaceComponentTitle.tsx | 40 +++ .../workspace/WorkspaceComponentViewer.tsx | 102 ++++++++ .../components/workspace/WorkspaceHeader.tsx | 102 ++++++++ .../js/components/workspace/WorkspaceNav.tsx | 63 +++++ ...ewer-view.js => WorkspaceNavComponent.tsx} | 53 ++-- .../components/workspace/WorkspaceNavItem.tsx | 52 ++++ .../components/workspace/WorkspaceNavRule.tsx | 47 ++++ .../{models/item.js => WorkspacePortal.tsx} | 42 ++-- .../workspace/WorkspaceRuleDetails.tsx | 114 +++++++++ .../workspace/WorkspaceRuleTitle.tsx | 36 +++ .../workspace/WorkspaceRuleViewer.tsx | 68 +++++ .../WorkspaceComponentTitle-test.tsx | 32 +++ .../WorkspaceComponentViewer-test.tsx | 61 +++++ .../__tests__/WorkspaceHeader-test.tsx | 47 ++++ .../workspace/__tests__/WorkspaceNav-test.tsx | 51 ++++ .../__tests__/WorkspaceNavComponent-test.tsx | 52 ++++ .../__tests__/WorkspaceNavItem-test.tsx | 49 ++++ .../__tests__/WorkspaceNavRule-test.tsx | 47 ++++ .../WorkspacePortal-test.tsx} | 20 +- .../__tests__/WorkspaceRuleDetails-test.tsx | 49 ++++ .../WorkspaceRuleTitle-test.tsx} | 45 +--- .../__tests__/WorkspaceRuleViewer-test.tsx | 57 +++++ .../WorkspaceComponentTitle-test.tsx.snap | 17 ++ .../WorkspaceComponentViewer-test.tsx.snap | 37 +++ .../WorkspaceHeader-test.tsx.snap | 52 ++++ .../__snapshots__/WorkspaceNav-test.tsx.snap | 111 +++++++++ .../WorkspaceNavComponent-test.tsx.snap | 18 ++ .../WorkspaceNavItem-test.tsx.snap | 26 ++ .../WorkspaceNavRule-test.tsx.snap | 18 ++ .../WorkspaceRuleDetails-test.tsx.snap | 51 ++++ .../WorkspaceRuleTitle-test.tsx.snap | 19 ++ .../WorkspaceRuleViewer-test.tsx.snap | 38 +++ .../main/js/components/workspace/context.ts | 39 +++ .../src/main/js/components/workspace/main.js | 143 ----------- .../js/components/workspace/models/items.js | 59 ----- .../main/js/components/workspace/styles.css | 42 +++- .../workspace/templates/workspace-item.hbs | 3 - .../workspace/templates/workspace-items.hbs | 1 - .../workspace/templates/workspace-rule.hbs | 76 ------ .../templates/workspace-viewer-header.hbs | 17 -- .../workspace/templates/workspace-viewer.hbs | 3 - .../components/workspace/views/rule-view.js | 53 ---- .../workspace/views/viewer-header-view.js | 110 -------- .../components/workspace/views/viewer-view.js | 75 ------ 56 files changed, 2027 insertions(+), 685 deletions(-) create mode 100644 server/sonar-web/src/main/js/components/icons-components/CollapseIcon.tsx create mode 100644 server/sonar-web/src/main/js/components/icons-components/ExpandIcon.tsx create mode 100644 server/sonar-web/src/main/js/components/icons-components/MinimizeIcon.tsx create mode 100644 server/sonar-web/src/main/js/components/workspace/Workspace.tsx create mode 100644 server/sonar-web/src/main/js/components/workspace/WorkspaceComponentTitle.tsx create mode 100644 server/sonar-web/src/main/js/components/workspace/WorkspaceComponentViewer.tsx create mode 100644 server/sonar-web/src/main/js/components/workspace/WorkspaceHeader.tsx create mode 100644 server/sonar-web/src/main/js/components/workspace/WorkspaceNav.tsx rename server/sonar-web/src/main/js/components/workspace/{views/base-viewer-view.js => WorkspaceNavComponent.tsx} (50%) create mode 100644 server/sonar-web/src/main/js/components/workspace/WorkspaceNavItem.tsx create mode 100644 server/sonar-web/src/main/js/components/workspace/WorkspaceNavRule.tsx rename server/sonar-web/src/main/js/components/workspace/{models/item.js => WorkspacePortal.tsx} (58%) create mode 100644 server/sonar-web/src/main/js/components/workspace/WorkspaceRuleDetails.tsx create mode 100644 server/sonar-web/src/main/js/components/workspace/WorkspaceRuleTitle.tsx create mode 100644 server/sonar-web/src/main/js/components/workspace/WorkspaceRuleViewer.tsx create mode 100644 server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceComponentTitle-test.tsx create mode 100644 server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceComponentViewer-test.tsx create mode 100644 server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceHeader-test.tsx create mode 100644 server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceNav-test.tsx create mode 100644 server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceNavComponent-test.tsx create mode 100644 server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceNavItem-test.tsx create mode 100644 server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceNavRule-test.tsx rename server/sonar-web/src/main/js/components/workspace/{views/items-view.js => __tests__/WorkspacePortal-test.tsx} (69%) create mode 100644 server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceRuleDetails-test.tsx rename server/sonar-web/src/main/js/components/workspace/{views/item-view.js => __tests__/WorkspaceRuleTitle-test.tsx} (53%) create mode 100644 server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceRuleViewer-test.tsx create mode 100644 server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceComponentTitle-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceComponentViewer-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceHeader-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceNav-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceNavComponent-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceNavItem-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceNavRule-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceRuleDetails-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceRuleTitle-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceRuleViewer-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/components/workspace/context.ts delete mode 100644 server/sonar-web/src/main/js/components/workspace/main.js delete mode 100644 server/sonar-web/src/main/js/components/workspace/models/items.js delete mode 100644 server/sonar-web/src/main/js/components/workspace/templates/workspace-item.hbs delete mode 100644 server/sonar-web/src/main/js/components/workspace/templates/workspace-items.hbs delete mode 100644 server/sonar-web/src/main/js/components/workspace/templates/workspace-rule.hbs delete mode 100644 server/sonar-web/src/main/js/components/workspace/templates/workspace-viewer-header.hbs delete mode 100644 server/sonar-web/src/main/js/components/workspace/templates/workspace-viewer.hbs delete mode 100644 server/sonar-web/src/main/js/components/workspace/views/rule-view.js delete mode 100644 server/sonar-web/src/main/js/components/workspace/views/viewer-header-view.js delete mode 100644 server/sonar-web/src/main/js/components/workspace/views/viewer-view.js diff --git a/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx b/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx index d4c6fb3a426..a89319b690a 100644 --- a/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx +++ b/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx @@ -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 {
- - - {this.props.children} + + + + {this.props.children} +
diff --git a/server/sonar-web/src/main/js/apps/code/components/ComponentPin.tsx b/server/sonar-web/src/main/js/apps/code/components/ComponentPin.tsx index 867f4bd476f..c9b3fb86af6 100644 --- a/server/sonar-web/src/main/js/apps/code/components/ComponentPin.tsx +++ b/server/sonar-web/src/main/js/apps/code/components/ComponentPin.tsx @@ -18,10 +18,11 @@ * 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) => { +export default class ComponentPin extends React.PureComponent { + // prettier-ignore + context!: { workspace: WorkspaceContext }; + + static contextTypes = { + workspace: PropTypes.object.isRequired + }; + + handleClick = (event: React.SyntheticEvent) => { 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 ( - - - - ); + render() { + return ( + + + + ); + } } diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsMeta.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsMeta.tsx index 1a272610065..97b99759784 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsMeta.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsMeta.tsx @@ -37,6 +37,7 @@ import { Button } from '../../../components/ui/buttons'; interface Props { canWrite: boolean | undefined; + hideSimilarRulesFilter?: boolean; onFilterChange: (changes: Partial) => void; onTagsChange: (tags: string[]) => void; organization: string | undefined; @@ -228,7 +229,9 @@ export default class RuleDetailsMeta extends React.PureComponent { to={getRuleUrl(ruleDetails.key, this.props.organization)}> - + {!this.props.hideSimilarRulesFilter && ( + + )}

{ruleDetails.name} diff --git a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeader.tsx b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeader.tsx index 0ba3cd6ec40..72ac6fa5bcd 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeader.tsx +++ b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeader.tsx @@ -20,10 +20,12 @@ 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 { + // prettier-ignore + context!: { workspace: WorkspaceContext }; + + static contextTypes = { + workspace: PropTypes.object.isRequired + }; + state: State = { measuresOverlay: false }; handleShowMeasuresClick = (event: React.SyntheticEvent) => { @@ -60,8 +69,7 @@ export default class SourceViewerHeader extends React.PureComponent) => { 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() { diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/CoveragePopup.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/CoveragePopup.tsx index ef06788347e..4d60126638e 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/CoveragePopup.tsx +++ b/server/sonar-web/src/main/js/components/SourceViewer/components/CoveragePopup.tsx @@ -19,10 +19,12 @@ */ 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 { + // 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 { 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(); }; diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/DuplicationPopup.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/DuplicationPopup.tsx index d4b75a93b28..445b750f807 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/DuplicationPopup.tsx +++ b/server/sonar-web/src/main/js/components/SourceViewer/components/DuplicationPopup.tsx @@ -19,10 +19,12 @@ */ 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 { + // 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 { handleFileClick = (event: React.MouseEvent) => { 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 index 00000000000..f1070ac740e --- /dev/null +++ b/server/sonar-web/src/main/js/components/icons-components/CollapseIcon.tsx @@ -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 ( + + + + ); +} 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 index 00000000000..1105b7833ed --- /dev/null +++ b/server/sonar-web/src/main/js/components/icons-components/ExpandIcon.tsx @@ -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 ( + + + + ); +} 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 index 00000000000..4478239ddf1 --- /dev/null +++ b/server/sonar-web/src/main/js/components/icons-components/MinimizeIcon.tsx @@ -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 ( + + + + ); +} diff --git a/server/sonar-web/src/main/js/components/issue/components/IssueMessage.js b/server/sonar-web/src/main/js/components/issue/components/IssueMessage.js index df26092b983..515edd9b3ba 100644 --- a/server/sonar-web/src/main/js/components/issue/components/IssueMessage.js +++ b/server/sonar-web/src/main/js/components/issue/components/IssueMessage.js @@ -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 }); diff --git a/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueMessage-test.js b/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueMessage-test.js index eafe943c67a..785bc2a21f1 100644 --- a/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueMessage-test.js +++ b/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueMessage-test.js @@ -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 index 00000000000..a5dcce0d75b --- /dev/null +++ b/server/sonar-web/src/main/js/components/workspace/Workspace.tsx @@ -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 => ({ + 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 => ({ + 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 => ({ + 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 => ({ + 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 => ({ + 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 => ({ + 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 => ({ + 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} + + + {openComponent && ( + + )} + {openRule && ( + + )} + + + ); + } +} 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 index 00000000000..8679e169731 --- /dev/null +++ b/server/sonar-web/src/main/js/components/workspace/WorkspaceComponentTitle.tsx @@ -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 && ( + + )} + {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 index 00000000000..eb2140ed5ef --- /dev/null +++ b/server/sonar-web/src/main/js/components/workspace/WorkspaceComponentViewer.tsx @@ -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 { + component: ComponentDescriptor; + height: number; + onClose: (componentKey: string) => void; + onLoad: (details: { key: string; name: string; qualifier: string }) => void; +} + +export default class WorkspaceComponentViewer extends React.PureComponent { + 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 ( +
+ + + + +
(this.container = node)} + style={{ height: this.props.height }}> + +
+
+ ); + } +} 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 index 00000000000..011991dd54d --- /dev/null +++ b/server/sonar-web/src/main/js/components/workspace/WorkspaceHeader.tsx @@ -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 { + handleDrag = (_event: MouseEvent, data: DraggableData) => { + this.props.onResize(data.deltaY); + }; + + render() { + return ( +
+
{this.props.children}
+ + +
+ + +
+ + + {this.props.maximized ? ( + + ) : ( + + )} + + +
+
+ ); + } +} + +interface WorkspaceHeaderButtonProps { + icon: React.SFC; + onClick: () => void; + tooltip: string; +} + +function WorkspaceHeaderButton({ icon: Icon, onClick, tooltip }: WorkspaceHeaderButtonProps) { + return ( + + + + ); +} 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 index 00000000000..52471f7fb63 --- /dev/null +++ b/server/sonar-web/src/main/js/components/workspace/WorkspaceNav.tsx @@ -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 ( + + ); +} 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/WorkspaceNavComponent.tsx similarity index 50% rename from server/sonar-web/src/main/js/components/workspace/views/base-viewer-view.js rename to server/sonar-web/src/main/js/components/workspace/WorkspaceNavComponent.tsx index f5883d57378..15a5c114b35 100644 --- a/server/sonar-web/src/main/js/components/workspace/views/base-viewer-view.js +++ b/server/sonar-web/src/main/js/components/workspace/WorkspaceNavComponent.tsx @@ -17,38 +17,31 @@ * 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'; +import * as React from 'react'; +import { ComponentDescriptor } from './context'; +import WorkspaceComponentTitle from './WorkspaceComponentTitle'; +import WorkspaceNavItem from './WorkspaceNavItem'; -export default Marionette.LayoutView.extend({ - className: 'workspace-viewer', +export interface Props { + component: ComponentDescriptor; + onClose: (componentKey: string) => void; + onOpen: (componentKey: string) => void; +} - modelEvents: { - destroy: 'destroy' - }, +export default class WorkspaceNavComponent extends React.PureComponent { + handleClose = () => { + this.props.onClose(this.props.component.key); + }; - regions: { - headerRegion: '.workspace-viewer-header', - viewerRegion: '.workspace-viewer-container' - }, + handleOpen = () => { + this.props.onOpen(this.props.component.key); + }; - 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); + render() { + return ( + + + + ); } -}); +} 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 index 00000000000..6f29f4caef4 --- /dev/null +++ b/server/sonar-web/src/main/js/components/workspace/WorkspaceNavItem.tsx @@ -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 { + handleNameClick = (event: React.MouseEvent) => { + event.preventDefault(); + event.currentTarget.blur(); + this.props.onOpen(); + }; + + render() { + return ( +
  • + + {this.props.children} + + + + +
  • + ); + } +} 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 index 00000000000..1dea8715464 --- /dev/null +++ b/server/sonar-web/src/main/js/components/workspace/WorkspaceNavRule.tsx @@ -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 { + handleClose = () => { + this.props.onClose(this.props.rule.key); + }; + + handleOpen = () => { + this.props.onOpen(this.props.rule.key); + }; + + render() { + return ( + + + + ); + } +} diff --git a/server/sonar-web/src/main/js/components/workspace/models/item.js b/server/sonar-web/src/main/js/components/workspace/WorkspacePortal.tsx similarity index 58% rename from server/sonar-web/src/main/js/components/workspace/models/item.js rename to server/sonar-web/src/main/js/components/workspace/WorkspacePortal.tsx index 08ad8fe02a1..63af9fe6be6 100644 --- a/server/sonar-web/src/main/js/components/workspace/models/item.js +++ b/server/sonar-web/src/main/js/components/workspace/WorkspacePortal.tsx @@ -17,31 +17,27 @@ * 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 * as React from 'react'; +import { createPortal } from 'react-dom'; -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'; - } - }, +export default class WorkspacePortal extends React.PureComponent { + el: HTMLElement; - isComponent() { - return this.get('__type__') === 'component'; - }, + constructor(props: {}) { + super(props); + this.el = document.createElement('div'); + this.el.classList.add('workspace'); + } + + componentDidMount() { + document.body.appendChild(this.el); + } - isRule() { - return this.get('__type__') === 'rule'; - }, + componentWillUnmount() { + document.body.removeChild(this.el); + } - destroy(options) { - this.stopListening(); - this.trigger('destroy', this, this.collection, options); + 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 index 00000000000..e154a145773 --- /dev/null +++ b/server/sonar-web/src/main/js/components/workspace/WorkspaceRuleDetails.tsx @@ -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 { + 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 ( + + {this.state.ruleDetails && ( + <> + + + + )} + + ); + } +} 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 index 00000000000..ac7387ce23b --- /dev/null +++ b/server/sonar-web/src/main/js/components/workspace/WorkspaceRuleTitle.tsx @@ -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 ( + <> + + {limited ? {name} : 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 index 00000000000..513f2ca8311 --- /dev/null +++ b/server/sonar-web/src/main/js/components/workspace/WorkspaceRuleViewer.tsx @@ -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 { + rule: RuleDescriptor; + height: number; + onClose: (componentKey: string) => void; + onLoad: (details: { key: string; name: string }) => void; +} + +export default class WorkspaceRuleViewer extends React.PureComponent { + 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 ( +
    + + + + +
    + +
    +
    + ); + } +} 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 index 00000000000..2db40894949 --- /dev/null +++ b/server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceComponentTitle-test.tsx @@ -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()).toMatchSnapshot(); +}); + +it('should render loaded component', () => { + const component = { branchLike: undefined, key: 'foo', name: 'src/foo.js', qualifier: 'FIL' }; + expect(shallow()).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 index 00000000000..b3cab1c4b41 --- /dev/null +++ b/server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceComponentViewer-test.tsx @@ -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('onClose')(); + expect(onClose).toBeCalledWith('foo'); +}); + +it('should call back after load', () => { + const onLoad = jest.fn(); + const wrapper = shallowRender({ onLoad }); + wrapper.find('[onLoaded]').prop('onLoaded')({ + key: 'foo', + path: 'src/foo.js', + q: 'FIL' + }); + expect(onLoad).toBeCalledWith({ key: 'foo', name: 'src/foo.js', qualifier: 'FIL' }); +}); + +function shallowRender(props?: Partial) { + const component = { branchLike: undefined, key: 'foo' }; + return shallow( + + ); +} 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 index 00000000000..a8be1a80c29 --- /dev/null +++ b/server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceHeader-test.tsx @@ -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('onDrag')({}, { deltaY: 15 }); + expect(onResize).toBeCalledWith(15); +}); + +function shallowRender(props?: Partial) { + return shallow( + +
    + + ); +} 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 index 00000000000..487e94a9b8c --- /dev/null +++ b/server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceNav-test.tsx @@ -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) { + const components = [{ branchLike: undefined, key: 'foo' }, { branchLike: undefined, key: 'bar' }]; + const rules = [{ key: 'qux', organization: 'org' }]; + return shallow( + + ); +} 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 index 00000000000..e14ddd018c1 --- /dev/null +++ b/server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceNavComponent-test.tsx @@ -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('onClose')(); + expect(onClose).toBeCalledWith('foo'); +}); + +it('should open', () => { + const onOpen = jest.fn(); + const wrapper = shallowRender({ onOpen }); + wrapper.find('WorkspaceNavItem').prop('onOpen')(); + expect(onOpen).toBeCalledWith('foo'); +}); + +function shallowRender(props?: Partial) { + const component = { branchLike: undefined, key: 'foo' }; + return shallow( + + ); +} 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 index 00000000000..3f81b901171 --- /dev/null +++ b/server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceNavItem-test.tsx @@ -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) { + return shallow( + +
    + + ); +} 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 index 00000000000..15be6798e8b --- /dev/null +++ b/server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceNavRule-test.tsx @@ -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('onClose')(); + expect(onClose).toBeCalledWith('foo'); +}); + +it('should open', () => { + const onOpen = jest.fn(); + const wrapper = shallowRender({ onOpen }); + wrapper.find('WorkspaceNavItem').prop('onOpen')(); + expect(onOpen).toBeCalledWith('foo'); +}); + +function shallowRender(props?: Partial) { + const rule = { key: 'foo', organization: 'org' }; + return shallow( + + ); +} diff --git a/server/sonar-web/src/main/js/components/workspace/views/items-view.js b/server/sonar-web/src/main/js/components/workspace/__tests__/WorkspacePortal-test.tsx similarity index 69% rename from server/sonar-web/src/main/js/components/workspace/views/items-view.js rename to server/sonar-web/src/main/js/components/workspace/__tests__/WorkspacePortal-test.tsx index 06f3f9353bb..35b0909a04b 100644 --- a/server/sonar-web/src/main/js/components/workspace/views/items-view.js +++ b/server/sonar-web/src/main/js/components/workspace/__tests__/WorkspacePortal-test.tsx @@ -17,17 +17,13 @@ * 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'; +import * as React from 'react'; +import { shallow } from 'enzyme'; +import WorkspacePortal from '../WorkspacePortal'; -export default Marionette.CompositeView.extend({ - className: 'workspace-nav', - template: Template, - childViewContainer: '.workspace-nav-list', - childView: ItemView, - - childViewOptions() { - return { collectionView: this }; - } +it('should create portal element', () => { + const wrapper = shallow(); + 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 index 00000000000..e1b5f68840f --- /dev/null +++ b/server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceRuleDetails-test.tsx @@ -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( + + ); + expect(wrapper).toMatchSnapshot(); + + await waitAndUpdate(wrapper); + expect(wrapper).toMatchSnapshot(); +}); + +it('should call back on load', async () => { + const onLoad = jest.fn(); + const wrapper = shallow( + + ); + await waitAndUpdate(wrapper); + expect(onLoad).toBeCalledWith({ name: 'Foo' }); +}); diff --git a/server/sonar-web/src/main/js/components/workspace/views/item-view.js b/server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceRuleTitle-test.tsx similarity index 53% rename from server/sonar-web/src/main/js/components/workspace/views/item-view.js rename to server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceRuleTitle-test.tsx index b007f55b7b9..b12796f1129 100644 --- a/server/sonar-web/src/main/js/components/workspace/views/item-view.js +++ b/server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceRuleTitle-test.tsx @@ -17,41 +17,16 @@ * 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'; +import * as React from 'react'; +import { shallow } from 'enzyme'; +import WorkspaceRuleTitle from '../WorkspaceRuleTitle'; -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'); - }, +it('should render rule', () => { + const rule = { key: 'foo', organization: 'org' }; + expect(shallow()).toMatchSnapshot(); +}); - onViewerHide() { - this.$el.removeClass('hidden'); - } +it('should render loaded rule', () => { + const rule = { key: 'foo', name: 'Foo', organization: 'org' }; + expect(shallow()).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 index 00000000000..978b87768e4 --- /dev/null +++ b/server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceRuleViewer-test.tsx @@ -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('onClose')(); + expect(onClose).toBeCalledWith('foo'); +}); + +it('should call back after load', () => { + const onLoad = jest.fn(); + const wrapper = shallowRender({ onLoad }); + wrapper.find('WorkspaceRuleDetails').prop('onLoad')({ name: 'Foo' }); + expect(onLoad).toBeCalledWith({ key: 'foo', name: 'Foo' }); +}); + +function shallowRender(props?: Partial) { + const rule = { key: 'foo', organization: 'org' }; + return shallow( + + ); +} 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 index 00000000000..ed1cae463d9 --- /dev/null +++ b/server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceComponentTitle-test.tsx.snap @@ -0,0 +1,17 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render component 1`] = ` + + — + +`; + +exports[`should render loaded component 1`] = ` + + + src/foo.js + +`; 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 index 00000000000..221000a98f5 --- /dev/null +++ b/server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceComponentViewer-test.tsx.snap @@ -0,0 +1,37 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render 1`] = ` +
    + + + +
    + +
    +
    +`; 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 index 00000000000..11212c4c2e5 --- /dev/null +++ b/server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceHeader-test.tsx.snap @@ -0,0 +1,52 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render 1`] = ` +
    +
    +
    +
    + } + onDrag={[Function]} + onMouseDown={[Function]} + onStart={[Function]} + onStop={[Function]} + transform={null} + > +
    + +
    + + + +
    +
    +`; 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 index 00000000000..28dfed90df5 --- /dev/null +++ b/server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceNav-test.tsx.snap @@ -0,0 +1,111 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should not render open component 1`] = ` + +`; + +exports[`should not render open rule 1`] = ` + +`; + +exports[`should render 1`] = ` + +`; 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 index 00000000000..0f6abcbcdb4 --- /dev/null +++ b/server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceNavComponent-test.tsx.snap @@ -0,0 +1,18 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render 1`] = ` + + + +`; 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 index 00000000000..3834b942dba --- /dev/null +++ b/server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceNavItem-test.tsx.snap @@ -0,0 +1,26 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render 1`] = ` +
  • + +
    + + + + +
  • +`; 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 index 00000000000..2855436401d --- /dev/null +++ b/server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceNavRule-test.tsx.snap @@ -0,0 +1,18 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render 1`] = ` + + + +`; 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 index 00000000000..682c1d5ac77 --- /dev/null +++ b/server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceRuleDetails-test.tsx.snap @@ -0,0 +1,51 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render 1`] = ` + +`; + +exports[`should render 2`] = ` + + + + + + +`; 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 index 00000000000..5ba5fc924a9 --- /dev/null +++ b/server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceRuleTitle-test.tsx.snap @@ -0,0 +1,19 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render loaded rule 1`] = ` + + + Foo + +`; + +exports[`should render rule 1`] = ` + + + — + +`; 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 index 00000000000..f8bc63a9a99 --- /dev/null +++ b/server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceRuleViewer-test.tsx.snap @@ -0,0 +1,38 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render 1`] = ` +
    + + + +
    + +
    +
    +`; 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 index 00000000000..07dde023200 --- /dev/null +++ b/server/sonar-web/src/main/js/components/workspace/context.ts @@ -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 index b2fd7941ecf..00000000000 --- a/server/sonar-web/src/main/js/components/workspace/main.js +++ /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/items.js b/server/sonar-web/src/main/js/components/workspace/models/items.js deleted file mode 100644 index c191ca98a90..00000000000 --- a/server/sonar-web/src/main/js/components/workspace/models/items.js +++ /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); - } -}); diff --git a/server/sonar-web/src/main/js/components/workspace/styles.css b/server/sonar-web/src/main/js/components/workspace/styles.css index 170a4e2c986..229b996cdea 100644 --- a/server/sonar-web/src/main/js/components/workspace/styles.css +++ b/server/sonar-web/src/main/js/components/workspace/styles.css @@ -22,7 +22,7 @@ z-index: 451; bottom: 0; right: 0; - height: 30px; + height: 28px; } .workspace-nav-list { @@ -30,21 +30,33 @@ } .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 { @@ -92,13 +104,17 @@ .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 index a98380cb682..00000000000 --- a/server/sonar-web/src/main/js/components/workspace/templates/workspace-item.hbs +++ /dev/null @@ -1,3 +0,0 @@ -{{#if q}}{{qualifierIcon q}} {{/if}}{{#eq type 'rule'}} {{/eq}}{{limitString name}} - - 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 index 6fe99c04b19..00000000000 --- a/server/sonar-web/src/main/js/components/workspace/templates/workspace-items.hbs +++ /dev/null @@ -1 +0,0 @@ -
      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 index 304daeaca48..00000000000 --- a/server/sonar-web/src/main/js/components/workspace/templates/workspace-rule.hbs +++ /dev/null @@ -1,76 +0,0 @@ -
      - -
      - - {{#if exist}} - -
        - {{#if severity}} -
      • - {{issueTypeIcon this.type}}{{issueType this.type}} -
      • - -
      • - {{severityIcon severity}} {{t "severity" severity}} -
      • - {{/if}} - - {{#notEq status 'READY'}} -
      • {{status}}
      • - {{/notEq}} - -
      • - - {{#if allTags}}{{join allTags ', '}}{{else}}{{t 'coding_rules.no_tags'}}{{/if}} -
      • - -
      • {{t 'coding_rules.available_since'}} {{d createdAt}}
      • - - {{#if debtRemFnType}} -
      • - {{t 'coding_rules.remediation_function' debtRemFnType}}: - - {{#if debtRemFnOffset}}{{debtRemFnOffset}}{{/if}} - {{#if debtRemFnCoeff}}{{#if debtRemFnOffset}}+{{/if}}{{debtRemFnCoeff}}{{/if}} - {{#if effortToFixDescription}}{{effortToFixDescription}}{{/if}} -
      • - {{/if}} - -
      • - - - - - - {{key}} -
      • -
      - -
      {{{htmlDesc}}}
      - - {{#if htmlNote}} -
      -
      {{{htmlNote}}}
      -
      - {{/if}} - - {{else}} - - {{! does not exist}} -
      {{t 'workspace.no_rule'}}
      - - {{/if}} - -
      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 index 409dcd3a6cf..00000000000 --- a/server/sonar-web/src/main/js/components/workspace/templates/workspace-viewer-header.hbs +++ /dev/null @@ -1,17 +0,0 @@ -
      {{#if q}}{{qualifierIcon q}} {{/if}}{{#eq type 'rule'}} {{/eq}}{{name}}
      - -
      - -
      - - - - - - - -
      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 index 45515fbecb0..00000000000 --- a/server/sonar-web/src/main/js/components/workspace/templates/workspace-viewer.hbs +++ /dev/null @@ -1,3 +0,0 @@ -
      - -
      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 index 88c2dbb5b8f..00000000000 --- a/server/sonar-web/src/main/js/components/workspace/views/rule-view.js +++ /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 index 756ede70c71..00000000000 --- a/server/sonar-web/src/main/js/components/workspace/views/viewer-header-view.js +++ /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 index eafcac4460e..00000000000 --- a/server/sonar-web/src/main/js/components/workspace/views/viewer-view.js +++ /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( - - { - this.model.set({ name: component.name, q: component.q }); - if (line) { - this.scrollToLine(line); - } - }} - /> - , - el - ); - } -}); -- 2.39.5