Skip to content

Commit

Permalink
Replace dummy data by data from the API
Browse files Browse the repository at this point in the history
The dummy data from the table
must be replaced by the data
from the API server.

Signed-off-by: Carla Martinez <[email protected]>
  • Loading branch information
carma12 committed Mar 6, 2024
1 parent 3f2508e commit 598149e
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 58 deletions.
16 changes: 9 additions & 7 deletions src/components/MemberOf/MemberOfTableUserGroups.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,23 @@ import React from "react";
// PatternFly
import { Table, Tr, Th, Td, Thead, Tbody } from "@patternfly/react-table";
// Data types
import { UserGroupOld } from "src/utils/datatypes/globalDataTypes";
import { UserGroup } from "src/utils/datatypes/globalDataTypes";
// Components
import SkeletonOnTableLayout from "../layouts/Skeleton/SkeletonOnTableLayout";
import EmptyBodyTable from "../tables/EmptyBodyTable";
// Utils
import { parseEmptyString } from "src/utils/utils";

export interface MemberOfUserGroupsTableProps {
userGroups: UserGroupOld[];
userGroups: UserGroup[];
checkedItems?: string[];
onCheckItemsChange?: (checkedItems: string[]) => void;
showTableRows: boolean;
}

// Body
const UserGroupsTableBody = (props: {
userGroups: UserGroupOld[];
userGroups: UserGroup[];
showCheckboxColumn: boolean;
checkedItems: string[];
onCheckboxChange: (checked: boolean, groupName: string) => void;
Expand All @@ -31,13 +33,13 @@ const UserGroupsTableBody = (props: {
select={{
rowIndex: index,
onSelect: (_e, isSelected) =>
props.onCheckboxChange(isSelected, userGroup.name),
isSelected: props.checkedItems.includes(userGroup.name),
props.onCheckboxChange(isSelected, userGroup.cn),
isSelected: props.checkedItems.includes(userGroup.cn),
}}
/>
)}
<Td>{userGroup.name}</Td>
<Td>{userGroup.gid}</Td>
<Td>{userGroup.cn}</Td>
<Td>{parseEmptyString(userGroup.gidnumber)}</Td>
<Td>{userGroup.description}</Td>
</Tr>
))}
Expand Down
101 changes: 67 additions & 34 deletions src/components/MemberOf/MemberOfUserGroups.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,60 +2,93 @@ import React from "react";
// PatternFly
import { Pagination, PaginationVariant } from "@patternfly/react-core";
// Data types
import { UserGroupOld } from "src/utils/datatypes/globalDataTypes";
// Redux
import { useAppSelector } from "src/store/hooks";
import { User, UserGroup } from "src/utils/datatypes/globalDataTypes";
// Components
import MemberOfToolbarUserGroups, {
MembershipDirection,
} from "./MemberOfToolbar";
import MemberOfUserGroupsTable from "./MemberOfTableUserGroups";
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 TypeWithName {
name: string;
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 TypeWithName>(
function filterUserGroupsData<Type extends TypeWithCN>(
list1: Array<Type>,
list2: Array<Type>
): Type[] {
// User groups
return list1.filter((item) => {
return !list2.some((itm) => {
return item.name === itm.name;
return item.cn === itm.cn;
});
});
}

interface MemberOfUserGroupsProps {
uid: string;
usersGroupsFromUser: UserGroupOld[];
updateUsersGroupsFromUser: (newList: UserGroupOld[]) => void;
user: Partial<User>;
}

const MemberOfUserGroups = (props: MemberOfUserGroupsProps) => {
const userGroupsFullList = useAppSelector(
(state) => state.usergroups.userGroupList
);

const [groupsNamesSelected, setGroupsNamesSelected] = React.useState<
string[]
// 'User groups' assigned to user
const [userGroupsFromUser, setUserGroupsFromUser] = React.useState<
UserGroup[]
>([]);

// Page indexes
const [page, setPage] = React.useState(1);
const [perPage, setPerPage] = React.useState(10);

const firstUserIdx = (page - 1) * perPage;
const lastUserIdx = page * perPage;

const uid = props.user.uid;

// API call: full list of 'User groups' available
const fullUserGroupsQuery = useUserMemberOfData({
uid,
firstUserIdx,
lastUserIdx,
});

const userGroupsFullList = fullUserGroupsQuery.userGroupsFullList;

// Get full data of the 'User groups' assigned to user
React.useEffect(() => {
if (!fullUserGroupsQuery.isFetching && userGroupsFullList) {
const userGroupsParsed: UserGroup[] = [];
props.user.memberof_group?.map((group) => {
userGroupsFullList.map((g) => {
if (g.cn === group) {
userGroupsParsed.push(g);
}
});
});
if (
JSON.stringify(userGroupsFromUser) !== JSON.stringify(userGroupsParsed)
) {
setUserGroupsFromUser(userGroupsParsed);
}
}
}, [fullUserGroupsQuery]);

const [groupsNamesSelected, setGroupsNamesSelected] = React.useState<
string[]
>([]);

const [searchValue, setSearchValue] = React.useState("");

const [membershipDirection, setMembershipDirection] =
Expand All @@ -66,22 +99,22 @@ const MemberOfUserGroups = (props: MemberOfUserGroupsProps) => {

// Computed "states"
const someItemSelected = groupsNamesSelected.length > 0;
const shownUserGroups = paginate(props.usersGroupsFromUser, page, perPage);
const showTableRows = props.usersGroupsFromUser.length > 0;
const shownUserGroups = paginate(userGroupsFromUser, page, perPage);
const showTableRows = userGroupsFromUser.length > 0;

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

// Parse availableItems to AvailableItems type
const parseAvailableItems = () => {
const avItems: AvailableItems[] = [];
userGroupsFilteredData.map((item) => {
avItems.push({
key: item.name,
title: item.name,
key: item.cn,
title: item.cn,
});
});
return avItems;
Expand All @@ -93,18 +126,18 @@ const MemberOfUserGroups = (props: MemberOfUserGroupsProps) => {
const onAddUserGroup = (items: AvailableItems[]) => {
const newItems = items.map((item) => item.key);
const newGroups = userGroupsFullList.filter((group) =>
newItems.includes(group.name)
newItems.includes(group.cn)
);
const updatedGroups = props.usersGroupsFromUser.concat(newGroups);
props.updateUsersGroupsFromUser(updatedGroups);
const updatedGroups = userGroupsFromUser.concat(newGroups);
setUserGroupsFromUser(updatedGroups);
};

// 'Delete' function
const onDeleteUserGroup = () => {
const updatedGroups = props.usersGroupsFromUser.filter(
(group) => !groupsNamesSelected.includes(group.name)
const updatedGroups = userGroupsFromUser.filter(
(group) => !groupsNamesSelected.includes(group.cn)
);
props.updateUsersGroupsFromUser(updatedGroups);
setUserGroupsFromUser(updatedGroups);
};

return (
Expand All @@ -121,7 +154,7 @@ const MemberOfUserGroups = (props: MemberOfUserGroupsProps) => {
membershipDirection={membershipDirection}
onMembershipDirectionChange={setMembershipDirection}
helpIconEnabled={true}
totalItems={props.usersGroupsFromUser.length}
totalItems={userGroupsFromUser.length}
perPage={perPage}
page={page}
onPerPageChange={setPerPage}
Expand All @@ -135,7 +168,7 @@ const MemberOfUserGroups = (props: MemberOfUserGroupsProps) => {
/>
<Pagination
className="pf-v5-u-pb-0 pf-v5-u-pr-md"
itemCount={props.usersGroupsFromUser.length}
itemCount={userGroupsFromUser.length}
widgetId="pagination-options-menu-bottom"
perPage={perPage}
page={page}
Expand All @@ -150,7 +183,7 @@ const MemberOfUserGroups = (props: MemberOfUserGroupsProps) => {
availableItems={availableUserGroupsItems}
onAdd={onAddUserGroup}
onSearchTextChange={setSearchValue}
title={"Add '" + props.uid + "' into User groups"}
title={"Add '" + props.user.uid + "' into User groups"}
ariaLabel="Add user of user group modal"
/>
)}
Expand All @@ -163,9 +196,9 @@ const MemberOfUserGroups = (props: MemberOfUserGroupsProps) => {
>
<MemberOfUserGroupsTable
userGroups={
props.usersGroupsFromUser.filter((group) =>
groupsNamesSelected.includes(group.name)
) as UserGroupOld[]
userGroupsFromUser.filter((group) =>
groupsNamesSelected.includes(group.cn)
) as UserGroup[]
}
showTableRows
/>
Expand Down
53 changes: 36 additions & 17 deletions src/pages/ActiveUsers/UserMemberOf.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import MemberOfToolbar from "src/components/MemberOf/MemberOfToolbarOld";
import MemberOfTable from "src/components/MemberOf/MemberOfTable";
// Data types
import {
UserGroupOld,
Netgroup,
Roles,
HBACRules,
Expand All @@ -25,7 +24,6 @@ import { useAppSelector } from "src/store/hooks";

// Repositories
import {
userGroupsInitialData,
netgroupsInitialData,
rolesInitialData,
hbacRulesInitialData,
Expand All @@ -36,13 +34,18 @@ import MemberOfAddModal from "src/components/MemberOf/MemberOfAddModalOld";
import MemberOfDeleteModal from "src/components/MemberOf/MemberOfDeleteModalOld";
// Wrappers
import MemberOfUserGroups from "src/components/MemberOf/MemberOfUserGroups";
// RPC
import { useGetUserByUidQuery } from "src/services/rpc";
// Utils
import { convertToString } from "src/utils/ipaObjectUtils";

interface PropsToUserMemberOf {
user: User;
}

const UserMemberOf = (props: PropsToUserMemberOf) => {
// Retrieve each group list from Redux:
// TODO: Remove this when all data is taken from the C.L.
let netgroupsList = useAppSelector((state) => state.netgroups.netgroupList);
let rolesList = useAppSelector((state) => state.roles.roleList);
let hbacRulesList = useAppSelector((state) => state.hbacrules.hbacRulesList);
Expand All @@ -62,10 +65,34 @@ const UserMemberOf = (props: PropsToUserMemberOf) => {
sudoRulesList = newAvOptionsList as SudoRules[];
};

// Page indexes
const [page, setPage] = React.useState(1);
const [perPage, setPerPage] = React.useState(10);

// User's full data
const userQuery = useGetUserByUidQuery(convertToString(props.user.uid));

const userData = userQuery.data || {};

const [user, setUser] = React.useState<Partial<User>>({});

React.useEffect(() => {
if (!userQuery.isFetching && userData) {
setUser({ ...userData });
}
}, [userData, userQuery.isFetching]);

// 'User groups' length to show in tab badge
const [userGroupsLength, setUserGroupLength] = React.useState(0);

React.useEffect(() => {
if (user && user.memberof_group) {
setUserGroupLength(user.memberof_group.length);
}
}, [user]);

// List of default dummy data (for each tab option)
const [userGroupsRepository, setUserGroupsRepository] = useState(
userGroupsInitialData
);
// TODO: Remove when all data is adapted to the C.L.
const [netgroupsRepository, setNetgroupsRepository] =
useState(netgroupsInitialData);
const [rolesRepository, setRolesRepository] = useState(rolesInitialData);
Expand All @@ -84,6 +111,7 @@ const UserMemberOf = (props: PropsToUserMemberOf) => {
// 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).
// TODO: Remove this when all tab are set into wrappers
const filterNetgroupsData = () => {
// Netgroups
return netgroupsList.filter((item) => {
Expand Down Expand Up @@ -205,11 +233,6 @@ const UserMemberOf = (props: PropsToUserMemberOf) => {
setActiveTabKey(tabIndex as number);
};

// -- Pagination
// TODO: Remove this when all tabs are adapted to its own wrapper
const [page, setPage] = useState(1);
const [perPage, setPerPage] = useState(10);

// Member groups displayed on the first page
const [shownNetgroupsList, setShownNetgroupsList] = useState(
netgroupsRepository.slice(0, perPage)
Expand All @@ -226,7 +249,7 @@ const UserMemberOf = (props: PropsToUserMemberOf) => {

// Update pagination
const changeMemberGroupsList = (
value: UserGroupOld[] | Netgroup[] | Roles[] | HBACRules[] | SudoRules[]
value: Netgroup[] | Roles[] | HBACRules[] | SudoRules[]
) => {
switch (activeTabKey) {
case 1:
Expand Down Expand Up @@ -426,16 +449,12 @@ const UserMemberOf = (props: PropsToUserMemberOf) => {
<TabTitleText>
User groups{" "}
<Badge key={0} isRead>
{userGroupsRepository.length}
{userGroupsLength}
</Badge>
</TabTitleText>
}
>
<MemberOfUserGroups
uid={props.user.uid}
usersGroupsFromUser={userGroupsRepository}
updateUsersGroupsFromUser={setUserGroupsRepository}
/>
<MemberOfUserGroups user={user} />
</Tab>
<Tab
eventKey={1}
Expand Down
13 changes: 13 additions & 0 deletions src/utils/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -295,3 +295,16 @@ export const isValidIpAddress = (ipAddress: string) => {
return regexIPv4.test(ipAddress);
}
};

/**
* Some values in a table might not have a specific value defined
*
* (i.e. empty string ""). This is not allowed by the table component.
* Therefore, this function will return "-" instead of "".
*/
export const parseEmptyString = (str: string) => {
if (str === "") {
return "-";
}
return str;
};

0 comments on commit 598149e

Please sign in to comment.