@sap-ux/ui5-test-writer 0.9.3 → 0.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/types.d.ts +19 -0
- package/dist/utils/actionUtils.d.ts +137 -0
- package/dist/utils/actionUtils.js +304 -0
- package/dist/utils/listReportUtils.d.ts +4 -20
- package/dist/utils/listReportUtils.js +14 -190
- package/dist/utils/modelUtils.js +1 -1
- package/dist/utils/objectPageUtils.d.ts +2 -1
- package/dist/utils/objectPageUtils.js +109 -9
- package/package.json +1 -1
- package/templates/v4/integration/ObjectPageJourney.js +51 -0
package/dist/types.d.ts
CHANGED
|
@@ -107,6 +107,9 @@ export type BodySectionFeatureData = {
|
|
|
107
107
|
fields: SectionFormField[];
|
|
108
108
|
tableColumns: TableColumnFeatureData;
|
|
109
109
|
subSections: BodySubSectionFeatureData[];
|
|
110
|
+
actions?: ActionButtonState[];
|
|
111
|
+
createButton?: ButtonState;
|
|
112
|
+
deleteButton?: ButtonState;
|
|
110
113
|
};
|
|
111
114
|
export type ObjectPageFeatures = {
|
|
112
115
|
name?: string;
|
|
@@ -115,6 +118,8 @@ export type ObjectPageFeatures = {
|
|
|
115
118
|
headerDescription?: string;
|
|
116
119
|
headerSections?: HeaderSectionFeatureData[];
|
|
117
120
|
bodySections?: BodySectionFeatureData[];
|
|
121
|
+
headerActions?: ActionButtonState[];
|
|
122
|
+
editButton?: ButtonState;
|
|
118
123
|
};
|
|
119
124
|
export type ListReportFeatures = {
|
|
120
125
|
name?: string;
|
|
@@ -135,6 +140,10 @@ export type ListReportFeatures = {
|
|
|
135
140
|
};
|
|
136
141
|
export interface ActionButtonState {
|
|
137
142
|
label: string;
|
|
143
|
+
/**
|
|
144
|
+
* For List Report actions: the full OData binding path (e.g. "namespace.ActionName(entityType=@odata.context)").
|
|
145
|
+
* For Object Page actions extracted from the spec model: the method name only (e.g. "Copy").
|
|
146
|
+
*/
|
|
138
147
|
action: string;
|
|
139
148
|
visible: boolean;
|
|
140
149
|
/**
|
|
@@ -153,6 +162,16 @@ export interface ActionButtonState {
|
|
|
153
162
|
* The invocation grouping type if specified (e.g., "Isolated", "ChangeSet").
|
|
154
163
|
*/
|
|
155
164
|
invocationGrouping?: string;
|
|
165
|
+
/**
|
|
166
|
+
* OData schema namespace used as the `service` parameter in iCheckAction({ service, action, unbound }).
|
|
167
|
+
* Populated for Object Page actions extracted via the spec model + metadata.
|
|
168
|
+
*/
|
|
169
|
+
service?: string;
|
|
170
|
+
/**
|
|
171
|
+
* Whether the action is unbound (not bound to a specific entity instance).
|
|
172
|
+
* Populated for Object Page actions extracted via the spec model + metadata.
|
|
173
|
+
*/
|
|
174
|
+
unbound?: boolean;
|
|
156
175
|
}
|
|
157
176
|
export type FPMFeatures = {
|
|
158
177
|
name?: string;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import type { ConvertedMetadata } from '@sap-ux/vocabularies-types';
|
|
2
|
+
import type { ActionButtonState, ButtonState, ButtonVisibilityResult } from '../types';
|
|
3
|
+
import type { DataFieldForAction } from '@sap-ux/vocabularies-types/vocabularies/UI';
|
|
4
|
+
import type { OperationAvailable } from '@sap-ux/vocabularies-types/vocabularies/Core';
|
|
5
|
+
import type { DeleteRestrictionsType, InsertRestrictionsType, UpdateRestrictionsType } from '@sap-ux/vocabularies-types/vocabularies/Capabilities';
|
|
6
|
+
import type { Logger } from '@sap-ux/logger';
|
|
7
|
+
type OperationAvailableWithPaths = OperationAvailable & {
|
|
8
|
+
$Path?: string;
|
|
9
|
+
path?: string;
|
|
10
|
+
};
|
|
11
|
+
type RestrictionValueWithPaths = (boolean | {
|
|
12
|
+
$Path?: string;
|
|
13
|
+
path?: string;
|
|
14
|
+
}) | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* Extracts the action method name from a fully qualified action string.
|
|
17
|
+
*
|
|
18
|
+
* @param actionName The fully qualified action name
|
|
19
|
+
* @returns The action method name
|
|
20
|
+
*/
|
|
21
|
+
export declare function extractActionMethodName(actionName: string): string;
|
|
22
|
+
/**
|
|
23
|
+
* Finds the Core.OperationAvailable annotation for a specific action.
|
|
24
|
+
*
|
|
25
|
+
* @param metadata The converted metadata
|
|
26
|
+
* @param actionMethodName The action method name
|
|
27
|
+
* @returns The OperationAvailable annotation value or undefined if not found
|
|
28
|
+
*/
|
|
29
|
+
export declare function findOperationAvailableAnnotation(metadata: ConvertedMetadata, actionMethodName: string): OperationAvailableWithPaths | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* Analyzes Core.OperationAvailable annotation to determine action availability.
|
|
32
|
+
* Single-entity bound actions (requiring row selection) are disabled by default when no annotation is present.
|
|
33
|
+
*
|
|
34
|
+
* @param operationAvailable The OperationAvailable annotation value
|
|
35
|
+
* @param isEntityBound Whether the action is bound to a single entity (requires row selection to enable)
|
|
36
|
+
* @returns Object containing enabled state and optional dynamic path
|
|
37
|
+
*/
|
|
38
|
+
export declare function analyzeOperationAvailability(operationAvailable: OperationAvailableWithPaths | undefined, isEntityBound?: boolean): {
|
|
39
|
+
enabled: boolean | 'dynamic';
|
|
40
|
+
dynamicPath?: string;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Extracts the enum member value from an annotation.
|
|
44
|
+
*
|
|
45
|
+
* @param enumValue The enum value object
|
|
46
|
+
* @returns The extracted enum value string
|
|
47
|
+
*/
|
|
48
|
+
export declare function extractEnumMemberValue(enumValue: unknown): string | undefined;
|
|
49
|
+
/**
|
|
50
|
+
* Builds an ActionButtonState object from a DataFieldForAction annotation item.
|
|
51
|
+
*
|
|
52
|
+
* @param item The DataFieldForAction annotation item
|
|
53
|
+
* @param metadata The converted metadata
|
|
54
|
+
* @returns ActionButtonState for the action
|
|
55
|
+
*/
|
|
56
|
+
export declare function buildActionButtonState(item: DataFieldForAction, metadata: ConvertedMetadata): ActionButtonState;
|
|
57
|
+
/**
|
|
58
|
+
* Builds an ActionButtonState from a spec model aggregation key.
|
|
59
|
+
*
|
|
60
|
+
* Key format: "DataFieldForAction::<namespace>.<Method>::<namespace>.<EntityType>"
|
|
61
|
+
* Example: "DataFieldForAction::com.example.Copy::com.example.POEntity".
|
|
62
|
+
*
|
|
63
|
+
* @param aggregationKey The spec model aggregation key for the action
|
|
64
|
+
* @param label Display label from the spec model item description
|
|
65
|
+
* @param convertedMetadata The converted OData metadata
|
|
66
|
+
* @param schemaNamespace The OData schema namespace (used as service identifier)
|
|
67
|
+
* @returns ActionButtonState or undefined if the key is not a DataFieldForAction key
|
|
68
|
+
*/
|
|
69
|
+
export declare function buildActionStateFromSpecModelKey(aggregationKey: string, label: string | undefined, convertedMetadata: ConvertedMetadata, schemaNamespace: string): ActionButtonState | undefined;
|
|
70
|
+
/**
|
|
71
|
+
* Analyzes a restriction value (Insertable, Deletable, or Updatable) to determine button state.
|
|
72
|
+
*
|
|
73
|
+
* @param value The annotation value — boolean, path object, or undefined
|
|
74
|
+
* @returns ButtonState indicating visibility and enabled state
|
|
75
|
+
*/
|
|
76
|
+
export declare function analyzeRestrictionValue(value: RestrictionValueWithPaths): ButtonState;
|
|
77
|
+
/**
|
|
78
|
+
* Analyzes InsertRestrictions annotation to determine create button visibility and enabled state.
|
|
79
|
+
*
|
|
80
|
+
* @param restriction The InsertRestrictions annotation for the entity set
|
|
81
|
+
* @returns ButtonState indicating visibility and enabled state based on the Insertable value
|
|
82
|
+
*/
|
|
83
|
+
export declare function analyzeInsertRestrictions(restriction: InsertRestrictionsType | undefined): ButtonState;
|
|
84
|
+
/**
|
|
85
|
+
* Analyzes DeleteRestrictions annotation to determine delete button visibility and enabled state.
|
|
86
|
+
*
|
|
87
|
+
* @param restriction The DeleteRestrictions annotation for the entity set
|
|
88
|
+
* @returns ButtonState indicating visibility and enabled state based on the Deletable value
|
|
89
|
+
*/
|
|
90
|
+
export declare function analyzeDeleteRestrictions(restriction: DeleteRestrictionsType | undefined): ButtonState;
|
|
91
|
+
/**
|
|
92
|
+
* Analyzes UpdateRestrictions annotation to determine edit button visibility and enabled state.
|
|
93
|
+
*
|
|
94
|
+
* @param restriction The UpdateRestrictions annotation for the entity set
|
|
95
|
+
* @returns ButtonState indicating visibility and enabled state based on the Updatable value
|
|
96
|
+
*/
|
|
97
|
+
export declare function analyzeUpdateRestrictions(restriction: UpdateRestrictionsType | undefined): ButtonState;
|
|
98
|
+
/**
|
|
99
|
+
* Checks the visibility and enabled state of create and delete buttons for a given entity set
|
|
100
|
+
* by analyzing OData Capabilities annotations in the metadata.
|
|
101
|
+
*
|
|
102
|
+
* @param metadataXml The OData metadata XML content as a string
|
|
103
|
+
* @param entitySetName The name of the entity set to check
|
|
104
|
+
* @returns ButtonVisibilityResult containing the state of create and delete buttons
|
|
105
|
+
* @throws {Error} If metadata cannot be parsed or entity set is not found
|
|
106
|
+
*/
|
|
107
|
+
export declare function checkButtonVisibility(metadataXml: string, entitySetName: string): ButtonVisibilityResult;
|
|
108
|
+
/**
|
|
109
|
+
* Checks the visibility and enabled state of the edit button for a given entity set
|
|
110
|
+
* by analyzing UpdateRestrictions in the metadata.
|
|
111
|
+
*
|
|
112
|
+
* @param metadataXml The OData metadata XML content as a string
|
|
113
|
+
* @param entitySetName The name of the entity set to check
|
|
114
|
+
* @returns ButtonState for the edit button
|
|
115
|
+
* @throws {Error} If metadata cannot be parsed or entity set is not found
|
|
116
|
+
*/
|
|
117
|
+
export declare function checkEditVisibility(metadataXml: string, entitySetName: string): ButtonState;
|
|
118
|
+
/**
|
|
119
|
+
* Safely checks button visibility with error handling.
|
|
120
|
+
*
|
|
121
|
+
* @param metadata The OData metadata XML content
|
|
122
|
+
* @param entitySetName The name of the entity set
|
|
123
|
+
* @param log Optional logger instance
|
|
124
|
+
* @returns Button visibility result or undefined if error occurs
|
|
125
|
+
*/
|
|
126
|
+
export declare function safeCheckButtonVisibility(metadata: string, entitySetName: string, log?: Logger): ButtonVisibilityResult | undefined;
|
|
127
|
+
/**
|
|
128
|
+
* Safely checks edit button visibility with error handling.
|
|
129
|
+
*
|
|
130
|
+
* @param metadata The OData metadata XML content
|
|
131
|
+
* @param entitySetName The name of the entity set
|
|
132
|
+
* @param log Optional logger instance
|
|
133
|
+
* @returns ButtonState for the edit button, or undefined if error occurs
|
|
134
|
+
*/
|
|
135
|
+
export declare function safeCheckEditVisibility(metadata: string, entitySetName: string, log?: Logger): ButtonState | undefined;
|
|
136
|
+
export {};
|
|
137
|
+
//# sourceMappingURL=actionUtils.d.ts.map
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.extractActionMethodName = extractActionMethodName;
|
|
4
|
+
exports.findOperationAvailableAnnotation = findOperationAvailableAnnotation;
|
|
5
|
+
exports.analyzeOperationAvailability = analyzeOperationAvailability;
|
|
6
|
+
exports.extractEnumMemberValue = extractEnumMemberValue;
|
|
7
|
+
exports.buildActionButtonState = buildActionButtonState;
|
|
8
|
+
exports.buildActionStateFromSpecModelKey = buildActionStateFromSpecModelKey;
|
|
9
|
+
exports.analyzeRestrictionValue = analyzeRestrictionValue;
|
|
10
|
+
exports.analyzeInsertRestrictions = analyzeInsertRestrictions;
|
|
11
|
+
exports.analyzeDeleteRestrictions = analyzeDeleteRestrictions;
|
|
12
|
+
exports.analyzeUpdateRestrictions = analyzeUpdateRestrictions;
|
|
13
|
+
exports.checkButtonVisibility = checkButtonVisibility;
|
|
14
|
+
exports.checkEditVisibility = checkEditVisibility;
|
|
15
|
+
exports.safeCheckButtonVisibility = safeCheckButtonVisibility;
|
|
16
|
+
exports.safeCheckEditVisibility = safeCheckEditVisibility;
|
|
17
|
+
const edmx_parser_1 = require("@sap-ux/edmx-parser");
|
|
18
|
+
const annotation_converter_1 = require("@sap-ux/annotation-converter");
|
|
19
|
+
const DATA_FIELD_FOR_ACTION = 'DataFieldForAction';
|
|
20
|
+
/**
|
|
21
|
+
* Extracts the action method name from a fully qualified action string.
|
|
22
|
+
*
|
|
23
|
+
* @param actionName The fully qualified action name
|
|
24
|
+
* @returns The action method name
|
|
25
|
+
*/
|
|
26
|
+
function extractActionMethodName(actionName) {
|
|
27
|
+
const match = /\.([^.()]+)\(/.exec(actionName);
|
|
28
|
+
if (match?.[1]) {
|
|
29
|
+
return match[1];
|
|
30
|
+
}
|
|
31
|
+
const lastDotIndex = actionName.lastIndexOf('.');
|
|
32
|
+
const parenIndex = actionName.indexOf('(');
|
|
33
|
+
if (lastDotIndex >= 0 && parenIndex >= 0 && parenIndex > lastDotIndex) {
|
|
34
|
+
return actionName.substring(lastDotIndex + 1, parenIndex);
|
|
35
|
+
}
|
|
36
|
+
// Handle namespace-qualified name without parentheses (spec model key format: "namespace.Method")
|
|
37
|
+
if (lastDotIndex >= 0) {
|
|
38
|
+
return actionName.substring(lastDotIndex + 1);
|
|
39
|
+
}
|
|
40
|
+
return actionName;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Finds the Core.OperationAvailable annotation for a specific action.
|
|
44
|
+
*
|
|
45
|
+
* @param metadata The converted metadata
|
|
46
|
+
* @param actionMethodName The action method name
|
|
47
|
+
* @returns The OperationAvailable annotation value or undefined if not found
|
|
48
|
+
*/
|
|
49
|
+
function findOperationAvailableAnnotation(metadata, actionMethodName) {
|
|
50
|
+
if (metadata.actions) {
|
|
51
|
+
const foundAction = metadata.actions.find((action) => action.name === actionMethodName || action.fullyQualifiedName?.includes(`.${actionMethodName}(`));
|
|
52
|
+
if (foundAction?.annotations?.Core?.OperationAvailable !== undefined) {
|
|
53
|
+
return foundAction.annotations.Core.OperationAvailable;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (metadata.entityContainer?.annotations) {
|
|
57
|
+
const annotations = metadata.entityContainer.annotations;
|
|
58
|
+
const matchingKey = Object.keys(annotations).find((key) => key === actionMethodName || key.endsWith(`.${actionMethodName}`));
|
|
59
|
+
if (matchingKey && annotations[matchingKey]?.Core?.OperationAvailable !== undefined) {
|
|
60
|
+
return annotations[matchingKey].Core.OperationAvailable;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Analyzes Core.OperationAvailable annotation to determine action availability.
|
|
67
|
+
* Single-entity bound actions (requiring row selection) are disabled by default when no annotation is present.
|
|
68
|
+
*
|
|
69
|
+
* @param operationAvailable The OperationAvailable annotation value
|
|
70
|
+
* @param isEntityBound Whether the action is bound to a single entity (requires row selection to enable)
|
|
71
|
+
* @returns Object containing enabled state and optional dynamic path
|
|
72
|
+
*/
|
|
73
|
+
function analyzeOperationAvailability(operationAvailable, isEntityBound) {
|
|
74
|
+
if (operationAvailable === undefined) {
|
|
75
|
+
return { enabled: !isEntityBound };
|
|
76
|
+
}
|
|
77
|
+
if (typeof operationAvailable === 'boolean') {
|
|
78
|
+
return { enabled: operationAvailable };
|
|
79
|
+
}
|
|
80
|
+
if (typeof operationAvailable === 'object' && operationAvailable !== null) {
|
|
81
|
+
const pathRecord = operationAvailable;
|
|
82
|
+
const path = pathRecord.$Path ?? pathRecord.path;
|
|
83
|
+
if (path) {
|
|
84
|
+
return { enabled: 'dynamic', dynamicPath: path };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return { enabled: true };
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Extracts the enum member value from an annotation.
|
|
91
|
+
*
|
|
92
|
+
* @param enumValue The enum value object
|
|
93
|
+
* @returns The extracted enum value string
|
|
94
|
+
*/
|
|
95
|
+
function extractEnumMemberValue(enumValue) {
|
|
96
|
+
if (typeof enumValue === 'string') {
|
|
97
|
+
return enumValue;
|
|
98
|
+
}
|
|
99
|
+
const enumRecord = enumValue;
|
|
100
|
+
if (enumRecord?.$EnumMember) {
|
|
101
|
+
const parts = enumRecord.$EnumMember.split('/');
|
|
102
|
+
return parts[1] ?? enumRecord.$EnumMember;
|
|
103
|
+
}
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Builds an ActionButtonState object from a DataFieldForAction annotation item.
|
|
108
|
+
*
|
|
109
|
+
* @param item The DataFieldForAction annotation item
|
|
110
|
+
* @param metadata The converted metadata
|
|
111
|
+
* @returns ActionButtonState for the action
|
|
112
|
+
*/
|
|
113
|
+
function buildActionButtonState(item, metadata) {
|
|
114
|
+
const actionString = item.Action || '';
|
|
115
|
+
const actionMethod = extractActionMethodName(actionString);
|
|
116
|
+
const operationAvailable = findOperationAvailableAnnotation(metadata, actionMethod);
|
|
117
|
+
// Bound actions whose binding parameter is a single entity (not a collection) require
|
|
118
|
+
// row selection to be invoked, so they are disabled by default (no row selected).
|
|
119
|
+
// Collection-bound actions operate on the entity set and are always enabled.
|
|
120
|
+
const actionTarget = item.ActionTarget;
|
|
121
|
+
const isEntityBound = actionTarget?.isBound === true && actionTarget?.parameters?.[0]?.isCollection !== true;
|
|
122
|
+
const { enabled, dynamicPath } = analyzeOperationAvailability(operationAvailable, isEntityBound);
|
|
123
|
+
return {
|
|
124
|
+
label: item.Label || '',
|
|
125
|
+
action: actionString,
|
|
126
|
+
visible: true,
|
|
127
|
+
enabled,
|
|
128
|
+
dynamicPath,
|
|
129
|
+
invocationGrouping: item.InvocationGrouping ? extractEnumMemberValue(item.InvocationGrouping) : undefined
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Builds an ActionButtonState from a spec model aggregation key.
|
|
134
|
+
*
|
|
135
|
+
* Key format: "DataFieldForAction::<namespace>.<Method>::<namespace>.<EntityType>"
|
|
136
|
+
* Example: "DataFieldForAction::com.example.Copy::com.example.POEntity".
|
|
137
|
+
*
|
|
138
|
+
* @param aggregationKey The spec model aggregation key for the action
|
|
139
|
+
* @param label Display label from the spec model item description
|
|
140
|
+
* @param convertedMetadata The converted OData metadata
|
|
141
|
+
* @param schemaNamespace The OData schema namespace (used as service identifier)
|
|
142
|
+
* @returns ActionButtonState or undefined if the key is not a DataFieldForAction key
|
|
143
|
+
*/
|
|
144
|
+
function buildActionStateFromSpecModelKey(aggregationKey, label, convertedMetadata, schemaNamespace) {
|
|
145
|
+
const keyParts = aggregationKey.split('::');
|
|
146
|
+
if (keyParts[0] !== DATA_FIELD_FOR_ACTION || !keyParts[1]) {
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
const actionFullName = keyParts[1]; // "namespace.Method"
|
|
150
|
+
const actionMethod = extractActionMethodName(actionFullName);
|
|
151
|
+
const actionDefinition = convertedMetadata.actions?.find((action) => action.name === actionMethod || action.fullyQualifiedName?.includes(`.${actionMethod}(`));
|
|
152
|
+
const firstParameter = actionDefinition?.parameters?.[0];
|
|
153
|
+
const isEntityBound = actionDefinition?.isBound === true && firstParameter?.isCollection !== true;
|
|
154
|
+
const operationAvailable = findOperationAvailableAnnotation(convertedMetadata, actionMethod);
|
|
155
|
+
const { enabled, dynamicPath } = analyzeOperationAvailability(operationAvailable, isEntityBound);
|
|
156
|
+
return {
|
|
157
|
+
label: label ?? '',
|
|
158
|
+
action: actionMethod,
|
|
159
|
+
service: schemaNamespace,
|
|
160
|
+
unbound: !isEntityBound,
|
|
161
|
+
visible: true,
|
|
162
|
+
enabled,
|
|
163
|
+
dynamicPath
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Analyzes a restriction value (Insertable, Deletable, or Updatable) to determine button state.
|
|
168
|
+
*
|
|
169
|
+
* @param value The annotation value — boolean, path object, or undefined
|
|
170
|
+
* @returns ButtonState indicating visibility and enabled state
|
|
171
|
+
*/
|
|
172
|
+
function analyzeRestrictionValue(value) {
|
|
173
|
+
const defaultState = { visible: true, enabled: true };
|
|
174
|
+
if (value === undefined || value === null) {
|
|
175
|
+
return defaultState;
|
|
176
|
+
}
|
|
177
|
+
if (typeof value === 'boolean') {
|
|
178
|
+
return { visible: value, enabled: value };
|
|
179
|
+
}
|
|
180
|
+
if (typeof value === 'object') {
|
|
181
|
+
const path = value.$Path ?? value.path;
|
|
182
|
+
if (path) {
|
|
183
|
+
return { visible: true, enabled: 'dynamic', dynamicPath: path };
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return defaultState;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Analyzes InsertRestrictions annotation to determine create button visibility and enabled state.
|
|
190
|
+
*
|
|
191
|
+
* @param restriction The InsertRestrictions annotation for the entity set
|
|
192
|
+
* @returns ButtonState indicating visibility and enabled state based on the Insertable value
|
|
193
|
+
*/
|
|
194
|
+
function analyzeInsertRestrictions(restriction) {
|
|
195
|
+
const value = restriction ? restriction['Insertable'] : undefined;
|
|
196
|
+
return analyzeRestrictionValue(value);
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Analyzes DeleteRestrictions annotation to determine delete button visibility and enabled state.
|
|
200
|
+
*
|
|
201
|
+
* @param restriction The DeleteRestrictions annotation for the entity set
|
|
202
|
+
* @returns ButtonState indicating visibility and enabled state based on the Deletable value
|
|
203
|
+
*/
|
|
204
|
+
function analyzeDeleteRestrictions(restriction) {
|
|
205
|
+
const value = restriction ? restriction['Deletable'] : undefined;
|
|
206
|
+
return analyzeRestrictionValue(value);
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Analyzes UpdateRestrictions annotation to determine edit button visibility and enabled state.
|
|
210
|
+
*
|
|
211
|
+
* @param restriction The UpdateRestrictions annotation for the entity set
|
|
212
|
+
* @returns ButtonState indicating visibility and enabled state based on the Updatable value
|
|
213
|
+
*/
|
|
214
|
+
function analyzeUpdateRestrictions(restriction) {
|
|
215
|
+
const value = restriction ? restriction['Updatable'] : undefined;
|
|
216
|
+
return analyzeRestrictionValue(value);
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Checks the visibility and enabled state of create and delete buttons for a given entity set
|
|
220
|
+
* by analyzing OData Capabilities annotations in the metadata.
|
|
221
|
+
*
|
|
222
|
+
* @param metadataXml The OData metadata XML content as a string
|
|
223
|
+
* @param entitySetName The name of the entity set to check
|
|
224
|
+
* @returns ButtonVisibilityResult containing the state of create and delete buttons
|
|
225
|
+
* @throws {Error} If metadata cannot be parsed or entity set is not found
|
|
226
|
+
*/
|
|
227
|
+
function checkButtonVisibility(metadataXml, entitySetName) {
|
|
228
|
+
try {
|
|
229
|
+
const convertedMetadata = (0, annotation_converter_1.convert)((0, edmx_parser_1.parse)(metadataXml));
|
|
230
|
+
const entitySet = convertedMetadata.entitySets.find((es) => es.name === entitySetName);
|
|
231
|
+
if (!entitySet) {
|
|
232
|
+
throw new Error(`Entity set '${entitySetName}' not found in metadata`);
|
|
233
|
+
}
|
|
234
|
+
const insertRestrictions = entitySet.annotations?.Capabilities?.InsertRestrictions;
|
|
235
|
+
const deleteRestrictions = entitySet.annotations?.Capabilities?.DeleteRestrictions;
|
|
236
|
+
return {
|
|
237
|
+
create: analyzeInsertRestrictions(insertRestrictions),
|
|
238
|
+
delete: analyzeDeleteRestrictions(deleteRestrictions)
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
catch (error) {
|
|
242
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
243
|
+
throw new Error(`Failed to analyze button visibility: ${errorMessage}`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Checks the visibility and enabled state of the edit button for a given entity set
|
|
248
|
+
* by analyzing UpdateRestrictions in the metadata.
|
|
249
|
+
*
|
|
250
|
+
* @param metadataXml The OData metadata XML content as a string
|
|
251
|
+
* @param entitySetName The name of the entity set to check
|
|
252
|
+
* @returns ButtonState for the edit button
|
|
253
|
+
* @throws {Error} If metadata cannot be parsed or entity set is not found
|
|
254
|
+
*/
|
|
255
|
+
function checkEditVisibility(metadataXml, entitySetName) {
|
|
256
|
+
try {
|
|
257
|
+
const convertedMetadata = (0, annotation_converter_1.convert)((0, edmx_parser_1.parse)(metadataXml));
|
|
258
|
+
const entitySet = convertedMetadata.entitySets.find((es) => es.name === entitySetName);
|
|
259
|
+
if (!entitySet) {
|
|
260
|
+
throw new Error(`Entity set '${entitySetName}' not found in metadata`);
|
|
261
|
+
}
|
|
262
|
+
const updateRestrictions = entitySet.annotations?.Capabilities?.UpdateRestrictions;
|
|
263
|
+
return analyzeUpdateRestrictions(updateRestrictions);
|
|
264
|
+
}
|
|
265
|
+
catch (error) {
|
|
266
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
267
|
+
throw new Error(`Failed to analyze edit visibility: ${errorMessage}`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Safely checks button visibility with error handling.
|
|
272
|
+
*
|
|
273
|
+
* @param metadata The OData metadata XML content
|
|
274
|
+
* @param entitySetName The name of the entity set
|
|
275
|
+
* @param log Optional logger instance
|
|
276
|
+
* @returns Button visibility result or undefined if error occurs
|
|
277
|
+
*/
|
|
278
|
+
function safeCheckButtonVisibility(metadata, entitySetName, log) {
|
|
279
|
+
try {
|
|
280
|
+
return checkButtonVisibility(metadata, entitySetName);
|
|
281
|
+
}
|
|
282
|
+
catch (error) {
|
|
283
|
+
log?.debug(`Failed to check button visibility: ${error instanceof Error ? error.message : String(error)}`);
|
|
284
|
+
return undefined;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Safely checks edit button visibility with error handling.
|
|
289
|
+
*
|
|
290
|
+
* @param metadata The OData metadata XML content
|
|
291
|
+
* @param entitySetName The name of the entity set
|
|
292
|
+
* @param log Optional logger instance
|
|
293
|
+
* @returns ButtonState for the edit button, or undefined if error occurs
|
|
294
|
+
*/
|
|
295
|
+
function safeCheckEditVisibility(metadata, entitySetName, log) {
|
|
296
|
+
try {
|
|
297
|
+
return checkEditVisibility(metadata, entitySetName);
|
|
298
|
+
}
|
|
299
|
+
catch (error) {
|
|
300
|
+
log?.debug(`Failed to check edit visibility: ${error instanceof Error ? error.message : String(error)}`);
|
|
301
|
+
return undefined;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
//# sourceMappingURL=actionUtils.js.map
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import type { Logger } from '@sap-ux/logger';
|
|
2
2
|
import type { TreeAggregations, TreeModel } from '@sap/ux-specification/dist/types/src/parser';
|
|
3
|
-
import type { ActionButtonsResult, ActionButtonState, ButtonState,
|
|
3
|
+
import type { ActionButtonsResult, ActionButtonState, ButtonState, FEV4ManifestTarget, ListReportFeatures } from '../types';
|
|
4
|
+
import { safeCheckButtonVisibility } from './actionUtils';
|
|
4
5
|
import type { PageWithModelV4 } from '@sap/ux-specification/dist/types/src/parser/application';
|
|
5
6
|
import type { Manifest } from '@sap-ux/project-access';
|
|
7
|
+
export { checkButtonVisibility, safeCheckEditVisibility } from './actionUtils';
|
|
8
|
+
export { safeCheckButtonVisibility };
|
|
6
9
|
/**
|
|
7
10
|
* Builds a button state object from button visibility result.
|
|
8
11
|
*
|
|
@@ -14,15 +17,6 @@ export declare function buildButtonState(buttonState?: ButtonState): {
|
|
|
14
17
|
enabled?: boolean | 'dynamic';
|
|
15
18
|
dynamicPath?: string;
|
|
16
19
|
};
|
|
17
|
-
/**
|
|
18
|
-
* Safely checks button visibility with error handling.
|
|
19
|
-
*
|
|
20
|
-
* @param metadata - The OData metadata XML content
|
|
21
|
-
* @param entitySetName - The name of the entity set
|
|
22
|
-
* @param log - Optional logger instance
|
|
23
|
-
* @returns Button visibility result or undefined if error occurs
|
|
24
|
-
*/
|
|
25
|
-
export declare function safeCheckButtonVisibility(metadata: string, entitySetName: string, log?: Logger): ButtonVisibilityResult | undefined;
|
|
26
20
|
/**
|
|
27
21
|
* Safely checks action button states with error handling.
|
|
28
22
|
*
|
|
@@ -67,16 +61,6 @@ export declare function getListReportFeatures(listReportPage: PageWithModelV4, l
|
|
|
67
61
|
* @returns The toolbar actions aggregation object.
|
|
68
62
|
*/
|
|
69
63
|
export declare function getToolBarActions(pageModel: TreeModel): TreeAggregations;
|
|
70
|
-
/**
|
|
71
|
-
* Checks the visibility and enabled state of create and delete buttons for a given entity set
|
|
72
|
-
* by analyzing OData Capabilities annotations in the metadata.
|
|
73
|
-
*
|
|
74
|
-
* @param metadataXml The OData metadata XML content as a string
|
|
75
|
-
* @param entitySetName The name of the entity set to check
|
|
76
|
-
* @returns ButtonVisibilityResult containing the state of create and delete buttons
|
|
77
|
-
* @throws {Error} If metadata cannot be parsed or entity set is not found
|
|
78
|
-
*/
|
|
79
|
-
export declare function checkButtonVisibility(metadataXml: string, entitySetName: string): ButtonVisibilityResult;
|
|
80
64
|
/**
|
|
81
65
|
* Retrieves filter field names from the page model using ux-specification.
|
|
82
66
|
*
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.safeCheckButtonVisibility = exports.safeCheckEditVisibility = exports.checkButtonVisibility = void 0;
|
|
3
4
|
exports.buildButtonState = buildButtonState;
|
|
4
|
-
exports.safeCheckButtonVisibility = safeCheckButtonVisibility;
|
|
5
5
|
exports.safeCheckActionButtonStates = safeCheckActionButtonStates;
|
|
6
6
|
exports.isALPManifestTarget = isALPManifestTarget;
|
|
7
7
|
exports.isALPFromManifest = isALPFromManifest;
|
|
8
8
|
exports.getListReportFeatures = getListReportFeatures;
|
|
9
9
|
exports.getToolBarActions = getToolBarActions;
|
|
10
|
-
exports.checkButtonVisibility = checkButtonVisibility;
|
|
11
10
|
exports.getFilterFieldNames = getFilterFieldNames;
|
|
12
11
|
exports.checkActionButtonStates = checkActionButtonStates;
|
|
13
12
|
exports.getToolBarActionNames = getToolBarActionNames;
|
|
@@ -15,6 +14,11 @@ exports.getToolBarActionItems = getToolBarActionItems;
|
|
|
15
14
|
const modelUtils_1 = require("./modelUtils");
|
|
16
15
|
const edmx_parser_1 = require("@sap-ux/edmx-parser");
|
|
17
16
|
const annotation_converter_1 = require("@sap-ux/annotation-converter");
|
|
17
|
+
const actionUtils_1 = require("./actionUtils");
|
|
18
|
+
Object.defineProperty(exports, "safeCheckButtonVisibility", { enumerable: true, get: function () { return actionUtils_1.safeCheckButtonVisibility; } });
|
|
19
|
+
var actionUtils_2 = require("./actionUtils");
|
|
20
|
+
Object.defineProperty(exports, "checkButtonVisibility", { enumerable: true, get: function () { return actionUtils_2.checkButtonVisibility; } });
|
|
21
|
+
Object.defineProperty(exports, "safeCheckEditVisibility", { enumerable: true, get: function () { return actionUtils_2.safeCheckEditVisibility; } });
|
|
18
22
|
/**
|
|
19
23
|
* Builds a button state object from button visibility result.
|
|
20
24
|
*
|
|
@@ -28,23 +32,6 @@ function buildButtonState(buttonState) {
|
|
|
28
32
|
dynamicPath: buttonState?.enabled === 'dynamic' ? buttonState.dynamicPath : undefined
|
|
29
33
|
};
|
|
30
34
|
}
|
|
31
|
-
/**
|
|
32
|
-
* Safely checks button visibility with error handling.
|
|
33
|
-
*
|
|
34
|
-
* @param metadata - The OData metadata XML content
|
|
35
|
-
* @param entitySetName - The name of the entity set
|
|
36
|
-
* @param log - Optional logger instance
|
|
37
|
-
* @returns Button visibility result or undefined if error occurs
|
|
38
|
-
*/
|
|
39
|
-
function safeCheckButtonVisibility(metadata, entitySetName, log) {
|
|
40
|
-
try {
|
|
41
|
-
return checkButtonVisibility(metadata, entitySetName);
|
|
42
|
-
}
|
|
43
|
-
catch (error) {
|
|
44
|
-
log?.debug(`Failed to check button visibility: ${error instanceof Error ? error.message : String(error)}`);
|
|
45
|
-
return undefined;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
35
|
/**
|
|
49
36
|
* Safely checks action button states with error handling.
|
|
50
37
|
*
|
|
@@ -103,7 +90,7 @@ function isALPFromManifest(manifest, targetKey) {
|
|
|
103
90
|
*/
|
|
104
91
|
function getListReportFeatures(listReportPage, log, metadata, manifest) {
|
|
105
92
|
const buttonVisibility = metadata && listReportPage.entitySet
|
|
106
|
-
? safeCheckButtonVisibility(metadata, listReportPage.entitySet, log)
|
|
93
|
+
? (0, actionUtils_1.safeCheckButtonVisibility)(metadata, listReportPage.entitySet, log)
|
|
107
94
|
: undefined;
|
|
108
95
|
const toolbarActions = getToolBarActionNames(listReportPage.model, log);
|
|
109
96
|
return {
|
|
@@ -133,34 +120,6 @@ function getToolBarActions(pageModel) {
|
|
|
133
120
|
const actionAggregations = (0, modelUtils_1.getAggregations)(actions);
|
|
134
121
|
return actionAggregations;
|
|
135
122
|
}
|
|
136
|
-
/**
|
|
137
|
-
* Checks the visibility and enabled state of create and delete buttons for a given entity set
|
|
138
|
-
* by analyzing OData Capabilities annotations in the metadata.
|
|
139
|
-
*
|
|
140
|
-
* @param metadataXml The OData metadata XML content as a string
|
|
141
|
-
* @param entitySetName The name of the entity set to check
|
|
142
|
-
* @returns ButtonVisibilityResult containing the state of create and delete buttons
|
|
143
|
-
* @throws {Error} If metadata cannot be parsed or entity set is not found
|
|
144
|
-
*/
|
|
145
|
-
function checkButtonVisibility(metadataXml, entitySetName) {
|
|
146
|
-
try {
|
|
147
|
-
const convertedMetadata = (0, annotation_converter_1.convert)((0, edmx_parser_1.parse)(metadataXml));
|
|
148
|
-
const entitySet = convertedMetadata.entitySets.find((es) => es.name === entitySetName);
|
|
149
|
-
if (!entitySet) {
|
|
150
|
-
throw new Error(`Entity set '${entitySetName}' not found in metadata`);
|
|
151
|
-
}
|
|
152
|
-
const insertRestrictions = entitySet.annotations?.Capabilities?.InsertRestrictions;
|
|
153
|
-
const deleteRestrictions = entitySet.annotations?.Capabilities?.DeleteRestrictions;
|
|
154
|
-
return {
|
|
155
|
-
create: analyzeRestriction(insertRestrictions, 'Insertable'),
|
|
156
|
-
delete: analyzeRestriction(deleteRestrictions, 'Deletable')
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
|
-
catch (error) {
|
|
160
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
161
|
-
throw new Error(`Failed to analyze button visibility: ${errorMessage}`);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
123
|
/**
|
|
165
124
|
* Retrieves filter field names from the page model using ux-specification.
|
|
166
125
|
*
|
|
@@ -182,33 +141,6 @@ function getFilterFieldNames(pageModel, log) {
|
|
|
182
141
|
}
|
|
183
142
|
return filterBarItems;
|
|
184
143
|
}
|
|
185
|
-
/**
|
|
186
|
-
* Analyzes a capability restriction annotation to determine button state.
|
|
187
|
-
*
|
|
188
|
-
* @param restriction The restriction annotation object (InsertRestrictions or DeleteRestrictions)
|
|
189
|
-
* @param propertyName The property name to check ('Insertable' or 'Deletable')
|
|
190
|
-
* @returns ButtonState for the button
|
|
191
|
-
*/
|
|
192
|
-
function analyzeRestriction(restriction, propertyName) {
|
|
193
|
-
const defaultState = { visible: true, enabled: true };
|
|
194
|
-
if (!restriction) {
|
|
195
|
-
return defaultState;
|
|
196
|
-
}
|
|
197
|
-
const value = restriction[propertyName];
|
|
198
|
-
if (value === undefined || value === null) {
|
|
199
|
-
return defaultState;
|
|
200
|
-
}
|
|
201
|
-
if (typeof value === 'boolean') {
|
|
202
|
-
return { visible: value, enabled: value };
|
|
203
|
-
}
|
|
204
|
-
if (typeof value === 'object' && value !== null) {
|
|
205
|
-
const path = value.$Path ?? value.path;
|
|
206
|
-
if (path) {
|
|
207
|
-
return { visible: true, enabled: 'dynamic', dynamicPath: path };
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
return defaultState;
|
|
211
|
-
}
|
|
212
144
|
/**
|
|
213
145
|
* Checks the state of action buttons defined in UI.LineItem annotations for a given entity set.
|
|
214
146
|
*
|
|
@@ -235,8 +167,8 @@ function checkActionButtonStates(metadataXml, entitySetName, actionNames) {
|
|
|
235
167
|
}
|
|
236
168
|
const dataFieldForActions = lineItemAnnotation.filter((item) => item.$Type === 'com.sap.vocabularies.UI.v1.DataFieldForAction');
|
|
237
169
|
const actions = actionNames
|
|
238
|
-
? findActionStates(dataFieldForActions, actionNames, convertedMetadata
|
|
239
|
-
: extractAllActionStates(dataFieldForActions, convertedMetadata
|
|
170
|
+
? findActionStates(dataFieldForActions, actionNames, convertedMetadata)
|
|
171
|
+
: extractAllActionStates(dataFieldForActions, convertedMetadata);
|
|
240
172
|
return { actions, entityType: entityType.name };
|
|
241
173
|
}
|
|
242
174
|
catch (error) {
|
|
@@ -250,18 +182,17 @@ function checkActionButtonStates(metadataXml, entitySetName, actionNames) {
|
|
|
250
182
|
* @param dataFieldForActions List of DataFieldForAction items from UI.LineItem
|
|
251
183
|
* @param actionNames List of action names to find
|
|
252
184
|
* @param metadata The converted metadata
|
|
253
|
-
* @param entityTypeName The entity type name
|
|
254
185
|
* @returns List of action button states for the specified actions
|
|
255
186
|
*/
|
|
256
|
-
function findActionStates(dataFieldForActions, actionNames, metadata
|
|
187
|
+
function findActionStates(dataFieldForActions, actionNames, metadata) {
|
|
257
188
|
const actionStates = [];
|
|
258
189
|
for (const actionName of actionNames) {
|
|
259
190
|
const item = dataFieldForActions.find((dfa) => {
|
|
260
|
-
const actionMethod = extractActionMethodName(dfa.Action || '');
|
|
191
|
+
const actionMethod = (0, actionUtils_1.extractActionMethodName)(dfa.Action || '');
|
|
261
192
|
return actionMethod === actionName || dfa.Label === actionName;
|
|
262
193
|
});
|
|
263
194
|
if (item) {
|
|
264
|
-
actionStates.push(buildActionButtonState(item, metadata
|
|
195
|
+
actionStates.push((0, actionUtils_1.buildActionButtonState)(item, metadata));
|
|
265
196
|
}
|
|
266
197
|
}
|
|
267
198
|
return actionStates;
|
|
@@ -271,117 +202,10 @@ function findActionStates(dataFieldForActions, actionNames, metadata, entityType
|
|
|
271
202
|
*
|
|
272
203
|
* @param dataFieldForActions List of DataFieldForAction items from UI.LineItem
|
|
273
204
|
* @param metadata The converted metadata
|
|
274
|
-
* @param entityTypeName The entity type name
|
|
275
205
|
* @returns List of all action button states
|
|
276
206
|
*/
|
|
277
|
-
function extractAllActionStates(dataFieldForActions, metadata
|
|
278
|
-
return dataFieldForActions.map((item) => buildActionButtonState(item, metadata
|
|
279
|
-
}
|
|
280
|
-
/**
|
|
281
|
-
* Builds an ActionButtonState object from a DataFieldForAction item.
|
|
282
|
-
*
|
|
283
|
-
* @param item The DataFieldForAction item
|
|
284
|
-
* @param metadata The converted metadata
|
|
285
|
-
* @param entityTypeName The entity type name
|
|
286
|
-
* @returns ActionButtonState for the action
|
|
287
|
-
*/
|
|
288
|
-
function buildActionButtonState(item, metadata, entityTypeName) {
|
|
289
|
-
const actionMethod = extractActionMethodName(item.Action || '');
|
|
290
|
-
const operationAvailable = findOperationAvailableAnnotation(metadata, entityTypeName, actionMethod);
|
|
291
|
-
// Bound actions whose binding parameter is a single entity (not a collection) require
|
|
292
|
-
// row selection to be invoked, so they are disabled by default (no row selected).
|
|
293
|
-
// Collection-bound actions operate on the entity set and are always enabled.
|
|
294
|
-
const isEntityBound = item.ActionTarget?.isBound === true && item.ActionTarget?.parameters?.[0]?.isCollection !== true;
|
|
295
|
-
const { enabled, dynamicPath } = analyzeOperationAvailability(operationAvailable, isEntityBound);
|
|
296
|
-
return {
|
|
297
|
-
label: item.Label || '',
|
|
298
|
-
action: item.Action || '',
|
|
299
|
-
visible: true,
|
|
300
|
-
enabled,
|
|
301
|
-
dynamicPath,
|
|
302
|
-
invocationGrouping: item.InvocationGrouping ? extractEnumMemberValue(item.InvocationGrouping) : undefined
|
|
303
|
-
};
|
|
304
|
-
}
|
|
305
|
-
/**
|
|
306
|
-
* Analyzes Core.OperationAvailable annotation to determine action availability.
|
|
307
|
-
* Single-entity bound actions (requiring row selection) are disabled by default when no annotation is present.
|
|
308
|
-
*
|
|
309
|
-
* @param operationAvailable The OperationAvailable annotation value
|
|
310
|
-
* @param isEntityBound Whether the action is bound to a single entity (requires row selection to enable)
|
|
311
|
-
* @returns Object containing enabled state and optional dynamic path
|
|
312
|
-
*/
|
|
313
|
-
function analyzeOperationAvailability(operationAvailable, isEntityBound) {
|
|
314
|
-
if (operationAvailable === undefined) {
|
|
315
|
-
return { enabled: !isEntityBound };
|
|
316
|
-
}
|
|
317
|
-
if (typeof operationAvailable === 'boolean') {
|
|
318
|
-
return { enabled: operationAvailable };
|
|
319
|
-
}
|
|
320
|
-
if (typeof operationAvailable === 'object' && operationAvailable !== null) {
|
|
321
|
-
const path = operationAvailable.$Path ?? operationAvailable.path;
|
|
322
|
-
if (path) {
|
|
323
|
-
return { enabled: 'dynamic', dynamicPath: path };
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
return { enabled: true };
|
|
327
|
-
}
|
|
328
|
-
/**
|
|
329
|
-
* Extracts the action method name from a fully qualified action string.
|
|
330
|
-
*
|
|
331
|
-
* @param actionName The fully qualified action name
|
|
332
|
-
* @returns The action method name
|
|
333
|
-
*/
|
|
334
|
-
function extractActionMethodName(actionName) {
|
|
335
|
-
const match = /\.([^.()]+)\(/.exec(actionName);
|
|
336
|
-
if (match?.[1]) {
|
|
337
|
-
return match[1];
|
|
338
|
-
}
|
|
339
|
-
const lastDotIndex = actionName.lastIndexOf('.');
|
|
340
|
-
const parenIndex = actionName.indexOf('(');
|
|
341
|
-
if (lastDotIndex !== -1 && parenIndex !== -1) {
|
|
342
|
-
return actionName.substring(lastDotIndex + 1, parenIndex);
|
|
343
|
-
}
|
|
344
|
-
return actionName;
|
|
345
|
-
}
|
|
346
|
-
/**
|
|
347
|
-
* Finds the Core.OperationAvailable annotation for a specific action.
|
|
348
|
-
*
|
|
349
|
-
* @param metadata The converted metadata
|
|
350
|
-
* @param entityTypeName The entity type name
|
|
351
|
-
* @param actionMethodName The action method name
|
|
352
|
-
* @returns The OperationAvailable annotation value or undefined if not found
|
|
353
|
-
*/
|
|
354
|
-
function findOperationAvailableAnnotation(metadata, entityTypeName, actionMethodName) {
|
|
355
|
-
if (metadata.actions) {
|
|
356
|
-
const action = metadata.actions.find((a) => a.name === actionMethodName || a.fullyQualifiedName?.includes(`.${actionMethodName}(`));
|
|
357
|
-
if (action?.annotations?.Core?.OperationAvailable !== undefined) {
|
|
358
|
-
return action.annotations.Core.OperationAvailable;
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
if (metadata.entityContainer?.annotations) {
|
|
362
|
-
const annotations = metadata.entityContainer.annotations;
|
|
363
|
-
const matchingKey = Object.keys(annotations).find((key) => key.includes(actionMethodName));
|
|
364
|
-
if (matchingKey && annotations[matchingKey]?.Core?.OperationAvailable !== undefined) {
|
|
365
|
-
return annotations[matchingKey].Core.OperationAvailable;
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
return undefined;
|
|
369
|
-
}
|
|
370
|
-
/**
|
|
371
|
-
* Extracts the enum member value from an annotation.
|
|
372
|
-
*
|
|
373
|
-
* @param enumValue The enum value object
|
|
374
|
-
* @returns The extracted enum value string
|
|
375
|
-
*/
|
|
376
|
-
function extractEnumMemberValue(enumValue) {
|
|
377
|
-
if (typeof enumValue === 'string') {
|
|
378
|
-
return enumValue;
|
|
379
|
-
}
|
|
380
|
-
if (enumValue?.$EnumMember) {
|
|
381
|
-
const parts = enumValue.$EnumMember.split('/');
|
|
382
|
-
return parts[1] ?? enumValue.$EnumMember;
|
|
383
|
-
}
|
|
384
|
-
return undefined;
|
|
207
|
+
function extractAllActionStates(dataFieldForActions, metadata) {
|
|
208
|
+
return dataFieldForActions.map((item) => (0, actionUtils_1.buildActionButtonState)(item, metadata));
|
|
385
209
|
}
|
|
386
210
|
/**
|
|
387
211
|
* Retrieves toolbar action names from the page model using ux-specification.
|
package/dist/utils/modelUtils.js
CHANGED
|
@@ -62,7 +62,7 @@ async function getAppFeatures(basePath, fs, log, metadata, manifest) {
|
|
|
62
62
|
}
|
|
63
63
|
if (objectPages) {
|
|
64
64
|
log?.warn('Extracting Object Page features from application model');
|
|
65
|
-
featureData.objectPages = await (0, objectPageUtils_1.getObjectPageFeatures)(objectPages, listReportPage?.name, log);
|
|
65
|
+
featureData.objectPages = await (0, objectPageUtils_1.getObjectPageFeatures)(objectPages, listReportPage?.name, log, projectMetadata);
|
|
66
66
|
log?.warn('objectPages features extracted: ' + JSON.stringify(featureData.objectPages));
|
|
67
67
|
}
|
|
68
68
|
if (fpmPage) {
|
|
@@ -8,9 +8,10 @@ import type { PageWithModelV4 } from '@sap/ux-specification/dist/types/src/parse
|
|
|
8
8
|
* @param objectPages - the array of object pages extracted from the application model
|
|
9
9
|
* @param listReportPageKey - the key of the List Report page in the application model, used to find navigation routes to object pages
|
|
10
10
|
* @param log - optional logger instance
|
|
11
|
+
* @param metadata - optional metadata for the OPA test generation
|
|
11
12
|
* @returns a record of object page feature data
|
|
12
13
|
*/
|
|
13
|
-
export declare function getObjectPageFeatures(objectPages: PageWithModelV4[], listReportPageKey?: string, log?: Logger): Promise<ObjectPageFeatures[]>;
|
|
14
|
+
export declare function getObjectPageFeatures(objectPages: PageWithModelV4[], listReportPageKey?: string, log?: Logger, metadata?: string): Promise<ObjectPageFeatures[]>;
|
|
14
15
|
/**
|
|
15
16
|
* Retrieves all Object Page definitions from the given application model, as long as the page is reachable via standard navigation routes.
|
|
16
17
|
*
|
|
@@ -5,29 +5,43 @@ exports.getObjectPages = getObjectPages;
|
|
|
5
5
|
const modelUtils_1 = require("./modelUtils");
|
|
6
6
|
const tableUtils_1 = require("./tableUtils");
|
|
7
7
|
const page_1 = require("@sap/ux-specification/dist/types/src/common/page");
|
|
8
|
+
const edmx_parser_1 = require("@sap-ux/edmx-parser");
|
|
9
|
+
const annotation_converter_1 = require("@sap-ux/annotation-converter");
|
|
10
|
+
const actionUtils_1 = require("./actionUtils");
|
|
8
11
|
/**
|
|
9
12
|
* Extracts feature data for object pages from the application model.
|
|
10
13
|
*
|
|
11
14
|
* @param objectPages - the array of object pages extracted from the application model
|
|
12
15
|
* @param listReportPageKey - the key of the List Report page in the application model, used to find navigation routes to object pages
|
|
13
16
|
* @param log - optional logger instance
|
|
17
|
+
* @param metadata - optional metadata for the OPA test generation
|
|
14
18
|
* @returns a record of object page feature data
|
|
15
19
|
*/
|
|
16
|
-
async function getObjectPageFeatures(objectPages, listReportPageKey, log) {
|
|
20
|
+
async function getObjectPageFeatures(objectPages, listReportPageKey, log, metadata) {
|
|
17
21
|
const objectPageFeatures = [];
|
|
18
22
|
if (!objectPages || objectPages.length === 0) {
|
|
19
23
|
log?.warn('Object Pages not found in application model. Dynamic tests will not be generated for Object Pages.');
|
|
20
24
|
return objectPageFeatures;
|
|
21
25
|
}
|
|
22
26
|
// attempt to get individual feature data for each object page
|
|
27
|
+
const convertedMetadata = metadata ? (0, annotation_converter_1.convert)((0, edmx_parser_1.parse)(metadata)) : undefined;
|
|
28
|
+
const schemaNamespace = convertedMetadata?.namespace ?? '';
|
|
23
29
|
for (const objectPage of objectPages) {
|
|
24
30
|
const pageFeatureData = {};
|
|
25
31
|
pageFeatureData.name = objectPage.name;
|
|
26
32
|
pageFeatureData.navigationParents = getObjectPageNavigationParents(objectPage.name, objectPages, listReportPageKey);
|
|
27
33
|
// extract header sections (facets)
|
|
28
34
|
pageFeatureData.headerSections = extractObjectPageHeaderSectionsData(objectPage);
|
|
29
|
-
// extract body sections
|
|
30
|
-
pageFeatureData.bodySections = extractObjectPageBodySectionsData(objectPage);
|
|
35
|
+
// extract body sections (includes section-level actions and standard create/delete buttons)
|
|
36
|
+
pageFeatureData.bodySections = extractObjectPageBodySectionsData(objectPage, convertedMetadata, schemaNamespace, metadata, log);
|
|
37
|
+
// extract header-level actions
|
|
38
|
+
pageFeatureData.headerActions = convertedMetadata
|
|
39
|
+
? extractHeaderActions(objectPage, convertedMetadata, schemaNamespace)
|
|
40
|
+
: [];
|
|
41
|
+
// determine edit button visibility from UpdateRestrictions on the OP entity set
|
|
42
|
+
if (metadata && objectPage.entitySet) {
|
|
43
|
+
pageFeatureData.editButton = (0, actionUtils_1.safeCheckEditVisibility)(metadata, objectPage.entitySet, log);
|
|
44
|
+
}
|
|
31
45
|
objectPageFeatures.push(pageFeatureData);
|
|
32
46
|
}
|
|
33
47
|
return objectPageFeatures;
|
|
@@ -110,9 +124,13 @@ function extractObjectPageHeaderSectionsData(objectPage) {
|
|
|
110
124
|
* Extracts body sections data from an object page model.
|
|
111
125
|
*
|
|
112
126
|
* @param objectPage - object page from the application model
|
|
127
|
+
* @param convertedMetadata - optional converted OData metadata for action extraction
|
|
128
|
+
* @param schemaNamespace - optional OData schema namespace used as service identifier in action assertions
|
|
129
|
+
* @param metadata - optional raw metadata XML for resolving standard button visibility (Create/Delete)
|
|
130
|
+
* @param log - optional logger instance
|
|
113
131
|
* @returns body sections data including sub-sections
|
|
114
132
|
*/
|
|
115
|
-
function extractObjectPageBodySectionsData(objectPage) {
|
|
133
|
+
function extractObjectPageBodySectionsData(objectPage, convertedMetadata, schemaNamespace, metadata, log) {
|
|
116
134
|
const bodySections = [];
|
|
117
135
|
if (objectPage.model) {
|
|
118
136
|
const sectionsAggregation = (0, modelUtils_1.getAggregations)(objectPage.model.root)['sections'];
|
|
@@ -120,20 +138,81 @@ function extractObjectPageBodySectionsData(objectPage) {
|
|
|
120
138
|
Object.entries(sections).forEach(([sectionKey, section]) => {
|
|
121
139
|
const sectionId = getSectionIdentifier(section) ?? sectionKey;
|
|
122
140
|
const subSections = extractBodySubSectionsData(section, sectionId);
|
|
123
|
-
|
|
141
|
+
const navigationProperty = getNavigationPropertyFromKey(sectionKey);
|
|
142
|
+
const sectionData = {
|
|
124
143
|
id: sectionId,
|
|
125
|
-
navigationProperty
|
|
144
|
+
navigationProperty,
|
|
126
145
|
isTable: !!section.isTable,
|
|
127
146
|
custom: !!section.custom,
|
|
128
|
-
order: section?.order ?? -1,
|
|
147
|
+
order: section?.order ?? -1,
|
|
129
148
|
fields: section.custom || section.isTable ? [] : extractFormFields(section),
|
|
130
149
|
tableColumns: section.custom || !section.isTable ? {} : (0, tableUtils_1.extractTableColumnsFromNode)(section),
|
|
131
|
-
subSections
|
|
132
|
-
|
|
150
|
+
subSections,
|
|
151
|
+
actions: !section.custom && convertedMetadata && schemaNamespace
|
|
152
|
+
? extractSectionActions(section, convertedMetadata, schemaNamespace)
|
|
153
|
+
: []
|
|
154
|
+
};
|
|
155
|
+
// For table sections, resolve Create/Delete visibility from target entity set
|
|
156
|
+
if (section.isTable && navigationProperty && metadata && convertedMetadata) {
|
|
157
|
+
const targetEntitySet = resolveNavigationTargetEntitySet(convertedMetadata, objectPage.entitySet, navigationProperty);
|
|
158
|
+
if (targetEntitySet) {
|
|
159
|
+
const buttonVisibility = (0, actionUtils_1.safeCheckButtonVisibility)(metadata, targetEntitySet, log);
|
|
160
|
+
sectionData.createButton = buttonVisibility?.create;
|
|
161
|
+
sectionData.deleteButton = buttonVisibility?.delete;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
bodySections.push(sectionData);
|
|
133
165
|
});
|
|
134
166
|
}
|
|
135
167
|
return bodySections;
|
|
136
168
|
}
|
|
169
|
+
/**
|
|
170
|
+
* Extracts header-level action button states from an object page model.
|
|
171
|
+
*
|
|
172
|
+
* @param objectPage - object page from the application model
|
|
173
|
+
* @param convertedMetadata - converted OData metadata for resolving action availability
|
|
174
|
+
* @param schemaNamespace - OData schema namespace used as service identifier in action assertions
|
|
175
|
+
* @returns array of action button states for the header toolbar
|
|
176
|
+
*/
|
|
177
|
+
function extractHeaderActions(objectPage, convertedMetadata, schemaNamespace) {
|
|
178
|
+
if (!objectPage.model) {
|
|
179
|
+
return [];
|
|
180
|
+
}
|
|
181
|
+
const headerAgg = (0, modelUtils_1.getAggregations)(objectPage.model.root)['header'];
|
|
182
|
+
const actionsAgg = (0, modelUtils_1.getAggregations)(headerAgg)['actions'];
|
|
183
|
+
const actionEntries = (0, modelUtils_1.getAggregations)(actionsAgg);
|
|
184
|
+
return Object.entries(actionEntries)
|
|
185
|
+
.map(([key, item]) => (0, actionUtils_1.buildActionStateFromSpecModelKey)(key, item.description, convertedMetadata, schemaNamespace))
|
|
186
|
+
.filter((actionState) => actionState !== undefined);
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Extracts section-level action button states from a body section.
|
|
190
|
+
* For table sections, actions are extracted from the table toolbar; for form sections from the form actions aggregation.
|
|
191
|
+
*
|
|
192
|
+
* @param section - body section entry from the application model
|
|
193
|
+
* @param convertedMetadata - converted OData metadata for resolving action availability
|
|
194
|
+
* @param schemaNamespace - OData schema namespace used as service identifier in action assertions
|
|
195
|
+
* @returns array of action button states for the section toolbar
|
|
196
|
+
*/
|
|
197
|
+
function extractSectionActions(section, convertedMetadata, schemaNamespace) {
|
|
198
|
+
let actionsAgg;
|
|
199
|
+
if (section.isTable) {
|
|
200
|
+
const tableAgg = (0, modelUtils_1.getAggregations)(section)['table'];
|
|
201
|
+
const toolBarAgg = (0, modelUtils_1.getAggregations)(tableAgg)['toolBar'];
|
|
202
|
+
actionsAgg = (0, modelUtils_1.getAggregations)(toolBarAgg)['actions'];
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
const formAgg = (0, modelUtils_1.getAggregations)(section)['form'];
|
|
206
|
+
actionsAgg = (0, modelUtils_1.getAggregations)(formAgg)['actions'];
|
|
207
|
+
}
|
|
208
|
+
if (!actionsAgg) {
|
|
209
|
+
return [];
|
|
210
|
+
}
|
|
211
|
+
const actionEntries = (0, modelUtils_1.getAggregations)(actionsAgg);
|
|
212
|
+
return Object.entries(actionEntries)
|
|
213
|
+
.map(([key, item]) => (0, actionUtils_1.buildActionStateFromSpecModelKey)(key, item.description, convertedMetadata, schemaNamespace))
|
|
214
|
+
.filter((actionState) => actionState !== undefined);
|
|
215
|
+
}
|
|
137
216
|
/**
|
|
138
217
|
* Extracts sub-sections data from a body section.
|
|
139
218
|
*
|
|
@@ -193,6 +272,27 @@ function getNavigationPropertyFromKey(sectionKey) {
|
|
|
193
272
|
const prefix = sectionKey.split('::')[0];
|
|
194
273
|
return prefix.startsWith('_') ? prefix : undefined;
|
|
195
274
|
}
|
|
275
|
+
/**
|
|
276
|
+
* Resolves the target entity set name for a navigation property by looking up navigation
|
|
277
|
+
* property bindings in the source entity set's metadata.
|
|
278
|
+
*
|
|
279
|
+
* @param convertedMetadata - converted OData metadata
|
|
280
|
+
* @param sourceEntitySetName - the name of the source entity set (the Object Page's entity set)
|
|
281
|
+
* @param navigationProperty - the navigation property name (e.g. '_Booking')
|
|
282
|
+
* @returns the target entity set name, or undefined if resolution fails
|
|
283
|
+
*/
|
|
284
|
+
function resolveNavigationTargetEntitySet(convertedMetadata, sourceEntitySetName, navigationProperty) {
|
|
285
|
+
if (!sourceEntitySetName) {
|
|
286
|
+
return undefined;
|
|
287
|
+
}
|
|
288
|
+
const sourceEntitySet = convertedMetadata.entitySets.find((es) => es.name === sourceEntitySetName);
|
|
289
|
+
if (!sourceEntitySet?.navigationPropertyBinding) {
|
|
290
|
+
return undefined;
|
|
291
|
+
}
|
|
292
|
+
const navPropName = navigationProperty.startsWith('_') ? navigationProperty.substring(1) : navigationProperty;
|
|
293
|
+
const binding = sourceEntitySet.navigationPropertyBinding[navPropName];
|
|
294
|
+
return binding?.name;
|
|
295
|
+
}
|
|
196
296
|
/**
|
|
197
297
|
* Gets the identifier of a section for OPA5 tests.
|
|
198
298
|
*
|
package/package.json
CHANGED
|
@@ -39,6 +39,26 @@ sap.ui.define([
|
|
|
39
39
|
Then.onThe<%- name%>.iSeeThisPage();
|
|
40
40
|
});
|
|
41
41
|
|
|
42
|
+
<% if (headerActions?.length > 0 && !isStandalone) { -%>
|
|
43
|
+
opaTest("Check header actions of the Object Page", function (Given, When, Then) {
|
|
44
|
+
<% if (editButton?.visible) { -%>
|
|
45
|
+
// Ensure the opened entity is not in Draft state before uncommenting
|
|
46
|
+
// Then.onThe<%- name%>.onHeader().iCheckEdit({ visible: true });
|
|
47
|
+
// When.onThe<%- name%>.onHeader().iPressEdit();
|
|
48
|
+
<% } -%>
|
|
49
|
+
<% headerActions.forEach(function(action) { -%>
|
|
50
|
+
<% if (action.visible) { -%>
|
|
51
|
+
<% if (action.enabled === 'dynamic') { -%>
|
|
52
|
+
Then.onThe<%- name%>.onHeader().iCheckAction("<%- action.label %>" /* , { enabled: true } */);
|
|
53
|
+
<% } else { -%>
|
|
54
|
+
Then.onThe<%- name%>.onHeader().iCheckAction("<%- action.label %>", { enabled: <%- action.enabled === true %> });
|
|
55
|
+
<% } -%>
|
|
56
|
+
// When.onThe<%- name%>.onHeader().iPressAction("<%- action.label %>");
|
|
57
|
+
<% } -%>
|
|
58
|
+
<% }); -%>
|
|
59
|
+
});
|
|
60
|
+
<% } -%>
|
|
61
|
+
|
|
42
62
|
<% if (headerSections?.length > 0) { -%>
|
|
43
63
|
opaTest("Check header facets of the Object Page", function (Given, When, Then) {
|
|
44
64
|
<% headerSections.forEach(function(section) { -%>
|
|
@@ -70,6 +90,37 @@ sap.ui.define([
|
|
|
70
90
|
When.onThe<%- name%>.iPressSectionIconTabFilterButton("<%- section.id %>");
|
|
71
91
|
<% } -%>
|
|
72
92
|
Then.onThe<%- name%>.iCheckSection({ section: "<%- section.id %>" });
|
|
93
|
+
<% if (section.actions && section.actions.length > 0) { -%>
|
|
94
|
+
<% section.actions.forEach(function(action) { -%>
|
|
95
|
+
<% if (action.visible) { -%>
|
|
96
|
+
<% if (section.isTable && section.navigationProperty) { -%>
|
|
97
|
+
<% if (action.enabled === 'dynamic') { -%>
|
|
98
|
+
Then.onThe<%- name%>.onTable({ property: "<%- section.navigationProperty %>" }).iCheckAction("<%- action.label %>" /* , { enabled: true } */);
|
|
99
|
+
<% } else { -%>
|
|
100
|
+
Then.onThe<%- name%>.onTable({ property: "<%- section.navigationProperty %>" }).iCheckAction("<%- action.label %>", { enabled: <%- action.enabled === true %> });
|
|
101
|
+
<% } -%>
|
|
102
|
+
// When.onThe<%- name%>.onTable({ property: "<%- section.navigationProperty %>" }).iPressAction("<%- action.label %>");
|
|
103
|
+
<% } else { -%>
|
|
104
|
+
<% if (action.enabled === 'dynamic') { -%>
|
|
105
|
+
Then.onThe<%- name%>.onForm({ section: "<%- section.id %>" }).iCheckAction("<%- action.label %>" /* , { enabled: true } */);
|
|
106
|
+
<% } else { -%>
|
|
107
|
+
Then.onThe<%- name%>.onForm({ section: "<%- section.id %>" }).iCheckAction("<%- action.label %>", { enabled: <%- action.enabled === true %> });
|
|
108
|
+
<% } -%>
|
|
109
|
+
// When.onThe<%- name%>.onForm({ section: "<%- section.id %>" }).iPressAction("<%- action.label %>");
|
|
110
|
+
<% } -%>
|
|
111
|
+
<% } -%>
|
|
112
|
+
<% }); -%>
|
|
113
|
+
<% } -%>
|
|
114
|
+
<% if (section.isTable && section.navigationProperty) { -%>
|
|
115
|
+
<% if (section.createButton?.visible) { -%>
|
|
116
|
+
Then.onThe<%- name%>.onTable({ property: "<%- section.navigationProperty %>" }).iCheckCreate({ visible: true });
|
|
117
|
+
// When.onThe<%- name%>.onTable({ property: "<%- section.navigationProperty %>" }).iPressCreate();
|
|
118
|
+
<% } -%>
|
|
119
|
+
<% if (section.deleteButton?.visible) { -%>
|
|
120
|
+
Then.onThe<%- name%>.onTable({ property: "<%- section.navigationProperty %>" }).iCheckDelete({ visible: true });
|
|
121
|
+
// When.onThe<%- name%>.onTable({ property: "<%- section.navigationProperty %>" }).iPressDelete();
|
|
122
|
+
<% } -%>
|
|
123
|
+
<% } -%>
|
|
73
124
|
<% if (section?.subSections?.length > 0) { -%>
|
|
74
125
|
<% section.subSections.forEach(function(subSection) { -%>
|
|
75
126
|
//When.onThe<%- name%>.iGoToSection({ section: "<%- section.id %>", subSection: "<%- subSection.id %>" });
|