Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve marketplace vendors behavior #2168

Merged
16 changes: 8 additions & 8 deletions src/components/DownloadDropdowns/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useRef, useState, useCallback, useEffect } from 'react';
import React, { useState, useCallback, useEffect } from 'react';
import { useStaticQuery, graphql } from 'gatsby';
import { useLocation } from '@gatsbyjs/reach-router';
import queryString from 'query-string';
Expand Down Expand Up @@ -139,16 +139,15 @@ const DownloadDropdowns = ({updaterAction, marketplace, Table}) => {
const [version, udateVersion] = useState(defaultSelectedVersion);

// Marketplace vendor selector only
const checkboxRef = useRef({});
const [checkbox, updateCheckbox] = useState({checkboxRef});
const [selectedVendorIdentifiers, updateSelectedVendorIdentifiers] = useState<string[]>([]);

const [releases, setReleases] = useState(null);

useEffect(() => {
(async () => {
setReleases(await updaterAction(version, os, arch, packageType, checkboxRef));
setReleases(await updaterAction(version, os, arch, packageType, selectedVendorIdentifiers));
})();
}, [version, os, arch, packageType, checkbox]);
}, [version, os, arch, packageType, selectedVendorIdentifiers]);

const setOS = useCallback((os) => {
setURLParam('os', os)
Expand All @@ -170,13 +169,14 @@ const DownloadDropdowns = ({updaterAction, marketplace, Table}) => {
udateVersion(version);
}, []);

const setCheckbox= useCallback(() => {
updateCheckbox({checkboxRef});
const setSelectedVendorIdentifiers= useCallback((newSelectedVendorIdentifiers) => {
// do not change the URL
updateSelectedVendorIdentifiers(newSelectedVendorIdentifiers);
}, []);

return (
<>
{marketplace && <VendorSelector checkboxRef={checkboxRef} setCheckbox={setCheckbox} />}
{marketplace && <VendorSelector selectedVendorIdentifiers={selectedVendorIdentifiers} setSelectedVendorIdentifiers={setSelectedVendorIdentifiers} />}
<div className="input-group mb-5 row g-2">
<div className="input-group-prepend flex-colunm col-12 col-md-3">
<label className="px-2 fw-bold" htmlFor="os"><Trans>Operating System</Trans></label>
Expand Down
28 changes: 18 additions & 10 deletions src/components/VendorSelector/__tests__/VendorSelector.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@ import { render, fireEvent, screen } from '@testing-library/react';
import { vi } from 'vitest';
import VendorSelector from '../index';
import vendors from '../../../json/marketplace.json';
import getVendorIdentifier from '../../../util/vendors';

describe('VendorSelector', () => {
const mockSetCheckbox = vi.fn();
const checkboxRef = { current: {} };
const mockSetSelectedVendorIdentifiers = vi.fn();
const mockSelectedVendorIdentifiers = vendors.map(v => getVendorIdentifier(v));

beforeEach(() => {
render(<VendorSelector checkboxRef={checkboxRef} setCheckbox={mockSetCheckbox} />);
render(<VendorSelector selectedVendorIdentifiers={mockSelectedVendorIdentifiers} setSelectedVendorIdentifiers={mockSetSelectedVendorIdentifiers} />);
});

afterEach(() => {
vi.clearAllMocks();
});

test('renders component correctly', () => {
Expand All @@ -22,15 +27,18 @@ describe('VendorSelector', () => {

vendors.forEach((vendor, i) => {
test(`renders the input checkbox with correct attributes for vendor ${i}`, () => {
const checkbox = screen.getByTestId(vendor.name);
expect(checkbox).toHaveAttribute('id', `vendor${vendor.name}`);
const identifier = getVendorIdentifier(vendor);
const checkbox = screen.getByTestId(`checkbox-${identifier}`);
expect(checkbox).toHaveAttribute('id', `vendor-${identifier}`);
expect(checkbox).toHaveAttribute('type', 'checkbox');
expect(checkbox).toHaveProperty('defaultChecked', true);
expect(checkbox).toHaveProperty('readOnly', true);
expect(checkbox).toHaveProperty('checked', true);
});

test(`renders the label element with correct attributes for vendor ${i}`, () => {
const identifier = getVendorIdentifier(vendor);
const label = screen.getByTitle(vendor.name);
expect(label).toHaveAttribute('for', `vendor${vendor.name}`);
expect(label).toHaveAttribute('for', `vendor-${identifier}`);
});

test(`renders the img element with correct attributes for vendor ${i}`, () => {
Expand All @@ -41,8 +49,8 @@ describe('VendorSelector', () => {
});

test('calls handleChange function when a checkbox is toggled', () => {
const checkbox = screen.getByTitle(vendors[0].name);
fireEvent.click(checkbox);
expect(mockSetCheckbox).toHaveBeenCalledTimes(1);
const li = screen.getByTestId(`li-${mockSelectedVendorIdentifiers[0]}`);
fireEvent.click(li);
expect(mockSetSelectedVendorIdentifiers).toHaveBeenCalledTimes(2 /* 1 by the useEffec() and 1 by the onclick() */);
});
});
37 changes: 26 additions & 11 deletions src/components/VendorSelector/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,50 @@ import React, { useState, useEffect } from "react"
import { shuffle } from '../../util/shuffle'
import vendors from '../../json/marketplace.json';
import './VendorSelector.scss';
import getVendorIdentifier from '../../util/vendors';

const VendorSelector = ({checkboxRef, setCheckbox}) => {
const VendorSelector = ({selectedVendorIdentifiers, setSelectedVendorIdentifiers}) => {

const [randomizedVendors, setRandomizedVendors] = useState(vendors);
const [randomizedVendors, setRandomizedVendors] = useState<VendorProps[]>([]);

useEffect(() => {
let vendorsCpy = [...vendors];
setRandomizedVendors(shuffle(vendorsCpy));
setSelectedVendorIdentifiers(vendorsCpy.map(vendor => getVendorIdentifier(vendor)))
}, [])

const handleChange = () => {
setCheckbox(checkboxRef.current.checked)
const handleChange = (e, identifier) => {
e.preventDefault();
let newselectedVendorIdentifiers = [...selectedVendorIdentifiers];
let idx = newselectedVendorIdentifiers.indexOf(identifier);
if(idx >= 0) newselectedVendorIdentifiers.splice(idx, 1);
else newselectedVendorIdentifiers.push(identifier);
setSelectedVendorIdentifiers(newselectedVendorIdentifiers)
};

return (
<ul className="vendor-list pt-5">
{randomizedVendors.map(
(vendor, i): string | JSX.Element =>
vendor && (
<li key={vendor.name} className="vendor-li">
<input data-testid={vendor.name} id={`vendor${vendor.name}`} ref={el => checkboxRef.current[`vendor${vendor.name.replace(/\s+/g, '')}`] = el} type="checkbox" defaultChecked={true} onChange={handleChange} />
<label className="vendor-label" htmlFor={`vendor${vendor.name}`} title={vendor.name}>
{randomizedVendors.map((vendor, i): string | JSX.Element => {
let identifier = getVendorIdentifier(vendor);
return (
<li key={`vendor-${i}`} data-testid={`li-${identifier}`} className="vendor-li" onClick={(e) => handleChange(e, identifier)}>
<input id={`vendor-${identifier}`} data-testid={`checkbox-${identifier}`} readOnly className="vendor-name" type="checkbox" checked={selectedVendorIdentifiers.indexOf(identifier) >= 0}/>
<label className="vendor-label" htmlFor={`vendor-${identifier}`} title={vendor.name}>
<img src={`/images/vendors/${vendor.icon}`} alt={`${vendor.name} icon`} style={ vendor.iconPadding ? { padding:vendor.iconPadding} : {}}/>
</label>
</li>
)
)}
)}
</ul>
);
};

export interface VendorProps {
name: string;
icon: string;
iconPadding: string;
postDownload: string;
selected: boolean
};

export default VendorSelector;
15 changes: 5 additions & 10 deletions src/hooks/__tests__/fetchMarketplace.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,10 @@ import { describe, expect, it, vi } from 'vitest'
import { getAllPkgsForVersion, getImageForDistribution } from '../fetchMarketplace';
import { createMockTemurinFeatureReleaseAPI } from '../../__fixtures__/hooks';
import vendors from '../../json/marketplace.json';
import getVendorIdentifier from '../../util/vendors';

let mockResponse = [createMockTemurinFeatureReleaseAPI(false)];
let ref = { current: {} };

// Mock checkbox values
for (let vendor of vendors) {
const checked = true;
ref.current[`vendor${vendor.name.replace(/\s+/g, '')}`] = { checked };
}
let selectedVendorIdentifiers = vendors.map(v => getVendorIdentifier(v));

global.fetch = vi.fn(() => Promise.resolve({
json: () => Promise.resolve(mockResponse)
Expand All @@ -24,15 +19,15 @@ afterEach(() => {
describe('getAllPkgsForVersion', () => {
it('returns valid JSON', async() => {
renderHook(async() => {
await getAllPkgsForVersion(8, 'linux', 'x64', 'jdk', ref).then((data) => {
await getAllPkgsForVersion(8, 'linux', 'x64', 'jdk', selectedVendorIdentifiers).then((data) => {
expect(data).toMatchSnapshot()
})
});
});

it('returns valid JSON - Alpine Linux', async() => {
renderHook(async() => {
await getAllPkgsForVersion(8, 'alpine-linux', 'x64', 'any', ref).then((data) => {
await getAllPkgsForVersion(8, 'alpine-linux', 'x64', 'any', selectedVendorIdentifiers).then((data) => {
expect(data).toMatchSnapshot()
})
});
Expand All @@ -41,7 +36,7 @@ describe('getAllPkgsForVersion', () => {
it('returns valid JSON - installer', async() => {
mockResponse = [createMockTemurinFeatureReleaseAPI(true)];
renderHook(async() => {
await getAllPkgsForVersion(8, 'linux', 'x64', 'jdk', ref).then((data) => {
await getAllPkgsForVersion(8, 'linux', 'x64', 'jdk', selectedVendorIdentifiers).then((data) => {
expect(data).toMatchSnapshot()
})
});
Expand Down
38 changes: 3 additions & 35 deletions src/hooks/fetchMarketplace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,8 @@ export async function getAllPkgsForVersion(
os: string,
architecture: string,
package_type: string,
checkboxRef: any,
vendors: string[]= [],
): Promise<MarketplaceRelease[] | null> {
let microsoftSelected = checkboxRef.current.vendorMicrosoft && checkboxRef.current.vendorMicrosoft.checked;
let temurinSelected = checkboxRef.current.vendorAdoptium && checkboxRef.current.vendorAdoptium.checked;
let redhatSelected = checkboxRef.current.vendorRedHat && checkboxRef.current.vendorRedHat.checked;
let huaweiSelected = checkboxRef.current.vendorHuawei && checkboxRef.current.vendorHuawei.checked;
let zuluSelected = checkboxRef.current.vendorAzul && checkboxRef.current.vendorAzul.checked;
let ibmSelected = checkboxRef.current.vendorIBM && checkboxRef.current.vendorIBM.checked;
let alibabaSelected = checkboxRef.current.vendorAlibaba && checkboxRef.current.vendorAlibaba.checked;

let params = '?'
params += 'feature_version=' + version;

Expand All @@ -37,32 +29,8 @@ export async function getAllPkgsForVersion(
params += ('&image_type=' + package_type)
}

if (temurinSelected) {
params += '&vendor=adoptium'
}

if (redhatSelected) {
params += '&vendor=redhat'
}

if (huaweiSelected) {
params += '&vendor=huawei'
}

if (microsoftSelected) {
params += ('&vendor=microsoft')
}

if (zuluSelected) {
params += ('&vendor=azul')
}

if (ibmSelected) {
params += ('&vendor=ibm')
}

if (alibabaSelected) {
params += ('&vendor=alibaba')
for(const vendor of vendors) {
params += ('&vendor=' + vendor)
}

const url = new URL(baseUrl + '/v1/assets/latestForVendors' + params);
Expand Down
9 changes: 6 additions & 3 deletions src/pages/__tests__/__snapshots__/marketplace.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -51,16 +51,19 @@ exports[`Marketplace page > renders correctly 1`] = `
>
<li
class="vendor-li"
data-testid="li-adoptium"
>
<input
checked=""
data-testid="Adoptium"
id="vendorAdoptium"
class="vendor-name"
data-testid="checkbox-adoptium"
id="vendor-adoptium"
readonly=""
type="checkbox"
/>
<label
class="vendor-label"
for="vendorAdoptium"
for="vendor-adoptium"
title="Adoptium"
>
<img
Expand Down
17 changes: 17 additions & 0 deletions src/util/__tests__/vendors.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import getVendorIdentifier from "../vendors"
import vendors from '../../json/marketplace.json';

describe("vendors", () => {
it("should be lowercase", () => {
const adoptium = vendors.find(v => v.name === "Adoptium");
expect(getVendorIdentifier(adoptium)).toBe("adoptium");

const ibm = vendors.find(v => v.name === "IBM");
expect(getVendorIdentifier(ibm)).toBe("ibm");
});

it("should be with no space", () => {
const redhat = vendors.find(v => v.name === "Red Hat");
expect(getVendorIdentifier(redhat)).toBe("redhat");
});
});
5 changes: 5 additions & 0 deletions src/util/vendors.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import VendorProps from '../components/VendorSelector'

export default function getVendorIdentifier (vendor: VendorProps) {
return vendor.name.toLocaleLowerCase().replace(/\s+/g, '');
}