Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Subordinate ids action buttons #652

Merged
merged 1 commit into from
Feb 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading