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

Dynamic catalog filters #2475

Merged
merged 3 commits into from
Apr 3, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 18 additions & 11 deletions assets/js/pages/ChecksCatalog/ChecksCatalog.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,7 @@ import {
TARGET_HOST,
TARGET_CLUSTER,
} from '@lib/model';
import {
ASCS_ERS,
HANA_SCALE_OUT,
clusterTypes,
getClusterTypeLabel,
} from '@lib/model/clusters';
import { clusterTypes, getClusterTypeLabel } from '@lib/model/clusters';
import Accordion from '@common/Accordion';
import PageHeader from '@common/PageHeader';
import Pill from '@common/Pill';
Expand Down Expand Up @@ -68,7 +63,19 @@ const targetTypeOptionRenderer = createOptionRenderer(
)
);

function ChecksCatalog({ catalogData, catalogError, loading, updateCatalog }) {
const hasChecksForTarget = (catalog, targetType) =>
catalog.some(({ metadata }) => metadata?.target_type === targetType);

const hasChecksForClusterType = (catalog, clusterType) =>
catalog.some(({ metadata }) => metadata?.cluster_type === clusterType);

function ChecksCatalog({
completeCatalog,
filteredCatalog = completeCatalog,
catalogError,
loading,
updateCatalog,
}) {
const [selectedProvider, setProviderSelected] = useState(OPTION_ALL);
const [selectedTargetType, setSelectedTargetType] = useState(OPTION_ALL);
const [selectedClusterType, setSelectedClusterType] = useState(OPTION_ALL);
Expand All @@ -85,7 +92,7 @@ function ChecksCatalog({ catalogData, catalogError, loading, updateCatalog }) {
optionsName: 'targets',
options: targetTypes.map((targetType) => ({
value: targetType,
disabled: targetType === TARGET_HOST,
disabled: !hasChecksForTarget(completeCatalog, targetType),
})),
renderOption: targetTypeOptionRenderer,
value: selectedTargetType,
Expand All @@ -95,7 +102,7 @@ function ChecksCatalog({ catalogData, catalogError, loading, updateCatalog }) {
optionsName: 'cluster-types',
options: clusterTypes.map((clusterType) => ({
value: clusterType,
disabled: [HANA_SCALE_OUT, ASCS_ERS].includes(clusterType),
disabled: !hasChecksForClusterType(completeCatalog, clusterType),
})),
renderOption: clusterTypeRenderer,
value: selectedClusterType,
Expand Down Expand Up @@ -144,12 +151,12 @@ function ChecksCatalog({ catalogData, catalogError, loading, updateCatalog }) {
onClear={clearFilters}
onRefresh={() => updateCatalog(selectedProvider)}
withResetFilters
empty={catalogData.length === 0}
empty={filteredCatalog.length === 0}
catalogError={catalogError}
loading={loading}
>
<div>
{Object.entries(groupBy(catalogData, 'group')).map(
{Object.entries(groupBy(filteredCatalog, 'group')).map(
([group, checks], index) => (
<ul key={group}>
<Accordion
Expand Down
27 changes: 15 additions & 12 deletions assets/js/pages/ChecksCatalog/ChecksCatalog.stories.jsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import React from 'react';
import { MemoryRouter } from 'react-router-dom';
import { faker } from '@faker-js/faker';

import { catalogCheckFactory } from '@lib/test-utils/factories';
import ChecksCatalog from './ChecksCatalog';

const groupName1 = faker.string.uuid();
const groupName2 = faker.string.uuid();
const groupName3 = faker.string.uuid();
const groupName1 = 'group 1';
const groupName2 = 'group 2';
const groupName3 = 'group 3';

const clusterCheck = catalogCheckFactory.build({
group: groupName1,
metadata: { target_type: 'cluster' },
metadata: { target_type: 'cluster', cluster_type: 'hana_scale_up' },
});

const hostCheck = catalogCheckFactory.build({
Expand All @@ -22,11 +21,11 @@ const hostCheck = catalogCheckFactory.build({
const group1 = [
clusterCheck,
hostCheck,
...catalogCheckFactory.buildList(5, { group: groupName1 }),
...catalogCheckFactory.buildList(1, { group: groupName1 }),
];

const group2 = catalogCheckFactory.buildList(5, { group: groupName2 });
const group3 = catalogCheckFactory.buildList(5, { group: groupName3 });
const group2 = catalogCheckFactory.buildList(2, { group: groupName2 });
const group3 = catalogCheckFactory.buildList(2, { group: groupName3 });
const catalogData = [...group1, ...group2, ...group3];

function ContainerWrapper({ children }) {
Expand All @@ -39,9 +38,13 @@ export default {
title: 'Layouts/ChecksCatalog',
component: ChecksCatalog,
argTypes: {
catalogData: {
completeCatalog: {
control: 'object',
description: 'Catalog content',
description: 'The whole Catalog content',
},
filteredCatalog: {
control: 'object',
description: 'The filtered Catalog content',
},
catalogError: {
control: 'text',
Expand Down Expand Up @@ -79,7 +82,7 @@ export default {

export const Default = {
args: {
catalogData,
completeCatalog: catalogData,
},
};

Expand All @@ -100,6 +103,6 @@ export const Error = {
export const Empty = {
args: {
...Default.args,
catalogData: [],
filteredCatalog: [],
},
};
132 changes: 119 additions & 13 deletions assets/js/pages/ChecksCatalog/ChecksCatalog.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ describe('ChecksCatalog ChecksCatalog component', () => {

render(
<ChecksCatalog
catalogData={catalogData}
completeCatalog={catalogData}
updateCatalog={mockUpdateCatalog}
/>
);
Expand All @@ -46,28 +46,135 @@ describe('ChecksCatalog ChecksCatalog component', () => {
});
});

const scenarios = [
{
name: 'catalog without host checks',
catalogData: catalogCheckFactory.buildList(5, {
metadata: { target_type: 'cluster' },
}),
filter: 'All targets',
expectDisabled: 'Hosts',
expectEnabled: 'Clusters',
},
{
name: 'catalog without cluster checks',
catalogData: catalogCheckFactory.buildList(5, {
metadata: { target_type: 'host' },
}),
filter: 'All targets',
expectDisabled: 'Clusters',
expectEnabled: 'Hosts',
},
{
name: 'catalog without hana scale out cluster checks',
catalogData: [
catalogCheckFactory.build({
metadata: {
target_type: 'cluster',
cluster_type: 'hana_scale_up',
},
}),
catalogCheckFactory.build({
metadata: {
target_type: 'host',
},
}),
catalogCheckFactory.build({
metadata: {
target_type: 'cluster',
cluster_type: 'ascs_ers',
},
}),
],
initialTargetType: 'Clusters',
filter: 'All cluster types',
expectDisabled: 'HANA Scale Out',
expectAllEnabled: ['HANA Scale Up', 'ASCS/ERS'],
},
];

it.each(scenarios)(
'should rely upon the whole catalog for filters rendering when $name',
async ({
catalogData,
initialTargetType,
filter,
expectDisabled,
expectEnabled,
expectAllEnabled,
}) => {
const user = userEvent.setup();
const mockUpdateCatalog = jest.fn();

render(
<ChecksCatalog
completeCatalog={catalogData}
filteredCatalog={catalogCheckFactory.buildList(2)}
updateCatalog={mockUpdateCatalog}
/>
);

if (initialTargetType) {
await user.click(screen.getByText('All targets'));
await user.click(screen.getByText(initialTargetType));
}

await user.click(screen.getByText(filter));

expect(
screen.getByText(expectDisabled, { exact: false }).closest('li')
).toHaveAttribute('aria-disabled', 'true');

const expectItemEnabled = (itemExpectedEnabled) =>
expect(
screen.getByText(itemExpectedEnabled).closest('li')
).not.toHaveAttribute('aria-disabled');

if (expectEnabled) {
expectItemEnabled(expectEnabled);
}
if (expectAllEnabled) {
expectAllEnabled.forEach(expectItemEnabled);
}
}
);

it('should query the catalog with the correct filters', async () => {
const user = userEvent.setup();

const catalogData = catalogCheckFactory.buildList(5);
const mockUpdateCatalog = jest.fn();

const catalogData = [
catalogCheckFactory.build({
metadata: { target_type: 'host' },
}),
catalogCheckFactory.build({
metadata: {
target_type: 'cluster',
cluster_type: 'hana_scale_up',
},
}),
catalogCheckFactory.build({
metadata: {
target_type: 'cluster',
cluster_type: 'ascs_ers',
},
}),
];

render(
<ChecksCatalog
catalogData={catalogData}
completeCatalog={catalogData}
updateCatalog={mockUpdateCatalog}
/>
);

await user.click(screen.getByText('All providers'));
await user.click(screen.getByText('AWS'));

await user.click(screen.getByText('All targets'));
await user.click(screen.getByText('Clusters'));

await user.click(screen.getByText('All cluster types'));
await user.click(screen.getByText('ASCS/ERS', { exact: false }));

await user.click(screen.getByText('ASCS/ERS'));
expect(mockUpdateCatalog).toHaveBeenNthCalledWith(1, {
selectedClusterType: 'all',
selectedProvider: 'all',
Expand All @@ -83,11 +190,10 @@ describe('ChecksCatalog ChecksCatalog component', () => {
selectedProvider: 'aws',
selectedTargetType: 'cluster',
});
// TODO: possibly re-enable this test once the catalog is updated with asc_ers checks
// expect(mockUpdateCatalog).toHaveBeenNthCalledWith(4, {
// selectedClusterType: 'ascs_ers',
// selectedProvider: 'aws',
// selectedTargetType: 'cluster',
// });
expect(mockUpdateCatalog).toHaveBeenNthCalledWith(4, {
selectedClusterType: 'ascs_ers',
selectedProvider: 'aws',
selectedTargetType: 'cluster',
});
});
});
20 changes: 15 additions & 5 deletions assets/js/pages/ChecksCatalog/ChecksCatalogPage.jsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,39 @@
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';

import { pickBy } from 'lodash';
import { pickBy, values } from 'lodash';

import { getCatalog } from '@state/selectors/catalog';
import { updateCatalog } from '@state/catalog';
import { OPTION_ALL } from '@common/Select';

import ChecksCatalog from './ChecksCatalog';

const buildUpdateCatalogAction = (selectedFilters) =>
updateCatalog(pickBy(selectedFilters, (value) => value !== OPTION_ALL));
const isSomeFilter = (value) => value !== OPTION_ALL;

const buildUpdateCatalogAction = (selectedFilters) => {
const hasFilters = values(selectedFilters).some(isSomeFilter);
const payload = {
...pickBy(selectedFilters, isSomeFilter),
...(hasFilters ? { filteredCatalog: true } : {}),
};
return updateCatalog(payload);
};

function ChecksCatalogPage() {
const dispatch = useDispatch();

const {
data: catalogData,
data: completeCatalog,
filteredCatalog,
error: catalogError,
loading,
} = useSelector(getCatalog());

return (
<ChecksCatalog
catalogData={catalogData}
completeCatalog={completeCatalog}
filteredCatalog={filteredCatalog}
catalogError={catalogError}
loading={loading}
updateCatalog={({
Expand Down
16 changes: 14 additions & 2 deletions assets/js/state/catalog.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export const updateCatalog = createAction(UPDATE_CATALOG);
const initialState = {
loading: true,
data: [],
filteredCatalog: [],
error: null,
};

Expand All @@ -18,18 +19,29 @@ export const catalogSlice = createSlice({
},
setCatalogData: (state, action) => {
state.data = action.payload.data;
state.filteredCatalog = action.payload.data;
state.error = null;
state.loading = false;
},
setFilteredCatalog: (state, action) => {
state.filteredCatalog = action.payload.data;
state.error = null;
state.loading = false;
},
setCatalogError: (state, action) => {
state.data = [];
state.filteredCatalog = [];
state.error = action.payload.error;
state.loading = false;
},
},
});

export const { setCatalogLoading, setCatalogData, setCatalogError } =
catalogSlice.actions;
export const {
setCatalogLoading,
setFilteredCatalog,
setCatalogData,
setCatalogError,
} = catalogSlice.actions;

export default catalogSlice.reducer;
Loading
Loading