Skip to content

Commit

Permalink
feat: Adding Controller Extension only on async views in Adp projects (
Browse files Browse the repository at this point in the history
…#1874)

* feat: adding Controller Extension only on async views for adp projects

* test: update test

* test: added test

* fix: fix review comments

* fix: fix test
  • Loading branch information
GDamyanov committed May 10, 2024
1 parent d8bf58e commit cad21d4
Show file tree
Hide file tree
Showing 12 changed files with 223 additions and 31 deletions.
8 changes: 8 additions & 0 deletions .changeset/six-pigs-hear.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@sap-ux-private/control-property-editor-common': patch
'@sap-ux-private/preview-middleware-client': patch
'@sap-ux/control-property-editor': patch
'@sap-ux/preview-middleware': patch
---

Enable Adding Controller Extension only on async views for Adp Projects
7 changes: 6 additions & 1 deletion packages/control-property-editor-common/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,11 @@ export interface PropertyChangeDeletionDetails {
fileName?: string;
}

export interface ShowMessage {
message: string;
shouldHideIframe: boolean;
}

/**
* ACTIONS
*/
Expand Down Expand Up @@ -226,7 +231,7 @@ export const changeProperty = createExternalAction<PropertyChange>('change-prope
export const propertyChanged = createExternalAction<PropertyChanged>('property-changed');
export const propertyChangeFailed = createExternalAction<PropertyChangeFailed>('change-property-failed');
export const changeStackModified = createExternalAction<ChangeStackModified>('change-stack-modified');
export const showMessage = createExternalAction<string>('show-dialog-message');
export const showMessage = createExternalAction<ShowMessage>('show-dialog-message');
export const reloadApplication = createExternalAction<void>('reload-application');
export const storageFileChanged = createExternalAction<string>('storage-file-changed');
export type ExternalAction =
Expand Down
36 changes: 27 additions & 9 deletions packages/control-property-editor/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { useAppDispatch } from './store';
import { changePreviewScale } from './slice';
import { useWindowSize } from './use-window-size';
import { DEFAULT_DEVICE_WIDTH, DEVICE_WIDTH_MAP } from './devices';
import { ShowMessage } from '@sap-ux-private/control-property-editor-common';

import './App.scss';
import './Workarounds.scss';
Expand Down Expand Up @@ -45,18 +46,18 @@ export default function App(appProps: AppProps): ReactElement {

const [hideWarningDialog, setHideWarningDialog] = useLocalStorage('hide-warning-dialog', false);
const [isWarningDialogVisible, setWarningDialogVisibility] = useState(() => hideWarningDialog !== true);
const [shouldShowDialogMessage, setShouldShowDialogMessage] = useState(false);
const [shouldHideIframe, setShouldHideIframe] = useState(false);

const [isInitialized, setIsInitialized] = useState(false);
const [shouldShowDialogMessageForAdpProjects, setShouldShowDialogMessageForAdpProjects] = useState(false);

const previewWidth = useSelector<RootState, string>(
(state) => `${DEVICE_WIDTH_MAP.get(state.deviceType) ?? DEFAULT_DEVICE_WIDTH}px`
);
const previewScale = useSelector<RootState, number>((state) => state.scale);
const fitPreview = useSelector<RootState, boolean>((state) => state.fitPreview ?? false);
const windowSize = useWindowSize();
const dialogMessage = useSelector<RootState, string | undefined>((state) => state.dialogMessage);

const dialogMessage = useSelector<RootState, ShowMessage | undefined>((state) => state.dialogMessage);
const containerRef = useCallback(
(node) => {
if (node === null) {
Expand Down Expand Up @@ -92,11 +93,16 @@ export default function App(appProps: AppProps): ReactElement {
setWarningDialogVisibility(false);
}

const closeAdpWarningDialog = (): void => {
setShouldShowDialogMessage(false);
};

useEffect(() => {
if (dialogMessage && isAdpProject) {
setShouldShowDialogMessageForAdpProjects(true);
setShouldShowDialogMessage(true);
setShouldHideIframe(dialogMessage.shouldHideIframe);
}
}, [dialogMessage]);
}, [dialogMessage, isAdpProject]);

return (
<div className="app">
Expand All @@ -105,7 +111,7 @@ export default function App(appProps: AppProps): ReactElement {
</section>
<section ref={containerRef} className="app-content">
<div className="app-canvas">
{!shouldShowDialogMessageForAdpProjects && (
{!shouldHideIframe && (
<iframe
className="app-preview"
id="preview"
Expand All @@ -122,15 +128,27 @@ export default function App(appProps: AppProps): ReactElement {
<section className="app-panel app-panel-right">
<PropertiesPanel />
</section>
{isAdpProject && (
{isAdpProject && shouldHideIframe && (
<UIDialog
hidden={!shouldShowDialogMessage}
dialogContentProps={{
title: t('TOOL_DISCLAIMER_TITLE'),
subText: dialogMessage?.message
}}
/>
)}
{isAdpProject && !shouldHideIframe && (
<UIDialog
hidden={!shouldShowDialogMessageForAdpProjects}
hidden={!shouldShowDialogMessage}
dialogContentProps={{
title: t('TOOL_DISCLAIMER_TITLE'),
subText: dialogMessage
subText: dialogMessage?.message
}}
acceptButtonText={t('OK')}
onAccept={closeAdpWarningDialog}
/>
)}

{scenario === 'FE_FROM_SCRATCH' ? (
<UIDialog
hidden={!isWarningDialogVisible}
Expand Down
5 changes: 3 additions & 2 deletions packages/control-property-editor/src/slice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import type {
PendingPropertyChange,
PropertyChange,
SavedPropertyChange,
Scenario
Scenario,
ShowMessage
} from '@sap-ux-private/control-property-editor-common';
import {
changeStackModified,
Expand Down Expand Up @@ -39,7 +40,7 @@ interface SliceState {
isAdpProject: boolean;
icons: IconDetails[];
changes: ChangesSlice;
dialogMessage: string | undefined;
dialogMessage: ShowMessage | undefined;
fileChanges?: string[];
}

Expand Down
39 changes: 37 additions & 2 deletions packages/control-property-editor/test/unit/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ import { render, mockDomEventListener } from './utils';
import { initI18n } from '../../src/i18n';

import App from '../../src/App';
import { controlSelected } from '@sap-ux-private/control-property-editor-common';
import { controlSelected, scenario, showMessage } from '@sap-ux-private/control-property-editor-common';
import { mockResizeObserver } from '../utils/utils';
import { InputType } from '../../src/panels/properties/types';
import { registerAppIcons } from '../../src/icons';
import { DeviceType } from '../../src/devices';
import { changePreviewScale, initialState } from '../../src/slice';
import { FilterName, SliceState, changePreviewScale, initialState } from '../../src/slice';

jest.useFakeTimers({ advanceTimers: true });
const windowEventListenerMock = mockDomEventListener(window);
Expand Down Expand Up @@ -182,6 +182,41 @@ test('renders warning dialog for "FE_FROM_SCRATCH" scenario', async () => {
fireEvent.click(okButton);
});

test('renders warning message for "ADAPTATION_PROJECT" scenario', async () => {
const initialState = {
deviceType: DeviceType.Desktop,
scale: 1.0,
selectedControl: undefined,
outline: [],
filterQuery: [
{ name: FilterName.focusEditable, value: true },
{ name: FilterName.focusCommonlyUsed, value: true },
{ name: FilterName.query, value: '' },
{ name: FilterName.changeSummaryFilterQuery, value: '' },
{ name: FilterName.showEditableProperties, value: true }
],
scenario: scenario.AdaptationProject,
isAdpProject: true,
icons: [],
changes: {
controls: {},
pending: [],
saved: [],
pendingChangeIds: []
},
dialogMessage: {
message: 'Some Text',
shouldHideIframe: false
}
};
render(<App previewUrl="" scenario="ADAPTATION_PROJECT" />, { initialState });

const warningDialog = screen.getByText(/Some Text/i);
expect(warningDialog).toBeInTheDocument();
const okButton = screen.getByText(/ok/i);
expect(okButton).toBeInTheDocument();
});

const testCases = [
{
deviceType: DeviceType.Desktop,
Expand Down
4 changes: 2 additions & 2 deletions packages/control-property-editor/test/unit/slice.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -389,8 +389,8 @@ describe('main redux slice', () => {
});

test('show message', () => {
expect(reducer({} as any, showMessage('testMessage'))).toStrictEqual({
dialogMessage: 'testMessage'
expect(reducer({} as any, showMessage({ message: 'testMessage', shouldHideIframe: false }))).toStrictEqual({
dialogMessage: { message: 'testMessage', shouldHideIframe: false }
});
});
test('reload application', () => {
Expand Down
27 changes: 24 additions & 3 deletions packages/preview-middleware-client/src/adp/init-dialogs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,16 @@ import type Dialog from 'sap/m/Dialog';

/** sap.ui.core */
import Fragment from 'sap/ui/core/Fragment';
import type UI5Element from 'sap/ui/core/Element';
import UI5Element from 'sap/ui/core/Element';

/** sap.ui.rta */
import type RuntimeAuthoring from 'sap/ui/rta/RuntimeAuthoring';

/** sap.ui.fl */
import FlUtils from 'sap/ui/fl/Utils';

import ElementOverlay from 'sap/ui/dt/ElementOverlay';

import AddFragment from './controllers/AddFragment.controller';
import ControllerExtension from './controllers/ControllerExtension.controller';
import { ExtensionPointData } from './extension-point';
Expand All @@ -25,8 +30,9 @@ type Controller = AddFragment | ControllerExtension | ExtensionPoint;
* Adds a new item to the context menu
*
* @param rta Runtime Authoring
* @param syncViewsIds Ids of all application sync views
*/
export const initDialogs = (rta: RuntimeAuthoring): void => {
export const initDialogs = (rta: RuntimeAuthoring, syncViewsIds: string[]): void => {
const contextMenu = rta.getDefaultPlugins().contextMenu;

contextMenu.addMenuItem({
Expand All @@ -40,10 +46,25 @@ export const initDialogs = (rta: RuntimeAuthoring): void => {
id: 'EXTEND_CONTROLLER',
text: 'Extend With Controller',
handler: async (overlays: UI5Element[]) => await handler(overlays[0], rta, DialogNames.CONTROLLER_EXTENSION),
icon: 'sap-icon://create-form'
icon: 'sap-icon://create-form',
enabled: (overlays: ElementOverlay[]) => isControllerExtensionEnabled(overlays, syncViewsIds)
});
};

/**
* Handler for enablement of Extend With Controller context menu entry
*
* @param overlays Control overlays
* @param syncViewsIds Runtime Authoring
*
* @returns boolean
*/
export const isControllerExtensionEnabled = (overlays: ElementOverlay[], syncViewsIds: string[]): boolean => {
const clickedControlId = FlUtils.getViewForControl(overlays[0].getElement()).getId();

return overlays.length <= 1 && !syncViewsIds.includes(clickedControlId);
};

/**
* Handler for new context menu entry
*
Expand Down
41 changes: 35 additions & 6 deletions packages/preview-middleware-client/src/adp/init.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import log from 'sap/base/Log';
import type RuntimeAuthoring from 'sap/ui/rta/RuntimeAuthoring';
import UI5ElementRegistry from 'sap/ui/core/ElementRegistry';

import init from '../cpe/init';
import { initDialogs } from './init-dialogs';
Expand All @@ -13,6 +14,7 @@ import {
import { ActionHandler } from '../cpe/types';
import VersionInfo from 'sap/ui/VersionInfo';
import { getUI5VersionValidationMessage } from './ui5-version-utils';
import UI5Element from 'sap/ui/dt/Element';

export default async function (rta: RuntimeAuthoring) {
const { version } = (await VersionInfo.load()) as { version: string };
Expand Down Expand Up @@ -44,23 +46,50 @@ export default async function (rta: RuntimeAuthoring) {
}
);

// initialize fragment content menu entry
initDialogs(rta);
const syncViewsIds = getAllSyncViewsIds();
initDialogs(rta, syncViewsIds);

// initialize extension point service
if (minor > 77) {
const ExtensionPointService = (await import('open/ux/preview/client/adp/extension-point')).default;
const extPointService = new ExtensionPointService(rta);
extPointService.init(subscribe);
}

// also initialize the editor
await init(rta);

const ui5VersionValidationMsg = getUI5VersionValidationMessage(version);

if (ui5VersionValidationMsg) {
sendAction(showMessage(ui5VersionValidationMsg));
sendAction(showMessage({ message: ui5VersionValidationMsg, shouldHideIframe: true }));

return;
}
if (syncViewsIds.length > 0) {
sendAction(
showMessage({
message:
'Have in mind that synchronous views are detected for this application and controller extensions are not supported for such views. Controller extension functionality on these views will be disabled.',
shouldHideIframe: false
})
);
}

log.debug('ADP init executed.');
}

/**
*
* Get Ids for all sync views
*
* @returns array of Ids for application sync views
*/
function getAllSyncViewsIds(): string[] {
const elements = UI5ElementRegistry.all() as Record<string, UI5Element>;
const syncViewIds: string[] = [];
Object.entries(elements).forEach(([key, ui5Element]) => {
if (ui5Element?.getMetadata()?.getName()?.includes('XMLView') && ui5Element?.oAsyncState === undefined) {
syncViewIds.push(key);
}
});

return syncViewIds;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default {
all: jest.fn().mockReturnValue([])
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ import Fragment from 'mock/sap/ui/core/Fragment';
import Controller from 'mock/sap/ui/core/mvc/Controller';
import RuntimeAuthoringMock from 'mock/sap/ui/rta/RuntimeAuthoring';

import { DialogNames, handler, initDialogs } from '../../../src/adp/init-dialogs';
import { DialogNames, handler, initDialogs, isControllerExtensionEnabled } from '../../../src/adp/init-dialogs';
import AddFragment from '../../../src/adp/controllers/AddFragment.controller';
import ControllerExtension from '../../../src/adp/controllers/ControllerExtension.controller';
import ExtensionPoint from '../../../src/adp/controllers/ExtensionPoint.controller';
import ElementOverlay from 'sap/ui/dt/ElementOverlay';
import FlUtils from 'sap/ui/fl/Utils';

describe('Dialogs', () => {
describe('initDialogs', () => {
Expand All @@ -26,7 +28,7 @@ describe('Dialogs', () => {
addMenuItem: addMenuItemSpy
}
});
initDialogs(rtaMock as unknown as RuntimeAuthoring);
initDialogs(rtaMock as unknown as RuntimeAuthoring, []);
expect(addMenuItemSpy).toHaveBeenCalledTimes(2);
});

Expand Down Expand Up @@ -58,4 +60,29 @@ describe('Dialogs', () => {
expect(Fragment.load).toHaveBeenCalledTimes(3);
});
});

describe('isControllerExtensionEnabled', () => {
const syncViewsIds = ['syncViewId1', 'syncViewId2'];
const elementOverlayMock = { getElement: jest.fn() } as unknown as ElementOverlay;

it('should return true when overlays length is 1 and clickedControlId is not in syncViewsIds', () => {
FlUtils.getViewForControl = jest.fn().mockReturnValue({ getId: jest.fn().mockReturnValue('asyncViewId2') });
const overlays: ElementOverlay[] = [elementOverlayMock];
expect(isControllerExtensionEnabled(overlays, syncViewsIds)).toBe(true);
});

it('should return false when overlays length is 1 and clickedControlId is in syncViewsIds', () => {
FlUtils.getViewForControl = jest.fn().mockReturnValue({ getId: jest.fn().mockReturnValue('syncViewId1') });
const overlays: ElementOverlay[] = [elementOverlayMock];

expect(isControllerExtensionEnabled(overlays, syncViewsIds)).toBe(false);
});

it('should return false when overlays length is more than 1', () => {
FlUtils.getViewForControl = jest.fn().mockReturnValue({ getId: jest.fn().mockReturnValue('syncViewId3') });
const overlays: ElementOverlay[] = [elementOverlayMock, elementOverlayMock];
const syncViewsIds = ['syncViewId1', 'syncViewId2'];
expect(isControllerExtensionEnabled(overlays, syncViewsIds)).toBe(false);
});
});
});

0 comments on commit cad21d4

Please sign in to comment.