From 943265febb392fb4dcda34854de33029eec970c7 Mon Sep 17 00:00:00 2001 From: Milkov Date: Tue, 1 Oct 2024 12:33:31 +0300 Subject: [PATCH 01/13] fix: CPE loading changes from backend and not from workspace --- .../preview-middleware/src/base/config.ts | 29 +++++++++++++++++-- packages/preview-middleware/src/base/flp.ts | 20 ++++++++++--- 2 files changed, 42 insertions(+), 7 deletions(-) diff --git a/packages/preview-middleware/src/base/config.ts b/packages/preview-middleware/src/base/config.ts index a161f9aa3b..7ac3726e43 100644 --- a/packages/preview-middleware/src/base/config.ts +++ b/packages/preview-middleware/src/base/config.ts @@ -7,6 +7,7 @@ import { readFileSync } from 'fs'; import { mergeTestConfigDefaults } from './test'; import { type Editor, create } from 'mem-fs-editor'; import { create as createStorage } from 'mem-fs'; +import type { MergedAppDescriptor } from '@sap-ux/axios-extension'; export interface CustomConnector { applyConnector: string; @@ -33,7 +34,7 @@ export interface TemplateConfig { additionalInformation: string; applicationType: 'URL'; url: string; - applicationDependencies?: { manifest: boolean }; + applicationDependencies?: MergedAppDescriptor; } >; ui5: { @@ -203,9 +204,31 @@ function getFlexSettings(): TemplateConfig['ui5']['flex'] { * @param manifest manifest of the additional target app * @param app configuration for the preview * @param logger logger instance + * @param descriptor descriptor of the additional target app */ -export async function addApp(templateConfig: TemplateConfig, manifest: Partial, app: App, logger: Logger) { +export async function addApp( + templateConfig: TemplateConfig, + manifest: Partial, + app: App, + logger: Logger, + descriptor?: MergedAppDescriptor +) { const id = manifest['sap.app']?.id ?? ''; + + let applicationDependencies = { manifest: true } as unknown as MergedAppDescriptor; + // Setting the descriptor (merged manifest) as applicationDependencies. + // If not set, CPE is loading the changes from the backend system and ignores the local changes in the workspace. + // This seems to happen only when an adaptation with the same namespace is already deployed in the backend system. + if (descriptor) { + applicationDependencies = descriptor; + if (descriptor.asyncHints?.components?.length > 0 && descriptor.asyncHints.components[0].url) { + // The URL in the manifest is set to `/webapp`, but in CPE this causes a 404 error when trying to fetch local changes from the workspace. + // URLs like `/webapp/changes/fragments` are not working in CPE it should be changed to `/changes/fragments`. + // This issue is not present in Visual Editor. + descriptor.asyncHints.components[0].url.url = '/'; + } + } + app.intent ??= { object: id.replace(/\./g, ''), action: 'preview' @@ -217,7 +240,7 @@ export async function addApp(templateConfig: TemplateConfig, manifest: Partial = {}): Promise { + async init( + manifest: Manifest, + componentId?: string, + resources: Record = {}, + descriptor?: MergedAppDescriptor + ): Promise { this.createFlexHandler(); this.config.libs ??= await this.hasLocateReuseLibsScript(); const id = manifest['sap.app'].id; @@ -120,7 +127,8 @@ export class FlpSandbox { local: '.', intent: this.config.intent }, - this.logger + this.logger, + descriptor ); this.addStandardRoutes(); if (this.rta) { @@ -531,10 +539,14 @@ export async function initAdp( } const descriptor = adp.descriptor; - descriptor.asyncHints.requests = []; + // TODO: This line is currently commented as it causes issues for SAPUI5 Versions > 1.71. + // This needs to be in a condition that checks the SAPUI5 version. + // descriptor.asyncHints.requests = []; const { name, manifest } = descriptor; - await flp.init(manifest, name, adp.resources); + // Passing the descriptor (merged manifest) from the backend system to the FLP sandbox + // to be propagated to be propagated to addApp method the assembles the flp.html to be added as applicationDependencies + await flp.init(manifest, name, adp.resources, descriptor); flp.router.use(adp.descriptor.url, adp.proxy.bind(adp) as RequestHandler); flp.addOnChangeRequestHandler(adp.onChangeRequest.bind(adp)); flp.router.use(json()); From 30bec72f1ad3cbff0d51a581bc8e5076dc5235fd Mon Sep 17 00:00:00 2001 From: Milkov Date: Wed, 2 Oct 2024 18:04:04 +0300 Subject: [PATCH 02/13] fix: set workspacePath as URL parameter --- .../adp-tooling/src/preview/adp-preview.ts | 2 +- .../axios-extension/src/abap/lrep-service.ts | 11 +++++++-- .../preview-middleware/src/base/config.ts | 24 ++++++------------- 3 files changed, 17 insertions(+), 20 deletions(-) diff --git a/packages/adp-tooling/src/preview/adp-preview.ts b/packages/adp-tooling/src/preview/adp-preview.ts index 55cca14f45..f16549cad3 100644 --- a/packages/adp-tooling/src/preview/adp-preview.ts +++ b/packages/adp-tooling/src/preview/adp-preview.ts @@ -132,7 +132,7 @@ export class AdpPreview { } const buffer = zip.toBuffer(); - this.mergedDescriptor = (await this.lrep.mergeAppDescriptorVariant(buffer))[this.descriptorVariantId]; + this.mergedDescriptor = (await this.lrep.mergeAppDescriptorVariant(buffer, '//'))[this.descriptorVariantId]; } /** diff --git a/packages/axios-extension/src/abap/lrep-service.ts b/packages/axios-extension/src/abap/lrep-service.ts index 1d5c9a0d13..2a30878594 100644 --- a/packages/axios-extension/src/abap/lrep-service.ts +++ b/packages/axios-extension/src/abap/lrep-service.ts @@ -166,13 +166,20 @@ export class LayeredRepositoryService extends Axios implements Service { * Merge a given app descriptor variant with the stord app descriptor. * * @param appDescriptorVariant zip file containing an app descriptor variant + * @param workspacePath value for workspacePath URL parameter * @returns a promise with an object containing merged app descriptors with their id as keys. */ public async mergeAppDescriptorVariant( - appDescriptorVariant: Buffer + appDescriptorVariant: Buffer, + workspacePath?: string ): Promise<{ [key: string]: MergedAppDescriptor }> { + let path = '/appdescr_variant_preview/'; + if (workspacePath) { + path += `?workspacePath=${workspacePath}`; + } + try { - const response = await this.put('/appdescr_variant_preview/', appDescriptorVariant, { + const response = await this.put(path, appDescriptorVariant, { headers: { 'Content-Type': 'application/zip' } diff --git a/packages/preview-middleware/src/base/config.ts b/packages/preview-middleware/src/base/config.ts index 7ac3726e43..d954caf2a8 100644 --- a/packages/preview-middleware/src/base/config.ts +++ b/packages/preview-middleware/src/base/config.ts @@ -215,33 +215,23 @@ export async function addApp( ) { const id = manifest['sap.app']?.id ?? ''; - let applicationDependencies = { manifest: true } as unknown as MergedAppDescriptor; - // Setting the descriptor (merged manifest) as applicationDependencies. - // If not set, CPE is loading the changes from the backend system and ignores the local changes in the workspace. - // This seems to happen only when an adaptation with the same namespace is already deployed in the backend system. - if (descriptor) { - applicationDependencies = descriptor; - if (descriptor.asyncHints?.components?.length > 0 && descriptor.asyncHints.components[0].url) { - // The URL in the manifest is set to `/webapp`, but in CPE this causes a 404 error when trying to fetch local changes from the workspace. - // URLs like `/webapp/changes/fragments` are not working in CPE it should be changed to `/changes/fragments`. - // This issue is not present in Visual Editor. - descriptor.asyncHints.components[0].url.url = '/'; - } - } - app.intent ??= { object: id.replace(/\./g, ''), action: 'preview' }; + + const appName = `${app.intent?.object}-${app.intent?.action}`; templateConfig.ui5.resources[id] = app.target; - templateConfig.apps[`${app.intent?.object}-${app.intent?.action}`] = { + templateConfig.apps[appName] = { title: (await getI18nTextFromProperty(app.local, manifest['sap.app']?.title, logger)) ?? id, description: (await getI18nTextFromProperty(app.local, manifest['sap.app']?.description, logger)) ?? '', additionalInformation: `SAPUI5.Component=${app.componentId ?? id}`, applicationType: 'URL', - url: app.target, - applicationDependencies + url: app.target }; + if (descriptor) { + templateConfig.apps[appName].applicationDependencies = descriptor; + } } /** From ca0327f13754df899d47acbd9b2928faee8f46e6 Mon Sep 17 00:00:00 2001 From: Milkov Date: Mon, 7 Oct 2024 14:05:06 +0300 Subject: [PATCH 03/13] refactor: move version validation in client --- .../src/flp/bootstrap.js | 16 +++++++++++++++- packages/preview-middleware/src/base/flp.ts | 6 ------ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/packages/preview-middleware-client/src/flp/bootstrap.js b/packages/preview-middleware-client/src/flp/bootstrap.js index 12538aa0a0..a9ad33af30 100644 --- a/packages/preview-middleware-client/src/flp/bootstrap.js +++ b/packages/preview-middleware-client/src/flp/bootstrap.js @@ -15,10 +15,24 @@ async function ushellBootstrap(fnCallback) { const response = await fetch(`${basePath}/resources/sap-ui-version.json`); const json = await response.json(); const version = json?.libraries?.find((lib) => lib.name === 'sap.ui.core')?.version ?? '1.121.0'; - const majorUi5Version = parseInt(version.split('.')[0], 10); + const [major, minor] = version.split('.'); + const majorUi5Version = parseInt(major, 10); + const minorUi5Version = parseInt(minor, 10); if (majorUi5Version >= 2) { src = `${basePath}/resources/sap/ushell/bootstrap/sandbox2.js`; } + if (majorUi5Version === 1 && minorUi5Version < 72) { + const getNestedProperty = (obj, target) => + target in obj + ? obj[target] + : Object.values(obj).reduce((acc, val) => { + if (acc !== undefined) return acc; + if (typeof val === 'object') return getNestedProperty(val, target); + }, undefined); + + const asyncHints = getNestedProperty(window['sap-ushell-config'], 'asyncHints'); + asyncHints.requests = []; + } } catch (error) { console.warn('Failed to fetch sap-ui-version.json. Assuming it is a 1.x version.'); } diff --git a/packages/preview-middleware/src/base/flp.ts b/packages/preview-middleware/src/base/flp.ts index 3fa8dd36c8..e2a3cac67f 100644 --- a/packages/preview-middleware/src/base/flp.ts +++ b/packages/preview-middleware/src/base/flp.ts @@ -539,13 +539,7 @@ export async function initAdp( } const descriptor = adp.descriptor; - // TODO: This line is currently commented as it causes issues for SAPUI5 Versions > 1.71. - // This needs to be in a condition that checks the SAPUI5 version. - // descriptor.asyncHints.requests = []; const { name, manifest } = descriptor; - - // Passing the descriptor (merged manifest) from the backend system to the FLP sandbox - // to be propagated to be propagated to addApp method the assembles the flp.html to be added as applicationDependencies await flp.init(manifest, name, adp.resources, descriptor); flp.router.use(adp.descriptor.url, adp.proxy.bind(adp) as RequestHandler); flp.addOnChangeRequestHandler(adp.onChangeRequest.bind(adp)); From 40ccd8bd937c6ad532ab6a560213fa4a107bd6a9 Mon Sep 17 00:00:00 2001 From: mmilko01 <162288787+mmilko01@users.noreply.github.com> Date: Mon, 7 Oct 2024 14:58:51 +0300 Subject: [PATCH 04/13] chore: update changeset --- .changeset/plenty-chairs-thank.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .changeset/plenty-chairs-thank.md diff --git a/.changeset/plenty-chairs-thank.md b/.changeset/plenty-chairs-thank.md new file mode 100644 index 0000000000..d5a55eb68b --- /dev/null +++ b/.changeset/plenty-chairs-thank.md @@ -0,0 +1,8 @@ +--- +"@sap-ux/adp-tooling": patch +"@sap-ux/axios-extension": patch +"@sap-ux-private/preview-middleware-client": patch +"@sap-ux/preview-middleware": patch +--- + +CPE loading changes from backend and not from workspace From c1178f91fff0bba419fa16493684e33f8802f6a0 Mon Sep 17 00:00:00 2001 From: Milkov Date: Mon, 7 Oct 2024 15:15:55 +0300 Subject: [PATCH 05/13] test: fix existing tests --- .../test/unit/preview/adp-preview.test.ts | 2 +- .../test/abap/lrep-service.test.ts | 1 - .../base/__snapshots__/config.test.ts.snap | 6 +-- .../unit/base/__snapshots__/flp.test.ts.snap | 42 +++---------------- 4 files changed, 10 insertions(+), 41 deletions(-) diff --git a/packages/adp-tooling/test/unit/preview/adp-preview.test.ts b/packages/adp-tooling/test/unit/preview/adp-preview.test.ts index 41e81e8c16..485f4671c3 100644 --- a/packages/adp-tooling/test/unit/preview/adp-preview.test.ts +++ b/packages/adp-tooling/test/unit/preview/adp-preview.test.ts @@ -126,7 +126,7 @@ describe('AdaptationProject', () => { .reply(200) .persist(true); nock(backend) - .put('/sap/bc/lrep/appdescr_variant_preview/') + .put('/sap/bc/lrep/appdescr_variant_preview/?workspacePath=//') .reply(200, { 'my.adaptation': mockMergedDescriptor }) diff --git a/packages/axios-extension/test/abap/lrep-service.test.ts b/packages/axios-extension/test/abap/lrep-service.test.ts index 2d6ff34a0f..d5ae7f27a7 100644 --- a/packages/axios-extension/test/abap/lrep-service.test.ts +++ b/packages/axios-extension/test/abap/lrep-service.test.ts @@ -6,7 +6,6 @@ import { LayeredRepositoryService, createForAbap } from '../../src'; import type { AxiosError } from '../../src'; import type { ToolsLogger } from '@sap-ux/logger'; import * as Logger from '@sap-ux/logger'; -import { describe } from 'node:test'; const loggerMock: ToolsLogger = { debug: jest.fn(), diff --git a/packages/preview-middleware/test/unit/base/__snapshots__/config.test.ts.snap b/packages/preview-middleware/test/unit/base/__snapshots__/config.test.ts.snap index 5adeb562a2..586a3d9cff 100644 --- a/packages/preview-middleware/test/unit/base/__snapshots__/config.test.ts.snap +++ b/packages/preview-middleware/test/unit/base/__snapshots__/config.test.ts.snap @@ -112,7 +112,7 @@ exports[`config generatePreviewFiles minimum settings 1`] = ` } } }, - applications: {\\"app-preview\\":{\\"title\\":\\"My Simple App\\",\\"description\\":\\"This is a very simple application.\\",\\"additionalInformation\\":\\"SAPUI5.Component=test.fe.v2.app\\",\\"applicationType\\":\\"URL\\",\\"url\\":\\"..\\",\\"applicationDependencies\\":{\\"manifest\\":true}}} + applications: {\\"app-preview\\":{\\"title\\":\\"My Simple App\\",\\"description\\":\\"This is a very simple application.\\",\\"additionalInformation\\":\\"SAPUI5.Component=test.fe.v2.app\\",\\"applicationType\\":\\"URL\\",\\"url\\":\\"..\\"}} }; @@ -190,7 +190,7 @@ Object { } } }, - applications: {\\"simpleApp-preview\\":{\\"title\\":\\"My Simple App\\",\\"description\\":\\"This is a very simple application.\\",\\"additionalInformation\\":\\"SAPUI5.Component=test.fe.v2.app\\",\\"applicationType\\":\\"URL\\",\\"url\\":\\"/apps/simple-app\\",\\"applicationDependencies\\":{\\"manifest\\":true}},\\"testfev2other-preview\\":{\\"title\\":\\"My Other App\\",\\"description\\":\\"This is a very simple application.\\",\\"additionalInformation\\":\\"SAPUI5.Component=test.fe.v2.other\\",\\"applicationType\\":\\"URL\\",\\"url\\":\\"/apps/other-app\\",\\"applicationDependencies\\":{\\"manifest\\":true}}} + applications: {\\"simpleApp-preview\\":{\\"title\\":\\"My Simple App\\",\\"description\\":\\"This is a very simple application.\\",\\"additionalInformation\\":\\"SAPUI5.Component=test.fe.v2.app\\",\\"applicationType\\":\\"URL\\",\\"url\\":\\"/apps/simple-app\\"},\\"testfev2other-preview\\":{\\"title\\":\\"My Other App\\",\\"description\\":\\"This is a very simple application.\\",\\"additionalInformation\\":\\"SAPUI5.Component=test.fe.v2.other\\",\\"applicationType\\":\\"URL\\",\\"url\\":\\"/apps/other-app\\"}} }; @@ -271,7 +271,7 @@ Object { } } }, - applications: {\\"myapp-myaction\\":{\\"title\\":\\"My Simple App\\",\\"description\\":\\"This is a very simple application.\\",\\"additionalInformation\\":\\"SAPUI5.Component=test.fe.v2.app\\",\\"applicationType\\":\\"URL\\",\\"url\\":\\"..\\",\\"applicationDependencies\\":{\\"manifest\\":true}}} + applications: {\\"myapp-myaction\\":{\\"title\\":\\"My Simple App\\",\\"description\\":\\"This is a very simple application.\\",\\"additionalInformation\\":\\"SAPUI5.Component=test.fe.v2.app\\",\\"applicationType\\":\\"URL\\",\\"url\\":\\"..\\"}} }; diff --git a/packages/preview-middleware/test/unit/base/__snapshots__/flp.test.ts.snap b/packages/preview-middleware/test/unit/base/__snapshots__/flp.test.ts.snap index 1ed59ed379..dd59a185c2 100644 --- a/packages/preview-middleware/test/unit/base/__snapshots__/flp.test.ts.snap +++ b/packages/preview-middleware/test/unit/base/__snapshots__/flp.test.ts.snap @@ -7,9 +7,6 @@ Object { "apps": Object { "app-preview": Object { "additionalInformation": "SAPUI5.Component=my.id", - "applicationDependencies": Object { - "manifest": true, - }, "applicationType": "URL", "description": "", "title": "my.id", @@ -17,9 +14,6 @@ Object { }, "myObject-action": Object { "additionalInformation": "SAPUI5.Component=test.fe.v2.other", - "applicationDependencies": Object { - "manifest": true, - }, "applicationType": "URL", "description": "This is a very simple application.", "title": "My Other App", @@ -27,9 +21,6 @@ Object { }, "myRemoteObject-action": Object { "additionalInformation": "SAPUI5.Component=myRemoteComponent", - "applicationDependencies": Object { - "manifest": true, - }, "applicationType": "URL", "description": "", "title": "myRemoteObject-action", @@ -37,9 +28,6 @@ Object { }, "testfev2app-preview": Object { "additionalInformation": "SAPUI5.Component=test.fe.v2.app", - "applicationDependencies": Object { - "manifest": true, - }, "applicationType": "URL", "description": "This is a very simple application.", "title": "My Simple App", @@ -88,9 +76,6 @@ Object { "apps": Object { "app-preview": Object { "additionalInformation": "SAPUI5.Component=my.id", - "applicationDependencies": Object { - "manifest": true, - }, "applicationType": "URL", "description": "My App Description", "title": "My App", @@ -136,9 +121,6 @@ Object { "apps": Object { "app-preview": Object { "additionalInformation": "SAPUI5.Component=my.id", - "applicationDependencies": Object { - "manifest": true, - }, "applicationType": "URL", "description": "myDifferentDescription", "title": "myDifferentTitle", @@ -184,9 +166,6 @@ Object { "apps": Object { "app-preview": Object { "additionalInformation": "SAPUI5.Component=my.id", - "applicationDependencies": Object { - "manifest": true, - }, "applicationType": "URL", "description": "myOtherDescription", "title": "myOtherTitle", @@ -232,9 +211,6 @@ Object { "apps": Object { "app-preview": Object { "additionalInformation": "SAPUI5.Component=my.id", - "applicationDependencies": Object { - "manifest": true, - }, "applicationType": "URL", "description": "", "title": "my.id", @@ -280,9 +256,6 @@ Object { "apps": Object { "app-preview": Object { "additionalInformation": "SAPUI5.Component=test.fe.v2.app", - "applicationDependencies": Object { - "manifest": true, - }, "applicationType": "URL", "description": "This is a very simple application.", "title": "My Simple App", @@ -330,9 +303,6 @@ Object { "apps": Object { "app-preview": Object { "additionalInformation": "SAPUI5.Component=test.fe.v2.app", - "applicationDependencies": Object { - "manifest": true, - }, "applicationType": "URL", "description": "This is a very simple application.", "title": "My Simple App", @@ -504,7 +474,7 @@ exports[`FlpSandbox router editor with config 1`] = ` } } }, - applications: {\\"app-preview\\":{\\"title\\":\\"My Simple App\\",\\"description\\":\\"This is a very simple application.\\",\\"additionalInformation\\":\\"SAPUI5.Component=test.fe.v2.app\\",\\"applicationType\\":\\"URL\\",\\"url\\":\\"..\\",\\"applicationDependencies\\":{\\"manifest\\":true}},\\"testfev2other-preview\\":{\\"title\\":\\"My Other App\\",\\"description\\":\\"This is a very simple application.\\",\\"additionalInformation\\":\\"SAPUI5.Component=test.fe.v2.other\\",\\"applicationType\\":\\"URL\\",\\"url\\":\\"/yet/another/app\\",\\"applicationDependencies\\":{\\"manifest\\":true}}} + applications: {\\"app-preview\\":{\\"title\\":\\"My Simple App\\",\\"description\\":\\"This is a very simple application.\\",\\"additionalInformation\\":\\"SAPUI5.Component=test.fe.v2.app\\",\\"applicationType\\":\\"URL\\",\\"url\\":\\"..\\"},\\"testfev2other-preview\\":{\\"title\\":\\"My Other App\\",\\"description\\":\\"This is a very simple application.\\",\\"additionalInformation\\":\\"SAPUI5.Component=test.fe.v2.other\\",\\"applicationType\\":\\"URL\\",\\"url\\":\\"/yet/another/app\\"}} }; @@ -583,7 +553,7 @@ exports[`FlpSandbox router rta 1`] = ` } } }, - applications: {\\"app-preview\\":{\\"title\\":\\"My Simple App\\",\\"description\\":\\"This is a very simple application.\\",\\"additionalInformation\\":\\"SAPUI5.Component=test.fe.v2.app\\",\\"applicationType\\":\\"URL\\",\\"url\\":\\"..\\",\\"applicationDependencies\\":{\\"manifest\\":true}},\\"testfev2other-preview\\":{\\"title\\":\\"My Other App\\",\\"description\\":\\"This is a very simple application.\\",\\"additionalInformation\\":\\"SAPUI5.Component=test.fe.v2.other\\",\\"applicationType\\":\\"URL\\",\\"url\\":\\"/yet/another/app\\",\\"applicationDependencies\\":{\\"manifest\\":true}}} + applications: {\\"app-preview\\":{\\"title\\":\\"My Simple App\\",\\"description\\":\\"This is a very simple application.\\",\\"additionalInformation\\":\\"SAPUI5.Component=test.fe.v2.app\\",\\"applicationType\\":\\"URL\\",\\"url\\":\\"..\\"},\\"testfev2other-preview\\":{\\"title\\":\\"My Other App\\",\\"description\\":\\"This is a very simple application.\\",\\"additionalInformation\\":\\"SAPUI5.Component=test.fe.v2.other\\",\\"applicationType\\":\\"URL\\",\\"url\\":\\"/yet/another/app\\"}} }; @@ -692,7 +662,7 @@ exports[`FlpSandbox router rta with developerMode=true 2`] = ` } } }, - applications: {\\"app-preview\\":{\\"title\\":\\"My Simple App\\",\\"description\\":\\"This is a very simple application.\\",\\"additionalInformation\\":\\"SAPUI5.Component=test.fe.v2.app\\",\\"applicationType\\":\\"URL\\",\\"url\\":\\"..\\",\\"applicationDependencies\\":{\\"manifest\\":true}},\\"testfev2other-preview\\":{\\"title\\":\\"My Other App\\",\\"description\\":\\"This is a very simple application.\\",\\"additionalInformation\\":\\"SAPUI5.Component=test.fe.v2.other\\",\\"applicationType\\":\\"URL\\",\\"url\\":\\"/yet/another/app\\",\\"applicationDependencies\\":{\\"manifest\\":true}}} + applications: {\\"app-preview\\":{\\"title\\":\\"My Simple App\\",\\"description\\":\\"This is a very simple application.\\",\\"additionalInformation\\":\\"SAPUI5.Component=test.fe.v2.app\\",\\"applicationType\\":\\"URL\\",\\"url\\":\\"..\\"},\\"testfev2other-preview\\":{\\"title\\":\\"My Other App\\",\\"description\\":\\"This is a very simple application.\\",\\"additionalInformation\\":\\"SAPUI5.Component=test.fe.v2.other\\",\\"applicationType\\":\\"URL\\",\\"url\\":\\"/yet/another/app\\"}} }; @@ -786,7 +756,7 @@ exports[`FlpSandbox router rta with developerMode=true and plugin 1`] = ` } } }, - applications: {\\"app-preview\\":{\\"title\\":\\"My Simple App\\",\\"description\\":\\"This is a very simple application.\\",\\"additionalInformation\\":\\"SAPUI5.Component=test.fe.v2.app\\",\\"applicationType\\":\\"URL\\",\\"url\\":\\"..\\",\\"applicationDependencies\\":{\\"manifest\\":true}},\\"testfev2other-preview\\":{\\"title\\":\\"My Other App\\",\\"description\\":\\"This is a very simple application.\\",\\"additionalInformation\\":\\"SAPUI5.Component=test.fe.v2.other\\",\\"applicationType\\":\\"URL\\",\\"url\\":\\"/yet/another/app\\",\\"applicationDependencies\\":{\\"manifest\\":true}}} + applications: {\\"app-preview\\":{\\"title\\":\\"My Simple App\\",\\"description\\":\\"This is a very simple application.\\",\\"additionalInformation\\":\\"SAPUI5.Component=test.fe.v2.app\\",\\"applicationType\\":\\"URL\\",\\"url\\":\\"..\\"},\\"testfev2other-preview\\":{\\"title\\":\\"My Other App\\",\\"description\\":\\"This is a very simple application.\\",\\"additionalInformation\\":\\"SAPUI5.Component=test.fe.v2.other\\",\\"applicationType\\":\\"URL\\",\\"url\\":\\"/yet/another/app\\"}} }; @@ -880,7 +850,7 @@ exports[`FlpSandbox router rta with editors path without leading "/" 1`] = ` } } }, - applications: {\\"app-preview\\":{\\"title\\":\\"My Simple App\\",\\"description\\":\\"This is a very simple application.\\",\\"additionalInformation\\":\\"SAPUI5.Component=test.fe.v2.app\\",\\"applicationType\\":\\"URL\\",\\"url\\":\\"..\\",\\"applicationDependencies\\":{\\"manifest\\":true}},\\"testfev2other-preview\\":{\\"title\\":\\"My Other App\\",\\"description\\":\\"This is a very simple application.\\",\\"additionalInformation\\":\\"SAPUI5.Component=test.fe.v2.other\\",\\"applicationType\\":\\"URL\\",\\"url\\":\\"/yet/another/app\\",\\"applicationDependencies\\":{\\"manifest\\":true}}} + applications: {\\"app-preview\\":{\\"title\\":\\"My Simple App\\",\\"description\\":\\"This is a very simple application.\\",\\"additionalInformation\\":\\"SAPUI5.Component=test.fe.v2.app\\",\\"applicationType\\":\\"URL\\",\\"url\\":\\"..\\"},\\"testfev2other-preview\\":{\\"title\\":\\"My Other App\\",\\"description\\":\\"This is a very simple application.\\",\\"additionalInformation\\":\\"SAPUI5.Component=test.fe.v2.other\\",\\"applicationType\\":\\"URL\\",\\"url\\":\\"/yet/another/app\\"}} }; @@ -958,7 +928,7 @@ exports[`FlpSandbox router test/flp.html 1`] = ` } } }, - applications: {\\"app-preview\\":{\\"title\\":\\"My Simple App\\",\\"description\\":\\"This is a very simple application.\\",\\"additionalInformation\\":\\"SAPUI5.Component=test.fe.v2.app\\",\\"applicationType\\":\\"URL\\",\\"url\\":\\"..\\",\\"applicationDependencies\\":{\\"manifest\\":true}},\\"testfev2other-preview\\":{\\"title\\":\\"My Other App\\",\\"description\\":\\"This is a very simple application.\\",\\"additionalInformation\\":\\"SAPUI5.Component=test.fe.v2.other\\",\\"applicationType\\":\\"URL\\",\\"url\\":\\"/yet/another/app\\",\\"applicationDependencies\\":{\\"manifest\\":true}}} + applications: {\\"app-preview\\":{\\"title\\":\\"My Simple App\\",\\"description\\":\\"This is a very simple application.\\",\\"additionalInformation\\":\\"SAPUI5.Component=test.fe.v2.app\\",\\"applicationType\\":\\"URL\\",\\"url\\":\\"..\\"},\\"testfev2other-preview\\":{\\"title\\":\\"My Other App\\",\\"description\\":\\"This is a very simple application.\\",\\"additionalInformation\\":\\"SAPUI5.Component=test.fe.v2.other\\",\\"applicationType\\":\\"URL\\",\\"url\\":\\"/yet/another/app\\"}} }; From 7c81e2acf360e99e2fa38a54260915896cb2ba0c Mon Sep 17 00:00:00 2001 From: Milkov Date: Mon, 7 Oct 2024 15:56:15 +0300 Subject: [PATCH 06/13] test: enhance tests --- .../axios-extension/test/abap/lrep-service.test.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/axios-extension/test/abap/lrep-service.test.ts b/packages/axios-extension/test/abap/lrep-service.test.ts index d5ae7f27a7..c6e0eb7413 100644 --- a/packages/axios-extension/test/abap/lrep-service.test.ts +++ b/packages/axios-extension/test/abap/lrep-service.test.ts @@ -263,14 +263,24 @@ describe('LayeredRepositoryService', () => { }); describe('mergeAppDescriptorVariant', () => { - const mockResult = { hello: 'world' }; test('merge valid app variant', async () => { + const mockResult = { hello: 'world', components: [{ url: '/webapp' }] }; nock(server).put(`${LayeredRepositoryService.PATH}/appdescr_variant_preview/`).reply(200, mockResult); const mergedDescriptor = await service.mergeAppDescriptorVariant(Buffer.from('~test')); expect(mergedDescriptor).toEqual(mockResult); }); + test('merge valid app variant with adjusted workspace path', async () => { + const mockResult = { hello: 'world', components: [{ url: '/' }] }; + nock(server) + .put(`${LayeredRepositoryService.PATH}/appdescr_variant_preview/?workspacePath=//`) + .reply(200, mockResult); + + const mergedDescriptor = await service.mergeAppDescriptorVariant(Buffer.from('~test'), '//'); + expect(mergedDescriptor).toEqual(mockResult); + }); + test('error is thrown', async () => { nock(server).put(`${LayeredRepositoryService.PATH}/appdescr_variant_preview/`).reply(500); try { From cd4deddace55670fa261dc00d30444d9215d8b49 Mon Sep 17 00:00:00 2001 From: Milkov Date: Mon, 7 Oct 2024 17:03:29 +0300 Subject: [PATCH 07/13] fix: pass URL parameter using params parameter --- packages/axios-extension/src/abap/lrep-service.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/axios-extension/src/abap/lrep-service.ts b/packages/axios-extension/src/abap/lrep-service.ts index 2a30878594..49c0fedefd 100644 --- a/packages/axios-extension/src/abap/lrep-service.ts +++ b/packages/axios-extension/src/abap/lrep-service.ts @@ -173,16 +173,19 @@ export class LayeredRepositoryService extends Axios implements Service { appDescriptorVariant: Buffer, workspacePath?: string ): Promise<{ [key: string]: MergedAppDescriptor }> { - let path = '/appdescr_variant_preview/'; + const path = '/appdescr_variant_preview/'; + const params: { [key: string]: string } = {}; + if (workspacePath) { - path += `?workspacePath=${workspacePath}`; + params.workspacePath = workspacePath; } try { const response = await this.put(path, appDescriptorVariant, { headers: { 'Content-Type': 'application/zip' - } + }, + params }); return JSON.parse(response.data); } catch (error) { From e31fc3ee49d0c18c72c8e7d1a9a250e465318b47 Mon Sep 17 00:00:00 2001 From: Milkov Date: Mon, 7 Oct 2024 17:35:09 +0300 Subject: [PATCH 08/13] chore: add comment --- packages/preview-middleware-client/src/flp/bootstrap.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/preview-middleware-client/src/flp/bootstrap.js b/packages/preview-middleware-client/src/flp/bootstrap.js index a9ad33af30..d12fb02d96 100644 --- a/packages/preview-middleware-client/src/flp/bootstrap.js +++ b/packages/preview-middleware-client/src/flp/bootstrap.js @@ -21,6 +21,10 @@ async function ushellBootstrap(fnCallback) { if (majorUi5Version >= 2) { src = `${basePath}/resources/sap/ushell/bootstrap/sandbox2.js`; } + + // For UI5 version 1.71 and below, we need to remove the asyncHints.requests to load the changes in an Adaptation project. + // This logic needs to be executed here to have a reliable check for UI5 version and remove the asyncHints.requests before the sandbox is loaded. + // The sandbox shell modifies the `window['sap-ushell-config']`. if (majorUi5Version === 1 && minorUi5Version < 72) { const getNestedProperty = (obj, target) => target in obj @@ -31,7 +35,7 @@ async function ushellBootstrap(fnCallback) { }, undefined); const asyncHints = getNestedProperty(window['sap-ushell-config'], 'asyncHints'); - asyncHints.requests = []; + asyncHints?.requests = []; } } catch (error) { console.warn('Failed to fetch sap-ui-version.json. Assuming it is a 1.x version.'); From dbeb08c302897f36e985ae92282995ee079c8b89 Mon Sep 17 00:00:00 2001 From: Milkov Date: Mon, 7 Oct 2024 17:49:26 +0300 Subject: [PATCH 09/13] fix: wrong assignment --- packages/preview-middleware-client/src/flp/bootstrap.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/preview-middleware-client/src/flp/bootstrap.js b/packages/preview-middleware-client/src/flp/bootstrap.js index d12fb02d96..05c238e1be 100644 --- a/packages/preview-middleware-client/src/flp/bootstrap.js +++ b/packages/preview-middleware-client/src/flp/bootstrap.js @@ -35,7 +35,9 @@ async function ushellBootstrap(fnCallback) { }, undefined); const asyncHints = getNestedProperty(window['sap-ushell-config'], 'asyncHints'); - asyncHints?.requests = []; + if (asyncHints && asyncHints.requests) { + asyncHints.requests = []; + } } } catch (error) { console.warn('Failed to fetch sap-ui-version.json. Assuming it is a 1.x version.'); From 130d7a2cd8aecdd0df9ef124965390db316295f5 Mon Sep 17 00:00:00 2001 From: Milkov Date: Mon, 7 Oct 2024 21:19:27 +0300 Subject: [PATCH 10/13] fix: decode params --- packages/axios-extension/src/abap/lrep-service.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/axios-extension/src/abap/lrep-service.ts b/packages/axios-extension/src/abap/lrep-service.ts index 49c0fedefd..0c141fa229 100644 --- a/packages/axios-extension/src/abap/lrep-service.ts +++ b/packages/axios-extension/src/abap/lrep-service.ts @@ -174,18 +174,19 @@ export class LayeredRepositoryService extends Axios implements Service { workspacePath?: string ): Promise<{ [key: string]: MergedAppDescriptor }> { const path = '/appdescr_variant_preview/'; - const params: { [key: string]: string } = {}; + const params = new URLSearchParams(this.defaults?.params); if (workspacePath) { - params.workspacePath = workspacePath; + params.append('workspacePath', workspacePath); } try { const response = await this.put(path, appDescriptorVariant, { + paramsSerializer: (params) => decodeURIComponent(params.toString()), + params, headers: { 'Content-Type': 'application/zip' - }, - params + } }); return JSON.parse(response.data); } catch (error) { From 57805ceabc6e1e09a4fb028c115d0721ad21c328 Mon Sep 17 00:00:00 2001 From: Milkov Date: Wed, 9 Oct 2024 14:02:44 +0300 Subject: [PATCH 11/13] refacor: enhance set nested property algorithm --- .../src/flp/bootstrap.js | 33 ++++++++++++------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/packages/preview-middleware-client/src/flp/bootstrap.js b/packages/preview-middleware-client/src/flp/bootstrap.js index 05c238e1be..0f0e13bfa9 100644 --- a/packages/preview-middleware-client/src/flp/bootstrap.js +++ b/packages/preview-middleware-client/src/flp/bootstrap.js @@ -26,18 +26,29 @@ async function ushellBootstrap(fnCallback) { // This logic needs to be executed here to have a reliable check for UI5 version and remove the asyncHints.requests before the sandbox is loaded. // The sandbox shell modifies the `window['sap-ushell-config']`. if (majorUi5Version === 1 && minorUi5Version < 72) { - const getNestedProperty = (obj, target) => - target in obj - ? obj[target] - : Object.values(obj).reduce((acc, val) => { - if (acc !== undefined) return acc; - if (typeof val === 'object') return getNestedProperty(val, target); - }, undefined); - - const asyncHints = getNestedProperty(window['sap-ushell-config'], 'asyncHints'); - if (asyncHints && asyncHints.requests) { - asyncHints.requests = []; + const setNestedProperty = (obj) => { + if (!obj || typeof obj !== 'object') return; + + const stack = [obj]; + + while (stack.length > 0) { + const current = stack.pop(); + + if (current.asyncHints) { + if (current.asyncHints.requests) { + current.asyncHints.requests = []; + } + return; + } + + for (const key in current) { + if (typeof current[key] === 'object' && current[key] !== null) { + stack.push(current[key]); + } + } } + }; + setNestedProperty(window['sap-ushell-config']['applications']); } } catch (error) { console.warn('Failed to fetch sap-ui-version.json. Assuming it is a 1.x version.'); From 33efc0fbff04e1939f2448cfc94fcda14e418c90 Mon Sep 17 00:00:00 2001 From: Milkov Date: Wed, 9 Oct 2024 16:36:43 +0300 Subject: [PATCH 12/13] test: add init test with descriptor --- .../unit/base/__snapshots__/flp.test.ts.snap | 68 +++++++++++++++++++ .../test/unit/base/flp.test.ts | 37 ++++++++++ 2 files changed, 105 insertions(+) diff --git a/packages/preview-middleware/test/unit/base/__snapshots__/flp.test.ts.snap b/packages/preview-middleware/test/unit/base/__snapshots__/flp.test.ts.snap index dd59a185c2..104492fdc2 100644 --- a/packages/preview-middleware/test/unit/base/__snapshots__/flp.test.ts.snap +++ b/packages/preview-middleware/test/unit/base/__snapshots__/flp.test.ts.snap @@ -343,6 +343,74 @@ Object { } `; +exports[`FlpSandbox init with passed descriptor 1`] = ` +Object { + "apps": Object { + "app-preview": Object { + "additionalInformation": "SAPUI5.Component=myComponent", + "applicationDependencies": Object { + "asyncHints": Object { + "requests": Array [ + Object { + "url": "myRequestUrl", + }, + ], + }, + "components": Array [ + Object { + "name": "myComponent", + "url": "myComponentUrl", + }, + ], + "libs": Array [ + Object { + "name": "myLib", + "url": "myLibUrl", + }, + ], + }, + "applicationType": "URL", + "description": "", + "title": "my.id", + "url": "..", + }, + }, + "basePath": "..", + "init": undefined, + "locateReuseLibsScript": false, + "ui5": Object { + "bootstrapOptions": "", + "flex": Array [ + Object { + "connector": "LrepConnector", + "layers": Array [], + "url": "/sap/bc/lrep", + }, + Object { + "applyConnector": "custom.connectors.WorkspaceConnector", + "custom": true, + "writeConnector": "custom.connectors.WorkspaceConnector", + }, + Object { + "connector": "LocalStorageConnector", + "layers": Array [ + "CUSTOMER", + "USER", + ], + }, + ], + "libs": "sap.m,sap.ui.core,sap.ushell", + "resources": Object { + "my.id": "..", + "myResources1": "myResourcesUrl1", + "myResources2": "myResourcesUrl2", + "open.ux.preview.client": "../preview/client", + }, + "theme": "sap_horizon", + }, +} +`; + exports[`FlpSandbox router GET /preview/api/changes 1`] = `"{\\"sap.ui.fl.myid\\":{\\"id\\":\\"myId\\"}}"`; exports[`FlpSandbox router custom opa5 path test/integration/opaTests.qunit.html 1`] = ` diff --git a/packages/preview-middleware/test/unit/base/flp.test.ts b/packages/preview-middleware/test/unit/base/flp.test.ts index 51429a2532..f6537680ed 100644 --- a/packages/preview-middleware/test/unit/base/flp.test.ts +++ b/packages/preview-middleware/test/unit/base/flp.test.ts @@ -15,6 +15,7 @@ import { type AdpPreviewConfig } from '@sap-ux/adp-tooling'; import * as adpTooling from '@sap-ux/adp-tooling'; import * as projectAccess from '@sap-ux/project-access'; import type { I18nEntry } from '@sap-ux/i18n/src/types'; +import type { MergedAppDescriptor } from '@sap-ux/axios-extension'; jest.mock('@sap-ux/adp-tooling', () => { return { @@ -150,6 +151,42 @@ describe('FlpSandbox', () => { expect(flp.templateConfig).toMatchSnapshot(); }); + test('with passed descriptor', async () => { + const flp = new FlpSandbox({}, mockProject, mockUtils, logger); + const manifest = { + 'sap.app': { id: 'my.id' } + } as Manifest; + const descriptor = { + components: [ + { + name: 'myComponent', + url: 'myComponentUrl' + } + ], + libs: [ + { + name: 'myLib', + url: 'myLibUrl' + } + ], + asyncHints: { + requests: [ + { + url: 'myRequestUrl' + } + ] + } + }; + const componendId = 'myComponent'; + const resources = { + 'myResources1': 'myResourcesUrl1', + 'myResources2': 'myResourcesUrl2' + }; + + await flp.init(manifest, componendId, resources, descriptor as unknown as MergedAppDescriptor); + expect(flp.templateConfig).toMatchSnapshot(); + }); + test('optional configurations', async () => { const flp = new FlpSandbox({}, mockProject, mockUtils, logger); const manifest = JSON.parse(readFileSync(join(fixtures, 'simple-app/webapp/manifest.json'), 'utf-8')); From 819600b123158267c9a7d86f6e5a259223b10b8c Mon Sep 17 00:00:00 2001 From: Milkov Date: Wed, 16 Oct 2024 10:27:17 +0300 Subject: [PATCH 13/13] refactor: move nested function --- .../src/flp/bootstrap.js | 59 +++++++++++-------- 1 file changed, 33 insertions(+), 26 deletions(-) diff --git a/packages/preview-middleware-client/src/flp/bootstrap.js b/packages/preview-middleware-client/src/flp/bootstrap.js index 0f0e13bfa9..8c31d6dac3 100644 --- a/packages/preview-middleware-client/src/flp/bootstrap.js +++ b/packages/preview-middleware-client/src/flp/bootstrap.js @@ -22,33 +22,8 @@ async function ushellBootstrap(fnCallback) { src = `${basePath}/resources/sap/ushell/bootstrap/sandbox2.js`; } - // For UI5 version 1.71 and below, we need to remove the asyncHints.requests to load the changes in an Adaptation project. - // This logic needs to be executed here to have a reliable check for UI5 version and remove the asyncHints.requests before the sandbox is loaded. - // The sandbox shell modifies the `window['sap-ushell-config']`. if (majorUi5Version === 1 && minorUi5Version < 72) { - const setNestedProperty = (obj) => { - if (!obj || typeof obj !== 'object') return; - - const stack = [obj]; - - while (stack.length > 0) { - const current = stack.pop(); - - if (current.asyncHints) { - if (current.asyncHints.requests) { - current.asyncHints.requests = []; - } - return; - } - - for (const key in current) { - if (typeof current[key] === 'object' && current[key] !== null) { - stack.push(current[key]); - } - } - } - }; - setNestedProperty(window['sap-ushell-config']['applications']); + removeAsyncHintsRequests(); } } catch (error) { console.warn('Failed to fetch sap-ui-version.json. Assuming it is a 1.x version.'); @@ -64,6 +39,38 @@ async function ushellBootstrap(fnCallback) { } } +/** + * For UI5 version 1.71 and below, we need to remove the asyncHints.requests + * to load the changes in an Adaptation project. + * This logic needs to be executed here to have a reliable check for + * UI5 version and remove the asyncHints.requests before the sandbox is loaded. + * The sandbox shell modifies the `window['sap-ushell-config']`. + */ +function removeAsyncHintsRequests() { + const obj = window['sap-ushell-config']['applications']; + + if (!obj || typeof obj !== 'object') return; + + const stack = [obj]; + + while (stack.length > 0) { + const current = stack.pop(); + + if (current.asyncHints) { + if (current.asyncHints.requests) { + current.asyncHints.requests = []; + } + return; + } + + for (const key in current) { + if (typeof current[key] === 'object' && current[key] !== null) { + stack.push(current[key]); + } + } + } +} + // eslint-disable-next-line fiori-custom/sap-no-global-define window['sap-ui-config'] = { 'xx-bootTask': ushellBootstrap