From 8dd6f19def78d86208dfaca6f150b73f705d30b8 Mon Sep 17 00:00:00 2001 From: adrien2p Date: Wed, 19 Oct 2022 16:58:14 +0200 Subject: [PATCH 01/13] feat(medusa): Align product import and export --- .../content/advanced/backend/endpoints/add.md | 64 +- .../advanced/backend/plugins/create.md | 107 ++- .../references/js-client/classes/Admin.md | 60 +- .../src/interfaces/batch-job-strategy.ts | 2 +- .../__tests__/batch-jobs/product/export.ts | 2 +- .../__tests__/batch-jobs/product/import.ts | 2 +- .../strategies/batch-jobs/product/export.ts | 149 ++-- .../strategies/batch-jobs/product/import.ts | 302 +------- .../strategies/batch-jobs/product/index.ts | 337 -------- .../strategies/batch-jobs/product/types.ts | 77 -- .../product/types/columns-definition.ts | 729 ++++++++++++++++++ .../batch-jobs/product/types/types.ts | 111 +++ .../strategies/batch-jobs/product/utils.ts | 2 +- 13 files changed, 1124 insertions(+), 820 deletions(-) delete mode 100644 packages/medusa/src/strategies/batch-jobs/product/index.ts delete mode 100644 packages/medusa/src/strategies/batch-jobs/product/types.ts create mode 100644 packages/medusa/src/strategies/batch-jobs/product/types/columns-definition.ts create mode 100644 packages/medusa/src/strategies/batch-jobs/product/types/types.ts diff --git a/docs/content/advanced/backend/endpoints/add.md b/docs/content/advanced/backend/endpoints/add.md index 9e78a59e95e48..9c97eb5b8dbfd 100644 --- a/docs/content/advanced/backend/endpoints/add.md +++ b/docs/content/advanced/backend/endpoints/add.md @@ -4,11 +4,14 @@ In this document, you’ll learn how to create endpoints in your Medusa server. ## Overview -Custom endpoints reside under the `src/api` directory in your Medusa Backend. They're defined in a TypeScript or JavaScript file that is named `index` (for example, `index.ts`). This file should export a function that returns an Express router. +Custom endpoints reside under the `src/api` directory in your Medusa Backend. They're defined in a TypeScript or +JavaScript file that is named `index` (for example, `types.ts`). This file should export a function that returns an +Express router. ## Implementation -To create a new endpoint, start by creating a new file in `src/api` called `index.ts`. At its basic format, `index.ts` should look something like this: +To create a new endpoint, start by creating a new file in `src/api` called `types.ts`. At its basic format, `types.ts` +should look something like this: ```ts import { Router } from "express" @@ -29,7 +32,8 @@ export default (rootDirectory, pluginOptions) => { This exports a function that returns an Express router. The function receives two parameters: - `rootDirectory` is the absolute path to the root directory that your server is running from. -- `pluginOptions` is an object that has your plugin's options. If your API route is not implemented in a plugin, then it will be an empty object. +- `pluginOptions` is an object that has your plugin's options. If your API route is not implemented in a plugin, then it + will be an empty object. ### Endpoints Path @@ -37,23 +41,28 @@ Your endpoint can be under any path you wish. By Medusa’s conventions: -- All Storefront REST APIs are prefixed by `/store`. For example, the `/store/products` endpoint lets you retrieve the products to display them on your storefront. -- All Admin REST APIs are prefixed by `/admin`. For example, the `/admin/products` endpoint lets you retrieve the products to display them on your Admin. +- All Storefront REST APIs are prefixed by `/store`. For example, the `/store/products` endpoint lets you retrieve the + products to display them on your storefront. +- All Admin REST APIs are prefixed by `/admin`. For example, the `/admin/products` endpoint lets you retrieve the + products to display them on your Admin. -You can also create endpoints that don't reside under these two prefixes, similar to the `hello` endpoint in the previous example. +You can also create endpoints that don't reside under these two prefixes, similar to the `hello` endpoint in the +previous example. ## CORS Configuration -If you’re adding a storefront or admin endpoint and you want to access these endpoints from the storefront or Medusa admin, you need to pass your endpoints Cross-Origin Resource Origin (CORS) options using the `cors` package. +If you’re adding a storefront or admin endpoint and you want to access these endpoints from the storefront or Medusa +admin, you need to pass your endpoints Cross-Origin Resource Origin (CORS) options using the `cors` package. -First, you need to import your Medusa configurations along with the `cors` library: +First, you need to import your Medusa configurations along with the `cors` library: ```ts import cors from "cors" import { projectConfig } from "../../medusa-config" ``` -Then, create an object that will hold the Cross-Origin Resource Sharing (CORS) configurations. If it’s a storefront endpoint, pass the `origin` property storefront options: +Then, create an object that will hold the Cross-Origin Resource Sharing (CORS) configurations. If it’s a storefront +endpoint, pass the `origin` property storefront options: ```ts const corsOptions = { @@ -71,7 +80,7 @@ const corsOptions = { } ``` -Finally, for each route you add, create an `OPTIONS` request and add `cors` as a middleware for the route: +Finally, for each route you add, create an `OPTIONS` request and add `cors` as a middleware for the route: ```ts router.options("/admin/hello", cors(corsOptions)) @@ -84,7 +93,7 @@ router.get("/admin/hello", cors(corsOptions), (req, res) => { ### Same File -You can add more than one endpoint in `src/api/index.ts`: +You can add more than one endpoint in `src/api/types.ts`: ```ts router.options("/store/hello", cors(storeCorsOptions)) @@ -106,7 +115,7 @@ router.get("/admin/hello", cors(adminCorsOptions), (req, res) => { Alternatively, you can add multiple files for each endpoint or set of endpoints for readability and easy maintenance. -To do that with the previous example, first, create the file `src/api/store.ts` with the following content: +To do that with the previous example, first, create the file `src/api/store.ts` with the following content: ```ts import cors from "cors" @@ -126,9 +135,9 @@ export default (router) => { } ``` -You export a function that receives an Express router as a parameter and adds the endpoint `store/hello` to it. +You export a function that receives an Express router as a parameter and adds the endpoint `store/hello` to it. -Next, create the file `src/api/admin.ts` with the following content: +Next, create the file `src/api/admin.ts` with the following content: ```ts import cors from "cors" @@ -148,9 +157,9 @@ export default (router) => { } ``` -Again, you export a function that receives an Express router as a parameter and adds the endpoint `admin/hello` to it. +Again, you export a function that receives an Express router as a parameter and adds the endpoint `admin/hello` to it. -Finally, in `src/api/index.ts` import the two functions at the beginning of the file: +Finally, in `src/api/types.ts` import the two functions at the beginning of the file: ```ts import storeRoutes from "./store" @@ -176,7 +185,7 @@ Protected routes are routes that should be accessible by logged-in customers or ### Protect Store Routes -To make a storefront route protected, first, import the `authenticate-customer` middleware: +To make a storefront route protected, first, import the `authenticate-customer` middleware: ```ts import authenticate from "@medusajs/medusa/dist/api/middlewares/authenticate-customer" @@ -195,13 +204,14 @@ router.get("/store/hello", cors(corsOptions), authenticate(), async (req, res) = }) ``` -Please note that the endpoint is still accessible by all users, however, you’ll be able to access the current logged-in customer if there’s any. +Please note that the endpoint is still accessible by all users, however, you’ll be able to access the current logged-in +customer if there’s any. To disallow guest customers from accessing the endpoint, you can throw an error if `req.user` is `false`. ### Protect Admin Routes -To make an admin route protected, first, import the `authenticate` middleware: +To make an admin route protected, first, import the `authenticate` middleware: ```ts import authenticate from "@medusajs/medusa/dist/api/middlewares/authenticate" @@ -225,9 +235,12 @@ Now, only authenticated users can access this endpoint. ## Use Services -Services in Medusa bundle a set of functionalities into one class. Then, you can use that class anywhere in your backend. For example, you can use the `ProductService` to retrieve products or perform operations like creating or updating a product. +Services in Medusa bundle a set of functionalities into one class. Then, you can use that class anywhere in your +backend. For example, you can use the `ProductService` to retrieve products or perform operations like creating or +updating a product. -You can retrieve any registered service in your endpoint using `req.scope.resolve` passing it the service’s registration name. +You can retrieve any registered service in your endpoint using `req.scope.resolve` passing it the service’s registration +name. Here’s an example of an endpoint that retrieves the count of products in your store: @@ -243,11 +256,13 @@ router.get("/admin/products/count", cors(corsOptions), authenticate(), (req, res }) ``` -The `productService` has a `count` method that returns a Promise. This Promise resolves to the count of the products. You return a JSON of the product count. +The `productService` has a `count` method that returns a Promise. This Promise resolves to the count of the products. +You return a JSON of the product count. ## Building Files -Custom endpoints must be transpiled and moved to the `dist` directory. This happens when you run your server using `medusa develop` and while it’s running, and when you run the following command: +Custom endpoints must be transpiled and moved to the `dist` directory. This happens when you run your server +using `medusa develop` and while it’s running, and when you run the following command: ```bash npm2yarn npm run build @@ -255,5 +270,6 @@ npm run build ## What’s Next -- Check out the available [Admin](https://docs.medusajs.com/api/admin/) and [Storefront](https://docs.medusajs.com/api/store/) APIs. +- Check out the available [Admin](https://docs.medusajs.com/api/admin/) + and [Storefront](https://docs.medusajs.com/api/store/) APIs. - Learn how to create a [Service](./../services/create-service.md). diff --git a/docs/content/advanced/backend/plugins/create.md b/docs/content/advanced/backend/plugins/create.md index b78e384660526..f61b0e9816665 100644 --- a/docs/content/advanced/backend/plugins/create.md +++ b/docs/content/advanced/backend/plugins/create.md @@ -1,10 +1,12 @@ # Create a Plugin -In this document, you’ll learn how to create a plugin and publish it. If you’re interested to learn more about what plugins are and where to find available official and community plugins, check out the [overview document](overview.md). +In this document, you’ll learn how to create a plugin and publish it. If you’re interested to learn more about what +plugins are and where to find available official and community plugins, check out the [overview document](overview.md). ## Prerequisites -This guide uses the Medusa CLI throughout different steps. If you don’t have the Medusa CLI installed you can install it with the following command: +This guide uses the Medusa CLI throughout different steps. If you don’t have the Medusa CLI installed you can install it +with the following command: ```bash npm2yarn npm install @medusajs/medusa-cli -g @@ -12,27 +14,32 @@ npm install @medusajs/medusa-cli -g :::note -If you run into any errors while installing the CLI tool, check out the [troubleshooting guide](../../../troubleshooting/cli-installation-errors.mdx). +If you run into any errors while installing the CLI tool, check out +the [troubleshooting guide](../../../troubleshooting/cli-installation-errors.mdx). ::: ## Initialize Project -The recommended way to create a plugin is using the Medusa CLI. Run the following command to create a new Medusa project: +The recommended way to create a plugin is using the Medusa CLI. Run the following command to create a new Medusa +project: ```bash medusa new medusa-plugin-custom ``` -Where `medusa-plugin-custom` is the name of the plugin you’re creating. In Medusa, plugins are named based on their functionalities. +Where `medusa-plugin-custom` is the name of the plugin you’re creating. In Medusa, plugins are named based on their +functionalities. -By convention, all plugin names start with `medusa` followed by a descriptive name of what the plugin does. For example, the Stripe plugin is named `medusa-payment-stripe`. +By convention, all plugin names start with `medusa` followed by a descriptive name of what the plugin does. For example, +the Stripe plugin is named `medusa-payment-stripe`. ## Changes to package.json ### Rename Project Name -Update the `name` field in the `package.json` file to the name of your plugin. This should be the same name that you chose when running the `medusa new` command. +Update the `name` field in the `package.json` file to the name of your plugin. This should be the same name that you +chose when running the `medusa new` command. ### Change Dependencies @@ -56,7 +63,9 @@ A basic Medusa server installed with the `medusa new` command has dependencies s } ``` -For a plugin, a lot of these dependencies are not necessary or should be labeled as [peer dependencies](https://docs.npmjs.com/cli/v8/configuring-npm/package-json#peerdependencies). Therefore, it’s important to make changes to the dependencies of your plugin. +For a plugin, a lot of these dependencies are not necessary or should be labeled +as [peer dependencies](https://docs.npmjs.com/cli/v8/configuring-npm/package-json#peerdependencies). Therefore, it’s +important to make changes to the dependencies of your plugin. The recommended change is the following: @@ -74,9 +83,11 @@ The recommended change is the following: } ``` -The packages `@medusajs/medusa` and `medusa-interfaces` act as peer dependencies. They’ll be installed while you develop your package, and they are required when your plugin is installed in another NPM project. +The packages `@medusajs/medusa` and `medusa-interfaces` act as peer dependencies. They’ll be installed while you develop +your package, and they are required when your plugin is installed in another NPM project. -You remove the packages `medusa-fulfillment-manual`, `medusa-payment-manual`, and `medusa-payment-stripe` as they are fulfillment and payment plugins necessary for a Medusa server, but not for a plugin. +You remove the packages `medusa-fulfillment-manual`, `medusa-payment-manual`, and `medusa-payment-stripe` as they are +fulfillment and payment plugins necessary for a Medusa server, but not for a plugin. Additionally, you remove `@medusajs/medusa-cli` as you don’t need to use the Medusa CLI while developing a plugin. @@ -86,13 +97,15 @@ Once you’re done making these changes, re-run the install command to update yo npm install ``` -This section includes recommended changes to your `package.json`. You can skip any of these changes if you don’t find them necessary to your plugin. +This section includes recommended changes to your `package.json`. You can skip any of these changes if you don’t find +them necessary to your plugin. ### Recommended: Change scripts :::caution -If you don't make changes to the `build` and `watch` commands, please be aware of the [expected plugin structure](#plugin-structure). +If you don't make changes to the `build` and `watch` commands, please be aware of +the [expected plugin structure](#plugin-structure). ::: @@ -138,24 +151,30 @@ npm install --save-dev cross-env ### Recommended: Change Basic Info -`package.json` holds information that further describes the package or the author that created the package. It is recommended to make the following changes: +`package.json` holds information that further describes the package or the author that created the package. It is +recommended to make the following changes: - `description`: Change this to a sentence that describes what your plugin does. - `author`: Your name and email. - `repository`: The repository that holds your plugin’s codebase. -- `keywords`: This should hold the keywords that are related to your plugin. It’s recommended that all plugins use the keywords `medusa-plugin` or `medusa`. +- `keywords`: This should hold the keywords that are related to your plugin. It’s recommended that all plugins use the + keywords `medusa-plugin` or `medusa`. ## Develop your Plugin -Now, You can start developing your plugin. This can include adding services, endpoints, entities, or anything that's relevant to your plugin. +Now, You can start developing your plugin. This can include adding services, endpoints, entities, or anything that's +relevant to your plugin. ### Plugin Structure -While developing your plugin, you can create your TypeScript or JavaScript files under the `src` directory. This includes creating services, endpoints, migrations, etc... +While developing your plugin, you can create your TypeScript or JavaScript files under the `src` directory. This +includes creating services, endpoints, migrations, etc... -However, before you test the changes on a Medusa server or publish your plugin, you must transpile your files, which moves them into the root of your plugin directory. +However, before you test the changes on a Medusa server or publish your plugin, you must transpile your files, which +moves them into the root of your plugin directory. -For example, if you have an endpoint in `src/api/index.js`, after running the `build` or `watch` commands [as defined earlier](#change-scripts), the file should be transpiled into `api/index.js` in your plugin's root. +For example, if you have an endpoint in `src/api/index.js`, after running the `build` or `watch` +commands [as defined earlier](#change-scripts), the file should be transpiled into `api/index.js` in your plugin's root. If files and directories aren't placed in the root of your plugin, the Medusa server won't detect or load them. @@ -176,7 +195,7 @@ medusa-plugin-custom | | | |_ _ _ api | | | -| | |_ _ _ index.ts +| | |_ _ _ types.ts | | | |_ _ _ migrations | | @@ -188,7 +207,8 @@ medusa-plugin-custom ### Development Resources -This guide doesn't cover how to create different files and components. If you’re interested in learning how to do that, you can check out these guides: +This guide doesn't cover how to create different files and components. If you’re interested in learning how to do that, +you can check out these guides: - How to [create endpoints](../endpoints/add.md) - How to [create a service](../services/create-service.md) @@ -198,7 +218,8 @@ This guide doesn't cover how to create different files and components. If you’ ## Add Plugin Configuration -Plugins often allow developers that will later use them to enter their own configuration. For example, you can allow developers to specify the API key of a service you’re integrating. +Plugins often allow developers that will later use them to enter their own configuration. For example, you can allow +developers to specify the API key of a service you’re integrating. To pass a plugin its configurations on a Medusa server, you have to add it to the `plugins` array in `medusa-config.js`: @@ -250,7 +271,8 @@ Make sure to include in the README of your plugin the configurations that can be ## Test Your Plugin -While you develop your plugin, you’ll need to test it on an actual Medusa server. This can be done by using the [npm link](https://docs.npmjs.com/cli/v8/commands/npm-link) command. +While you develop your plugin, you’ll need to test it on an actual Medusa server. This can be done by using +the [npm link](https://docs.npmjs.com/cli/v8/commands/npm-link) command. In the root of your plugin directory, run the following command: @@ -266,7 +288,8 @@ npm link medusa-plugin-custom Where `medusa-plugin-custom` is the package name of your plugin. -After linking to your plugin in a local Medusa server, either run the `build` or `watch` commands in your plugin directory: +After linking to your plugin in a local Medusa server, either run the `build` or `watch` commands in your plugin +directory: ```bash npm2yarn # in the directory of the plugin @@ -275,7 +298,8 @@ npm run watch :::tip -If you’re running the `watch` command, you don’t need to run the `build` command every time you make a change to your plugin. +If you’re running the `watch` command, you don’t need to run the `build` command every time you make a change to your +plugin. ::: @@ -296,7 +320,8 @@ const plugins = [ :::note -If your plugin has migrations, you must run them before you start the server. Check out the [Migrations guide](../migrations/overview.md#migrate-command) for more details. +If your plugin has migrations, you must run them before you start the server. Check out +the [Migrations guide](../migrations/overview.md#migrate-command) for more details. ::: @@ -310,7 +335,8 @@ npm run start #### Error: The class must be a valid service implementation -Please make sure that your plugin is following the correct structure. If the error persists then please try the following fix: +Please make sure that your plugin is following the correct structure. If the error persists then please try the +following fix: ```bash npm2yarn cd /node_modules/medusa-interfaces @@ -325,7 +351,8 @@ npm link your-plugin Where `` is the path to your Medusa server and `` is the path to your plugin. -This links the `medusa-interfaces` package from your `medusa-backend` to your plugin directory and then links your plugin to your `medusa-backend`. +This links the `medusa-interfaces` package from your `medusa-backend` to your plugin directory and then links your +plugin to your `medusa-backend`. #### APIs not loading @@ -342,7 +369,8 @@ cd npm run start ``` -Where `` is the path to your Medusa server, `` is the path to your plugin and `` is the name of your plugin as it is in your plugin `package.json` file. +Where `` is the path to your Medusa server, `` is the path to your plugin and `` +is the name of your plugin as it is in your plugin `package.json` file. :::note @@ -354,7 +382,8 @@ It is safe to ignore any `cross-env: command not found` error you may receive. Not all files that you use while developing your plugin are necessary to be published. -For example, the files you add in the `src` directory are compiled to the root of the plugin directory before publishing. Then, when a developer installs your plugin, they’ll just be using the files in the root. +For example, the files you add in the `src` directory are compiled to the root of the plugin directory before +publishing. Then, when a developer installs your plugin, they’ll just be using the files in the root. So, you can ignore files and directories like `src` from the final published NPM package. @@ -385,13 +414,15 @@ develop.sh ## Publish Plugin -Once you’re done developing your plugin you can publish the package on NPM’s registry so that other developers can benefit from it and use it. +Once you’re done developing your plugin you can publish the package on NPM’s registry so that other developers can +benefit from it and use it. Before you publish a plugin, you must [create an account on NPM](https://www.npmjs.com/signup). ### Prepare Plugin -Before you publish or update your plugin, make sure to run the `prepare` command [defined earlier](#recommended-change-scripts): +Before you publish or update your plugin, make sure to run the `prepare` +command [defined earlier](#recommended-change-scripts): ```bash npm2yarn npm run prepare @@ -437,13 +468,17 @@ npm publish ## Add Plugin to Medusa’s Repository -All officially-supported plugins are available in the [`packages` directory of the Medusa GitHub repository](https://github.com/medusajs/medusa/tree/master/packages). +All officially-supported plugins are available in +the [`packages` directory of the Medusa GitHub repository](https://github.com/medusajs/medusa/tree/master/packages). -If you’re interested in adding your plugin, you need to create a new pull request (PR) where you add your plugin inside the `packages` directory. Our team will then review your plugin, and if it’s approved the PR will be merged and your plugin will be available on Medusa’s repository. +If you’re interested in adding your plugin, you need to create a new pull request (PR) where you add your plugin inside +the `packages` directory. Our team will then review your plugin, and if it’s approved the PR will be merged and your +plugin will be available on Medusa’s repository. :::note -Before contributing to the Medusa repository, please check out the [contribution guidelines](https://github.com/medusajs/medusa/blob/master/CONTRIBUTING.md). +Before contributing to the Medusa repository, please check out +the [contribution guidelines](https://github.com/medusajs/medusa/blob/master/CONTRIBUTING.md). ::: @@ -457,6 +492,8 @@ npm install medusa-plugin-custom ## What’s Next -- Check out [available Services in Medusa](references/services/../../../../../references/services/classes/AuthService.md) that you can use in your plugin. +- Check + out [available Services in Medusa](references/services/../../../../../references/services/classes/AuthService.md) that + you can use in your plugin. - Check out [available events](../subscribers/events-list.md) that you can listen to in Subscribers. - Check out [available official plugins](https://github.com/medusajs/medusa/tree/master/packages). diff --git a/docs/content/references/js-client/classes/Admin.md b/docs/content/references/js-client/classes/Admin.md index 83e1d58bb4eed..1c82d51d7e875 100644 --- a/docs/content/references/js-client/classes/Admin.md +++ b/docs/content/references/js-client/classes/Admin.md @@ -14,7 +14,7 @@ #### Defined in -[medusa-js/src/resources/admin/index.ts:34](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L34) +[medusa-js/src/resources/admin/types.ts:34](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L34) ___ @@ -24,7 +24,7 @@ ___ #### Defined in -[medusa-js/src/resources/admin/index.ts:35](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L35) +[medusa-js/src/resources/admin/types.ts:35](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L35) ___ @@ -34,7 +34,7 @@ ___ #### Defined in -[medusa-js/src/resources/admin/index.ts:40](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L40) +[medusa-js/src/resources/admin/types.ts:40](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L40) ___ @@ -44,7 +44,7 @@ ___ #### Defined in -[medusa-js/src/resources/admin/index.ts:39](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L39) +[medusa-js/src/resources/admin/types.ts:39](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L39) ___ @@ -54,7 +54,7 @@ ___ #### Defined in -[medusa-js/src/resources/admin/index.ts:37](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L37) +[medusa-js/src/resources/admin/types.ts:37](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L37) ___ @@ -64,7 +64,7 @@ ___ #### Defined in -[medusa-js/src/resources/admin/index.ts:36](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L36) +[medusa-js/src/resources/admin/types.ts:36](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L36) ___ @@ -74,7 +74,7 @@ ___ #### Defined in -[medusa-js/src/resources/admin/index.ts:38](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L38) +[medusa-js/src/resources/admin/types.ts:38](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L38) ___ @@ -84,7 +84,7 @@ ___ #### Defined in -[medusa-js/src/resources/admin/index.ts:41](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L41) +[medusa-js/src/resources/admin/types.ts:41](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L41) ___ @@ -94,7 +94,7 @@ ___ #### Defined in -[medusa-js/src/resources/admin/index.ts:42](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L42) +[medusa-js/src/resources/admin/types.ts:42](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L42) ___ @@ -104,7 +104,7 @@ ___ #### Defined in -[medusa-js/src/resources/admin/index.ts:43](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L43) +[medusa-js/src/resources/admin/types.ts:43](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L43) ___ @@ -114,7 +114,7 @@ ___ #### Defined in -[medusa-js/src/resources/admin/index.ts:44](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L44) +[medusa-js/src/resources/admin/types.ts:44](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L44) ___ @@ -124,7 +124,7 @@ ___ #### Defined in -[medusa-js/src/resources/admin/index.ts:61](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L61) +[medusa-js/src/resources/admin/types.ts:61](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L61) ___ @@ -134,7 +134,7 @@ ___ #### Defined in -[medusa-js/src/resources/admin/index.ts:52](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L52) +[medusa-js/src/resources/admin/types.ts:52](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L52) ___ @@ -144,7 +144,7 @@ ___ #### Defined in -[medusa-js/src/resources/admin/index.ts:51](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L51) +[medusa-js/src/resources/admin/types.ts:51](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L51) ___ @@ -154,7 +154,7 @@ ___ #### Defined in -[medusa-js/src/resources/admin/index.ts:45](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L45) +[medusa-js/src/resources/admin/types.ts:45](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L45) ___ @@ -164,7 +164,7 @@ ___ #### Defined in -[medusa-js/src/resources/admin/index.ts:47](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L47) +[medusa-js/src/resources/admin/types.ts:47](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L47) ___ @@ -174,7 +174,7 @@ ___ #### Defined in -[medusa-js/src/resources/admin/index.ts:48](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L48) +[medusa-js/src/resources/admin/types.ts:48](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L48) ___ @@ -184,7 +184,7 @@ ___ #### Defined in -[medusa-js/src/resources/admin/index.ts:46](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L46) +[medusa-js/src/resources/admin/types.ts:46](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L46) ___ @@ -194,7 +194,7 @@ ___ #### Defined in -[medusa-js/src/resources/admin/index.ts:60](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L60) +[medusa-js/src/resources/admin/types.ts:60](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L60) ___ @@ -204,7 +204,7 @@ ___ #### Defined in -[medusa-js/src/resources/admin/index.ts:53](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L53) +[medusa-js/src/resources/admin/types.ts:53](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L53) ___ @@ -214,7 +214,7 @@ ___ #### Defined in -[medusa-js/src/resources/admin/index.ts:50](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L50) +[medusa-js/src/resources/admin/types.ts:50](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L50) ___ @@ -224,7 +224,7 @@ ___ #### Defined in -[medusa-js/src/resources/admin/index.ts:55](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L55) +[medusa-js/src/resources/admin/types.ts:55](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L55) ___ @@ -234,7 +234,7 @@ ___ #### Defined in -[medusa-js/src/resources/admin/index.ts:59](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L59) +[medusa-js/src/resources/admin/types.ts:59](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L59) ___ @@ -244,7 +244,7 @@ ___ #### Defined in -[medusa-js/src/resources/admin/index.ts:57](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L57) +[medusa-js/src/resources/admin/types.ts:57](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L57) ___ @@ -254,7 +254,7 @@ ___ #### Defined in -[medusa-js/src/resources/admin/index.ts:58](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L58) +[medusa-js/src/resources/admin/types.ts:58](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L58) ___ @@ -264,7 +264,7 @@ ___ #### Defined in -[medusa-js/src/resources/admin/index.ts:56](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L56) +[medusa-js/src/resources/admin/types.ts:56](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L56) ___ @@ -274,7 +274,7 @@ ___ #### Defined in -[medusa-js/src/resources/admin/index.ts:62](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L62) +[medusa-js/src/resources/admin/types.ts:62](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L62) ___ @@ -284,7 +284,7 @@ ___ #### Defined in -[medusa-js/src/resources/admin/index.ts:63](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L63) +[medusa-js/src/resources/admin/types.ts:63](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L63) ___ @@ -294,7 +294,7 @@ ___ #### Defined in -[medusa-js/src/resources/admin/index.ts:49](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L49) +[medusa-js/src/resources/admin/types.ts:49](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L49) ___ @@ -304,4 +304,4 @@ ___ #### Defined in -[medusa-js/src/resources/admin/index.ts:54](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L54) +[medusa-js/src/resources/admin/types.ts:54](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L54) diff --git a/packages/medusa/src/interfaces/batch-job-strategy.ts b/packages/medusa/src/interfaces/batch-job-strategy.ts index 02609dd00a14f..af87553ad71ef 100644 --- a/packages/medusa/src/interfaces/batch-job-strategy.ts +++ b/packages/medusa/src/interfaces/batch-job-strategy.ts @@ -1,6 +1,6 @@ import { TransactionBaseService } from "./transaction-base-service" import { BatchJobResultError, CreateBatchJobInput } from "../types/batch-job" -import { ProductExportBatchJob } from "../strategies/batch-jobs/product" +import { ProductExportBatchJob } from "../strategies/batch-jobs/product/types/types" import { BatchJobService } from "../services" import { BatchJob } from "../models" diff --git a/packages/medusa/src/strategies/__tests__/batch-jobs/product/export.ts b/packages/medusa/src/strategies/__tests__/batch-jobs/product/export.ts index 49e76b7a94cb4..12dc59c150dfe 100644 --- a/packages/medusa/src/strategies/__tests__/batch-jobs/product/export.ts +++ b/packages/medusa/src/strategies/__tests__/batch-jobs/product/export.ts @@ -7,7 +7,7 @@ import { AdminPostBatchesReq, defaultAdminProductRelations, } from "../../../../api" -import { ProductExportBatchJob } from "../../../batch-jobs/product" +import { ProductExportBatchJob } from "../../../batch-jobs/product/types/types" import { Request } from "express" import { FlagRouter } from "../../../../utils/flag-router" import SalesChannelFeatureFlag from "../../../../loaders/feature-flags/sales-channels" diff --git a/packages/medusa/src/strategies/__tests__/batch-jobs/product/import.ts b/packages/medusa/src/strategies/__tests__/batch-jobs/product/import.ts index 39af7bcff6718..3fa31c4e17271 100644 --- a/packages/medusa/src/strategies/__tests__/batch-jobs/product/import.ts +++ b/packages/medusa/src/strategies/__tests__/batch-jobs/product/import.ts @@ -15,7 +15,7 @@ import { import { BatchJobStatus } from "../../../../types/batch-job" import { FlagRouter } from "../../../../utils/flag-router" import ProductImportStrategy from "../../../batch-jobs/product/import" -import { InjectedProps } from "../../../batch-jobs/product/types" +import { InjectedProps } from "../../../batch-jobs/product/types/types" let fakeJob = { id: IdMap.getId("product-import-job"), diff --git a/packages/medusa/src/strategies/batch-jobs/product/export.ts b/packages/medusa/src/strategies/batch-jobs/product/export.ts index fba433d06eebf..d1281b3b95057 100644 --- a/packages/medusa/src/strategies/batch-jobs/product/export.ts +++ b/packages/medusa/src/strategies/batch-jobs/product/export.ts @@ -6,16 +6,19 @@ import { BatchJobStatus, CreateBatchJobInput } from "../../../types/batch-job" import { defaultAdminProductRelations } from "../../../api" import { prepareListQuery } from "../../../utils/get-query-config" import { + DynamicProductExportDescriptor, ProductExportBatchJob, ProductExportBatchJobContext, - ProductExportColumnSchemaDescriptor, ProductExportPriceData, - productExportSchemaDescriptors, -} from "./index" +} from "./types/types" import { FindProductConfig } from "../../../types/product" import { FlagRouter } from "../../../utils/flag-router" import SalesChannelFeatureFlag from "../../../loaders/feature-flags/sales-channels" import { csvCellContentFormatter } from "../../../utils" +import { + productColumnsDefinition, + productSalesChannelColumnsDefinition, +} from "./types/columns-definition" type InjectedDependencies = { manager: EntityManager @@ -48,10 +51,10 @@ export default class ProductExportStrategy extends AbstractBatchJobStrategy { * column descriptors to this map. * */ - protected readonly columnDescriptors: Map< - string, - ProductExportColumnSchemaDescriptor - > = new Map(productExportSchemaDescriptors) + protected readonly columnsDefinition = { ...productColumnsDefinition } + protected readonly salesChannelsColumnsDefinition = { + ...productSalesChannelColumnsDefinition, + } private readonly NEWLINE_ = "\r\n" private readonly DELIMITER_ = ";" @@ -324,58 +327,114 @@ export default class ProductExportStrategy extends AbstractBatchJobStrategy { this.appendImagesDescriptors(dynamicImageColumnCount) this.appendSalesChannelsDescriptors(dynamicSalesChannelsColumnCount) - return ( - [...this.columnDescriptors.keys()].join(this.DELIMITER_) + this.NEWLINE_ - ) + const exportedColumns = Object.entries(this.columnsDefinition) + .map( + ([name, descriptor]) => + descriptor.exportDescriptor && + !("isDynamic" in descriptor.exportDescriptor) && + name + ) + .filter((name): name is string => !!name) + + return exportedColumns.join(this.DELIMITER_) + this.NEWLINE_ } private appendImagesDescriptors(maxImagesCount: number): void { + const columnNameBuilder = (this.columnsDefinition["Image Url"]! + .exportDescriptor as DynamicProductExportDescriptor)! + .buildDynamicColumnName + for (let i = 0; i < maxImagesCount; ++i) { - this.columnDescriptors.set(`Image ${i + 1} Url`, { - accessor: (product: Product) => product?.images[i]?.url ?? "", - entityName: "product", - }) + const columnName = columnNameBuilder(i) + + this.columnsDefinition[columnName] = { + exportDescriptor: { + accessor: (product: Product) => product?.images[i]?.url ?? "", + entityName: "product", + }, + } } } private appendSalesChannelsDescriptors(maxScCount: number): void { + const columnNameNameBuilder = (this.salesChannelsColumnsDefinition[ + "Sales Channel Name" + ]!.exportDescriptor as DynamicProductExportDescriptor)! + .buildDynamicColumnName + + const columnNameDescriptionBuilder = (this.salesChannelsColumnsDefinition[ + "Sales Channel Description" + ]!.exportDescriptor as DynamicProductExportDescriptor)! + .buildDynamicColumnName + for (let i = 0; i < maxScCount; ++i) { - this.columnDescriptors.set(`Sales channel ${i + 1} Name`, { - accessor: (product: Product) => product?.sales_channels[i]?.name ?? "", - entityName: "product", - }) - this.columnDescriptors.set(`Sales channel ${i + 1} Description`, { - accessor: (product: Product) => - product?.sales_channels[i]?.description ?? "", - entityName: "product", - }) + const columnNameName = columnNameNameBuilder(i) + + this.columnsDefinition[columnNameName] = { + exportDescriptor: { + accessor: (product: Product) => + product?.sales_channels[i]?.name ?? "", + entityName: "product", + }, + } + + const columnNameDescription = columnNameDescriptionBuilder(i) + + this.columnsDefinition[columnNameDescription] = { + exportDescriptor: { + accessor: (product: Product) => + product?.sales_channels[i]?.description ?? "", + entityName: "product", + }, + } } } private appendOptionsDescriptors(maxOptionsCount: number): void { for (let i = 0; i < maxOptionsCount; ++i) { - this.columnDescriptors - .set(`Option ${i + 1} Name`, { + const columnNameNameBuilder = (this.columnsDefinition["Option Name"]! + .exportDescriptor as DynamicProductExportDescriptor)! + .buildDynamicColumnName + + const columnNameName = columnNameNameBuilder(i) + + this.columnsDefinition[columnNameName] = { + exportDescriptor: { accessor: (productOption: Product) => productOption?.options[i]?.title ?? "", entityName: "product", - }) - .set(`Option ${i + 1} Value`, { + }, + } + + const columnNameValueBuilder = (this.columnsDefinition["Option Value"]! + .exportDescriptor as DynamicProductExportDescriptor)! + .buildDynamicColumnName + + const columnNameNameValue = columnNameValueBuilder(i) + + this.columnsDefinition[columnNameNameValue] = { + exportDescriptor: { accessor: (variant: ProductVariant) => variant?.options[i]?.value ?? "", entityName: "variant", - }) + }, + } } } private appendMoneyAmountDescriptors( pricesData: ProductExportPriceData[] ): void { + const columnNameBuilder = (this.columnsDefinition["Price Currency"]! + .exportDescriptor as DynamicProductExportDescriptor)! + .buildDynamicColumnName + for (const priceData of pricesData) { if (priceData.currency_code) { - this.columnDescriptors.set( - `Price ${priceData.currency_code?.toUpperCase()}`, - { + const columnName = columnNameBuilder(priceData) + + this.columnsDefinition[columnName] = { + exportDescriptor: { accessor: (variant: ProductVariant) => { const price = variant.prices.find((variantPrice) => { return ( @@ -388,18 +447,18 @@ export default class ProductExportStrategy extends AbstractBatchJobStrategy { return price?.amount?.toString() ?? "" }, entityName: "variant", - } - ) + }, + } } if (priceData.region) { - this.columnDescriptors.set( - `Price ${priceData.region.name} ${ - priceData.region?.currency_code - ? "[" + priceData.region?.currency_code.toUpperCase() + "]" - : "" - }`, - { + const columnNameBuilder = (this.columnsDefinition["Price Region"]! + .exportDescriptor as DynamicProductExportDescriptor)! + .buildDynamicColumnName + const columnName = columnNameBuilder(priceData) + + this.columnsDefinition[columnName] = { + exportDescriptor: { accessor: (variant: ProductVariant) => { const price = variant.prices.find((variantPrice) => { return ( @@ -414,8 +473,8 @@ export default class ProductExportStrategy extends AbstractBatchJobStrategy { return price?.amount?.toString() ?? "" }, entityName: "variant", - } - ) + }, + } } } } @@ -425,7 +484,11 @@ export default class ProductExportStrategy extends AbstractBatchJobStrategy { for (const variant of product.variants) { const variantLineData: string[] = [] - for (const [, columnSchema] of this.columnDescriptors.entries()) { + for (const [, { exportDescriptor: columnSchema }] of Object.entries( + this.columnsDefinition + )) { + if (!columnSchema || "isDynamic" in columnSchema) continue + if (columnSchema.entityName === "product") { const formattedContent = csvCellContentFormatter( columnSchema.accessor(product) diff --git a/packages/medusa/src/strategies/batch-jobs/product/import.ts b/packages/medusa/src/strategies/batch-jobs/product/import.ts index 8cbdd448a30e9..172da47ba6a6b 100644 --- a/packages/medusa/src/strategies/batch-jobs/product/import.ts +++ b/packages/medusa/src/strategies/batch-jobs/product/import.ts @@ -17,19 +17,35 @@ import { CreateProductVariantInput, UpdateProductVariantInput, } from "../../../types/product-variant" +import { BatchJob, SalesChannel } from "../../../models" +import { FlagRouter } from "../../../utils/flag-router" +import { transformProductData, transformVariantData } from "./utils" +import SalesChannelFeatureFlag from "../../../loaders/feature-flags/sales-channels" +import { FileService } from "medusa-interfaces" import { - ImportJobContext, - InjectedProps, OperationType, ProductImportBatchJob, ProductImportCsvSchema, - TBuiltProductImportLine, + ProductImportJobContext, TParsedProductImportRowData, -} from "./types" -import { BatchJob, Product, SalesChannel } from "../../../models" -import { FlagRouter } from "../../../utils/flag-router" -import { transformProductData, transformVariantData } from "./utils" -import SalesChannelFeatureFlag from "../../../loaders/feature-flags/sales-channels" +} from "./types/types" +import { + productImportColumnsDefinition, + productImportSalesChannelsColumnsDefinition, +} from "./types/columns-definition" + +type InjectedProps = { + batchJobService: BatchJobService + productService: ProductService + productVariantService: ProductVariantService + shippingProfileService: ShippingProfileService + salesChannelService: SalesChannelService + regionService: RegionService + fileService: typeof FileService + + featureFlagRouter: FlagRouter + manager: EntityManager +} /** * Process this many variant rows before reporting progress. @@ -85,10 +101,11 @@ class ProductImportStrategy extends AbstractBatchJobStrategy { ) this.csvParser_ = new CsvParser({ - ...CSVSchema, columns: [ - ...CSVSchema.columns, - ...(isSalesChannelsFeatureOn ? SalesChannelsSchema.columns : []), + ...productImportColumnsDefinition.columns, + ...(isSalesChannelsFeatureOn + ? productImportSalesChannelsColumnsDefinition.columns + : []), ], }) @@ -235,7 +252,7 @@ class ProductImportStrategy extends AbstractBatchJobStrategy { .withTransaction(transactionManager) .retrieve(batchJobId) - const csvFileKey = (batchJob.context as ImportJobContext).fileKey + const csvFileKey = (batchJob.context as ProductImportJobContext).fileKey const csvStream = await this.fileService_.getDownloadStream({ fileKey: csvFileKey, }) @@ -393,7 +410,7 @@ class ProductImportStrategy extends AbstractBatchJobStrategy { productData["sales_channels"] = await this.processSalesChannels( productOp["product.sales_channels"] as Pick< SalesChannel, - "name" | "id" + "name" | "id" | "description" >[] ) } @@ -437,7 +454,7 @@ class ProductImportStrategy extends AbstractBatchJobStrategy { productData["sales_channels"] = await this.processSalesChannels( productOp["product.sales_channels"] as Pick< SalesChannel, - "name" | "id" + "name" | "id" | "description" >[] ) } @@ -685,7 +702,7 @@ class ProductImportStrategy extends AbstractBatchJobStrategy { result: { advancement_count: batchJob.result.count }, }) - const { fileKey } = batchJob.context as ImportJobContext + const { fileKey } = batchJob.context as ProductImportJobContext await this.fileService_ .withTransaction(transactionManager) @@ -728,258 +745,3 @@ class ProductImportStrategy extends AbstractBatchJobStrategy { } export default ProductImportStrategy - -/** - * Schema definition for the CSV parser. - */ -const CSVSchema: ProductImportCsvSchema = { - columns: [ - // PRODUCT - { - name: "Product id", - mapTo: "product.id", - }, - { - name: "Product Handle", - mapTo: "product.handle", - required: true, - }, - { name: "Product Title", mapTo: "product.title" }, - { name: "Product Subtitle", mapTo: "product.subtitle" }, - { name: "Product Description", mapTo: "product.description" }, - { name: "Product Status", mapTo: "product.status" }, - { name: "Product Thumbnail", mapTo: "product.thumbnail" }, - { name: "Product Weight", mapTo: "product.weight" }, - { name: "Product Length", mapTo: "product.length" }, - { name: "Product Width", mapTo: "product.width" }, - { name: "Product Height", mapTo: "product.height" }, - { name: "Product HS Code", mapTo: "product.hs_code" }, - { name: "Product Origin Country", mapTo: "product.origin_country" }, - { name: "Product MID Code", mapTo: "product.mid_code" }, - { name: "Product Material", mapTo: "product.material" }, - // PRODUCT-COLLECTION - { name: "Product Collection Title", mapTo: "product.collection.title" }, - { name: "Product Collection Handle", mapTo: "product.collection.handle" }, - // PRODUCT-TYPE - { - name: "Product Type", - match: /Product Type/, - reducer: ( - builtLine: TParsedProductImportRowData, - key, - value - ): TBuiltProductImportLine => { - if (typeof value === "undefined" || value === null) { - builtLine["product.type"] = undefined - } else { - builtLine["product.type.value"] = value - } - - return builtLine - }, - }, - // PRODUCT-TAGS - { - name: "Product Tags", - mapTo: "product.tags", - transform: (value: string) => - `${value}`.split(",").map((v) => ({ value: v })), - }, - // - { name: "Product Discountable", mapTo: "product.discountable" }, - { name: "Product External ID", mapTo: "product.external_id" }, - - // VARIANTS - { - name: "Variant id", - mapTo: "variant.id", - }, - { name: "Variant Title", mapTo: "variant.title" }, - { name: "Variant SKU", mapTo: "variant.sku" }, - { name: "Variant Barcode", mapTo: "variant.barcode" }, - { name: "Variant Inventory Quantity", mapTo: "variant.inventory_quantity" }, - { name: "Variant Allow backorder", mapTo: "variant.allow_backorder" }, - { name: "Variant Manage inventory", mapTo: "variant.manage_inventory" }, - { name: "Variant Weight", mapTo: "variant.weight" }, - { name: "Variant Length", mapTo: "variant.length" }, - { name: "Variant Width", mapTo: "variant.width" }, - { name: "Variant Height", mapTo: "variant.height" }, - { name: "Variant HS Code", mapTo: "variant.hs_code" }, - { name: "Variant Origin Country", mapTo: "variant.origin_country" }, - { name: "Variant MID Code", mapTo: "variant.mid_code" }, - { name: "Variant Material", mapTo: "variant.material" }, - - // ==== DYNAMIC FIELDS ==== - - // PRODUCT_OPTIONS - { - name: "Option Name", - match: /Option \d+ Name/, - reducer: (builtLine, key, value): TBuiltProductImportLine => { - builtLine["product.options"] = builtLine["product.options"] || [] - - if (typeof value === "undefined" || value === null) { - return builtLine - } - - const options = builtLine["product.options"] as Record< - string, - string | number - >[] - - options.push({ title: value }) - - return builtLine - }, - }, - { - name: "Option Value", - match: /Option \d+ Value/, - reducer: ( - builtLine: TParsedProductImportRowData, - key: string, - value: string, - context: any - ): TBuiltProductImportLine => { - builtLine["variant.options"] = builtLine["variant.options"] || [] - - if (typeof value === "undefined" || value === null) { - return builtLine - } - - const options = builtLine["variant.options"] as Record< - string, - string | number - >[] - - options.push({ - value, - _title: context.line[key.slice(0, -6) + " Name"], - }) - - return builtLine - }, - }, - - // PRICES - { - name: "Price Region", - match: /Price (.*) \[([A-Z]{3})\]/, - reducer: ( - builtLine: TParsedProductImportRowData, - key, - value - ): TBuiltProductImportLine => { - builtLine["variant.prices"] = builtLine["variant.prices"] || [] - - if (typeof value === "undefined" || value === null) { - return builtLine - } - - const [, regionName] = - key.trim().match(/Price (.*) \[([A-Z]{3})\]/) || [] - ;( - builtLine["variant.prices"] as Record[] - ).push({ - amount: parseFloat(value), - regionName, - }) - - return builtLine - }, - }, - { - name: "Price Currency", - match: /Price [A-Z]{3}/, - reducer: ( - builtLine: TParsedProductImportRowData, - key, - value - ): TBuiltProductImportLine => { - builtLine["variant.prices"] = builtLine["variant.prices"] || [] - - if (typeof value === "undefined" || value === null) { - return builtLine - } - - const currency = key.trim().split(" ")[1] - - ;( - builtLine["variant.prices"] as Record[] - ).push({ - amount: parseFloat(value), - currency_code: currency, - }) - - return builtLine - }, - }, - // IMAGES - { - name: "Image Url", - match: /Image \d+ Url/, - reducer: (builtLine: any, key, value): TBuiltProductImportLine => { - builtLine["product.images"] = builtLine["product.images"] || [] - - if (typeof value === "undefined" || value === null) { - return builtLine - } - - builtLine["product.images"].push(value) - - return builtLine - }, - }, - ], -} - -const SalesChannelsSchema: ProductImportCsvSchema = { - columns: [ - { - name: "Sales Channel Name", - match: /Sales Channel \d+ Name/, - reducer: (builtLine, key, value): TBuiltProductImportLine => { - builtLine["product.sales_channels"] = - builtLine["product.sales_channels"] || [] - - if (typeof value === "undefined" || value === null) { - return builtLine - } - - const channels = builtLine["product.sales_channels"] as Record< - string, - string | number - >[] - - channels.push({ - name: value, - }) - - return builtLine - }, - }, - { - name: "Sales Channel Id", - match: /Sales Channel \d+ Id/, - reducer: (builtLine, key, value): TBuiltProductImportLine => { - builtLine["product.sales_channels"] = - builtLine["product.sales_channels"] || [] - - if (typeof value === "undefined" || value === null) { - return builtLine - } - - const channels = builtLine["product.sales_channels"] as Record< - string, - string | number - >[] - - channels.push({ - id: value, - }) - - return builtLine - }, - }, - ], -} diff --git a/packages/medusa/src/strategies/batch-jobs/product/index.ts b/packages/medusa/src/strategies/batch-jobs/product/index.ts deleted file mode 100644 index 28bcdacf2d1c0..0000000000000 --- a/packages/medusa/src/strategies/batch-jobs/product/index.ts +++ /dev/null @@ -1,337 +0,0 @@ -import { BatchJob, Product, ProductVariant } from "../../../models" -import { Selector } from "../../../types/common" - -export type ProductExportBatchJobContext = { - retry_count?: number - max_retry?: number - offset?: number - limit?: number - batch_size?: number - order?: string - fields?: string - expand?: string - shape: { - prices: ProductExportPriceData[] - dynamicOptionColumnCount: number - dynamicImageColumnCount: number - dynamicSalesChannelsColumnCount: number - } - list_config?: { - select?: string[] - relations?: string[] - skip?: number - take?: number - order?: Record - } - filterable_fields?: Selector -} - -export type ProductExportPriceData = { - currency_code?: string - region?: { name: string; currency_code: string; id: string } -} - -export type ProductExportBatchJob = BatchJob & { - context: ProductExportBatchJobContext -} - -export type ProductExportColumnSchemaEntity = "product" | "variant" - -export type ProductExportColumnSchemaDescriptor = - | { - accessor: (product: Product) => string - entityName: Extract - } - | { - accessor: (variant: ProductVariant) => string - entityName: Extract - } - -export const productExportSchemaDescriptors = new Map< - string, - ProductExportColumnSchemaDescriptor ->([ - [ - "Product id", - { - accessor: (product: Product): string => product?.id ?? "", - entityName: "product", - }, - ], - [ - "Product Handle", - { - accessor: (product: Product): string => product?.handle ?? "", - entityName: "product", - }, - ], - [ - "Product Title", - { - accessor: (product: Product): string => product?.title ?? "", - entityName: "product", - }, - ], - [ - "Product Subtitle", - { - accessor: (product: Product): string => product?.subtitle ?? "", - entityName: "product", - }, - ], - [ - "Product Description", - { - accessor: (product: Product): string => product?.description ?? "", - entityName: "product", - }, - ], - [ - "Product Status", - { - accessor: (product: Product): string => product?.status ?? "", - entityName: "product", - }, - ], - [ - "Product Thumbnail", - { - accessor: (product: Product): string => product?.thumbnail ?? "", - entityName: "product", - }, - ], - [ - "Product Weight", - { - accessor: (product: Product): string => product?.weight?.toString() ?? "", - entityName: "product", - }, - ], - [ - "Product Length", - { - accessor: (product: Product): string => product?.length?.toString() ?? "", - entityName: "product", - }, - ], - [ - "Product Width", - { - accessor: (product: Product): string => product?.width?.toString() ?? "", - entityName: "product", - }, - ], - [ - "Product Height", - { - accessor: (product: Product): string => product?.height?.toString() ?? "", - entityName: "product", - }, - ], - [ - "Product HS Code", - { - accessor: (product: Product): string => - product?.hs_code?.toString() ?? "", - entityName: "product", - }, - ], - [ - "Product Origin Country", - { - accessor: (product: Product): string => - product?.origin_country?.toString() ?? "", - entityName: "product", - }, - ], - [ - "Product MID Code", - { - accessor: (product: Product): string => - product?.mid_code?.toString() ?? "", - entityName: "product", - }, - ], - [ - "Product Material", - { - accessor: (product: Product): string => - product?.material?.toString() ?? "", - entityName: "product", - }, - ], - [ - "Product Collection Title", - { - accessor: (product: Product): string => product?.collection?.title ?? "", - entityName: "product", - }, - ], - [ - "Product Collection Handle", - { - accessor: (product: Product): string => product?.collection?.handle ?? "", - entityName: "product", - }, - ], - [ - "Product Type", - { - accessor: (product: Product): string => product?.type?.value ?? "", - entityName: "product", - }, - ], - [ - "Product Tags", - { - accessor: (product: Product): string => - (product.tags.map((t) => t.value) ?? []).join(","), - entityName: "product", - }, - ], - [ - "Product Discountable", - { - accessor: (product: Product): string => - product?.discountable?.toString() ?? "", - entityName: "product", - }, - ], - [ - "Product External ID", - { - accessor: (product: Product): string => product?.external_id ?? "", - entityName: "product", - }, - ], - [ - "Product Profile Name", - { - accessor: (product: Product): string => product?.profile?.name ?? "", - entityName: "product", - }, - ], - [ - "Product Profile Type", - { - accessor: (product: Product): string => product?.profile?.type ?? "", - entityName: "product", - }, - ], - [ - "Variant id", - { - accessor: (variant: ProductVariant): string => variant?.id ?? "", - entityName: "variant", - }, - ], - [ - "Variant Title", - { - accessor: (variant: ProductVariant): string => variant?.title ?? "", - entityName: "variant", - }, - ], - [ - "Variant SKU", - { - accessor: (variant: ProductVariant): string => variant?.sku ?? "", - entityName: "variant", - }, - ], - [ - "Variant Barcode", - { - accessor: (variant: ProductVariant): string => variant?.barcode ?? "", - entityName: "variant", - }, - ], - [ - "Variant Inventory Quantity", - { - accessor: (variant: ProductVariant): string => - variant?.inventory_quantity?.toString() ?? "", - entityName: "variant", - }, - ], - [ - "Variant Allow backorder", - { - accessor: (variant: ProductVariant): string => - variant?.allow_backorder?.toString() ?? "", - entityName: "variant", - }, - ], - [ - "Variant Manage inventory", - { - accessor: (variant: ProductVariant): string => - variant?.manage_inventory?.toString() ?? "", - entityName: "variant", - }, - ], - [ - "Variant Weight", - { - accessor: (variant: ProductVariant): string => - variant?.weight?.toString() ?? "", - entityName: "variant", - }, - ], - [ - "Variant Length", - { - accessor: (variant: ProductVariant): string => - variant?.length?.toString() ?? "", - entityName: "variant", - }, - ], - [ - "Variant Width", - { - accessor: (variant: ProductVariant): string => - variant?.width?.toString() ?? "", - entityName: "variant", - }, - ], - [ - "Variant Height", - { - accessor: (variant: ProductVariant): string => - variant?.height?.toString() ?? "", - entityName: "variant", - }, - ], - [ - "Variant HS Code", - { - accessor: (variant: ProductVariant): string => - variant?.hs_code?.toString() ?? "", - entityName: "variant", - }, - ], - [ - "Variant Origin Country", - { - accessor: (variant: ProductVariant): string => - variant?.origin_country?.toString() ?? "", - entityName: "variant", - }, - ], - [ - "Variant MID Code", - { - accessor: (variant: ProductVariant): string => - variant?.mid_code?.toString() ?? "", - entityName: "variant", - }, - ], - [ - "Variant Material", - { - accessor: (variant: ProductVariant): string => - variant?.material?.toString() ?? "", - entityName: "variant", - }, - ], -]) diff --git a/packages/medusa/src/strategies/batch-jobs/product/types.ts b/packages/medusa/src/strategies/batch-jobs/product/types.ts deleted file mode 100644 index 383fa20e5c5a2..0000000000000 --- a/packages/medusa/src/strategies/batch-jobs/product/types.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { EntityManager } from "typeorm" -import { FileService } from "medusa-interfaces" - -import { - BatchJobService, - ProductService, - ProductVariantService, - RegionService, - SalesChannelService, - ShippingProfileService, -} from "../../../services" -import { CsvSchema } from "../../../interfaces/csv-parser" -import { FlagRouter } from "../../../utils/flag-router" -import { BatchJob } from "../../../models" - -export type ProductImportBatchJob = BatchJob & { - result: Pick & { - operations: { - [K in keyof typeof OperationType]: number - } - } -} - -/** - * DI props for the Product import strategy - */ -export type InjectedProps = { - batchJobService: BatchJobService - productService: ProductService - productVariantService: ProductVariantService - shippingProfileService: ShippingProfileService - salesChannelService: SalesChannelService - regionService: RegionService - fileService: typeof FileService - - featureFlagRouter: FlagRouter - manager: EntityManager -} - -/** - * Data shape returned by the CSVParser. - */ -export type TParsedProductImportRowData = Record< - string, - string | number | object | undefined | (string | number | object)[] -> - -/** - * CSV parser's row reducer result data shape. - */ -export type TBuiltProductImportLine = Record - -/** - * Schema definition of for an import CSV file. - */ -export type ProductImportCsvSchema = CsvSchema< - TParsedProductImportRowData, - TBuiltProductImportLine -> - -/** - * Import Batch job context column type. - */ -export type ImportJobContext = { - total: number - fileKey: string -} - -/** - * Supported batch job import ops. - */ -export enum OperationType { - ProductCreate = "PRODUCT_CREATE", - ProductUpdate = "PRODUCT_UPDATE", - VariantCreate = "VARIANT_CREATE", - VariantUpdate = "VARIANT_UPDATE", -} diff --git a/packages/medusa/src/strategies/batch-jobs/product/types/columns-definition.ts b/packages/medusa/src/strategies/batch-jobs/product/types/columns-definition.ts new file mode 100644 index 0000000000000..6fe7607da6b51 --- /dev/null +++ b/packages/medusa/src/strategies/batch-jobs/product/types/columns-definition.ts @@ -0,0 +1,729 @@ +import { Product, ProductVariant } from "../../../../models" +import { + ProductColumnDefinition, + ProductExportPriceData, + TBuiltProductImportLine, + TParsedProductImportRowData, +} from "./types" +import { CsvSchema, CsvSchemaColumn } from "../../../../interfaces/csv-parser" + +export const productColumnsDefinition: ProductColumnDefinition = { + "Product id": { + importDescriptor: { + name: "Product id", + mapTo: "product.id", + }, + exportDescriptor: { + accessor: (product: Product): string => product?.id ?? "", + entityName: "product", + }, + }, + "Product Handle": { + importDescriptor: { + name: "Product Handle", + mapTo: "product.handle", + required: true, + }, + exportDescriptor: { + accessor: (product: Product): string => product?.handle ?? "", + entityName: "product", + }, + }, + "Product Title": { + importDescriptor: { + name: "Product Title", + mapTo: "product.title", + }, + exportDescriptor: { + accessor: (product: Product): string => product?.title ?? "", + entityName: "product", + }, + }, + "Product Subtitle": { + importDescriptor: { + name: "Product Subtitle", + mapTo: "product.subtitle", + }, + exportDescriptor: { + accessor: (product: Product): string => product?.subtitle ?? "", + entityName: "product", + }, + }, + "Product Description": { + importDescriptor: { + name: "Product Description", + mapTo: "product.description", + }, + exportDescriptor: { + accessor: (product: Product): string => product?.description ?? "", + entityName: "product", + }, + }, + "Product Status": { + importDescriptor: { + name: "Product Status", + mapTo: "product.status", + }, + exportDescriptor: { + accessor: (product: Product): string => product?.status ?? "", + entityName: "product", + }, + }, + "Product Thumbnail": { + importDescriptor: { + name: "Product Thumbnail", + mapTo: "product.thumbnail", + }, + exportDescriptor: { + accessor: (product: Product): string => product?.thumbnail ?? "", + entityName: "product", + }, + }, + "Product Weight": { + importDescriptor: { + name: "Product Weight", + mapTo: "product.weight", + }, + exportDescriptor: { + accessor: (product: Product): string => product?.weight?.toString() ?? "", + entityName: "product", + }, + }, + "Product Length": { + importDescriptor: { + name: "Product Length", + mapTo: "product.length", + }, + exportDescriptor: { + accessor: (product: Product): string => product?.length?.toString() ?? "", + entityName: "product", + }, + }, + "Product Width": { + importDescriptor: { + name: "Product Width", + mapTo: "product.width", + }, + exportDescriptor: { + accessor: (product: Product): string => product?.width?.toString() ?? "", + entityName: "product", + }, + }, + "Product Height": { + importDescriptor: { + name: "Product Height", + mapTo: "product.height", + }, + exportDescriptor: { + accessor: (product: Product): string => product?.height?.toString() ?? "", + entityName: "product", + }, + }, + "Product HS Code": { + importDescriptor: { + name: "Product HS Code", + mapTo: "product.hs_code", + }, + exportDescriptor: { + accessor: (product: Product): string => + product?.hs_code?.toString() ?? "", + entityName: "product", + }, + }, + "Product Origin Country": { + importDescriptor: { + name: "Product Origin Country", + mapTo: "product.origin_country", + }, + exportDescriptor: { + accessor: (product: Product): string => + product?.origin_country?.toString() ?? "", + entityName: "product", + }, + }, + "Product MID Code": { + importDescriptor: { + name: "Product MID Code", + mapTo: "product.mid_code", + }, + exportDescriptor: { + accessor: (product: Product): string => + product?.mid_code?.toString() ?? "", + entityName: "product", + }, + }, + "Product Material": { + importDescriptor: { + name: "Product Material", + mapTo: "product.material", + }, + exportDescriptor: { + accessor: (product: Product): string => + product?.material?.toString() ?? "", + entityName: "product", + }, + }, + + // PRODUCT-COLLECTION + + "Product Collection Title": { + importDescriptor: { + name: "Product Collection Title", + mapTo: "product.collection.title", + }, + exportDescriptor: { + accessor: (product: Product): string => product?.collection?.title ?? "", + entityName: "product", + }, + }, + "Product Collection Handle": { + importDescriptor: { + name: "Product Collection Handle", + mapTo: "product.collection.handle", + }, + exportDescriptor: { + accessor: (product: Product): string => product?.collection?.handle ?? "", + entityName: "product", + }, + }, + + // PRODUCT-TYPE + + "Product Type": { + importDescriptor: { + name: "Product Type", + match: /Product Type/, + reducer: ( + builtLine: TParsedProductImportRowData, + key, + value + ): TBuiltProductImportLine => { + if (typeof value === "undefined" || value === null) { + builtLine["product.type"] = undefined + } else { + builtLine["product.type.value"] = value + } + + return builtLine + }, + }, + exportDescriptor: { + accessor: (product: Product): string => product?.type?.value ?? "", + entityName: "product", + }, + }, + + // PRODUCT-TAGS + + "Product Tags": { + importDescriptor: { + name: "Product Tags", + mapTo: "product.tags", + transform: (value: string) => + `${value}`.split(",").map((v) => ({ value: v })), + }, + exportDescriptor: { + accessor: (product: Product): string => + (product.tags.map((t) => t.value) ?? []).join(","), + entityName: "product", + }, + }, + + // + + "Product Discountable": { + importDescriptor: { + name: "Product Discountable", + mapTo: "product.discountable", + }, + exportDescriptor: { + accessor: (product: Product): string => + product?.discountable?.toString() ?? "", + entityName: "product", + }, + }, + "Product External ID": { + importDescriptor: { + name: "Product External ID", + mapTo: "product.external_id", + }, + exportDescriptor: { + accessor: (product: Product): string => product?.external_id ?? "", + entityName: "product", + }, + }, + + // PRODUCT-PROFILE + + "Product Profile Name": { + exportDescriptor: { + accessor: (product: Product): string => product?.profile?.name ?? "", + entityName: "product", + }, + }, + + "Product Profile Type": { + exportDescriptor: { + accessor: (product: Product): string => product?.profile?.type ?? "", + entityName: "product", + }, + }, + + // VARIANTS + + "Variant id": { + importDescriptor: { + name: "Variant id", + mapTo: "variant.id", + }, + exportDescriptor: { + accessor: (variant: ProductVariant): string => variant?.id ?? "", + entityName: "variant", + }, + }, + "Variant Title": { + importDescriptor: { + name: "Variant Title", + mapTo: "variant.title", + }, + exportDescriptor: { + accessor: (variant: ProductVariant): string => variant?.title ?? "", + entityName: "variant", + }, + }, + "Variant SKU": { + importDescriptor: { + name: "Variant SKU", + mapTo: "variant.sku", + }, + exportDescriptor: { + accessor: (variant: ProductVariant): string => variant?.sku ?? "", + entityName: "variant", + }, + }, + "Variant Barcode": { + importDescriptor: { + name: "Variant Barcode", + mapTo: "variant.barcode", + }, + exportDescriptor: { + accessor: (variant: ProductVariant): string => variant?.barcode ?? "", + entityName: "variant", + }, + }, + "Variant Inventory Quantity": { + importDescriptor: { + name: "Variant Inventory Quantity", + mapTo: "variant.inventory_quantity", + }, + exportDescriptor: { + accessor: (variant: ProductVariant): string => + variant?.inventory_quantity?.toString() ?? "", + entityName: "variant", + }, + }, + "Variant Allow backorder": { + importDescriptor: { + name: "Variant Allow backorder", + mapTo: "variant.allow_backorder", + }, + exportDescriptor: { + accessor: (variant: ProductVariant): string => + variant?.allow_backorder?.toString() ?? "", + entityName: "variant", + }, + }, + "Variant Manage inventory": { + importDescriptor: { + name: "Variant Manage inventory", + mapTo: "variant.manage_inventory", + }, + exportDescriptor: { + accessor: (variant: ProductVariant): string => + variant?.manage_inventory?.toString() ?? "", + entityName: "variant", + }, + }, + "Variant Weight": { + importDescriptor: { + name: "Variant Weight", + mapTo: "variant.weight", + }, + exportDescriptor: { + accessor: (variant: ProductVariant): string => + variant?.weight?.toString() ?? "", + entityName: "variant", + }, + }, + "Variant Length": { + importDescriptor: { + name: "Variant Length", + mapTo: "variant.length", + }, + exportDescriptor: { + accessor: (variant: ProductVariant): string => + variant?.length?.toString() ?? "", + entityName: "variant", + }, + }, + "Variant Width": { + importDescriptor: { + name: "Variant Width", + mapTo: "variant.width", + }, + exportDescriptor: { + accessor: (variant: ProductVariant): string => + variant?.width?.toString() ?? "", + entityName: "variant", + }, + }, + "Variant Height": { + importDescriptor: { + name: "Variant Height", + mapTo: "variant.height", + }, + exportDescriptor: { + accessor: (variant: ProductVariant): string => + variant?.height?.toString() ?? "", + entityName: "variant", + }, + }, + "Variant HS Code": { + importDescriptor: { + name: "Variant HS Code", + mapTo: "variant.hs_code", + }, + exportDescriptor: { + accessor: (variant: ProductVariant): string => + variant?.hs_code?.toString() ?? "", + entityName: "variant", + }, + }, + "Variant Origin Country": { + importDescriptor: { + name: "Variant Origin Country", + mapTo: "variant.origin_country", + }, + exportDescriptor: { + accessor: (variant: ProductVariant): string => + variant?.origin_country?.toString() ?? "", + entityName: "variant", + }, + }, + "Variant MID Code": { + importDescriptor: { + name: "Variant MID Code", + mapTo: "variant.mid_code", + }, + exportDescriptor: { + accessor: (variant: ProductVariant): string => + variant?.mid_code?.toString() ?? "", + entityName: "variant", + }, + }, + "Variant Material": { + importDescriptor: { + name: "Variant Material", + mapTo: "variant.material", + }, + exportDescriptor: { + accessor: (variant: ProductVariant): string => + variant?.material?.toString() ?? "", + entityName: "variant", + }, + }, + + // ==== DYNAMIC FIELDS ==== + + // PRODUCT_OPTIONS + + "Option Name": { + importDescriptor: { + name: "Option Name", + match: /Option \d+ Name/, + reducer: (builtLine, key, value): TBuiltProductImportLine => { + builtLine["product.options"] = builtLine["product.options"] || [] + + if (typeof value === "undefined" || value === null) { + return builtLine + } + + const options = builtLine["product.options"] as Record< + string, + string | number + >[] + + options.push({ title: value }) + + return builtLine + }, + }, + exportDescriptor: { + isDynamic: true, + buildDynamicColumnName: (index: number) => { + return `Option ${index + 1} Name` + }, + }, + }, + "Option Value": { + importDescriptor: { + name: "Option Value", + match: /Option \d+ Value/, + reducer: ( + builtLine: TParsedProductImportRowData, + key: string, + value: string, + context: any + ): TBuiltProductImportLine => { + builtLine["variant.options"] = builtLine["variant.options"] || [] + + if (typeof value === "undefined" || value === null) { + return builtLine + } + + const options = builtLine["variant.options"] as Record< + string, + string | number + >[] + + options.push({ + value, + _title: context.line[key.slice(0, -6) + " Name"], + }) + + return builtLine + }, + }, + exportDescriptor: { + isDynamic: true, + buildDynamicColumnName: (index: number) => { + return `Option ${index + 1} Value` + }, + }, + }, + + // PRICES + + "Price Region": { + importDescriptor: { + name: "Price Region", + match: /Price (.*) \[([A-Z]{3})\]/, + reducer: ( + builtLine: TParsedProductImportRowData, + key, + value + ): TBuiltProductImportLine => { + builtLine["variant.prices"] = builtLine["variant.prices"] || [] + + if (typeof value === "undefined" || value === null) { + return builtLine + } + + const [, regionName] = + key.trim().match(/Price (.*) \[([A-Z]{3})\]/) || [] + ;( + builtLine["variant.prices"] as Record[] + ).push({ + amount: parseFloat(value), + regionName, + }) + + return builtLine + }, + }, + exportDescriptor: { + isDynamic: true, + buildDynamicColumnName: (data: ProductExportPriceData) => { + return `Price ${data.region?.name} ${ + data.region?.currency_code + ? "[" + data.region?.currency_code.toUpperCase() + "]" + : "" + }` + }, + }, + }, + "Price Currency": { + importDescriptor: { + name: "Price Currency", + match: /Price [A-Z]{3}/, + reducer: ( + builtLine: TParsedProductImportRowData, + key, + value + ): TBuiltProductImportLine => { + builtLine["variant.prices"] = builtLine["variant.prices"] || [] + + if (typeof value === "undefined" || value === null) { + return builtLine + } + + const currency = key.trim().split(" ")[1] + + ;( + builtLine["variant.prices"] as Record[] + ).push({ + amount: parseFloat(value), + currency_code: currency, + }) + + return builtLine + }, + }, + exportDescriptor: { + isDynamic: true, + buildDynamicColumnName: (data: ProductExportPriceData) => { + return `Price ${data.currency_code?.toUpperCase()}` + }, + }, + }, + // IMAGES + "Image Url": { + importDescriptor: { + name: "Image Url", + match: /Image \d+ Url/, + reducer: (builtLine: any, key, value): TBuiltProductImportLine => { + builtLine["product.images"] = builtLine["product.images"] || [] + + if (typeof value === "undefined" || value === null) { + return builtLine + } + + builtLine["product.images"].push(value) + + return builtLine + }, + }, + exportDescriptor: { + isDynamic: true, + buildDynamicColumnName: (index: number) => { + return `Image ${index + 1} Url` + }, + }, + }, +} + +export const productSalesChannelColumnsDefinition: ProductColumnDefinition = { + "Sales Channel Name": { + importDescriptor: { + name: "Sales Channel Name", + match: /Sales Channel \d+ Name/, + reducer: (builtLine, key, value): TBuiltProductImportLine => { + builtLine["product.sales_channels"] = + builtLine["product.sales_channels"] || [] + + if (typeof value === "undefined" || value === null) { + return builtLine + } + + const channels = builtLine["product.sales_channels"] as Record< + string, + string | number + >[] + + channels.push({ + name: value, + }) + + return builtLine + }, + }, + exportDescriptor: { + isDynamic: true, + buildDynamicColumnName: (index: number) => { + return `Sales channel ${index + 1} Name` + }, + }, + }, + "Sales Channel Description": { + importDescriptor: { + name: "Sales Channel Description", + match: /Sales Channel \d+ Description/, + reducer: (builtLine, key, value): TBuiltProductImportLine => { + builtLine["product.sales_channels"] = + builtLine["product.sales_channels"] || [] + + if (typeof value === "undefined" || value === null) { + return builtLine + } + + const channels = builtLine["product.sales_channels"] as Record< + string, + string | number + >[] + + channels.push({ + description: value, + }) + + return builtLine + }, + }, + exportDescriptor: { + isDynamic: true, + buildDynamicColumnName: (index: number) => { + return `Sales channel ${index + 1} Description` + }, + }, + }, + "Sales Channel Id": { + importDescriptor: { + name: "Sales Channel Id", + match: /Sales Channel \d+ Id/, + reducer: (builtLine, key, value): TBuiltProductImportLine => { + builtLine["product.sales_channels"] = + builtLine["product.sales_channels"] || [] + + if (typeof value === "undefined" || value === null) { + return builtLine + } + + const channels = builtLine["product.sales_channels"] as Record< + string, + string | number + >[] + + channels.push({ + id: value, + }) + + return builtLine + }, + }, + }, +} + +export const productImportColumnsDefinition: CsvSchema< + TParsedProductImportRowData, + TBuiltProductImportLine +> = { + columns: Object.values(productColumnsDefinition) + .map((def) => ({ ...def.importDescriptor })) + .filter( + ( + v + ): v is CsvSchemaColumn< + TParsedProductImportRowData, + TBuiltProductImportLine + > => { + return !!v + } + ), +} + +export const productImportSalesChannelsColumnsDefinition: CsvSchema< + TParsedProductImportRowData, + TBuiltProductImportLine +> = { + columns: Object.values(productSalesChannelColumnsDefinition) + .map((def) => ({ ...def.importDescriptor })) + .filter( + ( + v + ): v is CsvSchemaColumn< + TParsedProductImportRowData, + TBuiltProductImportLine + > => { + return !!v + } + ), +} diff --git a/packages/medusa/src/strategies/batch-jobs/product/types/types.ts b/packages/medusa/src/strategies/batch-jobs/product/types/types.ts new file mode 100644 index 0000000000000..03a0d0e5f01b4 --- /dev/null +++ b/packages/medusa/src/strategies/batch-jobs/product/types/types.ts @@ -0,0 +1,111 @@ +import { BatchJob, Product, ProductVariant } from "../../../../models" +import { Selector } from "../../../../types/common" +import { CsvSchema, CsvSchemaColumn } from "../../../../interfaces/csv-parser" + +export type ProductExportBatchJobContext = { + retry_count?: number + max_retry?: number + offset?: number + limit?: number + batch_size?: number + order?: string + fields?: string + expand?: string + shape: { + prices: ProductExportPriceData[] + dynamicOptionColumnCount: number + dynamicImageColumnCount: number + dynamicSalesChannelsColumnCount: number + } + list_config?: { + select?: string[] + relations?: string[] + skip?: number + take?: number + order?: Record + } + filterable_fields?: Selector +} + +export type ProductExportBatchJob = BatchJob & { + context: ProductExportBatchJobContext +} + +export type ProductExportPriceData = { + currency_code?: string + region?: { name: string; currency_code: string; id: string } +} + +export type ProductExportColumnSchemaEntity = "product" | "variant" + +export type DynamicProductExportDescriptor = { + isDynamic: true + buildDynamicColumnName: (dataOrIndex: any) => string +} + +export type ProductExportDescriptor = + | { + accessor: (product: Product) => string + entityName: Extract + } + | { + accessor: (variant: ProductVariant) => string + entityName: Extract + } + +/** + * Import Batch job context column type. + */ +export type ProductImportJobContext = { + total: number + fileKey: string +} + +export type ProductImportBatchJob = BatchJob & { + result: Pick & { + operations: { + [K in keyof typeof OperationType]: number + } + } +} + +/** + * Schema definition of for an import CSV file. + */ +export type ProductImportCsvSchema = CsvSchema< + TParsedProductImportRowData, + TBuiltProductImportLine +> + +/** + * Supported batch job import ops. + */ +export enum OperationType { + ProductCreate = "PRODUCT_CREATE", + ProductUpdate = "PRODUCT_UPDATE", + VariantCreate = "VARIANT_CREATE", + VariantUpdate = "VARIANT_UPDATE", +} + +/** + * Data shape returned by the CSVParser. + */ +export type TParsedProductImportRowData = Record< + string, + string | number | object | undefined | (string | number | object)[] +> + +/** + * CSV parser's row reducer result data shape. + */ +export type TBuiltProductImportLine = Record + +export type ProductColumnDefinition = { + [key: string]: { + importDescriptor?: CsvSchemaColumn< + TParsedProductImportRowData, + TBuiltProductImportLine + > + exportDescriptor?: ProductExportDescriptor | DynamicProductExportDescriptor + } +} diff --git a/packages/medusa/src/strategies/batch-jobs/product/utils.ts b/packages/medusa/src/strategies/batch-jobs/product/utils.ts index 0df977a399d96..d339e6e236296 100644 --- a/packages/medusa/src/strategies/batch-jobs/product/utils.ts +++ b/packages/medusa/src/strategies/batch-jobs/product/utils.ts @@ -1,6 +1,6 @@ import set from "lodash/set" -import { TParsedProductImportRowData } from "./types" +import { TParsedProductImportRowData } from "./types/types" import { csvRevertCellContentFormatter } from "../../../utils" /** From 35e6218b8596d225ecff5ed1996a202de69a58f1 Mon Sep 17 00:00:00 2001 From: adrien2p Date: Wed, 19 Oct 2022 17:30:09 +0200 Subject: [PATCH 02/13] feat(medusa): Ensure the name of each column is shared between import and export --- packages/medusa/src/interfaces/csv-parser.ts | 31 +++-- .../strategies/batch-jobs/product/export.ts | 13 ++- .../product/types/columns-definition.ts | 109 ++++++++++-------- .../batch-jobs/product/types/types.ts | 12 +- 4 files changed, 98 insertions(+), 67 deletions(-) diff --git a/packages/medusa/src/interfaces/csv-parser.ts b/packages/medusa/src/interfaces/csv-parser.ts index 25c05a9990d94..28a5ccbce0c44 100644 --- a/packages/medusa/src/interfaces/csv-parser.ts +++ b/packages/medusa/src/interfaces/csv-parser.ts @@ -40,21 +40,28 @@ export abstract class AbstractCsvValidator ): Promise } -export type CsvSchemaColumn = { - name: string +export type CsvSchemaColumn< + TCsvLine, + TBuiltLine, + NameAsOptional = false +> = (NameAsOptional extends false + ? { + name: string + } + : {}) & { required?: boolean validator?: AbstractCsvValidator } & ( - | { - mapTo?: string - transform?: ColumnTransformer - } - | { - match?: RegExp - reducer?: ColumnReducer - transform?: ColumnTransformer - } -) + | { + mapTo?: string + transform?: ColumnTransformer + } + | { + match?: RegExp + reducer?: ColumnReducer + transform?: ColumnTransformer + } + ) export type ColumnTransformer = ( value: string, diff --git a/packages/medusa/src/strategies/batch-jobs/product/export.ts b/packages/medusa/src/strategies/batch-jobs/product/export.ts index d1281b3b95057..d41f58704b1f5 100644 --- a/packages/medusa/src/strategies/batch-jobs/product/export.ts +++ b/packages/medusa/src/strategies/batch-jobs/product/export.ts @@ -327,12 +327,12 @@ export default class ProductExportStrategy extends AbstractBatchJobStrategy { this.appendImagesDescriptors(dynamicImageColumnCount) this.appendSalesChannelsDescriptors(dynamicSalesChannelsColumnCount) - const exportedColumns = Object.entries(this.columnsDefinition) + const exportedColumns = Object.values(this.columnsDefinition) .map( - ([name, descriptor]) => + (descriptor) => descriptor.exportDescriptor && !("isDynamic" in descriptor.exportDescriptor) && - name + descriptor.name ) .filter((name): name is string => !!name) @@ -348,6 +348,7 @@ export default class ProductExportStrategy extends AbstractBatchJobStrategy { const columnName = columnNameBuilder(i) this.columnsDefinition[columnName] = { + name: columnName, exportDescriptor: { accessor: (product: Product) => product?.images[i]?.url ?? "", entityName: "product", @@ -371,6 +372,7 @@ export default class ProductExportStrategy extends AbstractBatchJobStrategy { const columnNameName = columnNameNameBuilder(i) this.columnsDefinition[columnNameName] = { + name: columnNameName, exportDescriptor: { accessor: (product: Product) => product?.sales_channels[i]?.name ?? "", @@ -381,6 +383,7 @@ export default class ProductExportStrategy extends AbstractBatchJobStrategy { const columnNameDescription = columnNameDescriptionBuilder(i) this.columnsDefinition[columnNameDescription] = { + name: columnNameDescription, exportDescriptor: { accessor: (product: Product) => product?.sales_channels[i]?.description ?? "", @@ -399,6 +402,7 @@ export default class ProductExportStrategy extends AbstractBatchJobStrategy { const columnNameName = columnNameNameBuilder(i) this.columnsDefinition[columnNameName] = { + name: columnNameName, exportDescriptor: { accessor: (productOption: Product) => productOption?.options[i]?.title ?? "", @@ -413,6 +417,7 @@ export default class ProductExportStrategy extends AbstractBatchJobStrategy { const columnNameNameValue = columnNameValueBuilder(i) this.columnsDefinition[columnNameNameValue] = { + name: columnNameNameValue, exportDescriptor: { accessor: (variant: ProductVariant) => variant?.options[i]?.value ?? "", @@ -434,6 +439,7 @@ export default class ProductExportStrategy extends AbstractBatchJobStrategy { const columnName = columnNameBuilder(priceData) this.columnsDefinition[columnName] = { + name: columnName, exportDescriptor: { accessor: (variant: ProductVariant) => { const price = variant.prices.find((variantPrice) => { @@ -458,6 +464,7 @@ export default class ProductExportStrategy extends AbstractBatchJobStrategy { const columnName = columnNameBuilder(priceData) this.columnsDefinition[columnName] = { + name: columnName, exportDescriptor: { accessor: (variant: ProductVariant) => { const price = variant.prices.find((variantPrice) => { diff --git a/packages/medusa/src/strategies/batch-jobs/product/types/columns-definition.ts b/packages/medusa/src/strategies/batch-jobs/product/types/columns-definition.ts index 6fe7607da6b51..8d8b63d0aad69 100644 --- a/packages/medusa/src/strategies/batch-jobs/product/types/columns-definition.ts +++ b/packages/medusa/src/strategies/batch-jobs/product/types/columns-definition.ts @@ -9,8 +9,8 @@ import { CsvSchema, CsvSchemaColumn } from "../../../../interfaces/csv-parser" export const productColumnsDefinition: ProductColumnDefinition = { "Product id": { + name: "Product id", importDescriptor: { - name: "Product id", mapTo: "product.id", }, exportDescriptor: { @@ -19,8 +19,8 @@ export const productColumnsDefinition: ProductColumnDefinition = { }, }, "Product Handle": { + name: "Product Handle", importDescriptor: { - name: "Product Handle", mapTo: "product.handle", required: true, }, @@ -30,8 +30,8 @@ export const productColumnsDefinition: ProductColumnDefinition = { }, }, "Product Title": { + name: "Product Title", importDescriptor: { - name: "Product Title", mapTo: "product.title", }, exportDescriptor: { @@ -40,8 +40,8 @@ export const productColumnsDefinition: ProductColumnDefinition = { }, }, "Product Subtitle": { + name: "Product Subtitle", importDescriptor: { - name: "Product Subtitle", mapTo: "product.subtitle", }, exportDescriptor: { @@ -50,8 +50,8 @@ export const productColumnsDefinition: ProductColumnDefinition = { }, }, "Product Description": { + name: "Product Description", importDescriptor: { - name: "Product Description", mapTo: "product.description", }, exportDescriptor: { @@ -60,8 +60,8 @@ export const productColumnsDefinition: ProductColumnDefinition = { }, }, "Product Status": { + name: "Product Status", importDescriptor: { - name: "Product Status", mapTo: "product.status", }, exportDescriptor: { @@ -70,8 +70,8 @@ export const productColumnsDefinition: ProductColumnDefinition = { }, }, "Product Thumbnail": { + name: "Product Thumbnail", importDescriptor: { - name: "Product Thumbnail", mapTo: "product.thumbnail", }, exportDescriptor: { @@ -80,8 +80,8 @@ export const productColumnsDefinition: ProductColumnDefinition = { }, }, "Product Weight": { + name: "Product Weight", importDescriptor: { - name: "Product Weight", mapTo: "product.weight", }, exportDescriptor: { @@ -90,8 +90,8 @@ export const productColumnsDefinition: ProductColumnDefinition = { }, }, "Product Length": { + name: "Product Length", importDescriptor: { - name: "Product Length", mapTo: "product.length", }, exportDescriptor: { @@ -100,8 +100,8 @@ export const productColumnsDefinition: ProductColumnDefinition = { }, }, "Product Width": { + name: "Product Width", importDescriptor: { - name: "Product Width", mapTo: "product.width", }, exportDescriptor: { @@ -110,8 +110,8 @@ export const productColumnsDefinition: ProductColumnDefinition = { }, }, "Product Height": { + name: "Product Height", importDescriptor: { - name: "Product Height", mapTo: "product.height", }, exportDescriptor: { @@ -120,8 +120,8 @@ export const productColumnsDefinition: ProductColumnDefinition = { }, }, "Product HS Code": { + name: "Product HS Code", importDescriptor: { - name: "Product HS Code", mapTo: "product.hs_code", }, exportDescriptor: { @@ -131,8 +131,8 @@ export const productColumnsDefinition: ProductColumnDefinition = { }, }, "Product Origin Country": { + name: "Product Origin Country", importDescriptor: { - name: "Product Origin Country", mapTo: "product.origin_country", }, exportDescriptor: { @@ -142,8 +142,8 @@ export const productColumnsDefinition: ProductColumnDefinition = { }, }, "Product MID Code": { + name: "Product MID Code", importDescriptor: { - name: "Product MID Code", mapTo: "product.mid_code", }, exportDescriptor: { @@ -153,8 +153,8 @@ export const productColumnsDefinition: ProductColumnDefinition = { }, }, "Product Material": { + name: "Product Material", importDescriptor: { - name: "Product Material", mapTo: "product.material", }, exportDescriptor: { @@ -167,8 +167,8 @@ export const productColumnsDefinition: ProductColumnDefinition = { // PRODUCT-COLLECTION "Product Collection Title": { + name: "Product Collection Title", importDescriptor: { - name: "Product Collection Title", mapTo: "product.collection.title", }, exportDescriptor: { @@ -177,8 +177,8 @@ export const productColumnsDefinition: ProductColumnDefinition = { }, }, "Product Collection Handle": { + name: "Product Collection Handle", importDescriptor: { - name: "Product Collection Handle", mapTo: "product.collection.handle", }, exportDescriptor: { @@ -190,8 +190,8 @@ export const productColumnsDefinition: ProductColumnDefinition = { // PRODUCT-TYPE "Product Type": { + name: "Product Type", importDescriptor: { - name: "Product Type", match: /Product Type/, reducer: ( builtLine: TParsedProductImportRowData, @@ -216,8 +216,8 @@ export const productColumnsDefinition: ProductColumnDefinition = { // PRODUCT-TAGS "Product Tags": { + name: "Product Tags", importDescriptor: { - name: "Product Tags", mapTo: "product.tags", transform: (value: string) => `${value}`.split(",").map((v) => ({ value: v })), @@ -232,8 +232,8 @@ export const productColumnsDefinition: ProductColumnDefinition = { // "Product Discountable": { + name: "Product Discountable", importDescriptor: { - name: "Product Discountable", mapTo: "product.discountable", }, exportDescriptor: { @@ -243,8 +243,8 @@ export const productColumnsDefinition: ProductColumnDefinition = { }, }, "Product External ID": { + name: "Product External ID", importDescriptor: { - name: "Product External ID", mapTo: "product.external_id", }, exportDescriptor: { @@ -256,6 +256,7 @@ export const productColumnsDefinition: ProductColumnDefinition = { // PRODUCT-PROFILE "Product Profile Name": { + name: "Product Profile Name", exportDescriptor: { accessor: (product: Product): string => product?.profile?.name ?? "", entityName: "product", @@ -263,6 +264,7 @@ export const productColumnsDefinition: ProductColumnDefinition = { }, "Product Profile Type": { + name: "Product Profile Type", exportDescriptor: { accessor: (product: Product): string => product?.profile?.type ?? "", entityName: "product", @@ -272,8 +274,8 @@ export const productColumnsDefinition: ProductColumnDefinition = { // VARIANTS "Variant id": { + name: "Variant id", importDescriptor: { - name: "Variant id", mapTo: "variant.id", }, exportDescriptor: { @@ -282,8 +284,9 @@ export const productColumnsDefinition: ProductColumnDefinition = { }, }, "Variant Title": { + name: "Variant Title", + importDescriptor: { - name: "Variant Title", mapTo: "variant.title", }, exportDescriptor: { @@ -292,8 +295,9 @@ export const productColumnsDefinition: ProductColumnDefinition = { }, }, "Variant SKU": { + name: "Variant SKU", + importDescriptor: { - name: "Variant SKU", mapTo: "variant.sku", }, exportDescriptor: { @@ -302,8 +306,9 @@ export const productColumnsDefinition: ProductColumnDefinition = { }, }, "Variant Barcode": { + name: "Variant Barcode", + importDescriptor: { - name: "Variant Barcode", mapTo: "variant.barcode", }, exportDescriptor: { @@ -312,8 +317,9 @@ export const productColumnsDefinition: ProductColumnDefinition = { }, }, "Variant Inventory Quantity": { + name: "Variant Inventory Quantity", + importDescriptor: { - name: "Variant Inventory Quantity", mapTo: "variant.inventory_quantity", }, exportDescriptor: { @@ -323,8 +329,9 @@ export const productColumnsDefinition: ProductColumnDefinition = { }, }, "Variant Allow backorder": { + name: "Variant Allow backorder", + importDescriptor: { - name: "Variant Allow backorder", mapTo: "variant.allow_backorder", }, exportDescriptor: { @@ -334,8 +341,8 @@ export const productColumnsDefinition: ProductColumnDefinition = { }, }, "Variant Manage inventory": { + name: "Variant Manage inventory", importDescriptor: { - name: "Variant Manage inventory", mapTo: "variant.manage_inventory", }, exportDescriptor: { @@ -345,8 +352,9 @@ export const productColumnsDefinition: ProductColumnDefinition = { }, }, "Variant Weight": { + name: "Variant Weight", + importDescriptor: { - name: "Variant Weight", mapTo: "variant.weight", }, exportDescriptor: { @@ -356,8 +364,9 @@ export const productColumnsDefinition: ProductColumnDefinition = { }, }, "Variant Length": { + name: "Variant Length", + importDescriptor: { - name: "Variant Length", mapTo: "variant.length", }, exportDescriptor: { @@ -367,8 +376,8 @@ export const productColumnsDefinition: ProductColumnDefinition = { }, }, "Variant Width": { + name: "Variant Width", importDescriptor: { - name: "Variant Width", mapTo: "variant.width", }, exportDescriptor: { @@ -378,8 +387,8 @@ export const productColumnsDefinition: ProductColumnDefinition = { }, }, "Variant Height": { + name: "Variant Height", importDescriptor: { - name: "Variant Height", mapTo: "variant.height", }, exportDescriptor: { @@ -389,8 +398,8 @@ export const productColumnsDefinition: ProductColumnDefinition = { }, }, "Variant HS Code": { + name: "Variant HS Code", importDescriptor: { - name: "Variant HS Code", mapTo: "variant.hs_code", }, exportDescriptor: { @@ -400,8 +409,8 @@ export const productColumnsDefinition: ProductColumnDefinition = { }, }, "Variant Origin Country": { + name: "Variant Origin Country", importDescriptor: { - name: "Variant Origin Country", mapTo: "variant.origin_country", }, exportDescriptor: { @@ -411,8 +420,8 @@ export const productColumnsDefinition: ProductColumnDefinition = { }, }, "Variant MID Code": { + name: "Variant MID Code", importDescriptor: { - name: "Variant MID Code", mapTo: "variant.mid_code", }, exportDescriptor: { @@ -422,8 +431,8 @@ export const productColumnsDefinition: ProductColumnDefinition = { }, }, "Variant Material": { + name: "Variant Material", importDescriptor: { - name: "Variant Material", mapTo: "variant.material", }, exportDescriptor: { @@ -438,8 +447,8 @@ export const productColumnsDefinition: ProductColumnDefinition = { // PRODUCT_OPTIONS "Option Name": { + name: "Option Name", importDescriptor: { - name: "Option Name", match: /Option \d+ Name/, reducer: (builtLine, key, value): TBuiltProductImportLine => { builtLine["product.options"] = builtLine["product.options"] || [] @@ -466,8 +475,8 @@ export const productColumnsDefinition: ProductColumnDefinition = { }, }, "Option Value": { + name: "Option Value", importDescriptor: { - name: "Option Value", match: /Option \d+ Value/, reducer: ( builtLine: TParsedProductImportRowData, @@ -505,8 +514,8 @@ export const productColumnsDefinition: ProductColumnDefinition = { // PRICES "Price Region": { + name: "Price Region", importDescriptor: { - name: "Price Region", match: /Price (.*) \[([A-Z]{3})\]/, reducer: ( builtLine: TParsedProductImportRowData, @@ -543,8 +552,8 @@ export const productColumnsDefinition: ProductColumnDefinition = { }, }, "Price Currency": { + name: "Price Currency", importDescriptor: { - name: "Price Currency", match: /Price [A-Z]{3}/, reducer: ( builtLine: TParsedProductImportRowData, @@ -578,8 +587,8 @@ export const productColumnsDefinition: ProductColumnDefinition = { }, // IMAGES "Image Url": { + name: "Image Url", importDescriptor: { - name: "Image Url", match: /Image \d+ Url/, reducer: (builtLine: any, key, value): TBuiltProductImportLine => { builtLine["product.images"] = builtLine["product.images"] || [] @@ -604,8 +613,8 @@ export const productColumnsDefinition: ProductColumnDefinition = { export const productSalesChannelColumnsDefinition: ProductColumnDefinition = { "Sales Channel Name": { + name: "Sales Channel Name", importDescriptor: { - name: "Sales Channel Name", match: /Sales Channel \d+ Name/, reducer: (builtLine, key, value): TBuiltProductImportLine => { builtLine["product.sales_channels"] = @@ -635,8 +644,8 @@ export const productSalesChannelColumnsDefinition: ProductColumnDefinition = { }, }, "Sales Channel Description": { + name: "Sales Channel Description", importDescriptor: { - name: "Sales Channel Description", match: /Sales Channel \d+ Description/, reducer: (builtLine, key, value): TBuiltProductImportLine => { builtLine["product.sales_channels"] = @@ -666,8 +675,8 @@ export const productSalesChannelColumnsDefinition: ProductColumnDefinition = { }, }, "Sales Channel Id": { + name: "Sales Channel Id", importDescriptor: { - name: "Sales Channel Id", match: /Sales Channel \d+ Id/, reducer: (builtLine, key, value): TBuiltProductImportLine => { builtLine["product.sales_channels"] = @@ -696,8 +705,10 @@ export const productImportColumnsDefinition: CsvSchema< TParsedProductImportRowData, TBuiltProductImportLine > = { - columns: Object.values(productColumnsDefinition) - .map((def) => ({ ...def.importDescriptor })) + columns: Object.entries(productColumnsDefinition) + .map(([name, def]) => { + return def.importDescriptor && { name, ...def.importDescriptor } + }) .filter( ( v @@ -714,8 +725,10 @@ export const productImportSalesChannelsColumnsDefinition: CsvSchema< TParsedProductImportRowData, TBuiltProductImportLine > = { - columns: Object.values(productSalesChannelColumnsDefinition) - .map((def) => ({ ...def.importDescriptor })) + columns: Object.entries(productSalesChannelColumnsDefinition) + .map(([name, def]) => { + return def.importDescriptor && { name, ...def.importDescriptor } + }) .filter( ( v diff --git a/packages/medusa/src/strategies/batch-jobs/product/types/types.ts b/packages/medusa/src/strategies/batch-jobs/product/types/types.ts index 03a0d0e5f01b4..3bdad7b3fbb70 100644 --- a/packages/medusa/src/strategies/batch-jobs/product/types/types.ts +++ b/packages/medusa/src/strategies/batch-jobs/product/types/types.ts @@ -100,12 +100,16 @@ export type TParsedProductImportRowData = Record< */ export type TBuiltProductImportLine = Record +export type ProductImportDescriptor = CsvSchemaColumn< + TParsedProductImportRowData, + TBuiltProductImportLine, + true +> + export type ProductColumnDefinition = { [key: string]: { - importDescriptor?: CsvSchemaColumn< - TParsedProductImportRowData, - TBuiltProductImportLine - > + name: string + importDescriptor?: ProductImportDescriptor exportDescriptor?: ProductExportDescriptor | DynamicProductExportDescriptor } } From c4598b6dc9c58f8d542cbba1881c373b102bebfa Mon Sep 17 00:00:00 2001 From: adrien2p Date: Wed, 19 Oct 2022 17:31:54 +0200 Subject: [PATCH 03/13] revert doc --- .../content/advanced/backend/endpoints/add.md | 64 ++++------- .../advanced/backend/plugins/create.md | 107 ++++++------------ .../references/js-client/classes/Admin.md | 60 +++++----- 3 files changed, 89 insertions(+), 142 deletions(-) diff --git a/docs/content/advanced/backend/endpoints/add.md b/docs/content/advanced/backend/endpoints/add.md index 9c97eb5b8dbfd..9e78a59e95e48 100644 --- a/docs/content/advanced/backend/endpoints/add.md +++ b/docs/content/advanced/backend/endpoints/add.md @@ -4,14 +4,11 @@ In this document, you’ll learn how to create endpoints in your Medusa server. ## Overview -Custom endpoints reside under the `src/api` directory in your Medusa Backend. They're defined in a TypeScript or -JavaScript file that is named `index` (for example, `types.ts`). This file should export a function that returns an -Express router. +Custom endpoints reside under the `src/api` directory in your Medusa Backend. They're defined in a TypeScript or JavaScript file that is named `index` (for example, `index.ts`). This file should export a function that returns an Express router. ## Implementation -To create a new endpoint, start by creating a new file in `src/api` called `types.ts`. At its basic format, `types.ts` -should look something like this: +To create a new endpoint, start by creating a new file in `src/api` called `index.ts`. At its basic format, `index.ts` should look something like this: ```ts import { Router } from "express" @@ -32,8 +29,7 @@ export default (rootDirectory, pluginOptions) => { This exports a function that returns an Express router. The function receives two parameters: - `rootDirectory` is the absolute path to the root directory that your server is running from. -- `pluginOptions` is an object that has your plugin's options. If your API route is not implemented in a plugin, then it - will be an empty object. +- `pluginOptions` is an object that has your plugin's options. If your API route is not implemented in a plugin, then it will be an empty object. ### Endpoints Path @@ -41,28 +37,23 @@ Your endpoint can be under any path you wish. By Medusa’s conventions: -- All Storefront REST APIs are prefixed by `/store`. For example, the `/store/products` endpoint lets you retrieve the - products to display them on your storefront. -- All Admin REST APIs are prefixed by `/admin`. For example, the `/admin/products` endpoint lets you retrieve the - products to display them on your Admin. +- All Storefront REST APIs are prefixed by `/store`. For example, the `/store/products` endpoint lets you retrieve the products to display them on your storefront. +- All Admin REST APIs are prefixed by `/admin`. For example, the `/admin/products` endpoint lets you retrieve the products to display them on your Admin. -You can also create endpoints that don't reside under these two prefixes, similar to the `hello` endpoint in the -previous example. +You can also create endpoints that don't reside under these two prefixes, similar to the `hello` endpoint in the previous example. ## CORS Configuration -If you’re adding a storefront or admin endpoint and you want to access these endpoints from the storefront or Medusa -admin, you need to pass your endpoints Cross-Origin Resource Origin (CORS) options using the `cors` package. +If you’re adding a storefront or admin endpoint and you want to access these endpoints from the storefront or Medusa admin, you need to pass your endpoints Cross-Origin Resource Origin (CORS) options using the `cors` package. -First, you need to import your Medusa configurations along with the `cors` library: +First, you need to import your Medusa configurations along with the `cors` library: ```ts import cors from "cors" import { projectConfig } from "../../medusa-config" ``` -Then, create an object that will hold the Cross-Origin Resource Sharing (CORS) configurations. If it’s a storefront -endpoint, pass the `origin` property storefront options: +Then, create an object that will hold the Cross-Origin Resource Sharing (CORS) configurations. If it’s a storefront endpoint, pass the `origin` property storefront options: ```ts const corsOptions = { @@ -80,7 +71,7 @@ const corsOptions = { } ``` -Finally, for each route you add, create an `OPTIONS` request and add `cors` as a middleware for the route: +Finally, for each route you add, create an `OPTIONS` request and add `cors` as a middleware for the route: ```ts router.options("/admin/hello", cors(corsOptions)) @@ -93,7 +84,7 @@ router.get("/admin/hello", cors(corsOptions), (req, res) => { ### Same File -You can add more than one endpoint in `src/api/types.ts`: +You can add more than one endpoint in `src/api/index.ts`: ```ts router.options("/store/hello", cors(storeCorsOptions)) @@ -115,7 +106,7 @@ router.get("/admin/hello", cors(adminCorsOptions), (req, res) => { Alternatively, you can add multiple files for each endpoint or set of endpoints for readability and easy maintenance. -To do that with the previous example, first, create the file `src/api/store.ts` with the following content: +To do that with the previous example, first, create the file `src/api/store.ts` with the following content: ```ts import cors from "cors" @@ -135,9 +126,9 @@ export default (router) => { } ``` -You export a function that receives an Express router as a parameter and adds the endpoint `store/hello` to it. +You export a function that receives an Express router as a parameter and adds the endpoint `store/hello` to it. -Next, create the file `src/api/admin.ts` with the following content: +Next, create the file `src/api/admin.ts` with the following content: ```ts import cors from "cors" @@ -157,9 +148,9 @@ export default (router) => { } ``` -Again, you export a function that receives an Express router as a parameter and adds the endpoint `admin/hello` to it. +Again, you export a function that receives an Express router as a parameter and adds the endpoint `admin/hello` to it. -Finally, in `src/api/types.ts` import the two functions at the beginning of the file: +Finally, in `src/api/index.ts` import the two functions at the beginning of the file: ```ts import storeRoutes from "./store" @@ -185,7 +176,7 @@ Protected routes are routes that should be accessible by logged-in customers or ### Protect Store Routes -To make a storefront route protected, first, import the `authenticate-customer` middleware: +To make a storefront route protected, first, import the `authenticate-customer` middleware: ```ts import authenticate from "@medusajs/medusa/dist/api/middlewares/authenticate-customer" @@ -204,14 +195,13 @@ router.get("/store/hello", cors(corsOptions), authenticate(), async (req, res) = }) ``` -Please note that the endpoint is still accessible by all users, however, you’ll be able to access the current logged-in -customer if there’s any. +Please note that the endpoint is still accessible by all users, however, you’ll be able to access the current logged-in customer if there’s any. To disallow guest customers from accessing the endpoint, you can throw an error if `req.user` is `false`. ### Protect Admin Routes -To make an admin route protected, first, import the `authenticate` middleware: +To make an admin route protected, first, import the `authenticate` middleware: ```ts import authenticate from "@medusajs/medusa/dist/api/middlewares/authenticate" @@ -235,12 +225,9 @@ Now, only authenticated users can access this endpoint. ## Use Services -Services in Medusa bundle a set of functionalities into one class. Then, you can use that class anywhere in your -backend. For example, you can use the `ProductService` to retrieve products or perform operations like creating or -updating a product. +Services in Medusa bundle a set of functionalities into one class. Then, you can use that class anywhere in your backend. For example, you can use the `ProductService` to retrieve products or perform operations like creating or updating a product. -You can retrieve any registered service in your endpoint using `req.scope.resolve` passing it the service’s registration -name. +You can retrieve any registered service in your endpoint using `req.scope.resolve` passing it the service’s registration name. Here’s an example of an endpoint that retrieves the count of products in your store: @@ -256,13 +243,11 @@ router.get("/admin/products/count", cors(corsOptions), authenticate(), (req, res }) ``` -The `productService` has a `count` method that returns a Promise. This Promise resolves to the count of the products. -You return a JSON of the product count. +The `productService` has a `count` method that returns a Promise. This Promise resolves to the count of the products. You return a JSON of the product count. ## Building Files -Custom endpoints must be transpiled and moved to the `dist` directory. This happens when you run your server -using `medusa develop` and while it’s running, and when you run the following command: +Custom endpoints must be transpiled and moved to the `dist` directory. This happens when you run your server using `medusa develop` and while it’s running, and when you run the following command: ```bash npm2yarn npm run build @@ -270,6 +255,5 @@ npm run build ## What’s Next -- Check out the available [Admin](https://docs.medusajs.com/api/admin/) - and [Storefront](https://docs.medusajs.com/api/store/) APIs. +- Check out the available [Admin](https://docs.medusajs.com/api/admin/) and [Storefront](https://docs.medusajs.com/api/store/) APIs. - Learn how to create a [Service](./../services/create-service.md). diff --git a/docs/content/advanced/backend/plugins/create.md b/docs/content/advanced/backend/plugins/create.md index f61b0e9816665..b78e384660526 100644 --- a/docs/content/advanced/backend/plugins/create.md +++ b/docs/content/advanced/backend/plugins/create.md @@ -1,12 +1,10 @@ # Create a Plugin -In this document, you’ll learn how to create a plugin and publish it. If you’re interested to learn more about what -plugins are and where to find available official and community plugins, check out the [overview document](overview.md). +In this document, you’ll learn how to create a plugin and publish it. If you’re interested to learn more about what plugins are and where to find available official and community plugins, check out the [overview document](overview.md). ## Prerequisites -This guide uses the Medusa CLI throughout different steps. If you don’t have the Medusa CLI installed you can install it -with the following command: +This guide uses the Medusa CLI throughout different steps. If you don’t have the Medusa CLI installed you can install it with the following command: ```bash npm2yarn npm install @medusajs/medusa-cli -g @@ -14,32 +12,27 @@ npm install @medusajs/medusa-cli -g :::note -If you run into any errors while installing the CLI tool, check out -the [troubleshooting guide](../../../troubleshooting/cli-installation-errors.mdx). +If you run into any errors while installing the CLI tool, check out the [troubleshooting guide](../../../troubleshooting/cli-installation-errors.mdx). ::: ## Initialize Project -The recommended way to create a plugin is using the Medusa CLI. Run the following command to create a new Medusa -project: +The recommended way to create a plugin is using the Medusa CLI. Run the following command to create a new Medusa project: ```bash medusa new medusa-plugin-custom ``` -Where `medusa-plugin-custom` is the name of the plugin you’re creating. In Medusa, plugins are named based on their -functionalities. +Where `medusa-plugin-custom` is the name of the plugin you’re creating. In Medusa, plugins are named based on their functionalities. -By convention, all plugin names start with `medusa` followed by a descriptive name of what the plugin does. For example, -the Stripe plugin is named `medusa-payment-stripe`. +By convention, all plugin names start with `medusa` followed by a descriptive name of what the plugin does. For example, the Stripe plugin is named `medusa-payment-stripe`. ## Changes to package.json ### Rename Project Name -Update the `name` field in the `package.json` file to the name of your plugin. This should be the same name that you -chose when running the `medusa new` command. +Update the `name` field in the `package.json` file to the name of your plugin. This should be the same name that you chose when running the `medusa new` command. ### Change Dependencies @@ -63,9 +56,7 @@ A basic Medusa server installed with the `medusa new` command has dependencies s } ``` -For a plugin, a lot of these dependencies are not necessary or should be labeled -as [peer dependencies](https://docs.npmjs.com/cli/v8/configuring-npm/package-json#peerdependencies). Therefore, it’s -important to make changes to the dependencies of your plugin. +For a plugin, a lot of these dependencies are not necessary or should be labeled as [peer dependencies](https://docs.npmjs.com/cli/v8/configuring-npm/package-json#peerdependencies). Therefore, it’s important to make changes to the dependencies of your plugin. The recommended change is the following: @@ -83,11 +74,9 @@ The recommended change is the following: } ``` -The packages `@medusajs/medusa` and `medusa-interfaces` act as peer dependencies. They’ll be installed while you develop -your package, and they are required when your plugin is installed in another NPM project. +The packages `@medusajs/medusa` and `medusa-interfaces` act as peer dependencies. They’ll be installed while you develop your package, and they are required when your plugin is installed in another NPM project. -You remove the packages `medusa-fulfillment-manual`, `medusa-payment-manual`, and `medusa-payment-stripe` as they are -fulfillment and payment plugins necessary for a Medusa server, but not for a plugin. +You remove the packages `medusa-fulfillment-manual`, `medusa-payment-manual`, and `medusa-payment-stripe` as they are fulfillment and payment plugins necessary for a Medusa server, but not for a plugin. Additionally, you remove `@medusajs/medusa-cli` as you don’t need to use the Medusa CLI while developing a plugin. @@ -97,15 +86,13 @@ Once you’re done making these changes, re-run the install command to update yo npm install ``` -This section includes recommended changes to your `package.json`. You can skip any of these changes if you don’t find -them necessary to your plugin. +This section includes recommended changes to your `package.json`. You can skip any of these changes if you don’t find them necessary to your plugin. ### Recommended: Change scripts :::caution -If you don't make changes to the `build` and `watch` commands, please be aware of -the [expected plugin structure](#plugin-structure). +If you don't make changes to the `build` and `watch` commands, please be aware of the [expected plugin structure](#plugin-structure). ::: @@ -151,30 +138,24 @@ npm install --save-dev cross-env ### Recommended: Change Basic Info -`package.json` holds information that further describes the package or the author that created the package. It is -recommended to make the following changes: +`package.json` holds information that further describes the package or the author that created the package. It is recommended to make the following changes: - `description`: Change this to a sentence that describes what your plugin does. - `author`: Your name and email. - `repository`: The repository that holds your plugin’s codebase. -- `keywords`: This should hold the keywords that are related to your plugin. It’s recommended that all plugins use the - keywords `medusa-plugin` or `medusa`. +- `keywords`: This should hold the keywords that are related to your plugin. It’s recommended that all plugins use the keywords `medusa-plugin` or `medusa`. ## Develop your Plugin -Now, You can start developing your plugin. This can include adding services, endpoints, entities, or anything that's -relevant to your plugin. +Now, You can start developing your plugin. This can include adding services, endpoints, entities, or anything that's relevant to your plugin. ### Plugin Structure -While developing your plugin, you can create your TypeScript or JavaScript files under the `src` directory. This -includes creating services, endpoints, migrations, etc... +While developing your plugin, you can create your TypeScript or JavaScript files under the `src` directory. This includes creating services, endpoints, migrations, etc... -However, before you test the changes on a Medusa server or publish your plugin, you must transpile your files, which -moves them into the root of your plugin directory. +However, before you test the changes on a Medusa server or publish your plugin, you must transpile your files, which moves them into the root of your plugin directory. -For example, if you have an endpoint in `src/api/index.js`, after running the `build` or `watch` -commands [as defined earlier](#change-scripts), the file should be transpiled into `api/index.js` in your plugin's root. +For example, if you have an endpoint in `src/api/index.js`, after running the `build` or `watch` commands [as defined earlier](#change-scripts), the file should be transpiled into `api/index.js` in your plugin's root. If files and directories aren't placed in the root of your plugin, the Medusa server won't detect or load them. @@ -195,7 +176,7 @@ medusa-plugin-custom | | | |_ _ _ api | | | -| | |_ _ _ types.ts +| | |_ _ _ index.ts | | | |_ _ _ migrations | | @@ -207,8 +188,7 @@ medusa-plugin-custom ### Development Resources -This guide doesn't cover how to create different files and components. If you’re interested in learning how to do that, -you can check out these guides: +This guide doesn't cover how to create different files and components. If you’re interested in learning how to do that, you can check out these guides: - How to [create endpoints](../endpoints/add.md) - How to [create a service](../services/create-service.md) @@ -218,8 +198,7 @@ you can check out these guides: ## Add Plugin Configuration -Plugins often allow developers that will later use them to enter their own configuration. For example, you can allow -developers to specify the API key of a service you’re integrating. +Plugins often allow developers that will later use them to enter their own configuration. For example, you can allow developers to specify the API key of a service you’re integrating. To pass a plugin its configurations on a Medusa server, you have to add it to the `plugins` array in `medusa-config.js`: @@ -271,8 +250,7 @@ Make sure to include in the README of your plugin the configurations that can be ## Test Your Plugin -While you develop your plugin, you’ll need to test it on an actual Medusa server. This can be done by using -the [npm link](https://docs.npmjs.com/cli/v8/commands/npm-link) command. +While you develop your plugin, you’ll need to test it on an actual Medusa server. This can be done by using the [npm link](https://docs.npmjs.com/cli/v8/commands/npm-link) command. In the root of your plugin directory, run the following command: @@ -288,8 +266,7 @@ npm link medusa-plugin-custom Where `medusa-plugin-custom` is the package name of your plugin. -After linking to your plugin in a local Medusa server, either run the `build` or `watch` commands in your plugin -directory: +After linking to your plugin in a local Medusa server, either run the `build` or `watch` commands in your plugin directory: ```bash npm2yarn # in the directory of the plugin @@ -298,8 +275,7 @@ npm run watch :::tip -If you’re running the `watch` command, you don’t need to run the `build` command every time you make a change to your -plugin. +If you’re running the `watch` command, you don’t need to run the `build` command every time you make a change to your plugin. ::: @@ -320,8 +296,7 @@ const plugins = [ :::note -If your plugin has migrations, you must run them before you start the server. Check out -the [Migrations guide](../migrations/overview.md#migrate-command) for more details. +If your plugin has migrations, you must run them before you start the server. Check out the [Migrations guide](../migrations/overview.md#migrate-command) for more details. ::: @@ -335,8 +310,7 @@ npm run start #### Error: The class must be a valid service implementation -Please make sure that your plugin is following the correct structure. If the error persists then please try the -following fix: +Please make sure that your plugin is following the correct structure. If the error persists then please try the following fix: ```bash npm2yarn cd /node_modules/medusa-interfaces @@ -351,8 +325,7 @@ npm link your-plugin Where `` is the path to your Medusa server and `` is the path to your plugin. -This links the `medusa-interfaces` package from your `medusa-backend` to your plugin directory and then links your -plugin to your `medusa-backend`. +This links the `medusa-interfaces` package from your `medusa-backend` to your plugin directory and then links your plugin to your `medusa-backend`. #### APIs not loading @@ -369,8 +342,7 @@ cd npm run start ``` -Where `` is the path to your Medusa server, `` is the path to your plugin and `` -is the name of your plugin as it is in your plugin `package.json` file. +Where `` is the path to your Medusa server, `` is the path to your plugin and `` is the name of your plugin as it is in your plugin `package.json` file. :::note @@ -382,8 +354,7 @@ It is safe to ignore any `cross-env: command not found` error you may receive. Not all files that you use while developing your plugin are necessary to be published. -For example, the files you add in the `src` directory are compiled to the root of the plugin directory before -publishing. Then, when a developer installs your plugin, they’ll just be using the files in the root. +For example, the files you add in the `src` directory are compiled to the root of the plugin directory before publishing. Then, when a developer installs your plugin, they’ll just be using the files in the root. So, you can ignore files and directories like `src` from the final published NPM package. @@ -414,15 +385,13 @@ develop.sh ## Publish Plugin -Once you’re done developing your plugin you can publish the package on NPM’s registry so that other developers can -benefit from it and use it. +Once you’re done developing your plugin you can publish the package on NPM’s registry so that other developers can benefit from it and use it. Before you publish a plugin, you must [create an account on NPM](https://www.npmjs.com/signup). ### Prepare Plugin -Before you publish or update your plugin, make sure to run the `prepare` -command [defined earlier](#recommended-change-scripts): +Before you publish or update your plugin, make sure to run the `prepare` command [defined earlier](#recommended-change-scripts): ```bash npm2yarn npm run prepare @@ -468,17 +437,13 @@ npm publish ## Add Plugin to Medusa’s Repository -All officially-supported plugins are available in -the [`packages` directory of the Medusa GitHub repository](https://github.com/medusajs/medusa/tree/master/packages). +All officially-supported plugins are available in the [`packages` directory of the Medusa GitHub repository](https://github.com/medusajs/medusa/tree/master/packages). -If you’re interested in adding your plugin, you need to create a new pull request (PR) where you add your plugin inside -the `packages` directory. Our team will then review your plugin, and if it’s approved the PR will be merged and your -plugin will be available on Medusa’s repository. +If you’re interested in adding your plugin, you need to create a new pull request (PR) where you add your plugin inside the `packages` directory. Our team will then review your plugin, and if it’s approved the PR will be merged and your plugin will be available on Medusa’s repository. :::note -Before contributing to the Medusa repository, please check out -the [contribution guidelines](https://github.com/medusajs/medusa/blob/master/CONTRIBUTING.md). +Before contributing to the Medusa repository, please check out the [contribution guidelines](https://github.com/medusajs/medusa/blob/master/CONTRIBUTING.md). ::: @@ -492,8 +457,6 @@ npm install medusa-plugin-custom ## What’s Next -- Check - out [available Services in Medusa](references/services/../../../../../references/services/classes/AuthService.md) that - you can use in your plugin. +- Check out [available Services in Medusa](references/services/../../../../../references/services/classes/AuthService.md) that you can use in your plugin. - Check out [available events](../subscribers/events-list.md) that you can listen to in Subscribers. - Check out [available official plugins](https://github.com/medusajs/medusa/tree/master/packages). diff --git a/docs/content/references/js-client/classes/Admin.md b/docs/content/references/js-client/classes/Admin.md index 1c82d51d7e875..83e1d58bb4eed 100644 --- a/docs/content/references/js-client/classes/Admin.md +++ b/docs/content/references/js-client/classes/Admin.md @@ -14,7 +14,7 @@ #### Defined in -[medusa-js/src/resources/admin/types.ts:34](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L34) +[medusa-js/src/resources/admin/index.ts:34](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L34) ___ @@ -24,7 +24,7 @@ ___ #### Defined in -[medusa-js/src/resources/admin/types.ts:35](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L35) +[medusa-js/src/resources/admin/index.ts:35](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L35) ___ @@ -34,7 +34,7 @@ ___ #### Defined in -[medusa-js/src/resources/admin/types.ts:40](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L40) +[medusa-js/src/resources/admin/index.ts:40](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L40) ___ @@ -44,7 +44,7 @@ ___ #### Defined in -[medusa-js/src/resources/admin/types.ts:39](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L39) +[medusa-js/src/resources/admin/index.ts:39](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L39) ___ @@ -54,7 +54,7 @@ ___ #### Defined in -[medusa-js/src/resources/admin/types.ts:37](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L37) +[medusa-js/src/resources/admin/index.ts:37](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L37) ___ @@ -64,7 +64,7 @@ ___ #### Defined in -[medusa-js/src/resources/admin/types.ts:36](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L36) +[medusa-js/src/resources/admin/index.ts:36](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L36) ___ @@ -74,7 +74,7 @@ ___ #### Defined in -[medusa-js/src/resources/admin/types.ts:38](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L38) +[medusa-js/src/resources/admin/index.ts:38](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L38) ___ @@ -84,7 +84,7 @@ ___ #### Defined in -[medusa-js/src/resources/admin/types.ts:41](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L41) +[medusa-js/src/resources/admin/index.ts:41](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L41) ___ @@ -94,7 +94,7 @@ ___ #### Defined in -[medusa-js/src/resources/admin/types.ts:42](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L42) +[medusa-js/src/resources/admin/index.ts:42](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L42) ___ @@ -104,7 +104,7 @@ ___ #### Defined in -[medusa-js/src/resources/admin/types.ts:43](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L43) +[medusa-js/src/resources/admin/index.ts:43](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L43) ___ @@ -114,7 +114,7 @@ ___ #### Defined in -[medusa-js/src/resources/admin/types.ts:44](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L44) +[medusa-js/src/resources/admin/index.ts:44](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L44) ___ @@ -124,7 +124,7 @@ ___ #### Defined in -[medusa-js/src/resources/admin/types.ts:61](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L61) +[medusa-js/src/resources/admin/index.ts:61](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L61) ___ @@ -134,7 +134,7 @@ ___ #### Defined in -[medusa-js/src/resources/admin/types.ts:52](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L52) +[medusa-js/src/resources/admin/index.ts:52](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L52) ___ @@ -144,7 +144,7 @@ ___ #### Defined in -[medusa-js/src/resources/admin/types.ts:51](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L51) +[medusa-js/src/resources/admin/index.ts:51](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L51) ___ @@ -154,7 +154,7 @@ ___ #### Defined in -[medusa-js/src/resources/admin/types.ts:45](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L45) +[medusa-js/src/resources/admin/index.ts:45](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L45) ___ @@ -164,7 +164,7 @@ ___ #### Defined in -[medusa-js/src/resources/admin/types.ts:47](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L47) +[medusa-js/src/resources/admin/index.ts:47](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L47) ___ @@ -174,7 +174,7 @@ ___ #### Defined in -[medusa-js/src/resources/admin/types.ts:48](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L48) +[medusa-js/src/resources/admin/index.ts:48](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L48) ___ @@ -184,7 +184,7 @@ ___ #### Defined in -[medusa-js/src/resources/admin/types.ts:46](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L46) +[medusa-js/src/resources/admin/index.ts:46](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L46) ___ @@ -194,7 +194,7 @@ ___ #### Defined in -[medusa-js/src/resources/admin/types.ts:60](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L60) +[medusa-js/src/resources/admin/index.ts:60](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L60) ___ @@ -204,7 +204,7 @@ ___ #### Defined in -[medusa-js/src/resources/admin/types.ts:53](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L53) +[medusa-js/src/resources/admin/index.ts:53](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L53) ___ @@ -214,7 +214,7 @@ ___ #### Defined in -[medusa-js/src/resources/admin/types.ts:50](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L50) +[medusa-js/src/resources/admin/index.ts:50](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L50) ___ @@ -224,7 +224,7 @@ ___ #### Defined in -[medusa-js/src/resources/admin/types.ts:55](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L55) +[medusa-js/src/resources/admin/index.ts:55](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L55) ___ @@ -234,7 +234,7 @@ ___ #### Defined in -[medusa-js/src/resources/admin/types.ts:59](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L59) +[medusa-js/src/resources/admin/index.ts:59](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L59) ___ @@ -244,7 +244,7 @@ ___ #### Defined in -[medusa-js/src/resources/admin/types.ts:57](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L57) +[medusa-js/src/resources/admin/index.ts:57](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L57) ___ @@ -254,7 +254,7 @@ ___ #### Defined in -[medusa-js/src/resources/admin/types.ts:58](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L58) +[medusa-js/src/resources/admin/index.ts:58](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L58) ___ @@ -264,7 +264,7 @@ ___ #### Defined in -[medusa-js/src/resources/admin/types.ts:56](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L56) +[medusa-js/src/resources/admin/index.ts:56](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L56) ___ @@ -274,7 +274,7 @@ ___ #### Defined in -[medusa-js/src/resources/admin/types.ts:62](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L62) +[medusa-js/src/resources/admin/index.ts:62](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L62) ___ @@ -284,7 +284,7 @@ ___ #### Defined in -[medusa-js/src/resources/admin/types.ts:63](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L63) +[medusa-js/src/resources/admin/index.ts:63](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L63) ___ @@ -294,7 +294,7 @@ ___ #### Defined in -[medusa-js/src/resources/admin/types.ts:49](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L49) +[medusa-js/src/resources/admin/index.ts:49](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L49) ___ @@ -304,4 +304,4 @@ ___ #### Defined in -[medusa-js/src/resources/admin/types.ts:54](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L54) +[medusa-js/src/resources/admin/index.ts:54](https://github.com/medusajs/medusa/blob/53e34d33d/packages/medusa-js/src/resources/admin/index.ts#L54) From f152d9945fead77b11d9bf726d69df94b9b99d53 Mon Sep 17 00:00:00 2001 From: adrien2p Date: Wed, 19 Oct 2022 17:33:41 +0200 Subject: [PATCH 04/13] cleanup --- packages/medusa/src/interfaces/batch-job-strategy.ts | 2 +- .../src/strategies/__tests__/batch-jobs/product/export.ts | 2 +- .../src/strategies/__tests__/batch-jobs/product/import.ts | 2 +- packages/medusa/src/strategies/batch-jobs/product/export.ts | 2 +- packages/medusa/src/strategies/batch-jobs/product/import.ts | 2 +- .../strategies/batch-jobs/product/types/columns-definition.ts | 2 +- .../strategies/batch-jobs/product/types/{types.ts => index.ts} | 0 packages/medusa/src/strategies/batch-jobs/product/utils.ts | 2 +- 8 files changed, 7 insertions(+), 7 deletions(-) rename packages/medusa/src/strategies/batch-jobs/product/types/{types.ts => index.ts} (100%) diff --git a/packages/medusa/src/interfaces/batch-job-strategy.ts b/packages/medusa/src/interfaces/batch-job-strategy.ts index af87553ad71ef..80e0598b9efcc 100644 --- a/packages/medusa/src/interfaces/batch-job-strategy.ts +++ b/packages/medusa/src/interfaces/batch-job-strategy.ts @@ -1,6 +1,6 @@ import { TransactionBaseService } from "./transaction-base-service" import { BatchJobResultError, CreateBatchJobInput } from "../types/batch-job" -import { ProductExportBatchJob } from "../strategies/batch-jobs/product/types/types" +import { ProductExportBatchJob } from "../strategies/batch-jobs/product/types" import { BatchJobService } from "../services" import { BatchJob } from "../models" diff --git a/packages/medusa/src/strategies/__tests__/batch-jobs/product/export.ts b/packages/medusa/src/strategies/__tests__/batch-jobs/product/export.ts index 12dc59c150dfe..4940e9abdd54a 100644 --- a/packages/medusa/src/strategies/__tests__/batch-jobs/product/export.ts +++ b/packages/medusa/src/strategies/__tests__/batch-jobs/product/export.ts @@ -7,7 +7,7 @@ import { AdminPostBatchesReq, defaultAdminProductRelations, } from "../../../../api" -import { ProductExportBatchJob } from "../../../batch-jobs/product/types/types" +import { ProductExportBatchJob } from "../../../batch-jobs/product/types" import { Request } from "express" import { FlagRouter } from "../../../../utils/flag-router" import SalesChannelFeatureFlag from "../../../../loaders/feature-flags/sales-channels" diff --git a/packages/medusa/src/strategies/__tests__/batch-jobs/product/import.ts b/packages/medusa/src/strategies/__tests__/batch-jobs/product/import.ts index 3fa31c4e17271..39af7bcff6718 100644 --- a/packages/medusa/src/strategies/__tests__/batch-jobs/product/import.ts +++ b/packages/medusa/src/strategies/__tests__/batch-jobs/product/import.ts @@ -15,7 +15,7 @@ import { import { BatchJobStatus } from "../../../../types/batch-job" import { FlagRouter } from "../../../../utils/flag-router" import ProductImportStrategy from "../../../batch-jobs/product/import" -import { InjectedProps } from "../../../batch-jobs/product/types/types" +import { InjectedProps } from "../../../batch-jobs/product/types" let fakeJob = { id: IdMap.getId("product-import-job"), diff --git a/packages/medusa/src/strategies/batch-jobs/product/export.ts b/packages/medusa/src/strategies/batch-jobs/product/export.ts index d41f58704b1f5..60545d4febfe6 100644 --- a/packages/medusa/src/strategies/batch-jobs/product/export.ts +++ b/packages/medusa/src/strategies/batch-jobs/product/export.ts @@ -10,7 +10,7 @@ import { ProductExportBatchJob, ProductExportBatchJobContext, ProductExportPriceData, -} from "./types/types" +} from "./types" import { FindProductConfig } from "../../../types/product" import { FlagRouter } from "../../../utils/flag-router" import SalesChannelFeatureFlag from "../../../loaders/feature-flags/sales-channels" diff --git a/packages/medusa/src/strategies/batch-jobs/product/import.ts b/packages/medusa/src/strategies/batch-jobs/product/import.ts index 172da47ba6a6b..2e4d22ccae7c6 100644 --- a/packages/medusa/src/strategies/batch-jobs/product/import.ts +++ b/packages/medusa/src/strategies/batch-jobs/product/import.ts @@ -28,7 +28,7 @@ import { ProductImportCsvSchema, ProductImportJobContext, TParsedProductImportRowData, -} from "./types/types" +} from "./types" import { productImportColumnsDefinition, productImportSalesChannelsColumnsDefinition, diff --git a/packages/medusa/src/strategies/batch-jobs/product/types/columns-definition.ts b/packages/medusa/src/strategies/batch-jobs/product/types/columns-definition.ts index 8d8b63d0aad69..274db42de5f46 100644 --- a/packages/medusa/src/strategies/batch-jobs/product/types/columns-definition.ts +++ b/packages/medusa/src/strategies/batch-jobs/product/types/columns-definition.ts @@ -4,7 +4,7 @@ import { ProductExportPriceData, TBuiltProductImportLine, TParsedProductImportRowData, -} from "./types" +} from "./index" import { CsvSchema, CsvSchemaColumn } from "../../../../interfaces/csv-parser" export const productColumnsDefinition: ProductColumnDefinition = { diff --git a/packages/medusa/src/strategies/batch-jobs/product/types/types.ts b/packages/medusa/src/strategies/batch-jobs/product/types/index.ts similarity index 100% rename from packages/medusa/src/strategies/batch-jobs/product/types/types.ts rename to packages/medusa/src/strategies/batch-jobs/product/types/index.ts diff --git a/packages/medusa/src/strategies/batch-jobs/product/utils.ts b/packages/medusa/src/strategies/batch-jobs/product/utils.ts index d339e6e236296..0df977a399d96 100644 --- a/packages/medusa/src/strategies/batch-jobs/product/utils.ts +++ b/packages/medusa/src/strategies/batch-jobs/product/utils.ts @@ -1,6 +1,6 @@ import set from "lodash/set" -import { TParsedProductImportRowData } from "./types/types" +import { TParsedProductImportRowData } from "./types" import { csvRevertCellContentFormatter } from "../../../utils" /** From 4e20f8364dcec89869c915bd288109e35da05cb7 Mon Sep 17 00:00:00 2001 From: adrien2p Date: Wed, 19 Oct 2022 18:24:42 +0200 Subject: [PATCH 05/13] test(medusa): Add tests to validate that an updated exported file can be imported --- .../__tests__/batch-jobs/product/export.js | 136 ++++++++++++++++-- packages/medusa/src/loaders/strategies.ts | 12 +- .../product/types/columns-definition.ts | 6 + 3 files changed, 132 insertions(+), 22 deletions(-) diff --git a/integration-tests/api/__tests__/batch-jobs/product/export.js b/integration-tests/api/__tests__/batch-jobs/product/export.js index 5e2d3c37bd3dd..6f568df5e3145 100644 --- a/integration-tests/api/__tests__/batch-jobs/product/export.js +++ b/integration-tests/api/__tests__/batch-jobs/product/export.js @@ -1,6 +1,6 @@ const path = require("path") const fs = require("fs/promises") -import { sep, resolve } from "path" +import { resolve, sep } from "path" const setupServer = require("../../../../helpers/setup-server") const { useApi } = require("../../../../helpers/use-api") @@ -16,7 +16,7 @@ const adminReqConfig = { }, } -jest.setTimeout(1000000) +jest.setTimeout(100000000) describe("Batch job of product-export type", () => { let medusaProcess @@ -56,21 +56,26 @@ describe("Batch job of product-export type", () => { const db = useDb() await db.teardown() - const isFileExists = (await fs.stat(exportFilePath))?.isFile() + try { + const isFileExists = (await fs.stat(exportFilePath))?.isFile() - if (isFileExists) { - const [, relativeRoot] = exportFilePath.replace(__dirname, "").split(sep) + if (isFileExists) { + const [, relativeRoot] = exportFilePath + .replace(__dirname, "") + .split(sep) - if ((await fs.stat(resolve(__dirname, relativeRoot)))?.isDirectory()) { - topDir = relativeRoot - } + if ((await fs.stat(resolve(__dirname, relativeRoot)))?.isDirectory()) { + topDir = relativeRoot + } - await fs.unlink(exportFilePath) + await fs.unlink(exportFilePath) + } + } catch (e) { + console.log(e) } }) it("should export a csv file containing the expected products", async () => { - jest.setTimeout(1000000) const api = useApi() const productPayload = { @@ -174,7 +179,6 @@ describe("Batch job of product-export type", () => { }) it("should export a csv file containing the expected products including new line char in the cells", async () => { - jest.setTimeout(1000000) const api = useApi() const productPayload = { @@ -278,7 +282,6 @@ describe("Batch job of product-export type", () => { }) it("should export a csv file containing a limited number of products", async () => { - jest.setTimeout(1000000) const api = useApi() const batchPayload = { @@ -333,4 +336,113 @@ describe("Batch job of product-export type", () => { const csvLine = lines[0].split(";") expect(csvLine[0]).toBe("test-product") }) + + it("should be able to import an exported csv file", async () => { + const api = useApi() + + const batchPayload = { + type: "product-export", + context: { + batch_size: 1, + filterable_fields: { collection_id: "test-collection" }, + order: "created_at", + }, + } + + const batchJobRes = await api.post( + "/admin/batch-jobs", + batchPayload, + adminReqConfig + ) + let batchJobId = batchJobRes.data.batch_job.id + + expect(batchJobId).toBeTruthy() + + // Pull to check the status until it is completed + let batchJob + let shouldContinuePulling = true + while (shouldContinuePulling) { + const res = await api.get( + `/admin/batch-jobs/${batchJobId}`, + adminReqConfig + ) + + await new Promise((resolve, _) => { + setTimeout(resolve, 1000) + }) + + batchJob = res.data.batch_job + shouldContinuePulling = !( + batchJob.status === "completed" || batchJob.status === "failed" + ) + } + + expect(batchJob.status).toBe("completed") + + exportFilePath = path.resolve(__dirname, batchJob.result.file_key) + const isFileExists = (await fs.stat(exportFilePath)).isFile() + + expect(isFileExists).toBeTruthy() + + const data = (await fs.readFile(exportFilePath)).toString() + const [header, ...lines] = data.split("\r\n").filter((l) => l) + + expect(lines.length).toBe(4) + + const csvLine = lines[0].split(";") + expect(csvLine[0]).toBe("test-product") + expect(csvLine[2]).toBe("Test product") + + csvLine[2] = "Updated test product" + lines.splice(0, 1, csvLine.join(";")) + + await fs.writeFile(exportFilePath, [header, ...lines].join("\r\n")) + + const importBatchJobRes = await api.post( + "/admin/batch-jobs", + { + type: "product-import", + context: { + fileKey: exportFilePath, + }, + }, + adminReqConfig + ) + + batchJobId = importBatchJobRes.data.batch_job.id + + expect(batchJobId).toBeTruthy() + + shouldContinuePulling = true + while (shouldContinuePulling) { + const res = await api.get( + `/admin/batch-jobs/${batchJobId}`, + adminReqConfig + ) + + await new Promise((resolve, _) => { + setTimeout(resolve, 1000) + }) + + batchJob = res.data.batch_job + + shouldContinuePulling = !( + batchJob.status === "completed" || batchJob.status === "failed" + ) + } + + expect(batchJob.status).toBe("completed") + + const productsResponse = await api.get("/admin/products", adminReqConfig) + expect(productsResponse.data.count).toBe(5) + expect(productsResponse.data.products).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: csvLine[0], + handle: csvLine[1], + title: csvLine[2], + }), + ]) + ) + }) }) diff --git a/packages/medusa/src/loaders/strategies.ts b/packages/medusa/src/loaders/strategies.ts index ba0da653ba347..6335c48146fae 100644 --- a/packages/medusa/src/loaders/strategies.ts +++ b/packages/medusa/src/loaders/strategies.ts @@ -1,6 +1,6 @@ import glob from "glob" import path from "path" -import { asFunction, aliasTo } from "awilix" +import { aliasTo, asFunction } from "awilix" import formatRegistrationName from "../utils/format-registration-name" import { isBatchJobStrategy } from "../interfaces" @@ -28,15 +28,7 @@ export default ({ container, configModule, isTest }: LoaderOptions): void => { const core = glob.sync(coreFull, { cwd: __dirname, - ignore: [ - "**/__fixtures__/**", - "**/index.js", - "**/index.ts", - "**/utils.js", - "**/utils.ts", - "**/types.js", - "**/types.ts", - ], + ignore: ["**/__fixtures__/**", "**/utils.js", "**/utils.ts", "**/types/**"], }) core.forEach((fn) => { diff --git a/packages/medusa/src/strategies/batch-jobs/product/types/columns-definition.ts b/packages/medusa/src/strategies/batch-jobs/product/types/columns-definition.ts index 274db42de5f46..f28bc649f7cc4 100644 --- a/packages/medusa/src/strategies/batch-jobs/product/types/columns-definition.ts +++ b/packages/medusa/src/strategies/batch-jobs/product/types/columns-definition.ts @@ -257,6 +257,9 @@ export const productColumnsDefinition: ProductColumnDefinition = { "Product Profile Name": { name: "Product Profile Name", + importDescriptor: { + mapTo: "__not_supported__", + }, exportDescriptor: { accessor: (product: Product): string => product?.profile?.name ?? "", entityName: "product", @@ -265,6 +268,9 @@ export const productColumnsDefinition: ProductColumnDefinition = { "Product Profile Type": { name: "Product Profile Type", + importDescriptor: { + mapTo: "__not_supported__", + }, exportDescriptor: { accessor: (product: Product): string => product?.profile?.type ?? "", entityName: "product", From 885c3666918d7439e3e7651f30e06e7932b513a6 Mon Sep 17 00:00:00 2001 From: adrien2p Date: Thu, 20 Oct 2022 00:36:30 +0200 Subject: [PATCH 06/13] fix(medusa): loarders --- packages/medusa/src/loaders/strategies.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/medusa/src/loaders/strategies.ts b/packages/medusa/src/loaders/strategies.ts index 6335c48146fae..ea0bd85462516 100644 --- a/packages/medusa/src/loaders/strategies.ts +++ b/packages/medusa/src/loaders/strategies.ts @@ -28,7 +28,16 @@ export default ({ container, configModule, isTest }: LoaderOptions): void => { const core = glob.sync(coreFull, { cwd: __dirname, - ignore: ["**/__fixtures__/**", "**/utils.js", "**/utils.ts", "**/types/**"], + ignore: [ + "**/__fixtures__/**", + "**/index.js", + "**/index.ts", + "**/utils.js", + "**/utils.ts", + "**/types.js", + "**/types.ts", + "**/types/**", + ], }) core.forEach((fn) => { From def41417657056a6a3d1d37c7b9b80228bd16eb9 Mon Sep 17 00:00:00 2001 From: adrien2p Date: Thu, 20 Oct 2022 11:35:53 +0200 Subject: [PATCH 07/13] cleanup types --- .../__tests__/batch-jobs/product/import.ts | 4 +-- .../strategies/batch-jobs/product/export.ts | 11 ++----- .../strategies/batch-jobs/product/import.ts | 17 ++-------- .../batch-jobs/product/types/index.ts | 33 +++++++++++++++++++ 4 files changed, 39 insertions(+), 26 deletions(-) diff --git a/packages/medusa/src/strategies/__tests__/batch-jobs/product/import.ts b/packages/medusa/src/strategies/__tests__/batch-jobs/product/import.ts index 39af7bcff6718..fdb4035c19d6d 100644 --- a/packages/medusa/src/strategies/__tests__/batch-jobs/product/import.ts +++ b/packages/medusa/src/strategies/__tests__/batch-jobs/product/import.ts @@ -15,7 +15,7 @@ import { import { BatchJobStatus } from "../../../../types/batch-job" import { FlagRouter } from "../../../../utils/flag-router" import ProductImportStrategy from "../../../batch-jobs/product/import" -import { InjectedProps } from "../../../batch-jobs/product/types" +import { ProductImportInjectedProps } from "../../../batch-jobs/product/types" let fakeJob = { id: IdMap.getId("product-import-job"), @@ -147,7 +147,7 @@ describe("Product import strategy", () => { productVariantServiceMock as unknown as ProductVariantService, regionService: regionServiceMock as unknown as RegionService, featureFlagRouter: new FlagRouter({}), - } as unknown as InjectedProps) + } as unknown as ProductImportInjectedProps) it("`preProcessBatchJob` should generate import ops and upload them to a bucket using the file service", async () => { const getImportInstructionsSpy = jest.spyOn( diff --git a/packages/medusa/src/strategies/batch-jobs/product/export.ts b/packages/medusa/src/strategies/batch-jobs/product/export.ts index 60545d4febfe6..57ed2f25bb04c 100644 --- a/packages/medusa/src/strategies/batch-jobs/product/export.ts +++ b/packages/medusa/src/strategies/batch-jobs/product/export.ts @@ -9,6 +9,7 @@ import { DynamicProductExportDescriptor, ProductExportBatchJob, ProductExportBatchJobContext, + ProductExportInjectedDependencies, ProductExportPriceData, } from "./types" import { FindProductConfig } from "../../../types/product" @@ -20,14 +21,6 @@ import { productSalesChannelColumnsDefinition, } from "./types/columns-definition" -type InjectedDependencies = { - manager: EntityManager - batchJobService: BatchJobService - productService: ProductService - fileService: IFileService - featureFlagRouter: FlagRouter -} - export default class ProductExportStrategy extends AbstractBatchJobStrategy { public static identifier = "product-export-strategy" public static batchType = "product-export" @@ -66,7 +59,7 @@ export default class ProductExportStrategy extends AbstractBatchJobStrategy { productService, fileService, featureFlagRouter, - }: InjectedDependencies) { + }: ProductExportInjectedDependencies) { super({ manager, batchJobService, diff --git a/packages/medusa/src/strategies/batch-jobs/product/import.ts b/packages/medusa/src/strategies/batch-jobs/product/import.ts index 2e4d22ccae7c6..a4a1175e0bc98 100644 --- a/packages/medusa/src/strategies/batch-jobs/product/import.ts +++ b/packages/medusa/src/strategies/batch-jobs/product/import.ts @@ -21,11 +21,11 @@ import { BatchJob, SalesChannel } from "../../../models" import { FlagRouter } from "../../../utils/flag-router" import { transformProductData, transformVariantData } from "./utils" import SalesChannelFeatureFlag from "../../../loaders/feature-flags/sales-channels" -import { FileService } from "medusa-interfaces" import { OperationType, ProductImportBatchJob, ProductImportCsvSchema, + ProductImportInjectedProps, ProductImportJobContext, TParsedProductImportRowData, } from "./types" @@ -34,19 +34,6 @@ import { productImportSalesChannelsColumnsDefinition, } from "./types/columns-definition" -type InjectedProps = { - batchJobService: BatchJobService - productService: ProductService - productVariantService: ProductVariantService - shippingProfileService: ShippingProfileService - salesChannelService: SalesChannelService - regionService: RegionService - fileService: typeof FileService - - featureFlagRouter: FlagRouter - manager: EntityManager -} - /** * Process this many variant rows before reporting progress. */ @@ -92,7 +79,7 @@ class ProductImportStrategy extends AbstractBatchJobStrategy { fileService, manager, featureFlagRouter, - }: InjectedProps) { + }: ProductImportInjectedProps) { // eslint-disable-next-line prefer-rest-params super(arguments[0]) diff --git a/packages/medusa/src/strategies/batch-jobs/product/types/index.ts b/packages/medusa/src/strategies/batch-jobs/product/types/index.ts index 3bdad7b3fbb70..9d2b1940ba267 100644 --- a/packages/medusa/src/strategies/batch-jobs/product/types/index.ts +++ b/packages/medusa/src/strategies/batch-jobs/product/types/index.ts @@ -1,6 +1,26 @@ import { BatchJob, Product, ProductVariant } from "../../../../models" import { Selector } from "../../../../types/common" import { CsvSchema, CsvSchemaColumn } from "../../../../interfaces/csv-parser" +import { + BatchJobService, + ProductService, + ProductVariantService, + RegionService, + SalesChannelService, + ShippingProfileService, +} from "../../../../services" +import { FileService } from "medusa-interfaces" +import { FlagRouter } from "../../../../utils/flag-router" +import { EntityManager } from "typeorm" +import { IFileService } from "../../../../interfaces" + +export type ProductExportInjectedDependencies = { + manager: EntityManager + batchJobService: BatchJobService + productService: ProductService + fileService: IFileService + featureFlagRouter: FlagRouter +} export type ProductExportBatchJobContext = { retry_count?: number @@ -53,6 +73,19 @@ export type ProductExportDescriptor = entityName: Extract } +export type ProductImportInjectedProps = { + batchJobService: BatchJobService + productService: ProductService + productVariantService: ProductVariantService + shippingProfileService: ShippingProfileService + salesChannelService: SalesChannelService + regionService: RegionService + fileService: typeof FileService + + featureFlagRouter: FlagRouter + manager: EntityManager +} + /** * Import Batch job context column type. */ From 23c29052cb28d2e5f5deec248197831206882c34 Mon Sep 17 00:00:00 2001 From: adrien2p Date: Thu, 20 Oct 2022 14:04:42 +0200 Subject: [PATCH 08/13] feedback --- packages/medusa/src/interfaces/csv-parser.ts | 4 ++- .../strategies/batch-jobs/product/export.ts | 16 ++++++++++ .../product/types/columns-definition.ts | 30 +++++++++++-------- 3 files changed, 37 insertions(+), 13 deletions(-) diff --git a/packages/medusa/src/interfaces/csv-parser.ts b/packages/medusa/src/interfaces/csv-parser.ts index 28a5ccbce0c44..1bc1864fb648e 100644 --- a/packages/medusa/src/interfaces/csv-parser.ts +++ b/packages/medusa/src/interfaces/csv-parser.ts @@ -48,7 +48,9 @@ export type CsvSchemaColumn< ? { name: string } - : {}) & { + : { + name?: string + }) & { required?: boolean validator?: AbstractCsvValidator } & ( diff --git a/packages/medusa/src/strategies/batch-jobs/product/export.ts b/packages/medusa/src/strategies/batch-jobs/product/export.ts index 57ed2f25bb04c..c2a03933bd56b 100644 --- a/packages/medusa/src/strategies/batch-jobs/product/export.ts +++ b/packages/medusa/src/strategies/batch-jobs/product/export.ts @@ -351,6 +351,11 @@ export default class ProductExportStrategy extends AbstractBatchJobStrategy { } private appendSalesChannelsDescriptors(maxScCount: number): void { + const columnNameIdBuilder = (this.salesChannelsColumnsDefinition[ + "Sales Channel Id" + ]!.exportDescriptor as DynamicProductExportDescriptor)! + .buildDynamicColumnName + const columnNameNameBuilder = (this.salesChannelsColumnsDefinition[ "Sales Channel Name" ]!.exportDescriptor as DynamicProductExportDescriptor)! @@ -362,6 +367,17 @@ export default class ProductExportStrategy extends AbstractBatchJobStrategy { .buildDynamicColumnName for (let i = 0; i < maxScCount; ++i) { + const columnNameId = columnNameIdBuilder(i) + + this.columnsDefinition[columnNameId] = { + name: columnNameId, + exportDescriptor: { + accessor: (product: Product) => + product?.sales_channels[i]?.name ?? "", + entityName: "product", + }, + } + const columnNameName = columnNameNameBuilder(i) this.columnsDefinition[columnNameName] = { diff --git a/packages/medusa/src/strategies/batch-jobs/product/types/columns-definition.ts b/packages/medusa/src/strategies/batch-jobs/product/types/columns-definition.ts index f28bc649f7cc4..3f687ddb017d1 100644 --- a/packages/medusa/src/strategies/batch-jobs/product/types/columns-definition.ts +++ b/packages/medusa/src/strategies/batch-jobs/product/types/columns-definition.ts @@ -8,8 +8,8 @@ import { import { CsvSchema, CsvSchemaColumn } from "../../../../interfaces/csv-parser" export const productColumnsDefinition: ProductColumnDefinition = { - "Product id": { - name: "Product id", + "Product Id": { + name: "Product Id", importDescriptor: { mapTo: "product.id", }, @@ -242,8 +242,8 @@ export const productColumnsDefinition: ProductColumnDefinition = { entityName: "product", }, }, - "Product External ID": { - name: "Product External ID", + "Product External Id": { + name: "Product External Id", importDescriptor: { mapTo: "product.external_id", }, @@ -279,8 +279,8 @@ export const productColumnsDefinition: ProductColumnDefinition = { // VARIANTS - "Variant id": { - name: "Variant id", + "Variant Id": { + name: "Variant Id", importDescriptor: { mapTo: "variant.id", }, @@ -334,8 +334,8 @@ export const productColumnsDefinition: ProductColumnDefinition = { entityName: "variant", }, }, - "Variant Allow backorder": { - name: "Variant Allow backorder", + "Variant Allow Backorder": { + name: "Variant Allow Backorder", importDescriptor: { mapTo: "variant.allow_backorder", @@ -346,8 +346,8 @@ export const productColumnsDefinition: ProductColumnDefinition = { entityName: "variant", }, }, - "Variant Manage inventory": { - name: "Variant Manage inventory", + "Variant Manage Inventory": { + name: "Variant Manage Inventory", importDescriptor: { mapTo: "variant.manage_inventory", }, @@ -645,7 +645,7 @@ export const productSalesChannelColumnsDefinition: ProductColumnDefinition = { exportDescriptor: { isDynamic: true, buildDynamicColumnName: (index: number) => { - return `Sales channel ${index + 1} Name` + return `Sales Channel ${index + 1} Name` }, }, }, @@ -676,7 +676,7 @@ export const productSalesChannelColumnsDefinition: ProductColumnDefinition = { exportDescriptor: { isDynamic: true, buildDynamicColumnName: (index: number) => { - return `Sales channel ${index + 1} Description` + return `Sales Channel ${index + 1} Description` }, }, }, @@ -704,6 +704,12 @@ export const productSalesChannelColumnsDefinition: ProductColumnDefinition = { return builtLine }, }, + exportDescriptor: { + isDynamic: true, + buildDynamicColumnName: (index: number) => { + return `Sales Channel ${index + 1} Id` + }, + }, }, } From c0b739409ce5dec7fd3f666404e0281e2c447f3f Mon Sep 17 00:00:00 2001 From: adrien2p Date: Thu, 20 Oct 2022 14:55:33 +0200 Subject: [PATCH 09/13] fix(medusa): import tags with no value + tests --- .../__tests__/batch-jobs/product/import.js | 63 ++++++++++++++++++- .../batch-jobs/product/product-import-ss.csv | 2 +- .../batch-jobs/product/product-import.csv | 5 -- package.json | 2 +- .../product/__snapshots__/export.ts.snap | 38 +++++++++-- .../__tests__/batch-jobs/product/export.ts | 44 +++++++------ .../__tests__/batch-jobs/product/import.ts | 2 +- .../product/types/columns-definition.ts | 5 +- 8 files changed, 124 insertions(+), 37 deletions(-) delete mode 100644 integration-tests/api/__tests__/batch-jobs/product/product-import.csv diff --git a/integration-tests/api/__tests__/batch-jobs/product/import.js b/integration-tests/api/__tests__/batch-jobs/product/import.js index e8d01c9936a20..846b39591c68e 100644 --- a/integration-tests/api/__tests__/batch-jobs/product/import.js +++ b/integration-tests/api/__tests__/batch-jobs/product/import.js @@ -68,7 +68,7 @@ describe("Product import batch job", () => { await db.teardown() }) - it("should import a csv file", async () => { + it.only("should import a csv file", async () => { jest.setTimeout(1000000) const api = useApi() @@ -133,7 +133,7 @@ describe("Product import batch job", () => { expect(batchJob.status).toBe("completed") const productsResponse = await api.get("/admin/products", adminReqConfig) - expect(productsResponse.data.count).toBe(2) + expect(productsResponse.data.count).toBe(3) expect(productsResponse.data.products).toEqual( expect.arrayContaining([ // NEW PRODUCT @@ -200,6 +200,65 @@ describe("Product import batch job", () => { }), ], }), + expect.objectContaining({ + title: "Test product", + description: + "Hopper Stripes Bedding, available as duvet cover, pillow sham and sheet.\\n100% organic cotton, soft and crisp to the touch. Made in Portugal.", + handle: "test-product-product-1-1", + is_giftcard: false, + status: "draft", + thumbnail: "test-image.png", + variants: [ + // NEW VARIANT + expect.objectContaining({ + title: "Test variant", + sku: "test-sku-1-1", + barcode: "test-barcode-1-1", + ean: null, + upc: null, + inventory_quantity: 10, + prices: [ + expect.objectContaining({ + currency_code: "eur", + amount: 100, + region_id: "region-product-import-0", + }), + expect.objectContaining({ + currency_code: "usd", + amount: 110, + }), + expect.objectContaining({ + currency_code: "dkk", + amount: 130, + region_id: "region-product-import-1", + }), + ], + options: expect.arrayContaining([ + expect.objectContaining({ + value: "option 1 value red", + }), + expect.objectContaining({ + value: "option 2 value 1", + }), + ]), + }), + ], + type: null, + images: [ + expect.objectContaining({ + url: "test-image.png", + }), + ], + options: [ + expect.objectContaining({ + title: "test-option-1", + }), + expect.objectContaining({ + title: "test-option-2", + }), + ], + tags: [], + }), // UPDATED PRODUCT expect.objectContaining({ id: existingProductToBeUpdated.id, diff --git a/integration-tests/api/__tests__/batch-jobs/product/product-import-ss.csv b/integration-tests/api/__tests__/batch-jobs/product/product-import-ss.csv index f2933ab938321..cf0c650a005bc 100644 --- a/integration-tests/api/__tests__/batch-jobs/product/product-import-ss.csv +++ b/integration-tests/api/__tests__/batch-jobs/product/product-import-ss.csv @@ -1,2 +1,2 @@ -Product id,Product Handle,Product Title,Product Subtitle,Product Description,Product Status,Product Thumbnail,Product Weight,Product Length,Product Width,Product Height,Product HS Code,Product Origin Country,Product MID Code,Product Material,Product Collection Title,Product Collection Handle,Product Type,Product Tags,Product Discountable,Product External ID,Variant id,Variant Title,Variant SKU,Variant Barcode,Variant Inventory Quantity,Variant Allow backorder,Variant Manage inventory,Variant Weight,Variant Length,Variant Width,Variant Height,Variant HS Code,Variant Origin Country,Variant MID Code,Variant Material,Price ImportLand [EUR],Price USD,Price denmark [DKK],Price Denmark [DKK],Option 1 Name,Option 1 Value,Option 2 Name,Option 2 Value,Image 1 Url,Sales Channel 1 Name,Sales Channel 2 Name,Sales Channel 1 Id,Sales Channel 2 Id +Product Id,Product Handle,Product Title,Product Subtitle,Product Description,Product Status,Product Thumbnail,Product Weight,Product Length,Product Width,Product Height,Product HS Code,Product Origin Country,Product MID Code,Product Material,Product Collection Title,Product Collection Handle,Product Type,Product Tags,Product Discountable,Product External Id,Variant Id,Variant Title,Variant SKU,Variant Barcode,Variant Inventory Quantity,Variant Allow Backorder,Variant Manage Inventory,Variant Weight,Variant Length,Variant Width,Variant Height,Variant HS Code,Variant Origin Country,Variant MID Code,Variant Material,Price ImportLand [EUR],Price USD,Price denmark [DKK],Price Denmark [DKK],Option 1 Name,Option 1 Value,Option 2 Name,Option 2 Value,Image 1 Url,Sales Channel 1 Name,Sales Channel 2 Name,Sales Channel 1 Id,Sales Channel 2 Id ,test-product-product-1,Test product,,"Hopper Stripes Bedding, available as duvet cover, pillow sham and sheet.\n100% organic cotton, soft and crisp to the touch. Made in Portugal.",draft,,,,,,,,,,Test collection 1,test-collection1,test-type-1,123_1,TRUE,,,Test variant,test-sku-1,test-barcode-1,10,FALSE,TRUE,,,,,,,,,1.00,1.10,1.30,,test-option-1,option 1 value red,test-option-2,option 2 value 1,test-image.png,Import Sales Channel 1,Import Sales Channel 2,, \ No newline at end of file diff --git a/integration-tests/api/__tests__/batch-jobs/product/product-import.csv b/integration-tests/api/__tests__/batch-jobs/product/product-import.csv deleted file mode 100644 index 3b44cda26d158..0000000000000 --- a/integration-tests/api/__tests__/batch-jobs/product/product-import.csv +++ /dev/null @@ -1,5 +0,0 @@ -Product id,Product Handle,Product Title,Product Subtitle,Product Description,Product Status,Product Thumbnail,Product Weight,Product Length,Product Width,Product Height,Product HS Code,Product Origin Country,Product MID Code,Product Material,Product Collection Title,Product Collection Handle,Product Type,Product Tags,Product Discountable,Product External ID,Variant id,Variant Title,Variant SKU,Variant Barcode,Variant Inventory Quantity,Variant Allow backorder,Variant Manage inventory,Variant Weight,Variant Length,Variant Width,Variant Height,Variant HS Code,Variant Origin Country,Variant MID Code,Variant Material,Price ImportLand [EUR],Price USD,Price denmark [DKK],Price Denmark [DKK],Option 1 Name,Option 1 Value,Option 2 Name,Option 2 Value,Image 1 Url -,test-product-product-1,Test product,,"Hopper Stripes Bedding, available as duvet cover, pillow sham and sheet.\n100% organic cotton, soft and crisp to the touch. Made in Portugal.",draft,,,,,,,,,,Test collection 1,test-collection1,,123_1,TRUE,,,Test variant,test-sku-1,test-barcode-1,10,FALSE,TRUE,,,,,,,,,1.00,1.10,1.30,,test-option-1,option 1 value red,test-option-2,option 2 value 1,test-image.png -existing-product-id,test-product-product-2,Test product,,test-product-description,draft,test-image.png,,,,,,,,,Test collection,test-collection2,test-type,123,TRUE,,,Test variant,test-sku-2,test-barcode-2,10,FALSE,TRUE,,,,,,,,,,,,1.10,Size,Small,,,test-image.png -existing-product-id,test-product-product-2,Test product,,test-product-description,draft,,,,,,,,,,Test collection,test-collection2,test-type,123,TRUE,,,Test variant,test-sku-3,test-barcode-3,10,FALSE,TRUE,,,,,,,,,,1.20,,,Size,Medium,,,test-image.png -existing-product-id,test-product-product-2,Test product,,test-product-description,draft,,,,,,,,,,Test collection,test-collection2,test-type,123,TRUE,,existing-variant-id,Test variant changed,test-sku-4,test-barcode-4,10,FALSE,TRUE,,,,,,,,,,,,,Size,Large,,,test-image.png diff --git a/package.json b/package.json index 7329fa1eb55e2..8991bc2f347e0 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ "jest": "jest", "test": "turbo run test", "test:integration": "NODE_ENV=test jest --runInBand --bail --config=integration-tests/jest.config.js", - "test:integration:api": "NODE_ENV=test jest --runInBand --bail --config=integration-tests/jest.config.js --projects=integration-tests/api", + "test:integration:api": "NODE_ENV=test jest --runInBand --bail --config=integration-tests/jest.config.js --projects=integration-tests/api -- integration-tests/api/__tests__/batch-jobs/product/import.js", "test:integration:plugins": "NODE_ENV=test jest --runInBand --bail --config=integration-tests/jest.config.js --projects=integration-tests/plugins", "test:fixtures": "NODE_ENV=test jest --config=docs-util/jest.config.js --runInBand --bail", "openapi:generate": "node ./scripts/build-openapi.js", diff --git a/packages/medusa/src/strategies/__tests__/batch-jobs/product/__snapshots__/export.ts.snap b/packages/medusa/src/strategies/__tests__/batch-jobs/product/__snapshots__/export.ts.snap index 1d6c2a442bfb6..676b1eadd6e2e 100644 --- a/packages/medusa/src/strategies/__tests__/batch-jobs/product/__snapshots__/export.ts.snap +++ b/packages/medusa/src/strategies/__tests__/batch-jobs/product/__snapshots__/export.ts.snap @@ -2,9 +2,12 @@ exports[`Product export strategy should process the batch job and generate the appropriate output 1`] = ` Array [ - "Product id;Product Handle;Product Title;Product Subtitle;Product Description;Product Status;Product Thumbnail;Product Weight;Product Length;Product Width;Product Height;Product HS Code;Product Origin Country;Product MID Code;Product Material;Product Collection Title;Product Collection Handle;Product Type;Product Tags;Product Discountable;Product External ID;Product Profile Name;Product Profile Type;Variant id;Variant Title;Variant SKU;Variant Barcode;Variant Inventory Quantity;Variant Allow backorder;Variant Manage inventory;Variant Weight;Variant Length;Variant Width;Variant Height;Variant HS Code;Variant Origin Country;Variant MID Code;Variant Material;Price france [USD];Price USD;Price denmark [DKK];Price Denmark [DKK];Option 1 Name;Option 1 Value;Option 2 Name;Option 2 Value;Image 1 Url + "Product Id;Product Handle;Product Title;Product Subtitle;Product Description;Product Status;Product Thumbnail;Product Weight;Product Length;Product Width;Product Height;Product HS Code;Product Origin Country;Product MID Code;Product Material;Product Collection Title;Product Collection Handle;Product Type;Product Tags;Product Discountable;Product External Id;Product Profile Name;Product Profile Type;Variant Id;Variant Title;Variant SKU;Variant Barcode;Variant Inventory Quantity;Variant Allow Backorder;Variant Manage Inventory;Variant Weight;Variant Length;Variant Width;Variant Height;Variant HS Code;Variant Origin Country;Variant MID Code;Variant Material;Price france [USD];Price USD;Price denmark [DKK];Price Denmark [DKK];Option 1 Name;Option 1 Value;Option 2 Name;Option 2 Value;Image 1 Url ", - "product-export-strategy-product-1;test-product-product-1;Test product;;\\"test-product-description-1\ntest-product-description-1 second line\ntest-product-description-1 third line\nforth line\\";draft;;;;;;;;;;Test collection 1;test-collection1;test-type-1;123_1;true;;profile_1;profile_type_1;product-export-strategy-variant-1;Test variant;test-sku;test-barcode;10;false;true;;;;;;;;;100;110;130;;test-option-1;option 1 value 1;test-option-2;option 2 value 1;test-image.png + "product-export-strategy-product-1;test-product-product-1;Test product;;\\"test-product-description-1 +test-product-description-1 second line +test-product-description-1 third line +forth line\\";draft;;;;;;;;;;Test collection 1;test-collection1;test-type-1;123_1;true;;profile_1;profile_type_1;product-export-strategy-variant-1;Test variant;test-sku;test-barcode;10;false;true;;;;;;;;;100;110;130;;test-option-1;option 1 value 1;test-option-2;option 2 value 1;test-image.png ", "product-export-strategy-product-2;test-product-product-2;Test product;;test-product-description;draft;;;;;;;;;;Test collection;test-collection2;test-type;123;true;;profile_2;profile_type_2;product-export-strategy-variant-2;Test variant;test-sku;test-barcode;10;false;true;;;;;;;;;;;;110;test-option;Option 1 value 1;;;test-image.png ", @@ -13,11 +16,36 @@ Array [ ] `; -exports[`Product export strategy with sales channels should process the batch job and generate the appropriate output 1`] = ` +exports[`Product export strategy with sales Channels should process the batch job and generate the appropriate output 1`] = ` Array [ - "Product id;Product Handle;Product Title;Product Subtitle;Product Description;Product Status;Product Thumbnail;Product Weight;Product Length;Product Width;Product Height;Product HS Code;Product Origin Country;Product MID Code;Product Material;Product Collection Title;Product Collection Handle;Product Type;Product Tags;Product Discountable;Product External ID;Product Profile Name;Product Profile Type;Variant id;Variant Title;Variant SKU;Variant Barcode;Variant Inventory Quantity;Variant Allow backorder;Variant Manage inventory;Variant Weight;Variant Length;Variant Width;Variant Height;Variant HS Code;Variant Origin Country;Variant MID Code;Variant Material;Price france [USD];Price USD;Price denmark [DKK];Price Denmark [DKK];Option 1 Name;Option 1 Value;Option 2 Name;Option 2 Value;Image 1 Url;Sales channel 1 Name;Sales channel 1 Description;Sales channel 2 Name;Sales channel 2 Description + "Product Id;Product Handle;Product Title;Product Subtitle;Product Description;Product Status;Product Thumbnail;Product Weight;Product Length;Product Width;Product Height;Product HS Code;Product Origin Country;Product MID Code;Product Material;Product Collection Title;Product Collection Handle;Product Type;Product Tags;Product Discountable;Product External Id;Product Profile Name;Product Profile Type;Variant Id;Variant Title;Variant SKU;Variant Barcode;Variant Inventory Quantity;Variant Allow Backorder;Variant Manage Inventory;Variant Weight;Variant Length;Variant Width;Variant Height;Variant HS Code;Variant Origin Country;Variant MID Code;Variant Material;Price france [USD];Price USD;Price denmark [DKK];Price Denmark [DKK];Option 1 Name;Option 1 Value;Option 2 Name;Option 2 Value;Image 1 Url;Sales Channel 1 Id;Sales Channel 1 Name;Sales Channel 1 Description;Sales Channel 2 Id;Sales Channel 2 Name;Sales Channel 2 Description +", + "product-export-strategy-product-1;test-product-product-1;Test product;;\\"test-product-description-1 +test-product-description-1 second line +test-product-description-1 third line +forth line\\";draft;;;;;;;;;;Test collection 1;test-collection1;test-type-1;123_1;true;;profile_1;profile_type_1;product-export-strategy-variant-1;Test variant;test-sku;test-barcode;10;false;true;;;;;;;;;100;110;130;;test-option-1;option 1 value 1;test-option-2;option 2 value 1;test-image.png;SC 1;SC 1;\\"SC 1 +SC 1 second line +SC 1 third line +SC 1 forth line\\";;; +", + "product-export-strategy-product-2;test-product-product-2;Test product;;test-product-description;draft;;;;;;;;;;Test collection;test-collection2;test-type;123;true;;profile_2;profile_type_2;product-export-strategy-variant-2;Test variant;test-sku;test-barcode;10;false;true;;;;;;;;;;;;110;test-option;Option 1 value 1;;;test-image.png;SC 1;SC 1;SC 1;SC 2;SC 2;SC 2 ", - "product-export-strategy-product-1;test-product-product-1;Test product;;\\"test-product-description-1\ntest-product-description-1 second line\ntest-product-description-1 third line\nforth line\\";draft;;;;;;;;;;Test collection 1;test-collection1;test-type-1;123_1;true;;profile_1;profile_type_1;product-export-strategy-variant-1;Test variant;test-sku;test-barcode;10;false;true;;;;;;;;;100;110;130;;test-option-1;option 1 value 1;test-option-2;option 2 value 1;test-image.png;SC 1;\\"SC 1\nSC 1 second line\nSC 1 third line\nSC 1 forth line\\";; + "product-export-strategy-product-2;test-product-product-2;Test product;;test-product-description;draft;;;;;;;;;;Test collection;test-collection2;test-type;123;true;;profile_2;profile_type_2;product-export-strategy-variant-3;Test variant;test-sku;test-barcode;10;false;true;;;;;;;;;;120;;;test-option;Option 1 Value 1;;;test-image.png;SC 1;SC 1;SC 1;SC 2;SC 2;SC 2 +", +] +`; + +exports[`Product export strategy with sales channels should process the batch job and generate the appropriate output 1`] = ` +Array [ + "Product Id;Product Handle;Product Title;Product Subtitle;Product Description;Product Status;Product Thumbnail;Product Weight;Product Length;Product Width;Product Height;Product HS Code;Product Origin Country;Product MID Code;Product Material;Product Collection Title;Product Collection Handle;Product Type;Product Tags;Product Discountable;Product External Id;Product Profile Name;Product Profile Type;Variant Id;Variant Title;Variant SKU;Variant Barcode;Variant Inventory Quantity;Variant Allow Backorder;Variant Manage Inventory;Variant Weight;Variant Length;Variant Width;Variant Height;Variant HS Code;Variant Origin Country;Variant MID Code;Variant Material;Price france [USD];Price USD;Price denmark [DKK];Price Denmark [DKK];Option 1 Name;Option 1 Value;Option 2 Name;Option 2 Value;Image 1 Url;Sales channel 1 Name;Sales channel 1 Description;Sales channel 2 Name;Sales channel 2 Description +", + "product-export-strategy-product-1;test-product-product-1;Test product;;\\"test-product-description-1 +test-product-description-1 second line +test-product-description-1 third line +forth line\\";draft;;;;;;;;;;Test collection 1;test-collection1;test-type-1;123_1;true;;profile_1;profile_type_1;product-export-strategy-variant-1;Test variant;test-sku;test-barcode;10;false;true;;;;;;;;;100;110;130;;test-option-1;option 1 value 1;test-option-2;option 2 value 1;test-image.png;SC 1;\\"SC 1 +SC 1 second line +SC 1 third line +SC 1 forth line\\";; ", "product-export-strategy-product-2;test-product-product-2;Test product;;test-product-description;draft;;;;;;;;;;Test collection;test-collection2;test-type;123;true;;profile_2;profile_type_2;product-export-strategy-variant-2;Test variant;test-sku;test-barcode;10;false;true;;;;;;;;;;;;110;test-option;Option 1 value 1;;;test-image.png;SC 1;SC 1;SC 2;SC 2 ", diff --git a/packages/medusa/src/strategies/__tests__/batch-jobs/product/export.ts b/packages/medusa/src/strategies/__tests__/batch-jobs/product/export.ts index 4940e9abdd54a..e7b631cb3e749 100644 --- a/packages/medusa/src/strategies/__tests__/batch-jobs/product/export.ts +++ b/packages/medusa/src/strategies/__tests__/batch-jobs/product/export.ts @@ -10,7 +10,7 @@ import { import { ProductExportBatchJob } from "../../../batch-jobs/product/types" import { Request } from "express" import { FlagRouter } from "../../../../utils/flag-router" -import SalesChannelFeatureFlag from "../../../../loaders/feature-flags/sales-channels" +import SalesChannelFeatureFlag from "../../../../loaders/feature-flags/sales-Channels" const productServiceMock = { withTransaction: function () { @@ -129,7 +129,7 @@ describe("Product export strategy", () => { ) await productExportStrategy.preProcessBatchJob(fakeJob.id) const template = await productExportStrategy.buildHeader(fakeJob) - expect(template).toMatch(/.*Product id.*/) + expect(template).toMatch(/.*Product Id.*/) expect(template).toMatch(/.*Product Handle.*/) expect(template).toMatch(/.*Product Title.*/) expect(template).toMatch(/.*Product Subtitle.*/) @@ -149,17 +149,17 @@ describe("Product export strategy", () => { expect(template).toMatch(/.*Product Type.*/) expect(template).toMatch(/.*Product Tags.*/) expect(template).toMatch(/.*Product Discountable.*/) - expect(template).toMatch(/.*Product External ID.*/) + expect(template).toMatch(/.*Product External Id.*/) expect(template).toMatch(/.*Product Profile Name.*/) expect(template).toMatch(/.*Product Profile Type.*/) expect(template).toMatch(/.*Product Profile Type.*/) - expect(template).toMatch(/.*Variant id.*/) + expect(template).toMatch(/.*Variant Id.*/) expect(template).toMatch(/.*Variant Title.*/) expect(template).toMatch(/.*Variant SKU.*/) expect(template).toMatch(/.*Variant Barcode.*/) - expect(template).toMatch(/.*Variant Allow backorder.*/) - expect(template).toMatch(/.*Variant Manage inventory.*/) + expect(template).toMatch(/.*Variant Allow Backorder.*/) + expect(template).toMatch(/.*Variant Manage Inventory.*/) expect(template).toMatch(/.*Variant Weight.*/) expect(template).toMatch(/.*Variant Length.*/) expect(template).toMatch(/.*Variant Width.*/) @@ -174,10 +174,12 @@ describe("Product export strategy", () => { expect(template).toMatch(/.*Option 2 Name.*/) expect(template).toMatch(/.*Option 2 Value.*/) - expect(template).not.toMatch(/.*Sales channel 1 Name.*/) - expect(template).not.toMatch(/.*Sales channel 1 Description.*/) - expect(template).not.toMatch(/.*Sales channel 2 Name.*/) - expect(template).not.toMatch(/.*Sales channel 2 Description.*/) + expect(template).not.toMatch(/.*Sales Channel 1 Id.*/) + expect(template).not.toMatch(/.*Sales Channel 1 Name.*/) + expect(template).not.toMatch(/.*Sales Channel 1 Description.*/) + expect(template).not.toMatch(/.*Sales Channel 2 Id.*/) + expect(template).not.toMatch(/.*Sales Channel 2 Name.*/) + expect(template).not.toMatch(/.*Sales Channel 2 Description.*/) expect(template).toMatch(/.*Price USD.*/) expect(template).toMatch(/.*Price france \[USD\].*/) @@ -298,7 +300,7 @@ describe("Product export strategy", () => { }) }) -describe("Product export strategy with sales channels", () => { +describe("Product export strategy with sales Channels", () => { const outputDataStorage: string[] = [] const fileServiceMock = { delete: jest.fn(), @@ -394,7 +396,7 @@ describe("Product export strategy with sales channels", () => { ) await productExportStrategy.preProcessBatchJob(fakeJob.id) const template = await productExportStrategy.buildHeader(fakeJob) - expect(template).toMatch(/.*Product id.*/) + expect(template).toMatch(/.*Product Id.*/) expect(template).toMatch(/.*Product Handle.*/) expect(template).toMatch(/.*Product Title.*/) expect(template).toMatch(/.*Product Subtitle.*/) @@ -414,17 +416,17 @@ describe("Product export strategy with sales channels", () => { expect(template).toMatch(/.*Product Type.*/) expect(template).toMatch(/.*Product Tags.*/) expect(template).toMatch(/.*Product Discountable.*/) - expect(template).toMatch(/.*Product External ID.*/) + expect(template).toMatch(/.*Product External Id.*/) expect(template).toMatch(/.*Product Profile Name.*/) expect(template).toMatch(/.*Product Profile Type.*/) expect(template).toMatch(/.*Product Profile Type.*/) - expect(template).toMatch(/.*Variant id.*/) + expect(template).toMatch(/.*Variant Id.*/) expect(template).toMatch(/.*Variant Title.*/) expect(template).toMatch(/.*Variant SKU.*/) expect(template).toMatch(/.*Variant Barcode.*/) - expect(template).toMatch(/.*Variant Allow backorder.*/) - expect(template).toMatch(/.*Variant Manage inventory.*/) + expect(template).toMatch(/.*Variant Allow Backorder.*/) + expect(template).toMatch(/.*Variant Manage Inventory.*/) expect(template).toMatch(/.*Variant Weight.*/) expect(template).toMatch(/.*Variant Length.*/) expect(template).toMatch(/.*Variant Width.*/) @@ -444,10 +446,12 @@ describe("Product export strategy with sales channels", () => { expect(template).toMatch(/.*Price denmark \[DKK\].*/) expect(template).toMatch(/.*Price Denmark \[DKK\].*/) - expect(template).toMatch(/.*Sales channel 1 Name.*/) - expect(template).toMatch(/.*Sales channel 1 Description.*/) - expect(template).toMatch(/.*Sales channel 2 Name.*/) - expect(template).toMatch(/.*Sales channel 2 Description.*/) + expect(template).toMatch(/.*Sales Channel 1 Id.*/) + expect(template).toMatch(/.*Sales Channel 1 Name.*/) + expect(template).toMatch(/.*Sales Channel 1 Description.*/) + expect(template).toMatch(/.*Sales Channel 2 Id.*/) + expect(template).toMatch(/.*Sales Channel 2 Name.*/) + expect(template).toMatch(/.*Sales Channel 2 Description.*/) expect(template).toMatch(/.*Image 1 Url.*/) }) diff --git a/packages/medusa/src/strategies/__tests__/batch-jobs/product/import.ts b/packages/medusa/src/strategies/__tests__/batch-jobs/product/import.ts index fdb4035c19d6d..3e27ef5b8795a 100644 --- a/packages/medusa/src/strategies/__tests__/batch-jobs/product/import.ts +++ b/packages/medusa/src/strategies/__tests__/batch-jobs/product/import.ts @@ -32,7 +32,7 @@ let fakeJob = { } async function* generateCSVDataForStream() { - yield "Product id,Product Handle,Product Title,Product Subtitle,Product Description,Product Status,Product Thumbnail,Product Weight,Product Length,Product Width,Product Height,Product HS Code,Product Origin Country,Product MID Code,Product Material,Product Collection Title,Product Collection Handle,Product Type,Product Tags,Product Discountable,Product External ID,Variant id,Variant Title,Variant SKU,Variant Barcode,Variant Inventory Quantity,Variant Allow backorder,Variant Manage inventory,Variant Weight,Variant Length,Variant Width,Variant Height,Variant HS Code,Variant Origin Country,Variant MID Code,Variant Material,Price france [USD],Price USD,Price denmark [DKK],Price Denmark [DKK],Option 1 Name,Option 1 Value,Option 2 Name,Option 2 Value,Image 1 Url\n" + yield "Product Id,Product Handle,Product Title,Product Subtitle,Product Description,Product Status,Product Thumbnail,Product Weight,Product Length,Product Width,Product Height,Product HS Code,Product Origin Country,Product MID Code,Product Material,Product Collection Title,Product Collection Handle,Product Type,Product Tags,Product Discountable,Product External Id,Variant Id,Variant Title,Variant SKU,Variant Barcode,Variant Inventory Quantity,Variant Allow Backorder,Variant Manage Inventory,Variant Weight,Variant Length,Variant Width,Variant Height,Variant HS Code,Variant Origin Country,Variant MID Code,Variant Material,Price france [USD],Price USD,Price denmark [DKK],Price Denmark [DKK],Option 1 Name,Option 1 Value,Option 2 Name,Option 2 Value,Image 1 Url\n" yield ",test-product-product-1,Test product,,test-product-description-1,draft,,,,,,,,,,Test collection 1,test-collection1,test-type-1,123_1,TRUE,,SebniWTDeC,Test variant,test-sku-1,test-barcode-1,10,FALSE,TRUE,,,,,,,,,100,110,130,,test-option-1,option 1 value red,test-option-2,option 2 value 1,test-image.png\n" yield "5VxiEkmnPV,test-product-product-2,Test product,,test-product-description,draft,,,,,,,,,,Test collection,test-collection2,test-type,123,TRUE,,,Test variant,test-sku-2,test-barcode-2,10,FALSE,TRUE,,,,,,,,,,,,110,test-option,Option 1 value 1,,,test-image.png\n" yield "5VxiEkmnPV,test-product-product-2,Test product,,test-product-description,draft,,,,,,,,,,Test collection,test-collection2,test-type,123,TRUE,,3SS1MHGDEJ,Test variant,test-sku-3,test-barcode-3,10,FALSE,TRUE,,,,,,,,,,120,,,test-option,Option 1 Value blue,,,test-image.png\n" diff --git a/packages/medusa/src/strategies/batch-jobs/product/types/columns-definition.ts b/packages/medusa/src/strategies/batch-jobs/product/types/columns-definition.ts index 3f687ddb017d1..29d99b5f85be4 100644 --- a/packages/medusa/src/strategies/batch-jobs/product/types/columns-definition.ts +++ b/packages/medusa/src/strategies/batch-jobs/product/types/columns-definition.ts @@ -219,8 +219,9 @@ export const productColumnsDefinition: ProductColumnDefinition = { name: "Product Tags", importDescriptor: { mapTo: "product.tags", - transform: (value: string) => - `${value}`.split(",").map((v) => ({ value: v })), + transform: (value: string) => { + return value && `${value}`.split(",").map((v) => ({ value: v })) + }, }, exportDescriptor: { accessor: (product: Product): string => From 0977b7ee890b35222cfe4665b7ec8067f6b01e41 Mon Sep 17 00:00:00 2001 From: adrien2p Date: Thu, 20 Oct 2022 15:08:32 +0200 Subject: [PATCH 10/13] cleanup --- integration-tests/api/__tests__/batch-jobs/product/import.js | 2 +- package.json | 2 +- .../src/strategies/__tests__/batch-jobs/product/export.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/integration-tests/api/__tests__/batch-jobs/product/import.js b/integration-tests/api/__tests__/batch-jobs/product/import.js index 846b39591c68e..f2ebab8248f10 100644 --- a/integration-tests/api/__tests__/batch-jobs/product/import.js +++ b/integration-tests/api/__tests__/batch-jobs/product/import.js @@ -68,7 +68,7 @@ describe("Product import batch job", () => { await db.teardown() }) - it.only("should import a csv file", async () => { + it("should import a csv file", async () => { jest.setTimeout(1000000) const api = useApi() diff --git a/package.json b/package.json index 8991bc2f347e0..7329fa1eb55e2 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ "jest": "jest", "test": "turbo run test", "test:integration": "NODE_ENV=test jest --runInBand --bail --config=integration-tests/jest.config.js", - "test:integration:api": "NODE_ENV=test jest --runInBand --bail --config=integration-tests/jest.config.js --projects=integration-tests/api -- integration-tests/api/__tests__/batch-jobs/product/import.js", + "test:integration:api": "NODE_ENV=test jest --runInBand --bail --config=integration-tests/jest.config.js --projects=integration-tests/api", "test:integration:plugins": "NODE_ENV=test jest --runInBand --bail --config=integration-tests/jest.config.js --projects=integration-tests/plugins", "test:fixtures": "NODE_ENV=test jest --config=docs-util/jest.config.js --runInBand --bail", "openapi:generate": "node ./scripts/build-openapi.js", diff --git a/packages/medusa/src/strategies/__tests__/batch-jobs/product/export.ts b/packages/medusa/src/strategies/__tests__/batch-jobs/product/export.ts index e7b631cb3e749..2112f9841c1f4 100644 --- a/packages/medusa/src/strategies/__tests__/batch-jobs/product/export.ts +++ b/packages/medusa/src/strategies/__tests__/batch-jobs/product/export.ts @@ -10,7 +10,7 @@ import { import { ProductExportBatchJob } from "../../../batch-jobs/product/types" import { Request } from "express" import { FlagRouter } from "../../../../utils/flag-router" -import SalesChannelFeatureFlag from "../../../../loaders/feature-flags/sales-Channels" +import SalesChannelFeatureFlag from "../../../../loaders/feature-flags/sales-channels" const productServiceMock = { withTransaction: function () { From 65374d29cb1f318668e1960564af8744252ae484 Mon Sep 17 00:00:00 2001 From: adrien2p Date: Thu, 20 Oct 2022 15:27:20 +0200 Subject: [PATCH 11/13] fix tests --- .../batch-jobs/product/product-import.csv | 5 +++++ .../product/__snapshots__/export.ts.snap | 19 ------------------- 2 files changed, 5 insertions(+), 19 deletions(-) create mode 100644 integration-tests/api/__tests__/batch-jobs/product/product-import.csv diff --git a/integration-tests/api/__tests__/batch-jobs/product/product-import.csv b/integration-tests/api/__tests__/batch-jobs/product/product-import.csv new file mode 100644 index 0000000000000..726767634929b --- /dev/null +++ b/integration-tests/api/__tests__/batch-jobs/product/product-import.csv @@ -0,0 +1,5 @@ +Product id,Product Handle,Product Title,Product Subtitle,Product Description,Product Status,Product Thumbnail,Product Weight,Product Length,Product Width,Product Height,Product HS Code,Product Origin Country,Product MID Code,Product Material,Product Collection Title,Product Collection Handle,Product Type,Product Tags,Product Discountable,Product External ID,Variant id,Variant Title,Variant SKU,Variant Barcode,Variant Inventory Quantity,Variant Allow backorder,Variant Manage inventory,Variant Weight,Variant Length,Variant Width,Variant Height,Variant HS Code,Variant Origin Country,Variant MID Code,Variant Material,Price ImportLand [EUR],Price USD,Price denmark [DKK],Price Denmark [DKK],Option 1 Name,Option 1 Value,Option 2 Name,Option 2 Value,Image 1 Url +,test-product-product-1,Test product,,"Hopper Stripes Bedding, available as duvet cover, pillow sham and sheet.\n100% organic cotton, soft and crisp to the touch. Made in Portugal.",draft,,,,,,,,,,Test collection 1,test-collection1,,123_1,TRUE,,,Test variant,test-sku-1,test-barcode-1,10,FALSE,TRUE,,,,,,,,,1.00,1.10,1.30,,test-option-1,option 1 value red,test-option-2,option 2 value 1,test-image.png +existing-product-id,test-product-product-2,Test product,,test-product-description,draft,test-image.png,,,,,,,,,Test collection,test-collection2,test-type,123,TRUE,,,Test variant,test-sku-2,test-barcode-2,10,FALSE,TRUE,,,,,,,,,,,,1.10,Size,Small,,,test-image.png +existing-product-id,test-product-product-2,Test product,,test-product-description,draft,,,,,,,,,,Test collection,test-collection2,test-type,123,TRUE,,,Test variant,test-sku-3,test-barcode-3,10,FALSE,TRUE,,,,,,,,,,1.20,,,Size,Medium,,,test-image.png +existing-product-id,test-product-product-2,Test product,,test-product-description,draft,,,,,,,,,,Test collection,test-collection2,test-type,123,TRUE,,existing-variant-id,Test variant changed,test-sku-4,test-barcode-4,10,FALSE,TRUE,,,,,,,,,,,,,Size,Large,,,test-image.png \ No newline at end of file diff --git a/packages/medusa/src/strategies/__tests__/batch-jobs/product/__snapshots__/export.ts.snap b/packages/medusa/src/strategies/__tests__/batch-jobs/product/__snapshots__/export.ts.snap index 676b1eadd6e2e..257435f994842 100644 --- a/packages/medusa/src/strategies/__tests__/batch-jobs/product/__snapshots__/export.ts.snap +++ b/packages/medusa/src/strategies/__tests__/batch-jobs/product/__snapshots__/export.ts.snap @@ -34,22 +34,3 @@ SC 1 forth line\\";;; ", ] `; - -exports[`Product export strategy with sales channels should process the batch job and generate the appropriate output 1`] = ` -Array [ - "Product Id;Product Handle;Product Title;Product Subtitle;Product Description;Product Status;Product Thumbnail;Product Weight;Product Length;Product Width;Product Height;Product HS Code;Product Origin Country;Product MID Code;Product Material;Product Collection Title;Product Collection Handle;Product Type;Product Tags;Product Discountable;Product External Id;Product Profile Name;Product Profile Type;Variant Id;Variant Title;Variant SKU;Variant Barcode;Variant Inventory Quantity;Variant Allow Backorder;Variant Manage Inventory;Variant Weight;Variant Length;Variant Width;Variant Height;Variant HS Code;Variant Origin Country;Variant MID Code;Variant Material;Price france [USD];Price USD;Price denmark [DKK];Price Denmark [DKK];Option 1 Name;Option 1 Value;Option 2 Name;Option 2 Value;Image 1 Url;Sales channel 1 Name;Sales channel 1 Description;Sales channel 2 Name;Sales channel 2 Description -", - "product-export-strategy-product-1;test-product-product-1;Test product;;\\"test-product-description-1 -test-product-description-1 second line -test-product-description-1 third line -forth line\\";draft;;;;;;;;;;Test collection 1;test-collection1;test-type-1;123_1;true;;profile_1;profile_type_1;product-export-strategy-variant-1;Test variant;test-sku;test-barcode;10;false;true;;;;;;;;;100;110;130;;test-option-1;option 1 value 1;test-option-2;option 2 value 1;test-image.png;SC 1;\\"SC 1 -SC 1 second line -SC 1 third line -SC 1 forth line\\";; -", - "product-export-strategy-product-2;test-product-product-2;Test product;;test-product-description;draft;;;;;;;;;;Test collection;test-collection2;test-type;123;true;;profile_2;profile_type_2;product-export-strategy-variant-2;Test variant;test-sku;test-barcode;10;false;true;;;;;;;;;;;;110;test-option;Option 1 value 1;;;test-image.png;SC 1;SC 1;SC 2;SC 2 -", - "product-export-strategy-product-2;test-product-product-2;Test product;;test-product-description;draft;;;;;;;;;;Test collection;test-collection2;test-type;123;true;;profile_2;profile_type_2;product-export-strategy-variant-3;Test variant;test-sku;test-barcode;10;false;true;;;;;;;;;;120;;;test-option;Option 1 Value 1;;;test-image.png;SC 1;SC 1;SC 2;SC 2 -", -] -`; From 7fdb665044b5d377239781a75fcaabc82b1f5906 Mon Sep 17 00:00:00 2001 From: Adrien de Peretti Date: Thu, 20 Oct 2022 15:29:57 +0200 Subject: [PATCH 12/13] Create rotten-squids-arrive.md --- .changeset/rotten-squids-arrive.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/rotten-squids-arrive.md diff --git a/.changeset/rotten-squids-arrive.md b/.changeset/rotten-squids-arrive.md new file mode 100644 index 0000000000000..0dec3ffd8c095 --- /dev/null +++ b/.changeset/rotten-squids-arrive.md @@ -0,0 +1,5 @@ +--- +"@medusajs/medusa": minor +--- + +feat(medusa): Align columns between product import/export, re visit the way the columns are defined and treated From 3e2738cf2ce5c6da350174eebe70321f5ad5efb0 Mon Sep 17 00:00:00 2001 From: adrien2p Date: Thu, 20 Oct 2022 16:22:53 +0200 Subject: [PATCH 13/13] fix import file --- .../api/__tests__/batch-jobs/product/product-import.csv | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/integration-tests/api/__tests__/batch-jobs/product/product-import.csv b/integration-tests/api/__tests__/batch-jobs/product/product-import.csv index 726767634929b..072a23baeb822 100644 --- a/integration-tests/api/__tests__/batch-jobs/product/product-import.csv +++ b/integration-tests/api/__tests__/batch-jobs/product/product-import.csv @@ -1,5 +1,6 @@ -Product id,Product Handle,Product Title,Product Subtitle,Product Description,Product Status,Product Thumbnail,Product Weight,Product Length,Product Width,Product Height,Product HS Code,Product Origin Country,Product MID Code,Product Material,Product Collection Title,Product Collection Handle,Product Type,Product Tags,Product Discountable,Product External ID,Variant id,Variant Title,Variant SKU,Variant Barcode,Variant Inventory Quantity,Variant Allow backorder,Variant Manage inventory,Variant Weight,Variant Length,Variant Width,Variant Height,Variant HS Code,Variant Origin Country,Variant MID Code,Variant Material,Price ImportLand [EUR],Price USD,Price denmark [DKK],Price Denmark [DKK],Option 1 Name,Option 1 Value,Option 2 Name,Option 2 Value,Image 1 Url +Product Id,Product Handle,Product Title,Product Subtitle,Product Description,Product Status,Product Thumbnail,Product Weight,Product Length,Product Width,Product Height,Product HS Code,Product Origin Country,Product MID Code,Product Material,Product Collection Title,Product Collection Handle,Product Type,Product Tags,Product Discountable,Product External Id,Variant Id,Variant Title,Variant SKU,Variant Barcode,Variant Inventory Quantity,Variant Allow Backorder,Variant Manage Inventory,Variant Weight,Variant Length,Variant Width,Variant Height,Variant HS Code,Variant Origin Country,Variant MID Code,Variant Material,Price ImportLand [EUR],Price USD,Price denmark [DKK],Price Denmark [DKK],Option 1 Name,Option 1 Value,Option 2 Name,Option 2 Value,Image 1 Url ,test-product-product-1,Test product,,"Hopper Stripes Bedding, available as duvet cover, pillow sham and sheet.\n100% organic cotton, soft and crisp to the touch. Made in Portugal.",draft,,,,,,,,,,Test collection 1,test-collection1,,123_1,TRUE,,,Test variant,test-sku-1,test-barcode-1,10,FALSE,TRUE,,,,,,,,,1.00,1.10,1.30,,test-option-1,option 1 value red,test-option-2,option 2 value 1,test-image.png +,test-product-product-1-1,Test product,,"Hopper Stripes Bedding, available as duvet cover, pillow sham and sheet.\n100% organic cotton, soft and crisp to the touch. Made in Portugal.",draft,,,,,,,,,,Test collection 1,test-collection1,,,TRUE,,,Test variant,test-sku-1-1,test-barcode-1-1,10,FALSE,TRUE,,,,,,,,,1.00,1.10,1.30,,test-option-1,option 1 value red,test-option-2,option 2 value 1,test-image.png existing-product-id,test-product-product-2,Test product,,test-product-description,draft,test-image.png,,,,,,,,,Test collection,test-collection2,test-type,123,TRUE,,,Test variant,test-sku-2,test-barcode-2,10,FALSE,TRUE,,,,,,,,,,,,1.10,Size,Small,,,test-image.png existing-product-id,test-product-product-2,Test product,,test-product-description,draft,,,,,,,,,,Test collection,test-collection2,test-type,123,TRUE,,,Test variant,test-sku-3,test-barcode-3,10,FALSE,TRUE,,,,,,,,,,1.20,,,Size,Medium,,,test-image.png existing-product-id,test-product-product-2,Test product,,test-product-description,draft,,,,,,,,,,Test collection,test-collection2,test-type,123,TRUE,,existing-variant-id,Test variant changed,test-sku-4,test-barcode-4,10,FALSE,TRUE,,,,,,,,,,,,,Size,Large,,,test-image.png \ No newline at end of file