feat(HMS-3391): add blueprint onclick handler

This commit is contained in:
Amir 2024-01-21 20:19:26 +02:00 committed by Lucas Garfield
parent dca9225df5
commit 017f5bfb51
7 changed files with 185 additions and 28 deletions

View file

@ -19,8 +19,10 @@ import { BlueprintItem } from '../../store/imageBuilderApi';
type blueprintProps = {
blueprint: BlueprintItem;
selectedBlueprint: string;
setSelectedBlueprint: React.Dispatch<React.SetStateAction<string>>;
selectedBlueprint: string | undefined;
setSelectedBlueprint: React.Dispatch<
React.SetStateAction<string | undefined>
>;
};
const BlueprintCard = ({
@ -34,8 +36,10 @@ const BlueprintCard = ({
setIsOpen(!isOpen);
};
const onClickHandler = () => {
setSelectedBlueprint(blueprint.id);
const onClickHandler = ({
currentTarget: { id: blueprintID },
}: React.ChangeEvent<HTMLInputElement>) => {
setSelectedBlueprint(blueprintID);
};
const headerActions = (
@ -76,7 +80,6 @@ const BlueprintCard = ({
<CardHeader
selectableActions={{
selectableActionId: blueprint.id,
selectableActionAriaLabelledby: 'blueprint radio select',
name: blueprint.name,
variant: 'single',
isChecked: isChecked,

View file

@ -20,8 +20,8 @@ import { BlueprintItem } from '../../store/imageBuilderApi';
type blueprintProps = {
blueprints: BlueprintItem[] | undefined;
selectedBlueprint: string;
setSelectedBlueprint: Dispatch<SetStateAction<string>>;
selectedBlueprint: string | undefined;
setSelectedBlueprint: Dispatch<SetStateAction<string | undefined>>;
};
const BlueprintsSidebar = ({
@ -66,6 +66,16 @@ const BlueprintsSidebar = ({
onClear={() => onChange('')}
/>
</StackItem>
<StackItem>
<Button
isBlock
onClick={() => setSelectedBlueprint(undefined)}
variant="link"
isDisabled={!selectedBlueprint}
>
Show all images
</Button>
</StackItem>
{blueprints.map((blueprint: BlueprintItem) => (
<StackItem key={blueprint.id}>
<BlueprintCard

View file

@ -54,6 +54,7 @@ import {
import {
ComposesResponseItem,
ComposeStatus,
useGetBlueprintComposesQuery,
useGetComposesQuery,
useGetComposeStatusQuery,
} from '../../store/imageBuilderApi';
@ -63,7 +64,11 @@ import {
timestampToDisplayString,
} from '../../Utilities/time';
const ImagesTable = () => {
type ImageTableProps = {
selectedBlueprint?: string | undefined;
};
const ImagesTable = ({ selectedBlueprint }: ImageTableProps) => {
const [page, setPage] = useState(1);
const [perPage, setPerPage] = useState(10);
@ -74,16 +79,53 @@ const ImagesTable = () => {
setPerPage(perPage);
};
const { data, isError, isSuccess } = useGetComposesQuery({
limit: perPage,
offset: perPage * (page - 1),
ignoreImageTypes: [
'rhel-edge-commit',
'rhel-edge-installer',
'edge-commit',
'edge-installer',
],
});
const {
data: blueprintsComposes,
isSuccess: isBlueprintsSuccess,
isFetching: isFetchingBlueprintsCompose,
isError: isBlueprintsError,
} = useGetBlueprintComposesQuery(
{
id: selectedBlueprint as string,
limit: perPage,
offset: perPage * (page - 1),
},
{ skip: !selectedBlueprint }
);
const {
data: composesData,
isSuccess: isComposesSuccess,
isError: isComposesError,
isFetching: isFetchingComposes,
} = useGetComposesQuery(
{
limit: perPage,
offset: perPage * (page - 1),
ignoreImageTypes: [
'rhel-edge-commit',
'rhel-edge-installer',
'edge-commit',
'edge-installer',
],
},
{ skip: !!selectedBlueprint }
);
const data = selectedBlueprint ? blueprintsComposes : composesData;
const isSuccess = selectedBlueprint ? isBlueprintsSuccess : isComposesSuccess;
const isError = selectedBlueprint ? isBlueprintsError : isComposesError;
const isFetching = selectedBlueprint
? isFetchingBlueprintsCompose
: isFetchingComposes;
if (isFetching) {
return (
<Bullseye>
<Spinner />
</Bullseye>
);
}
if (!isSuccess) {
if (isError) {
@ -103,13 +145,13 @@ const ImagesTable = () => {
);
}
const composes = data.data;
const itemCount = data.meta.count;
const composes = data?.data;
const itemCount = data?.meta.count || 0;
return (
<>
{data.meta.count === 0 && <EmptyImagesTable />}
{data.meta.count > 0 && (
{itemCount === 0 && <EmptyImagesTable />}
{itemCount > 0 && (
<>
<Toolbar>
<ToolbarContent>
@ -152,7 +194,7 @@ const ImagesTable = () => {
<Th />
</Tr>
</Thead>
{composes.map((compose, rowIndex) => {
{composes?.map((compose, rowIndex) => {
return (
<ImagesTableRow
compose={compose}

View file

@ -53,7 +53,9 @@ export const LandingPage = () => {
}
setActiveTabKey(tabIndex);
};
const [selectedBlueprint, setSelectedBlueprint] = useState<string>('');
const [selectedBlueprint, setSelectedBlueprint] = useState<
string | undefined
>();
const { data: blueprints, isLoading } = useGetBlueprintsQuery({});
const edgeParityFlag = useFlag('edgeParity.image-list');
@ -86,7 +88,7 @@ export const LandingPage = () => {
/>
</SidebarPanel>
<SidebarContent>
<ImagesTable />
<ImagesTable selectedBlueprint={selectedBlueprint} />
</SidebarContent>
</Sidebar>
</PageSection>

View file

@ -1,4 +1,5 @@
import { screen } from '@testing-library/react';
import { screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { rest } from 'msw';
import { IMAGE_BUILDER_API } from '../../../constants';
@ -6,6 +7,8 @@ import { emptyGetBlueprints } from '../../fixtures/blueprints';
import { server } from '../../mocks/server';
import { renderWithReduxRouter } from '../../testUtils';
import '@testing-library/jest-dom';
jest.mock('@redhat-cloud-services/frontend-components/useChrome', () => ({
useChrome: () => ({
isBeta: () => false,
@ -22,9 +25,14 @@ jest.mock('@unleash/proxy-client-react', () => ({
}));
describe('Blueprints', () => {
const user = userEvent.setup();
const blueprintNameWithComposes = 'Dark Chocolate';
const blueprintNameEmptyComposes = 'Milk Chocolate';
test('renders blueprints page', async () => {
renderWithReduxRouter('', {});
await screen.findByText('Dark Chocolate');
await screen.findByText(blueprintNameWithComposes);
await screen.findByText(blueprintNameEmptyComposes);
});
test('renders blueprint empty state', async () => {
server.use(
@ -39,4 +47,28 @@ describe('Blueprints', () => {
renderWithReduxRouter('', {});
await screen.findByText('No blueprints yet');
});
test('renders blueprint composes', async () => {
renderWithReduxRouter('', {});
const nameMatcher = (_, element) =>
element.getAttribute('name') === blueprintNameWithComposes;
const blueprintRadioBtn = await screen.findByRole('radio', {
name: nameMatcher,
});
await user.click(blueprintRadioBtn);
const table = await screen.findByTestId('images-table');
const { findByText } = within(table);
await findByText(blueprintNameWithComposes);
});
test('renders blueprint composes empty state', async () => {
renderWithReduxRouter('', {});
const nameMatcher = (_, element) =>
element.getAttribute('name') === blueprintNameEmptyComposes;
const blueprintRadioBtn = await screen.findByRole('radio', {
name: nameMatcher,
});
await user.click(blueprintRadioBtn);
expect(screen.queryByTestId('images-table')).not.toBeInTheDocument();
});
});

View file

@ -1,6 +1,8 @@
import { RHEL_9 } from '../../constants';
import {
GetBlueprintsApiResponse,
CreateBlueprintResponse,
GetBlueprintComposesApiResponse,
} from '../../store/imageBuilderApi';
export const mockBlueprintsCreation: CreateBlueprintResponse[] = [
@ -35,3 +37,55 @@ export const emptyGetBlueprints: GetBlueprintsApiResponse = {
meta: { count: 0 },
data: [],
};
export const mockBlueprintComposes: GetBlueprintComposesApiResponse = {
meta: { count: 2 },
data: [
{
id: '1579d95b-8f1d-4982-8c53-8c2afa4ab04c',
image_name: 'Dark Chocolate',
created_at: '2021-09-08T14:38:00.000Z',
request: {
distribution: RHEL_9,
image_requests: [
{
architecture: 'x86_64',
image_type: 'aws',
upload_request: {
type: 'aws',
options: {
share_with_accounts: ['123123123123'],
},
},
},
],
},
},
{
id: 'c1cfa347-4c37-49b5-8e73-6aa1d1746cfa',
created_at: '2021-04-27T12:31:12Z',
request: {
distribution: RHEL_9,
image_requests: [
{
architecture: 'x86_64',
image_type: 'gcp',
upload_request: {
type: 'gcp',
options: {
share_with_accounts: ['serviceAccount:test@email.com'],
},
},
},
],
},
},
],
links: { first: 'first', last: 'last' },
};
export const mockEmptyBlueprintsComposes: GetBlueprintComposesApiResponse = {
meta: { count: 0 },
data: [],
links: { first: 'first', last: 'last' },
};

View file

@ -11,7 +11,11 @@ import {
mockActivationKeysResults,
} from '../fixtures/activationKeys';
import { mockArchitecturesByDistro } from '../fixtures/architectures';
import { mockGetBlueprints } from '../fixtures/blueprints';
import {
mockBlueprintComposes,
mockEmptyBlueprintsComposes,
mockGetBlueprints,
} from '../fixtures/blueprints';
import {
composesEndpoint,
mockClones,
@ -111,4 +115,14 @@ export const handlers = [
rest.get(`${IMAGE_BUILDER_API}/experimental/blueprints`, (req, res, ctx) => {
return res(ctx.status(200), ctx.json(mockGetBlueprints));
}),
rest.get(
`${IMAGE_BUILDER_API}/experimental/blueprints/:id/composes`,
(req, res, ctx) => {
const MilkChocolateBlueprint = mockGetBlueprints.data[1].id;
if (req.params.id === MilkChocolateBlueprint) {
return res(ctx.status(200), ctx.json(mockEmptyBlueprintsComposes));
}
return res(ctx.status(200), ctx.json(mockBlueprintComposes));
}
),
];