Fix suggested object relationships are being tracked as array

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/9145
Co-authored-by: Vijay Prasanna <11921040+vijayprasanna13@users.noreply.github.com>
GitOrigin-RevId: f4f404ce7fab461ad62138ac7115e95a62bdbb13
This commit is contained in:
Luca Restagno 2023-05-15 17:32:01 +02:00 committed by hasura-bot
parent ca0f007bc3
commit d0acdcf414
8 changed files with 272 additions and 99 deletions

View File

@ -13,10 +13,7 @@ import { SuggestedRelationshipWithName } from '../../../DatabaseRelationships/co
import { RelationshipRow } from './RelationshipRow';
import { SuggestedRelationshipTrackModal } from '../../../DatabaseRelationships/components/SuggestedRelationshipTrackModal/SuggestedRelationshipTrackModal';
import Skeleton from 'react-loading-skeleton';
import {
AddSuggestedRelationship,
useAllSuggestedRelationships,
} from '../../../DatabaseRelationships/components/SuggestedRelationships/hooks/useAllSuggestedRelationships';
import { useAllSuggestedRelationships } from '../../../DatabaseRelationships/components/SuggestedRelationships/hooks/useAllSuggestedRelationships';
import { useCheckRows } from '../../../DatabaseRelationships/hooks/useCheckRows';
import { useTrackedRelationships } from './hooks/useTrackedRelationships';
@ -24,20 +21,6 @@ interface UntrackedRelationshipsProps {
dataSourceName: string;
}
const adaptTrackRelationship = (
relationship: SuggestedRelationshipWithName
): AddSuggestedRelationship => {
const isObjectRelationship = !!relationship.from?.constraint_name;
return {
name: relationship.constraintName,
fromColumnNames: relationship.from.columns,
toColumnNames: relationship.to.columns,
relationshipType: isObjectRelationship ? 'object' : 'array',
toTable: isObjectRelationship ? undefined : relationship.to.table,
fromTable: relationship.from.table,
};
};
export const UntrackedRelationships: React.VFC<UntrackedRelationshipsProps> = ({
dataSourceName,
}) => {
@ -98,9 +81,7 @@ export const UntrackedRelationships: React.VFC<UntrackedRelationshipsProps> = ({
const onTrackRelationship = async (
relationship: SuggestedRelationshipWithName
) => {
await onAddMultipleSuggestedRelationships([
adaptTrackRelationship(relationship),
]);
await onAddMultipleSuggestedRelationships([relationship]);
};
const [isTrackingSelectedRelationships, setTrackingSelectedRelationships] =
@ -112,10 +93,7 @@ export const UntrackedRelationships: React.VFC<UntrackedRelationshipsProps> = ({
checkedIds.includes(rel.constraintName)
);
const trackRelationships: AddSuggestedRelationship[] =
selectedRelationships.map(adaptTrackRelationship);
await onAddMultipleSuggestedRelationships(trackRelationships);
await onAddMultipleSuggestedRelationships(selectedRelationships);
} catch (err) {
setTrackingSelectedRelationships(false);
}

View File

@ -0,0 +1,104 @@
import { SuggestedRelationshipWithName } from '../../../../DatabaseRelationships/components/SuggestedRelationships/hooks/useSuggestedRelationships';
import adaptTrackRelationship from './adaptTrackRelationship';
describe('adaptTrackRelationship', () => {
it('returns the object relationship with constrain on the from table', () => {
const objectRelationship: SuggestedRelationshipWithName = {
type: 'object',
from: {
table: ['Album'],
columns: ['artistId'],
constraint_name: 'Album_artistId',
},
to: {
table: ['Artist'],
columns: ['id'],
},
constraintName: 'Album_artistId',
};
expect(adaptTrackRelationship(objectRelationship)).toEqual({
fromColumnNames: ['artistId'],
toColumnNames: ['id'],
fromTable: ['Album'],
name: 'Album_artistId',
relationshipType: 'object',
toTable: ['Artist'],
constraintOn: 'fromTable',
});
});
it('returns the object relationship with constrain on the to table', () => {
const objectRelationship: SuggestedRelationshipWithName = {
type: 'object',
from: {
table: ['Album'],
columns: ['artistId'],
},
to: {
table: ['Artist'],
columns: ['id'],
constraint_name: 'Album_artistId',
},
constraintName: 'Album_artistId',
};
expect(adaptTrackRelationship(objectRelationship)).toEqual({
fromColumnNames: ['artistId'],
toColumnNames: ['id'],
fromTable: ['Album'],
name: 'Album_artistId',
relationshipType: 'object',
toTable: ['Artist'],
constraintOn: 'toTable',
});
});
it('returns the array relationship with constrain on the from table', () => {
const objectRelationship: SuggestedRelationshipWithName = {
type: 'array',
from: {
table: ['Album'],
columns: ['artistId'],
constraint_name: 'Album_artistId',
},
to: {
table: ['Artist'],
columns: ['id'],
},
constraintName: 'Album_artistId',
};
expect(adaptTrackRelationship(objectRelationship)).toEqual({
fromColumnNames: ['artistId'],
toColumnNames: ['id'],
fromTable: ['Album'],
name: 'Album_artistId',
relationshipType: 'array',
toTable: ['Artist'],
constraintOn: 'fromTable',
});
});
it('returns the array relationship with constrain on the to table', () => {
const objectRelationship: SuggestedRelationshipWithName = {
type: 'array',
from: {
table: ['Album'],
columns: ['artistId'],
},
to: {
table: ['Artist'],
columns: ['id'],
constraint_name: 'Album_artistId',
},
constraintName: 'Album_artistId',
};
expect(adaptTrackRelationship(objectRelationship)).toEqual({
fromColumnNames: ['artistId'],
toColumnNames: ['id'],
fromTable: ['Album'],
name: 'Album_artistId',
relationshipType: 'array',
toTable: ['Artist'],
constraintOn: 'toTable',
});
});
});

View File

@ -0,0 +1,20 @@
import { AddSuggestedRelationship } from '../../../../DatabaseRelationships/components/SuggestedRelationships/hooks/useAllSuggestedRelationships';
import { SuggestedRelationshipWithName } from '../../../../DatabaseRelationships/components/SuggestedRelationships/hooks/useSuggestedRelationships';
const adaptTrackRelationship = (
relationship: SuggestedRelationshipWithName
): AddSuggestedRelationship => {
const isConstraintOnFromTable = !!relationship.from?.constraint_name;
const isObjectRelationship = relationship.type === 'object';
return {
name: relationship.constraintName,
fromColumnNames: relationship.from.columns,
toColumnNames: relationship.to.columns,
relationshipType: isObjectRelationship ? 'object' : 'array',
toTable: relationship.to.table,
fromTable: relationship.from.table,
constraintOn: isConstraintOnFromTable ? 'fromTable' : 'toTable',
};
};
export default adaptTrackRelationship;

View File

@ -33,14 +33,9 @@ export const SuggestedRelationshipTrackModal: React.VFC<
const onTrackRelationship = async (relationshipName: string) => {
try {
const isObjectRelationship = !!relationship.from?.constraint_name;
await onAddSuggestedRelationship({
name: relationshipName,
toColumnNames: relationship.to.columns,
fromColumnNames: relationship.from.columns,
relationshipType: isObjectRelationship ? 'object' : 'array',
toTable: isObjectRelationship ? undefined : relationship.to.table,
...relationship,
constraintName: relationshipName,
});
refetchSuggestedRelationships();

View File

@ -0,0 +1,59 @@
import { getLocalRelationshipPayload } from './getLocalRelationshipPayload';
describe('getLocalRelationshipPayload', () => {
it('returns an object relationship request payload with constrain on the fromTable', () => {
expect(
getLocalRelationshipPayload({
dataSourcePrefix: 'pg',
dataSourceName: 'aPostgres',
relationship: {
fromColumnNames: ['artistId'],
toColumnNames: ['id'],
fromTable: ['Album'],
name: 'Album_artistId',
relationshipType: 'object',
toTable: ['Artist'],
constraintOn: 'fromTable',
},
})
).toEqual({
type: 'pg_create_object_relationship',
args: {
name: 'Album_artistId',
source: 'aPostgres',
table: ['Album'],
using: {
foreign_key_constraint_on: ['artistId'],
},
},
});
});
it('returns an array relationship request payload with constrain on the toTable', () => {
expect(
getLocalRelationshipPayload({
dataSourcePrefix: 'pg',
dataSourceName: 'aPostgres',
relationship: {
fromColumnNames: ['artistId'],
toColumnNames: ['id'],
fromTable: ['Album'],
name: 'Album_artistId',
relationshipType: 'array',
toTable: ['Artist'],
constraintOn: 'toTable',
},
})
).toEqual({
type: 'pg_create_array_relationship',
args: {
name: 'Album_artistId',
source: 'aPostgres',
table: ['Album'],
using: {
foreign_key_constraint_on: { columns: ['id'], table: ['Artist'] },
},
},
});
});
});

View File

@ -0,0 +1,41 @@
import { TMigration } from '../../../../MetadataAPI';
import { AddSuggestedRelationship } from '../hooks/useAllSuggestedRelationships';
type GetRelationshipPayloadArgs = {
dataSourcePrefix: string;
dataSourceName: string;
relationship: AddSuggestedRelationship;
};
export type LocalRelationshipQuery = {
table: unknown;
name: string;
source: string;
using: {
foreign_key_constraint_on: string[] | { table: unknown; columns: string[] };
};
};
export const getLocalRelationshipPayload = ({
dataSourcePrefix,
dataSourceName,
relationship,
}: GetRelationshipPayloadArgs): TMigration<LocalRelationshipQuery>['query'] => {
return {
type: `${dataSourcePrefix}_create_${relationship.relationshipType}_relationship`,
args: {
table: relationship.fromTable,
name: relationship.name,
source: dataSourceName,
using: {
foreign_key_constraint_on:
relationship.constraintOn === 'fromTable'
? relationship.fromColumnNames
: {
table: relationship.toTable,
columns: relationship.toColumnNames,
},
},
},
};
};

View File

@ -8,8 +8,12 @@ import { useHttpClient } from '../../../../Network';
import {
addConstraintName,
SuggestedRelationshipsResponse,
SuggestedRelationshipWithName,
} from './useSuggestedRelationships';
import { useMetadataMigration } from '../../../../MetadataAPI';
import {
allowedMetadataTypes,
useMetadataMigration,
} from '../../../../MetadataAPI';
import {
BulkKeepGoingResponse,
NamingConvention,
@ -18,23 +22,19 @@ import {
import { getTrackedRelationshipsCacheKey } from '../../../../Data/TrackResources/components/hooks/useTrackedRelationships';
import { hasuraToast } from '../../../../../new-components/Toasts';
import { useDriverRelationshipSupport } from '../../../../Data/hooks/useDriverRelationshipSupport';
import adaptTrackRelationship from '../../../../Data/TrackResources/components/utils/adaptTrackRelationship';
import {
getLocalRelationshipPayload,
LocalRelationshipQuery,
} from '../adapters/getLocalRelationshipPayload';
type QueriesType =
| {
type: string;
args: {
table: unknown;
name: string;
source: string;
using: {
foreign_key_constraint_on:
| string[]
| { table: unknown; columns: string[] };
};
};
type: allowedMetadataTypes;
args: LocalRelationshipQuery;
}[]
| {
type: string;
type: allowedMetadataTypes;
args: {
table: unknown;
name: string;
@ -57,6 +57,7 @@ export type AddSuggestedRelationship = {
relationshipType: 'object' | 'array';
toTable?: Table;
fromTable?: Table;
constraintOn: 'fromTable' | 'toTable';
};
type UseSuggestedRelationshipsArgs = {
@ -135,8 +136,9 @@ export const useAllSuggestedRelationships = ({
const queryClient = useQueryClient();
const onAddMultipleSuggestedRelationships = async (
relationships: AddSuggestedRelationship[]
suggestedRelationships: SuggestedRelationshipWithName[]
) => {
const relationships = suggestedRelationships.map(adaptTrackRelationship);
let queries: QueriesType = [];
if (!driverSupportsLocalRelationship && !driverSupportsRemoteRelationship) {
@ -148,25 +150,13 @@ export const useAllSuggestedRelationships = ({
return;
}
if (driverSupportsLocalRelationship) {
queries = relationships.map(relationship => {
return {
type: `${dataSourcePrefix}_create_${relationship.relationshipType}_relationship`,
args: {
table: relationship.fromTable,
name: relationship.name,
source: dataSourceName,
using: {
foreign_key_constraint_on:
relationship.relationshipType === 'object'
? relationship.fromColumnNames
: {
table: relationship.toTable,
columns: relationship.toColumnNames,
},
},
},
};
});
queries = relationships.map(relationship =>
getLocalRelationshipPayload({
dataSourcePrefix: dataSourcePrefix || '',
dataSourceName,
relationship,
})
);
} else if (driverSupportsRemoteRelationship) {
queries = relationships.map(relationship => {
return {

View File

@ -17,6 +17,8 @@ import { generateQueryKeys } from '../../../utils/queryClientUtils';
import { useMetadataMigration } from '../../../../MetadataAPI';
import { useDriverRelationshipSupport } from '../../../../Data/hooks/useDriverRelationshipSupport';
import { hasuraToast } from '../../../../../new-components/Toasts/hasuraToast';
import adaptTrackRelationship from '../../../../Data/TrackResources/components/utils/adaptTrackRelationship';
import { getLocalRelationshipPayload } from '../adapters/getLocalRelationshipPayload';
type UseSuggestedRelationshipsArgs = {
dataSourceName: string;
@ -147,15 +149,6 @@ export const removeExistingRelationships = ({
return false;
});
type AddSuggestedRelationship = {
name: string;
fromColumnNames: string[];
toColumnNames: string[];
relationshipType: 'object' | 'array';
toTable?: Table;
fromTable?: Table;
};
export const getSuggestedRelationshipsCacheQuery = (
dataSourceName: string,
table: Table
@ -218,14 +211,11 @@ export const useSuggestedRelationships = ({
const [isAddingSuggestedRelationship, setAddingSuggestedRelationship] =
useState(false);
const onAddSuggestedRelationship = async ({
name,
fromColumnNames,
toColumnNames,
relationshipType,
toTable,
fromTable,
}: AddSuggestedRelationship) => {
const onAddSuggestedRelationship = async (
relationship: SuggestedRelationshipWithName
) => {
const addRelationship = adaptTrackRelationship(relationship);
setAddingSuggestedRelationship(true);
if (!driverSupportsLocalRelationship && !driverSupportsRemoteRelationship) {
@ -239,25 +229,21 @@ export const useSuggestedRelationships = ({
if (driverSupportsLocalRelationship) {
await metadataMutation.mutateAsync({
query: {
type: `${dataSourcePrefix}_create_${relationshipType}_relationship`,
args: {
table: fromTable || table,
name,
source: dataSourceName,
using: {
foreign_key_constraint_on:
relationshipType === 'object'
? fromColumnNames
: {
table: toTable,
columns: toColumnNames,
},
},
},
},
query: getLocalRelationshipPayload({
dataSourcePrefix: dataSourcePrefix || '',
dataSourceName,
relationship: addRelationship,
}),
});
} else if (driverSupportsRemoteRelationship) {
const {
fromTable,
name,
relationshipType,
fromColumnNames,
toColumnNames,
} = addRelationship;
await metadataMutation.mutateAsync({
query: {
type: `${dataSourcePrefix}_create_remote_relationship`,