diff options
author | Stas Vilchik <stas-vilchik@users.noreply.github.com> | 2017-04-20 16:59:36 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-04-20 16:59:36 +0200 |
commit | b27171e2644049cc08d9886183abb624ab2955ea (patch) | |
tree | 9037545fc3e3a6fbfec1a0994600cf116460d01c /server/sonar-web/src/main/js | |
parent | 7891bd3a71b0aec2d87a413f61e3c9859925717e (diff) | |
download | sonarqube-b27171e2644049cc08d9886183abb624ab2955ea.tar.gz sonarqube-b27171e2644049cc08d9886183abb624ab2955ea.zip |
SONAR-9065 Display concise issues list when browsing code (#1953)
Diffstat (limited to 'server/sonar-web/src/main/js')
26 files changed, 1028 insertions, 165 deletions
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 ( + <PageFilters> + {currentUser.isLoggedIn && + <MyIssuesFilter + myIssues={this.state.myIssues} + onMyIssuesChange={this.handleMyIssuesChange} + />} + <FiltersHeader displayReset={this.isFiltered()} onReset={this.handleReset} /> + <Sidebar + component={component} + facets={this.state.facets} + myIssues={this.state.myIssues} + onFacetToggle={this.handleFacetToggle} + onFilterChange={this.handleFilterChange} + openFacets={this.state.openFacets} + query={query} + referencedComponents={this.state.referencedComponents} + referencedLanguages={this.state.referencedLanguages} + referencedRules={this.state.referencedRules} + referencedUsers={this.state.referencedUsers} + /> + </PageFilters> + ); + } + + renderConciseIssuesList() { + const { issues, paging } = this.state; + + return ( + <PageFilters> + <ConciseIssuesListHeader + loading={this.state.loading} + onBackClick={this.closeIssue} + onReload={this.handleReloadAndOpenFirst} + paging={paging} + selectedIndex={this.getSelectedIndex()} + /> + <ConciseIssuesList + issues={issues} + onIssueSelect={this.openIssue} + selected={this.state.selected} + /> + {paging != null && + paging.total > 0 && + <ListFooter total={paging.total} count={issues.length} loadMore={this.fetchMoreIssues} />} + </PageFilters> + ); + } + + renderSide(openIssue?: Issue) { + const top = this.props.component ? 95 : 30; + + return ( + <PageSide top={top}> + {openIssue == null ? this.renderFacets() : this.renderConciseIssuesList()} + </PageSide> + ); + } + 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 ( <Page className="issues" id="issues-page"> <Helmet title={translate('issues.page')} titleTemplate="%s - SonarQube" /> - <PageSide top={top}> - <PageFilters> - {currentUser.isLoggedIn && - <MyIssuesFilter - myIssues={this.state.myIssues} - onMyIssuesChange={this.handleMyIssuesChange} - />} - <FiltersHeader displayReset={this.isFiltered()} onReset={this.handleReset} /> - <Sidebar - component={component} - facets={this.state.facets} - myIssues={this.state.myIssues} - onFacetToggle={this.handleFacetToggle} - onFilterChange={this.handleFilterChange} - openFacets={this.state.openFacets} - query={query} - referencedComponents={this.state.referencedComponents} - referencedLanguages={this.state.referencedLanguages} - referencedRules={this.state.referencedRules} - referencedUsers={this.state.referencedUsers} - /> - </PageFilters> - </PageSide> + {this.renderSide(openIssue)} <PageMain> - <HeaderPanel border={true} top={top}> - <PageMainInner> - {this.renderBulkChange(openIssue)} - {openIssue != null && - <div className="pull-left"> - <ComponentBreadcrumbs component={component} issue={openIssue} /> - </div>} - <PageActions - loading={this.state.loading} - openIssue={openIssue} - paging={paging} - selectedIndex={selectedIndex} - /> - </PageMainInner> - </HeaderPanel> + <div className="issues-header-panel issues-main-header"> + <div className="issues-header-panel-inner issues-main-header-inner"> + <PageMainInner> + {this.renderBulkChange(openIssue)} + {openIssue != null + ? <div className="pull-left"> + <ComponentBreadcrumbs component={component} issue={openIssue} /> + </div> + : <PageActions + loading={this.state.loading} + paging={paging} + selectedIndex={selectedIndex} + />} + </PageMainInner> + </div> + </div> <PageMainInner> <div> 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 - ? <div - className={css( - commonStyles, - { - position: 'fixed', - zIndex: 30, - top: this.props.top, - left: 'calc(50vw - 360px + 1px)', - right: 0, - boxShadow: this.state.scrolled ? '0 2px 4px rgba(0, 0, 0, .125)' : 'none', - transition: 'box-shadow 0.3s ease' - }, - media('(max-width: 1320px)', { left: 301 }) - )}> - {this.props.children} - </div> - : this.props.children; - - return ( - <div - className={css(clearfix(), commonStyles, { - marginTop: -20, - marginBottom: 20, - marginLeft: -20, - marginRight: -20, - '& .component-name': { lineHeight: '24px' } - })}> - {inner} - </div> - ); - } -} 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) => ( + <span> + <strong> + {props.current != null && <span>{props.current + 1} / </span>} + {formatMeasure(props.total, 'INT')} + </strong> + {' '} + {translate('issues.issues')} + </span> +); + +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 ( <div className={css({ float: 'right' })}> - {openIssue == null && this.renderShortcuts()} + {this.renderShortcuts()} <div className={css({ display: 'inline-block', minWidth: 80, textAlign: 'right' })}> {this.props.loading && <i className="spinner spacer-right" />} - {paging != null && - <span> - <strong> - {selectedIndex != null && <span>{selectedIndex + 1} / </span>} - {formatMeasure(paging.total, 'INT')} - </strong> - {' '} - {translate('issues.issues')} - </span>} + {paging != null && <IssuesCounter current={selectedIndex} total={paging.total} />} </div> </div> ); 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 = ( + <svg width="21" height="24" viewBox="0 0 21 24"> + <path d="M3.845 12.9992l5.993 5.993.052.056c.049.061.093.122.129.191.082.159.121.339.111.518-.006.102-.028.203-.064.298-.149.39-.537.652-.954.644-.102-.002-.204-.019-.301-.052-.148-.05-.273-.135-.387-.241l-8.407-8.407 8.407-8.407.056-.052c.061-.048.121-.092.19-.128.116-.06.237-.091.366-.108.076-.004.075-.004.153-.003.155.015.3.052.437.129.088.051.169.115.239.19.246.266.33.656.214.999-.051.149-.135.273-.241.387l-5.983 5.984c5.287-.044 10.577-.206 15.859.013.073.009.091.009.163.027.187.047.359.15.49.292.075.081.136.175.18.276.044.101.072.209.081.319.032.391-.175.775-.521.962-.097.052-.202.089-.311.107-.073.012-.091.01-.165.013H3.845z" /> + </svg> +); +/* eslint-enable max-len */ + +export default function BackButton(props: Props) { + const handleClick = (event: Event) => { + event.preventDefault(); + props.onClick(); + }; + + return ( + <a + className={classNames('concise-issues-list-header-button', props.className)} + href="#" + onClick={handleClick}> + {icon} + </a> + ); +} 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 ( + <div ref={this.props.innerRef}> + {displayComponent && <ConciseIssueComponent path={issue.componentLongName} />} + <ConciseIssueBox issue={issue} onClick={this.props.onSelect} selected={selected} /> + </div> + ); + } +} 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 ( + <div className={classNames('concise-issue-box', { selected })} {...clickAttributes}> + <div className="concise-issue-box-message">{issue.message}</div> + <div className="concise-issue-box-attributes"> + <TypeHelper type={issue.type} /> + <SeverityHelper className="big-spacer-left" severity={issue.severity} /> + <ConciseIssueLocations flows={issue.flows} /> + </div> + </div> + ); +} 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) => ( + <div className="concise-issue-component note text-ellipsis"> + {collapsePath(props.path, 20)} + </div> +); + +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 ( + <Tooltip + overlay={translateWithParameters( + 'issue.this_issue_involves_x_code_locations', + formatMeasure(props.count) + )}> + <div className="concise-issue-location-badge"> + {'+'}{props.count} + </div> + </Tooltip> + ); +} 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<FlowLocation> + }> +|}; + +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 ( + <div className="pull-right"> + {secondaryLocations > 0 && <ConciseIssueLocationBadge count={secondaryLocations} />} + + {realFlows.map((flow, index) => ( + <ConciseIssueLocationBadge + // $FlowFixMe locations are not null + count={flow.locations.length} + key={index} + /> + ))} + </div> + ); + } +} 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<Issue>, + 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 ( + <div> + {this.props.issues.map((issue, index) => ( + <ConciseIssue + key={issue.key} + innerRef={this.innerRef(issue.key)} + issue={issue} + onSelect={this.props.onIssueSelect} + previousIssue={index > 0 ? this.props.issues[index - 1] : null} + selected={issue.key === this.props.selected} + /> + ))} + </div> + ); + } +} 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 ( + <header className="issues-header-panel concise-issues-list-header"> + <div className="issues-header-panel-inner concise-issues-list-header-inner"> + <BackButton className="pull-left" onClick={props.onBackClick} /> + {props.loading + ? <i className="spinner pull-right" /> + : <ReloadButton className="pull-right" onClick={props.onReload} />} + {paging != null && <IssuesCounter current={selectedIndex} total={paging.total} />} + </div> + </header> + ); +} 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 = ( + <svg width="18" height="24" viewBox="0 0 18 24"> + <path d="M16.6454 8.1084c-.3-.5-.9-.7-1.4-.4-.5.3-.7.9-.4 1.4.9 1.6 1.1 3.4.6 5.1-.5 1.7-1.7 3.2-3.2 4-3.3 1.8-7.4.6-9.1-2.7-1.8-3.1-.8-6.9 2.1-8.8v3.3h2v-7h-7v2h3.9c-3.7 2.5-5 7.5-2.8 11.4 1.6 3 4.6 4.6 7.7 4.6 1.4 0 2.8-.3 4.2-1.1 2-1.1 3.5-3 4.2-5.2.6-2.2.3-4.6-.8-6.6z" /> + </svg> +); +/* eslint-enable max-len */ + +export default function ReloadButton(props: Props) { + const handleClick = (event: Event) => { + event.preventDefault(); + props.onClick(); + }; + + return ( + <a + className={classNames('concise-issues-list-header-button', props.className)} + href="#" + onClick={handleClick}> + {icon} + </a> + ); +} 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(<ConciseIssue issue={{}} onSelect={jest.fn()} selected={false} />) + ).toMatchSnapshot(); +}); + +it('should not render component', () => { + expect( + shallow( + <ConciseIssue + issue={{ component: 'foo' }} + onSelect={jest.fn()} + previousIssue={{ component: 'foo' }} + selected={false} + /> + ).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(<ConciseIssueComponent path="src/app/folder/sub-folder/long-folder/name/app.js" />) + ).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(<ConciseIssueLocationBadge count={7} />)).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(<ConciseIssueLocations flows={flows} />)).toMatchSnapshot(); +}); + +it('should render one flow', () => { + const flows = [ + { locations: [{ msg: '', textRange }, { msg: '', textRange }, { msg: '', textRange }] } + ]; + expect(shallow(<ConciseIssueLocations flows={flows} />)).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(<ConciseIssueLocations flows={flows} />)).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(<ConciseIssuesList issues={issues} />)).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`] = ` +<div> + <ConciseIssueComponent /> + <ConciseIssueBox + issue={Object {}} + onClick={[Function]} + selected={false} /> +</div> +`; 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`] = ` +<div + className="concise-issue-component note text-ellipsis"> + src/.../long-folder/name/app.js +</div> +`; 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`] = ` +<Tooltip + overlay="issue.this_issue_involves_x_code_locations.7" + placement="bottom"> + <div + className="concise-issue-location-badge"> + + + 7 + </div> +</Tooltip> +`; 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`] = ` +<div + className="pull-right"> + <ConciseIssueLocationBadge + count={3} /> +</div> +`; + +exports[`test should render only secondary locations 1`] = ` +<div + className="pull-right"> + <ConciseIssueLocationBadge + count={3} /> +</div> +`; + +exports[`test should render several flows 1`] = ` +<div + className="pull-right"> + <ConciseIssueLocationBadge + count={3} /> + <ConciseIssueLocationBadge + count={2} /> + <ConciseIssueLocationBadge + count={3} /> +</div> +`; 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`] = ` +<div> + <ConciseIssue + innerRef={[Function]} + issue={ + Object { + "key": "foo", + } + } + previousIssue={null} + selected={false} /> + <ConciseIssue + innerRef={[Function]} + issue={ + Object { + "key": "bar", + } + } + previousIssue={ + Object { + "key": "foo", + } + } + selected={false} /> +</div> +`; 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 ( <div className={sideStyles}> - <div className={sideStickyStyles} style={{ top: props.top || 30 }}> + <div className={`layout-page-side ${sideStickyStyles}`} style={{ top: props.top || 30 }}> <div className={sideInnerStyles}> {props.children} </div> 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) => ( + <span> + <IssueTypeIcon className="little-spacer-right" query={props.type} /> + {translate('issue.type', props.type)} + </span> +); + +export default TypeHelper; |