-
-
Notifications
You must be signed in to change notification settings - Fork 18
fix: update connection properties handling for table categories #1470
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR updates the connection properties handling logic to preserve existing table categories when they are not explicitly provided in update requests, rather than deleting them. The key change is a shift from a "replace all" approach to a more granular "create/update/delete" approach that allows categories to persist across property updates.
Key changes:
- Modified the update use case to selectively create, update, or remove table categories based on category_id matching
- Changed behavior to keep existing categories when table_categories field is not provided or empty
- Added comprehensive test coverage for the new "keep existing categories" behavior
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 10 comments.
| File | Description |
|---|---|
| backend/src/entities/connection-properties/use-cases/update-connection-properties.use.case.ts | Refactored category handling logic from "replace all" to "selective create/update/delete" with category_id-based matching |
| backend/test/ava-tests/saas-tests/connection-properties-e2e.test.ts | Updated existing test expectation from 0 to 1 categories and added new test case verifying categories persist across property updates |
Comments suppressed due to low confidence (1)
backend/test/ava-tests/saas-tests/connection-properties-e2e.test.ts:672
- Unused variable createTableCategoriesRO.
const createTableCategoriesRO = JSON.parse(createTableCategoriesResponse.text);
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // } | ||
| // }); | ||
| test.serial( | ||
| `${currentTest} should keep created table categories if the exists return updated connection properties`, |
Copilot
AI
Dec 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test description contains a grammatical error. The phrase "if the exists" appears to be missing a word. It should likely be "if they exist" or similar phrasing.
| `${currentTest} should keep created table categories if the exists return updated connection properties`, | |
| `${currentTest} should keep created table categories if they exist return updated connection properties`, |
|
|
||
| const createTableCategoriesRO = JSON.parse(createTableCategoriesResponse.text); | ||
|
|
||
| t.is(createTableCategoriesResponse.status, 200); |
Copilot
AI
Dec 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The variable 'createTableCategoriesRO' is assigned but never used in the test. Consider either removing this variable assignment or adding assertions to verify the response content from the table categories creation endpoint.
| t.is(createTableCategoriesResponse.status, 200); | |
| t.is(createTableCategoriesResponse.status, 200); | |
| // Assert the response content for table categories creation | |
| t.true(Array.isArray(createTableCategoriesRO)); | |
| t.is(createTableCategoriesRO.length, 1); | |
| t.is(createTableCategoriesRO[0].category_name, 'Category 1'); | |
| t.is(createTableCategoriesRO[0].category_color, '#FF5733'); | |
| t.deepEqual(createTableCategoriesRO[0].tables, [testTableName]); | |
| t.is(createTableCategoriesRO[0].category_id, 'cat-001'); |
| if (table_categories && table_categories.length > 0) { | ||
| const categoriesToRemove = foundCategories.filter((foundCategory) => { | ||
| return !table_categories?.some((inputCategory) => inputCategory.category_id === foundCategory.category_id); | ||
| }); | ||
| if (categoriesToRemove && categoriesToRemove.length > 0) { | ||
| await this._dbContext.tableCategoriesRepository.remove(categoriesToRemove); | ||
| } | ||
|
|
||
| const categoriesToCreate = table_categories.filter((inputCategory) => { | ||
| return !foundCategories.some((foundCategory) => foundCategory.category_id === inputCategory.category_id); | ||
| }); | ||
|
|
||
| if (categoriesToCreate && categoriesToCreate.length > 0) { | ||
| const createdCategories = categoriesToCreate.map((category) => { | ||
| const newCategory = this._dbContext.tableCategoriesRepository.create({ | ||
| category_name: category.category_name, | ||
| tables: category.tables, | ||
| category_color: category.category_color, | ||
| category_id: category.category_id, | ||
| }); | ||
| newCategory.connection_properties = connectionPropertiesToUpdate; | ||
| return newCategory; | ||
| }); | ||
| newCategory.connection_properties = updatedProperties; | ||
| return newCategory; | ||
| const savedNewCategories = await this._dbContext.tableCategoriesRepository.save(createdCategories); | ||
| newCategories.push(...savedNewCategories); | ||
| } | ||
|
|
||
| const categoriesToUpdate = table_categories.filter((inputCategory) => { | ||
| return foundCategories.some((foundCategory) => foundCategory.category_id === inputCategory.category_id); | ||
| }); | ||
| const savedCategories = await this._dbContext.tableCategoriesRepository.save(createdCategories); | ||
| updatedProperties.table_categories = savedCategories; | ||
|
|
||
| for (const category of categoriesToUpdate) { | ||
| const categoryToUpdate = foundCategories.find( | ||
| (foundCategory) => foundCategory.category_id === category.category_id, | ||
| ); | ||
| if (categoryToUpdate) { | ||
| categoryToUpdate.category_name = category.category_name; | ||
| categoryToUpdate.category_color = category.category_color; | ||
| categoryToUpdate.tables = category.tables; | ||
| const savedUpdatedCategory = await this._dbContext.tableCategoriesRepository.save(categoryToUpdate); | ||
| newCategories.push(savedUpdatedCategory); | ||
| } | ||
| } | ||
| } else { | ||
| newCategories.push(...foundCategories); | ||
| } |
Copilot
AI
Dec 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The current logic doesn't distinguish between "not provided" (undefined/null) and "explicitly set to empty" (empty array). When table_categories is an empty array, the condition 'table_categories && table_categories.length > 0' evaluates to false, causing existing categories to be kept (line 100). This makes it impossible for API consumers to delete all categories by passing an empty array. Consider checking if table_categories is explicitly undefined/null versus an empty array to support both "keep existing" and "delete all" use cases.
| updatedConnectionPropertiesWithOutCategories.default_showing_table, | ||
| ); | ||
| t.is(updateConnectionPropertiesROWithoutCategories.table_categories.length, 0); | ||
| t.is(updateConnectionPropertiesROWithoutCategories.table_categories.length, 1); |
Copilot
AI
Dec 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test comment at line 243 states "should delete categories on update with empty categories" but the test no longer verifies this behavior. After this change, the test expects 1 category to remain (line 277) instead of 0, which contradicts the comment's intent. Either the test needs to be updated to match the new behavior (keeping categories when not provided), or the comment needs to be updated to reflect the new expectation.
| for (const category of categoriesToUpdate) { | ||
| const categoryToUpdate = foundCategories.find( | ||
| (foundCategory) => foundCategory.category_id === category.category_id, | ||
| ); | ||
| if (categoryToUpdate) { | ||
| categoryToUpdate.category_name = category.category_name; | ||
| categoryToUpdate.category_color = category.category_color; | ||
| categoryToUpdate.tables = category.tables; | ||
| const savedUpdatedCategory = await this._dbContext.tableCategoriesRepository.save(categoryToUpdate); | ||
| newCategories.push(savedUpdatedCategory); | ||
| } | ||
| } |
Copilot
AI
Dec 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The update logic performs individual save operations in a loop, which results in N+1 database queries. Consider batching the updates using a single save call with an array of updated categories, similar to how new categories are created (line 79). This would reduce the number of database round-trips from N to 1.
| category_id: category.category_id, | ||
| if (table_categories && table_categories.length > 0) { | ||
| const categoriesToRemove = foundCategories.filter((foundCategory) => { | ||
| return !table_categories?.some((inputCategory) => inputCategory.category_id === foundCategory.category_id); |
Copilot
AI
Dec 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The optional chaining operator on 'table_categories' is redundant here since this code is inside a block that already checks 'table_categories && table_categories.length > 0' at line 56. The variable is guaranteed to be defined at this point, so 'table_categories?.some' can be simplified to 'table_categories.some'.
| return !table_categories?.some((inputCategory) => inputCategory.category_id === foundCategory.category_id); | |
| return !table_categories.some((inputCategory) => inputCategory.category_id === foundCategory.category_id); |
| `${currentTest} should keep created table categories if the exists return updated connection properties`, | ||
| async (t) => { | ||
| try { | ||
| const { newConnection2, newConnectionToTestDB, updateConnection, newGroup1, newConnection } = getTestData(); |
Copilot
AI
Dec 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused variable newConnection2.
| `${currentTest} should keep created table categories if the exists return updated connection properties`, | ||
| async (t) => { | ||
| try { | ||
| const { newConnection2, newConnectionToTestDB, updateConnection, newGroup1, newConnection } = getTestData(); |
Copilot
AI
Dec 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused variable newConnectionToTestDB.
| `${currentTest} should keep created table categories if the exists return updated connection properties`, | ||
| async (t) => { | ||
| try { | ||
| const { newConnection2, newConnectionToTestDB, updateConnection, newGroup1, newConnection } = getTestData(); |
Copilot
AI
Dec 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused variable updateConnection.
| const { newConnection2, newConnectionToTestDB, updateConnection, newGroup1, newConnection } = getTestData(); | |
| const { newConnection2, newConnectionToTestDB, newGroup1, newConnection } = getTestData(); |
| `${currentTest} should keep created table categories if the exists return updated connection properties`, | ||
| async (t) => { | ||
| try { | ||
| const { newConnection2, newConnectionToTestDB, updateConnection, newGroup1, newConnection } = getTestData(); |
Copilot
AI
Dec 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused variable newGroup1.
No description provided.