Browse Source

feat(server): Populate Collections as part of initial data

Michael Bromley 6 years ago
parent
commit
30cd331e22

+ 10 - 0
server/cli/populate.ts

@@ -14,6 +14,7 @@ export async function populate() {
         const initialData = require('./assets/initial-data.json');
         await populateInitialData(app, initialData);
         await populateProducts(app, initialData);
+        await populateCollections(app, initialData);
         logColored('\nDone!');
         await app.close();
         process.exit(0);
@@ -96,6 +97,15 @@ async function populateInitialData(app: INestApplication, initialData: any) {
     }
 }
 
+async function populateCollections(app: INestApplication, initialData: any) {
+    const populator = app.get(Populator);
+    try {
+        await populator.populateCollections(initialData);
+    } catch (err) {
+        console.error(err.message);
+    }
+}
+
 async function populateProducts(app: INestApplication, initialData: any) {
     // copy the images to the import folder
     const images = path.join(__dirname, 'assets', 'images');

+ 1 - 0
server/e2e/fixtures/e2e-initial-data.ts

@@ -19,4 +19,5 @@ export const initialData: InitialData = {
         { name: 'United Kingdom', code: 'GB', zone: 'Europe' },
         { name: 'United States of America', code: 'US', zone: 'Americas' },
     ],
+    collections: [],
 };

+ 9 - 0
server/mock-data/data-sources/initial-data.ts

@@ -10,6 +10,15 @@ export const initialData: InitialData = {
         { name: 'Zero Tax', percentage: 0 },
     ],
     shippingMethods: [{ name: 'Standard Shipping', price: 500 }, { name: 'Express Shipping', price: 1000 }],
+    collections: [
+        { name: 'Electronics', facetNames: ['electronics'] },
+        { name: 'Computers', facetNames: ['computers'], parentName: 'Electronics' },
+        { name: 'Camera & Photo', facetNames: ['photo'], parentName: 'Electronics' },
+        { name: 'Home & Garden', facetNames: ['home & garden'] },
+        { name: 'Furniture', facetNames: ['furniture'], parentName: 'Home & Garden' },
+        { name: 'Plants', facetNames: ['plants'], parentName: 'Home & Garden' },
+        { name: 'Sports Equipment', facetNames: ['sports equipment'] },
+    ],
     countries: [
         { name: 'Afghanistan', code: 'AF', zone: 'Asia' },
         { name: 'Åland Islands', code: 'AX', zone: 'Europe' },

+ 2 - 7
server/mock-data/mock-data.service.ts

@@ -102,15 +102,10 @@ export class MockDataService {
                     customerId: customer.id,
                 };
 
-                await this.client.query(query2, variables2).then(
-                    data => {
-                        this.log(`Created Customer ${i + 1}:`, data);
-                        return data as Customer;
-                    },
-                    err => this.log(err),
-                );
+                await this.client.query(query2, variables2).catch(err => this.log(err));
             }
         }
