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 Feb 7, 2024
1 parent a01c4ea commit 2264e75
Show file tree
Hide file tree
Showing 5 changed files with 335 additions and 2 deletions.
277 changes: 277 additions & 0 deletions src/components/MemberOf/MemberOfAddModalNew.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,277 @@
import React, { ReactNode, useEffect, useState } from "react";
// PatternFly
import { Button, DualListSelector } from "@patternfly/react-core";
// Modals
import ModalWithFormLayout from "src/components/layouts/ModalWithFormLayout";
// Data types
import {
Netgroup,
Roles,
HBACRules,
SudoRules,
HostGroup,
UserGroupNew,
} from "src/utils/datatypes/globalDataTypes";
// Hooks
import { AlertObject } from "src/hooks/useAlerts";
// Utils
import { getNameValue } from "./MemberOfTableNew";
import { ErrorResult, useGroupAddMemberMutation } from "src/services/rpc";

interface ModalData {
showModal: boolean;
handleModalToggle: () => void;
}

interface TabData {
tabName: string;
userName: string; // TODO: Change to a more generalistic name
}

export interface PropsToAdd {
modalData: ModalData;
availableData:
| UserGroupNew[]
| Netgroup[]
| Roles[]
| HBACRules[]
| SudoRules[]
| HostGroup[];
groupRepository: unknown[];
updateGroupRepository: (
args:
| UserGroupNew[]
| Netgroup[]
| Roles[]
| HBACRules[]
| SudoRules[]
| HostGroup[]
) => void;
updateAvOptionsList: (args: unknown[]) => void;
tabData: TabData;
alerts: AlertObject;
}

const MemberOfAddModal = (props: PropsToAdd) => {
// Dual list data
const data = props.availableData.map((d) => getNameValue(d));

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

// Dual list selector
const [availableOptions, setAvailableOptions] = useState<ReactNode[]>(data);
const [chosenOptions, setChosenOptions] = useState<ReactNode[]>([]);

const listChange = (
newAvailableOptions: ReactNode[],
newChosenOptions: ReactNode[]
) => {
setAvailableOptions(newAvailableOptions.sort());
setChosenOptions(newChosenOptions.sort());
};

const fields = [
{
id: "dual-list-selector",
pfComponent: (
<DualListSelector
isSearchable
availableOptions={availableOptions}
chosenOptions={chosenOptions}
onListChange={(
_event,
newAvailableOptions: ReactNode[],
newChosenOptions: ReactNode[]
) => listChange(newAvailableOptions, newChosenOptions)}
id="basicSelectorWithSearch"
/>
),
},
];

// When clean data, set to original values
const cleanData = () => {
setAvailableOptions(data);
setChosenOptions([]);
};

// Clean fields and close modal (To prevent data persistence when reopen modal)
const cleanAndCloseModal = () => {
cleanData();
props.modalData.handleModalToggle();
};

// Buttons are disabled until the user fills the required fields
const [buttonDisabled, setButtonDisabled] = useState(true);
useEffect(() => {
if (chosenOptions.length > 0) {
setButtonDisabled(false);
} else {
setButtonDisabled(true);
}
}, [chosenOptions]);

// Get all info from a chosen option
const getInfoFromGroupData = (option: unknown) => {
const availableDataCopy = [...props.availableData];
return availableDataCopy.find((d) => {
const name = getNameValue(d);
return option === name;
});
};

// 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
props.alerts.addAlert(
"add-member-success",
"Added new members to user group '" + toUid + "'",
"success"
);
// Close modal
props.modalData.handleModalToggle();
} else if (response.data.error) {
// Set alert: error
const errorMessage = response.data.error as unknown as ErrorResult;
props.alerts.addAlert(
"add-member-error",
errorMessage.message,
"danger"
);
}
}
});
};

