Skip to content

Commit d9ae55c

Browse files
devineagithub-actions[bot]vinayhospete
authored
fix(fiori-mcp-server): enhance floorplan descriptions and make service/entityConfig optional for FF_SIMPLE template (#4514)
* fix(fiori-mcp-server): enhance floorplan descriptions and make service/entityConfig optional for FF_SIMPLE template * feedback * Linting auto fix commit --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: vinayhospete <[email protected]>
1 parent 0369268 commit d9ae55c

9 files changed

Lines changed: 230 additions & 57 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@sap-ux/fiori-mcp-server": patch
3+
---
4+
5+
fix: improve floorplan descriptions and make service/entityConfig optional for FF_SIMPLE (Basic template)

packages/fiori-mcp-server/src/tools/functionalities/generate-fiori-ui-application-cap/command.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export async function command(params: ExecuteFunctionalityInput): Promise<Execut
3030
throw new Error('Please provide a valid path to the CAP project folder.');
3131
}
3232

33-
if (generatorConfig?.service.capService.serviceCdsPath) {
33+
if (generatorConfig?.service?.capService?.serviceCdsPath) {
3434
generatorConfig.service.capService.serviceCdsPath =
3535
generatorConfig?.service.capService.serviceCdsPath?.startsWith('/')
3636
? generatorConfig?.service.capService.serviceCdsPath

packages/fiori-mcp-server/src/tools/functionalities/generate-fiori-ui-application/execute-functionality.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,13 @@ export default async function (params: ExecuteFunctionalityInput): Promise<Execu
3838

3939
await checkIfGeneratorInstalled();
4040

41-
const metadataPath = generatorConfig.service.metadataFilePath ?? join(targetDir, 'metadata.xml');
41+
const metadataPath = generatorConfig.service?.metadataFilePath ?? join(targetDir, 'metadata.xml');
4242

4343
try {
44-
const metadata = await FSpromises.readFile(metadataPath, { encoding: 'utf8' });
45-
generatorConfig.service.edmx = metadata;
44+
if (generatorConfig.service) {
45+
const metadata = await FSpromises.readFile(metadataPath, { encoding: 'utf8' });
46+
generatorConfig.service.edmx = metadata;
47+
}
4648

4749
const content = JSON.stringify(generatorConfig, null, 4);
4850

@@ -71,7 +73,7 @@ export default async function (params: ExecuteFunctionalityInput): Promise<Execu
7173
if (existsSync(configPath)) {
7274
await FSpromises.unlink(configPath);
7375
}
74-
if (existsSync(metadataPath)) {
76+
if (generatorConfig.service && existsSync(metadataPath)) {
7577
await FSpromises.unlink(metadataPath);
7678
}
7779
}

packages/fiori-mcp-server/src/tools/schemas/appgen-config-schema-props.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,17 @@ export const PREDEFINED_GENERATOR_VALUES = {
1515
}
1616
};
1717

18-
export const floorplan = z
19-
.literal(['FE_FPM', 'FE_LROP', 'FE_OVP', 'FE_ALP', 'FE_FEOP', 'FE_WORKLIST', 'FF_SIMPLE'])
20-
.describe('SAP Fiori Elements floor plan type.');
18+
export const floorplan = z.union([
19+
z.literal('FE_LROP').describe('List Report Object Page (OData V2/V4).'),
20+
z.literal('FE_ALP').describe('Analytical List Page (OData V2/V4).'),
21+
z.literal('FE_OVP').describe('Overview Page (OData V2/V4).'),
22+
z.literal('FE_WORKLIST').describe('Worklist (OData V2/V4).'),
23+
z.literal('FE_FEOP').describe('Form Entry Object Page (OData V4 only).'),
24+
z.literal('FE_FPM').describe('Flexible Programming Model / Custom Page (OData V4 only).'),
25+
z
26+
.literal('FF_SIMPLE')
27+
.describe('Basic (SAPUI5 Freestyle template) — data source is optional for this template, supports "None".')
28+
]);
2129

2230
export const project = z.object({
2331
name: z
@@ -33,7 +41,9 @@ export const project = z.object({
3341
export const serviceOdata = z.object({
3442
servicePath: z
3543
.string()
36-
.describe('The odata endpoint. If the parameter is not provided, the agent should ask the user for it.')
44+
.describe(
45+
'The odata endpoint. Required for all floorplans except FF_SIMPLE. If the parameter is not provided, the agent should ask the user for it.'
46+
)
3747
.meta({
3848
examples: ['/sap/opu/odata/sap/<servicename>/', '/<servicename>/']
3949
}),
@@ -85,7 +95,9 @@ export const entityConfig = z.object({
8595
mainEntity: z.object({
8696
entityName: z
8797
.string()
88-
.describe('The name of the main entity. EntitySet Name attribute in OData Metadata.')
98+
.describe(
99+
'The name of the main entity. EntitySet Name attribute in OData Metadata. Required for all floorplans except FF_SIMPLE.'
100+
)
89101
.meta({ examples: ["'SalesOrder'", "'PurchaseOrderHeader'", "'MyEntity'"] })
90102
}),
91103
generateFormAnnotations: z

packages/fiori-mcp-server/src/tools/schemas/cap-schema.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ import { convertToSchema } from '../../utils';
55
import { entityConfig, floorplan, project, serviceCap as service } from './appgen-config-schema-props';
66

77
export const generatorConfigCAP = z.object({
8-
entityConfig,
8+
entityConfig: entityConfig.optional(),
99
floorplan,
1010
project,
11-
service
11+
service: service.optional()
1212
}).describe(`The configuration that will be used for the Application UI generation.
13-
The configuration **MUST** be a valid JSON object corresponding to the inputSchema of the functionality.`);
13+
The configuration **MUST** be a valid JSON object corresponding to the inputSchema of the functionality.
14+
For floorplan FF_SIMPLE (Basic/SAPUI5 Freestyle template), service and entityConfig are optional (data source may be "None").
15+
For all other floorplans, service and entityConfig are required.`);
1416

1517
// Input type for functionality parameters
1618
export type GeneratorConfigCAP = z.infer<typeof generatorConfigCAP>;

packages/fiori-mcp-server/src/tools/schemas/odata-schema.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,19 @@ import { convertToSchema } from '../../utils';
55
import { entityConfig, floorplan, project, serviceOdata as service } from './appgen-config-schema-props';
66

77
export const generatorConfigOData = z.object({
8-
entityConfig,
8+
entityConfig: entityConfig.optional(),
99
floorplan,
1010
project,
11-
service
11+
service: service.optional()
1212
}).describe(`The configuration that will be used for the Application UI generation.
13-
The configuration **MUST** be a valid JSON object corresponding to the inputSchema of the functionality.`);
13+
The configuration **MUST** be a valid JSON object corresponding to the inputSchema of the functionality.
14+
For floorplan FF_SIMPLE (Basic/SAPUI5 Freestyle template), service and entityConfig are optional (data source may be "None").
15+
For all other floorplans, service and entityConfig are required.`);
1416

1517
// Input type for functionality parameters
1618
export type GeneratorConfigOData = z.infer<typeof generatorConfigOData>;
1719
export type GeneratorConfigODataWithAPI = GeneratorConfigOData &
18-
typeof PREDEFINED_GENERATOR_VALUES & { service: { edmx?: string } };
20+
typeof PREDEFINED_GENERATOR_VALUES & { service?: { edmx?: string } };
1921

2022
// JSON schema for functionality description
2123
export const generatorConfigODataJson = convertToSchema(generatorConfigOData);

packages/fiori-mcp-server/test/unit/tools/functionalities/generate-fiori-ui-application-cap/__snapshots__/generate-fiori-ui-application-cap.test.ts.snap

Lines changed: 40 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ Object {
1313
"parameters": Object {
1414
"additionalProperties": false,
1515
"description": "The configuration that will be used for the Application UI generation.
16-
The configuration **MUST** be a valid JSON object corresponding to the inputSchema of the functionality.",
16+
The configuration **MUST** be a valid JSON object corresponding to the inputSchema of the functionality.
17+
For floorplan FF_SIMPLE (Basic/SAPUI5 Freestyle template), service and entityConfig are optional (data source may be \\"None\\").
18+
For all other floorplans, service and entityConfig are required.",
1719
"properties": Object {
1820
"entityConfig": Object {
1921
"additionalProperties": false,
@@ -32,7 +34,7 @@ Object {
3234
"additionalProperties": false,
3335
"properties": Object {
3436
"entityName": Object {
35-
"description": "The name of the main entity. EntitySet Name attribute in OData Metadata.",
37+
"description": "The name of the main entity. EntitySet Name attribute in OData Metadata. Required for all floorplans except FF_SIMPLE.",
3638
"examples": Array [
3739
"'SalesOrder'",
3840
"'PurchaseOrderHeader'",
@@ -55,17 +57,43 @@ Object {
5557
"type": "object",
5658
},
5759
"floorplan": Object {
58-
"description": "SAP Fiori Elements floor plan type.",
59-
"enum": Array [
60-
"FE_FPM",
61-
"FE_LROP",
62-
"FE_OVP",
63-
"FE_ALP",
64-
"FE_FEOP",
65-
"FE_WORKLIST",
66-
"FF_SIMPLE",
60+
"anyOf": Array [
61+
Object {
62+
"const": "FE_LROP",
63+
"description": "List Report Object Page (OData V2/V4).",
64+
"type": "string",
65+
},
66+
Object {
67+
"const": "FE_ALP",
68+
"description": "Analytical List Page (OData V2/V4).",
69+
"type": "string",
70+
},
71+
Object {
72+
"const": "FE_OVP",
73+
"description": "Overview Page (OData V2/V4).",
74+
"type": "string",
75+
},
76+
Object {
77+
"const": "FE_WORKLIST",
78+
"description": "Worklist (OData V2/V4).",
79+
"type": "string",
80+
},
81+
Object {
82+
"const": "FE_FEOP",
83+
"description": "Form Entry Object Page (OData V4 only).",
84+
"type": "string",
85+
},
86+
Object {
87+
"const": "FE_FPM",
88+
"description": "Flexible Programming Model / Custom Page (OData V4 only).",
89+
"type": "string",
90+
},
91+
Object {
92+
"const": "FF_SIMPLE",
93+
"description": "Basic (SAPUI5 Freestyle template) — data source is optional for this template, supports \\"None\\".",
94+
"type": "string",
95+
},
6796
],
68-
"type": "string",
6997
},
7098
"project": Object {
7199
"additionalProperties": false,
@@ -157,10 +185,8 @@ Object {
157185
},
158186
},
159187
"required": Array [
160-
"entityConfig",
161188
"floorplan",
162189
"project",
163-
"service",
164190
],
165191
"type": "object",
166192
},

packages/fiori-mcp-server/test/unit/tools/functionalities/generate-fiori-ui-application-cap/generate-fiori-ui-application-cap.test.ts

Lines changed: 103 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,36 @@ describe('executeFunctionality', () => {
204204
expect(config.project.sapux).toEqual(false);
205205
});
206206

207+
test('executeFunctionality - success with floorplan="FF_SIMPLE" without service (no data source)', async () => {
208+
let generatedConfigContent: string;
209+
mockExec.mockImplementation((_cmd, _opts, callback) => {
210+
callback(null, 'mock stdout', 'mock stderr');
211+
});
212+
mockFileWrite((content) => {
213+
generatedConfigContent = content;
214+
});
215+
const result = await generateFioriUIApplicationCapHandlers.executeFunctionality({
216+
appPath: join(testOutputDir, 'app1'),
217+
functionalityId: GENERATE_FIORI_UI_APPLICATION_CAP.functionalityId,
218+
parameters: {
219+
floorplan: 'FF_SIMPLE',
220+
project: {
221+
name: 'app1',
222+
targetFolder: join(testOutputDir, 'app1'),
223+
title: 'App 1',
224+
description: 'Description for App 1',
225+
ui5Version: '1.136.7',
226+
sapux: true
227+
}
228+
}
229+
});
230+
expect(result.status).toBe('Success');
231+
const config = JSON.parse(generatedConfigContent!);
232+
expect(config.project.sapux).toEqual(false);
233+
expect(config.service).toBeUndefined();
234+
expect(config.entityConfig).toBeUndefined();
235+
});
236+
207237
test('executeFunctionality - unsuccess', async () => {
208238
mockExec.mockImplementation((cmd, opts, callback) => {
209239
throw new Error('Dummy');
@@ -239,28 +269,83 @@ describe('executeFunctionality', () => {
239269
).rejects.toThrowErrorMatchingInlineSnapshot(`
240270
"Missing required fields in parameters. [
241271
{
242-
\\"expected\\": \\"object\\",
243-
\\"code\\": \\"invalid_type\\",
244-
\\"path\\": [
245-
\\"entityConfig\\"
246-
],
247-
\\"message\\": \\"Invalid input: expected object, received undefined\\"
248-
},
249-
{
250-
\\"code\\": \\"invalid_value\\",
251-
\\"values\\": [
252-
\\"FE_FPM\\",
253-
\\"FE_LROP\\",
254-
\\"FE_OVP\\",
255-
\\"FE_ALP\\",
256-
\\"FE_FEOP\\",
257-
\\"FE_WORKLIST\\",
258-
\\"FF_SIMPLE\\"
272+
\\"code\\": \\"invalid_union\\",
273+
\\"errors\\": [
274+
[
275+
{
276+
\\"code\\": \\"invalid_value\\",
277+
\\"values\\": [
278+
\\"FE_LROP\\"
279+
],
280+
\\"path\\": [],
281+
\\"message\\": \\"Invalid input: expected \\\\\\"FE_LROP\\\\\\"\\"
282+
}
283+
],
284+
[
285+
{
286+
\\"code\\": \\"invalid_value\\",
287+
\\"values\\": [
288+
\\"FE_ALP\\"
289+
],
290+
\\"path\\": [],
291+
\\"message\\": \\"Invalid input: expected \\\\\\"FE_ALP\\\\\\"\\"
292+
}
293+
],
294+
[
295+
{
296+
\\"code\\": \\"invalid_value\\",
297+
\\"values\\": [
298+
\\"FE_OVP\\"
299+
],
300+
\\"path\\": [],
301+
\\"message\\": \\"Invalid input: expected \\\\\\"FE_OVP\\\\\\"\\"
302+
}
303+
],
304+
[
305+
{
306+
\\"code\\": \\"invalid_value\\",
307+
\\"values\\": [
308+
\\"FE_WORKLIST\\"
309+
],
310+
\\"path\\": [],
311+
\\"message\\": \\"Invalid input: expected \\\\\\"FE_WORKLIST\\\\\\"\\"
312+
}
313+
],
314+
[
315+
{
316+
\\"code\\": \\"invalid_value\\",
317+
\\"values\\": [
318+
\\"FE_FEOP\\"
319+
],
320+
\\"path\\": [],
321+
\\"message\\": \\"Invalid input: expected \\\\\\"FE_FEOP\\\\\\"\\"
322+
}
323+
],
324+
[
325+
{
326+
\\"code\\": \\"invalid_value\\",
327+
\\"values\\": [
328+
\\"FE_FPM\\"
329+
],
330+
\\"path\\": [],
331+
\\"message\\": \\"Invalid input: expected \\\\\\"FE_FPM\\\\\\"\\"
332+
}
333+
],
334+
[
335+
{
336+
\\"code\\": \\"invalid_value\\",
337+
\\"values\\": [
338+
\\"FF_SIMPLE\\"
339+
],
340+
\\"path\\": [],
341+
\\"message\\": \\"Invalid input: expected \\\\\\"FF_SIMPLE\\\\\\"\\"
342+
}
343+
]
259344
],
260345
\\"path\\": [
261346
\\"floorplan\\"
262347
],
263-
\\"message\\": \\"Invalid option: expected one of \\\\\\"FE_FPM\\\\\\"|\\\\\\"FE_LROP\\\\\\"|\\\\\\"FE_OVP\\\\\\"|\\\\\\"FE_ALP\\\\\\"|\\\\\\"FE_FEOP\\\\\\"|\\\\\\"FE_WORKLIST\\\\\\"|\\\\\\"FF_SIMPLE\\\\\\"\\"
348+
\\"message\\": \\"Invalid input\\"
264349
},
265350
{
266351
\\"expected\\": \\"object\\",
@@ -269,14 +354,6 @@ describe('executeFunctionality', () => {
269354
\\"project\\"
270355
],
271356
\\"message\\": \\"Invalid input: expected object, received undefined\\"
272-
},
273-
{
274-
\\"expected\\": \\"object\\",
275-
\\"code\\": \\"invalid_type\\",
276-
\\"path\\": [
277-
\\"service\\"
278-
],
279-
\\"message\\": \\"Invalid input: expected object, received undefined\\"
280357
}
281358
]"
282359
`);

0 commit comments

Comments
 (0)