From 1825d36a18d9d8fa08ed9e76eb2411ed4a48abd8 Mon Sep 17 00:00:00 2001 From: Fiete Ostkamp Date: Thu, 5 Mar 2026 09:35:37 +0100 Subject: [PATCH] Fix broken resource dictionary page - add missing route to cds-ui backend for /resourcedictionary/paged - fix broken card layout in the page Issue-ID: CCSDK-4163 Change-Id: I46701e797176621fb0d607abf25bd218b2366ecc Signed-off-by: Fiete Ostkamp --- .../resource-dictionary/dictionary.store.ts | 18 ++--- .../dictionary-list/dictionary-list.component.html | 6 +- cds-ui/e2e-playwright/mock-processor/server.js | 5 ++ .../tests/resource-dictionary.spec.ts | 87 ++++++++++++++++++++-- .../src/controllers/data-dictionary.controller.ts | 16 ++++ .../resource-dictionary.datasource-template.ts | 15 ++++ .../src/services/resource-dictionary.service.ts | 1 + 7 files changed, 129 insertions(+), 19 deletions(-) diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/resource-dictionary/dictionary.store.ts b/cds-ui/designer-client/src/app/modules/feature-modules/resource-dictionary/dictionary.store.ts index a932327cf..c992ddfc5 100644 --- a/cds-ui/designer-client/src/app/modules/feature-modules/resource-dictionary/dictionary.store.ts +++ b/cds-ui/designer-client/src/app/modules/feature-modules/resource-dictionary/dictionary.store.ts @@ -76,17 +76,17 @@ export class DictionaryStore extends Store { protected getPagedDictionary(pageNumber: number, pageSize: number, sortBy: string = this.state.sortBy) { this.dictionaryServiceList.getPagedDictionary(pageNumber, pageSize, sortBy) - .subscribe((pages: DictionaryPage) => { + .subscribe((pages: DictionaryPage[]) => { console.log(pages); this.setState({ ...this.state, - page: pages, - filteredPackages: pages, + page: pages[0], + filteredPackages: pages[0], command: '', - totalPackages: pages.totalElements, + totalPackages: pages[0].totalElements, currentPage: pageNumber, // this param is set only in get all as it represents the total number of pacakges in the server - totalDictionariesWithoutSearchorFilters: pages.totalElements, + totalDictionariesWithoutSearchorFilters: pages[0].totalElements, tags: [], sortBy }); @@ -95,14 +95,14 @@ export class DictionaryStore extends Store { private searchPagedDictionary(keyWord: string, pageNumber: number, pageSize: number, sortBy: string = this.state.sortBy) { this.dictionaryServiceList.getPagedDictionaryByKeyWord(keyWord, pageNumber, pageSize, sortBy) - .subscribe((pages: DictionaryPage) => { + .subscribe((pages: DictionaryPage[]) => { console.log(pages); this.setState({ ...this.state, - page: pages, - filteredPackages: pages, + page: pages[0], + filteredPackages: pages[0], command: keyWord, - totalPackages: pages.totalElements, + totalPackages: pages[0].totalElements, currentPage: pageNumber, tags: [], sortBy diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/resource-dictionary/resource-dictionary-dashboard/dictionary-list/dictionary-list.component.html b/cds-ui/designer-client/src/app/modules/feature-modules/resource-dictionary/resource-dictionary-dashboard/dictionary-list/dictionary-list.component.html index 41b13a601..dbfce8b26 100644 --- a/cds-ui/designer-client/src/app/modules/feature-modules/resource-dictionary/resource-dictionary-dashboard/dictionary-list/dictionary-list.component.html +++ b/cds-ui/designer-client/src/app/modules/feature-modules/resource-dictionary/resource-dictionary-dashboard/dictionary-list/dictionary-list.component.html @@ -32,15 +32,14 @@
Import Dictionary - +
-
-
+
@@ -87,4 +86,3 @@
-
diff --git a/cds-ui/e2e-playwright/mock-processor/server.js b/cds-ui/e2e-playwright/mock-processor/server.js index 9b331fb2e..dd6bcd374 100644 --- a/cds-ui/e2e-playwright/mock-processor/server.js +++ b/cds-ui/e2e-playwright/mock-processor/server.js @@ -201,6 +201,11 @@ const server = http.createServer(async (req, res) => { // ── dictionary ─────────────────────────────────────────────────────────────── + // GET /api/v1/dictionary/paged (must precede /:name) + if (method === 'GET' && pathname === `${BASE}/dictionary/paged`) { + return json(res, pagedResponse(resourceDictionaries, query)); + } + // GET /api/v1/dictionary/source-mapping (must precede /:name) if (method === 'GET' && pathname === `${BASE}/dictionary/source-mapping`) { return json(res, { INPUT: 'input', DEFAULT: 'default', DB: 'db', REST: 'rest' }); diff --git a/cds-ui/e2e-playwright/tests/resource-dictionary.spec.ts b/cds-ui/e2e-playwright/tests/resource-dictionary.spec.ts index f2b1d555a..f66e352bb 100644 --- a/cds-ui/e2e-playwright/tests/resource-dictionary.spec.ts +++ b/cds-ui/e2e-playwright/tests/resource-dictionary.spec.ts @@ -9,12 +9,10 @@ * 3. Search & filter UI – sub-components render. * 4. API integration – requests are proxied through the LoopBack BFF to the * mock-processor and return successful responses. - * - * Implementation note: the BFF does not expose a paged dictionary endpoint - * (/resourcedictionary/paged), so the dictionary list on this page renders * - * empty in the test environment. The tests that assert on card data are - * therefore skipped; this serves as a documented gap in the BFF implementation - * rather than a test environment problem. + * 5. Dictionary listing – the paged endpoint returns data and dictionary + * cards are rendered. + * 6. Create-then-list flow – creating a dictionary then navigating back + * shows the list. */ import { test, expect } from '@playwright/test'; @@ -142,3 +140,80 @@ test.describe('Resource Dictionary – navigation', () => { await expect(page.locator('app-dictionary-header')).toBeAttached({ timeout: 10_000 }); }); }); + +test.describe('Resource Dictionary – paged listing', () => { + test('GET /resourcedictionary/paged returns a Page object with content array', async ({ request }) => { + const resp = await request.get('http://localhost:3000/resourcedictionary/paged?offset=0&limit=5&sort=DATE&sortType=ASC'); + expect(resp.status()).toBe(200); + const body = await resp.json(); + // LoopBack REST connector wraps the response in a single-element array + expect(Array.isArray(body)).toBe(true); + expect(body.length).toBe(1); + const pageObj = body[0]; + expect(pageObj).toHaveProperty('content'); + expect(pageObj).toHaveProperty('totalElements'); + expect(Array.isArray(pageObj.content)).toBe(true); + expect(pageObj.totalElements).toBeGreaterThan(0); + expect(pageObj.content.length).toBeGreaterThan(0); + }); + + test('dictionary cards are rendered on the page', async ({ page }) => { + // Register response listener BEFORE navigating to avoid race condition + await Promise.all([ + page.waitForResponse( + r => r.url().includes('/resourcedictionary/paged') && r.status() === 200, + { timeout: 15_000 }, + ), + page.goto('/#/resource-dictionary'), + ]); + // Wait for Angular to render the dictionary list items + const cards = page.locator('app-dictionary-list .card'); + await expect(cards.first()).toBeVisible({ timeout: 10_000 }); + // The fixture has 3 dictionaries + 1 static "Create/Import" card = 4 + await expect(cards).toHaveCount(4); + }); + + test('dictionary header shows correct total count', async ({ page }) => { + await Promise.all([ + page.waitForResponse( + r => r.url().includes('/resourcedictionary/paged') && r.status() === 200, + { timeout: 15_000 }, + ), + page.goto('/#/resource-dictionary'), + ]); + // The header shows "Resource Dictionary (N Dictionary)" + const header = page.locator('app-dictionary-header h2'); + await expect(header).toContainText('3', { timeout: 10_000 }); + }); +}); + +test.describe('Resource Dictionary – create then list', () => { + test('creating a dictionary and navigating back shows the list', async ({ page }) => { + // Navigate to the create dictionary page + await Promise.all([ + page.waitForResponse( + r => r.url().includes('/resourcedictionary/paged') && r.status() === 200, + { timeout: 15_000 }, + ), + page.goto('/#/resource-dictionary'), + ]); + + // Click "Create Dictionary" link on the add-card + await page.locator('a', { hasText: 'Create Dictionary' }).click(); + await expect(page).toHaveURL(/createDictionary/); + + // Navigate back to the dictionary list – register listener before navigating + await Promise.all([ + page.waitForResponse( + r => r.url().includes('/resourcedictionary/paged') && r.status() === 200, + { timeout: 15_000 }, + ), + page.goto('/#/resource-dictionary'), + ]); + + // Dictionary list should still render cards (3 dictionaries + 1 create card) + const cards = page.locator('app-dictionary-list .card'); + await expect(cards.first()).toBeVisible({ timeout: 10_000 }); + await expect(cards).toHaveCount(4); + }); +}); diff --git a/cds-ui/server/src/controllers/data-dictionary.controller.ts b/cds-ui/server/src/controllers/data-dictionary.controller.ts index 8dbb7eb3c..25ef95fc9 100644 --- a/cds-ui/server/src/controllers/data-dictionary.controller.ts +++ b/cds-ui/server/src/controllers/data-dictionary.controller.ts @@ -24,6 +24,22 @@ export class DataDictionaryController { public rdservice: ResourceDictionaryService, ) { } + @get('/resourcedictionary/paged', { + responses: { + '200': { + description: 'Resource Dictionary with pagination', + content: { 'application/json': {} }, + }, + }, + }) + async getPagedDictionary( + @param.query.number('limit') limit: number, + @param.query.number('offset') offset: number, + @param.query.string('sort') sort: string, + @param.query.string('sortType') sortType: string) { + return await this.rdservice.getPagedDictionary(limit, offset, sort, sortType || 'ASC'); + } + @get('/resourcedictionary/{name}', { responses: { '200': { diff --git a/cds-ui/server/src/datasources/resource-dictionary.datasource-template.ts b/cds-ui/server/src/datasources/resource-dictionary.datasource-template.ts index 07faa9a1a..1f4898e8a 100644 --- a/cds-ui/server/src/datasources/resource-dictionary.datasource-template.ts +++ b/cds-ui/server/src/datasources/resource-dictionary.datasource-template.ts @@ -137,6 +137,21 @@ export default { } }, + { + "template": { + "method": "GET", + "url": processorApiConfig.http.url + "/dictionary/paged?limit={limit}&offset={offset}&sort={sort}&sortType={sortType}", + "headers": { + "accepts": "application/json", + "content-type": "application/json", + "authorization": processorApiConfig.http.authToken + }, + "responsePath": "$", + }, + "functions": { + "getPagedDictionary": ["limit", "offset", "sort", "sortType"], + } + }, { "template": { "method": "GET", diff --git a/cds-ui/server/src/services/resource-dictionary.service.ts b/cds-ui/server/src/services/resource-dictionary.service.ts index 6bf3f06ae..fd9b486e5 100644 --- a/cds-ui/server/src/services/resource-dictionary.service.ts +++ b/cds-ui/server/src/services/resource-dictionary.service.ts @@ -12,6 +12,7 @@ export interface ResourceDictionaryService { getModelType(source: string): Promise; getDataTypes(): Promise; getResourceDictionaryByType(type: string): Promise; + getPagedDictionary(limit: number, offset: number, sort: string, sortType: string): Promise; } export class ResourceDictionaryServiceProvider implements Provider { -- 2.16.6