From b9c1a270e31e704f10111063106e7f3e6d2e4f4d Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Thu, 15 Oct 2026 19:34:55 +0200 Subject: [PATCH] feat(applications): tables, members and stats on the overview The application overview now shows which tables the application works on, who it is shared with, and a short summary. - Tables: every table the application uses, through its own nodes, a data widget on one of its pages, or a menu entry that opens a table or view. Each row shows the row count, the number of views and what uses the table. A table the user cannot access is listed by id. - Members (owner only): the owner and everyone the application is shared with, with a button to change the shares. - Stats: a line counting the pages and tables, plus the members for the owner. - Page filter: a filter field appears once an application has more than six pages, matching title, type and what the page shows. Assisted-by: ClaudeCode:claude-opus-5-5 --- playwright/e2e/application.spec.ts | 42 +++ .../main/sections/ApplicationOverview.vue | 271 +++++++++++++++++- 2 files changed, 308 insertions(+), 5 deletions(-) diff --git a/playwright/e2e/application.spec.ts b/playwright/e2e/application.spec.ts index c952c4edc4..eccf5285a6 100644 --- a/playwright/e2e/application.spec.ts +++ b/playwright/e2e/application.spec.ts @@ -124,6 +124,48 @@ test.describe('Applications', () => { await expect(page.locator('h1', { hasText: tableTitle })).toBeVisible({ timeout: 30000 }) }) + test('The overview lists the tables the application uses, its members and its counts', async ({ userPage: { page, user } }) => { + const tableResponse = await ocsRequest(page.request, user, { method: 'POST', url: '/index.php/apps/tables/api/1/tables', data: { title: 'Requests' } }) + const table = await tableResponse.json() + const contextResponse = await ocsRequest(page.request, user, { + method: 'POST', + url: `${API}/contexts?format=json`, + data: { + name: 'used tables application', + iconName: 'star', + menuItems: [ + { label: 'All requests', targetType: 'table', targetId: table.id }, + { label: 'Docs', targetType: 'url', url: 'https://docs.nextcloud.com' }, + ], + }, + }) + const context = (await contextResponse.json()).ocs.data + + await page.goto(`/index.php/apps/tables/#/application/${context.id}`) + + const stats = page.locator('[data-cy="context-stats"]') + await expect(stats).toContainText('1 page') + await expect(stats).toContainText('1 table') + await expect(stats).toContainText('1 member') + const row = page.locator('[data-cy="context-table-row"]').filter({ hasText: 'Requests' }) + await expect(row).toContainText('All requests') + await expect(page.locator('[data-cy="context-members-list"]')).toContainText('Owner') + }) + + test('An application with many pages gets a filter for them', async ({ userPage: { page, user } }) => { + const contextId = await createEmptyApplication(page.request, user, 'many pages application') + for (const title of ['Intake', 'Planning', 'Reports', 'Archive', 'Invoices', 'Contacts', 'Settings']) { + await ocsRequest(page.request, user, { method: 'POST', url: `${API}/contexts/${contextId}/pages?format=json`, data: { title } }) + } + + await page.goto(`/index.php/apps/tables/#/application/${contextId}`) + await expect(page.locator('[data-cy="page-card"]')).toHaveCount(8) + await page.getByRole('textbox', { name: 'Filter pages' }).fill('voic') + + await expect(page.locator('[data-cy="page-card"]')).toHaveCount(1) + await expect(page.locator('[data-cy="page-card"]')).toContainText('Invoices') + }) + test('A page with widgets added on the overview gets a menu entry and opens in edit mode', async ({ userPage: { page } }) => { await page.goto('/index.php/apps/tables') await createContext(page, 'overview application') diff --git a/src/modules/main/sections/ApplicationOverview.vue b/src/modules/main/sections/ApplicationOverview.vue index 0ba04585cf..7ad565b440 100644 --- a/src/modules/main/sections/ApplicationOverview.vue +++ b/src/modules/main/sections/ApplicationOverview.vue @@ -72,11 +72,21 @@ +

+ + {{ stat.label }} + +

{{ t('tables', 'Pages') }}

+