From b27171e2644049cc08d9886183abb624ab2955ea Mon Sep 17 00:00:00 2001 From: Stas Vilchik Date: Thu, 20 Apr 2017 16:59:36 +0200 Subject: SONAR-9065 Display concise issues list when browsing code (#1953) --- .../src/main/js/apps/issues/components/App.js | 139 ++++++++++++++------- .../main/js/apps/issues/components/HeaderPanel.js | 106 ---------------- .../js/apps/issues/components/IssuesCounter.js | 41 ++++++ .../main/js/apps/issues/components/PageActions.js | 17 +-- .../js/apps/issues/conciseIssuesList/BackButton.js | 51 ++++++++ .../apps/issues/conciseIssuesList/ConciseIssue.js | 49 ++++++++ .../issues/conciseIssuesList/ConciseIssueBox.js | 54 ++++++++ .../conciseIssuesList/ConciseIssueComponent.js | 34 +++++ .../conciseIssuesList/ConciseIssueLocationBadge.js | 42 +++++++ .../conciseIssuesList/ConciseIssueLocations.js | 57 +++++++++ .../issues/conciseIssuesList/ConciseIssuesList.js | 84 +++++++++++++ .../conciseIssuesList/ConciseIssuesListHeader.js | 49 ++++++++ .../apps/issues/conciseIssuesList/ReloadButton.js | 51 ++++++++ .../__tests__/ConciseIssue-test.js | 41 ++++++ .../__tests__/ConciseIssueComponent-test.js | 29 +++++ .../__tests__/ConciseIssueLocationBadge-test.js | 27 ++++ .../__tests__/ConciseIssueLocations-test.js | 50 ++++++++ .../__tests__/ConciseIssuesList-test.js | 27 ++++ .../__snapshots__/ConciseIssue-test.js.snap | 9 ++ .../ConciseIssueComponent-test.js.snap | 6 + .../ConciseIssueLocationBadge-test.js.snap | 11 ++ .../ConciseIssueLocations-test.js.snap | 27 ++++ .../__snapshots__/ConciseIssuesList-test.js.snap | 26 ++++ .../sonar-web/src/main/js/apps/issues/styles.css | 126 +++++++++++++++++++ .../src/main/js/components/layout/PageSide.js | 4 +- .../src/main/js/components/shared/TypeHelper.js | 36 ++++++ 26 files changed, 1028 insertions(+), 165 deletions(-) delete mode 100644 server/sonar-web/src/main/js/apps/issues/components/HeaderPanel.js create mode 100644 server/sonar-web/src/main/js/apps/issues/components/IssuesCounter.js create mode 100644 server/sonar-web/src/main/js/apps/issues/conciseIssuesList/BackButton.js create mode 100644 server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssue.js create mode 100644 server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueBox.js create mode 100644 server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueComponent.js create mode 100644 server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueLocationBadge.js create mode 100644 server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueLocations.js create mode 100644 server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssuesList.js create mode 100644 server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssuesListHeader.js create mode 100644 server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ReloadButton.js create mode 100644 server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/ConciseIssue-test.js create mode 100644 server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/ConciseIssueComponent-test.js create mode 100644 server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/ConciseIssueLocationBadge-test.js create mode 100644 server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/ConciseIssueLocations-test.js create mode 100644 server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/ConciseIssuesList-test.js create mode 100644 server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssue-test.js.snap create mode 100644 server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssueComponent-test.js.snap create mode 100644 server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssueLocationBadge-test.js.snap create mode 100644 server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssueLocations-test.js.snap create mode 100644 server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssuesList-test.js.snap create mode 100644 server/sonar-web/src/main/js/apps/issues/styles.css create mode 100644 server/sonar-web/src/main/js/components/shared/TypeHelper.js (limited to 'server/sonar-web/src/main/js') diff --git a/server/sonar-web/src/main/js/apps/issues/components/App.js b/server/sonar-web/src/main/js/apps/issues/components/App.js index efdfd182789..ebac6e45d41 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/App.js +++ b/server/sonar-web/src/main/js/apps/issues/components/App.js @@ -22,7 +22,6 @@ import React from 'react'; import Helmet from 'react-helmet'; import key from 'keymaster'; import { keyBy, without } from 'lodash'; -import HeaderPanel from './HeaderPanel'; import PageActions from './PageActions'; import FiltersHeader from './FiltersHeader'; import MyIssuesFilter from './MyIssuesFilter'; @@ -31,6 +30,8 @@ import IssuesList from './IssuesList'; import ComponentBreadcrumbs from './ComponentBreadcrumbs'; import IssuesSourceViewer from './IssuesSourceViewer'; import BulkChangeModal from './BulkChangeModal'; +import ConciseIssuesList from '../conciseIssuesList/ConciseIssuesList'; +import ConciseIssuesListHeader from '../conciseIssuesList/ConciseIssuesListHeader'; import { parseQuery, areMyIssuesSelected, @@ -59,6 +60,7 @@ import PageFilters from '../../../components/layout/PageFilters'; import { translate, translateWithParameters } from '../../../helpers/l10n'; import { scrollToElement } from '../../../helpers/scrolling'; import type { Issue } from '../../../components/issue/types'; +import '../styles.css'; type Props = { component?: Component, @@ -304,7 +306,7 @@ export default class App extends React.PureComponent { fetchFirstIssues() { this.setState({ loading: true }); - this.fetchIssues({}, true).then(({ facets, issues, paging, ...other }) => { + return this.fetchIssues({}, true).then(({ facets, issues, paging, ...other }) => { if (this.mounted) { const open = getOpen(this.props.location.query); this.setState({ @@ -321,6 +323,7 @@ export default class App extends React.PureComponent { : undefined }); } + return issues; }); } @@ -497,6 +500,14 @@ export default class App extends React.PureComponent { this.closeBulkChange(); }; + handleReloadAndOpenFirst = () => { + this.fetchFirstIssues().then(issues => { + if (issues.length > 0) { + this.openIssue(issues[0].key); + } + }); + }; + renderBulkChange(openIssue?: Issue) { const { component, currentUser } = this.props; const { bulkChange, checked, paging } = this.state; @@ -542,6 +553,69 @@ export default class App extends React.PureComponent { ); } + renderFacets() { + const { component, currentUser } = this.props; + const { query } = this.state; + + return ( + + {currentUser.isLoggedIn && + } + + + + ); + } + + renderConciseIssuesList() { + const { issues, paging } = this.state; + + return ( + + + + {paging != null && + paging.total > 0 && + } + + ); + } + + renderSide(openIssue?: Issue) { + const top = this.props.component ? 95 : 30; + + return ( + + {openIssue == null ? this.renderFacets() : this.renderConciseIssuesList()} + + ); + } + renderList(openIssue?: Issue) { const { component, currentUser } = this.props; const { issues, paging } = this.state; @@ -575,60 +649,37 @@ export default class App extends React.PureComponent { } render() { - const { component, currentUser } = this.props; - const { issues, paging, query } = this.state; + const { component } = this.props; + const { issues, paging } = this.state; const open = getOpen(this.props.location.query); const openIssue = issues.find(issue => issue.key === open); const selectedIndex = this.getSelectedIndex(); - const top = component ? 95 : 30; - return ( - - - {currentUser.isLoggedIn && - } - - - - + {this.renderSide(openIssue)} - - - {this.renderBulkChange(openIssue)} - {openIssue != null && -
- -
} - -
-
+
+
+ + {this.renderBulkChange(openIssue)} + {openIssue != null + ?
+ +
+ : } +
+
+
diff --git a/server/sonar-web/src/main/js/apps/issues/components/HeaderPanel.js b/server/sonar-web/src/main/js/apps/issues/components/HeaderPanel.js deleted file mode 100644 index 2d8530dab51..00000000000 --- a/server/sonar-web/src/main/js/apps/issues/components/HeaderPanel.js +++ /dev/null @@ -1,106 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2017 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 React from 'react'; -import { css, media } from 'glamor'; -import { clearfix } from 'glamor/utils'; -import { throttle } from 'lodash'; - -type Props = {| - border: boolean, - children?: React.Element<*>, - top?: number -|}; - -type State = { - scrolled: boolean -}; - -export default class HeaderPanel extends React.PureComponent { - props: Props; - state: State; - - constructor(props: Props) { - super(props); - this.state = { scrolled: this.isScrolled() }; - this.handleScroll = throttle(this.handleScroll, 50); - } - - componentDidMount() { - if (this.props.top != null) { - window.addEventListener('scroll', this.handleScroll); - } - } - - componentWillUnmount() { - if (this.props.top != null) { - window.removeEventListener('scroll', this.handleScroll); - } - } - - isScrolled = () => window.scrollY > 10; - - handleScroll = () => { - this.setState({ scrolled: this.isScrolled() }); - }; - - render() { - const commonStyles = { - height: 56, - lineHeight: '24px', - padding: '16px 20px', - boxSizing: 'border-box', - borderBottom: this.props.border ? '1px solid #e6e6e6' : undefined, - backgroundColor: '#f3f3f3' - }; - - const inner = this.props.top - ?
- {this.props.children} -
- : this.props.children; - - return ( -
- {inner} -
- ); - } -} diff --git a/server/sonar-web/src/main/js/apps/issues/components/IssuesCounter.js b/server/sonar-web/src/main/js/apps/issues/components/IssuesCounter.js new file mode 100644 index 00000000000..0c6814735e1 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/components/IssuesCounter.js @@ -0,0 +1,41 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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 React from 'react'; +import { translate } from '../../../helpers/l10n'; +import { formatMeasure } from '../../../helpers/measures'; + +type Props = { + current: ?number, + total: number +}; + +const IssuesCounter = (props: Props) => ( + + + {props.current != null && {props.current + 1} / } + {formatMeasure(props.total, 'INT')} + + {' '} + {translate('issues.issues')} + +); + +export default IssuesCounter; diff --git a/server/sonar-web/src/main/js/apps/issues/components/PageActions.js b/server/sonar-web/src/main/js/apps/issues/components/PageActions.js index d1b1d6cfdd8..dcefa0a7d1b 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/PageActions.js +++ b/server/sonar-web/src/main/js/apps/issues/components/PageActions.js @@ -20,13 +20,12 @@ // @flow import React from 'react'; import { css } from 'glamor'; +import IssuesCounter from './IssuesCounter'; import type { Paging } from '../utils'; import { translate } from '../../../helpers/l10n'; -import { formatMeasure } from '../../../helpers/measures'; type Props = {| loading: boolean, - openIssue: ?{}, paging: ?Paging, selectedIndex: ?number |}; @@ -53,23 +52,15 @@ export default class PageActions extends React.PureComponent { } render() { - const { openIssue, paging, selectedIndex } = this.props; + const { paging, selectedIndex } = this.props; return (
- {openIssue == null && this.renderShortcuts()} + {this.renderShortcuts()}
{this.props.loading && } - {paging != null && - - - {selectedIndex != null && {selectedIndex + 1} / } - {formatMeasure(paging.total, 'INT')} - - {' '} - {translate('issues.issues')} - } + {paging != null && }
); diff --git a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/BackButton.js b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/BackButton.js new file mode 100644 index 00000000000..f621e0a83ea --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/BackButton.js @@ -0,0 +1,51 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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 React from 'react'; +import classNames from 'classnames'; + +type Props = {| + className?: string, + onClick: () => void +|}; + +/* eslint-disable max-len */ +const icon = ( + + + +); +/* eslint-enable max-len */ + +export default function BackButton(props: Props) { + const handleClick = (event: Event) => { + event.preventDefault(); + props.onClick(); + }; + + return ( + + {icon} + + ); +} diff --git a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssue.js b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssue.js new file mode 100644 index 00000000000..94c1d531827 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssue.js @@ -0,0 +1,49 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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 React from 'react'; +import ConciseIssueBox from './ConciseIssueBox'; +import ConciseIssueComponent from './ConciseIssueComponent'; +import type { Issue } from '../../../components/issue/types'; + +type Props = {| + innerRef: HTMLElement => void, + issue: Issue, + onSelect: string => void, + previousIssue: ?Issue, + selected: boolean +|}; + +export default class ConciseIssue extends React.PureComponent { + props: Props; + + render() { + const { issue, previousIssue, selected } = this.props; + + const displayComponent = previousIssue == null || previousIssue.component !== issue.component; + + return ( +
+ {displayComponent && } + +
+ ); + } +} diff --git a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueBox.js b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueBox.js new file mode 100644 index 00000000000..d0f09aa95d0 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueBox.js @@ -0,0 +1,54 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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 React from 'react'; +import classNames from 'classnames'; +import ConciseIssueLocations from './ConciseIssueLocations'; +import SeverityHelper from '../../../components/shared/SeverityHelper'; +import TypeHelper from '../../../components/shared/TypeHelper'; +import type { Issue } from '../../../components/issue/types'; + +type Props = {| + issue: Issue, + onClick: string => void, + selected: boolean +|}; + +export default function ConciseIssueBox(props: Props) { + const { issue, selected } = props; + + const handleClick = (event: Event) => { + event.preventDefault(); + props.onClick(issue.key); + }; + + const clickAttributes = selected ? {} : { onClick: handleClick, role: 'listitem', tabIndex: 0 }; + + return ( +
+
{issue.message}
+
+ + + +
+
+ ); +} diff --git a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueComponent.js b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueComponent.js new file mode 100644 index 00000000000..17337603555 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueComponent.js @@ -0,0 +1,34 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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 React from 'react'; +import { collapsePath } from '../../../helpers/path'; + +type Props = { + path: string +}; + +const ConciseIssueComponent = (props: Props) => ( +
+ {collapsePath(props.path, 20)} +
+); + +export default ConciseIssueComponent; diff --git a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueLocationBadge.js b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueLocationBadge.js new file mode 100644 index 00000000000..fe8cbb7eecc --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueLocationBadge.js @@ -0,0 +1,42 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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 React from 'react'; +import Tooltip from '../../../components/controls/Tooltip'; +import { translateWithParameters } from '../../../helpers/l10n'; +import { formatMeasure } from '../../../helpers/measures'; + +type Props = {| + count: number +|}; + +export default function ConciseIssueLocationBadge(props: Props) { + return ( + +
+ {'+'}{props.count} +
+
+ ); +} diff --git a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueLocations.js b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueLocations.js new file mode 100644 index 00000000000..aedc4c5a261 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueLocations.js @@ -0,0 +1,57 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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 React from 'react'; +import ConciseIssueLocationBadge from './ConciseIssueLocationBadge'; +import type { FlowLocation } from '../../../components/issue/types'; + +type Props = {| + flows: Array<{ + locations?: Array + }> +|}; + +export default class ConciseIssueLocations extends React.PureComponent { + props: Props; + + render() { + const { flows } = this.props; + + const secondaryLocations = flows.filter( + flow => flow.locations != null && flow.locations.length === 1 + ).length; + + const realFlows = flows.filter(flow => flow.locations != null && flow.locations.length > 1); + + return ( +
+ {secondaryLocations > 0 && } + + {realFlows.map((flow, index) => ( + + ))} +
+ ); + } +} diff --git a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssuesList.js b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssuesList.js new file mode 100644 index 00000000000..055d1aba2e7 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssuesList.js @@ -0,0 +1,84 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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 React from 'react'; +import ConciseIssue from './ConciseIssue'; +import { scrollToElement } from '../../../helpers/scrolling'; +import type { Issue } from '../../../components/issue/types'; + +type Props = {| + issues: Array, + onIssueSelect: string => void, + selected?: string +|}; + +export default class ConciseIssuesList extends React.PureComponent { + nodes: { [string]: HTMLElement }; + props: Props; + + constructor(props: Props) { + super(props); + this.nodes = {}; + } + + componentDidMount() { + if (this.props.selected) { + this.ensureSelectedVisible(); + } + } + + componentDidUpdate(prevProps: Props) { + if (this.props.selected && prevProps.selected !== this.props.selected) { + this.ensureSelectedVisible(); + } + } + + ensureSelectedVisible() { + const { selected } = this.props; + if (selected) { + const scrollableElement = document.querySelector('.layout-page-side'); + const element = this.nodes[selected]; + if (element && scrollableElement) { + scrollToElement(element, 150, 100, scrollableElement); + } + } + } + + innerRef = (issue: string) => (node: HTMLElement) => { + this.nodes[issue] = node; + }; + + render() { + return ( +
+ {this.props.issues.map((issue, index) => ( + 0 ? this.props.issues[index - 1] : null} + selected={issue.key === this.props.selected} + /> + ))} +
+ ); + } +} diff --git a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssuesListHeader.js b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssuesListHeader.js new file mode 100644 index 00000000000..2be4b44732e --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssuesListHeader.js @@ -0,0 +1,49 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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 React from 'react'; +import BackButton from './BackButton'; +import ReloadButton from './ReloadButton'; +import IssuesCounter from '../components/IssuesCounter'; +import type { Paging } from '../utils'; + +type Props = {| + loading: boolean, + onBackClick: () => void, + onReload: () => void, + paging?: Paging, + selectedIndex: ?number +|}; + +export default function ConciseIssuesListHeader(props: Props) { + const { paging, selectedIndex } = props; + + return ( +
+
+ + {props.loading + ? + : } + {paging != null && } +
+
+ ); +} diff --git a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ReloadButton.js b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ReloadButton.js new file mode 100644 index 00000000000..0034fad7d48 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ReloadButton.js @@ -0,0 +1,51 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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 React from 'react'; +import classNames from 'classnames'; + +type Props = {| + className?: string, + onClick: () => void +|}; + +/* eslint-disable max-len */ +const icon = ( + + + +); +/* eslint-enable max-len */ + +export default function ReloadButton(props: Props) { + const handleClick = (event: Event) => { + event.preventDefault(); + props.onClick(); + }; + + return ( + + {icon} + + ); +} diff --git a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/ConciseIssue-test.js b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/ConciseIssue-test.js new file mode 100644 index 00000000000..3de6cb4120b --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/ConciseIssue-test.js @@ -0,0 +1,41 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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 React from 'react'; +import { shallow } from 'enzyme'; +import ConciseIssue from '../ConciseIssue'; + +it('should render', () => { + expect( + shallow() + ).toMatchSnapshot(); +}); + +it('should not render component', () => { + expect( + shallow( + + ).find('ConciseIssueComponent') + ).toHaveLength(0); +}); diff --git a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/ConciseIssueComponent-test.js b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/ConciseIssueComponent-test.js new file mode 100644 index 00000000000..f8db79b215b --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/ConciseIssueComponent-test.js @@ -0,0 +1,29 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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 React from 'react'; +import { shallow } from 'enzyme'; +import ConciseIssueComponent from '../ConciseIssueComponent'; + +it('should render', () => { + expect( + shallow() + ).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/ConciseIssueLocationBadge-test.js b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/ConciseIssueLocationBadge-test.js new file mode 100644 index 00000000000..ff27fa03501 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/ConciseIssueLocationBadge-test.js @@ -0,0 +1,27 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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 React from 'react'; +import { shallow } from 'enzyme'; +import ConciseIssueLocationBadge from '../ConciseIssueLocationBadge'; + +it('should render', () => { + expect(shallow()).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/ConciseIssueLocations-test.js b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/ConciseIssueLocations-test.js new file mode 100644 index 00000000000..400c17089a4 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/ConciseIssueLocations-test.js @@ -0,0 +1,50 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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 React from 'react'; +import { shallow } from 'enzyme'; +import ConciseIssueLocations from '../ConciseIssueLocations'; + +const textRange = { startLine: 1, startOffset: 1, endLine: 1, endOffset: 1 }; + +it('should render only secondary locations', () => { + const flows = [ + { locations: [{ msg: '', textRange }] }, + { locations: [{ msg: '', textRange }] }, + { locations: [{ msg: '', textRange }] } + ]; + expect(shallow()).toMatchSnapshot(); +}); + +it('should render one flow', () => { + const flows = [ + { locations: [{ msg: '', textRange }, { msg: '', textRange }, { msg: '', textRange }] } + ]; + expect(shallow()).toMatchSnapshot(); +}); + +it('should render several flows', () => { + const flows = [ + { locations: [{ msg: '', textRange }, { msg: '', textRange }, { msg: '', textRange }] }, + { locations: [{ msg: '', textRange }, { msg: '', textRange }] }, + { locations: [{ msg: '', textRange }, { msg: '', textRange }, { msg: '', textRange }] } + ]; + expect(shallow()).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/ConciseIssuesList-test.js b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/ConciseIssuesList-test.js new file mode 100644 index 00000000000..f57f5abe469 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/ConciseIssuesList-test.js @@ -0,0 +1,27 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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 React from 'react'; +import { shallow } from 'enzyme'; +import ConciseIssuesList from '../ConciseIssuesList'; + +it('should render', () => { + const issues = [{ key: 'foo' }, { key: 'bar' }]; + expect(shallow()).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssue-test.js.snap b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssue-test.js.snap new file mode 100644 index 00000000000..a85d767247e --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssue-test.js.snap @@ -0,0 +1,9 @@ +exports[`test should render 1`] = ` +
+ + +
+`; diff --git a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssueComponent-test.js.snap b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssueComponent-test.js.snap new file mode 100644 index 00000000000..5565dc617c9 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssueComponent-test.js.snap @@ -0,0 +1,6 @@ +exports[`test should render 1`] = ` +
+ src/.../long-folder/name/app.js +
+`; diff --git a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssueLocationBadge-test.js.snap b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssueLocationBadge-test.js.snap new file mode 100644 index 00000000000..654a5c5e517 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssueLocationBadge-test.js.snap @@ -0,0 +1,11 @@ +exports[`test should render 1`] = ` + +
+ + + 7 +
+
+`; diff --git a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssueLocations-test.js.snap b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssueLocations-test.js.snap new file mode 100644 index 00000000000..cbd406bb647 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssueLocations-test.js.snap @@ -0,0 +1,27 @@ +exports[`test should render one flow 1`] = ` +
+ +
+`; + +exports[`test should render only secondary locations 1`] = ` +
+ +
+`; + +exports[`test should render several flows 1`] = ` +
+ + + +
+`; diff --git a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssuesList-test.js.snap b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssuesList-test.js.snap new file mode 100644 index 00000000000..1db833d56a7 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssuesList-test.js.snap @@ -0,0 +1,26 @@ +exports[`test should render 1`] = ` +
+ + +
+`; diff --git a/server/sonar-web/src/main/js/apps/issues/styles.css b/server/sonar-web/src/main/js/apps/issues/styles.css new file mode 100644 index 00000000000..260e667fc5b --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/styles.css @@ -0,0 +1,126 @@ +.issues-header-panel, +.issues-header-panel-inner { + height: 56px; + box-sizing: border-box; +} + +.issues-header-panel { + margin-top: -20px; +} + +.issues-header-panel-inner { + position: fixed; + z-index: 30; + line-height: 24px; + padding-top: 16px; + padding-bottom: 16px; + border-bottom: 1px solid #e6e6e6; + background-color: #f3f3f3; +} + +.issues-main-header { + margin-bottom: 20px; +} + +.issues-main-header .component-name { + line-height: 24px; +} + +.issues-main-header-inner { + left: calc(50vw - 360px + 1px); + right: 0; + padding-left: 20px; + padding-right: 20px; +} + +@media (max-width: 1320px) { + .issues-main-header-inner { + left: 301px; + } +} + +.concise-issues-list-header, +.concise-issues-list-header-inner { +} + +.concise-issues-list-header { +} + +.concise-issues-list-header-inner { + width: 260px; + text-align: center; +} + +.concise-issues-list-header .spinner { + margin-top: 4px; + margin-left: 1px; + margin-right: 1px; +} + +.concise-issues-list-header-button { + border: none; +} + +.concise-issues-list-header-button path { + fill: #777; + transition: fill 0.3s ease; +} + +.concise-issues-list-header-button:hover path { + fill: #4b9fd5; +} + +.concise-issue-component { + margin-top: 16px; + margin-bottom: 4px; + padding-left: 8px; + padding-right: 8px; +} + +.concise-issue-box { + position: relative; + z-index: 1; + margin-bottom: 4px; + padding: 8px; + border: 1px solid #e6e6e6; + background-color: #fff; + cursor: pointer; + transition: background-color 0.3s ease, border-color 0.3s ease; +} + +.concise-issue-box:hover, +.concise-issue-box:focus { + background-color: #ffeaea; + outline: none +} + +.concise-issue-box.selected { + z-index: 2; + border-color: #dd4040; + background-color: #ffeaea; + cursor: default +} + +.concise-issue-box-message { + font-weight: bold; +} + +.concise-issue-box-attributes { + margin-top: 8px; + line-height: 16px; + font-size: 12px; +} + +.concise-issue-location-badge { + display: inline-block; + padding-left: 4px; + padding-right: 4px; + border-radius: 2px; + background-color: #ccc; + color: #fff; + transition: background-color 0.3s ease; +} + +.concise-issue-box.selected .concise-issue-location-badge { + background-color: #d18582; +} \ No newline at end of file diff --git a/server/sonar-web/src/main/js/components/layout/PageSide.js b/server/sonar-web/src/main/js/components/layout/PageSide.js index 24d810075ca..a647d83c0c1 100644 --- a/server/sonar-web/src/main/js/components/layout/PageSide.js +++ b/server/sonar-web/src/main/js/components/layout/PageSide.js @@ -36,7 +36,6 @@ const width = css( const sideStyles = css(width, { flexGrow: 0, flexShrink: 0, - borderRight: '1px solid #e6e6e6', backgroundColor: '#f3f3f3' }); @@ -46,6 +45,7 @@ const sideStickyStyles = css(width, { top: 0, bottom: 0, left: 0, + borderRight: '1px solid #e6e6e6', overflowY: 'auto', overflowX: 'hidden', backgroundColor: '#f3f3f3' @@ -63,7 +63,7 @@ const sideInnerStyles = css( export default function PageSide(props: Props) { return (
-
+
{props.children}
diff --git a/server/sonar-web/src/main/js/components/shared/TypeHelper.js b/server/sonar-web/src/main/js/components/shared/TypeHelper.js new file mode 100644 index 00000000000..f984e4f4e66 --- /dev/null +++ b/server/sonar-web/src/main/js/components/shared/TypeHelper.js @@ -0,0 +1,36 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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 React from 'react'; +import IssueTypeIcon from '../ui/IssueTypeIcon'; +import { translate } from '../../helpers/l10n'; + +type Props = { + type: string +}; + +const TypeHelper = (props: Props) => ( + + + {translate('issue.type', props.type)} + +); + +export default TypeHelper; -- cgit v1.2.3