import org.junit.Test;
import org.sonarqube.ws.client.PostRequest;
import org.sonarqube.ws.client.WsClient;
-import pageobjects.MyActivityPage;
import pageobjects.Navigation;
import util.selenium.SeleneseTest;
-import static com.codeborne.selenide.Condition.visible;
-import static com.codeborne.selenide.WebDriverRunner.url;
-import static org.assertj.core.api.Assertions.assertThat;
import static util.ItUtils.newAdminWsClient;
import static util.ItUtils.projectDir;
deactivateUser("account-user");
}
- @Test
- public void should_open_by_default() {
- nav.logIn().asAdmin().openHomepage();
- assertThat(url()).contains("/account");
- }
-
- @Test
- public void should_display_activity () {
- MyActivityPage page = nav.logIn().asAdmin().openMyActivity();
- page.getAllIssues().shouldBe(visible);
- page.getRecentIssues().shouldBe(visible);
- page.assertNoFavoriteProjects();
- }
-
@Test
public void should_display_user_details() throws Exception {
Selenese selenese = Selenese.builder().setHtmlTestsInClasspath("should_display_user_details",
+++ /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.
- */
-package pageobjects;
-
-import com.codeborne.selenide.SelenideElement;
-
-import static com.codeborne.selenide.Condition.visible;
-import static com.codeborne.selenide.Selenide.$;
-
-public class MyActivityPage {
-
- public MyActivityPage() {
- $("#my-activity-page").shouldBe(visible);
- }
-
- public SelenideElement getRecentIssues() {
- return $("#recent-issues");
- }
-
- public SelenideElement getAllIssues() {
- return $("#all-issues");
- }
-
- public SelenideElement getFavoriteProjects() {
- return $("#favorite-projects");
- }
-
- public void assertNoFavoriteProjects() {
- $("#no-favorite-projects").shouldBe(visible);
- }
-}
return open("/settings/server_id", ServerIdPage.class);
}
- public MyActivityPage openMyActivity() {
- return open("/account", MyActivityPage.class);
- }
-
public void open(String relativeUrl) {
Selenide.open(relativeUrl);
}
<td>css=.oauth-providers a</td>
<td></td>
</tr>
- <tr>
- <td>waitForVisible</td>
- <td>id=my-activity-page</td>
- <td></td>
- </tr>
<tr>
<td>waitForText</td>
<td>id=global-navigation</td>
</tr>
<tr>
<td>open</td>
- <td>/account/profile</td>
+ <td>/account/</td>
<td></td>
</tr>
<tr>
</tr>
<tr>
<td>open</td>
- <td>/account/profile</td>
+ <td>/account</td>
<td></td>
</tr>
<tr>
</tr>
<tr>
<td>open</td>
- <td>/sonar/account/profile/</td>
+ <td>/sonar/account/</td>
<td></td>
</tr>
<tr>
<Provider store={store}>
<Router history={history}>
<Route path="/" component={App}>
- {accountRoutes}
+ <Route path="account">{accountRoutes}</Route>
{projectsRoutes}
{qualityGatesRoutes}
{qualityProfilesRoutes}
import measures, * as fromMeasures from './measures/reducer';
import globalMessages, * as fromGlobalMessages from '../../components/store/globalMessages';
-import issuesActivity, * as fromIssuesActivity from '../../apps/account/home/store/reducer';
import projectsApp, * as fromProjectsApp from '../../apps/projects/store/reducer';
import qualityGatesApp from '../../apps/quality-gates/store/rootReducer';
users,
// apps
- issuesActivity,
projectsApp,
qualityGatesApp
});
fromFavorites.getFavorites(state.favorites)
);
-export const getIssuesActivity = state => (
- fromIssuesActivity.getIssuesActivity(state.issuesActivity)
-);
-
export const getComponentMeasure = (state, componentKey, metricKey) => (
fromMeasures.getComponentMeasure(state.measures, componentKey, metricKey)
);
float: left;
}
-.account-user > a {
- display: block;
- float: left;
- margin: -10px -15px -10px -10px;
- padding: 10px 15px 10px 10px;
- border-bottom: none;
- border-radius: 3px;
-}
-
-.account-user > a:hover,
-.account-user > a:active {
- background-color: rgba(0, 0, 0, 0.075);
-}
-
.account-user h1 {
line-height: 60px;
}
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
-import { Link } from 'react-router';
+import { Link, IndexLink } from 'react-router';
import { translate } from '../../../helpers/l10n';
const Nav = () => (
<nav className="account-nav clearfix">
<ul className="nav navbar-nav nav-tabs">
<li>
- <Link to="/account/profile/" activeClassName="active">
+ <IndexLink to="/account/" activeClassName="active">
{translate('my_account.profile')}
- </Link>
+ </IndexLink>
</li>
<li>
<Link to="/account/security/" activeClassName="active">
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
-import { IndexLink } from 'react-router';
import Avatar from '../../../components/ui/Avatar';
export default class UserCard extends React.Component {
return (
<div className="account-user">
- <IndexLink to="/account/">
- <div id="avatar" className="pull-left account-user-avatar">
- <Avatar email={user.email} size={60}/>
- </div>
- <h1 id="name" className="pull-left">{user.name}</h1>
- </IndexLink>
+ <div id="avatar" className="pull-left account-user-avatar">
+ <Avatar email={user.email} size={60}/>
+ </div>
+ <h1 id="name" className="pull-left">{user.name}</h1>
</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 React from 'react';
-import { connect } from 'react-redux';
-import sortBy from 'lodash/sortBy';
-import Favorite from '../../../../components/controls/Favorite';
-import Level from '../../../../components/ui/Level';
-import { TooltipsContainer } from '../../../../components/mixins/tooltips-mixin';
-import { getFavorites, getComponentMeasure } from '../../../../app/store/rootReducer';
-import { getComponentUrl, getProjectsUrl } from '../../../../helpers/urls';
-import { fetchFavoriteProjects } from '../store/actions';
-import { translate } from '../../../../helpers/l10n';
-
-class FavoriteProjects extends React.Component {
- static propTypes = {
- favorites: React.PropTypes.array,
- fetchFavoriteProjects: React.PropTypes.func.isRequired
- };
-
- componentDidMount () {
- this.props.fetchFavoriteProjects();
- }
-
- renderList () {
- const { favorites } = this.props;
-
- if (!favorites) {
- return null;
- }
-
- if (favorites.length === 0) {
- return (
- <div id="no-favorite-projects" className="boxed-group boxed-group-inner markdown text-center">
- <p className="note">{translate('my_activity.no_favorite_projects')}</p>
- <p>{translate('my_activity.no_favorite_projects.engagement')}</p>
- </div>
- );
- }
-
- const sorted = sortBy(favorites, project => project.name.toLowerCase());
-
- return (
- <ul id="favorite-projects">
- {sorted.map(project => (
- <li key={project.key}>
- <div className="pull-left" style={{ padding: '15px 15px 15px 10px' }}>
- <Favorite favorite={true} component={project.key}/>
- </div>
-
- <a href={getComponentUrl(project.key)}>
- {project.qualityGate != null && (
- <span className="pull-right">
- <Level level={project.qualityGate}/>
- </span>
- )}
- <strong>{project.name}</strong>
- </a>
- </li>
- ))}
- </ul>
- );
- }
-
- renderQualityGateTitle () {
- const { favorites } = this.props;
-
- const shouldBeRendered = favorites != null && favorites.some(f => f.qualityGate != null);
-
- if (!shouldBeRendered) {
- return null;
- }
-
- return (
- <TooltipsContainer>
- <div className="pull-right note">
- {translate('overview.quality_gate')}
- <i className="little-spacer-left icon-help"
- title={translate('quality_gates.intro.1')}
- data-toggle="tooltip"/>
- </div>
- </TooltipsContainer>
- );
- }
-
- render () {
- const { favorites } = this.props;
-
- return (
- <div className="my-activity-projects">
- <div className="my-activity-projects-header">
- {this.renderQualityGateTitle()}
- <h2>{translate('my_activity.my_favorite_projects')}</h2>
- </div>
-
- {favorites == null && (
- <div className="text-center">
- <i className="spinner"/>
- </div>
- )}
-
- {this.renderList()}
-
- <div className="more">
- <a className="button" href={getProjectsUrl()}>
- {translate('my_activity.explore_projects')}
- </a>
- </div>
- </div>
- );
- }
-}
-
-const mapStateToProps = state => {
- const fromState = getFavorites(state);
- const favorites = fromState == null ? null : fromState
- .filter(component => component.qualifier === 'TRK')
- .map(component => ({
- ...component,
- qualityGate: getComponentMeasure(state, component.key, 'alert_status')
- }));
-
- return { favorites };
-};
-
-export default connect(
- mapStateToProps,
- { fetchFavoriteProjects }
-)(FavoriteProjects);
+++ /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 { connect } from 'react-redux';
-import { fetchIssuesActivity } from '../store/actions';
-import { getIssuesActivity } from '../../../../app/store/rootReducer';
-import { getIssuesUrl } from '../../../../helpers/urls';
-import { translate } from '../../../../helpers/l10n';
-
-class IssuesActivity extends React.Component {
- componentDidMount () {
- this.props.fetchIssuesActivity();
- }
-
- getColorClass (number) {
- if (number == null) {
- return '';
- }
- return number > 0 ? 'text-danger' : 'text-success';
- }
-
- renderRecentIssues () {
- const number = this.props.issuesActivity && this.props.issuesActivity.recent;
- const url = getIssuesUrl({ resolved: 'false', assignees: '__me__', createdInLast: '1w' });
-
- return (
- <a className="my-activity-recent-issues" href={url}>
- <div id="recent-issues" className={'my-activity-issues-number ' + this.getColorClass(number)}>
- {number != null ? number : ' ' }
- </div>
- <div className="my-activity-issues-note">
- {translate('my_activity.my_issues')}<br/>{translate('my_activity.last_week')}
- </div>
- </a>
- );
- }
-
- renderAllIssues () {
- const number = this.props.issuesActivity && this.props.issuesActivity.all;
- const url = getIssuesUrl({ resolved: 'false', assignees: '__me__' });
-
- return (
- <a className="my-activity-all-issues" href={url}>
- <div id="all-issues" className={'my-activity-issues-number ' + this.getColorClass(number)}>
- {number != null ? number : ' ' }
- </div>
- <div className="my-activity-issues-note">
- {translate('my_activity.my_issues')}<br/>{translate('my_activity.all_time')}
- </div>
- </a>
- );
- }
-
- render () {
- return (
- <div className="my-activity-issues">
- {this.renderRecentIssues()}
- {this.renderAllIssues()}
- </div>
- );
- }
-}
-
-export default connect(
- state => ({ issuesActivity: getIssuesActivity(state) }),
- { fetchIssuesActivity }
-)(IssuesActivity);
+++ /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 IssuesActivity from './IssuesActivity';
-import FavoriteProjects from './FavoriteProjects';
-
-export default class MyActivity extends React.Component {
- static propTypes = {
- currentUser: React.PropTypes.object
- };
-
- render () {
- const { currentUser } = this.props;
-
- return (
- <div id="my-activity-page">
-
- {currentUser == null ? (
-
- <div className="account-body text-center">
- <i className="spinner"/>
- </div>
-
- ) : (
-
- <div className="account-body">
- <IssuesActivity/>
- <FavoriteProjects/>
- </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 MyActivity from './MyActivity';
-import { getCurrentUser } from '../../../../app/store/rootReducer';
-
-const mapStateToProps = state => ({
- currentUser: getCurrentUser(state)
-});
-
-export default connect(mapStateToProps)(MyActivity);
+++ /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 { getIssuesCount } from '../../../../api/issues';
-import { getFavorites } from '../../../../api/favorites';
-import { receiveFavorites } from '../../../../app/store/favorites/actions';
-import { getMeasures } from '../../../../api/measures';
-import { receiveComponentMeasure } from '../../../../app/store/measures/actions';
-
-export const RECEIVE_ISSUES_ACTIVITY = 'myActivity/RECEIVE_ISSUES_ACTIVITY';
-
-const receiveIssuesActivity = (recent, all) => ({
- type: RECEIVE_ISSUES_ACTIVITY,
- recent,
- all
-});
-
-export const fetchIssuesActivity = () => dispatch => {
- const query = { resolved: 'false', assignees: '__me__' };
- Promise.all([
- getIssuesCount(query),
- getIssuesCount({ ...query, createdInLast: '1w' })
- ]).then(responses => dispatch(receiveIssuesActivity(responses[1].issues, responses[0].issues)));
-};
-
-export const fetchFavoriteProjects = () => dispatch => {
- getFavorites().then(favorites => {
- dispatch(receiveFavorites(favorites));
-
- const projects = favorites.filter(component => component.qualifier === 'TRK');
- Promise.all(projects.map(project => getMeasures(project.key, ['alert_status'])))
- .then(responses => {
- responses.forEach((measures, index) => {
- measures.forEach(measure => {
- dispatch(receiveComponentMeasure(projects[index].key, measure.metric, measure.value));
- });
- });
- });
- });
-};
+++ /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 { RECEIVE_ISSUES_ACTIVITY } from './actions';
-
-const reducer = (state = null, action = {}) => {
- if (action.type === RECEIVE_ISSUES_ACTIVITY) {
- return { all: action.all, recent: action.recent };
- }
-
- return state;
-};
-
-export default reducer;
-
-export const getIssuesActivity = state => state;
*/
import React from 'react';
import { Route, IndexRoute } from 'react-router';
-import MyActivityContainer from './home/components/MyActivityContainer';
import Account from './components/Account';
import ProjectsContainer from './projects/ProjectsContainer';
import NotificationsContainer from './notifications/NotificationsContainer';
import Profile from './profile/Profile';
export default (
- <Route path="account" component={Account}>
- <IndexRoute component={MyActivityContainer}/>
- <Route path="profile" component={Profile}/>
+ <Route component={Account}>
+ <IndexRoute component={Profile}/>
<Route path="security" component={Security}/>
<Route path="notifications" component={NotificationsContainer}/>
<Route path="projects" component={ProjectsContainer}/>
},
render() {
- const homeController = window.SS.user ? '/account' : '/';
+ const homeController = window.SS.user ? '/projects/favorite' : '/projects';
const homeUrl = window.baseUrl + homeController;
const homeLinkClassName = 'navbar-brand' + (this.props.logoUrl ? ' navbar-brand-custom' : '');
return (
end
else
if logged_in?
- return redirect_to :controller => 'account'
+ return redirect_to :controller => 'projects', :action => 'favorite'
else
return redirect_to :controller => 'projects'
end