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] Adapt fields and sync data #135

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
164 changes: 164 additions & 0 deletions src/components/Form/IpaCalendar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import React from "react";
// PatternFly
import { CalendarMonth, DropdownItem } from "@patternfly/react-core";
// Layouts
import DataTimePickerLayout from "src/components/layouts/Calendar/DataTimePickerLayout";
// Components
import CalendarButton from "src/components/layouts/Calendar/CalendarButton";
// Data types
import { User } from "src/utils/datatypes/globalDataTypes";
// Utils
import {
getFullDate,
getFullTime,
getLDAPGeneralizedTime,
parseFullDateStringToUTCFormat,
} from "src/utils/utils";
// ipaObject Utils
import {
IPAParamDefinition,
getParamProperties,
updateIpaObject,
} from "src/utils/ipaObjectUtils";
import CalendarLayout from "src/components/layouts/Calendar/CalendarLayout";

const IpaCalendar = (props: IPAParamDefinition) => {
// Date and time picker (Calendar)
const [isCalendarOpen, setIsCalendarOpen] = React.useState(false);
const [isTimeOpen, setIsTimeOpen] = React.useState(false);
const [valueDate, setValueDate] = React.useState("YYYY-MM-DD");
const [valueTime, setValueTime] = React.useState("HH:MM");
const times = Array.from(new Array(10), (_, i) => i + 10);
const defaultTime = "10:00";

const { readOnly } = getParamProperties(props);

// Initialize parameters got from the 'User' object
React.useEffect(() => {
if (props.ipaObject !== undefined) {
// Parse ipaObject into 'User' type to access the 'datetime' parameter
const user = props.ipaObject as unknown as User;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IpaCalendar component should not have any reference to a specific object type (in this case user). The raw value should be obtained the same way as in text input, e.g.:

const { required, readOnly, value, onChange } = getParamProperties(props);

After that you can extract the base64 value and change it into Date object. There is also no need to do it in an effect as it is simple conversion of value from props.

But the question is whether we should be doing this parsing here. I think that the conversion from base64 to Date or string (depending what we find more useful) should happen in apiToUser method in userUtils or any similar method that is used in rpc for other object types.

The reason is that in ipaObject should be of the same type/structure after changing it. Or in other words, it should be possible to set it to the same value and be regarded as a same value. Atm the source is object with base64 string and after modification it is string, thus this doesn't apply.

if (user[props.name] && user[props.name][0].__datetime__) {
// Parse to UTC format
const paramUtcDate = parseFullDateStringToUTCFormat(
user[props.name][0].__datetime__
);

// Get date
const fullDate = getFullDate(paramUtcDate);
setValueDate(fullDate);

// Get time
const fullTime = getFullTime(paramUtcDate);
setValueTime(fullTime);
}
}
}, [props.ipaObject]);

// 'onToggle' calendar function
const onToggleCalendar = () => {
setIsCalendarOpen(!isCalendarOpen);
setIsTimeOpen(false);
};

// 'onToggle' time function
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars
const onToggleTime = (_ev: any) => {
setIsTimeOpen(!isTimeOpen);
setIsCalendarOpen(false);
};

// On selecting a date from the calendar
const onSelectCalendar = (newValueDate: Date) => {
const newValue = getFullDate(newValueDate);
setValueDate(newValue);

setIsCalendarOpen(!isCalendarOpen);
// setting default time when it is not picked
let valTime = valueTime;
if (valueTime === "HH:MM") {
setValueTime(defaultTime);
valTime = defaultTime;
}

// Convert to LDAP format
const newFullDate = new Date(newValue + " " + valTime);
const LDAPDate = getLDAPGeneralizedTime(newFullDate);

// Update 'ipaObject' with the new date
if (props.ipaObject !== undefined && props.onChange !== undefined) {
updateIpaObject(props.ipaObject, props.onChange, LDAPDate, props.name);
}
};

// On selecting a time from the dropdown
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const onSelectTime = (ev: any) => {
const newTime = ev.target.value as string;

setValueTime(newTime);
setIsTimeOpen(!isTimeOpen);

const newFullDate = new Date(valueDate + " " + newTime);
const LDAPDate = getLDAPGeneralizedTime(newFullDate);

// Update 'ipaObject' with the new date
// - Parse to ISO format (to return to the "user_mod" API call)
if (props.ipaObject !== undefined && props.onChange !== undefined) {
updateIpaObject(props.ipaObject, props.onChange, LDAPDate, props.name);
}
};

const timeOptions = times.map((time) => (
<DropdownItem key={time} component="button" value={`${time}:00`}>
{`${time}:00`}
</DropdownItem>
));

const calendar = (
<CalendarMonth
date={new Date(valueDate)}
onChange={onSelectCalendar}
disabled={readOnly}
/>
);

const time = (
<DataTimePickerLayout
dropdownOnSelect={onSelectTime}
toggleAriaLabel="Toggle the time picker menu"
toggleIndicator={null}
toggleOnToggle={onToggleTime}
toggleStyle={{ padding: "6px 16px" }}
dropdownIsOpen={isTimeOpen}
dropdownItems={timeOptions}
/>
);

const calendarButton = (
<CalendarButton
ariaLabel="Toggle the calendar"
onClick={onToggleCalendar}
/>
);

return (
<CalendarLayout
name={props.name}
position="bottom"
bodyContent={calendar}
showClose={false}
isVisible={isCalendarOpen}
hasNoPadding={true}
hasAutoWidth={true}
textInputId="date-time"
textInputAriaLabel="date and time picker"
textInputValue={valueDate + " " + valueTime}
>
{readOnly ? <></> : calendarButton}
{readOnly ? <></> : time}
</CalendarLayout>
);
};

