@@ -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> | |||
); | |||
} | |||
} |
@@ -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); |
@@ -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> | |||
); | |||
} |
@@ -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> | |||
); |
@@ -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> | |||
); | |||
} |
@@ -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> | |||
); | |||
} |
@@ -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}/> |
@@ -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> | |||
); | |||
} | |||
} |
@@ -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); |
@@ -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> | |||
); |
@@ -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'; |
@@ -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) | |||
); |
@@ -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 | |||
}); |
@@ -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; | |||
} |
@@ -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 }); | |||
@@ -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; |
@@ -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 | |||
{} | |||
); |
@@ -2,10 +2,6 @@ | |||
width: 260px; | |||
} | |||
.projects-list-container { | |||
width: 740px; | |||
} | |||
.projects-list .page-actions { | |||
margin-bottom: 0; | |||
} |
@@ -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 | |||
#------------------------------------------------------------------------------ |