fix: Allow filtering products by variant options in store (#7784)
This commit is contained in:
1 parent
ee35379e21
commit
944051a951
6 files changed
+246
-508
No files matched your search
@@ -702,6 +702,12 @@ export interface FilterableProductProps
|
||||
*/
|
||||
value?: string[]
|
||||
}
|
||||
/**
|
||||
* Filters on a product's variant properties.
|
||||
*/
|
||||
variants?: {
|
||||
options: { value: string; option_id: string }
|
||||
}
|
||||
/**
|
||||
* Filter a product by the ID of the associated type
|
||||
*/
|
||||
|
||||
@@ -22,6 +22,7 @@ export const StoreGetProductVariantsParams = createFindParams({
|
||||
q: z.string().optional(),
|
||||
id: z.union([z.string(), z.array(z.string())]).optional(),
|
||||
status: ProductStatusEnum.array().optional(),
|
||||
options: z.object({ value: z.string(), option_id: z.string() }).optional(),
|
||||
created_at: createOperatorMap().optional(),
|
||||
updated_at: createOperatorMap().optional(),
|
||||
deleted_at: createOperatorMap().optional(),
|
||||
@@ -39,7 +40,16 @@ export const StoreGetProductsParams = createFindParams({
|
||||
.object({
|
||||
region_id: z.string().optional(),
|
||||
currency_code: z.string().optional(),
|
||||
variants: StoreGetProductVariantsParams.optional(),
|
||||
variants: z
|
||||
.object({
|
||||
status: ProductStatusEnum.array().optional(),
|
||||
options: z
|
||||
.object({ value: z.string(), option_id: z.string() })
|
||||
.optional(),
|
||||
$and: z.lazy(() => StoreGetProductsParams.array()).optional(),
|
||||
$or: z.lazy(() => StoreGetProductsParams.array()).optional(),
|
||||
})
|
||||
.optional(),
|
||||
$and: z.lazy(() => StoreGetProductsParams.array()).optional(),
|
||||
$or: z.lazy(() => StoreGetProductsParams.array()).optional(),
|
||||
})
|
||||
|
||||
@@ -9,18 +9,41 @@ import {
|
||||
prepareRetrieveQuery,
|
||||
} from "../../utils/get-query-config"
|
||||
import { zodValidator } from "./zod-helper"
|
||||
import { MedusaError } from "@medusajs/utils"
|
||||
|
||||
/**
|
||||
* Normalize an input query, especially from array like query params to an array type
|
||||
* e.g: /admin/orders/?fields[]=id,status,cart_id becomes { fields: ["id", "status", "cart_id"] }
|
||||
*
|
||||
* We only support up to 2 levels of depth for query params in order to have a somewhat readable query param, and limit possible performance issues
|
||||
*/
|
||||
const normalizeQuery = (req: MedusaRequest) => {
|
||||
return Object.entries(req.query).reduce((acc, [key, val]) => {
|
||||
if (Array.isArray(val) && val.length === 1) {
|
||||
acc[key] = (val as string[])[0].split(",")
|
||||
} else {
|
||||
acc[key] = val
|
||||
let normalizedValue = val
|
||||
if (Array.isArray(val) && val.length === 1 && typeof val[0] === "string") {
|
||||
normalizedValue = val[0].split(",")
|
||||
}
|
||||
|
||||
if (key.includes(".")) {
|
||||
const [parent, child, ...others] = key.split(".")
|
||||
if (others.length > 0) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_ARGUMENT,
|
||||
`Key accessor more than 2 levels deep: ${key}`
|
||||
)
|
||||
}
|
||||
|
||||
if (!acc[parent]) {
|
||||
acc[parent] = {}
|
||||
}
|
||||
acc[parent] = {
|
||||
...acc[parent],
|
||||
[child]: normalizedValue,
|
||||
}
|
||||
} else {
|
||||
acc[key] = normalizedValue
|
||||
}
|
||||
|
||||
return acc
|
||||
}, {})
|
||||
}
|
||||
|
||||
+38
-9
@@ -803,6 +803,8 @@ moduleIntegrationTestRunner<IProductModuleService>({
|
||||
})
|
||||
|
||||
describe("list", function () {
|
||||
let productOneData
|
||||
let productTwoData
|
||||
beforeEach(async () => {
|
||||
const collections = await createCollections(
|
||||
MikroOrmWrapper.forkManager(),
|
||||
@@ -812,16 +814,20 @@ moduleIntegrationTestRunner<IProductModuleService>({
|
||||
productCollectionOne = collections[0]
|
||||
productCollectionTwo = collections[1]
|
||||
|
||||
const productOneData = buildProductAndRelationsData({
|
||||
collection_id: productCollectionOne.id,
|
||||
})
|
||||
const resp = await service.createProducts([
|
||||
buildProductAndRelationsData({
|
||||
collection_id: productCollectionOne.id,
|
||||
options: [{ title: "size", values: ["large", "small"] }],
|
||||
variants: [{ title: "variant 1", options: { size: "small" } }],
|
||||
}),
|
||||
buildProductAndRelationsData({
|
||||
collection_id: productCollectionTwo.id,
|
||||
tags: [],
|
||||
}),
|
||||
])
|
||||
|
||||
const productTwoData = buildProductAndRelationsData({
|
||||
collection_id: productCollectionTwo.id,
|
||||
tags: [],
|
||||
})
|
||||
|
||||
await service.createProducts([productOneData, productTwoData])
|
||||
productOneData = resp[0]
|
||||
productTwoData = resp[1]
|
||||
})
|
||||
|
||||
it("should return a list of products scoped by collection id", async () => {
|
||||
@@ -843,6 +849,29 @@ moduleIntegrationTestRunner<IProductModuleService>({
|
||||
])
|
||||
})
|
||||
|
||||
it("should return a list of products scoped by variant options", async () => {
|
||||
const productsWithVariants = await service.listProducts(
|
||||
{
|
||||
variants: {
|
||||
options: {
|
||||
option_id: productOneData.options[0].id,
|
||||
value: "small",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
relations: ["variants", "variants.options"],
|
||||
}
|
||||
)
|
||||
|
||||
expect(productsWithVariants).toHaveLength(1)
|
||||
expect(productsWithVariants).toEqual([
|
||||
expect.objectContaining({
|
||||
id: productOneData.id,
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("should return empty array when querying for a collection that doesnt exist", async () => {
|
||||
const products = await service.listProducts(
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user