Skip to content

Commit

Permalink
Add activate modal and user settings
Browse files Browse the repository at this point in the history
  • Loading branch information
mreynolds389 committed Nov 3, 2023
1 parent 3877279 commit 0f07089
Show file tree
Hide file tree
Showing 6 changed files with 306 additions and 12 deletions.
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
14 changes: 11 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 Expand Up @@ -261,6 +268,7 @@ const UserSettings = (props: PropsToUserSettings) => {
radiusProxyConf={props.radiusProxyData || []}
idpConf={props.idpData || []}
certData={props.certData}
from={props.from}
/>
<TitleLayout
key={2}
Expand Down
2 changes: 2 additions & 0 deletions src/components/UsersSections/UsersAccountSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ interface PropsToUsersAccountSettings {
radiusProxyConf: RadiusServer[];
idpConf: IDPServer[];
certData: Record<string, unknown>;
from: "active-users" | "stage-users" | "preserved-users";
}

// Generic data to pass to the Textbox adder
Expand Down Expand Up @@ -317,6 +318,7 @@ const UsersAccountSettings = (props: PropsToUsersAccountSettings) => {
ipaObject={ipaObject}
metadata={props.metadata}
onRefresh={props.onRefresh}
from={props.from}
/>
</FormGroup>
<FormGroup
Expand Down
261 changes: 261 additions & 0 deletions src/components/modals/ActivateStageUsers.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
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;
// NOTE: 'onRefresh' is handled as { (User) => void | undefined } as a temporal solution
// until the C.L. is adapted in 'stage-' and 'preserved users' (otherwise
// the operation will fail for those components)
onRefresh?: () => void;
// NOTE: 'onOpenAddModal' is handled as { () => void | undefined } as a temporal solution
// until the C.L. is adapted in 'stage-' and 'preserved users' (otherwise
// the operation will fail for those components)
onOpenDeleteModal?: () => void;
// NOTE: 'onCloseAddModal' is handled as { () => void | undefined } as a temporal solution
// until the C.L. is adapted in 'stage-' and 'preserved users' (otherwise
// the operation will fail for those components)
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"
>
Stage
</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="preserved-users-stage-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;
23 changes: 19 additions & 4 deletions src/hooks/useUserSettingsData.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import {
useGetIdpServerQuery,
useGetObjectMetadataQuery,
useGetRadiusProxyQuery,
useGetUsersFullDataQuery,
useGetUsersFullQuery,
useGetStageUsersFullQuery,
} from "src/services/rpc";
// Data types
import {
Expand Down Expand Up @@ -34,14 +35,28 @@ type UserSettingsData = {
idpServers: IDPServer[];
};

const useUserSettingsData = (userId: string): UserSettingsData => {
const useUserSettings = (userId: string): UserSettingsData => {
return useSettingsData(userId, "active");
};

const useStageUserSettings = (userId: string): UserSettingsData => {
return useSettingsData(userId, "stage");
};

const useSettingsData = (
userId: string,
userType: string
): UserSettingsData => {
// [API call] Metadata
const metadataQuery = useGetObjectMetadataQuery();
const metadata = metadataQuery.data || {};
const metadataLoading = metadataQuery.isLoading;

// [API call] User
const userFullDataQuery = useGetUsersFullDataQuery(userId);
let userFullDataQuery = useGetUsersFullQuery(userId);
if (userType === "stage") {
userFullDataQuery = useGetStageUsersFullQuery(userId);
}
const userFullData = userFullDataQuery.data;
const isFullDataLoading = userFullDataQuery.isLoading;

Expand Down Expand Up @@ -138,4 +153,4 @@ const useUserSettingsData = (userId: string): UserSettingsData => {
return settings;
};

export default useUserSettingsData;
export { useUserSettings, useStageUserSettings };
Loading

0 comments on commit 0f07089

Please sign in to comment.