From f81e91b23454534d92a1655c7ab40ca71cf00236 Mon Sep 17 00:00:00 2001 From: Martin Zanoni Date: Fri, 17 Nov 2023 13:14:35 +0100 Subject: [PATCH] Feat: Content and Content categories updates --- .../contentAdministrativeActionBuilder.ts | 44 +++++++++++++ .../builders/content/contentUpdateBuilder.ts | 60 ++++++++++++++++++ .../src/builders/content/index.ts | 2 + ...tentCategoryAdministrativeActionBuilder.ts | 44 +++++++++++++ .../contentCategoryUpdateBuilder.ts | 62 +++++++++++++++++++ .../src/builders/contentcategories/index.ts | 2 + packages/integrations/src/builders/index.ts | 4 +- packages/integrations/src/integrator.ts | 42 ++++++++++++- .../updates.integration.test.ts | 57 +++++++++++++++++ .../content/updates.integration.test.ts | 62 +++++++++++++++++++ 10 files changed, 377 insertions(+), 2 deletions(-) create mode 100644 packages/integrations/src/builders/content/contentAdministrativeActionBuilder.ts create mode 100644 packages/integrations/src/builders/content/contentUpdateBuilder.ts create mode 100644 packages/integrations/src/builders/content/index.ts create mode 100644 packages/integrations/src/builders/contentcategories/contentCategoryAdministrativeActionBuilder.ts create mode 100644 packages/integrations/src/builders/contentcategories/contentCategoryUpdateBuilder.ts create mode 100644 packages/integrations/src/builders/contentcategories/index.ts create mode 100644 packages/integrations/tests/integration-tests/content-categories/updates.integration.test.ts create mode 100644 packages/integrations/tests/integration-tests/content/updates.integration.test.ts diff --git a/packages/integrations/src/builders/content/contentAdministrativeActionBuilder.ts b/packages/integrations/src/builders/content/contentAdministrativeActionBuilder.ts new file mode 100644 index 0000000..f2bf450 --- /dev/null +++ b/packages/integrations/src/builders/content/contentAdministrativeActionBuilder.ts @@ -0,0 +1,44 @@ +import { ContentAdministrativeAction, FilterBuilder } from '@relewise/client'; + +export class ContentAdministrativeActionBuilder { + 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(): ContentAdministrativeAction { + const filters = this.filterBuilder.build(); + + if (!filters || !filters.items || filters.items.length === 0) { + throw new Error('No filters were provided for the content administrative action'); + } + + return { + $type: 'Relewise.Client.DataTypes.ContentAdministrativeAction, 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/content/contentUpdateBuilder.ts b/packages/integrations/src/builders/content/contentUpdateBuilder.ts new file mode 100644 index 0000000..0f6efe5 --- /dev/null +++ b/packages/integrations/src/builders/content/contentUpdateBuilder.ts @@ -0,0 +1,60 @@ +import { DataValue,Content, ContentUpdate } from '@relewise/client'; +import { CategoryPathBuilder } from '../categoryPathBuilder'; + +export class ContentUpdateBuilder { + private content: Content; + private updateKind: 'UpdateAndAppend' | 'ReplaceProvidedProperties' | 'ClearAndReplace'; + + constructor({ id, updateKind }: { + id: string, + updateKind: 'UpdateAndAppend' | 'ReplaceProvidedProperties' | 'ClearAndReplace', + }) { + this.content = { id: id }; + this.updateKind = updateKind; + } + + displayName(values: { + value: string; + language: string; + }[]): this { + this.content.displayName = { + values: values.map(x => ({ text: x.value, language: { value: x.language } })), + }; + + return this; + } + + data(data: Record): this { + this.content.data = data as Record; // TODO remove dirty hack + + return this; + } + + /** + * Add multiple category paths to a content. 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.content.categoryPaths = b.build(); + + return this; + } + + assortments(assortments: number[]): this { + this.content.assortments = assortments; + + return this; + } + + + build(): ContentUpdate { + return { + $type: 'Relewise.Client.DataTypes.ContentUpdate, Relewise.Client', + content: this.content, + kind: this.updateKind, + }; + } +} \ No newline at end of file diff --git a/packages/integrations/src/builders/content/index.ts b/packages/integrations/src/builders/content/index.ts new file mode 100644 index 0000000..479ec3d --- /dev/null +++ b/packages/integrations/src/builders/content/index.ts @@ -0,0 +1,2 @@ +export * from './contentUpdateBuilder'; +export * from './contentAdministrativeActionBuilder'; \ No newline at end of file diff --git a/packages/integrations/src/builders/contentcategories/contentCategoryAdministrativeActionBuilder.ts b/packages/integrations/src/builders/contentcategories/contentCategoryAdministrativeActionBuilder.ts new file mode 100644 index 0000000..76a2b3a --- /dev/null +++ b/packages/integrations/src/builders/contentcategories/contentCategoryAdministrativeActionBuilder.ts @@ -0,0 +1,44 @@ +import { ContentCategoryAdministrativeAction, FilterBuilder } from '@relewise/client'; + +export class ContentCategoryAdministrativeActionBuilder { + 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(): ContentCategoryAdministrativeAction { + const filters = this.filterBuilder.build(); + + if (!filters || !filters.items || filters.items.length === 0) { + throw new Error('No filters were provided for the product category administrative action'); + } + + return { + $type: 'Relewise.Client.DataTypes.ContentCategoryAdministrativeAction, 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/contentcategories/contentCategoryUpdateBuilder.ts b/packages/integrations/src/builders/contentcategories/contentCategoryUpdateBuilder.ts new file mode 100644 index 0000000..2e0f28d --- /dev/null +++ b/packages/integrations/src/builders/contentcategories/contentCategoryUpdateBuilder.ts @@ -0,0 +1,62 @@ +import { DataValue, ContentCategoryUpdate, ContentCategory } from '@relewise/client'; +import { CategoryPathBuilder } from '../categoryPathBuilder'; + +export class ContentCategoryUpdateBuilder { + private contentCategory: ContentCategory; + private kind: 'UpdateAndAppend' | 'ReplaceProvidedProperties' | 'ClearAndReplace'; + + constructor({ id, kind }: { + id: string, + kind: 'UpdateAndAppend' | 'ReplaceProvidedProperties' | 'ClearAndReplace' + }) { + this.contentCategory = { + $type: 'Relewise.Client.DataTypes.ContentCategory, Relewise.Client', + id: id, + }; + this.kind = kind; + } + + displayName(values: { + value: string; + language: string; + }[]): this { + this.contentCategory.displayName = { + values: values.map(x => ({ text: x.value, language: { value: x.language } })), + }; + + return this; + } + + data(data: Record): this { + this.contentCategory.data = data as Record; // TODO remove dirty hack + + return this; + } + + /** + * Add multiple category paths to a content 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.contentCategory.categoryPaths = b.build(); + + return this; + } + + assortments(assortments: number[]): this { + this.contentCategory.assortments = assortments; + + return this; + } + + build(): ContentCategoryUpdate { + return { + $type: 'Relewise.Client.DataTypes.ContentCategoryUpdate, Relewise.Client', + category: this.contentCategory, + kind: this.kind, + }; + } +} \ No newline at end of file diff --git a/packages/integrations/src/builders/contentcategories/index.ts b/packages/integrations/src/builders/contentcategories/index.ts new file mode 100644 index 0000000..ec19d2e --- /dev/null +++ b/packages/integrations/src/builders/contentcategories/index.ts @@ -0,0 +1,2 @@ +export * from './contentCategoryUpdateBuilder'; +export * from './contentCategoryAdministrativeActionBuilder'; \ No newline at end of file diff --git a/packages/integrations/src/builders/index.ts b/packages/integrations/src/builders/index.ts index f6c06b1..1d9d19d 100644 --- a/packages/integrations/src/builders/index.ts +++ b/packages/integrations/src/builders/index.ts @@ -1,3 +1,5 @@ export * from './categoryPathBuilder'; export * from './products'; -export * from './productcategories'; \ No newline at end of file +export * from './productcategories'; +export * from './contentcategories'; +export * from './content'; \ No newline at end of file diff --git a/packages/integrations/src/integrator.ts b/packages/integrations/src/integrator.ts index 9133ba4..9e84980 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, ProductCategoryUpdate, TrackProductCategoryUpdateRequest, TrackProductCategoryAdministrativeActionRequest, ProductCategoryAdministrativeAction } from '@relewise/client'; +import { RelewiseClient, RelewiseClientOptions, ProductUpdate, RelewiseRequestOptions, TrackProductUpdateRequest, ProductAdministrativeAction, TrackProductAdministrativeActionRequest, Trackable, SearchResponseCollection, BatchedTrackingRequest, ProductCategoryUpdate, TrackProductCategoryUpdateRequest, TrackProductCategoryAdministrativeActionRequest, ProductCategoryAdministrativeAction, ContentCategoryAdministrativeAction, ContentCategoryUpdate, TrackContentCategoryAdministrativeActionRequest, TrackContentCategoryUpdateRequest, ContentUpdate, TrackContentUpdateRequest, ContentAdministrativeAction, TrackContentAdministrativeActionRequest } from '@relewise/client'; export class Integrator extends RelewiseClient { @@ -48,6 +48,46 @@ export class Integrator extends RelewiseClient { options); } + public async updateContentCategory(request: ContentCategoryUpdate, options?: RelewiseRequestOptions): Promise { + return this.request( + 'TrackContentCategoryUpdateRequest', + { + $type: 'Relewise.Client.Requests.Tracking.TrackContentCategoryUpdateRequest, Relewise.Client', + contentCategoryUpdate: request, + }, + options); + } + + public async executeContentCategoryAdministrativeAction(request: ContentCategoryAdministrativeAction, options?: RelewiseRequestOptions): Promise { + return this.request( + 'TrackContentCategoryAdministrativeActionRequest', + { + $type: 'Relewise.Client.Requests.Tracking.TrackContentCategoryAdministrativeActionRequest, Relewise.Client', + administrativeAction: request, + }, + options); + } + + public async updateContent(request: ContentUpdate, options?: RelewiseRequestOptions): Promise { + return this.request( + 'TrackContentUpdateRequest', + { + $type: 'Relewise.Client.Requests.Tracking.TrackContentUpdateRequest, Relewise.Client', + contentUpdate: request, + }, + options); + } + + public async executeContentAdministrativeAction(request: ContentAdministrativeAction, options?: RelewiseRequestOptions): Promise { + return this.request( + 'TrackContentAdministrativeActionRequest', + { + $type: 'Relewise.Client.Requests.Tracking.TrackContentAdministrativeActionRequest, 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/content-categories/updates.integration.test.ts b/packages/integrations/tests/integration-tests/content-categories/updates.integration.test.ts new file mode 100644 index 0000000..65dbd9d --- /dev/null +++ b/packages/integrations/tests/integration-tests/content-categories/updates.integration.test.ts @@ -0,0 +1,57 @@ +import { test } from '@jest/globals'; +import { ContentCategoryUpdateBuilder, Integrator, ContentCategoryAdministrativeActionBuilder } 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 Content Category', async() => { + const category = new ContentCategoryUpdateBuilder({ + 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.updateContentCategory(category.build()); + + const enable = new ContentCategoryAdministrativeActionBuilder({ + filters: (f) => f.addContentCategoryDataFilter('UnixTimeStamp', c => c.addEqualsCondition(DataValueFactory.number(unixTimeStamp))), + kind: 'Enable', + }); + await integrator.executeContentCategoryAdministrativeAction(enable.build()); + + const disable = new ContentCategoryAdministrativeActionBuilder({ + filters: (f) => f.addContentCategoryDataFilter('UnixTimeStamp', c => c.addEqualsCondition(DataValueFactory.number(unixTimeStamp), /* negated: */ true)), + kind: 'Disable', + }); + await integrator.executeContentCategoryAdministrativeAction(disable.build()); +}); \ No newline at end of file diff --git a/packages/integrations/tests/integration-tests/content/updates.integration.test.ts b/packages/integrations/tests/integration-tests/content/updates.integration.test.ts new file mode 100644 index 0000000..2fb5e55 --- /dev/null +++ b/packages/integrations/tests/integration-tests/content/updates.integration.test.ts @@ -0,0 +1,62 @@ +import { test } from '@jest/globals'; +import { ContentAdministrativeActionBuilder, ContentUpdateBuilder, Integrator } 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 Content', async() => { + const Content = new ContentUpdateBuilder({ + id: '1234', + updateKind: 'ReplaceProvidedProperties', + }) + .displayName([ + { language: 'da', value: 'Toaster' }, + ]) + .data({ + 'UnixTimestamp': DataValueFactory.number(unixTimeStamp), + 'Description': DataValueFactory.string('Really nice Content'), + 'Tags': DataValueFactory.stringCollection(['fall collection', 'blue', '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' }], + })) + .path(p => p + .category({ + id: '4', + displayName: [{ language: 'da', value: 'Tilbud' }], + }))); + + await integrator.updateContent(Content.build()); + + const enable = new ContentAdministrativeActionBuilder({ + filters: (f) => f.addContentDataFilter('UnixTimeStamp', c => c.addEqualsCondition(DataValueFactory.number(unixTimeStamp))), + kind: 'Enable', + }); + await integrator.executeContentAdministrativeAction(enable.build()); + + const disable = new ContentAdministrativeActionBuilder({ + filters: (f) => f.addContentDataFilter('UnixTimeStamp', c => c.addEqualsCondition(DataValueFactory.number(unixTimeStamp), /* negated: */ true)), + kind: 'Disable', + }); + await integrator.executeContentAdministrativeAction(disable.build()); +}); \ No newline at end of file