Update: update database feature. (#225)
authorAllen Wang <Allen.Wang.123@outlook.com>
Thu, 31 Jan 2019 07:56:05 +0000 (15:56 +0800)
committer吴晟 Wu Sheng <wu.sheng@foxmail.com>
Thu, 31 Jan 2019 07:56:05 +0000 (15:56 +0800)
* 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.

13 files changed:
.roadhogrc.mock.js
mock/database.js [new file with mode: 0644]
query-protocol
src/common/menu.js
src/common/router.js
src/components/Database/DatabaseChartArea/index.js [new file with mode: 0644]
src/components/Database/DatabaseChartBar/index.js [new file with mode: 0644]
src/components/Database/DatabaseChartLine/index.js [new file with mode: 0644]
src/components/Database/index.js [new file with mode: 0644]
src/models/database.js [new file with mode: 0644]
src/routes/Database/Database.js [new file with mode: 0644]
src/routes/Endpoint/Endpoint.js
src/routes/Service/Service.js

index e8f2052..f5c8d91 100644 (file)
@@ -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 (file)
index 0000000..dd0f550
--- /dev/null
@@ -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;
+  },
+};
index 1122e97..6b23f7d 160000 (submodule)
@@ -1 +1 @@
-Subproject commit 1122e97b5604ae96447bd58ecdb248d7e02952aa
+Subproject commit 6b23f7d29a2a38434b2b6a6964632fa473e9e718
index eb64c7c..ff46426 100644 (file)
@@ -37,6 +37,9 @@ const menuData = [{
       name: 'Endpoint',
       path: 'endpoint',
     }, {
+      name: 'Database',
+      path: 'database',
+    }, {
       name: 'Alarm',
       path: 'alarm',
     },
index 6cbbf87..fb6b4ce 100644 (file)
@@ -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 (file)
index 0000000..5a65360
--- /dev/null
@@ -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 (file)
index 0000000..0857a68
--- /dev/null
@@ -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 (file)
index 0000000..e4ecdb7
--- /dev/null
@@ -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 (file)
index 0000000..97bf8ad
--- /dev/null
@@ -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 (file)
index 0000000..d68c293
--- /dev/null
@@ -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 (file)
index 0000000..c05d0cc
--- /dev/null
@@ -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>
+    );
+  }
+}
index f024da4..6b4ff62 100644 (file)
@@ -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)}
index bfd76b9..5d527bc 100644 (file)
@@ -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)}