Merge branch 'dev-1.3.0' of gitlab.corp.qunar.com:mfe/yapi into dev-1.3.0

This commit is contained in:
gaoxiaolin.gao 2017-12-11 20:45:44 +08:00
commit 1b9c4ccf73
24 changed files with 1526 additions and 1279 deletions

View File

@ -4,8 +4,11 @@
1. Api 路径兼容 postman {varible}
2. View Response Height 问题
#### Feature
1. 新增克隆测试集功能
2. 高级 Mock 期望支持 mockjs
3. pathname 允许只有一个 /
4. 高级 Mock 期望支持 mockjs
### v1.2.8

View File

@ -45,7 +45,9 @@ YApi 是<strong>高效</strong>、<strong>易用</strong>、<strong>功能强大
密码: ymfe.org
```
### YApi 资源
* [yapi docker](https://github.com/branchzero/yapi-docker) By branchzero
* [yapi sso 登录插件](https://github.com/YMFE/yapi-plugin-qsso)
* [yapi docker 部署](https://github.com/branchzero/yapi-docker) By branchzero
### YApi 的一些客户

View File

@ -5,7 +5,7 @@ import PropTypes from 'prop-types'
/**
* @author suxiaoxin
* @demo
* <EasyDragSort data={this.state.list} onChange={this.handleChange} >
* <EasyDragSort data={()=>this.state.list} onChange={this.handleChange} >
* {list}
* </EasyDragSot>
*/
@ -13,16 +13,19 @@ let curDragIndex = null;
EasyDragSort.propTypes = {
children: PropTypes.array,
data: PropTypes.array,
onChange: PropTypes.func,
onDragEnd: PropTypes.func
onDragEnd: PropTypes.func,
data: PropTypes.func
}
export default function EasyDragSort(props){
let container = props.children;
function onChange(from, to){
const onChange = (from, to)=>{
if(from === to ) return ;
let curValue = props.data;
let curValue;
curValue = props.data();
let newValue = arrMove(curValue, from, to);
if(typeof props.onChange === 'function'){
return props.onChange(newValue, from ,to);

View File

@ -157,6 +157,7 @@ export default class InterfaceColMenu extends Component {
}
}
}
showDelColConfirm = (colId) => {
let that = this;
const params = this.props.match.params;
@ -178,6 +179,47 @@ export default class InterfaceColMenu extends Component {
});
}
// 复制测试集合
copyInterface = async (item) => {
if(this._copyInterfaceSign === true){
return ;
}
this._copyInterfaceSign = true;
const { desc, project_id, _id: col_id } = item;
let { name } = item;
name = `${name} copy`;
// 添加集合
const add_col_res = await axios.post('/api/col/add_col', { name, desc, project_id });
if (add_col_res.data.errcode) {
message.error(add_col_res.data.errmsg);
return;
}
const new_col_id = add_col_res.data.data._id;
// 克隆集合
const add_case_list_res = await axios.post('/api/col/clone_case_list', {
new_col_id,
col_id,
project_id
})
this._copyInterfaceSign = false;
if (add_case_list_res.data.errcode) {
message.error(add_case_list_res.data.errmsg);
return;
}
// 刷新接口列表
await this.props.fetchInterfaceColList(project_id);
this.props.setColData({ currColId: + new_col_id, isRander: true })
message.success('克隆测试集成功')
}
showNoDelColConfirm = () => {
confirm({
title: '此测试集合为最后一个集合',
@ -257,7 +299,6 @@ export default class InterfaceColMenu extends Component {
}
onDrop = async (e) => {
console.log('e', e);
const projectId = this.props.match.params.id;
const dropColIndex = e.node.props.pos.split('-')[1];
@ -365,8 +406,9 @@ export default class InterfaceColMenu extends Component {
<div className="menu-title">
<span><Icon type="folder-open" style={{ marginRight: 5 }} /><span>{col.name}</span></span>
<div className="btns">
<Tooltip title="删除集合">
<Icon type='delete' className="interface-delete-icon" onClick={(e) => { e.stopPropagation(); list.length > 1 ? this.showDelColConfirm(col._id) : this.showNoDelColConfirm() }} />
<Icon type='delete' style={{display:list.length > 1 ? '': 'none' }} className="interface-delete-icon" onClick={() => { this.showDelColConfirm(col._id) }} />
</Tooltip>
<Tooltip title="编辑集合">
<Icon type='edit' className="interface-delete-icon" onClick={(e) => { e.stopPropagation(); this.showColModal('edit', col) }} />
@ -374,6 +416,9 @@ export default class InterfaceColMenu extends Component {
<Tooltip title="导入接口">
<Icon type='plus' className="interface-delete-icon" onClick={(e) => { e.stopPropagation(); this.showImportInterfaceModal(col._id) }} />
</Tooltip>
<Tooltip title="克隆集合">
<Icon type='copy' className="interface-delete-icon" onClick={(e) => { e.stopPropagation(); this.copyInterface(col) }} />
</Tooltip>
</div>
{/*<Dropdown overlay={menu(col)} trigger={['click']} onClick={e => e.stopPropagation()}>
<Icon className="opts-icon" type='ellipsis'/>

View File

@ -50,7 +50,6 @@ class InterfaceEdit extends Component {
if (result.data.errcode === 0) {
this.props.updateInterfaceData(params);
message.success('保存成功');
this.props.switchToView()
} else {
message.error(result.data.errmsg)
}

View File

@ -214,6 +214,7 @@ class InterfaceEditForm extends Component {
values.req_body_form = []
}
this.props.onSubmit(values)
EditFormContext.props.changeEditStatus(false);
}
});
}
@ -725,7 +726,7 @@ class InterfaceEditForm extends Component {
<Row className={'interface-edit-item ' + this.state.hideTabs.req.query}>
<Col>
<EasyDragSort data={this.props.form.getFieldValue('req_query')} onChange={this.handleDragMove('req_query')} >
<EasyDragSort data={()=>this.props.form.getFieldValue('req_query')} onChange={this.handleDragMove('req_query')} >
{QueryList}
</EasyDragSort>
</Col>
@ -740,7 +741,7 @@ class InterfaceEditForm extends Component {
<Row className={'interface-edit-item ' + this.state.hideTabs.req.headers}>
<Col>
<EasyDragSort data={this.props.form.getFieldValue('req_headers')} onChange={this.handleDragMove('req_headers')} >
<EasyDragSort data={()=>this.props.form.getFieldValue('req_headers')} onChange={this.handleDragMove('req_headers')} >
{headerList}
</EasyDragSort>
</Col>
@ -773,7 +774,7 @@ class InterfaceEditForm extends Component {
</Col>
</Row>
<EasyDragSort data={this.props.form.getFieldValue('req_body_form')} onChange={this.handleDragMove('req_body_form')} >
<EasyDragSort data={() => this.props.form.getFieldValue('req_body_form')} onChange={this.handleDragMove('req_body_form')} >
{requestBodyList}
</EasyDragSort>
@ -798,7 +799,7 @@ class InterfaceEditForm extends Component {
</Col>
</Row>
{this.props.form.getFieldValue('req_body_type') === 'file' ?
{this.props.form.getFieldValue('req_body_type') === 'file' && this.state.hideTabs.req.body !== 'hide' ?
<Row className="interface-edit-item" >
<Col className="interface-edit-item-other-body">
{getFieldDecorator('req_body_other', { initialValue: this.state.req_body_other })(
@ -811,7 +812,7 @@ class InterfaceEditForm extends Component {
:
null
}
{this.props.form.getFieldValue('req_body_type') === 'raw' ?
{this.props.form.getFieldValue('req_body_type') === 'raw' && this.state.hideTabs.req.body !== 'hide'?
<Row>
<Col>
{getFieldDecorator('req_body_other', { initialValue: this.state.req_body_other })(

View File

@ -11,5 +11,7 @@ module.exports = {
name: 'statistics'
},{
name: 'export-data'
}, {
name: 'api-history'
}]
}

View File

@ -0,0 +1,10 @@
import InterfaceHistory from './client/InterfaceHistory.js';
module.exports = function(){
this.bindHook('interface_tab', function(tabs){
tabs.advMock = {
name: '历史',
component: InterfaceHistory
}
})
}

View File

@ -0,0 +1,38 @@
import React, { Component } from 'react'
import axios from 'axios'
import PropTypes from 'prop-types'
//import { Form, Switch, Button, message, Icon, Tooltip, Radio } from 'antd';
class AdvMock extends Component {
// static propTypes = {
// form: PropTypes.object,
// match: PropTypes.object
// }
constructor(props) {
super(props);
this.state = {
}
}
handleSubmit = () => {
}
componentWillMount() {
}
render() {
return <div >
Hello, World.
</div>
}
}
module.exports = AdvMock;

View File

@ -0,0 +1,4 @@
module.exports = {
server: true,
client: true
}

View File

@ -0,0 +1,3 @@
module.exports = function(){
}

View File

@ -161,12 +161,12 @@ class interfaceColController extends baseController {
}
}
requestParamsToObj(arr){
if(!arr || !Array.isArray(arr) || arr.length === 0){
requestParamsToObj(arr) {
if (!arr || !Array.isArray(arr) || arr.length === 0) {
return {}
}
let obj = {};
arr.forEach(item=>{
arr.forEach(item => {
obj[item.name] = ''
})
return obj;
@ -190,7 +190,7 @@ class interfaceColController extends baseController {
return ctx.body = yapi.commons.resReturn(null, 407, 'col_id不能为空')
}
let resultList = await this.caseModel.list(id, 'all');
if(resultList.length === 0 ){
if (resultList.length === 0) {
return ctx.body = yapi.commons.resReturn([])
}
let project = await this.projectModel.getBaseInfo(resultList[0].project_id);
@ -216,9 +216,9 @@ class interfaceColController extends baseController {
item.body = Object.assign({}, body);
query = this.requestParamsToObj(data.req_query);
pathParams = this.requestParamsToObj(data.req_params);
if(data.req_body_type === 'form'){
if (data.req_body_type === 'form') {
bodyParams = this.requestParamsToObj(data.req_body_form);
}else{
} else {
bodyParams = yapi.commons.json_parse(data.req_body_other);
bodyParams = typeof bodyParams === 'object' ? bodyParams : {}
}
@ -375,6 +375,112 @@ class interfaceColController extends baseController {
}
}
async cloneCaseList(ctx) {
try {
let params = ctx.request.body;
params = yapi.commons.handleParams(params, {
project_id: 'number',
col_id: 'number',
new_col_id: 'number'
});
const { project_id, col_id, new_col_id } = params;
if (!project_id) {
return ctx.body = yapi.commons.resReturn(null, 400, '项目id不能为空');
}
let auth = await this.checkAuth(params.project_id, 'project', 'edit');
if (!auth) {
return ctx.body = yapi.commons.resReturn(null, 400, '没有权限');
}
if (!col_id) {
return ctx.body = yapi.commons.resReturn(null, 400, '被克隆的接口集id不能为空');
}
if (!new_col_id) {
return ctx.body = yapi.commons.resReturn(null, 400, '克隆的接口集id不能为空');
}
let oldColCaselistData = await this.caseModel.list(col_id, 'all');
oldColCaselistData = oldColCaselistData.sort((a, b) => {
return a.index - b.index;
});
const newCaseList = [];
const oldCaseObj = {};
let obj = {};
const handleTypeParams = (data, name) => {
let res = data[name];
const type = Object.prototype.toString.call(res);
switch (type) {
case "[object Array]":
if (res.length) {
res = JSON.stringify(res);
try {
res = JSON.parse(handleReplaceStr(res));
} catch (e) {
console.log('e ->', e);
}
}
break;
case "[object String]":
if (data[name]) {
res = handleReplaceStr(res);
}
break;
}
return res;
}
const handleReplaceStr = (str) => {
if (str.indexOf("$") !== -1) {
str = str.replace(/\$\.([0-9]+)\./g, function (match, p1) {
p1 = p1.toString();
return `$.${newCaseList[oldCaseObj[p1]]}.` || "";
})
}
return str;
}
// 处理数据里面的$id;
const handleParams = (data) => {
data.col_id = new_col_id;
delete data._id;
delete data.add_time;
delete data.up_time;
delete data.__v;
data.req_body_other = handleTypeParams(data, "req_body_other");
data.req_query = handleTypeParams(data, "req_query");
data.req_params = handleTypeParams(data, "req_params");
data.req_body_form = handleTypeParams(data, "req_body_form");
return data;
}
for (let i = 0; i < oldColCaselistData.length; i++) {
obj = oldColCaselistData[i].toObject();
// 将被克隆的id和位置绑定
oldCaseObj[obj._id] = i;
let caseData = handleParams(obj);
let newCase = await this.caseModel.save(caseData);
newCaseList.push(newCase._id);
}
this.projectModel.up(params.project_id, { up_time: new Date().getTime() }).then();
ctx.body = yapi.commons.resReturn('ok');
} catch (e) {
ctx.body = yapi.commons.resReturn(null, 402, e.message);
}
}
/**
* 更新一个接口用例
* @interface /col/up_case
@ -665,13 +771,13 @@ class interfaceColController extends baseController {
}
}
convertString(variable){
if(variable instanceof Error){
return variable.name + ': ' +variable.message;
convertString(variable) {
if (variable instanceof Error) {
return variable.name + ': ' + variable.message;
}
try{
try {
return JSON.stringify(variable, null, ' ');
}catch(err){
} catch (err) {
return variable || '';
}
}
@ -692,7 +798,7 @@ class interfaceColController extends baseController {
header: params.response.header,
records: params.records,
params: params.params,
log: (msg) =>{
log: (msg) => {
logs.push('log: ' + this.convertString(msg))
}
}

View File

@ -26,18 +26,18 @@ class interfaceCase extends baseModel {
name: String, value: String
}],
req_query: [{
name: String, value: String, enable: {type: Boolean, default: true}
name: String, value: String, enable: { type: Boolean, default: true }
}],
req_body_form: [{
name: String, value: String, enable: {type: Boolean, default: true}
name: String, value: String, enable: { type: Boolean, default: true }
}],
req_body_other: String,
test_res_body: String,
test_status: {type: String, enum: ['ok', 'invalid', 'error', '']},
test_status: { type: String, enum: ['ok', 'invalid', 'error', ''] },
test_res_header: Schema.Types.Mixed,
mock_verify: {type: Boolean, default: false},
enable_script: {type: Boolean, default: false},
mock_verify: { type: Boolean, default: false },
enable_script: { type: Boolean, default: false },
test_script: String
};
}
@ -59,7 +59,7 @@ class interfaceCase extends baseModel {
}
list(col_id, select) {
select = select || 'casename uid col_id _id index'
select = select || 'casename uid col_id _id index interface_id'
if (select === 'all') {
return this.model.find({
col_id: col_id
@ -67,7 +67,7 @@ class interfaceCase extends baseModel {
}
return this.model.find({
col_id: col_id
}).select("casename uid col_id _id index").exec();
}).select("casename uid col_id _id index interface_id").exec();
}
del(id) {
@ -82,7 +82,7 @@ class interfaceCase extends baseModel {
})
}
delByInterfaceId(id){
delByInterfaceId(id) {
return this.model.remove({
interface_id: id
})

View File

@ -328,6 +328,11 @@ let routerConfig = {
path: 'add_case_list',
method: 'post'
}, {
action: 'cloneCaseList',
path: 'clone_case_list',
method: 'post'
}, {
action: "list",
path: "list",
@ -415,7 +420,7 @@ let routerConfig = {
path: "http/code",
method: "post"
}
]
]
}
let pluginsRouterPath = [];
@ -430,7 +435,7 @@ function addPluginRouter(config) {
throw new Error('Plugin Route path conflict, please try rename the path')
}
pluginsRouterPath.push(routerPath);
createAction(router, "/api", config.controller, config.action, routerPath, method,false);
createAction(router, "/api", config.controller, config.action, routerPath, method, false);
}
yapi.emitHookSync('add_router', addPluginRouter);

View File

@ -1 +1,21 @@
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
window.WEBPACK_ASSETS = {"index.js":{"js":"index@cd86ca67ec83cf50bf7f.js","css":"index@cd86ca67ec83cf50bf7f.css"},"lib":{"js":"lib@440c1fc133fa194fb5a7.js"},"lib2":{"js":"lib2@2b54ffd5cfd140edb818.js"},"manifest":{"js":"manifest@b67af9f8b578904e66c5.js"}}
=======
<<<<<<< HEAD
window.WEBPACK_ASSETS = {"index.js":{"js":"index@5d0172e1e8d025077c6d.js","css":"index@5d0172e1e8d025077c6d.css"},"lib":{"js":"lib@9f81ac11019b0fb44526.js"},"lib2":{"js":"lib2@d45c8b21cd152647ed17.js"},"manifest":{"js":"manifest@b67af9f8b578904e66c5.js"}}
=======
window.WEBPACK_ASSETS = {"index.js":{"js":"index@1c90318cffcd9226f4ca.js","css":"index@1c90318cffcd9226f4ca.css"},"lib":{"js":"lib@b527656c385f817c6379.js"},"lib2":{"js":"lib2@89ae3d589a0377410eb8.js"},"manifest":{"js":"manifest@b67af9f8b578904e66c5.js"}}
>>>>>>> dev-1.2.0
>>>>>>> dev
=======
window.WEBPACK_ASSETS = {"index.js":{"js":"index@2749a62173cb051bd43a.js","css":"index@2749a62173cb051bd43a.css"},"lib":{"js":"lib@5ca86296ff97fbf15aa9.js"},"lib2":{"js":"lib2@4e246d55b157fd90cbd6.js"},"manifest":{"js":"manifest@b67af9f8b578904e66c5.js"}}
>>>>>>> dev
=======
window.WEBPACK_ASSETS = {"index.js":{"js":"index@61638219be6877d986cc.js","css":"index@61638219be6877d986cc.css"},"lib":{"js":"lib@d8397632ca19f2cbfc57.js"},"lib2":{"js":"lib2@7552d509ada57b7aecab.js"},"manifest":{"js":"manifest@b67af9f8b578904e66c5.js"}}
>>>>>>> dev
=======
window.WEBPACK_ASSETS = {"index.js":{"js":"index@773f87ecdd1b72f9076c.js","css":"index@773f87ecdd1b72f9076c.css"},"lib":{"js":"lib.min.js"},"lib2":{"js":"lib2.min.js"},"manifest":{"js":"manifest.min.js"}}
>>>>>>> e0633fd4cd88abe7a577250d3fb4a4d4a47e497f

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -86,19 +86,21 @@ module.exports = {
'react-router',
'react-router-dom',
'prop-types',
'axios',
'moment',
'react-dnd-html5-backend',
'react-dnd',
'reactabular-table',
'reactabular-dnd',
'table-resolver'
'table-resolver',
'recharts'
],
lib2: [
'brace',
'mockjs',
'json5',
'url'
'url',
'wangeditor',
'axios',
'moment'
]
}
},