fix: mssql support for create rest endpoint feature

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/9876
GitOrigin-RevId: f3a027a872e70c8c94b6b6b081644c30bcfc72cb
This commit is contained in:
Daniele Cammareri 2023-07-19 10:45:56 +02:00 committed by hasura-bot
parent 4f6ef10e17
commit a9468f620d
4 changed files with 205 additions and 93 deletions

View File

@ -38,6 +38,14 @@ export const RestEndpointModal = (props: RestEndpointModalProps) => {
[]
);
const filteredEndpoints = React.useMemo(
() =>
ENDPOINTS.filter(
method => !!tableEndpointDefinitions[method.value as EndpointType]
),
[tableEndpointDefinitions]
);
return (
<Dialog
hasBackdrop
@ -86,82 +94,103 @@ export const RestEndpointModal = (props: RestEndpointModalProps) => {
}
>
<div className="p-4 flex flex-col gap-4">
<CardedTable
columns={[
<Checkbox
checked={selectedMethods.length === ENDPOINTS.length}
onCheckedChange={checked => {
if (checked) {
setSelectedMethods(ENDPOINTS.map(endpoint => endpoint.value));
} else {
setSelectedMethods([]);
}
}}
/>,
'OPERATION',
'METHOD',
'PATH',
]}
data={ENDPOINTS.map(method => {
const endpointDefinition =
tableEndpointDefinitions[method.value as EndpointType];
return [
{filteredEndpoints.length > 0 && (
<CardedTable
columns={[
<Checkbox
checked={selectedMethods.includes(method.value as EndpointType)}
disabled={endpointDefinition?.exists}
checked={selectedMethods.length === ENDPOINTS.length}
onCheckedChange={checked => {
if (checked) {
setSelectedMethods([
...selectedMethods,
method.value as EndpointType,
]);
} else {
setSelectedMethods(
selectedMethods.filter(
selectedMethod => selectedMethod !== method.value
)
ENDPOINTS.map(endpoint => endpoint.value)
);
} else {
setSelectedMethods([]);
}
}}
/>,
'OPERATION',
'METHOD',
'PATH',
]}
data={filteredEndpoints.map(method => {
const endpointDefinition =
tableEndpointDefinitions[method.value as EndpointType];
return [
<Checkbox
checked={selectedMethods.includes(
method.value as EndpointType
)}
disabled={endpointDefinition?.exists}
onCheckedChange={checked => {
if (checked) {
setSelectedMethods([
...selectedMethods,
method.value as EndpointType,
]);
} else {
setSelectedMethods(
selectedMethods.filter(
selectedMethod => selectedMethod !== method.value
)
);
}
}}
/>,
<div>
{endpointDefinition?.exists ? (
<Link
to={{
pathname: `/api/rest/details/${endpointDefinition.restEndpoint?.name}`,
state: {
...endpointDefinition.restEndpoint,
currentQuery: endpointDefinition.query.query,
},
}}
>
{method.label}{' '}
<FaExternalLinkAlt className="relative ml-1 -top-0.5" />
</Link>
) : (
method.label
)}
</div>,
<Badge color={method.color}>
{endpointDefinition?.restEndpoint?.methods?.join(', ')}
</Badge>,
<div>/{endpointDefinition?.restEndpoint?.url ?? 'N/A'}</div>,
];
})}
/>
)}
{filteredEndpoints.length === 0 && (
<IndicatorCard
showIcon
children={
<div>
{endpointDefinition?.exists ? (
<Link
to={{
pathname: `/api/rest/details/${endpointDefinition.restEndpoint?.name}`,
state: {
...endpointDefinition.restEndpoint,
currentQuery: endpointDefinition.query.query,
},
}}
>
{method.label}{' '}
<FaExternalLinkAlt className="relative ml-1 -top-0.5" />
</Link>
) : (
method.label
)}
</div>,
<Badge color={method.color}>
{endpointDefinition?.restEndpoint?.methods?.join(', ')}
</Badge>,
<div>/{endpointDefinition?.restEndpoint?.url ?? 'N/A'}</div>,
];
})}
/>
<IndicatorCard
showIcon
children={
<div>
Creating REST Endpoints will add metadata entries to your Hasura
project
<LearnMoreLink href="https://hasura.io/docs/latest/restified/overview" />
</div>
}
status="info"
customIcon={FaExclamation}
/>
No REST endpoints can be created for this table
<LearnMoreLink href="https://hasura.io/docs/latest/restified/overview" />
</div>
}
status="negative"
customIcon={FaExclamation}
/>
)}
{filteredEndpoints.length > 0 && (
<IndicatorCard
showIcon
children={
<div>
Creating REST Endpoints will add metadata entries to your Hasura
project
<LearnMoreLink href="https://hasura.io/docs/latest/restified/overview" />
</div>
}
status="info"
customIcon={FaExclamation}
/>
)}
</div>
</Dialog>
);

