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

<WIP>- Add Users to review step - (HMS-4906) #2633

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
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
20 changes: 19 additions & 1 deletion src/Components/CreateImageWizard/CreateImageWizard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
useFirstBootValidation,
useDetailsValidation,
useRegistrationValidation,
useUserValidation,
} from './utilities/useValidation';
import {
isAwsAccountIdValid,
Expand Down Expand Up @@ -65,6 +66,9 @@ import {
selectGcpShareMethod,
selectImageTypes,
addImageType,
selectUserName,
selectUserSshKey,
selectUserPassword,
} from '../../store/wizardSlice';
import { resolveRelPath } from '../../Utilities/path';
import { useFlag } from '../../Utilities/useGetEnvironment';
Expand Down Expand Up @@ -205,6 +209,9 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
const azureSubscriptionId = useAppSelector(selectAzureSubscriptionId);
const azureResourceGroup = useAppSelector(selectAzureResourceGroup);
const azureSource = useAppSelector(selectAzureSource);
const userName = useAppSelector(selectUserName);
const userPassword = useAppSelector(selectUserPassword);
const userSshKey = useAppSelector(selectUserSshKey);
// Registration
const registrationValidation = useRegistrationValidation();
// Snapshots
Expand All @@ -216,6 +223,8 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
const firstBootValidation = useFirstBootValidation();
// Details
const detailsValidation = useDetailsValidation();
// User
const userValidation = useUserValidation();

let startIndex = 1; // default index
if (isEdit) {
Expand Down Expand Up @@ -448,7 +457,16 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
key="wizard-users"
isHidden={!isUsersEnabled}
footer={
<CustomWizardFooter disableNext={false} optional={true} />
<CustomWizardFooter
disableNext={
!(
userName === '' &&
userPassword === '' &&
userSshKey === ''
) && userValidation.disabledNext
}
optional={true}
/>
}
>
<UsersStep />
Expand Down
155 changes: 154 additions & 1 deletion src/Components/CreateImageWizard/ValidatedTextInput.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import React, { useState } from 'react';
import React, { ChangeEvent, useState } from 'react';

import {
HelperText,
HelperTextItem,
TextInput,
TextInputProps,
Button,
TextAreaProps,
TextArea,
} from '@patternfly/react-core';
import { EyeSlashIcon, EyeIcon } from '@patternfly/react-icons';

import type { StepValidation } from './utilities/useValidation';

Expand All @@ -30,6 +34,28 @@ interface HookValidatedTextInputPropTypes extends TextInputProps {
warning?: string;
}

interface HookValidatedTextAreaPropTypes extends TextAreaProps {
dataTestId?: string | undefined;
ariaLabel?: string;
value: string;
placeholder?: string;
stepValidation: StepValidation;
fieldName: string;
}

interface HookValidatedTextInputWithButtonPropTypes extends TextInputProps {
dataTestId?: string | undefined;
ouiaId?: string;
ariaLabel: string | undefined;
value: string;
placeholder?: string;
stepValidation: StepValidation;
fieldName: string;
togglePasswordVisibility: () => void;
isPasswordVisible: boolean;
isEmpty: boolean;
}

export const HookValidatedInput = ({
dataTestId,
ouiaId,
Expand Down Expand Up @@ -90,6 +116,76 @@ export const HookValidatedInput = ({
);
};

export const HookValidatedInputWithButton = ({
dataTestId,
ouiaId,
ariaLabel,
value,
placeholder,
onChange,
stepValidation,
fieldName,
type = 'text',
isEmpty,
togglePasswordVisibility,
isPasswordVisible,
}: HookValidatedTextInputWithButtonPropTypes) => {
const [isPristine, setIsPristine] = useState(!value ? true : false);
// Do not surface validation on pristine state components
// Allow step validation to be set on pristine state, when needed
const validated = isEmpty
? 'default'
: isPristine
? 'default'
: stepValidation.errors[fieldName] === 'default'
? 'default'
: stepValidation.errors[fieldName]
? 'error'
: 'success';

const handleBlur = () => {
setIsPristine(false);
};
return (
<>
<div
style={{ position: 'relative', display: 'flex', alignItems: 'center' }}
>
<TextInput
value={value}
data-testid={dataTestId}
ouiaId={ouiaId}
type={type}
onChange={onChange}
validated={validated}
aria-label={ariaLabel}
onBlur={handleBlur}
placeholder={placeholder}
style={{ paddingRight: '2rem' }}
/>
<Button
variant="plain"
onClick={togglePasswordVisibility}
aria-label="Toggle password visibility"
style={{
position: 'absolute',
right: '0.5rem',
}}
>
{isPasswordVisible ? <EyeSlashIcon /> : <EyeIcon />}
</Button>
</div>
{validated === 'error' && (
<HelperText>
<HelperTextItem variant="error" hasIcon>
{stepValidation.errors[fieldName]}
</HelperTextItem>
</HelperText>
)}
</>
);
};

export const ValidatedTextInput = ({
dataTestId,
ouiaId,
Expand Down Expand Up @@ -137,3 +233,60 @@ export const ValidatedTextInput = ({
</>
);
};

export const HookValidatedTextArea = ({
dataTestId,
ariaLabel,
value,
placeholder,
onChange,
stepValidation,
fieldName,
type = 'text',
isDisabled = false,
}: HookValidatedTextAreaPropTypes) => {
const [isPristine, setIsPristine] = useState(!value ? true : false);
// Do not surface validation on pristine state components
// Allow step validation to be set on pristine state, when needed
const validated = isDisabled
? 'default'
: isPristine
? 'default'
: stepValidation.errors[fieldName] === 'default'
? 'default'
: stepValidation.errors[fieldName]
? 'error'
: 'success';

const handleBlur = () => {
setIsPristine(false);
};
const handleChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
if (onChange) {
onChange(event, event.target.value);
}
};

return (
<>
<TextArea
value={value}
data-testid={dataTestId}
type={type}
onChange={handleChange}
validated={validated}
aria-label={ariaLabel}
onBlur={handleBlur}
placeholder={placeholder}
isDisabled={isDisabled}
/>
{validated === 'error' && (
<HelperText>
<HelperTextItem variant="error" hasIcon>
{stepValidation.errors[fieldName]}
</HelperTextItem>
</HelperText>
)}
</>
);
};
22 changes: 20 additions & 2 deletions src/Components/CreateImageWizard/steps/Review/ReviewStep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
DetailsList,
ImageOutputList,
OscapList,
UsersList,
RegisterLaterList,
RegisterNowList,
TargetEnvAWSList,
Expand Down Expand Up @@ -66,8 +67,8 @@ const Review = ({ snapshottingEnabled }: { snapshottingEnabled: boolean }) => {
const [isExpandedOscapDetail, setIsExpandedOscapDetail] = useState(true);
const [isExpandedComplianceDetail, setIsExpandedComplianceDetail] =
useState(true);
const [isExpandedUsers, setIsExpandedUsers] = useState(true);
const [isExpandableFirstBoot, setIsExpandedFirstBoot] = useState(true);

const onToggleImageOutput = (isExpandedImageOutput: boolean) =>
setIsExpandedImageOutput(isExpandedImageOutput);
const onToggleTargetEnvs = (isExpandedTargetEnvs: boolean) =>
Expand All @@ -84,6 +85,8 @@ const Review = ({ snapshottingEnabled }: { snapshottingEnabled: boolean }) => {
setIsExpandedOscapDetail(isExpandedOscapDetail);
const onToggleComplianceDetails = (isExpandedComplianceDetail: boolean) =>
setIsExpandedComplianceDetail(isExpandedComplianceDetail);
const onToggleUsers = (isExpandedUsers: boolean) =>
setIsExpandedUsers(isExpandedUsers);
const onToggleFirstBoot = (isExpandableFirstBoot: boolean) =>
setIsExpandedFirstBoot(isExpandableFirstBoot);

Expand Down Expand Up @@ -140,7 +143,7 @@ const Review = ({ snapshottingEnabled }: { snapshottingEnabled: boolean }) => {
</TextContent>
);
};

const isUsersEnabled = useFlag('image-builder.users.enabled');
const isFirstBootEnabled = useFlag('image-builder.firstboot.enabled');
return (
<>
Expand Down Expand Up @@ -298,6 +301,21 @@ const Review = ({ snapshottingEnabled }: { snapshottingEnabled: boolean }) => {
{/* Intentional prop drilling for simplicity - To be removed */}
<ContentList snapshottingEnabled={snapshottingEnabled} />
</ExpandableSection>
{isUsersEnabled && (
<ExpandableSection
toggleContent={composeExpandable(
'Users',
'revisit-users',
'wizard-users'
)}
onToggle={(_event, isExpandedUsers) => onToggleUsers(isExpandedUsers)}
isExpanded={isExpandedUsers}
isIndented
data-testid="timezone-expandable"
>
<UsersList />
</ExpandableSection>
)}
{isFirstBootEnabled && (
<ExpandableSection
toggleContent={composeExpandable(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@
selectUseLatest,
selectPartitions,
selectFirstBootScript,
selectUsers,
selectUserName,
selectUserPassword,
selectUserSshKey,
} from '../../../../store/wizardSlice';
import { toMonthAndYear, yyyyMMddFormat } from '../../../../Utilities/time';
import {
Expand Down Expand Up @@ -739,6 +743,50 @@
return <OscapProfileInformation allowChangingCompliancePolicy={true} />;
};

export const UsersList = () => {
const users = useAppSelector(selectUsers);
const userName = useAppSelector(selectUserName);
const userPassword = useAppSelector(selectUserPassword);
const userSshKey = useAppSelector(selectUserSshKey);

Check failure on line 750 in src/Components/CreateImageWizard/steps/Review/ReviewStepTextLists.tsx

View workflow job for this annotation

GitHub Actions / dev-check

'userSshKey' is assigned a value but never used

return (
<TextContent>
<TextList component={TextListVariants.dl}>
<TextListItem
component={TextListItemVariants.dt}
className="pf-u-min-width"
>
Users
</TextListItem>
<TextListItem component={TextListItemVariants.dd}>
{users.length > 0 && (
<>
<TextListItem
component={TextListItemVariants.dt}
className="pf-v5-u-min-width"
>
User name
</TextListItem>
<TextListItem component={TextListItemVariants.dd}>
{userName}
</TextListItem>
<TextListItem
component={TextListItemVariants.dt}
className="pf-v5-u-min-width"
>
Passwords
</TextListItem>
<TextListItem component={TextListItemVariants.dd}>
{userPassword}
</TextListItem>
</>
)}
</TextListItem>
</TextList>
</TextContent>
);
};

export const FirstBootList = () => {
const isFirstbootEnabled = !!useAppSelector(selectFirstBootScript);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,23 @@ import {
} from '@patternfly/react-core';
import UserIcon from '@patternfly/react-icons/dist/esm/icons/user-icon';

const EmptyUserState = () => {
interface EmptyUserStateProps {
onAddUserClick: () => void;
}
const EmptyUserState = ({ onAddUserClick }: EmptyUserStateProps) => {
return (
<EmptyState variant={EmptyStateVariant.lg}>
<EmptyStateHeader
icon={<EmptyStateIcon icon={UserIcon} />}
headingLevel="h4"
/>
<EmptyStateFooter>
<Button variant="secondary" onClick={() => {}}>
<Button variant="secondary" onClick={onAddUserClick}>
Add a user
</Button>
</EmptyStateFooter>
</EmptyState>
);
};

export default EmptyUserState;
Loading
Loading