aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main/js
diff options
context:
space:
mode:
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>2017-03-27 14:46:10 +0200
committerGrégoire Aubert <gregaubert@users.noreply.github.com>2017-03-31 10:29:27 +0200
commit5d4fa78653c23a9bb2314f7a49aba88709742f95 (patch)
treefaf9cb4221c299bc18ab1c8b9a408c9eb541ed2e /server/sonar-web/src/main/js
parentbc5356a7c0c644e19dd2ce20716ba1b3e0d1cdc1 (diff)
downloadsonarqube-5d4fa78653c23a9bb2314f7a49aba88709742f95.tar.gz
sonarqube-5d4fa78653c23a9bb2314f7a49aba88709742f95.zip
SONAR-8990 Create the organizations members store
Diffstat (limited to 'server/sonar-web/src/main/js')
-rw-r--r--server/sonar-web/src/main/js/api/organizations.js10
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/actions.js56
-rw-r--r--server/sonar-web/src/main/js/store/organizationsMembers/actions.js62
-rw-r--r--server/sonar-web/src/main/js/store/organizationsMembers/reducer.js65
-rw-r--r--server/sonar-web/src/main/js/store/rootReducer.js17
-rw-r--r--server/sonar-web/src/main/js/store/users/reducer.js33
6 files changed, 232 insertions, 11 deletions
diff --git a/server/sonar-web/src/main/js/api/organizations.js b/server/sonar-web/src/main/js/api/organizations.js
index 95de53bb6d9..0723fb0c258 100644
--- a/server/sonar-web/src/main/js/api/organizations.js
+++ b/server/sonar-web/src/main/js/api/organizations.js
@@ -54,3 +54,13 @@ export const updateOrganization = (key: string, changes: {}) =>
post('/api/organizations/update', { key, ...changes });
export const deleteOrganization = (key: string) => post('/api/organizations/delete', { key });
+
+export const searchMembers = (
+ data: { organizations?: string, p?: number, ps?: number, q?: string, selected?: string }
+) => getJSON('/api/organizations/search_members', data);
+
+export const addMember = (data: { login: string, organization: string }) =>
+ post('/api/organizations/add_member', data);
+
+export const removeMember = (data: { login: string, organization: string }) =>
+ post('/api/organizations/remove_member', data);
diff --git a/server/sonar-web/src/main/js/apps/organizations/actions.js b/server/sonar-web/src/main/js/apps/organizations/actions.js
index 61f0bd8ac0b..916a9d7e17a 100644
--- a/server/sonar-web/src/main/js/apps/organizations/actions.js
+++ b/server/sonar-web/src/main/js/apps/organizations/actions.js
@@ -19,18 +19,28 @@
*/
// @flow
import * as api from '../../api/organizations';
-import { onFail } from '../../store/rootActions';
import * as actions from '../../store/organizations/duck';
+import * as membersActions from '../../store/organizationsMembers/actions';
+import { onFail } from '../../store/rootActions';
+import { getOrganizationMembersState } from '../../store/rootReducer';
import { addGlobalSuccessMessage } from '../../store/globalMessages/duck';
import { translate, translateWithParameters } from '../../helpers/l10n';
import type { Organization } from '../../store/organizations/duck';
+const PAGE_SIZE = 50;
+
const onRejected = (dispatch: Function) =>
(error: Object) => {
onFail(dispatch)(error);
return Promise.reject();
};
+const onMembersFail = (organization: string, dispatch: Function) =>
+ (error: Object) => {
+ onFail(dispatch)(error);
+ dispatch(membersActions.updateState(organization, { loading: false }));
+ };
+
export const fetchOrganization = (key: string): Function =>
(dispatch: Function): Promise<*> => {
const onFulfilled = ([organization, navigation]) => {
@@ -77,3 +87,47 @@ export const deleteOrganization = (key: string): Function =>
return api.deleteOrganization(key).then(onFulfilled, onFail(dispatch));
};
+
+const fetchMembers = (
+ dispatch: Function,
+ receiveAction: Function,
+ key: string,
+ query?: string,
+ page?: number
+) => {
+ dispatch(membersActions.updateState(key, { loading: true }));
+ const data: Object = {
+ organizations: key,
+ ps: PAGE_SIZE
+ };
+ if (page != null) {
+ data.p = page;
+ }
+ if (query) {
+ data.q = query;
+ }
+ return api.searchMembers(data).then(
+ response => {
+ dispatch(receiveAction(key, response.users, {
+ loading: false,
+ total: response.paging.total,
+ pageIndex: response.paging.pageIndex,
+ query: query || null
+ }));
+ },
+ onMembersFail(key, dispatch)
+ );
+};
+
+export const fetchOrganizationMembers = (key: string, query?: string) =>
+ (dispatch: Function) => fetchMembers(dispatch, membersActions.receiveMembers, key, query);
+
+export const fetchMoreOrganizationMembers = (key: string, query?: string) =>
+ (dispatch: Function, getState: Function) =>
+ fetchMembers(
+ dispatch,
+ membersActions.receiveMoreMembers,
+ key,
+ query,
+ getOrganizationMembersState(getState(), key).pageIndex + 1
+ );
diff --git a/server/sonar-web/src/main/js/store/organizationsMembers/actions.js b/server/sonar-web/src/main/js/store/organizationsMembers/actions.js
new file mode 100644
index 00000000000..44de107c3f8
--- /dev/null
+++ b/server/sonar-web/src/main/js/store/organizationsMembers/actions.js
@@ -0,0 +1,62 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info 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.
+ */
+//@flow
+export type Member = {
+ login: string,
+ name: string,
+ avatar: string,
+ groupCount: number
+};
+
+type MembersState = {
+ paging?: number,
+ total?: number,
+ loading?: boolean,
+ query?: string | null
+};
+
+export const actions = {
+ UPDATE_STATE: 'organizations/UPDATE_STATE',
+ RECEIVE_MEMBERS: 'organizations/RECEIVE_MEMBERS',
+ RECEIVE_MORE_MEMBERS: 'organizations/RECEIVE_MORE_MEMBERS'
+};
+
+export const receiveMembers = (organizationKey: string, members: Array<Member>, stateChanges: MembersState) => ({
+ type: actions.RECEIVE_MEMBERS,
+ organization: organizationKey,
+ members,
+ stateChanges
+});
+
+export const receiveMoreMembers = (organizationKey: string, members: Array<Member>, stateChanges: MembersState) => ({
+ type: actions.RECEIVE_MORE_MEMBERS,
+ organization: organizationKey,
+ members,
+ stateChanges
+});
+
+export const updateState = (
+ organizationKey: string,
+ stateChanges: MembersState
+) => ({
+ type: actions.UPDATE_STATE,
+ organization: organizationKey,
+ stateChanges
+});
diff --git a/server/sonar-web/src/main/js/store/organizationsMembers/reducer.js b/server/sonar-web/src/main/js/store/organizationsMembers/reducer.js
new file mode 100644
index 00000000000..1e7e5094945
--- /dev/null
+++ b/server/sonar-web/src/main/js/store/organizationsMembers/reducer.js
@@ -0,0 +1,65 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info 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 { uniq } from 'lodash';
+import { actions } from './actions';
+
+export const getOrganizationMembersLogins = (state, organization) => {
+ if (organization && state[organization]) {
+ return state[organization].members || [];
+ }
+ return [];
+};
+
+export const getOrganizationMembersState = (state, organization) =>
+ organization && state[organization] ? state[organization] : {};
+
+const organizationMembers = (state = {}, action = {}) => {
+ switch (action.type) {
+ case actions.UPDATE_STATE:
+ return { ...state, ...action.stateChanges };
+ case actions.RECEIVE_MEMBERS:
+ return { ...state, ...action.stateChanges, members: action.members.map(member => member.login) };
+ case actions.RECEIVE_MORE_MEMBERS:
+ return {
+ ...state,
+ ...action.stateChanges,
+ members: uniq((state.members || []).concat(action.members.map(member => member.login)))
+ };
+ default:
+ return state;
+ }
+};
+
+const organizationsMembers = (state = {}, action = {}) => {
+ const organization = state[action.organization] || {};
+ switch (action.type) {
+ case actions.UPDATE_STATE:
+ case actions.RECEIVE_MEMBERS:
+ case actions.RECEIVE_MORE_MEMBERS:
+ return {
+ ...state,
+ [action.organization]: organizationMembers(organization, action)
+ };
+ default:
+ return state;
+ }
+};
+
+export default organizationsMembers;
diff --git a/server/sonar-web/src/main/js/store/rootReducer.js b/server/sonar-web/src/main/js/store/rootReducer.js
index 91c0a2a9ba6..cac6ee072ea 100644
--- a/server/sonar-web/src/main/js/store/rootReducer.js
+++ b/server/sonar-web/src/main/js/store/rootReducer.js
@@ -27,6 +27,8 @@ import languages, * as fromLanguages from './languages/reducer';
import measures, * as fromMeasures from './measures/reducer';
import notifications, * as fromNotifications from './notifications/duck';
import organizations, * as fromOrganizations from './organizations/duck';
+import organizationsMembers, * as fromOrganizationsMembers
+ from './organizationsMembers/reducer';
import globalMessages, * as fromGlobalMessages from './globalMessages/duck';
import projectActivity from './projectActivity/duck';
import measuresApp, * as fromMeasuresApp from '../apps/component-measures/store/rootReducer';
@@ -46,6 +48,7 @@ export default combineReducers({
measures,
notifications,
organizations,
+ organizationsMembers,
projectActivity,
users,
@@ -72,6 +75,14 @@ export const getLanguageByKey = (state, key) =>
export const getCurrentUser = state => fromUsers.getCurrentUser(state.users);
+export const getUserLogins = state => fromUsers.getUserLogins(state.users);
+
+export const getUserByLogin = (state, login) => fromUsers.getUserByLogin(state.users, login);
+
+export const getUsersByLogins = (state, logins) => fromUsers.getUsersByLogins(state.users, logins);
+
+export const getUsers = state => fromUsers.getUsers(state.users);
+
export const isFavorite = (state, componentKey) =>
fromFavorites.isFavorite(state.favorites, componentKey);
@@ -107,6 +118,12 @@ export const getMyOrganizations = state =>
export const areThereCustomOrganizations = state => getAppState(state).organizationsEnabled;
+export const getOrganizationMembersLogins = (state, organization) =>
+ fromOrganizationsMembers.getOrganizationMembersLogins(state.organizationsMembers, organization);
+
+export const getOrganizationMembersState = (state, organization) =>
+ fromOrganizationsMembers.getOrganizationMembersState(state.organizationsMembers, organization);
+
export const getProjectActivity = state => state.projectActivity;
export const getProjects = state => fromProjectsApp.getProjects(state.projectsApp);
diff --git a/server/sonar-web/src/main/js/store/users/reducer.js b/server/sonar-web/src/main/js/store/users/reducer.js
index f0ec0163898..a6206ffca58 100644
--- a/server/sonar-web/src/main/js/store/users/reducer.js
+++ b/server/sonar-web/src/main/js/store/users/reducer.js
@@ -18,33 +18,46 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { combineReducers } from 'redux';
-import { uniq } from 'lodash';
+import { uniq, keyBy } from 'lodash';
import { RECEIVE_CURRENT_USER } from './actions';
+import { actions as membersActions } from '../organizationsMembers/actions';
const usersByLogin = (state = {}, action = {}) => {
- if (action.type === RECEIVE_CURRENT_USER) {
- return { ...state, [action.user.login]: action.user };
+ switch (action.type) {
+ case RECEIVE_CURRENT_USER:
+ return { ...state, [action.user.login]: action.user };
+ case membersActions.RECEIVE_MEMBERS:
+ case membersActions.RECEIVE_MORE_MEMBERS:
+ return { ...state, ...keyBy(action.members, 'login') };
+ default:
+ return state;
}
-
- return state;
};
const userLogins = (state = [], action = {}) => {
- if (action.type === RECEIVE_CURRENT_USER) {
- return uniq([...state, action.user.login]);
+ switch (action.type) {
+ case RECEIVE_CURRENT_USER:
+ return uniq([...state, action.user.login]);
+ case membersActions.RECEIVE_MEMBERS:
+ case membersActions.RECEIVE_MORE_MEMBERS:
+ return uniq([...state, action.members.map(member => member.login)]);
+ default:
+ return state;
}
-
- return state;
};
const currentUser = (state = null, action = {}) => {
if (action.type === RECEIVE_CURRENT_USER) {
return action.user;
}
-
return state;
};
export default combineReducers({ usersByLogin, userLogins, currentUser });
export const getCurrentUser = state => state.currentUser;
+export const getUserLogins = state => state.userLogins;
+export const getUserByLogin = (state, login) => state.usersByLogin[login];
+export const getUsersByLogins = (state, logins) =>
+ logins.map(login => getUserByLogin(state, login));
+export const getUsers = state => getUsersByLogins(state, getUserLogins(state));