From a47996f3534dd96b91fc944763dbe2991502a07c Mon Sep 17 00:00:00 2001 From: Nafees Ahmed Date: Tue, 17 Sep 2024 21:38:41 +0200 Subject: [PATCH 1/6] feat: add page and table actions for v2 app --- .../adp-tooling/src/preview/change-handler.ts | 12 + .../templates/rta/common/custom-action.xml | 5 + .../adp/controllers/AddFragment.controller.ts | 16 +- .../fe-v2/change-table-columns.ts | 230 +---------------- .../quick-actions/fe-v2/create-page-action.ts | 26 ++ .../fe-v2/create-table-action.ts | 59 +++++ .../src/adp/quick-actions/fe-v2/registry.ts | 11 +- .../fe-v2/table-quick-action-base.ts | 242 ++++++++++++++++++ .../src/messagebundle.properties | 4 + .../test/unit/adp/quick-actions/fe-v2.test.ts | 12 + 10 files changed, 389 insertions(+), 228 deletions(-) create mode 100644 packages/adp-tooling/templates/rta/common/custom-action.xml create mode 100644 packages/preview-middleware-client/src/adp/quick-actions/fe-v2/create-page-action.ts create mode 100644 packages/preview-middleware-client/src/adp/quick-actions/fe-v2/create-table-action.ts create mode 100644 packages/preview-middleware-client/src/adp/quick-actions/fe-v2/table-quick-action-base.ts diff --git a/packages/adp-tooling/src/preview/change-handler.ts b/packages/adp-tooling/src/preview/change-handler.ts index 842cbd5dfc..bfcaf951d4 100644 --- a/packages/adp-tooling/src/preview/change-handler.ts +++ b/packages/adp-tooling/src/preview/change-handler.ts @@ -7,6 +7,7 @@ import { render } from 'ejs'; import { randomBytes } from 'crypto'; const OBJECT_PAGE_CUSTOM_SECTION = 'OBJECT_PAGE_CUSTOM_SECTION'; +const CUSTOM_ACTION = 'CUSTOM_ACTION'; interface FragmentTemplateConfig { /** @@ -29,6 +30,17 @@ const fragmentTemplateDefinitions: Record = { } }; } + }, + [CUSTOM_ACTION]: { + path: 'common/custom-action.xml', + getData: () => { + const uuid = randomBytes(4).toString('hex'); + return { + ids: { + toolbarActionButton: `btn-${uuid}` + } + }; + } } }; diff --git a/packages/adp-tooling/templates/rta/common/custom-action.xml b/packages/adp-tooling/templates/rta/common/custom-action.xml new file mode 100644 index 0000000000..4304364bb8 --- /dev/null +++ b/packages/adp-tooling/templates/rta/common/custom-action.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/packages/preview-middleware-client/src/adp/controllers/AddFragment.controller.ts b/packages/preview-middleware-client/src/adp/controllers/AddFragment.controller.ts index 941c5c080e..06d33e48a7 100644 --- a/packages/preview-middleware-client/src/adp/controllers/AddFragment.controller.ts +++ b/packages/preview-middleware-client/src/adp/controllers/AddFragment.controller.ts @@ -21,7 +21,7 @@ import OverlayRegistry from 'sap/ui/dt/OverlayRegistry'; import type ElementOverlay from 'sap/ui/dt/ElementOverlay'; /** sap.ui.fl */ -import {type AddFragmentChangeContentType} from 'sap/ui/fl/Change'; +import { type AddFragmentChangeContentType } from 'sap/ui/fl/Change'; import ControlUtils from '../control-utils'; import CommandExecutor from '../command-executor'; @@ -296,8 +296,16 @@ export default class AddFragment extends BaseDialog { private getFragmentTemplateName(targetAggregation: string): string { const currentControlName = this.runtimeControl.getMetadata().getName(); - return currentControlName === 'sap.uxap.ObjectPageLayout' && targetAggregation === 'sections' - ? 'OBJECT_PAGE_CUSTOM_SECTION' - : ''; + if (currentControlName === 'sap.uxap.ObjectPageLayout' && targetAggregation === 'sections') { + return 'OBJECT_PAGE_CUSTOM_SECTION'; + } else if ( + ((currentControlName === 'sap.f.DynamicPageTitle' || currentControlName === 'sap.uxap.ObjectPageHeader') && + targetAggregation === 'actions') || + (currentControlName === 'sap.m.OverflowToolbar' && targetAggregation === 'content') + ) { + return 'CUSTOM_ACTION'; + } else { + return ''; + } } } diff --git a/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/change-table-columns.ts b/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/change-table-columns.ts index 3798c7a1c1..16c0482173 100644 --- a/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/change-table-columns.ts +++ b/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/change-table-columns.ts @@ -1,237 +1,25 @@ -import OverlayUtil from 'sap/ui/dt/OverlayUtil'; + import FlexCommand from 'sap/ui/rta/command/FlexCommand'; -import UI5Element from 'sap/ui/core/Element'; -import type IconTabBar from 'sap/m/IconTabBar'; -import type IconTabFilter from 'sap/m/IconTabFilter'; import type Table from 'sap/m/Table'; import type SmartTable from 'sap/ui/comp/smarttable/SmartTable'; -import type { NestedQuickAction, NestedQuickActionChild } from '@sap-ux-private/control-property-editor-common'; -import { NESTED_QUICK_ACTION_KIND } from '@sap-ux-private/control-property-editor-common'; - import { QuickActionContext, NestedQuickActionDefinition } from '../../../cpe/quick-actions/quick-action-definition'; -import { getParentContainer, getRelevantControlFromActivePage } from '../../../cpe/quick-actions/utils'; -import { getControlById, isA, isManagedObject } from '../../../utils/core'; -import { getUi5Version, isLowerThanMinimalUi5Version } from '../../../utils/version'; -import ObjectPageSection from 'sap/uxap/ObjectPageSection'; -import ObjectPageSubSection from 'sap/uxap/ObjectPageSubSection'; -import TreeTable from 'sap/ui/table/TreeTable'; -import ObjectPageLayout from 'sap/uxap/ObjectPageLayout'; +import { getControlById, isA } from '../../../utils/core'; import ManagedObject from 'sap/ui/base/ManagedObject'; +import { TableQuickActionDefinitionBase } from './table-quick-action-base'; export const CHANGE_TABLE_COLUMNS = 'change-table-columns'; -const SMART_TABLE_ACTION_ID = 'CTX_COMP_VARIANT_CONTENT'; -const M_TABLE_ACTION_ID = 'CTX_ADD_ELEMENTS_AS_CHILD'; -const SETTINGS_ID = 'CTX_SETTINGS'; -const ICON_TAB_BAR_TYPE = 'sap.m.IconTabBar'; const SMART_TABLE_TYPE = 'sap.ui.comp.smarttable.SmartTable'; const M_TABLE_TYPE = 'sap.m.Table'; // maintain order if action id const CONTROL_TYPES = [SMART_TABLE_TYPE, M_TABLE_TYPE, 'sap.ui.table.TreeTable', 'sap.ui.table.Table']; -async function getActionId(table: UI5Element): Promise { - const { major, minor } = await getUi5Version(); - - if (isA(SMART_TABLE_TYPE, table)) { - if (major === 1 && minor === 96) { - return [SETTINGS_ID]; - } else { - return [SMART_TABLE_ACTION_ID]; - } - } - - return [M_TABLE_ACTION_ID, SETTINGS_ID]; -} - -export class ChangeTableColumnsQuickAction implements NestedQuickActionDefinition { - readonly kind = NESTED_QUICK_ACTION_KIND; - readonly type = CHANGE_TABLE_COLUMNS; - public get id(): string { - return `${this.context.key}-${this.type}`; - } - isActive = false; - isClearButtonEnabled = false; - children: NestedQuickActionChild[] = []; - tableMap: Record< - string, - { - table: UI5Element; - tableUpdateEventAttachedOnce: boolean; - iconTabBarFilterKey?: string; - changeColumnActionId: string; - sectionInfo?: { - section: ObjectPageSection; - subSection: ObjectPageSubSection; - layout?: ObjectPageLayout; - }; - } - > = {}; - private iconTabBar: IconTabBar | undefined; - constructor(private context: QuickActionContext) {} - - async initialize(): Promise { - // No action found in control design time for version < 1.96 - const version = await getUi5Version(); - if (isLowerThanMinimalUi5Version(version, { major: 1, minor: 96 })) { - this.isActive = false; - return; - } - const iconTabBarfilterMap = this.buildIconTabBarFilterMap(); - for (const table of getRelevantControlFromActivePage( - this.context.controlIndex, - this.context.view, - CONTROL_TYPES - )) { - const actions = await this.context.actionService.get(table.getId()); - const actionsIds = await getActionId(table); - const changeColumnAction = actionsIds.find( - (actionId) => actions.findIndex((action) => action.id === actionId) > -1 - ); - const tabKey = Object.keys(iconTabBarfilterMap).find((key) => table.getId().endsWith(key)); - if (changeColumnAction) { - const section = getParentContainer(table, 'sap.uxap.ObjectPageSection'); - if (section) { - this.collectChildrenInSection(section, table, changeColumnAction); - } else if (this.iconTabBar && tabKey) { - this.children.push({ - label: `'${iconTabBarfilterMap[tabKey]}' table`, - children: [] - }); - this.tableMap[`${this.children.length - 1}`] = { - table, - iconTabBarFilterKey: tabKey, - changeColumnActionId: changeColumnAction, - tableUpdateEventAttachedOnce: false - }; - } else { - this.processTable(table, changeColumnAction); - } - } - } - if (this.children.length > 0) { - this.isActive = true; - } - } - - private getTableLabel(table: UI5Element): string { - if (isA(SMART_TABLE_TYPE, table)) { - const header = table.getHeader(); - if (header) { - return `'${header}' table`; - } - } - if (isA(M_TABLE_TYPE, table)) { - const tilte = table?.getHeaderToolbar()?.getTitleControl()?.getText(); - if (tilte) { - return `'${tilte}' table`; - } - } - - return 'Unnamed table'; - } - - private buildIconTabBarFilterMap(): { [key: string]: string } { - const iconTabBarfilterMap: { [key: string]: string } = {}; - - // Assumption only a tab bar control per page. - const tabBar = getRelevantControlFromActivePage(this.context.controlIndex, this.context.view, [ - ICON_TAB_BAR_TYPE - ])[0]; - if (tabBar) { - const control = getControlById(tabBar.getId()); - if (isA(ICON_TAB_BAR_TYPE, control)) { - this.iconTabBar = control; - for (const item of control.getItems()) { - if (isManagedObject(item) && isA('sap.m.IconTabFilter', item)) { - iconTabBarfilterMap[item.getKey()] = item.getText(); - } - } - } - } - - return iconTabBarfilterMap; - } - - private collectChildrenInSection(section: ObjectPageSection, table: UI5Element, changeColumnAction: string): void { - const layout = getParentContainer(table, 'sap.uxap.ObjectPageLayout'); - const subSections = section.getSubSections(); - const subSection = getParentContainer(table, 'sap.uxap.ObjectPageSubSection'); - if (subSection) { - if (subSections?.length === 1) { - this.processTable(table, changeColumnAction, { section, subSection: subSections[0], layout }); - } else if (subSections.length > 1) { - const sectionChild = this.children.find((val) => val.label === `${section.getTitle()} section`); - let tableMapIndex = `${this.children.length - 1}`; - if (!sectionChild) { - tableMapIndex = `${tableMapIndex}/0`; - this.children.push({ - label: `'${section?.getTitle()}' section`, - children: [ - { - label: this.getTableLabel(table), - children: [] - } - ] - }); - } else { - tableMapIndex = `${tableMapIndex}/${sectionChild.children.length - 1}`; - sectionChild.children.push({ - label: this.getTableLabel(table), - children: [] - }); - } - - this.tableMap[tableMapIndex] = { - table, - changeColumnActionId: changeColumnAction, - sectionInfo: { section, subSection, layout }, - tableUpdateEventAttachedOnce: false - }; - } - } - } - - private processTable( - table: UI5Element, - changeColumnActionId: string, - sectionInfo?: { section: ObjectPageSection; subSection: ObjectPageSubSection; layout?: ObjectPageLayout } - ): void { - if (isA(SMART_TABLE_TYPE, table) || isA('sap.ui.table.TreeTable', table)) { - this.children.push({ - label: this.getTableLabel(table), - children: [] - }); - } - if (isA
(M_TABLE_TYPE, table)) { - this.children.push({ - label: this.getTableLabel(table), - children: [] - }); - } - this.tableMap[`${this.children.length - 1}`] = { - table, - changeColumnActionId, - sectionInfo: sectionInfo, - tableUpdateEventAttachedOnce: false - }; - } - - getActionObject(): NestedQuickAction { - return { - kind: NESTED_QUICK_ACTION_KIND, - id: this.id, - enabled: this.isActive, - title: - this.context.resourceBundle.getText('V2_QUICK_ACTION_CHANGE_TABLE_COLUMNS') ?? 'Change table columns', - children: this.children - }; - } - - private selectOverlay(table: UI5Element): void { - const controlOverlay = OverlayUtil.getClosestOverlayFor(table); - if (controlOverlay) { - controlOverlay.setSelected(true); - } +export class ChangeTableColumnsQuickAction + extends TableQuickActionDefinitionBase + implements NestedQuickActionDefinition +{ + constructor(context: QuickActionContext) { + super(CHANGE_TABLE_COLUMNS, CONTROL_TYPES, 'V2_QUICK_ACTION_CHANGE_TABLE_COLUMNS', context); } async execute(path: string): Promise { diff --git a/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/create-page-action.ts b/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/create-page-action.ts new file mode 100644 index 0000000000..20363e85cd --- /dev/null +++ b/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/create-page-action.ts @@ -0,0 +1,26 @@ +import OverlayRegistry from 'sap/ui/dt/OverlayRegistry'; +import FlexCommand from 'sap/ui/rta/command/FlexCommand'; + +import { DialogNames, handler } from '../../init-dialogs'; +import { QuickActionContext, SimpleQuickActionDefinition } from '../../../cpe/quick-actions/quick-action-definition'; +import { SimpleQuickActionDefinitionBase } from '../simple-quick-action-base'; + +export const ADD_PAGE_ACTION = 'add-page-action'; +const CONTROL_TYPES = ['sap.f.DynamicPageTitle', 'sap.uxap.ObjectPageHeader']; + +/** + * Quick Action for adding a custom page action. + */ +export class AddPageActionQuickAction extends SimpleQuickActionDefinitionBase implements SimpleQuickActionDefinition { + constructor(context: QuickActionContext) { + super(ADD_PAGE_ACTION, CONTROL_TYPES, 'QUICK_ACTION_ADD_CUSTOM_PAGE_ACTION', context); + } + + async execute(): Promise { + if (this.control) { + const overlay = OverlayRegistry.getOverlay(this.control) || []; + await handler(overlay, this.context.rta, DialogNames.ADD_FRAGMENT, undefined, 'actions'); + } + return []; + } +} diff --git a/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/create-table-action.ts b/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/create-table-action.ts new file mode 100644 index 0000000000..3a8e5cfa44 --- /dev/null +++ b/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/create-table-action.ts @@ -0,0 +1,59 @@ +import FlexCommand from 'sap/ui/rta/command/FlexCommand'; +import type Table from 'sap/m/Table'; +import type SmartTable from 'sap/ui/comp/smarttable/SmartTable'; + +import { QuickActionContext, NestedQuickActionDefinition } from '../../../cpe/quick-actions/quick-action-definition'; +import { getControlById, isA } from '../../../utils/core'; +import OverlayRegistry from 'sap/ui/dt/OverlayRegistry'; +import { DialogNames, handler } from '../../init-dialogs'; +import { TableQuickActionDefinitionBase } from './table-quick-action-base'; + +export const CREATE_TABLE_ACTION = 'create-table-action'; +const SMART_TABLE_TYPE = 'sap.ui.comp.smarttable.SmartTable'; +const M_TABLE_TYPE = 'sap.m.Table'; +// maintain order if action id + +const CONTROL_TYPES = [SMART_TABLE_TYPE, M_TABLE_TYPE, 'sap.ui.table.TreeTable', 'sap.ui.table.Table']; + +export class AddTableActionQuickAction extends TableQuickActionDefinitionBase implements NestedQuickActionDefinition { + constructor(context: QuickActionContext) { + super(CREATE_TABLE_ACTION, CONTROL_TYPES, 'QUICK_ACTION_ADD_CUSTOM_TABLE_ACTION', context); + } + + async execute(path: string): Promise { + const { table, iconTabBarFilterKey, sectionInfo } = this.tableMap[path]; + if (!table) { + return []; + } + + if (sectionInfo) { + const { layout, section, subSection } = sectionInfo; + layout?.setSelectedSection(section); + section.setSelectedSubSection(subSection); + this.selectOverlay(table); + } else { + getControlById(table.getId())?.getDomRef()?.scrollIntoView(); + this.selectOverlay(table); + } + + if (this.iconTabBar && iconTabBarFilterKey) { + this.iconTabBar.setSelectedKey(iconTabBarFilterKey); + } + + // open dialogBox to add, and content is selected ByDefault + const openContentDialog = async (headerToolbar: any) => { + if (headerToolbar) { + const overlay = OverlayRegistry.getOverlay(headerToolbar) || []; + await handler(overlay, this.context.rta, DialogNames.ADD_FRAGMENT, undefined, 'content'); + } + }; + let headerToolbar; + if (isA(SMART_TABLE_TYPE, table)) { + headerToolbar = (table as any).getAggregation('items')[0].getHeaderToolbar(); + } else if (isA
(M_TABLE_TYPE, table)) { + headerToolbar = table.getAggregation('headerToolbar'); + } + await openContentDialog(headerToolbar); + return []; + } +} diff --git a/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/registry.ts b/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/registry.ts index 09f5855014..62f4664dd1 100644 --- a/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/registry.ts +++ b/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/registry.ts @@ -14,7 +14,8 @@ import { ToggleClearFilterBarQuickAction } from './lr-toggle-clear-filter-bar'; import { ChangeTableColumnsQuickAction } from './change-table-columns'; import { AddHeaderFieldQuickAction } from '../common/op-add-header-field'; import { AddCustomSectionQuickAction } from '../common/op-add-custom-section'; - +import { AddTableActionQuickAction } from '../fe-v2/create-table-action'; +import { AddPageActionQuickAction } from '../fe-v2/create-page-action'; type PageName = 'listReport' | 'objectPage'; const OBJECT_PAGE_TYPE = 'sap.suite.ui.generic.template.ObjectPage.view.Details'; @@ -40,7 +41,9 @@ export default class FEV2QuickActionRegistry extends QuickActionDefinitionRegist definitions: [ ToggleClearFilterBarQuickAction, AddControllerToPageQuickAction, - ChangeTableColumnsQuickAction + ChangeTableColumnsQuickAction, + AddTableActionQuickAction, + AddPageActionQuickAction ], view, key: name + index @@ -52,7 +55,9 @@ export default class FEV2QuickActionRegistry extends QuickActionDefinitionRegist AddControllerToPageQuickAction, ChangeTableColumnsQuickAction, AddHeaderFieldQuickAction, - AddCustomSectionQuickAction + AddCustomSectionQuickAction, + AddTableActionQuickAction, + AddPageActionQuickAction ], view, key: name + index diff --git a/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/table-quick-action-base.ts b/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/table-quick-action-base.ts new file mode 100644 index 0000000000..46f951e2c7 --- /dev/null +++ b/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/table-quick-action-base.ts @@ -0,0 +1,242 @@ +import UI5Element from 'sap/ui/core/Element'; +import { NESTED_QUICK_ACTION_KIND, NestedQuickAction } from '@sap-ux-private/control-property-editor-common'; +import type IconTabBar from 'sap/m/IconTabBar'; +import type IconTabFilter from 'sap/m/IconTabFilter'; +import type Table from 'sap/m/Table'; +import type SmartTable from 'sap/ui/comp/smarttable/SmartTable'; +import { QuickActionContext } from '../../../cpe/quick-actions/quick-action-definition'; +import OverlayUtil from 'sap/ui/dt/OverlayUtil'; +import type { NestedQuickActionChild } from '@sap-ux-private/control-property-editor-common'; +import { getParentContainer, getRelevantControlFromActivePage } from '../../../cpe/quick-actions/utils'; +import { getControlById, isA, isManagedObject } from '../../../utils/core'; +import { getUi5Version, isLowerThanMinimalUi5Version } from '../../../utils/version'; +import ObjectPageSection from 'sap/uxap/ObjectPageSection'; +import ObjectPageSubSection from 'sap/uxap/ObjectPageSubSection'; +import TreeTable from 'sap/ui/table/TreeTable'; +import ObjectPageLayout from 'sap/uxap/ObjectPageLayout'; + +const SMART_TABLE_ACTION_ID = 'CTX_COMP_VARIANT_CONTENT'; +const M_TABLE_ACTION_ID = 'CTX_ADD_ELEMENTS_AS_CHILD'; +const SETTINGS_ID = 'CTX_SETTINGS'; +const ICON_TAB_BAR_TYPE = 'sap.m.IconTabBar'; +const SMART_TABLE_TYPE = 'sap.ui.comp.smarttable.SmartTable'; +const M_TABLE_TYPE = 'sap.m.Table'; +async function getActionId(table: UI5Element): Promise { + const { major, minor } = await getUi5Version(); + + if (isA(SMART_TABLE_TYPE, table)) { + if (major === 1 && minor === 96) { + return [SETTINGS_ID]; + } else { + return [SMART_TABLE_ACTION_ID]; + } + } + + return [M_TABLE_ACTION_ID, SETTINGS_ID]; +} + +/** + * Base class for table quick actions. + */ +export abstract class TableQuickActionDefinitionBase { + readonly kind = NESTED_QUICK_ACTION_KIND; + public get id(): string { + return `${this.context.key}-${this.type}`; + } + + public isActive = false; + + public children: NestedQuickActionChild[] = []; + public tableMap: Record< + string, + { + table: UI5Element; + tableUpdateEventAttachedOnce: boolean; + iconTabBarFilterKey?: string; + changeColumnActionId: string; + sectionInfo?: { + section: ObjectPageSection; + subSection: ObjectPageSubSection; + layout?: ObjectPageLayout; + }; + } + > = {}; + public iconTabBar: IconTabBar | undefined; + + protected get textKey(): string { + return this.defaultTextKey; + } + + protected control: UI5Element | undefined; + + constructor( + public readonly type: string, + protected readonly controlTypes: string[], + protected readonly defaultTextKey: string, + protected readonly context: QuickActionContext + ) {} + + async initialize(): Promise { + // No action found in control design time for version < 1.96 + const version = await getUi5Version(); + if (isLowerThanMinimalUi5Version(version, { major: 1, minor: 96 })) { + this.isActive = false; + return; + } + const iconTabBarfilterMap = this.buildIconTabBarFilterMap(); + for (const table of getRelevantControlFromActivePage( + this.context.controlIndex, + this.context.view, + this.controlTypes + )) { + const actions = await this.context.actionService.get(table.getId()); + const actionsIds = await getActionId(table); + const changeColumnAction = actionsIds.find( + (actionId) => actions.findIndex((action) => action.id === actionId) > -1 + ); + const tabKey = Object.keys(iconTabBarfilterMap).find((key) => table.getId().endsWith(key)); + if (changeColumnAction) { + const section = getParentContainer(table, 'sap.uxap.ObjectPageSection'); + if (section) { + this.collectChildrenInSection(section, table, changeColumnAction); + } else if (this.iconTabBar && tabKey) { + this.children.push({ + label: `'${iconTabBarfilterMap[tabKey]}' table`, + children: [] + }); + this.tableMap[`${this.children.length - 1}`] = { + table, + iconTabBarFilterKey: tabKey, + changeColumnActionId: changeColumnAction, + tableUpdateEventAttachedOnce: false + }; + } else { + this.processTable(table, changeColumnAction); + } + } + } + if (this.children.length > 0) { + this.isActive = true; + } + } + + private getTableLabel(table: UI5Element): string { + if (isA(SMART_TABLE_TYPE, table)) { + const header = table.getHeader(); + if (header) { + return `'${header}' table`; + } + } + if (isA
(M_TABLE_TYPE, table)) { + const tilte = table?.getHeaderToolbar()?.getTitleControl()?.getText(); + if (tilte) { + return `'${tilte}' table`; + } + } + + return 'Unnamed table'; + } + + private buildIconTabBarFilterMap(): { [key: string]: string } { + const iconTabBarfilterMap: { [key: string]: string } = {}; + + // Assumption only a tab bar control per page. + const tabBar = getRelevantControlFromActivePage(this.context.controlIndex, this.context.view, [ + ICON_TAB_BAR_TYPE + ])[0]; + if (tabBar) { + const control = getControlById(tabBar.getId()); + if (isA(ICON_TAB_BAR_TYPE, control)) { + this.iconTabBar = control; + for (const item of control.getItems()) { + if (isManagedObject(item) && isA('sap.m.IconTabFilter', item)) { + iconTabBarfilterMap[item.getKey()] = item.getText(); + } + } + } + } + + return iconTabBarfilterMap; + } + + private collectChildrenInSection(section: ObjectPageSection, table: UI5Element, changeColumnAction: string): void { + const layout = getParentContainer(table, 'sap.uxap.ObjectPageLayout'); + const subSections = section.getSubSections(); + const subSection = getParentContainer(table, 'sap.uxap.ObjectPageSubSection'); + if (subSection) { + if (subSections?.length === 1) { + this.processTable(table, changeColumnAction, { section, subSection: subSections[0], layout }); + } else if (subSections.length > 1) { + const sectionChild = this.children.find((val) => val.label === `${section.getTitle()} section`); + let tableMapIndex = `${this.children.length - 1}`; + if (!sectionChild) { + tableMapIndex = `${tableMapIndex}/0`; + this.children.push({ + label: `'${section?.getTitle()}' section`, + children: [ + { + label: this.getTableLabel(table), + children: [] + } + ] + }); + } else { + tableMapIndex = `${tableMapIndex}/${sectionChild.children.length - 1}`; + sectionChild.children.push({ + label: this.getTableLabel(table), + children: [] + }); + } + + this.tableMap[tableMapIndex] = { + table, + changeColumnActionId: changeColumnAction, + sectionInfo: { section, subSection, layout }, + tableUpdateEventAttachedOnce: false + }; + } + } + } + + private processTable( + table: UI5Element, + changeColumnActionId: string, + sectionInfo?: { section: ObjectPageSection; subSection: ObjectPageSubSection; layout?: ObjectPageLayout } + ): void { + if (isA(SMART_TABLE_TYPE, table) || isA('sap.ui.table.TreeTable', table)) { + this.children.push({ + label: this.getTableLabel(table), + children: [] + }); + } + if (isA
(M_TABLE_TYPE, table)) { + this.children.push({ + label: this.getTableLabel(table), + children: [] + }); + } + this.tableMap[`${this.children.length - 1}`] = { + table, + changeColumnActionId, + sectionInfo: sectionInfo, + tableUpdateEventAttachedOnce: false + }; + } + + protected selectOverlay(table: UI5Element): void { + const controlOverlay = OverlayUtil.getClosestOverlayFor(table); + if (controlOverlay) { + controlOverlay.setSelected(true); + } + } + + getActionObject(): NestedQuickAction { + return { + kind: NESTED_QUICK_ACTION_KIND, + id: this.id, + enabled: this.isActive, + title: this.context.resourceBundle.getText(this.textKey), + children: this.children + }; + } +} diff --git a/packages/preview-middleware-client/src/messagebundle.properties b/packages/preview-middleware-client/src/messagebundle.properties index 437da4b560..a2812f8e1c 100644 --- a/packages/preview-middleware-client/src/messagebundle.properties +++ b/packages/preview-middleware-client/src/messagebundle.properties @@ -2,6 +2,9 @@ QUICK_ACTION_ADD_PAGE_CONTROLLER=Add Controller to Page QUICK_ACTION_SHOW_PAGE_CONTROLLER=Show Page Controller QUICK_ACTION_OP_ADD_HEADER_FIELD=Add Header Field QUICK_ACTION_OP_ADD_CUSTOM_SECTION=Add Custom Section +QUICK_ACTION_ADD_CUSTOM_PAGE_ACTION=Add Custom Page Action +QUICK_ACTION_ADD_CUSTOM_TABLE_ACTION=Add Custom Table Action + V2_QUICK_ACTION_CHANGE_TABLE_COLUMNS=Change Table Columns @@ -10,6 +13,7 @@ V2_QUICK_ACTION_LR_DISABLE_CLEAR_FILTER_BAR=Disable "Clear" Button in Filter Bar V4_QUICK_ACTION_CHANGE_TABLE_COLUMNS=Change Table Columns +V4_QUICK_ACTION_ADD_CUSTOM_TABLE_ACTION=Add Custom Table Action V4_QUICK_ACTION_LR_ENABLE_CLEAR_FILTER_BAR=Enable "Clear" Button in Filter Bar V4_QUICK_ACTION_LR_DISABLE_CLEAR_FILTER_BAR=Disable "Clear" Button in Filter Bar diff --git a/packages/preview-middleware-client/test/unit/adp/quick-actions/fe-v2.test.ts b/packages/preview-middleware-client/test/unit/adp/quick-actions/fe-v2.test.ts index aa54c12514..57c36c35d7 100644 --- a/packages/preview-middleware-client/test/unit/adp/quick-actions/fe-v2.test.ts +++ b/packages/preview-middleware-client/test/unit/adp/quick-actions/fe-v2.test.ts @@ -357,6 +357,18 @@ describe('FE V2 quick actions', () => { label: `'MyTable' table` } ] + }, + { + 'kind': 'nested', + id: 'listReport0-create-table-action', + title: 'Add Custom Table Action', + enabled: true, + children: [ + { + children: [], + label: `'MyTable' table` + } + ] } ] } From d20d9d5b4944b5cdf553ff7c6610262ba03de476 Mon Sep 17 00:00:00 2001 From: Nafees Ahmed <120391284+nahmed22@users.noreply.github.com> Date: Tue, 17 Sep 2024 21:40:36 +0200 Subject: [PATCH 2/6] Create weak-seals-crash.md --- .changeset/weak-seals-crash.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/weak-seals-crash.md diff --git a/.changeset/weak-seals-crash.md b/.changeset/weak-seals-crash.md new file mode 100644 index 0000000000..778aa033c9 --- /dev/null +++ b/.changeset/weak-seals-crash.md @@ -0,0 +1,6 @@ +--- +"@sap-ux/adp-tooling": patch +"@sap-ux-private/preview-middleware-client": patch +--- + +feat: Enhance Quick action list to add custom actions From 1a47a413de22cc695dc26b98cf05d5089e418762 Mon Sep 17 00:00:00 2001 From: Nafees Ahmed Date: Tue, 17 Sep 2024 23:20:56 +0200 Subject: [PATCH 3/6] fix: linting --- .../quick-actions/fe-v2/create-table-action.ts | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/create-table-action.ts b/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/create-table-action.ts index 3a8e5cfa44..4de72a56ce 100644 --- a/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/create-table-action.ts +++ b/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/create-table-action.ts @@ -7,6 +7,8 @@ import { getControlById, isA } from '../../../utils/core'; import OverlayRegistry from 'sap/ui/dt/OverlayRegistry'; import { DialogNames, handler } from '../../init-dialogs'; import { TableQuickActionDefinitionBase } from './table-quick-action-base'; +import ManagedObject from 'sap/ui/base/ManagedObject'; +import UI5Element from 'sap/ui/core/Element'; export const CREATE_TABLE_ACTION = 'create-table-action'; const SMART_TABLE_TYPE = 'sap.ui.comp.smarttable.SmartTable'; @@ -39,21 +41,17 @@ export class AddTableActionQuickAction extends TableQuickActionDefinitionBase im if (this.iconTabBar && iconTabBarFilterKey) { this.iconTabBar.setSelectedKey(iconTabBarFilterKey); } - - // open dialogBox to add, and content is selected ByDefault - const openContentDialog = async (headerToolbar: any) => { - if (headerToolbar) { - const overlay = OverlayRegistry.getOverlay(headerToolbar) || []; - await handler(overlay, this.context.rta, DialogNames.ADD_FRAGMENT, undefined, 'content'); - } - }; + let headerToolbar; if (isA(SMART_TABLE_TYPE, table)) { - headerToolbar = (table as any).getAggregation('items')[0].getHeaderToolbar(); + headerToolbar = (table.getAggregation('items') as ManagedObject[])[0].getAggregation('headerToolbar'); } else if (isA
(M_TABLE_TYPE, table)) { headerToolbar = table.getAggregation('headerToolbar'); } - await openContentDialog(headerToolbar); + + // open dialogBox to add, and content is selected ByDefault + const overlay = OverlayRegistry.getOverlay(headerToolbar as UI5Element) || []; + await handler(overlay, this.context.rta, DialogNames.ADD_FRAGMENT, undefined, 'content'); return []; } } From 0329d446301501d96832d0e0728d559cfaf20c9e Mon Sep 17 00:00:00 2001 From: Nafees Ahmed Date: Wed, 18 Sep 2024 11:34:55 +0200 Subject: [PATCH 4/6] fix: unit test --- .../test/unit/preview/change-handler.test.ts | 30 +++++ .../AddFragment.controller.test.ts | 104 ++++++++++++++++++ 2 files changed, 134 insertions(+) diff --git a/packages/adp-tooling/test/unit/preview/change-handler.test.ts b/packages/adp-tooling/test/unit/preview/change-handler.test.ts index 1950262a9b..93e23eabb4 100644 --- a/packages/adp-tooling/test/unit/preview/change-handler.test.ts +++ b/packages/adp-tooling/test/unit/preview/change-handler.test.ts @@ -216,6 +216,36 @@ id="<%- ids.hBox %>"`); expect(mockLogger.info).toHaveBeenCalledWith(`XML Fragment "${fragmentName}.fragment.xml" was created`); }); + + it('should create custom action fragment', () => { + mockFs.exists.mockReturnValue(false); + const updatedChange = { + ...change, + content: { + ...change.content, + templateName: `CUSTOM_ACTION` + } + } as unknown as AddXMLChange; + mockFs.read.mockReturnValue(` +id="<%- ids.toolbarActionButton %>`); + addXmlFragment(path, updatedChange, mockFs as unknown as Editor, mockLogger as unknown as Logger); + + expect(mockFs.read).toHaveBeenCalled(); + expect( + (mockFs.read.mock.calls[0][0] as string) + .replace(/\\/g, '/') + .endsWith('templates/rta/common/custom-action.xml') + ).toBe(true); + + expect(mockFs.write).toHaveBeenCalled(); + expect(mockFs.write.mock.calls[0][0].replace(/\\/g, '/')).toMatchInlineSnapshot(`"project/path/changes/Share.fragment.xml"`); + expect(mockFs.write.mock.calls[0][1]).toMatchInlineSnapshot(` +" +id=\\"btn-30303030" +`); + + expect(mockLogger.info).toHaveBeenCalledWith(`XML Fragment "${fragmentName}.fragment.xml" was created`); + }); }); }); }); diff --git a/packages/preview-middleware-client/test/unit/adp/controllers/AddFragment.controller.test.ts b/packages/preview-middleware-client/test/unit/adp/controllers/AddFragment.controller.test.ts index 254f828cd8..0077d1d5e9 100644 --- a/packages/preview-middleware-client/test/unit/adp/controllers/AddFragment.controller.test.ts +++ b/packages/preview-middleware-client/test/unit/adp/controllers/AddFragment.controller.test.ts @@ -691,5 +691,109 @@ describe('AddFragment', () => { templateName: 'OBJECT_PAGE_CUSTOM_SECTION' }); }); + + test('creates new custom action fragment and a change', async () => { + sapMock.ui.version = '1.71.62'; + const executeSpy = jest.fn(); + rtaMock.getCommandStack.mockReturnValue({ + pushAndExecute: executeSpy + }); + rtaMock.getFlexSettings.mockReturnValue({ projectId: 'adp.app' }); + + const overlays = { + getId: jest.fn().mockReturnValue('some-id') + }; + + const addFragment = new AddFragment( + 'adp.extension.controllers.AddFragment', + overlays as unknown as UI5Element, + rtaMock as unknown as RuntimeAuthoring, + 'actions' + ); + + const event = { + getSource: jest.fn().mockReturnValue({ + setEnabled: jest.fn() + }) + }; + + const testModel = { + getProperty: jest + .fn() + .mockReturnValueOnce('Share') + .mockReturnValueOnce('0') + .mockReturnValueOnce('actions'), + setProperty: jest.fn() + } as unknown as JSONModel; + addFragment.model = testModel; + + const dummyContent: AddFragmentChangeContentType = { + fragmentPath: 'dummyPath', + index: 1, + targetAggregation: 'actions' + }; + + const setContentSpy = jest.fn(); + const commandForSpy = jest.fn().mockReturnValue({ + _oPreparedChange: { + _oDefinition: { moduleName: 'adp/app/changes/fragments/Share.fragment.xml' }, + setModuleName: jest.fn() + }, + getPreparedChange: jest.fn().mockReturnValue({ + getContent: jest.fn().mockReturnValue(dummyContent), + setContent: setContentSpy + }) + }); + CommandFactory.getCommandFor = commandForSpy; + + fetchMock.mockResolvedValue({ + json: jest.fn().mockReturnValue({ + id: 'id', + reference: 'reference', + namespace: 'namespace', + layer: 'layer' + }), + text: jest.fn().mockReturnValue('XML Fragment was created!'), + ok: true + }); + + jest.spyOn(sap.ui, 'getCore').mockReturnValue({ + byId: jest.fn().mockReturnValue({}) + } as unknown as Core); + + jest.spyOn(ControlUtils, 'getRuntimeControl').mockReturnValue({ + getMetadata: jest.fn().mockReturnValue({ + getAllAggregations: jest.fn().mockReturnValue({}), + getName: jest.fn().mockReturnValue('sap.uxap.ObjectPageHeader') + }) + } as unknown as ManagedObject); + + addFragment.handleDialogClose = jest.fn(); + + await addFragment.setup({ + setEscapeHandler: jest.fn(), + destroy: jest.fn(), + setModel: jest.fn(), + open: jest.fn(), + close: jest.fn() + } as unknown as Dialog); + + await addFragment.onCreateBtnPress(event as unknown as Event); + + expect(executeSpy).toHaveBeenCalledWith({ + _oPreparedChange: { + _oDefinition: { + moduleName: 'adp/app/changes/fragments/Share.fragment.xml' + }, + setModuleName: expect.any(Function) + }, + getPreparedChange: expect.any(Function) + }); + + expect(setContentSpy).toHaveBeenCalledWith({ + ...dummyContent, + templateName: 'CUSTOM_ACTION' + }); + }); }); }); From 2403e1ab54b85c96a7c688a301fdf47699042d53 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 18 Sep 2024 09:40:48 +0000 Subject: [PATCH 5/6] Linting auto fix commit --- packages/adp-tooling/test/unit/preview/change-handler.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/adp-tooling/test/unit/preview/change-handler.test.ts b/packages/adp-tooling/test/unit/preview/change-handler.test.ts index 93e23eabb4..74639314f9 100644 --- a/packages/adp-tooling/test/unit/preview/change-handler.test.ts +++ b/packages/adp-tooling/test/unit/preview/change-handler.test.ts @@ -238,7 +238,9 @@ id="<%- ids.toolbarActionButton %>`); ).toBe(true); expect(mockFs.write).toHaveBeenCalled(); - expect(mockFs.write.mock.calls[0][0].replace(/\\/g, '/')).toMatchInlineSnapshot(`"project/path/changes/Share.fragment.xml"`); + expect(mockFs.write.mock.calls[0][0].replace(/\\/g, '/')).toMatchInlineSnapshot( + `"project/path/changes/Share.fragment.xml"` + ); expect(mockFs.write.mock.calls[0][1]).toMatchInlineSnapshot(` " id=\\"btn-30303030" From e1a30a93671883e9b70c4b8e864804d918bbd7c0 Mon Sep 17 00:00:00 2001 From: Nafees Ahmed Date: Sun, 22 Sep 2024 10:44:18 +0200 Subject: [PATCH 6/6] fix: refactoring and unit test --- .../fe-v2/change-table-columns.ts | 25 +- .../fe-v2/table-quick-action-base.ts | 64 ++--- .../test/unit/adp/quick-actions/fe-v2.test.ts | 241 +++++++++++++++++- 3 files changed, 287 insertions(+), 43 deletions(-) diff --git a/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/change-table-columns.ts b/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/change-table-columns.ts index 16c0482173..d7722fe273 100644 --- a/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/change-table-columns.ts +++ b/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/change-table-columns.ts @@ -1,4 +1,3 @@ - import FlexCommand from 'sap/ui/rta/command/FlexCommand'; import type Table from 'sap/m/Table'; import type SmartTable from 'sap/ui/comp/smarttable/SmartTable'; @@ -19,7 +18,7 @@ export class ChangeTableColumnsQuickAction implements NestedQuickActionDefinition { constructor(context: QuickActionContext) { - super(CHANGE_TABLE_COLUMNS, CONTROL_TYPES, 'V2_QUICK_ACTION_CHANGE_TABLE_COLUMNS', context); + super(CHANGE_TABLE_COLUMNS, CONTROL_TYPES, 'V2_QUICK_ACTION_CHANGE_TABLE_COLUMNS', context, true); } async execute(path: string): Promise { @@ -41,17 +40,19 @@ export class ChangeTableColumnsQuickAction if (this.iconTabBar && iconTabBarFilterKey) { this.iconTabBar.setSelectedKey(iconTabBarFilterKey); } - - const executeAction = async () => await this.context.actionService.execute(table.getId(), changeColumnActionId); - if (isA(SMART_TABLE_TYPE, table)) { - await executeAction(); - } else if (isA
(M_TABLE_TYPE, table)) { - // if table is busy, i.e. lazy loading, then we subscribe to 'updateFinished' event and call action service when loading is done - // to avoid reopening the dialog after close - if (this.isTableLoaded(table)) { + if (changeColumnActionId) { + const executeAction = async () => + await this.context.actionService.execute(table.getId(), changeColumnActionId); + if (isA(SMART_TABLE_TYPE, table)) { await executeAction(); - } else { - table.attachEventOnce('updateFinished', executeAction, this); + } else if (isA
(M_TABLE_TYPE, table)) { + // if table is busy, i.e. lazy loading, then we subscribe to 'updateFinished' event and call action service when loading is done + // to avoid reopening the dialog after close + if (this.isTableLoaded(table)) { + await executeAction(); + } else { + table.attachEventOnce('updateFinished', executeAction, this); + } } } diff --git a/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/table-quick-action-base.ts b/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/table-quick-action-base.ts index 46f951e2c7..ac493b086c 100644 --- a/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/table-quick-action-base.ts +++ b/packages/preview-middleware-client/src/adp/quick-actions/fe-v2/table-quick-action-base.ts @@ -53,7 +53,7 @@ export abstract class TableQuickActionDefinitionBase { table: UI5Element; tableUpdateEventAttachedOnce: boolean; iconTabBarFilterKey?: string; - changeColumnActionId: string; + changeColumnActionId?: string; sectionInfo?: { section: ObjectPageSection; subSection: ObjectPageSubSection; @@ -73,7 +73,8 @@ export abstract class TableQuickActionDefinitionBase { public readonly type: string, protected readonly controlTypes: string[], protected readonly defaultTextKey: string, - protected readonly context: QuickActionContext + protected readonly context: QuickActionContext, + protected includeServiceAction?: boolean ) {} async initialize(): Promise { @@ -89,30 +90,36 @@ export abstract class TableQuickActionDefinitionBase { this.context.view, this.controlTypes )) { - const actions = await this.context.actionService.get(table.getId()); - const actionsIds = await getActionId(table); - const changeColumnAction = actionsIds.find( - (actionId) => actions.findIndex((action) => action.id === actionId) > -1 - ); const tabKey = Object.keys(iconTabBarfilterMap).find((key) => table.getId().endsWith(key)); - if (changeColumnAction) { - const section = getParentContainer(table, 'sap.uxap.ObjectPageSection'); - if (section) { - this.collectChildrenInSection(section, table, changeColumnAction); - } else if (this.iconTabBar && tabKey) { - this.children.push({ - label: `'${iconTabBarfilterMap[tabKey]}' table`, - children: [] - }); - this.tableMap[`${this.children.length - 1}`] = { - table, - iconTabBarFilterKey: tabKey, - changeColumnActionId: changeColumnAction, - tableUpdateEventAttachedOnce: false - }; - } else { - this.processTable(table, changeColumnAction); - } + const section = getParentContainer(table, 'sap.uxap.ObjectPageSection'); + if (section) { + this.collectChildrenInSection(section, table); + } else if (this.iconTabBar && tabKey) { + this.children.push({ + label: `'${iconTabBarfilterMap[tabKey]}' table`, + children: [] + }); + this.tableMap[`${this.children.length - 1}`] = { + table, + iconTabBarFilterKey: tabKey, + tableUpdateEventAttachedOnce: false + }; + } else { + this.processTable(table); + } + + // add action id to the table map, if the service actions are needed. + if (this.includeServiceAction) { + const actions = await this.context.actionService.get(table.getId()); + const actionsIds = await getActionId(table); + + const changeColumnAction = actionsIds.find( + (actionId) => actions.findIndex((action) => action.id === actionId) > -1 + ); + Object.keys(this.tableMap).forEach((key) => { + // Update the changeColumnActionId for each entry + this.tableMap[key].changeColumnActionId = changeColumnAction; + }); } } if (this.children.length > 0) { @@ -159,13 +166,13 @@ export abstract class TableQuickActionDefinitionBase { return iconTabBarfilterMap; } - private collectChildrenInSection(section: ObjectPageSection, table: UI5Element, changeColumnAction: string): void { + private collectChildrenInSection(section: ObjectPageSection, table: UI5Element): void { const layout = getParentContainer(table, 'sap.uxap.ObjectPageLayout'); const subSections = section.getSubSections(); const subSection = getParentContainer(table, 'sap.uxap.ObjectPageSubSection'); if (subSection) { if (subSections?.length === 1) { - this.processTable(table, changeColumnAction, { section, subSection: subSections[0], layout }); + this.processTable(table, { section, subSection: subSections[0], layout }); } else if (subSections.length > 1) { const sectionChild = this.children.find((val) => val.label === `${section.getTitle()} section`); let tableMapIndex = `${this.children.length - 1}`; @@ -190,7 +197,6 @@ export abstract class TableQuickActionDefinitionBase { this.tableMap[tableMapIndex] = { table, - changeColumnActionId: changeColumnAction, sectionInfo: { section, subSection, layout }, tableUpdateEventAttachedOnce: false }; @@ -200,7 +206,6 @@ export abstract class TableQuickActionDefinitionBase { private processTable( table: UI5Element, - changeColumnActionId: string, sectionInfo?: { section: ObjectPageSection; subSection: ObjectPageSubSection; layout?: ObjectPageLayout } ): void { if (isA(SMART_TABLE_TYPE, table) || isA('sap.ui.table.TreeTable', table)) { @@ -217,7 +222,6 @@ export abstract class TableQuickActionDefinitionBase { } this.tableMap[`${this.children.length - 1}`] = { table, - changeColumnActionId, sectionInfo: sectionInfo, tableUpdateEventAttachedOnce: false }; diff --git a/packages/preview-middleware-client/test/unit/adp/quick-actions/fe-v2.test.ts b/packages/preview-middleware-client/test/unit/adp/quick-actions/fe-v2.test.ts index 57c36c35d7..e4fb275105 100644 --- a/packages/preview-middleware-client/test/unit/adp/quick-actions/fe-v2.test.ts +++ b/packages/preview-middleware-client/test/unit/adp/quick-actions/fe-v2.test.ts @@ -271,6 +271,13 @@ describe('FE V2 quick actions', () => { getDomRef: () => ({ scrollIntoView }), + getAggregation: () => { + return [ + { + getAggregation: () => 'headerToolbar' + } + ]; + }, getParent: () => pageView, getBusy: () => false }; @@ -378,11 +385,243 @@ describe('FE V2 quick actions', () => { await subscribeMock.mock.calls[0][0]( executeQuickAction({ id: 'listReport0-change-table-columns', kind: 'nested', path: '0' }) ); - + await subscribeMock.mock.calls[0][0]( + executeQuickAction({ id: 'listReport0-create-table-action', kind: 'nested', path: '0' }) + ); expect(scrollIntoView).toHaveBeenCalled(); expect(execute).toHaveBeenCalledWith('SmartTable', 'CTX_COMP_VARIANT_CONTENT'); }); }); + + describe('create table action', () => { + test('initialize and execute action', async () => { + const pageView = new XMLView(); + + const scrollIntoView = jest.fn(); + sapCoreMock.byId.mockImplementation((id) => { + if (id == 'mTable') { + return { + isA: (type: string) => type === 'sap.m.Table', + getId: () => id, + getDomRef: () => ({ + scrollIntoView + }), + getAggregation: () => 'headerToolbar', + getParent: () => pageView, + getHeaderToolbar: () => { + return { + getTitleControl: () => { + return { + getText: () => 'MyTable' + }; + } + }; + } + }; + } + if (id == 'NavContainer') { + const container = new NavContainer(); + const component = new UIComponentMock(); + const view = new XMLView(); + pageView.getDomRef.mockImplementation(() => { + return { + contains: () => true + }; + }); + pageView.getViewName.mockImplementation( + () => 'sap.suite.ui.generic.template.ListReport.view.ListReport' + ); + const componentContainer = new ComponentContainer(); + const spy = jest.spyOn(componentContainer, 'getComponent'); + spy.mockImplementation(() => { + return 'component-id'; + }); + jest.spyOn(Component, 'getComponentById').mockImplementation((id: string | undefined) => { + if (id === 'component-id') { + return component; + } + }); + view.getContent.mockImplementation(() => { + return [componentContainer]; + }); + container.getCurrentPage.mockImplementation(() => { + return view; + }); + component.getRootControl.mockImplementation(() => { + return pageView; + }); + return container; + } + }); + const rtaMock = new RuntimeAuthoringMock({} as RTAOptions) as unknown as RuntimeAuthoring; + const registry = new FEV2QuickActionRegistry(); + const service = new QuickActionService(rtaMock, new OutlineService(rtaMock), [registry]); + await service.init(sendActionMock, subscribeMock); + + await service.reloadQuickActions({ + 'sap.m.Table': [ + { + controlId: 'mTable' + } as any + ], + 'sap.m.NavContainer': [ + { + controlId: 'NavContainer' + } as any + ] + }); + + expect(sendActionMock).toHaveBeenCalledWith( + quickActionListChanged([ + { + title: 'LIST REPORT', + actions: [ + { + 'kind': 'nested', + id: 'listReport0-create-table-action', + title: 'Add Custom Table Action', + enabled: true, + children: [ + { + children: [], + label: `'MyTable' table` + } + ] + } + ] + } + ]) + ); + + await subscribeMock.mock.calls[0][0]( + executeQuickAction({ id: 'listReport0-create-table-action', kind: 'nested', path: '0' }) + ); + + const { handler } = jest.requireMock<{ handler: () => Promise }>( + '../../../../src/adp/init-dialogs' + ); + + expect(handler).toHaveBeenCalledWith(mockOverlay, rtaMock, 'AddFragment', undefined, 'content'); + }); + }); + describe('add page action', () => { + test('initialize and execute action', async () => { + const pageView = new XMLView(); + FlexUtils.getViewForControl.mockImplementation(() => { + return { + getId: () => 'MyView', + getController: () => { + return { + getMetadata: () => { + return { + getName: () => 'MyController' + }; + } + }; + } + }; + }); + fetchMock.mockResolvedValue({ + json: jest + .fn() + .mockReturnValueOnce({ + controllerExists: false, + controllerPath: '', + controllerPathFromRoot: '', + isRunningInBAS: false + }) + .mockReturnValueOnce({ controllers: [] }), + text: jest.fn(), + ok: true + }); + + sapCoreMock.byId.mockImplementation((id) => { + if (id == 'DynamicPageTitle') { + return { + getId: () => id, + getDomRef: () => ({}), + getParent: () => pageView + }; + } + if (id == 'NavContainer') { + const container = new NavContainer(); + const component = new UIComponentMock(); + const view = new XMLView(); + pageView.getDomRef.mockImplementation(() => { + return { + contains: () => true + }; + }); + pageView.getViewName.mockImplementation( + () => 'sap.suite.ui.generic.template.ListReport.view.ListReport' + ); + const componentContainer = new ComponentContainer(); + const spy = jest.spyOn(componentContainer, 'getComponent'); + spy.mockImplementation(() => { + return 'component-id'; + }); + jest.spyOn(Component, 'getComponentById').mockImplementation((id: string | undefined) => { + if (id === 'component-id') { + return component; + } + }); + view.getContent.mockImplementation(() => { + return [componentContainer]; + }); + container.getCurrentPage.mockImplementation(() => { + return view; + }); + component.getRootControl.mockImplementation(() => { + return pageView; + }); + return container; + } + }); + + const rtaMock = new RuntimeAuthoringMock({} as RTAOptions) as unknown as RuntimeAuthoring; + const registry = new FEV2QuickActionRegistry(); + const service = new QuickActionService(rtaMock, new OutlineService(rtaMock), [registry]); + await service.init(sendActionMock, subscribeMock); + + await service.reloadQuickActions({ + 'sap.f.DynamicPageTitle': [ + { + controlId: 'DynamicPageTitle' + } as any + ], + 'sap.m.NavContainer': [ + { + controlId: 'NavContainer' + } as any + ] + }); + + expect(sendActionMock).toHaveBeenCalledWith( + quickActionListChanged([ + { + title: 'LIST REPORT', + actions: [ + { + kind: 'simple', + id: 'listReport0-add-page-action', + enabled: true, + title: 'Add Custom Page Action' + } + ] + } + ]) + ); + + await subscribeMock.mock.calls[0][0]( + executeQuickAction({ id: 'listReport0-add-page-action', kind: 'simple' }) + ); + const { handler } = jest.requireMock<{ handler: () => Promise }>( + '../../../../src/adp/init-dialogs' + ); + + expect(handler).toHaveBeenCalledWith(mockOverlay, rtaMock, 'AddFragment', undefined, 'actions'); + }); + }); }); describe('ObjectPage', () => { describe('add header field', () => {