Skip to content

Commit

Permalink
feat(ESSNTL-4365): Enable actions in the groups table (#1779)
Browse files Browse the repository at this point in the history
* Connect modals to the groups table actions

Implements https://issues.redhat.com/browse/ESSNTL-4365.

This enables the creation, deletion and renaming of one group in the
groups table.

* Fix propTypes for groups modals

* Allow multiple groups deletion
  • Loading branch information
gkarat authored Mar 3, 2023
1 parent edfe5f8 commit aa8ed51
Show file tree
Hide file tree
Showing 8 changed files with 243 additions and 46 deletions.
13 changes: 13 additions & 0 deletions cypress/support/interceptors.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,16 @@ export const groupDetailInterceptors = {
}).as('getGroupDetail');
}
};

export const deleteGroupsInterceptors = {
'successful deletion': () => {
cy.intercept('DELETE', '/api/inventory/v1/groups/*', {
statusCode: 204
}).as('deleteGroups');
},
'failed deletion (invalid request)': () => {
cy.intercept('DELETE', '/api/inventory/v1/groups/*', {
statusCode: 400
}).as('deleteGroups');
}
};
86 changes: 77 additions & 9 deletions src/components/GroupsTable/GroupsTable.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ import {
TEXT_INPUT,
TOOLBAR,
TOOLBAR_FILTER,
DROPDOWN_TOGGLE
DROPDOWN_TOGGLE,
DROPDOWN,
DROPDOWN_ITEM,
MODAL
} from '@redhat-cloud-services/frontend-components-utilities';
import _ from 'lodash';
import React from 'react';
Expand Down Expand Up @@ -125,14 +128,12 @@ describe('pagination', () => {
});

