Feat(medusa, medusa-js, medusa-react): Include sales channels in related queries as an optional expand parameter (#1816)

**What**
- Add `transformQuery` to get endpoints for product, order and cart
  - ensure that the default relations (when getting a singular entity) includes sales channels when enabled
- Add `EmptyQueryParams` class in common types to prevent query parameters while using `transformQuery`
- update product-, order- and cartFactory to include sales channels if provided
- remove `packages/medusa/src/controllers/products/admin-list-products.ts`

**Testing** 
- expands sales channel for single order
- expands sales channels for orders with expand parameter 
- returns single product with sales channel 
- expands sales channels for products with expand parameter
- returns cart with sales channel for single cart

Fixes CORE-293

Co-authored-by: Sebastian Rindom <7554214+srindom@users.noreply.github.com>
Co-authored-by: Adrien de Peretti <25098370+adrien2p@users.noreply.github.com>
This commit is contained in:
Philip Korsholm
2022-07-11 16:45:01 +00:00
committed by GitHub
co-authored by Sebastian Rindom Adrien de Peretti
parent fb4cfc3c3c
commit 19f35ba6aa
22 changed files with 510 additions and 255 deletions
@@ -1,40 +1,59 @@
import { getConfigFile } from "medusa-core-utils"
import { getConfigFile } from "medusa-core-utils"
import { Column, ColumnOptions, Entity, EntityOptions } from "typeorm"
import featureFlagsLoader from "../loaders/feature-flags"
import path from "path"
import { ConfigModule } from "../types/global"
import { FlagRouter } from "./flag-router"
/**
* If that file is required in a non node environment then the setImmediate timer does not exists.
* This can happen when a client package require a server based package and that one of the import
* require to import that file which is using the setImmediate.
* In order to take care of those cases, the setImmediate timer will use the one provided by the api (node)
* if possible and will provide a mock in a browser like environment.
*/
let setImmediate_
try {
setImmediate_ = setImmediate
} catch (e) {
console.warn(
"[feature-flag-decorator.ts] setImmediate will use a mock, this happen when this file is required in a browser environment and should not impact you"
)
setImmediate_ = ((callback: () => void | Promise<void>) => callback())
}
export function FeatureFlagColumn(
featureFlag: string,
columnOptions: ColumnOptions = {}
): PropertyDecorator {
const featureFlagRouter = getFeatureFlagRouter()
return function (target, propertyName) {
setImmediate_((): any => {
const featureFlagRouter = getFeatureFlagRouter()
if (!featureFlagRouter.isFeatureEnabled(featureFlag)) {
return (): void => {
// noop
}
if (!featureFlagRouter.isFeatureEnabled(featureFlag)) {
return
}
Column(columnOptions)(target, propertyName)
})
}
return Column(columnOptions)
}
export function FeatureFlagDecorators(
featureFlag: string,
decorators: PropertyDecorator[]
): PropertyDecorator {
const featureFlagRouter = getFeatureFlagRouter()
return function (target, propertyName) {
setImmediate_((): any => {
const featureFlagRouter = getFeatureFlagRouter()
if (!featureFlagRouter.isFeatureEnabled(featureFlag)) {
return (): void => {
// noop
}
}
// eslint-disable-next-line @typescript-eslint/ban-types
return (target: Object, propertyKey: string | symbol): void => {
decorators.forEach((decorator) => {
decorator(target, propertyKey)
if (!featureFlagRouter.isFeatureEnabled(featureFlag)) {
return
}
decorators.forEach((decorator: PropertyDecorator) => {
decorator(target, propertyName)
})
})
}
}
@@ -44,12 +63,10 @@ export function FeatureFlagEntity(
name?: string,
options?: EntityOptions
): ClassDecorator {
// eslint-disable-next-line @typescript-eslint/ban-types
return function (target: Function): void {
target["isFeatureEnabled"] = function (): boolean {
const featureFlagRouter = getFeatureFlagRouter()
// const featureFlagRouter = featureFlagsLoader(configModule)
return featureFlagRouter.isFeatureEnabled(featureFlag)
}
Entity(name, options)(target)
@@ -27,14 +27,14 @@ export function getRetrieveConfig<TModel extends BaseEntity>(
): FindConfig<TModel> {
let includeFields: (keyof TModel)[] = []
if (typeof fields !== "undefined") {
includeFields = Array
.from(new Set([...fields, "id"]))
.map(field => (typeof field === "string") ? field.trim() : field) as (keyof TModel)[]
includeFields = Array.from(new Set([...fields, "id"])).map((field) =>
typeof field === "string" ? field.trim() : field
) as (keyof TModel)[]
}
let expandFields: string[] = []
if (typeof expand !== "undefined") {
expandFields = expand.map(expandRelation => expandRelation.trim())
expandFields = expand.map((expandRelation) => expandRelation.trim())
}
return {
@@ -106,7 +106,10 @@ export function prepareListQuery<
orderBy = { [order]: "ASC" }
}
if (queryConfig?.allowedFields?.length && !queryConfig?.allowedFields.includes(orderField)) {
if (
queryConfig?.allowedFields?.length &&
!queryConfig?.allowedFields.includes(orderField)
) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Order field ${orderField} is not valid`