postman export V2 & request body huge update!!!

pull/71/head
Bosn 7 years ago
parent 64b5da7c93
commit e1d97c8d5d

@ -22,36 +22,38 @@
"author": "mozhi.gyy@alibaba-inc.com",
"license": "ISC",
"dependencies": {
"animate.css": "3.5.2",
"animate.css": "3.6.1",
"bootstrap": "^4.1.1",
"chart.js": "^2.6.0",
"codemirror": "5.27.2",
"codemirror": "5.39.0",
"graceful": "1.0.1",
"koa": "2.3.0",
"koa-router": "7.2.1",
"koa-session": "5.3.0",
"koa-static": "3.0.0",
"lodash": "4.17.4",
"jquery": "^3.3.1",
"koa": "2.5.1",
"koa-router": "7.4.0",
"koa-session": "5.8.1",
"koa-static": "5.0.0",
"lodash": "4.17.10",
"mockjs": "1.0.1-beta3",
"moment": "2.22.1",
"node-fetch": "1.7.1",
"moment": "2.22.2",
"node-fetch": "2.1.2",
"normalizr": "^3.2.4",
"nprogress": "0.2.0",
"parsleyjs": "^2.7.2",
"prop-types": "15.5.10",
"react": "15.6.1",
"react-dom": "15.6.1",
"react-icons": "2.2.5",
"react-modal": "2.1.0",
"react-redux": "5.0.5",
"react-router": "4.1.1",
"react-router-config": "1.0.0-beta.3",
"react-router-dom": "4.1.1",
"popper.js": "^1.14.3",
"prop-types": "15.6.2",
"react": "16.4.1",
"react-dom": "16.4.1",
"react-icons": "2.2.7",
"react-modal": "3.4.5",
"react-redux": "5.0.7",
"react-router": "4.3.1",
"react-router-config": "1.0.0-beta.4",
"react-router-dom": "4.3.1",
"react-router-redux": "5.0.0-alpha.6",
"redux": "3.7.1",
"redux-saga": "0.15.4",
"sortablejs": "1.6.0",
"urijs": "1.18.10"
"redux": "4.0.0",
"redux-saga": "0.16.0",
"sortablejs": "1.7.0",
"urijs": "1.19.1"
},
"standard": {
"parser": "babel-eslint",
@ -63,10 +65,10 @@
]
},
"devDependencies": {
"babel-eslint": "7.2.3",
"node-sass": "4.5.3",
"npm-run-all": "4.0.2",
"react-scripts": "0.9.5",
"standard": "10.0.2"
"babel-eslint": "8.2.5",
"node-sass": "4.9.0",
"npm-run-all": "4.1.3",
"react-scripts": "1.1.4",
"standard": "11.0.1"
}
}

