Skip to content

Commit

Permalink
Add 'Activate' option ('Stage users')
Browse files Browse the repository at this point in the history
The 'Activate' option enables the
account of a given user, moving it
from the 'Stage users' to the 'Active
users' page. This option is only
accessible form the 'Stage useres'
page.

The solution has adapted the API
call made. Instead of using a
generalistic batch endpoint a new
specific endpoint has been created:
`useActivateUserMutation`. This is
being used from the main page and
the kebab options located in the
'Settings' page.

Signed-off-by: Carla Martinez <carlmart@redhat.com>
  • Loading branch information
carma12 committed Jan 18, 2024
1 parent 2b98ce4 commit ebf781c
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 134 deletions.
16 changes: 15 additions & 1 deletion src/components/UserSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import UnlockUser from "./modals/UnlockUser";
import ResetPassword from "./modals/ResetPassword";
import IssueNewCertificate from "./modals/IssueNewCertificate";
import AddOtpToken from "./modals/AddOtpToken";
import ActivateStageUsers from "./modals/ActivateStageUsers";
// Utils
import { API_VERSION_BACKUP } from "src/utils/utils";

Expand Down Expand Up @@ -233,6 +234,12 @@ const UserSettings = (props: PropsToUserSettings) => {
});
};

// Stage users - 'Activate' option
const [isActivateModalOpen, setIsActivateModalOpen] = React.useState(false);
const onCloseActivateModal = () => {
setIsActivateModalOpen(false);
};

// Kebab
const [isKebabOpen, setIsKebabOpen] = useState(false);

Expand Down Expand Up @@ -295,7 +302,9 @@ const UserSettings = (props: PropsToUserSettings) => {
];

const stageDropdownItems = [
<DropdownItem key="activate">Activate</DropdownItem>,
<DropdownItem key="activate" onClick={() => setIsActivateModalOpen(true)}>
Activate
</DropdownItem>,
<DropdownItem key="delete" onClick={() => setIsDeleteModalOpen(true)}>
Delete
</DropdownItem>,
Expand Down Expand Up @@ -603,6 +612,11 @@ const UserSettings = (props: PropsToUserSettings) => {
onClose={onCloseAddOtpTokenModal}
/>
)}
<ActivateStageUsers
show={isActivateModalOpen}
handleModalToggle={onCloseActivateModal}
selectedUsersData={selectedUsersData}
/>
</>
);
};
Expand Down
170 changes: 39 additions & 131 deletions src/components/modals/ActivateStageUsers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,7 @@ import UsersDisplayTable from "src/components/tables/UsersDisplayTable";
import { useAppDispatch } from "src/store/hooks";
import { removeUser as removeStageUser } from "src/store/Identity/stageUsers-slice";
// RPC
import {
Command,
BatchRPCResponse,
useBatchMutCommandMutation,
} from "src/services/rpc";
import { FetchBaseQueryError } from "@reduxjs/toolkit/dist/query";
import { SerializedError } from "@reduxjs/toolkit";
// Modals
import ErrorModal from "./ErrorModal";
// Data types
import { ErrorData } from "src/utils/datatypes/globalDataTypes";
import { useActivateUserMutation, ErrorResult } from "src/services/rpc";
// Hooks
import useAlerts from "src/hooks/useAlerts";

Expand All @@ -39,8 +29,6 @@ export interface PropsToActivateUsers {
handleModalToggle: () => void;
selectedUsersData: SelectedUsersData;
onRefresh?: () => void;
onOpenDeleteModal?: () => void;
onCloseDeleteModal?: () => void;
}

