From 2772ae13b3ff735d5866b7ffd481b40f3867a031 Mon Sep 17 00:00:00 2001 From: Stas Vilchik Date: Mon, 19 Jun 2017 06:31:21 -0700 Subject: [PATCH] update projects page layout (#2182) --- .../pageobjects/projects/ProjectsPage.java | 9 +- .../src/main/js/apps/issues/components/App.js | 4 +- .../ConciseIssuesListHeader.js | 4 +- .../src/main/js/apps/issues/styles.css | 52 +----- .../OrganizationFavoriteProjects.js | 4 - .../components/OrganizationProjects.js | 4 - .../OrganizationProjectsContainer.js | 10 -- .../apps/projects/components/AllProjects.js | 153 +++++++++--------- .../main/js/apps/projects/components/App.js | 21 +-- .../components/DefaultPageSelector.js | 4 - .../js/apps/projects/components/PageHeader.js | 83 +++++++--- .../components/PageHeaderContainer.js | 9 +- .../projects/components/PerspectiveSelect.js | 4 +- .../projects/components/ProjectsOptionBar.js | 106 ------------ .../components/ProjectsOptionBarContainer.js | 29 ---- .../components/ProjectsSortingSelect.js | 7 +- .../components/__tests__/PageHeader-test.js | 54 ++++++- .../__tests__/ProjectOptionBar-test.js | 78 --------- .../__snapshots__/PageHeader-test.js.snap | 84 ++++++++-- .../ProjectOptionBar-test.js.snap | 136 ---------------- .../ProjectsSortingSelect-test.js.snap | 12 +- .../js/apps/projects/filters/SearchFilter.js | 4 +- .../projects/filters/SearchFilterContainer.js | 13 +- .../__snapshots__/SearchFilter-test.js.snap | 24 +-- .../src/main/js/apps/projects/styles.css | 92 ++++------- .../src/main/less/components/page.less | 48 +++++- 26 files changed, 387 insertions(+), 661 deletions(-) delete mode 100644 server/sonar-web/src/main/js/apps/projects/components/ProjectsOptionBar.js delete mode 100644 server/sonar-web/src/main/js/apps/projects/components/ProjectsOptionBarContainer.js delete mode 100644 server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectOptionBar-test.js delete mode 100644 server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectOptionBar-test.js.snap diff --git a/it/it-tests/src/test/java/pageobjects/projects/ProjectsPage.java b/it/it-tests/src/test/java/pageobjects/projects/ProjectsPage.java index 21bc73da4a0..66bc7e5fda4 100644 --- a/it/it-tests/src/test/java/pageobjects/projects/ProjectsPage.java +++ b/it/it-tests/src/test/java/pageobjects/projects/ProjectsPage.java @@ -90,7 +90,7 @@ public class ProjectsPage { } public ProjectsPage searchProject(String search) { - SelenideElement searchInput = $(".projects-facet-search input"); + SelenideElement searchInput = $(".projects-topbar-item-search input"); searchInput.setValue("").sendKeys(search); return this; } @@ -115,11 +115,6 @@ public class ProjectsPage { } private SelenideElement getOpenTopBar() { - SelenideElement topBar = $(".projects-topbar-actions").should(Condition.exist); - if (!topBar.has(Condition.hasClass("open"))){ - $(".js-projects-topbar-open").click(); - } - topBar.should(Condition.hasClass("open")); - return topBar; + return $(".projects-topbar-items").should(Condition.exist); } } 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 144f2f2b682..d827a0d1f1f 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 @@ -804,8 +804,8 @@ export default class App extends React.PureComponent { {this.renderSide(openIssue)}
-
-
+
+
{this.renderBulkChange(openIssue)} {openIssue != null 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 index e6a5f1c24b3..aed9b836953 100644 --- a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssuesListHeader.js +++ b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssuesListHeader.js @@ -36,8 +36,8 @@ export default function ConciseIssuesListHeader(props: Props) { const { paging, selectedIndex } = props; return ( -
-
+
+
{props.loading ? diff --git a/server/sonar-web/src/main/js/apps/issues/styles.css b/server/sonar-web/src/main/js/apps/issues/styles.css index 95f6f533b22..b2041786cf7 100644 --- a/server/sonar-web/src/main/js/apps/issues/styles.css +++ b/server/sonar-web/src/main/js/apps/issues/styles.css @@ -1,55 +1,13 @@ -.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 { } -.issues-main-header-spinner { - margin-left: 1px; - margin-right: 9px; - margin-top: -1px; +.concise-issues-list-header { } -.concise-issues-list-header, -.concise-issues-list-header-inner {} - -.concise-issues-list-header {} - .concise-issues-list-header-inner { width: 260px; text-align: center; @@ -156,8 +114,7 @@ text-align: right; } -.issues .search-navigator-facet-header, -.issues .search-navigator-facet-list { +.issues .search-navigator-facet-header, .issues .search-navigator-facet-list { padding-left: 0; padding-right: 0; } @@ -167,7 +124,8 @@ padding-bottom: 8px; } -.issues .search-navigator-facet-box:not(.hidden):not(.leak-facet-box) + .search-navigator-facet-box:not(.leak-facet-box) { +.issues .search-navigator-facet-box:not(.hidden):not(.leak-facet-box) + + .search-navigator-facet-box:not(.leak-facet-box) { border-top: none; } diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationFavoriteProjects.js b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationFavoriteProjects.js index 39bcb30a75a..0707e5ae59d 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationFavoriteProjects.js +++ b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationFavoriteProjects.js @@ -26,8 +26,6 @@ export default class OrganizationFavoriteProjects extends React.PureComponent { children?: React.Element<*>, currentUser: { isLoggedIn: boolean }, location: Object, - optionBarOpen: boolean, - optionBarToggle: (open: boolean) => void, organization: { key: string } @@ -53,8 +51,6 @@ export default class OrganizationFavoriteProjects extends React.PureComponent {
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationProjects.js b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationProjects.js index 39d5c76ce76..cf16b4fe52f 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationProjects.js +++ b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationProjects.js @@ -26,8 +26,6 @@ export default class OrganizationProjects extends React.PureComponent { children?: React.Element<*>, currentUser: { isLoggedIn: boolean }, location: Object, - optionBarOpen: boolean, - optionBarToggle: (open: boolean) => void, organization: { key: string } @@ -54,8 +52,6 @@ export default class OrganizationProjects extends React.PureComponent { currentUser={this.props.currentUser} isFavorite={false} location={this.props.location} - optionBarOpen={this.props.optionBarOpen} - optionBarToggle={this.props.optionBarToggle} organization={this.props.organization} />
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationProjectsContainer.js b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationProjectsContainer.js index faa2bf8df04..a08e864d1b9 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationProjectsContainer.js +++ b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationProjectsContainer.js @@ -23,20 +23,10 @@ import { connect } from 'react-redux'; import { getCurrentUser, getOrganizationByKey } from '../../../store/rootReducer'; import { updateOrganization } from '../actions'; -type State = { - optionBarOpen: boolean -}; - class OrganizationProjectsContainer extends React.PureComponent { - state: State = { optionBarOpen: false }; - - handleOptionBarToggle = (open: boolean) => this.setState({ optionBarOpen: open }); - render() { return React.cloneElement(this.props.children, { currentUser: this.props.currentUser, - optionBarOpen: this.state.optionBarOpen, - optionBarToggle: this.handleOptionBarToggle, organization: this.props.organization }); } diff --git a/server/sonar-web/src/main/js/apps/projects/components/AllProjects.js b/server/sonar-web/src/main/js/apps/projects/components/AllProjects.js index 9720d56560b..93754d09541 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/AllProjects.js +++ b/server/sonar-web/src/main/js/apps/projects/components/AllProjects.js @@ -21,7 +21,6 @@ import React from 'react'; import Helmet from 'react-helmet'; import PageHeaderContainer from './PageHeaderContainer'; -import ProjectsOptionBarContainer from './ProjectsOptionBarContainer'; import ProjectsListContainer from './ProjectsListContainer'; import ProjectsListFooterContainer from './ProjectsListFooterContainer'; import PageSidebar from './PageSidebar'; @@ -31,16 +30,14 @@ import { translate } from '../../../helpers/l10n'; import { SORTING_SWITCH, parseSorting } from '../utils'; import '../styles.css'; -type Props = { +type Props = {| isFavorite: boolean, location: { pathname: string, query: { [string]: string } }, fetchProjects: (query: string, isFavorite: boolean, organization?: {}) => Promise<*>, - optionBarOpen: boolean, - optionBarToggle: (open: boolean) => void, organization?: { key: string }, router: { push: ({ pathname: string, query?: {} }) => void }, currentUser?: { isLoggedIn: boolean } -}; +|}; type State = { query: { [string]: string } @@ -67,11 +64,13 @@ export default class AllProjects extends React.PureComponent { footer && footer.classList.remove('search-navigator-footer'); } - openOptionBar = (evt: Event & { currentTarget: HTMLElement }) => { - evt.currentTarget.blur(); - evt.preventDefault(); - this.props.optionBarToggle(true); - }; + getView = () => this.state.query.view || 'overall'; + + getVisualization = () => this.state.query.visualization || 'risk'; + + getSort = () => this.state.query.sort || 'name'; + + isFiltered = () => Object.keys(this.state.query).some(key => this.state.query[key] != null); handlePerspectiveChange = ({ view, visualization }: { view: string, visualization?: string }) => { const query: { view: ?string, visualization: ?string, sort?: ?string } = { @@ -111,78 +110,78 @@ export default class AllProjects extends React.PureComponent { }); }; - render() { - const { isFavorite, organization, optionBarOpen } = this.props; - const { query } = this.state; - const isFiltered = Object.keys(query).some(key => query[key] != null); - - const view = query.view || 'overall'; - const visualization = query.visualization || 'risk'; - const selectedSort = query.sort || 'name'; - - const sideBarTop = (organization ? 95 : 30) + (optionBarOpen ? 45 : 0); - const contentTop = optionBarOpen ? 65 : 20; + renderSide = () => ( +
+
+
+
+ +
+
+
+
+ ); + + renderHeader = () => ( +
+
+
+ +
+
+
+ ); + + renderMain = () => + (this.getView() === 'visualizations' + ?
+ +
+ :
+ + +
); + render() { return ( -
+
- - -
-
-
-
-
- -
-
-
-
+ {this.renderSide()} -
-
- - {view !== 'visualizations' && - } - {view !== 'visualizations' && - } - {view === 'visualizations' && - } -
-
+
+ {this.renderHeader()} + {this.renderMain()}
); diff --git a/server/sonar-web/src/main/js/apps/projects/components/App.js b/server/sonar-web/src/main/js/apps/projects/components/App.js index f1b4d2f71c2..83b097ed170 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/App.js +++ b/server/sonar-web/src/main/js/apps/projects/components/App.js @@ -20,32 +20,25 @@ //@flow import React from 'react'; -type State = { - optionBarOpen: boolean -}; - export default class App extends React.PureComponent { - state: State = { optionBarOpen: false }; - componentDidMount() { const elem = document.querySelector('html'); - elem && elem.classList.add('dashboard-page'); + if (elem) { + elem.classList.add('dashboard-page'); + } } componentWillUnmount() { const elem = document.querySelector('html'); - elem && elem.classList.remove('dashboard-page'); + if (elem) { + elem.classList.remove('dashboard-page'); + } } - handleOptionBarToggle = (open: boolean) => this.setState({ optionBarOpen: open }); - render() { return (
- {React.cloneElement(this.props.children, { - optionBarOpen: this.state.optionBarOpen, - optionBarToggle: this.handleOptionBarToggle - })} + {this.props.children}
); } diff --git a/server/sonar-web/src/main/js/apps/projects/components/DefaultPageSelector.js b/server/sonar-web/src/main/js/apps/projects/components/DefaultPageSelector.js index 988891c41d3..f3e39df8b4d 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/DefaultPageSelector.js +++ b/server/sonar-web/src/main/js/apps/projects/components/DefaultPageSelector.js @@ -29,8 +29,6 @@ import { searchProjects } from '../../../api/components'; type Props = { currentUser: { isLoggedIn: boolean }, location: { query: {} }, - optionBarOpen: boolean, - optionBarToggle: (open: boolean) => void, router: { replace: (location: { pathname?: string, query?: { [string]: string } }) => void } @@ -107,8 +105,6 @@ class DefaultPageSelector extends React.PureComponent { ); diff --git a/server/sonar-web/src/main/js/apps/projects/components/PageHeader.js b/server/sonar-web/src/main/js/apps/projects/components/PageHeader.js index b4104652feb..dc1aa2de123 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/PageHeader.js +++ b/server/sonar-web/src/main/js/apps/projects/components/PageHeader.js @@ -19,41 +19,88 @@ */ // @flow import React from 'react'; +import classNames from 'classnames'; import SearchFilterContainer from '../filters/SearchFilterContainer'; +import Tooltip from '../../../components/controls/Tooltip'; +import PerspectiveSelect from './PerspectiveSelect'; +import ProjectsSortingSelect from './ProjectsSortingSelect'; import { translate } from '../../../helpers/l10n'; -type Props = { +type Props = {| + currentUser?: { isLoggedIn: boolean }, isFavorite?: boolean, - loading: boolean, - onOpenOptionBar: () => void, - optionBarOpen?: boolean, + onPerspectiveChange: ({ view: string, visualization?: string }) => void, organization?: { key: string }, + projects: Array<*>, + projectsAppState: { loading: boolean, total?: number }, query: { [string]: string }, - total?: number -}; + onSortChange: (sort: string, desc: boolean) => void, + selectedSort: string, + view: string, + visualization?: string +|}; export default function PageHeader(props: Props) { + const renderSortingSelect = () => { + const { projectsAppState, projects, currentUser, view } = props; + const limitReached = + projects != null && + projectsAppState.total != null && + projects.length < projectsAppState.total; + const defaultOption = currentUser && currentUser.isLoggedIn ? 'name' : 'analysis_date'; + if (view === 'visualizations' && !limitReached) { + return ( + +
+ +
+
+ ); + } + return ( + + ); + }; + return ( -
+
+ + + {renderSortingSelect()} + -
- {!props.optionBarOpen && - - {translate('projects.view_settings')} - } - {!!props.loading && } +
+ {!!props.projectsAppState.loading && } - {props.total != null && + {props.projectsAppState.total != null && - {props.total} + {props.projectsAppState.total} {' '} {translate('projects._projects')} } diff --git a/server/sonar-web/src/main/js/apps/projects/components/PageHeaderContainer.js b/server/sonar-web/src/main/js/apps/projects/components/PageHeaderContainer.js index 5d817b926fe..7780da0a705 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/PageHeaderContainer.js +++ b/server/sonar-web/src/main/js/apps/projects/components/PageHeaderContainer.js @@ -19,6 +19,11 @@ */ import { connect } from 'react-redux'; import PageHeader from './PageHeader'; -import { getProjectsAppState } from '../../../store/rootReducer'; +import { getProjects, getProjectsAppState } from '../../../store/rootReducer'; -export default connect(state => getProjectsAppState(state))(PageHeader); +const mapStateToProps = state => ({ + projects: getProjects(state), + projectsAppState: getProjectsAppState(state) +}); + +export default connect(mapStateToProps)(PageHeader); diff --git a/server/sonar-web/src/main/js/apps/projects/components/PerspectiveSelect.js b/server/sonar-web/src/main/js/apps/projects/components/PerspectiveSelect.js index 2580a34c4b8..8f6f5f41c0d 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/PerspectiveSelect.js +++ b/server/sonar-web/src/main/js/apps/projects/components/PerspectiveSelect.js @@ -26,12 +26,12 @@ import { VIEWS, VISUALIZATIONS } from '../utils'; export type Option = { label: string, type: string, value: string }; -type Props = { +type Props = {| className?: string, onChange: ({ view: string, visualization?: string }) => void, view: string, visualization?: string -}; +|}; export default class PerspectiveSelect extends React.PureComponent { options: Array