Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Is a member of][User groups] 'Add' button functionality #298

Merged
merged 1 commit into from
Mar 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 memberId = payload[0];
const memberType = payload[1];
const groupNames = payload[2];
const membersToAdd: Command[] = [];
groupNames.map((groupName) => {
const payloadItem = {
method: "group_add_member",
params: [[groupName], { [memberType]: memberId }],
} 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;
17 changes: 17 additions & 0 deletions src/utils/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -308,3 +308,20 @@ export const parseEmptyString = (str: string) => {
}
return str;
};

/**
* Get a page (slice) from a list of elements
* @param array List of elements
* @param page Page number
* @param perPage Number of elements per page
* @returns List of elements for the given page
*/
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);
}
Loading