fix (dashboard): Parse foreign key relations correctly (#3458)

### **PR Type**
Bug fix, Tests


___

### **Description**
- Pass updated table name to refetch queries

- Extend `onSubmit` callbacks with `tableName`

- Strip quotes from foreign key column names

- Add and restructure tests for extractor


___



<details> <summary><h3> File Walkthrough</h3></summary>

<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Bug
fix</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>DataBrowserSidebar.tsx</strong><dd><code>Update
refetchQueries with new tableName</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>


dashboard/src/features/orgs/projects/database/dataGrid/components/DataBrowserSidebar/DataBrowserSidebar.tsx

<ul><li>Accept <code>tableName</code> in <code>onSubmit</code>
callback<br> <li> Use <code>tableName</code> for
<code>refetchQueries</code> key</ul>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3458/files#diff-3a9ff7af4a31fbf7e501a77399b2b35306d9e635b021c93f1bc13fc4e225219c">+2/-2</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>EditTableForm.tsx</strong><dd><code>Extend onSubmit to
receive tableName</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>


dashboard/src/features/orgs/projects/database/dataGrid/components/EditTableForm/EditTableForm.tsx

<ul><li>Change <code>onSubmit</code> prop to accept
<code>tableName</code><br> <li> Pass <code>updatedTable.name</code> to
<code>onSubmit</code></ul>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3458/files#diff-e628e74884ed048e1498960b80ad4d2a9fa6b4e05c89545c404e0ed50b43e50a">+2/-2</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>extractForeignKeyRelation.ts</strong><dd><code>Strip
quotes from extracted column names</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>


dashboard/src/features/orgs/projects/database/dataGrid/utils/extractForeignKeyRelation/extractForeignKeyRelation.ts

- Remove surrounding parentheses and double quotes from `columnName`


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3458/files#diff-ff940d21b4207265ccae2acaa2e5b8d2a0edc01fd51f14d1c0f6beed43596c49">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></td></tr><tr><td><strong>Tests</strong></td><td><table>
<tr>
  <td>
    <details>

<summary><strong>extractForeignKeyRelation.test.ts</strong><dd><code>Restructure
extractor tests and add cases</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>


dashboard/src/features/orgs/projects/database/dataGrid/utils/extractForeignKeyRelation/extractForeignKeyRelation.test.ts

<ul><li>Group tests under <code>describe</code> blocks<br> <li> Add test
for capital-letter column names<br> <li> Consolidate no-action
scenarios</ul>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3458/files#diff-9f5bd2c96f0cdcb925343201e389d2d57d8f1fb2adf7daf522338939c613f426">+126/-109</a></td>

</tr>
</table></td></tr><tr><td><strong>Configuration
changes</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>cold-toys-bow.md</strong><dd><code>Add changelog for
foreign key fix</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

.changeset/cold-toys-bow.md

- Add dashboard patch changelog entry


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3458/files#diff-fe0a7d0ca4fb132b472a16dacc0ebc4ade0b69f3280d10ac4c9b35c955ccdce3">+5/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></td></tr></tr></tbody></table>

</details>

___
This commit is contained in:
robertkasza
2025-09-04 11:00:52 +02:00
committed by GitHub
parent 0d183761ae
commit 397bfc948c
5 changed files with 140 additions and 118 deletions

View File

@@ -0,0 +1,5 @@
---
'@nhost/dashboard': patch
---
fix (dashboard): Parse foreign key relations correctly

View File

@@ -408,9 +408,9 @@ function DataBrowserSidebarContent({
title: 'Edit Table',
component: (
<EditTableForm
onSubmit={async () => {
onSubmit={async (tableName) => {
await queryClient.refetchQueries([
`${dataSourceSlug}.${table.table_schema}.${table.table_name}`,
`${dataSourceSlug}.${table.table_schema}.${tableName}`,
]);
await refetch();
}}

View File

@@ -38,7 +38,7 @@ export interface EditTableFormProps
/**
* Function to be called when the form is submitted.
*/
onSubmit?: () => Promise<void>;
onSubmit?: (tableName: string) => Promise<void>;
}
export default function EditTableForm({
@@ -182,7 +182,7 @@ export default function EditTableForm({
}
if (onSubmit) {
await onSubmit();
await onSubmit(updatedTable.name);
}
if (originalTable.table_name !== updatedTable.name) {

View File

@@ -1,122 +1,139 @@
import { expect, test } from 'vitest';
import extractForeignKeyRelation from './extractForeignKeyRelation';
test('should return null if there is no match', () => {
expect(
extractForeignKeyRelation(
'table_id_fkey',
'something_that_is_not_a_foreign_key',
),
).toBe(null);
});
test('should extract data from a raw foreign key constraint', () => {
expect(
extractForeignKeyRelation(
'table_id_fkey',
'FOREIGN KEY (user_id) REFERENCES auth.users(id) ON UPDATE RESTRICT ON DELETE RESTRICT',
),
).toMatchObject({
name: 'table_id_fkey',
columnName: 'user_id',
referencedSchema: 'auth',
referencedTable: 'users',
referencedColumn: 'id',
updateAction: 'RESTRICT',
deleteAction: 'RESTRICT',
describe('extractForeignKeyRelation', () => {
test('should return null if there is no match', () => {
expect(
extractForeignKeyRelation(
'table_id_fkey',
'something_that_is_not_a_foreign_key',
),
).toBe(null);
});
expect(
extractForeignKeyRelation(
'table_id_fkey',
'FOREIGN KEY (user_id) REFERENCES auth.users(id) ON UPDATE CASCADE ON DELETE CASCADE',
),
).toMatchObject({
name: 'table_id_fkey',
columnName: 'user_id',
referencedSchema: 'auth',
referencedTable: 'users',
referencedColumn: 'id',
updateAction: 'CASCADE',
deleteAction: 'CASCADE',
test('should extract data from a raw foreign key constraint', () => {
expect(
extractForeignKeyRelation(
'table_id_fkey',
'FOREIGN KEY (user_id) REFERENCES auth.users(id) ON UPDATE RESTRICT ON DELETE RESTRICT',
),
).toMatchObject({
name: 'table_id_fkey',
columnName: 'user_id',
referencedSchema: 'auth',
referencedTable: 'users',
referencedColumn: 'id',
updateAction: 'RESTRICT',
deleteAction: 'RESTRICT',
});
expect(
extractForeignKeyRelation(
'table_id_fkey',
'FOREIGN KEY (user_id) REFERENCES auth.users(id) ON UPDATE CASCADE ON DELETE CASCADE',
),
).toMatchObject({
name: 'table_id_fkey',
columnName: 'user_id',
referencedSchema: 'auth',
referencedTable: 'users',
referencedColumn: 'id',
updateAction: 'CASCADE',
deleteAction: 'CASCADE',
});
expect(
extractForeignKeyRelation(
'table_id_fkey',
'FOREIGN KEY (user_id) REFERENCES auth.users(id) ON UPDATE SET DEFAULT ON DELETE SET NULL',
),
).toMatchObject({
name: 'table_id_fkey',
columnName: 'user_id',
referencedSchema: 'auth',
referencedTable: 'users',
referencedColumn: 'id',
updateAction: 'SET DEFAULT',
deleteAction: 'SET NULL',
});
});
test("should return column's name with a capital letter without quotes", () => {
expect(
extractForeignKeyRelation(
'table_id_fkey',
'FOREIGN KEY ("userId") REFERENCES users(id) ON UPDATE RESTRICT ON DELETE RESTRICT',
),
).toMatchObject({
name: 'table_id_fkey',
columnName: 'userId',
referencedSchema: null,
referencedTable: 'users',
referencedColumn: 'id',
updateAction: 'RESTRICT',
deleteAction: 'RESTRICT',
});
});
test('should return null as referenced schema if it is not present', () => {
expect(
extractForeignKeyRelation(
'table_id_fkey',
'FOREIGN KEY (user_id) REFERENCES users(id) ON UPDATE RESTRICT ON DELETE RESTRICT',
),
).toMatchObject({
name: 'table_id_fkey',
columnName: 'user_id',
referencedSchema: null,
referencedTable: 'users',
referencedColumn: 'id',
updateAction: 'RESTRICT',
deleteAction: 'RESTRICT',
});
});
expect(
extractForeignKeyRelation(
'table_id_fkey',
'FOREIGN KEY (user_id) REFERENCES auth.users(id) ON UPDATE SET DEFAULT ON DELETE SET NULL',
),
).toMatchObject({
name: 'table_id_fkey',
columnName: 'user_id',
referencedSchema: 'auth',
referencedTable: 'users',
referencedColumn: 'id',
updateAction: 'SET DEFAULT',
deleteAction: 'SET NULL',
});
});
test('should return null as referenced schema if it is not present', () => {
expect(
extractForeignKeyRelation(
'table_id_fkey',
'FOREIGN KEY (user_id) REFERENCES users(id) ON UPDATE RESTRICT ON DELETE RESTRICT',
),
).toMatchObject({
name: 'table_id_fkey',
columnName: 'user_id',
referencedSchema: null,
referencedTable: 'users',
referencedColumn: 'id',
updateAction: 'RESTRICT',
deleteAction: 'RESTRICT',
});
});
test('should return NO ACTION for update and delete actions if they are not present', () => {
expect(
extractForeignKeyRelation(
'table_id_fkey',
'FOREIGN KEY (user_id) REFERENCES users(id) ON UPDATE RESTRICT',
),
).toMatchObject({
name: 'table_id_fkey',
columnName: 'user_id',
referencedSchema: null,
referencedTable: 'users',
referencedColumn: 'id',
updateAction: 'RESTRICT',
deleteAction: 'NO ACTION',
});
expect(
extractForeignKeyRelation(
'table_id_fkey',
'FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE RESTRICT',
),
).toMatchObject({
name: 'table_id_fkey',
columnName: 'user_id',
referencedSchema: null,
referencedTable: 'users',
referencedColumn: 'id',
updateAction: 'NO ACTION',
deleteAction: 'RESTRICT',
});
expect(
extractForeignKeyRelation(
'table_id_fkey',
'FOREIGN KEY (user_id) REFERENCES users(id)',
),
).toMatchObject({
name: 'table_id_fkey',
columnName: 'user_id',
referencedSchema: null,
referencedTable: 'users',
referencedColumn: 'id',
updateAction: 'NO ACTION',
deleteAction: 'NO ACTION',
test('should return NO ACTION for update and delete actions if they are not present', () => {
expect(
extractForeignKeyRelation(
'table_id_fkey',
'FOREIGN KEY (user_id) REFERENCES users(id) ON UPDATE RESTRICT',
),
).toMatchObject({
name: 'table_id_fkey',
columnName: 'user_id',
referencedSchema: null,
referencedTable: 'users',
referencedColumn: 'id',
updateAction: 'RESTRICT',
deleteAction: 'NO ACTION',
});
expect(
extractForeignKeyRelation(
'table_id_fkey',
'FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE RESTRICT',
),
).toMatchObject({
name: 'table_id_fkey',
columnName: 'user_id',
referencedSchema: null,
referencedTable: 'users',
referencedColumn: 'id',
updateAction: 'NO ACTION',
deleteAction: 'RESTRICT',
});
expect(
extractForeignKeyRelation(
'table_id_fkey',
'FOREIGN KEY (user_id) REFERENCES users(id)',
),
).toMatchObject({
name: 'table_id_fkey',
columnName: 'user_id',
referencedSchema: null,
referencedTable: 'users',
referencedColumn: 'id',
updateAction: 'NO ACTION',
deleteAction: 'NO ACTION',
});
});
});

View File

@@ -42,7 +42,7 @@ export default function extractForeignKeyRelation(
return {
name,
columnName: columnName.replace(/(^\(|\)$)/gi, ''),
columnName: columnName.replace(/(^\(|\)$)/gi, '').replaceAll('"', ''),
referencedSchema,
referencedTable,
referencedColumn: referencedColumn.replace(/(^\(|\)$)/gi, ''),