Skip to content

Commit

Permalink
Preview support for UI5 2.x (#2192)
Browse files Browse the repository at this point in the history
* dynamic ushell src

* require sap ushell

* review comments

* Create preview-2.0.md

* add window type

* eslint

* fix: eslint

* fix: eslint, review comments

* fix: run-time error with bootstrap

* fix: move call of initConnectors

* unit test for bootstrap

* fix: sap.ui.define wrapper

* cleanup of container type

* obsolete code

* added error handling if version info is not found

* reverting change in ui5-proxy

* eslint fixes

* fix erroneous usage of sap-ui-version.json

* refactoring of VersionInfo.load() usage

---------

Co-authored-by: Tobias Queck <[email protected]>
Co-authored-by: D048415 <[email protected]>
Co-authored-by: Klaus Keller <[email protected]>
Co-authored-by: Dominik Heim <[email protected]>
  • Loading branch information
5 people authored Jul 31, 2024
1 parent 13d5ee5 commit ab2e5a0
Show file tree
Hide file tree
Showing 23 changed files with 232 additions and 113 deletions.
6 changes: 6 additions & 0 deletions .changeset/preview-2.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@sap-ux-private/preview-middleware-client": patch
"@sap-ux/preview-middleware": patch
---

Preview support for UI5 2.x
3 changes: 2 additions & 1 deletion packages/preview-middleware-client/src/adp/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ import VersionInfo from 'sap/ui/VersionInfo';
import { getUI5VersionValidationMessage } from './ui5-version-utils';
import UI5Element from 'sap/ui/dt/Element';
import { getError } from '../cpe/error-utils';
import type {SingleVersionInfo} from '../../types/global';

export default async function (rta: RuntimeAuthoring) {
const { version } = (await VersionInfo.load()) as { version: string };
const version = (await VersionInfo.load({library:'sap.ui.core'}) as SingleVersionInfo)?.version;
const versionParts = version.split('.');
const minor = parseInt(versionParts[1], 10);
const flexSettings = rta.getFlexSettings();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ExternalAction, storageFileChanged } from '@sap-ux-private/control-property-editor-common';
import { ActionSenderFunction } from './types';
import VersionInfo from 'sap/ui/VersionInfo';
import type {SingleVersionInfo} from '../../types/global';

/**
* A Class of WorkspaceConnectorService
Expand All @@ -12,7 +13,7 @@ export class WorkspaceConnectorService {
* @param sendAction action sender function
*/
public async init(sendAction: ActionSenderFunction): Promise<void> {
const { version } = (await VersionInfo.load()) as { version: string };
const version = (await VersionInfo.load({library:'sap.ui.core'}) as SingleVersionInfo)?.version;
const versionParts = version.split('.');
const minor = parseInt(versionParts[1], 10);
if (minor > 72) {
Expand Down
3 changes: 2 additions & 1 deletion packages/preview-middleware-client/src/cpe/outline/nodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { OutlineViewNode } from 'sap/ui/rta/command/OutlineService';
import type { Scenario } from 'sap/ui/fl/Scenario';
import { isEditable, isReuseComponent } from './utils';
import VersionInfo from 'sap/ui/VersionInfo';
import type {SingleVersionInfo} from '../../../types/global';

interface AdditionalData {
text?: string;
Expand Down Expand Up @@ -87,7 +88,7 @@ export async function transformNodes(
): Promise<OutlineNode[]> {
const stack = [...input];
const items: OutlineNode[] = [];
const { version } = (await VersionInfo.load()) as { version: string };
const version = (await VersionInfo.load({library:'sap.ui.core'}) as SingleVersionInfo)?.version;
const versionParts = version.split('.');
const minor = parseInt(versionParts[1], 10);
while (stack.length) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import merge from 'sap/base/util/merge';
import ObjectStorageConnector from 'sap/ui/fl/write/api/connectors/ObjectStorageConnector';
import Layer from 'sap/ui/fl/Layer';
import VersionInfo from 'sap/ui/VersionInfo';
import type {SingleVersionInfo} from '../../types/global';
import { CHANGES_API_PATH, FlexChange, getFlexSettings } from './common';

const connector = merge({}, ObjectStorageConnector, {
Expand Down Expand Up @@ -69,8 +70,8 @@ const connector = merge({}, ObjectStorageConnector, {
loadFeatures: async function () {
const features = await ObjectStorageConnector.loadFeatures();

const ui5Version = (await VersionInfo.load()) as { version: string };
const [majorVersion, minorVersion] = ui5Version.version.split('.').map((v: string) => parseInt(v, 10));
const version = (await VersionInfo.load({library:'sap.ui.core'}) as SingleVersionInfo)?.version;
const [majorVersion, minorVersion] = version.split('.').map((v: string) => parseInt(v, 10));
features.isVariantAdaptationEnabled = majorVersion >= 1 && minorVersion >= 90;
const settings = getFlexSettings();
if (settings?.developerMode) {
Expand Down
38 changes: 38 additions & 0 deletions packages/preview-middleware-client/src/flp/bootstrap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment,
@typescript-eslint/no-unsafe-member-access,
@typescript-eslint/no-unsafe-assignment,
@typescript-eslint/no-unsafe-argument,
no-console */

/**
* Calculates the script content for accessing the right sap/ushell/bootstrap sandbox.
* @param fnCallback {Function} The callback function to be executed after the bootstrap is loaded.
*/
async function ushellBootstrap(fnCallback) {
let src = '/test-resources/sap/ushell/bootstrap/sandbox.js';
try {
const response = await fetch('/resources/sap-ui-version.json');
const json = await response.json();
const version = json?.libraries?.find((lib) => lib.name === 'sap.ui.core')?.version;
const major = version ? parseInt(version.split('.')[0], 10) : 2;
if (major >= 2) {
src = '/resources/sap/ushell/bootstrap/sandbox2.js';
}
} catch (error) {
console.warn('Failed to fetch sap-ui-version.json. Assuming it is a 1.x version.');
}

// eslint-disable-next-line fiori-custom/sap-no-dom-access,fiori-custom/sap-browser-api-warning
const shellBootstrap = document.getElementById('sap-ushell-bootstrap');
if (shellBootstrap) {
shellBootstrap.onload = () => {
window['sap-ui-config']['xx-bootTask'](fnCallback);
};
shellBootstrap.setAttribute('src', src);
}
}

// eslint-disable-next-line fiori-custom/sap-no-global-define
window['sap-ui-config'] = {
'xx-bootTask': ushellBootstrap
};
24 changes: 19 additions & 5 deletions packages/preview-middleware-client/src/flp/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ import IconPool from 'sap/ui/core/IconPool';
import ResourceBundle from 'sap/base/i18n/ResourceBundle';
import AppState from 'sap/ushell/services/AppState';
import { getManifestAppdescr } from '../adp/api-handler';
import VersionInfo from 'sap/ui/VersionInfo';
import { getError } from '../cpe/error-utils';
import initConnectors from './initConnectors';
import type {SingleVersionInfo} from '../../types/global';

/**
* SAPUI5 delivered namespaces from https://ui5.sap.com/#/api/sap
Expand Down Expand Up @@ -264,16 +268,16 @@ export async function init({
customInit?: string | null;
}): Promise<void> {
const urlParams = new URLSearchParams(window.location.search);
const container = sap?.ushell?.Container ?? (sap.ui.require('sap/ushell/Container') as typeof sap.ushell.Container);
const container = sap?.ushell?.Container ?? sap.ui.require('sap/ushell/Container');
let scenario: string = '';
const version = (await VersionInfo.load({library:'sap.ui.core'}) as SingleVersionInfo)?.version;
// Register RTA if configured
if (flex) {
const flexSettings = JSON.parse(flex) as FlexSettings;
scenario = flexSettings.scenario;
container.attachRendererCreatedEvent(async function () {
const lifecycleService = await container.getServiceAsync<AppLifeCycle>('AppLifeCycle');
lifecycleService.attachAppLoaded((event) => {
const version = sap.ui.version;
const minor = parseInt(version.split('.')[1], 10);
const view = event.getParameter('componentInstance');
const flexSettings = JSON.parse(flex) as FlexSettings;
Expand Down Expand Up @@ -317,6 +321,9 @@ export async function init({
await registerComponentDependencyPaths(JSON.parse(appUrls), urlParams);
}

// Load rta connector
await initConnectors();

// Load custom initialization module
if (customInit) {
sap.ui.require([customInit]);
Expand All @@ -326,15 +333,22 @@ export async function init({
const resourceBundle = await loadI18nResourceBundle(scenario as Scenario);
setI18nTitle(resourceBundle);
registerSAPFonts();
const renderer = await container.createRenderer(undefined, true);
const major = version ? parseInt(version.split('.')[0], 10) : 2;

const renderer =
major < 2
? await container.createRenderer(undefined, true)
: await container.createRendererInternal(undefined, true);
renderer.placeAt('content');
}

const bootstrapConfig = document.getElementById('sap-ui-bootstrap');
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')
}).catch(() => Log.error('Sandbox initialization failed.'));
}).catch((e) => {
const error = getError(e);
Log.error('Sandbox initialization failed: ' + error.message);
});
}
16 changes: 9 additions & 7 deletions packages/preview-middleware-client/src/flp/initConnectors.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import VersionInfo from 'sap/ui/VersionInfo';
import type {SingleVersionInfo} from '../../types/global';

/**
* Initializes UI5 connectors based on the current UI5 version.
*
Expand All @@ -7,13 +10,14 @@
*
* @example
* intiConnectors(); // Simply call the function without any arguments.
* @returns {void}
*/
export function initConnectors(): void {
const version = sap.ui.version;
const minor = parseInt(version.split('.')[1], 10);
export default async function initConnectors(): Promise<void> {
const version = (await VersionInfo.load({library:'sap.ui.core'}) as SingleVersionInfo)?.version;
const versionArray = version ? version.split('.') : ['2', '99'];
const minor = parseInt(versionArray[1], 10);
const major = parseInt(versionArray[0], 10);

if (minor < 72) {
if (major === 1 && minor < 72) {
sap.ui.require(['open/ux/preview/client/flp/enableFakeConnector'], function (enableFakeConnector: () => void) {
enableFakeConnector();
});
Expand All @@ -26,5 +30,3 @@ export function initConnectors(): void {
);
}
}

initConnectors();
1 change: 1 addition & 0 deletions packages/preview-middleware-client/test/__mock__/window.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export const sapMock = {
ushell: {
Container: {
createRenderer: jest.fn().mockReturnValue({ placeAt: jest.fn() }),
createRendererInternal: jest.fn().mockReturnValue({ placeAt: jest.fn() }),
attachRendererCreatedEvent: jest.fn(),
getServiceAsync: jest.fn()
}
Expand Down
8 changes: 4 additions & 4 deletions packages/preview-middleware-client/test/unit/adp/init.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ describe('adp', () => {
return { dispose: jest.fn(), sendAction: jest.fn() };
});
const enableTelemetry = jest.spyOn(common, 'enableTelemetry');
VersionInfo.load.mockResolvedValue({ version: '1.118.1' });
VersionInfo.load.mockResolvedValue({name: 'sap.ui.core', version: '1.118.1' });

await init(rtaMock as unknown as RuntimeAuthoring);

Expand Down Expand Up @@ -93,7 +93,7 @@ describe('adp', () => {
return { dispose: jest.fn(), sendAction: sendActionMock };
});

VersionInfo.load.mockResolvedValue({ version: '1.70.0' });
VersionInfo.load.mockResolvedValue({name: 'sap.ui.core', version: '1.70.0' });

await init(rtaMock as unknown as RuntimeAuthoring);

Expand Down Expand Up @@ -133,7 +133,7 @@ describe('adp', () => {
'application-app-preview-component---fin.ar.lineitems.display.appView': mockUI5Element
});

VersionInfo.load.mockResolvedValue({ version: '1.123.1' });
VersionInfo.load.mockResolvedValue({name: 'sap.ui.core', version: '1.123.1' });

await init(rtaMock as unknown as RuntimeAuthoring);

Expand Down Expand Up @@ -172,7 +172,7 @@ describe('adp', () => {

Element.registry.filter.mockReturnValue([mockUI5Element]);

VersionInfo.load.mockResolvedValue({ version: '1.118.1' });
VersionInfo.load.mockResolvedValue({name: 'sap.ui.core', version: '1.118.1' });

await init(rtaMock as unknown as RuntimeAuthoring);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ describe('connector-service', () => {
fetchMock.mockRestore();
});
test('init - ui5 > v1.72', async () => {
VersionInfo.load.mockResolvedValue({ version: '1.120.4' });
VersionInfo.load.mockResolvedValue({ name: 'sap.ui.core', version: '1.120.4' });
const wsConnector = new WorkspaceConnectorService();
await wsConnector.init(sendActionMock);

Expand All @@ -25,7 +25,7 @@ describe('connector-service', () => {
});

test('init - ui5 < v1.72', async () => {
VersionInfo.load.mockResolvedValue({ version: '1.71.67' });
VersionInfo.load.mockResolvedValue({name: 'sap.ui.core', version: '1.71.67' });
const wsConnector = new WorkspaceConnectorService();
await wsConnector.init(sendActionMock);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ describe('outline nodes', () => {
});

beforeAll(() => {
VersionInfo.load.mockResolvedValue({ version: '1.118.1' });
VersionInfo.load.mockResolvedValue({name: 'sap.ui.core', version: '1.118.1' });
})

describe('transformNodes', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,19 +97,19 @@ describe('flp/WorkspaceConnector', () => {
});

test('version >= 1.90, no developerMode', async () => {
VersionInfo.load.mockResolvedValueOnce({ version: '1.118.1' });
VersionInfo.load.mockResolvedValueOnce({name: 'sap.ui.core', version: '1.118.1' });
const features = await connector.loadFeatures();
expect(features.isVariantAdaptationEnabled).toBe(true);
});

test('version < 1.90', async () => {
VersionInfo.load.mockResolvedValueOnce({ version: '1.89.3' });
VersionInfo.load.mockResolvedValueOnce({name: 'sap.ui.core', version: '1.89.3' });
const features = await connector.loadFeatures();
expect(features.isVariantAdaptationEnabled).toBe(false);
});

test('version >= 1.90, developerMode=true', async () => {
VersionInfo.load.mockResolvedValueOnce({ version: '1.118.1' });
VersionInfo.load.mockResolvedValueOnce({name: 'sap.ui.core', version: '1.118.1' });
documentMock.getElementById.mockReturnValueOnce({
getAttribute: () => JSON.stringify({ developerMode: true })
});
Expand All @@ -118,7 +118,7 @@ describe('flp/WorkspaceConnector', () => {
});

test('scenario=ADAPTATION_PROJECT', async () => {
VersionInfo.load.mockResolvedValueOnce({ version: '1.118.1' });
VersionInfo.load.mockResolvedValueOnce({name: 'sap.ui.core', version: '1.118.1' });
documentMock.getElementById.mockReturnValueOnce({
getAttribute: () => JSON.stringify({ scenario: 'ADAPTATION_PROJECT' })
});
Expand Down
47 changes: 47 additions & 0 deletions packages/preview-middleware-client/test/unit/flp/bootstrap.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { documentMock } from 'mock/window';
import '../../../src/flp/bootstrap';
import { Window } from '../../../types/global';

describe('flp/ushellBootstrap', () => {
const htmlElement = {
onload: jest.fn(),
setAttribute: jest.fn()
};
documentMock.getElementById.mockReturnValue(htmlElement);
const fetchMock = jest.spyOn(global, 'fetch');

const ushellBootstrap = (window as unknown as Window)['sap-ui-config']['xx-bootTask'];

afterEach(() => {
jest.clearAllMocks();
});

test('xx-boottask defined', () => {
expect(ushellBootstrap).toBeDefined();
});

test('ushell src when ui5 version is 1.x', async () => {
fetchMock.mockResolvedValueOnce({
json: () => Promise.resolve({ libraries: [{name: 'sap.ui.core', version: '1.126.0'}] })
} as jest.Mocked<Response>);

await ushellBootstrap(() => {});
expect(htmlElement.setAttribute).toHaveBeenCalledWith('src', '/test-resources/sap/ushell/bootstrap/sandbox.js');
});

test('ushell src when ui5 version is 2.0', async () => {
fetchMock.mockResolvedValue({
json: () => Promise.resolve({ libraries: [{name: 'sap.ui.core', version: '2.0.0'}] })
} as jest.Mocked<Response>);

await ushellBootstrap(() => {});
expect(htmlElement.setAttribute).toHaveBeenCalledWith('src', '/resources/sap/ushell/bootstrap/sandbox2.js');
});

test('fetching version failed', async () => {
fetchMock.mockRejectedValueOnce('404');

await ushellBootstrap(() => {});
expect(htmlElement.setAttribute).toHaveBeenCalledWith('src', '/test-resources/sap/ushell/bootstrap/sandbox.js');
});
});
Loading

0 comments on commit ab2e5a0

Please sign in to comment.