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

[Active users][Settings][Account settings] Add functionality for Selectors #138

Merged
merged 1 commit into from
Aug 31, 2023
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
82 changes: 82 additions & 0 deletions src/components/Form/IpaSelect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import React from "react";
// PatternFly
import {
Select,
SelectOption,
SelectOptionObject,
SelectVariant,
} from "@patternfly/react-core";
// Utils
import {
IPAParamDefinition,
getParamProperties,
} from "src/utils/ipaObjectUtils";
import { updateIpaObject } from "src/utils/ipaObjectUtils";
import { NO_SELECTION_OPTION } from "src/utils/constUtils";

interface IPAParamDefinitionSelect extends IPAParamDefinition {
id?: string;
setIpaObject?: (ipaObject: Record<string, unknown>) => void;
variant?: "single" | "checkbox" | "typeahead" | "typeaheadmulti";
options: string[];
ariaLabelledBy?: string;
}

const IpaSelect = (props: IPAParamDefinitionSelect) => {
// Obtains the metadata of the parameter
const { required, readOnly, value } = getParamProperties(props);

// Handle selected value
let valueSelected = NO_SELECTION_OPTION;
if (value !== undefined && value && value !== "") {
valueSelected = value.toString();
}

const ipaObject = props.ipaObject || {};

const [isOpen, setIsOpen] = React.useState(false);

const onSelect = (
_event: React.MouseEvent | React.ChangeEvent,
selection: string | SelectOptionObject
) => {
let valueToUpdate = "";

if (selection !== NO_SELECTION_OPTION) {
valueToUpdate = selection as string;
}

if (ipaObject && props.setIpaObject !== undefined) {
updateIpaObject(ipaObject, props.setIpaObject, valueToUpdate, props.name);
}

setIsOpen(false);
};

// Provide empty option at the beginning of the list
const optionsToSelect: string[] = [...(props.options || [])];
optionsToSelect.unshift(NO_SELECTION_OPTION);

return (
<Select
id={props.id}
name={props.name}
variant={props.variant || SelectVariant.single}
aria-label={props.name}
onToggle={setIsOpen}
onSelect={onSelect}
selections={valueSelected}
isOpen={isOpen}
aria-labelledby={props.ariaLabelledBy || props.id}
readOnly={readOnly}
isDisabled={readOnly}
required={required}
>
{optionsToSelect.map((option, index) => {
return <SelectOption key={index} value={option} />;
})}
</Select>
);
};

