Skip to content

Commit

Permalink
feet(THEEDGE-3585): Allow immutable systems selection in group detail…
Browse files Browse the repository at this point in the history
…s view empty state.

In the context of edgeParity, when creating a new group and selecting that group, we enter the group details view,
because it has no systems added yet an empty state is displayed and Add systems button is displayed also to systems,
this PR allow to add conventional and immutable systems in that view.
- This should not affect the main add systems button when systems exists in group that's why edgeParityIsAllowed key is added.
- Two feature flags has be taken into considerations 'edgeParity.inventory-list' and 'edgeParity.inventory-groups-enabled'.
- If there is no immutable systems the immutable tab is not shown.
- Label is added in the header when edgeParity is enabled to show the overall number of the selected systems.
- The constraint is also taken into account also, when the immutable system has a group to disable adding system and show the warning message.

FIXES: https://issues.redhat.com/browse/THEEDGE-3585
  • Loading branch information
ldjebran committed Oct 12, 2023
1 parent 71340df commit 013ae05
Show file tree
Hide file tree
Showing 3 changed files with 163 additions and 18 deletions.
1 change: 1 addition & 0 deletions src/components/InventoryGroupDetail/NoSystemsEmptyState.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const NoSystemsEmptyState = ({ groupId, groupName }) => {
setIsModalOpen={setIsModalOpen}
groupId={groupId}
groupName={groupName}
edgeParityIsAllowed={true}
/>
<EmptyStateIcon
icon={PlusCircleIcon}
Expand Down
150 changes: 132 additions & 18 deletions src/components/InventoryGroups/Modals/AddSystemsToGroupModal.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
import { Alert, Button, Flex, FlexItem, Modal } from '@patternfly/react-core';
import {
Alert,
Button,
Flex,
FlexItem,
Label,
Modal,
Tab,
TabTitleText,
Tabs,
} from '@patternfly/react-core';
import { TableVariant } from '@patternfly/react-table';
import PropTypes from 'prop-types';
import React, { useCallback, useState } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import {
clearFilters,
Expand All @@ -16,12 +26,22 @@ import ConfirmSystemsAddModal from './ConfirmSystemsAddModal';
import { useBulkSelectConfig } from '../../../Utilities/hooks/useBulkSelectConfig';
import difference from 'lodash/difference';
import map from 'lodash/map';
import ImmutableDevicesView from '../../InventoryTabs/ImmutableDevices/EdgeDevicesView';
import useFeatureFlag from '../../../Utilities/useFeatureFlag';
import { PageHeaderTitle } from '@redhat-cloud-services/frontend-components/PageHeader';
import { InfoCircleIcon } from '@patternfly/react-icons';
import axios from 'axios';
import {
INVENTORY_TOTAL_FETCH_EDGE_PARAMS,
INVENTORY_TOTAL_FETCH_URL_SERVER,
} from '../../../Utilities/constants';

const AddSystemsToGroupModal = ({
isModalOpen,
setIsModalOpen,
groupId,
groupName,
edgeParityIsAllowed,
}) => {
const dispatch = useDispatch();

Expand All @@ -32,7 +52,6 @@ const AddSystemsToGroupModal = ({
);
const rows = useSelector(({ entities }) => entities?.rows || []);

const noneSelected = selected.size === 0;
const total = useSelector(({ entities }) => entities?.total);
const displayedIds = map(rows, 'id');
const pageSelected =
Expand All @@ -55,7 +74,7 @@ const AddSystemsToGroupModal = ({
);
}
);
const showWarning = alreadyHasGroup.length > 0;
let showWarning = alreadyHasGroup.length > 0;

const handleSystemAddition = useCallback(
(hostIds) => {
Expand Down Expand Up @@ -92,14 +111,74 @@ const AddSystemsToGroupModal = ({
dispatch(clearFilters());
};

const edgeParityEnabled =
edgeParityIsAllowed &&
useFeatureFlag('edgeParity.inventory-list') &&
useFeatureFlag('edgeParity.inventory-groups-enabled');

const [selectedImmutableDevices, setSelectedImmutableDevices] = useState([]);
const selectedImmutableKeys = selectedImmutableDevices.map(
(immutableDevice) => immutableDevice.id
);

// overallSelectedKeys is the list of the conventional and immutable systems ids
const overallSelectedKeys = [...selected.keys(), ...selectedImmutableKeys];
// noneSelected a boolean showing that no system is selected
const noneSelected = overallSelectedKeys.length === 0;

const immutableDevicesAlreadyHasGroup = selectedImmutableDevices.filter(
(immutableDevice) => immutableDevice.deviceGroups?.length > 0
);
// show warning also when immutable systems had groups
showWarning = showWarning || immutableDevicesAlreadyHasGroup.length > 0;

const [hasImmutableSystems, setHasImmutableSystems] = useState(false);

useEffect(() => {
if (edgeParityEnabled) {
axios
.get(
`${INVENTORY_TOTAL_FETCH_URL_SERVER}${INVENTORY_TOTAL_FETCH_EDGE_PARAMS}`
)
.then((result) => setHasImmutableSystems(result?.data?.total > 0));
}
}, []);

const [activeTab, setActiveTab] = useState(0);

const handleTabClick = (_event, tabIndex) => {
setActiveTab(tabIndex);
};

let overallSelectedText;
if (overallSelectedKeys.length === 1) {
overallSelectedText = '1 system selected';
} else if (overallSelectedKeys.length > 1) {
overallSelectedText = `${overallSelectedKeys.length} systems selected`;
}

const ConventionalInventoryTable = (
<InventoryTable
columns={(columns) => prepareColumns(columns, false, true)}
variant={TableVariant.compact} // TODO: this doesn't affect the table variant
tableProps={{
isStickyHeader: false,
canSelectAll: false,
}}
bulkSelect={bulkSelectConfig}
initialLoading={true}
showTags
/>
);

return (
isModalOpen && (
<>
{/** confirmation modal */}
<ConfirmSystemsAddModal
isModalOpen={confirmationModalOpen}
onSubmit={async () => {
await handleSystemAddition([...selected.keys()]);
await handleSystemAddition(overallSelectedKeys);
setTimeout(() => dispatch(fetchGroupDetail(groupId)), 500); // refetch data for this group
setIsModalOpen(false);
}}
Expand All @@ -112,7 +191,24 @@ const AddSystemsToGroupModal = ({
/>
{/** hosts selection modal */}
<Modal
title="Add systems"
header={
<Flex direction={{ default: 'row' }} style={{ width: '100%' }}>
<FlexItem align={{ default: 'alignLeft' }}>
<PageHeaderTitle title={'Add systems'} />
</FlexItem>
{edgeParityEnabled && !noneSelected && (
<FlexItem align={{ default: 'alignRight' }}>
<Label
variant="outline"
color="blue"
icon={<InfoCircleIcon />}
>
{overallSelectedText}
</Label>
</FlexItem>
)}
</Flex>
}
isOpen={systemsSelectModalOpen}
onClose={() => handleModalClose()}
footer={
Expand All @@ -135,7 +231,7 @@ const AddSystemsToGroupModal = ({
setSystemSelectModalOpen(false);
setConfirmationModalOpen(true); // switch to the confirmation modal
} else {
await handleSystemAddition([...selected.keys()]);
await handleSystemAddition(overallSelectedKeys);
dispatch(fetchGroupDetail(groupId));
handleModalClose();
}
Expand All @@ -156,17 +252,34 @@ const AddSystemsToGroupModal = ({
}
variant="large" // required to accomodate the systems table
>
<InventoryTable
columns={(columns) => prepareColumns(columns, false, true)}
variant={TableVariant.compact} // TODO: this doesn't affect the table variant
tableProps={{
isStickyHeader: false,
canSelectAll: false,
}}
bulkSelect={bulkSelectConfig}
initialLoading={true}
showTags
/>
{edgeParityEnabled && hasImmutableSystems ? (
<Tabs
className="pf-m-light pf-c-table"
activeKey={activeTab}
onSelect={handleTabClick}
aria-label="Hybrid inventory tabs"
>
<Tab
eventKey={0}
title={<TabTitleText>Conventional (RPM-DNF)</TabTitleText>}
>
{ConventionalInventoryTable}
</Tab>
<Tab
eventKey={1}
title={<TabTitleText>Immutable (OSTree)</TabTitleText>}
>
<ImmutableDevicesView
skeletonRowQuantity={15}
hasCheckbox={true}
isSystemsView={false}
selectedItems={setSelectedImmutableDevices}
/>
</Tab>
</Tabs>
) : (
ConventionalInventoryTable
)}
</Modal>
</>
)
Expand All @@ -179,6 +292,7 @@ AddSystemsToGroupModal.propTypes = {
reloadData: PropTypes.func,
groupId: PropTypes.string,
groupName: PropTypes.string,
edgeParityIsAllowed: PropTypes.bool,
};

export default AddSystemsToGroupModal;
30 changes: 30 additions & 0 deletions src/components/InventoryTabs/ImmutableDevices/EdgeDevicesView.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from 'react';
import AsyncComponent from '@redhat-cloud-services/frontend-components/AsyncComponent';
import ErrorState from '@redhat-cloud-services/frontend-components/ErrorState';
import { resolveRelPath } from '../../../Utilities/path';
import {
getNotificationProp,
manageEdgeInventoryUrlName,
} from '../../../Utilities/edge';
import { useLocation, useNavigate } from 'react-router-dom';
import { useDispatch } from 'react-redux';

const ImmutableDevicesView = (props) => {
const dispatch = useDispatch();
const notificationProp = getNotificationProp(dispatch);
return (
<AsyncComponent
appName="edge"
module="./DevicesView"
ErrorComponent={<ErrorState />}
navigateProp={useNavigate}
locationProp={useLocation}
notificationProp={notificationProp}
pathPrefix={resolveRelPath('')}
urlName={manageEdgeInventoryUrlName}
{...props}
/>
);
};

export default ImmutableDevicesView;

0 comments on commit 013ae05

Please sign in to comment.