diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/[...rest]/+page.ts b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/[...rest]/+page.ts index 0543bf4f1e..c67c03919d 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/[...rest]/+page.ts +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/[...rest]/+page.ts @@ -4,8 +4,9 @@ import { AppwriteException } from '@appwrite.io/console'; import { databaseRowSheetOptions } from '../table-[table]/store'; import { noSqlDocument } from '../collection-[collection]/store'; import { resolveRoute } from '$lib/stores/navigation'; +import { toDatabaseType } from '$database/(entity)'; -export const load: PageLoad = async ({ params, url }) => { +export const load: PageLoad = async ({ params, url, parent }) => { const restSegments = params.rest ? params.rest.split('/').filter(Boolean) : []; const baseUrl = resolveRoute( '/(console)/project-[region]-[project]/databases/database-[database]', @@ -36,7 +37,19 @@ export const load: PageLoad = async ({ params, url }) => { const documentMatch = lastSegment.match(/^document-([^/]+)$/); if (documentMatch) { const documentId = documentMatch[1]; - noSqlDocument.update({ documentId }); + const { database } = await parent(); + const type = toDatabaseType(database.type); + + if (type === 'legacy' || type === 'tablesdb') { + databaseRowSheetOptions.update((options) => ({ + ...options, + rowId: documentId, + show: true, + title: 'Update row' + })); + } else { + noSqlDocument.update({ documentId }); + } const parentSegments = restSegments.slice(0, -1); const newPath = `${baseUrl}/${parentSegments.join('/')}`; diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/+layout.ts b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/+layout.ts index 1a0b06a19d..6fc346bcd8 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/+layout.ts +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/+layout.ts @@ -2,16 +2,29 @@ import Header from './header.svelte'; import type { LayoutLoad } from './$types'; import { Dependencies } from '$lib/constants'; import { Breadcrumbs, toDatabaseType, useDatabaseSdk } from '$database/(entity)'; +import { redirect } from '@sveltejs/kit'; +import { resolveRoute } from '$lib/stores/navigation'; -export const load: LayoutLoad = async ({ params, depends, parent }) => { +export const load: LayoutLoad = async ({ params, depends, parent, url }) => { const { database } = await parent(); + const type = toDatabaseType(database.type); + + if (type === 'legacy' || type === 'tablesdb') { + const collectionPath = resolveRoute( + '/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]', + params + ); + const tablePath = resolveRoute( + '/(console)/project-[region]-[project]/databases/database-[database]/table-[table]', + { ...params, table: params.collection } + ); + + redirect(308, tablePath + url.pathname.slice(collectionPath.length) + url.search); + } + depends(Dependencies.COLLECTION); - const databaseSdk = useDatabaseSdk( - params.region, - params.project, - toDatabaseType(database.type) - ); + const databaseSdk = useDatabaseSdk(params.region, params.project, type); const collection = await databaseSdk.getEntity({ databaseId: params.database, diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/layout.test.ts b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/layout.test.ts new file mode 100644 index 0000000000..c7212cc399 --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/layout.test.ts @@ -0,0 +1,119 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import type { DatabaseType } from '$database/(entity)/helpers/terminology'; +import { get } from 'svelte/store'; +import { load } from './+layout'; +import { load as loadRecord } from '../[...rest]/+page'; +import { databaseRowSheetOptions } from '../table-[table]/store'; +import { noSqlDocument } from './store'; + +const { getEntity } = vi.hoisted(() => ({ getEntity: vi.fn() })); + +vi.mock('./header.svelte', () => ({ default: {} })); +vi.mock('$database/(entity)', async () => { + const { toDatabaseType } = await import('$database/(entity)/helpers/terminology'); + return { + Breadcrumbs: {}, + toDatabaseType, + useDatabaseSdk: () => ({ getEntity }) + }; +}); + +const params = { region: 'fra', project: 'project', database: 'database', collection: 'items' }; +const databasePath = '/console/project-fra-project/databases/database-database'; + +function event(type: DatabaseType, suffix = '') { + return { + params, + depends: vi.fn(), + parent: async () => ({ database: { $id: params.database, type } }), + url: new URL(`https://console.example${databasePath}/collection-items${suffix}`) + } as unknown as Parameters[0]; +} + +function recordEvent(type: DatabaseType, rest: string) { + return { + ...event(type), + params: { ...params, rest }, + url: new URL(`https://console.example${databasePath}/${rest}?limit=50`) + } as unknown as Parameters[0]; +} + +describe('collection layout', () => { + beforeEach(() => { + getEntity.mockReset(); + databaseRowSheetOptions.update((options) => ({ ...options, show: false, rowId: null })); + noSqlDocument.reset(); + }); + + it.each([ + ['legacy', ''], + ['legacy', '/indexes?search=name'], + ['legacy', '/settings'], + ['legacy', '/export'], + ['tablesdb', '?limit=50&query=%5B%5D'] + ] as const)('redirects %s collection links to the table page (%s)', async (type, suffix) => { + await expect(load(event(type, suffix))).rejects.toMatchObject({ + status: 308, + location: `${databasePath}/table-items${suffix}` + }); + }); + + it.each(['documentsdb', 'vectorsdb'] as const)('loads a %s collection', async (type) => { + const collection = { $id: params.collection, name: 'Items' }; + getEntity.mockResolvedValue(collection); + + const result = await load(event(type)); + + expect(result).toMatchObject({ collection }); + }); + + it.each(['legacy', 'tablesdb'] as const)( + 'opens the requested row after following a %s document link', + async (type) => { + await expect( + loadRecord(recordEvent(type, 'collection-items/document-record')) + ).rejects.toMatchObject({ + status: 308, + location: `${databasePath}/collection-items?limit=50` + }); + await expect(load(event(type, '?limit=50'))).rejects.toMatchObject({ + status: 308, + location: `${databasePath}/table-items?limit=50` + }); + + expect(get(databaseRowSheetOptions)).toMatchObject({ + rowId: 'record', + show: true, + title: 'Update row' + }); + expect(get(noSqlDocument).documentId).toBeNull(); + } + ); + + it.each(['documentsdb', 'vectorsdb'] as const)( + 'keeps a %s document link on the collection sheet', + async (type) => { + await expect( + loadRecord(recordEvent(type, 'collection-items/document-record')) + ).rejects.toMatchObject({ + status: 308, + location: `${databasePath}/collection-items?limit=50` + }); + + expect(get(noSqlDocument).documentId).toBe('record'); + expect(get(databaseRowSheetOptions).show).toBe(false); + } + ); + + it('keeps table row links working', async () => { + await expect( + loadRecord(recordEvent('tablesdb', 'table-items/row-record')) + ).rejects.toMatchObject({ + status: 308, + location: `${databasePath}/table-items?limit=50` + }); + + expect(get(databaseRowSheetOptions)).toMatchObject({ rowId: 'record', show: true }); + expect(get(noSqlDocument).documentId).toBeNull(); + }); +});