| @@ -27,4 +27,18 @@ | |||||
| height:45px; | height:45px; | ||||
| background:#ffffff; | background:#ffffff; | ||||
| box-shadow:0px 3px 6px rgba(146, 146, 146, 0.09); | box-shadow:0px 3px 6px rgba(146, 146, 146, 0.09); | ||||
| } | |||||
| .detailBox{ | |||||
| color:#1d1d20; | |||||
| font-size:15px; | |||||
| margin-bottom: 15px; | |||||
| display: flex; | |||||
| align-items: center; | |||||
| } | |||||
| .allMessageItem{ | |||||
| display: flex; | |||||
| align-items: center; | |||||
| color:rgba(29, 29, 32, 0.8); | |||||
| font-size:15px; | |||||
| margin-right: 30px; | |||||
| } | } | ||||
| @@ -8,14 +8,45 @@ import { s8 } from '../../../utils'; | |||||
| import { Button, message} from 'antd'; | import { Button, message} from 'antd'; | ||||
| import {SaveOutlined} from '@ant-design/icons'; | import {SaveOutlined} from '@ant-design/icons'; | ||||
| import {saveWorkflow,getWorkflowById,} from '@/services/pipeline/index.js' | import {saveWorkflow,getWorkflowById,} from '@/services/pipeline/index.js' | ||||
| import {getExperimentIns} from '@/services/experiment/index.js' | |||||
| import {getExperimentIns,} from '@/services/experiment/index.js' | |||||
| import { useNavigate} from 'react-router-dom'; | import { useNavigate} from 'react-router-dom'; | ||||
| import momnet from 'moment' | |||||
| const ExperimentText = React.FC = () => { | const ExperimentText = React.FC = () => { | ||||
| const propsRef=useRef() | const propsRef=useRef() | ||||
| const navgite=useNavigate(); | const navgite=useNavigate(); | ||||
| const locationParams =useParams () //新版本获取路由参数接口 | const locationParams =useParams () //新版本获取路由参数接口 | ||||
| let graph=null | let graph=null | ||||
| const [experimentStatusObj,setExperimentStatusObj]=useState({}) | const [experimentStatusObj,setExperimentStatusObj]=useState({}) | ||||
| const [experimentAllMessage,setExperimentAllMessage]=useState({}) | |||||
| const statusObj={ | |||||
| "Running":'运行中', | |||||
| "Succeeded":'成功', | |||||
| "Pending":'等待中', | |||||
| "Failed":'失败', | |||||
| "Error":'错误', | |||||
| "Terminated":'终止', | |||||
| "Skipped":'未执行', | |||||
| "Omitted":'未执行', | |||||
| } | |||||
| const statusColorObj={ | |||||
| "Running":'#165bff', | |||||
| "Succeeded":'#63a728', | |||||
| "Pending":'#f981eb', | |||||
| "Failed":'#c73131', | |||||
| "Error":'#c73131', | |||||
| "Terminated":'#8a8a8a', | |||||
| "Skipped":'#8a8a8a', | |||||
| "Omitted":'#8a8a8ae', | |||||
| } | |||||
| const timers=(time)=>{ | |||||
| let timer=new Date(time) | |||||
| let hours = timer.getHours(); //转换成时 | |||||
| let minutes = timer.getMinutes(); //转换成分 | |||||
| let secend = timer.getSeconds(); //转换成秒 | |||||
| let str = `${minutes}分${secend}秒`; | |||||
| return str; | |||||
| } | |||||
| const pipelineContainer = useEmotionCss(() => { | const pipelineContainer = useEmotionCss(() => { | ||||
| return { | return { | ||||
| display: 'flex', | display: 'flex', | ||||
| @@ -88,11 +119,13 @@ const ExperimentText = React.FC = () => { | |||||
| console.log(JSON.parse(ret.data.dag)); | console.log(JSON.parse(ret.data.dag)); | ||||
| getExperimentIns(locationParams.id).then(res=>{ | getExperimentIns(locationParams.id).then(res=>{ | ||||
| if(res.code==200){ | if(res.code==200){ | ||||
| console.log(ret.data,'data'); | |||||
| const experimentStatusObjs=JSON.parse(res.data.nodes_status) | const experimentStatusObjs=JSON.parse(res.data.nodes_status) | ||||
| const newNodeList= JSON.parse(ret.data.dag).nodes.map(item=>{console.log(experimentStatusObjs); return {...item,component_id:experimentStatusObjs&&experimentStatusObjs[item.id]&&experimentStatusObjs[item.id].id,img:experimentStatusObjs&&experimentStatusObjs[item.id]&&experimentStatusObjs[item.id].phase?item.img.slice(0,item.img.length-4)+'-'+experimentStatusObjs[item.id].phase+'.png':item.img}}) | |||||
| const newData={...JSON.parse(ret.data.dag),nodes:newNodeList} | |||||
| console.log(newData); | |||||
| getGraphData(newData) | |||||
| const newNodeList= JSON.parse(ret.data.dag).nodes.map(item=>{console.log(experimentStatusObjs); return {...item,experimentEndTime:experimentStatusObjs&&experimentStatusObjs[item.id]&&experimentStatusObjs[item.id].finishedAt,experimentStartTime:experimentStatusObjs&&experimentStatusObjs[item.id]&&experimentStatusObjs[item.id].startedAt,experimentStatus:experimentStatusObjs&&experimentStatusObjs[item.id]&&experimentStatusObjs[item.id].phase,component_id:experimentStatusObjs&&experimentStatusObjs[item.id]&&experimentStatusObjs[item.id].id,img:experimentStatusObjs&&experimentStatusObjs[item.id]&&experimentStatusObjs[item.id].phase?item.img.slice(0,item.img.length-4)+'-'+experimentStatusObjs[item.id].phase+'.png':item.img}}) | |||||
| const newData={...JSON.parse(ret.data.dag),nodes:newNodeList} | |||||
| console.log(newData); | |||||
| setExperimentAllMessage(res.data) | |||||
| getGraphData(newData) | |||||
| // setExperimentStatusObj(JSON.parse(ret.data.nodes_status)) | // setExperimentStatusObj(JSON.parse(ret.data.nodes_status)) | ||||
| } | } | ||||
| @@ -327,6 +360,16 @@ const ExperimentText = React.FC = () => { | |||||
| return (<div className={pipelineContainer}> | return (<div className={pipelineContainer}> | ||||
| <div className={Styles.centerContainer}> | <div className={Styles.centerContainer}> | ||||
| <div className={Styles.buttonList}> | <div className={Styles.buttonList}> | ||||
| <div className={Styles.allMessageItem}>启动时间:{momnet(experimentAllMessage.create_time).format('YYYY-MM-DD HH:mm:ss')}</div> | |||||
| <div className={Styles.allMessageItem}>执行时长:{experimentAllMessage.finish_time?timers(new Date(experimentAllMessage.finish_time).getTime()-new Date(experimentAllMessage.create_time).getTime()):timers(new Date().getTime()-new Date(experimentAllMessage.create_time).getTime())}</div> | |||||
| <div className={Styles.allMessageItem}>状态: | |||||
| <div style={{width:'8px', | |||||
| height:'8px', | |||||
| borderRadius:'50%', | |||||
| marginRight:'6px', | |||||
| backgroundColor:statusColorObj[experimentAllMessage.status] | |||||
| }}></div> | |||||
| <span style={{color:statusColorObj[experimentAllMessage.status]}}>{statusObj[experimentAllMessage.status]}</span></div> | |||||
| </div> | </div> | ||||
| <div className={graphStyle} ref={graphRef} id={Styles.graphStyle}></div> | <div className={graphStyle} ref={graphRef} id={Styles.graphStyle}></div> | ||||
| </div> | </div> | ||||
| @@ -3,19 +3,48 @@ import { Button, Drawer,Form, Input ,Tabs } from 'antd'; | |||||
| import Styles from './editPipeline.less' | import Styles from './editPipeline.less' | ||||
| import{getQueryByExperimentLog}from '@/services/experiment/index.js' | import{getQueryByExperimentLog}from '@/services/experiment/index.js' | ||||
| import { ProfileOutlined, DatabaseOutlined} from '@ant-design/icons'; | import { ProfileOutlined, DatabaseOutlined} from '@ant-design/icons'; | ||||
| import momnet from 'moment' | |||||
| const { TextArea } = Input; | const { TextArea } = Input; | ||||
| const Props = forwardRef(({onParentChange}, ref) =>{ | const Props = forwardRef(({onParentChange}, ref) =>{ | ||||
| const [form] = Form.useForm(); | const [form] = Form.useForm(); | ||||
| const [stagingItem,setStagingItem]=useState({}) | const [stagingItem,setStagingItem]=useState({}) | ||||
| const [messageItem,setMessageItem]=useState('') | const [messageItem,setMessageItem]=useState('') | ||||
| const statusObj={ | |||||
| "Running":'运行中', | |||||
| "Succeeded":'成功', | |||||
| "Pending":'等待中', | |||||
| "Failed":'失败', | |||||
| "Error":'错误', | |||||
| "Terminated":'终止', | |||||
| "Skipped":'未执行', | |||||
| "Omitted":'未执行', | |||||
| } | |||||
| const statusColorObj={ | |||||
| "Running":'#165bff', | |||||
| "Succeeded":'#63a728', | |||||
| "Pending":'#f981eb', | |||||
| "Failed":'#c73131', | |||||
| "Error":'#c73131', | |||||
| "Terminated":'#8a8a8a', | |||||
| "Skipped":'#8a8a8a', | |||||
| "Omitted":'#8a8a8ae', | |||||
| } | |||||
| const timers=(time)=>{ | |||||
| let timer=new Date(time) | |||||
| let hours = timer.getHours(); //转换成时 | |||||
| let minutes = timer.getMinutes(); //转换成分 | |||||
| let secend = timer.getSeconds(); //转换成秒 | |||||
| let str = `${minutes}分${secend}秒`; | |||||
| return str; | |||||
| } | |||||
| const items = [ | const items = [ | ||||
| { | { | ||||
| key: '1', | key: '1', | ||||
| label: '日志详情', | label: '日志详情', | ||||
| children: <div style={{height:'740px', | children: <div style={{height:'740px', | ||||
| background:'rgba(234, 234, 234, 0.5)', | |||||
| color:'rgba(29, 29, 32, 0.8)', | |||||
| background:'#19253b', | |||||
| color:'#fff', | |||||
| fontSize:'14px' | fontSize:'14px' | ||||
| }} dangerouslySetInnerHTML={{ __html: messageItem }}></div>, | }} dangerouslySetInnerHTML={{ __html: messageItem }}></div>, | ||||
| icon:<ProfileOutlined/> | icon:<ProfileOutlined/> | ||||
| @@ -226,11 +255,6 @@ const Props = forwardRef(({onParentChange}, ref) =>{ | |||||
| form.resetFields(); | form.resetFields(); | ||||
| form.setFieldsValue({...e.item.getModel(),in_parameters:JSON.parse(e.item.getModel().in_parameters),out_parameters:JSON.parse(e.item.getModel().out_parameters),control_strategy:JSON.parse(e.item.getModel().control_strategy)}) | form.setFieldsValue({...e.item.getModel(),in_parameters:JSON.parse(e.item.getModel().in_parameters),out_parameters:JSON.parse(e.item.getModel().out_parameters),control_strategy:JSON.parse(e.item.getModel().control_strategy)}) | ||||
| setStagingItem({...e.item.getModel(),in_parameters:JSON.parse(e.item.getModel().in_parameters),out_parameters:JSON.parse(e.item.getModel().out_parameters),control_strategy:JSON.parse(e.item.getModel().control_strategy)}) | setStagingItem({...e.item.getModel(),in_parameters:JSON.parse(e.item.getModel().in_parameters),out_parameters:JSON.parse(e.item.getModel().out_parameters),control_strategy:JSON.parse(e.item.getModel().control_strategy)}) | ||||
| // form.setFieldsValue({...e.item.getModel(),in_parameters:JSON.parse(e.item.getModel().in_parameters),out_parameters:JSON.parse(e.item.getModel().out_parameters)}) | |||||
| // setStagingItem({...e.item.getModel(),in_parameters:JSON.parse(e.item.getModel().in_parameters),out_parameters:JSON.parse(e.item.getModel().out_parameters)}) | |||||
| // setTimeout(() => { | |||||
| // console.log(stagingItem); | |||||
| // }, (500)); | |||||
| setOpen(true); | setOpen(true); | ||||
| }) | }) | ||||
| } | } | ||||
| @@ -238,11 +262,6 @@ const Props = forwardRef(({onParentChange}, ref) =>{ | |||||
| form.resetFields(); | form.resetFields(); | ||||
| form.setFieldsValue({...e.item.getModel(),in_parameters:JSON.parse(e.item.getModel().in_parameters),out_parameters:JSON.parse(e.item.getModel().out_parameters),control_strategy:JSON.parse(e.item.getModel().control_strategy)}) | form.setFieldsValue({...e.item.getModel(),in_parameters:JSON.parse(e.item.getModel().in_parameters),out_parameters:JSON.parse(e.item.getModel().out_parameters),control_strategy:JSON.parse(e.item.getModel().control_strategy)}) | ||||
| setStagingItem({...e.item.getModel(),in_parameters:JSON.parse(e.item.getModel().in_parameters),out_parameters:JSON.parse(e.item.getModel().out_parameters),control_strategy:JSON.parse(e.item.getModel().control_strategy)}) | setStagingItem({...e.item.getModel(),in_parameters:JSON.parse(e.item.getModel().in_parameters),out_parameters:JSON.parse(e.item.getModel().out_parameters),control_strategy:JSON.parse(e.item.getModel().control_strategy)}) | ||||
| // form.setFieldsValue({...e.item.getModel(),in_parameters:JSON.parse(e.item.getModel().in_parameters),out_parameters:JSON.parse(e.item.getModel().out_parameters)}) | |||||
| // setStagingItem({...e.item.getModel(),in_parameters:JSON.parse(e.item.getModel().in_parameters),out_parameters:JSON.parse(e.item.getModel().out_parameters)}) | |||||
| // setTimeout(() => { | |||||
| // console.log(stagingItem); | |||||
| // }, (500)); | |||||
| setOpen(true); | setOpen(true); | ||||
| } | } | ||||
| // console.log(e.item.getModel().in_parameters); | // console.log(e.item.getModel().in_parameters); | ||||
| @@ -252,7 +271,18 @@ const Props = forwardRef(({onParentChange}, ref) =>{ | |||||
| })); | })); | ||||
| return ( | return ( | ||||
| <> | <> | ||||
| <Drawer title="编辑任务" placement="right" closeIcon={false} onClose={onClose} afterOpenChange={afterOpenChange} open={open}> | |||||
| <Drawer title="任务执行详情" placement="right" closeIcon={false} onClose={onClose} afterOpenChange={afterOpenChange} open={open}> | |||||
| <div className={Styles.detailBox}>任务名称:{stagingItem.label}</div> | |||||
| <div className={Styles.detailBox} >执行状态: | |||||
| <div style={{width:'8px', | |||||
| height:'8px', | |||||
| borderRadius:'50%', | |||||
| marginRight:'6px', | |||||
| backgroundColor:statusColorObj[stagingItem.experimentStatus] | |||||
| }}></div> | |||||
| <span style={{color:statusColorObj[stagingItem.experimentStatus]}}>{statusObj[stagingItem.experimentStatus]}</span></div> | |||||
| <div className={Styles.detailBox}>启动时间:{momnet(stagingItem.experimentStartTime).format('YYYY-MM-DD HH:mm:ss')}</div> | |||||
| <div className={Styles.detailBox}>耗时:{stagingItem.experimentEndTime?timers(new Date(stagingItem.experimentEndTime).getTime()-new Date(stagingItem.experimentStartTime).getTime()):timers(new Date().getTime()-new Date(stagingItem.experimentStartTime).getTime())}</div> | |||||
| <Tabs | <Tabs | ||||
| defaultActiveKey="1" | defaultActiveKey="1" | ||||
| items={items}/> | items={items}/> | ||||
| @@ -1,8 +1,8 @@ | |||||
| import React ,{ useState,useEffect,useRef }from 'react'; | import React ,{ useState,useEffect,useRef }from 'react'; | ||||
| import { Space, Table, Tag,Button,Modal, Form, Input ,message, Select,} from 'antd'; | import { Space, Table, Tag,Button,Modal, Form, Input ,message, Select,} from 'antd'; | ||||
| import { PlusOutlined, EditOutlined ,PlayCircleOutlined} from '@ant-design/icons'; | |||||
| import {getWorkflow,addWorkflow,removeWorkflow,cloneWorkflow} from '@/services/pipeline/index.js' | |||||
| import {getExperiment,runExperiments,getExperimentById,postExperiment,putExperiment,getQueryByExperimentId} from '@/services/experiment/index.js' | |||||
| import { PlusOutlined, EditOutlined ,PlayCircleOutlined,DeleteOutlined,FieldTimeOutlined} from '@ant-design/icons'; | |||||
| import {getWorkflow,} from '@/services/pipeline/index.js' | |||||
| import {getExperiment,runExperiments,getExperimentById,postExperiment,putExperiment,getQueryByExperimentId,deleteExperimentById,deleteQueryByExperimentInsId,putQueryByExperimentInsId} from '@/services/experiment/index.js' | |||||
| import Styles from './index.less' | import Styles from './index.less' | ||||
| import { useNavigate} from 'react-router-dom'; | import { useNavigate} from 'react-router-dom'; | ||||
| import momnet from 'moment' | import momnet from 'moment' | ||||
| @@ -14,9 +14,12 @@ const Experiment = React.FC = () => { | |||||
| const statusObj={ | const statusObj={ | ||||
| "Running":'运行中', | "Running":'运行中', | ||||
| "Succeeded":'成功', | "Succeeded":'成功', | ||||
| "Pending":'等待中', | |||||
| "Failed":'失败', | "Failed":'失败', | ||||
| "Error":'错误', | "Error":'错误', | ||||
| "Teminated":'终止' | |||||
| "Terminated":'终止', | |||||
| "Skipped":'未执行', | |||||
| "Omitted":'未执行', | |||||
| } | } | ||||
| const [experimentList, setExperimentList] = useState([]); | const [experimentList, setExperimentList] = useState([]); | ||||
| @@ -258,7 +261,41 @@ const Experiment = React.FC = () => { | |||||
| > | > | ||||
| 编辑 | 编辑 | ||||
| </Button> | </Button> | ||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| danger | |||||
| key="batchRemove" | |||||
| icon = {< DeleteOutlined />} | |||||
| onClick={async () => { | |||||
| Modal.confirm({ | |||||
| title: '删除', | |||||
| content: '确定删除该条实验吗?', | |||||
| okText: '确认', | |||||
| cancelText: '取消', | |||||
| onOk: () => { | |||||
| console.log(record); | |||||
| deleteExperimentById(record.id).then(ret=>{ | |||||
| if(ret.code==200){ | |||||
| message.success('删除成功') | |||||
| getList() | |||||
| } | |||||
| else{ | |||||
| message.error(ret.msg) | |||||
| } | |||||
| }); | |||||
| // if (success) { | |||||
| // if (actionRef.current) { | |||||
| // actionRef.current.reload(); | |||||
| // } | |||||
| // } | |||||
| }, | |||||
| }); | |||||
| }} | |||||
| > | |||||
| 删除 | |||||
| </Button> | |||||
| </Space> | </Space> | ||||
| ), | ), | ||||
| }, | }, | ||||
| @@ -278,14 +315,66 @@ const Experiment = React.FC = () => { | |||||
| <div style={{width:'200px'}}>状态</div> | <div style={{width:'200px'}}>状态</div> | ||||
| <div style={{width:'300px'}}>运行时长</div> | <div style={{width:'300px'}}>运行时长</div> | ||||
| <div style={{width:'300px'}}>开始时间</div> | <div style={{width:'300px'}}>开始时间</div> | ||||
| <div style={{width:'300px'}}>操作</div> | |||||
| </div>:''} | </div>:''} | ||||
| {experimentInList&&experimentInList.length>0?experimentInList.map((item,index)=>( | {experimentInList&&experimentInList.length>0?experimentInList.map((item,index)=>( | ||||
| <div className={Styles.tableExpandBox} style={{border:'1px solid #eaeaea',backgroundColor:'#fff',height:'45px'}}> | <div className={Styles.tableExpandBox} style={{border:'1px solid #eaeaea',backgroundColor:'#fff',height:'45px'}}> | ||||
| <a style={{width:'50px'}} onClick={(e)=>routerToText(e,item,record)}>{index+1}</a> | <a style={{width:'50px'}} onClick={(e)=>routerToText(e,item,record)}>{index+1}</a> | ||||
| <div style={{width:'200px'}}>{statusObj[item.status]}</div> | <div style={{width:'200px'}}>{statusObj[item.status]}</div> | ||||
| <div style={{width:'300px'}}>{item.finish_time?timers(new Date(item.finish_time).getTime()-new Date(item.start_time).getTime()):timers(new Date().getTime()-new Date(item.start_time).getTime())}</div> | |||||
| <div style={{width:'300px'}}>{momnet(item.start_time).format('YYYY-MM-DD HH:mm:ss')}</div> | |||||
| <div style={{width:'300px'}}>{item.finish_time?timers(new Date(item.finish_time).getTime()-new Date(item.create_time).getTime()):timers(new Date().getTime()-new Date(item.create_time).getTime())}</div> | |||||
| <div style={{width:'300px'}}>{momnet(item.create_time).format('YYYY-MM-DD HH:mm:ss')}</div> | |||||
| <div style={{width:'300px'}}> | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| key="batchRemove" | |||||
| disabled={item.status=='Succeeded'||item.status=='Failed'||item.status=='Terminated'} | |||||
| icon = {<FieldTimeOutlined />} | |||||
| onClick={async () => { | |||||
| putQueryByExperimentInsId(item.id).then(ret=>{ | |||||
| if(ret.code==200){ | |||||
| message.success('终止成功') | |||||
| getQueryByExperiment(record.id) | |||||
| } | |||||
| else{ | |||||
| message.error(ret.msg) | |||||
| } | |||||
| }) | |||||
| }} | |||||
| > | |||||
| 终止 | |||||
| </Button> | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| danger | |||||
| key="batchRemove" | |||||
| disabled={item.status=='Running'||item.status=='Pending'} | |||||
| icon = {< DeleteOutlined />} | |||||
| onClick={async () => { | |||||
| Modal.confirm({ | |||||
| title: '删除', | |||||
| content: '确定删除该条实例吗?', | |||||
| okText: '确认', | |||||
| cancelText: '取消', | |||||
| onOk: () => { | |||||
| deleteQueryByExperimentInsId(item.id).then(ret=>{ | |||||
| if(ret.code==200){ | |||||
| message.success('删除成功') | |||||
| getQueryByExperiment(record.id) | |||||
| } | |||||
| else{ | |||||
| message.error(ret.msg) | |||||
| } | |||||
| }); | |||||
| }, | |||||
| }); | |||||
| }} | |||||
| > | |||||
| 删除 | |||||
| </Button> | |||||
| </div> | |||||
| </div> | </div> | ||||
| )):''} | )):''} | ||||
| </div> | </div> | ||||
| @@ -21,12 +21,30 @@ export function getExperimentById(id) { | |||||
| method: 'GET', | method: 'GET', | ||||
| }); | }); | ||||
| } | } | ||||
| // 根据id删除实验 | |||||
| export function deleteExperimentById(id) { | |||||
| return request(`/api/mmp/experiment/${id}`, { | |||||
| method: 'DELETE', | |||||
| }); | |||||
| } | |||||
| // 根据id查询实验实例 | // 根据id查询实验实例 | ||||
| export function getQueryByExperimentId(id) { | export function getQueryByExperimentId(id) { | ||||
| return request(`/api/mmp/experimentIns/queryByExperimentId/${id}`, { | return request(`/api/mmp/experimentIns/queryByExperimentId/${id}`, { | ||||
| method: 'GET', | method: 'GET', | ||||
| }); | }); | ||||
| } | } | ||||
| // 根据id删除实验实例 | |||||
| export function deleteQueryByExperimentInsId(id) { | |||||
| return request(`/api/mmp/experimentIns/${id}`, { | |||||
| method: 'DELETE', | |||||
| }); | |||||
| } | |||||
| // 根据id终止实验实例 | |||||
| export function putQueryByExperimentInsId(id) { | |||||
| return request(`/api/mmp/experimentIns/${id}`, { | |||||
| method: 'PUT', | |||||
| }); | |||||
| } | |||||
| // 根据id查询查询日志 | // 根据id查询查询日志 | ||||
| export function getQueryByExperimentLog(params) { | export function getQueryByExperimentLog(params) { | ||||
| return request(`/api/mmp/experimentIns/log/`, { | return request(`/api/mmp/experimentIns/log/`, { | ||||