diff --git a/assets/js/common/UpgradablePackagesList/UpgradablePackagesList.jsx b/assets/js/common/UpgradablePackagesList/UpgradablePackagesList.jsx
new file mode 100644
index 0000000000..496859b27c
--- /dev/null
+++ b/assets/js/common/UpgradablePackagesList/UpgradablePackagesList.jsx
@@ -0,0 +1,57 @@
+import React from 'react';
+import Table from '@common/Table';
+
+const upgradablePackagesDefault = [];
+
+function UpgradablePackagesList({
+ upgradablePackages = upgradablePackagesDefault,
+}) {
+ const config = {
+ pagination: true,
+ usePadding: false,
+ columns: [
+ {
+ title: 'Installed Packages',
+ key: 'installedPackage',
+ render: (content, _) =>
{content}
,
+ },
+ {
+ title: 'Latest Package',
+ key: 'latestPackage',
+ render: (content, _) => {content}
,
+ },
+ {
+ title: 'Related Patches',
+ key: 'patches',
+ render: (content, { to_package_id }) => (
+
+ {content.map(({ advisory_name }) => (
+
+ {advisory_name}
+
+ ))}
+
+ ),
+ },
+ ],
+ };
+
+ const data = upgradablePackages.map((packageDetails) => {
+ const { name, from_version, from_release, to_version, to_release, arch } =
+ packageDetails;
+
+ return {
+ ...packageDetails,
+ installedPackage: `${name}-${from_version}-${from_release}.${arch}`,
+ latestPackage: `${name}-${to_version}-${to_release}.${arch}`,
+ };
+ });
+
+ return (
+
+ );
+}
+
+export default UpgradablePackagesList;
diff --git a/assets/js/common/UpgradablePackagesList/UpgradablePackagesList.stories.jsx b/assets/js/common/UpgradablePackagesList/UpgradablePackagesList.stories.jsx
new file mode 100644
index 0000000000..2fcfcec8e1
--- /dev/null
+++ b/assets/js/common/UpgradablePackagesList/UpgradablePackagesList.stories.jsx
@@ -0,0 +1,27 @@
+import { upgradablePackageFactory } from '@lib/test-utils/factories/upgradablePackage';
+import UpgradablePackagesList from './UpgradablePackagesList';
+
+export default {
+ title: 'Components/UpgradablePackagesList',
+ component: UpgradablePackagesList,
+ argTypes: {
+ hostname: {
+ type: 'string',
+ control: { type: 'text' },
+ description: 'The name of the host',
+ },
+ upgradablePackages: {
+ control: {
+ type: 'array',
+ },
+ description: 'List of upgradable packages',
+ },
+ },
+};
+
+export const Default = {
+ args: {
+ hostname: 'Example Host',
+ upgradablePackages: upgradablePackageFactory.buildList(4),
+ },
+};
diff --git a/assets/js/common/UpgradablePackagesList/UpgradablePackagesList.test.jsx b/assets/js/common/UpgradablePackagesList/UpgradablePackagesList.test.jsx
new file mode 100644
index 0000000000..50bd9f3a99
--- /dev/null
+++ b/assets/js/common/UpgradablePackagesList/UpgradablePackagesList.test.jsx
@@ -0,0 +1,23 @@
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import '@testing-library/jest-dom';
+import { upgradablePackageFactory } from '@lib/test-utils/factories/upgradablePackage';
+import UpgradablePackagesList from './UpgradablePackagesList';
+
+describe('UpgradablePackagesList component', () => {
+ it('should render the upgradable packages list', () => {
+ const upgradablePackage = upgradablePackageFactory.build();
+ const { patches } = upgradablePackage;
+
+ const expectedInstalledPackage = `${upgradablePackage.name}-${upgradablePackage.from_version}-${upgradablePackage.from_release}.${upgradablePackage.arch}`;
+ const expectedLatestPackage = `${upgradablePackage.name}-${upgradablePackage.to_version}-${upgradablePackage.to_release}.${upgradablePackage.arch}`;
+
+ render();
+
+ expect(screen.getByText(expectedInstalledPackage)).toBeVisible();
+ expect(screen.getByText(expectedLatestPackage)).toBeVisible();
+ patches.forEach(({ advisory_name }) => {
+ expect(screen.getByText(advisory_name)).toBeVisible();
+ });
+ });
+});
diff --git a/assets/js/lib/test-utils/factories/upgradablePackage.js b/assets/js/lib/test-utils/factories/upgradablePackage.js
new file mode 100644
index 0000000000..1c6b7d215d
--- /dev/null
+++ b/assets/js/lib/test-utils/factories/upgradablePackage.js
@@ -0,0 +1,21 @@
+import { faker } from '@faker-js/faker';
+import { Factory } from 'fishery';
+import { relevantPatchFactory } from './relevantPatches';
+
+const releaseVersionFactory = () =>
+ `${faker.number.int({ min: 100000, max: 160000 })}.${faker.system.semver()}`;
+
+export const upgradablePackageFactory = Factory.define(() => ({
+ from_epoch: faker.date.anytime(),
+ to_release: releaseVersionFactory(),
+ name: faker.company.buzzNoun(),
+ from_release: releaseVersionFactory(),
+ to_epoch: faker.date.anytime(),
+ arch: faker.airline.flightNumber(),
+ to_package_id: faker.number.int({ min: 2000, max: 5000 }),
+ from_version: faker.system.semver(),
+ to_version: faker.system.semver(),
+ from_arch: faker.airline.flightNumber(),
+ to_arch: faker.airline.flightNumber(),
+ patches: relevantPatchFactory.buildList(2),
+}));