it('can change page limit', () => {
cy.wait('@getGroups').then(() => {
// first initial call
cy.wrap(PAGINATION_VALUES).each((el) => {
changePagination(el).then(() => {
cy.wait('@getGroups')
.its('request.url')
.should('include', `perPage=${el}`);
});
cy.wait('@getGroups'); // first initial call
PAGINATION_VALUES.forEach((el) => {
changePagination(el).then(() => {
cy.wait('@getGroups')
.its('request.url')
.should('include', `perPage=${el}`);
});
});
});
Expand Down Expand Up @@ -255,6 +256,73 @@ describe('selection and bulk selection', () => {
});
});

describe('actions', () => {
beforeEach(() => {
interceptors['successful with some items']();
mountTable();

cy.wait('@getGroups'); // first initial request
});

const TEST_ID = 0;

it('bulk rename and delete actions are disabled when no items selected', () => {
cy.get(`${TOOLBAR} ${DROPDOWN}`).eq(1).click(); // open bulk action toolbar
cy.get(DROPDOWN_ITEM).should('have.class', 'pf-m-disabled');
});

it('can rename a group, 1', () => {
cy.get(ROW).eq(TEST_ID + 1).find(`${DROPDOWN} button`).click();
cy.get(DROPDOWN_ITEM).contains('Rename group').click();
cy.get(MODAL).find('h1').should('contain.text', 'Rename group');
cy.get(MODAL).find('input').should('have.value', fixtures.results[TEST_ID].name);

cy.wait('@getGroups'); // validate request
});

it('can rename a group, 2', () => {
selectRowN(TEST_ID + 1);
cy.get(`${TOOLBAR} ${DROPDOWN}`).eq(1).click(); // open bulk action toolbar
cy.get(DROPDOWN_ITEM).contains('Rename group').click();
cy.get(MODAL).find('h1').should('contain.text', 'Rename group');
cy.get(MODAL).find('input').should('have.value', fixtures.results[TEST_ID].name);

cy.wait('@getGroups'); // validate request
});

it('can delete a group, 1', () => {
cy.get(ROW).eq(TEST_ID + 1).find(`${DROPDOWN} button`).click();
cy.get(DROPDOWN_ITEM).contains('Delete group').click();
cy.get(MODAL).find('h1').should('contain.text', 'Delete group?');
cy.get(MODAL).find('p').should('contain.text', fixtures.results[TEST_ID].name);
});

it('can delete a group, 2', () => {
selectRowN(TEST_ID + 1);
cy.get(`${TOOLBAR} ${DROPDOWN}`).eq(1).click(); // open bulk action toolbar
cy.get(DROPDOWN_ITEM).contains('Delete group').click();
cy.get(MODAL).find('h1').should('contain.text', 'Delete group?');
cy.get(MODAL).find('p').should('contain.text', fixtures.results[TEST_ID].name);
});

it('can delete more groups', () => {
const TEST_ROWS = [2, 3];
TEST_ROWS.forEach((row) => selectRowN(row));

cy.get(`${TOOLBAR} ${DROPDOWN}`).eq(1).click(); // open bulk action toolbar
cy.get(DROPDOWN_ITEM).contains('Delete groups').click();
cy.get(MODAL).find('h1').should('contain.text', 'Delete groups?');
cy.get(MODAL).find('p').should('contain.text', `${TEST_ROWS.length} groups and all their data`);
});

it('can create a group', () => {
cy.get(TOOLBAR).find('button').contains('Create group').click();
cy.get(MODAL).find('h1').should('contain.text', 'Create group');

cy.wait('@getGroups'); // validate request
});
});

describe('edge cases', () => {
it('no groups match', () => {
interceptors['successful empty']();
Expand Down
75 changes: 75 additions & 0 deletions src/components/GroupsTable/GroupsTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ import { Link } from 'react-router-dom';
import { TABLE_DEFAULT_PAGINATION } from '../../constants';
import { fetchGroups } from '../../store/inventory-actions';
import useFetchBatched from '../../Utilities/hooks/useFetchBatched';
import CreateGroupModal from '../InventoryGroups/Modals/CreateGroupModal';
import DeleteGroupModal from '../InventoryGroups/Modals/DeleteGroupModal';
import RenameGroupModal from '../InventoryGroups/Modals/RenameGroupModal';
import { getGroups } from '../InventoryGroups/utils/api';
import { generateLoadingRows } from '../InventoryTable/helpers';
import NoEntitiesFound from '../InventoryTable/NoEntitiesFound';
Expand Down Expand Up @@ -70,6 +73,10 @@ const GroupsTable = () => {
const [filters, setFilters] = useState(GROUPS_TABLE_INITIAL_STATE);
const [rows, setRows] = useState([]);
const [selectedIds, setSelectedIds] = useState([]);
const [selectedGroup, setSelectedGroup] = useState({});
const [createModalOpen, setCreateModalOpen] = useState(false);
const [renameModalOpen, setRenameModalOpen] = useState(false);
const [deleteModalOpen, setDeleteModalOpen] = useState(false);
const groups = useMemo(() => data?.results || [], [data]);
const { fetchBatched } = useFetchBatched();

Expand Down Expand Up @@ -105,9 +112,15 @@ const GroupsTable = () => {
<span key={index}>{<DateFormat date={group.updated_at} />}</span>
],
groupId: group.id,
groupName: group.name,
selected: selectedIds.includes(group.id)
}));
setRows(newRows);

setSelectedGroup({
id: selectedIds[0],
name: groups.find(({ id }) => id === selectedIds[0])?.name
});
}, [groups, selectedIds]);

// TODO: convert initial URL params to filters
Expand Down Expand Up @@ -233,6 +246,25 @@ const GroupsTable = () => {

return (
<div id="groups-table">
<CreateGroupModal
isModalOpen={createModalOpen}
setIsModalOpen={setCreateModalOpen}
reloadData={() => {fetchData(filters);}}
/>
<RenameGroupModal
isModalOpen={renameModalOpen}
setIsModalOpen={setRenameModalOpen}
reloadData={() => fetchData(filters)}
modalState={selectedGroup}
/>
<DeleteGroupModal
isModalOpen={deleteModalOpen}
setIsModalOpen={setDeleteModalOpen}
reloadData={() => fetchData(filters)}
modalState={selectedIds.length > 1 ? {
ids: selectedIds
} : selectedGroup}
/>
<PrimaryToolbar
pagination={{
itemCount: data?.total || 0,
Expand Down Expand Up @@ -287,6 +319,27 @@ const GroupsTable = () => {
ouiaId: 'groups-selector',
count: selectedIds.length
}}
actionsConfig={{
actions: [
{
label: 'Create group',
onClick: () => setCreateModalOpen(true)
},
{
label: 'Rename group',
onClick: () => setRenameModalOpen(true),
props: {
isDisabled: selectedIds.length !== 1
}
},
{
label: selectedIds.length > 1 ? 'Delete groups' : 'Delete group',
onClick: () => setDeleteModalOpen(true),
props: {
isDisabled: selectedIds.length === 0
}
}
] }}
/>
<Table
aria-label="Groups table"
Expand All @@ -303,6 +356,28 @@ const GroupsTable = () => {
isStickyHeader
onSelect={onSelect}
canSelectAll={false}
actions={[
{
title: 'Rename group',
onClick: (event, rowIndex, { groupId, groupName }) => {
setSelectedGroup({
id: groupId,
name: groupName
});
setRenameModalOpen(true);
}
},
{
title: 'Delete group',
onClick: (event, rowIndex, { groupId, groupName }) => {
setSelectedGroup({
id: groupId,
name: groupName
});
setDeleteModalOpen(true);
}
}
]}
>
<TableHeader />
<TableBody />
Expand Down
4 changes: 1 addition & 3 deletions src/components/InventoryGroups/Modals/CreateGroupModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,5 @@ export default CreateGroupModal;
CreateGroupModal.propTypes = {
isModalOpen: PropTypes.bool,
setIsModalOpen: PropTypes.func,
reloadData: PropTypes.func,
deviceIds: PropTypes.array,
isOpen: PropTypes.bool
reloadData: PropTypes.func
};
51 changes: 38 additions & 13 deletions src/components/InventoryGroups/Modals/DeleteGroupModal.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@ import { Provider } from 'react-redux';
import { MemoryRouter } from 'react-router-dom';
import { getStore } from '../../../store';
import DeleteGroupModal from './DeleteGroupModal';
import { deleteGroupsInterceptors } from '../../../../cypress/support/interceptors';

const mountModal = (props) =>
mount(
<MemoryRouter>
<Provider store={getStore()}>
<DeleteGroupModal {...props} />
</Provider>
</MemoryRouter>
);

describe('Delete Group Modal', () => {
before(() => {
Expand All @@ -21,23 +31,38 @@ describe('Delete Group Modal', () => {
});

beforeEach(() => {
cy.intercept('DELETE', '**/api/inventory/v1/groups/1', {
statusCode: 200, body: {
deleteGroupsInterceptors['successful deletion']();
});

it('fires a network request, single group', () => {
const id = 'foo-bar-1';
const name = 'foobar group';

mountModal({
isModalOpen: true,
modalState: {
id,
name
}
}).as('delete');

mount(
<MemoryRouter>
<Provider store={getStore()}>
<DeleteGroupModal isModalOpen={true} modalState={ { id: 1, name: 'test name' } }/>
</Provider>
</MemoryRouter>
);
});

cy.get(`div[class="pf-c-check"]`).click();
cy.get(`button[type="submit"]`).click();
cy.wait('@deleteGroups').its('request.url').should('include', 'foo-bar-1');
});

it('Input is fillable and firing a delete request', () => {
it('fires a network request, more groups', () => {
const ids = ['foo-bar-1', 'foo-bar-2'];

mountModal({
isModalOpen: true,
modalState: {
ids
}
});

cy.get(`div[class="pf-c-check"]`).click();
cy.get(`button[type="submit"]`).click();
cy.wait('@delete');
cy.wait('@deleteGroups').its('request.url').should('include', 'foo-bar-1').and('include', 'foo-bar-2');
});
});
Loading

0 comments on commit aa8ed51

Please sign in to comment.