Fix broken resource dictionary page 00/143500/2
authorFiete Ostkamp <fiete.ostkamp@telekom.de>
Thu, 5 Mar 2026 08:35:37 +0000 (09:35 +0100)
committerFiete Ostkamp <fiete.ostkamp@telekom.de>
Thu, 5 Mar 2026 08:44:05 +0000 (09:44 +0100)
- 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 <fiete.ostkamp@telekom.de>
cds-ui/designer-client/src/app/modules/feature-modules/resource-dictionary/dictionary.store.ts
cds-ui/designer-client/src/app/modules/feature-modules/resource-dictionary/resource-dictionary-dashboard/dictionary-list/dictionary-list.component.html
cds-ui/e2e-playwright/mock-processor/server.js
cds-ui/e2e-playwright/tests/resource-dictionary.spec.ts
cds-ui/server/src/controllers/data-dictionary.controller.ts
cds-ui/server/src/datasources/resource-dictionary.datasource-template.ts
cds-ui/server/src/services/resource-dictionary.service.ts

index a932327..c992ddf 100644 (file)
@@ -76,17 +76,17 @@ export class DictionaryStore extends Store<DictionaryDashboardState> {
     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<DictionaryDashboardState> {
 
     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
index 41b13a6..dbfce8b 100644 (file)
                     </a>
                     <br />
                     <a href="#" role="button" aria-pressed="true" class="btn-import-package float"><i class="icon-import-blue" aria-hidden="true"></i>Import Dictionary
-                    </a> 
+                    </a>
                 </div>
             </div>
         </div>
     </div>
     <div class="col-lg-3 col-md-6 d-flex" *ngFor="let dictionary of viewedDictionary">
         <!--Card 1-->
-        <div>
-            <div class="card">
+        <div class="card">
                 <div class="card-body">
                     <div class="row">
                         <div class="col-9 pr-0">
@@ -87,4 +86,3 @@
             </div>
         </div>
     </div>
-</div>
index 9b331fb..dd6bcd3 100644 (file)
@@ -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' });
index f2b1d55..f66e352 100644 (file)
@@ -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);
+    });
+});
index 8dbb7eb..25ef95f 100644 (file)
@@ -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': {
index 07faa9a..1f4898e 100644 (file)
@@ -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",
index 6bf3f06..fd9b486 100644 (file)
@@ -12,6 +12,7 @@ export interface ResourceDictionaryService {
   getModelType(source: string): Promise<JSON>;
   getDataTypes(): Promise<JSON>;
   getResourceDictionaryByType(type: string): Promise<JSON>;
+  getPagedDictionary(limit: number, offset: number, sort: string, sortType: string): Promise<any>;
 }
 
 export class ResourceDictionaryServiceProvider implements Provider<ResourceDictionaryService> {