diff --git a/packages/elements/src/__fixtures__/operations/put-todos.ts b/packages/elements/src/__fixtures__/operations/put-todos.ts index 36607dda83..6a195a0978 100644 --- a/packages/elements/src/__fixtures__/operations/put-todos.ts +++ b/packages/elements/src/__fixtures__/operations/put-todos.ts @@ -373,6 +373,35 @@ export const httpOperation: IHttpOperation = { name: 'account-id', style: HttpParamStyles.Simple, required: true, + examples: [ + { + value: 'example id', + key: 'example', + }, + ], + }, + { + schema: { + type: 'string', + description: 'Your Stoplight account id', + }, + name: 'message-id', + style: HttpParamStyles.Simple, + required: true, + examples: [ + { + value: 'example value', + key: 'example 1', + }, + { + value: 'another example', + key: 'example 2', + }, + { + value: 'something else', + key: 'example 3', + }, + ], }, ], path: [ diff --git a/packages/elements/src/components/TryIt/BasicSend.tsx b/packages/elements/src/components/TryIt/BasicSend.tsx index 638bf5c607..cef7daad94 100644 --- a/packages/elements/src/components/TryIt/BasicSend.tsx +++ b/packages/elements/src/components/TryIt/BasicSend.tsx @@ -7,7 +7,7 @@ import * as React from 'react'; import { HttpCodeDescriptions } from '../../constants'; import { getHttpCodeColor } from '../../utils/http'; -import { OperationParameters } from './OperationParameters'; +import { initialParameterValues, OperationParameters } from './OperationParameters'; export interface BasicSendProps { httpOperation: IHttpOperation; @@ -32,7 +32,9 @@ export const BasicSend: React.FC = ({ httpOperation }) => { headers: httpOperation.request?.headers, }; - const [parameterValues, setParameterValues] = React.useState>({}); + const [parameterValues, setParameterValues] = React.useState>( + initialParameterValues(operationParameters), + ); if (!server) return null; diff --git a/packages/elements/src/components/TryIt/OperationParameters.tsx b/packages/elements/src/components/TryIt/OperationParameters.tsx index bd51c47d50..d163220094 100644 --- a/packages/elements/src/components/TryIt/OperationParameters.tsx +++ b/packages/elements/src/components/TryIt/OperationParameters.tsx @@ -1,9 +1,16 @@ -import { Flex, Input, Panel, Text } from '@stoplight/mosaic'; -import { Dictionary, IHttpHeaderParam, IHttpPathParam, IHttpQueryParam } from '@stoplight/types'; -import { sortBy } from 'lodash'; +import { Flex, Input, Panel, Select, Text } from '@stoplight/mosaic'; +import { + Dictionary, + IHttpHeaderParam, + IHttpPathParam, + IHttpQueryParam, + INodeExample, + INodeExternalExample, +} from '@stoplight/types'; +import { map, sortBy } from 'lodash'; import * as React from 'react'; -interface OperationParameters { +export interface OperationParameters { path?: IHttpPathParam[]; query?: IHttpQueryParam[]; headers?: IHttpHeaderParam[]; @@ -15,37 +22,111 @@ interface OperationParametersProps { onChangeValues: (newValues: Dictionary) => void; } +const booleanOptions = [ + { label: 'Not Set', value: '' }, + { label: 'False', value: 'false' }, + { label: 'True', value: 'true' }, +]; + +export function flattenParameters(parameters: OperationParameters) { + const pathParameters = sortBy(parameters.path ?? [], ['name']); + const queryParameters = sortBy(parameters.query ?? [], ['name']); + const headerParameters = sortBy(parameters.headers ?? [], ['name']); + return [...pathParameters, ...queryParameters, ...headerParameters]; +} + +export function initialParameterValues(operationParameters: OperationParameters) { + const parameters = flattenParameters(operationParameters); + const enums = parameters + .filter(p => p.schema?.enum) + .reduce((params, p) => { + if (p.schema?.enum?.length) { + return { ...params, [p.name]: String(p.schema?.enum[0]) }; + } else { + return { ...params }; + } + }, {}); + const examples = parameters + .filter(p => Array.isArray(p.examples)) + .reduce((params, p) => { + if (p.examples?.length) { + return { + ...params, + [p.name]: exampleValue(p.examples[0]), + }; + } else { + return { ...params }; + } + }, {}); + + return { + // order matters - enums should be override examples + ...examples, + ...enums, + }; +} + +function exampleValue(example: INodeExample | INodeExternalExample) { + return 'value' in example + ? (example as INodeExample).value + : 'externalValue' in example + ? (example as INodeExternalExample).externalValue + : ''; +} + export const OperationParameters: React.FC = ({ operationParameters, values, onChangeValues, }) => { - const pathParameters = sortBy(operationParameters.path ?? [], ['name']); - const queryParameters = sortBy(operationParameters.query ?? [], ['name']); - const headerParameters = sortBy(operationParameters.headers ?? [], ['name']); - const parameters = [...pathParameters, ...queryParameters, ...headerParameters]; + const parameters = flattenParameters(operationParameters); + + const onChange = React.useCallback( + parameter => (e: React.FormEvent | React.ChangeEvent) => { + const newValue = e.currentTarget.value; + onChangeValues({ ...values, [parameter.name]: newValue }); + }, + [onChangeValues, values], + ); return ( Parameters {parameters.map(parameter => { + const options = + parameter.schema?.type === 'boolean' + ? booleanOptions + : parameter.schema?.enum !== undefined + ? map(parameter.schema.enum, v => { + return Number.isNaN(Number(v)) ? String(v) : Number(v); + }) + : null; + const exampleOptions = + parameter.examples?.length && parameter.examples.length > 1 + ? parameter.examples.map(example => ({ label: example.key, value: exampleValue(example) })) + : null; return ( - + : - { - const newValue = e.currentTarget.value; - onChangeValues({ ...values, [parameter.name]: newValue }); - }} - /> + {options ? ( + + {exampleOptions &&