Skip to content

Commit

Permalink
Add Subordinate IDs action button 'Add'
Browse files Browse the repository at this point in the history
The 'Add' button should generate a Subordinate ID
for a given user.

The addition has been handled via modal and the data
has been filtered to show those users that don't have
a SubID. On every generation, the data is refetch thus
showing an updated version of the data.

Signed-off-by: Carla Martinez <[email protected]>
  • Loading branch information
carma12 committed Feb 28, 2025
1 parent 014bfba commit e18ada7
Show file tree
Hide file tree
Showing 4 changed files with 340 additions and 5 deletions.
224 changes: 224 additions & 0 deletions src/components/modals/SubIdsModals/AddModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
import React from "react";
// PatternFly
import { Button } from "@patternfly/react-core";
// Components
import ModalWithFormLayout, {
Field,
} from "src/components/layouts/ModalWithFormLayout";
import SimpleSelector from "src/components/layouts/SimpleSelector";
// Data types
import { SubId } from "src/utils/datatypes/globalDataTypes";
// RPC
import { useUserFindQuery } from "src/services/rpcUsers";
import {
SubidFindPayload,
useSubidFindQuery,
useSubidGenerateMutation,
} from "src/services/rpcSubordinateIDs";
// Hooks
import useAlerts from "src/hooks/useAlerts";
// Errors
import { FetchBaseQueryError } from "@reduxjs/toolkit/query";
import { SerializedError } from "@reduxjs/toolkit";

interface PropsToAddModal {
isOpen: boolean;
onOpenModal?: () => void;
onCloseModal: () => void;
title: string;
onRefresh?: () => void;
}

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

// API call
const [generateSubid] = useSubidGenerateMutation();

// States
const [selectedItem, setSelectedItem] = React.useState<string>("");
const [subIdsAdded, setSubIdsAdded] = React.useState<string[]>([]);
const [availableItems, setAvailableItems] = React.useState<string[]>([]);
const [isAddButtonSpinning, setIsAddButtonSpinning] = React.useState(false);
const [isAddAnotherButtonSpinning, setIsAddAnotherButtonSpinning] =
React.useState(false);

// Get all the Subordinate IDs to perform the filtering
// API calls
const subIdsDataResponse = useSubidFindQuery({
searchValue: "",
pkeyOnly: false,
sizeLimit: 100, // Established a maximum of 100 elements to retrieve
} as SubidFindPayload);

const { data: subidFindData, isLoading, error } = subIdsDataResponse;

// Handle data when the API call is finished
React.useEffect(() => {
// On error
if (!isLoading && error) {
alerts.addAlert("subid-find-error", error, "danger");
}

// On success
if (!isLoading && subidFindData) {
const listResult = subidFindData.result.result as unknown as SubId[];
const totalCount = subidFindData.result.count;

const subids: string[] = [];
for (let i = 0; i < totalCount; i++) {
const subid = listResult[i];
if (subid.ipauniqueid) subids.push(subid.ipaowner[0]);
}
setSubIdsAdded(subids);
}
}, [subidFindData]);

// API call - Prepare available items to show in Add modal
const usersResult = useUserFindQuery({
uid: null,
noMembers: true,
});

// Filter data to show in the selector
React.useEffect(() => {
const usersData = usersResult.data;
const userIds: string[] = [];
if (usersData !== undefined && !usersResult.isLoading) {
usersData.forEach((user) => {
userIds.push(user.uid);
});

// Filter the users to return the ones that have not been added yet
const filteredIds = userIds.filter((user) => {
return !subIdsAdded.some((subId) => subId === user);
});
setAvailableItems(filteredIds);
}
}, [usersResult, subIdsAdded]);

// Refetch data when the modal is opened
React.useEffect(() => {
if (props.isOpen) {
subIdsDataResponse.refetch();
usersResult.refetch();
}
}, [props.isOpen]);

// on Add subordinate ID
const onAdd = (keepModalOpen: boolean) => {
setIsAddButtonSpinning(true);
setIsAddAnotherButtonSpinning(true);

generateSubid(selectedItem).then((result) => {
if ("data" in result) {
const data = result.data.result;
const error = result.data.error as
| FetchBaseQueryError
| SerializedError;

if (error) {
alerts.addAlert("add-subid-error", error, "danger");
}

if (data) {
alerts.addAlert(
"add-subid-success",
"Subordinate ID successfully added",
"success"
);
// Reset selected item
setSelectedItem("");
// Update data
if (props.onRefresh) {
props.onRefresh();
}
subIdsDataResponse.refetch();
// 'Add and add another' will keep the modal open
if (!keepModalOpen) {
props.onCloseModal();
}
// Reset button spinners
setIsAddButtonSpinning(false);
setIsAddAnotherButtonSpinning(false);
}
}
});
};

// Clean and close modal
const cleanAndCloseModal = () => {
setSelectedItem("");
props.onCloseModal();
};

// Fields
const fields: Field[] = [
{
id: "owner",
name: "Owner",
pfComponent: (
<SimpleSelector
id="owner"
options={availableItems.map((name) => ({
label: name,
value: name,
}))}
selected={selectedItem}
onSelectedChange={(selected: string) => setSelectedItem(selected)}
/>
),
fieldRequired: true,
},
];

