Skip to content

Commit

Permalink
feat(RHINENG-9248): create bootc tree view (#2178)
Browse files Browse the repository at this point in the history
* feat(RHINENG-9248): create bootc tree view

To test:
Log into account with systems with bootc data
Go to /insights/inventory url
Click the toggle that is right-aligned in the Systems page header

From this point, you should see a table with expandable rows. Those expanded rows include specific hash data for each image name. The system counts  for the hash commits for the nested table should add up to equal the system counts for the parent image.

Please also ensure that the numbers in the system column line up for the table and nested table.

* feat: Make suggested updates

* feat: Add non bootc system count

Adds count of systems without bootc images to the bootc image table.

---------

Co-authored-by: Michael Johnson <[email protected]>
  • Loading branch information
johnsonm325 and Michael Johnson authored Apr 24, 2024
1 parent 5fc6d2d commit fd9bff4
Show file tree
Hide file tree
Showing 10 changed files with 303 additions and 12 deletions.
7 changes: 5 additions & 2 deletions src/Utilities/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,11 @@ export const INVENTORY_TOTAL_FETCH_URL_SERVER = '/api/inventory/v1/hosts';
export const INVENTORY_TOTAL_FETCH_EDGE_PARAMS =
'?filter[system_profile][host_type]=edge&page=1&per_page=1';
export const INVENTORY_TOTAL_FETCH_CONVENTIONAL_PARAMS = '?page=1&per_page=1';
export const INVENTORY_TOTAL_FETCH_BIFROST_PARAMS =
'?filter[system_profile][bootc_status][booted][image_digest][is]=not_nil&per_page=1';
export const INVENTORY_FETCH_BOOTC_PARAMS =
'?filter[system_profile][bootc_status][booted][image_digest][is]';
export const INVENTORY_FETCH_BOOTC = `${INVENTORY_FETCH_BOOTC_PARAMS}=not_nil`;
export const INVENTORY_FETCH_NON_BOOTC = `${INVENTORY_FETCH_BOOTC_PARAMS}=nil`;
export const INVENTORY_TOTAL_FETCH_BOOTC_PARAMS = `${INVENTORY_FETCH_BOOTC}&per_page=1`;
export function subtractDate(days) {
const date = new Date();
date.setDate(date.getDate() - days);
Expand Down
4 changes: 2 additions & 2 deletions src/Utilities/edge.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useGetImageData } from '../api';
import {
INVENTORY_TOTAL_FETCH_EDGE_PARAMS,
INVENTORY_TOTAL_FETCH_URL_SERVER,
INVENTORY_TOTAL_FETCH_BIFROST_PARAMS,
INVENTORY_TOTAL_FETCH_BOOTC_PARAMS,
} from './constants';

const manageEdgeInventoryUrlName = 'manage-edge-inventory';
Expand Down Expand Up @@ -59,7 +59,7 @@ const inventoryHasEdgeSystems = async () => {

const inventoryHasBootcImages = async () => {
const result = await axios.get(
`${INVENTORY_TOTAL_FETCH_URL_SERVER}${INVENTORY_TOTAL_FETCH_BIFROST_PARAMS}`
`${INVENTORY_TOTAL_FETCH_URL_SERVER}${INVENTORY_TOTAL_FETCH_BOOTC_PARAMS}`
);
return result?.data?.total > 0;
};
Expand Down
77 changes: 77 additions & 0 deletions src/routes/InventoryComponents/BifrostPage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import React, { useEffect, useState } from 'react';
import axios from 'axios';
import {
INVENTORY_FETCH_BOOTC,
INVENTORY_FETCH_NON_BOOTC,
INVENTORY_TOTAL_FETCH_URL_SERVER,
} from '../../Utilities/constants';
import BifrostTable from './BifrostTable';

const BifrostPage = () => {
const [bootcImages, setBootcImages] = useState();
const [loaded, setLoaded] = useState(false);

useEffect(() => {
const fetchBootcImages = async () => {
setLoaded(false);
const result = await axios.get(
`${INVENTORY_TOTAL_FETCH_URL_SERVER}${INVENTORY_FETCH_BOOTC}&fields[system_profile]=bootc_status`
);

const packageBasedSystems = await axios.get(
`${INVENTORY_TOTAL_FETCH_URL_SERVER}${INVENTORY_FETCH_NON_BOOTC}&per_page=1`
);

const booted = result.data.results.map(
(system) => system.system_profile.bootc_status.booted
);

const target = {};

booted.forEach((bootedImage) => {
const { image, image_digest } = bootedImage;
if (!target[image]) {
target[image] = {
image,
systemCount: 1,
hashes: {},
hashCommitCount: 0,
};
} else {
target[image].systemCount += 1;
}

if (!target[image].hashes[image_digest]) {
target[image].hashes[image_digest] = {
image_digest,
hashSystemCount: 1,
};
target[image].hashCommitCount += 1;
} else {
target[image].hashes[image_digest].hashSystemCount += 1;
}
});

const updated = [
...Object.values(target).map((val) => ({
...val,
hashes: Object.values(val.hashes),
})),
{
image: 'Package based systems',
systemCount: packageBasedSystems.data.total,
hashCommitCount: '-',
},
];

setLoaded(true);
setBootcImages(updated);
};

fetchBootcImages();
}, []);

return <BifrostTable bootcImages={bootcImages} loaded={loaded} />;
};

export default BifrostPage;
109 changes: 106 additions & 3 deletions src/routes/InventoryComponents/BifrostTable.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,110 @@
import React from 'react';
import React, { useState } from 'react';
import propTypes from 'prop-types';
import {
Table,
Thead,
Tr,
Th,
Tbody,
Td,
ExpandableRowContent,
} from '@patternfly/react-table';
import { SkeletonTable } from '@redhat-cloud-services/frontend-components/SkeletonTable';
import { imageTableColumns } from './BifrostTableColumns';
import NestedHashTable from './NestedHashTable';
import BifrostTableRows from './BifrostTableRows';

const BifrostTable = () => {
return <div>Bifrost table will be here</div>;
const BifrostTable = ({ bootcImages, loaded }) => {
const [expandedImageNames, setExpandedImageNames] = useState([]);

const setImageExpanded = (image, isExpanding = true) =>
setExpandedImageNames((prevExpanded) => {
const otherExpandedImageNames = prevExpanded.filter(
(r) => r !== image.image
);
return isExpanding
? [...otherExpandedImageNames, image.image]
: otherExpandedImageNames;
});

const isImageExpanded = (image) => expandedImageNames.includes(image.image);

return (
<>
{loaded ? (
<Table aria-label="Bootc image table">
<Thead>
<Tr>
<Td />
{imageTableColumns.map((col) => (
<Th
key={col.title}
colSpan={col.colSpan}
className={col.classname}
>
{col.title}
</Th>
))}
</Tr>
</Thead>
<Tbody>
{bootcImages?.map((image, rowIndex) => (
<>
<Tr key={image.image}>
{image.hashes ? (
<Td
expand={{
rowIndex,
isExpanded: isImageExpanded(image),
onToggle: () =>
setImageExpanded(image, !isImageExpanded(image)),
expandId: 'composable-nested-table-expandable-example',
}}
/>
) : (
<Td />
)}
{imageTableColumns.map((col) => (
<BifrostTableRows
key={`${image.image}-${col.title}`}
column={col}
data={image}
/>
))}
</Tr>
{image.hashes ? (
<Tr isExpanded={isImageExpanded(image)}>
<Td
dataLabel={`${imageTableColumns[0].title} expanded`}
colSpan={12}
style={{ paddingRight: '0px' }}
>
<ExpandableRowContent
style={{ paddingTop: '0px', paddingLeft: '64px' }}
>
<NestedHashTable hashes={image.hashes} />
</ExpandableRowContent>
</Td>
</Tr>
) : null}
</>
))}
</Tbody>
</Table>
) : (
<SkeletonTable
columns={imageTableColumns.map(({ title }) => title)}
rows={15}
variant={'compact'}
/>
)}
</>
);
};

BifrostTable.propTypes = {
bootcImages: propTypes.array,
loaded: propTypes.bool,
};

export default BifrostTable;
39 changes: 39 additions & 0 deletions src/routes/InventoryComponents/BifrostTableColumns.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
const ImageColumn = {
title: 'Image name',
colSpan: 8,
ref: 'image',
};

const HashCommitsColumn = {
title: 'Hash commits',
colSpan: 2,
ref: 'hashCommitCount',
};

const SystemsColumn = {
title: 'Systems',
colSpan: 2,
ref: 'systemCount',
classname: 'ins-c-inventory__bootc-systems-count-cell',
};

const HashCommitColumn = {
title: 'Hash commit',
colSpan: 2,
ref: 'image_digest',
};

const HashSystemColumn = {
title: '',
colSpan: 10,
ref: 'hashSystemCount',
classname: 'ins-c-inventory__bootc-systems-count-cell',
};

export const imageTableColumns = [
ImageColumn,
HashCommitsColumn,
SystemsColumn,
];

export const hashTableColumns = [HashCommitColumn, HashSystemColumn];
22 changes: 22 additions & 0 deletions src/routes/InventoryComponents/BifrostTableRows.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from 'react';
import propTypes from 'prop-types';
import { Td } from '@patternfly/react-table';

const BifrostTableRows = ({ column, data }) => {
return (
<Td
dataLabel={column.title}
colSpan={column.colSpan}
className={column.classname}
>
{data[column.ref]}
</Td>
);
};

BifrostTableRows.propTypes = {
column: propTypes.object,
data: propTypes.object,
};

export default BifrostTableRows;
42 changes: 42 additions & 0 deletions src/routes/InventoryComponents/NestedHashTable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React from 'react';
import propTypes from 'prop-types';
import { Table, Thead, Tr, Th, Tbody } from '@patternfly/react-table';
import { hashTableColumns } from './BifrostTableColumns';
import BifrostTableRows from './BifrostTableRows';

const NestedHashTable = ({ hashes }) => {
return (
<Table aria-label="Image table" variant="compact">
<Thead>
<Tr>
{hashTableColumns.map((col) => {
return (
<Th key={col.title} colSpan={col.colSpan}>
{col.title}
</Th>
);
})}
</Tr>
</Thead>
<Tbody>
{hashes.map((hash) => (
<Tr key={hash.image_digest}>
{hashTableColumns.map((col) => (
<BifrostTableRows
key={`${hash.image_digest}-${col.title}`}
column={col}
data={hash}
/>
))}
</Tr>
))}
</Tbody>
</Table>
);
};

NestedHashTable.propTypes = {
hashes: propTypes.array,
};

export default NestedHashTable;
4 changes: 2 additions & 2 deletions src/routes/InventoryPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import './inventory.scss';
import Main from '@redhat-cloud-services/frontend-components/Main';
import HybridInventory from './InventoryComponents/HybridInventory';
import InventoryPageHeader from './InventoryComponents/InventoryPageHeader';
import BifrostTable from './InventoryComponents/BifrostTable';
import BifrostPage from './InventoryComponents/BifrostPage';

export const pageContents = {
hybridInventory: {
Expand All @@ -12,7 +12,7 @@ export const pageContents = {
},
bifrost: {
key: 'bifrost',
component: BifrostTable,
component: BifrostPage,
},
};

Expand Down
6 changes: 3 additions & 3 deletions src/routes/InventoryPage.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import InventoryPage from './InventoryPage';
jest.mock('./InventoryComponents/HybridInventory', () => () => (
<div data-testid="HybridInventory" />
));
jest.mock('./InventoryComponents/BifrostTable', () => () => (
<div data-testid="BifrostTable" />
jest.mock('./InventoryComponents/BifrostPage', () => () => (
<div data-testid="BifrostPage" />
));
jest.mock('../Utilities/useFeatureFlag', () => () => true);
const defaultContextValues = {
Expand Down Expand Up @@ -39,6 +39,6 @@ describe('Inventory', () => {

await userEvent.click(bifrostToggle);

expect(screen.getByTestId('BifrostTable')).toBeInTheDocument();
expect(screen.getByTestId('BifrostPage')).toBeInTheDocument();
});
});
5 changes: 5 additions & 0 deletions src/routes/inventory.scss
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,8 @@
overflow: hidden;
text-overflow: ellipsis;
}

.ins-c-inventory__bootc-systems-count-cell {
text-align: right;
padding-right: 64px;
}

0 comments on commit fd9bff4

Please sign in to comment.