// Add group option
const onClickAddGroupHandler = () => {
// List of items to be added
const newGroupRepoItems: unknown[] = [...props.groupRepository];
const optionsToAdd: string[] = [];

chosenOptions.map((opt) => {
let optionData = getInfoFromGroupData(opt);
if (optionData !== undefined) {
// User groups
if (props.tabData.tabName === "User groups") {
optionData = optionData as UserGroupNew;
newGroupRepoItems.push({
cn: optionData.cn !== undefined && optionData.cn,
description:
optionData.description !== undefined && optionData.description,
gidnumber:
optionData.gidnumber !== undefined && optionData.gidnumber,
});
optionsToAdd.push(optionData.cn);
}
// Netgroups
if (props.tabData.tabName === "Netgroups") {
optionData = optionData as Netgroup;
props.groupRepository.push({
name: optionData.name !== undefined && optionData.name,
description:
optionData.description !== undefined && optionData.description,
} as Netgroup);
props.updateGroupRepository(props.groupRepository as Netgroup[]);
}
// Roles
if (props.tabData.tabName === "Roles") {
optionData = optionData as Roles;
props.groupRepository.push({
name: optionData.name !== undefined && optionData.name,
description:
optionData.description !== undefined && optionData.description,
} as Roles);
props.updateGroupRepository(props.groupRepository as Roles[]);
}
// HBAC rules
if (props.tabData.tabName === "HBAC rules") {
optionData = optionData as HBACRules;
props.groupRepository.push({
name: optionData.name !== undefined && optionData.name,
description:
optionData.description !== undefined && optionData.description,
} as HBACRules);
props.updateGroupRepository(props.groupRepository as HBACRules[]);
}
// Sudo rules
if (props.tabData.tabName === "Sudo rules") {
optionData = optionData as SudoRules;
props.groupRepository.push({
name: optionData.name !== undefined && optionData.name,
description:
optionData.description !== undefined && optionData.description,
} as SudoRules);
props.updateGroupRepository(props.groupRepository as SudoRules[]);
}
// Host groups
if (props.tabData.tabName === "Host groups") {
optionData = optionData as HostGroup;
props.groupRepository.push({
name: optionData.name !== undefined && optionData.name,
description:
optionData.description !== undefined && optionData.description,
} as HostGroup);
props.updateGroupRepository(props.groupRepository as HostGroup[]);
}
}
});

// API call
onAddToUserGroup(props.tabData.userName, optionsToAdd);

// Send updated data to table based on data type (New approach)
switch (props.tabData.tabName) {
case "User groups":
props.updateGroupRepository(newGroupRepoItems as UserGroupNew[]);
break;
// TODO: Add other cases here as they're adapted to the C.L.
}

// Clean chosen options and close modal
setChosenOptions([]);
props.modalData.handleModalToggle();
};

// Buttons that will be shown at the end of the form
const modalActions = [
<Button
key="add-new-user"
variant="secondary"
isDisabled={buttonDisabled}
form="modal-form"
onClick={onClickAddGroupHandler}
>
Add
</Button>,
<Button key="cancel-new-user" variant="link" onClick={cleanAndCloseModal}>
Cancel
</Button>,
];

// Render 'MemberOfaddModal'
return (
<ModalWithFormLayout
variantType="medium"
modalPosition="top"
offPosition="76px"
title={
"Add user '" +
props.tabData.userName +
"' into " +
props.tabData.tabName
}
formId="is-member-of-add-modal"
fields={fields}
show={props.modalData.showModal}
onClose={cleanAndCloseModal}
actions={modalActions}
/>
);
};

