edge.js 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997
  1. import React from 'react';
  2. import ReactDOM from 'react-dom';
  3. import { connect } from 'react-redux';
  4. import classNames from 'classnames';
  5. import { enterEdge, leaveEdge } from '../actions/app-actions';
  6. import { encodeIdAttribute, decodeIdAttribute } from '../utils/dom-utils';
  7. import {
  8. DETAILS_PANEL_WIDTH as WIDTH,
  9. DETAILS_PANEL_OFFSET as OFFSET,
  10. DETAILS_PANEL_MARGINS as MARGINS
  11. } from '../constants/styles';
  12. import { setNodeColor } from '../utils/color-utils';
  13. import '../../styles/index.less'
  14. import '../../styles/iconfont.css'
  15. // import { GlobalIcon } from '../../styles/icon.js';
  16. // import echarts from 'echarts'
  17. import * as echarts from 'echarts'
  18. import axios from 'axios'
  19. import moment from 'moment'
  20. import { Table,Icon,Radio,Drawer, Button,Switch,Checkbox } from "antd";
  21. import "antd/dist/antd.css";
  22. import getToken from '../utils/get-token'
  23. function isStorageComponent(id) {
  24. const storageComponents = ['<persistent_volume>', '<storage_class>', '<persistent_volume_claim>', '<volume_snapshot>', '<volume_snapshot_data>'];
  25. return storageComponents.includes(id);
  26. }
  27. // getAdjacencyClass takes id which contains information about edge as a topology
  28. // of parent and child node.
  29. // For example: id is of form "nodeA;<storage_class>---nodeB;<persistent_volume_claim>"
  30. function getAdjacencyClass(id) {
  31. const topologyId = id.split('---');
  32. const fromNode = topologyId[0].split(';');
  33. const toNode = topologyId[1].split(';');
  34. if (fromNode[1] !== undefined && toNode[1] !== undefined) {
  35. if (isStorageComponent(fromNode[1]) || isStorageComponent(toNode[1])) {
  36. return 'link-storage';
  37. }
  38. }
  39. return 'link-none';
  40. }
  41. class Edge extends React.Component {
  42. constructor(props, context) {
  43. super(props, context);
  44. this.handleMouseEnter = this.handleMouseEnter.bind(this);
  45. this.handleMouseLeave = this.handleMouseLeave.bind(this);
  46. this.handleClick = this.handleClick.bind(this)
  47. this.state = {
  48. showElem:false,
  49. x:0,
  50. y:'30%',
  51. lineTip:'',
  52. visible: false, //控制弹窗显示隐藏
  53. AnalystData:{}, //延迟分位图数据存储
  54. queryParams:{
  55. start_time:Math.round((new Date().getTime())/1000 - (5*60)),
  56. end_time:Math.round(new Date().getTime()/1000),
  57. app_alias:'UNSET',
  58. service_name:'frontend',
  59. percentile:0.95
  60. },
  61. traceData:[],
  62. // traceUrl:'http://observe-front.cestong.com.cn', //本地调试使用
  63. // baseUrl:'http://observe-server.cestong.com.cn', //本地调试使用
  64. baseUrl:'/re', //上线时打开
  65. traceUrl:'', //上线时打开
  66. scoreObj:[],
  67. bgColor:setNodeColor('R'),
  68. pagination: {
  69. pageIndex:1,
  70. pageSize:10,
  71. total:0,
  72. current:1,
  73. },
  74. traceQuery:{
  75. only_exception:0,
  76. only_database:0,
  77. },
  78. loading: false,
  79. value:0.95,
  80. total:0,
  81. getToken: ''
  82. }
  83. }
  84. render() {
  85. let transform;
  86. const panelHeight = window.innerHeight - MARGINS.bottom - MARGINS.top;
  87. const {
  88. id, path, highlighted, focused, thickness, source, target
  89. } = this.props;
  90. const shouldRenderMarker = (focused || highlighted) && (source !== target);
  91. const className = classNames('edge', { highlighted });
  92. const spinnerClassName = classNames('fa fa-circle-notch', { 'fa-spin': this.props.mounted });
  93. const wrapStyle = {
  94. left: this.state.showElem ? MARGINS.right : null,
  95. width: this.state.showElem ? null : WIDTH,
  96. zIndex: 9999,
  97. }
  98. // const bgColor = setNodeColor('B')
  99. const styles = {
  100. header: {
  101. backgroundColor: this.state.bgColor
  102. },
  103. headerColor:{
  104. color:this.state.bgColor
  105. }
  106. }
  107. let labelsContainer = {
  108. // transform: 'scale(2)',
  109. pointerEvents: 'none',
  110. position:'absolute',
  111. wordWrap: 'break-word', /* 当单词过长时进行断开 */
  112. overflowWrap: 'break-word', /* 支持更多语言的断开 */
  113. borderRadius: '8px',
  114. }
  115. let TipboxStyle={
  116. borderRadius: '8px',
  117. padding: '10px',
  118. zIndex: 9999,
  119. background:'#fff',
  120. width:'100%',
  121. border:'1px solid #f1f1f1',
  122. // color:'#fff',
  123. // boxShadow:'0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 9px 28px 8px rgba(0, 0, 0, 0.05)',
  124. boxShadow: '0px 0px 22px 2px rgba(0, 0, 0, 0.1)'
  125. }
  126. let box={
  127. position: 'fixed',
  128. display: 'flex',
  129. right: '30px',
  130. top: '100px',
  131. bottom: '48px',
  132. transition: 'transform 0.33333s cubic-bezier(0, 0, 0.21, 1) 0s, margin-top 0.15s ease-in-out 0s !important',
  133. transform: 'translateX(0px)',
  134. width: '420px',
  135. height:'100%'
  136. }
  137. const columns = [
  138. {
  139. title: 'TraceID',
  140. dataIndex: 'trace_id',
  141. key: 'trace_id',
  142. width:'27%',
  143. ellipsis:true,
  144. align:'left',
  145. render: (text,record) => <a target='_blank' href={`${this.state.traceUrl}/#/latency/index?traceId=${text}&app_alias=${this.state.queryParams.app_alias}&span_id=${record.span_id}&datetime=${Date.parse(record.datetime)/1000}`}>{text}</a>
  146. // scopedSlots:{customRender:'trace_id'},
  147. // render:traceID => <a target='_blank' href={`${this.state.traceUrl}/#/latency/index?traceId=${traceID}&app_alias=${this.state.queryParams.app_alias}`}>{traceID}</a>
  148. // render: traceID => <a target='_blank' href={`${grafanaRoot}/explore?orgId=1&left={"datasource":"sV_Dh0LVz","queries":[{"refId":"A","datasource":{"type":"tempo","uid":"sV_Dh0LVz"},"queryType":"nativeSearch","serviceName":"${this.props.selectedNodeId}"}],"range":{"from":"now-1h","to":"now"}}`}>{traceID}</a>,
  149. },
  150. {
  151. title: 'Service',
  152. dataIndex: 'service_name',
  153. key: 'service_name',
  154. width:'20%',
  155. ellipsis:true,
  156. align:'right'
  157. },
  158. {
  159. title: 'Meth.',
  160. dataIndex: 'method',
  161. key: 'method',
  162. width:'18%',
  163. ellipsis:true,
  164. align:'right'
  165. },
  166. {
  167. title: 'Code',
  168. dataIndex: 'code',
  169. key: 'code',
  170. width:'15%',
  171. ellipsis:true,
  172. align:'right'
  173. },
  174. {
  175. title: 'Dur.(ms)',
  176. dataIndex: 'duration',
  177. key: 'duration',
  178. width:'20%',
  179. ellipsis:true,
  180. align:'right',
  181. render:(text) => <span>{text.toFixed(2)}</span>,
  182. },
  183. ]
  184. return (
  185. <g
  186. id={encodeIdAttribute(id)}
  187. className={className}
  188. onMouseEnter={this.handleMouseEnter}
  189. onMouseLeave={this.handleMouseLeave}
  190. onClick={this.handleClick}
  191. style={{position:'relative'}}
  192. >
  193. {/* {this.state.showElem?
  194. <foreignObject
  195. style={labelsContainer}
  196. y={this.state.y}
  197. x={(this.state.x)-10}
  198. width={300}
  199. height={100}
  200. >
  201. <div className='Tipbox' style={TipboxStyle} dangerouslySetInnerHTML={{ __html:this.state.lineTip }}>
  202. </div>
  203. </foreignObject>:null
  204. } */}
  205. <path className="shadow" d={path} style={{ strokeWidth: 10 * thickness }} />
  206. <path
  207. className={getAdjacencyClass(id)}
  208. d={path}
  209. style={{ strokeWidth: 5 }}
  210. />
  211. <path
  212. className="link"
  213. d={path}
  214. markerEnd={shouldRenderMarker ? 'url(#end-arrow)' : null}
  215. style={{ strokeWidth: thickness }}
  216. />
  217. {this.state.showElem?
  218. <div className='drawerBox'>
  219. <Drawer
  220. placement="right"
  221. onClose={this.onClose}
  222. visible={this.state.showElem}
  223. destroyOnClose={true}
  224. width="420px"
  225. >
  226. <div className="tour-step-anchor node-details">
  227. {/* {tools} */}
  228. <div className="node-details-header" style={styles.header}>
  229. <div className='node-details-header-flex'>
  230. <div className="node-details-header-wrapper">
  231. <div className='node-details-header-icon'>
  232. {/* <div className='fa fa-level-down'></div> */}
  233. <div className='roll_box'><Icon className='roll' type='swap-right'></Icon></div>
  234. {/* <div className='roll_box'>
  235. <span className='iconfont icon-chehui2'></span>
  236. </div> */}
  237. </div>
  238. <div style={{width:'85%'}}>
  239. <h2 className="node-details-header-label truncate" title='标题部分'>
  240. {source}
  241. </h2>
  242. <div className="node-details-header-relatives truncate ">
  243. </div>
  244. <h2 className="node-details-header-label truncate" title='标题部分'>
  245. {target}
  246. </h2>
  247. <div className="node-details-header-relatives truncate">
  248. </div>
  249. </div>
  250. </div>
  251. <div className='node-details-header-apdex' style={styles.headerColor}>
  252. Apdex:
  253. {
  254. this.state.scoreObj.length>0?
  255. this.state.scoreObj.map((item,i)=>{
  256. return <div key={i} className='node-details-header-score'>{Math.floor(item.apdex*100)}</div>
  257. }):<div className='node-details-header-score'>0</div>
  258. }
  259. </div>
  260. </div>
  261. </div>
  262. <div className="node-details-content" style={{marginTop:'123px'}}>
  263. {/* 状态 */}
  264. <div className="node-details-content-section">
  265. <div className="node-details-content-section-header">状态</div>
  266. <div className='node-details-info'>
  267. {
  268. this.state.scoreObj.length>0?
  269. this.state.scoreObj.map((v,i)=>{
  270. return (
  271. <div key={i} className='node-details-info-field-flex'>
  272. <div className='node-details-info-field'>
  273. <div className='node-details-info-field-label truncate w50'>Total:</div>
  274. <div className='node-details-info-field-value truncate w50'>
  275. {v.total_num?v.total_num:0}
  276. </div>
  277. </div>
  278. <div className='node-details-info-field'>
  279. <div className='node-details-info-field-label truncate w50'>Avg:</div>
  280. <div className='node-details-info-field-value truncate w50'>{v.duration_average?v.duration_average:0}</div>
  281. </div>
  282. <div className='node-details-info-field'>
  283. <div className='node-details-info-field-label truncate w50'>Qps:</div>
  284. <div className='node-details-info-field-value truncate w50'>{v.qps?v.qps:0}r/s</div>
  285. </div>
  286. <div className='node-details-info-field'>
  287. <div className='node-details-info-field-label truncate w50'>P.50:</div>
  288. <div className='node-details-info-field-value truncate w50'>{v.duration_median?v.duration_median:0}</div>
  289. </div>
  290. <div className='node-details-info-field'>
  291. <div className='node-details-info-field-label truncate w50'>ErrRate:</div>
  292. <div className='node-details-info-field-value truncate w50'>{v.error_rate?v.error_rate.toFixed(2):0}%</div>
  293. </div>
  294. <div className='node-details-info-field'>
  295. <div className='node-details-info-field-label truncate w50'>P.90:</div>
  296. <div className='node-details-info-field-value truncate w50'>{v.duration_p90?v.duration_p90:0}</div>
  297. </div>
  298. <div className='node-details-info-field'>
  299. <div className='node-details-info-field-label truncate w50'>ErrNum:</div>
  300. <div className='node-details-info-field-value truncate w50'>{v.error_num?v.error_num:0}</div>
  301. </div>
  302. <div className='node-details-info-field'>
  303. <div className='node-details-info-field-label truncate w50'>P.99:</div>
  304. <div className='node-details-info-field-value truncate w50'>{v.duration_p99?v.duration_p99:0}</div>
  305. </div>
  306. </div>
  307. )
  308. }):(
  309. <div className='node-details-info-field-flex'>
  310. <div className='node-details-info-field'>
  311. <div className='node-details-info-field-label truncate w50'>Total:</div>
  312. <div className='node-details-info-field-value truncate w50'>0</div>
  313. </div>
  314. <div className='node-details-info-field'>
  315. <div className='node-details-info-field-label truncate w50'>Avg:</div>
  316. <div className='node-details-info-field-value truncate w50'>0</div>
  317. </div>
  318. <div className='node-details-info-field'>
  319. <div className='node-details-info-field-label truncate w50'>Qps:</div>
  320. <div className='node-details-info-field-value truncate w50'>0r/s</div>
  321. </div>
  322. <div className='node-details-info-field'>
  323. <div className='node-details-info-field-label truncate w50'>P.50:</div>
  324. <div className='node-details-info-field-value truncate w50'>0</div>
  325. </div>
  326. <div className='node-details-info-field'>
  327. <div className='node-details-info-field-label truncate w50'>ErrRate:</div>
  328. <div className='node-details-info-field-value truncate w50'>0%</div>
  329. </div>
  330. <div className='node-details-info-field'>
  331. <div className='node-details-info-field-label truncate w50'>P.90:</div>
  332. <div className='node-details-info-field-value truncate w50'>0</div>
  333. </div>
  334. <div className='node-details-info-field'>
  335. <div className='node-details-info-field-label truncate w50'>ErrNum:</div>
  336. <div className='node-details-info-field-value truncate w50'>0</div>
  337. </div>
  338. <div className='node-details-info-field'>
  339. <div className='node-details-info-field-label truncate w50'>P.99:</div>
  340. <div className='node-details-info-field-value truncate w50'>0</div>
  341. </div>
  342. </div>
  343. )
  344. }
  345. </div>
  346. </div>
  347. <div className="node-details-content-section">
  348. <div className="node-details-content-section-header">延迟分位</div>
  349. <div className='node-details-info' style={{marginTop: "-15px",position:'relative'}}>
  350. {
  351. JSON.stringify(this.state.AnalystData)!="{}"?
  352. (<div>
  353. <div id='main' className="echartsbox" style={{height:'240px'}}></div>
  354. </div>)
  355. :(
  356. <div className='noData'>暂无数据</div>
  357. )
  358. }
  359. <div className='LatencySelect'>
  360. <Radio.Group onChange={this.onChange} value={this.state.queryParams.percentile} size="small">
  361. <Radio value={0.5}>p.50</Radio>
  362. <Radio value={0.95}>p.95</Radio>
  363. <Radio value={0.99}>p.99</Radio>
  364. </Radio.Group>
  365. </div>
  366. </div>
  367. </div>
  368. <div className='node-details-content-section'>
  369. <div className="node-details-content-section-header">异常Trace</div>
  370. <div className='node-serch'>
  371. <Checkbox onChange={this.onChangeError}>仅异常</Checkbox>
  372. <Checkbox onChange={this.onChangeSql}>仅SQL</Checkbox>
  373. </div>
  374. <Table dataSource={this.state.traceData} columns={columns} rowKey='span_id' pagination={this.state.pagination}
  375. onChange={this.handleTableChange} size='small'>
  376. {/* <span slot='trace_id' slot-scope='text,record'>
  377. <template>
  378. <div>
  379. <a target='_blank' href={`${this.state.traceUrl}/#/latency/index?traceId=${record.trace_id}&app_alias=${this.state.queryParams.app_alias}&span_id=${record.span_id}`}>{record.trace_id}</a>
  380. </div>
  381. </template>
  382. </span> */}
  383. </Table>
  384. </div>
  385. </div>
  386. </div>
  387. </Drawer>
  388. </div> :null
  389. }
  390. </g>
  391. );
  392. }
  393. onClose = (e) => {
  394. e.stopPropagation();
  395. e.preventDefault();
  396. this.setState({
  397. showElem: false,
  398. },()=>{
  399. // console.log(this.state.showElem,'关闭按钮的值')
  400. });
  401. }
  402. componentDidMount() {
  403. let _this = this
  404. //上线时打开开始
  405. this.setQueryParams();
  406. const traceURL = `http://${parent.location.hostname}`
  407. this.setState({
  408. traceUrl:traceURL
  409. },()=>{
  410. })
  411. //上线时打开结束
  412. }
  413. setQueryParams(){
  414. var strr = parent.location.href; //上线做为iframe嵌套时使用
  415. let param = this.parseQueryString(strr); //全链路需要的参数多,因此解析成对象形式
  416. const queryParams = this.state.queryParams;
  417. if(parseInt(param.start_time)!=0 && parseInt(param.end_time) !=0){ //上线时打开
  418. let newStartTime = parseInt(param.start_time); // 设置新的属性值
  419. let newEndTime = parseInt(param.end_time); // 设置新的属性值
  420. queryParams.start_time = newStartTime
  421. queryParams.end_time = newEndTime
  422. }
  423. const newAppAlias = param.app_alias
  424. queryParams.app_alias = newAppAlias
  425. this.setState({
  426. queryParams: queryParams
  427. },()=>{
  428. });
  429. }
  430. //解析URL
  431. parseQueryString(url){
  432. var json = {};
  433. var arr = url.substr(url.indexOf('?') + 1).split('&');
  434. arr.forEach(item=>{
  435. var tmp = item.split('=');
  436. json[tmp[0]] = tmp[1];
  437. });
  438. return json;
  439. }
  440. // 散点图渲染
  441. initChart(tmpData) {
  442. // let chart = id+'Chart';
  443. var chart;
  444. chart = echarts.init(document.getElementById('main'))
  445. chart.clear();
  446. let option = {
  447. title: {
  448. text: '',
  449. subtext: '',
  450. textStyle:{
  451. fontSize:14
  452. },
  453. },
  454. grid: {
  455. top:'8%',
  456. left: '3%',
  457. right: '2%',
  458. bottom: '14%',
  459. containLabel: true
  460. },
  461. tooltip: {
  462. // trigger: 'axis',
  463. showDelay: 0,
  464. formatter: function (params) {
  465. let newParams = moment(params.data[0]).format('YYYY-MM-DD HH:mm:ss');
  466. let time = newParams+"<br/>"+ params.data[1]+"ms"
  467. return time;
  468. },
  469. axisPointer: {
  470. show: true,
  471. type: 'cross',
  472. lineStyle: {
  473. type: 'dashed',
  474. width: 1
  475. }
  476. }
  477. },
  478. toolbox: {
  479. show:true,
  480. showTitle: true,
  481. feature: {
  482. rect: {
  483. show: true,
  484. title: 'Trace选择'
  485. },
  486. },
  487. left:"40%", //组件离容器左侧的距离,'left', 'center', 'right','20%'
  488. top:"-3%", //组件离容器上侧的距离,'top', 'middle', 'bottom','20%'
  489. right:"auto", //组件离容器右侧的距离,'20%'
  490. bottom:"auto",
  491. },
  492. brush: {
  493. toolbox: ['rect'],
  494. xAxisIndex: 0,
  495. throttleType:'debounce',
  496. throttleDelay:600,
  497. // 'brush': () => {
  498. // //手动触发缩放 解决数据显示不全问题
  499. // let echartsInstance = document.getElementById('main').getEchartsInstance()
  500. // //首先获取当前缩放位置
  501. // let {start, end} = echartsInstance.getOption().dataZoom[0]
  502. // echartsInstance.dispatchAction({
  503. // type: 'dataZoom',
  504. // // 可选,dataZoom 组件的 index,多个 dataZoom 组件时有用,默认为 0
  505. // dataZoomIndex: 0,
  506. // // 开始位置的百分比,0 - 100
  507. // start: start,
  508. // // 结束位置的百分比,0 - 100
  509. // end: end,
  510. // // // 开始位置的数值
  511. // // startValue: 0,
  512. // // // 结束位置的数值
  513. // // endValue: 100
  514. // })
  515. // }
  516. },
  517. legend: {
  518. data: ['Success', 'Failed'],
  519. left: 'center',
  520. bottom: 0,
  521. itemGap: 100,
  522. textStyle: {//文字颜色
  523. fontSize: 12,
  524. padding:[0,3],//文字与图形之间的左右间距
  525. rich:{
  526. labelName:{
  527. fontSize:14,
  528. color:'#333',
  529. fontWeight:500
  530. }
  531. }
  532. },
  533. formatter: function (params) {
  534. // 获取legend显示内容
  535. let data = tmpData;
  536. let sl,fl;
  537. if(data.success!=null){
  538. sl = tmpData.success.length;
  539. }else{
  540. sl = 0
  541. }
  542. if( data.failed!=null){
  543. fl = tmpData.failed.length;
  544. }else{
  545. fl = 0
  546. }
  547. var target;
  548. if(params == 'Success'){
  549. target = sl;
  550. }else if(params == 'Failed'){
  551. target = fl;
  552. }
  553. return target != undefined?params +' '+`{labelName|${target}}`:params
  554. },
  555. },
  556. xAxis: [
  557. {
  558. // type: 'category',
  559. type:'time',
  560. // scale: true,
  561. gridIndex:0,
  562. // splitNumber: 4,
  563. axisLabel: {
  564. show:false,
  565. textStyle: {
  566. fontSize: 12,
  567. textAlign:'center'
  568. },
  569. formatter: function(params) {
  570. let newParams = moment(params).format('YYYY-MM-DD HH:mm:ss');
  571. let newArr = newParams.split(' ')
  572. let time = newArr[0] + "\n" + newArr[1]
  573. return time;
  574. }
  575. },
  576. splitLine: {
  577. show: true
  578. },
  579. }
  580. ],
  581. yAxis: [
  582. {
  583. type: 'value',
  584. // scale: true,
  585. gridIndex:0,
  586. axisLabel: {
  587. formatter: '{value}'
  588. },
  589. axisLine:{
  590. show:true
  591. },
  592. axisTick:{
  593. show:true
  594. },
  595. splitLine: {
  596. show: true
  597. },
  598. data:[0,2500,5000,7500,10000],
  599. }
  600. ],
  601. series: [
  602. {
  603. name: 'Success',
  604. type: 'scatter',
  605. emphasis: {
  606. focus: 'series'
  607. },
  608. //设置散点图样式
  609. itemStyle:{
  610. color:'#13ce66'
  611. },
  612. symbolSize:10,//设置散点的大小
  613. data:tmpData.success,
  614. markArea: {
  615. silent: true,
  616. itemStyle: {
  617. color: 'transparent',
  618. borderWidth: 0,
  619. borderType: 'dashed'
  620. },
  621. },
  622. },
  623. {
  624. name: 'Failed',
  625. type: 'scatter',
  626. emphasis: {
  627. focus: 'series'
  628. },
  629. itemStyle:{
  630. color:'#ff4949'
  631. },
  632. // prettier-ignore
  633. data:tmpData.failed,
  634. // data:[],
  635. markArea: {
  636. silent: true,
  637. itemStyle: {
  638. color: 'transparent',
  639. borderWidth: 0,
  640. borderType: 'dashed'
  641. },
  642. },
  643. }
  644. ]
  645. }
  646. chart.setOption(option,true)
  647. chart.off("brushSelected");
  648. //框选选择数据
  649. chart.on('brushSelected', (params) => {
  650. var brushComponent = params.batch[0];
  651. let successIndexList=[];
  652. let failIndexList =[];
  653. let successList=[];
  654. let failList=[];
  655. if(brushComponent.selected.length>1){
  656. successIndexList = brushComponent.selected[0].dataIndex
  657. failIndexList = brushComponent.selected[1].dataIndex
  658. }else{
  659. if(brushComponent.selected[0].seriesName !=undefined){
  660. if(brushComponent.selected[0].seriesName =="Failed"){
  661. failIndexList = brushComponent.selected[0].dataIndex
  662. }else{
  663. successIndexList = brushComponent.selected[0].dataIndex
  664. }
  665. }
  666. }
  667. if(successIndexList.length>0){
  668. for(let i = 0;i<tmpData.success.length;i++){
  669. for(let j=0;j<successIndexList.length;j++){
  670. if(successIndexList[j] == i){
  671. successList.push(tmpData.success[i])
  672. }
  673. }
  674. }
  675. }
  676. if(failIndexList.length>0){
  677. for(let k=0;k<tmpData.failed.length;k++){
  678. for(let l=0;l<failIndexList.length;l++){
  679. if(failIndexList[l] == k){
  680. failList.push(tmpData.failed[k])
  681. }
  682. }
  683. }
  684. }
  685. let arr =successList.concat(failList);
  686. let dataRange={};
  687. if(arr.length>0){
  688. let timeArr =[];
  689. let valueArr=[];
  690. for(let m=0;m<arr.length;m++){
  691. timeArr.push(Math.round(Date.parse(arr[m][0])/1000));
  692. valueArr.push(arr[m][1]);
  693. }
  694. let minTime = Math.min(...timeArr);
  695. let maxTime = Math.max(...timeArr);
  696. let minValue = Math.min(...valueArr);
  697. let maxValue = Math.max(...valueArr)
  698. if(minTime == maxTime){
  699. minTime = minTime-1;
  700. maxTime = maxTime+1;
  701. }
  702. if(minValue == maxValue){
  703. minValue = minValue-1;
  704. maxValue = maxValue+1;
  705. }
  706. //看是否有成功节点
  707. if(successIndexList.length>0){
  708. dataRange = {
  709. start_time:minTime,
  710. end_time:maxTime,
  711. min_duration:minValue,
  712. max_duration:maxValue,
  713. failed:false,
  714. app_alias:this.state.queryParams.app_alias,
  715. service_name:this.props.id,
  716. }
  717. }else{
  718. dataRange = {
  719. start_time:minTime,
  720. end_time:maxTime,
  721. min_duration:minValue,
  722. max_duration:maxValue,
  723. failed:true,
  724. app_alias:this.state.queryParams.app_alias,
  725. service_name:this.props.id,
  726. }
  727. }
  728. let timeAndDuration = JSON.stringify(dataRange);
  729. // let href = this.$router.resolve({
  730. // path:'/latency/index',
  731. // query:{
  732. // data:timeAndDuration
  733. // }
  734. // })
  735. // window.open(window.location.origin+"/"+href.href,"_blank")
  736. let href = `${this.state.traceUrl}/#/latency/index?start_time=${dataRange.start_time}&end_time=${dataRange.end_time}&min_duration=${dataRange.min_duration}&max_duration=${dataRange.max_duration}&failed=${dataRange.failed}&app_alias=${this.state.queryParams.app_alias}&service_name=${this.props.id}`
  737. window.open(href,"_blank")
  738. setTimeout(()=>{
  739. chart.dispatchAction({
  740. type: 'brush',//选择action行为
  741. areas:[]//areas表示选框的集合,此时为空即可。
  742. });
  743. },500)
  744. }
  745. });
  746. window.addEventListener("resize",function (){
  747. chart.resize();
  748. });
  749. }
  750. handleMouseEnter(ev) {
  751. ev.stopPropagation();
  752. ev.preventDefault();
  753. this.props.enterEdge(decodeIdAttribute(ev.currentTarget.id));
  754. }
  755. handleMouseLeave(ev) {
  756. ev.stopPropagation();
  757. ev.preventDefault();
  758. this.props.leaveEdge(decodeIdAttribute(ev.currentTarget.id));
  759. }
  760. //点击事件
  761. handleClick(ev){
  762. if(ev.target.tagName=='path'){
  763. ev.stopPropagation();
  764. ev.preventDefault();
  765. // this.setQueryParams();
  766. this.getEdgeAppsScore(this.props.source,this.props.target)
  767. this.getEdgeAnalyst(this.props.source,this.props.target)
  768. this.getServiceSpans(this.props.source,this.props.target)
  769. this.setState({
  770. showElem:true,
  771. },()=>{
  772. })
  773. }
  774. }
  775. //获取边线弹窗内的信息
  776. getEdgeAppsScore(sourceService,targetService){
  777. axios({
  778. url: `${this.state.baseUrl}/api/v1/apps_score/${this.state.queryParams.app_alias}/edge`,
  779. method: "get",
  780. headers: { 'Authorization': getToken },
  781. params: {
  782. source_service:sourceService,
  783. target_service:targetService,
  784. start_time:this.state.queryParams.start_time,
  785. end_time:this.state.queryParams.end_time
  786. }
  787. }).then(res => {
  788. if(res && res.data.code == 200){
  789. const newArr= ((res || {}).data || {}).data || []
  790. this.setState({
  791. scoreObj:[...newArr]
  792. },()=>{
  793. if(this.state.scoreObj.length>0){
  794. for(let i=0;i<this.state.scoreObj.length;i++){
  795. this.state.scoreObj[i].apdex>=0.94?this.setState({bgColor:setNodeColor('G')})
  796. :(this.state.scoreObj[i].apdex>=0.85&&this.state.scoreObj[i].apdex<0.94)?this.setState({bgColor:setNodeColor('B')})
  797. :(this.state.scoreObj[i].apdex>=0.7&&this.state.scoreObj[i].apdex<0.85)?this.setState({bgColor:setNodeColor('DI')})
  798. :(this.state.scoreObj[i].apdex>=0.5&&this.state.scoreObj[i].apdex<0.7)?this.setState({bgColor:setNodeColor('Y')})
  799. :this.setState({bgColor:setNodeColor('R')})
  800. }
  801. }else{
  802. this.setState({bgColor:setNodeColor('R')})
  803. }
  804. })
  805. }
  806. });
  807. }
  808. //获取边线散点图
  809. getEdgeAnalyst(sourceService,targetService){
  810. axios({
  811. url: `${this.state.baseUrl}/api/v1/app/analyst/${this.state.queryParams.app_alias}/edge`,
  812. method: "get",
  813. headers: { 'Authorization': getToken },
  814. params: {
  815. source_service:sourceService,
  816. target_service:targetService,
  817. start_time:this.state.queryParams.start_time,
  818. end_time:this.state.queryParams.end_time,
  819. percentile:this.state.queryParams.percentile
  820. }
  821. }).then(res => {
  822. if(res && res.data.code == 200){
  823. const obj = ((res || {}).data || {}).data || {}
  824. this.setState({
  825. AnalystData:{...obj}
  826. },()=>{
  827. if(JSON.stringify(this.state.AnalystData)!="{}"){
  828. this.initChart(this.state.AnalystData)
  829. }
  830. })
  831. }
  832. });
  833. }
  834. //获取异常trace列表 /api/v1/service/spans
  835. getServiceSpans(sourceService,targetService){
  836. this.setState({ loading: true });
  837. this.setState({
  838. traceData:[],
  839. pagination:{total:0}
  840. },()=>{
  841. })
  842. axios({
  843. url: `${this.state.baseUrl}/api/v1/service/spans`,
  844. method: "get",
  845. headers: { 'Authorization': getToken },
  846. params: {
  847. // source_service:sourceService,
  848. // target_service:targetService,
  849. service_name:sourceService,
  850. service_name:targetService,
  851. only_exception:this.state.traceQuery.only_exception, // 仅显示异常trace相关
  852. only_database:this.state.traceQuery.only_database,
  853. pageIndex:this.state.pagination.pageIndex,
  854. pageSize:this.state.pagination.pageSize,
  855. start_time:this.state.queryParams.start_time,
  856. end_time:this.state.queryParams.end_time,
  857. app_alias:this.state.queryParams.app_alias
  858. }
  859. }).then(res => {
  860. if(res && res.data.code == 200){
  861. const list = (((res || {}).data || {}).data || {}).list || []
  862. const total = (((res || {}).data || {}).data || {}).count
  863. this.setState({
  864. loading:false,
  865. traceData:[...list],
  866. pagination:{...this.state.pagination,total:total}
  867. },()=>{
  868. })
  869. }
  870. });
  871. }
  872. handleTableChange=(pagination,pageSize)=>{
  873. const {current} = pagination
  874. this.setState({
  875. pagination: {...this.state.pagination,pageIndex:current,current:current},
  876. },()=>{
  877. this.getServiceSpans(this.props.source,this.props.target);
  878. });
  879. }
  880. //仅异常
  881. onChangeError = e =>{
  882. const only_exception = e.target.checked ? 1:0;
  883. const pageIndex = 1
  884. const current = 1
  885. this.setState({
  886. traceQuery:{...this.state.traceQuery,only_exception:only_exception},
  887. pagination: {...this.state.pagination,pageIndex:pageIndex,current:current},
  888. },()=>{
  889. this.getServiceSpans(this.props.source,this.props.target);
  890. });
  891. }
  892. //仅sql
  893. onChangeSql = e =>{
  894. const only_database = e.target.checked ? 1:0;
  895. const pageIndex = 1
  896. const current = 1
  897. this.setState({
  898. traceQuery:{...this.state.traceQuery,only_database:only_database},
  899. pagination: {...this.state.pagination,pageIndex:pageIndex,current:current},
  900. },()=>{
  901. this.getServiceSpans(this.props.source,this.props.target);
  902. });
  903. }
  904. //单选按钮
  905. onChange = e => {
  906. const percentile = e.target.value
  907. this.setState({
  908. queryParams:{...this.state.queryParams,percentile:percentile}
  909. },()=>{
  910. this.getEdgeAnalyst(this.props.source,this.props.target);
  911. });
  912. }
  913. }
  914. function mapStateToProps(state) {
  915. return {
  916. contrastMode: state.get('contrastMode')
  917. };
  918. }
  919. export default connect(
  920. mapStateToProps,
  921. { enterEdge, leaveEdge }
  922. )(Edge);