-
Notifications
You must be signed in to change notification settings - Fork 59
Expand file tree
/
Copy pathmodelUtils.ts
More file actions
253 lines (230 loc) · 9.07 KB
/
modelUtils.ts
File metadata and controls
253 lines (230 loc) · 9.07 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
import type { Editor } from 'mem-fs-editor';
import { createApplicationAccess } from '@sap-ux/project-access';
import type { Logger } from '@sap-ux/logger';
import { PageTypeV4 } from '@sap/ux-specification/dist/types/src/common/page';
import type { ReadAppResult, Specification } from '@sap/ux-specification/dist/types/src';
import type { PageWithModelV4 } from '@sap/ux-specification/dist/types/src/parser/application';
import type {
TreeAggregation,
TreeAggregations,
TreeModel,
ApplicationModel
} from '@sap/ux-specification/dist/types/src/parser';
import type { AppFeatures, FPMFeatures } from '../types';
import { getObjectPageFeatures, getObjectPages } from './objectPageUtils';
import { getFilterFieldNames, getListReportFeatures } from './listReportUtils';
import { extractTableColumnsFromNode } from './tableUtils';
export interface AggregationItem extends TreeAggregation {
description: string;
schema: {
keys: { name: string; value: string }[];
dataType?: string;
};
}
export interface FieldItem extends AggregationItem {
name: string;
}
export interface SectionItem extends AggregationItem {
title?: string;
custom?: boolean;
name?: string;
order?: number;
schema: {
keys: { name: string; value: string }[];
dataType?: string;
};
}
export interface BodySectionItem extends SectionItem {
isTable?: boolean;
}
export interface HeaderSectionItem extends SectionItem {
properties: {
stashed: {
freeText: string | boolean;
};
};
}
export interface PageWithModelV4WithProperties extends PageWithModelV4 {
routePattern?: string;
}
/**
* Gets app features from the application model using ux-specification.
*
* @param basePath - the absolute target path where the application will be generated
* @param fs - optional mem-fs editor instance
* @param log - optional logger instance
* @param metadata - optional metadata for the OPA test generation
* @returns feature data extracted from the application model
*/
export async function getAppFeatures(
basePath: string,
fs?: Editor,
log?: Logger,
metadata?: string
): Promise<AppFeatures> {
const featureData: AppFeatures = {};
let listReportPage: PageWithModelV4 | null = null;
let objectPages: PageWithModelV4[] | null = null;
let fpmPage: PageWithModelV4 | null = null;
let projectMetadata = metadata;
// Read application model to extract control information needed for test generation
// specification and readApp might not be available due to specification version, fail gracefully
try {
// readApp calls createApplicationAccess internally if given a path, but it uses the "live" version of project-access without fs enhancement
const appAccess = await createApplicationAccess(basePath, { fs: fs });
const specification = await appAccess.getSpecification<Specification>();
const appModel: ReadAppResult = await specification.readApp({ app: appAccess, fs: fs });
if (!projectMetadata) {
const metadataPath = appAccess.project?.apps['']?.services?.mainService?.local;
if (metadataPath) {
projectMetadata = fs?.read(metadataPath);
}
}
listReportPage = appModel?.applicationModel ? getListReportPage(appModel.applicationModel) : listReportPage;
objectPages = appModel?.applicationModel ? getObjectPages(appModel.applicationModel) : objectPages;
fpmPage = appModel?.applicationModel ? getFPMPage(appModel.applicationModel, log) : fpmPage;
} catch (error) {
log?.warn(
'Error analyzing project model using specification. No dynamic tests will be generated. Error: ' +
(error as Error).message
);
return featureData;
}
if (!listReportPage && !objectPages && !fpmPage) {
log?.warn('Pages not found in application model. Dynamic tests will not be generated.');
return featureData;
}
// attempt to get individual feature data
try {
if (listReportPage) {
featureData.listReport = getListReportFeatures(listReportPage, log, projectMetadata);
}
if (objectPages) {
log?.warn('Extracting Object Page features from application model');
featureData.objectPages = await getObjectPageFeatures(objectPages, listReportPage?.name, log);
log?.warn('objectPages features extracted: ' + JSON.stringify(featureData.objectPages));
}
if (fpmPage) {
featureData.fpm = getFPMFeatures(fpmPage, log);
}
} catch (error) {
// do noting here, as individual feature extraction methods already log warnings
}
return featureData;
}
/**
* Retrieves table column data from the page model using ux-specification.
*
* @param pageModel - the tree model containing table column definitions
* @param log - optional logger instance
* @returns - a map of table columns
*/
export function getTableColumnData(
pageModel: TreeModel,
log?: Logger
): Record<string, Record<string, string | number | boolean>> {
let tableColumns: Record<string, Record<string, string | number | boolean>> = {};
try {
tableColumns = extractTableColumnsFromNode(pageModel.root);
} catch (error) {
log?.debug(error);
}
if (!tableColumns || !Object.keys(tableColumns).length) {
log?.warn(
'Unable to extract table columns from project model using specification. No table column tests will be generated.'
);
}
return tableColumns;
}
/**
* Retrieves List Report definition from the given application model.
* Only a single List Report page is expected, so the first match is returned.
*
* @param applicationModel - The application model containing page definitions.
* @returns An object containing the key and page definition of the List Report, or null if not found.
*/
export function getListReportPage(applicationModel: ApplicationModel): PageWithModelV4 | null {
for (const pageKey in applicationModel.pages) {
const page = applicationModel.pages[pageKey];
if (page.pageType === PageTypeV4.ListReport) {
page.name = pageKey; // store page key as name for later identification
return page;
}
}
return null;
}
/**
* Retrieves all FPM Custom Page definitions from the given application model.
*
* @param applicationModel - The application model containing page definitions.
* @param log - optional logger instance
* @returns An array of FPM Custom Page definitions.
*/
export function getFPMPage(applicationModel: ApplicationModel, log?: Logger): PageWithModelV4 | null {
for (const pageKey in applicationModel.pages) {
const page = applicationModel.pages[pageKey];
log?.warn('pageType:' + page.pageType);
if (page.pageType === PageTypeV4.FPMCustomPage) {
page.name = pageKey; // store page key as name for later identification
return page;
}
}
return null;
}
/**
* Gets FPM features from the page model using ux-specification.
*
* @param page - the FPM Custom Page containing model definitions
* @param log - optional logger instance
* @returns feature data extracted from the FPM Custom Page model
*/
export function getFPMFeatures(page: PageWithModelV4, log?: Logger): FPMFeatures {
return {
name: page.name,
filterBarItems: getFilterFieldNames(page.model, log),
tableColumns: getTableColumnData(page.model, log)
};
}
/**
* Retrieves the aggregations from the given tree aggregations node.
*
* @param node - The tree aggregations node.
* @returns The aggregations object.
*/
export function getAggregations(node: TreeAggregation): TreeAggregations {
if (node && typeof node === 'object' && 'aggregations' in node) {
return node.aggregations;
}
return {} as TreeAggregations;
}
/**
* Retrieves selection field items from the given selection fields aggregation.
*
* @param selectionFieldsAgg - The selection fields aggregation containing field definitions.
* @returns An array of selection field descriptions.
*/
export function getSelectionFieldItems(selectionFieldsAgg: TreeAggregations): string[] {
if (selectionFieldsAgg && typeof selectionFieldsAgg === 'object') {
const items: string[] = [];
for (const itemKey in selectionFieldsAgg) {
items.push(
(selectionFieldsAgg[itemKey as keyof TreeAggregation] as unknown as AggregationItem).description
);
}
return items;
}
return [];
}
/**
* Retrieves filter field descriptions from the given tree model.
*
* @param pageModel - The tree model containing filter bar definitions.
* @returns An array of filter field descriptions.
*/
export function getFilterFields(pageModel: TreeModel): TreeAggregations {
const filterBar = getAggregations(pageModel.root)['filterBar'];
const filterBarAggregations = getAggregations(filterBar);
const selectionFields = filterBarAggregations['selectionFields'];
const selectionFieldsAggregations = getAggregations(selectionFields);
return selectionFieldsAggregations;
}