Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
6 changes: 6 additions & 0 deletions .changeset/moody-plants-tickle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"integration-tests-http": patch
"@medusajs/medusa": patch
---

feat(promotions): add product_variant attribute
Original file line number Diff line number Diff line change
Expand Up @@ -3756,6 +3756,14 @@ medusaIntegrationTestRunner({
field_type: "multiselect",
operators: expect.anything(),
}),
expect.objectContaining({
id: "product_variant",
value: "items.variant.id",
label: "Product Variant",
required: false,
field_type: "multiselect",
operators: expect.anything(),
}),
expect.objectContaining({
id: "product_category",
value: "items.product.categories.id",
Expand Down Expand Up @@ -4083,6 +4091,22 @@ medusaIntegrationTestRunner({
])
)

response = await api.get(
`/admin/promotions/rule-value-options/target-rules/product_variant`,
adminHeaders
)

expect(response.status).toEqual(200)
expect(response.data.values.length).toEqual(4) // 2 products × 2 variants each
expect(response.data.values).toEqual(
expect.arrayContaining([
expect.objectContaining({ value: product1.variants[0].id }),
expect.objectContaining({ value: product1.variants[1].id }),
expect.objectContaining({ value: product2.variants[0].id }),
expect.objectContaining({ value: product2.variants[1].id }),
])
)

const soType1 = (
await api.post(
"/admin/shipping-option-types",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,30 @@ export const GET = async (
filters: filterableFields,
...req.queryConfig.pagination,
},
fields: [queryConfig.labelAttr, queryConfig.valueAttr],
fields: ruleAttributeId === "product_variant"
? ["title", "sku", queryConfig.valueAttr]
: [queryConfig.labelAttr, queryConfig.valueAttr],
})
)

const values = rows.map((r) => ({
label: r[queryConfig.labelAttr],
value: r[queryConfig.valueAttr],
}))
const values = rows.map((r) => {
let label = r[queryConfig.labelAttr]

if (ruleAttributeId === "product_variant") {
const title = r.title
const sku = r.sku
if (sku) {
label = `${title} (SKU: ${sku})`
} else {
label = title
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Ambiguous product variant labels in response

The generated label for product variants includes only the variant title and SKU. This results in indistinguishable options when multiple products share generic variant names (e.g., "Small"). Users cannot differentiate which product a variant belongs to without the product title in the label.

Fix in Cursor Fix in Web

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Ambiguous product variant labels in response

The generated label for product variants includes only the variant title and SKU. This results in indistinguishable options when multiple products share generic variant names (e.g., "Small" or "Red") or when SKUs are similar. Users cannot differentiate which product a variant belongs to without the product title explicitly included in the label.

Fix in Cursor Fix in Web


return {
label,
value: r[queryConfig.valueAttr],
}
})

res.json({
values,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,14 @@ const itemsAttributes = [
field_type: "multiselect",
operators: Object.values(operatorsMap),
},
{
id: "product_variant",
value: "items.variant.id",
label: "Product Variant",
required: false,
field_type: "multiselect",
operators: Object.values(operatorsMap),
},
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Variant ID Path Mismatch

The product_variant attribute uses value: "items.variant.id" but the test expects "items.variant_id". Since variant_id is a direct field on cart items (not a nested relation), the path should be "items.variant_id" to match the API response structure and the test expectations.

Fix in Cursor Fix in Web

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Align Data Paths for API and Rule Evaluation

The product_variant attribute uses value: "items.variant.id" but the test expects "items.variant_id". Since variant_id is a direct field on cart items (not a nested relation), the path should be "items.variant_id" to match the API response structure and enable proper rule evaluation.

Fix in Cursor Fix in Web

{
id: "product_category",
value: "items.product.categories.id",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ export const ruleQueryConfigurations = {
labelAttr: "title",
valueAttr: "id",
},
product_variant: {
entryPoint: "product_variant",
labelAttr: "sku",
valueAttr: "id",
},
product_category: {
entryPoint: "product_category",
labelAttr: "name",
Expand Down