Browse Source

feat(server): Implement importing of facets

Michael Bromley 7 years ago
parent
commit
35795af965

File diff suppressed because it is too large
+ 0 - 0
schema.json


+ 106 - 0
server/e2e/__snapshots__/import.e2e-spec.ts.snap

@@ -31,6 +31,26 @@ Array [
     "variants": Array [
       Object {
         "assets": Array [],
+        "facetValues": Array [
+          Object {
+            "code": "kb",
+            "facet": Object {
+              "id": "T_1",
+              "name": "Brand",
+            },
+            "id": "T_11",
+            "name": "KB",
+          },
+          Object {
+            "code": "accessory",
+            "facet": Object {
+              "id": "T_2",
+              "name": "Type",
+            },
+            "id": "T_12",
+            "name": "Accessory",
+          },
+        ],
         "featuredAsset": null,
         "id": "T_1",
         "name": "Perfect Paper Stretcher Half Imperial",
@@ -49,6 +69,26 @@ Array [
       },
       Object {
         "assets": Array [],
+        "facetValues": Array [
+          Object {
+            "code": "kb",
+            "facet": Object {
+              "id": "T_1",
+              "name": "Brand",
+            },
+            "id": "T_11",
+            "name": "KB",
+          },
+          Object {
+            "code": "accessory",
+            "facet": Object {
+              "id": "T_2",
+              "name": "Type",
+            },
+            "id": "T_12",
+            "name": "Accessory",
+          },
+        ],
         "featuredAsset": null,
         "id": "T_2",
         "name": "Perfect Paper Stretcher Quarter Imperial",
@@ -67,6 +107,26 @@ Array [
       },
       Object {
         "assets": Array [],
+        "facetValues": Array [
+          Object {
+            "code": "kb",
+            "facet": Object {
+              "id": "T_1",
+              "name": "Brand",
+            },
+            "id": "T_11",
+            "name": "KB",
+          },
+          Object {
+            "code": "accessory",
+            "facet": Object {
+              "id": "T_2",
+              "name": "Type",
+            },
+            "id": "T_12",
+            "name": "Accessory",
+          },
+        ],
         "featuredAsset": null,
         "id": "T_3",
         "name": "Perfect Paper Stretcher Full Imperial",
@@ -96,6 +156,26 @@ Array [
     "variants": Array [
       Object {
         "assets": Array [],
+        "facetValues": Array [
+          Object {
+            "code": "mabef",
+            "facet": Object {
+              "id": "T_1",
+              "name": "Brand",
+            },
+            "id": "T_13",
+            "name": "Mabef",
+          },
+          Object {
+            "code": "easel",
+            "facet": Object {
+              "id": "T_2",
+              "name": "Type",
+            },
+            "id": "T_14",
+            "name": "Easel",
+          },
+        ],
         "featuredAsset": null,
         "id": "T_4",
         "name": "Mabef M/02 Studio Easel",
@@ -131,6 +211,17 @@ Array [
             "name": "box-of-8.jpg",
           },
         ],
+        "facetValues": Array [
+          Object {
+            "code": "xmas-sale",
+            "facet": Object {
+              "id": "T_3",
+              "name": "Collection",
+            },
+            "id": "T_15",
+            "name": "Xmas Sale",
+          },
+        ],
         "featuredAsset": Object {
           "id": "T_8",
           "name": "box-of-8.jpg",
@@ -157,6 +248,17 @@ Array [
             "name": "box-of-12.jpg",
           },
         ],
+        "facetValues": Array [
+          Object {
+            "code": "xmas-sale",
+            "facet": Object {
+              "id": "T_3",
+              "name": "Collection",
+            },
+            "id": "T_15",
+            "name": "Xmas Sale",
+          },
+        ],
         "featuredAsset": Object {
           "id": "T_9",
           "name": "box-of-12.jpg",
@@ -200,6 +302,7 @@ Array [
     "variants": Array [
       Object {
         "assets": Array [],
+        "facetValues": Array [],
         "featuredAsset": null,
         "id": "T_7",
         "name": "Artists Smock small beige",
@@ -222,6 +325,7 @@ Array [
       },
       Object {
         "assets": Array [],
+        "facetValues": Array [],
         "featuredAsset": null,
         "id": "T_8",
         "name": "Artists Smock large beige",
@@ -244,6 +348,7 @@ Array [
       },
       Object {
         "assets": Array [],
+        "facetValues": Array [],
         "featuredAsset": null,
         "id": "T_9",
         "name": "Artists Smock small navy",
@@ -266,6 +371,7 @@ Array [
       },
       Object {
         "assets": Array [],
+        "facetValues": Array [],
         "featuredAsset": null,
         "id": "T_10",
         "name": "Artists Smock large navy",

+ 11 - 11
server/e2e/fixtures/product-import.csv

@@ -1,12 +1,12 @@
-name                    , slug                    , description                          , assets               , optionGroups   , optionValues     , sku    , price , taxCategory , variantAssets
-Perfect Paper Stretcher , perfect-paper-stretcher , A great device for stretching paper. , "pps1.jpg|pps2.jpg" , size           , Half Imperial    , PPS12  , 45.3  , standard    ,
-                        ,                         ,                                      ,                      ,                , Quarter Imperial , PPS14  , 32.5  , standard    ,
-                        ,                         ,                                      ,                      ,                , Full Imperial    , PPSF   , 59.5  , standard    ,
-Mabef M/02 Studio Easel ,                         , Mabef description                    ,                      ,                ,                  , M02    , 910.7 , standard    ,
-Giotto Mega Pencils     ,                         , Really mega pencils                  ,                      , box size       , Box of 8         , 225400 , 4.16  , standard    , "box-of-8.jpg"
-                        ,                         ,                                      ,                      ,                , Box of 12        , 225600 , 6.24  , standard    , "box-of-12.jpg"
+name                    , slug                    , description                          , assets              , optionGroups  , optionValues     , sku    , price , taxCategory , variantAssets   , facets
+Perfect Paper Stretcher , perfect-paper-stretcher , A great device for stretching paper. , "pps1.jpg|pps2.jpg" , size          , Half Imperial    , PPS12  , 45.3  , standard    ,                 , Brand:KB|Type:Accessory
+                        ,                         ,                                      ,                     ,               , Quarter Imperial , PPS14  , 32.5  , standard    ,                 , Brand:KB|Type:Accessory
+                        ,                         ,                                      ,                     ,               , Full Imperial    , PPSF   , 59.5  , standard    ,                 , Brand:KB|Type:Accessory
+Mabef M/02 Studio Easel ,                         , Mabef description                    ,                     ,               ,                  , M02    , 910.7 , standard    ,                 , Brand:Mabef|Type:Easel
+Giotto Mega Pencils     ,                         , Really mega pencils                  ,                     , box size      , Box of 8         , 225400 , 4.16  , standard    , "box-of-8.jpg"  , Collection:Xmas Sale
+                        ,                         ,                                      ,                     ,               , Box of 12        , 225600 , 6.24  , standard    , "box-of-12.jpg" , Collection:Xmas Sale
 
-Artists Smock           ,                         , Keeps the paint off the clothes      ,                      , "size|colour" , "small|beige"   , 10112  , 11.99 , reduced     ,
-                        ,                         ,                                      ,                      ,                , "large|beige"   , 10113  , 11.99 , reduced     ,
-                        ,                         ,                                      ,                      ,                , "small|navy"    , 10114  , 11.99 , reduced     ,
-                        ,                         ,                                      ,                      ,                , "large|navy"    , 10115  , 11.99 , reduced     ,
+Artists Smock           ,                         , Keeps the paint off the clothes      ,                     , "size|colour" , "small|beige"    , 10112  , 11.99 , reduced     ,                 ,
+                        ,                         ,                                      ,                     ,               , "large|beige"    , 10113  , 11.99 , reduced     ,                 ,
+                        ,                         ,                                      ,                     ,               , "small|navy"     , 10114  , 11.99 , reduced     ,                 ,
+                        ,                         ,                                      ,                     ,               , "large|navy"     , 10115  , 11.99 , reduced     ,                 ,

+ 10 - 1
server/e2e/import.e2e-spec.ts

@@ -39,7 +39,7 @@ describe('Import resolver', () => {
         const result = await client.importProducts(csvFile);
 
         expect(result.importProducts.errors).toEqual([
-            'Invalid Record Length: header length is 10, got 1 on line 8',
+            'Invalid Record Length: header length is 11, got 1 on line 8',
         ]);
         expect(result.importProducts.imported).toBe(4);
         expect(result.importProducts.processed).toBe(4);
@@ -88,6 +88,15 @@ describe('Import resolver', () => {
                                     id
                                     name
                                 }
+                                facetValues {
+                                    id
+                                    code
+                                    name
+                                    facet {
+                                        id
+                                        name
+                                    }
+                                }
                             }
                         }
                     }

+ 33 - 0
server/src/data-import/providers/import-parser/__snapshots__/import-parser.spec.ts.snap

@@ -22,6 +22,7 @@ Array [
     "variants": Array [
       Object {
         "assetPaths": Array [],
+        "facets": Array [],
         "optionValues": Array [
           "Half Imperial",
         ],
@@ -31,6 +32,7 @@ Array [
       },
       Object {
         "assetPaths": Array [],
+        "facets": Array [],
         "optionValues": Array [
           "Quarter Imperial",
         ],
@@ -40,6 +42,7 @@ Array [
       },
       Object {
         "assetPaths": Array [],
+        "facets": Array [],
         "optionValues": Array [
           "Full Imperial",
         ],
@@ -60,6 +63,7 @@ Array [
     "variants": Array [
       Object {
         "assetPaths": Array [],
+        "facets": Array [],
         "optionValues": Array [],
         "price": 910.7,
         "sku": "M02",
@@ -86,6 +90,7 @@ Array [
     "variants": Array [
       Object {
         "assetPaths": Array [],
+        "facets": Array [],
         "optionValues": Array [
           "Box of 8",
         ],
@@ -95,6 +100,7 @@ Array [
       },
       Object {
         "assetPaths": Array [],
+        "facets": Array [],
         "optionValues": Array [
           "Box of 12",
         ],
@@ -130,6 +136,7 @@ Array [
     "variants": Array [
       Object {
         "assetPaths": Array [],
+        "facets": Array [],
         "optionValues": Array [
           "small",
           "beige",
@@ -140,6 +147,7 @@ Array [
       },
       Object {
         "assetPaths": Array [],
+        "facets": Array [],
         "optionValues": Array [
           "large",
           "beige",
@@ -150,6 +158,7 @@ Array [
       },
       Object {
         "assetPaths": Array [],
+        "facets": Array [],
         "optionValues": Array [
           "small",
           "navy",
@@ -160,6 +169,7 @@ Array [
       },
       Object {
         "assetPaths": Array [],
+        "facets": Array [],
         "optionValues": Array [
           "large",
           "navy",
@@ -195,6 +205,7 @@ Array [
     "variants": Array [
       Object {
         "assetPaths": Array [],
+        "facets": Array [],
         "optionValues": Array [
           "Half Imperial",
         ],
@@ -204,6 +215,7 @@ Array [
       },
       Object {
         "assetPaths": Array [],
+        "facets": Array [],
         "optionValues": Array [
           "Quarter Imperial",
         ],
@@ -213,6 +225,7 @@ Array [
       },
       Object {
         "assetPaths": Array [],
+        "facets": Array [],
         "optionValues": Array [
           "Full Imperial",
         ],
@@ -241,6 +254,16 @@ Array [
     "variants": Array [
       Object {
         "assetPaths": Array [],
+        "facets": Array [
+          Object {
+            "facet": "brand",
+            "value": "KB",
+          },
+          Object {
+            "facet": "type",
+            "value": "Accessory",
+          },
+        ],
         "optionValues": Array [],
         "price": 45.3,
         "sku": "PPS12",
@@ -273,6 +296,7 @@ Array [
     "variants": Array [
       Object {
         "assetPaths": Array [],
+        "facets": Array [],
         "optionValues": Array [
           "Half Imperial",
         ],
@@ -282,6 +306,7 @@ Array [
       },
       Object {
         "assetPaths": Array [],
+        "facets": Array [],
         "optionValues": Array [
           "Quarter Imperial",
         ],
@@ -291,6 +316,7 @@ Array [
       },
       Object {
         "assetPaths": Array [],
+        "facets": Array [],
         "optionValues": Array [
           "Full Imperial",
         ],
@@ -311,6 +337,7 @@ Array [
     "variants": Array [
       Object {
         "assetPaths": Array [],
+        "facets": Array [],
         "optionValues": Array [],
         "price": 910.7,
         "sku": "M02",
@@ -337,6 +364,7 @@ Array [
     "variants": Array [
       Object {
         "assetPaths": Array [],
+        "facets": Array [],
         "optionValues": Array [
           "Box of 8",
         ],
@@ -346,6 +374,7 @@ Array [
       },
       Object {
         "assetPaths": Array [],
+        "facets": Array [],
         "optionValues": Array [
           "Box of 12",
         ],
@@ -381,6 +410,7 @@ Array [
     "variants": Array [
       Object {
         "assetPaths": Array [],
+        "facets": Array [],
         "optionValues": Array [
           "small",
           "beige",
@@ -391,6 +421,7 @@ Array [
       },
       Object {
         "assetPaths": Array [],
+        "facets": Array [],
         "optionValues": Array [
           "large",
           "beige",
@@ -401,6 +432,7 @@ Array [
       },
       Object {
         "assetPaths": Array [],
+        "facets": Array [],
         "optionValues": Array [
           "small",
           "navy",
@@ -411,6 +443,7 @@ Array [
       },
       Object {
         "assetPaths": Array [],
+        "facets": Array [],
         "optionValues": Array [
           "large",
           "navy",

+ 2 - 2
server/src/data-import/providers/import-parser/import-parser.spec.ts

@@ -76,8 +76,8 @@ describe('ImportParser', () => {
                 const result = await importParser.parseProducts(input);
 
                 expect(result.errors).toEqual([
-                    'Invalid Record Length: header length is 10, got 8 on line 3',
-                    'Invalid Record Length: header length is 10, got 1 on line 4',
+                    'Invalid Record Length: header length is 11, got 9 on line 3',
+                    'Invalid Record Length: header length is 11, got 1 on line 4',
                 ]);
                 expect(result.results.length).toBe(2);
             });

+ 10 - 0
server/src/data-import/providers/import-parser/import-parser.ts

@@ -15,6 +15,7 @@ export interface RawProductRecord {
     price?: string;
     taxCategory?: string;
     variantAssets?: string;
+    facets?: string;
 }
 
 export interface ParsedProductVariant {
@@ -23,6 +24,10 @@ export interface ParsedProductVariant {
     price: number;
     taxCategory: string;
     assetPaths: string[];
+    facets: Array<{
+        facet: string;
+        value: string;
+    }>;
 }
 
 export interface ParsedProduct {
@@ -58,6 +63,7 @@ const requiredColumns: Array<keyof RawProductRecord> = [
     'price',
     'taxCategory',
     'variantAssets',
+    'facets',
 ];
 
 /**
@@ -223,6 +229,10 @@ function parseVariantFromRecord(r: RawProductRecord): ParsedProductVariant {
         price: parseNumber(r.price),
         taxCategory: parseString(r.taxCategory),
         assetPaths: parseStringArray(r.variantAssets),
+        facets: parseStringArray(r.facets).map(pair => {
+            const [facet, value] = pair.split(':');
+            return { facet, value };
+        }),
     };
 }
 

+ 5 - 5
server/src/data-import/providers/import-parser/test-fixtures/invalid-columns.csv

@@ -1,5 +1,5 @@
-"name"          , "description"                     , "optionGroups" , "optionValues" , "sku"   , "price" , "taxCategory" , "variantAssets"
-"Artists Smock" , "Keeps the paint off the clothes" , "size|colour" , "small|beige" , "10112" , "11.99" , "standard"    , ""
-""              , ""                                , ""             , "large|beige" , "10113" , "11.99" , "standard"    , ""
-""              , ""                                , ""             , "small|navy"  , "10114" , "11.99" , "standard"    , ""
-""              , ""                                , ""             , "large|navy"  , "10115" , "11.99" , "standard"    , ""
+"name"          , "description"                     , "optionGroups" , "optionValues" , "sku"   , "price" , "taxCategory" , "variantAssets" , facets
+"Artists Smock" , "Keeps the paint off the clothes" , "size|colour"  , "small|beige"  , "10112" , "11.99" , "standard"    , ""              ,
+""              , ""                                , ""             , "large|beige"  , "10113" , "11.99" , "standard"    , ""              ,
+""              , ""                                , ""             , "small|navy"   , "10114" , "11.99" , "standard"    , ""              ,
+""              , ""                                , ""             , "large|navy"   , "10115" , "11.99" , "standard"    , ""              ,

+ 5 - 5
server/src/data-import/providers/import-parser/test-fixtures/invalid-option-values.csv

@@ -1,5 +1,5 @@
-"name"          , "slug" , "description"                     , "assets" , "optionGroups" , "optionValues" , "sku"   , "price" , "taxCategory" , "variantAssets"
-"Artists Smock" , ""     , "Keeps the paint off the clothes" , ""       , "size"         , "small|beige" , "10112" , "11.99" , "standard"    , ""
-""              , ""     , ""                                , ""       , ""             , "large|beige" , "10113" , "11.99" , "standard"    , ""
-""              , ""     , ""                                , ""       , ""             , "small|navy"  , "10114" , "11.99" , "standard"    , ""
-""              , ""     , ""                                , ""       , ""             , "large|navy"  , "10115" , "11.99" , "standard"    , ""
+"name"          , "slug" , "description"                     , "assets" , "optionGroups" , "optionValues" , "sku"   , "price" , "taxCategory" , "variantAssets" , facets
+"Artists Smock" , ""     , "Keeps the paint off the clothes" , ""       , "size"         , "small|beige"  , "10112" , "11.99" , "standard"    , ""              ,
+""              , ""     , ""                                , ""       , ""             , "large|beige"  , "10113" , "11.99" , "standard"    , ""              ,
+""              , ""     , ""                                , ""       , ""             , "small|navy"   , "10114" , "11.99" , "standard"    , ""              ,
+""              , ""     , ""                                , ""       , ""             , "large|navy"   , "10115" , "11.99" , "standard"    , ""              ,

+ 4 - 4
server/src/data-import/providers/import-parser/test-fixtures/invalid-row-length.csv

@@ -1,5 +1,5 @@
-name                    , slug                    , description                          , assets , optionGroups   , optionValues     , sku    , price , taxCategory , variantAssets
-Mabef M/02 Studio Easel ,                         , Mabef description                    ,        ,                ,                  , M02    , 910.7 , standard    ,
-Giotto Mega Pencils     ,                         , Really mega pencils                  ,        , 225400 , 4.16  , standard    ,
+name                    , slug , description                     , assets , optionGroups , optionValues , sku      , price , taxCategory , variantAssets , facets
+Mabef M/02 Studio Easel ,      , Mabef description               ,        ,              ,              , M02      , 910.7 , standard    ,               ,
+Giotto Mega Pencils     ,      , Really mega pencils             ,        , 225400       , 4.16         , standard ,       ,
 
-Artists Smock           ,                         , Keeps the paint off the clothes      ,        ,  ,    , 10112  , 11.99 , standard    ,
+Artists Smock           ,      , Keeps the paint off the clothes ,        ,              ,              , 10112    , 11.99 , standard    ,               ,

+ 11 - 11
server/src/data-import/providers/import-parser/test-fixtures/multiple-products-multiple-variants.csv

@@ -1,11 +1,11 @@
-name                    , slug                    , description                          , assets , optionGroups   , optionValues     , sku    , price , taxCategory , variantAssets
-Perfect Paper Stretcher , Perfect-paper-stretcher , A great device for stretching paper. ,        , size           , Half Imperial    , PPS12  , 45.3  , standard    ,
-                        ,                         ,                                      ,        ,                , Quarter Imperial , PPS14  , 32.5  , standard    ,
-                        ,                         ,                                      ,        ,                , Full Imperial    , PPSF   , 59.5  , standard    ,
-Mabef M/02 Studio Easel ,                         , Mabef description                    ,        ,                ,                  , M02    , 910.7 , standard    ,
-Giotto Mega Pencils     ,                         , Really mega pencils                  ,        , box size       , Box of 8         , 225400 , 4.16  , standard    ,
-                        ,                         ,                                      ,        ,                , Box of 12        , 225600 , 6.24  , standard    ,
-Artists Smock           ,                         , Keeps the paint off the clothes      ,        , "size|colour" ,  "small|beige"   , 10112  , 11.99 , standard    ,
-                        ,                         ,                                      ,        ,                , "large|beige"   , 10113  , 11.99 , standard    ,
-                        ,                         ,                                      ,        ,                , "small|navy"    , 10114  , 11.99 , standard    ,
-                        ,                         ,                                      ,        ,                , "large|navy"    , 10115  , 11.99 , standard    ,
+name                    , slug                    , description                          , assets , optionGroups  , optionValues     , sku    , price , taxCategory , variantAssets , facets
+Perfect Paper Stretcher , Perfect-paper-stretcher , A great device for stretching paper. ,        , size          , Half Imperial    , PPS12  , 45.3  , standard    ,               ,
+                        ,                         ,                                      ,        ,               , Quarter Imperial , PPS14  , 32.5  , standard    ,               ,
+                        ,                         ,                                      ,        ,               , Full Imperial    , PPSF   , 59.5  , standard    ,               ,
+Mabef M/02 Studio Easel ,                         , Mabef description                    ,        ,               ,                  , M02    , 910.7 , standard    ,               ,
+Giotto Mega Pencils     ,                         , Really mega pencils                  ,        , box size      , Box of 8         , 225400 , 4.16  , standard    ,               ,
+                        ,                         ,                                      ,        ,               , Box of 12        , 225600 , 6.24  , standard    ,               ,
+Artists Smock           ,                         , Keeps the paint off the clothes      ,        , "size|colour" , "small|beige"    , 10112  , 11.99 , standard    ,               ,
+                        ,                         ,                                      ,        ,               , "large|beige"    , 10113  , 11.99 , standard    ,               ,
+                        ,                         ,                                      ,        ,               , "small|navy"     , 10114  , 11.99 , standard    ,               ,
+                        ,                         ,                                      ,        ,               , "large|navy"     , 10115  , 11.99 , standard    ,               ,

+ 4 - 4
server/src/data-import/providers/import-parser/test-fixtures/single-product-multiple-variants.csv

@@ -1,4 +1,4 @@
-name                    , slug , description                          , assets , optionGroups , optionValues     , sku   , price , taxCategory , variantAssets
-Perfect Paper Stretcher ,      , A great device for stretching paper. ,        , size         , Half Imperial    , PPS12 , 45.3  , standard    ,
-                        ,      ,                                      ,        ,              , Quarter Imperial , PPS14 , 32.5  , standard    ,
-                        ,      ,                                      ,        ,              , Full Imperial    , PPSF  , 59.5  , standard    ,
+name                    , slug , description                          , assets , optionGroups , optionValues     , sku   , price , taxCategory , variantAssets , facets
+Perfect Paper Stretcher ,      , A great device for stretching paper. ,        , size         , Half Imperial    , PPS12 , 45.3  , standard    ,               ,
+                        ,      ,                                      ,        ,              , Quarter Imperial , PPS14 , 32.5  , standard    ,               ,
+                        ,      ,                                      ,        ,              , Full Imperial    , PPSF  , 59.5  , standard    ,               ,

+ 2 - 2
server/src/data-import/providers/import-parser/test-fixtures/single-product-single-variant.csv

@@ -1,2 +1,2 @@
-name                    , slug                    , description                          , assets , optionGroups , optionValues , sku   , price , taxCategory , variantAssets
-Perfect Paper Stretcher ,  , A great device for stretching paper. , "pps1.jpg|pps2.jpg" ,              ,              , PPS12 , 45.3  , standard    ,
+name                    , slug , description                          , assets              , optionGroups , optionValues , sku   , price , taxCategory , variantAssets , facets
+Perfect Paper Stretcher ,      , A great device for stretching paper. , "pps1.jpg|pps2.jpg" ,              ,              , PPS12 , 45.3  , standard    ,               , brand:KB|type:Accessory

+ 78 - 9
server/src/data-import/providers/importer/importer.ts

@@ -9,15 +9,23 @@ import { normalizeString } from '../../../../../shared/normalize-string';
 import { RequestContext } from '../../../api/common/request-context';
 import { ConfigService } from '../../../config/config.service';
 import { Asset } from '../../../entity/asset/asset.entity';
+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';
 import { AssetService } from '../../../service/services/asset.service';
 import { ChannelService } from '../../../service/services/channel.service';
+import { FacetValueService } from '../../../service/services/facet-value.service';
+import { FacetService } from '../../../service/services/facet.service';
 import { ProductOptionGroupService } from '../../../service/services/product-option-group.service';
 import { ProductOptionService } from '../../../service/services/product-option.service';
 import { ProductVariantService } from '../../../service/services/product-variant.service';
 import { ProductService } from '../../../service/services/product.service';
 import { TaxCategoryService } from '../../../service/services/tax-category.service';
-import { ImportParser, ParsedProductWithVariants } from '../import-parser/import-parser';
+import {
+    ImportParser,
+    ParsedProductVariant,
+    ParsedProductWithVariants,
+} from '../import-parser/import-parser';
 
 export interface ImportProgress extends ImportInfo {
     currentProduct: string;
@@ -27,17 +35,19 @@ export type OnProgressFn = (progess: ImportProgress) => void;
 @Injectable()
 export class Importer {
     private taxCategoryMatches: { [name: string]: string } = {};
-    /**
-     * This map is used to cache created assets against the file name. This prevents
-     * multiple assets being created for a single identical file.
-     */
+    // These Maps are used to cache newly-created entities and prevent duplicates
+    // from being created.
     private assetMap = new Map<string, Asset>();
+    private facetMap = new Map<string, Facet>();
+    private facetValueMap = new Map<string, FacetValue>();
 
     constructor(
         private configService: ConfigService,
         private importParser: ImportParser,
         private channelService: ChannelService,
         private productService: ProductService,
+        private facetService: FacetService,
+        private facetValueService: FacetValueService,
         private productVariantService: ProductVariantService,
         private productOptionGroupService: ProductOptionGroupService,
         private assetService: AssetService,
@@ -124,7 +134,7 @@ export class Importer {
         const languageCode = ctx.languageCode;
         const taxCategories = await this.taxCategoryService.findAll();
         for (const { product, variants } of rows) {
-            const createProductAssets = await this.createAssets(product.assetPaths);
+            const createProductAssets = await this.getAssets(product.assetPaths);
             const productAssets = createProductAssets.assets;
             if (createProductAssets.errors.length) {
                 errors = errors.concat(createProductAssets.errors);
@@ -171,12 +181,17 @@ export class Importer {
             }
 
             for (const variant of variants) {
-                const createVariantAssets = await this.createAssets(variant.assetPaths);
+                const createVariantAssets = await this.getAssets(variant.assetPaths);
                 const variantAssets = createVariantAssets.assets;
                 if (createVariantAssets.errors.length) {
                     errors = errors.concat(createVariantAssets.errors);
                 }
-                await this.productVariantService.create(ctx, createdProduct, {
+                let facetValueIds: string[] = [];
+                if (0 < variant.facets.length) {
+                    facetValueIds = await this.getFacetValueIds(variant.facets, languageCode);
+                }
+                const createdVariant = await this.productVariantService.create(ctx, createdProduct, {
+                    facetValueIds,
                     featuredAssetId: variantAssets.length ? (variantAssets[0].id as string) : undefined,
                     assetIds: variantAssets.map(a => a.id) as string[],
                     sku: variant.sku,
@@ -202,7 +217,11 @@ export class Importer {
         return errors;
     }
 
-    private async createAssets(assetPaths: string[]): Promise<{ assets: Asset[]; errors: string[] }> {
+    /**
+     * Creates Asset entities for the given paths, using the assetMap cache to prevent the
+     * creation of duplicates.
+     */
+    private async getAssets(assetPaths: string[]): Promise<{ assets: Asset[]; errors: string[] }> {
         const assets: Asset[] = [];
         const errors: string[] = [];
         const { importAssetsDir } = this.configService.importExportOptions;
@@ -229,6 +248,56 @@ export class Importer {
         return { assets, errors };
     }
 
+    private async getFacetValueIds(
+        facets: ParsedProductVariant['facets'],
+        languageCode: LanguageCode,
+    ): Promise<string[]> {
+        const facetValueIds: string[] = [];
+
+        for (const item of facets) {
+            const facetName = item.facet;
+            const valueName = item.value;
+
+            let facetEntity: Facet;
+            const cachedFacet = this.facetMap.get(facetName);
+            if (cachedFacet) {
+                facetEntity = cachedFacet;
+            } else {
+                const existing = await this.facetService.findByCode(normalizeString(facetName), languageCode);
+                if (existing) {
+                    facetEntity = existing;
+                } else {
+                    facetEntity = await this.facetService.create({
+                        code: normalizeString(facetName, '-'),
+                        translations: [{ languageCode, name: facetName }],
+                    });
+                }
+                this.facetMap.set(facetName, facetEntity);
+            }
+
+            let facetValueEntity: FacetValue;
+            const facetValueMapKey = `${facetName}:${valueName}`;
+            const cachedFacetValue = this.facetValueMap.get(facetValueMapKey);
+            if (cachedFacetValue) {
+                facetValueEntity = cachedFacetValue;
+            } else {
+                const existing = facetEntity.values.find(v => v.name === valueName);
+                if (existing) {
+                    facetValueEntity = existing;
+                } else {
+                    facetValueEntity = await this.facetValueService.create(facetEntity, {
+                        code: normalizeString(valueName, '-'),
+                        translations: [{ languageCode, name: valueName }],
+                    });
+                }
+                this.facetValueMap.set(facetValueMapKey, facetValueEntity);
+            }
+            facetValueIds.push(facetValueEntity.id as string);
+        }
+
+        return facetValueIds;
+    }
+
     /**
      * 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.

+ 1 - 0
server/src/entity/product-variant/product-variant.graphql

@@ -33,6 +33,7 @@ input ProductVariantTranslationInput {
 
 input CreateProductVariantInput {
     translations: [ProductVariantTranslationInput!]!
+    facetValueIds: [ID!]
     sku: String!
     price: Int
     taxCategoryId: ID!

+ 13 - 0
server/src/service/services/facet.service.ts

@@ -50,6 +50,19 @@ export class FacetService {
             .then(facet => facet && translateDeep(facet, lang, ['values', ['values', 'facet']]));
     }
 
+    findByCode(facetCode: string, lang: LanguageCode): Promise<Translated<Facet> | undefined> {
+        const relations = ['values', 'values.facet'];
+        return this.connection
+            .getRepository(Facet)
+            .findOne({
+                where: {
+                    code: facetCode,
+                },
+                relations,
+            })
+            .then(facet => facet && translateDeep(facet, lang, ['values', ['values', 'facet']]));
+    }
+
     async create(input: CreateFacetInput): Promise<Translated<Facet>> {
         const facet = await this.translatableSaver.create({
             input,

+ 3 - 0
server/src/service/services/product-variant.service.ts

@@ -93,6 +93,9 @@ export class ProductVariantService {
                         .findByIds(optionIds);
                     variant.options = selectedOptions;
                 }
+                if (input.facetValueIds) {
+                    variant.facetValues = await this.facetValueService.findByIds(input.facetValueIds);
+                }
                 variant.product = product;
                 variant.taxCategory = { id: input.taxCategoryId } as any;
                 await this.assetUpdater.updateEntityAssets(variant, input);

+ 2 - 0
shared/generated-types.ts

@@ -1381,6 +1381,7 @@ export interface UpdateZoneInput {
 
 export interface CreateProductVariantInput {
     translations: ProductVariantTranslationInput[];
+    facetValueIds?: string[] | null;
     sku: string;
     price?: number | null;
     taxCategoryId: string;
@@ -5287,6 +5288,7 @@ export namespace GetProductList {
 
     export type FeaturedAsset = {
         __typename?: 'Asset';
+        id: string;
         preview: string;
     };
 }

Some files were not shown because too many files changed in this diff