enable sorting for all table category#1620
Conversation
There was a problem hiding this comment.
Pull request overview
This pull request enables user-defined sorting for the "All tables" category in the v2 table categories endpoint. The implementation introduces a special category identifier 'all-tables-kitten' that allows storing custom table ordering preferences, replacing the previous behavior where the "All tables" category had a null category_id.
Changes:
- Modified v2 endpoint to use 'all-tables-kitten' as a persistable category ID instead of null
- Implemented logic to preserve user-defined table order and automatically append newly discovered tables
- Added comprehensive tests validating order persistence and dynamic table detection
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 7 comments.
| File | Description |
|---|---|
| backend/test/ava-tests/saas-tests/table-categories-e2e.test.ts | Updated existing test assertion and added two new tests covering order preservation and new table appending scenarios |
| backend/src/entities/table-categories/use-cases/find-table-categories-with-tables.use.case.ts | Implemented logic to retrieve stored table order, detect new tables, and persist updated ordering |
| backend/src/entities/table-categories/use-cases/create-or-update-table-categories.use.case.ts | Changed filtering logic to handle 'all-tables-kitten' as a persistable category rather than excluding it |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const allTableCategory: FoundTableCategoriesWithTablesRo = { | ||
| category_id: null, | ||
| category_color: null, | ||
| category_id: 'all-tables-kitten', |
There was a problem hiding this comment.
The identifier 'all-tables-kitten' is an unusual naming choice for a system-level constant. Consider using a more conventional identifier like 'all-tables', 'all', or '_all_tables' that better reflects its special system status. The '-kitten' suffix appears arbitrary and may confuse developers unfamiliar with the codebase context.
| const storedAllTablesCategory = foundTableCategories.find((category) => category.category_id === 'all-tables-kitten'); | ||
| const otherCategories = foundTableCategories.filter((category) => category.category_id !== 'all-tables-kitten'); | ||
|
|
||
| let allTablesOrdered: Array<FoundTableDs>; | ||
| if (storedAllTablesCategory) { | ||
| allTablesOrdered = storedAllTablesCategory.tables | ||
| .map((tableName) => tablesRO.find((tableRO) => tableRO.table === tableName)) | ||
| .filter(Boolean); | ||
|
|
||
| const storedTableNames = new Set(storedAllTablesCategory.tables); | ||
| const newTables = tablesRO.filter((tableRO) => !storedTableNames.has(tableRO.table)); | ||
|
|
||
| if (newTables.length > 0) { | ||
| allTablesOrdered = [...allTablesOrdered, ...newTables]; | ||
| storedAllTablesCategory.tables = allTablesOrdered.map((t) => t.table); | ||
| await this._dbContext.tableCategoriesRepository.save(storedAllTablesCategory); | ||
| } | ||
| } else { | ||
| allTablesOrdered = tablesRO; | ||
| } | ||
|
|
||
| const allTableCategory: FoundTableCategoriesWithTablesRo = { | ||
| category_id: null, | ||
| category_color: null, | ||
| category_id: 'all-tables-kitten', |
There was a problem hiding this comment.
The magic string 'all-tables-kitten' is duplicated across multiple files (in this file at lines 93, 94, 115, and in create-or-update-table-categories.use.case.ts at lines 26, 27). Consider extracting this to a shared constant to ensure consistency and make future changes easier. For example, define it as a constant like ALL_TABLES_CATEGORY_ID in a shared constants file.
|
|
||
| const createdCategories = filteredCategories.map((category) => { | ||
| const categoriesToSave = [...filteredCategories]; | ||
| if (allTablesCategory) { |
There was a problem hiding this comment.
The all-tables-kitten category bypasses validation (line 34 validates only filteredCategories). This means invalid table names could be stored in all-tables-kitten without validation. While the find use case filters out non-existent tables at runtime, consider whether basic validation (non-empty category_name, tables as array) should still apply to all-tables-kitten for data integrity.
| if (allTablesCategory) { | |
| if (allTablesCategory) { | |
| if ( | |
| typeof allTablesCategory.category_name !== 'string' || | |
| allTablesCategory.category_name.trim().length === 0 || | |
| !Array.isArray(allTablesCategory.tables) | |
| ) { | |
| throw new Error('Invalid all-tables-kitten category payload'); | |
| } |
| category_color: null, | ||
| category_id: 'all-tables-kitten', | ||
| category_color: storedAllTablesCategory?.category_color ?? null, | ||
| category_name: 'All tables', |
There was a problem hiding this comment.
The category_name is hardcoded to 'All tables' regardless of what name is stored in the database for the all-tables-kitten category. This means if a user creates an all-tables-kitten category with a different name, it will be silently changed to 'All tables' in the response. Consider whether this behavior should validate and enforce the name during creation, or document this override behavior clearly.
| category_name: 'All tables', | |
| category_name: storedAllTablesCategory?.category_name ?? 'All tables', |
| if (storedAllTablesCategory) { | ||
| allTablesOrdered = storedAllTablesCategory.tables | ||
| .map((tableName) => tablesRO.find((tableRO) => tableRO.table === tableName)) | ||
| .filter(Boolean); | ||
|
|
||
| const storedTableNames = new Set(storedAllTablesCategory.tables); | ||
| const newTables = tablesRO.filter((tableRO) => !storedTableNames.has(tableRO.table)); | ||
|
|
||
| if (newTables.length > 0) { | ||
| allTablesOrdered = [...allTablesOrdered, ...newTables]; | ||
| storedAllTablesCategory.tables = allTablesOrdered.map((t) => t.table); | ||
| await this._dbContext.tableCategoriesRepository.save(storedAllTablesCategory); | ||
| } |
There was a problem hiding this comment.
The logic handles adding new tables but does not handle removing deleted tables from the stored order. If a table is deleted from the database but still exists in storedAllTablesCategory.tables, it will be filtered out by filter(Boolean) in line 100, but the database will not be updated to reflect this change. This can lead to the stored order containing references to non-existent tables that accumulate over time. Consider updating the stored order in the database when tables are removed, similar to how it's done when new tables are added.
| if (storedAllTablesCategory) { | ||
| allTablesOrdered = storedAllTablesCategory.tables | ||
| .map((tableName) => tablesRO.find((tableRO) => tableRO.table === tableName)) | ||
| .filter(Boolean); |
There was a problem hiding this comment.
The filter(Boolean) operation is potentially unsafe in TypeScript because the type system doesn't correctly narrow Array<FoundTableDs | undefined> to Array<FoundTableDs>. While it works at runtime, it would be more type-safe to use .filter((table): table is FoundTableDs => table !== undefined). This also applies to similar logic in lines 121-123 for other categories.
| .filter(Boolean); | |
| .filter((table): table is FoundTableDs => table !== undefined); |
| const allTableCategory: FoundTableCategoriesWithTablesRo = { | ||
| category_id: null, | ||
| category_color: null, | ||
| category_id: 'all-tables-kitten', |
There was a problem hiding this comment.
This changes the API contract for the v2 endpoint where category_id was previously null for the "All tables" category and is now 'all-tables-kitten'. This is a breaking change for any API consumers expecting null. Consider whether this should be versioned as v3 or if there's documentation/migration guide needed for existing API consumers.
| category_id: 'all-tables-kitten', | |
| category_id: null, |
No description provided.