mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-15 01:12:56 +03:00
console: add db to local relationship widget
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/4169 Co-authored-by: Vijay Prasanna <11921040+vijayprasanna13@users.noreply.github.com> Co-authored-by: Varun Choudhary <68095256+Varun-Choudhary@users.noreply.github.com> GitOrigin-RevId: 1c4ea2412d2f43dc3524f705d2b8f1847991c06d
This commit is contained in:
parent
1231d1145b
commit
4f9a08239d
@ -6,7 +6,7 @@ import {
|
||||
useFeatureFlags,
|
||||
availableFeatureFlagIds,
|
||||
} from '@/features/FeatureFlags';
|
||||
import { DatabaseRelationshipsTab } from '@/features/DatabaseRelationshipsTab';
|
||||
import { DatabaseRelationshipsTab } from '@/features/DataRelationships';
|
||||
import TableHeader from '../TableCommon/TableHeader';
|
||||
import {
|
||||
addNewRelClicked,
|
||||
|
@ -5,7 +5,7 @@ import {
|
||||
useFeatureFlags,
|
||||
availableFeatureFlagIds,
|
||||
} from '@/features/FeatureFlags';
|
||||
import { DatabaseRelationshipsTab } from '@/features/DatabaseRelationshipsTab';
|
||||
import { DatabaseRelationshipsTab } from '@/features/DataRelationships';
|
||||
import TableHeader from '../TableCommon/TableHeader';
|
||||
import { getObjArrRelList } from './utils';
|
||||
import { setTable, UPDATE_REMOTE_SCHEMA_MANUAL_REL } from '../DataActions';
|
||||
|
@ -9,7 +9,7 @@ import {
|
||||
relSetType,
|
||||
relSetColumns,
|
||||
} from './state';
|
||||
import { useTableColumns } from '@/features/SqlQueries/hooks/useTableColumns';
|
||||
import { useTableColumns } from '@/features/SqlQueries';
|
||||
import { getColumnNameArrayFromHookData } from './utils';
|
||||
import { MetadataSelector, useMetadata } from '@/features/MetadataAPI';
|
||||
|
||||
|
@ -107,6 +107,7 @@ export const DatabaseSelector = (props: Props) => {
|
||||
data-testid={`${name}_database`}
|
||||
icon={<FaDatabase />}
|
||||
label={labels?.database ?? 'Source'}
|
||||
name={`${name}_database`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -139,6 +140,7 @@ export const DatabaseSelector = (props: Props) => {
|
||||
? labels?.dataset
|
||||
: labels?.schema) ?? 'Schema'
|
||||
}
|
||||
name={`${name}_schema`}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
@ -165,6 +167,7 @@ export const DatabaseSelector = (props: Props) => {
|
||||
data-testid={`${name}_table`}
|
||||
icon={<FaTable />}
|
||||
label={labels?.table || 'Table'}
|
||||
name={`${name}_table`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -33,6 +33,7 @@ export const Select = ({
|
||||
)}
|
||||
disabled={disabled}
|
||||
value={props.value}
|
||||
id={props.name}
|
||||
>
|
||||
{placeholder ? (
|
||||
<option disabled value="">
|
||||
|
@ -5,7 +5,7 @@ import React from 'react';
|
||||
import { DatabaseRelationshipsTab } from './DatabaseRelationshipsTab';
|
||||
|
||||
export default {
|
||||
title: 'Relationships / Database Relationships Tab',
|
||||
title: 'Data Relationships/Database Relationships Tab',
|
||||
component: DatabaseRelationshipsTab,
|
||||
decorators: [ReactQueryDecorator()],
|
||||
} as ComponentMeta<typeof DatabaseRelationshipsTab>;
|
@ -0,0 +1,12 @@
|
||||
import React from 'react';
|
||||
import { ReactQueryDecorator } from '@/storybook/decorators/react-query';
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
import { Form } from './Form';
|
||||
|
||||
export default {
|
||||
title: 'Data Relationships/Form',
|
||||
decorators: [ReactQueryDecorator()],
|
||||
component: Form,
|
||||
} as ComponentMeta<typeof Form>;
|
||||
|
||||
export const Primary: ComponentStory<typeof Form> = () => <Form />;
|
@ -0,0 +1,65 @@
|
||||
import { Button } from '@/new-components/Button';
|
||||
import React, { useState } from 'react';
|
||||
import { CardRadioGroup } from '@/new-components/CardRadioGroup';
|
||||
import { LocalRelationshipWidget } from '../LocalDBRelationshipWidget';
|
||||
|
||||
type Value =
|
||||
| 'Local Relationship'
|
||||
| 'Remote Database Relationship'
|
||||
| 'Remote Schema Relationship';
|
||||
|
||||
const data: { value: Value; title: string; body: string }[] = [
|
||||
{
|
||||
value: 'Local Relationship',
|
||||
title: 'Local Relationship',
|
||||
body: 'Relationships from this table to a local database table.',
|
||||
},
|
||||
{
|
||||
value: 'Remote Database Relationship',
|
||||
title: 'Remote Database Relationship',
|
||||
body: 'Relationship from this local table to a remote database table.',
|
||||
},
|
||||
{
|
||||
value: 'Remote Schema Relationship',
|
||||
title: 'Remote Schema Relationship',
|
||||
body: 'Relationship from this local table to a remote schema.',
|
||||
},
|
||||
];
|
||||
|
||||
export const Form = () => {
|
||||
const [option, setOption] = useState('Local Relationship');
|
||||
return (
|
||||
<div className="w-full sm:w-9/12 bg-white shadow-sm rounded p-md border border-gray-300 shadow show">
|
||||
<div className="flex items-center mb-md">
|
||||
<Button size="sm">Cancel</Button>
|
||||
<span className="font-semibold text-muted ml-1.5">
|
||||
Create New Relationship
|
||||
</span>
|
||||
</div>
|
||||
<hr className="mb-md border-gray-300" />
|
||||
<div className="mb-md">
|
||||
<p className="mb-sm text-muted font-semibold">
|
||||
Select a Relationship Method
|
||||
</p>
|
||||
<CardRadioGroup
|
||||
items={data}
|
||||
onChange={relType => setOption(relType)}
|
||||
value={option}
|
||||
/>
|
||||
{option === 'Local Relationship' ? (
|
||||
<LocalRelationshipWidget
|
||||
sourceTableInfo={{
|
||||
database: 'default',
|
||||
schema: 'public',
|
||||
table: 'resident',
|
||||
}}
|
||||
/>
|
||||
) : option === 'Remote Database Relationship' ? (
|
||||
<>do something for remote DB relationships</>
|
||||
) : (
|
||||
<>do something for remote schema relationships</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -0,0 +1,106 @@
|
||||
import React from 'react';
|
||||
import { Controller, useFormContext } from 'react-hook-form';
|
||||
import { ListMap } from '@/new-components/ListMap';
|
||||
import { DatabaseSelector } from '@/features/Data';
|
||||
import { useTableColumns } from '@/features/SqlQueries';
|
||||
import {
|
||||
LinkBlockHorizontal,
|
||||
LinkBlockVertical,
|
||||
} from '@/new-components/LinkBlock';
|
||||
import { Schema } from './schema';
|
||||
|
||||
export const FormElements = () => {
|
||||
const { control, watch } = useFormContext<Schema>();
|
||||
const [source, destination] = watch(['source', 'destination']);
|
||||
|
||||
const { data: sourceColumnData } = useTableColumns(source.database, {
|
||||
name: source.table,
|
||||
schema: source.schema ?? source.dataset ?? '',
|
||||
});
|
||||
|
||||
const { data: referenceColumnData } = useTableColumns(destination.database, {
|
||||
name: destination.table,
|
||||
schema: destination.schema ?? destination.dataset ?? '',
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="grid grid-cols-12">
|
||||
<div className="col-span-5">
|
||||
<Controller
|
||||
control={control}
|
||||
name="source"
|
||||
render={({ field: { onChange, value }, formState: { errors } }) => (
|
||||
<DatabaseSelector
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
name="source"
|
||||
errors={errors}
|
||||
className="border-l-4 border-l-green-600"
|
||||
hiddenKeys={['database']}
|
||||
disabledKeys={['schema', 'table', 'database']}
|
||||
labels={{
|
||||
database: 'Source Database',
|
||||
schema: 'Source Schema',
|
||||
dataset: 'Source Dataset',
|
||||
table: 'Source Table',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<LinkBlockHorizontal />
|
||||
|
||||
<div className="col-span-5">
|
||||
<Controller
|
||||
control={control}
|
||||
name="destination"
|
||||
render={({ field: { onChange, value }, formState: { errors } }) => (
|
||||
<DatabaseSelector
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
name="destination"
|
||||
errors={errors}
|
||||
className="border-l-4 border-l-indigo-600"
|
||||
hiddenKeys={['database']}
|
||||
labels={{
|
||||
database: 'Reference Database',
|
||||
schema: 'Reference Schema',
|
||||
dataset: 'Reference Dataset',
|
||||
table: 'Reference Table',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<LinkBlockVertical title="Columns Mapped To" />
|
||||
|
||||
<Controller
|
||||
control={control}
|
||||
name="mapping"
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<ListMap
|
||||
onChange={onChange}
|
||||
fromLabel="Source Column"
|
||||
toLabel="Reference Column"
|
||||
maps={value}
|
||||
fromOptions={
|
||||
sourceColumnData
|
||||
? sourceColumnData?.slice(1).map((x: string[]) => x[3])
|
||||
: []
|
||||
}
|
||||
toOptions={
|
||||
referenceColumnData
|
||||
? referenceColumnData?.slice(1).map((x: string[]) => x[3])
|
||||
: []
|
||||
}
|
||||
name="mapping"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
@ -0,0 +1,81 @@
|
||||
import React from 'react';
|
||||
import { Story, Meta } from '@storybook/react';
|
||||
import { ReactQueryDecorator } from '@/storybook/decorators/react-query';
|
||||
import { within, userEvent } from '@storybook/testing-library';
|
||||
import { expect } from '@storybook/jest';
|
||||
import {
|
||||
LocalRelationshipWidget,
|
||||
LocalRelationshipWidgetProps,
|
||||
} from './LocalRelationshipWidget';
|
||||
import { handlers } from '../../../RemoteRelationships/RemoteSchemaRelationships/__mocks__/handlers.mock';
|
||||
|
||||
export default {
|
||||
title:
|
||||
'Data Relationships/Local DB Relationships/Local DB Relationships Form',
|
||||
component: LocalRelationshipWidget,
|
||||
decorators: [ReactQueryDecorator()],
|
||||
parameters: {
|
||||
msw: handlers(),
|
||||
},
|
||||
} as Meta;
|
||||
|
||||
export const Primary: Story<LocalRelationshipWidgetProps> = args => (
|
||||
<LocalRelationshipWidget {...args} />
|
||||
);
|
||||
Primary.args = {
|
||||
sourceTableInfo: {
|
||||
database: 'chinook',
|
||||
schema: 'public',
|
||||
table: 'Album',
|
||||
},
|
||||
};
|
||||
|
||||
export const WithExistingObjectRelationship: Story<LocalRelationshipWidgetProps> = args => (
|
||||
<LocalRelationshipWidget {...args} />
|
||||
);
|
||||
WithExistingObjectRelationship.args = {
|
||||
...Primary.args,
|
||||
existingRelationshipName: 'relt1obj',
|
||||
};
|
||||
|
||||
export const WithExistingArrayRelationship: Story<LocalRelationshipWidgetProps> = args => (
|
||||
<LocalRelationshipWidget {...args} />
|
||||
);
|
||||
WithExistingArrayRelationship.args = {
|
||||
...Primary.args,
|
||||
existingRelationshipName: 'relt1array',
|
||||
};
|
||||
|
||||
export const PrimaryWithTest: Story<LocalRelationshipWidgetProps> = args => (
|
||||
<LocalRelationshipWidget {...args} />
|
||||
);
|
||||
PrimaryWithTest.args = { ...Primary.args };
|
||||
|
||||
PrimaryWithTest.play = async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
const submitButton = await canvas.findByText('Add Relationship');
|
||||
|
||||
userEvent.click(submitButton);
|
||||
|
||||
const nameError = await canvas.findByText('Name is required!');
|
||||
const tableError = await canvas.findByText('Reference Table is required!');
|
||||
|
||||
// expect error messages
|
||||
|
||||
expect(nameError).toBeInTheDocument();
|
||||
expect(tableError).toBeInTheDocument();
|
||||
|
||||
// update fields
|
||||
const nameInput = await canvas.findByLabelText('Name');
|
||||
userEvent.type(nameInput, 'test');
|
||||
|
||||
const typeLabel = await canvas.findByLabelText('Type');
|
||||
const schemaLabel = await canvas.findByLabelText('Reference Schema');
|
||||
const tableLabel = await canvas.findByLabelText('Reference Table');
|
||||
|
||||
userEvent.selectOptions(typeLabel, 'Array Relationship');
|
||||
userEvent.selectOptions(schemaLabel, 'user');
|
||||
userEvent.selectOptions(tableLabel, 'userAddress');
|
||||
userEvent.click(submitButton);
|
||||
};
|
@ -0,0 +1,151 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
allowedMetadataTypes,
|
||||
useMetadataMigration,
|
||||
} from '@/features/MetadataAPI';
|
||||
import { fireNotification } from '@/new-components/Notifications';
|
||||
import { DataTarget } from '@/features/Datasources';
|
||||
import { InputField, Select, Form } from '@/new-components/Form';
|
||||
import { Button } from '@/new-components/Button';
|
||||
import { IndicatorCard } from '@/new-components/IndicatorCard';
|
||||
import { getMetadataQuery, MetadataQueryType } from '@/metadata/queryUtils';
|
||||
import { schema, Schema } from './schema';
|
||||
import { FormElements } from './FormElements';
|
||||
import { useDefaultValues } from './hooks';
|
||||
|
||||
export type LocalRelationshipWidgetProps = {
|
||||
sourceTableInfo: DataTarget;
|
||||
existingRelationshipName?: string;
|
||||
};
|
||||
|
||||
type MetadataPayloadType = {
|
||||
type: allowedMetadataTypes;
|
||||
args: { [key: string]: any };
|
||||
version?: number;
|
||||
};
|
||||
|
||||
export const LocalRelationshipWidget = ({
|
||||
sourceTableInfo,
|
||||
existingRelationshipName,
|
||||
}: LocalRelationshipWidgetProps) => {
|
||||
// hook to fetch data for existing relationship
|
||||
const { data: defaultValues, isLoading, isError } = useDefaultValues({
|
||||
sourceTableInfo,
|
||||
existingRelationshipName,
|
||||
});
|
||||
|
||||
const mutation = useMetadataMigration({
|
||||
onSuccess: () => {
|
||||
fireNotification({
|
||||
title: 'Success!',
|
||||
message: 'Relationship saved successfully',
|
||||
type: 'success',
|
||||
});
|
||||
},
|
||||
onError: () => {
|
||||
fireNotification({
|
||||
title: 'Error',
|
||||
message: 'Error while creating the relationship',
|
||||
type: 'error',
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const submit = (values: Schema) => {
|
||||
const remote_table: {
|
||||
database?: string;
|
||||
schema?: string;
|
||||
dataset?: string;
|
||||
table: string;
|
||||
} = { ...values.destination };
|
||||
delete remote_table.database;
|
||||
|
||||
const args = {
|
||||
source: sourceTableInfo.database,
|
||||
table: sourceTableInfo.table,
|
||||
name: values.relationshipName,
|
||||
using: {
|
||||
manual_configuration: {
|
||||
remote_table,
|
||||
mapping: values.mapping,
|
||||
},
|
||||
},
|
||||
};
|
||||
const requestBody = getMetadataQuery(
|
||||
values.relationshipType as MetadataQueryType,
|
||||
sourceTableInfo.database,
|
||||
args
|
||||
);
|
||||
|
||||
mutation.mutate({
|
||||
source: '',
|
||||
query: requestBody as MetadataPayloadType,
|
||||
migrationName: 'createLocalDBToDBRelationship',
|
||||
});
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return <div>Loading relationship data...</div>;
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
return <div>Something went wrong while loading relationship data</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Form schema={schema} onSubmit={submit} options={{ defaultValues }}>
|
||||
{options => (
|
||||
<>
|
||||
<div>
|
||||
<div className="w-full sm:w-6/12 mb-md">
|
||||
<div className="mb-md">
|
||||
<InputField
|
||||
name="relationshipName"
|
||||
label="Name"
|
||||
placeholder="Relationship name"
|
||||
dataTest="local-db-to-db-rel-name"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mb-md">
|
||||
<Select
|
||||
name="relationshipType"
|
||||
label="Type"
|
||||
dataTest="local-db-to-db-select-rel-type"
|
||||
placeholder="Select a relationship type..."
|
||||
options={[
|
||||
{
|
||||
label: 'Object Relationship',
|
||||
value: 'create_object_relationship',
|
||||
},
|
||||
{
|
||||
label: 'Array Relationship',
|
||||
value: 'create_array_relationship',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<FormElements />
|
||||
|
||||
<Button
|
||||
mode="primary"
|
||||
type="submit"
|
||||
isLoading={mutation.isLoading}
|
||||
loadingText="Saving relationship"
|
||||
data-test="add-local-db-relationship"
|
||||
>
|
||||
Add Relationship
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{!!Object.keys(options.formState.errors).length && (
|
||||
<IndicatorCard status="negative">
|
||||
Error saving relationship
|
||||
</IndicatorCard>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Form>
|
||||
);
|
||||
};
|
@ -0,0 +1,68 @@
|
||||
import { MetadataSelector, useMetadata } from '@/features/MetadataAPI';
|
||||
import { QualifiedTable } from '@/metadata/types';
|
||||
import { DataTarget } from '@/features/Datasources';
|
||||
import { Schema } from '../schema';
|
||||
|
||||
interface UseDefaultValuesProps {
|
||||
sourceTableInfo: DataTarget;
|
||||
existingRelationshipName?: string;
|
||||
}
|
||||
|
||||
const getSchemaKey = (sourceTableInfo: DataTarget) => {
|
||||
return 'dataset' in sourceTableInfo ? 'dataset' : 'schema';
|
||||
};
|
||||
|
||||
type RelationshipType =
|
||||
| 'create_object_relationship'
|
||||
| 'create_array_relationship';
|
||||
|
||||
export const useDefaultValues = ({
|
||||
sourceTableInfo,
|
||||
existingRelationshipName,
|
||||
}: UseDefaultValuesProps) => {
|
||||
const { data: metadataTable, isLoading, isError } = useMetadata(
|
||||
MetadataSelector.getTable(sourceTableInfo.database, {
|
||||
name: sourceTableInfo.table,
|
||||
schema:
|
||||
(sourceTableInfo as any).schema ?? (sourceTableInfo as any).dataset,
|
||||
})
|
||||
);
|
||||
|
||||
const manual_relationships = [
|
||||
...(metadataTable?.object_relationships?.map(rel => ({
|
||||
...rel,
|
||||
type: 'create_object_relationship' as RelationshipType,
|
||||
})) ?? []),
|
||||
...(metadataTable?.array_relationships?.map(rel => ({
|
||||
...rel,
|
||||
type: 'create_array_relationship' as RelationshipType,
|
||||
})) ?? []),
|
||||
];
|
||||
|
||||
const relationship = manual_relationships?.find(
|
||||
rel => rel.name === existingRelationshipName
|
||||
);
|
||||
|
||||
const defaultValues: Schema = {
|
||||
relationshipType: relationship?.type ?? 'create_object_relationship',
|
||||
relationshipName: existingRelationshipName ?? '',
|
||||
source: {
|
||||
database: sourceTableInfo.database,
|
||||
[getSchemaKey(sourceTableInfo)]:
|
||||
(sourceTableInfo as any).dataset ?? (sourceTableInfo as any).schema,
|
||||
table: sourceTableInfo.table,
|
||||
},
|
||||
destination: {
|
||||
database: sourceTableInfo.database,
|
||||
[getSchemaKey(sourceTableInfo)]:
|
||||
(relationship?.using?.manual_configuration
|
||||
?.remote_table as QualifiedTable)?.schema ?? '',
|
||||
table:
|
||||
(relationship?.using?.manual_configuration
|
||||
?.remote_table as QualifiedTable)?.name ?? '',
|
||||
},
|
||||
mapping: relationship?.using.manual_configuration?.column_mapping ?? {},
|
||||
};
|
||||
|
||||
return { data: defaultValues, isLoading, isError };
|
||||
};
|
@ -0,0 +1 @@
|
||||
export * from './LocalRelationshipWidget';
|
@ -0,0 +1,23 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const schema = z.object({
|
||||
relationshipType: z
|
||||
.literal('create_array_relationship')
|
||||
.or(z.literal('create_object_relationship')),
|
||||
relationshipName: z.string().min(1, { message: 'Name is required!' }),
|
||||
source: z.object({
|
||||
database: z.string().min(1, 'Source Database is required!'),
|
||||
schema: z.string().optional(),
|
||||
dataset: z.string().optional(),
|
||||
table: z.string().min(1, 'Source Table is required!'),
|
||||
}),
|
||||
destination: z.object({
|
||||
database: z.string().min(1, 'Reference Database is required!'),
|
||||
schema: z.string().optional(),
|
||||
dataset: z.string().optional(),
|
||||
table: z.string().min(1, 'Reference Table is required!'),
|
||||
}),
|
||||
mapping: z.record(z.string()),
|
||||
});
|
||||
|
||||
export type Schema = z.infer<typeof schema>;
|
@ -9,6 +9,8 @@ import type {
|
||||
rsToRsRelDef,
|
||||
TableEntry,
|
||||
rsToDbRelDef,
|
||||
ObjectRelationship,
|
||||
ArrayRelationship,
|
||||
} from '@/metadata/types';
|
||||
import { MetadataResponse } from '..';
|
||||
|
||||
@ -171,6 +173,26 @@ export namespace MetadataSelector {
|
||||
return remote_schema_relationships;
|
||||
};
|
||||
|
||||
export const getLocalDBObjectRelationships = (
|
||||
currentDataSource: string,
|
||||
table: QualifiedTable
|
||||
) => (m: MetadataResponse) => {
|
||||
const metadataTable = getTable(currentDataSource, table)(m);
|
||||
const object_relationships: ObjectRelationship[] =
|
||||
metadataTable?.object_relationships ?? [];
|
||||
return object_relationships;
|
||||
};
|
||||
|
||||
export const getLocalDBArrayRelationships = (
|
||||
currentDataSource: string,
|
||||
table: QualifiedTable
|
||||
) => (m: MetadataResponse) => {
|
||||
const metadataTable = getTable(currentDataSource, table)(m);
|
||||
const array_relationships: ArrayRelationship[] =
|
||||
metadataTable?.array_relationships ?? [];
|
||||
return array_relationships;
|
||||
};
|
||||
|
||||
export const getAllDriversList = (m: MetadataResponse) =>
|
||||
m.metadata?.sources.map(s => ({ source: s.name, kind: s.kind }));
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { MetadataQueryType } from '@/metadata/queryUtils';
|
||||
import { HasuraMetadataV3 } from '@/metadata/types';
|
||||
import { IntrospectionQuery } from 'graphql';
|
||||
|
||||
@ -35,4 +36,15 @@ export const allowedMetadataTypesArr = [
|
||||
'bulk',
|
||||
] as const;
|
||||
|
||||
export type allowedMetadataTypes = typeof allowedMetadataTypesArr[number];
|
||||
type SupportedDataSourcesPrefix =
|
||||
| 'mysql_'
|
||||
| 'mssql_'
|
||||
| 'bigquery_'
|
||||
| 'citus_'
|
||||
| 'pg_';
|
||||
|
||||
export type AllMetadataQueries = `${SupportedDataSourcesPrefix}${MetadataQueryType}`;
|
||||
|
||||
export type allowedMetadataTypes =
|
||||
| typeof allowedMetadataTypesArr[number]
|
||||
| AllMetadataQueries;
|
||||
|
@ -3,7 +3,12 @@ import { rest } from 'msw';
|
||||
import { schema } from './schema';
|
||||
import { countries } from './countries_schema';
|
||||
import { metadata } from './metadata';
|
||||
import { tableColumnsResult } from './tables';
|
||||
import {
|
||||
albumTableColumnsResult,
|
||||
userAddressTableColumnsResult,
|
||||
userInfoTableColumnsResult,
|
||||
artistTableColumnsResult,
|
||||
} from './tables';
|
||||
|
||||
const baseUrl = 'http://localhost:8080';
|
||||
|
||||
@ -55,10 +60,26 @@ export const handlers = (url = baseUrl) => [
|
||||
return res(ctx.json({ message: 'success' }));
|
||||
}
|
||||
|
||||
if (body.type === 'pg_create_object_relationship') {
|
||||
return res(ctx.json({ message: 'success' }));
|
||||
}
|
||||
|
||||
if (body.type === 'pg_create_array_relationship') {
|
||||
return res(ctx.json({ message: 'success' }));
|
||||
}
|
||||
|
||||
return res(ctx.json([{ message: 'success' }]));
|
||||
}),
|
||||
|
||||
rest.post(`${url}/v2/query`, (req, res, ctx) => {
|
||||
return res(ctx.json(tableColumnsResult));
|
||||
const reqSql: string = (req?.body as Record<string, any>)?.args?.sql;
|
||||
if (reqSql.toLowerCase().includes('album')) {
|
||||
return res(ctx.json(albumTableColumnsResult));
|
||||
} else if (reqSql.toLowerCase().includes('address')) {
|
||||
return res(ctx.json(userAddressTableColumnsResult));
|
||||
} else if (reqSql.toLowerCase().includes('artist')) {
|
||||
return res(ctx.json(artistTableColumnsResult));
|
||||
}
|
||||
return res(ctx.json(userInfoTableColumnsResult));
|
||||
}),
|
||||
];
|
||||
|
@ -39,6 +39,42 @@ export const metadata = {
|
||||
schema: 'public',
|
||||
name: 'Album',
|
||||
},
|
||||
object_relationships: [
|
||||
{
|
||||
name: 'relt1obj',
|
||||
using: {
|
||||
manual_configuration: {
|
||||
remote_table: {
|
||||
schema: 'user',
|
||||
name: 'userAddress',
|
||||
},
|
||||
insertion_order: null,
|
||||
column_mapping: {
|
||||
AlbumId: 'Id',
|
||||
Title: 'Country',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
array_relationships: [
|
||||
{
|
||||
name: 'relt1array',
|
||||
using: {
|
||||
manual_configuration: {
|
||||
remote_table: {
|
||||
schema: 'public',
|
||||
name: 'Artist',
|
||||
},
|
||||
insertion_order: null,
|
||||
column_mapping: {
|
||||
AlbumId: 'Id',
|
||||
Title: 'Name',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
table: {
|
||||
@ -106,6 +142,18 @@ export const metadata = {
|
||||
name: 'comedies',
|
||||
},
|
||||
},
|
||||
{
|
||||
table: {
|
||||
schema: 'user',
|
||||
name: 'userAddress',
|
||||
},
|
||||
},
|
||||
{
|
||||
table: {
|
||||
schema: 'user',
|
||||
name: 'userInfo',
|
||||
},
|
||||
},
|
||||
],
|
||||
configuration: {
|
||||
connection_info: {
|
||||
|
@ -8,7 +8,7 @@ export const tables = {
|
||||
],
|
||||
};
|
||||
|
||||
export const tableColumnsResult = {
|
||||
export const albumTableColumnsResult = {
|
||||
result_type: 'TuplesOk',
|
||||
result: [
|
||||
['database', 'table_schema', 'table_name', 'column_name', 'data_type'],
|
||||
@ -17,3 +17,37 @@ export const tableColumnsResult = {
|
||||
['chinook', 'public', 'Album', 'ArtistId', 'integer'],
|
||||
],
|
||||
};
|
||||
|
||||
export const artistTableColumnsResult = {
|
||||
result_type: 'TuplesOk',
|
||||
result: [
|
||||
['database', 'table_schema', 'table_name', 'column_name', 'data_type'],
|
||||
['chinook', 'public', 'Artist', 'Id', 'integer'],
|
||||
['chinook', 'public', 'Artist', 'Name', 'character varying'],
|
||||
['chinook', 'public', 'Artist', 'Age', 'integer'],
|
||||
],
|
||||
};
|
||||
|
||||
export const userInfoTableColumnsResult = {
|
||||
result_type: 'TuplesOk',
|
||||
result: [
|
||||
['database', 'table_schema', 'table_name', 'column_name', 'data_type'],
|
||||
['chinook', 'user', 'userInfo', 'Id', 'integer'],
|
||||
['chinook', 'user', 'userInfo', 'FirstName', 'character varying'],
|
||||
['chinook', 'user', 'userInfo', 'LastName', 'character varying'],
|
||||
['chinook', 'user', 'userInfo', 'Age', 'integer'],
|
||||
],
|
||||
};
|
||||
|
||||
export const userAddressTableColumnsResult = {
|
||||
result_type: 'TuplesOk',
|
||||
result: [
|
||||
['database', 'table_schema', 'table_name', 'column_name', 'data_type'],
|
||||
['chinook', 'user', 'userAddress', 'Id', 'integer'],
|
||||
['chinook', 'user', 'userAddress', 'Block', 'character varying'],
|
||||
['chinook', 'user', 'userAddress', 'Street', 'character varying'],
|
||||
['chinook', 'user', 'userAddress', 'City', 'character varying'],
|
||||
['chinook', 'user', 'userAddress', 'Country', 'character varying'],
|
||||
['chinook', 'user', 'userAddress', 'CountryCode', 'integer'],
|
||||
],
|
||||
};
|
||||
|
@ -1,8 +1,7 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import { useRemoteSchema } from '@/features/MetadataAPI';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useTableColumns } from '@/features/SqlQueries/hooks/useTableColumns';
|
||||
import { useTableColumns } from '@/features/SqlQueries';
|
||||
import { InputField, Select } from '@/new-components/Form';
|
||||
import { MapSelector } from '@/new-components/MapSelector';
|
||||
import {
|
||||
|
@ -1,2 +1,3 @@
|
||||
export { dataSourceSqlQueries } from './datasources';
|
||||
export { useTableColumns } from './hooks/useTableColumns';
|
||||
export type { DatasourceSqlQueries } from './datasources';
|
||||
|
Loading…
Reference in New Issue
Block a user