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

feat: create delete user groups modal #1703

Merged
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
14 changes: 14 additions & 0 deletions cypress/e2e/users-and-user-groups.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,4 +128,18 @@ describe('Users and User Groups page', () => {
cy.get('[data-ouia-component-id="user-details-drawer"]').contains(mockUsers.data[0].last_name).should('exist');
cy.get('[data-ouia-component-id="user-details-drawer"]').contains(mockUsers.data[0].email).should('exist');
});

it('should be able to open Delete User Groups Modal from row actions', () => {
cy.get('[data-ouia-component-id="user-groups-tab-button"]').click();
cy.get('[data-ouia-component-id^="iam-user-groups-table-table-td-0-7"]').click();
cy.get('[data-ouia-component-id^="iam-user-groups-table-table-td-0-7"] button').contains('Delete user group').click();
cy.get('[data-ouia-component-id="iam-user-groups-table-remove-user-modal"]').should('be.visible');
});

it('should be able to open Delete User Groups modal from toolbar', () => {
cy.get('[data-ouia-component-id="user-groups-tab-button"]').click();
cy.get('[data-ouia-component-id^="iam-user-groups-table-table-tr-0"]').find('input[type="checkbox"]').click();
cy.get('[data-ouia-component-id="iam-user-groups-table-actions-dropdown-menu-control"]').click();
cy.get('[data-ouia-component-id="iam-user-groups-table-actions-dropdown-menu-control"] button').contains('Delete user group').click();
});
});
10 changes: 10 additions & 0 deletions src/Messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -2350,6 +2350,16 @@ export default defineMessages({
description: 'Modal body text for delete user',
defaultMessage: 'will lose all the roles associated with the user groups it belongs to.',
},
deleteUserGroupModalTitle: {
id: 'deleteUserGroupModalTitle',
description: 'Title for delete user group modal',
defaultMessage: 'Delete user {count, plural, one {group} other {groups}}?',
},
deleteUserGroupModalBody: {
id: 'deleteUserGroupModalBody',
description: 'Modal body text for delete user group',
defaultMessage: 'Deleting {count, plural, one {the <b>{name}</b> user group} other {{count} user groups}} will impact user access configuration.',
},
addToUserGroup: {
id: 'addToUserGroup',
description: 'Action column option to add user to group',
Expand Down
1 change: 1 addition & 0 deletions src/redux/reducers/group-reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export interface Group {
modified?: string;
admin_default?: boolean;
platform_default?: boolean;
system?: boolean;
}

export interface GroupStore {
Expand Down
147 changes: 112 additions & 35 deletions src/smart-components/access-management/UserGroupsTable.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
import React, { useEffect, useCallback, useMemo, useState } from 'react';
import React, { useEffect, useCallback, useMemo, useState, Fragment } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { useDataViewSelection, useDataViewPagination } from '@patternfly/react-data-view/dist/dynamic/Hooks';
import { BulkSelect, BulkSelectValue } from '@patternfly/react-component-groups/dist/dynamic/BulkSelect';
import { DataView } from '@patternfly/react-data-view/dist/dynamic/DataView';
import { DataViewToolbar } from '@patternfly/react-data-view/dist/dynamic/DataViewToolbar';
import { DataViewTable } from '@patternfly/react-data-view/dist/dynamic/DataViewTable';
import { EmptyState, EmptyStateBody, EmptyStateHeader, EmptyStateIcon, Pagination, Tooltip } from '@patternfly/react-core';
import { ButtonVariant, EmptyState, EmptyStateBody, EmptyStateHeader, EmptyStateIcon, Pagination, Tooltip } from '@patternfly/react-core';
import { ActionsColumn, TableVariant } from '@patternfly/react-table';
import { mappedProps } from '../../helpers/shared/helpers';
import { RBACStore } from '../../redux/store';
import { useSearchParams } from 'react-router-dom';
import { fetchGroups } from '../../redux/actions/group-actions';
import { fetchGroups, removeGroups } from '../../redux/actions/group-actions';
import { formatDistanceToNow } from 'date-fns';
import { FormattedMessage, useIntl } from 'react-intl';
import messages from '../../Messages';
import { Group } from '../../redux/reducers/group-reducer';
import { DataViewState, EventTypes, useDataViewEventsContext } from '@patternfly/react-data-view';
import { DataViewTrObject, DataViewState, EventTypes, useDataViewEventsContext } from '@patternfly/react-data-view';
import { SearchIcon } from '@patternfly/react-icons';
import { SkeletonTable } from '@patternfly/react-component-groups';
import { ResponsiveAction, ResponsiveActions, SkeletonTable, WarningModal } from '@patternfly/react-component-groups';

const COLUMNS: string[] = ['User group name', 'Description', 'Users', 'Service accounts', 'Roles', 'Workspaces', 'Last modified'];

Expand Down Expand Up @@ -46,15 +46,17 @@ const UserGroupsTable: React.FunctionComponent<UserGroupsTableProps> = ({
onChange,
focusedGroup,
}) => {
const [isDeleteModalOpen, setIsDeleteModalOpen] = React.useState(false);
const [currentGroups, setCurrentGroups] = React.useState<Group[]>([]);
const dispatch = useDispatch();
const [activeState, setActiveState] = useState<DataViewState | undefined>(DataViewState.loading);
const intl = useIntl();
const { trigger } = useDataViewEventsContext();

const rowActions = [
{ title: intl.formatMessage(messages['usersAndUserGroupsEditUserGroup']), onClick: () => console.log('EDIT USER GROUP') },
{ title: intl.formatMessage(messages['usersAndUserGroupsDeleteUserGroup']), onClick: () => console.log('DELETE USER GROUP') },
];
const handleDeleteModalToggle = (groups: Group[]) => {
setCurrentGroups(groups);
setIsDeleteModalOpen(!isDeleteModalOpen);
};

const { groups, totalCount, isLoading } = useSelector((state: RBACStore) => ({
groups: state.groupReducer?.groups?.data || [],
Expand Down Expand Up @@ -130,7 +132,7 @@ const UserGroupsTable: React.FunctionComponent<UserGroupsTableProps> = ({
(event.target.matches('td') || event.target.matches('tr')) && trigger(EventTypes.rowClick, group);
};

return groups.map((group: any) => ({
return groups.map((group: Group) => ({
id: group.uuid,
row: [
group.name,
Expand All @@ -142,12 +144,27 @@ const UserGroupsTable: React.FunctionComponent<UserGroupsTableProps> = ({
<div className="pf-v5-u-color-400">{intl.formatMessage(messages['usersAndUserGroupsNoDescription'])}</div>
),
group.principalCount,
group.serviceAccounts || '?', // not currently in API
'?', // not currently in API
group.roleCount,
group.workspaces || '?', // not currently in API
formatDistanceToNow(new Date(group.modified), { addSuffix: true }),
'?', // not currently in API
group.modified ? formatDistanceToNow(new Date(group.modified), { addSuffix: true }) : '',
enableActions && {
cell: <ActionsColumn items={rowActions} />,
cell: (
<ActionsColumn
items={[
{
title: intl.formatMessage(messages['usersAndUserGroupsEditUserGroup']),
onClick: () => console.log('EDIT USER GROUP'),
},
{
title: intl.formatMessage(messages['usersAndUserGroupsDeleteUserGroup']),
onClick: () => handleDeleteModalToggle([group]),
},
]}
rowData={group}
isDisabled={group.platform_default || group.system}
/>
),
props: { isActionCell: true },
},
],
Expand All @@ -161,6 +178,21 @@ const UserGroupsTable: React.FunctionComponent<UserGroupsTableProps> = ({

const pageSelected = rows.length > 0 && rows.every(isSelected);
const pagePartiallySelected = !pageSelected && rows.some(isSelected);
const isRowSystemOrPlatformDefault = (selectedRow: any) => {
const group = groups.find((group) => group.uuid === selectedRow.id);
return group?.platform_default || group?.system;
};

const handleDeleteGroups = async (groupsToDelete: Group[]) => {
await dispatch(removeGroups(groupsToDelete.map((group) => group.uuid)));
setIsDeleteModalOpen(false);
fetchData({
limit: perPage,
offset: (page - 1) * perPage,
orderBy: 'name',
count: totalCount || 0,
});
};

const paginationComponent = (
<Pagination
Expand Down Expand Up @@ -192,29 +224,74 @@ const UserGroupsTable: React.FunctionComponent<UserGroupsTableProps> = ({
);

return (
<DataView ouiaId={ouiaId} selection={selection} activeState={activeState}>
<DataViewToolbar
ouiaId={`${ouiaId}-header-toolbar`}
bulkSelect={
<BulkSelect
isDataPaginated
pageCount={groups.length}
selectedCount={selected.length}
totalCount={totalCount}
pageSelected={pageSelected}
pagePartiallySelected={pagePartiallySelected}
onSelect={handleBulkSelect}
<Fragment>
{isDeleteModalOpen && (
<WarningModal
ouiaId={`${ouiaId}-remove-user-modal`}
isOpen={isDeleteModalOpen}
title={intl.formatMessage(messages.deleteUserGroupModalTitle, { count: currentGroups.length })}
withCheckbox
checkboxLabel={intl.formatMessage(messages.understandActionIrreversible)}
confirmButtonLabel={intl.formatMessage(messages.remove)}
confirmButtonVariant={ButtonVariant.danger}
onClose={() => setIsDeleteModalOpen(false)}
onConfirm={() => {
handleDeleteGroups(currentGroups);
}}
>
<FormattedMessage
{...messages.deleteUserGroupModalBody}
values={{
b: (text) => <b>{text}</b>,
count: currentGroups.length,
plural: currentGroups.length > 1 ? intl.formatMessage(messages.groups) : intl.formatMessage(messages.group),
name: currentGroups[0]?.name,
}}
/>
}
pagination={React.cloneElement(paginationComponent, { isCompact: true })}
/>
{isLoading ? (
<SkeletonTable rowsCount={10} columns={COLUMNS} variant={TableVariant.compact} />
) : (
<DataViewTable variant="compact" aria-label="Users Table" ouiaId={`${ouiaId}-table`} columns={COLUMNS} rows={rows} states={{ empty }} />
</WarningModal>
)}
<DataViewToolbar ouiaId={`${ouiaId}-footer-toolbar`} pagination={paginationComponent} />
</DataView>
<DataView ouiaId={ouiaId} selection={selection} activeState={activeState}>
<DataViewToolbar
ouiaId={`${ouiaId}-header-toolbar`}
bulkSelect={
<BulkSelect
isDataPaginated
pageCount={groups.length}
selectedCount={selected.length}
totalCount={totalCount}
pageSelected={pageSelected}
pagePartiallySelected={pagePartiallySelected}
onSelect={handleBulkSelect}
/>
}
actions={
<ResponsiveActions breakpoint="lg" ouiaId={`${ouiaId}-actions-dropdown`}>
<ResponsiveAction
isDisabled={selected.length === 0 || selected.some(isRowSystemOrPlatformDefault)}
onClick={() => console.log('EDIT USER GROUP')}
>
{intl.formatMessage(messages.usersAndUserGroupsEditUserGroup)}
</ResponsiveAction>
<ResponsiveAction
isDisabled={selected.length === 0 || selected.some(isRowSystemOrPlatformDefault)}
onClick={() => {
handleDeleteModalToggle(groups.filter((group) => selected.some((selectedRow: DataViewTrObject) => selectedRow.id === group.uuid)));
}}
>
{intl.formatMessage(messages.usersAndUserGroupsDeleteUserGroup)}
</ResponsiveAction>
</ResponsiveActions>
}
pagination={React.cloneElement(paginationComponent, { isCompact: true })}
/>
{isLoading ? (
<SkeletonTable rowsCount={10} columns={COLUMNS} variant={TableVariant.compact} />
) : (
<DataViewTable variant="compact" aria-label="Users Table" ouiaId={`${ouiaId}-table`} columns={COLUMNS} rows={rows} states={{ empty }} />
)}
<DataViewToolbar ouiaId={`${ouiaId}-footer-toolbar`} pagination={paginationComponent} />
</DataView>
</Fragment>
);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import UserGroupsTable from './UserGroupsTable';
import { useLocation, useNavigate } from 'react-router-dom';
import AddUserGroupModal from './AddUserGroupModal';
import { User } from '../../redux/reducers/user-reducer';
import UserDetailsDrawer from './UserDetailsDrawer';
import { Group } from '../../redux/reducers/group-reducer';
import GroupDetailsDrawer from './GroupDetailsDrawer';
import { DataViewEventsProvider } from '@patternfly/react-data-view';
import UserDetailsDrawer from './UserDetailsDrawer';

const TAB_NAMES = ['users', 'user-groups'];

Expand Down