| /** |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| import React, { PureComponent } from 'react'; |
| import { Form, Input, Select, Button, Card, InputNumber, Row, Col, Pagination, DatePicker } from 'antd'; |
| import { Chart, Geom, Axis, Tooltip, Legend } from 'bizcharts'; |
| import { DataSet } from '@antv/data-set'; |
| import moment from 'moment'; |
| import TraceList from '../../components/Trace/TraceList'; |
| import { generateDuration } from '../../utils/time'; |
| import styles from './Trace.less'; |
| |
| const { Option } = Select; |
| const { RangePicker } = DatePicker; |
| const FormItem = Form.Item; |
| const initPaging = { |
| pageNum: 1, |
| pageSize: 20, |
| needTotal: true, |
| }; |
| |
| @Form.create({ |
| mapPropsToFields(props) { |
| const { variables: { values } } = props.trace; |
| const result = {}; |
| Object.keys(values).filter(_ => _ !== 'range-time-picker').forEach((_) => { |
| result[_] = Form.createFormField({ |
| value: values[_], |
| }); |
| }); |
| const { duration } = values; |
| if (duration) { |
| result['range-time-picker'] = Form.createFormField({ |
| value: [duration.raw.start, duration.raw.end], |
| }); |
| } |
| return result; |
| }, |
| }) |
| export default class Trace extends PureComponent { |
| componentDidMount() { |
| const {...propsData} = this.props; |
| const { trace: { variables: { values } } } = this.props; |
| const { duration } = values; |
| propsData.dispatch({ |
| type: 'trace/initOptions', |
| payload: { variables: { duration: duration.input } }, |
| }); |
| const condition = { ...values }; |
| condition.queryDuration = values.duration.input; |
| delete condition.duration; |
| this.fetchData(condition, initPaging); |
| } |
| |
| getDefaultDuration = () => { |
| return generateDuration({ |
| from() { |
| return moment().subtract(15, 'minutes'); |
| }, |
| to() { |
| return moment(); |
| }, |
| }); |
| } |
| |
| handleSearch = (e) => { |
| if (e) { |
| e.preventDefault(); |
| } |
| const { form, dispatch } = this.props; |
| |
| form.validateFields((err, fieldsValue) => { |
| if (err) return; |
| const condition = { ...fieldsValue }; |
| delete condition['range-time-picker']; |
| const rangeTime = fieldsValue['range-time-picker']; |
| const duration = generateDuration({ from: () => rangeTime[0], to: () => rangeTime[1] }); |
| dispatch({ |
| type: 'trace/saveVariables', |
| payload: { |
| values: { |
| ...condition, |
| duration, |
| paging: initPaging, |
| }, |
| }, |
| }); |
| this.fetchData({ ...condition, queryDuration: duration.input }); |
| }); |
| } |
| |
| fetchData = (queryCondition, paging = initPaging) => { |
| const {...propsData} = this.props; |
| propsData.dispatch({ |
| type: 'trace/fetchData', |
| payload: { |
| variables: { |
| condition: { |
| ...queryCondition, |
| paging, |
| }, |
| }, |
| }, |
| }); |
| } |
| |
| handleTableChange = (pagination) => { |
| const { dispatch, trace: { variables: { values } } } = this.props; |
| const condition = { |
| ...values, |
| paging: { |
| pageNum: pagination.current, |
| pageSize: pagination.pageSize, |
| needTotal: true, |
| }, |
| }; |
| dispatch({ |
| type: 'trace/saveVariables', |
| payload: { |
| values: { |
| ...condition, |
| }, |
| }, |
| }); |
| delete condition.duration; |
| this.fetchData({ ...condition, queryDuration: values.duration.input }, condition.paging); |
| } |
| |
| handleShowTrace = (traceId) => { |
| const { dispatch } = this.props; |
| dispatch({ |
| type: 'trace/fetchSpans', |
| payload: { variables: { traceId } }, |
| }); |
| } |
| |
| renderPointChart = (traces) => { |
| if (!traces) { |
| return null; |
| } |
| const ds = new DataSet(); |
| const dv = ds.createView().source(traces); |
| dv.transform({ |
| type: 'map', |
| callback(row) { |
| return { |
| ...row, |
| state: row.isError ? 'error' : 'success', |
| startTime: moment(parseInt(row.start, 10)).format('YYYY-MM-DD HH:mm:ss.SSS'), |
| }; |
| }, |
| }); |
| return ( |
| <Chart |
| data={dv} |
| height={680} |
| forceFit |
| scale={{ |
| startTime: { |
| tickCount: 4, |
| }, |
| }} |
| > |
| <Tooltip |
| showTitle={false} |
| crosshairs={{ type: 'cross' }} |
| itemTpl='<li data-index={index} style="margin-bottom:4px;"><span style="background-color:{color};" class="g2-tooltip-marker"></span>{name}<br/>{value}</li>' |
| /> |
| <Axis name="startTime" /> |
| <Axis |
| name="duration" |
| label={{ |
| formatter: (text) => { |
| if (parseInt(text, 10) >= 1000) { |
| return `${parseInt(text, 10) / 1000} s`; |
| } |
| return `${text} ms`; |
| }, |
| }} |
| /> |
| <Legend /> |
| <Geom |
| type="point" |
| position="startTime*duration" |
| color={['state', state => (state === 'error' ? 'red' : '#1890ff')]} |
| opacity={0.65} |
| shape="circle" |
| size={4} |
| tooltip={['endpointName*startTime*duration', (endpointName, startTime, duration) => { |
| return { |
| name: endpointName, |
| value: ` |
| ${startTime} |
| ${duration}ms |
| `, |
| }; |
| }]} |
| /> |
| </Chart>); |
| } |
| |
| renderForm() { |
| const {...propsData} = this.props; |
| const { getFieldDecorator } = propsData.form; |
| const { trace: { variables: { options } }, zone } = this.props; |
| return ( |
| <Form onSubmit={this.handleSearch} layout="vertical"> |
| <FormItem label={`Time Range(${zone})`}> |
| {getFieldDecorator('range-time-picker', { |
| rules: [{ |
| required: true, |
| message: 'Please select the correct date', |
| }], |
| })( |
| <RangePicker |
| showTime |
| disabledDate={current => current && current.valueOf() >= Date.now()} |
| format="YYYY-MM-DD HH:mm" |
| style={{ width: '100%' }} |
| /> |
| )} |
| </FormItem> |
| <FormItem label="Service"> |
| {getFieldDecorator('serviceId')( |
| <Select placeholder="All service" style={{ width: '100%' }}> |
| {options.serviceId && options.serviceId.map((service) => { |
| return ( |
| <Option key={service.key ? service.key : -1} value={service.key}> |
| {service.label} |
| </Option>); |
| })} |
| </Select> |
| )} |
| </FormItem> |
| <FormItem label="Trace State"> |
| {getFieldDecorator('traceState')( |
| <Select placeholder="All" style={{ width: '100%' }}> |
| <Option key="success" value="SUCCESS">Success</Option> |
| <Option key="error" value="ERROR">Error</Option> |
| <Option key="all" value="ALL">All</Option> |
| </Select> |
| )} |
| </FormItem> |
| <FormItem label="Order By"> |
| {getFieldDecorator('queryOrder')( |
| <Select placeholder="Start Time" style={{ width: '100%' }}> |
| <Option key="BY_START_TIME" value="BY_START_TIME">Start Time</Option> |
| <Option key="BY_DURATION" value="BY_DURATION">Duration</Option> |
| </Select> |
| )} |
| </FormItem> |
| <FormItem label="EndpointName"> |
| {getFieldDecorator('endpointName')( |
| <Input placeholder="eg Kafka/Trace-topic-1/Consumer" /> |
| )} |
| </FormItem> |
| <FormItem label="TraceId"> |
| {getFieldDecorator('traceId')( |
| <Input placeholder="eg 3.84.15204769998380001" /> |
| )} |
| </FormItem> |
| <Row> |
| <Col xs={24} sm={24} md={24} lg={12} xl={12}> |
| <FormItem label="Min Duration"> |
| {getFieldDecorator('minTraceDuration')( |
| <InputNumber placeholder="eg 100" /> |
| )} |
| </FormItem> |
| </Col> |
| <Col xs={24} sm={24} md={24} lg={12} xl={12}> |
| <FormItem label="Max Duration"> |
| {getFieldDecorator('maxTraceDuration')( |
| <InputNumber placeholder="eg 5000" /> |
| )} |
| </FormItem> |
| </Col> |
| </Row> |
| <FormItem> |
| <Button type="primary" htmlType="submit">Search</Button> |
| </FormItem> |
| </Form> |
| ); |
| } |
| |
| renderPage = (values, total) => { |
| if (total < 1) { |
| return null; |
| } |
| let currentPageNum = 1; |
| let currentPageSize = 20; |
| if (values.paging) { |
| const { paging: { pageNum, pageSize } } = values; |
| currentPageNum = pageNum; |
| currentPageSize = pageSize; |
| } |
| return ( |
| <Row type="flex" justify="end"> |
| <Col> |
| <Pagination |
| size="small" |
| current={currentPageNum} |
| pageSize={currentPageSize} |
| total={total} |
| defaultPageSize={20} |
| showSizeChanger |
| pageSizeOptions={['20', '50', '100', '200']} |
| onChange={(page, pageSize) => { |
| this.handleTableChange({ current: page, pageSize }); |
| }} |
| onShowSizeChange={(current, size) => { |
| this.handleTableChange({ current: 1, pageSize: size }); |
| }} |
| /> |
| </Col> |
| </Row>); |
| } |
| |
| render() { |
| const { trace: { variables: { values }, data: { queryBasicTraces } }, loading } = this.props; |
| return ( |
| <Card bordered={false}> |
| <div className={styles.tableList}> |
| <Row> |
| <Col xs={24} sm={24} md={24} lg={8} xl={8}> |
| {this.renderForm()} |
| </Col> |
| <Col xs={24} sm={24} md={24} lg={16} xl={16}> |
| {this.renderPointChart(queryBasicTraces.traces)} |
| </Col> |
| </Row> |
| {this.renderPage(values, queryBasicTraces.total)} |
| <TraceList |
| loading={loading} |
| data={queryBasicTraces.traces} |
| onClickTraceTag={this.handleShowTrace} |
| /> |
| {this.renderPage(values, queryBasicTraces.total)} |
| </div> |
| </Card> |
| ); |
| } |
| } |