Skip to content

Commit

Permalink
Add activate modal and user settings for stage users
Browse files Browse the repository at this point in the history
  • Loading branch information
mreynolds389 committed Nov 15, 2023
1 parent ad067bb commit 57bf689
Show file tree
Hide file tree
Showing 17 changed files with 714 additions and 176 deletions.
3 changes: 3 additions & 0 deletions src/components/BulkSelectorUsersPrep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,9 @@ const BulkSelectorPrep = (props: PropsToBulkSelectorPrep) => {
isSelecting = true,
selectableUsersList: User[]
) => {
if (selectableUsersList.length === 0) {
return;
}
props.usersData.changeSelectedUserNames(
isSelecting ? selectableUsersList.map((r) => r.uid) : []
);
Expand Down
14 changes: 11 additions & 3 deletions src/components/Form/PrincipalAliasMultiTextBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
ErrorResult,
useAddPrincipalAliasMutation,
useRemovePrincipalAliasMutation,
useAddStagePrincipalAliasMutation,
useRemoveStagePrincipalAliasMutation,
} from "src/services/rpc";
// Layouts
import SecondaryButton from "../layouts/SecondaryButton";
Expand All @@ -25,16 +27,22 @@ interface PrincipalAliasMultiTextBoxProps {
ipaObject: Record<string, unknown>;
metadata: Metadata;
onRefresh: () => void;
from: "active-users" | "stage-users" | "preserved-users";
}

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

// RTK hooks
const [addPrincipalAlias] = useAddPrincipalAliasMutation();
const [removePrincipalAlias] = useRemovePrincipalAliasMutation();

let [addPrincipalAlias] = useAddPrincipalAliasMutation();
if (props.from === "stage-users") {
[addPrincipalAlias] = useAddStagePrincipalAliasMutation();
}
let [removePrincipalAlias] = useRemovePrincipalAliasMutation();
if (props.from === "stage-users") {
[removePrincipalAlias] = useRemoveStagePrincipalAliasMutation();
}
// 'krbprincipalname' value from ipaObject
const krbprincipalname = props.ipaObject["krbprincipalname"] as string[];

Expand Down
13 changes: 10 additions & 3 deletions src/components/UserSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,11 @@ import UsersMailingAddress from "src/components/UsersSections/UsersMailingAddres
import UsersEmployeeInfo from "src/components/UsersSections/UsersEmployeeInfo";
import UsersAttributesSMB from "src/components/UsersSections/UsersAttributesSMB";
// RPC
import { ErrorResult, useSaveUserMutation } from "src/services/rpc";
import {
ErrorResult,
useSaveUserMutation,
useSaveStageUserMutation,
} from "src/services/rpc";
// Hooks
import useAlerts from "src/hooks/useAlerts";

Expand Down Expand Up @@ -67,8 +71,11 @@ const UserSettings = (props: PropsToUserSettings) => {
// Alerts to show in the UI
const alerts = useAlerts();

// RTK hook: save user
const [saveUser] = useSaveUserMutation();
// RTK hook: save user (acive/preserved and stage)
let [saveUser] = useSaveUserMutation();
if (props.from === "stage-users") {
[saveUser] = useSaveStageUserMutation();
}

// Kebab
const [isKebabOpen, setIsKebabOpen] = useState(false);
Expand Down
7 changes: 7 additions & 0 deletions src/components/UsersSections/UsersAccountSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ interface PropsToUsersAccountSettings {
from: "active-users" | "stage-users" | "preserved-users";
}

// Generic data to pass to the Textbox adder
interface ElementData {
id: string | number;
element: string;
}

const UsersAccountSettings = (props: PropsToUsersAccountSettings) => {
// TODO: Handle the `has_password` variable (boolean) by another Ipa component
const [password] = useState("");
Expand Down Expand Up @@ -185,6 +191,7 @@ const UsersAccountSettings = (props: PropsToUsersAccountSettings) => {
ipaObject={ipaObject}
metadata={props.metadata}
onRefresh={props.onRefresh}
from={props.from}
/>
</FormGroup>
<FormGroup
Expand Down
252 changes: 252 additions & 0 deletions src/components/modals/ActivateStageUsers.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
import React, { useState } from "react";
// PatternFly
import {
Button,
Checkbox,
Text,
TextContent,
TextVariants,
} from "@patternfly/react-core";
// Layouts
import ModalWithFormLayout from "src/components/layouts/ModalWithFormLayout";
// Tables
import UsersDisplayTable from "src/components/tables/UsersDisplayTable";
// Redux
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";
// 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;
}

const ActivateStageUsers = (props: PropsToActivateUsers) => {
// Set dispatch (Redux)
const dispatch = useAppDispatch();

// Alerts
const alerts = useAlerts();

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

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

// List of fields
const fields = [
{
id: "question-text",
pfComponent: (
<TextContent>
<Text component={TextVariants.p}>
Are you sure you want to activate the selected stage users?
</Text>
</TextContent>
),
},
{
id: "activate-users-table",
pfComponent: (
<UsersDisplayTable
usersToDisplay={props.selectedUsersData.selectedUsers}
from={"stage-users"}
/>
),
},
{
id: "no-members",
pfComponent: (
<Checkbox
label="Suppress processing of membership attributes"
isChecked={noMembersChecked}
onChange={() => {
setNoMembers(!noMembersChecked);
}}
id="no-members-checkbox"
name="no-members"
/>
),
},
];

// Close modal
const closeModal = () => {
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);
});

// [API call] activate elements
executeUserActivateCommand(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);
}
}
});
};

// Set the Modal and Action buttons for 'Stage' option
const modalStageActions: JSX.Element[] = [
<Button
key="stage-users"
variant="primary"
onClick={activateUsers}
form="stage-users-modal"
>
Activate
</Button>,
<Button key="cancel-stage-user" variant="link" onClick={closeModal}>
Cancel
</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}
/>
)}
</>
);
};

export default ActivateStageUsers;
Loading

0 comments on commit 57bf689

Please sign in to comment.