export default IpaSelect;
11 changes: 10 additions & 1 deletion src/components/UserSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@ import {
// Icons
import OutlinedQuestionCircleIcon from "@patternfly/react-icons/dist/esm/icons/outlined-question-circle-icon";
// Data types
import { Metadata, User } from "src/utils/datatypes/globalDataTypes";
import {
Metadata,
User,
IDPServer,
RadiusServer,
} from "src/utils/datatypes/globalDataTypes";
// Layouts
import ToolbarLayout from "src/components/layouts/ToolbarLayout";
import TitleLayout from "src/components/layouts/TitleLayout";
Expand Down Expand Up @@ -53,6 +58,8 @@ export interface PropsToUserSettings {
isDataLoading?: boolean;
modifiedValues: () => Partial<User>;
onResetValues: () => void;
radiusProxyData?: RadiusServer[];
idpData?: IDPServer[];
from: "active-users" | "stage-users" | "preserved-users";
}

Expand Down Expand Up @@ -254,6 +261,8 @@ const UserSettings = (props: PropsToUserSettings) => {
onUserChange={props.onUserChange}
metadata={props.metadata}
onRefresh={props.onRefresh}
radiusProxyConf={props.radiusProxyData || []}
idpConf={props.idpData || []}
/>
<TitleLayout
key={2}
Expand Down
99 changes: 31 additions & 68 deletions src/components/UsersSections/UsersAccountSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,17 @@ import {
FormGroup,
TextInput,
Checkbox,
Select,
SelectVariant,
SelectOption,
DropdownItem,
CalendarMonth,
Button,
} from "@patternfly/react-core";
// Data types
import { IDPServer, Metadata, User } from "src/utils/datatypes/globalDataTypes";
import {
IDPServer,
Metadata,
User,
RadiusServer,
} from "src/utils/datatypes/globalDataTypes";
// Layouts
import SecondaryButton from "src/components/layouts/SecondaryButton";
import DataTimePickerLayout from "src/components/layouts/Calendar/DataTimePickerLayout";
Expand All @@ -30,12 +32,15 @@ import CertificateMappingDataModal from "src/components/modals/CertificateMappin
import { asRecord } from "src/utils/userUtils";
// Form
import IpaTextInput from "src/components/Form/IpaTextInput";
import IpaSelect from "../Form/IpaSelect";

interface PropsToUsersAccountSettings {
user: Partial<User>;
onUserChange: (element: Partial<User>) => void;
metadata: Metadata;
onRefresh: () => void;
radiusProxyConf: RadiusServer[];
idpConf: IDPServer[];
}

// Generic data to pass to the Textbox adder
Expand All @@ -54,6 +59,14 @@ const UsersAccountSettings = (props: PropsToUsersAccountSettings) => {
props.onUserChange
);

// Dropdown 'Radius proxy configuration'
const radiusProxyList = props.radiusProxyConf.map((item) =>
item.cn.toString()
);

// Dropdown 'External IdP configuration'
const idpConfOptions = props.idpConf.map((item) => item.cn.toString());

const [principalAliasList, setPrincipalAliasList] = useState<ElementData[]>([
{
id: 0,
Expand Down Expand Up @@ -415,38 +428,6 @@ const UsersAccountSettings = (props: PropsToUsersAccountSettings) => {
/>
);

// Dropdown 'Radius proxy configuration'
const [isRadiusConfOpen, setIsRadiusConfOpen] = useState(false);
const [radiusConfSelected, setRadiusConfSelected] = useState("");
const radiusConfOptions = [
{ value: "Option 1", disabled: false },
{ value: "Option 2", disabled: false },
{ value: "Option 3", disabled: false },
];
const radiusConfOnToggle = (isOpen: boolean) => {
setIsRadiusConfOpen(isOpen);
};

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const radiusConfOnSelect = (selection: any) => {
setRadiusConfSelected(selection.target.textContent);
setIsRadiusConfOpen(false);
};

// Dropdown 'External IdP configuration'
const [isIdpConfOpen, setIsIdpConfOpen] = useState(false);
const [idpConfSelected, setIdpConfSelected] = useState("");
const [idpConfOptions] = useState<IDPServer[]>([]);

const idpConfOnToggle = (isOpen: boolean) => {
setIsIdpConfOpen(isOpen);
};

const idpConfOnSelect = (selection: any) => {
setIdpConfSelected(selection.target.textContent);
setIsIdpConfOpen(false);
};

// Messages for the popover
const certificateMappingDataMessage = () => (
<div>
Expand Down Expand Up @@ -703,26 +684,15 @@ const UsersAccountSettings = (props: PropsToUsersAccountSettings) => {
label="Radius proxy configuration"
fieldId="radius-proxy-configuration"
>
<Select
<IpaSelect
id="radius-proxy-configuration"
name="ipatokenradiusconfiglink"
variant={SelectVariant.single}
placeholderText=" "
aria-label="Select Input with descriptions"
onToggle={radiusConfOnToggle}
onSelect={radiusConfOnSelect}
selections={radiusConfSelected}
isOpen={isRadiusConfOpen}
aria-labelledby="radius-proxy-conf"
>
{radiusConfOptions.map((option, index) => (
<SelectOption
isDisabled={option.disabled}
key={index}
value={option.value}
/>
))}
</Select>
options={radiusProxyList}
ipaObject={ipaObject}
setIpaObject={recordOnChange}
objectName="user"
metadata={props.metadata}
/>
</FormGroup>
<FormGroup
label="Radius proxy username"
Expand All @@ -740,22 +710,15 @@ const UsersAccountSettings = (props: PropsToUsersAccountSettings) => {
label="External IdP configuration"
fieldId="external-idp-configuration"
>
<Select
<IpaSelect
id="external-idp-configuration"
name="ipaidpconfiglink"
variant={SelectVariant.single}
placeholderText=" "
aria-label="Select Input with descriptions"
onToggle={idpConfOnToggle}
onSelect={idpConfOnSelect}
selections={idpConfSelected}
isOpen={isIdpConfOpen}
aria-labelledby="external-idp-conf"
>
{idpConfOptions.map((option, index) => (
<SelectOption key={index} value={option.cn} />
))}
</Select>
options={idpConfOptions}
ipaObject={ipaObject}
setIpaObject={recordOnChange}
objectName="user"
metadata={props.metadata}
/>
</FormGroup>
<FormGroup
label="External IdP user identifier"
Expand Down
34 changes: 31 additions & 3 deletions src/hooks/useUserSettingsData.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,18 @@ import { useState, useEffect } from "react";

// RPC
import {
useGetIdpServerQuery,
useGetObjectMetadataQuery,
useGetRadiusProxyQuery,
useGetUsersFullDataQuery,
} from "src/services/rpc";

import { Metadata, User } from "src/utils/datatypes/globalDataTypes";
// Data types
import {
IDPServer,
Metadata,
RadiusServer,
User,
} from "src/utils/datatypes/globalDataTypes";

type UserSettingsData = {
isLoading: boolean;
Expand All @@ -22,6 +29,8 @@ type UserSettingsData = {
certData?: Record<string, unknown>;
refetch: () => void;
modifiedValues: () => Partial<User>;
radiusServers: RadiusServer[];
idpServers: IDPServer[];
};

const useUserSettingsData = (userId: string): UserSettingsData => {
Expand All @@ -30,27 +39,45 @@ const useUserSettingsData = (userId: string): UserSettingsData => {
const metadata = metadataQuery.data || {};
const metadataLoading = metadataQuery.isLoading;

// [API call] User
const userFullDataQuery = useGetUsersFullDataQuery(userId);
const userFullData = userFullDataQuery.data;
const isFullDataLoading = userFullDataQuery.isLoading;

// [API call] RADIUS proxy server
const radiusProxyQuery = useGetRadiusProxyQuery();
const radiusProxyData = radiusProxyQuery.data;
const isRadiusProxyLoading = radiusProxyQuery.isLoading;

// [API call] IdP server
const idpQuery = useGetIdpServerQuery();
const idpData = idpQuery.data;
const isIdpLoading = idpQuery.isLoading;

const [modified, setModified] = useState(false);

// Data displayed and modified by the user
const [user, setUser] = useState<Partial<User>>({});

useEffect(() => {
if (userFullData && !userFullDataQuery.isFetching) {
setUser({ ...userFullData.user });
}
}, [userFullData, userFullDataQuery.isFetching]);

const settingsData = {
isLoading: metadataLoading || isFullDataLoading,
isLoading:
metadataLoading ||
isFullDataLoading ||
isRadiusProxyLoading ||
isIdpLoading,
isFetching: userFullDataQuery.isFetching,
modified,
metadata,
user,
setUser,
radiusServers: radiusProxyData || [],
idpServers: idpData || [],
refetch: userFullDataQuery.refetch,
} as UserSettingsData;

Expand Down Expand Up @@ -78,6 +105,7 @@ const useUserSettingsData = (userId: string): UserSettingsData => {
};
settingsData.modifiedValues = getModifiedValues;

// Detect any change in 'originalUser' and 'user' objects
useEffect(() => {
if (!userFullData || !userFullData.user) {
return;
Expand Down
2 changes: 2 additions & 0 deletions src/pages/ActiveUsers/ActiveUsersTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ const ActiveUsersTabs = () => {
isModified={userSettingsData.modified}
onResetValues={userSettingsData.resetValues}
modifiedValues={userSettingsData.modifiedValues}
radiusProxyData={userSettingsData.radiusServers}
idpData={userSettingsData.idpServers}
from="active-users"
/>
</Tab>
Expand Down
Loading
Loading