Skip to content

Commit

Permalink
feat(ESSNTL-3737, -3735): Rename and delete group (#1780)
Browse files Browse the repository at this point in the history
Implements https://issues.redhat.com/browse/ESSNTL-3737.
Implements https://issues.redhat.com/browse/ESSNTL-3735.

This makes it possible to rename or delete a group from the group
details page.
gkarat authored Mar 9, 2023

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
1 parent 5c7c604 commit bdf4c6a
Showing 5 changed files with 150 additions and 24 deletions.
10 changes: 10 additions & 0 deletions cypress/support/interceptors.js
Original file line number Diff line number Diff line change
@@ -60,6 +60,16 @@ export const groupDetailInterceptors = {
delay: 42000000 // milliseconds
});
}).as('getGroupDetail');
},
'patch successful': () => {
cy
.intercept('PATCH', '/api/inventory/v1/groups/*', { statusCode: 200 })
.as('patchGroup');
},
'delete successful': () => {
cy
.intercept('DELETE', '/api/inventory/v1/groups/*', { statusCode: 204 })
.as('deleteGroup');
}
};

15 changes: 6 additions & 9 deletions src/components/GroupsTable/GroupsTable.cy.js
Original file line number Diff line number Diff line change
@@ -153,7 +153,7 @@ describe('sorting', () => {
interceptors['successful with some items']();
mountTable();

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

const checkSorting = (label, order, dataField) => {
@@ -187,7 +187,7 @@ describe('filtering', () => {
interceptors['successful with some items']();
mountTable();

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

const applyNameFilter = () =>
@@ -201,24 +201,21 @@ describe('filtering', () => {
});

it('sends correct request', () => {
applyNameFilter().then(() => {
cy.wait('@getGroups')
.its('request.url')
.should('include', 'hostname_or_id=lorem');
});
applyNameFilter();
cy.wait('@getGroups').its('request.url').should('include', 'hostname_or_id=lorem');
});

it('can remove the chip or reset filters', () => {
applyNameFilter();
cy.wait('@getGroups');
cy.wait('@getGroups').its('request.url').should('contain', 'hostname_or_id=lorem');
cy.get(CHIP_GROUP)
.find(CHIP)
.ouiaId('close', 'button')
.each(() => {
cy.get(CHIP_GROUP).find(CHIP).ouiaId('close', 'button');
});
cy.get('button').contains('Reset filters').click();
cy.wait('@getGroups');
cy.wait('@getGroups').its('request.url').should('not.contain', 'hostname_or_id');
cy.get(CHIP_GROUP).should('not.exist');

});
101 changes: 88 additions & 13 deletions src/components/InventoryGroupDetail/GroupDetailHeader.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,108 @@
import { Breadcrumb, BreadcrumbItem, Skeleton } from '@patternfly/react-core';
import {
Breadcrumb,
BreadcrumbItem,
Dropdown,
DropdownItem,
DropdownToggle,
Flex,
FlexItem,
Skeleton
} from '@patternfly/react-core';
import {
PageHeader,
PageHeaderTitle
} from '@redhat-cloud-services/frontend-components';
import React from 'react';
import { useSelector } from 'react-redux';
import { Link } from 'react-router-dom';
import React, { useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Link, useHistory } from 'react-router-dom';
import { routes } from '../../Routes';
import PropTypes from 'prop-types';
import DeleteGroupModal from '../InventoryGroups/Modals/DeleteGroupModal';
import RenameGroupModal from '../InventoryGroups/Modals/RenameGroupModal';
import { fetchGroupDetail } from '../../store/inventory-actions';

const GroupDetailHeader = ({ groupId }) => {
const { uninitialized, loading, data } = useSelector((state) => state.groupDetail);

const nameOrId = uninitialized || loading ? (
<Skeleton width="250px" screenreaderText="Loading group details" />
) : (
// in case of error, render just id from URL
data?.results?.[0]?.name || groupId
const dispatch = useDispatch();
const { uninitialized, loading, data } = useSelector(
(state) => state.groupDetail
);

const [dropdownOpen, setDropdownOpen] = useState(false);
const [renameModalOpen, setRenameModalOpen] = useState(false);
const [deleteModalOpen, setDeleteModalOpen] = useState(false);

const name = data?.results?.[0]?.name;
const title =
uninitialized || loading ? (
<Skeleton width="250px" screenreaderText="Loading group details" />
) : (
name || groupId // in case of error, render just id from URL
);

const history = useHistory();

return (
<PageHeader>
<RenameGroupModal
isModalOpen={renameModalOpen}
setIsModalOpen={() => setRenameModalOpen(false)}
modalState={{
id: groupId,
name: name || groupId
}}
reloadData={() => dispatch(fetchGroupDetail(groupId))}
/>
<DeleteGroupModal
isModalOpen={deleteModalOpen}
setIsModalOpen={() => setDeleteModalOpen(false)}
modalState={{
id: groupId,
name: name || groupId
}}
reloadData={() => history.push('/groups')}
/>
<Breadcrumb>
<BreadcrumbItem>
<Link to={routes.groups}>Groups</Link>
</BreadcrumbItem>
<BreadcrumbItem isActive>{nameOrId}</BreadcrumbItem>
<BreadcrumbItem isActive>{title}</BreadcrumbItem>
</Breadcrumb>
<PageHeaderTitle title={nameOrId} />
<Flex id="group-header" justifyContent={{ default: 'justifyContentSpaceBetween' }}>
<FlexItem>
<PageHeaderTitle title={title} />
</FlexItem>
<FlexItem id="group-header-dropdown">
<Dropdown
onSelect={() => setDropdownOpen(!dropdownOpen)}
autoFocus={false}
isOpen={dropdownOpen}
toggle={
<DropdownToggle
id="group-dropdown-toggle"
onToggle={(isOpen) => setDropdownOpen(isOpen)}
toggleVariant="secondary"
isDisabled={uninitialized || loading}
>
Group actions
</DropdownToggle>
}
dropdownItems={[
<DropdownItem
key="rename-group"
onClick={() => setRenameModalOpen(true)}
>
Rename
</DropdownItem>,
<DropdownItem
key="delete-group"
onClick={() => setDeleteModalOpen(true)}
>
Delete
</DropdownItem>
]}
/>
</FlexItem>
</Flex>
</PageHeader>
);
};
37 changes: 36 additions & 1 deletion src/components/InventoryGroupDetail/InventoryGroupDetail.cy.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { mount } from '@cypress/react';
import { DROPDOWN, DROPDOWN_ITEM, MODAL } from '@redhat-cloud-services/frontend-components-utilities';
import React from 'react';
import { Provider } from 'react-redux';
import { MemoryRouter } from 'react-router-dom';
import groupDetailFixtures from '../../../cypress/fixtures/groups/620f9ae75A8F6b83d78F3B55Af1c4b2C.json';
import { groupDetailInterceptors as interceptors } from '../../../cypress/support/interceptors';
import { groupDetailInterceptors as interceptors, groupsInterceptors } from '../../../cypress/support/interceptors';
import { getStore } from '../../store';
import InventoryGroupDetail from './InventoryGroupDetail';

@@ -59,4 +60,38 @@ describe('group detail page', () => {
cy.get('h1').find('.pf-c-skeleton');
cy.get('.pf-c-empty-state').find('.pf-c-spinner');
});

it('can open rename group modal', () => {
interceptors.successful();
interceptors['patch successful']();
groupsInterceptors['successful with some items'](); // intercept modal validation requests
mountPage();

cy.wait('@getGroupDetail');

cy.get(DROPDOWN).click();
cy.get(DROPDOWN_ITEM).contains('Rename').click();

cy.get(MODAL).find('input').type('1');
cy.get(MODAL).find('button[type=submit]').click();

cy.wait('@patchGroup').its('request.body')
.should('deep.equal', { name: `${groupDetailFixtures.results[0].name}1` });
cy.wait('@getGroupDetail'); // the page is refreshed after submition
});

it('can open delete group modal', () => {
interceptors.successful();
interceptors['delete successful']();
mountPage();
cy.wait('@getGroupDetail');

cy.get(DROPDOWN).click();
cy.get(DROPDOWN_ITEM).contains('Delete').click();

cy.get(`div[class="pf-c-check"]`).click();
cy.get(`button[type="submit"]`).click();
cy.wait('@deleteGroup').its('request.url')
.should('contain', groupDetailFixtures.results[0].id);
});
});
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@ import { render } from '@testing-library/react';
import React from 'react';
import { MemoryRouter } from 'react-router-dom';
import GroupDetailHeader from '../GroupDetailHeader';
import { DROPDOWN } from '@redhat-cloud-services/frontend-components-utilities/CypressUtils/selectors';

jest.mock('react-redux', () => {
return {
@@ -17,12 +18,14 @@ jest.mock('react-redux', () => {
}
]
}
})
}),
useDispatch: () => {}
};
});

describe('group detail header', () => {
let getByRole;
let container;

beforeEach(() => {
const rendered = render(
@@ -31,6 +34,7 @@ describe('group detail header', () => {
</MemoryRouter>
);
getByRole = rendered.getByRole;
container = rendered.container;
});

it('renders title and breadcrumbs', () => {
@@ -41,4 +45,9 @@ describe('group detail header', () => {
expect(getByRole('navigation')).toHaveClass('pf-c-breadcrumb');
expect(getByRole('navigation')).toHaveTextContent('group-name-1');
});

it('renders the actions dropdown', () => {
expect(container.querySelector('#group-header-dropdown')).toHaveTextContent('Group actions');
expect(container.querySelector(DROPDOWN)).toBeVisible();
});
});

0 comments on commit bdf4c6a

Please sign in to comment.