]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-8990 Create the organizations members store
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>
Mon, 27 Mar 2017 12:46:10 +0000 (14:46 +0200)
committerGrégoire Aubert <gregaubert@users.noreply.github.com>
Fri, 31 Mar 2017 08:29:27 +0000 (10:29 +0200)
server/sonar-web/src/main/js/api/organizations.js
server/sonar-web/src/main/js/apps/organizations/actions.js
server/sonar-web/src/main/js/store/organizationsMembers/actions.js [new file with mode: 0644]
server/sonar-web/src/main/js/store/organizationsMembers/reducer.js [new file with mode: 0644]
server/sonar-web/src/main/js/store/rootReducer.js
server/sonar-web/src/main/js/store/users/reducer.js

index 95de53bb6d97d1d0380a2185296fcc69f327b7d9..0723fb0c2588be389f6e3c3441efa3cfd714392e 100644 (file)
@@ -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);
index 61f0bd8ac0b7b346b20b2039d1827a7a8245a03a..916a9d7e17a53a63e6e05f07a23965609b1df2f4 100644 (file)
  */
 // @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 (file)
index 0000000..44de107
--- /dev/null
@@ -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 (file)
index 0000000..1e7e509
--- /dev/null
@@ -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;
index 91c0a2a9ba6df7eddfebb71f6756f5ab6cee9e81..cac6ee072eaf8027ef6cf005b3dcb339c8d1558c 100644 (file)
@@ -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);
index f0ec0163898884df3c4dfabbcfabe47556e74fbc..a6206ffca589ebb5ecd065bf60508bd72806caf8 100644 (file)
  * 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));