chore(medusa): Improve store list products (#3252)

* chore(medusa): Improve list products by 46.5 percents

* fix handler

* todo's

* Create tricky-terms-wash.md

---------

Co-authored-by: Oliver Windall Juhl <59018053+olivermrbl@users.noreply.github.com>
This commit is contained in:
Adrien de Peretti
2023-02-20 12:45:05 +01:00
committed by GitHub
co-authored by Oliver Windall Juhl
parent 13c200ad2f
commit 46547f29c7
4 changed files with 66 additions and 48 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"@medusajs/medusa": patch
---
feat(medusa): Improve store list products
@@ -475,10 +475,10 @@ describe("/store/products", () => {
}) })
describe("Product Category filtering", () => { describe("Product Category filtering", () => {
let categoryWithProduct, let categoryWithProduct
categoryWithoutProduct, let categoryWithoutProduct
nestedCategoryWithProduct, let nestedCategoryWithProduct
nested2CategoryWithProduct let nested2CategoryWithProduct
const nestedCategoryWithProductId = "nested-category-with-product-id" const nestedCategoryWithProductId = "nested-category-with-product-id"
const nested2CategoryWithProductId = "nested2-category-with-product-id" const nested2CategoryWithProductId = "nested2-category-with-product-id"
const categoryWithProductId = "category-with-product-id" const categoryWithProductId = "category-with-product-id"
@@ -11,7 +11,6 @@ import {
CartService, CartService,
ProductService, ProductService,
ProductVariantInventoryService, ProductVariantInventoryService,
RegionService,
} from "../../../../services" } from "../../../../services"
import SalesChannelFeatureFlag from "../../../../loaders/feature-flags/sales-channels" import SalesChannelFeatureFlag from "../../../../loaders/feature-flags/sales-channels"
import PricingService from "../../../../services/pricing" import PricingService from "../../../../services/pricing"
@@ -23,6 +22,7 @@ import { IsType } from "../../../../utils/validators/is-type"
import { FlagRouter } from "../../../../utils/flag-router" import { FlagRouter } from "../../../../utils/flag-router"
import PublishableAPIKeysFeatureFlag from "../../../../loaders/feature-flags/publishable-api-keys" import PublishableAPIKeysFeatureFlag from "../../../../loaders/feature-flags/publishable-api-keys"
import { cleanResponseData } from "../../../../utils/clean-response-data" import { cleanResponseData } from "../../../../utils/clean-response-data"
import { Cart, Product } from "../../../../models"
/** /**
* @oas [get] /products * @oas [get] /products
@@ -187,15 +187,16 @@ export default async (req, res) => {
req.scope.resolve("productVariantInventoryService") req.scope.resolve("productVariantInventoryService")
const pricingService: PricingService = req.scope.resolve("pricingService") const pricingService: PricingService = req.scope.resolve("pricingService")
const cartService: CartService = req.scope.resolve("cartService") const cartService: CartService = req.scope.resolve("cartService")
const regionService: RegionService = req.scope.resolve("regionService")
const validated = req.validatedQuery as StoreGetProductsParams const validated = req.validatedQuery as StoreGetProductsParams
let { let {
cart_id, cart_id,
region_id: regionId, region_id: regionId,
currency_code: currencyCode, currency_code: currencyCode,
...filterableFields ...filterableFields
} = req.filterableFields } = req.filterableFields
const listConfig = req.listConfig const listConfig = req.listConfig
// get only published products for store endpoint // get only published products for store endpoint
@@ -212,37 +213,50 @@ export default async (req, res) => {
} }
} }
const [rawProducts, count] = await productService.listAndCount( const promises: Promise<any>[] = []
filterableFields,
listConfig promises.push(productService.listAndCount(filterableFields, listConfig))
)
if (validated.cart_id) { if (validated.cart_id) {
const cart = await cartService.retrieve(validated.cart_id, { promises.push(
select: ["id", "region_id"], cartService.retrieve(validated.cart_id, {
}) select: ["id", "region_id"] as any,
const region = await regionService.retrieve(cart.region_id, { relations: ["region"],
select: ["id", "currency_code"], })
}) )
regionId = region.id
currencyCode = region.currency_code
} }
const pricedProducts = await pricingService.setProductPrices(rawProducts, { const [[rawProducts, count], cart] = (await Promise.all(promises)) as [
cart_id: cart_id, [Product[], number],
region_id: regionId, Cart
currency_code: currencyCode, ]
customer_id: req.user?.customer_id,
include_discount_prices: true,
})
const products = await productVariantInventoryService.setProductAvailability( if (validated.cart_id) {
pricedProducts, regionId = cart.region_id
filterableFields.sales_channel_id currencyCode = cart.region.currency_code
) }
// Create a new reference just for naming purpose
const computedProducts = rawProducts
// We can run them concurrently as the new properties are assigned to the references
// of the appropriate entity
await Promise.all([
pricingService.setProductPrices(computedProducts, {
cart_id: cart_id,
region_id: regionId,
currency_code: currencyCode,
customer_id: req.user?.customer_id,
include_discount_prices: true,
}),
productVariantInventoryService.setProductAvailability(
computedProducts,
filterableFields.sales_channel_id
),
])
res.json({ res.json({
products: cleanResponseData(products, req.allowedProperties || []), products: cleanResponseData(computedProducts, req.allowedProperties || []),
count, count,
offset: validated.offset, offset: validated.offset,
limit: validated.limit, limit: validated.limit,
+17 -18
View File
@@ -98,10 +98,10 @@ class PricingService extends TransactionBaseService {
* @param productRates - the tax rates that the product has applied * @param productRates - the tax rates that the product has applied
* @return The tax related variant prices. * @return The tax related variant prices.
*/ */
async calculateTaxes( calculateTaxes(
variantPricing: ProductVariantPricing, variantPricing: ProductVariantPricing,
productRates: TaxServiceRate[] productRates: TaxServiceRate[]
): Promise<TaxedPricing> { ): TaxedPricing {
const rate = productRates.reduce( const rate = productRates.reduce(
(accRate: number, nextTaxRate: TaxServiceRate) => { (accRate: number, nextTaxRate: TaxServiceRate) => {
return accRate + (nextTaxRate.rate || 0) / 100 return accRate + (nextTaxRate.rate || 0) / 100
@@ -167,6 +167,10 @@ class PricingService extends TransactionBaseService {
): Promise<ProductVariantPricing> { ): Promise<ProductVariantPricing> {
context.price_selection.tax_rates = taxRates context.price_selection.tax_rates = taxRates
// TODO: Should think about updating the price strategy to take
// a collection of variantId so that the strategy can do a bulk computation
// and therefore improve the overall perf. Then the method can return a map
// of variant pricing Map<id, variant pricing>
const pricing = await this.priceSelectionStrategy const pricing = await this.priceSelectionStrategy
.withTransaction(this.activeManager_) .withTransaction(this.activeManager_)
.calculateVariantPrice(variantId, context.price_selection) .calculateVariantPrice(variantId, context.price_selection)
@@ -186,7 +190,7 @@ class PricingService extends TransactionBaseService {
} }
if (context.automatic_taxes && context.price_selection.region_id) { if (context.automatic_taxes && context.price_selection.region_id) {
const taxResults = await this.calculateTaxes(pricingResult, taxRates) const taxResults = this.calculateTaxes(pricingResult, taxRates)
pricingResult.original_price_incl_tax = taxResults.original_price_incl_tax pricingResult.original_price_incl_tax = taxResults.original_price_incl_tax
pricingResult.calculated_price_incl_tax = pricingResult.calculated_price_incl_tax =
@@ -360,6 +364,8 @@ class PricingService extends TransactionBaseService {
const pricings = {} const pricings = {}
await Promise.all( await Promise.all(
variants.map(async ({ id }) => { variants.map(async ({ id }) => {
// TODO: Depending on the todo inside the getProductVariantPricing_ we would just have
// to return the map
const variantPricing = await this.getProductVariantPricing_( const variantPricing = await this.getProductVariantPricing_(
id, id,
taxRates, taxRates,
@@ -451,28 +457,21 @@ class PricingService extends TransactionBaseService {
return product return product
} }
// TODO: Depending on the todo in getProductPricing_ update this method to
// consume the map to assign the data to the variants
const variantPricing = await this.getProductPricing_( const variantPricing = await this.getProductPricing_(
product.id, product.id,
product.variants, product.variants,
pricingContext pricingContext
) )
const pricedVariants = product.variants.map( product.variants.map((productVariant): PricedVariant => {
(productVariant): PricedVariant => { const pricing = variantPricing[productVariant.id]
const pricing = variantPricing[productVariant.id] Object.assign(productVariant, pricing)
return { return productVariant as unknown as PricedVariant
...productVariant, })
...pricing,
}
}
)
const pricedProduct = { return product
...product,
variants: pricedVariants,
}
return pricedProduct
}) })
) )
} }