export default IpaCalendar;
124 changes: 20 additions & 104 deletions src/components/UsersSections/UsersAccountSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,14 @@ import {
} from "src/utils/datatypes/globalDataTypes";
// Layouts
import SecondaryButton from "src/components/layouts/SecondaryButton";
import DataTimePickerLayout from "src/components/layouts/Calendar/DataTimePickerLayout";
import CalendarButton from "src/components/layouts/Calendar/CalendarButton";
import CalendarLayout from "src/components/layouts/Calendar/CalendarLayout";
import PopoverWithIconLayout from "src/components/layouts/PopoverWithIconLayout";
import ModalWithTextAreaLayout from "src/components/layouts/ModalWithTextAreaLayout";
// Modals
import CertificateMappingDataModal from "src/components/modals/CertificateMappingDataModal";
import IpaTextInputFromList from "../Form/IpaTextInputFromList";
import AddTextInputFromListModal from "../modals/AddTextInputFromListModal";
// Utils
import { asRecord, isSimpleValue } from "src/utils/userUtils";
import { asRecord } from "src/utils/userUtils";
// RTK
import {
ErrorResult,
Expand All @@ -43,6 +40,10 @@ import DeletionConfirmationModal from "../modals/DeletionConfirmationModal";
// Form
import IpaCheckbox from "../Form/IpaCheckbox";
import IpaSelect from "../Form/IpaSelect";
import IpaTextInput from "../Form/IpaTextInput";
import IpaCalendar from "../Form/IpaCalendar";
// ipaObject utils
import { updateIpaObject } from "src/utils/ipaObjectUtils";

interface PropsToUsersAccountSettings {
user: Partial<User>;
Expand Down Expand Up @@ -124,16 +125,6 @@ const UsersAccountSettings = (props: PropsToUsersAccountSettings) => {
</Button>,
];

// Updates 'ipaObject'
const updateIpaObject = (newValue: string | string[], paramName: string) => {
if (!isSimpleValue(paramName)) {
const paramToModify = newValue as string[];
recordOnChange({ ...ipaObject, [paramName]: paramToModify });
} else {
recordOnChange({ ...ipaObject, [paramName]: newValue });
}
};

// Add 'principal alias'
const onAddPrincipalAlias = () => {
const payload = [props.user.uid, [newAliasValue]];
Expand Down Expand Up @@ -216,6 +207,8 @@ const UsersAccountSettings = (props: PropsToUsersAccountSettings) => {
const radiusConfOnSelect = (selection: any) => {
setRadiusConfSelected(selection.target.textContent as string);
updateIpaObject(
ipaObject,
recordOnChange,
selection.target.textContent as string,
"ipatokenradiusconfiglink"
);
Expand All @@ -242,7 +235,12 @@ const UsersAccountSettings = (props: PropsToUsersAccountSettings) => {

const idpConfOnSelect = (selection: any) => {
setIdpConfSelected(selection.target.textContent as string);
updateIpaObject(selection.target.textContent as string, "ipaidpconfiglink");
updateIpaObject(
ipaObject,
recordOnChange,
selection.target.textContent as string,
"ipaidpconfiglink"
);
setIsIdpConfOpen(false);
};

Expand Down Expand Up @@ -537,80 +535,6 @@ const UsersAccountSettings = (props: PropsToUsersAccountSettings) => {
setIdpIdentifier(value);
};

// Date and time picker (Calendar)
const [isCalendarOpen, setIsCalendarOpen] = React.useState(false);
const [isTimeOpen, setIsTimeOpen] = React.useState(false);
const [valueDate, setValueDate] = React.useState("YYYY-MM-DD");
const [valueTime, setValueTime] = React.useState("HH:MM");
const times = Array.from(new Array(10), (_, i) => i + 10);
const defaultTime = "0:00";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const dateFormat = (date: any) =>
date
.toLocaleDateString("en-US", {
year: "numeric",
month: "2-digit",
day: "2-digit",
})
.replace(/\//g, "-");

const onToggleCalendar = () => {
setIsCalendarOpen(!isCalendarOpen);
setIsTimeOpen(false);
};

// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars
const onToggleTime = (_ev: any) => {
setIsTimeOpen(!isTimeOpen);
setIsCalendarOpen(false);
};

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const onSelectCalendar = (newValueDate: any) => {
const newValue = dateFormat(newValueDate);
setValueDate(newValue);
setIsCalendarOpen(!isCalendarOpen);
// setting default time when it is not picked
if (valueTime === "HH:MM") {
setValueTime(defaultTime);
}
};

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const onSelectTime = (ev: any) => {
setValueTime(ev.target.value);
setIsTimeOpen(!isTimeOpen);
};

const timeOptions = times.map((time) => (
<DropdownItem key={time} component="button" value={`${time}:00`}>
{`${time}:00`}
</DropdownItem>
));

const calendar = (
<CalendarMonth date={new Date(valueDate)} onChange={onSelectCalendar} />
);

const time = (
<DataTimePickerLayout
dropdownOnSelect={onSelectTime}
toggleAriaLabel="Toggle the time picker menu"
toggleIndicator={null}
toggleOnToggle={onToggleTime}
toggleStyle={{ padding: "6px 16px" }}
dropdownIsOpen={isTimeOpen}
dropdownItems={timeOptions}
/>
);

const calendarButton = (
<CalendarButton
ariaLabel="Toggle the calendar"
onClick={onToggleCalendar}
/>
);

// Messages for the popover
const certificateMappingDataMessage = () => (
<div>
Expand Down Expand Up @@ -730,21 +654,13 @@ const UsersAccountSettings = (props: PropsToUsersAccountSettings) => {
label="Kerberos principal expiration (UTC)"
fieldId="kerberos-principal-expiration"
>
<CalendarLayout
name="krbprincipalexpiration"
position="bottom"
bodyContent={calendar}
showClose={false}
isVisible={isCalendarOpen}
hasNoPadding={true}
hasAutoWidth={true}
textInputId="date-time"
textInputAriaLabel="date and time picker"
textInputValue={valueDate + " " + valueTime}
>
{calendarButton}
{time}
</CalendarLayout>
<IpaCalendar
name={"krbprincipalexpiration"}
ipaObject={ipaObject}
onChange={recordOnChange}
objectName="user"
metadata={props.metadata}
/>
</FormGroup>
<FormGroup label="Login shell" fieldId="login-shell">
<TextInput
Expand Down
2 changes: 2 additions & 0 deletions src/components/layouts/Calendar/DataTimePickerLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ interface PropsToDataTimePicker {
toggleIndicator?: React.ElementType | null;
toggleOnToggle?: (value: boolean, event: any) => void;
toggleStyle?: React.CSSProperties | undefined;
// isDisabled: boolean;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes in this file looks like forgotten leftovers.

}

const DataTimePickerLayout = (props: PropsToDataTimePicker) => {
Expand All @@ -26,6 +27,7 @@ const DataTimePickerLayout = (props: PropsToDataTimePicker) => {
toggleIndicator={props.toggleIndicator}
onToggle={props.toggleOnToggle}
style={props.toggleStyle}
// isDisabled={props.isDisabled}
>
<OutlinedClockIcon />
</DropdownToggle>
Expand Down
15 changes: 15 additions & 0 deletions src/utils/ipaObjectUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -459,3 +459,18 @@ export function convertApiObj(
}
return obj;
}

// Updates 'ipaObject'
export const updateIpaObject = (
ipaObject: Record<string, unknown>,
setIpaObject: (ipaObject: Record<string, unknown>) => void,
newValue: string | string[],
paramName: string
) => {
if (!isSimpleValue(paramName)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

import { isSimpleValue } from "./userUtils"; should not be used in ipaObjectUtils module as that implementation is limited to user object thus would be unusable for other object types.

In this form, this method should be in userUtils module.

To be generic for other objects, we would need to find a different way, e.g. pass the isSimpleValue method to the updateIpaObject or something else.

const paramToModify = newValue as string[];
setIpaObject({ ...ipaObject, [paramName]: paramToModify });
} else {
setIpaObject({ ...ipaObject, [paramName]: newValue });
}
};
Loading