Quellcode durchsuchen

feat(server): Add breadcrumbs property to Collection type

Michael Bromley vor 6 Jahren
Ursprung
Commit
5305450191

Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
schema-admin.json


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
schema-shop.json


+ 67 - 0
schema.json

@@ -6159,6 +6159,30 @@
             "isDeprecated": false,
             "deprecationReason": null
           },
+          {
+            "name": "breadcrumbs",
+            "description": null,
+            "args": [],
+            "type": {
+              "kind": "NON_NULL",
+              "name": null,
+              "ofType": {
+                "kind": "LIST",
+                "name": null,
+                "ofType": {
+                  "kind": "NON_NULL",
+                  "name": null,
+                  "ofType": {
+                    "kind": "OBJECT",
+                    "name": "CollectionBreadcrumb",
+                    "ofType": null
+                  }
+                }
+              }
+            },
+            "isDeprecated": false,
+            "deprecationReason": null
+          },
           {
             "name": "position",
             "description": null,
@@ -6362,6 +6386,49 @@
         "enumValues": null,
         "possibleTypes": null
       },
+      {
+        "kind": "OBJECT",
+        "name": "CollectionBreadcrumb",
+        "description": null,
+        "fields": [
+          {
+            "name": "id",
+            "description": null,
+            "args": [],
+            "type": {
+              "kind": "NON_NULL",
+              "name": null,
+              "ofType": {
+                "kind": "SCALAR",
+                "name": "ID",
+                "ofType": null
+              }
+            },
+            "isDeprecated": false,
+            "deprecationReason": null
+          },
+          {
+            "name": "name",
+            "description": null,
+            "args": [],
+            "type": {
+              "kind": "NON_NULL",
+              "name": null,
+              "ofType": {
+                "kind": "SCALAR",
+                "name": "String",
+                "ofType": null
+              }
+            },
+            "isDeprecated": false,
+            "deprecationReason": null
+          }
+        ],
+        "inputFields": null,
+        "interfaces": [],
+        "enumValues": null,
+        "possibleTypes": null
+      },
       {
         "kind": "OBJECT",
         "name": "ConfigurableOperation",

+ 1 - 1
server/e2e/__snapshots__/collection.e2e-spec.ts.snap

@@ -51,7 +51,7 @@ Object {
   "name": "Electronics",
   "parent": Object {
     "id": "T_1",
-    "name": "__root_category__",
+    "name": "__root_collection__",
   },
   "translations": Array [
     Object {

+ 42 - 4
server/e2e/collection.e2e-spec.ts

@@ -30,7 +30,7 @@ import {
     UpdateProduct,
     UpdateProductVariants,
 } from '../../shared/generated-types';
-import { ROOT_CATEGORY_NAME } from '../../shared/shared-constants';
+import { ROOT_COLLECTION_NAME } from '../../shared/shared-constants';
 import { facetValueCollectionFilter } from '../src/config/collection/default-collection-filters';
 
 import { TEST_SETUP_TIMEOUT_MS } from './config/test-config';
@@ -101,7 +101,7 @@ describe('Collection resolver', () => {
 
             electronicsCollection = result.createCollection;
             expect(electronicsCollection).toMatchSnapshot();
-            expect(electronicsCollection.parent.name).toBe(ROOT_CATEGORY_NAME);
+            expect(electronicsCollection.parent.name).toBe(ROOT_COLLECTION_NAME);
         });
 
         it('creates a nested category', async () => {
@@ -162,12 +162,39 @@ describe('Collection resolver', () => {
             id: computersCollection.id,
         });
         if (!result.collection) {
-            fail(`did not return the category`);
+            fail(`did not return the collection`);
             return;
         }
         expect(result.collection.id).toBe(computersCollection.id);
     });
 
+    it('breadcrumbs', async () => {
+        const result = await client.query(GET_COLLECTION_BREADCRUMBS, {
+            id: pearCollection.id,
+        });
+        if (!result.collection) {
+            fail(`did not return the collection`);
+            return;
+        }
+        expect(result.collection.breadcrumbs).toEqual([
+            { id: 'T_1', name: ROOT_COLLECTION_NAME },
+            { id: electronicsCollection.id, name: electronicsCollection.name },
+            { id: computersCollection.id, name: computersCollection.name },
+            { id: pearCollection.id, name: pearCollection.name },
+        ]);
+    });
+
+    it('breadcrumbs for root collection', async () => {
+        const result = await client.query(GET_COLLECTION_BREADCRUMBS, {
+            id: 'T_1',
+        });
+        if (!result.collection) {
+            fail(`did not return the collection`);
+            return;
+        }
+        expect(result.collection.breadcrumbs).toEqual([{ id: 'T_1', name: ROOT_COLLECTION_NAME }]);
+    });
+
     it('updateCollection', async () => {
         const result = await client.query<UpdateCollection.Mutation, UpdateCollection.Variables>(
             UPDATE_COLLECTION,
@@ -185,7 +212,7 @@ describe('Collection resolver', () => {
     });
 
     describe('moveCollection', () => {
-        it('moves a category to a new parent', async () => {
+        it('moves a collection to a new parent', async () => {
             const result = await client.query<MoveCollection.Mutation, MoveCollection.Variables>(
                 MOVE_COLLECTION,
                 {
@@ -547,3 +574,14 @@ const CREATE_COLLECTION_SELECT_VARIANTS = gql`
         }
     }
 `;
+
+const GET_COLLECTION_BREADCRUMBS = gql`
+    query GetCollectionBreadcrumbs($id: ID!) {
+        collection(id: $id) {
+            breadcrumbs {
+                id
+                name
+            }
+        }
+    }
+`;

+ 14 - 2
server/src/api/resolvers/entity/collection-entity.resolver.ts

@@ -1,16 +1,20 @@
 import { Args, Parent, ResolveProperty, Resolver } from '@nestjs/graphql';
 
-import { ProductVariantListOptions } from '../../../../../shared/generated-types';
+import { CollectionBreadcrumb, ProductVariantListOptions } from '../../../../../shared/generated-types';
 import { PaginatedList } from '../../../../../shared/shared-types';
 import { Translated } from '../../../common/types/locale-types';
 import { Collection, ProductVariant } from '../../../entity';
+import { CollectionService } from '../../../service/services/collection.service';
 import { ProductVariantService } from '../../../service/services/product-variant.service';
 import { RequestContext } from '../../common/request-context';
 import { Ctx } from '../../decorators/request-context.decorator';
 
 @Resolver('Collection')
 export class CollectionEntityResolver {
-    constructor(private productVariantService: ProductVariantService) {}
+    constructor(
+        private productVariantService: ProductVariantService,
+        private collectionService: CollectionService,
+    ) {}
 
     @ResolveProperty()
     async productVariants(
@@ -20,4 +24,12 @@ export class CollectionEntityResolver {
     ): Promise<PaginatedList<Translated<ProductVariant>>> {
         return this.productVariantService.getVariantsByCollectionId(ctx, collection.id, args.options);
     }
+
+    @ResolveProperty()
+    async breadcrumbs(
+        @Ctx() ctx: RequestContext,
+        @Parent() collection: Collection,
+    ): Promise<CollectionBreadcrumb[]> {
+        return this.collectionService.getBreadcrumbs(ctx, collection) as any;
+    }
 }

+ 6 - 0
server/src/api/schema/type/collection.type.graphql

@@ -4,6 +4,7 @@ type Collection implements Node {
     updatedAt: DateTime!
     languageCode: LanguageCode
     name: String!
+    breadcrumbs: [CollectionBreadcrumb!]!
     position: Int!
     description: String!
     featuredAsset: Asset
@@ -15,6 +16,11 @@ type Collection implements Node {
     productVariants(options: ProductVariantListOptions): ProductVariantList!
 }
 
+type CollectionBreadcrumb {
+    id: ID!
+    name: String!
+}
+
 type CollectionTranslation {
     id: ID!
     createdAt: DateTime!

+ 22 - 3
server/src/service/services/collection.service.ts

@@ -3,12 +3,14 @@ import { InjectConnection } from '@nestjs/typeorm';
 import { Connection } from 'typeorm';
 
 import {
+    CollectionBreadcrumb,
     ConfigurableOperation,
     CreateCollectionInput,
     MoveCollectionInput,
     UpdateCollectionInput,
 } from '../../../../shared/generated-types';
-import { ROOT_CATEGORY_NAME } from '../../../../shared/shared-constants';
+import { pick } from '../../../../shared/pick';
+import { ROOT_COLLECTION_NAME } from '../../../../shared/shared-constants';
 import { ID, PaginatedList } from '../../../../shared/shared-types';
 import { notNullOrUndefined } from '../../../../shared/shared-utils';
 import { RequestContext } from '../../api/common/request-context';
@@ -103,6 +105,22 @@ export class CollectionService implements OnModuleInit {
         return this.availableFilters.map(configurableDefToOperation);
     }
 
+    async getBreadcrumbs(
+        ctx: RequestContext,
+        collection: Collection,
+    ): Promise<Array<{ name: string; id: ID }>> {
+        const rootCollection = await this.getRootCollection(ctx);
+        if (idsAreEqual(collection.id, rootCollection.id)) {
+            return [pick(rootCollection, ['id', 'name'])];
+        }
+        const ancestors = await this.getAncestors(collection.id, ctx);
+        return [
+            pick(rootCollection, ['id', 'name']),
+            ...ancestors.map(pick(['id', 'name'])),
+            pick(collection, ['id', 'name']),
+        ];
+    }
+
     /**
      * Returns the descendants of a Collection as a flat array.
      */
@@ -377,19 +395,20 @@ export class CollectionService implements OnModuleInit {
             .getRepository(Collection)
             .createQueryBuilder('collection')
             .leftJoin('collection.channels', 'channel')
+            .leftJoinAndSelect('collection.translations', 'translation')
             .where('collection.isRoot = :isRoot', { isRoot: true })
             .andWhere('channel.id = :channelId', { channelId: ctx.channelId })
             .getOne();
 
         if (existingRoot) {
-            this.rootCategories[ctx.channel.code] = existingRoot;
+            this.rootCategories[ctx.channel.code] = translateDeep(existingRoot, ctx.languageCode);
             return existingRoot;
         }
 
         const rootTranslation = await this.connection.getRepository(CollectionTranslation).save(
             new CollectionTranslation({
                 languageCode: DEFAULT_LANGUAGE_CODE,
-                name: ROOT_CATEGORY_NAME,
+                name: ROOT_COLLECTION_NAME,
                 description: 'The root of the Collection tree.',
             }),
         );

+ 9 - 1
shared/generated-shop-types.ts

@@ -1,5 +1,5 @@
 // tslint:disable
-// Generated in 2019-03-08T12:05:06+01:00
+// Generated in 2019-03-20T14:52:47+01:00
 export type Maybe<T> = T | null;
 
 export interface OrderListOptions {
@@ -1378,6 +1378,8 @@ export interface Collection extends Node {
 
     name: string;
 
+    breadcrumbs: CollectionBreadcrumb[];
+
     position: number;
 
     description: string;
@@ -1399,6 +1401,12 @@ export interface Collection extends Node {
     customFields?: Maybe<Json>;
 }
 
+export interface CollectionBreadcrumb {
+    id: string;
+
+    name: string;
+}
+
 export interface CollectionTranslation {
     id: string;
 

+ 11 - 1
shared/generated-types.ts

@@ -1,5 +1,5 @@
 // tslint:disable
-// Generated in 2019-03-08T12:05:07+01:00
+// Generated in 2019-03-20T14:52:48+01:00
 export type Maybe<T> = T | null;
 
 
@@ -4804,6 +4804,8 @@ export interface Collection extends Node {
   
   name: string;
   
+  breadcrumbs: CollectionBreadcrumb[];
+  
   position: number;
   
   description: string;
@@ -4826,6 +4828,14 @@ export interface Collection extends Node {
 }
 
 
+export interface CollectionBreadcrumb {
+  
+  id: string;
+  
+  name: string;
+}
+
+
 export interface ConfigurableOperation {
   
   code: string;

+ 2 - 2
shared/pick.ts

@@ -3,8 +3,8 @@
  * Can be called with a single argument (array of props to pick), in which case it returns a partially
  * applied pick function.
  */
-export function pick<T extends object, U extends T = any>(props: Array<keyof T>): (input: U) => T;
-export function pick<T extends object, U extends T = any>(input: U, props: Array<keyof T>): T;
+export function pick<T extends object, U extends T>(props: Array<keyof T>): (input: U) => Pick<U, keyof T>;
+export function pick<T extends object, U extends T = any>(input: U, props: Array<keyof T>): Pick<U, keyof T>;
 export function pick<T extends object, U extends T = any>(
     inputOrProps: U | Array<keyof T>,
     maybeProps?: Array<keyof T>,

+ 1 - 1
shared/shared-constants.ts

@@ -12,4 +12,4 @@ export const SUPER_ADMIN_USER_IDENTIFIER = 'superadmin';
 export const SUPER_ADMIN_USER_PASSWORD = 'superadmin';
 export const CUSTOMER_ROLE_CODE = '__customer_role__';
 export const CUSTOMER_ROLE_DESCRIPTION = 'Customer';
-export const ROOT_CATEGORY_NAME = '__root_category__';
+export const ROOT_COLLECTION_NAME = '__root_collection__';

Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.