diff --git a/.changeset/modern-seals-ring.md b/.changeset/modern-seals-ring.md new file mode 100644 index 0000000000..0c5c64e861 --- /dev/null +++ b/.changeset/modern-seals-ring.md @@ -0,0 +1,7 @@ +--- +'@sap-ux/preview-middleware': minor +'@sap-ux-private/preview-middleware-client': patch +--- + +feat: introduce enhanced flp homepage + - controlled via boolean property `flp.enhancedHomePage`, which is false by default \ No newline at end of file diff --git a/packages/preview-middleware-client/package.json b/packages/preview-middleware-client/package.json index d3d5355454..9adc643758 100644 --- a/packages/preview-middleware-client/package.json +++ b/packages/preview-middleware-client/package.json @@ -18,7 +18,8 @@ "format": "prettier --write '**/*.{js,json,ts,yaml,yml}' --ignore-path ../../.prettierignore", "lint": "eslint . --ext .ts", "lint:fix": "eslint . --ext .ts --fix", - "test": "jest --ci --forceExit --detectOpenHandles --colors" + "test": "jest --ci --forceExit --detectOpenHandles --colors", + "test-u": "jest --ci --forceExit --detectOpenHandles --colors -u" }, "files": [ "dist" diff --git a/packages/preview-middleware-client/src/flp/homepage/Component.ts b/packages/preview-middleware-client/src/flp/homepage/Component.ts new file mode 100644 index 0000000000..2f85e83dea --- /dev/null +++ b/packages/preview-middleware-client/src/flp/homepage/Component.ts @@ -0,0 +1,10 @@ +import BaseComponent from 'sap/ui/core/UIComponent'; + +/** + * @namespace open.ux.preview.client.flp.homepage + */ +export default class Component extends BaseComponent { + public static readonly metadata = { + manifest: 'json' + }; +} \ No newline at end of file diff --git a/packages/preview-middleware-client/src/flp/homepage/css/style.css b/packages/preview-middleware-client/src/flp/homepage/css/style.css new file mode 100644 index 0000000000..00b4c9e889 --- /dev/null +++ b/packages/preview-middleware-client/src/flp/homepage/css/style.css @@ -0,0 +1,3 @@ +.homeNewsContainer .sapCuxBaseWrapper > * { + width: 100%; +} diff --git a/packages/preview-middleware-client/src/flp/homepage/manifest.json b/packages/preview-middleware-client/src/flp/homepage/manifest.json new file mode 100644 index 0000000000..8a7af8bc07 --- /dev/null +++ b/packages/preview-middleware-client/src/flp/homepage/manifest.json @@ -0,0 +1,41 @@ +{ + "sap.app": { + "id": "open.ux.preview.client.flp.homepage", + "type": "application", + "title": "FLP HomePage" + }, + "sap.ui": { + "technology": "UI5", + "deviceTypes": { + "desktop": true, + "tablet": true, + "phone": true + } + }, + "sap.ui5": { + "rootView": { + "viewName": "open.ux.preview.client.flp.homepage.view.MyHome", + "type": "XML", + "async": true, + "id": "myhome" + }, + "dependencies": { + "minUI5Version": "1.123.0", + "libs": { + "sap.ui.core": {}, + "sap.cux.home": {} + } + }, + "contentDensities": { + "compact": true, + "cozy": true + }, + "resources": { + "css": [ + { + "uri": "css/style.css" + } + ] + } + } +} \ No newline at end of file diff --git a/packages/preview-middleware-client/src/flp/homepage/view/MyHome.view.xml b/packages/preview-middleware-client/src/flp/homepage/view/MyHome.view.xml new file mode 100644 index 0000000000..c19f512f5f --- /dev/null +++ b/packages/preview-middleware-client/src/flp/homepage/view/MyHome.view.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + diff --git a/packages/preview-middleware-client/src/flp/init.ts b/packages/preview-middleware-client/src/flp/init.ts index ba62199ce0..a22881789f 100644 --- a/packages/preview-middleware-client/src/flp/init.ts +++ b/packages/preview-middleware-client/src/flp/init.ts @@ -8,6 +8,7 @@ import ResourceBundle from 'sap/base/i18n/ResourceBundle'; import AppState from 'sap/ushell/services/AppState'; import { getManifestAppdescr } from '../adp/api-handler'; import { getError } from '../utils/error'; +import initCdm from './initCdm'; import initConnectors from './initConnectors'; import { getUi5Version, isLowerThanMinimalUi5Version, Ui5VersionInfo } from '../utils/version'; import { CommunicationService } from '../cpe/communication-service'; @@ -257,16 +258,19 @@ export function setI18nTitle(resourceBundle: ResourceBundle, i18nKey = 'appTitle * @param params.appUrls JSON containing a string array of application urls * @param params.flex JSON containing the flex configuration * @param params.customInit path to the custom init module to be called + * @param params.enhancedHomePage boolean indicating if enhanced homepage is enabled * @returns promise */ export async function init({ appUrls, flex, - customInit + customInit, + enhancedHomePage }: { appUrls?: string | null; flex?: string | null; customInit?: string | null; + enhancedHomePage?: boolean | null; }): Promise { const urlParams = new URLSearchParams(window.location.search); const container = sap?.ushell?.Container ?? @@ -340,6 +344,10 @@ export async function init({ setI18nTitle(resourceBundle); registerSAPFonts(); + if (enhancedHomePage) { + await initCdm(container); + } + const renderer = (ui5VersionInfo.major < 2 && !ui5VersionInfo.label?.includes('legacy-free')) ? await container.createRenderer(undefined, true) @@ -353,7 +361,8 @@ if (bootstrapConfig) { init({ appUrls: bootstrapConfig.getAttribute('data-open-ux-preview-libs-manifests'), flex: bootstrapConfig.getAttribute('data-open-ux-preview-flex-settings'), - customInit: bootstrapConfig.getAttribute('data-open-ux-preview-customInit') + customInit: bootstrapConfig.getAttribute('data-open-ux-preview-customInit'), + enhancedHomePage: !!bootstrapConfig.getAttribute('data-open-ux-preview-enhanced-homepage') }).catch((e) => { const error = getError(e); Log.error('Sandbox initialization failed: ' + error.message); diff --git a/packages/preview-middleware-client/src/flp/initCdm.ts b/packages/preview-middleware-client/src/flp/initCdm.ts new file mode 100644 index 0000000000..92b052277f --- /dev/null +++ b/packages/preview-middleware-client/src/flp/initCdm.ts @@ -0,0 +1,118 @@ +import { Window } from 'types/global'; + +/** + * Initializes the CDM (Common Data Model) configuration for the SAP Fiori Launchpad. + * + * @param {sap.ushell.Container} container - The SAP Fiori Launchpad container. + * @returns {Promise} A promise that resolves when the initialization is complete. + */ +export default async function initCdm(container: typeof sap.ushell.Container): Promise { + (window as unknown as Window)['sap-ushell-config'] = { + defaultRenderer: 'fiori2', + renderers: { + fiori2: { + componentData: { + config: { + enableSearch: false, + enableRecentActivity: true, + rootIntent: 'Shell-home' + } + } + } + }, + ushell: { + customPreload: { + enabled: false + }, + spaces: { + enabled: true, + myHome: { + enabled: true + } + }, + homeApp: { + component: { + name: 'open.ux.preview.client.flp.homepage', + url: '/preview/client/flp/homepage' + } + } + }, + services: { + Container: { + adapter: { + config: { + userProfile: { + metadata: { + editablePropterties: [ + 'accessibility', + 'contentDensity', + 'theme' + ] + }, + defaults: { + email: 'john.doe@sap.com', + firstName: 'John', + lastName: 'Doe', + fullName: 'John Doe', + id: 'DOEJ' + } + } + } + } + }, + CommonDataModel: { + adapter: { + config: { + ignoreSiteDataPersonalization: true, + siteDataUrl: '/cdm.json' + } + } + }, + Personalization: { + adapter: { + module: 'sap.ushell.adapters.local.PersonalizationAdapter', + config: { + storageType: 'MEMORY' + } + } + }, + PersonalizationV2: { + adapter: { + module: 'sap.ushell.adapters.local.PersonalizationAdapter', + config: { + storageType: 'MEMORY' + } + } + }, + AppState: { + adapter: { + module: 'sap.ushell.adapters.local.AppStateAdapter' + }, + config: { + transient: true + } + }, + NavTargetResolutionInternal: { + config: { + allowTestUrlComponentConfig: false, + enableClientSideTargetResolution: true + }, + adapter: { + module: 'sap.ushell.adapters.local.NavTargetResolutionInternalAdapter' + } + }, + UserInfo: { + adapter: { + module: 'sap.ushell.adapters.local.UserInfoAdapter' + } + }, + FlpLaunchPage: { + adapter: { + module: 'sap.ushell.adapters.cdm.v3.FlpLaunchPageAdapter' + } + } + } + }; + + await container.init('cdm'); +} diff --git a/packages/preview-middleware-client/src/utils/version.ts b/packages/preview-middleware-client/src/utils/version.ts index 21b6af818d..9cf1f013e8 100644 --- a/packages/preview-middleware-client/src/utils/version.ts +++ b/packages/preview-middleware-client/src/utils/version.ts @@ -48,8 +48,8 @@ function checkVersionInfo(versionInfo: Ui5VersionInfo): void { export async function getUi5Version(library: string = 'sap.ui.core'): Promise { let version = ((await VersionInfo.load({ library })) as SingleVersionInfo)?.version; if (!version) { - Log.error('Could not get UI5 version of application. Using 1.121.0 as fallback.'); - version = '1.121.0'; + Log.error('Could not get UI5 version of application. Using 1.130.0 as fallback.'); + version = '1.130.0'; } const [major, minor, patch] = version.split('.').map((versionPart) => parseInt(versionPart, 10)); const label = version.split(/-(.*)/s)?.[1]; diff --git a/packages/preview-middleware-client/test/__mock__/window.ts b/packages/preview-middleware-client/test/__mock__/window.ts index 0233bd2fc7..95fe6025e6 100644 --- a/packages/preview-middleware-client/test/__mock__/window.ts +++ b/packages/preview-middleware-client/test/__mock__/window.ts @@ -28,7 +28,8 @@ export const sapMock = { createRenderer: jest.fn().mockReturnValue({ placeAt: jest.fn() }), createRendererInternal: jest.fn().mockReturnValue({ placeAt: jest.fn() }), attachRendererCreatedEvent: jest.fn(), - getServiceAsync: jest.fn() + getServiceAsync: jest.fn(), + init: jest.fn() } } }; diff --git a/packages/preview-middleware-client/test/unit/flp/__snapshots__/initCdm.test.ts.snap b/packages/preview-middleware-client/test/unit/flp/__snapshots__/initCdm.test.ts.snap new file mode 100644 index 0000000000..5972c436e4 --- /dev/null +++ b/packages/preview-middleware-client/test/unit/flp/__snapshots__/initCdm.test.ts.snap @@ -0,0 +1,110 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`flp/initCdm ensure that ushell config is set properly 1`] = ` +Object { + "defaultRenderer": "fiori2", + "renderers": Object { + "fiori2": Object { + "componentData": Object { + "config": Object { + "enableRecentActivity": true, + "enableSearch": false, + "rootIntent": "Shell-home", + }, + }, + }, + }, + "services": Object { + "AppState": Object { + "adapter": Object { + "module": "sap.ushell.adapters.local.AppStateAdapter", + }, + "config": Object { + "transient": true, + }, + }, + "CommonDataModel": Object { + "adapter": Object { + "config": Object { + "ignoreSiteDataPersonalization": true, + "siteDataUrl": "/cdm.json", + }, + }, + }, + "Container": Object { + "adapter": Object { + "config": Object { + "userProfile": Object { + "defaults": Object { + "email": "john.doe@sap.com", + "firstName": "John", + "fullName": "John Doe", + "id": "DOEJ", + "lastName": "Doe", + }, + "metadata": Object { + "editablePropterties": Array [ + "accessibility", + "contentDensity", + "theme", + ], + }, + }, + }, + }, + }, + "FlpLaunchPage": Object { + "adapter": Object { + "module": "sap.ushell.adapters.cdm.v3.FlpLaunchPageAdapter", + }, + }, + "NavTargetResolutionInternal": Object { + "adapter": Object { + "module": "sap.ushell.adapters.local.NavTargetResolutionInternalAdapter", + }, + "config": Object { + "allowTestUrlComponentConfig": false, + "enableClientSideTargetResolution": true, + }, + }, + "Personalization": Object { + "adapter": Object { + "config": Object { + "storageType": "MEMORY", + }, + "module": "sap.ushell.adapters.local.PersonalizationAdapter", + }, + }, + "PersonalizationV2": Object { + "adapter": Object { + "config": Object { + "storageType": "MEMORY", + }, + "module": "sap.ushell.adapters.local.PersonalizationAdapter", + }, + }, + "UserInfo": Object { + "adapter": Object { + "module": "sap.ushell.adapters.local.UserInfoAdapter", + }, + }, + }, + "ushell": Object { + "customPreload": Object { + "enabled": false, + }, + "homeApp": Object { + "component": Object { + "name": "open.ux.preview.client.flp.homepage", + "url": "/preview/client/flp/homepage", + }, + }, + "spaces": Object { + "enabled": true, + "myHome": Object { + "enabled": true, + }, + }, + }, +} +`; diff --git a/packages/preview-middleware-client/test/unit/flp/initCdm.test.ts b/packages/preview-middleware-client/test/unit/flp/initCdm.test.ts new file mode 100644 index 0000000000..c355ad6a18 --- /dev/null +++ b/packages/preview-middleware-client/test/unit/flp/initCdm.test.ts @@ -0,0 +1,22 @@ +import { sapMock } from 'mock/window'; +import { Window } from 'types/global'; +import initCdm from '../../../src/flp/initCdm'; + +describe('flp/initCdm', () => { + afterEach(() => { + jest.restoreAllMocks(); + sapMock.ui.require.mockReset(); + }); + + test('ensure that ushell config is set properly', async () => { + const container = sapMock.ushell.Container; + await initCdm(container as unknown as typeof sap.ushell.Container); + + expect((window as unknown as Window)['sap-ushell-config']).toMatchSnapshot(); + expect(sapMock.ushell.Container.init).toHaveBeenCalledWith('cdm'); + }); + + test('ensure that homepage component is defined', async () => { + expect(await import('../../../src/flp/homepage/Component')).toBeDefined(); + }); +}); diff --git a/packages/preview-middleware-client/test/unit/utils/version.test.ts b/packages/preview-middleware-client/test/unit/utils/version.test.ts index 418dd529e5..bcbad68fa9 100644 --- a/packages/preview-middleware-client/test/unit/utils/version.test.ts +++ b/packages/preview-middleware-client/test/unit/utils/version.test.ts @@ -32,10 +32,10 @@ describe('utils/version', () => { expect(version.patch).toEqual(11); }); - test('getUi5Version fallback to 1.121.0', async () => { + test('getUi5Version fallback to 1.130.0', async () => { const version = await getUi5Version(); expect(version.major).toEqual(1); - expect(version.minor).toEqual(121); + expect(version.minor).toEqual(130); }); test('getUi5Version for snapshot', async () => { diff --git a/packages/preview-middleware-client/types/global.d.ts b/packages/preview-middleware-client/types/global.d.ts index a257fae6ec..d50895aa1c 100644 --- a/packages/preview-middleware-client/types/global.d.ts +++ b/packages/preview-middleware-client/types/global.d.ts @@ -2,5 +2,8 @@ export interface Window { 'sap-ui-config': { [key: string]: (fnCallback: () => void) => void | Promise; }; + 'sap-ushell-config': { + [key: string]: unknown; + }; [key: string]: string; } diff --git a/packages/preview-middleware-client/types/sap.ushell.d.ts b/packages/preview-middleware-client/types/sap.ushell.d.ts index e19ecb9f10..dc6466af5d 100644 --- a/packages/preview-middleware-client/types/sap.ushell.d.ts +++ b/packages/preview-middleware-client/types/sap.ushell.d.ts @@ -8,6 +8,7 @@ declare namespace sap.ushell { static createRenderer(async: true): Promise; static createRenderer(renderer?: string, async?: true): Promise; static createRendererInternal(renderer?: string, async: true): Promise; + static init(platform: string): Promise; } } @@ -28,4 +29,4 @@ declare module 'sap/ushell/services/AppState' { } export default AppState; -} +} \ No newline at end of file diff --git a/packages/preview-middleware/README.md b/packages/preview-middleware/README.md index ce4709c3b5..de24db408f 100644 --- a/packages/preview-middleware/README.md +++ b/packages/preview-middleware/README.md @@ -24,6 +24,7 @@ When this middleware is used together with the `reload-middleware`, then the ord | `flp.apps` | `array` | `undefined` | Optional additional local apps that are available in local Fiori launchpad | | `flp.libs` | `boolean` | `undefined` | Optional flag to add a generic script fetching the paths of used libraries not available in UI5. To disable set it to `false`, if not set, then the project is checked for a `load-reuse-libs` script and if available the libraries are fetched as well. | | `flp.theme` | `string` | `undefined` | Optional flag for setting the UI5 Theme. | +| `flp.enhancedHomePage` | `boolean` | `undefined` | Optional flag for enabling enhanced FLP Homepage, available only from UI5 1.123.0 onwards. | | `adp.target` | | | Required configuration for adaptation projects defining the connected backend | | `adp.ignoreCertErrors` | `boolean` | `false` | Optional setting to ignore certification validation errors when working with e.g. development systems with self signed certificates | | `rta` | | | 🚫 *Deprecated: use 'editors.rta' instead*
Optional configuration allowing to add mount points for runtime adaptation | diff --git a/packages/preview-middleware/src/base/cdm.ts b/packages/preview-middleware/src/base/cdm.ts new file mode 100644 index 0000000000..16955a3849 --- /dev/null +++ b/packages/preview-middleware/src/base/cdm.ts @@ -0,0 +1,93 @@ +import { readFileSync } from 'fs'; +import { join } from 'path'; +import type { TemplateConfig } from './config'; +import { type FLPCdmConfig, FLPHomePageDefaults } from '../types'; + +/** + * Generates a CDM by embedding the provided app tiles into the FLP homepage. + * + * @param apps - A list of app to be embedded. + * @returns The generated CDM configuration + */ +export function generateCdm(apps: TemplateConfig['apps'] = {}): FLPCdmConfig { + const cdm = JSON.parse(readFileSync(join(__dirname, '../../templates/flp/cdm.base.json'), 'utf-8')) as FLPCdmConfig; + + // add apps + Object.keys(apps).forEach((id) => { + const appId = apps[id].additionalInformation.split('=')[1]; + const vizId = `VIZ:${appId}`; + const [object, action] = id.split('-'); + const { title, description, url } = apps[id]; + + // add app to default catalog + cdm.catalogs[FLPHomePageDefaults.catalogId].payload.viz.push(vizId); + + // create flp visualization + cdm.visualizations[vizId] = { + 'vizType': 'sap.ushell.StaticAppLauncher', + 'businessApp': appId, + 'vizConfig': { + 'sap.app': { + title, + subTitle: description + }, + 'sap.flp': { + 'target': { + 'appId': appId, + 'inboundId': `${object}-${action}`, + 'parameters': [ + { + 'name': 'sap-ui-tech-hint', + 'value': 'UI5' + } + ] + } + } + } + }; + + // create flp application + cdm.applications[appId] = { + 'sap.app': { + id: appId, + title, + crossNavigation: { + inbounds: { + [`${object}-${action}`]: { + 'semanticObject': object, + 'action': action, + title, + 'subTitle': description, + 'signature': { + 'additionalParameters': 'allowed' + } + } + } + } + }, + 'sap.ui5': { + 'componentName': appId + }, + 'sap.ui': { + 'technology': 'UI5' + }, + 'sap.platform.runtime': { + 'componentProperties': { + url, + 'asyncHints': {} + } + } + }; + + // add app to default section + cdm.pages[FLPHomePageDefaults.pageName].payload.sections[FLPHomePageDefaults.sectionId].layout.vizOrder.push( + appId + ); + cdm.pages[FLPHomePageDefaults.pageName].payload.sections[FLPHomePageDefaults.sectionId].viz[appId] = { + id: appId, + vizId + }; + }); + + return cdm; +} diff --git a/packages/preview-middleware/src/base/config.ts b/packages/preview-middleware/src/base/config.ts index 0b09debfd5..a7cbe89ca2 100644 --- a/packages/preview-middleware/src/base/config.ts +++ b/packages/preview-middleware/src/base/config.ts @@ -76,6 +76,7 @@ export interface TemplateConfig { }; features?: { feature: string; isEnabled: boolean }[]; locateReuseLibsScript?: boolean; + enhancedHomePage?: boolean; } /** @@ -172,7 +173,8 @@ export function getFlpConfigWithDefaults(config: Partial = {}): FlpCo apps: config.apps ?? [], libs: config.libs, theme: config.theme, - init: config.init + init: config.init, + enhancedHomePage: config.enhancedHomePage === true } satisfies FlpConfig; if (!flpConfig.path.startsWith('/')) { flpConfig.path = `/${flpConfig.path}`; @@ -359,7 +361,8 @@ export function createFlpTemplateConfig( }, bootstrapOptions: '' }, - locateReuseLibsScript: config.libs + locateReuseLibsScript: config.libs, + enhancedHomePage: config.enhancedHomePage } satisfies TemplateConfig; } diff --git a/packages/preview-middleware/src/base/flp.ts b/packages/preview-middleware/src/base/flp.ts index ee4d17cfed..8a854731ca 100644 --- a/packages/preview-middleware/src/base/flp.ts +++ b/packages/preview-middleware/src/base/flp.ts @@ -35,6 +35,7 @@ import { getAppName, sanitizeRtaConfig } from './config'; +import { generateCdm } from './cdm'; const DEFAULT_LIVERELOAD_PORT = 35729; @@ -155,6 +156,9 @@ export class FlpSandbox { this.createTestSuite(this.test); } + if (this.flpConfig.enhancedHomePage) { + this.addCDMRoute(); + } await this.addRoutesForAdditionalApps(); this.logger.info(`Initialized for app ${id}`); this.logger.debug(`Configured apps: ${JSON.stringify(this.templateConfig.apps)}`); @@ -454,11 +458,17 @@ export class FlpSandbox { } } if (!version) { - this.logger.error('Could not get UI5 version of application. Using 1.121.0 as fallback.'); - version = '1.121.0'; + this.logger.error('Could not get UI5 version of application. Using 1.130.0 as fallback.'); + version = '1.130.0'; } const [major, minor, patch] = version.split('.').map((versionPart) => parseInt(versionPart, 10)); const label = version.split(/-(.*)/s)?.[1]; + + if ((major < 2 && minor < 123) || major >= 2 || label?.includes('legacy-free')) { + this.flpConfig.enhancedHomePage = this.templateConfig.enhancedHomePage = false; + this.logger.warn(`Feature enhancedHomePage disabled: UI5 version ${version} not supported.`); + } + return { major, minor, @@ -480,7 +490,8 @@ export class FlpSandbox { }.` ); const filePrefix = ui5Version.major > 1 || ui5Version.label?.includes('legacy-free') ? '2' : ''; - return readFileSync(join(__dirname, `../../templates/flp/sandbox${filePrefix}.html`), 'utf-8'); + const template = this.flpConfig.enhancedHomePage ? 'cdm' : 'sandbox'; + return readFileSync(join(__dirname, `../../templates/flp/${template}${filePrefix}.html`), 'utf-8'); } /** @@ -538,6 +549,20 @@ export class FlpSandbox { } } + /** + * Add routes for cdm.json required by FLP during bootstrapping via cdm. + * + */ + private addCDMRoute(): void { + this.router.get( + '/cdm.json', + async (_req: EnhancedRequest | connect.IncomingMessage, res: Response | http.ServerResponse) => { + const json = generateCdm(this.templateConfig.apps); + this.sendResponse(res, 'application/json', 200, JSON.stringify(json)); + } + ); + } + /** * Handler for flex changes GET requests. * diff --git a/packages/preview-middleware/src/types/index.ts b/packages/preview-middleware/src/types/index.ts index 15b9894bc0..cc81d6b4cd 100644 --- a/packages/preview-middleware/src/types/index.ts +++ b/packages/preview-middleware/src/types/index.ts @@ -59,6 +59,10 @@ export interface FlpConfig { * Optional: allows to specify a custom init script executed in addition to the default one */ init?: string; + /** + * Optional: if set to true then the new FLP homepage will be enabled + */ + enhancedHomePage?: boolean; } interface OptionalTestConfig { @@ -128,3 +132,114 @@ export type DefaultIntent = { object: 'app'; action: 'preview'; }; + +type FLPAppsCatalog = { + identification: { + id: string; + title: string; + }; + payload: { + viz: string[]; + }; +}; + +type FLPAppVisualization = { + vizType: string; + businessApp: string; + vizConfig: { + 'sap.app': { + title: string; + subTitle: string; + }; + 'sap.flp': { + target: { + appId: string; + inboundId: string; + parameters: { + name: string; + value: string; + }[]; + }; + }; + }; +}; + +type FLPApp = { + 'sap.app': { + id: string; + title: string; + crossNavigation: { + inbounds: { + [key: string]: { + semanticObject: string; + action: string; + title: string; + subTitle: string; + signature: { + additionalParameters: string; + }; + }; + }; + }; + }; + 'sap.ui5': { + componentName: string; + }; + 'sap.ui': { + technology: string; + }; + 'sap.platform.runtime': { + componentProperties: { + url: string; + asyncHints: Record; + }; + }; +}; + +type FLPSectionVizConfig = { + id: string; + vizId: string; +}; + +type FLPSection = { + id: string; + title: string; + default: boolean; + layout: { + vizOrder: FLPSectionVizConfig['id'][]; + }; + viz: Record; +}; + +type FLPPage = { + identification: { + id: string; + title: string; + }; + payload: { + layout: { + sectionOrder: FLPSection['id'][]; + }; + sections: Record; + }; +}; + +/** + * FLP CDM configuration. + */ +export type FLPCdmConfig = { + _version: string; + catalogs: Record; + visualizations: Record; + applications: Record; + pages: Record; +}; + +/** + * Default FLP homepage configuration. + */ +export const FLPHomePageDefaults = { + pageName: 'SAP_BASIS_PG_UI_MYHOME', + catalogId: 'homeCatalog', + sectionId: 'homeAppsSection' +}; diff --git a/packages/preview-middleware/templates/flp/cdm.base.json b/packages/preview-middleware/templates/flp/cdm.base.json new file mode 100644 index 0000000000..35e11ae260 --- /dev/null +++ b/packages/preview-middleware/templates/flp/cdm.base.json @@ -0,0 +1,47 @@ +{ + "_version": "3.1.0", + "catalogs": { + "homeCatalog": { + "identification": { + "id": "homeCatalog", + "title": "Homepage Apps" + }, + "payload": { + "viz": [] + } + } + }, + "site": { + "payload": { + "groupsOrder": [] + } + }, + "groups": {}, + "visualizations": {}, + "applications": {}, + "pages": { + "SAP_BASIS_PG_UI_MYHOME": { + "identification": { + "id": "SAP_BASIS_PG_UI_MYHOME" + }, + "payload": { + "layout": { + "sectionOrder": [ + "homeAppsSection" + ] + }, + "sections": { + "homeAppsSection": { + "id": "homeAppsSection", + "title": "Recently Added Apps", + "default": true, + "layout": { + "vizOrder": [] + }, + "viz": {} + } + } + } + } + } +} diff --git a/packages/preview-middleware/templates/flp/cdm.html b/packages/preview-middleware/templates/flp/cdm.html new file mode 100644 index 0000000000..2bd347febb --- /dev/null +++ b/packages/preview-middleware/templates/flp/cdm.html @@ -0,0 +1,73 @@ + + + + + + + + + Local FLP Sandbox + + + + + + + + +<% if (locals.flex && flex?.developerMode) { %> + + <% } %> + + + + + + + \ No newline at end of file diff --git a/packages/preview-middleware/test/unit/base/__snapshots__/cdm.test.ts.snap b/packages/preview-middleware/test/unit/base/__snapshots__/cdm.test.ts.snap new file mode 100644 index 0000000000..e35b0f8ec2 --- /dev/null +++ b/packages/preview-middleware/test/unit/base/__snapshots__/cdm.test.ts.snap @@ -0,0 +1,224 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`cdm generate cdm with additional apps 1`] = ` +Object { + "_version": "3.1.0", + "applications": Object { + "my.app": Object { + "sap.app": Object { + "crossNavigation": Object { + "inbounds": Object { + "my-app": Object { + "action": "app", + "semanticObject": "my", + "signature": Object { + "additionalParameters": "allowed", + }, + "subTitle": "My App Description", + "title": "My App", + }, + }, + }, + "id": "my.app", + "title": "My App", + }, + "sap.platform.runtime": Object { + "componentProperties": Object { + "asyncHints": Object {}, + "url": "/my/app", + }, + }, + "sap.ui": Object { + "technology": "UI5", + }, + "sap.ui5": Object { + "componentName": "my.app", + }, + }, + "my.app2": Object { + "sap.app": Object { + "crossNavigation": Object { + "inbounds": Object { + "my-app2": Object { + "action": "app2", + "semanticObject": "my", + "signature": Object { + "additionalParameters": "allowed", + }, + "subTitle": "My App Description 2", + "title": "My App 2", + }, + }, + }, + "id": "my.app2", + "title": "My App 2", + }, + "sap.platform.runtime": Object { + "componentProperties": Object { + "asyncHints": Object {}, + "url": "/my/app2", + }, + }, + "sap.ui": Object { + "technology": "UI5", + }, + "sap.ui5": Object { + "componentName": "my.app2", + }, + }, + }, + "catalogs": Object { + "homeCatalog": Object { + "identification": Object { + "id": "homeCatalog", + "title": "Homepage Apps", + }, + "payload": Object { + "viz": Array [ + "VIZ:my.app", + "VIZ:my.app2", + ], + }, + }, + }, + "groups": Object {}, + "pages": Object { + "SAP_BASIS_PG_UI_MYHOME": Object { + "identification": Object { + "id": "SAP_BASIS_PG_UI_MYHOME", + }, + "payload": Object { + "layout": Object { + "sectionOrder": Array [ + "homeAppsSection", + ], + }, + "sections": Object { + "homeAppsSection": Object { + "default": true, + "id": "homeAppsSection", + "layout": Object { + "vizOrder": Array [ + "my.app", + "my.app2", + ], + }, + "title": "Recently Added Apps", + "viz": Object { + "my.app": Object { + "id": "my.app", + "vizId": "VIZ:my.app", + }, + "my.app2": Object { + "id": "my.app2", + "vizId": "VIZ:my.app2", + }, + }, + }, + }, + }, + }, + }, + "site": Object { + "payload": Object { + "groupsOrder": Array [], + }, + }, + "visualizations": Object { + "VIZ:my.app": Object { + "businessApp": "my.app", + "vizConfig": Object { + "sap.app": Object { + "subTitle": "My App Description", + "title": "My App", + }, + "sap.flp": Object { + "target": Object { + "appId": "my.app", + "inboundId": "my-app", + "parameters": Array [ + Object { + "name": "sap-ui-tech-hint", + "value": "UI5", + }, + ], + }, + }, + }, + "vizType": "sap.ushell.StaticAppLauncher", + }, + "VIZ:my.app2": Object { + "businessApp": "my.app2", + "vizConfig": Object { + "sap.app": Object { + "subTitle": "My App Description 2", + "title": "My App 2", + }, + "sap.flp": Object { + "target": Object { + "appId": "my.app2", + "inboundId": "my-app2", + "parameters": Array [ + Object { + "name": "sap-ui-tech-hint", + "value": "UI5", + }, + ], + }, + }, + }, + "vizType": "sap.ushell.StaticAppLauncher", + }, + }, +} +`; + +exports[`cdm generate cdm without any apps 1`] = ` +Object { + "_version": "3.1.0", + "applications": Object {}, + "catalogs": Object { + "homeCatalog": Object { + "identification": Object { + "id": "homeCatalog", + "title": "Homepage Apps", + }, + "payload": Object { + "viz": Array [], + }, + }, + }, + "groups": Object {}, + "pages": Object { + "SAP_BASIS_PG_UI_MYHOME": Object { + "identification": Object { + "id": "SAP_BASIS_PG_UI_MYHOME", + }, + "payload": Object { + "layout": Object { + "sectionOrder": Array [ + "homeAppsSection", + ], + }, + "sections": Object { + "homeAppsSection": Object { + "default": true, + "id": "homeAppsSection", + "layout": Object { + "vizOrder": Array [], + }, + "title": "Recently Added Apps", + "viz": Object {}, + }, + }, + }, + }, + }, + "site": Object { + "payload": Object { + "groupsOrder": Array [], + }, + }, + "visualizations": Object {}, +} +`; 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 f2af935411..18a52a1b55 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 @@ -4,6 +4,7 @@ exports[`config createFlpTemplateConfig minimum settings 1`] = ` Object { "apps": Object {}, "basePath": "..", + "enhancedHomePage": false, "init": undefined, "locateReuseLibsScript": undefined, "ui5": Object { @@ -40,6 +41,7 @@ exports[`config createFlpTemplateConfig minimum settings with one reuse lib 1`] Object { "apps": Object {}, "basePath": "..", + "enhancedHomePage": false, "init": undefined, "locateReuseLibsScript": undefined, "ui5": Object { 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 e6f16b7f8c..509a2c6243 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 @@ -35,6 +35,7 @@ Object { }, }, "basePath": "..", + "enhancedHomePage": false, "init": undefined, "locateReuseLibsScript": false, "ui5": Object { @@ -97,6 +98,7 @@ Object { }, }, "basePath": "..", + "enhancedHomePage": false, "init": undefined, "locateReuseLibsScript": false, "ui5": Object { @@ -142,6 +144,7 @@ Object { }, }, "basePath": "..", + "enhancedHomePage": false, "init": undefined, "locateReuseLibsScript": false, "ui5": Object { @@ -187,6 +190,7 @@ Object { }, }, "basePath": "..", + "enhancedHomePage": false, "init": undefined, "locateReuseLibsScript": false, "ui5": Object { @@ -232,6 +236,7 @@ Object { }, }, "basePath": "..", + "enhancedHomePage": false, "init": undefined, "locateReuseLibsScript": false, "ui5": Object { @@ -277,6 +282,7 @@ Object { }, }, "basePath": "..", + "enhancedHomePage": false, "init": undefined, "locateReuseLibsScript": false, "ui5": Object { @@ -324,6 +330,7 @@ Object { }, }, "basePath": "..", + "enhancedHomePage": false, "init": undefined, "locateReuseLibsScript": false, "ui5": Object { @@ -522,6 +529,67 @@ exports[`FlpSandbox router - connect API GET default routes with connect API (us " `; +exports[`FlpSandbox router - connect API GET default routes with connect API when enhancedHomePage is enabled 1`] = ` +" + + + + + + + + Local FLP Sandbox + + + + + + + + + + + + + + + +" +`; + +exports[`FlpSandbox router - connect API GET default routes with connect API when enhancedHomePage is enabled 2`] = `"{\\"_version\\":\\"3.1.0\\",\\"catalogs\\":{\\"homeCatalog\\":{\\"identification\\":{\\"id\\":\\"homeCatalog\\",\\"title\\":\\"Homepage Apps\\"},\\"payload\\":{\\"viz\\":[\\"VIZ:test.fe.v2.app\\",\\"VIZ:test.fe.v2.other\\"]}}},\\"site\\":{\\"payload\\":{\\"groupsOrder\\":[]}},\\"groups\\":{},\\"visualizations\\":{\\"VIZ:test.fe.v2.app\\":{\\"vizType\\":\\"sap.ushell.StaticAppLauncher\\",\\"businessApp\\":\\"test.fe.v2.app\\",\\"vizConfig\\":{\\"sap.app\\":{\\"title\\":\\"My Simple App\\",\\"subTitle\\":\\"This is a very simple application.\\"},\\"sap.flp\\":{\\"target\\":{\\"appId\\":\\"test.fe.v2.app\\",\\"inboundId\\":\\"app-preview\\",\\"parameters\\":[{\\"name\\":\\"sap-ui-tech-hint\\",\\"value\\":\\"UI5\\"}]}}}},\\"VIZ:test.fe.v2.other\\":{\\"vizType\\":\\"sap.ushell.StaticAppLauncher\\",\\"businessApp\\":\\"test.fe.v2.other\\",\\"vizConfig\\":{\\"sap.app\\":{\\"title\\":\\"My Other App\\",\\"subTitle\\":\\"This is a very simple application.\\"},\\"sap.flp\\":{\\"target\\":{\\"appId\\":\\"test.fe.v2.other\\",\\"inboundId\\":\\"testfev2other-preview\\",\\"parameters\\":[{\\"name\\":\\"sap-ui-tech-hint\\",\\"value\\":\\"UI5\\"}]}}}}},\\"applications\\":{\\"test.fe.v2.app\\":{\\"sap.app\\":{\\"id\\":\\"test.fe.v2.app\\",\\"title\\":\\"My Simple App\\",\\"crossNavigation\\":{\\"inbounds\\":{\\"app-preview\\":{\\"semanticObject\\":\\"app\\",\\"action\\":\\"preview\\",\\"title\\":\\"My Simple App\\",\\"subTitle\\":\\"This is a very simple application.\\",\\"signature\\":{\\"additionalParameters\\":\\"allowed\\"}}}}},\\"sap.ui5\\":{\\"componentName\\":\\"test.fe.v2.app\\"},\\"sap.ui\\":{\\"technology\\":\\"UI5\\"},\\"sap.platform.runtime\\":{\\"componentProperties\\":{\\"url\\":\\"..\\",\\"asyncHints\\":{}}}},\\"test.fe.v2.other\\":{\\"sap.app\\":{\\"id\\":\\"test.fe.v2.other\\",\\"title\\":\\"My Other App\\",\\"crossNavigation\\":{\\"inbounds\\":{\\"testfev2other-preview\\":{\\"semanticObject\\":\\"testfev2other\\",\\"action\\":\\"preview\\",\\"title\\":\\"My Other App\\",\\"subTitle\\":\\"This is a very simple application.\\",\\"signature\\":{\\"additionalParameters\\":\\"allowed\\"}}}}},\\"sap.ui5\\":{\\"componentName\\":\\"test.fe.v2.other\\"},\\"sap.ui\\":{\\"technology\\":\\"UI5\\"},\\"sap.platform.runtime\\":{\\"componentProperties\\":{\\"url\\":\\"/yet/another/app\\",\\"asyncHints\\":{}}}}},\\"pages\\":{\\"SAP_BASIS_PG_UI_MYHOME\\":{\\"identification\\":{\\"id\\":\\"SAP_BASIS_PG_UI_MYHOME\\"},\\"payload\\":{\\"layout\\":{\\"sectionOrder\\":[\\"homeAppsSection\\"]},\\"sections\\":{\\"homeAppsSection\\":{\\"id\\":\\"homeAppsSection\\",\\"title\\":\\"Recently Added Apps\\",\\"default\\":true,\\"layout\\":{\\"vizOrder\\":[\\"test.fe.v2.app\\",\\"test.fe.v2.other\\"]},\\"viz\\":{\\"test.fe.v2.app\\":{\\"id\\":\\"test.fe.v2.app\\",\\"vizId\\":\\"VIZ:test.fe.v2.app\\"},\\"test.fe.v2.other\\":{\\"id\\":\\"test.fe.v2.other\\",\\"vizId\\":\\"VIZ:test.fe.v2.other\\"}}}}}}}}"`; + 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`] = ` @@ -614,7 +682,7 @@ exports[`FlpSandbox router default Qunit path test/unitTests.qunit.html 1`] = ` " `; -exports[`FlpSandbox router editor with config 1`] = ` +exports[`FlpSandbox router enhanced homepage disabled editor with config 1`] = ` " @@ -665,7 +733,7 @@ exports[`FlpSandbox router editor with config 1`] = ` @@ -692,64 +757,7 @@ exports[`FlpSandbox router editor with config 1`] = ` " `; -exports[`FlpSandbox router rta 1`] = `"Found. Redirecting to /my/rta.html?sap-ui-xx-viewCache=false&fiori-tools-rta-mode=true&sap-ui-rta-skip-flex-validation=true&sap-ui-xx-condense-changes=true"`; - -exports[`FlpSandbox router rta with adp instance - preview 1`] = ` -Object { - "apps": Object { - "app-preview": Object { - "additionalInformation": "SAPUI5.Component=myComponent", - "applicationType": "URL", - "description": "", - "title": "my.id", - "url": "..", - }, - "testfev2other-preview": Object { - "additionalInformation": "SAPUI5.Component=test.fe.v2.other", - "applicationType": "URL", - "description": "This is a very simple application.", - "title": "My Other App", - "url": "/yet/another/app", - }, - }, - "basePath": "..", - "init": undefined, - "locateReuseLibsScript": false, - "ui5": Object { - "bootstrapOptions": "", - "flex": Array [ - Object { - "connector": "LrepConnector", - "layers": Array [], - "url": "/sap/bc/lrep", - }, - Object { - "applyConnector": "open/ux/preview/client/flp/WorkspaceConnector", - "custom": true, - "writeConnector": "open/ux/preview/client/flp/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", - "test.fe.v2.other": "/yet/another/app", - }, - "theme": "sap_horizon", - }, -} -`; - -exports[`FlpSandbox router rta with adp instance - preview 2`] = ` +exports[`FlpSandbox router enhanced homepage disabled test/flp.html 1`] = ` " @@ -788,7 +796,7 @@ exports[`FlpSandbox router rta with adp instance - preview 2`] = ` } } }, - applications: {\\"app-preview\\":{\\"title\\":\\"my.id\\",\\"description\\":\\"\\",\\"additionalInformation\\":\\"SAPUI5.Component=myComponent\\",\\"applicationType\\":\\"URL\\",\\"url\\":\\"..\\",\\"applicationDependencies\\":{\\"manifest\\":{},\\"name\\":\\"descriptorName\\",\\"url\\":\\"http://sap.example\\",\\"asyncHints\\":{\\"requests\\":[]}}},\\"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\\"}} + 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\\"}} }; @@ -800,15 +808,15 @@ exports[`FlpSandbox router rta with adp instance - preview 2`] = ` @@ -926,120 +879,378 @@ exports[`FlpSandbox router rta with adp instance 2`] = ` window[\\"data-open-ux-preview-basePath\\"] = \\"..\\"; - + - - - " `; -exports[`FlpSandbox router rta with developerMode=true 1`] = ` -" +exports[`FlpSandbox router enhanced homepage disabled test/flp.html UI5 legacy-free 1`] = ` +" - - - - - - - Editing test.fe.v2.app - - - -
- - - - -" + + + + + + + Local FLP Sandbox + + + + + + + + + + + + + + + + + +" `; -exports[`FlpSandbox router rta with developerMode=true 2`] = `"Found. Redirecting to /my/editor.html.inner.html?sap-ui-xx-viewCache=false&fiori-tools-rta-mode=true&sap-ui-rta-skip-flex-validation=true&sap-ui-xx-condense-changes=true"`; +exports[`FlpSandbox router enhanced homepage disabled test/flp.html UI5 snapshot 1`] = ` +" + + + + + + + + Local FLP Sandbox + + + + + + + + + + + + + + + + + +" +`; + +exports[`FlpSandbox router enhanced homepage disabled test/flp.html missing sap-ui-xx-viewCache set to false 1`] = `"Found. Redirecting to /test/flp.html?sap-ui-xx-viewCache=false"`; + +exports[`FlpSandbox router enhanced homepage disabled test/flp.html sap-ui-xx-viewCache set to true 1`] = ` +" + + + + + + + + Local FLP Sandbox + + + + + + + + + + + + + + + + + +" +`; + +exports[`FlpSandbox router enhanced homepage enabled editor with config 1`] = ` +" + + + + + + + + Local FLP Sandbox + + + + + + + + + + + + + + + +" +`; + +exports[`FlpSandbox router enhanced homepage enabled test/cdm.json should return cdm when homepage is enabled 1`] = `"{\\"_version\\":\\"3.1.0\\",\\"catalogs\\":{\\"homeCatalog\\":{\\"identification\\":{\\"id\\":\\"homeCatalog\\",\\"title\\":\\"Homepage Apps\\"},\\"payload\\":{\\"viz\\":[\\"VIZ:test.fe.v2.app\\",\\"VIZ:test.fe.v2.other\\"]}}},\\"site\\":{\\"payload\\":{\\"groupsOrder\\":[]}},\\"groups\\":{},\\"visualizations\\":{\\"VIZ:test.fe.v2.app\\":{\\"vizType\\":\\"sap.ushell.StaticAppLauncher\\",\\"businessApp\\":\\"test.fe.v2.app\\",\\"vizConfig\\":{\\"sap.app\\":{\\"title\\":\\"My Simple App\\",\\"subTitle\\":\\"This is a very simple application.\\"},\\"sap.flp\\":{\\"target\\":{\\"appId\\":\\"test.fe.v2.app\\",\\"inboundId\\":\\"app-preview\\",\\"parameters\\":[{\\"name\\":\\"sap-ui-tech-hint\\",\\"value\\":\\"UI5\\"}]}}}},\\"VIZ:test.fe.v2.other\\":{\\"vizType\\":\\"sap.ushell.StaticAppLauncher\\",\\"businessApp\\":\\"test.fe.v2.other\\",\\"vizConfig\\":{\\"sap.app\\":{\\"title\\":\\"My Other App\\",\\"subTitle\\":\\"This is a very simple application.\\"},\\"sap.flp\\":{\\"target\\":{\\"appId\\":\\"test.fe.v2.other\\",\\"inboundId\\":\\"testfev2other-preview\\",\\"parameters\\":[{\\"name\\":\\"sap-ui-tech-hint\\",\\"value\\":\\"UI5\\"}]}}}}},\\"applications\\":{\\"test.fe.v2.app\\":{\\"sap.app\\":{\\"id\\":\\"test.fe.v2.app\\",\\"title\\":\\"My Simple App\\",\\"crossNavigation\\":{\\"inbounds\\":{\\"app-preview\\":{\\"semanticObject\\":\\"app\\",\\"action\\":\\"preview\\",\\"title\\":\\"My Simple App\\",\\"subTitle\\":\\"This is a very simple application.\\",\\"signature\\":{\\"additionalParameters\\":\\"allowed\\"}}}}},\\"sap.ui5\\":{\\"componentName\\":\\"test.fe.v2.app\\"},\\"sap.ui\\":{\\"technology\\":\\"UI5\\"},\\"sap.platform.runtime\\":{\\"componentProperties\\":{\\"url\\":\\"..\\",\\"asyncHints\\":{}}}},\\"test.fe.v2.other\\":{\\"sap.app\\":{\\"id\\":\\"test.fe.v2.other\\",\\"title\\":\\"My Other App\\",\\"crossNavigation\\":{\\"inbounds\\":{\\"testfev2other-preview\\":{\\"semanticObject\\":\\"testfev2other\\",\\"action\\":\\"preview\\",\\"title\\":\\"My Other App\\",\\"subTitle\\":\\"This is a very simple application.\\",\\"signature\\":{\\"additionalParameters\\":\\"allowed\\"}}}}},\\"sap.ui5\\":{\\"componentName\\":\\"test.fe.v2.other\\"},\\"sap.ui\\":{\\"technology\\":\\"UI5\\"},\\"sap.platform.runtime\\":{\\"componentProperties\\":{\\"url\\":\\"/yet/another/app\\",\\"asyncHints\\":{}}}}},\\"pages\\":{\\"SAP_BASIS_PG_UI_MYHOME\\":{\\"identification\\":{\\"id\\":\\"SAP_BASIS_PG_UI_MYHOME\\"},\\"payload\\":{\\"layout\\":{\\"sectionOrder\\":[\\"homeAppsSection\\"]},\\"sections\\":{\\"homeAppsSection\\":{\\"id\\":\\"homeAppsSection\\",\\"title\\":\\"Recently Added Apps\\",\\"default\\":true,\\"layout\\":{\\"vizOrder\\":[\\"test.fe.v2.app\\",\\"test.fe.v2.other\\"]},\\"viz\\":{\\"test.fe.v2.app\\":{\\"id\\":\\"test.fe.v2.app\\",\\"vizId\\":\\"VIZ:test.fe.v2.app\\"},\\"test.fe.v2.other\\":{\\"id\\":\\"test.fe.v2.other\\",\\"vizId\\":\\"VIZ:test.fe.v2.other\\"}}}}}}}}"`; + +exports[`FlpSandbox router enhanced homepage enabled test/flp.html 1`] = ` +" + + + + + + + + Local FLP Sandbox + + + + + + + + -exports[`FlpSandbox router rta with developerMode=true UI5 version 2.x 1`] = ` -" - - - - - - - - Editing test.fe.v2.app - - - -
- - - - -" + + + + + + +" `; -exports[`FlpSandbox router rta with developerMode=true UI5 version 2.x 2`] = ` +exports[`FlpSandbox router enhanced homepage enabled test/flp.html UI5 2.x 1`] = ` " @@ -1090,7 +1301,7 @@ exports[`FlpSandbox router rta with developerMode=true UI5 version 2.x 2`] = ` - - - " `; -exports[`FlpSandbox router rta with developerMode=true and plugin 1`] = ` +exports[`FlpSandbox router enhanced homepage enabled test/flp.html UI5 legacy-free 1`] = ` " @@ -1178,11 +1370,67 @@ exports[`FlpSandbox router rta with developerMode=true and plugin 1`] = ` window[\\"data-open-ux-preview-basePath\\"] = \\"..\\"; - + + + + + + + + + +" +`; + +exports[`FlpSandbox router enhanced homepage enabled test/flp.html UI5 snapshot 1`] = ` +" + + + + + + + + Local FLP Sandbox + + + + + + + - - - " `; -exports[`FlpSandbox router rta with editors path without leading "/" 1`] = `"Found. Redirecting to /without/slash/rta.html?sap-ui-xx-viewCache=false&fiori-tools-rta-mode=true&sap-ui-rta-skip-flex-validation=true&sap-ui-xx-condense-changes=true"`; +exports[`FlpSandbox router enhanced homepage enabled test/flp.html missing sap-ui-xx-viewCache set to false 1`] = `"Found. Redirecting to /test/flp.html?sap-ui-xx-viewCache=false"`; -exports[`FlpSandbox router rta with url parameters 1`] = ` +exports[`FlpSandbox router enhanced homepage enabled test/flp.html sap-ui-xx-viewCache set to true 1`] = ` +" + + + + + + + + Local FLP Sandbox + + + + + + + + + + + + + + + +" +`; + +exports[`FlpSandbox router enhanced homepage enabled test/flp.html should fallback to old homepage if ui5 version is less than 1.123.0 1`] = ` " @@ -1278,7 +1567,7 @@ exports[`FlpSandbox router rta with url parameters 1`] = ` @@ -1300,12 +1587,69 @@ exports[`FlpSandbox router rta with url parameters 1`] = ` - " `; -exports[`FlpSandbox router test/flp.html 1`] = ` +exports[`FlpSandbox router rta 1`] = `"Found. Redirecting to /my/rta.html?sap-ui-xx-viewCache=false&fiori-tools-rta-mode=true&sap-ui-rta-skip-flex-validation=true&sap-ui-xx-condense-changes=true"`; + +exports[`FlpSandbox router rta with adp instance - preview 1`] = ` +Object { + "apps": Object { + "app-preview": Object { + "additionalInformation": "SAPUI5.Component=myComponent", + "applicationType": "URL", + "description": "", + "title": "my.id", + "url": "..", + }, + "testfev2other-preview": Object { + "additionalInformation": "SAPUI5.Component=test.fe.v2.other", + "applicationType": "URL", + "description": "This is a very simple application.", + "title": "My Other App", + "url": "/yet/another/app", + }, + }, + "basePath": "..", + "enhancedHomePage": false, + "init": undefined, + "locateReuseLibsScript": false, + "ui5": Object { + "bootstrapOptions": "", + "flex": Array [ + Object { + "connector": "LrepConnector", + "layers": Array [], + "url": "/sap/bc/lrep", + }, + Object { + "applyConnector": "open/ux/preview/client/flp/WorkspaceConnector", + "custom": true, + "writeConnector": "open/ux/preview/client/flp/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", + "test.fe.v2.other": "/yet/another/app", + }, + "theme": "sap_horizon", + }, +} +`; + +exports[`FlpSandbox router rta with adp instance - preview 2`] = ` " @@ -1344,7 +1688,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\\":\\"..\\"},\\"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\\"}} + applications: {\\"app-preview\\":{\\"title\\":\\"my.id\\",\\"description\\":\\"\\",\\"additionalInformation\\":\\"SAPUI5.Component=myComponent\\",\\"applicationType\\":\\"URL\\",\\"url\\":\\"..\\",\\"applicationDependencies\\":{\\"manifest\\":{},\\"name\\":\\"descriptorName\\",\\"url\\":\\"http://sap.example\\",\\"asyncHints\\":{\\"requests\\":[]}}},\\"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\\"}} }; @@ -1356,15 +1700,15 @@ exports[`FlpSandbox router test/flp.html 1`] = ` + + + +" +`; + +exports[`FlpSandbox router rta with developerMode=true 2`] = `"Found. Redirecting to /my/editor.html.inner.html?sap-ui-xx-viewCache=false&fiori-tools-rta-mode=true&sap-ui-rta-skip-flex-validation=true&sap-ui-xx-condense-changes=true"`; + +exports[`FlpSandbox router rta with developerMode=true UI5 version 2.x 1`] = ` +" + + + + + + + + Editing test.fe.v2.app + + + +
+ + + + +" +`; + +exports[`FlpSandbox router rta with developerMode=true UI5 version 2.x 2`] = ` " @@ -1580,7 +1991,7 @@ exports[`FlpSandbox router test/flp.html UI5 2.x 1`] = ` + + + " `; -exports[`FlpSandbox router test/flp.html UI5 legacy-free 1`] = ` +exports[`FlpSandbox router rta with developerMode=true and plugin 1`] = ` " @@ -1649,33 +2079,56 @@ exports[`FlpSandbox router test/flp.html UI5 legacy-free 1`] = ` window[\\"data-open-ux-preview-basePath\\"] = \\"..\\"; - + + + + " `; -exports[`FlpSandbox router test/flp.html UI5 snapshot 1`] = ` +exports[`FlpSandbox router rta with editors path without leading "/" 1`] = `"Found. Redirecting to /without/slash/rta.html?sap-ui-xx-viewCache=false&fiori-tools-rta-mode=true&sap-ui-rta-skip-flex-validation=true&sap-ui-xx-condense-changes=true"`; + +exports[`FlpSandbox router rta with url parameters 1`] = ` " @@ -1726,7 +2179,7 @@ exports[`FlpSandbox router test/flp.html UI5 snapshot 1`] = ` @@ -1746,13 +2201,68 @@ exports[`FlpSandbox router test/flp.html UI5 snapshot 1`] = ` + " `; -exports[`FlpSandbox router test/flp.html missing sap-ui-xx-viewCache set to false 1`] = `"Found. Redirecting to /test/flp.html?sap-ui-xx-viewCache=false"`; +exports[`FlpSandbox router test/flp.html UI5 1.71 with asyncHints.requests 1`] = ` +Object { + "apps": Object { + "app-preview": Object { + "additionalInformation": "SAPUI5.Component=myComponent", + "applicationType": "URL", + "description": "", + "title": "my.id", + "url": "..", + }, + "testfev2other-preview": Object { + "additionalInformation": "SAPUI5.Component=test.fe.v2.other", + "applicationType": "URL", + "description": "This is a very simple application.", + "title": "My Other App", + "url": "/yet/another/app", + }, + }, + "basePath": "..", + "enhancedHomePage": false, + "init": undefined, + "locateReuseLibsScript": false, + "ui5": Object { + "bootstrapOptions": "", + "flex": Array [ + Object { + "connector": "LrepConnector", + "layers": Array [], + "url": "/sap/bc/lrep", + }, + Object { + "applyConnector": "open/ux/preview/client/flp/WorkspaceConnector", + "custom": true, + "writeConnector": "open/ux/preview/client/flp/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", + "test.fe.v2.other": "/yet/another/app", + }, + "theme": "sap_horizon", + }, +} +`; -exports[`FlpSandbox router test/flp.html sap-ui-xx-viewCache set to true 1`] = ` +exports[`FlpSandbox router test/flp.html UI5 1.71 with asyncHints.requests 2`] = ` " @@ -1791,7 +2301,7 @@ exports[`FlpSandbox router test/flp.html sap-ui-xx-viewCache set to true 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\\":\\"..\\"},\\"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\\"}} + applications: {\\"app-preview\\":{\\"title\\":\\"my.id\\",\\"description\\":\\"\\",\\"additionalInformation\\":\\"SAPUI5.Component=myComponent\\",\\"applicationType\\":\\"URL\\",\\"url\\":\\"..\\",\\"applicationDependencies\\":{\\"manifest\\":{},\\"name\\":\\"descriptorName\\",\\"url\\":\\"http://sap.example\\",\\"asyncHints\\":{\\"requests\\":[]}}},\\"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\\"}} }; @@ -1803,26 +2313,45 @@ exports[`FlpSandbox router test/flp.html sap-ui-xx-viewCache set to true 1`] = ` + + + " `; diff --git a/packages/preview-middleware/test/unit/base/cdm.test.ts b/packages/preview-middleware/test/unit/base/cdm.test.ts new file mode 100644 index 0000000000..baf7323566 --- /dev/null +++ b/packages/preview-middleware/test/unit/base/cdm.test.ts @@ -0,0 +1,31 @@ +import { generateCdm } from '../../../src/base/cdm'; +import type { TemplateConfig } from '../../../src/base/config'; + +describe('cdm', () => { + test('generate cdm without any apps', () => { + const cdm = generateCdm(); + expect(cdm).toMatchSnapshot(); + }); + + test('generate cdm with additional apps', () => { + const apps = { + 'my-app': { + title: 'My App', + url: '/my/app', + applicationType: 'URL', + description: 'My App Description', + additionalInformation: 'SAPUI5.Component=my.app' + }, + 'my-app2': { + title: 'My App 2', + url: '/my/app2', + applicationType: 'URL', + description: 'My App Description 2', + additionalInformation: 'SAPUI5.Component=my.app2' + } + } as TemplateConfig['apps']; + + const cdm = generateCdm(apps); + expect(cdm).toMatchSnapshot(); + }); +}); diff --git a/packages/preview-middleware/test/unit/base/flp.test.ts b/packages/preview-middleware/test/unit/base/flp.test.ts index 01f303e521..8a70a447b8 100644 --- a/packages/preview-middleware/test/unit/base/flp.test.ts +++ b/packages/preview-middleware/test/unit/base/flp.test.ts @@ -64,6 +64,7 @@ describe('FlpSandbox', () => { describe('constructor', () => { test('default (no) config', () => { const flp = new FlpSandbox({}, mockProject, mockUtils, logger); + expect(flp.flpConfig.enhancedHomePage).toBeFalsy(); expect(flp.flpConfig.path).toBe('/test/flp.html'); expect(flp.flpConfig.apps).toBeDefined(); expect(flp.flpConfig.apps).toHaveLength(0); @@ -73,6 +74,7 @@ describe('FlpSandbox', () => { test('advanced config', () => { const flpConfig: FlpConfig = { + enhancedHomePage: true, path: 'my/custom/path', intent: { object: 'movie', action: 'start' }, theme: 'sap_fiori_3', @@ -84,6 +86,7 @@ describe('FlpSandbox', () => { ] }; const flp = new FlpSandbox({ flp: flpConfig }, mockProject, mockUtils, logger); + expect(flp.flpConfig.enhancedHomePage).toBeTruthy(); expect(flp.flpConfig.path).toBe(`/${flpConfig.path}`); expect(flp.flpConfig.apps).toEqual(flpConfig.apps); expect(flp.flpConfig.intent).toStrictEqual({ object: 'movie', action: 'start' }); @@ -270,6 +273,7 @@ describe('FlpSandbox', () => { let server!: SuperTest; const mockConfig = { flp: { + enhancedHomePage: false, apps: [ { target: '/yet/another/app', @@ -317,71 +321,120 @@ describe('FlpSandbox', () => { fetchMock.mockRestore(); }); - beforeAll(async () => { - const flp = new FlpSandbox( - mockConfig as unknown as Partial, - mockProject, - mockUtils, - logger - ); + const setupMiddleware = async (mockConfig: Partial) => { + const flp = new FlpSandbox(mockConfig, mockProject, mockUtils, logger); const manifest = JSON.parse(readFileSync(join(fixtures, 'simple-app/webapp/manifest.json'), 'utf-8')); await flp.init(manifest); const app = express(); app.use(flp.router); - server = await supertest(app); - }); - - test('test/flp.html UI5 2.x', async () => { - const jsonSpy = () => Promise.resolve({ libraries: [{ name: 'sap.ui.core', version: '2.0.0' }] }); - fetchMock.mockResolvedValue({ - json: jsonSpy, - text: jest.fn(), - ok: true - }); - const response = await server.get('/test/flp.html?sap-ui-xx-viewCache=false').expect(200); - expect(response.text).toMatchSnapshot(); - }); - - test('test/flp.html UI5 legacy-free', async () => { - const jsonSpy = () => - Promise.resolve({ libraries: [{ name: 'sap.ui.core', version: '1.136.0-legacy-free' }] }); - fetchMock.mockResolvedValue({ - json: jsonSpy, - text: jest.fn(), - ok: true - }); - const response = await server.get('/test/flp.html?sap-ui-xx-viewCache=false').expect(200); - expect(response.text).toMatchSnapshot(); - }); + server = supertest(app); + }; - test('test/flp.html UI5 snapshot', async () => { - const jsonSpy = () => - Promise.resolve({ libraries: [{ name: 'sap.ui.core', version: '1.136.0-SNAPSHOT' }] }); - fetchMock.mockResolvedValue({ - json: jsonSpy, - text: jest.fn(), - ok: true + beforeAll(() => setupMiddleware(mockConfig as MiddlewareConfig)); + + const runTestsWithHomepageToggle = (enableEnhancedHomePage: boolean = false) => { + describe(`enhanced homepage ${enableEnhancedHomePage ? 'enabled' : 'disabled'}`, () => { + afterEach(() => { + fetchMock.mockRestore(); + }); + + beforeEach(() => + setupMiddleware({ + ...mockConfig, + flp: { ...mockConfig.flp, enhancedHomePage: enableEnhancedHomePage } + } as MiddlewareConfig) + ); + + test('test/flp.html UI5 2.x', async () => { + const jsonSpy = () => Promise.resolve({ libraries: [{ name: 'sap.ui.core', version: '2.0.0' }] }); + fetchMock.mockResolvedValue({ + json: jsonSpy, + text: jest.fn(), + ok: true + }); + const response = await server.get('/test/flp.html?sap-ui-xx-viewCache=false').expect(200); + expect(response.text).toMatchSnapshot(); + }); + + test('test/flp.html UI5 legacy-free', async () => { + const jsonSpy = () => + Promise.resolve({ libraries: [{ name: 'sap.ui.core', version: '1.136.0-legacy-free' }] }); + fetchMock.mockResolvedValue({ + json: jsonSpy, + text: jest.fn(), + ok: true + }); + const response = await server.get('/test/flp.html?sap-ui-xx-viewCache=false').expect(200); + expect(response.text).toMatchSnapshot(); + }); + + test('test/flp.html UI5 snapshot', async () => { + const jsonSpy = () => + Promise.resolve({ libraries: [{ name: 'sap.ui.core', version: '1.136.0-SNAPSHOT' }] }); + fetchMock.mockResolvedValue({ + json: jsonSpy, + text: jest.fn(), + ok: true + }); + const response = await server.get('/test/flp.html?sap-ui-xx-viewCache=false').expect(200); + expect(response.text).toMatchSnapshot(); + }); + + test('test/flp.html', async () => { + const response = await server + .get('/test/flp.html?sap-ui-xx-viewCache=false#app-preview') + .expect(200); + expect(response.text).toMatchSnapshot(); + }); + + test('test/flp.html sap-ui-xx-viewCache set to true', async () => { + const response = await server.get('/test/flp.html?sap-ui-xx-viewCache=true').expect(200); + expect(response.text).toMatchSnapshot(); + }); + + test('test/flp.html missing sap-ui-xx-viewCache set to false', async () => { + const response = await server.get('/test/flp.html').expect(302); + expect(response.text).toMatchSnapshot(); + }); + + test('editor with config', async () => { + const response = await server.get('/test/flp.html?sap-ui-xx-viewCache=false').expect(200); + expect(response.text).toMatchSnapshot(); + }); + + test(`test/cdm.json should ${enableEnhancedHomePage ? 'return cdm' : 'fail'} when homepage is ${ + enableEnhancedHomePage ? 'enabled' : 'disabled' + }`, async () => { + const response = await server.get('/cdm.json').expect(enableEnhancedHomePage ? 200 : 404); + if (enableEnhancedHomePage) { + expect(response.text).toMatchSnapshot(); + } + }); + + // enhanced homepage related tests + if (enableEnhancedHomePage) { + test('test/flp.html should fallback to old homepage if ui5 version is less than 1.123.0', async () => { + const jsonSpy = () => + Promise.resolve({ libraries: [{ name: 'sap.ui.core', version: '1.120.0' }] }); + fetchMock.mockResolvedValue({ + json: jsonSpy, + text: jest.fn(), + ok: true + }); + const response = await server.get('/test/flp.html?sap-ui-xx-viewCache=false').expect(200); + expect(response.text).toMatchSnapshot(); + }); + } }); - const response = await server.get('/test/flp.html?sap-ui-xx-viewCache=false').expect(200); - expect(response.text).toMatchSnapshot(); - }); - - test('test/flp.html', async () => { - const response = await server.get('/test/flp.html?sap-ui-xx-viewCache=false#app-preview').expect(200); - expect(response.text).toMatchSnapshot(); - }); + }; - test('test/flp.html sap-ui-xx-viewCache set to true', async () => { - const response = await server.get('/test/flp.html?sap-ui-xx-viewCache=true').expect(200); - expect(response.text).toMatchSnapshot(); - }); + // run tests with enhanced homepage enabled + runTestsWithHomepageToggle(true); - test('test/flp.html missing sap-ui-xx-viewCache set to false', async () => { - const response = await server.get('/test/flp.html').expect(302); - expect(response.text).toMatchSnapshot(); - }); + // run tests with homepage disabled + runTestsWithHomepageToggle(false); test('rta', async () => { const response = await server.get('/my/rta.html').expect(302); @@ -578,11 +631,6 @@ describe('FlpSandbox', () => { .expect(400); }); - test('editor with config', async () => { - const response = await server.get('/test/flp.html?sap-ui-xx-viewCache=false').expect(200); - expect(response.text).toMatchSnapshot(); - }); - test('default Qunit path test/unitTests.qunit.html', async () => { const response = await server.get('/test/unitTests.qunit.html').expect(200); expect(response.text).toMatchSnapshot(); @@ -786,6 +834,7 @@ describe('FlpSandbox', () => { expect(logger.info).toBeCalledWith( 'HTML file returned at /test/existingFlp.html is loaded from the file system.' ); + await server.get('/cdm.json').expect(404); }); }); @@ -793,6 +842,7 @@ describe('FlpSandbox', () => { let server!: SuperTest; const mockConfig = { flp: { + enhancedHomePage: false, apps: [ { target: '/yet/another/app', @@ -811,6 +861,8 @@ describe('FlpSandbox', () => { } ] }; + const manifest = JSON.parse(readFileSync(join(fixtures, 'simple-app/webapp/manifest.json'), 'utf-8')); + test('GET default routes with connect API (used by karma test runner)', async () => { const flp = new FlpSandbox( mockConfig as unknown as Partial, @@ -818,7 +870,6 @@ describe('FlpSandbox', () => { mockUtils, logger ); - const manifest = JSON.parse(readFileSync(join(fixtures, 'simple-app/webapp/manifest.json'), 'utf-8')); await flp.init(manifest); const app = connect(); @@ -833,6 +884,27 @@ describe('FlpSandbox', () => { expect(response.text).toMatchSnapshot(); response = await server.get('/test/integration/opaTests.qunit.html').expect(200); expect(response.text).toMatchSnapshot(); + await server.get('/cdm.json').expect(404); + }); + + test('GET default routes with connect API when enhancedHomePage is enabled', async () => { + mockConfig.flp.enhancedHomePage = true; + const flp = new FlpSandbox( + mockConfig as unknown as Partial, + mockProject, + mockUtils, + logger + ); + await flp.init(manifest); + + const app = connect(); + app.use(flp.router as unknown as connect.Server); + + server = await supertest(app); + let response = await server.get('/test/flp.html').expect(200); + expect(response.text).toMatchSnapshot(); + response = await server.get('/cdm.json').expect(200); + expect(response.text).toMatchSnapshot(); }); });