const currentUser = (state = null, action = {}) => {
if (action.type === RECEIVE_CURRENT_USER) {
- return action.user.login;
+ return action.user.isLoggedIn ? action.user.login : false;
}
return state;
export default combineReducers({ usersByLogin, userLogins, currentUser });
export const getCurrentUser = state => (
- state.currentUser ? state.usersByLogin[state.currentUser] : null
+ state.currentUser ? state.usersByLogin[state.currentUser] : state.currentUser
);
import ProjectsListContainer from './ProjectsListContainer';
import ProjectsListFooterContainer from './ProjectsListFooterContainer';
import PageSidebarContainer from './PageSidebarContainer';
+import FavoriteFilterContainer from './FavoriteFilterContainer';
import GlobalMessagesContainer from '../../../app/components/GlobalMessagesContainer';
import { parseUrlQuery } from '../store/utils';
-import '../styles.css';
import { translate } from '../../../helpers/l10n';
+import '../styles.css';
export default class App extends React.Component {
static propTypes = {
+ user: React.PropTypes.object,
fetchProjects: React.PropTypes.func.isRequired
};
}
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"/>
<ProjectsListFooterContainer query={this.state.query}/>
</div>
<aside className="page-sidebar-fixed">
+ <FavoriteFilterContainer/>
<PageSidebarContainer query={this.state.query}/>
</aside>
</div>
import { connect } from 'react-redux';
import App from './App';
import { fetchProjects } from '../store/actions';
+import { getCurrentUser } from '../../../app/store/rootReducer';
export default connect(
- () => ({}),
+ state => ({
+ user: getCurrentUser(state)
+ }),
{ fetchProjects }
)(App);
--- /dev/null
+/*
+ * 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 { IndexLink, Link } from 'react-router';
+
+export default class FavoriteFilter extends React.Component {
+ render () {
+ if (!this.props.user) {
+ return null;
+ }
+
+ return (
+ <div className="button-group projects-favorite-filter">
+ <IndexLink to="/projects" className="button" activeClassName="button-active">All</IndexLink>
+ <Link to="/projects/favorite" className="button" activeClassName="button-active">Favorite</Link>
+ </div>
+ );
+ }
+}
--- /dev/null
+/*
+ * 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 FavoriteFilter from './FavoriteFilter';
+import { getCurrentUser } from '../../../app/store/rootReducer';
+
+const mapStateToProps = state => ({
+ user: getCurrentUser(state)
+});
+
+export default connect(
+ mapStateToProps
+)(FavoriteFilter);
--- /dev/null
+/*
+ * 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 Helmet from 'react-helmet';
+import PageHeaderContainer from './PageHeaderContainer';
+import ProjectsListContainer from './ProjectsListContainer';
+import FavoriteFilterContainer from './FavoriteFilterContainer';
+import GlobalMessagesContainer from '../../../app/components/GlobalMessagesContainer';
+import { translate } from '../../../helpers/l10n';
+import '../styles.css';
+
+export default class FavoriteProjects extends React.Component {
+ static propTypes = {
+ user: React.PropTypes.object,
+ fetchFavoriteProjects: React.PropTypes.func.isRequired
+ };
+
+ 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"/>
+
+ <PageHeaderContainer/>
+
+ <GlobalMessagesContainer/>
+
+ <div className="page-with-sidebar page-with-left-sidebar">
+ <div className="page-main">
+ <ProjectsListContainer/>
+ </div>
+ <aside className="page-sidebar-fixed">
+ <FavoriteFilterContainer/>
+
+ <p className="note text-center">Filters are not available.</p>
+ </aside>
+ </div>
+ </div>
+ );
+ }
+}
--- /dev/null
+/*
+ * 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 FavoriteProjects from './FavoriteProjects';
+import { fetchFavoriteProjects } from '../store/actions';
+import { getCurrentUser } from '../../../app/store/rootReducer';
+
+const mapStateToProps = state => ({
+ user: getCurrentUser(state)
+});
+
+export default connect(
+ mapStateToProps,
+ { fetchFavoriteProjects }
+)(FavoriteProjects);
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
-import { Route } from 'react-router';
+import { Route, IndexRoute } from 'react-router';
import AppContainer from './components/AppContainer';
+import FavoriteProjectsContainer from './components/FavoriteProjectsContainer';
export default (
- <Route path="projects" component={AppContainer}/>
+ <Route path="projects">
+ <IndexRoute component={AppContainer}/>
+ <Route path="favorite" component={FavoriteProjectsContainer}/>
+ </Route>
);
import { getMeasuresForComponents } from '../../../api/measures';
import { receiveComponentsMeasures } from '../../../app/store/measures/actions';
import { convertToFilter } from './utils';
+import { getFavorites } from '../../../api/favorites';
+import { receiveFavorites } from '../../../app/store/favorites/actions';
const PAGE_SIZE = 50;
}
return searchProjects(data).then(onReceiveMoreProjects(dispatch), onFail(dispatch));
};
+
+export const fetchFavoriteProjects = () => dispatch => {
+ dispatch(updateState({ loading: true }));
+
+ return getFavorites().then(favorites => {
+ dispatch(receiveFavorites(favorites));
+
+ const projects = favorites.filter(component => component.qualifier === 'TRK');
+
+ dispatch(receiveComponents(projects));
+ dispatch(receiveProjects(projects, []));
+ dispatch(fetchProjectMeasures(projects)).then(() => {
+ dispatch(updateState({ loading: false }));
+ });
+ dispatch(updateState({
+ total: projects.length,
+ pageIndex: 1,
+ }));
+ }, onFail(dispatch));
+};
.projects-facets-reset .button {
}
+
+.projects-favorite-filter {
+ width: 100%;
+ margin-bottom: 30px;
+ padding-left: 10px;
+ padding-right: 10px;
+ box-sizing: border-box;
+}
+
+.projects-favorite-filter > a {
+ width: 50%;
+}
outline: none;
transition: border-color 0.2s ease;
- &:hover, &:focus {
+ &:hover, &:focus, &.button-active {
background: @darkBlue;
color: #fff;
}
end
+ def favorite
+ render :action => 'index'
+ end
+
end