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 19, 2024
1 parent 2b98ce4 commit 363ab86
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 145 deletions.
23 changes: 22 additions & 1 deletion src/components/UserSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,12 @@ 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";
// Navigate
import { URL_PREFIX } from "src/navigation/NavRoutes";
import { useNavigate } from "react-router-dom";

export interface PropsToUserSettings {
originalUser: Partial<User>;
Expand All @@ -82,6 +86,9 @@ const UserSettings = (props: PropsToUserSettings) => {
// Alerts to show in the UI
const alerts = useAlerts();

// Navigate
const navigate = useNavigate();

// RTK hook: save user (acive/preserved and stage)
let [saveUser] = useSaveUserMutation();
if (props.from === "stage-users") {
Expand Down Expand Up @@ -233,6 +240,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 +308,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 +618,12 @@ const UserSettings = (props: PropsToUserSettings) => {
onClose={onCloseAddOtpTokenModal}
/>
)}
<ActivateStageUsers
show={isActivateModalOpen}
handleModalToggle={onCloseActivateModal}
selectedUids={selectedUsers}
onSuccess={() => navigate(URL_PREFIX + "/stage-users")}
/>
</>
);
};
Expand Down
181 changes: 41 additions & 140 deletions src/components/modals/ActivateStageUsers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,32 +15,15 @@ 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";

interface SelectedUsersData {
selectedUsers: string[];
updateSelectedUsers: (newSelectedUsers: string[]) => void;
}

export interface PropsToActivateUsers {
show: boolean;
handleModalToggle: () => void;
selectedUsersData: SelectedUsersData;
onRefresh?: () => void;
onOpenDeleteModal?: () => void;
onCloseDeleteModal?: () => void;
selectedUids: string[];
onSuccess: () => void;
}

const ActivateStageUsers = (props: PropsToActivateUsers) => {
Expand All @@ -51,7 +34,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 All @@ -71,7 +54,7 @@ const ActivateStageUsers = (props: PropsToActivateUsers) => {
id: "activate-users-table",
pfComponent: (
<UsersDisplayTable
usersToDisplay={props.selectedUsersData.selectedUsers}
usersToDisplay={props.selectedUids}
from={"stage-users"}
/>
),
Expand All @@ -97,106 +80,37 @@ 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 uidsToActivatePayload = props.selectedUids;

// [API call] activate elements
executeUserActivateCommand(uidsToActivatePayload).then((response) => {
activateUsersCommand(uidsToActivatePayload).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();
}
} else if (error) {
// Handle error
handleAPIError(error);
if (response.data.result) {
// Close modal
props.handleModalToggle();
// Update data from Redux
props.selectedUids.map((user) => {
dispatch(removeStageUser(user[0]));
});
// Set alert: success
alerts.addAlert(
"activate-users-success",
response.data.result.count + " users activated",
"success"
);
// Refresh data ('Stage users' main page) or redirect ('Settings' page)
props.onSuccess();
} 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 +131,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
4 changes: 2 additions & 2 deletions src/pages/StageUsers/StageUsers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -508,8 +508,8 @@ const StageUsers = () => {
<ActivateStageUsers
show={showActivateModal}
handleModalToggle={onActivateModalToggle}
selectedUsersData={selectedUsersData}
onRefresh={refreshUsersData}
selectedUids={selectedUsers.map((uid) => uid[0])}
onSuccess={refreshUsersData}
/>
</Page>
);
Expand Down
17 changes: 17 additions & 0 deletions src/services/rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -770,6 +770,22 @@ export const api = createApi({
},
invalidatesTags: ["FullUser"],
}),
activateUser: build.mutation<FindRPCResponse, string[]>({
query: (query_args) => {
const batchPayload: Command[] = [];
query_args.map((uid) => {
batchPayload.push({
method: "stageuser_activate",
params: [[uid], {}],
});
});

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

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

0 comments on commit 363ab86

Please sign in to comment.