diff --git a/README.md b/README.md index f2fd3b624..dd549bb89 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,14 @@ ### crazy-yapi分支补充功能说明: #### TO DO -- -- 调研:支持文件上传类接口测试(计划采用二进制方式支持文件上传) -- 优化tree加载为异步 -- 增强:增加jmeter driver服务,支持性能压测 -- 精度bug修复 :js 数字最大16位 -- 公共参数备注链接 -- 用例执行统计报表 -- 接口统计报表 + +- [ ] 调研:支持文件上传类接口测试(计划采用二进制方式支持文件上传) +- [ ] 优化tree加载为异步 +- [ ] 增强:增加jmeter driver服务,支持性能压测 +- [ ] 精度bug修复 :js 数字最大16位 +- [ ] 公共参数备注链接 +- [ ] 用例执行统计报表 +- [ ] 接口统计报表 ### 2019/8/16 @@ -175,6 +175,11 @@ ### crazy-yapi 分支 作者 * crazy 330126160@qq.com +### crazy-yapi 分支 License + +Copyright © 2019, [小影](http://quvideo.com/). +Released under the [Apache License 2.0](LICENSE). + ---------------------以下内容为官方主分支说明文档------------------------------ diff --git a/client/common.js b/client/common.js index 3ebce052c..64a90a94c 100644 --- a/client/common.js +++ b/client/common.js @@ -311,3 +311,25 @@ exports.arrayChangeIndex = (arr, start, end) => { return changes; }; + +exports.findCategoriesById = (list, id, listKey = 'list') => { + const find = (category, id, pIds = []) => { + if (category[listKey] && category[listKey].some(v => v._id === id)) { + return [...pIds, category._id]; + } else if (category.children && category.children.length) { + const newPIds = [...pIds, category._id]; + for (let i = 0; i < category.children.length; i++) { + const ids = find(category.children[i], id, newPIds); + if (ids.length !== newPIds.length) { + return ids; + } + } + return pIds; + } else { + return pIds; + } + }; + return list + .map(category => find(category, id)) + .reduce((a, b) => [...a, ...b], []); +}; diff --git a/client/components/Header/Search/Search.js b/client/components/Header/Search/Search.js index 2dbc7bbec..793ee9e2c 100644 --- a/client/components/Header/Search/Search.js +++ b/client/components/Header/Search/Search.js @@ -1,7 +1,8 @@ import React, { PureComponent as Component } from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; -import { Icon, Input, AutoComplete } from 'antd'; +import { Icon, Input, AutoComplete, Tooltip } from 'antd'; +import _ from 'underscore'; import './Search.scss'; import { withRouter } from 'react-router'; import axios from 'axios'; @@ -30,6 +31,7 @@ export default class Srch extends Component { this.state = { dataSource: [] }; + this.handleSearch = _.debounce(this.handleSearch, 500); } static propTypes = { @@ -45,7 +47,7 @@ export default class Srch extends Component { }; onSelect = async (value, option) => { - console.log("option.props.type:"+option.props.type); + // console.log("option.props.type:"+option.props.type); if (option.props.type === '分组') { this.props.changeMenuItem('/group'); this.props.history.push('/group/' + option.props['id']); @@ -78,10 +80,12 @@ export default class Srch extends Component { ); break; @@ -92,8 +96,11 @@ export default class Srch extends Component { type="项目" id={`${item._id}`} groupId={`${item.groupId}`} + text={`项目: ${item.name}`} > - {`项目: ${item.name}`} + + {`项目: ${item.name}`} + ); break; @@ -104,8 +111,11 @@ export default class Srch extends Component { type="接口" id={`${item._id}`} projectId={`${item.projectId}`} + text={`接口: ${item.title}`} > - {`接口: ${item.title}`} + + {`接口: ${item.title}`} + ); break; @@ -116,8 +126,11 @@ export default class Srch extends Component { type="路径" id={`${item._id}`} projectId={`${item.projectId}`} + text={`路径: ${item.path}`} > - {`路径: ${item.path}`} + + {`路径: ${item.path}`} + ); break; @@ -158,8 +171,13 @@ export default class Srch extends Component { defaultActiveFirstOption={false} onSelect={this.onSelect} onSearch={this.handleSearch} - filterOption={(inputValue, option) => - option.props.children.toUpperCase().indexOf(inputValue.toUpperCase()) !== -1 + optionLabelProp="text" + filterOption={(inputValue, option) => { + if (typeof option.props.children !== 'string' ) { + return option.props.children.props.title.toUpperCase().indexOf(inputValue.toUpperCase()) !== -1; + } + return option.props.children.toUpperCase().indexOf(inputValue.toUpperCase()) !== -1 + } } > { + // val = val.replace(/^\{\{(.+)\}\}$/g, '$1'); + this.setState({ + safeConstantInput: val + }); + this.mockClick(0)( encodeURIComponent(val)); + }; + handleParamsInput = (e, clickIndex, paramsIndex) => { let newParamsList = deepEqual(this.state.methodsParamsList); newParamsList[clickIndex].params[paramsIndex] = e; @@ -209,7 +229,7 @@ class ModalPostman extends Component { render() { const { visible, envType } = this.props; - const { methodsParamsList, constantInput } = this.state; + const { methodsParamsList, constantInput, safeConstantInput } = this.state; const outputParams = () => { let str = ''; @@ -254,11 +274,32 @@ class ModalPostman extends Component { accordion > 常量} key="1"> - this.handleConstantsInput(e.target.value, index)} - /> +
+ 注意如果参数值中含有 + {"{"} + {"}"} + | + , + : + 等符号,请在安全常量内输入, + 其余请使用 YAPI 原版常量 +
+ + 原版常量: + this.handleConstantsInput(e.target.value, index)} + /> + + + 安全常量: + this.handleSafeConstantsInput(e.target.value, index)} + /> +
mock数据} key="2"> diff --git a/client/components/ModalPostman/index.scss b/client/components/ModalPostman/index.scss index d6a64ecc8..b21b579c1 100644 --- a/client/components/ModalPostman/index.scss +++ b/client/components/ModalPostman/index.scss @@ -10,6 +10,14 @@ max-height: 500px; min-height: 400px; overflow-y: scroll; + + .notice { + background: antiquewhite; + b { + margin:0 0.2em; + } + } + .ant-radio-group{ width:100%; } @@ -42,7 +50,7 @@ } .modal-postman-preview { background-color: #f5f5f5; - + } .modal-postman-collapse{ .ant-collapse-item > .ant-collapse-header{ @@ -139,5 +147,5 @@ } } - + } diff --git a/client/components/Postman/Postman.js b/client/components/Postman/Postman.js index 4f6b350f5..47d09a88a 100644 --- a/client/components/Postman/Postman.js +++ b/client/components/Postman/Postman.js @@ -113,7 +113,10 @@ const ParamsNameComponent = props => { ); }; ParamsNameComponent.propTypes = { - example: PropTypes.string, + example: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.element + ]), desc: PropTypes.string, name: PropTypes.string }; @@ -1179,7 +1182,7 @@ export default class Run extends Component {

注:Test 脚本只有做自动化测试才执行

- + - +
{InsertCodeMap.map(item => { return ( diff --git a/client/containers/Project/Interface/InterfaceCol/InterfaceColContent.js b/client/containers/Project/Interface/InterfaceCol/InterfaceColContent.js index 85e0a63c3..b58764826 100644 --- a/client/containers/Project/Interface/InterfaceCol/InterfaceColContent.js +++ b/client/containers/Project/Interface/InterfaceCol/InterfaceColContent.js @@ -504,7 +504,7 @@ class InterfaceColContent extends Component { } onChangeTest = d => { - + this.setState({ commonSetting: { ...this.state.commonSetting, @@ -974,12 +974,12 @@ class InterfaceColContent extends Component { >
- + - + { let {commonSetting} = this.state; this.setState({ @@ -993,29 +993,29 @@ class InterfaceColContent extends Component { - + - + - + - + - + - + { let {commonSetting} = this.state; this.setState({ @@ -1029,12 +1029,12 @@ class InterfaceColContent extends Component { - + - +
{ let {commonSetting} = this.state; this.setState({ @@ -1056,7 +1056,7 @@ class InterfaceColContent extends Component { }} /> - +
{InsertCodeMap.map(item => { return ( @@ -1295,7 +1295,7 @@ class InterfaceColContent extends Component { - diff --git a/client/containers/Project/Interface/InterfaceCol/InterfaceColMenu.js b/client/containers/Project/Interface/InterfaceCol/InterfaceColMenu.js index b4bcd9450..ba5b5bd93 100644 --- a/client/containers/Project/Interface/InterfaceCol/InterfaceColMenu.js +++ b/client/containers/Project/Interface/InterfaceCol/InterfaceColMenu.js @@ -3,6 +3,7 @@ import {connect} from 'react-redux'; import {withRouter} from 'react-router'; import produce from 'immer'; import PropTypes from 'prop-types'; +import _ from 'underscore'; import { fetchCaseData, fetchCaseList, @@ -15,7 +16,7 @@ import axios from 'axios'; import MoveCase from './MoveCase'; import ImportInterface from './ImportInterface'; import {Button, Form, Icon, Input, message, Modal, Tooltip, Tree} from 'antd'; -import {arrayChangeIndex, findMeInTree} from '../../../../common.js'; +import {arrayChangeIndex, findMeInTree, findCategoriesById} from '../../../../common'; import './InterfaceColMenu.scss'; const TreeNode = Tree.TreeNode; @@ -80,6 +81,7 @@ export default class InterfaceColMenu extends Component { setColData: PropTypes.func, currCaseId: PropTypes.number, history: PropTypes.object, + location: PropTypes.object, isRander: PropTypes.bool, // list: PropTypes.array, router: PropTypes.object, @@ -97,6 +99,7 @@ export default class InterfaceColMenu extends Component { importInterVisible: false, importInterIds: [], importColId: 0, + selectedKey: [], expandedKeys: [], list: [], delIcon: null, @@ -117,49 +120,64 @@ export default class InterfaceColMenu extends Component { } componentWillReceiveProps(nextProps) { - console.log({"this.props":this.props}) + // console.log({"this.props":this.props}) if (this.props.interfaceColList !== nextProps.interfaceColList) { this.setState({ list: nextProps.interfaceColList }); } - if(this.state.expandedKeys.length===0){ - this.initexpandedKeys(this.state.list); + const { pathname } = this.props.location; + const { pathname: nextPathname } = nextProps.location; + if (pathname !== nextPathname || this.state.expandedKeys.length===0) { + this.initexpandedKeys(nextProps.interfaceColList, nextProps); } - // console.log({"componentWillReceiveProps.props": this.props, "state": this.state}) } - initexpandedKeys =list=>{ + initexpandedKeys = (list, props) => { let treePath=[]; - let colid=0; - let selectedKey=[]; + let selectedKey = []; try { - switch (this.props.router.params.action) { - case 'case': - colid = this.props.currCase.col_id; - selectedKey.push('case_'+this.props.router.params.actionId); + const { action, actionId } = props.router.params; + + const { expandedKeys } = this.state; + + switch (action) { + case 'case': { + let ids = findCategoriesById(list, Number(actionId), 'caseList'); + ids = ids.map(it => { + return 'col_' + it + }); + selectedKey.push('case_' + actionId); + const newExpandedKeys = _.uniq([...expandedKeys, ...ids]); + this.setState({ + expandedKeys: newExpandedKeys, + selectedKey:selectedKey + }); break; - case 'col': - colid = Number(this.props.router.params.actionId); - selectedKey.push('col_'+this.props.router.params.actionId); + } + case 'col': { + let colid = Number(actionId); + selectedKey.push('col_' + actionId); + if (colid) { + treePath = findMeInTree(list, colid).treePath.slice(); + treePath.push(colid); + treePath = treePath.map(it => { + return 'col_' + it + }); + const newExpandedKeys = _.uniq([...expandedKeys,...treePath]); + this.setState( + { + expandedKeys: newExpandedKeys, + selectedKey:selectedKey + } + ) + } break; + } default: break; } - if (colid) { - treePath = findMeInTree(list, colid).treePath; - treePath.push(colid); - treePath = treePath.map(it => { - return 'col_' + it - }); - this.setState( - { - expandedKeys: treePath, - selectedKey:selectedKey - } - ) - } }catch (e){ } } @@ -198,10 +216,27 @@ export default class InterfaceColMenu extends Component { onSelect = (keys, e) => { - if (keys.length) { - const type = keys[0].split('_')[0]; - const id = keys[0].split('_')[1]; + let key = e.node.props.eventKey; + if (key) { + const type = key.split('_')[0]; + const id = key.split('_')[1]; const project_id = this.props.match.params.id; + const { expandedKeys, selectedKey } = this.state; + + if (expandedKeys.includes(key) && selectedKey.includes(key)) { + this.setState({ + expandedKeys: expandedKeys.filter(i => i !== key), + selectedKey: keys + }) + } else { + this.setState({ + expandedKeys: type === 'col' ? [...expandedKeys, key] : expandedKeys, + selectedKey: keys + }) + } + + if (selectedKey.includes(key)) return; + if (type === 'col') { this.props.setColData({ isRander: false @@ -214,28 +249,12 @@ export default class InterfaceColMenu extends Component { this.props.history.push('/project/' + project_id + '/interface/case/' + id); } } + }; - let key = e.node.props.eventKey; - console.log({key}) - let ex=JSON.parse(JSON.stringify(this.state.expandedKeys)); - if (ex.indexOf(key) === -1) { - ex.push(key); - this.setState({ - expandedKeys: ex, - selectedKey: [key] - }); - } else { - let nex=ex; - if(!e.selected){ - nex=ex.filter(it=>{return it!==key}) - } - this.setState({ - expandedKeys: nex, - selectedKey: [key] - }) - } - - + onExpand = (expandedKeys) => { + this.setState({ + expandedKeys + }) }; showDelColConfirm = colId => { @@ -313,7 +332,7 @@ export default class InterfaceColMenu extends Component { let data = caseData.payload.data.data; data = JSON.parse(JSON.stringify(data)); data.casename=`${data.casename}_copy` - delete data._id + delete data._id const res = await axios.post('/api/col/add_case',data); if (!res.data.errcode) { message.success('克隆用例成功'); @@ -632,6 +651,7 @@ export default class InterfaceColMenu extends Component { render() { + // console.log('this.state.expandedKeys: ', this.state.expandedKeys); // const { currColId, currCaseId, isShowCol } = this.props; const {colModalType, colModalVisible, importInterVisible, currentCol} = this.state; const currProjectId = this.props.match.params.id; @@ -789,6 +809,7 @@ export default class InterfaceColMenu extends Component { expandedKeys={this.state.expandedKeys} selectedKeys={this.state.selectedKey} onSelect={this.onSelect} + onExpand={this.onExpand} draggable onDrop={this.onDrop} > diff --git a/client/containers/Project/Interface/InterfaceList/InterfaceMenu.js b/client/containers/Project/Interface/InterfaceList/InterfaceMenu.js index 5814f0a49..4dd14d257 100644 --- a/client/containers/Project/Interface/InterfaceList/InterfaceMenu.js +++ b/client/containers/Project/Interface/InterfaceList/InterfaceMenu.js @@ -1,6 +1,7 @@ import React, {PureComponent as Component} from 'react'; import {connect} from 'react-redux'; import PropTypes from 'prop-types'; +import _ from 'underscore'; import { deleteInterfaceCatData, deleteInterfaceData, @@ -18,7 +19,7 @@ import AddInterfaceCatForm from './AddInterfaceCatForm'; import axios from 'axios'; import {Link, withRouter} from 'react-router-dom'; import produce from 'immer'; -import {arrayChangeIndex,findMeInTree,isContained} from '../../../../common.js'; +import {arrayChangeIndex,findMeInTree,isContained, findCategoriesById } from '../../../../common.js'; import './interfaceMenu.scss'; @@ -59,6 +60,7 @@ class InterfaceMenu extends Component { deleteInterfaceData: PropTypes.func, initInterface: PropTypes.func, history: PropTypes.object, + location: PropTypes.object, router: PropTypes.object, getProject: PropTypes.func, fetchInterfaceCatList: PropTypes.func, @@ -69,7 +71,9 @@ class InterfaceMenu extends Component { moveInterVisible: false, moveId: 0, moveToProjectId: 0, - moveToCatId: 0 + moveToCatId: 0, + expandedKeys: [], + selectedKey: [] }; constructor(props) { @@ -120,20 +124,20 @@ class InterfaceMenu extends Component { componentWillMount() { this.handleRequest(); - } componentWillReceiveProps(nextProps) { - console.log({"this.props":this.props}) + // console.log('nextProps: ', nextProps); if (this.props.list !== nextProps.list) { // console.log('next', nextProps.list) this.setState({ list: nextProps.list }); } - //this.appendSetExpandedKeys(this.props.curProject.cat); - if(this.state.expandedKeys.length===0){ - this.initexpandedKeys(this.props.curProject.cat); + const { pathname } = this.props.location; + const { pathname: nextPathname } = nextProps.location; + if (pathname !== nextPathname || this.state.expandedKeys.length===0) { + this.initexpandedKeys(nextProps.curProject.cat, nextProps); } } appendSetExpandedKeys=list=>{ @@ -172,9 +176,7 @@ class InterfaceMenu extends Component { onSelect = (selectedKeys,e) => { const {history, match} = this.props; - //console.log({"onselect": this.state,selectedKeys,e}); let key = e.node.props.eventKey; - let basepath = '/project/' + match.params.id + '/interface/api'; if (key === 'root') { history.push(basepath); @@ -182,26 +184,26 @@ class InterfaceMenu extends Component { history.push(basepath + '/' + key); } + const { expandedKeys, selectedKey } = this.state; - let ex=JSON.parse(JSON.stringify(this.state.expandedKeys)); - if (ex.indexOf(key) === -1) { - ex.push(key); + if (expandedKeys.includes(key) && selectedKey.includes(key)) { this.setState({ - expandedKeys: ex, + expandedKeys: expandedKeys.filter(i => i !== key), selectedKey: [key] - }); + }) } else { - let nex=ex; - if(!e.selected){ - nex=ex.filter(it=>{return it!==key}) - } this.setState({ - expandedKeys: nex, + expandedKeys: key.startsWith('cat_') ? [...expandedKeys, key] : expandedKeys, selectedKey: [key] }) } + }; + onExpand = (expandedKeys) => { + this.setState({ + expandedKeys + }) }; @@ -545,33 +547,54 @@ class InterfaceMenu extends Component { }) }; - initexpandedKeys =list=>{ - + initexpandedKeys =(list, props)=>{ try { - let treePath=[]; - let catid=0; - let par=this.props.router.params; - let selectedKey=[]; - if(par.actionId) { - catid = Number(par.actionId.indexOf('cat_')===0?par.actionId.substr(4):this.props.inter.catid); - selectedKey.push(par.actionId); + let treePath = []; + let catid = 0; + let selectedKey = []; + const router = props.router || {}; + const { actionId = "" } = router.params || {}; + if (actionId) { + // catid = Number(actionId.indexOf('cat_') ===0 ? actionId.substr(4) : props.inter.catid); + selectedKey.push(actionId); + } + + if (actionId.startsWith('cat_')) { + catid = Number(actionId.substr(4)); + // 目录 + } else { + let ids = findCategoriesById(props.list, Number(actionId)); + ids = ids.map(it => { + return 'cat_' + it + }); + const { expandedKeys } = this.state; + const newExpandedKeys = _.uniq([...expandedKeys, ...ids]); + this.setState( + { + expandedKeys: newExpandedKeys, + selectedKey:selectedKey + } + ) } + if (catid) { - treePath = findMeInTree(list, catid).treePath; + treePath = findMeInTree(list, catid).treePath.slice(); treePath.push(catid); treePath = treePath.map(it => { return 'cat_' + it }); + const { expandedKeys } = this.state; + const newExpandedKeys = _.uniq([...expandedKeys, ...treePath]); this.setState( { - expandedKeys: treePath, + expandedKeys: newExpandedKeys, selectedKey:selectedKey } ) } }catch (e){ } - } + }; render() { @@ -805,7 +828,6 @@ class InterfaceMenu extends Component { let menuList=this.state.list; - // //console.log("render this.state.moveInterVisible " + this.state.moveInterVisible); //console.log({menuList}); return ( @@ -821,6 +843,7 @@ class InterfaceMenu extends Component { selectedKeys={this.state.selectedKey} expandedKeys={this.state.expandedKeys} onSelect={this.onSelect} + onExpand={this.onExpand} draggable onDrop={this.onDrop} > diff --git a/client/containers/Project/Interface/InterfaceList/Run/AddColModal.js b/client/containers/Project/Interface/InterfaceList/Run/AddColModal.js index 263f44e96..8d7b53590 100644 --- a/client/containers/Project/Interface/InterfaceList/Run/AddColModal.js +++ b/client/containers/Project/Interface/InterfaceList/Run/AddColModal.js @@ -97,10 +97,10 @@ export default class AddColModal extends Component { onCancel={this.props.onCancel} > - +
接口用例名:
- + - +
集合名:
- +
- +
简介:
- +