View File

@ -295,8 +295,8 @@ export const getValueWithType = (variableData: VariableState) => {
// NOTE: bool_exp are of JSON type, so pass it as JSON object (issue: https://github.com/hasura/graphql-engine/issues/9671)
if (
(variableData.type.endsWith('_exp') ||
variableData.type.endsWith('_input')) &&
(variableData.type.includes('_exp') ||
variableData.type.includes('_input')) &&
isJsonString(variableData.value)
) {
return JSON.parse(variableData.value);

View File

@ -9,7 +9,14 @@ export const dataInitialData: Partial<Metadata['metadata']> = {
{
name: 'default',
kind: 'postgres',
tables: [],
tables: [
{
table: {
name: 'user',
schema: 'public',
},
},
],
configuration: {
connection_info: {
database_url: {

View File

@ -1,7 +1,12 @@
import { Microfiber } from 'microfiber';
import { useIntrospectionSchema } from '../../../components/Services/Actions/Common/components/ImportTypesModal/useIntrospectionSchema';
import { useEffect, useState } from 'react';
import { Query, RestEndpoint } from '../../hasura-metadata-types';
import {
MetadataTable,
Query,
RestEndpoint,
Source,
} from '../../hasura-metadata-types';
import {
Operation,
generateDeleteEndpoint,
@ -11,7 +16,7 @@ import {
generateViewEndpoint,
} from './utils';
import { formatSdl } from 'format-graphql';
import { useMetadata } from '../../MetadataAPI';
import { useMetadata } from '../../hasura-metadata-api';
export type EndpointType = 'READ' | 'READ_ALL' | 'CREATE' | 'UPDATE' | 'DELETE';
@ -26,8 +31,10 @@ type EndpointDefinitions = {
>;
};
type Table = MetadataTable & { table: { name: string; schema: string } };
export type Generator = {
regExp: RegExp;
operationName: (source: Source, table: Table) => string;
generator: (
root: string,
table: string,
@ -36,6 +43,21 @@ export type Generator = {
) => EndpointDefinition;
};
export const getSchemaPrefix = (source: Source, table: Table) => {
const schemaName = table.table.schema;
if (source.kind === 'mssql' && schemaName === 'dbo') {
return '';
}
if (
['cockroach', 'postgres', 'citus'].includes(source.kind) &&
schemaName === 'public'
) {
return '';
}
return `${table?.table?.schema}_`;
};
export const getOperations = (microfiber: any) => {
const queryType = microfiber.getQueryType();
const mutationType = microfiber.getMutationType();
@ -90,24 +112,59 @@ export const getOperations = (microfiber: any) => {
const generators: Record<EndpointType, Generator> = {
READ: {
regExp: /fetch data from the table: "(.+)" using primary key columns$/,
operationName: (source, table) => {
if (table?.configuration?.custom_root_fields?.select_by_pk) {
return table?.configuration?.custom_root_fields?.select_by_pk;
}
const schemaPrefix = getSchemaPrefix(source, table);
const tableName = table.configuration?.custom_name ?? table?.table?.name;
return `${schemaPrefix}${tableName}_by_pk`;
},
generator: generateViewEndpoint,
},
READ_ALL: {
regExp: /fetch data from the table: "(.+)"$/,
operationName: (source, table) => {
if (table?.configuration?.custom_root_fields?.select) {
return table?.configuration?.custom_root_fields?.select;
}
const schemaPrefix = getSchemaPrefix(source, table);
const tableName = table.configuration?.custom_name ?? table?.table?.name;
return `${schemaPrefix}${tableName}`;
},
generator: generateViewAllEndpoint,
},
CREATE: {
regExp: /insert a single row into the table: "(.+)"$/,
operationName: (source, table) => {
if (table?.configuration?.custom_root_fields?.insert_one) {
return table?.configuration?.custom_root_fields?.insert_one;
}
const schemaPrefix = getSchemaPrefix(source, table);
const tableName = table.configuration?.custom_name ?? table?.table?.name;
return `insert_${schemaPrefix}${tableName}_one`;
},
generator: generateInsertEndpoint,
},
UPDATE: {
regExp: /update single row of the table: "(.+)"$/,
operationName: (source, table) => {
if (table?.configuration?.custom_root_fields?.update_by_pk) {
return table?.configuration?.custom_root_fields?.update_by_pk;
}
const schemaPrefix = getSchemaPrefix(source, table);
const tableName = table.configuration?.custom_name ?? table?.table?.name;
return `update_${schemaPrefix}${tableName}_by_pk`;
},
generator: generateUpdateEndpoint,
},
DELETE: {
regExp: /delete single row from the table: "(.+)"$/,
operationName: (source, table) => {
if (table?.configuration?.custom_root_fields?.delete_by_pk) {
return table?.configuration?.custom_root_fields?.delete_by_pk;
}
const schemaPrefix = getSchemaPrefix(source, table);
const tableName = table.configuration?.custom_name ?? table?.table?.name;
return `delete_${schemaPrefix}${tableName}_by_pk`;
},
generator: generateDeleteEndpoint,
},
};
@ -119,12 +176,15 @@ export const useRestEndpointDefinitions = () => {
error,
} = useIntrospectionSchema();
const { data: metadata } = useMetadata();
const { data: metadata } = useMetadata(m => ({
restEndpoints: m.metadata?.rest_endpoints,
sources: m.metadata?.sources,
}));
const [data, setData] = useState<EndpointDefinitions>();
useEffect(() => {
const existingRestEndpoints = metadata?.metadata?.rest_endpoints || [];
const existingRestEndpoints = metadata?.restEndpoints || [];
if (introspectionSchema) {
const response: EndpointDefinitions = {};
@ -137,25 +197,40 @@ export const useRestEndpointDefinitions = () => {
return;
}
for (const operation of operations.operations) {
for (const endpointType in generators) {
const match = operation.description?.match(
generators[endpointType as EndpointType].regExp
);
const table = match?.[1];
for (const source of metadata?.sources || []) {
const sourcePrefix = source.customization?.root_fields?.prefix || '';
if (match) {
const definition = generators[
endpointType as EndpointType
].generator(operations.root, table, operation, microfiber);
const sourceSuffix = source.customization?.root_fields?.suffix || '';
for (const table of source.tables as Table[]) {
for (const [type, generator] of Object.entries(generators)) {
const operationName = `${sourcePrefix}${generator.operationName(
source,
table
)}${sourceSuffix}`;
const operation = operations.operations.find(
operation => operation.name === operationName
);
if (!operation) {
continue;
}
const tableName = table?.table?.name;
const definition = generators[type as EndpointType].generator(
operations.root,
tableName,
operation,
microfiber
);
if (definition.query.query) {
definition.query.query = formatSdl(definition.query.query);
}
response[table] = {
...(response[table] || {}),
[endpointType]: {
response[tableName] = {
...(response[tableName] || {}),
[type]: {
...definition,
exists: existingRestEndpoints.some(
endpoint =>
@ -167,6 +242,7 @@ export const useRestEndpointDefinitions = () => {
}
}
}
setData(response);
}
}, [introspectionSchema, metadata]);