const ActivateStageUsers = (props: PropsToActivateUsers) => {
Expand All @@ -51,7 +39,7 @@ const ActivateStageUsers = (props: PropsToActivateUsers) => {
const alerts = useAlerts();

// Define 'executeUserStageCommand' to activate user data to IPA server
const [executeUserActivateCommand] = useBatchMutCommandMutation();
const [activateUsersCommand] = useActivateUserMutation();

const [noMembersChecked, setNoMembers] = useState<boolean>(false);

Expand Down Expand Up @@ -97,106 +85,39 @@ const ActivateStageUsers = (props: PropsToActivateUsers) => {
props.handleModalToggle();
};

// Handle API error data
const [isModalErrorOpen, setIsModalErrorOpen] = useState(false);
const [errorTitle, setErrorTitle] = useState("");
const [errorMessage, setErrorMessage] = useState("");

const closeAndCleanErrorParameters = () => {
setIsModalErrorOpen(false);
setErrorTitle("");
setErrorMessage("");
};

const onCloseErrorModal = () => {
closeAndCleanErrorParameters();
};

const errorModalActions = [
<Button key="cancel" variant="link" onClick={onCloseErrorModal}>
OK
</Button>,
];

const handleAPIError = (error: FetchBaseQueryError | SerializedError) => {
if ("code" in error) {
setErrorTitle("IPA error " + error.code + ": " + error.name);
if (error.message !== undefined) {
setErrorMessage(error.message);
}
} else if ("data" in error) {
const errorData = error.data as ErrorData;
const errorCode = errorData.code as string;
const errorName = errorData.name as string;
const errorMessage = errorData.error as string;

setErrorTitle("IPA error " + errorCode + ": " + errorName);
setErrorMessage(errorMessage);
}
setIsModalErrorOpen(true);
};

// Stage user
const activateUsers = () => {
// Prepare users params
const uidsToActivatePayload: Command[] = [];

props.selectedUsersData.selectedUsers.map((uid) => {
const payloadItem = {
method: "stageuser_activate",
params: [uid, { no_members: noMembersChecked }],
} as Command;
uidsToActivatePayload.push(payloadItem);
});
const usersToActivatePayload = props.selectedUsersData.selectedUsers;

// [API call] activate elements
executeUserActivateCommand(uidsToActivatePayload).then((response) => {
activateUsersCommand(usersToActivatePayload).then((response) => {
if ("data" in response) {
const data = response.data as BatchRPCResponse;
const result = data.result;
const error = data.error as FetchBaseQueryError | SerializedError;

if (result) {
if ("error" in result.results[0] && result.results[0].error) {
const errorData = {
code: result.results[0].error_code,
name: result.results[0].error_name,
error: result.results[0].error,
} as ErrorData;

const error = {
status: "CUSTOM_ERROR",
data: errorData,
} as FetchBaseQueryError;

// Handle error
handleAPIError(error);
} else {
// Update data from Redux
props.selectedUsersData.selectedUsers.map((user) => {
dispatch(removeStageUser(user[0]));
});

// Reset selected values
props.selectedUsersData.updateSelectedUsers([]);

// Refresh data
if (props.onRefresh !== undefined) {
props.onRefresh();
}

// Show alert: success
alerts.addAlert(
"activate-users-success",
"Users activated",
"success"
);

closeModal();
if (response.data.result) {
// Close modal
props.handleModalToggle();
// Update data from Redux
props.selectedUsersData.selectedUsers.map((user) => {
dispatch(removeStageUser(user[0]));
});
// Set alert: success
alerts.addAlert(
"activate-users-success",
response.data.result.count + " users activated",
"success"
);
// Refresh data
if (props.onRefresh !== undefined) {
props.onRefresh();
}
} else if (error) {
// Handle error
handleAPIError(error);
} else if (response.data.error) {
// Set alert: error
const errorMessage = response.data.error as ErrorResult;
alerts.addAlert(
"activate-users-error",
errorMessage.message,
"danger"
);
}
}
});
Expand All @@ -217,34 +138,21 @@ const ActivateStageUsers = (props: PropsToActivateUsers) => {
</Button>,
];

const modalActivate: JSX.Element = (
<ModalWithFormLayout
variantType="medium"
modalPosition="top"
offPosition="76px"
title="Activate Stage User"
formId="stage-user-activate-modal"
fields={fields}
show={props.show}
onClose={closeModal}
actions={modalStageActions}
/>
);

// Render 'ActivateStageUsers'
return (
<>
<alerts.ManagedAlerts />
{modalActivate}
{isModalErrorOpen && (
<ErrorModal
title={errorTitle}
isOpen={isModalErrorOpen}
onClose={onCloseErrorModal}
actions={errorModalActions}
errorMessage={errorMessage}
/>
)}
<ModalWithFormLayout
variantType="medium"
modalPosition="top"
offPosition="76px"
title="Activate Stage User"
formId="stage-user-activate-modal"
fields={fields}
show={props.show}
onClose={closeModal}
actions={modalStageActions}
/>
</>
);
};
Expand Down
4 changes: 2 additions & 2 deletions src/components/tables/UsersDisplayTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ const UsersDisplayTable = (props: PropsToDisplayUsersTable) => {
case "stage-users":
stageUsersListCopy.map((user) => {
props.usersToDisplay.map((selected) => {
if (user.uid === selected || user.uid[0] === selected) {
if (user.uid[0] === selected[0] || user.uid[0] === selected) {
usersToDisplay.push(user);
}
});
Expand All @@ -63,7 +63,7 @@ const UsersDisplayTable = (props: PropsToDisplayUsersTable) => {
case "preserved-users":
preservedUsersListCopy.map((user) => {
props.usersToDisplay.map((selected) => {
if (user.uid === selected || user.uid[0] === selected) {
if (user.uid[0] === selected[0] || user.uid[0] === selected) {
usersToDisplay.push(user);
}
});
Expand Down
24 changes: 24 additions & 0 deletions src/services/rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -770,6 +770,29 @@ export const api = createApi({
},
invalidatesTags: ["FullUser"],
}),
activateUser: build.mutation<FindRPCResponse, any[]>({
query: (query_args) => {
const batchPayload: Command[] = [];
query_args.map((userToActivate) => {
let individualUserParams;
if (Array.isArray(userToActivate)) {
individualUserParams = [userToActivate, {}];
} else {
individualUserParams = [[userToActivate], {}];
}

batchPayload.push({
method: "stageuser_activate",
params: individualUserParams,
});
});

return getBatchCommand(
batchPayload,
query_args["version"] || API_VERSION_BACKUP
);
},
}),
}),
});

Expand Down Expand Up @@ -842,4 +865,5 @@ export const {
useGetCertProfileQuery,
useAddOtpTokenMutation,
useGenerateSubIdsMutation,
useActivateUserMutation,
} = api;

0 comments on commit ebf781c

Please sign in to comment.