diff --git a/packages/shared/src/constants/graphql.ts b/packages/shared/src/constants/graphql.ts index 85aec18b..016023d4 100644 --- a/packages/shared/src/constants/graphql.ts +++ b/packages/shared/src/constants/graphql.ts @@ -1,75 +1,194 @@ import { VariableType, jsonToGraphQLQuery } from 'json-to-graphql-query' -import { IProductFieldFilterInput } from '../types' +import { IGraphqlQueryContent, IProductFieldFilterInput } from '../types' +import { IDocumentFieldFilterInput } from '../types/documents' export function getSearchProductsQuery( filter: IProductFieldFilterInput | IProductFieldFilterInput[] = null, withAggregations = false ): string { + const productQueryContent = getSearchProductsQueryContent( + filter, + withAggregations + ) return jsonToGraphQLQuery({ query: { __name: 'getProducts', - __variables: { - requestType: 'ProductRequestTypeEnum!', - localizedCatalog: 'String!', - currentPage: 'Int', - currentCategoryId: 'String', - pageSize: 'Int', - search: 'String', - sort: 'ProductSortInput', - }, + __variables: { ...productQueryContent.variables }, products: { - __args: { - requestType: new VariableType('requestType'), - localizedCatalog: new VariableType('localizedCatalog'), - currentPage: new VariableType('currentPage'), - currentCategoryId: new VariableType('currentCategoryId'), - pageSize: new VariableType('pageSize'), - search: new VariableType('search'), - sort: new VariableType('sort'), - filter, - }, - collection: { - __on: { - __typeName: 'Product', - id: true, - sku: true, - name: true, - score: true, - image: true, + __aliasFor: 'products', + __args: { ...productQueryContent.args }, + ...productQueryContent.fields, + }, + }, + }) +} - stock: { - status: true, - }, - price: { - price: true, - }, +function getSearchProductsQueryContent( + filter: IProductFieldFilterInput | IProductFieldFilterInput[] = null, + withAggregations = false +): IGraphqlQueryContent { + return { + variables: { + requestType: 'ProductRequestTypeEnum!', + localizedCatalog: 'String!', + currentPage: 'Int', + currentCategoryId: 'String', + pageSize: 'Int', + search: 'String', + sort: 'ProductSortInput', + }, + args: { + requestType: new VariableType('requestType'), + localizedCatalog: new VariableType('localizedCatalog'), + currentPage: new VariableType('currentPage'), + currentCategoryId: new VariableType('currentCategoryId'), + pageSize: new VariableType('pageSize'), + search: new VariableType('search'), + sort: new VariableType('sort'), + filter, + }, + fields: { + collection: { + __on: { + __typeName: 'Product', + id: true, + sku: true, + name: true, + score: true, + image: true, + + stock: { + status: true, + }, + price: { + price: true, }, }, - paginationInfo: { - lastPage: true, - itemsPerPage: true, - totalCount: true, + }, + paginationInfo: { + lastPage: true, + itemsPerPage: true, + totalCount: true, + }, + sortInfo: { + current: { + field: true, + direction: true, }, - sortInfo: { - current: { - field: true, - direction: true, + }, + ...(withAggregations && { + aggregations: { + field: true, + label: true, + type: true, + options: { + count: true, + label: true, + value: true, }, + hasMore: true, }, - ...(withAggregations && { - aggregations: { - field: true, + }), + }, + } +} + +export function getSearchCategoryQueryContent( + filter: IDocumentFieldFilterInput | IDocumentFieldFilterInput[] = null, + withAggregations = false +): IGraphqlQueryContent { + return getSearchDocumentQueryContent(filter, withAggregations, 'category') +} + +export function getSearchDocumentQueryContent( + filter: IDocumentFieldFilterInput | IDocumentFieldFilterInput[] = null, + withAggregations = false, + variablePrefix = 'document' +): IGraphqlQueryContent { + return { + variables: { + [`${variablePrefix}EntityType`]: 'String!', + [`${variablePrefix}LocalizedCatalog`]: 'String!', + [`${variablePrefix}CurrentPage`]: 'Int', + [`${variablePrefix}PageSize`]: 'Int', + [`${variablePrefix}Search`]: 'String', + [`${variablePrefix}Sort`]: 'SortInput', + }, + args: { + entityType: new VariableType(`${variablePrefix}EntityType`), + localizedCatalog: new VariableType(`${variablePrefix}LocalizedCatalog`), + currentPage: new VariableType(`${variablePrefix}CurrentPage`), + pageSize: new VariableType(`${variablePrefix}PageSize`), + search: new VariableType(`${variablePrefix}Search`), + sort: new VariableType(`${variablePrefix}Sort`), + filter, + }, + fields: { + collection: { + __on: { + __typeName: 'Document', + id: true, + score: true, + source: true, + }, + }, + paginationInfo: { + lastPage: true, + itemsPerPage: true, + totalCount: true, + }, + sortInfo: { + current: { + field: true, + direction: true, + }, + }, + ...(withAggregations && { + aggregations: { + field: true, + label: true, + type: true, + options: { + count: true, label: true, - type: true, - options: { - count: true, - label: true, - value: true, - }, - hasMore: true, + value: true, }, - }), + hasMore: true, + }, + }), + }, + } +} + +export function getAutoCompleteSearchQuery( + filter: IProductFieldFilterInput | IProductFieldFilterInput[] = null, + withAggregations = false +): string { + const productQueryContent = getSearchProductsQueryContent( + filter, + withAggregations + ) + const categoryQueryContent = getSearchCategoryQueryContent( + filter, + withAggregations + ) + return jsonToGraphQLQuery({ + query: { + __name: 'getAutoCompleteDocuments', + __variables: { + ...productQueryContent.variables, + ...categoryQueryContent.variables, + }, + products: { + __aliasFor: 'products', + __args: { ...productQueryContent.args }, + ...productQueryContent.fields, + }, + categories: { + __aliasFor: 'documents', + __args: { ...categoryQueryContent.args }, + ...categoryQueryContent.fields, }, }, }) diff --git a/packages/shared/src/services/category.test.ts b/packages/shared/src/services/category.test.ts index 76198714..b45286e9 100644 --- a/packages/shared/src/services/category.test.ts +++ b/packages/shared/src/services/category.test.ts @@ -1,7 +1,7 @@ import categories from '../mocks/static/categories.json' import { ITreeItem } from '../types' -import { flatTree } from './category' +import { flatTree, getCategoryPathLabel } from './category' describe('Category service', () => { describe('flatTree', () => { @@ -47,4 +47,29 @@ describe('Category service', () => { ]) }) }) + + describe('getCategoryPathLabel', () => { + it('should replace category path with ids by category path with names (category level 2)', () => { + const pathLabel: string = getCategoryPathLabel( + ['one', 'three'], + categories.categories + ) + expect(pathLabel).toEqual('Catégorie Trois') + }) + it('should replace category path with ids by category path with names (category level 3)', () => { + const pathLabel: string = getCategoryPathLabel( + ['one', 'three', 'five'], + categories.categories + ) + expect(pathLabel).toEqual('Catégorie Trois / Catégorie Cinq') + }) + it('should replace category path with ids by category path with names (category level 3 + "@" separator)', () => { + const pathLabel: string = getCategoryPathLabel( + ['one', 'three', 'five'], + categories.categories, + ' @ ' + ) + expect(pathLabel).toEqual('Catégorie Trois @ Catégorie Cinq') + }) + }) }) diff --git a/packages/shared/src/services/category.ts b/packages/shared/src/services/category.ts index 1a473ad7..56dae439 100644 --- a/packages/shared/src/services/category.ts +++ b/packages/shared/src/services/category.ts @@ -1,4 +1,4 @@ -import { ITreeItem } from '../types' +import { ICategory, ITreeItem } from '../types' export function flatTree(tree: ITreeItem[], flat: ITreeItem[]): void { tree.forEach((item) => { @@ -8,3 +8,29 @@ export function flatTree(tree: ITreeItem[], flat: ITreeItem[]): void { } }) } + +export function getCategoryPathLabel( + path: string[], + categories: ICategory[], + separator = ' / ' +): string { + let label = '' + if (path.length > 0) { + const category = categories.find( + (category: ICategory) => category.id === path[0] + ) + + if (category.level === 1) { + const [_parent, ...children] = path + return getCategoryPathLabel(children, category?.children ?? [], separator) + } + + path.shift() + label = + category?.name + + (category?.children?.length > 0 && path.length > 0 ? separator : '') + + getCategoryPathLabel(path, category?.children ?? [], separator) + } + + return label +} diff --git a/packages/shared/src/services/format.test.ts b/packages/shared/src/services/format.test.ts index b735be40..1f08d89d 100644 --- a/packages/shared/src/services/format.test.ts +++ b/packages/shared/src/services/format.test.ts @@ -1,4 +1,5 @@ import { + addPrefixKeyObject, firstLetterLowercase, firstLetterUppercase, formatPrice, @@ -52,6 +53,17 @@ describe('Format service', () => { }) }) + describe('addPrefixKeyObject', () => { + it('Should add the prefix "category", on all keys of the object', () => { + expect( + addPrefixKeyObject( + { localizedCatalog: 'com_fr', search: 'bag' }, + 'category' + ) + ).toEqual({ categoryLocalizedCatalog: 'com_fr', categorySearch: 'bag' }) + }) + }) + describe('formatPrice', () => { it('Should format price', () => { expect(formatPrice(100, 'USD', 'fr-FR')).toContain('100,00') diff --git a/packages/shared/src/services/format.ts b/packages/shared/src/services/format.ts index adc13874..929086af 100644 --- a/packages/shared/src/services/format.ts +++ b/packages/shared/src/services/format.ts @@ -35,6 +35,15 @@ export function isObjectEmpty(obj: Record): boolean { return Object.keys(obj).length === 0 } +export function addPrefixKeyObject(obj: object, prefix: string): object { + return Object.entries(obj).reduce((acc, [key, value]) => { + return { + ...acc, + [prefix + firstLetterUppercase(key)]: value, + } + }, {}) +} + export function concatenateValuesWithLineBreaks(global: string[]): string { return global.join('\n') } diff --git a/packages/shared/src/types/categories.ts b/packages/shared/src/types/categories.ts index 184d6f1b..e9616e12 100644 --- a/packages/shared/src/types/categories.ts +++ b/packages/shared/src/types/categories.ts @@ -1,4 +1,5 @@ import { IJsonldBase } from './jsonld' +import { IGraphqlSearchDocument } from './documents' export interface ICategory { id: string @@ -19,3 +20,7 @@ export interface ICategories extends IJsonldBase { export interface IGraphqlCategories { getCategoryTree: Partial } + +export interface IGraphqlSearchCategories { + categories: IGraphqlSearchDocument +} diff --git a/packages/shared/src/types/documents.ts b/packages/shared/src/types/documents.ts new file mode 100644 index 00000000..1128d3a9 --- /dev/null +++ b/packages/shared/src/types/documents.ts @@ -0,0 +1,103 @@ +import { SortOrder } from './categorySortingOption' +import { ISearchParameters } from './fetch' +export interface IGraphqlSearchDocumentsVariables { + entityType: string + localizedCatalog: string + currentCategoryId?: string + currentPage?: number + filter?: IDocumentFieldFilterInput[] | IDocumentFieldFilterInput + pageSize?: number + search?: string + sort?: Record +} + +export enum AggregationType { + CATEGORY = 'category', + CHECKBOX = 'checkbox', + SLIDER = 'slider', + BOOLEAN = 'boolean', +} + +export interface IGraphqlSearchDocuments { + documents: IGraphqlSearchDocument +} + +export interface IGraphqlSearchDocument { + collection: IGraphqlDocument[] + paginationInfo: IGraphqlDocumentPaginationInfo + sortInfo: IGraphqlDocumentSortInfo + aggregations?: IGraphqlDocumentAggregation[] +} + +export interface IGraphqlDocument { + id: string + source: Record + score: string +} + +export interface IGraphqlDocumentPaginationInfo { + lastPage: number + totalCount: number +} + +export interface IGraphqlDocumentSortInfo { + current: IGraphqlDocumentSortInfoCurrent[] +} + +export interface IGraphqlDocumentSortInfoCurrent { + field: string + direction: SortOrder +} + +export interface IGraphqlDocumentAggregation { + count: number + field: string + label: string + type: AggregationType + options: IGraphqlDocumentAggregationOption[] + hasMore: boolean | null +} + +export interface IGraphqlDocumentAggregationOption { + count: number + label: string + value: string +} + +export interface IFetchParams { + options: RequestInit + searchParameters: ISearchParameters +} + +export interface IDocumentBoolFilterInput { + _must?: IDocumentFieldFilterInput[] + _should?: IDocumentFieldFilterInput[] + _not?: IDocumentFieldFilterInput[] +} + +export interface IDocumentEqualFilterInput { + field?: string + eq: string + in: string[] +} + +export interface IDocumentMatchFilterInput { + field?: string + match?: string +} + +export interface IDocumentRangeFilterInput { + field?: string + gte?: string + gt?: string + lt?: string + lte?: string +} + +export interface IDocumentExistFilterInput { + field?: string +} + +export interface IDocumentFieldFilterInput { + boolFilter?: IDocumentBoolFilterInput +} diff --git a/packages/shared/src/types/graphql.ts b/packages/shared/src/types/graphql.ts index ebcd6427..614a8e54 100644 --- a/packages/shared/src/types/graphql.ts +++ b/packages/shared/src/types/graphql.ts @@ -47,3 +47,9 @@ export type IGraphqlApi = ( variables?: Record, options?: RequestInit ) => Promise + +export interface IGraphqlQueryContent { + variables?: Record + args?: Record + fields: Record +}