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-3737, -3735): Rename and delete group #1780

Merged
10 changes: 10 additions & 0 deletions cypress/support/interceptors.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}
};

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

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

const checkSorting = (label, order, dataField) => {
Expand Down Expand Up @@ -181,6 +183,8 @@ describe('filtering', () => {
beforeEach(() => {
interceptors['successful with some items']();
mountTable();

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

const applyNameFilter = () =>
Expand All @@ -190,27 +194,25 @@ describe('filtering', () => {
it('renders filter chip', () => {
applyNameFilter();
hasChip('Name', 'lorem');
cy.wait('@getGroups');
});

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');

});
Expand Down
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>
);
};
Expand Down
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';

Expand Down Expand Up @@ -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
Expand Up @@ -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 {
Expand All @@ -17,12 +18,14 @@ jest.mock('react-redux', () => {
}
]
}
})
}),
useDispatch: () => {}
};
});

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

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

it('renders title and breadcrumbs', () => {
Expand All @@ -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();
});
});