Relationship new UI, array relationships shown with the wrong details

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/9315
Co-authored-by: Vijay Prasanna <11921040+vijayprasanna13@users.noreply.github.com>
GitOrigin-RevId: cb61006a21ca0ef17033d68d2769a9d150ba409d
This commit is contained in:
Luca Restagno 2023-05-26 17:48:59 +02:00 committed by hasura-bot
parent cee83014e1
commit 4dec50ba5b
7 changed files with 339 additions and 144 deletions

View File

@ -2,9 +2,8 @@ import { Table } from '../../../../hasura-metadata-types';
import {
addConstraintName,
filterTableRelationships,
removeExistingRelationships,
} from './useSuggestedRelationships';
import { LocalRelationship, SuggestedRelationship } from '../../../types';
import { SuggestedRelationship } from '../../../types';
describe('filterTableRelationships', () => {
it('filters relationships', () => {
@ -40,69 +39,6 @@ describe('filterTableRelationships', () => {
});
});
describe('removeExistingRelationships', () => {
it('removes existing relationships', () => {
const relationships: SuggestedRelationship[] = [
{
type: 'object',
from: {
table: ['Album'],
columns: ['ArtistId'],
},
to: {
table: ['Artist'],
columns: ['ArtistId'],
},
},
{
type: 'object',
from: {
table: ['Genre'],
columns: ['GenreId'],
},
to: {
table: ['Artist'],
columns: ['GenreId'],
},
},
];
const existingRelationships: LocalRelationship[] = [
{
type: 'localRelationship',
relationshipType: 'Object',
definition: {
toTable: ['Artist'],
mapping: {
GenreId: 'GenreId',
},
},
fromSource: 'dataSource',
fromTable: ['Genre'],
name: 'aName',
},
];
expect(
removeExistingRelationships({
relationships,
existingRelationships,
})
).toEqual([
{
type: 'object',
from: {
table: ['Album'],
columns: ['ArtistId'],
},
to: {
table: ['Artist'],
columns: ['ArtistId'],
},
},
]);
});
});
describe('addConstraintName', () => {
describe('when the naming convention is hasura-default', () => {
it('adds the constraint name', () => {

View File

@ -1,7 +1,6 @@
import { useEffect, useState } from 'react';
import inflection from 'inflection';
import camelCase from 'lodash/camelCase';
import { isEqual } from '../../../../../components/Common/utils/jsUtils';
import { LocalRelationship, SuggestedRelationship } from '../../../types';
import { getTableDisplayName } from '../../../utils/helpers';
import { getDriverPrefix, runMetadataQuery } from '../../../../DataSource';
@ -96,59 +95,6 @@ export const addConstraintName = (
};
});
type RemoveExistingRelationshipsArgs = {
relationships: SuggestedRelationship[];
existingRelationships: LocalRelationship[];
};
export const removeExistingRelationships = ({
relationships,
existingRelationships,
}: RemoveExistingRelationshipsArgs) =>
relationships.filter(relationship => {
const fromTable = relationship.from.table;
const fromTableExists = existingRelationships.find(rel =>
areTablesEqual(rel.fromTable, fromTable)
);
if (!fromTableExists) {
return true;
}
const existingRelationshipsFromSameTable = existingRelationships.filter(
rel => areTablesEqual(rel.fromTable, fromTable)
);
const toTable = relationship.to.table;
const toTableExists = existingRelationshipsFromSameTable.find(rel =>
areTablesEqual(rel.definition.toTable, toTable)
);
if (!toTableExists) {
return true;
}
const existingRelationshipsFromAndToSameTable =
existingRelationshipsFromSameTable.filter(rel =>
areTablesEqual(rel.definition.toTable, toTable)
);
const existingRelationshipsFromAndToSameTableAndSameFromColumns =
existingRelationshipsFromAndToSameTable.filter(rel => {
const existingToColumns = Object.values(rel.definition.mapping).sort();
const relationshipToColumns = relationship.to.columns.sort();
return isEqual(existingToColumns, relationshipToColumns);
});
if (!existingRelationshipsFromAndToSameTableAndSameFromColumns) {
return true;
}
return false;
});
export const getSuggestedRelationshipsCacheQuery = (
dataSourceName: string,
table: Table
@ -293,6 +239,16 @@ export const useSuggestedRelationships = ({
const suggestedRelationships = data?.relationships || [];
/**
* This is needed because the suggested_relationships metadata API returns Foreign Keys
*
* from current table -> to other table
* but also
*
* from other table -> to current table
*
* After the tracking, the second type of Foreign Keys would not be shown in the current table UI
*/
const tableFilteredRelationships = table
? filterTableRelationships({
table,
@ -300,14 +256,8 @@ export const useSuggestedRelationships = ({
})
: suggestedRelationships;
// TODO: remove when the metadata request will correctly omit already tracked relationships
const notExistingRelationships = removeExistingRelationships({
relationships: tableFilteredRelationships,
existingRelationships,
});
const relationshipsWithConstraintName = addConstraintName(
notExistingRelationships,
tableFilteredRelationships,
namingConvention
);

View File

@ -31,6 +31,9 @@ export type LocalRelationship = BasicRelationshipDetails & {
relationshipType: 'Array' | 'Object';
definition: {
toTable: Table;
toColumns?: string[];
fromTable?: Table;
fromColumns?: string[];
mapping: Record<string, string>;
};
};

View File

@ -0,0 +1,256 @@
import {
LocalTableArrayRelationship,
SameTableObjectRelationship,
Table,
} from '../../hasura-metadata-types';
import { LocalRelationship, SuggestedRelationship } from '../types';
import {
adaptLocalArrayRelationshipWithFkConstraint,
adaptLocalObjectRelationshipWithFkConstraint,
} from './adaptResponse';
describe('adaptLocalArrayRelationshipWithFkConstraint', () => {
it('returns the correct array relationships', () => {
const table: Table = { name: 'Album', schema: 'public' };
const dataSourceName = 'chinook';
const relationship: LocalTableArrayRelationship = {
name: 'Album_Tracks',
using: {
foreign_key_constraint_on: {
column: 'AlbumId',
table: {
name: 'Track',
schema: 'public',
},
},
},
};
const suggestedRelationships: SuggestedRelationship[] = [
{
type: 'object',
from: {
table: {
schema: 'public',
name: 'Album',
},
columns: ['ArtistId'],
constraint_name: 'FK_AlbumArtistId',
},
to: {
table: {
schema: 'public',
name: 'Artist',
},
columns: ['ArtistId'],
},
},
{
type: 'array',
from: {
table: {
schema: 'public',
name: 'Artist',
},
columns: ['ArtistId'],
},
to: {
table: {
schema: 'public',
name: 'Album',
},
columns: ['ArtistId'],
constraint_name: 'FK_AlbumArtistId',
},
},
{
type: 'object',
from: {
table: {
schema: 'public',
name: 'Track',
},
columns: ['AlbumId'],
constraint_name: 'FK_TrackAlbumId',
},
to: {
table: {
schema: 'public',
name: 'Album',
},
columns: ['AlbumId'],
},
},
{
type: 'array',
from: {
table: {
schema: 'public',
name: 'Album',
},
columns: ['AlbumId'],
},
to: {
table: {
schema: 'public',
name: 'Track',
},
columns: ['AlbumId'],
constraint_name: 'FK_TrackAlbumId',
},
},
];
const expected: LocalRelationship = {
name: 'Album_Tracks',
fromSource: 'chinook',
fromTable: { name: 'Album', schema: 'public' },
relationshipType: 'Array',
type: 'localRelationship',
definition: {
toTable: { name: 'Track', schema: 'public' },
toColumns: ['AlbumId'],
fromTable: { name: 'Album', schema: 'public' },
fromColumns: ['AlbumId'],
mapping: { AlbumId: 'AlbumId' },
},
};
const result = adaptLocalArrayRelationshipWithFkConstraint({
table,
dataSourceName,
relationship,
suggestedRelationships,
});
console.log(result);
expect(result).toEqual(expected);
});
});
describe('adaptLocalObjectRelationshipWithFkConstraint', () => {
it('returns the correct object relationships', () => {
const table: Table = {
name: 'Album',
schema: 'public',
};
const dataSourceName = 'chinook';
const suggestedRelationships: SuggestedRelationship[] = [
{
type: 'object',
from: {
table: {
schema: 'public',
name: 'Album',
},
columns: ['ArtistId'],
constraint_name: 'FK_AlbumArtistId',
},
to: {
table: {
schema: 'public',
name: 'Artist',
},
columns: ['ArtistId'],
},
},
{
type: 'array',
from: {
table: {
schema: 'public',
name: 'Artist',
},
columns: ['ArtistId'],
},
to: {
table: {
schema: 'public',
name: 'Album',
},
columns: ['ArtistId'],
constraint_name: 'FK_AlbumArtistId',
},
},
{
type: 'object',
from: {
table: {
schema: 'public',
name: 'Track',
},
columns: ['AlbumId'],
constraint_name: 'FK_TrackAlbumId',
},
to: {
table: {
schema: 'public',
name: 'Album',
},
columns: ['AlbumId'],
},
},
{
type: 'array',
from: {
table: {
schema: 'public',
name: 'Album',
},
columns: ['AlbumId'],
},
to: {
table: {
schema: 'public',
name: 'Track',
},
columns: ['AlbumId'],
constraint_name: 'FK_TrackAlbumId',
},
},
];
const relationship: SameTableObjectRelationship = {
name: 'Album_Artist',
using: {
foreign_key_constraint_on: 'ArtistId',
},
};
const expected: LocalRelationship = {
name: 'Album_Artist',
fromSource: 'chinook',
fromTable: {
name: 'Album',
schema: 'public',
},
relationshipType: 'Object',
type: 'localRelationship',
definition: {
toTable: {
schema: 'public',
name: 'Artist',
},
toColumns: ['ArtistId'],
fromTable: {
schema: 'public',
name: 'Album',
},
fromColumns: ['ArtistId'],
mapping: {
ArtistId: 'ArtistId',
},
},
};
expect(
adaptLocalObjectRelationshipWithFkConstraint({
table,
dataSourceName,
relationship,
suggestedRelationships,
})
).toEqual(expected);
});
});

View File

@ -27,6 +27,14 @@ const getKeyValuePair = (arr1: string[], arr2: string[]) => {
return result;
};
type FkDefinition = {
fromColumns?: string[];
fromTable: Table;
toColumns?: string[];
toTable: Table;
mapping: Record<string, string>;
};
const getFkDefinition = (
table: Table,
relationship:
@ -34,7 +42,7 @@ const getFkDefinition = (
| LocalTableObjectRelationship
| LocalTableArrayRelationship,
suggestedRelationships: SuggestedRelationship[]
): { toTable: Table; mapping: Record<string, string> } => {
): FkDefinition => {
if (isSameTableObjectRelationship(relationship)) {
const fromTable = table;
const fromColumns = Array.isArray(
@ -51,6 +59,9 @@ const getFkDefinition = (
return {
toTable: matchingFkConstraint?.to.table,
toColumns: matchingFkConstraint?.to.columns,
fromTable: matchingFkConstraint?.from.table,
fromColumns: matchingFkConstraint?.from.columns,
mapping: matchingFkConstraint
? getKeyValuePair(
matchingFkConstraint.from.columns,
@ -60,20 +71,36 @@ const getFkDefinition = (
};
}
const toTable = table;
const toColumn =
'column' in relationship.using.foreign_key_constraint_on
? [relationship.using.foreign_key_constraint_on.column]
: relationship.using.foreign_key_constraint_on.columns;
const matchingFkConstraint = suggestedRelationships.find(
suggestedRelationship =>
areTablesEqual(toTable, suggestedRelationship.to.table) &&
isEqual(toColumn.sort(), suggestedRelationship.to.columns)
suggestedRelationship => {
const sameFromTable = areTablesEqual(
table,
suggestedRelationship.from.table
);
const sameToColumns = isEqual(
toColumn.sort(),
suggestedRelationship.from.columns
);
const sameToTable = areTablesEqual(
relationship.using.foreign_key_constraint_on.table,
suggestedRelationship.to.table
);
return sameFromTable && sameToColumns && sameToTable;
}
);
return {
toTable: matchingFkConstraint?.from.table,
toTable: matchingFkConstraint?.to.table,
toColumns: matchingFkConstraint?.to.columns,
fromTable: matchingFkConstraint?.from.table,
fromColumns: matchingFkConstraint?.from.columns,
mapping: matchingFkConstraint
? getKeyValuePair(
matchingFkConstraint.to.columns,
@ -116,14 +143,22 @@ export const adaptLocalObjectRelationshipWithFkConstraint = ({
relationship: SameTableObjectRelationship | LocalTableObjectRelationship;
suggestedRelationships: SuggestedRelationship[];
}): LocalRelationship => {
return {
const fkDefinition = getFkDefinition(
table,
relationship,
suggestedRelationships
);
const localRelationship: LocalRelationship = {
name: relationship.name,
fromSource: dataSourceName,
fromTable: table,
relationshipType: 'Object',
type: 'localRelationship',
definition: getFkDefinition(table, relationship, suggestedRelationships),
definition: fkDefinition,
};
return localRelationship;
};
export const adaptLocalArrayRelationshipWithManualConfiguration = ({
@ -159,14 +194,22 @@ export const adaptLocalArrayRelationshipWithFkConstraint = ({
relationship: LocalTableArrayRelationship;
suggestedRelationships: SuggestedRelationship[];
}): LocalRelationship => {
return {
const fkDefinition = getFkDefinition(
table,
relationship,
suggestedRelationships
);
const localRelationship: LocalRelationship = {
name: relationship.name,
fromSource: dataSourceName,
fromTable: table,
fromTable: fkDefinition.fromTable,
relationshipType: 'Array',
type: 'localRelationship',
definition: getFkDefinition(table, relationship, suggestedRelationships),
};
return localRelationship;
};
export const adaptRemoteSchemaRelationship = ({

View File

@ -5,12 +5,13 @@ import {
isRemoteSchemaRelationship,
} from '../../DataSource';
import { MetadataTable } from '../../hasura-metadata-types';
import { SuggestedRelationshipWithName } from '../components/SuggestedRelationships/hooks/useSuggestedRelationships';
import {
LocalRelationship,
Relationship,
RemoteDatabaseRelationship,
RemoteSchemaRelationship,
SuggestedRelationship,
// SuggestedRelationship,
} from '../types';
import {
adaptLegacyRemoteSchemaRelationship,
@ -25,7 +26,7 @@ import {
export const getTableLocalRelationships = (
metadataTable: MetadataTable | undefined,
dataSourceName: string,
suggestedRelationships: SuggestedRelationship[]
suggestedRelationships: SuggestedRelationshipWithName[]
) => {
const table = metadataTable?.table;
// adapt local array relationships
@ -39,15 +40,17 @@ export const getTableLocalRelationships = (
relationship,
});
const arraySuggestedRelationship = suggestedRelationships.filter(
rel => rel.type === 'array'
);
return adaptLocalArrayRelationshipWithFkConstraint({
table,
dataSourceName,
relationship,
suggestedRelationships,
suggestedRelationships: arraySuggestedRelationship,
});
});
// adapt local object relationships
const localObjectRelationships = (
metadataTable?.object_relationships ?? []
).map<LocalRelationship>(relationship => {
@ -57,11 +60,16 @@ export const getTableLocalRelationships = (
dataSourceName,
relationship,
});
const objectSuggestedRelationship = suggestedRelationships.filter(
rel => rel.type === 'object'
);
return adaptLocalObjectRelationshipWithFkConstraint({
table,
dataSourceName,
relationship,
suggestedRelationships,
suggestedRelationships: objectSuggestedRelationship,
});
});
@ -71,7 +79,7 @@ export const getTableLocalRelationships = (
export const getAllTableRelationships = (
metadataTable: MetadataTable | undefined,
dataSourceName: string,
suggestedRelationships: SuggestedRelationship[]
suggestedRelationships: SuggestedRelationshipWithName[]
): Relationship[] => {
const table = metadataTable?.table;
// adapt local array relationships
@ -80,7 +88,6 @@ export const getAllTableRelationships = (
dataSourceName,
suggestedRelationships
);
const remoteRelationships = (metadataTable?.remote_relationships ?? []).map<
RemoteSchemaRelationship | RemoteDatabaseRelationship
>(relationship => {

View File

@ -75,7 +75,7 @@ export type LocalTableObjectRelationship = BaseLocalRelationshipProps & {
table: Table;
} & (
| {
// Recommened type to use
// Recommended type to use
columns: string[];
}
| {