mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-15 01:12:56 +03:00
console: integrate useCreateTableRelationships
and useSuggestedRelationships
hooks in the relationships tab
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/9939 GitOrigin-RevId: 71391115fe7403663c53dfbc0bec6e7b62019b50
This commit is contained in:
parent
4d86cd3a62
commit
1f11bd388f
frontend/libs/console/legacy-ce/src/lib/features
BrowseRows
Data/TrackResources
TrackRelationships
components/hooks
DatabaseRelationships
DatabaseRelationships.stories.tsxDatabaseRelationships.tsx
components
RenameRelationship
RenderWidget/parts
SuggestedRelationshipTrackModal
SuggestedRelationships
hooks
@ -58,7 +58,7 @@ export const BasicDisplayTest: StoryObj<typeof BrowseRows> = {
|
||||
const albumRows = await canvas.findAllByTestId(/^@table-row-.*$/);
|
||||
expect(albumRows.length).toBe(10);
|
||||
},
|
||||
{ timeout: 5000 }
|
||||
{ timeout: 10000 }
|
||||
);
|
||||
|
||||
/**
|
||||
|
@ -38,12 +38,20 @@ export const Testing: StoryObj<typeof DataGrid> = {
|
||||
|
||||
await waitFor(
|
||||
async () => {
|
||||
await userEvent.click(await canvas.findByTestId('@nextPageBtn'));
|
||||
await canvas.findAllByTestId(/^@table-cell-0-.*$/);
|
||||
},
|
||||
{ timeout: 10000 }
|
||||
);
|
||||
|
||||
await userEvent.click(await canvas.findByTestId('@nextPageBtn'));
|
||||
|
||||
await waitFor(
|
||||
async () => {
|
||||
const firstRow = await canvas.findAllByTestId(/^@table-cell-0-.*$/);
|
||||
expect(firstRow.length).toBe(5);
|
||||
expect(firstRow[0]).toHaveTextContent('11'); // AlbumId
|
||||
},
|
||||
{ timeout: 5000 }
|
||||
{ timeout: 10000 }
|
||||
);
|
||||
|
||||
const firstRow = await canvas.findAllByTestId(/^@table-cell-0-.*$/);
|
||||
|
@ -12,15 +12,12 @@ export const ManageSuggestedRelationships = ({
|
||||
}: ManageDatabaseProps) => {
|
||||
const [tab, setTab] = React.useState<TabState>('untracked');
|
||||
|
||||
const {
|
||||
data: { tracked = [], untracked = [] } = {},
|
||||
isLoading,
|
||||
invalidateQuery,
|
||||
} = useSuggestedRelationships({
|
||||
dataSourceName,
|
||||
which: 'all',
|
||||
schema,
|
||||
});
|
||||
const { data: { tracked = [], untracked = [] } = {}, isLoading } =
|
||||
useSuggestedRelationships({
|
||||
dataSourceName,
|
||||
which: 'all',
|
||||
schema,
|
||||
});
|
||||
|
||||
return (
|
||||
<TrackableResourceTabs
|
||||
@ -36,9 +33,6 @@ export const ManageSuggestedRelationships = ({
|
||||
<TrackedSuggestedRelationships
|
||||
dataSourceName={dataSourceName}
|
||||
trackedRelationships={tracked}
|
||||
onChange={() => {
|
||||
invalidateQuery();
|
||||
}}
|
||||
/>
|
||||
),
|
||||
},
|
||||
@ -48,9 +42,6 @@ export const ManageSuggestedRelationships = ({
|
||||
<UntrackedRelationships
|
||||
untrackedRelationships={untracked}
|
||||
dataSourceName={dataSourceName}
|
||||
onTrack={() => {
|
||||
invalidateQuery();
|
||||
}}
|
||||
/>
|
||||
),
|
||||
},
|
||||
|
@ -81,10 +81,12 @@ export const useSuggestedRelationships = ({
|
||||
}) => {
|
||||
const httpClient = useHttpClient();
|
||||
|
||||
const { data: { source, fkRels = [] } = {} } = useMetadata(m => ({
|
||||
fkRels: MetadataSelectors.getForeignKeyRelationships(dataSourceName)(m),
|
||||
source: MetadataSelectors.findSource(dataSourceName)(m),
|
||||
}));
|
||||
const { data: { source, fkRels = [] } = {}, isFetching } = useMetadata(m => {
|
||||
return {
|
||||
fkRels: MetadataSelectors.getForeignKeyRelationships(dataSourceName)(m),
|
||||
source: MetadataSelectors.findSource(dataSourceName)(m),
|
||||
};
|
||||
});
|
||||
|
||||
const selector = useCallback(
|
||||
(data: QueryReturnType) => {
|
||||
@ -125,6 +127,7 @@ export const useSuggestedRelationships = ({
|
||||
const query = useConsoleQuery<QueryReturnType, SelectReturnType>({
|
||||
queryKey: [dataSourceName, QUERY_KEY],
|
||||
select: selector,
|
||||
enabled: !isFetching,
|
||||
queryFn: async () => {
|
||||
if (!source)
|
||||
throw Error(`Unable to find source, "${dataSourceName}" in metadata`);
|
||||
|
@ -1,60 +0,0 @@
|
||||
import { useQuery } from 'react-query';
|
||||
import { useAllSuggestedRelationships } from '../../../../DatabaseRelationships/components/SuggestedRelationships/hooks/useAllSuggestedRelationships';
|
||||
import { getTableLocalRelationships } from '../../../../DatabaseRelationships/utils/tableRelationships';
|
||||
import {
|
||||
MetadataSelectors,
|
||||
useMetadata,
|
||||
} from '../../../../hasura-metadata-api';
|
||||
|
||||
export const getTrackedRelationshipsCacheKey = (dataSourceName: string) => [
|
||||
'tracked_relationships',
|
||||
dataSourceName,
|
||||
];
|
||||
|
||||
export const useTrackedRelationships = (dataSourceName: string) => {
|
||||
const { suggestedRelationships } = useAllSuggestedRelationships({
|
||||
dataSourceName,
|
||||
isEnabled: true,
|
||||
omitTracked: false,
|
||||
});
|
||||
const { data: currentMetadataSource } = useMetadata(
|
||||
MetadataSelectors.findSource(dataSourceName)
|
||||
);
|
||||
|
||||
const fetchLocalRelationships = async () => {
|
||||
const metadataTables = currentMetadataSource?.tables || [];
|
||||
|
||||
const _tableRelationships = [];
|
||||
if (metadataTables) {
|
||||
for (const metadataTable of metadataTables) {
|
||||
const tableRelationships = getTableLocalRelationships(
|
||||
metadataTable,
|
||||
dataSourceName,
|
||||
suggestedRelationships
|
||||
);
|
||||
_tableRelationships.push(...tableRelationships);
|
||||
}
|
||||
}
|
||||
|
||||
return _tableRelationships;
|
||||
};
|
||||
|
||||
const {
|
||||
data: relationships,
|
||||
isLoading: isLoadingRelationships,
|
||||
isFetching: isFetchingRelationships,
|
||||
refetch: refetchRelationships,
|
||||
error,
|
||||
} = useQuery({
|
||||
queryFn: fetchLocalRelationships,
|
||||
queryKey: getTrackedRelationshipsCacheKey(dataSourceName),
|
||||
});
|
||||
|
||||
return {
|
||||
data: relationships || [],
|
||||
isFetching: isFetchingRelationships,
|
||||
isLoading: isLoadingRelationships,
|
||||
error: [error],
|
||||
refetchRelationships,
|
||||
};
|
||||
};
|
@ -1,5 +1,5 @@
|
||||
import { expect } from '@storybook/jest';
|
||||
import { StoryFn, Meta, StoryObj } from '@storybook/react';
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { waitFor, within, userEvent } from '@storybook/testing-library';
|
||||
import { ReactQueryDecorator } from '../../storybook/decorators/react-query';
|
||||
import { DatabaseRelationships } from './DatabaseRelationships';
|
||||
@ -16,12 +16,17 @@ export default {
|
||||
},
|
||||
} as Meta<typeof DatabaseRelationships>;
|
||||
|
||||
export const Basic: StoryFn<typeof DatabaseRelationships> = () => (
|
||||
<DatabaseRelationships
|
||||
dataSourceName="bikes"
|
||||
table={{ name: 'products', schema: 'production' }}
|
||||
/>
|
||||
);
|
||||
export const Basic: StoryObj<typeof DatabaseRelationships> = {
|
||||
render: () => (
|
||||
<DatabaseRelationships
|
||||
dataSourceName="aPostgres"
|
||||
table={{ name: 'Album', schema: 'public' }}
|
||||
/>
|
||||
),
|
||||
parameters: {
|
||||
msw: trackedArrayRelationshipsHandlers(),
|
||||
},
|
||||
};
|
||||
|
||||
export const Testing: StoryObj<typeof DatabaseRelationships> = {
|
||||
name: '🧪 Test - Tracked array relationships',
|
||||
@ -37,7 +42,12 @@ export const Testing: StoryObj<typeof DatabaseRelationships> = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
expect(await canvas.findByText('NAME')).toBeVisible();
|
||||
await waitFor(
|
||||
async () => {
|
||||
expect(await canvas.findByText('NAME')).toBeVisible();
|
||||
},
|
||||
{ timeout: 10000 }
|
||||
);
|
||||
|
||||
const firstRelationship = await canvas.findByText('albumAlbumCovers');
|
||||
expect(firstRelationship).toBeVisible();
|
||||
|
@ -2,7 +2,11 @@ import { useState } from 'react';
|
||||
import { FaPlusCircle } from 'react-icons/fa';
|
||||
import { Button } from '../../new-components/Button';
|
||||
import { useFireNotification } from '../../new-components/Notifications';
|
||||
import { useSyncResourceVersionOnMount } from '../hasura-metadata-api';
|
||||
import {
|
||||
MetadataSelectors,
|
||||
useMetadata,
|
||||
useSyncResourceVersionOnMount,
|
||||
} from '../hasura-metadata-api';
|
||||
import { Table } from '../hasura-metadata-types';
|
||||
import { AvailableRelationshipsList } from './components/AvailableRelationshipsList/AvailableRelationshipsList';
|
||||
import Legend from './components/Legend';
|
||||
@ -10,6 +14,11 @@ import { RenderWidget } from './components/RenderWidget/RenderWidget';
|
||||
import { SuggestedRelationships } from './components/SuggestedRelationships/SuggestedRelationships';
|
||||
import { NOTIFICATIONS } from './components/constants';
|
||||
import { MODE, Relationship } from './types';
|
||||
import { useDriverCapabilities } from '../Data/hooks/useDriverCapabilities';
|
||||
import { Feature } from '../DataSource';
|
||||
import Skeleton from 'react-loading-skeleton';
|
||||
import { useAppDispatch } from '../../storeHooks';
|
||||
import { updateSchemaInfo } from '../../components/Services/Data/DataActions';
|
||||
|
||||
export interface DatabaseRelationshipsProps {
|
||||
dataSourceName: string;
|
||||
@ -29,6 +38,22 @@ export const DatabaseRelationships = ({
|
||||
});
|
||||
const { fireNotification } = useFireNotification();
|
||||
|
||||
const { data: driver } = useMetadata(
|
||||
m => MetadataSelectors.findSource(dataSourceName)(m)?.kind
|
||||
);
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const isLoadSchemaRequired = driver === 'mssql' || driver === 'postgres';
|
||||
|
||||
const { data: areForeignKeysSupported, isLoading } = useDriverCapabilities({
|
||||
dataSourceName,
|
||||
select: data => {
|
||||
if (data === Feature.NotImplemented) return false;
|
||||
|
||||
return data.data_schema?.supports_foreign_keys;
|
||||
},
|
||||
});
|
||||
|
||||
const onCancel = () => {
|
||||
setTabState({
|
||||
mode: undefined,
|
||||
@ -41,21 +66,29 @@ export const DatabaseRelationships = ({
|
||||
});
|
||||
|
||||
const onError = (err: Error) => {
|
||||
if (mode)
|
||||
if (mode) {
|
||||
fireNotification({
|
||||
type: 'error',
|
||||
title: NOTIFICATIONS.onError[mode],
|
||||
message: err?.message ?? '',
|
||||
});
|
||||
if (isLoadSchemaRequired) {
|
||||
dispatch(updateSchemaInfo());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onSuccess = () => {
|
||||
if (mode)
|
||||
if (mode) {
|
||||
fireNotification({
|
||||
type: 'success',
|
||||
title: 'Success!',
|
||||
message: NOTIFICATIONS.onSuccess[mode],
|
||||
});
|
||||
if (isLoadSchemaRequired) {
|
||||
dispatch(updateSchemaInfo());
|
||||
}
|
||||
}
|
||||
|
||||
setTabState({
|
||||
mode: undefined,
|
||||
@ -63,6 +96,8 @@ export const DatabaseRelationships = ({
|
||||
});
|
||||
};
|
||||
|
||||
if (isLoading) return <Skeleton count={10} height={20} />;
|
||||
|
||||
return (
|
||||
<div className="my-2">
|
||||
<div>
|
||||
@ -77,7 +112,12 @@ export const DatabaseRelationships = ({
|
||||
}}
|
||||
/>
|
||||
|
||||
<SuggestedRelationships dataSourceName={dataSourceName} table={table} />
|
||||
{areForeignKeysSupported && (
|
||||
<SuggestedRelationships
|
||||
dataSourceName={dataSourceName}
|
||||
table={table}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Legend />
|
||||
</div>
|
||||
|
@ -3,8 +3,8 @@ import { z } from 'zod';
|
||||
import { Dialog } from '../../../../new-components/Dialog';
|
||||
import { InputField, SimpleForm } from '../../../../new-components/Form';
|
||||
import { IndicatorCard } from '../../../../new-components/IndicatorCard';
|
||||
import { useManageLocalRelationship } from '../../hooks/useManageLocalRelationship';
|
||||
import { Relationship } from '../../types';
|
||||
import { useCreateTableRelationships } from '../../hooks/useCreateTableRelationships/useCreateTableRelationships';
|
||||
|
||||
interface RenameRelationshipProps {
|
||||
relationship: Relationship;
|
||||
@ -15,12 +15,10 @@ interface RenameRelationshipProps {
|
||||
|
||||
export const RenameRelationship = (props: RenameRelationshipProps) => {
|
||||
const { relationship, onCancel, onSuccess, onError } = props;
|
||||
const { renameRelationship } = useManageLocalRelationship({
|
||||
dataSourceName: relationship.fromSource,
|
||||
table: relationship.fromTable,
|
||||
onSuccess,
|
||||
onError,
|
||||
});
|
||||
const { renameRelationships } = useCreateTableRelationships(
|
||||
relationship.fromSource,
|
||||
{ onSuccess, onError }
|
||||
);
|
||||
|
||||
if (
|
||||
relationship.type === 'remoteDatabaseRelationship' ||
|
||||
@ -56,7 +54,16 @@ export const RenameRelationship = (props: RenameRelationshipProps) => {
|
||||
updatedName: z.string().min(1, 'Updated name cannot be empty!'),
|
||||
})}
|
||||
onSubmit={data => {
|
||||
renameRelationship(relationship, data.updatedName);
|
||||
renameRelationships({
|
||||
data: [
|
||||
{
|
||||
name: relationship.name,
|
||||
new_name: data.updatedName,
|
||||
source: relationship.fromSource,
|
||||
table: relationship.fromTable,
|
||||
},
|
||||
],
|
||||
});
|
||||
}}
|
||||
>
|
||||
<>
|
||||
|
@ -1,9 +1,7 @@
|
||||
import { Dialog } from '../../../../../new-components/Dialog';
|
||||
import React from 'react';
|
||||
import { Relationship } from '../../../types';
|
||||
import { useManageLocalRelationship } from '../../../hooks/useManageLocalRelationship';
|
||||
import { useManageRemoteDatabaseRelationship } from '../../../hooks/useManageRemoteDatabaseRelationship';
|
||||
import { useManageRemoteSchemaRelationship } from '../../../hooks/useManageRemoteSchemaRelationship';
|
||||
import { useCreateTableRelationships } from '../../../hooks/useCreateTableRelationships/useCreateTableRelationships';
|
||||
|
||||
interface ConfirmDeleteRelationshipPopupProps {
|
||||
relationship: Relationship;
|
||||
@ -17,33 +15,13 @@ export const ConfirmDeleteRelationshipPopup = (
|
||||
) => {
|
||||
const { relationship, onCancel, onSuccess, onError } = props;
|
||||
|
||||
const {
|
||||
deleteRelationship: deleteLocalRelationship,
|
||||
isLoading: isDeleteLocalRelationshipLoading,
|
||||
} = useManageLocalRelationship({
|
||||
dataSourceName: relationship.fromSource,
|
||||
table: relationship.fromTable,
|
||||
onSuccess,
|
||||
onError,
|
||||
});
|
||||
|
||||
const {
|
||||
deleteRelationship: deleteRemoteDatabaseRelationship,
|
||||
isLoading: isDeleteRemoteDatabaseRelationshipLoading,
|
||||
} = useManageRemoteDatabaseRelationship({
|
||||
dataSourceName: relationship.fromSource,
|
||||
onSuccess,
|
||||
onError,
|
||||
});
|
||||
|
||||
const {
|
||||
deleteRelationship: deleteRemoteSchemaRelationship,
|
||||
isLoading: isDeleteRemoteSchemaRelationshipLoading,
|
||||
} = useManageRemoteSchemaRelationship({
|
||||
dataSourceName: relationship.fromSource,
|
||||
onSuccess,
|
||||
onError,
|
||||
});
|
||||
const { deleteRelationships, isLoading } = useCreateTableRelationships(
|
||||
relationship.fromSource,
|
||||
{
|
||||
onSuccess,
|
||||
onError,
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
@ -55,21 +33,20 @@ export const ConfirmDeleteRelationshipPopup = (
|
||||
footer={
|
||||
<Dialog.Footer
|
||||
onSubmit={() => {
|
||||
if (relationship.type === 'localRelationship') {
|
||||
deleteLocalRelationship(relationship);
|
||||
} else if (relationship.type === 'remoteDatabaseRelationship') {
|
||||
deleteRemoteDatabaseRelationship(relationship);
|
||||
} else if (relationship.type === 'remoteSchemaRelationship')
|
||||
deleteRemoteSchemaRelationship(relationship);
|
||||
deleteRelationships({
|
||||
data: [
|
||||
{
|
||||
name: relationship.name,
|
||||
source: relationship.fromSource,
|
||||
table: relationship.fromTable,
|
||||
},
|
||||
],
|
||||
});
|
||||
}}
|
||||
onClose={onCancel}
|
||||
callToDeny="Cancel"
|
||||
callToAction="Drop Relationship"
|
||||
isLoading={
|
||||
isDeleteLocalRelationshipLoading ||
|
||||
isDeleteRemoteDatabaseRelationshipLoading ||
|
||||
isDeleteRemoteSchemaRelationshipLoading
|
||||
}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
}
|
||||
>
|
||||
|
@ -6,12 +6,12 @@ import {
|
||||
GraphQLSanitizedInputField,
|
||||
} from '../../../../new-components/Form';
|
||||
import { hasuraToast } from '../../../../new-components/Toasts';
|
||||
import {
|
||||
SuggestedRelationshipWithName,
|
||||
useSuggestedRelationships,
|
||||
} from '../SuggestedRelationships/hooks/useSuggestedRelationships';
|
||||
import { SuggestedRelationshipWithName } from '../SuggestedRelationships/hooks/useSuggestedRelationships';
|
||||
import { useCreateTableRelationships } from '../../hooks/useCreateTableRelationships/useCreateTableRelationships';
|
||||
import { DisplayToastErrorMessage } from '../../../Data/components/DisplayErrorMessage';
|
||||
import { useAppDispatch } from '../../../../storeHooks';
|
||||
import { updateSchemaInfo } from '../../../../components/Services/Data/DataActions';
|
||||
import { MetadataSelectors, useMetadata } from '../../../hasura-metadata-api';
|
||||
|
||||
type SuggestedRelationshipTrackModalProps = {
|
||||
relationship: SuggestedRelationshipWithName;
|
||||
@ -22,14 +22,23 @@ type SuggestedRelationshipTrackModalProps = {
|
||||
export const SuggestedRelationshipTrackModal: React.VFC<
|
||||
SuggestedRelationshipTrackModalProps
|
||||
> = ({ relationship, dataSourceName, onClose }) => {
|
||||
const { refetchSuggestedRelationships } = useSuggestedRelationships({
|
||||
dataSourceName,
|
||||
table: relationship.from.table,
|
||||
isEnabled: true,
|
||||
});
|
||||
const dispatch = useAppDispatch();
|
||||
const { data: driver } = useMetadata(
|
||||
m => MetadataSelectors.findSource(dataSourceName)(m)?.kind
|
||||
);
|
||||
|
||||
const { createTableRelationships, isLoading } =
|
||||
useCreateTableRelationships(dataSourceName);
|
||||
const isLoadSchemaRequired = driver === 'mssql' || driver === 'postgres';
|
||||
|
||||
const { createTableRelationships, isLoading } = useCreateTableRelationships(
|
||||
dataSourceName,
|
||||
{
|
||||
onSuccess: () => {
|
||||
if (isLoadSchemaRequired) {
|
||||
dispatch(updateSchemaInfo());
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const onTrackRelationship = async (relationshipName: string) => {
|
||||
createTableRelationships({
|
||||
@ -62,7 +71,6 @@ export const SuggestedRelationshipTrackModal: React.VFC<
|
||||
type: 'success',
|
||||
title: 'Tracked Successfully',
|
||||
});
|
||||
refetchSuggestedRelationships();
|
||||
onClose();
|
||||
},
|
||||
onError: err => {
|
||||
|
@ -10,15 +10,12 @@ import {
|
||||
FaMagic,
|
||||
FaTable,
|
||||
} from 'react-icons/fa';
|
||||
import { MetadataSelectors, useMetadata } from '../../../hasura-metadata-api';
|
||||
import { getSupportsForeignKeys } from '../../../hasura-metadata-api/utils';
|
||||
import { areTablesEqual } from '../../../hasura-metadata-api';
|
||||
import { getTableDisplayName } from '../../utils/helpers';
|
||||
import {
|
||||
SuggestedRelationshipWithName,
|
||||
useSuggestedRelationships,
|
||||
} from './hooks/useSuggestedRelationships';
|
||||
import { SuggestedRelationshipWithName } from './hooks/useSuggestedRelationships';
|
||||
import Skeleton from 'react-loading-skeleton';
|
||||
import { SuggestedRelationshipTrackModal } from '../SuggestedRelationshipTrackModal/SuggestedRelationshipTrackModal';
|
||||
import { useSuggestedRelationships } from '../../../Data/TrackResources/TrackRelationships/hooks/useSuggestedRelationships';
|
||||
|
||||
type SuggestedRelationshipsProps = {
|
||||
dataSourceName: string;
|
||||
@ -29,27 +26,23 @@ export const SuggestedRelationships = ({
|
||||
dataSourceName,
|
||||
table,
|
||||
}: SuggestedRelationshipsProps) => {
|
||||
const { data: source } = useMetadata(
|
||||
MetadataSelectors.findSource(dataSourceName)
|
||||
);
|
||||
|
||||
const supportsForeignKeys = getSupportsForeignKeys(source);
|
||||
|
||||
const { suggestedRelationships, isLoadingSuggestedRelationships } =
|
||||
const { data: { untracked = [] } = {}, isLoading } =
|
||||
useSuggestedRelationships({
|
||||
dataSourceName,
|
||||
table,
|
||||
isEnabled: supportsForeignKeys,
|
||||
which: 'all',
|
||||
});
|
||||
|
||||
const untrackedSuggestedRelationships = untracked.filter(rel =>
|
||||
areTablesEqual(rel.from.table, table)
|
||||
);
|
||||
|
||||
const [isModalVisible, setModalVisible] = useState(false);
|
||||
const [selectedRelationship, setSelectedRelationship] =
|
||||
useState<SuggestedRelationshipWithName | null>(null);
|
||||
|
||||
if (isLoadingSuggestedRelationships)
|
||||
return <Skeleton count={4} height={30} />;
|
||||
if (isLoading) return <Skeleton count={4} height={30} />;
|
||||
|
||||
return suggestedRelationships.length > 0 ? (
|
||||
return untrackedSuggestedRelationships.length > 0 ? (
|
||||
<>
|
||||
<CardedTable.Table>
|
||||
<CardedTable.Header
|
||||
@ -64,7 +57,7 @@ export const SuggestedRelationships = ({
|
||||
/>
|
||||
|
||||
<CardedTable.TableBody>
|
||||
{suggestedRelationships.map(relationship => (
|
||||
{untrackedSuggestedRelationships.map(relationship => (
|
||||
<CardedTable.TableBodyRow key={relationship.constraintName}>
|
||||
<CardedTable.TableBodyCell>
|
||||
<div className="flex flex-row items-center">
|
||||
|
@ -1,23 +1,9 @@
|
||||
import { useEffect } from 'react';
|
||||
import inflection from 'inflection';
|
||||
import camelCase from 'lodash/camelCase';
|
||||
import { SuggestedRelationship } from '../../../types';
|
||||
import { getTableDisplayName } from '../../../utils/helpers';
|
||||
import { getDriverPrefix, runMetadataQuery } from '../../../../DataSource';
|
||||
import {
|
||||
areTablesEqual,
|
||||
MetadataSelectors,
|
||||
} from '../../../../hasura-metadata-api';
|
||||
import { useMetadata } from '../../../../hasura-metadata-api/useMetadata';
|
||||
import { NamingConvention, Table } from '../../../../hasura-metadata-types';
|
||||
import { useHttpClient } from '../../../../Network';
|
||||
import { useQuery } from 'react-query';
|
||||
|
||||
type UseSuggestedRelationshipsArgs = {
|
||||
dataSourceName: string;
|
||||
table?: Table;
|
||||
isEnabled: boolean;
|
||||
};
|
||||
import { areTablesEqual } from '../../../../hasura-metadata-api';
|
||||
|
||||
export type SuggestedRelationshipsResponse = {
|
||||
relationships: SuggestedRelationship[];
|
||||
@ -87,89 +73,3 @@ export const addConstraintName = (
|
||||
constraintName,
|
||||
};
|
||||
});
|
||||
|
||||
export const getSuggestedRelationshipsCacheQuery = (
|
||||
dataSourceName: string,
|
||||
table: Table
|
||||
) => ['suggested_relationships', dataSourceName, table];
|
||||
|
||||
export const useSuggestedRelationships = ({
|
||||
dataSourceName,
|
||||
table,
|
||||
isEnabled,
|
||||
}: UseSuggestedRelationshipsArgs) => {
|
||||
const { data: metadataSource, isFetching } = useMetadata(
|
||||
MetadataSelectors.findSource(dataSourceName)
|
||||
);
|
||||
|
||||
const namingConvention: NamingConvention =
|
||||
metadataSource?.customization?.naming_convention || 'hasura-default';
|
||||
|
||||
const dataSourcePrefix = metadataSource?.kind
|
||||
? getDriverPrefix(metadataSource?.kind)
|
||||
: undefined;
|
||||
|
||||
const httpClient = useHttpClient();
|
||||
|
||||
const {
|
||||
data,
|
||||
refetch: refetchSuggestedRelationships,
|
||||
isLoading: isLoadingSuggestedRelationships,
|
||||
} = useQuery({
|
||||
queryKey: getSuggestedRelationshipsCacheQuery(dataSourceName, table),
|
||||
queryFn: async () => {
|
||||
const body = {
|
||||
type: `${dataSourcePrefix}_suggest_relationships`,
|
||||
args: {
|
||||
omit_tracked: true,
|
||||
source: dataSourceName,
|
||||
...(table ? { tables: [table] } : {}),
|
||||
},
|
||||
};
|
||||
const result = await runMetadataQuery<SuggestedRelationshipsResponse>({
|
||||
httpClient,
|
||||
body,
|
||||
});
|
||||
|
||||
return result;
|
||||
},
|
||||
enabled: isEnabled && !isFetching,
|
||||
refetchOnWindowFocus: false,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (dataSourcePrefix) {
|
||||
refetchSuggestedRelationships();
|
||||
}
|
||||
}, [dataSourcePrefix, refetchSuggestedRelationships]);
|
||||
|
||||
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,
|
||||
relationships: suggestedRelationships,
|
||||
})
|
||||
: suggestedRelationships;
|
||||
|
||||
const relationshipsWithConstraintName = addConstraintName(
|
||||
tableFilteredRelationships,
|
||||
namingConvention
|
||||
);
|
||||
|
||||
return {
|
||||
suggestedRelationships: relationshipsWithConstraintName,
|
||||
isLoadingSuggestedRelationships,
|
||||
refetchSuggestedRelationships,
|
||||
};
|
||||
};
|
||||
|
@ -20,6 +20,7 @@ import {
|
||||
deleteTableRelationshipRequestBody,
|
||||
renameRelationshipRequestBody,
|
||||
} from './utils';
|
||||
import { useInvalidateSuggestedRelationships } from '../../../Data/TrackResources/TrackRelationships/hooks/useSuggestedRelationships';
|
||||
|
||||
type AllowedRelationshipDefinitions =
|
||||
| Omit<LocalTableRelationshipDefinition, 'capabilities'>
|
||||
@ -72,6 +73,11 @@ export const useCreateTableRelationships = (
|
||||
) => {
|
||||
// get these capabilities
|
||||
|
||||
const { invalidateSuggestedRelationships } =
|
||||
useInvalidateSuggestedRelationships({
|
||||
dataSourceName,
|
||||
});
|
||||
|
||||
const { data: driverCapabilties = [] } = useAllDriverCapabilities({
|
||||
select: data => {
|
||||
const result = data.map(item => {
|
||||
@ -129,6 +135,8 @@ export const useCreateTableRelationships = (
|
||||
errorTransform: transformErrorResponse,
|
||||
onSuccess: (data, variable, ctx) => {
|
||||
globalMutateOptions?.onSuccess?.(data, variable, ctx);
|
||||
console.log('invalidate');
|
||||
invalidateSuggestedRelationships();
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -1,7 +1,24 @@
|
||||
import { Table } from '../../hasura-metadata-types';
|
||||
import { useMetadata, MetadataSelectors } from '../../hasura-metadata-api';
|
||||
import { getAllTableRelationships } from '../utils/tableRelationships';
|
||||
import { useAllSuggestedRelationships } from '../components/SuggestedRelationships/hooks/useAllSuggestedRelationships';
|
||||
import {
|
||||
useMetadata,
|
||||
MetadataSelectors,
|
||||
areTablesEqual,
|
||||
} from '../../hasura-metadata-api';
|
||||
import { useSuggestedRelationships } from '../../Data/TrackResources/TrackRelationships/hooks/useSuggestedRelationships';
|
||||
import {
|
||||
isLegacyRemoteSchemaRelationship,
|
||||
isManualArrayRelationship,
|
||||
isManualObjectRelationship,
|
||||
isRemoteSchemaRelationship,
|
||||
} from '../../DataSource';
|
||||
import {
|
||||
adaptLegacyRemoteSchemaRelationship,
|
||||
adaptLocalArrayRelationshipWithManualConfiguration,
|
||||
adaptLocalObjectRelationshipWithManualConfiguration,
|
||||
adaptRemoteDatabaseRelationship,
|
||||
adaptRemoteSchemaRelationship,
|
||||
} from '../utils/adaptResponse';
|
||||
import { LocalRelationship } from '../types';
|
||||
|
||||
export const useListAllDatabaseRelationships = ({
|
||||
dataSourceName,
|
||||
@ -11,31 +28,96 @@ export const useListAllDatabaseRelationships = ({
|
||||
table: Table;
|
||||
}) => {
|
||||
const {
|
||||
data: metadataTable,
|
||||
isFetching: isMetadataPending,
|
||||
isLoading: isMetadataLoading,
|
||||
error: metadataError,
|
||||
} = useMetadata(MetadataSelectors.findTable(dataSourceName, table));
|
||||
data: { tracked = [] } = {},
|
||||
isLoading: isSuggestedRelationshipsLoading,
|
||||
isFetching: isSuggestedRelationshipsFetching,
|
||||
error: suggestedRelationshipsError,
|
||||
} = useSuggestedRelationships({
|
||||
dataSourceName,
|
||||
which: 'all',
|
||||
});
|
||||
|
||||
const filteredTrackedFkRels: LocalRelationship[] = tracked
|
||||
.filter(rel => areTablesEqual(rel.fromTable, table))
|
||||
.map(rel => ({
|
||||
name: rel.name,
|
||||
fromSource: dataSourceName,
|
||||
fromTable: rel.fromTable,
|
||||
type: 'localRelationship',
|
||||
relationshipType: rel.type === 'object' ? 'Object' : 'Array',
|
||||
definition: {
|
||||
toTable: rel.toTable,
|
||||
mapping: rel.columnMapping,
|
||||
},
|
||||
}));
|
||||
|
||||
const {
|
||||
suggestedRelationships,
|
||||
isLoadingSuggestedRelationships,
|
||||
isFetchingSuggestedRelationships,
|
||||
error,
|
||||
} = useAllSuggestedRelationships({
|
||||
dataSourceName,
|
||||
isEnabled: true,
|
||||
omitTracked: false,
|
||||
data: relationshipsWithManualConfigs = [],
|
||||
isLoading: isMetadataLoading,
|
||||
isFetching: isMetadataFetching,
|
||||
error: metadataError,
|
||||
} = useMetadata(m => {
|
||||
const metadataTable = MetadataSelectors.findTable(dataSourceName, table)(m);
|
||||
|
||||
const localArrayRelationshipsWithManualConfig = (
|
||||
metadataTable?.array_relationships ?? []
|
||||
)
|
||||
.filter(isManualArrayRelationship)
|
||||
.map(relationship =>
|
||||
adaptLocalArrayRelationshipWithManualConfiguration({
|
||||
table,
|
||||
dataSourceName,
|
||||
relationship,
|
||||
})
|
||||
);
|
||||
|
||||
const localObjectRelationshipsWithManualConfig = (
|
||||
metadataTable?.object_relationships ?? []
|
||||
)
|
||||
.filter(isManualObjectRelationship)
|
||||
.map(relationship =>
|
||||
adaptLocalObjectRelationshipWithManualConfiguration({
|
||||
table,
|
||||
dataSourceName,
|
||||
relationship,
|
||||
})
|
||||
);
|
||||
|
||||
const remoteRels = (metadataTable?.remote_relationships ?? []).map(
|
||||
relationship => {
|
||||
if (isRemoteSchemaRelationship(relationship))
|
||||
return adaptRemoteSchemaRelationship({
|
||||
table,
|
||||
dataSourceName,
|
||||
relationship,
|
||||
});
|
||||
|
||||
if (isLegacyRemoteSchemaRelationship(relationship))
|
||||
return adaptLegacyRemoteSchemaRelationship({
|
||||
table,
|
||||
dataSourceName,
|
||||
relationship,
|
||||
});
|
||||
|
||||
return adaptRemoteDatabaseRelationship({
|
||||
table,
|
||||
dataSourceName,
|
||||
relationship,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
return [
|
||||
...remoteRels,
|
||||
...localArrayRelationshipsWithManualConfig,
|
||||
...localObjectRelationshipsWithManualConfig,
|
||||
];
|
||||
});
|
||||
|
||||
return {
|
||||
data: getAllTableRelationships(
|
||||
metadataTable,
|
||||
dataSourceName,
|
||||
suggestedRelationships
|
||||
),
|
||||
isFetching: isMetadataPending || isFetchingSuggestedRelationships,
|
||||
isLoading: isMetadataLoading || isLoadingSuggestedRelationships,
|
||||
error: [metadataError, error],
|
||||
data: [...filteredTrackedFkRels, ...relationshipsWithManualConfigs],
|
||||
isLoading: isSuggestedRelationshipsLoading || isMetadataLoading,
|
||||
isFetching: isSuggestedRelationshipsFetching || isMetadataFetching,
|
||||
error: [metadataError, suggestedRelationshipsError],
|
||||
};
|
||||
};
|
||||
|
@ -1,114 +0,0 @@
|
||||
import { useMetadataMigration } from '../../MetadataAPI';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useQueryClient } from 'react-query';
|
||||
import { useMetadata } from '../../hasura-metadata-api';
|
||||
import { LocalRelationship } from '../types';
|
||||
import {
|
||||
generateCreateLocalRelationshipWithManualConfigurationRequest,
|
||||
generateDeleteLocalRelationshipRequest,
|
||||
generateRenameLocalRelationshipRequest,
|
||||
} from '../utils/generateRequest';
|
||||
import { generateQueryKeys } from '../utils/queryClientUtils';
|
||||
import { Table } from '../../hasura-metadata-types';
|
||||
|
||||
export const useManageLocalRelationship = ({
|
||||
dataSourceName,
|
||||
table,
|
||||
onSuccess,
|
||||
onError,
|
||||
}: {
|
||||
dataSourceName: string;
|
||||
table: Table;
|
||||
onSuccess?: () => void;
|
||||
onError?: (err: Error) => void;
|
||||
}) => {
|
||||
const { data } = useMetadata(m => {
|
||||
return {
|
||||
resource_version: m.resource_version,
|
||||
source: m.metadata.sources.find(s => s.name === dataSourceName),
|
||||
};
|
||||
});
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
const { mutate, ...rest } = useMetadataMigration();
|
||||
const mutationOptions = useMemo(
|
||||
() => ({
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries(
|
||||
generateQueryKeys.suggestedRelationships({ dataSourceName, table })
|
||||
);
|
||||
onSuccess?.();
|
||||
},
|
||||
onError: (err: Error) => {
|
||||
onError?.(err);
|
||||
},
|
||||
}),
|
||||
[onError, onSuccess, queryClient]
|
||||
);
|
||||
|
||||
const metadataSource = data?.source;
|
||||
const resource_version = data?.resource_version;
|
||||
const driver = metadataSource?.kind;
|
||||
|
||||
const renameRelationship = useCallback(
|
||||
async (relationship: LocalRelationship, newName: string) => {
|
||||
if (!resource_version || !driver) throw Error('Metadata not ready');
|
||||
|
||||
mutate(
|
||||
{
|
||||
query: generateRenameLocalRelationshipRequest({
|
||||
resource_version,
|
||||
relationship,
|
||||
driver,
|
||||
newName,
|
||||
}),
|
||||
},
|
||||
mutationOptions
|
||||
);
|
||||
},
|
||||
[driver, mutate, mutationOptions, resource_version]
|
||||
);
|
||||
|
||||
const createRelationship = useCallback(
|
||||
async (relationship: LocalRelationship) => {
|
||||
if (!resource_version || !driver) throw Error('Metadata not ready');
|
||||
|
||||
mutate(
|
||||
{
|
||||
query: generateCreateLocalRelationshipWithManualConfigurationRequest({
|
||||
resource_version,
|
||||
relationship,
|
||||
driver,
|
||||
}),
|
||||
},
|
||||
mutationOptions
|
||||
);
|
||||
},
|
||||
[driver, mutate, mutationOptions, resource_version]
|
||||
);
|
||||
|
||||
const deleteRelationship = useCallback(
|
||||
async (relationship: LocalRelationship) => {
|
||||
if (!resource_version || !driver) throw Error('Metadata not ready');
|
||||
|
||||
mutate(
|
||||
{
|
||||
query: generateDeleteLocalRelationshipRequest({
|
||||
driver,
|
||||
resource_version,
|
||||
relationship,
|
||||
}),
|
||||
},
|
||||
mutationOptions
|
||||
);
|
||||
},
|
||||
[driver, mutate, mutationOptions, resource_version]
|
||||
);
|
||||
|
||||
return {
|
||||
renameRelationship,
|
||||
deleteRelationship,
|
||||
createRelationship,
|
||||
...rest,
|
||||
};
|
||||
};
|
@ -1,106 +0,0 @@
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useQueryClient } from 'react-query';
|
||||
import { useMetadataMigration } from '../../MetadataAPI';
|
||||
import { useMetadata } from '../../hasura-metadata-api';
|
||||
import { RemoteDatabaseRelationship } from '../types';
|
||||
import {
|
||||
generateRemoteRelationshipCreateRequest,
|
||||
generateRemoteRelationshipDeleteRequest,
|
||||
generateRemoteRelationshipEditRequest,
|
||||
} from '../utils/generateRequest';
|
||||
|
||||
export const useManageRemoteDatabaseRelationship = ({
|
||||
dataSourceName,
|
||||
onSuccess,
|
||||
onError,
|
||||
}: {
|
||||
dataSourceName: string;
|
||||
onSuccess?: () => void;
|
||||
onError?: (err: Error) => void;
|
||||
}) => {
|
||||
const { data } = useMetadata(m => {
|
||||
return {
|
||||
resource_version: m.resource_version,
|
||||
source: m.metadata.sources.find(s => s.name === dataSourceName),
|
||||
};
|
||||
});
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
const { mutate, ...rest } = useMetadataMigration();
|
||||
const mutationOptions = useMemo(
|
||||
() => ({
|
||||
onSuccess: () => {
|
||||
onSuccess?.();
|
||||
},
|
||||
onError: (err: Error) => {
|
||||
onError?.(err);
|
||||
},
|
||||
}),
|
||||
[onError, onSuccess, queryClient]
|
||||
);
|
||||
|
||||
const metadataSource = data?.source;
|
||||
const resource_version = data?.resource_version;
|
||||
const driver = metadataSource?.kind;
|
||||
|
||||
const createRelationship = useCallback(
|
||||
async (relationship: RemoteDatabaseRelationship) => {
|
||||
if (!resource_version || !driver) throw Error('Metadata not ready');
|
||||
|
||||
mutate(
|
||||
{
|
||||
query: generateRemoteRelationshipCreateRequest({
|
||||
resource_version,
|
||||
relationship,
|
||||
driver,
|
||||
}),
|
||||
},
|
||||
mutationOptions
|
||||
);
|
||||
},
|
||||
[driver, mutate, mutationOptions, resource_version]
|
||||
);
|
||||
|
||||
const editRelationship = useCallback(
|
||||
async (relationship: RemoteDatabaseRelationship) => {
|
||||
if (!resource_version || !driver) throw Error('Metadata not ready');
|
||||
|
||||
mutate(
|
||||
{
|
||||
query: generateRemoteRelationshipEditRequest({
|
||||
resource_version,
|
||||
relationship,
|
||||
driver,
|
||||
}),
|
||||
},
|
||||
mutationOptions
|
||||
);
|
||||
},
|
||||
[driver, mutate, mutationOptions, resource_version]
|
||||
);
|
||||
|
||||
const deleteRelationship = useCallback(
|
||||
async (relationship: RemoteDatabaseRelationship) => {
|
||||
if (!resource_version || !driver) throw Error('Metadata not ready');
|
||||
|
||||
mutate(
|
||||
{
|
||||
query: generateRemoteRelationshipDeleteRequest({
|
||||
driver,
|
||||
resource_version,
|
||||
relationship,
|
||||
}),
|
||||
},
|
||||
mutationOptions
|
||||
);
|
||||
},
|
||||
[driver, mutate, mutationOptions, resource_version]
|
||||
);
|
||||
|
||||
return {
|
||||
editRelationship,
|
||||
deleteRelationship,
|
||||
createRelationship,
|
||||
...rest,
|
||||
};
|
||||
};
|
@ -1,105 +0,0 @@
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useQueryClient } from 'react-query';
|
||||
import { useMetadataMigration } from '../../MetadataAPI';
|
||||
import { useMetadata } from '../../hasura-metadata-api';
|
||||
import { RemoteSchemaRelationship } from '../types';
|
||||
import {
|
||||
generateRemoteRelationshipCreateRequest,
|
||||
generateRemoteRelationshipDeleteRequest,
|
||||
generateRemoteRelationshipEditRequest,
|
||||
} from '../utils/generateRequest';
|
||||
|
||||
export const useManageRemoteSchemaRelationship = ({
|
||||
dataSourceName,
|
||||
onSuccess,
|
||||
onError,
|
||||
}: {
|
||||
dataSourceName: string;
|
||||
onSuccess?: () => void;
|
||||
onError?: (err: Error) => void;
|
||||
}) => {
|
||||
const { data } = useMetadata(m => {
|
||||
return {
|
||||
resource_version: m.resource_version,
|
||||
source: m.metadata.sources.find(s => s.name === dataSourceName),
|
||||
};
|
||||
});
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
const { mutate, ...rest } = useMetadataMigration();
|
||||
const mutationOptions = useMemo(
|
||||
() => ({
|
||||
onSuccess: () => {
|
||||
onSuccess?.();
|
||||
},
|
||||
onError: (err: Error) => {
|
||||
onError?.(err);
|
||||
},
|
||||
}),
|
||||
[onError, onSuccess, queryClient]
|
||||
);
|
||||
|
||||
const metadataSource = data?.source;
|
||||
const resource_version = data?.resource_version;
|
||||
const driver = metadataSource?.kind;
|
||||
|
||||
const createRelationship = useCallback(
|
||||
async (relationship: RemoteSchemaRelationship) => {
|
||||
if (!resource_version || !driver) throw Error('Metadata not ready');
|
||||
|
||||
mutate(
|
||||
{
|
||||
query: generateRemoteRelationshipCreateRequest({
|
||||
resource_version,
|
||||
relationship,
|
||||
driver,
|
||||
}),
|
||||
},
|
||||
mutationOptions
|
||||
);
|
||||
},
|
||||
[driver, mutate, mutationOptions, resource_version]
|
||||
);
|
||||
|
||||
const editRelationship = useCallback(
|
||||
async (relationship: RemoteSchemaRelationship) => {
|
||||
if (!resource_version || !driver) throw Error('Metadata not ready');
|
||||
|
||||
mutate(
|
||||
{
|
||||
query: generateRemoteRelationshipEditRequest({
|
||||
resource_version,
|
||||
relationship,
|
||||
driver,
|
||||
}),
|
||||
},
|
||||
mutationOptions
|
||||
);
|
||||
},
|
||||
[driver, mutate, mutationOptions, resource_version]
|
||||
);
|
||||
|
||||
const deleteRelationship = useCallback(
|
||||
async (relationship: RemoteSchemaRelationship) => {
|
||||
if (!resource_version || !driver) throw Error('Metadata not ready');
|
||||
mutate(
|
||||
{
|
||||
query: generateRemoteRelationshipDeleteRequest({
|
||||
driver,
|
||||
resource_version,
|
||||
relationship,
|
||||
}),
|
||||
},
|
||||
mutationOptions
|
||||
);
|
||||
},
|
||||
[driver, mutate, mutationOptions, resource_version]
|
||||
);
|
||||
|
||||
return {
|
||||
editRelationship,
|
||||
deleteRelationship,
|
||||
createRelationship,
|
||||
...rest,
|
||||
};
|
||||
};
|
Loading…
Reference in New Issue
Block a user