From 10e761044d64fb204472a7629415746d1d3bc693 Mon Sep 17 00:00:00 2001 From: Darren Belding Date: Tue, 5 Apr 2022 12:43:36 +0100 Subject: [PATCH 1/4] Add translations support to start and publish commands --- src/cli/run/validate.ts | 2 +- src/server/index.ts | 9 ++++++++ src/services/api/widget.ts | 1 + .../translationLoader.test.ts | 6 ++--- .../translationLoader/translationLoader.ts | 23 ++++++++++++------- .../validateTranslation.ts} | 8 +++---- .../widgetRenderer/widgetRenderer.test.ts | 7 ++++++ src/services/widgetRenderer/widgetRenderer.ts | 7 ++++++ src/services/widgetTemplate/publish.ts | 8 +++++++ 9 files changed, 54 insertions(+), 17 deletions(-) rename src/services/translation/{validate.ts => translationValidator/validateTranslation.ts} (62%) diff --git a/src/cli/run/validate.ts b/src/cli/run/validate.ts index 1f408c4..c2a7b6e 100644 --- a/src/cli/run/validate.ts +++ b/src/cli/run/validate.ts @@ -6,7 +6,7 @@ import { Command } from 'commander'; import validateSchema from '../../services/schema/schemaValidator/validateSchema'; import validateQueryParamsBuilder from '../../services/query/queryParamsBuilderValidator/validateQueryParamsBuilder'; -import validateTranslation from '../../services/translation/validate'; +import validateTranslation from '../../services/translation/translationValidator/validateTranslation'; const helperText = ` Usage: diff --git a/src/server/index.ts b/src/server/index.ts index fcd385d..80a1c67 100755 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -16,6 +16,7 @@ import generateQueryParams from '../services/query/generateQueryParams'; import validateQueryParamsBuilder from '../services/query/queryParamsBuilderValidator/validateQueryParamsBuilder'; import renderWidget from '../services/widgetRenderer/widgetRenderer'; import generateConfig from '../services/widgetConfig/generateConfig'; +import validateTranslation from '../services/translation/translationValidator/validateTranslation'; const BUILDER_ADDRESS = `${host}:${port}`; @@ -62,6 +63,14 @@ function setupFileWatcher({ directory, sockets, options }: Watcher) { break; + case WidgetFileType.TRANSLATION: + validateTranslation(directory); + + liveReload({ + directory, sockets, fileEvent, filePath, options, + }); + break; + case WidgetFileType.META: // We are not currently handling this file type break; diff --git a/src/services/api/widget.ts b/src/services/api/widget.ts index e6d1933..a1bd7b5 100644 --- a/src/services/api/widget.ts +++ b/src/services/api/widget.ts @@ -22,6 +22,7 @@ export interface WidgetPreviewRenderRequest { storefront_api_query: string; storefront_api_query_params: object; channel_id: number; + schema_translations?: string; } export function getWidget(data: WidgetPreviewRenderRequest): Promise { diff --git a/src/services/translation/translationLoader/translationLoader.test.ts b/src/services/translation/translationLoader/translationLoader.test.ts index 3de2f2c..777cf9e 100644 --- a/src/services/translation/translationLoader/translationLoader.test.ts +++ b/src/services/translation/translationLoader/translationLoader.test.ts @@ -1,8 +1,6 @@ import fs from 'fs'; -import { messages } from '../../../messages'; - -import translationLoader, { handleSchemaLoader } from './translationLoader'; +import translationLoader, { handleSchemaLoader, translationDefaultPayload } from './translationLoader'; const schemaData = fs.readFileSync('src/services/__fixtures__/schema_translations.json', 'utf8').toString(); @@ -33,7 +31,7 @@ describe('Schema Loader', () => { it('should return with no data', () => { const result = translationLoader('dummyPath'); - expect(result).rejects.toEqual(messages.invalidTranslationSchema()); + expect(result).resolves.toEqual(translationDefaultPayload); }); }); }); diff --git a/src/services/translation/translationLoader/translationLoader.ts b/src/services/translation/translationLoader/translationLoader.ts index ffee814..f0fc03d 100644 --- a/src/services/translation/translationLoader/translationLoader.ts +++ b/src/services/translation/translationLoader/translationLoader.ts @@ -3,17 +3,20 @@ import * as fs from 'fs'; import WidgetFileType, { FileLoaderResponse } from '../../../types'; import { messages } from '../../../messages'; -export function handleSchemaLoader(error: Error | null, schemaData: string): FileLoaderResponse { - const payload = { - type: WidgetFileType.TRANSLATION, - data: '', - }; +export const translationDefaultPayload = { + type: WidgetFileType.TRANSLATION, + data: '{}', +}; +export function handleSchemaLoader(error: Error | null, schemaData: string): FileLoaderResponse { if (schemaData && !error) { - payload.data = schemaData; + return { + ...translationDefaultPayload, + data: schemaData, + } } - return payload; + return translationDefaultPayload; } export default function translationLoader(widgetDir: string): Promise { @@ -21,7 +24,11 @@ export default function translationLoader(widgetDir: string): Promise { + (error: NodeJS.ErrnoException, schemaData: string) => { + if (error && error.code === 'ENOENT') { + resolve(translationDefaultPayload); + } + const schemaResults = handleSchemaLoader(error, schemaData); if (!schemaResults.data) { diff --git a/src/services/translation/validate.ts b/src/services/translation/translationValidator/validateTranslation.ts similarity index 62% rename from src/services/translation/validate.ts rename to src/services/translation/translationValidator/validateTranslation.ts index 277c6a0..89addbd 100644 --- a/src/services/translation/validate.ts +++ b/src/services/translation/translationValidator/validateTranslation.ts @@ -1,8 +1,8 @@ -import { FileLoaderResponse } from '../../types'; -import { log } from '../../messages'; +import { FileLoaderResponse } from '../../../types'; +import { log } from '../../../messages'; -import TranslationValidator from './translationValidator/translationValidator'; -import translationLoader from './translationLoader/translationLoader'; +import TranslationValidator from './translationValidator'; +import translationLoader from '../translationLoader/translationLoader'; export default function validateTranslation(directory: string) { return translationLoader(directory) diff --git a/src/services/widgetRenderer/widgetRenderer.test.ts b/src/services/widgetRenderer/widgetRenderer.test.ts index af46cfa..98ad07a 100644 --- a/src/services/widgetRenderer/widgetRenderer.test.ts +++ b/src/services/widgetRenderer/widgetRenderer.test.ts @@ -5,6 +5,7 @@ import WidgetFileType, { FileLoaderResponse } from '../../types'; import { generateRenderPayloadFromFileLoaderResults } from './widgetRenderer'; const configurationData = fs.readFileSync('src/services/__fixtures__/config.json', 'utf8').toString(); +const translationsData = fs.readFileSync('src/services/__fixtures__/schema_translations.json', 'utf8').toString(); const htmlData = fs.readFileSync('src/services/__fixtures__/widget.html', 'utf8').toString(); const query = fs.readFileSync('src/services/__fixtures__/query.graphql', 'utf8').toString(); const queryParams = fs.readFileSync('src/services/__fixtures__/queryParams.json', 'utf8').toString(); @@ -26,6 +27,10 @@ const fileLoaderResponseData: FileLoaderResponse[] = [ type: WidgetFileType.QUERY_PARAMS, data: queryParams, }, + { + type: WidgetFileType.TRANSLATION, + data: translationsData, + }, ]; describe('Widget Renderer', () => { @@ -38,6 +43,7 @@ describe('Widget Renderer', () => { widget_uuid, storefront_api_query, storefront_api_query_params, + schema_translations, } = generateRenderPayloadFromFileLoaderResults(fileLoaderResponseData); @@ -47,5 +53,6 @@ describe('Widget Renderer', () => { expect(storefront_api_query_params).toEqual(JSON.parse(queryParams)); expect(placement_uuid).not.toBeNull(); expect(widget_uuid).not.toBeNull(); + expect(schema_translations).toEqual(JSON.parse(translationsData)); }); }); diff --git a/src/services/widgetRenderer/widgetRenderer.ts b/src/services/widgetRenderer/widgetRenderer.ts index ecaeba5..79e6b22 100644 --- a/src/services/widgetRenderer/widgetRenderer.ts +++ b/src/services/widgetRenderer/widgetRenderer.ts @@ -6,6 +6,7 @@ import widgetTemplateLoader from '../widgetTemplate/widgetTemplateLoader/widgetT import widgetConfigLoader from '../widgetConfig/widgetConfigLoader/widgetConfigLoader'; import queryLoader from '../query/queryLoader/queryLoader'; import queryParamsLoader from '../query/queryParamsLoader/queryParamsLoader'; +import translationsLoader from '../translation/translationLoader/translationLoader'; const channelId = process.env.WIDGET_BUILDER_CHANNEL_ID ? parseInt(process.env.WIDGET_BUILDER_CHANNEL_ID, 10) : 1; @@ -17,6 +18,7 @@ const getInitialRenderingPayload = (): WidgetPreviewRenderRequest => ({ storefront_api_query: '', storefront_api_query_params: {}, channel_id: channelId, + schema_translations: '', }); export function generateRenderPayloadFromFileLoaderResults(results: FileLoaderResponse[]): WidgetPreviewRenderRequest { @@ -40,6 +42,10 @@ export function generateRenderPayloadFromFileLoaderResults(results: FileLoaderRe return { ...acc, storefront_api_query_params: JSON.parse(data) }; } + if (type === WidgetFileType.TRANSLATION) { + return { ...acc, schema_translations: JSON.parse(data) }; + } + return acc; }, getInitialRenderingPayload(), ); @@ -51,6 +57,7 @@ export default function renderWidget(widgetDir: string): Promise { widgetConfigLoader(widgetDir), queryLoader(widgetDir), queryParamsLoader(widgetDir), + translationsLoader(widgetDir), ]).then( (results: FileLoaderResponse[]) => getWidget( generateRenderPayloadFromFileLoaderResults(results), diff --git a/src/services/widgetTemplate/publish.ts b/src/services/widgetTemplate/publish.ts index d84351b..b6e88f5 100644 --- a/src/services/widgetTemplate/publish.ts +++ b/src/services/widgetTemplate/publish.ts @@ -6,6 +6,7 @@ import WidgetFileType, { FileLoaderResponse } from '../../types'; import schemaLoader from '../schema/schemaLoader/schemaLoader'; import widgetTemplateLoader from './widgetTemplateLoader/widgetTemplateLoader'; +import translationsLoader from '../translation/translationLoader/translationLoader'; import track from './track'; interface CreateWidgetTemplateReq { @@ -14,6 +15,7 @@ interface CreateWidgetTemplateReq { template: string; storefront_api_query: string; channel_id: number; + schema_translations?: string; } const widgetTemplatePayload = (widgetName: string): CreateWidgetTemplateReq => ({ @@ -22,6 +24,7 @@ const widgetTemplatePayload = (widgetName: string): CreateWidgetTemplateReq => ( template: '', storefront_api_query: '', channel_id: 1, + schema_translations: '' }); const publishWidgetTemplate = async (widgetName: string, widgetTemplateDir: string) => { @@ -30,6 +33,7 @@ const publishWidgetTemplate = async (widgetName: string, widgetTemplateDir: stri try { const widgetConfiguration = await Promise.all([ widgetTemplateLoader(widgetTemplateDir), + translationsLoader(widgetTemplateDir), schemaLoader(widgetTemplateDir), queryLoader(widgetTemplateDir), queryParamsLoader(widgetTemplateDir), @@ -49,6 +53,10 @@ const publishWidgetTemplate = async (widgetName: string, widgetTemplateDir: stri return { ...acc, storefront_api_query: data }; } + if (type === WidgetFileType.TRANSLATION) { + return { ...acc, schema_translations: data }; + } + return acc; }, widgetTemplatePayload(widgetName), )); From 5b4aff42363982ea30fda3fdfaa8212e0fe15b11 Mon Sep 17 00:00:00 2001 From: Darren Belding Date: Tue, 5 Apr 2022 16:15:10 +0100 Subject: [PATCH 2/4] Correct validate command options --- src/cli/run/validate.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/cli/run/validate.ts b/src/cli/run/validate.ts index 1f408c4..82725ef 100644 --- a/src/cli/run/validate.ts +++ b/src/cli/run/validate.ts @@ -10,8 +10,9 @@ import validateTranslation from '../../services/translation/validate'; const helperText = ` Usage: - --validate-schema sample-widget/schema.json - --validate-query-params sample-widget/queryParamsBuilder.json + widget-builder validate --schema sample-widget + widget-builder validate --query-params sample-widget + widget-builder validate --translation sample-widget `; const validateCommands = () => { @@ -24,12 +25,13 @@ const validateCommands = () => { .option('--translation', 'validates schema_translation.json file') .addHelpText('afterAll', helperText) .action((name, options) => { - const directory = path.resolve('.'); - if (options.validateSchema) { + const directory = path.resolve('.', name); + + if (options.schema) { validateSchema(directory); } - if (options.validateQueryParamsBuilder) { + if (options.queryParams) { validateQueryParamsBuilder(directory); } From f3b4bd92f60ec849213a97e4912200b8606f8ca7 Mon Sep 17 00:00:00 2001 From: Darren Belding Date: Mon, 23 May 2022 13:51:36 +0100 Subject: [PATCH 3/4] Replace tracking with publish checks --- src/services/api/widget.ts | 50 ++++++++++++++++++++++++++ src/services/widgetTemplate/publish.ts | 8 ++--- 2 files changed, 53 insertions(+), 5 deletions(-) diff --git a/src/services/api/widget.ts b/src/services/api/widget.ts index a1bd7b5..3180dea 100644 --- a/src/services/api/widget.ts +++ b/src/services/api/widget.ts @@ -6,6 +6,7 @@ import { WidgetConfiguration } from '../schema/schemaParser/schemaParser'; export const widgetApi = { widgetPreviewRender: `${AUTH_CONFIG.apiPath}/content/widget-templates/preview`, widgetTemplatePublish: `${AUTH_CONFIG.apiPath}/content/widget-templates`, + widgetTemplateList: `${AUTH_CONFIG.apiPath}/content/widget-templates`, }; interface WidgetPreviewRenderResponse { @@ -65,3 +66,52 @@ export const publishWidget = ( .then(({ data: { data } }) => resolve(data)) .catch(error => reject(error)); }); + +export const getWidgetTemplate = ( + name: string +): Promise => new Promise((resolve, reject) => + getAllTemplates() + .then((data) => { + const match = data.find( + template => name === template.name, + ); + + resolve(match?.uuid || ''); + }) + .catch(error => reject(error))) + +export interface WidgetTemplateResult { + name: string; + schema: any[]; + template: string; + storefront_api_query: string; + uuid: string; + kind: string; + date_created: string; + date_modified: string; + current_version_uuid: string; + icon_name: string; + } + +const getAllTemplates = async (page: number = 1): Promise => { + let listResults: WidgetTemplateResult[] = []; + let done = false; + + while (!done) { + const { data } = (await Axios({ + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + 'X-Auth-Client': AUTH_CONFIG.authId, + 'X-Auth-Token': AUTH_CONFIG.authToken, + }, + url: `${widgetApi.widgetTemplateList}?limit=250&page=${page}`, + })).data; + + done = data.length === 0; + page++; + listResults = listResults.concat(data); + } + + return listResults; +} diff --git a/src/services/widgetTemplate/publish.ts b/src/services/widgetTemplate/publish.ts index b6e88f5..fbd4f71 100644 --- a/src/services/widgetTemplate/publish.ts +++ b/src/services/widgetTemplate/publish.ts @@ -1,13 +1,12 @@ import { log, messages } from '../../messages'; import queryLoader from '../query/queryLoader/queryLoader'; import queryParamsLoader from '../query/queryParamsLoader/queryParamsLoader'; -import { publishWidget } from '../api/widget'; +import { publishWidget, getWidgetTemplate } from '../api/widget'; import WidgetFileType, { FileLoaderResponse } from '../../types'; import schemaLoader from '../schema/schemaLoader/schemaLoader'; import widgetTemplateLoader from './widgetTemplateLoader/widgetTemplateLoader'; import translationsLoader from '../translation/translationLoader/translationLoader'; -import track from './track'; interface CreateWidgetTemplateReq { name: string; @@ -28,7 +27,7 @@ const widgetTemplatePayload = (widgetName: string): CreateWidgetTemplateReq => ( }); const publishWidgetTemplate = async (widgetName: string, widgetTemplateDir: string) => { - const widgetTemplateUuid = track.isTracked(widgetTemplateDir); + const widgetTemplateUuid = await getWidgetTemplate(widgetName); try { const widgetConfiguration = await Promise.all([ @@ -61,10 +60,9 @@ const publishWidgetTemplate = async (widgetName: string, widgetTemplateDir: stri }, widgetTemplatePayload(widgetName), )); - const { uuid } = await publishWidget(widgetConfiguration, widgetTemplateUuid); + await publishWidget(widgetConfiguration, widgetTemplateUuid); if (!widgetTemplateUuid) { - track.startTracking(widgetTemplateDir, uuid); log.success(messages.widgetRelease.success(widgetName)); } else { log.success(`Successfully updated ${widgetName}`); From 3a89b936302d13ddd7ecd79c514de0c968e39fa3 Mon Sep 17 00:00:00 2001 From: joeward98 Date: Thu, 10 Nov 2022 10:17:01 +0000 Subject: [PATCH 4/4] extended cli tool to publish widgets across multiple channels --- src/config.ts | 1 + src/services/widgetRenderer/widgetRenderer.ts | 3 +-- src/services/widgetTemplate/publish.ts | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/config.ts b/src/config.ts index abdf37a..a992a75 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,2 +1,3 @@ export const port: string = process.env.WIDGET_BUILDER_PORT || '8080'; export const host: string = process.env.WIDGET_BUILDER_HOST || 'http://localhost'; +export const channelId = process.env.WIDGET_BUILDER_CHANNEL_ID ? parseInt(process.env.WIDGET_BUILDER_CHANNEL_ID, 10) : 1; diff --git a/src/services/widgetRenderer/widgetRenderer.ts b/src/services/widgetRenderer/widgetRenderer.ts index ecaeba5..df16e5c 100644 --- a/src/services/widgetRenderer/widgetRenderer.ts +++ b/src/services/widgetRenderer/widgetRenderer.ts @@ -6,8 +6,7 @@ import widgetTemplateLoader from '../widgetTemplate/widgetTemplateLoader/widgetT import widgetConfigLoader from '../widgetConfig/widgetConfigLoader/widgetConfigLoader'; import queryLoader from '../query/queryLoader/queryLoader'; import queryParamsLoader from '../query/queryParamsLoader/queryParamsLoader'; - -const channelId = process.env.WIDGET_BUILDER_CHANNEL_ID ? parseInt(process.env.WIDGET_BUILDER_CHANNEL_ID, 10) : 1; +import { channelId } from '../../config'; const getInitialRenderingPayload = (): WidgetPreviewRenderRequest => ({ widget_configuration: {}, diff --git a/src/services/widgetTemplate/publish.ts b/src/services/widgetTemplate/publish.ts index d84351b..4e11b59 100644 --- a/src/services/widgetTemplate/publish.ts +++ b/src/services/widgetTemplate/publish.ts @@ -4,6 +4,7 @@ import queryParamsLoader from '../query/queryParamsLoader/queryParamsLoader'; import { publishWidget } from '../api/widget'; import WidgetFileType, { FileLoaderResponse } from '../../types'; import schemaLoader from '../schema/schemaLoader/schemaLoader'; +import { channelId } from '../../config'; import widgetTemplateLoader from './widgetTemplateLoader/widgetTemplateLoader'; import track from './track'; @@ -21,7 +22,7 @@ const widgetTemplatePayload = (widgetName: string): CreateWidgetTemplateReq => ( schema: [], template: '', storefront_api_query: '', - channel_id: 1, + channel_id: channelId, }); const publishWidgetTemplate = async (widgetName: string, widgetTemplateDir: string) => {