Skip to content

Commit

Permalink
feat(ESSNTL-3728): Enable multiple hosts addition to group (#1798)
Browse files Browse the repository at this point in the history
  • Loading branch information
gkarat authored Mar 30, 2023
1 parent 696661d commit c9a1d4e
Show file tree
Hide file tree
Showing 17 changed files with 641 additions and 91 deletions.
1 change: 1 addition & 0 deletions cypress/fixtures/hosts.json
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,7 @@
"reporter": "adipisicing veniam velit",
"created": "1962-06-25T23:00:00.0Z",
"account": null,
"group_name": "abc",
"mac_addresses": null,
"provider_id": "aute ut sit",
"facts": [
Expand Down
29 changes: 28 additions & 1 deletion cypress/support/interceptors.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import groupsSecondPage from '../fixtures/groupsSecondPage.json';
import groupDetailFixtures from '../fixtures/groups/620f9ae75A8F6b83d78F3B55Af1c4b2C.json';
import hostsFixtures from '../fixtures/hosts.json';

export { hostsFixtures, groupDetailFixtures };
export const groupsInterceptors = {
'successful with some items': () =>
cy
Expand Down Expand Up @@ -63,6 +64,23 @@ export const groupDetailInterceptors = {
}
)
.as('getGroupDetail'),
'successful with hosts': () =>
cy
.intercept(
'GET',
'/api/inventory/v1/groups/620f9ae75A8F6b83d78F3B55Af1c4b2C',
{
statusCode: 200,
body: {
...groupDetailFixtures,
results: [{
...groupDetailFixtures.results[0],
host_ids: ['host-1', 'host-2']
}]
}
}
)
.as('getGroupDetail'),
empty: () =>
cy
.intercept(
Expand Down Expand Up @@ -162,7 +180,16 @@ export const featureFlagsInterceptors = {
cy.intercept('GET', '/feature_flags*', {
statusCode: 200,
body: {
toggles: []
toggles: [
{
name: 'hbi.ui.inventory-groups',
enabled: true,
variant: {
name: 'disabled',
enabled: true
}
}
]
}
}).as('getFeatureFlag');
}
Expand Down
19 changes: 17 additions & 2 deletions src/components/GroupSystems/GroupSystems.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
CHIP_GROUP,
DROPDOWN_TOGGLE,
hasChip,
MODAL,
PAGINATION_VALUES,
SORTING_ORDERS,
TEXT_INPUT,
Expand Down Expand Up @@ -38,7 +39,7 @@ import _ from 'lodash';

const GROUP_NAME = 'foobar';
const ROOT = 'div[id="group-systems-table"]';
const TABLE_HEADERS = ['Name', 'Tags', 'OS', 'Update methods', 'Last seen'];
const TABLE_HEADERS = ['Name', 'Tags', 'OS', 'Update method', 'Last seen'];
const SORTABLE_HEADERS = ['Name', 'OS', 'Last seen'];
const DEFAULT_ROW_COUNT = 50;

Expand Down Expand Up @@ -300,7 +301,21 @@ describe('selection and bulk selection', () => {
});

describe('actions', () => {
// TBA
beforeEach(() => {
cy.intercept('*', { statusCode: 200 });
hostsInterceptors.successful();

mountTable();

cy.wait('@getHosts');
});

it('can open systems add modal', () => {
cy.get('button').contains('Add systems').click();
cy.get(MODAL).find('h1').contains('Add systems');

cy.wait('@getHosts');
});
});

describe('edge cases', () => {
Expand Down
144 changes: 94 additions & 50 deletions src/components/GroupSystems/GroupSystems.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,45 @@
import { Button } from '@patternfly/react-core';
import { fitContent, TableVariant } from '@patternfly/react-table';
import difference from 'lodash/difference';
import map from 'lodash/map';
import PropTypes from 'prop-types';
import React from 'react';
import React, { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { selectEntity } from '../../store/inventory-actions';
import { clearFilters, selectEntity } from '../../store/inventory-actions';
import AddSystemsToGroupModal from '../InventoryGroups/Modals/AddSystemsToGroupModal';
import InventoryTable from '../InventoryTable/InventoryTable';

export const bulkSelectConfig = (dispatch, selectedNumber, noneSelected, pageSelected, rowsNumber) => ({
count: selectedNumber,
id: 'bulk-select-groups',
items: [
{
title: 'Select none (0)',
onClick: () => dispatch(selectEntity(-1, false)),
props: { isDisabled: noneSelected }
},
{
title: `${pageSelected ? 'Deselect' : 'Select'} page (${
rowsNumber
} items)`,
onClick: () => dispatch(selectEntity(0, !pageSelected))
}
// TODO: Implement "select all"
],
onSelect: (value) => {
dispatch(selectEntity(0, value));
},
checked: selectedNumber > 0 // TODO: support partial selection (dash sign) in FEC BulkSelect
});

const prepareColumns = (initialColumns) => {
// hides the "groups" column
const columns = initialColumns.filter(({ key }) => key !== 'groups');

// additionally insert the "update methods" column
// additionally insert the "update method" column
columns.splice(columns.length - 1 /* must be penultimate */, 0, {
key: 'update_method',
title: 'Update methods',
title: 'Update method',
sortKey: 'update_method',
transforms: [fitContent],
renderFunc: (value, hostId, systemData) =>
Expand All @@ -29,7 +54,7 @@ const prepareColumns = (initialColumns) => {
return columns;
};

const GroupSystems = ({ groupName }) => {
const GroupSystems = ({ groupName, groupId }) => {
const dispatch = useDispatch();

const selected = useSelector(
Expand All @@ -42,58 +67,77 @@ const GroupSystems = ({ groupName }) => {
const pageSelected =
difference(displayedIds, [...selected.keys()]).length === 0;

const [isModalOpen, setIsModalOpen] = useState(false);

const resetTable = () => {
dispatch(clearFilters());
dispatch(selectEntity(-1, false));
};

useEffect(() => {
return () => {
resetTable();
};
}, []);

return (
<div id='group-systems-table'>
<InventoryTable
columns={prepareColumns}
getEntities={async (items, config, showTags, defaultGetEntities) =>
await defaultGetEntities(
items,
// filter systems by the group name
{
...config,
filters: {
...config.filters,
groupName: [groupName] // TODO: the param is not yet supported by `apiHostGetHostList`
}
},
showTags
)
}
tableProps={{
isStickyHeader: true,
variant: TableVariant.compact,
canSelectAll: false
}}
bulkSelect={{
count: selected.size,
id: 'bulk-select-groups',
items: [
{
title: 'Select none (0)',
onClick: () => dispatch(selectEntity(-1, false)),
props: { isDisabled: noneSelected }
},
{
title: `${pageSelected ? 'Deselect' : 'Select'} page (${
rows.length
} items)`,
onClick: () => dispatch(selectEntity(0, !pageSelected))
}
// TODO: Implement "select all"
],
onSelect: (value) => {
dispatch(selectEntity(0, value));
},
checked: selected.size > 0 // TODO: support partial selection (dash sign) in FEC BulkSelect
}}
/>
{
isModalOpen && <AddSystemsToGroupModal
isModalOpen={isModalOpen}
setIsModalOpen={(value) => {
resetTable();
setIsModalOpen(value);
}
}
groupId={groupId}
groupName={groupName}
/>
}
{
!isModalOpen &&
<InventoryTable
columns={prepareColumns}
getEntities={async (items, config, showTags, defaultGetEntities) =>
await defaultGetEntities(
items,
// filter systems by the group name
{
...config,
filters: {
...config.filters,
groupName: [groupName] // TODO: the param is not yet supported by `apiHostGetHostList`
}
},
showTags
)
}
tableProps={{
isStickyHeader: true,
variant: TableVariant.compact,
canSelectAll: false
}}
bulkSelect={bulkSelectConfig(dispatch, selected.size, noneSelected, pageSelected, rows.length)}
>
<Button
variant='primary'
onClick={() => {
resetTable();
setIsModalOpen(true);
}}

>
Add systems
</Button>
</InventoryTable>
}
</div>
);
};

GroupSystems.propTypes = {
groupName: PropTypes.string.isRequired
groupName: PropTypes.string.isRequired,
groupId: PropTypes.string.isRequired
};

export default GroupSystems;
11 changes: 6 additions & 5 deletions src/components/GroupSystems/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import { EmptyState, EmptyStateBody, Spinner } from '@patternfly/react-core';
import PropTypes from 'prop-types';
import React from 'react';
import { useSelector } from 'react-redux';
import NoGroupsEmptyState from '../InventoryGroups/NoGroupsEmptyState';
import NoSystemsEmptyState from '../InventoryGroupDetail/NoSystemsEmptyState';
import GroupSystems from './GroupSystems';

const GroupSystemsWrapper = ({ groupName }) => {
const GroupSystemsWrapper = ({ groupName, groupId }) => {
const { uninitialized, loading, data } = useSelector((state) => state.groupDetail);
const hosts = data?.results?.[0]?.host_ids /* can be null */ || [];

Expand All @@ -16,13 +16,14 @@ const GroupSystemsWrapper = ({ groupName }) => {
</EmptyStateBody>
</EmptyState>
) : hosts.length > 0 ? (
<GroupSystems groupName={groupName}/>
<GroupSystems groupId={groupId} groupName={groupName} />
) :
<NoGroupsEmptyState />;
<NoSystemsEmptyState groupId={groupId} groupName={groupName} />;
};

GroupSystemsWrapper.propTypes = {
groupName: PropTypes.string.isRequired
groupName: PropTypes.string.isRequired,
groupId: PropTypes.string.isRequired
};

export default GroupSystemsWrapper;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ const InventoryGroupDetail = ({ groupId }) => {
aria-label="Group systems tab"
>
<PageSection>
<GroupSystems groupName={groupName}/>
<GroupSystems groupName={groupName} groupId={groupId}/>
</PageSection>
</Tab>
<Tab
Expand Down
23 changes: 19 additions & 4 deletions src/components/InventoryGroupDetail/NoSystemsEmptyState.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useState } from 'react';
import {
Button,
EmptyState,
Expand All @@ -10,33 +10,48 @@ import {
import { ExternalLinkAltIcon, PlusCircleIcon } from '@patternfly/react-icons';

import { global_palette_black_600 as globalPaletteBlack600 } from '@patternfly/react-tokens/dist/js/global_palette_black_600';
import AddSystemsToGroupModal from '../InventoryGroups/Modals/AddSystemsToGroupModal';
import PropTypes from 'prop-types';

const NoSystemsEmptyState = ({ groupId, groupName }) => {
const [isModalOpen, setIsModalOpen] = useState(false);

const NoSystemsEmptyState = () => {
return (
<EmptyState
data-ouia-component-id="empty-state"
data-ouia-component-type="PF4/EmptyState"
data-ouia-safe={true}
>
<AddSystemsToGroupModal
isModalOpen={isModalOpen}
setIsModalOpen={setIsModalOpen}
groupId={groupId}
groupName={groupName}
/>
<EmptyStateIcon icon={PlusCircleIcon} color={globalPaletteBlack600.value} />
<Title headingLevel="h4" size="lg">
No systems added
</Title>
<EmptyStateBody>
To manage systems more effectively, add systems to the group.
</EmptyStateBody>
<Button variant="primary" onClick={() => {}}>Add systems</Button>
<Button variant="primary" onClick={() => setIsModalOpen(true)}>
Add systems
</Button>
<EmptyStateSecondaryActions>
<Button
variant="link"
icon={<ExternalLinkAltIcon />}
iconPosition="right"
// TODO: component={(props) => <a href='' {...props} />}
>
Learn more about system groups
</Button>
</EmptyStateSecondaryActions>
</EmptyState>
);};

NoSystemsEmptyState.propTypes = {
groupId: PropTypes.string,
groupName: PropTypes.string
};
export default NoSystemsEmptyState;
Loading

0 comments on commit c9a1d4e

Please sign in to comment.