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(ESSNTL-4365): Enable actions in the groups table #1779

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
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