Skip to content

Commit

Permalink
'Add' button functionality (User groups)
Browse files Browse the repository at this point in the history
The 'Add' button functionality allows
to associate any user group's member
to a given user.

Signed-off-by: Carla Martinez <[email protected]>
  • Loading branch information
carma12 committed Mar 6, 2024
1 parent 85bcd25 commit 4dd7c04
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 36 deletions.
102 changes: 68 additions & 34 deletions src/components/MemberOf/MemberOfUserGroups.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,39 +12,33 @@ import MemberOfAddModal, { AvailableItems } from "./MemberOfAddModal";
import MemberOfDeleteModal from "./MemberOfDeleteModal";
// Hooks
import { useUserMemberOfData } from "src/hooks/useUserMemberOfData";
import useAlerts from "src/hooks/useAlerts";
// RPC
import { ErrorResult, useGroupAddMemberMutation } from "src/services/rpc";

function paginate<Type>(array: Type[], page: number, perPage: number): Type[] {
function paginate(
array: UserGroup[],
page: number,
perPage: number
): UserGroup[] {
const startIdx = (page - 1) * perPage;
const endIdx = perPage * page - 1;
return array.slice(startIdx, endIdx);
}

interface TypeWithCN {
cn: string;
}

// Filter functions to compare the available data with the data that
// the user is already member of. This is done to prevent duplicates
// (e.g: adding the same element twice).
function filterUserGroupsData<Type extends TypeWithCN>(
list1: Array<Type>,
list2: Array<Type>
): Type[] {
// User groups
return list1.filter((item) => {
return !list2.some((itm) => {
return item.cn === itm.cn;
});
});
}

interface MemberOfUserGroupsProps {
user: Partial<User>;
isUserDataLoading: boolean;
onRefreshUserData: () => void;
}

const MemberOfUserGroups = (props: MemberOfUserGroupsProps) => {
// Alerts to show in the UI
const alerts = useAlerts();

// API call
const [addMemberToUserGroup] = useGroupAddMemberMutation();

// 'User groups' assigned to user
const [userGroupsFromUser, setUserGroupsFromUser] = React.useState<
UserGroup[]
Expand All @@ -66,8 +60,13 @@ const MemberOfUserGroups = (props: MemberOfUserGroupsProps) => {
lastUserIdx,
});

// Member of
const userGroupsFullList = fullUserGroupsQuery.userGroupsFullList;

// Not member of
const userGroupsNotMemberOfFullList =
fullUserGroupsQuery.userGroupsNotMemberOfFullList;

// Get full data of the 'User groups' assigned to user
React.useEffect(() => {
if (!fullUserGroupsQuery.isFetching && userGroupsFullList) {
Expand All @@ -87,6 +86,12 @@ const MemberOfUserGroups = (props: MemberOfUserGroupsProps) => {
}
}, [fullUserGroupsQuery]);

// Refetch User groups when user data changes
React.useEffect(() => {
fullUserGroupsQuery.refetch();
fullUserGroupsQuery.notMemberOfRefetch();
}, [props.user, userGroupsFromUser]);

const [groupsNamesSelected, setGroupsNamesSelected] = React.useState<
string[]
>([]);
Expand All @@ -104,24 +109,21 @@ const MemberOfUserGroups = (props: MemberOfUserGroupsProps) => {
const shownUserGroups = paginate(userGroupsFromUser, page, perPage);
const showTableRows = userGroupsFromUser.length > 0;

// Available data to be added as member of
const userGroupsFilteredData: UserGroup[] = filterUserGroupsData(
userGroupsFullList,
userGroupsFromUser
);

// Parse availableItems to AvailableItems type
const parseAvailableItems = () => {
// Parse availableItems to 'AvailableItems' type
const parseAvailableItems = (itemsList: UserGroup[]) => {
const avItems: AvailableItems[] = [];
userGroupsFilteredData.map((item) => {
itemsList.map((item) => {
avItems.push({
key: item.cn,
title: item.cn,
});
});
return avItems;
};
const availableUserGroupsItems: AvailableItems[] = parseAvailableItems();

const availableUserGroupsItems: AvailableItems[] = parseAvailableItems(
userGroupsNotMemberOfFullList
);

// Buttons functionality
// - Refresh
Expand All @@ -132,20 +134,51 @@ const MemberOfUserGroups = (props: MemberOfUserGroupsProps) => {
React.useEffect(() => {
if (props.isUserDataLoading) {
setIsRefreshButtonEnabled(false);
setIsAddButtonEnabled(false);
} else {
setIsRefreshButtonEnabled(true);
setIsAddButtonEnabled(true);
}
}, [props.isUserDataLoading]);

// 'Add' function
// - 'Add'
const [isAddButtonEnabled, setIsAddButtonEnabled] = React.useState(true);

// Add new member to 'User group'
const onAddToUserGroup = (toUid: string, newData: string[]) => {
addMemberToUserGroup([toUid, newData]).then((response) => {
if ("data" in response) {
if (response.data.result) {
// Set alert: success
alerts.addAlert(
"add-member-success",
"Added new members to user group '" + toUid + "'",
"success"
);
// Refresh data
props.onRefreshUserData();
// Close modal
setShowAddModal(false);
} else if (response.data.error) {
// Set alert: error
const errorMessage = response.data.error as unknown as ErrorResult;
alerts.addAlert("add-member-error", errorMessage.message, "danger");
}
}
});
};

// TODO: Adapt to work with real data
const onAddUserGroup = (items: AvailableItems[]) => {
const newItems = items.map((item) => item.key);
const newGroups = userGroupsFullList.filter((group) =>
newItems.includes(group.cn)
);
const updatedGroups = userGroupsFromUser.concat(newGroups);
setUserGroupsFromUser(updatedGroups);
if (props.user.uid !== undefined) {
onAddToUserGroup(props.user.uid, newItems);
const updatedGroups = userGroupsFromUser.concat(newGroups);
setUserGroupsFromUser(updatedGroups);
}
};

// 'Delete' function
Expand All @@ -158,14 +191,15 @@ const MemberOfUserGroups = (props: MemberOfUserGroupsProps) => {

return (
<>
<alerts.ManagedAlerts />
<MemberOfToolbarUserGroups
searchText={searchValue}
onSearchTextChange={setSearchValue}
refreshButtonEnabled={isRefreshButtonEnabled}
onRefreshButtonClick={props.onRefreshUserData}
deleteButtonEnabled={someItemSelected}
onDeleteButtonClick={() => setShowDeleteModal(true)}
addButtonEnabled={true}
addButtonEnabled={isAddButtonEnabled}
onAddButtonClick={() => setShowAddModal(true)}
membershipDirectionEnabled={true}
membershipDirection={membershipDirection}
Expand Down
47 changes: 45 additions & 2 deletions src/hooks/useUserMemberOfData.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ type MemberOfData = {
isFetching: boolean;
refetch: () => void;
userGroupsFullList: UserGroup[];
isLoadingNotMembers: boolean;
isFetchingNotMembers: boolean;
notMemberOfRefetch: () => void;
userGroupsNotMemberOfFullList: UserGroup[];
};

const useUserMemberOfData = ({
Expand All @@ -28,6 +32,13 @@ const useUserMemberOfData = ({
stopIdx: lastUserIdx,
});

const userGroupsNotMemberOfQuery = useGettingGroupsQuery({
no_user: uid,
apiVersion: API_VERSION_BACKUP,
startIdx: firstUserIdx,
stopIdx: lastUserIdx,
});

const [userGroupsFullList, setUserGroupsFullList] = React.useState<
UserGroup[]
>([]);
Expand All @@ -49,18 +60,50 @@ const useUserMemberOfData = ({
}
}, [userGroupsData, userGroupsQuery.isFetching]);

// Not member of
const [userGroupsNotMemberOfFullList, setUserGroupsNotMemberOfFullList] =
React.useState<UserGroup[]>([]);
const userGroupsNotMemberOfData = userGroupsNotMemberOfQuery.data || {};
const isUserGroupsNotMemberOfLoading = userGroupsNotMemberOfQuery.isLoading;

React.useEffect(() => {
if (
userGroupsNotMemberOfData !== undefined &&
!userGroupsNotMemberOfQuery.isFetching
) {
const dataParsed = userGroupsNotMemberOfData as BatchRPCResponse;
const count = dataParsed.result.count;
const results = dataParsed.result.results;

const userGroupsNotMemberOfTempList: UserGroup[] = [];

for (let i = 0; i < count; i++) {
userGroupsNotMemberOfTempList.push(apiToGroup(results[i].result));
}
setUserGroupsNotMemberOfFullList(userGroupsNotMemberOfTempList);
}
}, [userGroupsNotMemberOfData, userGroupsNotMemberOfQuery.isFetching]);

// [API call] Refresh
const refetch = () => {
userGroupsQuery.refetch();
};

const notMemberOfRefetch = () => {
userGroupsNotMemberOfQuery.refetch();
};

// Return data
return {
isFetching: userGroupsQuery.isFetching,
isLoading: isUserGroupsLoading,
isFetching: userGroupsQuery.isFetching,
refetch,
userGroupsFullList,
} as MemberOfData;
isLoadingNotMembers: isUserGroupsNotMemberOfLoading,
ifFetchingNotMembers: userGroupsNotMemberOfQuery.isFetching,
notMemberOfRefetch,
userGroupsNotMemberOfFullList,
} as unknown as MemberOfData;
};

export { useUserMemberOfData };
16 changes: 16 additions & 0 deletions src/services/rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1114,6 +1114,21 @@ export const api = createApi({
transformResponse: (response: FindRPCResponse): User =>
apiToUser(response.result.result),
}),
groupAddMember: build.mutation<BatchRPCResponse, [string, string[]]>({
query: (payload) => {
const toUid = payload[0];
const listOfMembers = payload[1];
const membersToAdd: Command[] = [];
listOfMembers.map((member) => {
const payloadItem = {
method: "group_add_member",
params: [[member], { user: toUid }],
} as Command;
membersToAdd.push(payloadItem);
});
return getBatchCommand(membersToAdd, API_VERSION_BACKUP);
},
}),
}),
});

Expand Down Expand Up @@ -1230,4 +1245,5 @@ export const {
useRemoveServicesMutation,
useSearchEntriesMutation,
useGetUserByUidQuery,
useGroupAddMemberMutation,
} = api;

0 comments on commit 4dd7c04

Please sign in to comment.