Fix products filtering (#2746)

* Fix products filtering

* Refactor products filtering functions

* Update filter test snapshots

* Add tests for attribute filtering
This commit is contained in:
Dawid 2022-12-09 15:18:59 +01:00 committed by GitHub
parent b612e2be69
commit 6725f6fde2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 482 additions and 134 deletions

View file

@ -2,6 +2,7 @@ import { IFilter } from "@saleor/components/Filter";
import { SingleAutocompleteChoiceType } from "@saleor/components/SingleAutocompleteSelectField";
import { AttributeInputTypeEnum, StockAvailability } from "@saleor/graphql";
import { commonMessages, sectionNames } from "@saleor/intl";
import { ProductListUrlFiltersAsDictWithMultipleValues } from "@saleor/products/urls";
import {
AutocompleteFilterOpts,
FilterOpts,
@ -20,17 +21,18 @@ import {
} from "@saleor/utils/filters/fields";
import { defineMessages, IntlShape } from "react-intl";
export enum ProductFilterKeys {
attributes = "attributes",
categories = "categories",
collections = "collections",
metadata = "metadata",
price = "price",
productType = "productType",
stock = "stock",
channel = "channel",
productKind = "productKind",
}
export const ProductFilterKeys = {
...ProductListUrlFiltersAsDictWithMultipleValues,
categories: "categories",
collections: "collections",
metadata: "metadata",
price: "price",
productType: "productType",
stock: "stock",
channel: "channel",
productKind: "productKind",
} as const;
export type ProductFilterKeys = typeof ProductFilterKeys[keyof typeof ProductFilterKeys];
export type AttributeFilterOpts = FilterOpts<string[]> & {
id: string;
@ -260,7 +262,7 @@ export function createFilterStructure(
},
),
active: attr.active,
group: ProductFilterKeys.attributes,
group: ProductFilterKeys.booleanAttributes,
})),
...dateAttributes.map(attr => ({
...createDateField(attr.slug, attr.name, {
@ -268,7 +270,7 @@ export function createFilterStructure(
max: attr.value[1] ?? attr.value[0],
}),
active: attr.active,
group: ProductFilterKeys.attributes,
group: ProductFilterKeys.dateAttributes,
})),
...dateTimeAttributes.map(attr => ({
...createDateTimeField(attr.slug, attr.name, {
@ -276,7 +278,7 @@ export function createFilterStructure(
max: attr.value[1] ?? attr.value[0],
}),
active: attr.active,
group: ProductFilterKeys.attributes,
group: ProductFilterKeys.dateTimeAttributes,
})),
...numericAttributes.map(attr => ({
...createNumberField(attr.slug, attr.name, {
@ -284,7 +286,7 @@ export function createFilterStructure(
max: attr.value[1] ?? attr.value[0],
}),
active: attr.active,
group: ProductFilterKeys.attributes,
group: ProductFilterKeys.numericAttributes,
})),
...defaultAttributes.map(attr => ({
...createAutocompleteField(
@ -304,7 +306,7 @@ export function createFilterStructure(
attr.id,
),
active: attr.active,
group: ProductFilterKeys.attributes,
group: ProductFilterKeys.stringAttributes,
})),
];
}

View file

@ -42,9 +42,14 @@ export enum ProductListUrlFiltersWithMultipleValues {
collections = "collections",
productTypes = "productTypes",
}
export enum ProductListUrlFiltersAsDictWithMultipleValues {
attributes = "attributes",
}
export const ProductListUrlFiltersAsDictWithMultipleValues = {
booleanAttributes: "boolean-attributes",
dateAttributes: "date-attributes",
dateTimeAttributes: "datetime-attributes",
numericAttributes: "numeric-attributes",
stringAttributes: "string-attributes",
} as const;
export type ProductListUrlFiltersAsDictWithMultipleValues = typeof ProductListUrlFiltersAsDictWithMultipleValues[keyof typeof ProductListUrlFiltersAsDictWithMultipleValues];
export enum ProductListUrlFiltersWithKeyValueValues {
metadata = "metadata",
}

View file

@ -2,7 +2,27 @@
exports[`Filtering URL params should not be empty if active filters are present 1`] = `
Object {
"attributes": Object {
"categories": Array [
"878752",
],
"channel": "default-channel",
"collections": Array [
"Q29sbGVjdGlvbjoc",
],
"metadata": Array [
Object {
"key": "metadataKey",
"value": "metadataValue",
},
],
"priceFrom": "10",
"priceTo": "20",
"productKind": "NORMAL",
"productTypes": Array [
"UHJvZHVjdFR5cGU6MQ==",
],
"stockStatus": "IN_STOCK",
"string-attributes": Object {
"author": Array [
"john-doe",
false,
@ -52,27 +72,7 @@ Object {
"m",
],
},
"categories": Array [
"878752",
],
"channel": "default-channel",
"collections": Array [
"Q29sbGVjdGlvbjoc",
],
"metadata": Array [
Object {
"key": "metadataKey",
"value": "metadataValue",
},
],
"priceFrom": "10",
"priceTo": "20",
"productKind": "NORMAL",
"productTypes": Array [
"UHJvZHVjdFR5cGU6MQ==",
],
"stockStatus": "IN_STOCK",
}
`;
exports[`Filtering URL params should not be empty if active filters are present 2`] = `"channel=default-channel&metadata%5B0%5D%5Bkey%5D=metadataKey&metadata%5B0%5D%5Bvalue%5D=metadataValue&productKind=NORMAL&stockStatus=IN_STOCK&priceFrom=10&priceTo=20&categories%5B0%5D=878752&collections%5B0%5D=Q29sbGVjdGlvbjoc&productTypes%5B0%5D=UHJvZHVjdFR5cGU6MQ%3D%3D&attributes%5Bauthor%5D%5B0%5D=john-doe&attributes%5Bauthor%5D%5B1%5D=false&attributes%5Bbox-size%5D%5B0%5D=100g&attributes%5Bbox-size%5D%5B1%5D=500g&attributes%5Bbrand%5D%5B0%5D=saleor&attributes%5Bbrand%5D%5B1%5D=false&attributes%5Bcandy-box-size%5D%5B0%5D=100g&attributes%5Bcandy-box-size%5D%5B1%5D=500g&attributes%5Bcoffee-genre%5D%5B0%5D=arabica&attributes%5Bcoffee-genre%5D%5B1%5D=false&attributes%5Bcollar%5D%5B0%5D=round&attributes%5Bcollar%5D%5B1%5D=polo&attributes%5Bcolor%5D%5B0%5D=blue&attributes%5Bcolor%5D%5B1%5D=false&attributes%5Bcover%5D%5B0%5D=soft&attributes%5Bcover%5D%5B1%5D=middle-soft&attributes%5Bflavor%5D%5B0%5D=sour&attributes%5Bflavor%5D%5B1%5D=false&attributes%5Blanguage%5D%5B0%5D=english&attributes%5Blanguage%5D%5B1%5D=false&attributes%5Bpublisher%5D%5B0%5D=mirumee-press&attributes%5Bpublisher%5D%5B1%5D=false&attributes%5Bsize%5D%5B0%5D=xs&attributes%5Bsize%5D%5B1%5D=m"`;
exports[`Filtering URL params should not be empty if active filters are present 2`] = `"channel=default-channel&metadata%5B0%5D%5Bkey%5D=metadataKey&metadata%5B0%5D%5Bvalue%5D=metadataValue&productKind=NORMAL&stockStatus=IN_STOCK&priceFrom=10&priceTo=20&categories%5B0%5D=878752&collections%5B0%5D=Q29sbGVjdGlvbjoc&productTypes%5B0%5D=UHJvZHVjdFR5cGU6MQ%3D%3D&string-attributes%5Bauthor%5D%5B0%5D=john-doe&string-attributes%5Bauthor%5D%5B1%5D=false&string-attributes%5Bbox-size%5D%5B0%5D=100g&string-attributes%5Bbox-size%5D%5B1%5D=500g&string-attributes%5Bbrand%5D%5B0%5D=saleor&string-attributes%5Bbrand%5D%5B1%5D=false&string-attributes%5Bcandy-box-size%5D%5B0%5D=100g&string-attributes%5Bcandy-box-size%5D%5B1%5D=500g&string-attributes%5Bcoffee-genre%5D%5B0%5D=arabica&string-attributes%5Bcoffee-genre%5D%5B1%5D=false&string-attributes%5Bcollar%5D%5B0%5D=round&string-attributes%5Bcollar%5D%5B1%5D=polo&string-attributes%5Bcolor%5D%5B0%5D=blue&string-attributes%5Bcolor%5D%5B1%5D=false&string-attributes%5Bcover%5D%5B0%5D=soft&string-attributes%5Bcover%5D%5B1%5D=middle-soft&string-attributes%5Bflavor%5D%5B0%5D=sour&string-attributes%5Bflavor%5D%5B1%5D=false&string-attributes%5Blanguage%5D%5B0%5D=english&string-attributes%5Blanguage%5D%5B1%5D=false&string-attributes%5Bpublisher%5D%5B0%5D=mirumee-press&string-attributes%5Bpublisher%5D%5B1%5D=false&string-attributes%5Bsize%5D%5B0%5D=xs&string-attributes%5Bsize%5D%5B1%5D=m"`;

View file

@ -1,4 +1,4 @@
import { StockAvailability } from "@saleor/graphql";
import { AttributeInputTypeEnum, StockAvailability } from "@saleor/graphql";
import { createFilterStructure } from "@saleor/products/components/ProductListPage";
import { ProductListUrlFilters } from "@saleor/products/urls";
import { getFilterQueryParams } from "@saleor/utils/filters";
@ -7,7 +7,15 @@ import { getExistingKeys, setFilterOptsStatus } from "@test/filters";
import { config } from "@test/intl";
import { createIntl } from "react-intl";
import { getFilterQueryParam, getFilterVariables } from "./filters";
import { ProductListUrlFiltersAsDictWithMultipleValues } from "../../urls";
import {
FilterParam,
getAttributeValuesFromParams,
getFilterQueryParam,
getFilterVariables,
mapAttributeParamsToFilterOpts,
parseFilterValue,
} from "./filters";
import { productListFilterOpts } from "./fixtures";
describe("Filtering query params", () => {
@ -31,6 +39,150 @@ describe("Filtering query params", () => {
});
});
describe("Get attribute values from URL params", () => {
type GetAttributeValuesFromParams = Parameters<
typeof getAttributeValuesFromParams
>;
it("should return empty array when attribute doesn't exist in params", () => {
// Arrange
const params: GetAttributeValuesFromParams[0] = {};
const attribute: GetAttributeValuesFromParams[1] = {
slug: "test",
inputType: AttributeInputTypeEnum.DROPDOWN,
};
// Act
const attributeValues = getAttributeValuesFromParams(params, attribute);
// Assert
expect(attributeValues).toHaveLength(0);
});
it("should return attribute values when attribute exists in params", () => {
// Arrange
const params: GetAttributeValuesFromParams[0] = {
"string-attributes": {
test: ["value-1", "value-2"],
},
};
const attribute: GetAttributeValuesFromParams[1] = {
slug: "test",
inputType: AttributeInputTypeEnum.DROPDOWN,
};
// Act
const attributeValues = getAttributeValuesFromParams(params, attribute);
// Assert
expect(attributeValues).toEqual(["value-1", "value-2"]);
});
});
describe("Map attribute params to filter opts", () => {
type MapAttributeParamsToFilterOpts = Parameters<
typeof mapAttributeParamsToFilterOpts
>;
type MapAttributeParamsToFilterOptsReturn = ReturnType<
typeof mapAttributeParamsToFilterOpts
>;
it("should return empty array when no params given", () => {
// Arrange
const attributes: MapAttributeParamsToFilterOpts[0] = [
{
id: "1",
slug: "test",
inputType: AttributeInputTypeEnum.DROPDOWN,
name: "Test",
__typename: "Attribute",
},
];
const params: MapAttributeParamsToFilterOpts[1] = {};
// Act
const filterOpts = mapAttributeParamsToFilterOpts(attributes, params);
// Assert
const expectedFilterOpts: MapAttributeParamsToFilterOptsReturn = [
{
id: "1",
slug: "test",
inputType: AttributeInputTypeEnum.DROPDOWN,
name: "Test",
active: false,
value: [],
},
];
expect(filterOpts).toEqual(expectedFilterOpts);
});
it("should return filter opts with proper values selected according to passed values selection in params", () => {
// Arrange
const attributes: MapAttributeParamsToFilterOpts[0] = [
{
id: "1",
slug: "test-1",
inputType: AttributeInputTypeEnum.MULTISELECT,
name: "Test 1",
__typename: "Attribute",
},
{
id: "2",
slug: "test-2",
inputType: AttributeInputTypeEnum.DROPDOWN,
name: "Test 2",
__typename: "Attribute",
},
{
id: "3",
slug: "test-3",
inputType: AttributeInputTypeEnum.DROPDOWN,
name: "Test 3",
__typename: "Attribute",
},
];
const params: MapAttributeParamsToFilterOpts[1] = {
"string-attributes": {
"test-1": ["value-1", "value-2"],
"test-2": ["value-3"],
},
};
// Act
const filterOpts = mapAttributeParamsToFilterOpts(attributes, params);
// Assert
const expectedFilterOpts: MapAttributeParamsToFilterOptsReturn = [
{
id: "1",
slug: "test-1",
inputType: AttributeInputTypeEnum.MULTISELECT,
name: "Test 1",
active: true,
value: ["value-1", "value-2"],
},
{
id: "2",
slug: "test-2",
inputType: AttributeInputTypeEnum.DROPDOWN,
name: "Test 2",
active: true,
value: ["value-3"],
},
{
id: "3",
slug: "test-3",
inputType: AttributeInputTypeEnum.DROPDOWN,
name: "Test 3",
active: false,
value: [],
},
];
expect(filterOpts).toEqual(expectedFilterOpts);
});
});
describe("Filtering URL params", () => {
const intl = createIntl(config);
@ -55,3 +207,161 @@ describe("Filtering URL params", () => {
expect(stringifyQs(filterQueryParams)).toMatchSnapshot();
});
});
describe("Parsing filter value", () => {
it("should return boolean values when boolean attributes values passed", () => {
// Arrange
const params: ProductListUrlFilters = {
"boolean-attributes": {
"test-1": ["true"],
"test-2": ["false"],
},
};
const type =
ProductListUrlFiltersAsDictWithMultipleValues.booleanAttributes;
// Act
const parsedValue1 = parseFilterValue(params, "test-1", type);
const parsedValue2 = parseFilterValue(params, "test-2", type);
// Assert
const expectedValue1: FilterParam = {
slug: "test-1",
boolean: true,
};
const expectedValue2: FilterParam = {
slug: "test-2",
boolean: false,
};
expect(parsedValue1).toEqual(expectedValue1);
expect(parsedValue2).toEqual(expectedValue2);
});
it("should return numeric values when numeric attributes values passed", () => {
// Arrange
const params: ProductListUrlFilters = {
"numeric-attributes": {
"test-1": ["1"],
"test-2": ["1", "2"],
},
};
const type =
ProductListUrlFiltersAsDictWithMultipleValues.numericAttributes;
// Act
const parsedValue1 = parseFilterValue(params, "test-1", type);
const parsedValue2 = parseFilterValue(params, "test-2", type);
// Assert
const expectedValue1: FilterParam = {
slug: "test-1",
valuesRange: {
gte: 1,
lte: 1,
},
};
const expectedValue2: FilterParam = {
slug: "test-2",
valuesRange: {
gte: 1,
lte: 2,
},
};
expect(parsedValue1).toEqual(expectedValue1);
expect(parsedValue2).toEqual(expectedValue2);
});
it("should return string values when string attributes values passed", () => {
// Arrange
const params: ProductListUrlFilters = {
"string-attributes": {
"test-1": ["value-1"],
"test-2": ["value-2", "value-3"],
},
};
const type = ProductListUrlFiltersAsDictWithMultipleValues.stringAttributes;
// Act
const parsedValue1 = parseFilterValue(params, "test-1", type);
const parsedValue2 = parseFilterValue(params, "test-2", type);
// Assert
const expectedValue1: FilterParam = {
slug: "test-1",
values: ["value-1"],
};
const expectedValue2: FilterParam = {
slug: "test-2",
values: ["value-2", "value-3"],
};
expect(parsedValue1).toEqual(expectedValue1);
expect(parsedValue2).toEqual(expectedValue2);
});
it("should return date values when date attributes values passed", () => {
// Arrange
const params: ProductListUrlFilters = {
"date-attributes": {
"test-1": ["2020-01-01"],
"test-2": ["2020-01-01", "2020-02-02"],
},
};
const type = ProductListUrlFiltersAsDictWithMultipleValues.dateAttributes;
// Act
const parsedValue1 = parseFilterValue(params, "test-1", type);
const parsedValue2 = parseFilterValue(params, "test-2", type);
// Assert
const expectedValue1: FilterParam = {
slug: "test-1",
date: {
gte: "2020-01-01",
lte: "2020-01-01",
},
};
const expectedValue2: FilterParam = {
slug: "test-2",
date: {
gte: "2020-01-01",
lte: "2020-02-02",
},
};
expect(parsedValue1).toEqual(expectedValue1);
expect(parsedValue2).toEqual(expectedValue2);
});
it("should return datetime values when datetime attributes values passed", () => {
// Arrange
const params: ProductListUrlFilters = {
"datetime-attributes": {
"test-1": ["2020-01-01T00:00:00"],
"test-2": ["2020-01-01T00:00:00", "2020-02-02T00:00:00"],
},
};
const type =
ProductListUrlFiltersAsDictWithMultipleValues.dateTimeAttributes;
// Act
const parsedValue1 = parseFilterValue(params, "test-1", type);
const parsedValue2 = parseFilterValue(params, "test-2", type);
// Assert
const expectedValue1: FilterParam = {
slug: "test-1",
dateTime: {
gte: "2020-01-01T00:00:00",
lte: "2020-01-01T00:00:00",
},
};
const expectedValue2: FilterParam = {
slug: "test-2",
dateTime: {
gte: "2020-01-01T00:00:00",
lte: "2020-02-02T00:00:00",
},
};
expect(parsedValue1).toEqual(expectedValue1);
expect(parsedValue2).toEqual(expectedValue2);
});
});

View file

@ -1,5 +1,7 @@
import { SingleAutocompleteChoiceType } from "@saleor/components/SingleAutocompleteSelectField";
import {
AttributeFragment,
AttributeInputTypeEnum,
InitialProductFilterAttributesQuery,
InitialProductFilterCategoriesQuery,
InitialProductFilterCollectionsQuery,
@ -27,7 +29,6 @@ import {
mapNodeToChoice,
mapSlugNodeToChoice,
} from "@saleor/utils/maps";
import moment from "moment-timezone";
import {
FilterElement,
@ -57,6 +58,52 @@ import {
import { getProductGiftCardFilterParam } from "./utils";
export const PRODUCT_FILTERS_KEY = "productFilters";
function getAttributeFilterParamType(inputType: AttributeInputTypeEnum) {
switch (inputType) {
case AttributeInputTypeEnum.DATE:
return ProductListUrlFiltersAsDictWithMultipleValues.dateAttributes;
case AttributeInputTypeEnum.DATE_TIME:
return ProductListUrlFiltersAsDictWithMultipleValues.dateTimeAttributes;
case AttributeInputTypeEnum.NUMERIC:
return ProductListUrlFiltersAsDictWithMultipleValues.numericAttributes;
case AttributeInputTypeEnum.BOOLEAN:
return ProductListUrlFiltersAsDictWithMultipleValues.booleanAttributes;
default:
return ProductListUrlFiltersAsDictWithMultipleValues.stringAttributes;
}
}
export function getAttributeValuesFromParams(
params: ProductListUrlFilters,
attribute: Pick<AttributeFragment, "inputType" | "slug">,
) {
return (
params[getAttributeFilterParamType(attribute.inputType)]?.[
attribute.slug
] || []
);
}
export function mapAttributeParamsToFilterOpts(
attributes: RelayToFlat<InitialProductFilterAttributesQuery["attributes"]>,
params: ProductListUrlFilters,
) {
return attributes
.sort((a, b) => (a.name > b.name ? 1 : -1))
.map(attr => {
const attrValues = getAttributeValuesFromParams(params, attr);
return {
active: attrValues.length > 0,
id: attr.id,
name: attr.name,
slug: attr.slug,
inputType: attr.inputType,
value: dedupeFilter(attrValues),
};
});
}
export function getFilterOpts(
params: ProductListUrlFilters,
attributes: RelayToFlat<InitialProductFilterAttributesQuery["attributes"]>,
@ -89,16 +136,7 @@ export function getFilterOpts(
channels: SingleAutocompleteChoiceType[],
): ProductListFilterOpts {
return {
attributes: attributes
.sort((a, b) => (a.name > b.name ? 1 : -1))
.map(attr => ({
active: maybe(() => params.attributes[attr.slug].length > 0, false),
id: attr.id,
name: attr.name,
slug: attr.slug,
inputType: attr.inputType,
value: dedupeFilter(params.attributes?.[attr.slug] || []),
})),
attributes: mapAttributeParamsToFilterOpts(attributes, params),
attributeChoices: {
active: true,
choices: mapSlugNodeToChoice(
@ -233,40 +271,6 @@ export function getFilterOpts(
};
}
const parseFilterValue = (
params: ProductListUrlFilters,
key: string,
): {
type: "boolean" | "date" | "dateTime" | "numeric" | "string";
isMulti: boolean;
value: string[];
} => {
const value = params.attributes[key];
const isMulti = params.attributes[key].length > 1;
const isBooleanValue = value.every(val => val === "true" || val === "false");
const isDateValue = (isMulti ? value : [value]).some(val =>
moment(val, moment.HTML5_FMT.DATE, true).isValid(),
);
const isDateTimeValue = (isMulti ? value : [value]).some(val =>
moment(val, moment.ISO_8601, true).isValid(),
);
const isNumericValue = value.some(value => !isNaN(parseFloat(value)));
const data = { isMulti, value };
if (isBooleanValue) {
return { ...data, type: "boolean" };
} else if (isDateValue) {
return { ...data, type: "date" };
} else if (isDateTimeValue) {
return { ...data, type: "dateTime" };
} else if (isNumericValue) {
return { ...data, type: "numeric" };
}
return { ...data, type: "string" };
};
interface BaseFilterParam {
slug: string;
}
@ -282,26 +286,30 @@ interface DateTimeFilterParam extends BaseFilterParam {
interface DefaultFilterParam extends BaseFilterParam {
values: string[];
}
function getFilteredAttributeValue(
params: ProductListUrlFilters,
): Array<
interface NumericFilterParam extends BaseFilterParam {
valuesRange: GteLte<number>;
}
export type FilterParam =
| BooleanFilterParam
| BaseFilterParam
| DateTimeFilterParam
| DateFilterParam
| DateTimeFilterParam
| DefaultFilterParam
> {
return !!params.attributes
? Object.keys(params.attributes).map(key => {
const { isMulti, type, value } = parseFilterValue(params, key);
| NumericFilterParam;
export const parseFilterValue = (
params: ProductListUrlFilters,
key: string,
type: ProductListUrlFiltersAsDictWithMultipleValues,
): FilterParam => {
const value = params[type][key];
const isMulti = params[type][key].length > 1;
const name = { slug: key };
switch (type) {
case "boolean":
case ProductListUrlFiltersAsDictWithMultipleValues.booleanAttributes:
return { ...name, boolean: JSON.parse(value[0]) };
case "date":
case ProductListUrlFiltersAsDictWithMultipleValues.dateAttributes:
return {
...name,
date: getGteLteVariables({
@ -309,8 +317,7 @@ function getFilteredAttributeValue(
lte: isMulti ? value[1] || null : value[0],
}),
};
case "dateTime":
case ProductListUrlFiltersAsDictWithMultipleValues.dateTimeAttributes:
return {
...name,
dateTime: getGteLteVariables({
@ -318,21 +325,45 @@ function getFilteredAttributeValue(
lte: isMulti ? value[1] || null : value[0],
}),
};
case ProductListUrlFiltersAsDictWithMultipleValues.numericAttributes:
const [gte, lte] = value.map(v => parseFloat(v));
case "numeric":
return {
...name,
valuesRange: {
gte: value[0] || undefined,
lte: isMulti ? value[1] || undefined : value[0] || undefined,
gte: gte || undefined,
lte: isMulti ? lte || undefined : gte || undefined,
},
};
default:
return { ...name, values: value };
}
})
: null;
};
function getFilteredAttributeValue(
params: ProductListUrlFilters,
): FilterParam[] {
const attrValues = Object.values(
ProductListUrlFiltersAsDictWithMultipleValues,
).reduce<FilterParam[]>((attrValues, attributeType) => {
const attributes = params[attributeType];
if (!attributes) {
return attrValues;
}
return [
...attrValues,
...Object.keys(attributes).map(key =>
parseFilterValue(params, key, attributeType),
),
];
}, []);
if (!attrValues.length) {
return null;
}
return attrValues;
}
export function getFilterVariables(
@ -408,7 +439,7 @@ export function getFilterQueryParam(
case ProductFilterKeys.stock:
return getSingleEnumValueQueryParam(
filter as FilterElementRegular<ProductFilterKeys.stock>,
filter as FilterElementRegular<ProductFilterKeys>,
ProductListUrlFiltersEnum.stockStatus,
StockAvailability,
);