Skip to content

Commit

Permalink
fix: update diffing and required fields
Browse files Browse the repository at this point in the history
  • Loading branch information
CodyWMitchell committed Jan 20, 2025
1 parent 7651189 commit a405640
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 103 deletions.
129 changes: 71 additions & 58 deletions src/smart-components/access-management/EditUserGroup.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import ContentHeader from '@patternfly/react-component-groups/dist/esm/ContentHeader';
import { PageSection, PageSectionVariants, Spinner } from '@patternfly/react-core';
import React, { useEffect, useMemo } from 'react';
import React, { useEffect, useMemo, useState } from 'react';
import { useIntl } from 'react-intl';
import Messages from '../../Messages';
import { FormRenderer, componentTypes, validatorTypes } from '@data-driven-forms/react-form-renderer';
Expand All @@ -22,9 +22,18 @@ export const EditUserGroup: React.FunctionComponent = () => {
const groupId = params.groupId;
const navigate = useNavigate();

const [isLoading, setIsLoading] = useState(true);
const [initialFormData, setInitialFormData] = useState<{
name?: string;
description?: string;
users?: string[];
serviceAccounts?: string[];
} | null>(null);

const group = useSelector((state: RBACStore) => state.groupReducer?.selectedGroup);
const allGroups = useSelector((state: RBACStore) => state.groupReducer?.groups?.data || []);
const [isLoading, setIsLoading] = React.useState(true);
const groupUsers = useSelector((state: RBACStore) => state.groupReducer?.selectedGroup?.members?.data || []);
const groupServiceAccounts = useSelector((state: RBACStore) => state.groupReducer?.selectedGroup?.serviceAccounts?.data || []);

const breadcrumbsList = useMemo(
() => [
Expand All @@ -48,48 +57,72 @@ export const EditUserGroup: React.FunctionComponent = () => {
groupId ? dispatch(fetchGroup(groupId)) : Promise.resolve(),
]);
} finally {
if (group) {
setInitialFormData({
name: group.name,
description: group.description,
users: groupUsers.map((user) => user.username),
serviceAccounts: groupServiceAccounts.map((sa) => sa.clientId),
});
}
setIsLoading(false);
}
};
fetchData();
}, [dispatch, groupId]);
}, [dispatch, groupId, group?.uuid]);

