hotfix(medusa): Optimize product retrieval (#185)

This commit is contained in:
Oliver Windall Juhl
2021-02-26 07:58:59 +01:00
committed by GitHub
parent 3ca04a8ca1
commit 4d708d2306
4 changed files with 111 additions and 28 deletions
@@ -14,13 +14,23 @@ export default async (req, res) => {
selector.q = req.query.q selector.q = req.query.q
} }
let includeFields = []
if ("fields" in req.query) {
includeFields = req.query.fields.split(",")
}
let expandFields = []
if ("expand" in req.query) {
expandFields = req.query.expand.split(",")
}
if ("is_giftcard" in req.query) { if ("is_giftcard" in req.query) {
selector.is_giftcard = req.query.is_giftcard === "true" selector.is_giftcard = req.query.is_giftcard === "true"
} }
const listConfig = { const listConfig = {
select: defaultFields, select: includeFields.length ? includeFields : defaultFields,
relations: defaultRelations, relations: expandFields.length ? expandFields : defaultRelations,
skip: offset, skip: offset,
take: limit, take: limit,
} }
+50 -2
View File
@@ -1,5 +1,53 @@
import { EntityRepository, Repository } from "typeorm" import { flatten, groupBy, map, merge } from "lodash"
import { EntityRepository, FindManyOptions, Repository } from "typeorm"
import { Product } from "../models/product" import { Product } from "../models/product"
@EntityRepository(Product) @EntityRepository(Product)
export class ProductRepository extends Repository<Product> {} export class ProductRepository extends Repository<Product> {
public async findWithRelations(
relations: Array<keyof Product> = [],
optionsWithoutRelations: Omit<FindManyOptions<Product>, "relations"> = {}
): Promise<Product[]> {
const entities = await this.find(optionsWithoutRelations)
const entitiesIds = entities.map(({ id }) => id)
const groupedRelations = {}
for (const rel of relations) {
const [topLevel] = rel.split(".")
if (groupedRelations[topLevel]) {
groupedRelations[topLevel].push(rel)
} else {
groupedRelations[topLevel] = [rel]
}
}
const entitiesIdsWithRelations = await Promise.all(
Object.entries(groupedRelations).map(([_, rels]) => {
return this.findByIds(entitiesIds, {
select: ["id"],
relations: rels as string[],
})
})
).then(flatten)
const entitiesAndRelations = entitiesIdsWithRelations.concat(entities)
const entitiesAndRelationsById = groupBy(entitiesAndRelations, "id")
return map(entitiesAndRelationsById, entityAndRelations =>
merge({}, ...entityAndRelations)
)
}
public async findOneWithRelations(
relations: Array<keyof Product> = [],
optionsWithoutRelations: Omit<FindManyOptions<Product>, "relations"> = {}
): Promise<Product> {
// Limit 1
optionsWithoutRelations.take = 1
const result = await this.findWithRelations(
relations,
optionsWithoutRelations
)
return result[0]
}
}
@@ -11,7 +11,8 @@ const eventBusService = {
describe("ProductService", () => { describe("ProductService", () => {
describe("retrieve", () => { describe("retrieve", () => {
const productRepo = MockRepository({ const productRepo = MockRepository({
findOne: () => Promise.resolve({ id: IdMap.getId("ironman") }), findOneWithRelations: () =>
Promise.resolve({ id: IdMap.getId("ironman") }),
}) })
const productService = new ProductService({ const productService = new ProductService({
manager: MockManager, manager: MockManager,
@@ -25,8 +26,8 @@ describe("ProductService", () => {
it("successfully retrieves a product", async () => { it("successfully retrieves a product", async () => {
const result = await productService.retrieve(IdMap.getId("ironman")) const result = await productService.retrieve(IdMap.getId("ironman"))
expect(productRepo.findOne).toHaveBeenCalledTimes(1) expect(productRepo.findOneWithRelations).toHaveBeenCalledTimes(1)
expect(productRepo.findOne).toHaveBeenCalledWith({ expect(productRepo.findOneWithRelations).toHaveBeenCalledWith(undefined, {
where: { id: IdMap.getId("ironman") }, where: { id: IdMap.getId("ironman") },
}) })
@@ -42,7 +43,7 @@ describe("ProductService", () => {
options: [], options: [],
collection: { id: IdMap.getId("cat"), title: "Suits" }, collection: { id: IdMap.getId("cat"), title: "Suits" },
}), }),
findOne: () => ({ findOneWithRelations: () => ({
id: IdMap.getId("ironman"), id: IdMap.getId("ironman"),
title: "Suit", title: "Suit",
options: [], options: [],
@@ -137,7 +138,7 @@ describe("ProductService", () => {
describe("update", () => { describe("update", () => {
const productRepository = MockRepository({ const productRepository = MockRepository({
findOne: query => { findOneWithRelations: (rels, query) => {
if (query.where.id === IdMap.getId("ironman&co")) { if (query.where.id === IdMap.getId("ironman&co")) {
return Promise.resolve({ return Promise.resolve({
id: IdMap.getId("ironman&co"), id: IdMap.getId("ironman&co"),
@@ -322,7 +323,7 @@ describe("ProductService", () => {
describe("addOption", () => { describe("addOption", () => {
const productRepository = MockRepository({ const productRepository = MockRepository({
findOne: query => findOneWithRelations: query =>
Promise.resolve({ Promise.resolve({
id: IdMap.getId("ironman"), id: IdMap.getId("ironman"),
options: [{ title: "Color" }], options: [{ title: "Color" }],
@@ -395,7 +396,7 @@ describe("ProductService", () => {
describe("reorderVariants", () => { describe("reorderVariants", () => {
const productRepository = MockRepository({ const productRepository = MockRepository({
findOne: query => findOneWithRelations: query =>
Promise.resolve({ Promise.resolve({
id: IdMap.getId("ironman"), id: IdMap.getId("ironman"),
variants: [{ id: IdMap.getId("green") }, { id: IdMap.getId("blue") }], variants: [{ id: IdMap.getId("green") }, { id: IdMap.getId("blue") }],
@@ -453,7 +454,7 @@ describe("ProductService", () => {
describe("reorderOptions", () => { describe("reorderOptions", () => {
const productRepository = MockRepository({ const productRepository = MockRepository({
findOne: query => findOneWithRelations: query =>
Promise.resolve({ Promise.resolve({
id: IdMap.getId("ironman"), id: IdMap.getId("ironman"),
options: [ options: [
@@ -519,7 +520,7 @@ describe("ProductService", () => {
describe("updateOption", () => { describe("updateOption", () => {
const productRepository = MockRepository({ const productRepository = MockRepository({
findOne: query => findOneWithRelations: query =>
Promise.resolve({ Promise.resolve({
id: IdMap.getId("ironman"), id: IdMap.getId("ironman"),
options: [ options: [
@@ -594,7 +595,7 @@ describe("ProductService", () => {
describe("deleteOption", () => { describe("deleteOption", () => {
const productRepository = MockRepository({ const productRepository = MockRepository({
findOne: query => findOneWithRelations: query =>
Promise.resolve({ Promise.resolve({
id: IdMap.getId("ironman"), id: IdMap.getId("ironman"),
variants: [ variants: [
+38 -14
View File
@@ -93,6 +93,17 @@ class ProductService extends BaseService {
const query = this.buildQuery_(selector, config) const query = this.buildQuery_(selector, config)
if (config.relations && config.relations.length > 0) {
query.relations = config.relations
}
if (config.select && config.select.length > 0) {
query.select = config.select
}
const rels = query.relations
delete query.relations
if (q) { if (q) {
const where = query.where const where = query.where
@@ -122,7 +133,7 @@ class ProductService extends BaseService {
} }
} }
return productRepo.find(query) return productRepo.findWithRelations(rels, query)
} }
/** /**
@@ -143,20 +154,33 @@ class ProductService extends BaseService {
* @return {Promise<Product>} the result of the find one operation. * @return {Promise<Product>} the result of the find one operation.
*/ */
async retrieve(productId, config = {}) { async retrieve(productId, config = {}) {
return this.atomicPhase_(async manager => { const productRepo = this.manager_.getCustomRepository(
const productRepo = manager.getCustomRepository(this.productRepository_) this.productRepository_
const validatedId = this.validateId_(productId) )
const query = this.buildQuery_({ id: validatedId }, config) const validatedId = this.validateId_(productId)
const product = await productRepo.findOne(query)
if (!product) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`Product with id: ${productId} was not found`
)
}
return product const query = { where: { id: validatedId } }
})
if (config.relations && config.relations.length > 0) {
query.relations = config.relations
}
if (config.select && config.select.length > 0) {
query.select = config.select
}
const rels = query.relations
delete query.relations
const product = await productRepo.findOneWithRelations(rels, query)
if (!product) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`Product with id: ${productId} was not found`
)
}
return product
} }
/** /**