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 12, 2024
1 parent b9deb55 commit 00dabd5
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 43 deletions.
101 changes: 60 additions & 41 deletions src/components/MemberOf/MemberOfUserGroups.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,31 +12,11 @@ import MemberOfAddModal, { AvailableItems } from "./MemberOfAddModal";
import MemberOfDeleteModal from "./MemberOfDeleteModal";
// Hooks
import { useUserMemberOfData } from "src/hooks/useUserMemberOfData";

function paginate<Type>(array: Type[], page: number, perPage: number): Type[] {
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;
});
});
}
import useAlerts from "src/hooks/useAlerts";
// RPC
import { ErrorResult, useAddToGroupsMutation } from "src/services/rpc";
// Utils
import { paginate } from "src/utils/utils";

interface MemberOfUserGroupsProps {
user: Partial<User>;
Expand All @@ -45,6 +25,12 @@ interface MemberOfUserGroupsProps {
}

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

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

// 'User groups' assigned to user
const [userGroupsFromUser, setUserGroupsFromUser] = React.useState<
UserGroup[]
Expand All @@ -66,8 +52,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,9 +78,11 @@ const MemberOfUserGroups = (props: MemberOfUserGroupsProps) => {
}
}, [fullUserGroupsQuery]);

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

const [groupsNamesSelected, setGroupsNamesSelected] = React.useState<
string[]
Expand All @@ -108,38 +101,63 @@ 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
const isRefreshButtonEnabled = !props.isUserDataLoading;

// 'Add' function
// TODO: Adapt to work with real data
// - 'Add'
const isAddButtonEnabled = !props.isUserDataLoading;

// Add new member to 'User group'
const onAddToUserGroup = (toUid: string, type: string, newData: string[]) => {
addMemberToUserGroup([toUid, type, 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");
}
}
});
};

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, "user", newItems);
const updatedGroups = userGroupsFromUser.concat(newGroups);
setUserGroupsFromUser(updatedGroups);
}
};

// 'Delete' function
Expand All @@ -152,14 +170,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 };
24 changes: 24 additions & 0 deletions src/services/rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1273,6 +1273,29 @@ export const api = createApi({
});
},
}),
/**
* Add entity to groups
* @param {string} toId - ID of the entity to add to groups
* @param {string} type - Type of the entity
* Available types: user | host | service
* @param {string[]} listOfMembers - List of members to add to the groups
*/
addToGroups: build.mutation<BatchRPCResponse, [string, string, string[]]>({
query: (payload) => {
const toId = payload[0];
const type = payload[1];
const listOfMembers = payload[2];
const membersToAdd: Command[] = [];
listOfMembers.map((member) => {
const payloadItem = {
method: "group_add_member",
params: [[member], { [type]: toId }],
} as Command;
membersToAdd.push(payloadItem);
});
return getBatchCommand(membersToAdd, API_VERSION_BACKUP);
},
}),
}),
});

Expand Down Expand Up @@ -1397,4 +1420,5 @@ export const {
useGetIDListMutation,
useUpdateKeyTabMutation,
useGetEntriesMutation,
useAddToGroupsMutation,
} = api;
13 changes: 13 additions & 0 deletions src/utils/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -308,3 +308,16 @@ export const parseEmptyString = (str: string) => {
}
return str;
};

/**
* Pagination of list elements based on page and page size
*/
export function paginate<Type>(
array: Type[],
page: number,
perPage: number
): Type[] {
const startIdx = (page - 1) * perPage;
const endIdx = perPage * page - 1;
return array.slice(startIdx, endIdx);
}

0 comments on commit 00dabd5

Please sign in to comment.