fix: 测试集合环境变量展示问题

This commit is contained in:
gaoxiaolin.gao 2018-05-22 16:42:03 +08:00
parent 52d1b51810
commit e5aff7a41c
6 changed files with 553 additions and 460 deletions

View File

@ -10,23 +10,36 @@ import './index.scss';
export default class CaseEnv extends React.Component {
constructor(props) {
super(props);
// this.state = { envList: [] };
}
static propTypes = {
envList: PropTypes.array,
currProjectEnvChange: PropTypes.func
currProjectEnvChange: PropTypes.func,
changeClose: PropTypes.func,
collapseKey: PropTypes.any,
envValue: PropTypes.object
};
callback = (key) => {
this.props.changeClose && this.props.changeClose(key)
}
render() {
return (
<Collapse
style={{
margin: 0,
marginBottom: '16px'
}}
onChange={this.callback}
// activeKey={this.state.activeKey}
activeKey={this.props.collapseKey}
>
<Panel
header={
@ -54,14 +67,14 @@ export default class CaseEnv extends React.Component {
className="env-item"
>
<Col span={6} className="label">
{item.name}
:
<Tooltip title={item.name} ><span className="label-name">{item.name}</span></Tooltip>
</Col>
<Col span={18}>
<Select
style={{
width: '100%'
}}
value={this.props.envValue[item._id]|| ""}
defaultValue=""
onChange={val => this.props.currProjectEnvChange(val, item._id)}
>

View File

@ -1,7 +1,22 @@
.case-env {
.label {
// width: 100%;
text-align: right;
padding-right: 8px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.label:after{
content: ":";
margin: 0 8px 0 2px;
position: relative;
top: -.5px;
}
.label-name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.env-item {

View File

@ -61,6 +61,7 @@ class Interface extends Component {
isShowCol: PropTypes.bool,
getProject: PropTypes.func,
setColData: PropTypes.func
// fetchInterfaceColList: PropTypes.func
}
constructor(props) {
@ -78,11 +79,11 @@ class Interface extends Component {
this.props.history.push('/project/' + params.id + '/interface/' + action)
}
componentWillMount(){
async componentWillMount(){
this.props.setColData({
isShowCol: true
})
// this.props.getProject(this.props.match.params.id)
// await this.props.fetchInterfaceColList(this.props.match.params.id)
}
render() {
const { action } = this.props.match.params;

View File

@ -1,25 +1,19 @@
import React, {PureComponent as Component} from 'react';
import {connect} from 'react-redux';
import React, { PureComponent as Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import {withRouter} from 'react-router';
import {Link} from 'react-router-dom';
import { withRouter } from 'react-router';
import { Link } from 'react-router-dom';
//import constants from '../../../../constants/variable.js'
import { Tooltip, Icon, Button, Row, Col, Spin, Modal, message, Select, Switch } from 'antd';
import {
Tooltip,
Icon,
Button,
Row,
Col,
Spin,
Modal,
message,
Select,
Switch
} from 'antd';
import {fetchInterfaceColList, fetchCaseList, setColData, fetchCaseEnvList} from '../../../../reducer/modules/interfaceCol';
fetchInterfaceColList,
fetchCaseList,
setColData,
fetchCaseEnvList
} from '../../../../reducer/modules/interfaceCol';
import HTML5Backend from 'react-dnd-html5-backend';
import {getToken, getEnv} from '../../../../reducer/modules/project';
import {DragDropContext} from 'react-dnd';
import { getToken, getEnv } from '../../../../reducer/modules/project';
import { DragDropContext } from 'react-dnd';
import AceEditor from 'client/components/AceEditor/AceEditor';
import * as Table from 'reactabular-table';
import * as dnd from 'reactabular-dnd';
@ -27,10 +21,15 @@ import * as resolve from 'table-resolver';
import axios from 'axios';
import CaseReport from './CaseReport.js';
import _ from 'underscore';
import {initCrossRequest} from 'client/components/Postman/CheckCrossInstall.js';
import { initCrossRequest } from 'client/components/Postman/CheckCrossInstall.js';
import produce from 'immer';
const {handleParams, crossRequest, handleCurrDomain, checkNameIsExistInArray} = require('common/postmanLib.js');
const {handleParamsValue, json_parse} = require('common/utils.js');
const {
handleParams,
crossRequest,
handleCurrDomain,
checkNameIsExistInArray
} = require('common/postmanLib.js');
const { handleParamsValue, json_parse } = require('common/utils.js');
import CaseEnv from 'client/components/CaseEnv';
const Option = Select.Option;
@ -45,28 +44,31 @@ function handleReport(json) {
}
}
@connect(state => {
return {
interfaceColList: state.interfaceCol.interfaceColList,
currColId: state.interfaceCol.currColId,
currCaseId: state.interfaceCol.currCaseId,
isShowCol: state.interfaceCol.isShowCol,
isRander: state.interfaceCol.isRander,
currCaseList: state.interfaceCol.currCaseList,
currProject: state.project.currProject,
token: state.project.token,
envList: state.interfaceCol.envList,
curProjectRole: state.project.currProject.role,
projectEnv: state.project.projectEnv
};
}, {
fetchInterfaceColList,
fetchCaseList,
setColData,
getToken,
getEnv,
fetchCaseEnvList
})
@connect(
state => {
return {
interfaceColList: state.interfaceCol.interfaceColList,
currColId: state.interfaceCol.currColId,
currCaseId: state.interfaceCol.currCaseId,
isShowCol: state.interfaceCol.isShowCol,
isRander: state.interfaceCol.isRander,
currCaseList: state.interfaceCol.currCaseList,
currProject: state.project.currProject,
token: state.project.token,
envList: state.interfaceCol.envList,
curProjectRole: state.project.currProject.role,
projectEnv: state.project.projectEnv
};
},
{
fetchInterfaceColList,
fetchCaseList,
setColData,
getToken,
getEnv,
fetchCaseEnvList
}
)
@withRouter
@DragDropContext(HTML5Backend)
class InterfaceColContent extends Component {
@ -109,60 +111,45 @@ class InterfaceColContent extends Component {
autoVisible: false,
mode: 'html',
email: false,
currColEnvObj: {}
currColEnvObj: {},
collapseKey: ''
};
this.onRow = this
.onRow
.bind(this);
this.onMoveRow = this
.onMoveRow
.bind(this);
this.onRow = this.onRow.bind(this);
this.onMoveRow = this.onMoveRow.bind(this);
}
async componentWillMount() {
const result = await this
.props
.fetchInterfaceColList(this.props.match.params.id);
await this
.props
.getToken(this.props.match.params.id);
let {currColId} = this.props;
const result = await this.props.fetchInterfaceColList(this.props.match.params.id);
await this.props.getToken(this.props.match.params.id);
let { currColId } = this.props;
const params = this.props.match.params;
const {actionId} = params;
const { actionId } = params;
this.currColId = currColId = +actionId || result.payload.data.data[0]._id;
this
.props
.history
.push('/project/' + params.id + '/interface/col/' + currColId);
this.props.history.push('/project/' + params.id + '/interface/col/' + currColId);
if (currColId && currColId != 0) {
let result = await this
.props
.fetchCaseList(currColId);
let result = await this.props.fetchCaseList(currColId);
if (result.payload.data.errcode === 0) {
this.reports = handleReport(result.payload.data.colData.test_report);
}
await this
.props
.fetchCaseEnvList(currColId);
this.props.setColData({
currColId: +currColId,
isShowCol: true,
isRander: false
});
this
.props
.setColData({
currColId: + currColId,
isShowCol: true,
isRander: false
});
await this.props.fetchCaseEnvList(currColId);
this.handleColdata(this.props.currCaseList);
}
this._crossRequestInterval = initCrossRequest(hasPlugin => {
this.setState({hasPlugin: hasPlugin});
this.setState({ hasPlugin: hasPlugin });
});
// console.log('project', this.props.currProject) const group_id =
// this.props.currProject.group_id; await this.props.fetchProjectList(group_id)
}
componentWillUnmount() {
@ -174,8 +161,9 @@ class InterfaceColContent extends Component {
let envItem = _.find(this.props.envList, item => {
return item._id === project_id;
});
// let env = this.props.currProject.env; console.log('env', case_env);
let currDomain = handleCurrDomain(envItem.env, case_env);
let currDomain = handleCurrDomain(envItem && envItem.env, case_env);
let header = currDomain.header;
// console.log('header', header)
header.forEach(item => {
@ -192,44 +180,42 @@ class InterfaceColContent extends Component {
};
handleColdata = (rows, currColEnv = '', project_id = null) => {
// let newRows = JSON.parse(JSON.stringify(rows)) newRows = newRows.map((item)
// => { item.id = item._id; item._test_status = item.test_status;
// item.case_env = this.state.currColEnv || item.case_env item.req_headers =
// this.handleReqHeader(item.req_headers) return item; }) const { currColEnv,
// curProjectId } = this.state; if (curProjectId && currColEnv) { }
let that = this;
let newRows = produce(rows, draftRows => {
draftRows.map(item => {
item.id = item._id;
item._test_status = item.test_status;
item.case_env = item.project_id === project_id
? currColEnv || item.case_env
: item.case_env;
item.case_env =
item.project_id === project_id ? currColEnv || item.case_env : item.case_env;
item.req_headers = that.handleReqHeader(item.project_id, item.req_headers, item.case_env);
return item;
});
});
this.setState({rows: newRows});
this.setState({ rows: newRows });
};
executeTests = async() => {
executeTests = async () => {
for (let i = 0, l = this.state.rows.length, newRows, curitem; i < l; i++) {
let {rows} = this.state;
let { rows } = this.state;
let envItem = _.find(this.props.envList, item => {
return item._id === rows[i].project_id;
});
curitem = Object.assign({}, rows[i], {
env: envItem.env,
pre_script: this.props.currProject.pre_script,
after_script: this.props.currProject.after_script
}, {test_status: 'loading'});
curitem = Object.assign(
{},
rows[i],
{
env: envItem.env,
pre_script: this.props.currProject.pre_script,
after_script: this.props.currProject.after_script
},
{ test_status: 'loading' }
);
newRows = [].concat([], rows);
newRows[i] = curitem;
this.setState({rows: newRows});
this.setState({ rows: newRows });
// console.log('newRows', newRows);
let status = 'error',
result;
@ -255,10 +241,10 @@ class InterfaceColContent extends Component {
body: result.res_body
};
curitem = Object.assign({}, rows[i], {test_status: status});
curitem = Object.assign({}, rows[i], { test_status: status });
newRows = [].concat([], rows);
newRows[i] = curitem;
this.setState({rows: newRows});
this.setState({ rows: newRows });
}
await axios.post('/api/col/up_col', {
col_id: this.props.currColId,
@ -295,12 +281,15 @@ class InterfaceColContent extends Component {
let validRes = [];
let responseData = Object.assign({}, {
status: data.res.status,
body: res,
header: data.res.header,
statusText: data.res.statusText
});
let responseData = Object.assign(
{},
{
status: data.res.status,
body: res,
header: data.res.header,
statusText: data.res.statusText
}
);
await this.handleScriptTest(interfaceData, responseData, validRes, requestParams);
if (validRes.length === 0) {
result.code = 0;
@ -330,7 +319,7 @@ class InterfaceColContent extends Component {
};
//response, validRes
handleScriptTest = async(interfaceData, response, validRes, requestParams) => {
handleScriptTest = async (interfaceData, response, validRes, requestParams) => {
if (interfaceData.enable_script !== true) {
return null;
}
@ -342,13 +331,9 @@ class InterfaceColContent extends Component {
params: requestParams
});
if (test.data.errcode !== 0) {
test
.data
.data
.logs
.forEach(item => {
validRes.push({message: item});
});
test.data.data.logs.forEach(item => {
validRes.push({ message: item });
});
}
} catch (err) {
validRes.push({
@ -376,80 +361,85 @@ class InterfaceColContent extends Component {
};
onRow(row) {
return {rowId: row.id, onMove: this.onMoveRow, onDrop: this.onDrop};
return { rowId: row.id, onMove: this.onMoveRow, onDrop: this.onDrop };
}
onDrop = () => {
let changes = [];
this
.state
.rows
.forEach((item, index) => {
changes.push({id: item._id, index: index});
});
axios
.post('/api/col/up_case_index', changes)
.then(() => {
this
.props
.fetchInterfaceColList(this.props.match.params.id);
});
this.state.rows.forEach((item, index) => {
changes.push({ id: item._id, index: index });
});
axios.post('/api/col/up_case_index', changes).then(() => {
this.props.fetchInterfaceColList(this.props.match.params.id);
});
};
onMoveRow({sourceRowId, targetRowId}) {
let rows = dnd.moveRows({sourceRowId, targetRowId})(this.state.rows);
onMoveRow({ sourceRowId, targetRowId }) {
let rows = dnd.moveRows({ sourceRowId, targetRowId })(this.state.rows);
if (rows) {
this.setState({rows});
this.setState({ rows });
}
}
async componentWillReceiveProps(nextProps) {
let newColId = !isNaN(nextProps.match.params.actionId)
? + nextProps.match.params.actionId
: 0;
let newColId = !isNaN(nextProps.match.params.actionId) ? +nextProps.match.params.actionId : 0;
if ((newColId && this.currColId && newColId !== this.currColId) || nextProps.isRander) {
this.currColId = newColId;
await this
.props
.fetchCaseList(newColId);
this
.props
.setColData({
currColId: + newColId,
isShowCol: true,
isRander: false
});
this.props.setColData({
currColId: +newColId,
isShowCol: true,
isRander: false
});
await this.props.fetchCaseList(newColId);
await this.props.fetchCaseEnvList(newColId);
this.changeCollapseClose();
this.handleColdata(this.props.currCaseList);
await this
.props
.fetchCaseEnvList(newColId);
}
}
// 测试用例环境面板折叠
changeCollapseClose = key => {
if (key) {
this.setState({
collapseKey: key
});
} else {
this.setState({
collapseKey: '',
currColEnvObj: {}
});
}
};
openReport = id => {
if (!this.reports[id]) {
return message.warn('还没有生成报告');
}
this.setState({visible: true, curCaseid: id});
this.setState({ visible: true, curCaseid: id });
};
openAdv = id => {
let findCase = _.find(this.props.currCaseList, item => item.id === id);
this.setState({enableScript: findCase.enable_script, curScript: findCase.test_script, advVisible: true, curCaseid: id});
this.setState({
enableScript: findCase.enable_script,
curScript: findCase.test_script,
advVisible: true,
curCaseid: id
});
};
handleScriptChange = d => {
this.setState({curScript: d.text});
this.setState({ curScript: d.text });
};
handleAdvCancel = () => {
this.setState({advVisible: false});
this.setState({ advVisible: false });
};
handleAdvOk = async() => {
const {curCaseid, enableScript, curScript} = this.state;
handleAdvOk = async () => {
const { curCaseid, enableScript, curScript } = this.state;
const res = await axios.post('/api/col/up_case', {
id: curCaseid,
test_script: curScript,
@ -458,42 +448,37 @@ class InterfaceColContent extends Component {
if (res.data.errcode === 0) {
message.success('更新成功');
}
this.setState({advVisible: false});
this.setState({ advVisible: false });
let currColId = this.currColId;
await this
.props
.fetchCaseList(currColId);
this
.props
.setColData({
currColId: + currColId,
isShowCol: true,
isRander: false
});
this.props.setColData({
currColId: +currColId,
isShowCol: true,
isRander: false
});
await this.props.fetchCaseList(currColId);
this.handleColdata(this.props.currCaseList);
};
handleCancel = () => {
this.setState({visible: false});
this.setState({ visible: false });
};
// colEnvChange = envName => { this.setState( { currColEnv: envName
// }, () => this.handleColdata(this.props.currCaseList) ); }; 处理各项目下的环境变量
currProjectEnvChange = (envName, project_id) => {
let currColEnvObj = {
...this.state.currColEnvObj,
[project_id]: envName
};
this.setState({currColEnvObj});
this.setState({ currColEnvObj });
this.handleColdata(this.props.currCaseList, envName, project_id);
};
autoTests = () => {
this.setState({autoVisible: true});
this.setState({ autoVisible: true, currColEnvObj: {}, collapseKey: ''});
};
handleAuto = () => {
this.setState({autoVisible: false, email: false, mode: 'html'});
this.setState({ autoVisible: false, email: false, mode: 'html', currColEnvObj: {}, collapseKey: '' });
};
copyUrl = url => {
@ -502,19 +487,17 @@ class InterfaceColContent extends Component {
};
modeChange = mode => {
this.setState({mode});
this.setState({ mode });
};
emailChange = email => {
this.setState({email});
this.setState({ email });
};
handleColEnvObj = envObj => {
let str = '';
for (let key in envObj) {
str += envObj[key]
? `&env_${key}=${envObj[key]}`
: '';
str += envObj[key] ? `&env_${key}=${envObj[key]}` : '';
}
return str;
};
@ -534,27 +517,43 @@ class InterfaceColContent extends Component {
}
},
cell: {
formatters: [(text, {rowData}) => {
formatters: [
(text, { rowData }) => {
let record = rowData;
return (
<Link to={'/project/' + currProjectId + '/interface/case/' + record._id}>
{record.casename.length > 23
? record
.casename
.substr(0, 20) + '...'
? record.casename.substr(0, 20) + '...'
: record.casename}
</Link>
);
}
]
}
}, {
},
{
header: {
label: 'key',
formatters: [() => {
formatters: [
() => {
return (
<Tooltip
title={<span> 每个用例都有唯一的key用于获取所匹配接口的响应数据例如使用 {' '} <a href = "https://yapi.ymfe.org/case.html#变量参数" className = "link-tooltip" target = "blank" > 变量参数 </a>{' '} </span>}>
title={
<span>
{' '}
每个用例都有唯一的key用于获取所匹配接口的响应数据例如使用{' '}
<a
href="https://yapi.ymfe.org/case.html#变量参数"
className="link-tooltip"
target="blank"
>
{' '}
变量参数{' '}
</a>{' '}
功能{' '}
</span>
}
>
Key
</Tooltip>
);
@ -567,12 +566,14 @@ class InterfaceColContent extends Component {
}
},
cell: {
formatters: [(value, {rowData}) => {
formatters: [
(value, { rowData }) => {
return <span>{rowData._id}</span>;
}
]
}
}, {
},
{
property: 'test_status',
header: {
label: '状态'
@ -583,15 +584,14 @@ class InterfaceColContent extends Component {
}
},
cell: {
formatters: [(value, {rowData}) => {
formatters: [
(value, { rowData }) => {
let id = rowData._id;
let code = this.reports[id]
? this.reports[id].code
: 0;
let code = this.reports[id] ? this.reports[id].code : 0;
if (rowData.test_status === 'loading') {
return (
<div>
<Spin/>
<Spin />
</div>
);
}
@ -603,9 +603,10 @@ class InterfaceColContent extends Component {
<Tooltip title="Pass">
<Icon
style={{
color: '#00a854'
}}
type="check-circle"/>
color: '#00a854'
}}
type="check-circle"
/>
</Tooltip>
</div>
);
@ -616,8 +617,9 @@ class InterfaceColContent extends Component {
<Icon
type="info-circle"
style={{
color: '#f04134'
}}/>
color: '#f04134'
}}
/>
</Tooltip>
</div>
);
@ -628,8 +630,9 @@ class InterfaceColContent extends Component {
<Icon
type="exclamation-circle"
style={{
color: '#ffbf00'
}}/>
color: '#ffbf00'
}}
/>
</Tooltip>
</div>
);
@ -638,36 +641,38 @@ class InterfaceColContent extends Component {
<div>
<Icon
style={{
color: '#00a854'
}}
type="check-circle"/>
color: '#00a854'
}}
type="check-circle"
/>
</div>
);
}
}
]
}
}, {
},
{
property: 'path',
header: {
label: '接口路径'
},
cell: {
formatters: [(text, {rowData}) => {
formatters: [
(text, { rowData }) => {
let record = rowData;
return (
<Tooltip title="跳转到对应接口">
<Link to={`/project/${record.project_id}/interface/api/${record.interface_id}`}>
{record.path.length > 23
? record.path + '...'
: record.path}
{record.path.length > 23 ? record.path + '...' : record.path}
</Link>
</Tooltip>
);
}
]
}
}, {
},
{
header: {
label: '测试报告'
},
@ -677,7 +682,8 @@ class InterfaceColContent extends Component {
}
},
cell: {
formatters: [(text, {rowData}) => {
formatters: [
(text, { rowData }) => {
let reportFun = () => {
if (!this.reports[rowData.id]) {
return null;
@ -690,7 +696,7 @@ class InterfaceColContent extends Component {
}
}
];
const {rows} = this.state;
const { rows } = this.state;
const components = {
header: {
cell: dnd.Header
@ -699,34 +705,39 @@ class InterfaceColContent extends Component {
row: dnd.Row
}
};
const resolvedColumns = resolve.columnChildren({columns});
const resolvedRows = resolve.resolve({columns: resolvedColumns, method: resolve.nested})(rows);
const resolvedColumns = resolve.columnChildren({ columns });
const resolvedRows = resolve.resolve({ columns: resolvedColumns, method: resolve.nested })(
rows
);
const localUrl = location.protocol + '//' + location.hostname + (location.port !== ''
? ':' + location.port
: '');
const localUrl =
location.protocol +
'//' +
location.hostname +
(location.port !== '' ? ':' + location.port : '');
let currColEnvObj = this.handleColEnvObj(this.state.currColEnvObj);
const autoTestsUrl = `/api/open/run_auto_test?id=${this.props.currColId}&token=${
this.props.token}${currColEnvObj
? currColEnvObj
: ''}&mode=${this.state.mode}&email=${this.state.email}`;
this.props.token
}${currColEnvObj ? currColEnvObj : ''}&mode=${this.state.mode}&email=${this.state.email}`;
return (
<div className="interface-col">
<Row type="flex" justify="center" align="top">
<Col span={6}>
<Col span={5}>
<h2
className="interface-title"
style={{
display: 'inline-block',
margin: '8px 20px 16px'
}}>
display: 'inline-block',
margin: '8px 20px 16px'
}}
>
测试集合&nbsp;<a
target="_blank"
rel="noopener noreferrer"
href="https://yapi.ymfe.org/documents/case.html">
href="https://yapi.ymfe.org/documents/case.html"
>
<Tooltip title="点击查看文档">
<Icon type="question-circle-o"/>
<Icon type="question-circle-o" />
</Tooltip>
</a>
</h2>
@ -735,45 +746,49 @@ class InterfaceColContent extends Component {
<CaseEnv
envList={this.props.envList}
currProjectEnvChange={this.currProjectEnvChange}
envValue={this.state.currColEnvObj}
collapseKey={this.state.collapseKey}
changeClose={this.changeCollapseClose}
/>
</Col>
<Col span={6}>
{this.state.hasPlugin
? (
<div
style={{
<Col span={7}>
{this.state.hasPlugin ? (
<div
style={{
float: 'right',
paddingTop: '8px'
}}>
{this.props.curProjectRole !== 'guest' && (
<Tooltip title="在 YApi 服务端跑自动化测试,测试环境不能为私有网络,请确保 YApi 服务器可以访问到自动化测试环境domain">
<Button
style={{
}}
>
{this.props.curProjectRole !== 'guest' && (
<Tooltip title="在 YApi 服务端跑自动化测试,测试环境不能为私有网络,请确保 YApi 服务器可以访问到自动化测试环境domain">
<Button
style={{
marginRight: '8px'
}}
onClick={this.autoTests}>
服务端测试
</Button>
</Tooltip>
)}
<Button type="primary" onClick={this.executeTests}>
开始测试
</Button>
</div>
)
: (
<Tooltip title="请安装 cross-request Chrome 插件">
<Button
disabled
type="primary"
style={{
onClick={this.autoTests}
>
服务端测试
</Button>
</Tooltip>
)}
<Button type="primary" onClick={this.executeTests}>
开始测试
</Button>
</div>
) : (
<Tooltip title="请安装 cross-request Chrome 插件">
<Button
disabled
type="primary"
style={{
float: 'right',
marginTop: '8px'
}}>
开始测试
</Button>
</Tooltip>
)}
}}
>
开始测试
</Button>
</Tooltip>
)}
</Col>
</Row>
@ -781,74 +796,86 @@ class InterfaceColContent extends Component {
components={components}
columns={resolvedColumns}
style={{
width: '100%',
borderCollapse: 'collapse'
}}>
width: '100%',
borderCollapse: 'collapse'
}}
>
<Table.Header
className="interface-col-table-header"
headerRows={resolve.headerRows({columns})}/>
headerRows={resolve.headerRows({ columns })}
/>
<Table.Body
className="interface-col-table-body"
rows={resolvedRows}
rowKey="id"
onRow={this.onRow}/>
onRow={this.onRow}
/>
</Table.Provider>
<Modal
title="测试报告"
width="900px"
style={{
minHeight: '500px'
}}
minHeight: '500px'
}}
visible={this.state.visible}
onCancel={this.handleCancel}
footer={null}>
<CaseReport {...this.reports[this.state.curCaseid]}/>
footer={null}
>
<CaseReport {...this.reports[this.state.curCaseid]} />
</Modal>
<Modal
title="自定义测试脚本"
width="660px"
style={{
minHeight: '500px'
}}
minHeight: '500px'
}}
visible={this.state.advVisible}
onCancel={this.handleAdvCancel}
onOk={this.handleAdvOk}
maskClosable={false}>
maskClosable={false}
>
<h3>
是否开启:&nbsp;
<Switch
checked={this.state.enableScript}
onChange={e => this.setState({enableScript: e})}/>
onChange={e => this.setState({ enableScript: e })}
/>
</h3>
<AceEditor
className="case-script"
data={this.state.curScript}
onChange={this.handleScriptChange}/>
onChange={this.handleScriptChange}
/>
</Modal>
<Modal
{this.state.autoVisible && <Modal
title="服务端自动化测试"
width="780px"
style={{
minHeight: '500px'
}}
minHeight: '500px'
}}
visible={this.state.autoVisible}
onCancel={this.handleAuto}
className="autoTestsModal"
footer={null}>
footer={null}
>
<Row type="flex" justify="space-around" className="row" align="top">
<Col span={3} className="label" style={{paddingTop: '16px'}}>
<Col span={3} className="label" style={{ paddingTop: '16px' }}>
选择环境
<Tooltip title="默认使用测试用例选择的环境">
<Icon type="question-circle-o"/>
<Icon type="question-circle-o" />
</Tooltip>
&nbsp;
</Col>
<Col span={21}>
<CaseEnv
envList={this.props.envList}
currProjectEnvChange={this.currProjectEnvChange}/>
currProjectEnvChange={this.currProjectEnvChange}
envValue={this.state.currColEnvObj}
collapseKey={this.state.collapseKey}
changeClose={this.changeCollapseClose}
/>
</Col>
</Row>
<Row type="flex" justify="space-around" className="row" align="middle">
@ -873,8 +900,9 @@ class InterfaceColContent extends Component {
<Icon
type="question-circle-o"
style={{
width: '10px'
}}/>
width: '10px'
}}
/>
</Tooltip>
&nbsp;
</Col>
@ -883,7 +911,8 @@ class InterfaceColContent extends Component {
checked={this.state.email}
checkedChildren="开"
unCheckedChildren="关"
onChange={this.emailChange}/>
onChange={this.emailChange}
/>
</Col>
</Row>
@ -894,9 +923,7 @@ class InterfaceColContent extends Component {
</a>
</Col>
<Col span={3}>
<Button
className="copy-btn"
onClick={() => this.copyUrl(localUrl + autoTestsUrl)}>
<Button className="copy-btn" onClick={() => this.copyUrl(localUrl + autoTestsUrl)}>
复制
</Button>
</Col>
@ -904,7 +931,7 @@ class InterfaceColContent extends Component {
<div className="autoTestMsg">
访问该URL可以测试所有用例请确保YApi服务器可以访问到环境配置的 domain
</div>
</Modal>
</Modal>}
</div>
);
}

View File

@ -1,51 +1,45 @@
import React, { PureComponent as Component } from 'react'
import React, { PureComponent as Component } from 'react';
import { connect } from 'react-redux';
import { withRouter } from 'react-router'
import PropTypes from 'prop-types'
import { fetchInterfaceColList, fetchInterfaceCaseList, setColData, fetchCaseList } from '../../../../reducer/modules/interfaceCol'
import { withRouter } from 'react-router';
import PropTypes from 'prop-types';
import {
fetchInterfaceColList,
fetchInterfaceCaseList,
setColData,
fetchCaseList
} from '../../../../reducer/modules/interfaceCol';
// import { fetchInterfaceListMenu } from '../../../../reducer/modules/interface.js';
import { fetchProjectList } from '../../../../reducer/modules/project'
import { fetchProjectList } from '../../../../reducer/modules/project';
import axios from 'axios';
// import { Input, Icon, Button, Modal, message, Tooltip, Tree, Dropdown, Menu, Form } from 'antd';
import ImportInterface from './ImportInterface'
import ImportInterface from './ImportInterface';
import { Input, Icon, Button, Modal, message, Tooltip, Tree, Form } from 'antd';
// import produce from 'immer'
import { arrayChangeIndex } from '../../../../common.js'
import { arrayChangeIndex } from '../../../../common.js';
const TreeNode = Tree.TreeNode;
const FormItem = Form.Item;
const confirm = Modal.confirm;
import './InterfaceColMenu.scss'
import './InterfaceColMenu.scss';
const ColModalForm = Form.create()((props) => {
const ColModalForm = Form.create()(props => {
const { visible, onCancel, onCreate, form, title } = props;
const { getFieldDecorator } = form;
return (
<Modal
visible={visible}
title={title}
onCancel={onCancel}
onOk={onCreate}
>
<Modal visible={visible} title={title} onCancel={onCancel} onOk={onCreate}>
<Form layout="vertical">
<FormItem label="集合名">
{getFieldDecorator('colName', {
rules: [{ required: true, message: '请输入集合命名!' }]
})(
<Input />
)}
</FormItem>
<FormItem label="简介">
{getFieldDecorator('colDesc')(<Input type="textarea" />)}
})(<Input />)}
</FormItem>
<FormItem label="简介">{getFieldDecorator('colDesc')(<Input type="textarea" />)}</FormItem>
</Form>
</Modal>
)
);
});
@connect(
state => {
return {
@ -57,7 +51,7 @@ const ColModalForm = Form.create()((props) => {
// 当前项目的信息
curProject: state.project.currProject
// projectList: state.project.projectList
}
};
},
{
fetchInterfaceColList,
@ -70,7 +64,6 @@ const ColModalForm = Form.create()((props) => {
)
@withRouter
export default class InterfaceColMenu extends Component {
static propTypes = {
match: PropTypes.object,
interfaceColList: PropTypes.array,
@ -88,7 +81,7 @@ export default class InterfaceColMenu extends Component {
curProject: PropTypes.object,
fetchProjectList: PropTypes.func
// projectList: PropTypes.array
}
};
state = {
colModalType: '',
@ -102,31 +95,31 @@ export default class InterfaceColMenu extends Component {
list: [],
delIcon: null,
selectedProject: null
}
};
constructor(props) {
super(props)
super(props);
}
componentWillMount() {
this.getList()
this.getList();
}
componentWillReceiveProps(nextProps) {
if (this.props.interfaceColList !== nextProps.interfaceColList) {
this.setState({
list: nextProps.interfaceColList
})
});
}
}
async getList() {
let r = await this.props.fetchInterfaceColList(this.props.match.params.id);
this.setState({
list: r.payload.data.data
})
return r
});
return r;
}
addorEditCol = async () => {
@ -135,9 +128,9 @@ export default class InterfaceColMenu extends Component {
const project_id = this.props.match.params.id;
let res = {};
if (colModalType === 'add') {
res = await axios.post('/api/col/add_col', { name, desc, project_id })
res = await axios.post('/api/col/add_col', { name, desc, project_id });
} else if (colModalType === 'edit') {
res = await axios.post('/api/col/up_col', { name, desc, col_id })
res = await axios.post('/api/col/up_col', { name, desc, col_id });
}
if (!res.data.errcode) {
this.setState({
@ -149,45 +142,44 @@ export default class InterfaceColMenu extends Component {
} else {
message.error(res.data.errmsg);
}
}
};
onExpand = (keys) => {
this.setState({ expands: keys })
onExpand = keys => {
this.setState({ expands: keys });
};
}
onSelect = (keys) => {
onSelect = keys => {
if (keys.length) {
const type = keys[0].split('_')[0];
const id = keys[0].split('_')[1];
const project_id = this.props.match.params.id
const project_id = this.props.match.params.id;
if (type === 'col') {
this.props.setColData({
isRander: false
})
this.props.history.push('/project/' + project_id + '/interface/col/' + id)
});
this.props.history.push('/project/' + project_id + '/interface/col/' + id);
} else {
this.props.setColData({
isRander: false
})
this.props.history.push('/project/' + project_id + '/interface/case/' + id)
});
this.props.history.push('/project/' + project_id + '/interface/case/' + id);
}
}
this.setState({
expands: null
})
}
});
};
showDelColConfirm = (colId) => {
showDelColConfirm = colId => {
let that = this;
const params = this.props.match.params;
confirm({
title: '您确认删除此测试集合',
content: '温馨提示:该操作会删除该集合下所有测试用例,用例删除后无法恢复',
okText:"确认",
cancelText:"取消",
okText: '确认',
cancelText: '取消',
async onOk() {
const res = await axios.get('/api/col/del_col?col_id=' + colId)
const res = await axios.get('/api/col/del_col?col_id=' + colId);
if (!res.data.errcode) {
message.success('删除集合成功');
const result = await that.getList();
@ -199,11 +191,10 @@ export default class InterfaceColMenu extends Component {
}
}
});
}
};
// 复制测试集合
copyInterface = async (item) => {
// 复制测试集合
copyInterface = async item => {
if (this._copyInterfaceSign === true) {
return;
}
@ -227,7 +218,7 @@ export default class InterfaceColMenu extends Component {
new_col_id,
col_id,
project_id
})
});
this._copyInterfaceSign = false;
if (add_case_list_res.data.errcode) {
@ -235,72 +226,72 @@ export default class InterfaceColMenu extends Component {
return;
}
// 刷新接口列表
// 刷新接口列表
// await this.props.fetchInterfaceColList(project_id);
this.getList()
this.props.setColData({ isRander: true })
message.success('克隆测试集成功')
}
this.getList();
this.props.setColData({ isRander: true });
message.success('克隆测试集成功');
};
showNoDelColConfirm = () => {
confirm({
title: '此测试集合为最后一个集合',
content: '温馨提示:建议不要删除'
});
}
showDelCaseConfirm = (caseId) => {
};
showDelCaseConfirm = caseId => {
let that = this;
const params = this.props.match.params;
confirm({
title: '您确认删除此测试用例',
content: '温馨提示:用例删除后无法恢复',
okText:"确认",
cancelText:"取消",
okText: '确认',
cancelText: '取消',
async onOk() {
const res = await axios.get('/api/col/del_case?caseid=' + caseId)
const res = await axios.get('/api/col/del_case?caseid=' + caseId);
if (!res.data.errcode) {
message.success('删除用例成功');
that.getList()
that.getList();
// 如果删除当前选中 case切换路由到集合
if (+caseId === +that.props.currCaseId) {
that.props.history.push('/project/' + params.id + '/interface/col/')
that.props.history.push('/project/' + params.id + '/interface/col/');
} else {
// that.props.fetchInterfaceColList(that.props.match.params.id);
that.props.setColData({ isRander: true })
that.props.setColData({ isRander: true });
}
} else {
message.error(res.data.errmsg);
}
}
});
}
};
showColModal = (type, col) => {
const editCol = type === 'edit' ? { colName: col.name, colDesc: col.desc } : { colName: '', colDesc: '' };
const editCol =
type === 'edit' ? { colName: col.name, colDesc: col.desc } : { colName: '', colDesc: '' };
this.setState({
colModalVisible: true,
colModalType: type || 'add',
editColId: col && col._id
})
this.form.setFieldsValue(editCol)
}
saveFormRef = (form) => {
});
this.form.setFieldsValue(editCol);
};
saveFormRef = form => {
this.form = form;
}
};
selectInterface = (importInterIds, selectedProject) => {
this.setState({ importInterIds, selectedProject })
}
this.setState({ importInterIds, selectedProject });
};
showImportInterfaceModal = async (colId) => {
showImportInterfaceModal = async colId => {
// const projectId = this.props.match.params.id;
// console.log('project', this.props.curProject)
const groupId = this.props.curProject.group_id
await this.props.fetchProjectList(groupId)
const groupId = this.props.curProject.group_id;
await this.props.fetchProjectList(groupId);
// await this.props.fetchInterfaceListMenu(projectId)
this.setState({ importInterVisible: true, importColId: colId })
}
this.setState({ importInterVisible: true, importColId: colId });
};
handleImportOk = async () => {
const project_id = this.state.selectedProject || this.props.match.params.id;
const { importColId, importInterIds } = this.state;
@ -308,23 +299,23 @@ export default class InterfaceColMenu extends Component {
interface_list: importInterIds,
col_id: importColId,
project_id
})
});
if (!res.data.errcode) {
this.setState({ importInterVisible: false })
this.setState({ importInterVisible: false });
message.success('导入集合成功');
// await this.props.fetchInterfaceColList(project_id);
this.getList()
this.getList();
this.props.setColData({ isRander: true })
this.props.setColData({ isRander: true });
} else {
message.error(res.data.errmsg);
}
}
};
handleImportCancel = () => {
this.setState({ importInterVisible: false })
}
this.setState({ importInterVisible: false });
};
filterCol = (e) => {
filterCol = e => {
const value = e.target.value;
// console.log('list', this.props.interfaceColList);
// const newList = produce(this.props.interfaceColList, draftList => {})
@ -333,10 +324,10 @@ export default class InterfaceColMenu extends Component {
filterValue: value,
list: JSON.parse(JSON.stringify(this.props.interfaceColList))
// list: newList
})
}
});
};
onDrop = async (e) => {
onDrop = async e => {
// const projectId = this.props.match.params.id;
const { interfaceColList } = this.props;
const dropColIndex = e.node.props.pos.split('-')[1];
@ -350,31 +341,31 @@ export default class InterfaceColMenu extends Component {
const dragPos = e.dragNode.props.pos.split('-');
const dragIndex = Number(dragPos[dragPos.length - 1]);
if (id.indexOf('col') === -1 ) {
if (id.indexOf('col') === -1) {
if (dropColId === dragColId) {
// 同一个测试集合下的接口交换顺序
let caseList = interfaceColList[dropColIndex].caseList;
let changes = arrayChangeIndex(caseList, dragIndex, dropIndex)
axios.post('/api/col/up_case_index', changes).then()
let changes = arrayChangeIndex(caseList, dragIndex, dropIndex);
axios.post('/api/col/up_case_index', changes).then();
}
await axios.post('/api/col/up_case', { id: id.split('_')[1], col_id: dropColId });
// this.props.fetchInterfaceColList(projectId);
this.getList()
this.props.setColData({ isRander: true })
this.getList();
this.props.setColData({ isRander: true });
} else {
let changes = arrayChangeIndex(interfaceColList, dragIndex, dropIndex);
axios.post('/api/col/up_col_index', changes).then()
this.getList()
let changes = arrayChangeIndex(interfaceColList, dragIndex, dropIndex);
axios.post('/api/col/up_col_index', changes).then();
this.getList();
}
}
};
enterItem = (id) => {
this.setState({ delIcon: id })
}
enterItem = id => {
this.setState({ delIcon: id });
};
leaveItem = () => {
this.setState({ delIcon: null })
}
this.setState({ delIcon: null });
};
render() {
// const { currColId, currCaseId, isShowCol } = this.props;
@ -399,7 +390,8 @@ export default class InterfaceColMenu extends Component {
// };
const defaultExpandedKeys = () => {
const { router, currCase, interfaceColList } = this.props, rNull = { expands: [], selects: [] };
const { router, currCase, interfaceColList } = this.props,
rNull = { expands: [], selects: [] };
if (interfaceColList.length === 0) {
return rNull;
}
@ -410,69 +402,77 @@ export default class InterfaceColMenu extends Component {
}
return {
expands: this.state.expands ? this.state.expands : ['col_' + currCase.col_id],
selects: ['case_' + currCase._id + ""]
}
selects: ['case_' + currCase._id + '']
};
} else {
let col_id = router.params.actionId;
return {
expands: this.state.expands ? this.state.expands : ['col_' + col_id],
selects: ['col_' + col_id]
}
};
}
} else {
return {
expands: this.state.expands ? this.state.expands : ['col_' + interfaceColList[0]._id],
selects: ['root']
}
};
}
}
};
const item_interface_col_create = (interfaceCase) => {
const item_interface_col_create = interfaceCase => {
return (
<TreeNode
style={{ width: '100%' }}
key={'case_' + interfaceCase._id}
title={
<div className="menu-title" onMouseEnter={() => this.enterItem(interfaceCase._id)} onMouseLeave={this.leaveItem} title={interfaceCase.casename}>
title={
<div
className="menu-title"
onMouseEnter={() => this.enterItem(interfaceCase._id)}
onMouseLeave={this.leaveItem}
title={interfaceCase.casename}
>
<span className="casename">{interfaceCase.casename}</span>
<Tooltip title="删除用例">
<Icon type='delete' className="case-delete-icon" onClick={(e) => { e.stopPropagation(); this.showDelCaseConfirm(interfaceCase._id) }} style={{ display: this.state.delIcon == interfaceCase._id ? 'block' : 'none' }} />
<Icon
type="delete"
className="case-delete-icon"
onClick={e => {
e.stopPropagation();
this.showDelCaseConfirm(interfaceCase._id);
}}
style={{ display: this.state.delIcon == interfaceCase._id ? 'block' : 'none' }}
/>
</Tooltip>
</div>
}
></TreeNode>
)
}
/>
);
};
let currentKes = defaultExpandedKeys();
// console.log('currentKey', currentKes)
let list = this.state.list;
if (this.state.filterValue) {
let arr = [];
list = list.filter((item) => {
list = list.filter(item => {
let interfaceFilter = false;
if (item.name.indexOf(this.state.filterValue) === -1) {
item.caseList = item.caseList.filter(inter => {
if (inter.casename.indexOf(this.state.filterValue) === -1) {
return false;
}
}
//arr.push('cat_' + inter.catid)
interfaceFilter = true;
return true;
})
arr.push('col_' + item._id)
return interfaceFilter === true
});
arr.push('col_' + item._id);
return interfaceFilter === true;
}
arr.push('col_' + item._id)
arr.push('col_' + item._id);
return true;
})
});
// console.log('arr', arr);
if (arr.length > 0) {
currentKes.expands = arr;
@ -487,7 +487,14 @@ export default class InterfaceColMenu extends Component {
<div className="interface-filter">
<Input placeholder="搜索测试集合" onChange={this.filterCol} />
<Tooltip placement="bottom" title="添加集合">
<Button type="primary" style={{ marginLeft: "16px" }} onClick={() => this.showColModal('add')} className="btn-filter" >添加集合</Button>
<Button
type="primary"
style={{ marginLeft: '16px' }}
onClick={() => this.showColModal('add')}
className="btn-filter"
>
添加集合
</Button>
</Tooltip>
</div>
<Tree
@ -501,49 +508,78 @@ export default class InterfaceColMenu extends Component {
draggable
onExpand={this.onExpand}
onDrop={this.onDrop}
>
{
list.map((col) => (
<TreeNode
key={'col_' + col._id}
title={
<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' 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) }} />
</Tooltip>
<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()}>
{list.map(col => (
<TreeNode
key={'col_' + col._id}
title={
<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"
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);
}}
/>
</Tooltip>
<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'/>
</Dropdown>*/}
</div>
}
>
{col.caseList.map(item_interface_col_create)}
</TreeNode>
))
}
</div>
}
>
{col.caseList.map(item_interface_col_create)}
</TreeNode>
))}
</Tree>
<ColModalForm
ref={this.saveFormRef}
type={colModalType}
visible={colModalVisible}
onCancel={() => { this.setState({ colModalVisible: false }) }}
onCancel={() => {
this.setState({ colModalVisible: false });
}}
onCreate={this.addorEditCol}
></ColModalForm>
/>
<Modal
title="导入接口到集合"
visible={importInterVisible}
@ -555,6 +591,6 @@ export default class InterfaceColMenu extends Component {
<ImportInterface currProjectId={currProjectId} selectInterface={this.selectInterface} />
</Modal>
</div>
)
);
}
}

View File

@ -115,6 +115,7 @@ export function fetchVariableParamsList(colId) {
}
export function setColData(data) {
return {
type: SET_COL_DATA,
payload: data