console: add support for array as query param in import from OAS

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/7630
GitOrigin-RevId: ce8a374b055c4ced7bb6fddebdc11016a5787d23
This commit is contained in:
Daniele Cammareri 2023-01-24 16:30:35 +01:00 committed by hasura-bot
parent 9138a09d3e
commit bd0533fcf3
7 changed files with 134 additions and 28 deletions

View File

@ -35,9 +35,10 @@ const RequestUrlEditor: React.FC<RequestUrlEditorProps> = ({
requestQueryParamsOnChange,
}) => {
const [localUrl, setLocalUrl] = useState<string>(requestUrl);
const [queryParamsType, setQueryParamstype] = useState(
typeof requestQueryParams === 'string' ? 'url-string' : 'key-value'
);
const [queryParamsType, setQueryParamstype] = useState<
QueryParmsOptions['value']
>(typeof requestQueryParams === 'string' ? 'url-string' : 'key-value');
const [keyValueQueryParams, setKeyValueQueryParams] = useState(
typeof requestQueryParams !== 'string'
? requestQueryParams
@ -51,6 +52,22 @@ const RequestUrlEditor: React.FC<RequestUrlEditorProps> = ({
const [localQueryParams, setLocalQueryParams] =
useState<QueryParams>(requestQueryParams);
useEffect(() => {
setQueryParamstype(
typeof requestQueryParams === 'string' ? 'url-string' : 'key-value'
);
setKeyValueQueryParams(
typeof requestQueryParams !== 'string'
? requestQueryParams
: keyValueQueryParams
);
setStringQueryParams(
typeof requestQueryParams === 'string'
? requestQueryParams
: stringQueryParams
);
}, [requestQueryParams]);
const executionOptions: QueryParmsOptions[] = [
{
value: 'key-value',

View File

@ -394,13 +394,8 @@ const AddAction: React.FC<AddActionProps> = ({
transformDispatch(setRequestSampleInput(sampleInput));
requestMethodOnChange(method);
requestUrlTransformOnChange(true);
requestUrlOnChange(path);
requestQueryParamsOnChange(
queryParams.map(name => ({
name,
value: `{{$body.input.${name}}}`,
}))
);
requestUrlOnChange(path.replace(/\{([^}]+)\}/g, '{{$body.input.$1}}'));
requestQueryParamsOnChange(queryParams);
setHeaders(
actionHeaders.map(name => ({
name,

View File

@ -26,6 +26,12 @@ const getArgObjFromDefinition = (
if (type === 'Int' || type === 'Float' || type === 'BigInt')
return { [name]: 10 };
if (type === 'Boolean') return { [name]: false };
if (type === '[String]' || type === '[ID]') {
return { [name]: ['foo', 'bar'] };
}
if (type === '[Int]' || type === '[Float]' || type === '[BigInt]') {
return { [name]: [10, 20] };
}
const userDefType = typesdef?.types.find(
(t: Record<string, any>) => t.name === type

View File

@ -17,7 +17,7 @@ export type GeneratedAction = {
responseTransforms: string;
sampleInput: string;
headers: string[];
queryParams: string[];
queryParams: string | { name: string; value: string }[];
};
export type Result = Awaited<ReturnType<typeof createGraphQLSchema>>;
@ -25,3 +25,5 @@ export type Operation = Result['data']['operations'][0];
export type DataDefinition = Operation['responseDefinition'];
export type SubDefinition = Operation['responseDefinition']['subDefinitions'];
export type OperationParameters = Operation['parameters'];

View File

@ -1,6 +1,34 @@
import { Oas3 } from 'openapi-to-graphql';
import { ParameterObject } from 'openapi-to-graphql/dist/types/oas3';
import petStore from './petstore.json';
import { getOperations, generateAction } from './utils';
import { getOperations, generateAction, generateQueryParams } from './utils';
const tags: ParameterObject = {
name: 'tags',
in: 'query',
description: 'Tags to filter by',
required: false,
explode: true,
schema: {
type: 'array',
items: {
type: 'string',
},
},
};
const status: ParameterObject = {
name: 'status',
in: 'query',
description: 'Status values that need to be considered for filter',
required: false,
explode: true,
schema: {
type: 'string',
default: 'available',
enum: ['available', 'pending', 'sold'],
},
};
describe('getOperations', () => {
it('should return an array of operations', async () => {
@ -44,17 +72,22 @@ describe('generateAction', () => {
path: '/pets',
requestTransforms: '',
responseTransforms: '',
sampleInput:
'{\n' +
' "action": {\n' +
' "name": "findPets"\n' +
' },\n' +
' "input": {\n' +
' "limit": 10\n' +
' }\n' +
'}',
sampleInput: JSON.stringify(
{
action: {
name: 'findPets',
},
input: {
limit: 10,
tags: ['foo', 'bar'],
},
},
null,
2
),
headers: [],
queryParams: ['tags', 'limit'],
queryParams:
'{{ concat ([concat({{ range _, x := $body.input.tags }} "tags={{x}}&" {{ end }}), "limit={{$body.input.limit}}&"]) }}',
});
});
@ -64,3 +97,24 @@ describe('generateAction', () => {
).rejects.toThrow();
});
});
describe('generateQueryParams', () => {
it('should generate query params with one non-array param', async () => {
const queryParams = await generateQueryParams([status]);
expect(queryParams).toStrictEqual([
{ name: 'status', value: '{{$body.input.status}}' },
]);
});
it('should generate query params with one non-array param and one array param', async () => {
const queryParams = await generateQueryParams([status, tags]);
expect(queryParams).toBe(
'{{ concat (["status={{$body.input.status}}&", concat({{ range _, x := $body.input.tags }} "tags={{x}}&" {{ end }})]) }}'
);
});
it('should generate query params with one non-array param and one array param (reversed)', async () => {
const queryParams = await generateQueryParams([tags, status]);
expect(queryParams).toBe(
'{{ concat ([concat({{ range _, x := $body.input.tags }} "tags={{x}}&" {{ end }}), "status={{$body.input.status}}&"]) }}'
);
});
});

View File

@ -22,6 +22,7 @@ import {
DataDefinition,
GeneratedAction,
Operation,
OperationParameters,
Result,
SubDefinition,
} from './types';
@ -55,6 +56,37 @@ export const formatQuery = (query?: string): string => {
}
};
const isSchemaObject = (
schema: SchemaObject | ReferenceObject | undefined
): schema is SchemaObject => schema !== undefined && 'type' in schema;
// {{ concat ([concat({{ range _, x := ["apple", "banana"] }} "tags={{x}}&" {{ end }})]) }}
export const generateQueryParams = (parameters: OperationParameters) => {
const isThereArray = parameters.some(parameter => {
return (
isSchemaObject(parameter?.schema) && parameter.schema.type === 'array'
);
});
if (isThereArray) {
const stringParams = parameters.map(param => {
if (isSchemaObject(param?.schema) && param.schema.type === 'array') {
return `concat({{ range _, x := $body.input.${param.name} }} "${param.name}={{x}}&" {{ end }})`;
}
return `"${param.name}={{$body.input.${param.name}}}&"`;
});
return `{{ concat ([${stringParams.join(', ')}]) }}`.replace(/&&/, '&');
}
const parameterNames =
parameters
?.filter(param => param.in === 'query')
?.map(param => param.name) || [];
return parameterNames.map(name => ({
name,
value: `{{$body.input.${name}}}`,
}));
};
const lowerCaseFirstLetter = (str: string): string => {
return str ? str.charAt(0).toLowerCase() + str.slice(1) : '';
};
@ -370,10 +402,8 @@ export const translateAction = (
?.filter(param => param.in === 'header')
?.map(param => param.name) || [];
const queryParams =
operation.parameters
?.filter(param => param.in === 'query')
?.map(param => param.name) || [];
const queryParams = generateQueryParams(operation.parameters ?? []);
return {
operationId: operation.operationId,
actionType:

View File

@ -134,8 +134,10 @@ export const getTypesFromSdl = sdl => {
schemaAst.definitions.forEach(def => {
const typeDef = getTypeFromAstDef(def);
typeDefinition.error = typeDef.error;
typeDefinition.types.push(typeDef);
if (typeDef) {
typeDefinition.error = typeDef.error;
typeDefinition.types.push(typeDef);
}
});
return typeDefinition;