This is an automated email from the ASF dual-hosted git repository. hanahmily pushed a commit to branch 6.0.0/dev in repository https://gitbox.apache.org/repos/asf/incubator-skywalking-ui.git
commit 2fab03fff22ec23f78994ca16ade1cf6c827d875 Author: Gao Hongtao <[email protected]> AuthorDate: Mon Sep 10 14:18:06 2018 +0800 Refactor endpoint page with v6 protocol --- .roadhogrc.mock.js | 10 +- mock/metadata.js | 10 +- mock/metric.js | 8 ++ mock/topology.js | 49 ++++++++ src/common/menu.js | 4 +- src/common/router.js | 4 +- src/models/{service.js => endpoint.js} | 100 ++++++++-------- .../{Service/Service.js => Endpoint/Endpoint.js} | 128 ++++++++++----------- src/utils/time.js | 5 + src/utils/utils.js | 7 ++ 10 files changed, 199 insertions(+), 126 deletions(-) diff --git a/.roadhogrc.mock.js b/.roadhogrc.mock.js index 9fa1e25..1021e7f 100644 --- a/.roadhogrc.mock.js +++ b/.roadhogrc.mock.js @@ -1,7 +1,7 @@ import mockjs from 'mockjs'; import fs from 'fs'; import { delay } from 'roadhog-api-doc'; -import { getTopology } from './mock/topology'; +import { getTopology, getServiceTopology } from './mock/topology'; import { getAllApplication, getApplication } from './mock/application'; import { searchServer, getServer } from './mock/server'; import { searchService, getService } from './mock/service'; @@ -9,8 +9,8 @@ import { Alarms, getNoticeAlarm, AlarmTrend } from './mock/alarm'; import { TraceBrief, Trace } from './mock/trace' import { makeExecutableSchema, addMockFunctionsToSchema } from 'graphql-tools'; import { graphql } from 'graphql'; -import { ClusterBrief, getAllServices } from './mock/metadata'; -import { Thermodynamic } from './mock/metric'; +import { ClusterBrief, getAllServices, searchEndpoint } from './mock/metadata'; +import { IntValues, Thermodynamic } from './mock/metric'; import { getTopN } from './mock/aggregation'; const noMock = process.env.NO_MOCK === 'true'; @@ -19,6 +19,8 @@ const resolvers = { Query: { getTopN, getAllServices, + getServiceTopology, + searchEndpoint, } } @@ -30,6 +32,7 @@ const schema = makeExecutableSchema({ typeDefs: [ fs.readFileSync('query-protocol/metric.graphqls', 'utf8'), fs.readFileSync('query-protocol/aggregation.graphqls', 'utf8'), fs.readFileSync('query-protocol/trace.graphqls', 'utf8'), + fs.readFileSync('query-protocol/topology.graphqls', 'utf8'), ], resolvers }); addMockFunctionsToSchema({ @@ -42,6 +45,7 @@ addMockFunctionsToSchema({ Alarms, TraceBrief, Trace, + IntValues, }, preserveResolvers: true }); diff --git a/mock/metadata.js b/mock/metadata.js index 351f7f6..12fe2c3 100644 --- a/mock/metadata.js +++ b/mock/metadata.js @@ -28,8 +28,14 @@ export default { , getAllServices: () => { const data = mockjs.mock({ - 'serviceId|20-50': [{ 'id|+1': 3, name: function() { return `app-${this.id}`; } }], // eslint-disable-line + 'serviceId|20-50': [{ 'id|+1': 3, name: function() { return `service-${this.id}`; } }], // eslint-disable-line }); return data.serviceId; - } + }, + searchEndpoint: () => { + const data = mockjs.mock({ + 'endpointId|20-50': [{ 'id|+1': 3, name: function() { return `endpoint-${this.id}`; } }], // eslint-disable-line + }); + return data.endpointId; + }, }; diff --git a/mock/metric.js b/mock/metric.js index 673cc63..410d006 100644 --- a/mock/metric.js +++ b/mock/metric.js @@ -30,4 +30,12 @@ export default { }, responseTimeStep: 50, }), + IntValues: () => (mockjs.mock( + { + 'values|60': [{ + 'id|+1': 1, + value: '@natural(0, 1000)', + }], + } + )), }; diff --git a/mock/topology.js b/mock/topology.js index e0b816b..5480589 100644 --- a/mock/topology.js +++ b/mock/topology.js @@ -19,6 +19,55 @@ import mockjs from 'mockjs'; export default { + getServiceTopology: () => { + const upNodes = mockjs.mock({ + 'nodes|1-5': [ + { + 'id|+1': 100, + name: '@url', + 'type|1': ['DUBBO', 'USER', 'SPRINGMVC'], + }, + ], + }); + const centerNodes = mockjs.mock({ + nodes: [ + { + 'id|+1': 1, + name: '@url', + 'type|1': ['DUBBO', 'tomcat', 'SPRINGMVC'], + }, + ], + }); + const downNodes = mockjs.mock({ + 'nodes|2-5': [ + { + 'id|+1': 200, + name: '@url', + 'type|1': ['Oracle', 'MYSQL', 'REDIS'], + }, + ], + }); + downNodes.nodes.push({ id: -111 }); + const nodes = upNodes.nodes.concat(centerNodes.nodes, downNodes.nodes); + const calls = upNodes.nodes.map(node => (mockjs.mock({ + source: node.id, + target: 1, + 'isAlert|1': true, + 'callType|1': ['rpc', 'http', 'dubbo'], + 'cpm|0-1000': 1, + }))).concat(downNodes.nodes.map(node => (mockjs.mock({ + source: 1, + target: node.id, + 'isAlert|1': true, + 'callType|1': ['rpc', 'http', 'dubbo'], + 'cpm|0-2000': 1, + })))); + calls.push({ source: '-175', target: 1, isAlert: false, callType: 'GRPC', cpm: 0, avgResponseTime: 52 }); + return { + nodes, + calls, + }; + }, getTopology(req, res) { res.json(mockjs.mock( { diff --git a/src/common/menu.js b/src/common/menu.js index cbf1815..bc98328 100644 --- a/src/common/menu.js +++ b/src/common/menu.js @@ -34,8 +34,8 @@ const menuData = [{ name: 'Application', path: 'application', }, { - name: 'Service', - path: 'service', + name: 'Endpoint', + path: 'endpoint', }, { name: 'Alarm', path: 'alarm', diff --git a/src/common/router.js b/src/common/router.js index 00e3934..db30edd 100644 --- a/src/common/router.js +++ b/src/common/router.js @@ -100,8 +100,8 @@ export const getRouterData = (app) => { '/monitor/application': { component: dynamicWrapper(app, ['application'], () => import('../routes/Application/Application')), }, - '/monitor/service': { - component: dynamicWrapper(app, ['service'], () => import('../routes/Service/Service')), + '/monitor/endpoint': { + component: dynamicWrapper(app, ['endpoint'], () => import('../routes/Endpoint/Endpoint')), }, '/trace': { component: dynamicWrapper(app, ['trace'], () => import('../routes/Trace/Trace')), diff --git a/src/models/service.js b/src/models/endpoint.js similarity index 64% rename from src/models/service.js rename to src/models/endpoint.js index bd873ec..662546a 100644 --- a/src/models/service.js +++ b/src/models/endpoint.js @@ -16,12 +16,12 @@ */ -import { generateModal, saveOptionsInState } from '../utils/models'; -import { query } from '../services/graphql'; +import { base, saveOptionsInState } from '../utils/models'; +import { exec } from '../services/graphql'; const optionsQuery = ` - query ApplicationOption($duration: Duration!) { - applicationId: getAllApplication(duration: $duration) { + query ServiceOption($duration: Duration!) { + serviceId: getAllServices(duration: $duration) { key: id label: name } @@ -29,15 +29,30 @@ const optionsQuery = ` `; const dataQuery = ` - query Service($serviceId: ID!, $duration: Duration!, $traceCondition: TraceQueryCondition!) { - getServiceResponseTimeTrend(serviceId: $serviceId, duration: $duration) { - trendList + query Endpoint($endpointId: ID!, $duration: Duration!, $traceCondition: TraceQueryCondition!) { + getEndpointResponseTimeTrend: getLinearIntValues(metric: { + name: "endpointResponseTimeTrend" + id: $endpointId + }, duration: $duration) { + values { + value + } } - getServiceThroughputTrend(serviceId: $serviceId, duration: $duration) { - trendList + getEndpointThroughputTrend: getLinearIntValues(metric: { + name: "endpointResponseTimeTrend" + id: $endpointId + }, duration: $duration) { + values { + value + } } - getServiceSLATrend(serviceId: $serviceId, duration: $duration) { - trendList + getEndpointSLATrend: getLinearIntValues(metric: { + name: "endpointResponseTimeTrend" + id: $endpointId + }, duration: $duration) { + values { + value + } } queryBasicTraces(condition: $traceCondition) { traces { @@ -50,26 +65,6 @@ const dataQuery = ` } total } - getServiceTopology(serviceId: $serviceId, duration: $duration) { - nodes { - id - name - type - ... on ServiceNode { - sla - calls - numOfServiceAlarm - } - } - calls { - source - target - isAlert - callType - cpm - avgResponseTime - } - } } `; @@ -111,19 +106,19 @@ const spanQuery = `query Spans($traceId: ID!) { } }`; -export default generateModal({ - namespace: 'service', +export default base({ + namespace: 'endpoint', state: { - getServiceResponseTimeTrend: { - trendList: [], + getEndpointResponseTimeTrend: { + values: [], }, - getServiceThroughputTrend: { - trendList: [], + getEndpointThroughputTrend: { + values: [], }, - getServiceSLATrend: { - trendList: [], + getEndpointSLATrend: { + values: [], }, - getServiceTopology: { + getEndpointTopology: { nodes: [], calls: [], }, @@ -136,7 +131,7 @@ export default generateModal({ optionsQuery, effects: { *fetchSpans({ payload }, { call, put }) { - const response = yield call(query, 'spans', { query: spanQuery, variables: payload.variables }); + const response = yield call(exec, { query: spanQuery, variables: payload.variables }); yield put({ type: 'saveSpans', payload: response, @@ -157,21 +152,21 @@ export default generateModal({ }, }; }, - saveAppInfo(preState, { payload: allOptions }) { + saveServiceInfo(preState, { payload: allOptions }) { const rawState = saveOptionsInState(null, preState, { payload: allOptions }); const { data } = rawState; - if (data.appInfo) { + if (data.serviceInfo) { return rawState; } const { variables: { values } } = rawState; - if (!values.applicationId) { + if (!values.serviceId) { return rawState; } return { ...rawState, data: { ...data, - appInfo: { applicationId: values.applicationId }, + serviceInfo: { serviceId: values.serviceId }, }, }; }, @@ -189,26 +184,25 @@ export default generateModal({ subscriptions: { setup({ history, dispatch }) { return history.listen(({ pathname, state }) => { - if (pathname === '/monitor/service' && state) { - console.info(state); + if (pathname === '/monitor/endpoint' && state) { dispatch({ type: 'saveVariables', payload: { values: { - serviceId: state.key, - applicationId: state.applicationId, + endpointId: state.key, + serviceId: state.serviceId, }, labels: { - serviceId: state.label, - applicationId: state.applicationName, + endpointId: state.label, + serviceId: state.serviceName, }, }, }); dispatch({ type: 'saveData', payload: { - appInfo: { applicationId: state.applicationId }, - serviceInfo: { key: state.key, label: state.label }, + serviceInfo: { serviceId: state.serviceId }, + endpointInfo: { key: state.key, label: state.label }, }, }); } diff --git a/src/routes/Service/Service.js b/src/routes/Endpoint/Endpoint.js similarity index 67% rename from src/routes/Service/Service.js rename to src/routes/Endpoint/Endpoint.js index 62e467f..d6add80 100644 --- a/src/routes/Service/Service.js +++ b/src/routes/Endpoint/Endpoint.js @@ -21,9 +21,9 @@ import { connect } from 'dva'; import { Row, Col, Form, Button, Icon, Select } from 'antd'; import { ChartCard, MiniArea, MiniBar, Sankey, -} from '../../components/Charts'; -import { axis } from '../../utils/time'; -import { avgTimeSeries } from '../../utils/utils'; +} from 'components/Charts'; +import { axisY } from '../../utils/time'; +import { avgTS } from '../../utils/utils'; import { Panel, Search } from '../../components/Page'; import TraceList from '../../components/Trace/TraceList'; import TraceTimeline from '../Trace/TraceTimeline'; @@ -32,29 +32,29 @@ const { Item: FormItem } = Form; const { Option } = Select; @connect(state => ({ - service: state.service, + endpoint: state.endpoint, duration: state.global.duration, globalVariables: state.global.globalVariables, - loading: state.loading.models.service, + loading: state.loading.models.endpoint, })) @Form.create({ mapPropsToFields(props) { - const { variables: { values, labels } } = props.service; + const { variables: { values, labels } } = props.endpoint; return { - applicationId: Form.createFormField({ - value: { key: values.applicationId ? values.applicationId : '', label: labels.applicationId ? labels.applicationId : '' }, - }), serviceId: Form.createFormField({ value: { key: values.serviceId ? values.serviceId : '', label: labels.serviceId ? labels.serviceId : '' }, }), + endpointId: Form.createFormField({ + value: { key: values.endpointId ? values.endpointId : '', label: labels.endpointId ? labels.endpointId : '' }, + }), }; }, }) -export default class Service extends PureComponent { +export default class Endpoint extends PureComponent { componentDidMount() { this.props.dispatch({ - type: 'service/initOptions', - payload: { variables: this.props.globalVariables, reducer: 'saveAppInfo' }, + type: 'endpoint/initOptions', + payload: { variables: this.props.globalVariables, reducer: 'saveServiceInfo' }, }); } @@ -63,21 +63,21 @@ export default class Service extends PureComponent { return; } this.props.dispatch({ - type: 'service/initOptions', - payload: { variables: nextProps.globalVariables, reducer: 'saveAppInfo' }, + type: 'endpoint/initOptions', + payload: { variables: nextProps.globalVariables, reducer: 'saveServiceInfo' }, }); } - handleAppSelect = (selected) => { + handleServiceSelect = (selected) => { this.props.dispatch({ - type: 'service/save', + type: 'endpoint/save', payload: { variables: { - values: { applicationId: selected.key, serviceId: null }, - labels: { applicationId: selected.label, serviceId: null }, + values: { serviceId: selected.key, endpointId: null }, + labels: { serviceId: selected.label, endpointId: null }, }, data: { - appInfo: { applicationId: selected.key }, + serviceInfo: { serviceId: selected.key }, }, }, }); @@ -85,36 +85,36 @@ export default class Service extends PureComponent { handleSelect = (selected) => { this.props.dispatch({ - type: 'service/save', + type: 'endpoint/save', payload: { variables: { - values: { serviceId: selected.key }, - labels: { serviceId: selected.label }, + values: { endpointId: selected.key }, + labels: { endpointId: selected.label }, }, data: { - serviceInfo: selected, + endpointInfo: selected, }, }, }); } handleChange = (variables) => { - const { variables: { values } } = this.props.service; - if (!values.applicationId) { + const { variables: { values } } = this.props.endpoint; + if (!values.serviceId) { return; } - const { key: serviceId, label: serviceName, duration } = variables; - if (!serviceId) { + const { key: endpointId, label: endpointName, duration } = variables; + if (!endpointId) { return; } this.props.dispatch({ - type: 'service/fetchData', + type: 'endpoint/fetchData', payload: { variables: { - serviceId, + endpointId, duration, traceCondition: { - applicationId: values.applicationId, - operationName: serviceName, + endpointId: parseInt(values.endpointId, 10), + operationName: endpointName, queryDuration: duration, traceState: 'ALL', queryOrder: 'BY_DURATION', @@ -131,30 +131,30 @@ export default class Service extends PureComponent { handleShowTrace = (traceId) => { const { dispatch } = this.props; dispatch({ - type: 'service/fetchSpans', + type: 'endpoint/fetchSpans', payload: { variables: { traceId } }, }); } handleGoBack = () => { this.props.dispatch({ - type: 'service/hideTimeline', + type: 'endpoint/hideTimeline', }); } - edgeWith = edge => edge.cpm * edge.avgResponseTime; + edgeWith = edge => edge.cpm; renderPanel = () => { - const { service, duration } = this.props; - const { variables: { values }, data } = service; - const { getServiceResponseTimeTrend, getServiceThroughputTrend, - getServiceSLATrend, getServiceTopology, queryBasicTraces } = data; - if (!values.serviceId) { + const { endpoint, duration } = this.props; + const { variables: { values }, data } = endpoint; + const { getEndpointResponseTimeTrend, getEndpointThroughputTrend, + getEndpointSLATrend, getEndpointTopology, queryBasicTraces } = data; + if (!values.endpointId) { return null; } return ( <Panel - variables={data.serviceInfo} + variables={data.endpointInfo} globalVariables={this.props.globalVariables} onChange={this.handleChange} > @@ -162,35 +162,35 @@ export default class Service extends PureComponent { <Col xs={24} sm={24} md={24} lg={8} xl={8} style={{ marginTop: 8 }}> <ChartCard title="Avg Throughput" - total={`${avgTimeSeries(getServiceThroughputTrend.trendList)} cpm`} + total={`${avgTS(getEndpointThroughputTrend.values)} cpm`} contentHeight={46} > <MiniArea color="#975FE4" - data={axis(duration, getServiceThroughputTrend.trendList)} + data={axisY(duration, getEndpointThroughputTrend.values)} /> </ChartCard> </Col> <Col xs={24} sm={24} md={24} lg={8} xl={8} style={{ marginTop: 8 }}> <ChartCard title="Avg Response Time" - total={`${avgTimeSeries(getServiceResponseTimeTrend.trendList)} ms`} + total={`${avgTS(getEndpointResponseTimeTrend.values)} ms`} contentHeight={46} > <MiniArea - data={axis(duration, getServiceResponseTimeTrend.trendList)} + data={axisY(duration, getEndpointResponseTimeTrend.values)} /> </ChartCard> </Col> <Col xs={24} sm={24} md={24} lg={8} xl={8} style={{ marginTop: 8 }}> <ChartCard title="Avg SLA" - total={`${(avgTimeSeries(getServiceSLATrend.trendList) / 100).toFixed(2)} %`} + total={`${(avgTS(getEndpointSLATrend.values) / 100).toFixed(2)} %`} > <MiniBar animate={false} height={46} - data={axis(duration, getServiceSLATrend.trendList, + data={axisY(duration, getEndpointSLATrend.values, ({ x, y }) => ({ x, y: y / 100 }))} /> </ChartCard> @@ -209,7 +209,7 @@ export default class Service extends PureComponent { </ChartCard> </Col> </Row> - {this.renderSankey(getServiceTopology)} + {this.renderSankey(getEndpointTopology)} </Panel> ); } @@ -238,13 +238,13 @@ export default class Service extends PureComponent { > <Sankey data={nData} - edgeTooltip={['target*source*cpm*avgResponseTime*isAlert', (target, source, cpm, avgResponseTime) => { + edgeTooltip={['target*source*cpm', (target, source, cpm) => { return { name: `${source.name} to ${target.name} </span>`, - value: `${cpm < 1 ? '<1' : cpm} cpm ${avgResponseTime}ms`, + value: `${cpm} cpm`, }; }]} - edgeColor={['isAlert', isAlert => (isAlert ? '#DC143C' : '#bbb')]} + edgeColor="#bbb" /> </ChartCard> </Col> @@ -252,9 +252,9 @@ export default class Service extends PureComponent { } render() { - const { form, service } = this.props; + const { form, endpoint } = this.props; const { getFieldDecorator } = form; - const { variables: { options }, data } = service; + const { variables: { options }, data } = endpoint; const { showTimeline, queryTrace, currentTraceId } = data; return ( <div> @@ -271,31 +271,31 @@ export default class Service extends PureComponent { <Col span={showTimeline ? 0 : 24}> <Form layout="inline"> <FormItem> - {getFieldDecorator('applicationId')( + {getFieldDecorator('serviceId')( <Select showSearch optionFilterProp="children" style={{ width: 200 }} - placeholder="Select a application" + placeholder="Select a service" labelInValue - onSelect={this.handleAppSelect.bind(this)} + onSelect={this.handleServiceSelect.bind(this)} > - {options.applicationId && options.applicationId.map(app => - <Option key={app.key} value={app.key}>{app.label}</Option>)} + {options.serviceId && options.serviceId.map(service => + <Option key={service.key} value={service.key}>{service.label}</Option>)} </Select> )} </FormItem> - {data.appInfo ? ( + {data.serviceInfo ? ( <FormItem> - {getFieldDecorator('serviceId')( + {getFieldDecorator('endpointId')( <Search - placeholder="Search a service" + placeholder="Search a endpoint" onSelect={this.handleSelect.bind(this)} - url="/service/search" - variables={data.appInfo} + url="/graphql" + variables={data.serviceInfo} query={` - query SearchService($applicationId: ID!, $keyword: String!) { - searchService(applicationId: $applicationId, keyword: $keyword, topN: 10) { + query SearchEndpoint($serviceId: ID!, $keyword: String!) { + searchEndpoint(serviceId: $serviceId, keyword: $keyword, limit: 10) { key: id label: name } diff --git a/src/utils/time.js b/src/utils/time.js index e2476ca..b4e7301 100644 --- a/src/utils/time.js +++ b/src/utils/time.js @@ -22,6 +22,11 @@ export function axis({ display }, data, tranformFunc) { (tranformFunc ? tranformFunc({ x: v, y: data[i] }) : { x: v, y: data[i] })); } +export function axisY({ display }, data, tranformFunc) { + return display.range.map((v, i) => + (tranformFunc ? tranformFunc({ x: v, y: data[i] ? data[i].value : null }) : { x: v, y: data[i] ? data[i].value : null })); +} + export function generateDuration({ from, to }) { const start = from(); const end = to(); diff --git a/src/utils/utils.js b/src/utils/utils.js index 2969ebf..d6c4e3b 100644 --- a/src/utils/utils.js +++ b/src/utils/utils.js @@ -27,6 +27,13 @@ export function avgTimeSeries(list) { (acc, curr) => acc + curr) / filteredList.length).toFixed(2)) : 0; } +export function avgTS(list) { + const filteredList = list.map(_ => _.value).filter(_ => _ > 0); + return filteredList.length > 0 ? + parseFloat((filteredList.reduce( + (acc, curr) => acc + curr) / filteredList.length).toFixed(2)) : 0; +} + export function getPlainNode(nodeList, parentPath = '') { const arr = []; nodeList.forEach((node) => {
