浏览代码

SONAR-8300 Create new "Projects" page [5th iteration] (#1357)

tags/6.2-RC1
Stas Vilchik 7 年前
父节点
当前提交
377cd3114a
共有 19 个文件被更改,包括 167 次插入244 次删除
  1. 70
    0
      server/sonar-web/src/main/js/apps/projects/components/AllProjects.js
  2. 7
    5
      server/sonar-web/src/main/js/apps/projects/components/AllProjectsContainer.js
  3. 6
    42
      server/sonar-web/src/main/js/apps/projects/components/App.js
  4. 8
    4
      server/sonar-web/src/main/js/apps/projects/components/FavoriteFilter.js
  5. 5
    30
      server/sonar-web/src/main/js/apps/projects/components/FavoriteProjects.js
  6. 16
    4
      server/sonar-web/src/main/js/apps/projects/components/PageHeader.js
  7. 3
    2
      server/sonar-web/src/main/js/apps/projects/components/PageSidebar.js
  8. 0
    43
      server/sonar-web/src/main/js/apps/projects/components/ProjectsListHeader.js
  9. 0
    30
      server/sonar-web/src/main/js/apps/projects/components/ProjectsListHeaderContainer.js
  10. 4
    3
      server/sonar-web/src/main/js/apps/projects/routes.js
  11. 2
    2
      server/sonar-web/src/main/js/apps/projects/store/actions.js
  12. 4
    4
      server/sonar-web/src/main/js/apps/projects/store/facetsDuck.js
  13. 0
    33
      server/sonar-web/src/main/js/apps/projects/store/projects/actions.js
  14. 17
    3
      server/sonar-web/src/main/js/apps/projects/store/projectsDuck.js
  15. 3
    3
      server/sonar-web/src/main/js/apps/projects/store/reducer.js
  16. 0
    30
      server/sonar-web/src/main/js/apps/projects/store/state/reducer.js
  17. 20
    2
      server/sonar-web/src/main/js/apps/projects/store/stateDuck.js
  18. 0
    4
      server/sonar-web/src/main/js/apps/projects/styles.css
  19. 2
    0
      sonar-core/src/main/resources/org/sonar/l10n/core.properties

+ 70
- 0
server/sonar-web/src/main/js/apps/projects/components/AllProjects.js 查看文件

@@ -0,0 +1,70 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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 ProjectsListContainer from './ProjectsListContainer';
import ProjectsListFooterContainer from './ProjectsListFooterContainer';
import PageSidebar from './PageSidebar';
import { parseUrlQuery } from '../store/utils';
import '../styles.css';

export default class AllProjects extends React.Component {
static propTypes = {
user: React.PropTypes.oneOfType([React.PropTypes.object, React.PropTypes.bool]),
fetchProjects: React.PropTypes.func.isRequired
};

state = {
query: {}
};

componentDidMount () {
this.handleQueryChange();
}

componentDidUpdate (prevProps) {
if (prevProps.location.query !== this.props.location.query) {
this.handleQueryChange();
}
}

handleQueryChange () {
const query = parseUrlQuery(this.props.location.query);
this.setState({ query });
this.props.fetchProjects(query);
}

render () {
if (this.props.user == null) {
return null;
}

return (
<div className="page-with-sidebar projects-page">
<div className="page-main">
<ProjectsListContainer/>
<ProjectsListFooterContainer query={this.state.query}/>
</div>
<aside className="page-sidebar-fixed projects-sidebar">
<PageSidebar query={this.state.query}/>
</aside>
</div>
);
}
}

server/sonar-web/src/main/js/apps/projects/components/AppContainer.js → server/sonar-web/src/main/js/apps/projects/components/AllProjectsContainer.js 查看文件

@@ -18,13 +18,15 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { connect } from 'react-redux';
import App from './App';
import AllProjects from './AllProjects';
import { fetchProjects } from '../store/actions';
import { getCurrentUser } from '../../../app/store/rootReducer';

const mapStateToProps = state => ({
user: getCurrentUser(state)
});

export default connect(
state => ({
user: getCurrentUser(state)
}),
mapStateToProps,
{ fetchProjects }
)(App);
)(AllProjects);

+ 6
- 42
server/sonar-web/src/main/js/apps/projects/components/App.js 查看文件

@@ -20,70 +20,34 @@
import React from 'react';
import Helmet from 'react-helmet';
import PageHeaderContainer from './PageHeaderContainer';
import ProjectsListContainer from './ProjectsListContainer';
import ProjectsListFooterContainer from './ProjectsListFooterContainer';
import PageSidebar from './PageSidebar';
import ProjectsListHeaderContainer from './ProjectsListHeaderContainer';
import GlobalMessagesContainer from '../../../app/components/GlobalMessagesContainer';
import { parseUrlQuery } from '../store/utils';
import { translate } from '../../../helpers/l10n';
import '../styles.css';

