diff --git a/docs/content/developer-guide/importing-product-data.md b/docs/content/developer-guide/importing-product-data.md index a7a85a2815..4f08339ea4 100644 --- a/docs/content/developer-guide/importing-product-data.md +++ b/docs/content/developer-guide/importing-product-data.md @@ -58,6 +58,24 @@ If you have [CustomFields]({{< relref "customizing-models" >}}) defined on your For a real example, see the [products.csv file used to populate the Vendure demo data](https://github.com/vendure-ecommerce/vendure/blob/master/packages/core/mock-data/data-sources/products.csv) {{< /alert >}} +#### Importing `relation` custom fields + +To import custom fields with the type `relation`, the value in the CSV must be a stringified object with an `id` property: + +```csv +... ,product:featuredReview +... ,"{ ""id"": 123 }" +``` + +#### Importing `list` custom fields + +To import custom fields with `list` set to `true`, the data should be separated with a pipe (`|`) character: + +```csv +... ,product:keywords +... ,tablet|pad|android +``` + ## Initial Data As well as product data, other initialization data can be populated using the [`InitialData` object]({{< relref "initial-data" >}}). **This format is intentionally limited**; more advanced requirements (e.g. setting up ShippingMethods that use custom checkers & calculators) should be carried out via scripts which interact with the [Admin GraphQL API]({{< relref "/docs/graphql-api/admin" >}}). diff --git a/packages/core/e2e/__snapshots__/import.e2e-spec.ts.snap b/packages/core/e2e/__snapshots__/import.e2e-spec.ts.snap index 8b98cb9482..8a89633d3f 100644 --- a/packages/core/e2e/__snapshots__/import.e2e-spec.ts.snap +++ b/packages/core/e2e/__snapshots__/import.e2e-spec.ts.snap @@ -17,6 +17,11 @@ Object { }, ], "customFields": Object { + "keywords": Array [ + "paper", + "stretching", + "watercolor", + ], "owner": Object { "id": "T_1", }, @@ -114,6 +119,7 @@ exports[`Import resolver imports products 2`] = ` Object { "assets": Array [], "customFields": Object { + "keywords": Array [], "owner": Object { "id": "T_1", }, @@ -160,6 +166,7 @@ exports[`Import resolver imports products 3`] = ` Object { "assets": Array [], "customFields": Object { + "keywords": Array [], "owner": Object { "id": "T_1", }, @@ -250,6 +257,10 @@ exports[`Import resolver imports products 4`] = ` Object { "assets": Array [], "customFields": Object { + "keywords": Array [ + "apron", + "clothing", + ], "owner": Object { "id": "T_1", }, diff --git a/packages/core/e2e/fixtures/product-import.csv b/packages/core/e2e/fixtures/product-import.csv index 1be2a60dff..d5be974e10 100644 --- a/packages/core/e2e/fixtures/product-import.csv +++ b/packages/core/e2e/fixtures/product-import.csv @@ -1,12 +1,12 @@ -name , slug , description , assets , facets , optionGroups , optionValues , sku , price , taxCategory , stockOnHand , trackInventory , variantAssets , variantFacets , product:pageType , variant:weight,product:owner -Perfect Paper Stretcher , perfect-paper-stretcher , A great device for stretching paper. , "pps1.jpg|pps2.jpg" , , size , Half Imperial , PPS12 , 45.3 , standard , 0 , false , , Brand:KB|Type:Accessory , default , 100,"{""id"": 1}" - , , , , , , Quarter Imperial , PPS14 , 32.5 , standard , 0 , false , , Brand:KB|Type:Accessory , , 100,"{""id"": 1}" - , , , , , , Full Imperial , PPSF , 59.5 , standard , -10 , false , , Brand:KB|Type:Accessory , , 100,"{""id"": 1}" -Mabef M/02 Studio Easel , , Mabef description , , , , , M02 , 910.7 , standard , 100 , false , , Brand:Mabef|Type:Easel , expanded , 300,"{""id"": 1}" -Giotto Mega Pencils , , Really mega pencils , , , box size , Box of 8 , 225400 , 4.16 , standard , , false , "box-of-8.jpg" , Collection:Xmas Sale , default , 200,"{""id"": 1}" - , , , , , , Box of 12 , 225600 , 6.24 , standard , , false , "box-of-12.jpg" , Collection:Xmas Sale , , 200,"{""id"": 1}" +name ,slug ,description ,assets ,facets ,optionGroups ,optionValues ,sku ,price,taxCategory,stockOnHand,trackInventory,variantAssets ,variantFacets ,product:pageType,variant:weight,product:owner,product:keywords +Perfect Paper Stretcher,perfect-paper-stretcher,A great device for stretching paper.,"pps1.jpg|pps2.jpg", ,size ,Half Imperial ,PPS12 ,45.3 ,standard ,0 ,false , ,Brand:KB|Type:Accessory,default ,100 ,"{""id"": 1}",paper|stretching|watercolor + , , , , , ,Quarter Imperial,PPS14 ,32.5 ,standard ,0 ,false , ,Brand:KB|Type:Accessory, ,100 ,"{""id"": 1}", + , , , , , ,Full Imperial ,PPSF ,59.5 ,standard ,-10 ,false , ,Brand:KB|Type:Accessory, ,100 ,"{""id"": 1}", +Mabef M/02 Studio Easel, ,Mabef description , , , , ,M02 ,910.7,standard ,100 ,false , ,Brand:Mabef|Type:Easel ,expanded ,300 ,"{""id"": 1}", +Giotto Mega Pencils , ,Really mega pencils , , ,box size ,Box of 8 ,225400,4.16 ,standard , ,false ,"box-of-8.jpg" ,Collection:Xmas Sale ,default ,200 ,"{""id"": 1}", + , , , , , ,Box of 12 ,225600,6.24 ,standard , ,false ,"box-of-12.jpg",Collection:Xmas Sale , ,200 ,"{""id"": 1}"", -Artists Smock , , Keeps the paint off the clothes , , Material:Denim|Collection:clothes , "size|colour" , "small|beige" , 10112 , 11.99 , reduced , , false , , , default , 500,"{""id"": 1}" - , , , , , , "large|beige" , 10113 , 11.99 , reduced , , false , , , default , 500,"{""id"": 1}" - , , , , , , "small|navy" , 10114 , 11.99 , reduced , , false , , , default , 500,"{""id"": 1}" - , , , , , , "large|navy" , 10115 , 11.99 , reduced , , false , , , default , 500,"{""id"": 1}" +Artists Smock , ,Keeps the paint off the clothes , ,Material:Denim|Collection:clothes,"size|colour","small|beige" ,10112 ,11.99,reduced , ,false , , ,default ,500 ,"{""id"": 1}",apron|clothing + , , , , , ,"large|beige" ,10113 ,11.99,reduced , ,false , , ,default ,500 ,"{""id"": 1}", + , , , , , ,"small|navy" ,10114 ,11.99,reduced , ,false , , ,default ,500 ,"{""id"": 1}", + , , , , , ,"large|navy" ,10115 ,11.99,reduced , ,false , , ,default ,500 ,"{""id"": 1}", diff --git a/packages/core/e2e/import.e2e-spec.ts b/packages/core/e2e/import.e2e-spec.ts index c56aa5b81f..700d8841f7 100644 --- a/packages/core/e2e/import.e2e-spec.ts +++ b/packages/core/e2e/import.e2e-spec.ts @@ -21,6 +21,13 @@ describe('Import resolver', () => { entity: User, eager: true, }, + { + name: 'keywords', + public: true, + nullable: true, + type: 'string', + list: true, + }, ], ProductVariant: [{ type: 'int', name: 'weight' }], }, @@ -69,7 +76,7 @@ describe('Import resolver', () => { }); expect(result.importProducts.errors).toEqual([ - 'Invalid Record Length: header length is 17, got 1 on line 8', + 'Invalid Record Length: header length is 18, got 1 on line 8', ]); expect(result.importProducts.imported).toBe(4); expect(result.importProducts.processed).toBe(4); @@ -114,6 +121,7 @@ describe('Import resolver', () => { owner { id } + keywords } variants { id @@ -216,9 +224,16 @@ describe('Import resolver', () => { expect(smock.variants[2].options.map(byCode).sort()).toEqual(['navy', 'small']); expect(smock.variants[3].options.map(byCode).sort()).toEqual(['large', 'navy']); + // Import relation custom fields expect(paperStretcher.customFields.owner.id).toBe('T_1'); expect(easel.customFields.owner.id).toBe('T_1'); expect(pencils.customFields.owner.id).toBe('T_1'); expect(smock.customFields.owner.id).toBe('T_1'); + + // Import list custom fields + expect(paperStretcher.customFields.keywords).toEqual(['paper', 'stretching', 'watercolor']); + expect(easel.customFields.keywords).toEqual([]); + expect(pencils.customFields.keywords).toEqual([]); + expect(smock.customFields.keywords).toEqual(['apron', 'clothing']); }, 20000); }); diff --git a/packages/core/src/data-import/providers/importer/importer.ts b/packages/core/src/data-import/providers/importer/importer.ts index 147ac1d336..43c8cc2ed9 100644 --- a/packages/core/src/data-import/providers/importer/importer.ts +++ b/packages/core/src/data-import/providers/importer/importer.ts @@ -8,6 +8,7 @@ import { Stream } from 'stream'; import { RequestContext } from '../../../api/common/request-context'; import { ConfigService } from '../../../config/config.service'; +import { CustomFieldConfig } from '../../../config/custom-field/custom-field-types'; import { FacetValue } from '../../../entity/facet-value/facet-value.entity'; import { Facet } from '../../../entity/facet/facet.entity'; import { TaxCategory } from '../../../entity/tax-category/tax-category.entity'; @@ -160,7 +161,10 @@ export class Importer { slug: product.slug, }, ], - customFields: product.customFields, + customFields: this.processCustomFieldValues( + product.customFields, + this.configService.customFields.Product, + ), }); const optionsMap: { [optionName: string]: ID } = {}; @@ -219,7 +223,10 @@ export class Importer { }, ], price: Math.round(variant.price * 100), - customFields: variant.customFields, + customFields: this.processCustomFieldValues( + variant.customFields, + this.configService.customFields.ProductVariant, + ), }); } imported++; @@ -295,6 +302,16 @@ export class Importer { return facetValueIds; } + private processCustomFieldValues(customFields: { [field: string]: string }, config: CustomFieldConfig[]) { + const processed: { [field: string]: string | string[] } = {}; + for (const fieldDef of config) { + const value = customFields[fieldDef.name]; + processed[fieldDef.name] = + fieldDef.list === true ? value.split('|').filter(val => val.trim() !== '') : value; + } + return processed; + } + /** * Attempts to match a TaxCategory entity against the name supplied in the import table. If no matches * are found, the first TaxCategory id is returned. diff --git a/packages/core/src/service/services/customer.service.ts b/packages/core/src/service/services/customer.service.ts index c26330ecfa..38a148caa3 100644 --- a/packages/core/src/service/services/customer.service.ts +++ b/packages/core/src/service/services/customer.service.ts @@ -381,9 +381,7 @@ export class CustomerService { throw new InternalServerError('error.cannot-locate-customer-for-user'); } if (ctx.channelId) { - await this.channelService.assignToChannels(ctx, Customer, customer.id, [ - ctx.channelId, - ]); + await this.channelService.assignToChannels(ctx, Customer, customer.id, [ctx.channelId]); } await this.historyService.createHistoryEntryForCustomer({ customerId: customer.id,