import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.ClassRule;
+import org.junit.Rule;
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;
public static Orchestrator orchestrator = Category4Suite.ORCHESTRATOR;
private static WsClient adminWsClient;
+ @Rule
+ public Navigation nav = Navigation.get(orchestrator);
+
@BeforeClass
public static void setUp() {
orchestrator.resetData();
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",
new SeleneseTest(selenese).runOn(orchestrator);
}
- @Test
- public void should_display_issues() throws Exception {
- Selenese selenese = Selenese.builder().setHtmlTestsInClasspath("should_display_issues",
- "/user/MyAccountPageTest/should_display_issues.html"
- ).build();
- new SeleneseTest(selenese).runOn(orchestrator);
- }
-
@Test
public void should_display_projects() throws Exception {
// first, try on empty instance
--- /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);
}
</tr>
<tr>
<td>open</td>
- <td>/account/index</td>
+ <td>/account/profile</td>
<td></td>
</tr>
<tr>
</tr>
<tr>
<td>open</td>
- <td>/account/index</td>
+ <td>/account/profile</td>
<td></td>
</tr>
<tr>
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-<head profile="http://selenium-ide.openqa.org/profiles/test-case">
- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
- <link rel="selenium.base" href="http://localhost:49506"/>
- <title>should_display_issues</title>
-</head>
-<body>
-<table cellpadding="1" cellspacing="1" border="1">
- <thead>
- <tr>
- <td rowspan="1" colspan="3">should_display_issues</td>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>open</td>
- <td>/sonar/sessions/login</td>
- <td></td>
-</tr>
-<tr>
- <td>type</td>
- <td>id=login</td>
- <td>account-user</td>
-</tr>
-<tr>
- <td>type</td>
- <td>id=password</td>
- <td>password</td>
-</tr>
-<tr>
- <td>clickAndWait</td>
- <td>commit</td>
- <td></td>
-</tr>
-<tr>
- <td>waitForElementPresent</td>
- <td>css=.js-user-authenticated</td>
- <td></td>
-</tr>
-<tr>
- <td>open</td>
- <td>/sonar/account/issues</td>
- <td></td>
-</tr>
-<tr>
- <td>waitForElementPresent</td>
- <td>css=[data-unresolved]</td>
- <td></td>
-</tr>
-</tbody>
-</table>
-</body>
-</html>
</tr>
<tr>
<td>open</td>
- <td>/account/projects</td>
+ <td>/account/projects/</td>
<td></td>
</tr>
<tr>
<td>waitForElementPresent</td>
- <td>css=.account-projects</td>
+ <td>css=#account-projects</td>
<td></td>
</tr>
<tr>
</tr>
<tr>
<td>open</td>
- <td>/account/projects</td>
+ <td>/account/projects/</td>
<td></td>
</tr>
<tr>
</tr>
<tr>
<td>open</td>
- <td>/sonar/account/</td>
+ <td>/sonar/account/profile/</td>
<td></td>
</tr>
<tr>
<td>css=#avatar img</td>
<td></td>
</tr>
-<tr>
- <td>waitForElementPresent</td>
- <td>id=favorite-components</td>
- <td></td>
-</tr>
-<tr>
- <td>waitForElementPresent</td>
- <td>id=favorite-issue-filters</td>
- <td></td>
-</tr>
-<tr>
- <td>waitForElementPresent</td>
- <td>id=favorite-measure-filters</td>
- <td></td>
-</tr>
</tbody>
</table>
</body>
'sonar': './src/main/js/libs/sonar.js',
'main': './src/main/js/main/app.js',
+ 'app': './src/main/js/app/index.js',
- 'account': './src/main/js/apps/account/app.js',
'background-tasks': './src/main/js/apps/background-tasks/app.js',
'code': './src/main/js/apps/code/app.js',
'coding-rules': './src/main/js/apps/coding-rules/app.js',
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { post, requestDelete } from '../helpers/request';
+import { post, requestDelete, getJSON } from '../helpers/request';
+
+export const getFavorites = () => getJSON('/api/favourites');
export function addFavorite (componentKey) {
const url = '/api/favourites';
--- /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 { fetchCurrentUser } from '../store/users/actions';
+
+class App extends React.Component {
+ static propTypes = {
+ fetchCurrentUser: React.PropTypes.func.isRequired
+ };
+
+ componentDidMount () {
+ this.props.fetchCurrentUser();
+ }
+
+ render () {
+ return this.props.children;
+ }
+}
+
+export default connect(
+ () => ({}),
+ { fetchCurrentUser }
+)(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 { render } from 'react-dom';
+import { Router, Route, useRouterHistory } from 'react-router';
+import { createHistory } from 'history';
+import { Provider } from 'react-redux';
+import App from './components/App';
+import accountRoutes from '../apps/account/routes';
+import configureStore from '../components/store/configureStore';
+import rootReducer from './store/rootReducer';
+import './styles.css';
+
+window.sonarqube.appStarted.then(options => {
+ const el = document.querySelector(options.el);
+
+ const history = useRouterHistory(createHistory)({
+ basename: window.baseUrl + '/'
+ });
+
+ const store = configureStore(rootReducer);
+
+ render((
+ <Provider store={store}>
+ <Router history={history}>
+ <Route path="/" component={App}>
+ {accountRoutes}
+ </Route>
+ </Router>
+ </Provider>
+ ), el);
+});
--- /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.
+ */
+export const RECEIVE_FAVORITES = 'RECEIVE_FAVORITES';
+
+export const receiveFavorites = favorites => ({
+ type: RECEIVE_FAVORITES,
+ favorites
+});
--- /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 { combineReducers } from 'redux';
+import keyBy from 'lodash/keyBy';
+import { RECEIVE_FAVORITES } from './actions';
+
+const favoritesByKey = (state = {}, action = {}) => {
+ if (action.type === RECEIVE_FAVORITES) {
+ const byKey = keyBy(action.favorites, 'key');
+ return { ...state, ...byKey };
+ }
+
+ return state;
+};
+
+const favoriteKeys = (state = null, action = {}) => {
+ if (action.type === RECEIVE_FAVORITES) {
+ return action.favorites.map(f => f.key);
+ }
+
+ return state;
+};
+
+export default combineReducers({ favoritesByKey, favoriteKeys });
+
+export const getFavorites = state => (
+ state.favoriteKeys ?
+ state.favoriteKeys.map(key => state.favoritesByKey[key]) :
+ null
+);
--- /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.
+ */
+export const RECEIVE_COMPONENT_MEASURE = 'RECEIVE_COMPONENT_MEASURE';
+
+export const receiveComponentMeasure = (componentKey, metricKey, value) => ({
+ type: RECEIVE_COMPONENT_MEASURE,
+ componentKey,
+ metricKey,
+ 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_COMPONENT_MEASURE } from './actions';
+
+const byMetricKey = (state = {}, action = {}) => {
+ if (action.type === RECEIVE_COMPONENT_MEASURE) {
+ return { ...state, [action.metricKey]: action.value };
+ }
+
+ return state;
+};
+
+const reducer = (state = {}, action = {}) => {
+ if (action.type === RECEIVE_COMPONENT_MEASURE) {
+ const component = state[action.componentKey];
+ return { ...state, [action.componentKey]: byMetricKey(component, action) };
+ }
+
+ return state;
+};
+
+export default reducer;
+
+export const getComponentMeasure = (state, componentKey, metricKey) => {
+ const component = state[componentKey];
+ return component && component[metricKey];
+};
--- /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 { combineReducers } from 'redux';
+import users, * as fromUsers from './users/reducer';
+import favorites, * as fromFavorites from './favorites/reducer';
+import measures, * as fromMeasures from './measures/reducer';
+
+import issuesActivity, * as fromIssuesActivity from '../../apps/account/home/store/reducer';
+
+export default combineReducers({ users, favorites, issuesActivity, measures });
+
+export const getCurrentUser = state => (
+ fromUsers.getCurrentUser(state.users)
+);
+
+export const getFavorites = state => (
+ fromFavorites.getFavorites(state.favorites)
+);
+
+export const getIssuesActivity = state => (
+ fromIssuesActivity.getIssuesActivity(state.issuesActivity)
+);
+
+export const getComponentMeasure = (state, componentKey, metricKey) => (
+ fromMeasures.getComponentMeasure(state.measures, componentKey, metricKey)
+);
--- /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 { getCurrentUser } from '../../../api/users';
+
+export const RECEIVE_CURRENT_USER = 'RECEIVE_CURRENT_USER';
+
+export const receiveCurrentUser = user => ({
+ type: RECEIVE_CURRENT_USER,
+ user
+});
+
+export const fetchCurrentUser = () => dispatch => {
+ getCurrentUser().then(user => dispatch(receiveCurrentUser(user)));
+};
--- /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 { combineReducers } from 'redux';
+import uniq from 'lodash/uniq';
+import { RECEIVE_CURRENT_USER } from './actions';
+
+const usersByLogin = (state = {}, action = {}) => {
+ if (action.type === RECEIVE_CURRENT_USER) {
+ return { ...state, [action.user.login]: action.user };
+ }
+
+ return state;
+};
+
+const userLogins = (state = [], action = {}) => {
+ if (action.type === RECEIVE_CURRENT_USER) {
+ return uniq([...state, action.user.login]);
+ }
+
+ return state;
+};
+
+const currentUser = (state = null, action = {}) => {
+ if (action.type === RECEIVE_CURRENT_USER) {
+ return action.user.login;
+ }
+
+ return state;
+};
+
+export default combineReducers({ usersByLogin, userLogins, currentUser });
+
+export const getCurrentUser = state => (
+ state.currentUser ? state.usersByLogin[state.currentUser] : null
+);
--- /dev/null
+.boxed-group {
+ margin-bottom: 20px;
+ border: 1px solid #e6e6e6;
+ border-radius: 2px;
+ background-color: #fff;
+}
+
+.boxed-group > h2 {
+ line-height: 24px;
+ padding: 15px 20px 0;
+}
+
+.boxed-group hr {
+ height: 0;
+ border-top: 1px solid #efefef;
+ margin: 15px -20px;
+}
+
+.boxed-group-actions {
+ float: right;
+ margin-top: 15px;
+ margin-right: 20px;
+}
+
+.boxed-group-inner {
+ padding: 15px 20px;
+}
+
+.boxed-group-inner:empty {
+ padding-top: 0;
+}
+
+.boxed-group-list {
+ margin-top: -8px;
+ margin-bottom: -8px;
+}
+
+.boxed-group-list > li {
+ margin-left: -20px;
+ margin-right: -20px;
+ padding: 8px 20px;
+}
+.account-container {
+ width: 600px;
+ margin-left: auto;
+ margin-right: auto;
+}
+
.account-header {
- position: fixed;
- top: 30px;
- left: 0;
- right: 0;
- z-index: 420;
+ padding-top: 20px;
+ padding-bottom: 20px;
+ border-bottom: 1px solid #e6e6e6;
background-color: #f3f3f3;
}
-.account-nav { }
+.account-nav {
+ float: right;
+ padding-top: 11px;
+}
.account-nav .nav-tabs {
width: 100%;
+ border-bottom: none;
+}
+
+.account-nav .navbar-nav > li > a {
+ padding-top: 8px;
+ padding-bottom: 8px;
}
.account-user {
- padding: 10px;
+ float: left;
}
-.account-nav-avatar {
+.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;
+}
+
+.account-user-avatar {
margin-right: 20px;
}
-.account-page {
- padding-top: 102px;
+.account-user-avatar > img {
+ border-radius: 60px;
+}
+
+.account-user-avatar:empty {
+ display: none;
+}
+
+.account-body {
+ padding: 40px 0;
+}
+
+.account-separator {
+ height: 0;
+ margin: 40px 0;
+ border-top: 1px solid #e6e6e6;
}
.account-bar-chart .bar-chart-bar {
text-anchor: start;
}
-.account-projects {
- max-width: 960px;
+.account-projects-list {
+ margin-left: -20px;
+ margin-right: -20px;
+}
+
+.account-projects-list > li {
+ padding: 15px 20px;
}
.account-projects-list > li + li {
- margin-top: 10px;
+ border-top: 1px solid #e6e6e6;
}
.account-project-side {
.account-project-card {
position: relative;
display: block;
- padding: 10px 15px;
- border: 1px solid #e6e6e6;
- border-radius: 3px;
- background-color: #fff;
}
.account-project-name {
color: #777;
font-size: 12px;
}
+
+.my-activity-issues {
+ position: relative;
+ display: flex;
+ justify-content: center;
+ margin-bottom: 70px;
+}
+
+.my-activity-issues:after {
+ position: absolute;
+ z-index: 5;
+ top: -15px;
+ left: 50%;
+ width: 1px;
+ height: 100px;
+ background-color: #e6e6e6;
+ transform: rotate(30deg);
+ content: "";
+}
+
+.my-activity-issues > a {
+ display: block;
+ padding: 15px 20px;
+ border: none;
+ border-radius: 2px;
+ color: #444;
+}
+
+.my-activity-issues > a:hover {
+ background-color: #f3f3f3;
+}
+
+.my-activity-recent-issues {
+ margin-right: 50px;
+ text-align: right;
+}
+
+.my-activity-recent-issues .my-activity-issues-note {
+ text-align: left;
+}
+
+.my-activity-all-issues {
+ margin-left: 50px;
+}
+
+.my-activity-issues-number {
+ display: inline-block;
+ vertical-align: middle;
+ line-height: 36px;
+ font-size: 36px;
+ font-weight: 300;
+}
+
+.my-activity-issues-note {
+ display: inline-block;
+ vertical-align: middle;
+ padding-left: 10px;
+ padding-top: 2px;
+ line-height: 16px;
+ font-size: 12px;
+}
+
+.my-activity-projects {
+ width: 360px;
+ margin: 0 auto;
+ padding: 40px 0;
+}
+
+.my-activity-projects-header {
+ line-height: 24px;
+ margin-bottom: 15px;
+ padding: 0 10px;
+}
+
+.my-activity-projects > ul > li + li {
+ border-top: 1px solid #e6e6e6;
+}
+
+.my-activity-projects > ul > li > a {
+ display: block;
+ padding: 15px 10px;
+ border: none;
+}
+
+.my-activity-projects > ul > li > a:hover {
+ background-color: #f3f3f3;
+}
+
+.my-activity-projects .level {
+ width: 60px;
+}
+
+.my-activity-projects .more {
+ margin-top: 30px;
+ text-align: center;
+}
+++ /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 { render } from 'react-dom';
-import { Router, Route, IndexRoute, Redirect, useRouterHistory } from 'react-router';
-import { createHistory } from 'history';
-import AccountApp from './components/AccountApp';
-import Home from './components/Home';
-import NotificationsContainer from './components/NotificationsContainer';
-import Security from './components/Security';
-import Issues from './components/Issues';
-import ProjectsContainer from './projects/ProjectsContainer';
-
-window.sonarqube.appStarted.then(options => {
- const el = document.querySelector(options.el);
-
- const history = useRouterHistory(createHistory)({
- basename: window.baseUrl + '/account'
- });
-
- render((
- <Router history={history}>
- <Route path="/" component={AccountApp}>
- <IndexRoute component={Home}/>
- <Route path="issues" component={Issues}/>
- <Route path="notifications" component={NotificationsContainer}/>
- <Route path="security" component={Security}/>
- <Route path="projects" component={ProjectsContainer}/>
-
- <Redirect from="/index" to="/"/>
- </Route>
- </Router>
- ), el);
-});
--- /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 Nav from './Nav';
+import UserCard from './UserCard';
+import { getCurrentUser } from '../../../app/store/rootReducer';
+import '../account.css';
+
+class Account extends React.Component {
+ render () {
+ const { currentUser, children } = this.props;
+
+ if (currentUser == null) {
+ return (
+ <div id="account-page">
+ <div className="text-center">
+ <i className="spinner"/>
+ </div>
+ </div>
+ );
+ }
+
+ return (
+ <div id="account-page">
+ <header className="account-header">
+ <div className="account-container clearfix">
+ <UserCard user={currentUser}/>
+ <Nav user={currentUser}/>
+ </div>
+ </header>
+
+ {children}
+ </div>
+ );
+ }
+}
+
+export default connect(
+ state => ({
+ currentUser: getCurrentUser(state)
+ })
+)(Account);
+++ /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, { Component, cloneElement } from 'react';
-import Nav from './Nav';
-import { getCurrentUser } from '../../../api/users';
-import { getIssueFilters } from '../../../api/issues';
-import '../account.css';
-
-export default class AccountApp extends Component {
- state = {
- loading: true
- };
-
- componentDidMount () {
- this.mounted = true;
- Promise.all([
- this.loadUser(),
- this.loadFavoriteIssueFilters()
- ]).then(() => this.finishLoading());
- }
-
- componentWillUnmount () {
- this.mounted = false;
- }
-
- loadUser () {
- return getCurrentUser().then(user => {
- if (this.mounted) {
- this.setState({ user });
- }
- });
- }
-
- loadFavoriteIssueFilters () {
- return getIssueFilters().then(issueFilters => {
- const favoriteIssueFilters = issueFilters.filter(f => f.favorite);
- this.setState({ issueFilters: favoriteIssueFilters });
- });
- }
-
- finishLoading () {
- if (this.mounted) {
- this.setState({ loading: false });
- }
- }
-
- render () {
- const { user, issueFilters, loading } = this.state;
-
- if (loading) {
- return (
- <div>
- <i className="spinner spinner-margin"/>
- </div>
- );
- }
-
- const { favorites } = window.sonarqube.user;
- const measureFilters = window.sonarqube.user.favoriteMeasureFilters;
- const children = cloneElement(this.props.children, {
- measureFilters,
- user,
- favorites,
- issueFilters
- });
-
- return (
- <div className="account-page">
- <Nav user={user}/>
- {children}
- </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 FavoriteIssueFilter from '../../../components/controls/FavoriteIssueFilter';
-import { translate } from '../../../helpers/l10n';
-
-const FavoriteIssueFilters = ({ issueFilters }) => (
- <section className="huge-spacer-top">
- <h2 className="spacer-bottom">
- {translate('my_account.favorite_issue_filters')}
- </h2>
-
- {!issueFilters.length && (
- <p className="note">
- {translate('my_account.no_favorite_issue_filters')}
- </p>
- )}
-
- <table id="favorite-issue-filters" className="data">
- <tbody>
- {issueFilters.map(f => (
- <tr key={f.name}>
- <td className="thin">
- <FavoriteIssueFilter filter={f} favorite={true}/>
- </td>
- <td>
- <a href={`${window.baseUrl}/issues/search#id=${f.id}`}>
- {f.name}
- </a>
- </td>
- </tr>
- ))}
- </tbody>
- </table>
-
- <div className="spacer-top small">
- <a href={`${window.baseUrl}/issues/manage`}>{translate('see_all')}</a>
- </div>
-
- </section>
-);
-
-export default FavoriteIssueFilters;
+++ /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 FavoriteMeasureFilter from '../../../components/controls/FavoriteMeasureFilter';
-import { translate } from '../../../helpers/l10n';
-
-const FavoriteMeasureFilters = ({ measureFilters }) => (
- <section className="huge-spacer-top">
- <h2 className="spacer-bottom">
- {translate('my_account.favorite_measure_filters')}
- </h2>
-
- {!measureFilters.length && (
- <p className="note">
- {translate('my_account.no_favorite_measure_filters')}
- </p>
- )}
-
- <table id="favorite-measure-filters" className="data">
- <tbody>
- {measureFilters.map(f => (
- <tr key={f.name}>
- <td className="thin">
- <FavoriteMeasureFilter filter={f} favorite={true}/>
- </td>
- <td>
- <a href={`${window.baseUrl}/measures/filter/${f.id}`}>
- {f.name}
- </a>
- </td>
- </tr>
- ))}
- </tbody>
- </table>
-
- <div className="spacer-top small">
- <a href={`${window.baseUrl}/measures/manage`}>{translate('see_all')}</a>
- </div>
-
- </section>
-);
-
-export default FavoriteMeasureFilters;
+++ /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 Favorite from '../../../components/controls/Favorite';
-import QualifierIcon from '../../../components/shared/qualifier-icon';
-import { translate } from '../../../helpers/l10n';
-import { getComponentUrl } from '../../../helpers/urls';
-
-const Favorites = ({ favorites }) => (
- <section>
- <h2 className="spacer-bottom">
- {translate('my_account.favorite_components')}
- </h2>
-
- {!favorites.length && (
- <p className="note">
- {translate('my_account.no_favorite_components')}
- </p>
- )}
-
- <table id="favorite-components" className="data">
- <tbody>
- {favorites.map(f => (
- <tr key={f.key}>
- <td className="thin">
- <Favorite component={f.key} favorite={true}/>
- </td>
- <td>
- <a href={getComponentUrl(f.key)} className="link-with-icon">
- <QualifierIcon qualifier={f.qualifier}/>
- {' '}
- <span>{f.name}</span>
- </a>
- </td>
- </tr>
- ))}
- </tbody>
- </table>
-
- </section>
-);
-
-export default Favorites;
+++ /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 NotificationsList from './NotificationsList';
-import { translate } from '../../../helpers/l10n';
-
-export default function GlobalNotifications ({ notifications, channels }) {
- return (
- <section>
- <h2 className="spacer-bottom">
- {translate('my_profile.overall_notifications.title')}
- </h2>
-
- <table className="form">
- <thead>
- <tr>
- <th></th>
- {channels.map(channel => (
- <th key={channel} className="text-center">
- <h4>{translate('notification.channel', channel)}</h4>
- </th>
- ))}
- </tr>
- </thead>
-
- <NotificationsList
- notifications={notifications}
- checkboxId={(d, c) => `global_notifs_${d}_${c}`}
- checkboxName={(d, c) => `global_notifs[${d}.${c}]`}/>
- </table>
- </section>
- );
-}
+++ /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 Favorites from './Favorites';
-import FavoriteIssueFilters from './FavoriteIssueFilters';
-import FavoriteMeasureFilters from './FavoriteMeasureFilters';
-import IssueWidgets from './IssueWidgets';
-import { translate } from '../../../helpers/l10n';
-
-const Home = ({ user, favorites, issueFilters, measureFilters }) => (
- <div className="page page-limited">
- <Helmet
- title={translate('my_account.page')}
- titleTemplate="SonarQube - %s"/>
-
- <div className="columns">
- <div className="column-third">
- <Favorites favorites={favorites}/>
- {issueFilters && <FavoriteIssueFilters issueFilters={issueFilters}/>}
- {measureFilters && <FavoriteMeasureFilters measureFilters={measureFilters}/>}
- </div>
-
- <div className="column-third">
- <IssueWidgets/>
- </div>
-
- <div className="column-third">
- <section>
- <h2 className="spacer-bottom">{translate('my_profile.groups')}</h2>
- <ul id="groups">
- {user.groups.map(group => (
- <li
- key={group}
- className="little-spacer-bottom text-ellipsis"
- title={group}>
- {group}
- </li>
- ))}
- </ul>
- </section>
-
- <section className="huge-spacer-top">
- <h2 className="spacer-bottom">{translate('my_profile.scm_accounts')}</h2>
- <ul id="scm-accounts">
- <li
- className="little-spacer-bottom text-ellipsis"
- title={user.login}>
- {user.login}
- </li>
- {user.email && (
- <li
- className="little-spacer-bottom text-ellipsis"
- title={user.email}>
- {user.email}
- </li>
- )}
- {user.scmAccounts.map(scmAccount => (
- <li
- key={scmAccount}
- className="little-spacer-bottom text-ellipsis"
- title={scmAccount}>
- {scmAccount}
- </li>
- ))}
- </ul>
- </section>
- </div>
- </div>
- </div>
-);
-
-export default Home;
+++ /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 _ from 'underscore';
-import moment from 'moment';
-import React, { Component } from 'react';
-
-import SeverityHelper from '../../../components/shared/severity-helper';
-import { BarChart } from '../../../components/charts/bar-chart';
-import { getFacets, getFacet } from '../../../api/issues';
-import { translate } from '../../../helpers/l10n';
-import { formatMeasure } from '../../../helpers/measures';
-
-const BASE_QUERY = { resolved: false, assignees: '__me__' };
-
-function getTotalUrl () {
- return window.baseUrl + '/account/issues#resolved=false';
-}
-
-function getToFixUrl () {
- return window.baseUrl + '/account/issues#resolved=false|statuses=CONFIRMED';
-}
-
-function getToReviewUrl () {
- return window.baseUrl + '/account/issues#resolved=false|statuses=' + encodeURIComponent('OPEN,REOPENED');
-}
-
-function getSeverityUrl (severity) {
- return window.baseUrl + '/account/issues#resolved=false|severities=' + severity;
-}
-
-function getProjectUrl (project) {
- return window.baseUrl + '/account/issues#resolved=false|projectUuids=' + project;
-}
-
-function getPeriodUrl (createdAfter, createdBefore) {
- return window.baseUrl + `/account/issues#resolved=false|createdAfter=${createdAfter}|createdBefore=${createdBefore}`;
-}
-
-export default class IssueWidgets extends Component {
- state = {
- loading: true
- };
-
- componentDidMount () {
- this.fetchIssues();
- }
-
- fetchIssues () {
- Promise.all([
- this.fetchFacets(),
- this.fetchByDate()
- ]).then(responses => {
- const facets = responses[0];
- const byDate = responses[1];
-
- this.setState({
- loading: false,
- total: facets.total,
- severities: facets.severities,
- projects: facets.projects,
- toFix: facets.toFix,
- toReview: facets.toReview,
- byDate
- });
- });
- }
-
- fetchFacets () {
- return getFacets(BASE_QUERY, ['statuses', 'severities', 'projectUuids']).then(r => {
- const severities = _.sortBy(
- _.findWhere(r.facets, { property: 'severities' }).values,
- (facet) => window.severityComparator(facet.val)
- );
-
- const projects = _.findWhere(r.facets, { property: 'projectUuids' }).values.map(p => {
- const base = _.findWhere(r.response.components, { uuid: p.val });
- return Object.assign({}, p, base);
- });
-
- const statuses = _.findWhere(r.facets, { property: 'statuses' }).values;
- const toFix = _.findWhere(statuses, { val: 'CONFIRMED' }).count;
- const toReview = _.findWhere(statuses, { val: 'OPEN' }).count +
- _.findWhere(statuses, { val: 'REOPENED' }).count;
-
- const total = r.response.total;
-
- return { severities, projects, toFix, toReview, total };
- });
- }
-
- fetchByDate () {
- return getFacet(Object.assign({ createdInLast: '1w' }, BASE_QUERY), 'createdAt').then(r => r.facet);
- }
-
- handleByDateClick ({ value }) {
- const created = moment(value);
- const createdAfter = created.format('YYYY-MM-DD');
- const createdBefore = created.add(1, 'days').format('YYYY-MM-DD');
- window.location = getPeriodUrl(createdAfter, createdBefore);
- }
-
- renderByDate () {
- const data = this.state.byDate.map((d, x) => {
- return { x, y: d.count, value: d.val };
- });
- const xTicks = this.state.byDate.map(d => moment(d.val).format('dd'));
- const xValues = this.state.byDate.map(d => d.count);
-
- return (
- <section className="abs-width-300 huge-spacer-top account-bar-chart">
- <h4 className="spacer-bottom">
- {translate('my_account.issue_widget.leak_last_week')}
- </h4>
- <BarChart
- data={data}
- xTicks={xTicks}
- xValues={xValues}
- barsWidth={20}
- padding={[25, 10, 25, 10]}
- height={80}
- onBarClick={this.handleByDateClick.bind(this)}/>
- </section>
- );
- }
-
- render () {
- return (
- <section>
- <h2 className="spacer-bottom">{translate('my_account.my_issues')}</h2>
-
- {this.state.loading && (
- <i className="spinner"/>
- )}
-
- {!this.state.loading && (
- <section className="abs-width-300">
- <table className="data zebra">
- <tbody>
- <tr>
- <td>
- <strong>{translate('total')}</strong>
- </td>
- <td className="thin nowrap text-right">
- <a href={getTotalUrl()}>
- {formatMeasure(this.state.total, 'SHORT_INT')}
- </a>
- </td>
- </tr>
- <tr>
- <td>
- <span className="spacer-left">{translate('my_account.to_review')}</span>
- </td>
- <td className="thin nowrap text-right">
- <a href={getToReviewUrl()}>
- {formatMeasure(this.state.toReview, 'SHORT_INT')}
- </a>
- </td>
- </tr>
- <tr>
- <td>
- <span className="spacer-left">{translate('my_account.to_fix')}</span>
- </td>
- <td className="thin nowrap text-right">
- <a href={getToFixUrl()}>
- {formatMeasure(this.state.toFix, 'SHORT_INT')}
- </a>
- </td>
- </tr>
- </tbody>
- </table>
- </section>
- )}
-
- {!this.state.loading && this.renderByDate()}
-
- {!this.state.loading && (
- <section className="abs-width-300 huge-spacer-top">
- <h4 className="spacer-bottom">
- {translate('my_account.issue_widget.by_severity')}
- </h4>
- <table className="data zebra">
- <tbody>
- {this.state.severities.map(s => (
- <tr key={s.val}>
- <td>
- <SeverityHelper severity={s.val}/>
- </td>
- <td className="thin nowrap text-right">
- <a href={getSeverityUrl(s.val)}>
- {formatMeasure(s.count, 'SHORT_INT')}
- </a>
- </td>
- </tr>
- ))}
- </tbody>
- </table>
- </section>
- )}
-
- {!this.state.loading && (
- <section className="abs-width-300 huge-spacer-top">
- <h4 className="spacer-bottom">
- {translate('my_account.issue_widget.by_project')}
- </h4>
- <table className="data zebra">
- <tbody>
- {this.state.projects.map(p => (
- <tr key={p.val}>
- <td>
- {p.name}
- </td>
- <td className="thin nowrap text-right">
- <a href={getProjectUrl(p.val)}>
- {formatMeasure(p.count, 'SHORT_INT')}
- </a>
- </td>
- </tr>
- ))}
- </tbody>
- </table>
- </section>
- )}
- </section>
- );
- }
-}
+++ /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, { Component } from 'react';
-import Helmet from 'react-helmet';
-
-import IssuesApp from '../issues-app';
-import { translate } from '../../../helpers/l10n';
-
-export default class Issues extends Component {
- componentDidMount () {
- this.issuesApp = IssuesApp;
- this.issuesApp.start({
- el: this.refs.container
- });
- }
-
- componentWillUnmount () {
- this.issuesApp.stop();
- }
-
- render () {
- const title = translate('my_account.page') + ' - ' +
- translate('issues.page');
-
- return (
- <div>
- <Helmet
- title={title}
- titleTemplate="SonarQube - %s"/>
- <div ref="container"></div>
- </div>
- );
- }
-}
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
-import { IndexLink } from 'react-router';
-
-import UserCard from './UserCard';
+import { Link } from 'react-router';
import { translate } from '../../../helpers/l10n';
-const Nav = ({ user }) => (
- <header className="account-header">
- <UserCard user={user}/>
-
- <nav className="account-nav clearfix">
- <ul className="nav navbar-nav nav-tabs">
- <li>
- <IndexLink to="/" activeClassName="active">
- <i className="icon-home"/>
- </IndexLink>
- </li>
- <li>
- <a
- className={window.location.pathname === `${window.baseUrl}/account/issues` && 'active'}
- href={`${window.baseUrl}/account/issues`}>
- {translate('issues.page')}
- </a>
- </li>
- <li>
- <IndexLink to="projects" activeClassName="active">
- {translate('my_account.projects')}
- </IndexLink>
- </li>
- <li>
- <IndexLink to="notifications" activeClassName="active">
- {translate('my_account.notifications')}
- </IndexLink>
- </li>
- <li>
- <IndexLink to="security" activeClassName="active">
- {translate('my_account.security')}
- </IndexLink>
- </li>
- </ul>
- </nav>
- </header>
+const Nav = () => (
+ <nav className="account-nav clearfix">
+ <ul className="nav navbar-nav nav-tabs">
+ <li>
+ <Link to="/account/profile/" activeClassName="active">
+ {translate('my_account.profile')}
+ </Link>
+ </li>
+ <li>
+ <Link to="/account/security/" activeClassName="active">
+ {translate('my_account.security')}
+ </Link>
+ </li>
+ <li>
+ <Link to="/account/notifications/" activeClassName="active">
+ {translate('my_account.notifications')}
+ </Link>
+ </li>
+ <li>
+ <Link to="/account/projects/" activeClassName="active">
+ {translate('my_account.projects')}
+ </Link>
+ </li>
+ </ul>
+ </nav>
);
export default Nav;
+++ /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 GlobalNotifications from './GlobalNotifications';
-import ProjectNotifications from './ProjectNotifications';
-import { translate } from '../../../helpers/l10n';
-
-const Notifications = ({ globalNotifications, projectNotifications, onAddProject, onRemoveProject }) => {
- const channels = globalNotifications[0].channels.map(c => c.id);
-
- return (
- <div className="page page-limited">
- <p className="big-spacer-bottom">
- {translate('notification.dispatcher.information')}
- </p>
- <form id="notif_form" method="post" action={`${window.baseUrl}/account/update_notifications`}>
- <div className="columns columns-overflow-visible">
- <div className="column-half">
- <GlobalNotifications
- notifications={globalNotifications}
- channels={channels}/>
- </div>
-
- <div className="column-half">
- <ProjectNotifications
- notifications={projectNotifications}
- channels={channels}
- onAddProject={onAddProject}
- onRemoveProject={onRemoveProject}/>
- </div>
- </div>
-
- <p className="big-spacer-top panel panel-vertical bordered-top text-right">
- <button id="submit-notifications" type="submit">
- {translate('my_profile.notifications.submit')}
- </button>
- </p>
- </form>
- </div>
- );
-};
-
-export default Notifications;
+++ /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 Notifications from './Notifications';
-import { translate } from '../../../helpers/l10n';
-
-export default class NotificationsContainer extends React.Component {
- state = {
- globalNotifications: window.sonarqube.notifications.global,
- projectNotifications: window.sonarqube.notifications.project
- };
-
- componentWillMount () {
- this.handleAddProject = this.handleAddProject.bind(this);
- this.handleRemoveProject = this.handleRemoveProject.bind(this);
- }
-
- handleAddProject (project) {
- const { projectNotifications } = this.state;
- const found = projectNotifications
- .find(notification => notification.project.internalId === project.internalId);
-
- if (!found) {
- const newProjectNotification = {
- project,
- notifications: window.sonarqube.notifications.projectDispatchers.map(dispatcher => {
- const channels = window.sonarqube.notifications.channels.map(channel => {
- return { id: channel, checked: false };
- });
- return { dispatcher, channels };
- })
- };
-
- this.setState({
- projectNotifications: [...projectNotifications, newProjectNotification]
- });
- }
- }
-
- handleRemoveProject (project) {
- const projectNotifications = this.state.projectNotifications
- .filter(notification => notification.project.internalId !== project.internalId);
- this.setState({ projectNotifications });
- }
-
- render () {
- const title = translate('my_account.page') + ' - ' +
- translate('my_account.notifications');
-
- return (
- <div>
- <Helmet
- title={title}
- titleTemplate="SonarQube - %s"/>
-
- <Notifications
- globalNotifications={this.state.globalNotifications}
- projectNotifications={this.state.projectNotifications}
- onAddProject={this.handleAddProject}
- onRemoveProject={this.handleRemoveProject}/>
- </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 { translate } from '../../../helpers/l10n';
-
-export default function NotificationsList ({ notifications, checkboxName, checkboxId }) {
- return (
- <tbody>
- {notifications.map(notification => (
- <tr key={notification.dispatcher}>
- <td>{translate('notification.dispatcher', notification.dispatcher)}</td>
- {notification.channels.map(channel => (
- <td key={channel.id} className="text-center">
- <input defaultChecked={channel.checked}
- id={checkboxId(notification.dispatcher, channel.id)}
- name={checkboxName(notification.dispatcher, channel.id)}
- type="checkbox"/>
- </td>
- ))}
- </tr>
- ))}
- </tbody>
- );
-}
+++ /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 classNames from 'classnames';
-import React, { Component } from 'react';
-
-import NotificationsList from './NotificationsList';
-import { translate } from '../../../helpers/l10n';
-
-export default class ProjectNotification extends Component {
- state = {
- toDelete: false
- };
-
- handleRemoveProject (e) {
- e.preventDefault();
- if (this.state.toDelete) {
- const { data, onRemoveProject } = this.props;
- onRemoveProject(data.project);
- } else {
- this.setState({ toDelete: true });
- }
- }
-
- render () {
- const { data, channels } = this.props;
- const buttonClassName = classNames('big-spacer-left', 'button-red', {
- 'active': this.state.toDelete
- });
-
- return (
- <table key={data.project.internalId} className="form big-spacer-bottom">
- <thead>
- <tr>
- <th>
- <h4 className="display-inline-block">{data.project.name}</h4>
- <button
- onClick={this.handleRemoveProject.bind(this)}
- className={buttonClassName}>
- {this.state.toDelete ? 'Sure?' : translate('delete')}
- </button>
- </th>
- {channels.map(channel => (
- <th key={channel} className="text-center">
- <h4>{translate('notification.channel', channel)}</h4>
- </th>
- ))}
- </tr>
- </thead>
- <NotificationsList
- notifications={data.notifications}
- checkboxId={(d, c) => `project_notifs_${data.project.internalId}_${d}_${c}`}
- checkboxName={(d, c) => `project_notifs[${data.project.internalId}][${d}][${c}]`}/>
- </table>
- );
- }
-}
+++ /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 Select from 'react-select';
-
-import ProjectNotification from './ProjectNotification';
-import { translate } from '../../../helpers/l10n';
-import { getProjectsWithInternalId } from '../../../api/components';
-
-export default function ProjectNotifications ({ notifications, channels, onAddProject, onRemoveProject }) {
- const loadOptions = (query) => {
- return getProjectsWithInternalId(query)
- .then(results => results.map(r => {
- return {
- value: r.id,
- label: r.text
- };
- }))
- .then(options => {
- return { options };
- });
- };
-
- const handleAddProject = (selected) => {
- const project = {
- internalId: selected.value,
- name: selected.label
- };
- onAddProject(project);
- };
-
- return (
- <section>
- <h2 className="spacer-bottom">
- {translate('my_profile.per_project_notifications.title')}
- </h2>
-
- {!notifications.length && (
- <div className="note">
- {translate('my_account.no_project_notifications')}
- </div>
- )}
-
- {notifications.map(p => (
- <ProjectNotification
- key={p.project.internalId}
- data={p}
- channels={channels}
- onRemoveProject={onRemoveProject}/>
- ))}
-
- <div className="huge-spacer-top panel bg-muted">
- <span className="text-middle spacer-right">
- Set notifications for:
- </span>
- <Select.Async
- name="new_project"
- style={{ width: '150px' }}
- loadOptions={loadOptions}
- onChange={handleAddProject}
- placeholder="Search Project"/>
- </div>
- </section>
- );
-}
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
+import { connect } from 'react-redux';
import Helmet from 'react-helmet';
import Password from './Password';
import Tokens from './Tokens';
import { translate } from '../../../helpers/l10n';
+import { getCurrentUser } from '../../../app/store/rootReducer';
-const Security = ({ user }) => {
- const title = translate('my_account.page') + ' - ' +
- translate('my_account.security');
+class Security extends React.Component {
+ render () {
+ const { user } = this.props;
- return (
- <div className="page page-limited">
- <Helmet
- title={title}
- titleTemplate="SonarQube - %s"/>
+ const title = translate('my_account.page') + ' - ' +
+ translate('my_account.security');
- <div className="columns">
- <div className="column-half">
- <Tokens user={user}/>
- </div>
+ return (
+ <div className="account-body account-container">
+ <Helmet
+ title={title}
+ titleTemplate="SonarQube - %s"/>
+
+ <Tokens user={user}/>
+
+ {user.local && (
+ <hr className="account-separator"/>
+ )}
{user.local && (
- <div className="column-half">
- <Password user={user}/>
- </div>
+ <Password user={user}/>
)}
</div>
- </div>
- );
-};
+ );
+ }
+}
-export default Security;
+export default connect(
+ state => ({ user: getCurrentUser(state) })
+)(Security);
*/
import React from 'react';
import { IndexLink } from 'react-router';
-
-import UserExternalIdentity from './UserExternalIdentity';
import Avatar from '../../../components/ui/Avatar';
-const UserCard = ({ user }) => {
- return (
- <section className="account-user clearfix">
- <div id="avatar" className="account-nav-avatar">
- <IndexLink to="/" className="link-no-underline">
- <Avatar email={user.email} size={48}/>
- </IndexLink>
- </div>
- <div>
- <IndexLink to="/" className="link-no-underline">
- <h1 id="name" className="display-inline-block">{user.name}</h1>
+export default class UserCard extends React.Component {
+ static propTypes = {
+ user: React.PropTypes.object.isRequired
+ };
+
+ render () {
+ const { user } = this.props;
+
+ 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>
- <span id="login" className="note big-spacer-left">{user.login}</span>
- {!user.local && user.externalProvider !== 'sonarqube' && (
- <span id="identity-provider" className="big-spacer-left">
- <UserExternalIdentity user={user}/>
- </span>
- )}
</div>
- <div id="email" className="little-spacer-top">{user.email}</div>
- </section>
- );
-};
-
-export default UserCard;
+ );
+ }
+}
+++ /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 { getIdentityProviders } from '../../../api/users';
-
-export default class UserExternalIdentity extends React.Component {
- state = {
- loading: true
- };
-
- componentDidMount () {
- this.mounted = true;
- this.fetchIdentityProviders();
- }
-
- componentDidUpdate (nextProps) {
- if (nextProps.user !== this.props.user) {
- this.this.fetchIdentityProviders();
- }
- }
-
- componentWillUnmount () {
- this.mounted = false;
- }
-
- fetchIdentityProviders () {
- this.setState({ loading: true });
- getIdentityProviders()
- .then(r => r.identityProviders)
- .then(providers => {
- if (this.mounted) {
- const identityProvider = providers
- .find(provider => provider.key === this.props.user.externalProvider);
- this.setState({ loading: false, identityProvider });
- }
- })
- .catch(() => {
- if (this.mounted) {
- this.setState({ loading: false });
- }
- });
- }
-
- render () {
- const { user } = this.props;
- const { loading, identityProvider } = this.state;
-
- if (loading) {
- return null;
- }
-
- if (!identityProvider) {
- return (
- <span className="note">
- {user.externalProvider}{': '}{user.externalIdentity}
- </span>
- );
- }
-
- return (
- <div
- className="identity-provider"
- style={{ backgroundColor: identityProvider.backgroundColor }}>
- <img src={window.baseUrl + identityProvider.iconPath} width="14" height="14"/>
- {' '}
- {user.externalIdentity}
- </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;
+++ /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 $ from 'jquery';
-import _ from 'underscore';
-import Backbone from 'backbone';
-import Marionette from 'backbone.marionette';
-import State from '../issues/models/state';
-import Layout from '../issues/layout';
-import Issues from '../issues/models/issues';
-import Facets from '../../components/navigator/models/facets';
-import Filters from '../issues/models/filters';
-import Controller from '../issues/controller';
-import Router from '../issues/router';
-import WorkspaceListView from '../issues/workspace-list-view';
-import WorkspaceHeaderView from '../issues/workspace-header-view';
-import FacetsView from './../issues/facets-view';
-
-const App = new Marionette.Application();
-
-const init = function (options) {
- this.config = options.config;
- this.state = new State({
- isContext: true,
- contextQuery: { assignees: '__me__' }
- });
- this.updateContextFacets();
- this.list = new Issues();
- this.facets = new Facets();
- this.filters = new Filters();
-
- this.layout = new Layout({ app: this });
- this.layout.$el.appendTo(options.el);
- this.layout.render();
- $('#footer').addClass('search-navigator-footer');
-
- this.controller = new Controller({ app: this });
-
- this.issuesView = new WorkspaceListView({
- app: this,
- collection: this.list
- });
- this.layout.workspaceListRegion.show(this.issuesView);
- this.issuesView.bindScrollEvents();
-
- this.workspaceHeaderView = new WorkspaceHeaderView({
- app: this,
- collection: this.list
- });
- this.layout.workspaceHeaderRegion.show(this.workspaceHeaderView);
-
- this.facetsView = new FacetsView({
- app: this,
- collection: this.facets
- });
- this.layout.facetsRegion.show(this.facetsView);
-
- this.controller.fetchFilters().done(function () {
- key.setScope('list');
- App.router = new Router({ app: App });
- Backbone.history.start();
- });
-};
-
-App.getContextQuery = function () {
- return { assignees: '__me__' };
-};
-
-App.updateContextFacets = function () {
- const facets = this.state.get('facets');
- const allFacets = this.state.get('allFacets');
- const facetsFromServer = this.state.get('facetsFromServer');
- return this.state.set({
- facets,
- allFacets: _.difference(allFacets, ['assignees']),
- facetsFromServer: _.difference(facetsFromServer, ['assignees'])
- });
-};
-
-App.stop = function () {
- App.layout.destroy();
- Backbone.history.stop();
- $('#footer').removeClass('search-navigator-footer');
-};
-
-App.on('start', function (options) {
- init.call(App, options);
-});
-
-export default 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 NotificationsList from './NotificationsList';
+import { translate } from '../../../helpers/l10n';
+
+export default function GlobalNotifications ({ notifications, channels }) {
+ return (
+ <section>
+ <h2 className="spacer-bottom">
+ {translate('my_profile.overall_notifications.title')}
+ </h2>
+
+ <table className="form">
+ <thead>
+ <tr>
+ <th></th>
+ {channels.map(channel => (
+ <th key={channel} className="text-center">
+ <h4>{translate('notification.channel', channel)}</h4>
+ </th>
+ ))}
+ </tr>
+ </thead>
+
+ <NotificationsList
+ notifications={notifications}
+ checkboxId={(d, c) => `global_notifs_${d}_${c}`}
+ checkboxName={(d, c) => `global_notifs[${d}.${c}]`}/>
+ </table>
+ </section>
+ );
+}
--- /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 GlobalNotifications from './GlobalNotifications';
+import ProjectNotifications from './ProjectNotifications';
+import { translate } from '../../../helpers/l10n';
+
+const Notifications = ({ globalNotifications, projectNotifications, onAddProject, onRemoveProject }) => {
+ const channels = globalNotifications[0].channels.map(c => c.id);
+
+ return (
+ <div>
+ <p className="big-spacer-bottom">
+ {translate('notification.dispatcher.information')}
+ </p>
+ <form id="notif_form" method="post" action={`${window.baseUrl}/account/update_notifications`}>
+ <GlobalNotifications
+ notifications={globalNotifications}
+ channels={channels}/>
+
+ <hr className="account-separator"/>
+
+ <ProjectNotifications
+ notifications={projectNotifications}
+ channels={channels}
+ onAddProject={onAddProject}
+ onRemoveProject={onRemoveProject}/>
+
+ <hr className="account-separator"/>
+
+ <div className="text-center">
+ <button id="submit-notifications" type="submit">
+ {translate('my_profile.notifications.submit')}
+ </button>
+ </div>
+ </form>
+ </div>
+ );
+};
+
+export default Notifications;
--- /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 Notifications from './Notifications';
+import { translate } from '../../../helpers/l10n';
+
+export default class NotificationsContainer extends React.Component {
+ state = {
+ globalNotifications: window.sonarqube.notifications.global,
+ projectNotifications: window.sonarqube.notifications.project
+ };
+
+ componentWillMount () {
+ this.handleAddProject = this.handleAddProject.bind(this);
+ this.handleRemoveProject = this.handleRemoveProject.bind(this);
+ }
+
+ handleAddProject (project) {
+ const { projectNotifications } = this.state;
+ const found = projectNotifications
+ .find(notification => notification.project.internalId === project.internalId);
+
+ if (!found) {
+ const newProjectNotification = {
+ project,
+ notifications: window.sonarqube.notifications.projectDispatchers.map(dispatcher => {
+ const channels = window.sonarqube.notifications.channels.map(channel => {
+ return { id: channel, checked: false };
+ });
+ return { dispatcher, channels };
+ })
+ };
+
+ this.setState({
+ projectNotifications: [...projectNotifications, newProjectNotification]
+ });
+ }
+ }
+
+ handleRemoveProject (project) {
+ const projectNotifications = this.state.projectNotifications
+ .filter(notification => notification.project.internalId !== project.internalId);
+ this.setState({ projectNotifications });
+ }
+
+ render () {
+ const title = translate('my_account.page') + ' - ' +
+ translate('my_account.notifications');
+
+ return (
+ <div className="account-body account-container">
+ <Helmet
+ title={title}
+ titleTemplate="SonarQube - %s"/>
+
+ <Notifications
+ globalNotifications={this.state.globalNotifications}
+ projectNotifications={this.state.projectNotifications}
+ onAddProject={this.handleAddProject}
+ onRemoveProject={this.handleRemoveProject}/>
+ </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 { translate } from '../../../helpers/l10n';
+
+export default function NotificationsList ({ notifications, checkboxName, checkboxId }) {
+ return (
+ <tbody>
+ {notifications.map(notification => (
+ <tr key={notification.dispatcher}>
+ <td>{translate('notification.dispatcher', notification.dispatcher)}</td>
+ {notification.channels.map(channel => (
+ <td key={channel.id} className="text-center">
+ <input defaultChecked={channel.checked}
+ id={checkboxId(notification.dispatcher, channel.id)}
+ name={checkboxName(notification.dispatcher, channel.id)}
+ type="checkbox"/>
+ </td>
+ ))}
+ </tr>
+ ))}
+ </tbody>
+ );
+}
--- /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 classNames from 'classnames';
+import React, { Component } from 'react';
+
+import NotificationsList from './NotificationsList';
+import { translate } from '../../../helpers/l10n';
+
+export default class ProjectNotification extends Component {
+ state = {
+ toDelete: false
+ };
+
+ handleRemoveProject (e) {
+ e.preventDefault();
+ if (this.state.toDelete) {
+ const { data, onRemoveProject } = this.props;
+ onRemoveProject(data.project);
+ } else {
+ this.setState({ toDelete: true });
+ }
+ }
+
+ render () {
+ const { data, channels } = this.props;
+ const buttonClassName = classNames('big-spacer-left', 'button-red', {
+ 'active': this.state.toDelete
+ });
+
+ return (
+ <table key={data.project.internalId} className="form big-spacer-bottom">
+ <thead>
+ <tr>
+ <th>
+ <h4 className="display-inline-block">{data.project.name}</h4>
+ <button
+ onClick={this.handleRemoveProject.bind(this)}
+ className={buttonClassName}>
+ {this.state.toDelete ? 'Sure?' : translate('delete')}
+ </button>
+ </th>
+ {channels.map(channel => (
+ <th key={channel} className="text-center">
+ <h4>{translate('notification.channel', channel)}</h4>
+ </th>
+ ))}
+ </tr>
+ </thead>
+ <NotificationsList
+ notifications={data.notifications}
+ checkboxId={(d, c) => `project_notifs_${data.project.internalId}_${d}_${c}`}
+ checkboxName={(d, c) => `project_notifs[${data.project.internalId}][${d}][${c}]`}/>
+ </table>
+ );
+ }
+}
--- /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 Select from 'react-select';
+
+import ProjectNotification from './ProjectNotification';
+import { translate } from '../../../helpers/l10n';
+import { getProjectsWithInternalId } from '../../../api/components';
+
+export default function ProjectNotifications ({ notifications, channels, onAddProject, onRemoveProject }) {
+ const loadOptions = (query) => {
+ return getProjectsWithInternalId(query)
+ .then(results => results.map(r => {
+ return {
+ value: r.id,
+ label: r.text
+ };
+ }))
+ .then(options => {
+ return { options };
+ });
+ };
+
+ const handleAddProject = (selected) => {
+ const project = {
+ internalId: selected.value,
+ name: selected.label
+ };
+ onAddProject(project);
+ };
+
+ return (
+ <section>
+ <h2 className="spacer-bottom">
+ {translate('my_profile.per_project_notifications.title')}
+ </h2>
+
+ {!notifications.length && (
+ <div className="note">
+ {translate('my_account.no_project_notifications')}
+ </div>
+ )}
+
+ {notifications.map(p => (
+ <ProjectNotification
+ key={p.project.internalId}
+ data={p}
+ channels={channels}
+ onRemoveProject={onRemoveProject}/>
+ ))}
+
+ <div className="spacer-top panel bg-muted">
+ <span className="text-middle spacer-right">
+ Set notifications for:
+ </span>
+ <Select.Async
+ name="new_project"
+ style={{ width: '150px' }}
+ loadOptions={loadOptions}
+ onChange={handleAddProject}
+ placeholder="Search Project"/>
+ </div>
+ </section>
+ );
+}
--- /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 UserExternalIdentity from './UserExternalIdentity';
+import UserGroups from './UserGroups';
+import UserScmAccounts from './UserScmAccounts';
+import { getCurrentUser } from '../../../app/store/rootReducer';
+
+class Profile extends React.Component {
+ render () {
+ const { user } = this.props;
+
+ return (
+ <div className="account-body account-container">
+ <div className="spacer-bottom">
+ Login: <strong id="login">{user.login}</strong>
+ </div>
+
+ {!user.local && user.externalProvider !== 'sonarqube' && (
+ <div id="identity-provider" className="spacer-bottom">
+ <UserExternalIdentity user={user}/>
+ </div>
+ )}
+
+ {!!user.email && (
+ <div className="spacer-bottom">
+ Email: <strong id="email">{user.email}</strong>
+ </div>
+ )}
+
+ <hr className="account-separator"/>
+
+ <UserGroups groups={user.groups}/>
+
+ <hr className="account-separator"/>
+
+ <UserScmAccounts user={user} scmAccounts={user.scmAccounts}/>
+ </div>
+ );
+ }
+}
+
+export default connect(
+ state => ({ user: getCurrentUser(state) })
+)(Profile);
--- /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 { getIdentityProviders } from '../../../api/users';
+
+export default class UserExternalIdentity extends React.Component {
+ state = {
+ loading: true
+ };
+
+ componentDidMount () {
+ this.mounted = true;
+ this.fetchIdentityProviders();
+ }
+
+ componentDidUpdate (nextProps) {
+ if (nextProps.user !== this.props.user) {
+ this.this.fetchIdentityProviders();
+ }
+ }
+
+ componentWillUnmount () {
+ this.mounted = false;
+ }
+
+ fetchIdentityProviders () {
+ this.setState({ loading: true });
+ getIdentityProviders()
+ .then(r => r.identityProviders)
+ .then(providers => {
+ if (this.mounted) {
+ const identityProvider = providers
+ .find(provider => provider.key === this.props.user.externalProvider);
+ this.setState({ loading: false, identityProvider });
+ }
+ })
+ .catch(() => {
+ if (this.mounted) {
+ this.setState({ loading: false });
+ }
+ });
+ }
+
+ render () {
+ const { user } = this.props;
+ const { loading, identityProvider } = this.state;
+
+ if (loading) {
+ return null;
+ }
+
+ if (!identityProvider) {
+ return (
+ <div>
+ {user.externalProvider}{': '}{user.externalIdentity}
+ </div>
+ );
+ }
+
+ return (
+ <div className="identity-provider"
+ style={{ backgroundColor: identityProvider.backgroundColor }}>
+ <img src={window.baseUrl + identityProvider.iconPath} width="14" height="14"/>
+ {' '}
+ {user.externalIdentity}
+ </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 { translate } from '../../../helpers/l10n';
+
+export default class UserGroups extends React.Component {
+ static propTypes = {
+ groups: React.PropTypes.arrayOf(React.PropTypes.string).isRequired
+ };
+
+ render () {
+ const { groups } = this.props;
+
+ return (
+ <div>
+ <h2 className="spacer-bottom">{translate('my_profile.groups')}</h2>
+ <ul id="groups">
+ {groups.map(group => (
+ <li key={group} className="little-spacer-bottom" title={group}>
+ {group}
+ </li>
+ ))}
+ </ul>
+ </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 { translate } from '../../../helpers/l10n';
+
+export default class UserScmAccounts extends React.Component {
+ static propTypes = {
+ user: React.PropTypes.object.isRequired,
+ scmAccounts: React.PropTypes.arrayOf(React.PropTypes.string).isRequired
+ };
+
+ render () {
+ const { user, scmAccounts } = this.props;
+
+ return (
+ <div>
+ <h2 className="spacer-bottom">{translate('my_profile.scm_accounts')}</h2>
+ <ul id="scm-accounts">
+ <li className="little-spacer-bottom text-ellipsis" title={user.login}>
+ {user.login}
+ </li>
+
+ {user.email && (
+ <li className="little-spacer-bottom text-ellipsis" title={user.email}>
+ {user.email}
+ </li>
+ )}
+
+ {scmAccounts.map(scmAccount => (
+ <li key={scmAccount} className="little-spacer-bottom" title={scmAccount}>
+ {scmAccount}
+ </li>
+ ))}
+ </ul>
+ </div>
+ );
+ }
+}
*/
import React from 'react';
import ProjectCard from './ProjectCard';
-import ProjectsSearch from './ProjectsSearch';
import ListFooter from '../../../components/controls/ListFooter';
import { projectsListType } from './propTypes';
import { translate } from '../../../helpers/l10n';
const { projects } = this.props;
return (
- <div className="page page-limited account-projects">
- <header className="page-header">
- <h1 className="page-title">
- My Projects
- </h1>
- <div className="pull-right">
- <ProjectsSearch onSearch={this.props.search}/>
- </div>
- <div className="page-description">
- {translate('my_account.projects.description')}
- </div>
- </header>
-
- {projects.length === 0 && (
+ <div id="account-projects">
+ {projects.length === 0 ? (
<div className="js-no-results">
{translate('my_account.projects.no_results')}
</div>
+ ) : (
+ <p>
+ {translate('my_account.projects.description')}
+ </p>
)}
{projects.length > 0 && (
componentWillMount () {
this.loadMore = this.loadMore.bind(this);
this.search = this.search.bind(this);
- document.querySelector('html').classList.add('dashboard-page');
}
componentDidMount () {
componentWillUnmount () {
this.mounted = false;
- document.querySelector('html').classList.remove('dashboard-page');
}
loadProjects (page = this.state.page, query = this.state.query) {
translate('my_account.projects');
return (
- <div>
+ <div className="account-body account-container">
<Helmet
title={title}
titleTemplate="SonarQube - %s"/>
+++ /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 debounce from 'lodash/debounce';
-import { translate, translateWithParameters } from '../../../helpers/l10n';
-
-export default class ProjectsSearch extends React.Component {
- static propTypes = {
- onSearch: React.PropTypes.func.isRequired
- };
-
- componentWillMount () {
- this.handleChange = this.handleChange.bind(this);
- this.handleSubmit = this.handleSubmit.bind(this);
- this.onSearch = debounce(this.props.onSearch, 250);
- }
-
- handleChange () {
- const { value } = this.refs.input;
- if (value.length > 2 || value.length === 0) {
- this.onSearch(value);
- }
- }
-
- handleSubmit (e) {
- e.preventDefault();
- this.handleChange();
- }
-
- render () {
- return (
- <div>
- <form onSubmit={this.handleSubmit}>
- <input
- ref="input"
- type="search"
- className="input-large"
- placeholder={translate('search_verb')}
- onChange={this.handleChange}/>
- <div className="note little-spacer-top text-right">
- {translateWithParameters(
- 'my_account.projects.x_characters_min', 3)}
- </div>
- </form>
- </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 { 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 Security from './components/Security';
+import Profile from './profile/Profile';
+
+export default (
+ <Route path="account" component={Account}>
+ <IndexRoute component={MyActivityContainer}/>
+ <Route path="profile" component={Profile}/>
+ <Route path="security" component={Security}/>
+ <Route path="notifications" component={NotificationsContainer}/>
+ <Route path="projects" component={ProjectsContainer}/>
+ </Route>
+);
</table>
{{/notNull}}
-<h4 class="big-spacer-top spacer-bottom">Generate Tokens</h4>
-
{{#each errors}}
<div class="alert alert-danger">{{msg}}</div>
{{/each}}
-<form class="js-generate-token-form">
+<form class="js-generate-token-form spacer-top panel bg-muted">
+ <label>Generate New Token:</label>
<input type="text" required maxlength="100" placeholder="Enter Token Name">
<button>Generate</button>
</form>
* @returns {Promise}
*/
export function delay (response) {
- return new Promise(resolve => setTimeout(() => resolve(response), 500));
+ return new Promise(resolve => setTimeout(() => resolve(response), 1200));
}
return window.baseUrl + '/dashboard?id=' + encodeURIComponent(componentKey);
}
+/**
+ * Generate URL for a global issues page
+ * @param {object} query
+ * @returns {string}
+ */
+export function getIssuesUrl (query) {
+ const serializedQuery = Object.keys(query).map(criterion => (
+ `${encodeURIComponent(criterion)}=${encodeURIComponent(query[criterion])}`
+ )).join('|');
+ return window.baseUrl + '/issues/search#' + serializedQuery;
+}
+
/**
* Generate URL for a component's issues page
* @param {string} componentKey
const baseQuery = { activation: 'true', statuses: 'DEPRECATED' };
return getRulesUrl({ ...query, ...baseQuery });
}
+
+export const getProjectsUrl = () => {
+ return window.baseUrl + '/measures/search?qualifiers[]=TRK';
+};
end
end
- redirect_to "#{ApplicationController.root_context}/account/notifications"
+ redirect_to "#{ApplicationController.root_context}/account/notifications/"
end
private
<% content_for :extra_script do %>
<script>
- window.sonarqube.user = {
- favorites: [
- <% current_user.favourites.each_with_index do |f, index| %>
- {
- id: '<%= escape_javascript f.uuid -%>',
- key: '<%= escape_javascript f.key -%>',
- name: '<%= escape_javascript f.name -%>',
- qualifier: '<%= escape_javascript f.qualifier -%>'
- },
- <% end %>
- ],
- favoriteMeasureFilters: [
- <% current_user.favourited_measure_filters.each do |filter| %>
- {
- id: <%= filter.id -%>,
- name: '<%= escape_javascript filter.name -%>'
- },
- <% end %>
- ]
- };
window.sonarqube.notifications = {
channels: [
<% for channel in @channels -%>
]
};
</script>
- <script src="<%= ApplicationController.root_context -%>/js/bundles/account.js?v=<%= sonar_version -%>"></script>
+ <script src="<%= ApplicationController.root_context -%>/js/bundles/app.js?v=<%= sonar_version -%>"></script>
<% end %>
move_left=Move left
move_right=Move right
my_account.favorite_components=Favorite Components
-my_account.favorite_issue_filters=Favorite Issue Filters
my_account.favorite_measure_filters=Favorite Measure Filters
my_account.issue_widget.by_project=My Issues by Project
my_account.issue_widget.by_severity=My Issues by Severity
my_account.security=Security
my_account.tokens_description=If you want to enforce security by not providing credentials of a real SonarQube user to run your code scan or to invoke web services, you can provide a User Token as a replacement of the user login. This will increase the security of your installation by not letting your analysis user's password going through your network.
my_account.to_fix=To Fix
-my_account.to_review=To Review
my_profile.add_project=Add project
my_profile.email=Email
my_profile.favorites.title=Favorites
my_account.no_favorite_measure_filters=You do not have favorite measure filters yet.
my_account.notifications=Notifications
my_account.no_project_notifications=You have not set project notifications yet.
+my_account.profile=Profile
my_account.security=Security
my_account.tokens_description=If you want to enforce security by not providing credentials of a real SonarQube user to run your code scan or to invoke web services, you can provide a User Token as a replacement of the user login. This will increase the security of your installation by not letting your analysis user's password going through your network.
my_account.my_issues=My Issues
component_measures.legend.size_x=Size: {0}
component_measures.x_of_y={0} of {1}
component_measures.no_history=There is no historical data.
+
+
+#------------------------------------------------------------------------------
+#
+# MY ACTIVITY
+#
+#------------------------------------------------------------------------------
+my_activity.my_issues=My Issues
+my_activity.last_week=Last Week
+my_activity.all_time=All Time
+my_activity.my_favorite_projects=My Favorite Projects
+my_activity.no_favorite_projects=You don't have any favorite projects yet.
+my_activity.no_favorite_projects.engagement=Discover and favorite projects you are interested in to have a quick access to them.
+my_activity.explore_projects=Explore Projects