// Actions
const modalActions: JSX.Element[] = [
<Button
key="add-new"
variant="secondary"
isDisabled={isAddButtonSpinning || selectedItem === ""}
form="add-modal-form"
onClick={() => {
onAdd(false);
}}
>
Add
</Button>,
<Button
key="add-new-again"
variant="secondary"
isDisabled={isAddAnotherButtonSpinning || selectedItem === ""}
form="add-again-modal-form"
onClick={() => {
onAdd(true);
}}
>
Add and add again
</Button>,
<Button key="cancel-new" variant="link" onClick={cleanAndCloseModal}>
Cancel
</Button>,
];

// Render component
return (
<>
<alerts.ManagedAlerts />
<ModalWithFormLayout
variantType={"small"}
modalPosition={"top"}
offPosition={"76px"}
title={props.title}
formId="add-modal-form"
fields={fields}
show={props.isOpen}
onClose={cleanAndCloseModal}
actions={modalActions}
/>
</>
);
};

export default AddModal;
30 changes: 27 additions & 3 deletions src/pages/SubordinateIDs/SubordinateIDs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ import {
useGetSubIdEntriesQuery,
useSearchSubIdEntriesMutation,
} from "src/services/rpcSubordinateIDs";
// Modals
import AddModal from "src/components/modals/SubIdsModals/AddModal";

const SubordinateIDs = () => {
// Update current route data to Redux and highlight the current page in the Nav bar
Expand Down Expand Up @@ -188,8 +190,8 @@ const SubordinateIDs = () => {
searchValue: searchValue,
apiVersion,
sizelimit: 100,
startIdx: firstUserIdx,
stopIdx: lastUserIdx,
startIdx: 0,
stopIdx: 200, // Search will consider a max. of elements
}).then((result) => {
if ("data" in result) {
const searchError = result.data.error as
Expand Down Expand Up @@ -251,6 +253,17 @@ const SubordinateIDs = () => {
submitSearchValue,
};

// Modals functionality
const [showAddModal, setShowAddModal] = React.useState(false);

const onOpenAddModal = () => {
setShowAddModal(true);
};

const onCloseAddModal = () => {
setShowAddModal(false);
};

// List of Toolbar items
const toolbarItems: ToolbarItem[] = [
{
Expand Down Expand Up @@ -285,7 +298,12 @@ const SubordinateIDs = () => {
{
key: 3,
element: (
<SecondaryButton isDisabled={!showTableRows}>Add</SecondaryButton>
<SecondaryButton
isDisabled={!showTableRows}
onClickHandler={onOpenAddModal}
>
Add
</SecondaryButton>
),
},
{
Expand Down Expand Up @@ -370,6 +388,12 @@ const SubordinateIDs = () => {
className="pf-v5-u-pb-0 pf-v5-u-pr-md"
/>
</PageSection>
<AddModal
isOpen={showAddModal}
onCloseModal={onCloseAddModal}
onRefresh={refreshData}
title="Add Subordinate ID"
/>
</Page>
);
};
Expand Down
50 changes: 48 additions & 2 deletions src/services/rpcSubordinateIDs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import {
import { FetchBaseQueryError } from "@reduxjs/toolkit/query";
// Data types
import { SubId } from "src/utils/datatypes/globalDataTypes";
// Utils
import { API_VERSION_BACKUP } from "src/utils/utils";

/**
* Endpoints: useGetSubIdEntriesQuery, useSearchSubIdEntriesMutation
Expand All @@ -26,6 +28,13 @@ export interface SubIdDataPayload {
stopIdx: number;
}

export interface SubidFindPayload {
searchValue: string;
pkeyOnly: boolean;
sizeLimit: number;
version?: string;
}

// API
const extendedApi = api.injectEndpoints({
endpoints: (build) => ({
Expand Down Expand Up @@ -180,8 +189,45 @@ const extendedApi = api.injectEndpoints({
return { data: response };
},
}),
/**
* Retrieves all subordinate IDs (unique IDs).
* @param SubidFindPayload
* @returns FindRPCResponse
*/
subidFind: build.query<FindRPCResponse, SubidFindPayload>({
query: (payload) => {
return getCommand({
method: "subid_find",
params: [
[payload.searchValue],
{
pkey_only: payload.pkeyOnly,
sizelimit: payload.sizeLimit,
version: payload.version || API_VERSION_BACKUP,
},
],
});
},
}),
/**
* Generate a subordinate ID for a given user
* @param string
* @returns FindRPCResponse
*/
subidGenerate: build.mutation<FindRPCResponse, string>({
query: (uid) => {
return getCommand({
method: "subid_generate",
params: [[], { ipaowner: uid, version: API_VERSION_BACKUP }],
});
},
}),
}),
});

export const { useGetSubIdEntriesQuery, useSearchSubIdEntriesMutation } =
extendedApi;
export const {
useGetSubIdEntriesQuery,
useSearchSubIdEntriesMutation,
useSubidGenerateMutation,
useSubidFindQuery,
} = extendedApi;
Loading

0 comments on commit e18ada7

Please sign in to comment.