const schema = {
fields: [
{
name: 'name',
label: intl.formatMessage(Messages.name),
component: componentTypes.TEXT_FIELD,
validate: [
{ type: validatorTypes.REQUIRED },
(value: string) => {
if (value === group?.name) {
return undefined;
}
const schema = useMemo(
() => ({
fields: [
{
name: 'name',
label: intl.formatMessage(Messages.name),
component: componentTypes.TEXT_FIELD,
validate: [
{ type: validatorTypes.REQUIRED },
(value: string) => {
if (value === initialFormData?.name) {
return undefined;
}

const isDuplicate = allGroups.some(
(existingGroup) => existingGroup.name.toLowerCase() === value?.toLowerCase() && existingGroup.uuid !== groupId
);
const isDuplicate = allGroups.some(
(existingGroup) => existingGroup.name.toLowerCase() === value?.toLowerCase() && existingGroup.uuid !== groupId
);

return isDuplicate ? intl.formatMessage(Messages.groupNameTakenTitle) : undefined;
return isDuplicate ? intl.formatMessage(Messages.groupNameTakenTitle) : undefined;
},
],
initialValue: initialFormData?.name,
isRequired: true,
},
{
name: 'description',
label: intl.formatMessage(Messages.description),
component: componentTypes.TEXTAREA,
initialValue: initialFormData?.description,
isRequired: true,
},
{
name: 'users-and-service-accounts',
component: 'users-and-service-accounts',
groupId: groupId,
initialUsers: initialFormData?.users || [],
initialServiceAccounts: initialFormData?.serviceAccounts || [],
initialValue: {
users: {
initial: initialFormData?.users || [],
updated: initialFormData?.users || [],
},
serviceAccounts: {
initial: initialFormData?.serviceAccounts || [],
updated: initialFormData?.serviceAccounts || [],
},
},
],
initialValue: group?.name,
},
{
name: 'description',
label: intl.formatMessage(Messages.description),
component: componentTypes.TEXTAREA,
initialValue: group?.description,
},
{
name: 'users-and-service-accounts',
component: 'users-and-service-accounts',
initializeOnMount: true,
groupId: groupId,
},
],
};
},
],
}),
[initialFormData, allGroups, groupId, intl]
);

const returnToPreviousPage = () => {
navigate(-1);
Expand All @@ -98,9 +131,8 @@ export const EditUserGroup: React.FunctionComponent = () => {
const handleSubmit = async (values: Record<string, any>) => {
if (values.name !== group?.name || values.description !== group?.description) {
dispatch(updateGroup({ uuid: groupId, name: values.name, description: values.description }));
console.log(`Dispatched update group with name: ${values.name} and description: ${values.description}`);
}
console.log('submitted values:', values);

if (values['users-and-service-accounts']) {
const { users, serviceAccounts } = values['users-and-service-accounts'];
if (users.updated.length > 0) {
Expand All @@ -117,33 +149,14 @@ export const EditUserGroup: React.FunctionComponent = () => {
}
};

// const initialValues = useMemo(() => {
// const values = {
// name: group?.name,
// description: group?.description,
// 'users-and-service-accounts': {
// serviceAccounts: {
// initial: group?.serviceAccounts?.data?.map((serviceAccount) => serviceAccount.uuid) || [],
// updated: [],
// },
// users: {
// initial: group?.members?.data?.map((user) => user.username) || [],
// updated: [],
// },
// },
// };
// console.log('initial values:', values);
// return values;
// }, [group]);

return (
<React.Fragment>
<section className="pf-v5-c-page__main-breadcrumb">
<RbacBreadcrumbs {...breadcrumbsList} />
</section>
<ContentHeader title={intl.formatMessage(Messages.usersAndUserGroupsEditUserGroup)} subtitle={''} />
<PageSection data-ouia-component-id="edit-user-group-form" className="pf-v5-u-m-lg-on-lg" variant={PageSectionVariants.light} isWidthLimited>
{isLoading ? (
{isLoading || !initialFormData ? (
<div style={{ textAlign: 'center' }}>
<Spinner />
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const EmptyTable: React.FunctionComponent<{ titleText: string }> = ({ titleText
interface EditGroupServiceAccountsTableProps {
groupId?: string;
onChange: (serviceAccounts: TableState) => void;
initialServiceAccountIds: string[];
}

const PER_PAGE_OPTIONS = [
Expand All @@ -53,12 +54,11 @@ const reducer = ({ serviceAccountReducer }: { serviceAccountReducer: ServiceAcco
offset: serviceAccountReducer.offset,
});

const EditGroupServiceAccountsTable: React.FunctionComponent<EditGroupServiceAccountsTableProps> = ({ groupId, onChange }) => {
const EditGroupServiceAccountsTable: React.FunctionComponent<EditGroupServiceAccountsTableProps> = ({ groupId, onChange, initialServiceAccountIds }) => {
const dispatch = useDispatch();
const pagination = useDataViewPagination({ perPage: 20 });
const { page, perPage, onSetPage, onPerPageSelect } = pagination;
const { auth, getEnvironmentDetails } = useChrome();
const initialServiceAccountIds = useRef<string[]>([]);
const [activeState, setActiveState] = useState<DataViewState | undefined>(DataViewState.loading);
const intl = useIntl();

Expand All @@ -79,11 +79,10 @@ const EditGroupServiceAccountsTable: React.FunctionComponent<EditGroupServiceAcc
const { onSelect, selected } = selection;

useEffect(() => {
return () => {
onSelect(false);
initialServiceAccountIds.current = [];
};
}, []);
onSelect(false);
const initialSelectedServiceAccounts = initialServiceAccountIds.map((id) => ({ id }));
onSelect(true, initialSelectedServiceAccounts);
}, [initialServiceAccountIds]);

const { serviceAccounts, status, isLoading } = useSelector(reducer);
const totalCount = useMemo(() => {
Expand Down Expand Up @@ -143,17 +142,8 @@ const EditGroupServiceAccountsTable: React.FunctionComponent<EditGroupServiceAcc
);

useEffect(() => {
// on mount, select the accounts that are in the current group
onSelect(false);
initialServiceAccountIds.current = [];
const initialSelectedServiceAccounts = groupServiceAccounts.map((account) => ({ id: account.clientId }));
onSelect(true, initialSelectedServiceAccounts);
initialServiceAccountIds.current = initialSelectedServiceAccounts.map((account) => account.id);
}, [groupServiceAccounts]);

useEffect(() => {
onChange({ initial: initialServiceAccountIds.current, updated: selection.selected.map((account) => account.id) });
}, [selection.selected]);
onChange({ initial: initialServiceAccountIds, updated: selection.selected.map((user) => user.id) });
}, [selection.selected, initialServiceAccountIds]);

const handleBulkSelect = (value: BulkSelectValue) => {
if (value === BulkSelectValue.none) {
Expand Down
29 changes: 10 additions & 19 deletions src/smart-components/access-management/EditUserGroupUsers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,10 @@ const EmptyTable: React.FunctionComponent<{ titleText: string }> = ({ titleText
);
};

interface EditGroupUsersTableUsersTableProps {
interface EditGroupUsersTableProps {
onChange: (userDiff: TableState) => void;
groupId: string;
initialUserIds: string[];
}

const PER_PAGE_OPTIONS = [
Expand All @@ -40,13 +41,12 @@ const PER_PAGE_OPTIONS = [
{ title: '100', value: 100 },
];

const EditGroupUsersTable: React.FunctionComponent<EditGroupUsersTableUsersTableProps> = ({ onChange, groupId }) => {
const EditGroupUsersTable: React.FunctionComponent<EditGroupUsersTableProps> = ({ onChange, groupId, initialUserIds }) => {
const dispatch = useDispatch();
const pagination = useDataViewPagination({ perPage: 20 });
const { page, perPage, onSetPage, onPerPageSelect } = pagination;
const initialUserIds = useRef<string[]>([]);
const intl = useIntl();
const [activeState, setActiveState] = useState<DataViewState | undefined>(DataViewState.loading);
const intl = useIntl();

const columns = useMemo(
() => [
Expand All @@ -66,11 +66,10 @@ const EditGroupUsersTable: React.FunctionComponent<EditGroupUsersTableUsersTable
const { selected, onSelect, isSelected } = selection;

useEffect(() => {
return () => {
onSelect(false);
initialUserIds.current = [];
};
}, []);
onSelect(false);
const initialSelectedUsers = initialUserIds.map((id) => ({ id }));
onSelect(true, initialSelectedUsers);
}, [initialUserIds]);

const { users, groupUsers, totalCount, isLoading } = useSelector((state: RBACStore) => ({
users: state.userReducer?.users?.data || [],
Expand Down Expand Up @@ -121,16 +120,8 @@ const EditGroupUsersTable: React.FunctionComponent<EditGroupUsersTableUsersTable
}, [fetchData, page, perPage]);

useEffect(() => {
onSelect(false);
initialUserIds.current = [];
const initialSelectedUsers = groupUsers.map((user) => ({ id: user.username }));
onSelect(true, initialSelectedUsers);
initialUserIds.current = initialSelectedUsers.map((user) => user.id);
}, [groupUsers]);

useEffect(() => {
onChange({ initial: initialUserIds.current, updated: selection.selected.map((user) => user.id) });
}, [selection.selected]);
onChange({ initial: initialUserIds, updated: selection.selected.map((user) => user.id) });
}, [selection.selected, initialUserIds]);

const pageSelected = rows.length > 0 && rows.every(isSelected);
const pagePartiallySelected = !pageSelected && rows.some(isSelected);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { FormGroup, Tab, Tabs } from '@patternfly/react-core';
import React, { useState } from 'react';
import React, { useState, useEffect } from 'react';
import { UseFieldApiConfig, useFieldApi, useFormApi } from '@data-driven-forms/react-form-renderer';
import EditGroupServiceAccountsTable from './EditUserGroupServiceAccounts';
import EditGroupUsersTable from './EditUserGroupUsers';
Expand All @@ -11,23 +11,47 @@ export interface TableState {
updated: string[];
}

export const EditGroupUsersAndServiceAccounts: React.FunctionComponent<UseFieldApiConfig> = (props) => {
interface ExtendedUseFieldApiConfig extends UseFieldApiConfig {
initialUsers?: string[];
initialServiceAccounts?: string[];
}

export const EditGroupUsersAndServiceAccounts: React.FunctionComponent<ExtendedUseFieldApiConfig> = (props) => {
const [activeTabKey, setActiveTabKey] = useState(0);
const formOptions = useFormApi();
const { input, groupId } = useFieldApi(props);
const values = formOptions.getState().values[input.name];
const { input, groupId, initialUsers = [], initialServiceAccounts = [] } = useFieldApi(props);
const intl = useIntl();

// Initialize form values once when the component mounts
useEffect(() => {
const initialState = {
users: {
initial: initialUsers,
updated: initialUsers,
},
serviceAccounts: {
initial: initialServiceAccounts,
updated: initialServiceAccounts,
},
};

if (!formOptions.getState().values[input.name]) {
input.onChange(initialState);
}
}, [initialUsers, initialServiceAccounts]);

const handleUserChange = (users: TableState) => {
const currentValue = formOptions.getState().values[input.name] || {};
input.onChange({
...currentValue,
users,
serviceAccounts: values?.serviceAccounts,
});
};

const handleServiceAccountsChange = (serviceAccounts: TableState) => {
const currentValue = formOptions.getState().values[input.name] || {};
input.onChange({
users: values?.users,
...currentValue,
serviceAccounts,
});
};
Expand All @@ -41,13 +65,19 @@ export const EditGroupUsersAndServiceAccounts: React.FunctionComponent<UseFieldA
<FormGroup label={intl.formatMessage(Messages.selectUsersAndOrServiceAccounts)}>
<Tabs activeKey={activeTabKey} onSelect={handleTabSelect}>
<Tab eventKey={0} title={intl.formatMessage(Messages.users)}>
<EditGroupUsersTable groupId={groupId} onChange={handleUserChange} />
<EditGroupUsersTable groupId={groupId} onChange={handleUserChange} initialUserIds={initialUsers} />
</Tab>
<Tab eventKey={1} title={intl.formatMessage(Messages.serviceAccounts)}>
<EditGroupServiceAccountsTable groupId={groupId} onChange={handleServiceAccountsChange} />
<EditGroupServiceAccountsTable
groupId={groupId}
onChange={handleServiceAccountsChange}
initialServiceAccountIds={initialServiceAccounts}
/>
</Tab>
</Tabs>
</FormGroup>
</React.Fragment>
);
};

export default EditGroupUsersAndServiceAccounts;

0 comments on commit a405640

Please sign in to comment.