From 28ff15f5b8d90e53750acea40a69a696d2ef4dc1 Mon Sep 17 00:00:00 2001 From: Aidan Cunniffe Date: Fri, 15 Nov 2019 08:56:53 -0500 Subject: [PATCH 1/5] Updated UI for design spec flow --- webapp/package.json | 1 + .../loaders/ExampleDrivenSpecLoader.js | 306 +++++++++++------- webapp/src/components/requests/DocGrid.js | 6 +- webapp/src/components/shared/JsonTextarea.js | 27 ++ webapp/yarn.lock | 31 ++ 5 files changed, 244 insertions(+), 127 deletions(-) create mode 100644 webapp/src/components/shared/JsonTextarea.js diff --git a/webapp/package.json b/webapp/package.json index 2f05ab6c41..b08fd846e2 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -55,6 +55,7 @@ "postcss-safe-parser": "4.0.1", "prop-types": "latest", "react": "^16.8.6", + "react-ace": "^8.0.0", "react-dom": "^16.8.6", "react-helmet": "5.2.1", "react-hook-form": "^3.27.0", diff --git a/webapp/src/components/loaders/ExampleDrivenSpecLoader.js b/webapp/src/components/loaders/ExampleDrivenSpecLoader.js index e1db730e7e..cc49d116b5 100644 --- a/webapp/src/components/loaders/ExampleDrivenSpecLoader.js +++ b/webapp/src/components/loaders/ExampleDrivenSpecLoader.js @@ -1,42 +1,61 @@ -import React from 'react'; -import { InitialRfcCommandsStore } from '../../contexts/InitialRfcCommandsContext'; -import { TrafficAndDiffSessionStore } from '../../contexts/TrafficAndDiffSessionContext'; -import { LocalDiffRfcStore, withRfcContext } from '../../contexts/RfcContext'; -import { Route, Switch } from 'react-router-dom'; -import { UrlsX } from '../paths/NewUnmatchedUrlWizard'; +import React, {useState} from 'react'; +import {InitialRfcCommandsStore} from '../../contexts/InitialRfcCommandsContext'; +import {TrafficAndDiffSessionStore} from '../../contexts/TrafficAndDiffSessionContext'; +import {LocalDiffRfcStore, withRfcContext} from '../../contexts/RfcContext'; +import {Route, Switch} from 'react-router-dom'; +import {UrlsX} from '../paths/NewUnmatchedUrlWizard'; import RequestDiffX from '../diff/RequestDiffX'; -import { NavigationStore } from '../../contexts/NavigationContext'; -import { routerPaths } from '../../routes'; -import { SpecOverview } from '../routes/local'; +import {NavigationStore} from '../../contexts/NavigationContext'; +import {routerPaths} from '../../routes'; +import {SpecOverview} from '../routes/local'; import NewBehavior from '../navigation/NewBehavior'; -import { RequestsDetailsPage } from '../requests/EndpointPage'; -import { TextField, Button, Paper, Select, Grid, Typography } from '@material-ui/core'; +import {RequestsDetailsPage} from '../requests/EndpointPage'; +import { + TextField, + Button, + Paper, + Select, + Grid, + Typography, + DialogTitle, + DialogContent, + DialogActions +} from '@material-ui/core'; import useForm from 'react-hook-form'; -import { STATUS_CODES } from 'http'; -import { InteractionDiffer, toInteraction } from '../../engine'; +import {STATUS_CODES} from 'http'; +import {InteractionDiffer, toInteraction} from '../../engine'; +import Dialog from '@material-ui/core/Dialog'; +import DialogContentText from '@material-ui/core/DialogContentText'; +import {DiffDocGrid} from '../requests/DocGrid'; +import {DocSubGroup} from '../requests/DocSubGroup'; +import JsonTextarea from '../shared/JsonTextarea'; export const basePath = `/spec-by-example`; + function parseLoosely(nonEmptyBodyString) { if (!nonEmptyBodyString) { return [true]; } try { - return [true, JSON.parse(nonEmptyBodyString)] + return [true, JSON.parse(nonEmptyBodyString)]; } catch (e) { try { - const result = eval(`(${nonEmptyBodyString})`) - console.log({ result }) + const result = eval(`(${nonEmptyBodyString})`); + console.log({result}); if (result) { - return [true, result] + return [true, result]; } - return [false] + return [false]; } catch (e) { - return [false] + return [false]; } } } + function ExampleBuilderBase(props) { - const { register, handleSubmit, getValues, watch } = useForm({ + const [showExampleBuilder, setShowExampleBuilder] = useState(false); + + const {register, handleSubmit, setValue, getValues, watch} = useForm({ defaultValues: { request: { method: 'GET', @@ -47,21 +66,22 @@ function ExampleBuilderBase(props) { } } }); + // need watch() to update getValues() on change - const watchAll = watch() - const formValues = getValues({ nest: true }) + const watchAll = watch(); + const formValues = getValues({nest: true}); const parseFormState = state => { - console.log({ state }); + console.log({state}); const [parsedRequestBodySuccess, parsedRequestBody] = parseLoosely(state.request.body); const request = { method: state.request.method, url: state.request.url || '/', headers: {} - } + }; if (parsedRequestBodySuccess) { - request.headers['content-type'] = 'application/json' - request.body = parsedRequestBody + request.headers['content-type'] = 'application/json'; + request.body = parsedRequestBody; } @@ -69,24 +89,24 @@ function ExampleBuilderBase(props) { const response = { statusCode: parseInt(state.response.statusCode, 10), headers: {}, - } + }; if (parsedResponseBodySuccess) { - response.headers['content-type'] = 'application/json' - response.body = parsedResponseBody + response.headers['content-type'] = 'application/json'; + response.body = parsedResponseBody; } const sample = { request, response - } + }; - const { rfcService, rfcId } = props; - const rfcState = rfcService.currentState(rfcId) + const {rfcService, rfcId} = props; + const rfcState = rfcService.currentState(rfcId); const interactionDiffer = new InteractionDiffer(rfcState); - const interaction = toInteraction(sample) - const hasUnrecognizedPath = interactionDiffer.hasUnrecognizedPath(interaction) + const interaction = toInteraction(sample); + const hasUnrecognizedPath = interactionDiffer.hasUnrecognizedPath(interaction); const hasDiff = interactionDiffer.hasDiff(interaction); const result = { state, @@ -95,72 +115,110 @@ function ExampleBuilderBase(props) { hasDiff, parsedRequestBodySuccess, parsedResponseBodySuccess, - } - console.log(result) - return result - } + }; + console.log(result); + return result; + }; const onSubmit = data => { - const { onSampleAdded } = props; - const { sample } = parseFormState(data) + const {onSampleAdded} = props; + const {sample} = parseFormState(data); - onSampleAdded(sample) + onSampleAdded(sample); }; - const { specService } = props; + const {specService} = props; const { hasDiff, hasUnrecognizedPath, parsedRequestBodySuccess, parsedResponseBodySuccess } = parseFormState(formValues); return (
- + + setShowExampleBuilder(false)} + >
- - - Request -
- -
-
- - {hasUnrecognizedPath ?
this is a new URL
: null} -
-
- -
-
- - Response -
- -
-
- -
-
-
-
- - {hasDiff ?
this request does not match the spec
: null} -
+ + Add Example + + + + Design your API by example. Optic will use the examples to guide you through the development of your API + specification. + + + + Request + +
+ + + + {hasUnrecognizedPath ?
this is a new URL
: null} + +
+
+ + + setValue('request.body', value)} + value={formValues.request.body} + name="request.body" + /> + + + )} + right={( + <> + Response + +
+ +
+
+ + setValue('response.body', value)} + value={formValues.response.body} + /> + + + )} + /> + + + {hasDiff ?
this request does not match the spec
: null} +
+
-
- + +
- ) + ); } -const ExampleBuilder = withRfcContext(ExampleBuilderBase) + +const ExampleBuilder = withRfcContext(ExampleBuilderBase); class ExampleDrivenSpecLoader extends React.Component { @@ -173,15 +231,15 @@ class ExampleDrivenSpecLoader extends React.Component { }; handleSampleAdded = (sample) => { - console.log({ sample }) + console.log({sample}); const session = { ...this.state.session, samples: [...this.state.session.samples, sample] - } + }; this.setState({ session - }) - } + }); + }; render() { const sessionId = 'someSessionId'; @@ -189,89 +247,89 @@ class ExampleDrivenSpecLoader extends React.Component { loadSession: (sessionId) => { return Promise.resolve({ diffStateResponse: { - diffState: { - - } + diffState: {} }, sessionResponse: { session: this.state.session } - }) + }); }, listEvents() { - return Promise.resolve([]) + return Promise.resolve([]); }, listSessions() { - return Promise.resolve({ sessions: [sessionId] }) + return Promise.resolve({sessions: [sessionId]}); }, saveEvents: (eventStore, rfcId) => { - const events = eventStore.serializeEvents(rfcId) + const events = eventStore.serializeEvents(rfcId); this.setState({ events - }) + }); }, listExamples: (requestId) => { - return Promise.resolve({ examples: this.state.examples[requestId] || [] }) + return Promise.resolve({examples: this.state.examples[requestId] || []}); }, saveExample: (interaction, requestId) => { - const examples = this.state.examples - const requestExamples = examples[requestId] || [] - requestExamples.push(interaction) - examples[requestId] = requestExamples - this.setState({ examples }) + const examples = this.state.examples; + const requestExamples = examples[requestId] || []; + requestExamples.push(interaction); + examples[requestId] = requestExamples; + this.setState({examples}); }, - saveDiffState: () => { } - } + saveDiffState: () => { + } + }; - const diffBasePath = routerPaths.diff(basePath) + const diffBasePath = routerPaths.diff(basePath); //@todo add before modal here eventually const ExampleSessionsSpecOverview = () => { return ( } /> - ) - } + notificationAreaComponent={}/> + ); + }; function SessionWrapper(props) { - const { match } = props; - const { sessionId } = match.params; + const {match} = props; + const {sessionId} = match.params; return ( - - + + - ) + ); } return ( - - - + + + - ) + ); } } class ExampleDrivenSpecLoaderRoutes extends React.Component { render() { - const { match } = this.props + const {match} = this.props; return ( - + - ) + ); } } -export default ExampleDrivenSpecLoaderRoutes +export default ExampleDrivenSpecLoaderRoutes; diff --git a/webapp/src/components/requests/DocGrid.js b/webapp/src/components/requests/DocGrid.js index 2ffdcb045a..8d48982a7c 100644 --- a/webapp/src/components/requests/DocGrid.js +++ b/webapp/src/components/requests/DocGrid.js @@ -88,17 +88,17 @@ export function DocGrid({left, right, style}) { ); } -export function DiffDocGrid({left, leftColor, right, style}) { +export function DiffDocGrid({left, leftColor, right, style, colMaxWidth}) { const classes = useStyles(); return ( -
{left}
+
{left}
-
{right}
+
{right}
); diff --git a/webapp/src/components/shared/JsonTextarea.js b/webapp/src/components/shared/JsonTextarea.js new file mode 100644 index 0000000000..2446d277c0 --- /dev/null +++ b/webapp/src/components/shared/JsonTextarea.js @@ -0,0 +1,27 @@ +import React from 'react'; +import withStyles from '@material-ui/core/styles/withStyles'; +import AceEditor from 'react-ace'; +import 'ace-builds/src-noconflict/mode-json'; +import 'ace-builds/src-noconflict/theme-github'; + +const styles = theme => ({ + root: {}, +}); + +function TypeModal({value, onChange}) { + + return ( + + ); +} + +export default withStyles(styles)(TypeModal); diff --git a/webapp/yarn.lock b/webapp/yarn.lock index 2d7c074be3..d8a783b049 100644 --- a/webapp/yarn.lock +++ b/webapp/yarn.lock @@ -2141,6 +2141,11 @@ accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7: mime-types "~2.1.24" negotiator "0.6.2" +ace-builds@^1.4.6: + version "1.4.7" + resolved "https://registry.yarnpkg.com/ace-builds/-/ace-builds-1.4.7.tgz#56e5465270b6c48a48d30e70d6b8f6b92fbf2b08" + integrity sha512-gwQGVFewBopRLho08BfahyvRa9FlB43JUig5ItAKTYc9kJJsbA9QNz75p28QtQomoPQ9rJx82ymL21x4ZSZmdg== + acorn-dynamic-import@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/acorn-dynamic-import/-/acorn-dynamic-import-4.0.0.tgz#482210140582a36b83c3e342e1cfebcaa9240948" @@ -4311,6 +4316,11 @@ detect-port@^1.3.0: address "^1.0.1" debug "^2.6.0" +diff-match-patch@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/diff-match-patch/-/diff-match-patch-1.0.4.tgz#6ac4b55237463761c4daf0dc603eb869124744b1" + integrity sha512-Uv3SW8bmH9nAtHKaKSanOQmj2DnlH65fUpcrMdfdaOxUG02QQ4YGZ8AE7kKOMisF7UqvOlGKVYWRvezdncW9lg== + diff-sequences@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.9.0.tgz#5715d6244e2aa65f48bba0bc972db0b0b11e95b5" @@ -7435,11 +7445,21 @@ lodash.debounce@^4.0.8: resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= +lodash.get@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" + integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= + lodash.groupby@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.groupby/-/lodash.groupby-4.6.0.tgz#0b08a1dcf68397c397855c3239783832df7403d1" integrity sha1-Cwih3PaDl8OXhVwyOXg4Mt90A9E= +lodash.isequal@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" + integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= + lodash.isfunction@~2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/lodash.isfunction/-/lodash.isfunction-2.4.1.tgz#2cfd575c73e498ab57e319b77fa02adef13a94d1" @@ -9756,6 +9776,17 @@ rc@^1.2.7: minimist "^1.2.0" strip-json-comments "~2.0.1" +react-ace@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/react-ace/-/react-ace-8.0.0.tgz#e6fc155ec3cf240e92bdf2e156a50458a78ed0a4" + integrity sha512-EvU14vXbZpAenb1ZVKdn8yTQs/shZ9RghFulHtt67bBXT6sjrNHcfOEXHYtSEmwMb6pQVVNNuulzzd8o+Uouig== + dependencies: + ace-builds "^1.4.6" + diff-match-patch "^1.0.4" + lodash.get "^4.4.2" + lodash.isequal "^4.5.0" + prop-types "^15.7.2" + react-clientside-effect@^1.2.0: version "1.2.2" resolved "https://registry.yarnpkg.com/react-clientside-effect/-/react-clientside-effect-1.2.2.tgz#6212fb0e07b204e714581dd51992603d1accc837" From 24e041f81208d719f2e94b2df198227a7abd364e Mon Sep 17 00:00:00 2001 From: Aidan Cunniffe Date: Fri, 15 Nov 2019 09:26:52 -0500 Subject: [PATCH 2/5] Example add modal --- .../loaders/ExampleDrivenSpecLoader.js | 53 +++++++++++++------ webapp/src/components/shared/JsonTextarea.js | 3 +- 2 files changed, 39 insertions(+), 17 deletions(-) diff --git a/webapp/src/components/loaders/ExampleDrivenSpecLoader.js b/webapp/src/components/loaders/ExampleDrivenSpecLoader.js index cc49d116b5..9db54636e4 100644 --- a/webapp/src/components/loaders/ExampleDrivenSpecLoader.js +++ b/webapp/src/components/loaders/ExampleDrivenSpecLoader.js @@ -9,6 +9,8 @@ import {NavigationStore} from '../../contexts/NavigationContext'; import {routerPaths} from '../../routes'; import {SpecOverview} from '../routes/local'; import NewBehavior from '../navigation/NewBehavior'; +import CompareArrowsIcon from '@material-ui/icons/CompareArrows'; +import FiberNewIcon from '@material-ui/icons/FiberNew'; import {RequestsDetailsPage} from '../requests/EndpointPage'; import { TextField, @@ -29,6 +31,8 @@ import DialogContentText from '@material-ui/core/DialogContentText'; import {DiffDocGrid} from '../requests/DocGrid'; import {DocSubGroup} from '../requests/DocSubGroup'; import JsonTextarea from '../shared/JsonTextarea'; +import {DocDivider} from '../requests/DocConstants'; +import Chip from '@material-ui/core/Chip'; export const basePath = `/spec-by-example`; @@ -167,7 +171,6 @@ function ExampleBuilderBase(props) { - {hasUnrecognizedPath ?
this is a new URL
: null} @@ -176,7 +179,6 @@ function ExampleBuilderBase(props) { setValue('request.body', value)} value={formValues.request.body} - name="request.body" /> @@ -186,29 +188,50 @@ function ExampleBuilderBase(props) { Response
- +
setValue('response.body', value)} + onChange={(value) => setValue('response', 'body', value)} value={formValues.response.body} /> )} /> - - - {hasDiff ?
this request does not match the spec
: null} + + + +
+ {hasUnrecognizedPath ? } + label="This URL Path is new." + /> : null} + {hasDiff ? } + label="This example produces a diff" + /> : null} + +
+ +
+ diff --git a/webapp/src/components/shared/JsonTextarea.js b/webapp/src/components/shared/JsonTextarea.js index 2446d277c0..fe4e3f61a0 100644 --- a/webapp/src/components/shared/JsonTextarea.js +++ b/webapp/src/components/shared/JsonTextarea.js @@ -9,7 +9,6 @@ const styles = theme => ({ }); function TypeModal({value, onChange}) { - return ( From 283c3e19446750e7375f3a3acc50d429db9cdb69 Mon Sep 17 00:00:00 2001 From: Aidan Cunniffe Date: Fri, 15 Nov 2019 09:34:09 -0500 Subject: [PATCH 3/5] Updated home page --- .../loaders/ExampleDrivenSpecLoader.js | 31 +++++++++++-------- webapp/src/components/shared/JsonTextarea.js | 3 +- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/webapp/src/components/loaders/ExampleDrivenSpecLoader.js b/webapp/src/components/loaders/ExampleDrivenSpecLoader.js index 9db54636e4..ca8d745c69 100644 --- a/webapp/src/components/loaders/ExampleDrivenSpecLoader.js +++ b/webapp/src/components/loaders/ExampleDrivenSpecLoader.js @@ -128,6 +128,7 @@ function ExampleBuilderBase(props) { const {sample} = parseFormState(data); onSampleAdded(sample); + setShowExampleBuilder(false); }; const {specService} = props; const { @@ -136,13 +137,17 @@ function ExampleBuilderBase(props) { return (
- +
+ +
setShowExampleBuilder(false)} >
@@ -200,7 +205,7 @@ function ExampleBuilderBase(props) { setValue('response', 'body', value)} + onChange={(value) => setValue('response', value)} value={formValues.response.body} /> @@ -214,24 +219,24 @@ function ExampleBuilderBase(props) { {hasUnrecognizedPath ? } - label="This URL Path is new." + icon={} + label="This URL path is new" /> : null} {hasDiff ? } + icon={} label="This example produces a diff" /> : null}
-
- +
+ diff --git a/webapp/src/components/shared/JsonTextarea.js b/webapp/src/components/shared/JsonTextarea.js index fe4e3f61a0..f236474d13 100644 --- a/webapp/src/components/shared/JsonTextarea.js +++ b/webapp/src/components/shared/JsonTextarea.js @@ -16,7 +16,8 @@ function TypeModal({value, onChange}) { tabSize={2} showPrintMargin={false} width={'90%'} - onChange={this.onChange} + height={365} + onChange={onChange} value={value} editorProps={{$blockScrolling: true}} /> From c7c3147cbd15231f1e9b848da0aa460ec99f9f24 Mon Sep 17 00:00:00 2001 From: Dev Doshi Date: Fri, 15 Nov 2019 13:16:26 -0500 Subject: [PATCH 4/5] fix example dialog --- .../loaders/ExampleDrivenSpecLoader.js | 299 ++++++++++-------- webapp/src/components/shared/JsonTextarea.js | 2 +- 2 files changed, 160 insertions(+), 141 deletions(-) diff --git a/webapp/src/components/loaders/ExampleDrivenSpecLoader.js b/webapp/src/components/loaders/ExampleDrivenSpecLoader.js index 9db54636e4..7a5dd6873f 100644 --- a/webapp/src/components/loaders/ExampleDrivenSpecLoader.js +++ b/webapp/src/components/loaders/ExampleDrivenSpecLoader.js @@ -1,17 +1,17 @@ -import React, {useState} from 'react'; -import {InitialRfcCommandsStore} from '../../contexts/InitialRfcCommandsContext'; -import {TrafficAndDiffSessionStore} from '../../contexts/TrafficAndDiffSessionContext'; -import {LocalDiffRfcStore, withRfcContext} from '../../contexts/RfcContext'; -import {Route, Switch} from 'react-router-dom'; -import {UrlsX} from '../paths/NewUnmatchedUrlWizard'; +import React, { useState, useEffect } from 'react'; +import { InitialRfcCommandsStore } from '../../contexts/InitialRfcCommandsContext'; +import { TrafficAndDiffSessionStore } from '../../contexts/TrafficAndDiffSessionContext'; +import { LocalDiffRfcStore, withRfcContext } from '../../contexts/RfcContext'; +import { Route, Switch } from 'react-router-dom'; +import { UrlsX } from '../paths/NewUnmatchedUrlWizard'; import RequestDiffX from '../diff/RequestDiffX'; -import {NavigationStore} from '../../contexts/NavigationContext'; -import {routerPaths} from '../../routes'; -import {SpecOverview} from '../routes/local'; +import { NavigationStore } from '../../contexts/NavigationContext'; +import { routerPaths } from '../../routes'; +import { SpecOverview } from '../routes/local'; import NewBehavior from '../navigation/NewBehavior'; import CompareArrowsIcon from '@material-ui/icons/CompareArrows'; import FiberNewIcon from '@material-ui/icons/FiberNew'; -import {RequestsDetailsPage} from '../requests/EndpointPage'; +import { RequestsDetailsPage } from '../requests/EndpointPage'; import { TextField, Button, @@ -24,14 +24,14 @@ import { DialogActions } from '@material-ui/core'; import useForm from 'react-hook-form'; -import {STATUS_CODES} from 'http'; -import {InteractionDiffer, toInteraction} from '../../engine'; +import { STATUS_CODES } from 'http'; +import { InteractionDiffer, toInteraction } from '../../engine'; import Dialog from '@material-ui/core/Dialog'; import DialogContentText from '@material-ui/core/DialogContentText'; -import {DiffDocGrid} from '../requests/DocGrid'; -import {DocSubGroup} from '../requests/DocSubGroup'; +import { DiffDocGrid } from '../requests/DocGrid'; +import { DocSubGroup } from '../requests/DocSubGroup'; import JsonTextarea from '../shared/JsonTextarea'; -import {DocDivider} from '../requests/DocConstants'; +import { DocDivider } from '../requests/DocConstants'; import Chip from '@material-ui/core/Chip'; export const basePath = `/spec-by-example`; @@ -45,7 +45,7 @@ function parseLoosely(nonEmptyBodyString) { } catch (e) { try { const result = eval(`(${nonEmptyBodyString})`); - console.log({result}); + console.log({ result }); if (result) { return [true, result]; } @@ -56,10 +56,31 @@ function parseLoosely(nonEmptyBodyString) { } } -function ExampleBuilderBase(props) { +function DialogWrapper(props) { const [showExampleBuilder, setShowExampleBuilder] = useState(false); + const { specService, onSampleAdded } = props; + return ( + +
+ + setShowExampleBuilder(false)} + > + + + +
+ ) +} + +function ExampleBuilderBase(props) { - const {register, handleSubmit, setValue, getValues, watch} = useForm({ + const { register, handleSubmit, setValue, getValues, watch } = useForm({ defaultValues: { request: { method: 'GET', @@ -68,22 +89,23 @@ function ExampleBuilderBase(props) { response: { statusCode: 200 } - } + }, }); + // need watch() to update getValues() on change const watchAll = watch(); - const formValues = getValues({nest: true}); + const formValues = getValues({ nest: true }); const parseFormState = state => { - console.log({state}); + console.log({ state }); const [parsedRequestBodySuccess, parsedRequestBody] = parseLoosely(state.request.body); const request = { method: state.request.method, url: state.request.url || '/', headers: {} }; - if (parsedRequestBodySuccess) { + if (parsedRequestBodySuccess && parsedRequestBody) { request.headers['content-type'] = 'application/json'; request.body = parsedRequestBody; } @@ -94,7 +116,7 @@ function ExampleBuilderBase(props) { statusCode: parseInt(state.response.statusCode, 10), headers: {}, }; - if (parsedResponseBodySuccess) { + if (parsedResponseBodySuccess && parsedResponseBody) { response.headers['content-type'] = 'application/json'; response.body = parsedResponseBody; } @@ -106,7 +128,7 @@ function ExampleBuilderBase(props) { }; - const {rfcService, rfcId} = props; + const { rfcService, rfcId } = props; const rfcState = rfcService.currentState(rfcId); const interactionDiffer = new InteractionDiffer(rfcState); const interaction = toInteraction(sample); @@ -124,120 +146,114 @@ function ExampleBuilderBase(props) { return result; }; const onSubmit = data => { - const {onSampleAdded} = props; - const {sample} = parseFormState(data); - + const { onSampleAdded } = props; + const { sample } = parseFormState(data); onSampleAdded(sample); }; - const {specService} = props; const { hasDiff, hasUnrecognizedPath, parsedRequestBodySuccess, parsedResponseBodySuccess } = parseFormState(formValues); + useEffect(() => { + register({ name: "request.body" }); + register({ name: "response.body" }); + }, []); return ( -
- - setShowExampleBuilder(false)} - > -
- - Add Example + + + Add Example - - - Design your API by example. Optic will use the examples to guide you through the development of your API - specification. + + + Design your API by example. Optic will use the examples to guide you through the development of your API + specification. - - Request - -
- - - - -
-
- - - setValue('request.body', value)} - value={formValues.request.body} - /> - - - )} - right={( - <> - Response - -
- -
-
- - setValue('response', 'body', value)} + + Request + +
+ + + + +
+
+ + + { + setValue('request.body', value) + }} + value={formValues.request.body} + /> + + + )} + right={( + <> + Response + +
+ +
+
+ + { + setValue('response.body', value) + }} value={formValues.response.body} /> - - - )} - /> - - - -
- {hasUnrecognizedPath ? } - label="This URL Path is new." - /> : null} - {hasDiff ? } - label="This example produces a diff" - /> : null} - -
- -
- - - - -
- -
+ + + ); } @@ -254,7 +270,7 @@ class ExampleDrivenSpecLoader extends React.Component { }; handleSampleAdded = (sample) => { - console.log({sample}); + console.log({ sample }); const session = { ...this.state.session, samples: [...this.state.session.samples, sample] @@ -281,7 +297,7 @@ class ExampleDrivenSpecLoader extends React.Component { return Promise.resolve([]); }, listSessions() { - return Promise.resolve({sessions: [sessionId]}); + return Promise.resolve({ sessions: [sessionId] }); }, saveEvents: (eventStore, rfcId) => { const events = eventStore.serializeEvents(rfcId); @@ -290,14 +306,14 @@ class ExampleDrivenSpecLoader extends React.Component { }); }, listExamples: (requestId) => { - return Promise.resolve({examples: this.state.examples[requestId] || []}); + return Promise.resolve({ examples: this.state.examples[requestId] || [] }); }, saveExample: (interaction, requestId) => { const examples = this.state.examples; const requestExamples = examples[requestId] || []; requestExamples.push(interaction); examples[requestId] = requestExamples; - this.setState({examples}); + this.setState({ examples }); }, saveDiffState: () => { } @@ -310,19 +326,22 @@ class ExampleDrivenSpecLoader extends React.Component { return ( }/> + notificationAreaComponent={( + + )} /> ); }; function SessionWrapper(props) { - const {match} = props; - const {sessionId} = match.params; + const { match } = props; + const { sessionId } = match.params; return ( - - + + ); @@ -332,9 +351,9 @@ class ExampleDrivenSpecLoader extends React.Component { - - - + + + @@ -344,11 +363,11 @@ class ExampleDrivenSpecLoader extends React.Component { class ExampleDrivenSpecLoaderRoutes extends React.Component { render() { - const {match} = this.props; + const { match } = this.props; return ( - + ); diff --git a/webapp/src/components/shared/JsonTextarea.js b/webapp/src/components/shared/JsonTextarea.js index fe4e3f61a0..9f32e78655 100644 --- a/webapp/src/components/shared/JsonTextarea.js +++ b/webapp/src/components/shared/JsonTextarea.js @@ -16,7 +16,7 @@ function TypeModal({value, onChange}) { tabSize={2} showPrintMargin={false} width={'90%'} - onChange={this.onChange} + onChange={onChange} value={value} editorProps={{$blockScrolling: true}} /> From dd93cf94797774292199a96bc155f3ce368bd546 Mon Sep 17 00:00:00 2001 From: Aidan Cunniffe Date: Fri, 15 Nov 2019 13:48:53 -0500 Subject: [PATCH 5/5] 6.4.0 --- api-cli/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api-cli/package.json b/api-cli/package.json index 6ce29f428f..0311e0de5c 100644 --- a/api-cli/package.json +++ b/api-cli/package.json @@ -1,7 +1,7 @@ { "name": "@useoptic/cli", - "description": "Make APIs Developer Friendly", - "version": "6.3.0", + "description": "Optic's open source tool for building APIs that document & test themselves", + "version": "6.4.0", "author": "@useoptic", "bin": { "api": "./bin/run"