+        this.log(`Created ${count} Customers`);
     }
 
     private log(...args: any[]) {

+ 23 - 2
server/mock-data/populate.ts

@@ -38,8 +38,9 @@ export async function populate(
     await clearAllTables(config.dbConnectionOptions, logging);
     const app = await bootstrapFn(config);
 
-    await populateInitialData(app, options.initialDataPath);
+    await populateInitialData(app, options.initialDataPath, logging);
     await populateProducts(app, options.productsCsvPath, logging);
+    await populateCollections(app, options.initialDataPath, logging);
 
     const defaultChannelToken = await getDefaultChannelToken(logging);
     const client = new SimpleGraphQLClient(`http://localhost:${config.port}/${config.adminApiPath}`);
@@ -52,12 +53,32 @@ export async function populate(
     return app;
 }
 
-async function populateInitialData(app: INestApplication, initialDataPath: string) {
+async function populateInitialData(app: INestApplication, initialDataPath: string, logging: boolean) {
     const { Populator } = await import('../src/data-import/providers/populator/populator');
     const { initialData } = await import(initialDataPath);
     const populator = app.get(Populator);
     try {
         await populator.populateInitialData(initialData);
+        if (logging) {
+            console.log(`\nPopulated initial data`);
+        }
+    } catch (err) {
+        console.error(err.message);
+    }
+}
+
+async function populateCollections(app: INestApplication, initialDataPath: string, logging: boolean) {
+    const { Populator } = await import('../src/data-import/providers/populator/populator');
+    const { initialData } = await import(initialDataPath);
+    if (!initialData.collections.length) {
+        return;
+    }
+    const populator = app.get(Populator);
+    try {
+        await populator.populateCollections(initialData);
+        if (logging) {
+            console.log(`\nCreated ${initialData.collections.length} Collections`);
+        }
     } catch (err) {
         console.error(err.message);
     }

+ 62 - 7
server/src/data-import/providers/populator/populator.ts

@@ -2,11 +2,13 @@ import { Injectable } from '@nestjs/common';
 
 import { ConfigArgType, LanguageCode } from '../../../../../shared/generated-types';
 import { normalizeString } from '../../../../../shared/normalize-string';
+import { notNullOrUndefined } from '../../../../../shared/shared-utils';
 import { RequestContext } from '../../../api/common/request-context';
 import { defaultShippingCalculator, defaultShippingEligibilityChecker } from '../../../config';
-import { TaxCategory } from '../../../entity';
+import { facetValueCollectionFilter } from '../../../config/collection/default-collection-filters';
+import { Collection, TaxCategory } from '../../../entity';
 import { Zone } from '../../../entity/zone/zone.entity';
-import { ShippingMethodService } from '../../../service';
+import { CollectionService, FacetValueService, ShippingMethodService } from '../../../service';
 import { ChannelService } from '../../../service/services/channel.service';
 import { CountryService } from '../../../service/services/country.service';
 import { TaxCategoryService } from '../../../service/services/tax-category.service';
@@ -27,6 +29,7 @@ export interface InitialData {
     countries: CountryData[];
     taxRates: Array<{ name: string; percentage: number }>;
     shippingMethods: Array<{ name: string; price: number }>;
+    collections: Array<{ name: string; facetNames: string[]; parentName?: string }>;
 }
 
 /**
@@ -41,9 +44,65 @@ export class Populator {
         private taxRateService: TaxRateService,
         private taxCategoryService: TaxCategoryService,
         private shippingMethodService: ShippingMethodService,
+        private collectionService: CollectionService,
+        private facetValueService: FacetValueService,
     ) {}
 
+    /**
+     * Should be run *before* populating the products, so that there are TaxRates by which
+     * product prices can be set.
+     */
     async populateInitialData(data: InitialData) {
+        const { channel, ctx } = await this.createRequestContext(data);
+
+        const zoneMap = await this.populateCountries(ctx, data.countries);
+        await this.populateTaxRates(ctx, data.taxRates, zoneMap);
+        await this.populateShippingMethods(ctx, data.shippingMethods);
+        await this.setChannelDefaults(zoneMap, data, channel);
+    }
+
+    /**
+     * Should be run *after* the products have been populated, otherwise the expected FacetValues will not
+     * yet exist.
+     */
+    async populateCollections(data: InitialData) {
+        const { ctx } = await this.createRequestContext(data);
+
+        const allFacetValues = await this.facetValueService.findAll(ctx.languageCode);
+        const collectionMap = new Map<string, Collection>();
+        for (const collectionDef of data.collections) {
+            const facetValueIds = collectionDef.facetNames
+                .map(name => allFacetValues.find(fv => fv.name === name))
+                .filter(notNullOrUndefined)
+                .map(fv => fv.id);
+            const parent = collectionDef.parentName && collectionMap.get(collectionDef.parentName);
+            const parentId = parent ? parent.id.toString() : undefined;
+            const collection = await this.collectionService.create(ctx, {
+                translations: [
+                    {
+                        languageCode: ctx.languageCode,
+                        name: collectionDef.name,
+                    },
+                ],
+                parentId,
+                filters: [
+                    {
+                        code: facetValueCollectionFilter.code,
+                        arguments: [
+                            {
+                                name: 'facetValueIds',
+                                type: ConfigArgType.FACET_VALUE_IDS,
+                                value: JSON.stringify(facetValueIds),
+                            },
+                        ],
+                    },
+                ],
+            });
+            collectionMap.set(collectionDef.name, collection);
+        }
+    }
+
+    private async createRequestContext(data: InitialData) {
         const channel = await this.channelService.getDefaultChannel();
         const ctx = new RequestContext({
             isAuthorized: true,
@@ -51,11 +110,7 @@ export class Populator {
             channel,
             languageCode: data.defaultLanguage,
         });
-
-        const zoneMap = await this.populateCountries(ctx, data.countries);
-        await this.populateTaxRates(ctx, data.taxRates, zoneMap);
-        await this.populateShippingMethods(ctx, data.shippingMethods);
-        await this.setChannelDefaults(zoneMap, data, channel);
+        return { channel, ctx };
     }
 
     private async setChannelDefaults(zoneMap, data: InitialData, channel) {

+ 0 - 19
server/src/service/services/facet-value.service.ts

@@ -61,25 +61,6 @@ export class FacetValueService {
         }
     }
 
-    async findByCategoryIds(ctx: RequestContext, ids: ID[]): Promise<Array<Translated<FacetValue>>> {
-        if (ids.length === 0) {
-            return [];
-        }
-        const facetValues = await this.connection
-            .getRepository(FacetValue)
-            .createQueryBuilder('facetValue')
-            .leftJoinAndSelect(
-                'collection_facet_values_facet_value',
-                'joinTable',
-                'joinTable.facetValueId = facetValue.id',
-            )
-            .where('joinTable.collectionId IN (:...ids)', { ids })
-            .getMany();
-        return this.findByIds(facetValues.map(v => v.id)).then(values =>
-            values.map(value => translateDeep(value, ctx.languageCode)),
-        );
-    }
-
     async create(
         facet: Facet,
         input: CreateFacetValueInput | CreateFacetValueWithFacetInput,