diff --git a/cypress/fixtures/hosts.json b/cypress/fixtures/hosts.json
index 3b156ab11..cd4c5c50b 100644
--- a/cypress/fixtures/hosts.json
+++ b/cypress/fixtures/hosts.json
@@ -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": [
diff --git a/cypress/support/interceptors.js b/cypress/support/interceptors.js
index 4c42f9e3b..c4308f442 100644
--- a/cypress/support/interceptors.js
+++ b/cypress/support/interceptors.js
@@ -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
@@ -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(
@@ -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');
}
diff --git a/src/components/GroupSystems/GroupSystems.cy.js b/src/components/GroupSystems/GroupSystems.cy.js
index 07c072f57..703d79f41 100644
--- a/src/components/GroupSystems/GroupSystems.cy.js
+++ b/src/components/GroupSystems/GroupSystems.cy.js
@@ -9,6 +9,7 @@ import {
CHIP_GROUP,
DROPDOWN_TOGGLE,
hasChip,
+ MODAL,
PAGINATION_VALUES,
SORTING_ORDERS,
TEXT_INPUT,
@@ -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;
@@ -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', () => {
diff --git a/src/components/GroupSystems/GroupSystems.js b/src/components/GroupSystems/GroupSystems.js
index d02d42118..942aece8c 100644
--- a/src/components/GroupSystems/GroupSystems.js
+++ b/src/components/GroupSystems/GroupSystems.js
@@ -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) =>
@@ -29,7 +54,7 @@ const prepareColumns = (initialColumns) => {
return columns;
};
-const GroupSystems = ({ groupName }) => {
+const GroupSystems = ({ groupName, groupId }) => {
const dispatch = useDispatch();
const selected = useSelector(
@@ -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 (
-
- 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 && {
+ resetTable();
+ setIsModalOpen(value);
+ }
+ }
+ groupId={groupId}
+ groupName={groupName}
+ />
+ }
+ {
+ !isModalOpen &&
+
+ 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)}
+ >
+
+
+ }
);
};
GroupSystems.propTypes = {
- groupName: PropTypes.string.isRequired
+ groupName: PropTypes.string.isRequired,
+ groupId: PropTypes.string.isRequired
};
export default GroupSystems;
diff --git a/src/components/GroupSystems/index.js b/src/components/GroupSystems/index.js
index e2c71c39b..9c7177334 100644
--- a/src/components/GroupSystems/index.js
+++ b/src/components/GroupSystems/index.js
@@ -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 */ || [];
@@ -16,13 +16,14 @@ const GroupSystemsWrapper = ({ groupName }) => {
) : hosts.length > 0 ? (
-
+
) :
- ;
+ ;
};
GroupSystemsWrapper.propTypes = {
- groupName: PropTypes.string.isRequired
+ groupName: PropTypes.string.isRequired,
+ groupId: PropTypes.string.isRequired
};
export default GroupSystemsWrapper;
diff --git a/src/components/InventoryGroupDetail/InventoryGroupDetail.js b/src/components/InventoryGroupDetail/InventoryGroupDetail.js
index ae8126d92..bc414da7b 100644
--- a/src/components/InventoryGroupDetail/InventoryGroupDetail.js
+++ b/src/components/InventoryGroupDetail/InventoryGroupDetail.js
@@ -53,7 +53,7 @@ const InventoryGroupDetail = ({ groupId }) => {
aria-label="Group systems tab"
>
-
+
{
+ const [isModalOpen, setIsModalOpen] = useState(false);
-const NoSystemsEmptyState = () => {
return (
+
No systems added
@@ -25,13 +35,14 @@ const NoSystemsEmptyState = () => {
To manage systems more effectively, add systems to the group.
-
+
}
iconPosition="right"
- // TODO: component={(props) => }
>
Learn more about system groups
@@ -39,4 +50,8 @@ const NoSystemsEmptyState = () => {
);};
+NoSystemsEmptyState.propTypes = {
+ groupId: PropTypes.string,
+ groupName: PropTypes.string
+};
export default NoSystemsEmptyState;
diff --git a/src/components/InventoryGroupDetail/__tests__/InventoryGroupDetail.test.js b/src/components/InventoryGroupDetail/__tests__/InventoryGroupDetail.test.js
index 44f42fa57..2b59832e3 100644
--- a/src/components/InventoryGroupDetail/__tests__/InventoryGroupDetail.test.js
+++ b/src/components/InventoryGroupDetail/__tests__/InventoryGroupDetail.test.js
@@ -2,25 +2,11 @@ import '@testing-library/jest-dom';
import { render } from '@testing-library/react';
import React from 'react';
import { MemoryRouter } from 'react-router-dom';
+import { getStore } from '../../../store';
import InventoryGroupDetail from '../InventoryGroupDetail';
+import { Provider } from 'react-redux';
-jest.mock('react-redux', () => {
- return {
- ...jest.requireActual('react-redux'),
- useSelector: () => ({
- uninitialized: false,
- loading: false,
- data: {
- results: [
- {
- name: 'group-name-1'
- }
- ]
- }
- }),
- useDispatch: () => () => {}
- };
-});
+jest.mock('../../../Utilities/useFeatureFlag');
describe('group detail page component', () => {
let getByRole;
@@ -29,7 +15,9 @@ describe('group detail page component', () => {
beforeEach(() => {
const rendered = render(
-
+
+
+
);
getByRole = rendered.getByRole;
diff --git a/src/components/InventoryGroups/Modals/AddSystemsToGroupModal.cy.js b/src/components/InventoryGroups/Modals/AddSystemsToGroupModal.cy.js
new file mode 100644
index 000000000..bc7220c5f
--- /dev/null
+++ b/src/components/InventoryGroups/Modals/AddSystemsToGroupModal.cy.js
@@ -0,0 +1,159 @@
+import { mount } from '@cypress/react';
+import {
+ checkTableHeaders,
+ MODAL,
+ ouiaId,
+ TABLE
+} from '@redhat-cloud-services/frontend-components-utilities';
+import FlagProvider from '@unleash/proxy-client-react';
+import _ from 'lodash';
+import React from 'react';
+import { Provider } from 'react-redux';
+import { MemoryRouter } from 'react-router-dom';
+import {
+ featureFlagsInterceptors,
+ groupDetailInterceptors,
+ hostsFixtures,
+ hostsInterceptors
+} from '../../../../cypress/support/interceptors';
+import {
+ selectRowN,
+ unleashDummyConfig
+} from '../../../../cypress/support/utils';
+import { getStore } from '../../../store';
+import AddSystemsToGroupModal from './AddSystemsToGroupModal';
+
+const TABLE_HEADERS = [
+ 'Name',
+ 'OS',
+ 'Tags',
+ 'Update method',
+ 'Group',
+ 'Last seen'
+];
+
+const ALERT = '[data-ouia-component-type="PF4/Alert"]';
+
+before(() => {
+ cy.window().then(
+ (window) =>
+ (window.insights = {
+ chrome: {
+ isProd: false,
+ auth: {
+ getUser: () => {
+ return Promise.resolve({});
+ }
+ }
+ }
+ })
+ );
+});
+
+const mountModal = () =>
+ mount(
+
+
+
+ {}} // TODO: test that the func is called on close
+ />
+
+
+
+ );
+
+describe('test data', () => {
+ it('at least one system is already in a group', () => {
+ const alreadyInGroup = hostsFixtures.results.filter(
+ // eslint-disable-next-line camelcase
+ ({ group_name }) => !_.isEmpty(group_name)
+ );
+ expect(alreadyInGroup.length).to.be.gte(1);
+ });
+
+ it('the first system in group has specific id', () => {
+ const alreadyInGroup = hostsFixtures.results.filter(
+ // eslint-disable-next-line camelcase
+ ({ group_name }) => !_.isEmpty(group_name)
+ );
+ expect(alreadyInGroup[0].id).to.eq('anim commodo');
+ });
+});
+
+describe('AddSystemsToGroupModal', () => {
+ beforeEach(() => {
+ cy.viewport(1920, 1080); // to accomadate the inventory table
+ cy.intercept('*', { statusCode: 200 });
+ hostsInterceptors.successful(); // default hosts list
+ featureFlagsInterceptors.successful(); // to enable the Group column
+ });
+
+ it('renders correct header and buttons', () => {
+ mountModal();
+
+ cy.wait('@getHosts');
+ cy.get('h1').contains('Add systems');
+ cy.get('button').contains('Add systems');
+ cy.get('button').contains('Cancel');
+ });
+
+ it('renders the inventory table', () => {
+ mountModal();
+
+ cy.wait('@getHosts');
+ cy.get(ouiaId('PrimaryToolbar'));
+ cy.get(TABLE);
+ cy.get('#options-menu-bottom-pagination');
+ checkTableHeaders(TABLE_HEADERS);
+ });
+
+ it('can add systems that are not yet in group', () => {
+ groupDetailInterceptors['patch successful']();
+ groupDetailInterceptors['successful with hosts']();
+ mountModal();
+
+ cy.wait('@getHosts');
+ cy.get('button').contains('Add systems').should('be.disabled');
+ selectRowN(1);
+ cy.get('button').contains('Add systems').click();
+ cy.wait('@getGroupDetail'); // requests the current hosts list
+ cy.wait('@patchGroup')
+ .its('request.body')
+ .should('deep.equal', {
+ // eslint-disable-next-line camelcase
+ host_ids: ['host-1', 'host-2', 'dolor'] // sends the merged list of hosts
+ });
+ });
+
+ it('can add systems that are already in group', () => {
+ groupDetailInterceptors['patch successful']();
+ groupDetailInterceptors['successful with hosts']();
+ mountModal();
+
+ cy.wait('@getHosts');
+ const i =
+ hostsFixtures.results.findIndex(
+ // eslint-disable-next-line camelcase
+ ({ group_name }) => !_.isEmpty(group_name)
+ ) + 1;
+ selectRowN(i);
+ cy.get(ALERT); // check the alert is shown
+ cy.get('button').contains('Add systems').click();
+ cy.get(MODAL).find('h1').contains('Add all selected systems to group?');
+ cy.get('button')
+ .contains('Yes, add all systems to group')
+ .should('be.disabled');
+ cy.get('input[name="confirmation"]').check();
+ cy.get('button').contains('Yes, add all systems to group').click();
+ cy.wait('@getGroupDetail');
+ cy.wait('@patchGroup')
+ .its('request.body')
+ .should('deep.equal', {
+ // eslint-disable-next-line camelcase
+ host_ids: ['host-1', 'host-2', 'anim commodo'] // sends the merged list of hosts
+ });
+ });
+});
diff --git a/src/components/InventoryGroups/Modals/AddSystemsToGroupModal.js b/src/components/InventoryGroups/Modals/AddSystemsToGroupModal.js
new file mode 100644
index 000000000..828f8328e
--- /dev/null
+++ b/src/components/InventoryGroups/Modals/AddSystemsToGroupModal.js
@@ -0,0 +1,197 @@
+import {
+ Alert,
+ Button,
+ Flex,
+ FlexItem,
+ Modal
+} 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, { useCallback, useState } from 'react';
+import { useDispatch, useSelector } from 'react-redux';
+import { fetchGroupDetail } from '../../../store/inventory-actions';
+import { bulkSelectConfig } from '../../GroupSystems/GroupSystems';
+import InventoryTable from '../../InventoryTable/InventoryTable';
+import { addHostsToGroupById } from '../utils/api';
+import apiWithToast from '../utils/apiWithToast';
+import ConfirmSystemsAddModal from './ConfirmSystemsAddModal';
+
+export const prepareColumns = (initialColumns) => {
+ const columns = initialColumns;
+
+ // additionally insert the "update method" column
+ columns.splice(columns.length - 2 /* must be the 3rd col from the end */, 0, {
+ key: 'update_method',
+ title: 'Update method',
+ sortKey: 'update_method',
+ transforms: [fitContent],
+ renderFunc: (value, hostId, systemData) =>
+ systemData?.system_profile?.system_update_method || 'N/A',
+ props: {
+ // TODO: remove isStatic when the sorting is supported by API
+ isStatic: true,
+ width: 10
+ }
+ });
+
+ // map columns to the speicifc order
+ return [
+ 'display_name',
+ 'system_profile',
+ 'tags',
+ 'update_method',
+ 'groups',
+ 'updated'
+ ].map((colKey) => columns.find(({ key }) => key === colKey));
+};
+
+const AddSystemsToGroupModal = ({
+ isModalOpen,
+ setIsModalOpen,
+ groupId,
+ groupName
+}) => {
+ const dispatch = useDispatch();
+
+ const [confirmationModalOpen, setConfirmationModalOpen] = useState(false);
+ const [systemsSelectModalOpen, setSystemSelectModalOpen] = useState(true);
+ const selected = useSelector(
+ (state) => state?.entities?.selected || new Map()
+ );
+ const rows = useSelector(({ entities }) => entities?.rows || []);
+
+ const noneSelected = selected.size === 0;
+ const displayedIds = map(rows, 'id');
+ const pageSelected = difference(displayedIds, [...selected.keys()]).length === 0;
+
+ const alreadyHasGroup = [...selected].filter(
+ // eslint-disable-next-line camelcase
+ (entry) => {
+ return entry[1].group_name !== undefined && entry[1].group_name !== '';
+ }
+ );
+ const showWarning = alreadyHasGroup.length > 0;
+
+ const handleSystemAddition = useCallback(
+ (hostIds) => {
+ const statusMessages = {
+ onSuccess: {
+ title: 'Success',
+ description: `${hostIds.length > 1 ? 'Systems' : 'System'} added to ${groupName || groupId}`
+ },
+ onError: {
+ title: 'Error',
+ description: `Failed to add ${hostIds.length > 1 ? 'systems' : 'system'} to ${groupName || groupId}`
+ }
+ };
+ return apiWithToast(
+ dispatch,
+ () => addHostsToGroupById(groupId, hostIds),
+ statusMessages
+ );
+ },
+ [isModalOpen]
+ );
+
+ return (
+ isModalOpen && (
+ <>
+ {/** confirmation modal */}
+ {
+ await handleSystemAddition([...selected.keys()]);
+ setTimeout(() => dispatch(fetchGroupDetail(groupId)), 500); // refetch data for this group
+ setIsModalOpen(false);
+
+ }}
+ onBack={() => {
+ setConfirmationModalOpen(false);
+ setSystemSelectModalOpen(true); // switch back to the systems table modal
+ }}
+ onCancel={() => setIsModalOpen(false)}
+ hostsNumber={alreadyHasGroup.length}
+ />
+ {/** hosts selection modal */}
+ setIsModalOpen(false)}
+ footer={
+
+ {showWarning && (
+
+
+
+ )}
+
+
+
+
+
+ }
+ variant="large" // required to accomodate the systems table
+ >
+
+
+ >
+ )
+ );
+};
+
+AddSystemsToGroupModal.propTypes = {
+ isModalOpen: PropTypes.bool,
+ setIsModalOpen: PropTypes.func,
+ reloadData: PropTypes.func,
+ groupId: PropTypes.string,
+ groupName: PropTypes.string
+};
+
+export default AddSystemsToGroupModal;
diff --git a/src/components/InventoryGroups/Modals/ConfirmSystemsAddModal.js b/src/components/InventoryGroups/Modals/ConfirmSystemsAddModal.js
new file mode 100644
index 000000000..48682b155
--- /dev/null
+++ b/src/components/InventoryGroups/Modals/ConfirmSystemsAddModal.js
@@ -0,0 +1,74 @@
+import { FormSpy, useFormApi } from '@data-driven-forms/react-form-renderer';
+import { Button, Flex } from '@patternfly/react-core';
+import ExclamationTriangleIcon from '@patternfly/react-icons/dist/js/icons/exclamation-triangle-icon';
+import warningColor from '@patternfly/react-tokens/dist/esm/global_warning_color_100';
+import PropTypes from 'prop-types';
+import React from 'react';
+import Modal from './Modal';
+import { confirmSystemsAddSchema } from './ModalSchemas/schemes';
+
+const ConfirmSystemsAddModal = ({
+ isModalOpen,
+ onSubmit,
+ onBack,
+ onCancel,
+ hostsNumber
+}) => (
+ (
+
+ )}
+ closeModal={onCancel}
+ schema={confirmSystemsAddSchema(hostsNumber)}
+ reloadData={() => {}}
+ onSubmit={onSubmit}
+ customFormTemplate={({ formFields, schema }) => {
+ const { handleSubmit, getState } = useFormApi();
+ const { submitting, valid } = getState();
+
+ return (
+
+ );
+ }}
+ />
+);
+
+ConfirmSystemsAddModal.propTypes = {
+ isModalOpen: PropTypes.bool,
+ onSubmit: PropTypes.func,
+ onBack: PropTypes.func,
+ onCancel: PropTypes.func,
+ hostsNumber: PropTypes.number
+};
+
+export default ConfirmSystemsAddModal;
diff --git a/src/components/InventoryGroups/Modals/CreateGroupModal.js b/src/components/InventoryGroups/Modals/CreateGroupModal.js
index 0a9d2ea88..48dd78803 100644
--- a/src/components/InventoryGroups/Modals/CreateGroupModal.js
+++ b/src/components/InventoryGroups/Modals/CreateGroupModal.js
@@ -48,7 +48,6 @@ const CreateGroupModal = ({
return (
setIsModalOpen(false)}
title="Create group"
diff --git a/src/components/InventoryGroups/Modals/Modal.js b/src/components/InventoryGroups/Modals/Modal.js
index cf3b7ada9..a562a8860 100644
--- a/src/components/InventoryGroups/Modals/Modal.js
+++ b/src/components/InventoryGroups/Modals/Modal.js
@@ -17,6 +17,7 @@ const RepoModal = ({
reloadData,
size,
onSubmit,
+ customFormTemplate,
additionalMappers
}) => {
return (
@@ -30,7 +31,7 @@ const RepoModal = ({
>
(
+ FormTemplate={customFormTemplate ? customFormTemplate : (props) => (
({
]
});
+export const confirmSystemsAddSchema = (hostsNumber) => ({
+ fields: [
+ {
+ component: componentTypes.PLAIN_TEXT,
+ name: 'warning-message',
+ label: `${hostsNumber} of the systems you selected already belong to a group.
+ Moving them to a different group will impact their configuration.`
+ },
+ {
+ component: componentTypes.CHECKBOX,
+ name: 'confirmation',
+ label: 'I acknowledge that this action cannot be undone.',
+ validate: [{ type: validatorTypes.REQUIRED }]
+ }
+ ]
+});
+
const createDescription = (systemName) => {
return (
diff --git a/src/components/InventoryGroups/Modals/RenameGroupModal.js b/src/components/InventoryGroups/Modals/RenameGroupModal.js
index 2fbcf9ba4..8c1767a70 100644
--- a/src/components/InventoryGroups/Modals/RenameGroupModal.js
+++ b/src/components/InventoryGroups/Modals/RenameGroupModal.js
@@ -45,7 +45,7 @@ const RenameGroupModal = ({
},
onError: { title: 'Error', description: 'Failed to rename group' }
};
- apiWithToast(dispatch, () => updateGroupById(id, values), statusMessages);
+ apiWithToast(dispatch, () => updateGroupById(id, { name: values.name }), statusMessages);
};
const schema = useMemo(() => {
diff --git a/src/components/InventoryGroups/utils/api.js b/src/components/InventoryGroups/utils/api.js
index 7352c007b..2ae0ac650 100644
--- a/src/components/InventoryGroups/utils/api.js
+++ b/src/components/InventoryGroups/utils/api.js
@@ -3,6 +3,7 @@ import { instance } from '@redhat-cloud-services/frontend-components-utilities/i
import { INVENTORY_API_BASE } from '../../../api';
import { TABLE_DEFAULT_PAGINATION } from '../../../constants';
import PropTypes from 'prop-types';
+import union from 'lodash/union';
import fixtureGroups from '../../../../cypress/fixtures/groups.json';
import fixtureGroupsDetails from '../../../../cypress/fixtures/groups/Ba8B79ab5adC8E41e255D5f8aDb8f1F3.json';
@@ -49,14 +50,21 @@ export const getGroupDetail = (groupId) => {
};
export const updateGroupById = (id, payload) => {
- return instance.patch(`${INVENTORY_API_BASE}/groups/${id}`, {
- name: payload.name
- });
+ return instance.patch(`${INVENTORY_API_BASE}/groups/${id}`, payload);
};
export const deleteGroupsById = (ids = []) => {
return instance.delete(`${INVENTORY_API_BASE}/groups/${ids.join(',')}`);
+};
+export const addHostsToGroupById = (id, hostIds) => {
+ // the current hosts must be fetched before merging with the new ones
+ return getGroupDetail(id).then((response) =>
+ updateGroupById(id, {
+ // eslint-disable-next-line camelcase
+ host_ids: union(response.results[0].host_ids, hostIds)
+ })
+ );
};
export const addHostToGroup = (groupId, newHostId) => {
diff --git a/src/store/entities.js b/src/store/entities.js
index d99bea7ce..e319e9e8c 100644
--- a/src/store/entities.js
+++ b/src/store/entities.js
@@ -22,6 +22,7 @@ import OperatingSystemFormatter from '../Utilities/OperatingSystemFormatter';
import { Tooltip } from '@patternfly/react-core';
import { verifyCulledInsightsClient } from '../Utilities/sharedFunctions';
import { fitContent } from '@patternfly/react-table';
+import isEmpty from 'lodash/isEmpty';
export const defaultState = {
loaded: false,
@@ -47,9 +48,11 @@ export const defaultColumns = (groupsEnabled = false) => ([
...(groupsEnabled ? [{
key: 'groups',
sortKey: 'groups',
- title: 'Groups',
+ title: 'Group',
props: { width: 10 },
- renderFunc: () => N/A
+ // eslint-disable-next-line camelcase
+ renderFunc: (value, systemId, { group_name }) => isEmpty(group_name) ? 'N/A' : group_name,
+ transforms: [fitContent]
}] : []),
{
key: 'tags',
@@ -172,7 +175,7 @@ function selectEntity(state, { payload }) {
function versionsLoaded(state, { payload: { results } }) {
return {
...state,
- operatingSystems: results.map(entry => {
+ operatingSystems: (results || []).map(entry => {
const { name, major, minor } = entry.value;
const versionStringified = `${major}.${minor}`;
return { label: `${name} ${versionStringified}`, value: versionStringified };