You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by wu...@apache.org on 2019/01/31 07:56:10 UTC

[incubator-skywalking-ui] branch master updated: Update: update database feature. (#225)

This is an automated email from the ASF dual-hosted git repository.

wusheng pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-skywalking-ui.git


The following commit(s) were added to refs/heads/master by this push:
     new d5e46c0  Update: update database feature. (#225)
d5e46c0 is described below

commit d5e46c075cb30e3de42aeaaa9e839a758d3fc029
Author: Allen Wang <Al...@outlook.com>
AuthorDate: Thu Jan 31 15:56:05 2019 +0800

    Update: update database feature. (#225)
    
    * Update: update database feature.
    
    1. update the database page.
    2. update the database mock.
    3. update the database router.
    4. update the database query.
    5. update the service and databse select width.
    
    * Update: add database type.
---
 .roadhogrc.mock.js                                 |   3 +
 mock/database.js                                   |  27 ++++
 query-protocol                                     |   2 +-
 src/common/menu.js                                 |   3 +
 src/common/router.js                               |   3 +
 src/components/Database/DatabaseChartArea/index.js |  39 +++++
 src/components/Database/DatabaseChartBar/index.js  |  40 +++++
 src/components/Database/DatabaseChartLine/index.js |  38 +++++
 src/components/Database/index.js                   |  27 ++++
 src/models/database.js                             | 178 +++++++++++++++++++++
 src/routes/Database/Database.js                    | 142 ++++++++++++++++
 src/routes/Endpoint/Endpoint.js                    |   2 +-
 src/routes/Service/Service.js                      |   2 +-
 13 files changed, 503 insertions(+), 3 deletions(-)

diff --git a/.roadhogrc.mock.js b/.roadhogrc.mock.js
index e8f2052..f5c8d91 100644
--- a/.roadhogrc.mock.js
+++ b/.roadhogrc.mock.js
@@ -3,6 +3,7 @@ import { delay } from 'roadhog-api-doc';
 import { getGlobalTopology, getServiceTopology, getEndpointTopology } from './mock/topology';
 import { Alarms, AlarmTrend } from './mock/alarm';
 import { TraceBrief, Trace } from './mock/trace'
+import { getAllDatabases } from './mock/database'
 import { makeExecutableSchema, addMockFunctionsToSchema } from 'graphql-tools';
 import { graphql } from 'graphql';
 import { ClusterBrief, getServiceInstances, getAllServices, searchEndpoint, EndpointInfo } from './mock/metadata';
@@ -14,6 +15,7 @@ const noMock = process.env.NO_MOCK === 'true';
 const resolvers = {
   Query: {
     getAllServices,
+    getAllDatabases,
     getServiceInstances,
     getServiceTopN,
     getAllEndpointTopN,
@@ -30,6 +32,7 @@ const schema = makeExecutableSchema({ typeDefs: [
   "scalar Long",
   fs.readFileSync('query-protocol/common.graphqls', 'utf8'),
   fs.readFileSync('query-protocol/metadata.graphqls', 'utf8'),
+  fs.readFileSync('query-protocol/database.graphqls', 'utf8'),
   fs.readFileSync('query-protocol/alarm.graphqls', 'utf8'),
   fs.readFileSync('query-protocol/metric.graphqls', 'utf8'),
   fs.readFileSync('query-protocol/aggregation.graphqls', 'utf8'),
diff --git a/mock/database.js b/mock/database.js
new file mode 100644
index 0000000..dd0f550
--- /dev/null
+++ b/mock/database.js
@@ -0,0 +1,27 @@
+/**
+ * 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 mockjs from 'mockjs';
+
+export default {
+  getAllDatabases: () => {
+    const data = mockjs.mock({
+      'databaseId|20-50': [{ 'id|+1': 3, name: function() { return `database-${this.id}`; }, type: function() { return `type-${this.id}`; } }], // eslint-disable-line
+    });
+    return data.databaseId;
+  },
+};
diff --git a/query-protocol b/query-protocol
index 1122e97..6b23f7d 160000
--- a/query-protocol
+++ b/query-protocol
@@ -1 +1 @@
-Subproject commit 1122e97b5604ae96447bd58ecdb248d7e02952aa
+Subproject commit 6b23f7d29a2a38434b2b6a6964632fa473e9e718
diff --git a/src/common/menu.js b/src/common/menu.js
index eb64c7c..ff46426 100644
--- a/src/common/menu.js
+++ b/src/common/menu.js
@@ -37,6 +37,9 @@ const menuData = [{
       name: 'Endpoint',
       path: 'endpoint',
     }, {
+      name: 'Database',
+      path: 'database',
+    }, {
       name: 'Alarm',
       path: 'alarm',
     },
diff --git a/src/common/router.js b/src/common/router.js
index 6cbbf87..fb6b4ce 100644
--- a/src/common/router.js
+++ b/src/common/router.js
@@ -103,6 +103,9 @@ export const getRouterData = (app) => {
     '/monitor/endpoint': {
       component: dynamicWrapper(app, ['endpoint'], () => import('../routes/Endpoint/Endpoint')),
     },
+    '/monitor/database': {
+      component: dynamicWrapper(app, ['database'], () => import('../routes/Database/Database')),
+    },
     '/trace': {
       component: dynamicWrapper(app, ['trace'], () => import('../routes/Trace/Trace')),
     },
diff --git a/src/components/Database/DatabaseChartArea/index.js b/src/components/Database/DatabaseChartArea/index.js
new file mode 100644
index 0000000..5a65360
--- /dev/null
+++ b/src/components/Database/DatabaseChartArea/index.js
@@ -0,0 +1,39 @@
+/**
+ * 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, { Component } from 'react';
+import { Col } from 'antd';
+import { ChartCard, MiniArea } from 'components/Charts';
+
+export default class DatabaseChartLine extends Component {
+  render() {
+    const {title, total, data} = this.props;
+    return (
+      <Col xs={24} sm={24} md={24} lg={8} xl={8} style={{ padding: '0 4px',marginTop: 8 }}>
+        <ChartCard
+          title={title}
+          total={total}
+          contentHeight={46}
+        >
+          <MiniArea
+            data={data}
+          />
+        </ChartCard>
+      </Col>
+    );
+  }
+}
diff --git a/src/components/Database/DatabaseChartBar/index.js b/src/components/Database/DatabaseChartBar/index.js
new file mode 100644
index 0000000..0857a68
--- /dev/null
+++ b/src/components/Database/DatabaseChartBar/index.js
@@ -0,0 +1,40 @@
+/**
+ * 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, { Component } from 'react';
+import { Col } from 'antd';
+import { ChartCard, MiniBar } from 'components/Charts';
+
+export default class DatabaseChartBar extends Component {
+  render() {
+    const {title, total, data} = this.props;
+    return (
+      <Col xs={24} sm={24} md={24} lg={8} xl={8} style={{ padding: '0 4px',marginTop: 8 }}>
+        <ChartCard
+          title={title}
+          total={total}
+          contentHeight={46}
+        >
+          <MiniBar
+            // animate={false}
+            data={data}
+          />
+        </ChartCard>
+      </Col>
+    );
+  }
+}
diff --git a/src/components/Database/DatabaseChartLine/index.js b/src/components/Database/DatabaseChartLine/index.js
new file mode 100644
index 0000000..e4ecdb7
--- /dev/null
+++ b/src/components/Database/DatabaseChartLine/index.js
@@ -0,0 +1,38 @@
+/**
+ * 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, { Component } from 'react';
+import { Card } from 'antd';
+import { Line } from 'components/Charts';
+
+export default class DatabaseChartLine extends Component {
+  render() {
+    const {title, data} = this.props;
+    return (
+      <Card
+        style={{ marginTop: 8 }}
+        title={title}
+        bordered={false}
+        bodyStyle={{ padding: 5, height: 150}}
+      >
+        <Line
+          data={data}
+        />
+      </Card>
+    );
+  }
+}
diff --git a/src/components/Database/index.js b/src/components/Database/index.js
new file mode 100644
index 0000000..97bf8ad
--- /dev/null
+++ b/src/components/Database/index.js
@@ -0,0 +1,27 @@
+/**
+ * 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 DatabaseChartArea from './DatabaseChartArea';
+import DatabaseChartBar from './DatabaseChartBar';
+import DatabaseChartLine from './DatabaseChartLine';
+
+export {
+  DatabaseChartArea,
+  DatabaseChartBar,
+  DatabaseChartLine,
+};
\ No newline at end of file
diff --git a/src/models/database.js b/src/models/database.js
new file mode 100644
index 0000000..d68c293
--- /dev/null
+++ b/src/models/database.js
@@ -0,0 +1,178 @@
+/**
+ * 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 { base } from '../utils/models';
+// import { exec } from '../services/graphql';
+
+const optionsQuery = `
+  query DatabaseOption($duration: Duration!) {
+    databaseId: getAllDatabases(duration: $duration) {
+      key: id
+      label: name
+      type
+    }
+  }
+`;
+
+const dataQuery = `
+  query Database($databaseId: ID!, $duration: Duration!) {
+    getResponseTimeTrend: getLinearIntValues(metric: {
+      name: "database_access_resp_time"
+      id: $databaseId
+    }, duration: $duration) {
+      values {
+        value
+      }
+    }
+    getThroughputTrend: getLinearIntValues(metric: {
+      name: "database_access_cpm"
+      id: $databaseId
+    }, duration: $duration) {
+      values {
+        value
+      }
+    }
+    getSLATrend: getLinearIntValues(metric: {
+      name: "database_access_sla"
+      id: $databaseId
+    }, duration: $duration) {
+      values {
+        value
+      }
+    }
+    getP99: getLinearIntValues(metric: {
+      name: "database_access_p99"
+      id: $databaseId
+    }, duration: $duration) {
+      values {
+        value
+      }
+    }
+    getP95: getLinearIntValues(metric: {
+      name: "database_access_p95"
+      id: $databaseId
+    }, duration: $duration) {
+      values {
+        value
+      }
+    }
+    getP90: getLinearIntValues(metric: {
+      name: "database_access_p90"
+      id: $databaseId
+    }, duration: $duration) {
+      values {
+        value
+      }
+    }
+    getP75: getLinearIntValues(metric: {
+      name: "database_access_p75"
+      id: $databaseId
+    }, duration: $duration) {
+      values {
+        value
+      }
+    }
+    getP50: getLinearIntValues(metric: {
+      name: "database_access_p50"
+      id: $databaseId
+    }, duration: $duration) {
+      values {
+        value
+      }
+    }
+  }
+`;
+
+
+export default base({
+  namespace: 'database',
+  state: {
+    allDatabase: [],
+    getResponseTimeTrend: {
+      values: [],
+    },
+    getThroughputTrend: {
+      values: [],
+    },
+    getSLATrend: {
+      values: [],
+    },
+    getP99: {
+      values: [],
+    },
+    getP95: {
+      values: [],
+    },
+    getP90: {
+      values: [],
+    },
+    getP75: {
+      values: [],
+    },
+    getP50: {
+      values: [],
+    },
+  },
+  optionsQuery,
+  dataQuery,
+  effects: {
+    // *fetchServiceInstance({ payload }, { call, put }) {
+    //   const { variables, serviceInstanceInfo } = payload;
+    //   const response = yield call(exec, { variables, query: serviceInstanceQuery });
+    //   if (!response.data) {
+    //     return;
+    //   }
+    //   yield put({
+    //     type: 'saveServiceInstance',
+    //     payload: response.data,
+    //     serviceInstanceInfo,
+    //   });
+    // },
+  },
+  reducers: {
+    saveDatabase(preState, { payload }) {
+      const { data } = preState;
+      return {
+        ...preState,
+        data: {
+          ...data,
+          ...payload,
+        },
+      };
+    },
+  },
+  subscriptions: {
+    setup({ history, dispatch }) {
+      return history.listen(({ pathname, state }) => {
+        if (pathname === '/monitor/database' && state) {
+          dispatch({
+            type: 'saveVariables',
+            payload: {
+              values: {
+                databaseId: `${state.key}`,
+              },
+              labels: {
+                databaseId: state.label,
+              },
+            },
+          });
+        }
+      });
+    },
+  },
+});
diff --git a/src/routes/Database/Database.js b/src/routes/Database/Database.js
new file mode 100644
index 0000000..c05d0cc
--- /dev/null
+++ b/src/routes/Database/Database.js
@@ -0,0 +1,142 @@
+/**
+ * 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, { Component } from 'react';
+import { connect } from 'dva';
+import { Row, Select, Form } from 'antd';
+import { Panel } from 'components/Page';
+import { DatabaseChartArea, DatabaseChartBar, DatabaseChartLine } from 'components/Database';
+import { avgTS } from '../../utils/utils';
+import { axisY, axisMY } from '../../utils/time';
+
+const { Option } = Select;
+const { Item: FormItem } = Form;
+
+@connect(state => ({
+  database: state.database,
+  duration: state.global.duration,
+  globalVariables: state.global.globalVariables,
+}))
+@Form.create({
+  mapPropsToFields(props) {
+    const { variables: { values, labels } } = props.database;
+    return {
+      databaseId: Form.createFormField({
+        value: { key: values.databaseId ? values.databaseId : '', label: labels.databaseId ? labels.databaseId : '' },
+      }),
+    };
+  },
+})
+export default class Database extends Component {
+  componentDidMount() {
+    const propsData = this.props;
+    propsData.dispatch({
+      type: 'database/initOptions',
+      payload: { variables: propsData.globalVariables },
+    });
+  }
+
+  componentWillUpdate(nextProps) {
+    const propsData = this.props;
+    if (nextProps.globalVariables.duration === propsData.globalVariables.duration) {
+      return;
+    }
+    propsData.dispatch({
+      type: 'database/initOptions',
+      payload: { variables: nextProps.globalVariables },
+    });
+  }
+
+  handleSelect = (selected) => {
+    const propsData = this.props;
+    propsData.dispatch({
+      type: 'database/saveVariables',
+      payload: {
+        values: { databaseId: selected.key },
+        labels: { databaseId: selected.label },
+      },
+    });
+  }
+
+  handleChange = (variables) => {
+    const {...propsData} = this.props;
+    propsData.dispatch({
+      type: 'database/fetchData',
+      payload: { variables, reducer: 'saveDatabase' },
+    });
+  }
+
+  render() {
+    const propsData = this.props;
+    const { duration } = this.props;
+    const { getFieldDecorator } = propsData.form;
+    const { variables: { values, options }, data } = propsData.database;
+    return (
+      <div>
+        <Form layout="inline">
+          <FormItem style={{ width: '100%' }}>
+            {getFieldDecorator('databaseId')(
+              <Select
+                showSearch
+                style={{ minWidth: 350 }}
+                optionFilterProp="children"
+                placeholder="Select a database"
+                labelInValue
+                onSelect={this.handleSelect.bind(this)}
+              >
+                {options.databaseId && options.databaseId.map(db =>
+                  db.key ?
+                    <Option key={db.key} value={db.key}>{db.type}: {db.label}</Option>
+                    :
+                    null
+                )}
+              </Select>
+            )}
+          </FormItem>
+        </Form>
+        <Panel
+          variables={values}
+          globalVariables={propsData.globalVariables}
+          onChange={this.handleChange}
+        >
+          <Row>
+            <DatabaseChartArea
+              title="Avg Throughput"
+              total={`${avgTS(data.getThroughputTrend.values)} cpm`}
+              data={axisY(duration, data.getThroughputTrend.values)}
+            />
+            <DatabaseChartArea
+              title="Avg Response Time"
+              total={`${avgTS(data.getResponseTimeTrend.values)} ms`}
+              data={axisY(duration, data.getResponseTimeTrend.values)}
+            />
+            <DatabaseChartBar
+              title="Avg SLA"
+              total={`${(avgTS(data.getSLATrend.values) / 100).toFixed(2)} %`}
+              data={axisY(duration, data.getSLATrend.values, ({ x, y }) => ({ x, y: y / 100 }))}
+            />
+          </Row>
+          <DatabaseChartLine
+            title="Response Time"
+            data={axisMY(propsData.duration, [{ title: 'p99', value: data.getP99}, { title: 'p95', value: data.getP95},
+            { title: 'p90', value: data.getP90}, { title: 'p75', value: data.getP75}, { title: 'p50', value: data.getP50}])}
+          />
+        </Panel>
+      </div>
+    );
+  }
+}
diff --git a/src/routes/Endpoint/Endpoint.js b/src/routes/Endpoint/Endpoint.js
index f024da4..6b4ff62 100644
--- a/src/routes/Endpoint/Endpoint.js
+++ b/src/routes/Endpoint/Endpoint.js
@@ -289,7 +289,7 @@ export default class Endpoint extends PureComponent {
                   <Select
                     showSearch
                     optionFilterProp="children"
-                    style={{ width: 200 }}
+                    style={{ minWidth: 250, maxWidth: 400 }}
                     placeholder="Select a service"
                     labelInValue
                     onSelect={this.handleServiceSelect.bind(this)}
diff --git a/src/routes/Service/Service.js b/src/routes/Service/Service.js
index bfd76b9..5d527bc 100644
--- a/src/routes/Service/Service.js
+++ b/src/routes/Service/Service.js
@@ -136,7 +136,7 @@ export default class Service extends PureComponent {
               <Select
                 showSearch
                 optionFilterProp="children"
-                style={{ width: 200 }}
+                style={{ minWidth: 250, maxWidth: 400 }}
                 placeholder="Select a service"
                 labelInValue
                 onSelect={this.handleSelect.bind(this)}