mock平台

InterfaceColContent.js 36KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243
  1. import React, { PureComponent as Component } from 'react';
  2. import { connect } from 'react-redux';
  3. import PropTypes from 'prop-types';
  4. import { withRouter } from 'react-router';
  5. import { Link } from 'react-router-dom';
  6. //import constants from '../../../../constants/variable.js'
  7. import { Tooltip, Icon,Input, Button, Row, Col, Spin, Modal, message, Select, Switch } from 'antd';
  8. import {
  9. fetchInterfaceColList,
  10. fetchCaseList,
  11. setColData,
  12. fetchCaseEnvList
  13. } from '../../../../reducer/modules/interfaceCol';
  14. import HTML5Backend from 'react-dnd-html5-backend';
  15. import { getToken, getEnv } from '../../../../reducer/modules/project';
  16. import { DragDropContext } from 'react-dnd';
  17. import AceEditor from 'client/components/AceEditor/AceEditor';
  18. import * as Table from 'reactabular-table';
  19. import * as dnd from 'reactabular-dnd';
  20. import * as resolve from 'table-resolver';
  21. import axios from 'axios';
  22. import CaseReport from './CaseReport.js';
  23. import _ from 'underscore';
  24. import { initCrossRequest } from 'client/components/Postman/CheckCrossInstall.js';
  25. import produce from 'immer';
  26. import {InsertCodeMap} from 'client/components/Postman/Postman.js'
  27. const {
  28. handleParams,
  29. crossRequest,
  30. handleCurrDomain,
  31. checkNameIsExistInArray
  32. } = require('common/postmanLib.js');
  33. const { handleParamsValue, json_parse, ArrayToObject } = require('common/utils.js');
  34. import CaseEnv from 'client/components/CaseEnv';
  35. import Label from '../../../../components/Label/Label.js';
  36. const Option = Select.Option;
  37. const createContext = require('common/createContext')
  38. import copy from 'copy-to-clipboard';
  39. const defaultModalStyle = {
  40. top: 10
  41. }
  42. function handleReport(json) {
  43. try {
  44. return JSON.parse(json);
  45. } catch (e) {
  46. return {};
  47. }
  48. }
  49. @connect(
  50. state => {
  51. return {
  52. interfaceColList: state.interfaceCol.interfaceColList,
  53. currColId: state.interfaceCol.currColId,
  54. currCaseId: state.interfaceCol.currCaseId,
  55. isShowCol: state.interfaceCol.isShowCol,
  56. isRander: state.interfaceCol.isRander,
  57. currCaseList: state.interfaceCol.currCaseList,
  58. currProject: state.project.currProject,
  59. token: state.project.token,
  60. envList: state.interfaceCol.envList,
  61. curProjectRole: state.project.currProject.role,
  62. projectEnv: state.project.projectEnv,
  63. curUid: state.user.uid
  64. };
  65. },
  66. {
  67. fetchInterfaceColList,
  68. fetchCaseList,
  69. setColData,
  70. getToken,
  71. getEnv,
  72. fetchCaseEnvList
  73. }
  74. )
  75. @withRouter
  76. @DragDropContext(HTML5Backend)
  77. class InterfaceColContent extends Component {
  78. static propTypes = {
  79. match: PropTypes.object,
  80. interfaceColList: PropTypes.array,
  81. fetchInterfaceColList: PropTypes.func,
  82. fetchCaseList: PropTypes.func,
  83. setColData: PropTypes.func,
  84. history: PropTypes.object,
  85. currCaseList: PropTypes.array,
  86. currColId: PropTypes.number,
  87. currCaseId: PropTypes.number,
  88. isShowCol: PropTypes.bool,
  89. isRander: PropTypes.bool,
  90. currProject: PropTypes.object,
  91. getToken: PropTypes.func,
  92. token: PropTypes.string,
  93. curProjectRole: PropTypes.string,
  94. getEnv: PropTypes.func,
  95. projectEnv: PropTypes.object,
  96. fetchCaseEnvList: PropTypes.func,
  97. envList: PropTypes.array,
  98. curUid: PropTypes.number
  99. };
  100. constructor(props) {
  101. super(props);
  102. this.reports = {};
  103. this.records = {};
  104. this.state = {
  105. rows: [],
  106. reports: {},
  107. visible: false,
  108. curCaseid: null,
  109. hasPlugin: false,
  110. advVisible: false,
  111. curScript: '',
  112. enableScript: false,
  113. autoVisible: false,
  114. mode: 'html',
  115. email: false,
  116. download: false,
  117. currColEnvObj: {},
  118. collapseKey: '1',
  119. commonSettingModalVisible: false,
  120. commonSetting: {
  121. checkHttpCodeIs200: false,
  122. checkResponseField: {
  123. name: 'code',
  124. value: '0',
  125. enable: false
  126. },
  127. checkResponseSchema: false,
  128. checkScript:{
  129. enable: false,
  130. content: ''
  131. }
  132. }
  133. };
  134. this.onRow = this.onRow.bind(this);
  135. this.onMoveRow = this.onMoveRow.bind(this);
  136. }
  137. async handleColIdChange(newColId){
  138. this.props.setColData({
  139. currColId: +newColId,
  140. isShowCol: true,
  141. isRander: false
  142. });
  143. let result = await this.props.fetchCaseList(newColId);
  144. if (result.payload.data.errcode === 0) {
  145. this.reports = handleReport(result.payload.data.colData.test_report);
  146. this.setState({
  147. commonSetting:{
  148. ...this.state.commonSetting,
  149. ...result.payload.data.colData
  150. }
  151. })
  152. }
  153. await this.props.fetchCaseList(newColId);
  154. await this.props.fetchCaseEnvList(newColId);
  155. this.changeCollapseClose();
  156. this.handleColdata(this.props.currCaseList);
  157. }
  158. async componentWillMount() {
  159. const result = await this.props.fetchInterfaceColList(this.props.match.params.id);
  160. await this.props.getToken(this.props.match.params.id);
  161. let { currColId } = this.props;
  162. const params = this.props.match.params;
  163. const { actionId } = params;
  164. this.currColId = currColId = +actionId || result.payload.data.data[0]._id;
  165. this.props.history.push('/project/' + params.id + '/interface/col/' + currColId);
  166. if (currColId && currColId != 0) {
  167. await this.handleColIdChange(currColId)
  168. }
  169. this._crossRequestInterval = initCrossRequest(hasPlugin => {
  170. this.setState({ hasPlugin: hasPlugin });
  171. });
  172. }
  173. componentWillUnmount() {
  174. clearInterval(this._crossRequestInterval);
  175. }
  176. // 更新分类简介
  177. handleChangeInterfaceCol = (desc, name) => {
  178. let params = {
  179. col_id: this.props.currColId,
  180. name: name,
  181. desc: desc
  182. };
  183. axios.post('/api/col/up_col', params).then(async res => {
  184. if (res.data.errcode) {
  185. return message.error(res.data.errmsg);
  186. }
  187. let project_id = this.props.match.params.id;
  188. await this.props.fetchInterfaceColList(project_id);
  189. message.success('接口集合简介更新成功');
  190. });
  191. };
  192. // 整合header信息
  193. handleReqHeader = (project_id, req_header, case_env) => {
  194. let envItem = _.find(this.props.envList, item => {
  195. return item._id === project_id;
  196. });
  197. let currDomain = handleCurrDomain(envItem && envItem.env, case_env);
  198. let header = currDomain.header;
  199. header.forEach(item => {
  200. if (!checkNameIsExistInArray(item.name, req_header)) {
  201. // item.abled = true;
  202. item = {
  203. ...item,
  204. abled: true
  205. };
  206. req_header.push(item);
  207. }
  208. });
  209. return req_header;
  210. };
  211. handleColdata = (rows, currColEnvObj = {}) => {
  212. let that = this;
  213. let newRows = produce(rows, draftRows => {
  214. draftRows.map(item => {
  215. item.id = item._id;
  216. item._test_status = item.test_status;
  217. if(currColEnvObj[item.project_id]){
  218. item.case_env =currColEnvObj[item.project_id];
  219. }
  220. item.req_headers = that.handleReqHeader(item.project_id, item.req_headers, item.case_env);
  221. return item;
  222. });
  223. });
  224. this.setState({ rows: newRows });
  225. };
  226. executeTests = async () => {
  227. for (let i = 0, l = this.state.rows.length, newRows, curitem; i < l; i++) {
  228. let { rows } = this.state;
  229. let envItem = _.find(this.props.envList, item => {
  230. return item._id === rows[i].project_id;
  231. });
  232. curitem = Object.assign(
  233. {},
  234. rows[i],
  235. {
  236. env: envItem.env,
  237. pre_script: this.props.currProject.pre_script,
  238. after_script: this.props.currProject.after_script
  239. },
  240. { test_status: 'loading' }
  241. );
  242. newRows = [].concat([], rows);
  243. newRows[i] = curitem;
  244. this.setState({ rows: newRows });
  245. let status = 'error',
  246. result;
  247. try {
  248. result = await this.handleTest(curitem);
  249. if (result.code === 400) {
  250. status = 'error';
  251. } else if (result.code === 0) {
  252. status = 'ok';
  253. } else if (result.code === 1) {
  254. status = 'invalid';
  255. }
  256. } catch (e) {
  257. console.error(e);
  258. status = 'error';
  259. result = e;
  260. }
  261. //result.body = result.data;
  262. this.reports[curitem._id] = result;
  263. this.records[curitem._id] = {
  264. status: result.status,
  265. params: result.params,
  266. body: result.res_body
  267. };
  268. curitem = Object.assign({}, rows[i], { test_status: status });
  269. newRows = [].concat([], rows);
  270. newRows[i] = curitem;
  271. this.setState({ rows: newRows });
  272. }
  273. await axios.post('/api/col/up_col', {
  274. col_id: this.props.currColId,
  275. test_report: JSON.stringify(this.reports)
  276. });
  277. };
  278. handleTest = async interfaceData => {
  279. let requestParams = {};
  280. let options = handleParams(interfaceData, this.handleValue, requestParams);
  281. let result = {
  282. code: 400,
  283. msg: '数据异常',
  284. validRes: []
  285. };
  286. try {
  287. let data = await crossRequest(options, interfaceData.pre_script, interfaceData.after_script, createContext(
  288. this.props.curUid,
  289. this.props.match.params.id,
  290. interfaceData.interface_id
  291. ));
  292. options.taskId = this.props.curUid;
  293. let res = (data.res.body = json_parse(data.res.body));
  294. result = {
  295. ...options,
  296. ...result,
  297. res_header: data.res.header,
  298. res_body: res,
  299. status: data.res.status,
  300. statusText: data.res.statusText
  301. };
  302. if (options.data && typeof options.data === 'object') {
  303. requestParams = {
  304. ...requestParams,
  305. ...options.data
  306. };
  307. }
  308. let validRes = [];
  309. let responseData = Object.assign(
  310. {},
  311. {
  312. status: data.res.status,
  313. body: res,
  314. header: data.res.header,
  315. statusText: data.res.statusText
  316. }
  317. );
  318. // 断言测试
  319. await this.handleScriptTest(interfaceData, responseData, validRes, requestParams);
  320. if (validRes.length === 0) {
  321. result.code = 0;
  322. result.validRes = [
  323. {
  324. message: '验证通过'
  325. }
  326. ];
  327. } else if (validRes.length > 0) {
  328. result.code = 1;
  329. result.validRes = validRes;
  330. }
  331. } catch (data) {
  332. result = {
  333. ...options,
  334. ...result,
  335. res_header: data.header,
  336. res_body: data.body || data.message,
  337. status: 0,
  338. statusText: data.message,
  339. code: 400,
  340. validRes: [
  341. {
  342. message: data.message
  343. }
  344. ]
  345. };
  346. }
  347. result.params = requestParams;
  348. return result;
  349. };
  350. //response, validRes
  351. // 断言测试
  352. handleScriptTest = async (interfaceData, response, validRes, requestParams) => {
  353. // 是否启动断言
  354. try {
  355. let test = await axios.post('/api/col/run_script', {
  356. response: response,
  357. records: this.records,
  358. script: interfaceData.test_script,
  359. params: requestParams,
  360. col_id: this.props.currColId,
  361. interface_id: interfaceData.interface_id
  362. });
  363. if (test.data.errcode !== 0) {
  364. test.data.data.logs.forEach(item => {
  365. validRes.push({ message: item });
  366. });
  367. }
  368. } catch (err) {
  369. validRes.push({
  370. message: 'Error: ' + err.message
  371. });
  372. }
  373. };
  374. handleValue = (val, global) => {
  375. let globalValue = ArrayToObject(global);
  376. let context = Object.assign({}, { global: globalValue }, this.records);
  377. return handleParamsValue(val, context);
  378. };
  379. arrToObj = (arr, requestParams) => {
  380. arr = arr || [];
  381. const obj = {};
  382. arr.forEach(item => {
  383. if (item.name && item.enable && item.type !== 'file') {
  384. obj[item.name] = this.handleValue(item.value);
  385. if (requestParams) {
  386. requestParams[item.name] = obj[item.name];
  387. }
  388. }
  389. });
  390. return obj;
  391. };
  392. onRow(row) {
  393. return { rowId: row.id, onMove: this.onMoveRow, onDrop: this.onDrop };
  394. }
  395. onDrop = () => {
  396. let changes = [];
  397. this.state.rows.forEach((item, index) => {
  398. changes.push({ id: item._id, index: index });
  399. });
  400. axios.post('/api/col/up_case_index', changes).then(() => {
  401. this.props.fetchInterfaceColList(this.props.match.params.id);
  402. });
  403. };
  404. onMoveRow({ sourceRowId, targetRowId }) {
  405. let rows = dnd.moveRows({ sourceRowId, targetRowId })(this.state.rows);
  406. if (rows) {
  407. this.setState({ rows });
  408. }
  409. }
  410. onChangeTest = d => {
  411. this.setState({
  412. commonSetting: {
  413. ...this.state.commonSetting,
  414. checkScript: {
  415. ...this.state.commonSetting.checkScript,
  416. content: d.text
  417. }
  418. }
  419. });
  420. };
  421. handleInsertCode = code => {
  422. this.aceEditor.editor.insertCode(code);
  423. };
  424. async componentWillReceiveProps(nextProps) {
  425. let newColId = !isNaN(nextProps.match.params.actionId) ? +nextProps.match.params.actionId : 0;
  426. if ((newColId && this.currColId && newColId !== this.currColId) || nextProps.isRander) {
  427. this.currColId = newColId;
  428. this.handleColIdChange(newColId)
  429. }
  430. }
  431. // 测试用例环境面板折叠
  432. changeCollapseClose = key => {
  433. if (key) {
  434. this.setState({
  435. collapseKey: key
  436. });
  437. } else {
  438. this.setState({
  439. collapseKey: '1',
  440. currColEnvObj: {}
  441. });
  442. }
  443. };
  444. openReport = id => {
  445. if (!this.reports[id]) {
  446. return message.warn('还没有生成报告');
  447. }
  448. this.setState({ visible: true, curCaseid: id });
  449. };
  450. openAdv = id => {
  451. let findCase = _.find(this.props.currCaseList, item => item.id === id);
  452. this.setState({
  453. enableScript: findCase.enable_script,
  454. curScript: findCase.test_script,
  455. advVisible: true,
  456. curCaseid: id
  457. });
  458. };
  459. handleScriptChange = d => {
  460. this.setState({ curScript: d.text });
  461. };
  462. handleAdvCancel = () => {
  463. this.setState({ advVisible: false });
  464. };
  465. handleAdvOk = async () => {
  466. const { curCaseid, enableScript, curScript } = this.state;
  467. const res = await axios.post('/api/col/up_case', {
  468. id: curCaseid,
  469. test_script: curScript,
  470. enable_script: enableScript
  471. });
  472. if (res.data.errcode === 0) {
  473. message.success('更新成功');
  474. }
  475. this.setState({ advVisible: false });
  476. let currColId = this.currColId;
  477. this.props.setColData({
  478. currColId: +currColId,
  479. isShowCol: true,
  480. isRander: false
  481. });
  482. await this.props.fetchCaseList(currColId);
  483. this.handleColdata(this.props.currCaseList);
  484. };
  485. handleCancel = () => {
  486. this.setState({ visible: false });
  487. };
  488. currProjectEnvChange = (envName, project_id) => {
  489. let currColEnvObj = {
  490. ...this.state.currColEnvObj,
  491. [project_id]: envName
  492. };
  493. this.setState({ currColEnvObj });
  494. // this.handleColdata(this.props.currCaseList, envName, project_id);
  495. this.handleColdata(this.props.currCaseList,currColEnvObj);
  496. };
  497. autoTests = () => {
  498. this.setState({ autoVisible: true, currColEnvObj: {}, collapseKey: '' });
  499. };
  500. handleAuto = () => {
  501. this.setState({
  502. autoVisible: false,
  503. email: false,
  504. download: false,
  505. mode: 'html',
  506. currColEnvObj: {},
  507. collapseKey: ''
  508. });
  509. };
  510. copyUrl = url => {
  511. copy(url);
  512. message.success('已经成功复制到剪切板');
  513. };
  514. modeChange = mode => {
  515. this.setState({ mode });
  516. };
  517. emailChange = email => {
  518. this.setState({ email });
  519. };
  520. downloadChange = download => {
  521. this.setState({ download });
  522. };
  523. handleColEnvObj = envObj => {
  524. let str = '';
  525. for (let key in envObj) {
  526. str += envObj[key] ? `&env_${key}=${envObj[key]}` : '';
  527. }
  528. return str;
  529. };
  530. handleCommonSetting = ()=>{
  531. let setting = this.state.commonSetting;
  532. let params = {
  533. col_id: this.props.currColId,
  534. ...setting
  535. };
  536. console.log(params)
  537. axios.post('/api/col/up_col', params).then(async res => {
  538. if (res.data.errcode) {
  539. return message.error(res.data.errmsg);
  540. }
  541. message.success('配置测试集成功');
  542. });
  543. this.setState({
  544. commonSettingModalVisible: false
  545. })
  546. }
  547. cancelCommonSetting = ()=>{
  548. this.setState({
  549. commonSettingModalVisible: false
  550. })
  551. }
  552. openCommonSetting = ()=>{
  553. this.setState({
  554. commonSettingModalVisible: true
  555. })
  556. }
  557. changeCommonFieldSetting = (key)=>{
  558. return (e)=>{
  559. let value = e;
  560. if(typeof e === 'object' && e){
  561. value = e.target.value;
  562. }
  563. let {checkResponseField} = this.state.commonSetting;
  564. this.setState({
  565. commonSetting: {
  566. ...this.state.commonSetting,
  567. checkResponseField: {
  568. ...checkResponseField,
  569. [key]: value
  570. }
  571. }
  572. })
  573. }
  574. }
  575. render() {
  576. const currProjectId = this.props.currProject._id;
  577. const columns = [
  578. {
  579. property: 'casename',
  580. header: {
  581. label: '用例名称'
  582. },
  583. props: {
  584. style: {
  585. width: '250px'
  586. }
  587. },
  588. cell: {
  589. formatters: [
  590. (text, { rowData }) => {
  591. let record = rowData;
  592. return (
  593. <Link to={'/project/' + currProjectId + '/interface/case/' + record._id}>
  594. {record.casename.length > 23
  595. ? record.casename.substr(0, 20) + '...'
  596. : record.casename}
  597. </Link>
  598. );
  599. }
  600. ]
  601. }
  602. },
  603. {
  604. header: {
  605. label: 'key',
  606. formatters: [
  607. () => {
  608. return (
  609. <Tooltip
  610. title={
  611. <span>
  612. {' '}
  613. 每个用例都有唯一的key,用于获取所匹配接口的响应数据,例如使用{' '}
  614. <a
  615. href="https://hellosean1025.github.io/yapi/documents/case.html#%E7%AC%AC%E4%BA%8C%E6%AD%A5%EF%BC%8C%E7%BC%96%E8%BE%91%E6%B5%8B%E8%AF%95%E7%94%A8%E4%BE%8B"
  616. className="link-tooltip"
  617. target="blank"
  618. >
  619. {' '}
  620. 变量参数{' '}
  621. </a>{' '}
  622. 功能{' '}
  623. </span>
  624. }
  625. >
  626. Key
  627. </Tooltip>
  628. );
  629. }
  630. ]
  631. },
  632. props: {
  633. style: {
  634. width: '100px'
  635. }
  636. },
  637. cell: {
  638. formatters: [
  639. (value, { rowData }) => {
  640. return <span>{rowData._id}</span>;
  641. }
  642. ]
  643. }
  644. },
  645. {
  646. property: 'test_status',
  647. header: {
  648. label: '状态'
  649. },
  650. props: {
  651. style: {
  652. width: '100px'
  653. }
  654. },
  655. cell: {
  656. formatters: [
  657. (value, { rowData }) => {
  658. let id = rowData._id;
  659. let code = this.reports[id] ? this.reports[id].code : 0;
  660. if (rowData.test_status === 'loading') {
  661. return (
  662. <div>
  663. <Spin />
  664. </div>
  665. );
  666. }
  667. switch (code) {
  668. case 0:
  669. return (
  670. <div>
  671. <Tooltip title="Pass">
  672. <Icon
  673. style={{
  674. color: '#00a854'
  675. }}
  676. type="check-circle"
  677. />
  678. </Tooltip>
  679. </div>
  680. );
  681. case 400:
  682. return (
  683. <div>
  684. <Tooltip title="请求异常">
  685. <Icon
  686. type="info-circle"
  687. style={{
  688. color: '#f04134'
  689. }}
  690. />
  691. </Tooltip>
  692. </div>
  693. );
  694. case 1:
  695. return (
  696. <div>
  697. <Tooltip title="验证失败">
  698. <Icon
  699. type="exclamation-circle"
  700. style={{
  701. color: '#ffbf00'
  702. }}
  703. />
  704. </Tooltip>
  705. </div>
  706. );
  707. default:
  708. return (
  709. <div>
  710. <Icon
  711. style={{
  712. color: '#00a854'
  713. }}
  714. type="check-circle"
  715. />
  716. </div>
  717. );
  718. }
  719. }
  720. ]
  721. }
  722. },
  723. {
  724. property: 'path',
  725. header: {
  726. label: '接口路径'
  727. },
  728. cell: {
  729. formatters: [
  730. (text, { rowData }) => {
  731. let record = rowData;
  732. return (
  733. <Tooltip title="跳转到对应接口">
  734. <Link to={`/project/${record.project_id}/interface/api/${record.interface_id}`}>
  735. {record.path.length > 23 ? record.path + '...' : record.path}
  736. </Link>
  737. </Tooltip>
  738. );
  739. }
  740. ]
  741. }
  742. },
  743. {
  744. header: {
  745. label: '测试报告'
  746. },
  747. props: {
  748. style: {
  749. width: '200px'
  750. }
  751. },
  752. cell: {
  753. formatters: [
  754. (text, { rowData }) => {
  755. let reportFun = () => {
  756. if (!this.reports[rowData.id]) {
  757. return null;
  758. }
  759. return <Button onClick={() => this.openReport(rowData.id)}>测试报告</Button>;
  760. };
  761. return <div className="interface-col-table-action">{reportFun()}</div>;
  762. }
  763. ]
  764. }
  765. }
  766. ];
  767. const { rows } = this.state;
  768. const components = {
  769. header: {
  770. cell: dnd.Header
  771. },
  772. body: {
  773. row: dnd.Row
  774. }
  775. };
  776. const resolvedColumns = resolve.columnChildren({ columns });
  777. const resolvedRows = resolve.resolve({ columns: resolvedColumns, method: resolve.nested })(
  778. rows
  779. );
  780. const localUrl =
  781. location.protocol +
  782. '//' +
  783. location.hostname +
  784. (location.port !== '' ? ':' + location.port : '');
  785. let currColEnvObj = this.handleColEnvObj(this.state.currColEnvObj);
  786. const autoTestsUrl = `/api/open/run_auto_test?id=${this.props.currColId}&token=${
  787. this.props.token
  788. }${currColEnvObj ? currColEnvObj : ''}&mode=${this.state.mode}&email=${
  789. this.state.email
  790. }&download=${this.state.download}`;
  791. let col_name = '';
  792. let col_desc = '';
  793. for (var i = 0; i < this.props.interfaceColList.length; i++) {
  794. if (this.props.interfaceColList[i]._id === this.props.currColId) {
  795. col_name = this.props.interfaceColList[i].name;
  796. col_desc = this.props.interfaceColList[i].desc;
  797. break;
  798. }
  799. }
  800. return (
  801. <div className="interface-col">
  802. <Modal
  803. title="通用规则配置"
  804. visible={this.state.commonSettingModalVisible}
  805. onOk={this.handleCommonSetting}
  806. onCancel={this.cancelCommonSetting}
  807. width={'1000px'}
  808. style={defaultModalStyle}
  809. >
  810. <div className="common-setting-modal">
  811. <Row className="setting-item">
  812. <Col className="col-item" span="4">
  813. <label>检查HttpCode:&nbsp;<Tooltip title={'检查 http code 是否为 200'}>
  814. <Icon type="question-circle-o" style={{ width: '10px' }} />
  815. </Tooltip></label>
  816. </Col>
  817. <Col className="col-item" span="18">
  818. <Switch onChange={e=>{
  819. let {commonSetting} = this.state;
  820. this.setState({
  821. commonSetting :{
  822. ...commonSetting,
  823. checkHttpCodeIs200: e
  824. }
  825. })
  826. }} checked={this.state.commonSetting.checkHttpCodeIs200} checkedChildren="开" unCheckedChildren="关" />
  827. </Col>
  828. </Row>
  829. <Row className="setting-item">
  830. <Col className="col-item" span="4">
  831. <label>检查返回json:&nbsp;<Tooltip title={'检查接口返回数据字段值,比如检查 code 是不是等于 0'}>
  832. <Icon type="question-circle-o" style={{ width: '10px' }} />
  833. </Tooltip></label>
  834. </Col>
  835. <Col className="col-item" span="6">
  836. <Input value={this.state.commonSetting.checkResponseField.name} onChange={this.changeCommonFieldSetting('name')} placeholder="字段名" />
  837. </Col>
  838. <Col className="col-item" span="6">
  839. <Input onChange={this.changeCommonFieldSetting('value')} value={this.state.commonSetting.checkResponseField.value} placeholder="值" />
  840. </Col>
  841. <Col className="col-item" span="6">
  842. <Switch onChange={this.changeCommonFieldSetting('enable')} checked={this.state.commonSetting.checkResponseField.enable} checkedChildren="开" unCheckedChildren="关" />
  843. </Col>
  844. </Row>
  845. <Row className="setting-item">
  846. <Col className="col-item" span="4">
  847. <label>检查返回数据结构:&nbsp;<Tooltip title={'只有 response 基于 json-schema 方式定义,该检查才会生效'}>
  848. <Icon type="question-circle-o" style={{ width: '10px' }} />
  849. </Tooltip></label>
  850. </Col>
  851. <Col className="col-item" span="18">
  852. <Switch onChange={e=>{
  853. let {commonSetting} = this.state;
  854. this.setState({
  855. commonSetting :{
  856. ...commonSetting,
  857. checkResponseSchema: e
  858. }
  859. })
  860. }} checked={this.state.commonSetting.checkResponseSchema} checkedChildren="开" unCheckedChildren="关" />
  861. </Col>
  862. </Row>
  863. <Row className="setting-item">
  864. <Col className="col-item " span="4">
  865. <label>全局测试脚本:&nbsp;<Tooltip title={'在跑自动化测试时,优先调用全局脚本,只有全局脚本通过测试,才会开始跑case自定义的测试脚本'}>
  866. <Icon type="question-circle-o" style={{ width: '10px' }} />
  867. </Tooltip></label>
  868. </Col>
  869. <Col className="col-item" span="14">
  870. <div><Switch onChange={e=>{
  871. let {commonSetting} = this.state;
  872. this.setState({
  873. commonSetting :{
  874. ...commonSetting,
  875. checkScript: {
  876. ...this.state.checkScript,
  877. enable: e
  878. }
  879. }
  880. })
  881. }} checked={this.state.commonSetting.checkScript.enable} checkedChildren="开" unCheckedChildren="关" /></div>
  882. <AceEditor
  883. onChange={this.onChangeTest}
  884. className="case-script"
  885. data={this.state.commonSetting.checkScript.content}
  886. ref={aceEditor => {
  887. this.aceEditor = aceEditor;
  888. }}
  889. />
  890. </Col>
  891. <Col span="6">
  892. <div className="insert-code">
  893. {InsertCodeMap.map(item => {
  894. return (
  895. <div
  896. style={{ cursor: 'pointer' }}
  897. className="code-item"
  898. key={item.title}
  899. onClick={() => {
  900. this.handleInsertCode('\n' + item.code);
  901. }}
  902. >
  903. {item.title}
  904. </div>
  905. );
  906. })}
  907. </div>
  908. </Col>
  909. </Row>
  910. </div>
  911. </Modal>
  912. <Row type="flex" justify="center" align="top">
  913. <Col span={5}>
  914. <h2
  915. className="interface-title"
  916. style={{
  917. display: 'inline-block',
  918. margin: '8px 20px 16px 0px'
  919. }}
  920. >
  921. 测试集合&nbsp;<a
  922. target="_blank"
  923. rel="noopener noreferrer"
  924. href="https://hellosean1025.github.io/yapi/documents/case.html"
  925. >
  926. <Tooltip title="点击查看文档">
  927. <Icon type="question-circle-o" />
  928. </Tooltip>
  929. </a>
  930. </h2>
  931. </Col>
  932. <Col span={10}>
  933. <CaseEnv
  934. envList={this.props.envList}
  935. currProjectEnvChange={this.currProjectEnvChange}
  936. envValue={this.state.currColEnvObj}
  937. collapseKey={this.state.collapseKey}
  938. changeClose={this.changeCollapseClose}
  939. />
  940. </Col>
  941. <Col span={9}>
  942. {this.state.hasPlugin ? (
  943. <div
  944. style={{
  945. float: 'right',
  946. paddingTop: '8px'
  947. }}
  948. >
  949. {this.props.curProjectRole !== 'guest' && (
  950. <Tooltip title="在 YApi 服务端跑自动化测试,测试环境不能为私有网络,请确保 YApi 服务器可以访问到自动化测试环境domain">
  951. <Button
  952. style={{
  953. marginRight: '8px'
  954. }}
  955. onClick={this.autoTests}
  956. >
  957. 服务端测试
  958. </Button>
  959. </Tooltip>
  960. )}
  961. <Button onClick={this.openCommonSetting} style={{
  962. marginRight: '8px'
  963. }} >通用规则配置</Button>
  964. &nbsp;
  965. <Button type="primary" onClick={this.executeTests}>
  966. 开始测试
  967. </Button>
  968. </div>
  969. ) : (
  970. <Tooltip title="请安装 cross-request Chrome 插件">
  971. <Button
  972. disabled
  973. type="primary"
  974. style={{
  975. float: 'right',
  976. marginTop: '8px'
  977. }}
  978. >
  979. 开始测试
  980. </Button>
  981. </Tooltip>
  982. )}
  983. </Col>
  984. </Row>
  985. <div className="component-label-wrapper">
  986. <Label onChange={val => this.handleChangeInterfaceCol(val, col_name)} desc={col_desc} />
  987. </div>
  988. <Table.Provider
  989. components={components}
  990. columns={resolvedColumns}
  991. style={{
  992. width: '100%',
  993. borderCollapse: 'collapse'
  994. }}
  995. >
  996. <Table.Header
  997. className="interface-col-table-header"
  998. headerRows={resolve.headerRows({ columns })}
  999. />
  1000. <Table.Body
  1001. className="interface-col-table-body"
  1002. rows={resolvedRows}
  1003. rowKey="id"
  1004. onRow={this.onRow}
  1005. />
  1006. </Table.Provider>
  1007. <Modal
  1008. title="测试报告"
  1009. width="900px"
  1010. style={{
  1011. minHeight: '500px'
  1012. }}
  1013. visible={this.state.visible}
  1014. onCancel={this.handleCancel}
  1015. footer={null}
  1016. >
  1017. <CaseReport {...this.reports[this.state.curCaseid]} />
  1018. </Modal>
  1019. <Modal
  1020. title="自定义测试脚本"
  1021. width="660px"
  1022. style={{
  1023. minHeight: '500px'
  1024. }}
  1025. visible={this.state.advVisible}
  1026. onCancel={this.handleAdvCancel}
  1027. onOk={this.handleAdvOk}
  1028. maskClosable={false}
  1029. >
  1030. <h3>
  1031. 是否开启:&nbsp;
  1032. <Switch
  1033. checked={this.state.enableScript}
  1034. onChange={e => this.setState({ enableScript: e })}
  1035. />
  1036. </h3>
  1037. <AceEditor
  1038. className="case-script"
  1039. data={this.state.curScript}
  1040. onChange={this.handleScriptChange}
  1041. />
  1042. </Modal>
  1043. {this.state.autoVisible && (
  1044. <Modal
  1045. title="服务端自动化测试"
  1046. width="780px"
  1047. style={{
  1048. minHeight: '500px'
  1049. }}
  1050. visible={this.state.autoVisible}
  1051. onCancel={this.handleAuto}
  1052. className="autoTestsModal"
  1053. footer={null}
  1054. >
  1055. <Row type="flex" justify="space-around" className="row" align="top">
  1056. <Col span={3} className="label" style={{ paddingTop: '16px' }}>
  1057. 选择环境
  1058. <Tooltip title="默认使用测试用例选择的环境">
  1059. <Icon type="question-circle-o" />
  1060. </Tooltip>
  1061. &nbsp;:
  1062. </Col>
  1063. <Col span={21}>
  1064. <CaseEnv
  1065. envList={this.props.envList}
  1066. currProjectEnvChange={this.currProjectEnvChange}
  1067. envValue={this.state.currColEnvObj}
  1068. collapseKey={this.state.collapseKey}
  1069. changeClose={this.changeCollapseClose}
  1070. />
  1071. </Col>
  1072. </Row>
  1073. <Row type="flex" justify="space-around" className="row" align="middle">
  1074. <Col span={3} className="label">
  1075. 输出格式:
  1076. </Col>
  1077. <Col span={21}>
  1078. <Select value={this.state.mode} onChange={this.modeChange}>
  1079. <Option key="html" value="html">
  1080. html
  1081. </Option>
  1082. <Option key="json" value="json">
  1083. json
  1084. </Option>
  1085. </Select>
  1086. </Col>
  1087. </Row>
  1088. <Row type="flex" justify="space-around" className="row" align="middle">
  1089. <Col span={3} className="label">
  1090. 消息通知
  1091. <Tooltip title={'测试不通过时,会给项目组成员发送消息通知'}>
  1092. <Icon
  1093. type="question-circle-o"
  1094. style={{
  1095. width: '10px'
  1096. }}
  1097. />
  1098. </Tooltip>
  1099. &nbsp;:
  1100. </Col>
  1101. <Col span={21}>
  1102. <Switch
  1103. checked={this.state.email}
  1104. checkedChildren="开"
  1105. unCheckedChildren="关"
  1106. onChange={this.emailChange}
  1107. />
  1108. </Col>
  1109. </Row>
  1110. <Row type="flex" justify="space-around" className="row" align="middle">
  1111. <Col span={3} className="label">
  1112. 下载数据
  1113. <Tooltip title={'开启后,测试数据将被下载到本地'}>
  1114. <Icon
  1115. type="question-circle-o"
  1116. style={{
  1117. width: '10px'
  1118. }}
  1119. />
  1120. </Tooltip>
  1121. &nbsp;:
  1122. </Col>
  1123. <Col span={21}>
  1124. <Switch
  1125. checked={this.state.download}
  1126. checkedChildren="开"
  1127. unCheckedChildren="关"
  1128. onChange={this.downloadChange}
  1129. />
  1130. </Col>
  1131. </Row>
  1132. <Row type="flex" justify="space-around" className="row" align="middle">
  1133. <Col span={21} className="autoTestUrl">
  1134. <a
  1135. target="_blank"
  1136. rel="noopener noreferrer"
  1137. href={localUrl + autoTestsUrl} >
  1138. {autoTestsUrl}
  1139. </a>
  1140. </Col>
  1141. <Col span={3}>
  1142. <Button className="copy-btn" onClick={() => this.copyUrl(localUrl + autoTestsUrl)}>
  1143. 复制
  1144. </Button>
  1145. </Col>
  1146. </Row>
  1147. <div className="autoTestMsg">
  1148. 注:访问该URL,可以测试所有用例,请确保YApi服务器可以访问到环境配置的 domain
  1149. </div>
  1150. </Modal>
  1151. )}
  1152. </div>
  1153. );
  1154. }
  1155. }
  1156. export default InterfaceColContent;