mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-14 17:02:49 +03:00
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:
parent
cee83014e1
commit
4dec50ba5b
@ -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', () => {
|
||||
|
@ -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
|
||||
);
|
||||
|
||||
|
@ -31,6 +31,9 @@ export type LocalRelationship = BasicRelationshipDetails & {
|
||||
relationshipType: 'Array' | 'Object';
|
||||
definition: {
|
||||
toTable: Table;
|
||||
toColumns?: string[];
|
||||
fromTable?: Table;
|
||||
fromColumns?: string[];
|
||||
mapping: Record<string, string>;
|
||||
};
|
||||
};
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
@ -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 = ({
|
||||
|
@ -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 => {
|
||||
|
@ -75,7 +75,7 @@ export type LocalTableObjectRelationship = BaseLocalRelationshipProps & {
|
||||
table: Table;
|
||||
} & (
|
||||
| {
|
||||
// Recommened type to use
|
||||
// Recommended type to use
|
||||
columns: string[];
|
||||
}
|
||||
| {
|
||||
|
Loading…
Reference in New Issue
Block a user