export default MemberOfAddModal;
2 changes: 1 addition & 1 deletion src/components/MemberOf/MemberOfTableNew.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ interface ColumnNames {
// To display all the possible data types for all the tabs (and not only the mandatory ones)
// an extra interface 'MemberOfElement' will be defined. This will be called in the 'PropsToTable'
// // interface instead of each type (UserGroup | Netgroup | Roles | HBACRules | SudoRules | HostGroup).
interface MemberOfElement {
export interface MemberOfElement {
cn?: string;
name?: string;
gid?: string;
Expand Down
9 changes: 9 additions & 0 deletions src/hooks/useAlerts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@ export interface AlertInfo {
variant: AlertVariant;
}

// In some cases, it is needed to propagate a specific alert to child components.
// The following object is defined to be refered everytime an Alert is propagated via props.
export interface AlertObject {
addAlert: (name: string, title: string, variant: AlertVariant) => void;
removeAlert: (name: string) => void;
removeAllAlerts: () => void;
ManagedAlerts: () => JSX.Element;
}

export function useAlerts() {
const [alerts, setAlerts] = React.useState<AlertInfo[]>([]);

Expand Down
33 changes: 32 additions & 1 deletion src/pages/ActiveUsers/UserMemberOf.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,21 +36,31 @@ import {
// Modals
import MemberOfAddModal from "src/components/MemberOf/MemberOfAddModal";
import MemberOfDeleteModal from "src/components/MemberOf/MemberOfDeleteModal";
import MemberOfAddModalNew from "src/components/MemberOf/MemberOfAddModalNew";
// RPC
import { useGetUserByUidQuery } from "src/services/rpc";
// Hooks
import { useUserMemberOfData } from "src/hooks/useUserMemberOfData";
import useAlerts from "src/hooks/useAlerts";

interface PropsToUserMemberOf {
user: User;
}

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

// Retrieve each group list from Redux:
let netgroupsList = useAppSelector((state) => state.netgroups.netgroupList);
let rolesList = useAppSelector((state) => state.roles.roleList);
let hbacRulesList = useAppSelector((state) => state.hbacrules.hbacRulesList);
let sudoRulesList = useAppSelector((state) => state.sudorules.sudoRulesList);

// Alter the available options list to keep the state of the recently added / removed items
const updateUserGroupsList = (newAvOptionsList: unknown[]) => {
setUserGroupsFromUser(newAvOptionsList as UserGroupNew[]);
};
const updateNetgroupsList = (newAvOptionsList: unknown[]) => {
netgroupsList = newAvOptionsList as Netgroup[];
};
Expand All @@ -71,7 +81,7 @@ const UserMemberOf = (props: PropsToUserMemberOf) => {

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

// Member groups associated to user (string[] | UserGroupNew[])
// Member groups associated to user
const [userGroupsFromUser, setUserGroupsFromUser] = React.useState<
UserGroupNew[]
>([]);
Expand Down Expand Up @@ -118,6 +128,14 @@ 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).
const filterUserGroupsData = () => {
// User groups
return userGroupsFullList.filter((item) => {
return !userGroupsFromUser.some((itm) => {
return item.cn === itm.cn;
});
});
};
const filterNetgroupsData = () => {
// Netgroups
return netgroupsList.filter((item) => {
Expand Down Expand Up @@ -152,6 +170,7 @@ const UserMemberOf = (props: PropsToUserMemberOf) => {
};

// Available data to be added as member of
const userGroupsFilteredData: UserGroupNew[] = filterUserGroupsData();
const netgroupsFilteredData: Netgroup[] = filterNetgroupsData();
const rolesFilteredData: Roles[] = filterRolesData();
const hbacRulesFilteredData: HBACRules[] = filterHbacRulesData();
Expand Down Expand Up @@ -557,6 +576,7 @@ const UserMemberOf = (props: PropsToUserMemberOf) => {
// Render 'ActiveUsersIsMemberOf'
return (
<>
<alerts.ManagedAlerts />
<Page>
<PageSection
variant={PageSectionVariants.light}
Expand Down Expand Up @@ -766,6 +786,17 @@ const UserMemberOf = (props: PropsToUserMemberOf) => {
)}
</>
)} */}
{showAddModal && (
<MemberOfAddModalNew
modalData={addModalData}
availableData={userGroupsFilteredData}
groupRepository={userGroupsFromUser}
updateGroupRepository={updateGroupRepository}
updateAvOptionsList={updateUserGroupsList}
tabData={tabData}
alerts={alerts}
/>
)}
{tabName === "Netgroups" && (
<>
{showAddModal && (
Expand Down
Loading

0 comments on commit 2264e75

Please sign in to comment.