This is an automated email from the ASF dual-hosted git repository. hanahmily pushed a commit to branch 5.0.0/beta2 in repository https://gitbox.apache.org/repos/asf/incubator-skywalking-ui.git
The following commit(s) were added to refs/heads/5.0.0/beta2 by this push: new c6b57cc Attach span info panel with timeline. c6b57cc is described below commit c6b57ccf46d0a6ba28a95d87915612f2caa85504 Author: gaohongtao <hanahm...@gmail.com> AuthorDate: Fri Jun 15 10:59:43 2018 +0800 Attach span info panel with timeline. --- mock/trace.js | 21 +++--- src/components/TraceStack/index.js | 131 ++++++++++++++++++++++++----------- src/components/TraceStack/index.less | 7 +- 3 files changed, 110 insertions(+), 49 deletions(-) diff --git a/mock/trace.js b/mock/trace.js index 7c40205..2f9083c 100644 --- a/mock/trace.js +++ b/mock/trace.js @@ -68,9 +68,9 @@ export default { 'type|1': ['Local', 'Entry', 'Exit'], 'component|1': ['MySQL', 'H2', 'Spring'], peer: '@ip', - 'tags|1-5': [{ key: 'db.type', value: 'aa' }], + tags: [{ key: 'db.type', value: 'aa' }], 'logs|2-10': [{ 'time|+1': 1516151345000, - 'data|3-8': [ + data: [ { key: 'db.type', value: 'aa' }, { key: 'stack', value: 'java.lang.NullPointerException\nat com.a.eye.skywalking.test.cache.jedis.JedisServiceManager.findWithException(JedisServiceManager.java:52)\nat com.a.eye.skywalking.test.cache.CacheServiceImpl.findCacheWithException(CacheServiceImpl.java:49)\nat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\nat sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)\nat sun.reflect.DelegatingMethodAccessorImpl.invoke(Delegati [...] ] }], @@ -87,7 +87,7 @@ export default { 'type|1': ['Local', 'Entry', 'Exit'], 'component|1': ['MySQL', 'H2', 'Spring'], peer: '@ip', - 'tags|1-5': [{ key: 'db.type', value: 'aa' }], + tags: [{ key: 'db.type', value: 'aa' }], 'isError|1': true, }, { @@ -101,7 +101,7 @@ export default { 'type|1': ['Local', 'Entry', 'Exit'], 'component|1': ['MySQL', 'H2', 'Spring'], peer: '@ip', - 'tags|1-5': [{ key: 'db.type', value: 'aa' }], + tags: [{ key: 'db.type', value: 'aa' }], 'isError|1': true, }, { @@ -115,7 +115,7 @@ export default { 'type|1': ['Local', 'Entry', 'Exit'], 'component|1': ['MySQL', 'H2', 'Spring'], peer: '@ip', - 'tags|1-5': [{ key: 'db.type', value: 'aa' }], + tags: [{ key: 'db.type', value: 'aa' }], 'isError|1': true, }, { @@ -129,7 +129,7 @@ export default { 'type|1': ['Exit'], 'component|1': ['RockerMQ'], peer: '@ip', - 'tags|1-5': [{ key: 'producer', value: 'tt' }], + tags: [{ key: 'producer', value: 'tt' }], 'isError|1': true, }, { @@ -142,7 +142,7 @@ export default { 'type|1': ['Entry'], 'component|1': ['RockerMQ'], peer: '@ip', - 'tags|1-5': [{ key: 'consumer', value: 'tt' }], + tags: [{ key: 'consumer', value: 'tt' }], refs: [ { parentSpanId: 5, @@ -161,7 +161,7 @@ export default { 'type|1': ['Entry'], 'component|1': ['Kafka'], peer: '@ip', - 'tags|1-5': [{ key: 'consumer', value: 'tt' }], + tags: [{ key: 'consumer', value: 'tt' }], refs: [ { traceId: 121212, @@ -173,6 +173,11 @@ export default { }, ], 'isError|1': true, + 'logs|2-10': [{ 'time|+1': 1516151345000, + data: [ + { key: 'db.type', value: 'aa' }, + { key: 'stack', value: 'java.lang.NullPointerException\nat com.a.eye.skywalking.test.cache.jedis.JedisServiceManager.findWithException(JedisServiceManager.java:52)\nat com.a.eye.skywalking.test.cache.CacheServiceImpl.findCacheWithException(CacheServiceImpl.java:49)\nat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\nat sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)\nat sun.reflect.DelegatingMethodAccessorImpl.invoke(Delegati [...] + ] }], }, ], }, diff --git a/src/components/TraceStack/index.js b/src/components/TraceStack/index.js index 6d7c3f0..d65df8e 100644 --- a/src/components/TraceStack/index.js +++ b/src/components/TraceStack/index.js @@ -17,14 +17,13 @@ import React, { PureComponent } from 'react'; -import { Tag, List, Tabs, Card, Row, Col, Badge } from 'antd'; +import { Tag, List, Card, Row, Col, Badge, Button } from 'antd'; import * as d3 from 'd3'; import moment from 'moment'; import { formatDuration } from '../../utils/time'; import DescriptionList from '../../components/DescriptionList'; import styles from './index.less'; -const { TabPane } = Tabs; const { Description } = DescriptionList; const colors = [ @@ -46,8 +45,8 @@ class TraceStack extends PureComponent { idMap: {}, colorMap: {}, bap: [], - visible: false, span: {}, + key: 'tags', } componentWillMount() { const { spans } = this.props; @@ -71,6 +70,9 @@ class TraceStack extends PureComponent { this.displayData(); window.addEventListener('resize', this.resize); } + onTabChange = (key, type) => { + this.setState({ [type]: key }); + } buildNode = (span) => { const { nodes, idMap } = this.state; const node = {}; @@ -152,19 +154,20 @@ class TraceStack extends PureComponent { const beginY = index * height; positionMap[spanSegId] = { x: beginX, y: beginY }; const rectHeight = height - margin; + const position = { width, top: beginY, left: beginX }; const container = bar.append('rect').attr('spanSegId', spanSegId).attr('x', -5).attr('y', beginY - 5) .attr('width', width + 10) .attr('height', rectHeight + 10) .attr('class', styles.backgroudHide) .on('mouseover', () => { this.selectTimeline(container, true); }) .on('mouseout', () => { this.selectTimeline(container, false); }) - .on('click', () => { this.showSpanModal(node); }); + .on('click', () => { this.showSpanModal(node, position, container); }); bar.append('rect').attr('x', beginX).attr('y', beginY).attr('width', rectWith) .attr('height', rectHeight) .on('mouseover', () => { this.selectTimeline(container, true); }) .on('mouseout', () => { this.selectTimeline(container, false); }) - .on('click', () => { this.showSpanModal(node); }) + .on('click', () => { this.showSpanModal(node, position, container); }) .style('fill', colorMap[applicationCode]); bar.append('text') @@ -173,7 +176,7 @@ class TraceStack extends PureComponent { .attr('class', styles.rectText) .on('mouseover', () => { this.selectTimeline(container, true); }) .on('mouseout', () => { this.selectTimeline(container, false); }) - .on('click', () => { this.showSpanModal(node); }) + .on('click', () => { this.showSpanModal(node, position, container); }) .text(`${content} ${formatDuration(duration)}`); if (node.isError) { bar.append('svg:image') @@ -209,21 +212,21 @@ class TraceStack extends PureComponent { svgContainer.append('line').attr('x1', parentLeftBottomX - offX).attr('y1', parentLeftBottomY - offY).attr('class', styles.connlines) .on('mouseover', () => { this.selectTimeline(container, true); }) .on('mouseout', () => { this.selectTimeline(container, false); }) - .on('click', () => { this.showSpanModal(node); }) + .on('click', () => { this.showSpanModal(node, position, container); }) .attr('x2', parentLeftBottomX) .attr('y2', parentLeftBottomY - offY); svgContainer.append('line').attr('x1', parentLeftBottomX - offX).attr('y1', parentLeftBottomY - offY).attr('class', styles.connlines) .on('mouseover', () => { this.selectTimeline(container, true); }) .on('mouseout', () => { this.selectTimeline(container, false); }) - .on('click', () => { this.showSpanModal(node); }) + .on('click', () => { this.showSpanModal(node, position, container); }) .attr('x2', parentLeftBottomX - offX) .attr('y2', selfMiddleY); svgContainer.append('line').attr('x1', parentLeftBottomX - offX).attr('y1', selfMiddleY).attr('class', styles.connlines) .on('mouseover', () => { this.selectTimeline(container, true); }) .on('mouseout', () => { this.selectTimeline(container, false); }) - .on('click', () => { this.showSpanModal(node); }) + .on('click', () => { this.showSpanModal(node, position, container); }) .attr('x2', selfMiddleX - 5) .attr('y2', selfMiddleY) .attr('marker-end', 'url(#arrow)'); @@ -231,14 +234,14 @@ class TraceStack extends PureComponent { svgContainer.append('line').attr('x1', parentLeftBottomX).attr('y1', parentLeftBottomY).attr('class', styles.connlines) .on('mouseover', () => { this.selectTimeline(container, true); }) .on('mouseout', () => { this.selectTimeline(container, false); }) - .on('click', () => { this.showSpanModal(node); }) + .on('click', () => { this.showSpanModal(node, position, container); }) .attr('x2', parentLeftBottomX) .attr('y2', selfMiddleY); svgContainer.append('line').attr('x1', parentLeftBottomX).attr('y1', selfMiddleY).attr('class', styles.connlines) .on('mouseover', () => { this.selectTimeline(container, true); }) .on('mouseout', () => { this.selectTimeline(container, false); }) - .on('click', () => { this.showSpanModal(node); }) + .on('click', () => { this.showSpanModal(node, position, container); }) .attr('x2', selfMiddleX - 5) .attr('y2', selfMiddleY) .attr('marker-end', 'url(#arrow)'); @@ -247,13 +250,34 @@ class TraceStack extends PureComponent { }); } selectTimeline = (container, isOver) => { + if (this.state.container === container) { + return; + } container.attr('class', isOver ? styles.backgroud : styles.backgroudHide); } - showSpanModal = (span) => { + showSpanModal = (span, position, container) => { + const { container: old } = this.state; + if (old) { + old.attr('class', styles.backgroudHide); + } + container.attr('class', styles.backgroudSelected); this.setState({ ...this.state, - visible: true, span, + key: 'tags', + position, + container, + }); + } + hideSpanModal = () => { + const { container: old } = this.state; + if (old) { + old.attr('class', styles.backgroudHide); + } + this.setState({ + ...this.state, + span: {}, + container: undefined, }); } resize = () => { @@ -268,6 +292,7 @@ class TraceStack extends PureComponent { this.duration.innerHTML = ''; this.drawAxis(); this.displayData(); + this.setState({ ...this.state, span: {} }); } renderTitle = (items) => { return ( @@ -286,11 +311,16 @@ class TraceStack extends PureComponent { ); } render() { - const { colorMap, span = {} } = this.state; + const { colorMap, span = {}, position = { width: 100, top: 0 } } = this.state; const legendButtons = Object.keys(colorMap).map(key => (<Tag color={colorMap[key]} key={key}>{key}</Tag>)); - let data; + const tabList = []; + const contentList = {}; if (span.content) { + tabList.push({ + key: 'tags', + tab: 'Tags', + }); const base = [ { title: 'span type', @@ -309,10 +339,19 @@ class TraceStack extends PureComponent { content: `${span.isError}`, }, ]; - data = base.concat(span.tags.map(t => ({ title: t.key, content: t.value }))); + const data = base.concat(span.tags.map(t => ({ title: t.key, content: t.value }))); + contentList.tags = ( + <DescriptionList layout="vertical"> + {data.map(_ => + <Description key={_.title} term={_.title}>{_.content}</Description>)} + </DescriptionList>); } - const logs = span.logs ? ( - <TabPane tab="Logs" key={2}> + if (span.logs) { + tabList.push({ + key: 'logs', + tab: 'Logs', + }); + contentList.logs = ( <List itemLayout="horizontal" dataSource={span.logs} @@ -334,16 +373,34 @@ class TraceStack extends PureComponent { /> </List.Item> )} - /> - </TabPane> - ) : null; - const relatedTraces = (span.parentSpanSegId || !span.refs) ? null : ( - <TabPane tab="Related Trace" key={3}> + />); + } + if (!span.parentSpanSegId && span.refs) { + tabList.push({ + key: 'relatedTraces', + tab: 'Related Trace', + }); + contentList.relatedTraces = ( <DescriptionList layout="vertical"> {span.refs.map(_ => <Description key={_.type} term={_.type}>{_.traceId}</Description>)} - </DescriptionList> - </TabPane> - ); + </DescriptionList>); + } + const { top, left, width } = position; + + const toolTipStyle = { position: 'absolute', top: top + 86 }; + if (contentList.logs) { + toolTipStyle.left = 0; + toolTipStyle.width = width; + } else { + const right = width - left; + if (left * 2 > width) { + toolTipStyle.right = right; + toolTipStyle.maxWidth = left; + } else { + toolTipStyle.left = left; + toolTipStyle.maxWidth = right; + } + } return ( <div className={styles.stack}> <div style={{ paddingBottom: 10 }}> @@ -351,12 +408,10 @@ class TraceStack extends PureComponent { </div> <div className={styles.duration} ref={(el) => { this.duration = el; }} /> <div ref={(el) => { this.axis = el; }} /> - {data ? ( + {tabList.length > 0 ? ( <Card type="inner" - title={span.content} - > - {this.renderTitle([ + title={this.renderTitle([ { name: 'Start Time', count: `${moment(span.startTime).format(timeFormat)}`, @@ -366,16 +421,12 @@ class TraceStack extends PureComponent { count: `${formatDuration(span.duration)}`, }, ])} - <Tabs defaultActiveKey="1"> - <TabPane tab="Tags" key="1"> - <DescriptionList layout="vertical"> - {data.map(_ => - <Description key={_.title} term={_.title}>{_.content}</Description>)} - </DescriptionList> - </TabPane> - {logs} - {relatedTraces} - </Tabs> + tabList={tabList} + onTabChange={(key) => { this.onTabChange(key, 'key'); }} + style={toolTipStyle} + extra={<Button type="primary" shape="circle" icon="close" ghost onClick={this.hideSpanModal} />} + > + {contentList[this.state.key]} </Card> ) : null} </div> diff --git a/src/components/TraceStack/index.less b/src/components/TraceStack/index.less index e095f52..62e59ba 100644 --- a/src/components/TraceStack/index.less +++ b/src/components/TraceStack/index.less @@ -41,7 +41,7 @@ .rectText { font: 11px; font-weight: inherit; - fill: #fafafa; + fill: #797979; } .stack { @@ -61,6 +61,11 @@ opacity: 0; } +.backgroudSelected { + opacity: 0.2; + fill: #faad14; +} + .backgroud { opacity: 0.2; fill: #1890ff; -- To stop receiving notification emails like this one, please contact hanahm...@apache.org.