From 689a68f13253cf7abeb7d0136847144195c67cb5 Mon Sep 17 00:00:00 2001 From: Martin Zanoni Date: Fri, 17 Nov 2023 10:50:55 +0100 Subject: [PATCH 1/3] feat: product category updates --- .../src/builders/categoryPathBuilder.ts | 53 +++++++++++++++ packages/integrations/src/builders/index.ts | 4 +- .../src/builders/productcategories/index.ts | 2 + ...ductCategoryAdministrativeActionBuilder.ts | 44 +++++++++++++ .../productCategoryUpdateBuilder.ts | 62 +++++++++++++++++ .../builders/products/productUpdateBuilder.ts | 66 +------------------ packages/integrations/src/integrator.ts | 22 ++++++- .../updates.integration.test.ts | 57 ++++++++++++++++ 8 files changed, 244 insertions(+), 66 deletions(-) create mode 100644 packages/integrations/src/builders/categoryPathBuilder.ts create mode 100644 packages/integrations/src/builders/productcategories/index.ts create mode 100644 packages/integrations/src/builders/productcategories/productCategoryAdministrativeActionBuilder.ts create mode 100644 packages/integrations/src/builders/productcategories/productCategoryUpdateBuilder.ts create mode 100644 packages/integrations/tests/integration-tests/product-categories/updates.integration.test.ts diff --git a/packages/integrations/src/builders/categoryPathBuilder.ts b/packages/integrations/src/builders/categoryPathBuilder.ts new file mode 100644 index 0000000..0b663a0 --- /dev/null +++ b/packages/integrations/src/builders/categoryPathBuilder.ts @@ -0,0 +1,53 @@ +import { CategoryNameAndId, CategoryPath } from '@relewise/client'; + +export type ProductCategoryPath = { + path: PathNode[] +}; + +export type PathNode = { + id: string; + displayName: { + value: string; + language: string; + }[] +} + +export class CategoryPathBuilder { + private paths: CategoryPath[] = []; + + path(builder: (builder: PathBuilder) => void): this { + const b = new PathBuilder(); + builder(b); + this.paths.push({ breadcrumbPathStartingFromRoot: b.build() }); + + return this; + } + + build(): CategoryPath[] { + return this.paths; + } +} + +export class PathBuilder { + private path: PathNode[] = []; + + category(categoryIdAndName: { + id: string; + displayName: { + value: string; + language: string; + }[] + }): this { + + this.path.push(categoryIdAndName); + + return this; + } + + build(): CategoryNameAndId[] { + return this.path.map(x=> ({ + id: x.id, + displayName: { values: x.displayName.map(d => ({ text: d.value, language: { value: d.language } })) }, + })); + } +} \ No newline at end of file diff --git a/packages/integrations/src/builders/index.ts b/packages/integrations/src/builders/index.ts index 0adcaa6..f6c06b1 100644 --- a/packages/integrations/src/builders/index.ts +++ b/packages/integrations/src/builders/index.ts @@ -1 +1,3 @@ -export * from './products'; \ No newline at end of file +export * from './categoryPathBuilder'; +export * from './products'; +export * from './productcategories'; \ No newline at end of file diff --git a/packages/integrations/src/builders/productcategories/index.ts b/packages/integrations/src/builders/productcategories/index.ts new file mode 100644 index 0000000..cd39689 --- /dev/null +++ b/packages/integrations/src/builders/productcategories/index.ts @@ -0,0 +1,2 @@ +export * from './productCategoryUpdateBuilder'; +export * from './productCategoryAdministrativeActionBuilder'; \ No newline at end of file diff --git a/packages/integrations/src/builders/productcategories/productCategoryAdministrativeActionBuilder.ts b/packages/integrations/src/builders/productcategories/productCategoryAdministrativeActionBuilder.ts new file mode 100644 index 0000000..1d75c96 --- /dev/null +++ b/packages/integrations/src/builders/productcategories/productCategoryAdministrativeActionBuilder.ts @@ -0,0 +1,44 @@ +import { FilterBuilder, ProductCategoryAdministrativeAction } from '@relewise/client'; + +export class ProductCategoryAdministrativeActionBuilder { + private filterBuilder = new FilterBuilder(); + private kind: 'Disable' | 'Enable' | 'Delete'; + + language: string | null | undefined; + currency: string | null | undefined; + + constructor({ language, currency, kind, filters }: { + currency?: string | null, + language?: string | null, + kind: 'Disable' | 'Enable' | 'Delete', + filters: (filterBuilder: FilterBuilder) => void + }) { + this.language = language; + this.currency = currency; + this.kind = kind; + + filters(this.filterBuilder); + } + + filters(filters: (filterBuilder: FilterBuilder) => void): this { + filters(this.filterBuilder); + + return this; + } + + build(): ProductCategoryAdministrativeAction { + const filters = this.filterBuilder.build(); + + if (!filters || !filters.items || filters.items.length === 0) { + throw new Error('No filters was provided for the product administrative action'); + } + + return { + $type: 'Relewise.Client.DataTypes.ProductCategoryAdministrativeAction, Relewise.Client', + ...(this.language && { language: { value: this.language } }), + ...(this.currency && { currency: { value: this.currency } }), + filters: filters, + kind: this.kind, + }; + } +} \ No newline at end of file diff --git a/packages/integrations/src/builders/productcategories/productCategoryUpdateBuilder.ts b/packages/integrations/src/builders/productcategories/productCategoryUpdateBuilder.ts new file mode 100644 index 0000000..40c9ada --- /dev/null +++ b/packages/integrations/src/builders/productcategories/productCategoryUpdateBuilder.ts @@ -0,0 +1,62 @@ +import { DataValue, ProductCategoryUpdate, ProductCategory } from '@relewise/client'; +import { CategoryPathBuilder } from '../categoryPathBuilder'; + +export class ProductCategoryUpdateBuilder { + private productCategory: ProductCategory; + private kind: 'UpdateAndAppend' | 'ReplaceProvidedProperties' | 'ClearAndReplace'; + + constructor({ id, kind }: { + id: string, + kind: 'UpdateAndAppend' | 'ReplaceProvidedProperties' | 'ClearAndReplace' + }) { + this.productCategory = { + $type: 'Relewise.Client.DataTypes.ProductCategory, Relewise.Client', + id: id, + }; + this.kind = kind; + } + + displayName(values: { + value: string; + language: string; + }[]): this { + this.productCategory.displayName = { + values: values.map(x => ({ text: x.value, language: { value: x.language } })), + }; + + return this; + } + + data(data: Record): this { + this.productCategory.data = data as Record; // TODO remove dirty hack + + return this; + } + + /** + * Add multiple category paths to a product category. Start from the root to the lowest child. Example: Tools -> Outdoor -> Shovel + * @param paths + * @returns + */ + categoryPaths(builder: (b: CategoryPathBuilder) => void): this { + const b = new CategoryPathBuilder(); + builder(b); + this.productCategory.categoryPaths = b.build(); + + return this; + } + + assortments(assortments: number[]): this { + this.productCategory.assortments = assortments; + + return this; + } + + build(): ProductCategoryUpdate { + return { + $type: 'Relewise.Client.DataTypes.ProductCategoryUpdate, Relewise.Client', + category: this.productCategory, + kind: this.kind, + }; + } +} \ No newline at end of file diff --git a/packages/integrations/src/builders/products/productUpdateBuilder.ts b/packages/integrations/src/builders/products/productUpdateBuilder.ts index 8f01f43..5ff2b78 100644 --- a/packages/integrations/src/builders/products/productUpdateBuilder.ts +++ b/packages/integrations/src/builders/products/productUpdateBuilder.ts @@ -1,16 +1,5 @@ -import { Product, DataValue, ProductVariant, Brand, ProductUpdate, CategoryNameAndId, CategoryPath } from '@relewise/client'; - -export type ProductCategoryPath = { - path: PathNode[] -}; - -export type PathNode = { - id: string; - displayName: { - value: string; - language: string; - }[] -} +import { Product, DataValue, ProductVariant, Brand, ProductUpdate } from '@relewise/client'; +import { CategoryPathBuilder } from '../categoryPathBuilder'; export class ProductUpdateBuilder { private product: Product; @@ -62,7 +51,6 @@ export class ProductUpdateBuilder { * @param paths * @returns */ - categoryPaths(builder: (b: CategoryPathBuilder) => void): this { const b = new CategoryPathBuilder(); builder(b); @@ -70,16 +58,6 @@ export class ProductUpdateBuilder { return this; } - // categoryPaths(paths: ProductCategoryPath[]): this { - // this.product.categoryPaths = paths.map(p => ({ - // breadcrumbPathStartingFromRoot: p.path.map(path => ({ - // id: path.id, - // displayName: { values: path.displayName.map(x => ({ text: x.value, language: { value: x.language } })) }, - // })), - // })); - - // return this; - // } assortments(assortments: number[]): this { this.product.assortments = assortments; @@ -116,44 +94,4 @@ export class ProductUpdateBuilder { replaceExistingVariants: this.replaceExistingVariants, }; } -} - -export class CategoryPathBuilder { - private paths: CategoryPath[] = []; - - path(builder: (builder: PathBuilder) => void): this { - const b = new PathBuilder(); - builder(b); - this.paths.push({ breadcrumbPathStartingFromRoot: b.build() }); - - return this; - } - - build(): CategoryPath[] { - return this.paths; - } -} - -export class PathBuilder { - private path: PathNode[] = []; - - category(categoryIdAndName: { - id: string; - displayName: { - value: string; - language: string; - }[] - }): this { - - this.path.push(categoryIdAndName); - - return this; - } - - build(): CategoryNameAndId[] { - return this.path.map(x=> ({ - id: x.id, - displayName: { values: x.displayName.map(d => ({ text: d.value, language: { value: d.language } })) }, - })); - } } \ No newline at end of file diff --git a/packages/integrations/src/integrator.ts b/packages/integrations/src/integrator.ts index fa4244a..9133ba4 100644 --- a/packages/integrations/src/integrator.ts +++ b/packages/integrations/src/integrator.ts @@ -1,4 +1,4 @@ -import { RelewiseClient, RelewiseClientOptions, ProductUpdate, RelewiseRequestOptions, TrackProductUpdateRequest, ProductAdministrativeAction, TrackProductAdministrativeActionRequest, Trackable, SearchResponseCollection, BatchedTrackingRequest } from '@relewise/client'; +import { RelewiseClient, RelewiseClientOptions, ProductUpdate, RelewiseRequestOptions, TrackProductUpdateRequest, ProductAdministrativeAction, TrackProductAdministrativeActionRequest, Trackable, SearchResponseCollection, BatchedTrackingRequest, ProductCategoryUpdate, TrackProductCategoryUpdateRequest, TrackProductCategoryAdministrativeActionRequest, ProductCategoryAdministrativeAction } from '@relewise/client'; export class Integrator extends RelewiseClient { @@ -28,6 +28,26 @@ export class Integrator extends RelewiseClient { options); } + public async updateProductCategory(request: ProductCategoryUpdate, options?: RelewiseRequestOptions): Promise { + return this.request( + 'TrackProductCategoryUpdateRequest', + { + $type: 'Relewise.Client.Requests.Tracking.TrackProductCategoryUpdateRequest, Relewise.Client', + productCategoryUpdate: request, + }, + options); + } + + public async executeProductCategoryAdministrativeAction(request: ProductCategoryAdministrativeAction, options?: RelewiseRequestOptions): Promise { + return this.request( + 'TrackProductCategoryAdministrativeActionRequest', + { + $type: 'Relewise.Client.Requests.Tracking.TrackProductCategoryAdministrativeActionRequest, Relewise.Client', + administrativeAction: request, + }, + options); + } + public async batch(trackable: Trackable[], options?: RelewiseRequestOptions): Promise { if (!trackable) { throw new Error('No trackable items was provided'); diff --git a/packages/integrations/tests/integration-tests/product-categories/updates.integration.test.ts b/packages/integrations/tests/integration-tests/product-categories/updates.integration.test.ts new file mode 100644 index 0000000..cfede7b --- /dev/null +++ b/packages/integrations/tests/integration-tests/product-categories/updates.integration.test.ts @@ -0,0 +1,57 @@ +import { test } from '@jest/globals'; +import { Integrator, ProductCategoryAdministrativeActionBuilder, ProductCategoryUpdateBuilder } from '../../../src'; +import { DataValueFactory } from '@relewise/client'; +const { npm_config_API_KEY: API_KEY, npm_config_DATASET_ID: DATASET_ID, npm_config_SERVER_URL: SERVER_URL } = process.env; + +const integrator = new Integrator(DATASET_ID!, API_KEY!, { serverUrl: SERVER_URL }); + +const unixTimeStamp: number = Date.now(); + +test('Create Product Category', async() => { + const category = new ProductCategoryUpdateBuilder({ + id: '1234', + kind: 'ReplaceProvidedProperties', + }) + .displayName([ + { language: 'da', value: 'Skovle' }, + ]) + .data({ + 'UnixTimestamp': DataValueFactory.number(unixTimeStamp), + 'Description': DataValueFactory.string('Misc. skovle'), + 'Tags': DataValueFactory.stringCollection(['outdoor', 'quality', 'good-deal']), + 'InStock': DataValueFactory.boolean(true), + 'Removed': null, + 'Complex': DataValueFactory.object({ + 'nestedDataKey': DataValueFactory.string('Key'), + }), + }) + .assortments([1, 2, 3]) + .categoryPaths(b => b + .path(p => p + .category({ + id: '1', + displayName: [{ language: 'da', value: 'Værktøj' }], + }) + .category({ + id: '2', + displayName: [{ language: 'da', value: 'Udendørs' }], + }) + .category({ + id: '3', + displayName: [{ language: 'da', value: 'Skovle' }], + }))); + + await integrator.updateProductCategory(category.build()); + + const enable = new ProductCategoryAdministrativeActionBuilder({ + filters: (f) => f.addProductCategoryDataFilter('UnixTimeStamp', c => c.addEqualsCondition(DataValueFactory.number(unixTimeStamp))), + kind: 'Enable', + }); + await integrator.executeProductCategoryAdministrativeAction(enable.build()); + + const disable = new ProductCategoryAdministrativeActionBuilder({ + filters: (f) => f.addProductCategoryDataFilter('UnixTimeStamp', c => c.addEqualsCondition(DataValueFactory.number(unixTimeStamp), /* negated: */ true)), + kind: 'Disable', + }); + await integrator.executeProductCategoryAdministrativeAction(disable.build()); +}); \ No newline at end of file From 70f9d265b205b53f02b8dac4cd0ef466012ed064 Mon Sep 17 00:00:00 2001 From: Martin Zanoni Date: Fri, 17 Nov 2023 10:51:52 +0100 Subject: [PATCH 2/3] self review --- .../productCategoryAdministrativeActionBuilder.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/integrations/src/builders/productcategories/productCategoryAdministrativeActionBuilder.ts b/packages/integrations/src/builders/productcategories/productCategoryAdministrativeActionBuilder.ts index 1d75c96..65ed54a 100644 --- a/packages/integrations/src/builders/productcategories/productCategoryAdministrativeActionBuilder.ts +++ b/packages/integrations/src/builders/productcategories/productCategoryAdministrativeActionBuilder.ts @@ -30,7 +30,7 @@ export class ProductCategoryAdministrativeActionBuilder { const filters = this.filterBuilder.build(); if (!filters || !filters.items || filters.items.length === 0) { - throw new Error('No filters was provided for the product administrative action'); + throw new Error('No filters was provided for the product category administrative action'); } return { From 5eb38335b3cf14857af462c7963374a2fca5bc25 Mon Sep 17 00:00:00 2001 From: Martin Zanoni Date: Fri, 17 Nov 2023 13:10:06 +0100 Subject: [PATCH 3/3] review fixes --- .../productCategoryAdministrativeActionBuilder.ts | 2 +- .../src/builders/products/productAdministrativeActionBuilder.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/integrations/src/builders/productcategories/productCategoryAdministrativeActionBuilder.ts b/packages/integrations/src/builders/productcategories/productCategoryAdministrativeActionBuilder.ts index 65ed54a..003da97 100644 --- a/packages/integrations/src/builders/productcategories/productCategoryAdministrativeActionBuilder.ts +++ b/packages/integrations/src/builders/productcategories/productCategoryAdministrativeActionBuilder.ts @@ -30,7 +30,7 @@ export class ProductCategoryAdministrativeActionBuilder { const filters = this.filterBuilder.build(); if (!filters || !filters.items || filters.items.length === 0) { - throw new Error('No filters was provided for the product category administrative action'); + throw new Error('No filters were provided for the product category administrative action'); } return { diff --git a/packages/integrations/src/builders/products/productAdministrativeActionBuilder.ts b/packages/integrations/src/builders/products/productAdministrativeActionBuilder.ts index cd34732..6909149 100644 --- a/packages/integrations/src/builders/products/productAdministrativeActionBuilder.ts +++ b/packages/integrations/src/builders/products/productAdministrativeActionBuilder.ts @@ -32,7 +32,7 @@ export class ProductAdministrativeActionBuilder { const filters = this.filterBuilder.build(); if (!filters || !filters.items || filters.items.length === 0) { - throw new Error('No filters was provided for the product administrative action'); + throw new Error('No filters were provided for the product administrative action'); } return {