export default class App extends React.Component {
static propTypes = {
user: React.PropTypes.oneOfType([React.PropTypes.object, React.PropTypes.bool]),
fetchProjects: React.PropTypes.func.isRequired
};

state = {
query: {}
};

componentDidMount () {
document.querySelector('html').classList.add('dashboard-page');
this.handleQueryChange();
}

componentDidUpdate (prevProps) {
if (prevProps.location.query !== this.props.location.query) {
this.handleQueryChange();
}
}

componentWillUnmount () {
document.querySelector('html').classList.remove('dashboard-page');
}

handleQueryChange () {
const query = parseUrlQuery(this.props.location.query);
this.setState({ query });
this.props.fetchProjects(query);
}

render () {
if (this.props.user == null) {
return null;
}

return (
<div id="projects-page" className="page page-limited">
<Helmet title={translate('projects.page')} titleTemplate="SonarQube - %s"/>
<Helmet title={translate('projects.page')} titleTemplate="%s - SonarQube"/>

<GlobalMessagesContainer/>

<div className="page-with-sidebar page-with-left-sidebar">
<div className="page-with-sidebar">
<div className="page-main">
<div className="projects-list-container">
<ProjectsListHeaderContainer/>
<ProjectsListContainer/>
<ProjectsListFooterContainer query={this.state.query}/>
</div>
</div>
<aside className="page-sidebar-fixed projects-sidebar">
<PageHeaderContainer/>
<PageSidebar query={this.state.query}/>
</aside>
</div>
<aside className="page-sidebar-fixed projects-sidebar"/>
</div>

{this.props.children}
</div>
);
}

+ 8
- 4
server/sonar-web/src/main/js/apps/projects/components/FavoriteFilter.js 查看文件

@@ -19,6 +19,7 @@
*/
import React from 'react';
import { IndexLink, Link } from 'react-router';
import { translate } from '../../../helpers/l10n';

export default class FavoriteFilter extends React.Component {
render () {
@@ -27,11 +28,14 @@ export default class FavoriteFilter extends React.Component {
}

return (
<div>
<span className="note spacer-right">Quick Filters:</span>
<div className="pull-left big-spacer-left">
<div className="button-group">
<IndexLink to="/projects" className="button" activeClassName="button-active">All</IndexLink>
<Link to="/projects/favorite" className="button" activeClassName="button-active">Favorite</Link>
<Link to="/projects/favorite" className="button" activeClassName="button-active">
{translate('favorite')}
</Link>
<IndexLink to="/projects" className="button" activeClassName="button-active">
{translate('all')}
</IndexLink>
</div>
</div>
);

+ 5
- 30
server/sonar-web/src/main/js/apps/projects/components/FavoriteProjects.js 查看文件

@@ -18,12 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
import Helmet from 'react-helmet';
import PageHeaderContainer from './PageHeaderContainer';
import ProjectsListContainer from './ProjectsListContainer';
import ProjectsListHeaderContainer from './ProjectsListHeaderContainer';
import GlobalMessagesContainer from '../../../app/components/GlobalMessagesContainer';
import { translate } from '../../../helpers/l10n';
import '../styles.css';

export default class FavoriteProjects extends React.Component {
@@ -33,42 +28,22 @@ export default class FavoriteProjects extends React.Component {
};

componentDidMount () {
document.querySelector('html').classList.add('dashboard-page');
this.props.fetchFavoriteProjects();
}

componentWillUnmount () {
document.querySelector('html').classList.remove('dashboard-page');
}

render () {
if (!this.props.user) {
return null;
}

return (
<div id="projects-page" className="page page-limited">
<Helmet title={translate('projects.page')} titleTemplate="SonarQube - %s"/>

<GlobalMessagesContainer/>

<div className="page-with-sidebar page-with-left-sidebar">
<div className="page-main">
<div className="projects-list-container">
<ProjectsListHeaderContainer/>
<ProjectsListContainer/>
</div>
<div className="page-with-sidebar">
<div className="page-main">
<div className="projects-list-container">
<ProjectsListContainer/>
</div>
<aside className="page-sidebar-fixed projects-sidebar">
<PageHeaderContainer/>
<div className="search-navigator-facets-list">
<div className="projects-facets-header">
<h3>Filters</h3>
</div>
<p className="note text-center">Filters are not available.</p>
</div>
</aside>
</div>
<aside className="page-sidebar-fixed projects-sidebar"/>
</div>
);
}

+ 16
- 4
server/sonar-web/src/main/js/apps/projects/components/PageHeader.js 查看文件

@@ -18,11 +18,13 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
import FavoriteFilterContainer from './FavoriteFilterContainer';
import { translate } from '../../../helpers/l10n';

export default class PageHeader extends React.Component {
static propTypes = {
loading: React.PropTypes.bool
loading: React.PropTypes.bool,
total: React.PropTypes.number
};

render () {
@@ -30,11 +32,21 @@ export default class PageHeader extends React.Component {

return (
<header className="page-header">
<div className="page-actions">
{!!loading && (
<i className="spinner spacer-right"/>
)}

{this.props.total != null && (
<span>
<strong>{this.props.total}</strong> {translate('projects._projects')}
</span>
)}
</div>

<h1 className="page-title">{translate('projects.page')}</h1>

{!!loading && (
<i className="spinner"/>
)}
<FavoriteFilterContainer/>
</header>
);
}

+ 3
- 2
server/sonar-web/src/main/js/apps/projects/components/PageSidebar.js 查看文件

@@ -26,6 +26,7 @@ import QualityGateFilter from '../filters/QualityGateFilter';
import ReliabilityFilter from '../filters/ReliabilityFilter';
import SecurityFilter from '../filters/SecurityFilter';
import MaintainabilityFilter from '../filters/MaintainabilityFilter';
import { translate } from '../../../helpers/l10n';

export default class PageSidebar extends React.Component {
static propTypes = {
@@ -41,12 +42,12 @@ export default class PageSidebar extends React.Component {
{isFiltered && (
<div className="projects-facets-reset">
<Link to="/projects" className="button button-red">
Clear All Filters
{translate('projects.clear_all_filters')}
</Link>
</div>
)}

<h3>Filters</h3>
<h3>{translate('filters')}</h3>
</div>

<QualityGateFilter query={this.props.query}/>

+ 0
- 43
server/sonar-web/src/main/js/apps/projects/components/ProjectsListHeader.js 查看文件

@@ -1,43 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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 FavoriteFilterContainer from './FavoriteFilterContainer';
import { translate } from '../../../helpers/l10n';

export default class ProjectsListHeader extends React.Component {
static propTypes = {
total: React.PropTypes.number
};

render () {
return (
<header className="page-header">
<div className="page-actions">
{this.props.total != null && (
<span>
<strong>{this.props.total}</strong> {translate('projects._projects')}
</span>
)}
</div>
<FavoriteFilterContainer/>
</header>
);
}
}

+ 0
- 30
server/sonar-web/src/main/js/apps/projects/components/ProjectsListHeaderContainer.js 查看文件

@@ -1,30 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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 { connect } from 'react-redux';
import ProjectsListHeader from './ProjectsListHeader';
import { getProjectsAppState } from '../../../app/store/rootReducer';

const mapStateToProps = state => (
getProjectsAppState(state)
);

export default connect(
mapStateToProps
)(ProjectsListHeader);

+ 4
- 3
server/sonar-web/src/main/js/apps/projects/routes.js 查看文件

@@ -19,12 +19,13 @@
*/
import React from 'react';
import { Route, IndexRoute } from 'react-router';
import AppContainer from './components/AppContainer';
import App from './components/App';
import AllProjectsContainer from './components/AllProjectsContainer';
import FavoriteProjectsContainer from './components/FavoriteProjectsContainer';

export default (
<Route path="projects">
<IndexRoute component={AppContainer}/>
<Route path="projects" component={App}>
<IndexRoute component={AllProjectsContainer}/>
<Route path="favorite" component={FavoriteProjectsContainer}/>
</Route>
);

+ 2
- 2
server/sonar-web/src/main/js/apps/projects/store/actions.js 查看文件

@@ -22,8 +22,8 @@ import { searchProjects } from '../../../api/components';
import { addGlobalErrorMessage } from '../../../components/store/globalMessages';
import { parseError } from '../../code/utils';
import { receiveComponents } from '../../../app/store/components/actions';
import { receiveProjects, receiveMoreProjects } from './projects/actions';
import { updateState } from './state/actions';
import { receiveProjects, receiveMoreProjects } from './projectsDuck';
import { updateState } from './stateDuck';
import { getProjectsAppState } from '../../../app/store/rootReducer';
import { getMeasuresForProjects } from '../../../api/measures';
import { receiveComponentsMeasures } from '../../../app/store/measures/actions';

server/sonar-web/src/main/js/apps/projects/store/facets/reducer.js → server/sonar-web/src/main/js/apps/projects/store/facetsDuck.js 查看文件

@@ -19,9 +19,9 @@
*/
import flatMap from 'lodash/flatMap';
import sumBy from 'lodash/sumBy';
import { createMap } from '../../../../components/store/generalReducers';
import { RECEIVE_PROJECTS } from '../projects/actions';
import { mapMetricToProperty } from '../utils';
import { createMap } from '../../../components/store/generalReducers';
import { actions } from './projectsDuck';
import { mapMetricToProperty } from './utils';

const CUMULATIVE_FACETS = [
'reliability',
@@ -70,7 +70,7 @@ const getFacetsMap = facets => {
};

const reducer = createMap(
(state, action) => action.type === RECEIVE_PROJECTS,
(state, action) => action.type === actions.RECEIVE_PROJECTS,
() => false,
(state, action) => getFacetsMap(action.facets)
);

+ 0
- 33
server/sonar-web/src/main/js/apps/projects/store/projects/actions.js 查看文件

@@ -1,33 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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.
*/
export const RECEIVE_PROJECTS = 'projects/RECEIVE_PROJECTS';

export const receiveProjects = (projects, facets) => ({
type: RECEIVE_PROJECTS,
projects,
facets
});

export const RECEIVE_MORE_PROJECTS = 'projects/RECEIVE_MORE_PROJECTS';

export const receiveMoreProjects = projects => ({
type: RECEIVE_MORE_PROJECTS,
projects
});

server/sonar-web/src/main/js/apps/projects/store/projects/reducer.js → server/sonar-web/src/main/js/apps/projects/store/projectsDuck.js 查看文件

@@ -17,14 +17,28 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { RECEIVE_PROJECTS, RECEIVE_MORE_PROJECTS } from './actions';
export const actions = {
RECEIVE_PROJECTS: 'projects/RECEIVE_PROJECTS',
RECEIVE_MORE_PROJECTS: 'projects/RECEIVE_MORE_PROJECTS'
};

export const receiveProjects = (projects, facets) => ({
type: actions.RECEIVE_PROJECTS,
projects,
facets
});

export const receiveMoreProjects = projects => ({
type: actions.RECEIVE_MORE_PROJECTS,
projects
});

const reducer = (state = null, action = {}) => {
if (action.type === RECEIVE_PROJECTS) {
if (action.type === actions.RECEIVE_PROJECTS) {
return action.projects.map(project => project.key);
}

if (action.type === RECEIVE_MORE_PROJECTS) {
if (action.type === actions.RECEIVE_MORE_PROJECTS) {
const keys = action.projects.map(project => project.key);
return state != null ? [...state, ...keys] : keys;
}

+ 3
- 3
server/sonar-web/src/main/js/apps/projects/store/reducer.js 查看文件

@@ -18,9 +18,9 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { combineReducers } from 'redux';
import projects, * as fromProjects from './projects/reducer';
import state from './state/reducer';
import facets, * as fromFacets from './facets/reducer';
import projects, * as fromProjects from './projectsDuck';
import state from './stateDuck';
import facets, * as fromFacets from './facetsDuck';

export default combineReducers({ projects, state, facets });


+ 0
- 30
server/sonar-web/src/main/js/apps/projects/store/state/reducer.js 查看文件

@@ -1,30 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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 { UPDATE_STATE } from './actions';

const reducer = (state = {}, action = {}) => {
if (action.type === UPDATE_STATE) {
return { ...state, ...action.changes };
}

return state;
};

export default reducer;

server/sonar-web/src/main/js/apps/projects/store/state/actions.js → server/sonar-web/src/main/js/apps/projects/store/stateDuck.js 查看文件

@@ -17,9 +17,27 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
export const UPDATE_STATE = 'projects/UPDATE_STATE';
import { createValue } from '../../../components/store/generalReducers';

export const actions = {
UPDATE_STATE: 'projects/UPDATE_STATE'
};

export const updateState = changes => ({
type: UPDATE_STATE,
type: actions.UPDATE_STATE,
changes
});

export default createValue(
// should update
(state, action) => action.type === actions.UPDATE_STATE,

// should reset
() => false,

// get next value
(state, action) => ({ ...state, ...action.changes }),

// default value
{}
);

+ 0
- 4
server/sonar-web/src/main/js/apps/projects/styles.css 查看文件

@@ -2,10 +2,6 @@
width: 260px;
}

.projects-list-container {
width: 740px;
}

.projects-list .page-actions {
margin-bottom: 0;
}

+ 2
- 0
sonar-core/src/main/resources/org/sonar/l10n/core.properties 查看文件

@@ -69,6 +69,7 @@ false=False
favorite=Favorite
file=File
files=Files
filters=Filters
filter_verb=Filter
follow=Follow
global=Global
@@ -888,6 +889,7 @@ projects.page.description=Explore projects by coverage, duplications or size.
projects._projects=projects
projects.no_projects.1=We couldn't find any projects matching selected criteria.
projects.no_projects.2=Try to change filters to get some results.
projects.clear_all_filters=Clear All Filters


#------------------------------------------------------------------------------

正在加载...
取消
保存