@ -6,7 +6,7 @@ export const updateProperty = (property, onResolved) => ({ type: 'PROPERTY_UPDAT
export const updatePropertySucceeded = (property) => ({ type: 'PROPERTY_UPDATE_SUCCEEDED', property })
export const updatePropertyFailed = (message) => ({ type: 'PROPERTY_UPDATE_FAILED', message })
export const updateProperties = (itf, properties, onResolved) => ({ type: 'PROPERTIES_UPDATE', itf, properties, onResolved })
export const updateProperties = (itf, properties, summary, onResolved) => ({ type: 'PROPERTIES_UPDATE', itf, properties, summary, onResolved })
export const updatePropertiesSucceeded = (properties) => ({ type: 'PROPERTIES_UPDATE_SUCCEEDED', properties })
export const updatePropertiesFailed = (message) => ({ type: 'PROPERTIES_UPDATE_FAILED', message })

@ -1,12 +1,12 @@
import React, { Component } from 'react'
import { PropTypes, connect, replace, StoreStateRouterLocationURI, _ } from '../../family'
import InterfaceEditorToolbar from './InterfaceEditorToolbar'
import InterfaceSummary from './InterfaceSummary'
import InterfaceSummary, { BODY_OPTION, REQUEST_PARAMS_TYPE, rptFromStr2Num } from './InterfaceSummary'
import PropertyList from './PropertyList'
export const RequestPropertyList = (props) => (
<PropertyList scope='request' title='请求参数' label='请求' {...props} />
)
export const RequestPropertyList = (props) => {
return <PropertyList scope='request' title='请求参数' label='请求' {...props} />
}
export const ResponsePropertyList = (props) => (
<PropertyList scope='response' title='响应内容' label='响应' {...props} />
)
@ -36,53 +36,65 @@ class InterfaceEditor extends Component {
handleDeleteMemoryProperty: PropTypes.func.isRequired,
handleChangeProperty: PropTypes.func.isRequired
}
constructor (props) {
super(props)
this.state = {
...InterfaceEditor.mapPropsToState(props),
summaryState: {
bodyOption: BODY_OPTION.FORM_DATA,
requestParamsType: REQUEST_PARAMS_TYPE.QUERY_PARAMS
}
}
this.summaryStateChange = this.summaryStateChange.bind(this)
// { itf: {}, properties: [] }
}
getChildContext () {
return _.pick(this, Object.keys(InterfaceEditor.childContextTypes))
// return {
// handleLockInterface: this.handleLockInterface,
// handleUnlockInterface: this.handleUnlockInterface,
// handleSaveInterface: this.handleSaveInterface,
// handleAddMemoryProperty: this.handleAddMemoryProperty,
// handleAddMemoryProperties: this.handleAddMemoryProperties,
// handleDeleteMemoryProperty: this.handleDeleteMemoryProperty,
// handleChangeProperty: this.handleChangeProperty
// }
}
static mapPropsToState (props) {
let { auth, itf, properties } = props
}
static mapPropsToState (prevProps, prevStates) {
let { auth, itf, properties } = prevProps
return {
itf: { ...itf },
...prevStates,
itf,
properties: properties.map(property => ({ ...property })),
editable: !!(itf.locker && (itf.locker.id === auth.id))
}
}
componentDidMount () { }
summaryStateChange (summaryState) {
this.setState({ summaryState })
}
componentWillReceiveProps (nextProps) {
if (
nextProps.itf.id === this.state.itf.id &&
nextProps.itf.updatedAt === this.state.itf.updatedAt
) return
this.setState(InterfaceEditor.mapPropsToState(nextProps))
const prevStates = this.state
this.setState(InterfaceEditor.mapPropsToState(nextProps, prevStates))
}
// Use shouldComponentUpdate() to let React know if a component's output is not affected by the current change in state or props.
// TODO 2.2
// shouldComponentUpdate (nextProps, nextState) {}
constructor (props) {
super(props)
this.state = InterfaceEditor.mapPropsToState(props)
// { itf: {}, properties: [] }
}
render () {
let { auth, repository, mod, itf } = this.props
let { id, locker } = this.state.itf
const { auth, repository, mod, itf } = this.props
const { editable } = this.state
const { id, locker } = this.state.itf
if (!id) return null
return (
<article className='InterfaceEditor'>
<InterfaceEditorToolbar locker={locker} auth={auth} repository={repository} editable={this.state.editable} />
<InterfaceSummary repository={repository} mod={mod} itf={itf} active />
<RequestPropertyList properties={this.state.properties} editable={this.state.editable}
repository={repository} mod={mod} itf={this.state.itf} />
<ResponsePropertyList properties={this.state.properties} editable={this.state.editable}
<InterfaceEditorToolbar locker={locker} auth={auth} repository={repository} editable={editable} />
<InterfaceSummary repository={repository} mod={mod} itf={itf} active editable={editable} stateChangeHandler={this.summaryStateChange} />
<RequestPropertyList
properties={this.state.properties}
editable={editable}
repository={repository}
mod={mod}
itf={this.state.itf}
bodyOption={this.state.summaryState.bodyOption}
requestParamsType={this.state.summaryState.requestParamsType}
/>
<ResponsePropertyList properties={this.state.properties} editable={editable}
repository={repository} mod={mod} itf={this.state.itf} />
</article>
)
@ -91,9 +103,13 @@ class InterfaceEditor extends Component {
this.handleAddMemoryProperties([property], cb)
}
handleAddMemoryProperties = (properties, cb) => {
const requestParamsType = this.state.summaryState.requestParamsType
const rpt = rptFromStr2Num(requestParamsType)
properties.forEach(item => {
if (item.memory === undefined) item.memory = true
if (item.id === undefined) item.id = _.uniqueId('memory-')
item.pos = rpt
})
let nextState = { properties: [...this.state.properties, ...properties] }
this.setState(nextState, () => {
@ -131,8 +147,8 @@ class InterfaceEditor extends Component {
}
handleSaveInterface = (e) => {
e.preventDefault()
let { onUpdateProperties } = this.context
onUpdateProperties(this.state.itf.id, this.state.properties, () => {
const { onUpdateProperties } = this.context
onUpdateProperties(this.state.itf.id, this.state.properties, this.state.summaryState, () => {
this.handleUnlockInterface()
})
}

@ -2,7 +2,7 @@ import React, { Component } from 'react'
import { PropTypes, connect, Link, Mock } from '../../family'
import { SmartTextarea } from '../utils'
export const METHODS = ['GET', 'POST', 'PUT', 'DELETE']
export const METHODS = ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'PATCH', 'HEAD']
//
const mockInterface = process.env.NODE_ENV === 'development'

@ -2,10 +2,49 @@ import React, { Component } from 'react'
import { PropTypes, Link, replace, StoreStateRouterLocationURI } from '../../family'
import { DialogController } from '../utils'
import { serve } from '../../relatives/services/constant'
import InterfaceForm from './InterfaceForm'
import InterfaceForm, { METHODS } from './InterfaceForm'
import { getRelativeUrl } from '../../utils/URLUtils'
import './InterfaceSummary.css'
export const BODY_OPTION = {
FORM_DATA: 'FORM_DATA',
FORM_URLENCODED: 'FORM_URLENCODED',
RAW: 'RAW',
BINARY: 'BINARY'
}
export const REQUEST_PARAMS_TYPE = {
HEADERS: 'HEADERS',
QUERY_PARAMS: 'QUERY_PARAMS',
BODY_PARAMS: 'BODY_PARAMS'
}
export function rptFromStr2Num (rpt) {
let pos = 2
if (rpt === 'HEADERS') {
pos = 1
} else if (rpt === 'BODY_PARAMS') {
pos = 3
}
return pos
}
class InterfaceSummary extends Component {
constructor (props) {
super(props)
this.state = {
name: props.itf.name,
method: props.itf.method,
url: props.itf.url,
description: props.itf.description,
bodyOption: BODY_OPTION.FORM_DATA,
requestParamsType: props.method === 'POST' ? REQUEST_PARAMS_TYPE.BODY_PARAMS : REQUEST_PARAMS_TYPE.QUERY_PARAMS
}
this.changeMethod = this.changeMethod.bind(this)
this.changeHandler = this.changeHandler.bind(this)
this.switchBodyOptions = this.switchBodyOption.bind(this)
this.switchRequestParamsType = this.switchRequestParamsType.bind(this)
}
static contextTypes = {
store: PropTypes.object.isRequired,
onDeleteInterface: PropTypes.func.isRequired
@ -14,17 +53,50 @@ class InterfaceSummary extends Component {
repository: PropTypes.object.isRequired,
mod: PropTypes.object.isRequired,
itf: PropTypes.object.isRequired,
active: PropTypes.bool.isRequired
active: PropTypes.bool.isRequired,
editable: PropTypes.bool.isRequired,
stateChangeHandler: PropTypes.func.isRequired
}
componentDidUpdate () {
}
switchBodyOption (val) {
return () => {
this.setState({
bodyOption: val
}, () => {
this.props.stateChangeHandler(this.state)
})
}
}
switchRequestParamsType (val) {
return () => {
this.setState({
requestParamsType: val
}, () => {
this.props.stateChangeHandler(this.state)
})
}
}
changeMethod (method) {
this.setState({ method })
}
changeHandler (e) {
this.setState({
[e.target.name]: e.target.value
})
}
render () {
let { repository = {}, mod = {}, itf = {} } = this.props
const { repository = {}, mod = {}, itf = {}, editable } = this.props
const { method, name, url, description, bodyOption, requestParamsType } = this.state
if (!itf.id) return null
return (
return !editable ? (
<div className='InterfaceSummary'>
<div className='header'>
<span className='title'>
{mod.name}
<span className='slash'> / </span>
{itf.name}
</span>
{/* TODO 2.2 √模板接口、√数据接口、JSONSchema 接口 */}
@ -42,13 +114,104 @@ class InterfaceSummary extends Component {
<span className='label'>地址</span>
<Link to={`${serve}/app/mock/${repository.id}${getRelativeUrl(itf.url)}`} target='_blank'>{itf.url}</Link>
</li>
<li><span className='label'>类型</span>{itf.method}</li>
<li><span className='label'>类型</span>
{itf.method}
</li>
{itf.description &&
<li><span className='label'>简介</span>{itf.description}</li>
}
</ul>
</div>
)
: (
<div className='InterfaceSummary'>
<div className='head'>
<div className='form-group row'>
<label className='col-sm-1 col-form-label'>接口名</label>
<div className='col-sm-10'>
<input type='text' className='form-control' name='name' value={name || ''} onChange={this.changeHandler} />
</div>
</div>
{/* TODO 2.2 √模板接口、√数据接口、JSONSchema 接口 */}
{/* TODO 2.2 权限控制,被别人锁定时不能编辑和删除 */}
{/* TODO 2.2 这里的接口编辑和右侧的编辑容易引起歧义,很难受 */}
<span className='hide'>
<DialogController content={<InterfaceForm title='修改接口' repository={repository} mod={mod} itf={itf} />} onResolved={this.handleUpdate}>
<Link to='' onClick={e => e.preventDefault()} title='修改接口' className='edit'>编辑</Link>
</DialogController>
<Link to='' onClick={e => this.handleDelete(e, itf)} className='delete'>删除</Link>
</span>
</div>
<div className='body'>
<div className='form-group row'>
<label className='col-sm-1 col-form-label'>地址</label>
<div className='col-sm-10'>
<input type='text' className='form-control' name='url' value={url || ''} onChange={this.changeHandler} />
</div>
</div>
<div className='form-group row'>
<label className='col-sm-1 col-form-label'>类型</label>
<div className='dropdown col-sm-10'>
<button
className='btn btn-secondary dropdown-toggle'
style={{ width: 160, display: 'block' }}
type='button'
id='btnDropdownMethods'
data-toggle='dropdown'
aria-haspopup='true'
aria-expanded='false'
>
{method}
</button>
<div className='dropdown-menu' aria-labelledby='btnDropdownMethods'>
{METHODS.map(method =>
<button className='dropdown-item' key={method} onClick={() => { this.changeMethod(method); return false }}>{method}</button>
)}
</div>
</div>
</div>
<div className='form-group row'>
<label className='col-sm-1 col-form-label'>简介</label>
<div className='col-sm-10'>
<textarea className='form-control' name='description' onChange={this.changeHandler} value={description || ''} />
</div>
</div>
<ul className='nav nav-tabs' role='tablist'>
<li className='nav-item' onClick={this.switchRequestParamsType(REQUEST_PARAMS_TYPE.HEADERS)}>
<a className={`nav-link ${requestParamsType === REQUEST_PARAMS_TYPE.HEADERS ? 'active' : ''}`} href='#' role='tab' data-toggle='tab'>headers</a>
</li>
<li className='nav-item' onClick={this.switchRequestParamsType(REQUEST_PARAMS_TYPE.QUERY_PARAMS)}>
<a className={`nav-link ${requestParamsType === REQUEST_PARAMS_TYPE.QUERY_PARAMS ? 'active' : ''}`} href='#' role='tab' data-toggle='tab'>Query Params</a>
</li>
<li className='nav-item' onClick={this.switchRequestParamsType(REQUEST_PARAMS_TYPE.BODY_PARAMS)}>
<a className={`nav-link ${requestParamsType === REQUEST_PARAMS_TYPE.BODY_PARAMS ? 'active' : ''}`} href='#' role='tab' data-toggle='tab'>Body Params</a>
</li>
</ul>
</div>
{requestParamsType === REQUEST_PARAMS_TYPE.BODY_PARAMS
? <div className='body-options'>
<div className='form-check form-check-inline' onClick={this.switchBodyOption(BODY_OPTION.FORM_DATA)}>
<input className='form-check-input' type='radio' name='inlineRadioOptions' id='inlineRadio1' value='option1' />
<label className='form-check-label' htmlFor='inlineRadio1'>form-data</label>
</div>
<div className='form-check form-check-inline' onClick={this.switchBodyOption(BODY_OPTION.FORM_URLENCODED)}>
<input className='form-check-input' type='radio' name='inlineRadioOptions' id='inlineRadio2' value='option2' />
<label className='form-check-label' htmlFor='inlineRadio2'>x-www-form-urlencoded</label>
</div>
<div className='form-check form-check-inline' onClick={this.switchBodyOption(BODY_OPTION.RAW)}>
<input className='form-check-input' type='radio' name='inlineRadioOptions' id='inlineRadio3' value='option3' />
<label className='form-check-label' htmlFor='inlineRadio3'>raw</label>
</div>
<div className='form-check form-check-inline' onClick={this.switchBodyOption(BODY_OPTION.BINARY)}>
<input className='form-check-input' type='radio' name='inlineRadioOptions' id='inlineRadio4' value='option4' />
<label className='form-check-label' htmlFor='inlineRadio4'>binary</label>
</div>
</div> : null
}
</div>
)
}
handleDelete = (e, itf) => {
e.preventDefault()

@ -0,0 +1,7 @@
.InterfaceSummary
.dropdown
display: inline-block
.form-control
max-width: 500px
.body-options
margin: 8px

@ -121,8 +121,6 @@ class PropertyForm extends Component {
parentId: parent.id
})
handleAddMemoryProperty(property, () => {
console.log('cb:')
console.log(arguments)
let { rmodal } = this.context
if (rmodal) rmodal.resolve()
})

@ -5,6 +5,7 @@ import PropertyForm from './PropertyForm'
import Importer from './Importer'
import Previewer from './InterfacePreviewer'
import { GoMention, GoFileCode, GoEye, GoPlus, GoTrashcan, GoQuestion } from 'react-icons/lib/go'
import { rptFromStr2Num } from './InterfaceSummary'
export const RequestPropertyListPreviewer = (props) => (
<Previewer {...props} />
@ -44,6 +45,17 @@ class SortableTreeTableHeader extends Component {
}
}
const PropertyLabel = (props) => {
const { pos } = props
if (pos === 1) {
return <label className='ml5 badge badge-danger'>HEAD</label>
} else if (pos === 3) {
return <label className='ml5 badge badge-primary'>BODY</label>
} else {
return <label className='ml5 badge badge-secondary'>QUERY</label>
}
}
class SortableTreeTableRow extends Component {
render () {
let { property, editable } = this.props
@ -64,7 +76,7 @@ class SortableTreeTableRow extends Component {
}
<div className={`td payload name depth-${item.depth} nowrap`}>
{!editable
? <span className='nowrap'>{item.name}</span>
? <span className='nowrap'>{item.name}{item.scope === 'request' && item.depth === 0 ? <PropertyLabel pos={item.pos} /> : null}</span>
: <input value={item.name} onChange={e => handleChangePropertyField(item.id, 'name', e.target.value)} className='form-control editable' spellCheck='false' placeholder='' />
}
</div>
@ -134,7 +146,11 @@ class PropertyList extends Component {
repository: PropTypes.object.isRequired,
mod: PropTypes.object.isRequired,
itf: PropTypes.object.isRequired,
editable: PropTypes.bool.isRequired
editable: PropTypes.bool.isRequired,
/** optional */
bodyOption: PropTypes.string,
requestParamsType: PropTypes.string
}
constructor (props) {
super(props)
@ -148,9 +164,12 @@ class PropertyList extends Component {
render () {
let { title, label, scope, properties = [], repository = {}, mod = {}, itf = {} } = this.props
if (!itf.id) return null
let { editable, bodyOption, requestParamsType } = this.props // itf.locker && (itf.locker.id === auth.id)
const pos = rptFromStr2Num(requestParamsType)
let scopedProperties = properties.map(property => ({ ...property })).filter(property => property.scope === scope)
let { editable } = this.props // itf.locker && (itf.locker.id === auth.id)
if (scope === 'request' && editable) {
scopedProperties = scopedProperties.filter(s => s.pos === pos)
}
return (
<section className='PropertyList'>
@ -216,9 +235,9 @@ class PropertyList extends Component {
handleChangeProperty({ ...property, [key]: value })
}
handleCreatePropertySucceeded = () => {
let { store } = this.context
let uri = StoreStateRouterLocationURI(store)
store.dispatch(replace(uri.href()))
// let { store } = this.context
// let uri = StoreStateRouterLocationURI(store)
// store.dispatch(replace(uri.href()))
}
handleDeleteMemoryProperty = (e, property) => {
e.preventDefault()

@ -105,7 +105,7 @@ class RepositoryEditor extends Component {
<Link to={`${serve}/repository/get?id=${repository.id}`} target='_blank' className='api'><GoDatabase /> 数据</Link>
<Link to={`${serve}/test/test.plugin.jquery.html?id=${repository.id}`} target='_blank' className='api'><GoJersey /> 测试</Link>
<span className='fake-link edit' onClick={e => this.setState({ exportPostman: true })}><GoLinkExternal /> 导出Postman Collection</span>
<RModal when={this.state.exportPostman} onClose={e => this.setState({ exportPostman: false })}>
<RModal when={this.state.exportPostman} onClose={e => this.setState({ exportPostman: false })} onResolve={e => this.setState({ exportPostman: false })}>
<ExportPostmanForm title='导出到Postman' repoId={repository.id} />
</RModal>
</div>

@ -38,7 +38,6 @@ class Example extends Component {
})
}
handleChange = (value) => {
console.log(value)
this.setState({ members: value, options: [] })
}
}

@ -1,4 +1,5 @@
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap'
import 'bootstrap/dist/css/bootstrap.min.css'
import './assets/index.css'
import 'animate.css'

@ -36,7 +36,7 @@ export function * updateProperty (action) {
}
export function * updateProperties (action) {
try {
const properties = yield call(EditorService.updateProperties, action.itf, action.properties)
const properties = yield call(EditorService.updateProperties, action.itf, action.properties, action.summary)
yield put(PropertyAction.updatePropertiesSucceeded(properties))
if (action.onResolved) action.onResolved()
} catch (e) {

@ -190,11 +190,11 @@ export default {
.then(res => res.json())
.then(json => json.data)
},
updateProperties (itf, properties) {
updateProperties (itf, properties, summary) {
return fetch(`${serve}/properties/update?itf=${itf}`, {
...CREDENTIALS,
method: 'POST',
body: JSON.stringify(properties),
body: JSON.stringify({ properties, summary }),
headers: { 'Content-Type': 'application/json' }
})
.then(res => res.json())

Loading…
Cancel
Save