You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by qi...@apache.org on 2023/05/08 09:45:35 UTC

[skywalking-grafana-plugins] branch main updated (5ee35b9 -> 4778ed9)

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

qiuxiafan pushed a change to branch main
in repository https://gitbox.apache.org/repos/asf/skywalking-grafana-plugins.git


    from 5ee35b9  feat: add layer
     new 83c5777  fix: update
     new a19e250  feat: update query config
     new cb58031  feat: validation
     new 4778ed9  feat: update config

The 4 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 src/components/QueryEditor.tsx | 37 +++++++++++------
 src/datasource.ts              | 91 +++++++++++++++++++-----------------------
 src/types.ts                   |  3 +-
 3 files changed, 68 insertions(+), 63 deletions(-)


[skywalking-grafana-plugins] 01/04: fix: update

Posted by qi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

qiuxiafan pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/skywalking-grafana-plugins.git

commit 83c5777e9507517963523ea008488f5266eb0606
Author: Fine0830 <fa...@gmail.com>
AuthorDate: Mon May 8 14:30:05 2023 +0800

    fix: update
---
 src/datasource.ts | 15 ++++++++-------
 1 file changed, 8 insertions(+), 7 deletions(-)

diff --git a/src/datasource.ts b/src/datasource.ts
index 99ee36f..cbeb427 100644
--- a/src/datasource.ts
+++ b/src/datasource.ts
@@ -100,13 +100,13 @@ export class DataSource extends DataSourceApi<MyQuery, MyDataSourceOptions> {
         nodes,
         calls,
         nodeMetrics: nodeMetricsResp ? {...nodeMetricsResp, config: nodeMetrics} : undefined,
-        edgeServerMetrics: edgeServerMetricsResp ? {...edgeServerMetricsResp, config: nodeMetrics} : undefined,
-        edgeClientMetrics: edgeClientMetricsResp ? {...edgeClientMetricsResp, config: nodeMetrics} : undefined,
+        edgeServerMetrics: edgeServerMetricsResp ? {...edgeServerMetricsResp, config: edgeServerMetrics} : undefined,
+        edgeClientMetrics: edgeClientMetricsResp ? {...edgeClientMetricsResp, config: edgeClientMetrics} : undefined,
       });
       const {nodeFieldTypes, edgeServerFieldTypes, edgeClientFieldTypes} = this.setFieldTypes({
         nodeMetrics: nodeMetricsResp ? {...nodeMetricsResp, config: nodeMetrics} : undefined,
-        edgeServerMetrics: edgeServerMetricsResp ? {...edgeServerMetricsResp, config: nodeMetrics} : undefined,
-        edgeClientMetrics: edgeClientMetricsResp ? {...edgeClientMetricsResp, config: nodeMetrics} : undefined,
+        edgeServerMetrics: edgeServerMetricsResp ? {...edgeServerMetricsResp, config: edgeServerMetrics} : undefined,
+        edgeClientMetrics: edgeClientMetricsResp ? {...edgeClientMetricsResp, config: edgeClientMetrics} : undefined,
       });
       console.log(topology);
       const nodeFrame =  new MutableDataFrame({
@@ -135,10 +135,10 @@ export class DataSource extends DataSourceApi<MyQuery, MyDataSourceOptions> {
           preferredVisualisationType: 'nodeGraph',
         }
       });
-      for (const node of nodes) {
+      for (const node of topology.nodes) {
         nodeFrame.add({...node, title: node.name});
       }
-      for (const call of calls) {
+      for (const call of topology.calls) {
         edgeFrame.add(call);
       }
       return [nodeFrame, edgeFrame];
@@ -150,7 +150,7 @@ export class DataSource extends DataSourceApi<MyQuery, MyDataSourceOptions> {
   async doRequest(params?: Recordable) {
     // Do the request on proxy; the server will replace url + routePath with the url
     // defined in plugin.json
-    const result = getBackendSrv().post(`${this.URL}${RoutePath}`, params, {headers: {
+    const result = getBackendSrv().post(`${this.URL}${RoutePath}`, params, { headers: {
       'Content-Type': 'application/json',
       'Authorization': 'Basic c2t5d2Fsa2luZzpza3l3YWxraW5n',
     } });
@@ -208,6 +208,7 @@ export class DataSource extends DataSourceApi<MyQuery, MyDataSourceOptions> {
   }
 
   getTypes(metrics: Recordable, type: string) {
+    console.log(metrics.config);
     const types = Object.keys(metrics.data).map((k: string, index: number) => {
       const c = metrics.config.find((d: MetricData) => d.name === k) || {};
       if (type === 'edgeC') {


[skywalking-grafana-plugins] 02/04: feat: update query config

Posted by qi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

qiuxiafan pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/skywalking-grafana-plugins.git

commit a19e250259156aba9a3d6475e3caf7e5db8360a9
Author: Fine0830 <fa...@gmail.com>
AuthorDate: Mon May 8 15:04:18 2023 +0800

    feat: update query config
---
 src/components/QueryEditor.tsx | 16 +++------
 src/datasource.ts              | 74 +++++++++++++++++++-----------------------
 src/types.ts                   |  3 +-
 3 files changed, 39 insertions(+), 54 deletions(-)

diff --git a/src/components/QueryEditor.tsx b/src/components/QueryEditor.tsx
index e80c34c..bbf02ec 100644
--- a/src/components/QueryEditor.tsx
+++ b/src/components/QueryEditor.tsx
@@ -16,16 +16,13 @@ export function QueryEditor({ query, onChange, onRunQuery }: Props) {
   const onNodeMetricsChange = (event: ChangeEvent<HTMLInputElement>) => {
     onChange({ ...query, nodeMetrics: event.target.value });
   };
-  const onEdgeServerMetricsChange = (event: ChangeEvent<HTMLInputElement>) => {
-    onChange({ ...query, edgeServerMetrics: event.target.value });
-  };
-  const onEdgeClientMetricsChange = (event: ChangeEvent<HTMLInputElement>) => {
-    onChange({ ...query, edgeClientMetrics: event.target.value });
+  const onEdgeMetricsChange = (event: ChangeEvent<HTMLInputElement>) => {
+    onChange({ ...query, edgeMetrics: event.target.value });
   };
   const onRunQueryText = () => {
     onRunQuery();
   };
-  const { service, nodeMetrics, edgeServerMetrics, edgeClientMetrics, layer } = query;
+  const { service, nodeMetrics, edgeMetrics, layer } = query;
 
   return (
     <div className="gf-form-group">
@@ -38,12 +35,9 @@ export function QueryEditor({ query, onChange, onRunQuery }: Props) {
       <InlineField label="Node Metrics"  tooltip="Not used yet" labelWidth={20}>
         <Input onBlur={onRunQueryText} onChange={onNodeMetricsChange} value={nodeMetrics || ''} width={60} />
       </InlineField>
-      <InlineField label="Edge Server Metrics"  tooltip="Not used yet" labelWidth={20}>
-          <Input onBlur={onRunQueryText} onChange={onEdgeServerMetricsChange} value={edgeServerMetrics || ''} width={60} />
+      <InlineField label="Edge Metrics"  tooltip="Not used yet" labelWidth={20}>
+          <Input onBlur={onRunQueryText} onChange={onEdgeMetricsChange} value={edgeMetrics || ''} width={60} />
         </InlineField>
-      <InlineField label="Edge Client Metrics"  tooltip="Not used yet" labelWidth={20}>
-        <Input onBlur={onRunQueryText} onChange={onEdgeClientMetricsChange} value={edgeClientMetrics || ''} width={60} />
-      </InlineField>
     </div> 
   );
 }
diff --git a/src/datasource.ts b/src/datasource.ts
index cbeb427..5753383 100644
--- a/src/datasource.ts
+++ b/src/datasource.ts
@@ -59,10 +59,8 @@ export class DataSource extends DataSourceApi<MyQuery, MyDataSourceOptions> {
       const serviceName = getTemplateSrv().replace(query.service, options.scopedVars);
       const nodeMetricsStr = getTemplateSrv().replace(query.nodeMetrics, options.scopedVars);
       const nodeMetrics = nodeMetricsStr ? this.parseMetrics(nodeMetricsStr) : [];
-      const edgeServerMetricsStr = getTemplateSrv().replace(query.edgeServerMetrics, options.scopedVars);
-      const edgeServerMetrics = edgeServerMetricsStr ? this.parseMetrics(edgeServerMetricsStr) : [];
-      const edgeClientMetricsStr = getTemplateSrv().replace(query.edgeClientMetrics, options.scopedVars);
-      const edgeClientMetrics = edgeClientMetricsStr ? this.parseMetrics(edgeClientMetricsStr) : [];
+      const edgeMetricsStr = getTemplateSrv().replace(query.edgeMetrics, options.scopedVars);
+      const edgeMetrics = edgeMetricsStr ? this.parseMetrics(edgeMetricsStr) : [];
       let services = [];
       // fetch services from api
       const  s =  {
@@ -77,7 +75,7 @@ export class DataSource extends DataSourceApi<MyQuery, MyDataSourceOptions> {
         variables: Recordable;
       } = {
         query: Fragments.servicesTopolgy,
-        variables: {duration},
+        variables: { duration },
       };
       let serviceObj;
       if (serviceName) {
@@ -91,22 +89,32 @@ export class DataSource extends DataSourceApi<MyQuery, MyDataSourceOptions> {
       const {nodes, calls} = this.setTopologyData({nodes: res.data.topology.nodes || [],  calls: res.data.topology.calls || []});
       const idsS = calls.filter((i: Call) => i.detectPoints.includes("SERVER")).map((b: Call) => b.id);
       const idsC = calls.filter((i: Call) => i.detectPoints.includes("CLIENT")).map((b: Call) => b.id);
+      const serverMetrics = [];
+      const clientMetrics = [];
+      for (const m of edgeMetrics) {
+        if (m.type === 'SERVER') {
+          serverMetrics.push(m);
+        } else {
+          clientMetrics.push(m);
+        }
+      }
       const ids = nodes.map((d: Node) => d.id);
       // fetch topology metrics from api
       const nodeMetricsResp = nodeMetrics.length ? await this.queryMetrics(nodeMetrics, ids, duration) : null;
-      const edgeServerMetricsResp = edgeServerMetrics.length && idsS.length ? await this.queryMetrics(edgeServerMetrics, idsS, duration) : null;
-      const edgeClientMetricsResp = edgeClientMetrics.length && idsC.length ? await this.queryMetrics(edgeClientMetrics, idsC, duration) : null;
+      const edgeServerMetricsResp = serverMetrics.length && idsS.length ? await this.queryMetrics(serverMetrics, idsS, duration) : null;
+      const edgeClientMetricsResp = clientMetrics.length && idsC.length ? await this.queryMetrics(clientMetrics, idsC, duration) : null;
+      const edgeMetricsResp: any = (edgeServerMetricsResp || edgeClientMetricsResp) ? {
+        data: {...edgeServerMetricsResp.data, ...edgeClientMetricsResp.data}
+      } : null;
       const topology = this.setTopologyMetrics({
         nodes,
         calls,
         nodeMetrics: nodeMetricsResp ? {...nodeMetricsResp, config: nodeMetrics} : undefined,
-        edgeServerMetrics: edgeServerMetricsResp ? {...edgeServerMetricsResp, config: edgeServerMetrics} : undefined,
-        edgeClientMetrics: edgeClientMetricsResp ? {...edgeClientMetricsResp, config: edgeClientMetrics} : undefined,
+        edgeMetrics: edgeMetricsResp ? {...edgeMetricsResp, config: serverMetrics} : undefined,
       });
-      const {nodeFieldTypes, edgeServerFieldTypes, edgeClientFieldTypes} = this.setFieldTypes({
+      const {nodeFieldTypes, edgeFieldTypes} = this.setFieldTypes({
         nodeMetrics: nodeMetricsResp ? {...nodeMetricsResp, config: nodeMetrics} : undefined,
-        edgeServerMetrics: edgeServerMetricsResp ? {...edgeServerMetricsResp, config: edgeServerMetrics} : undefined,
-        edgeClientMetrics: edgeClientMetricsResp ? {...edgeClientMetricsResp, config: edgeClientMetrics} : undefined,
+        edgeMetrics: edgeMetricsResp ? {...edgeMetricsResp, config: edgeMetrics} : undefined,
       });
       console.log(topology);
       const nodeFrame =  new MutableDataFrame({
@@ -128,8 +136,7 @@ export class DataSource extends DataSourceApi<MyQuery, MyDataSourceOptions> {
           { name: 'id', type: FieldType.string },
           { name: 'source', type: FieldType.string },
           { name: 'target', type: FieldType.string },
-          ...edgeServerFieldTypes,
-          ...edgeClientFieldTypes,
+          ...edgeFieldTypes,
         ],
         meta: {
           preferredVisualisationType: 'nodeGraph',
@@ -196,43 +203,34 @@ export class DataSource extends DataSourceApi<MyQuery, MyDataSourceOptions> {
       return {nodes, calls}
   }
 
-  setFieldTypes(params: {nodeMetrics: Recordable, edgeServerMetrics: Recordable, edgeClientMetrics: Recordable}) {
+  setFieldTypes(params: {nodeMetrics: Recordable, edgeMetrics: Recordable}) {
     const nodeMetrics = params.nodeMetrics || {config: [], data: {}};
-    const edgeServerMetrics = params.edgeServerMetrics || {config: [], data: {}};
-    const edgeClientMetrics = params.edgeClientMetrics || {config: [], data: {}};
-    const nodeFieldTypes = this.getTypes(nodeMetrics, 'node');
-    const edgeServerFieldTypes = this.getTypes(edgeServerMetrics, 'edgeS');
-    const edgeClientFieldTypes = this.getTypes(edgeClientMetrics, 'edgeC');
+    const edgeServerMetrics = params.edgeMetrics || {config: [], data: {}};
+    const nodeFieldTypes = this.getTypes(nodeMetrics);
+    const edgeFieldTypes = this.getTypes(edgeServerMetrics);
 
-    return {nodeFieldTypes, edgeServerFieldTypes, edgeClientFieldTypes};
+    return {nodeFieldTypes, edgeFieldTypes};
   }
 
-  getTypes(metrics: Recordable, type: string) {
-    console.log(metrics.config);
+  getTypes(metrics: Recordable) {
     const types = Object.keys(metrics.data).map((k: string, index: number) => {
       const c = metrics.config.find((d: MetricData) => d.name === k) || {};
-      if (type === 'edgeC') {
-        return { name: `detail__${k}`, type: FieldType.number, config: {displayName: `${c.label || k} ${c.unit || ''}`} };
-      }
       if (index === 0) {
         return { name: 'mainstat', type: FieldType.number};
       }
       if (index === 1) {
         return { name: 'secondarystat', type: FieldType.number};
       }
-      if (type === 'edgeS') {
-        return { name: `detail__${k}`, type: FieldType.number, config: {displayName: `${c.label || k} ${c.unit || ''}`} };
-      }
+
       return { name: `detail__${k}`, type: FieldType.number, config: {displayName:`${c.label || k} ${c.unit || ''}`} };
     });
 
     return types;
   }
 
-  setTopologyMetrics(params: {nodes: Node[], calls: Call[], nodeMetrics: Recordable, edgeServerMetrics: Recordable, edgeClientMetrics: Recordable}) {
+  setTopologyMetrics(params: {nodes: Node[], calls: Call[], nodeMetrics: Recordable, edgeMetrics: Recordable}) {
     const nodeMetrics = params.nodeMetrics || {config: [], data: {}};
-    const edgeServerMetrics = params.edgeServerMetrics || {config: [], data: {}};
-    const edgeClientMetrics = params.edgeClientMetrics || {config: [], data: {}};
+    const edgeMetrics = params.edgeMetrics || {config: [], data: {}};
     const nodes = params.nodes.map((next: Node) => {
       for (const [index, k] of Object.keys(nodeMetrics.data).entries()) {
         const c = nodeMetrics.config.find((d: MetricData) => d.name === k) || {};
@@ -249,9 +247,9 @@ export class DataSource extends DataSourceApi<MyQuery, MyDataSourceOptions> {
       return next;
     })
     const calls = params.calls.map((next: Call) => {
-      for (const [index, k] of Object.keys(edgeServerMetrics.data).entries()) {
-        const c = edgeServerMetrics.config.find((d: MetricData) => d.name === k) || {};
-        const m = (edgeServerMetrics.data[k].values).find((v: {id: string}) => v.id === next.id) || {value: NaN};
+      for (const [index, k] of Object.keys(edgeMetrics.data).entries()) {
+        const c = edgeMetrics.config.find((d: MetricData) => d.name === k) || {};
+        const m = (edgeMetrics.data[k].values).find((v: {id: string}) => v.id === next.id) || {value: NaN};
         const value = m.isEmptyValue ? NaN : this.expression(Number(m.value), c.calculation);
         if (index === 0) {
           next.mainstat = value;
@@ -261,12 +259,6 @@ export class DataSource extends DataSourceApi<MyQuery, MyDataSourceOptions> {
           next[`detail__${k}`] = value;
         }
       }
-      for (const k of Object.keys(edgeClientMetrics.data)) {
-        const c = edgeClientMetrics.config.find((d: MetricData) => d.name === k) || {};
-        const m = (edgeClientMetrics.data[k].values).find((v: {id: string}) => v.id === next.id) || {value: NaN};
-        const value = m.isEmptyValue ? NaN : this.expression(Number(m.value), c.calculation);
-        next[`detail__${k}`] = value;
-      }
       return next;
     })
 
diff --git a/src/types.ts b/src/types.ts
index ea009ca..1444af5 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -4,8 +4,7 @@ export interface MyQuery extends DataQuery {
   layer?: string;
   service?: string;
   nodeMetrics?: string;
-  edgeServerMetrics?: string;
-  edgeClientMetrics?: string;
+  edgeMetrics?: string;
 }
 
 export const DEFAULT_QUERY: Partial<MyQuery> = {};


[skywalking-grafana-plugins] 04/04: feat: update config

Posted by qi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

qiuxiafan pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/skywalking-grafana-plugins.git

commit 4778ed9ce05fbb47f974e53eed757a0996454172
Author: Fine0830 <fa...@gmail.com>
AuthorDate: Mon May 8 17:44:45 2023 +0800

    feat: update config
---
 src/components/QueryEditor.tsx |  4 ++--
 src/datasource.ts              | 16 ++++++++--------
 2 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/src/components/QueryEditor.tsx b/src/components/QueryEditor.tsx
index 757b33f..1d51020 100644
--- a/src/components/QueryEditor.tsx
+++ b/src/components/QueryEditor.tsx
@@ -1,5 +1,5 @@
 import React, { ChangeEvent } from 'react';
-import { InlineField, Input, Alert } from '@grafana/ui';
+import { InlineField, Input } from '@grafana/ui';
 import { QueryEditorProps } from '@grafana/data';
 import { DataSource } from '../datasource';
 import { MyDataSourceOptions, MyQuery } from '../types';
@@ -27,7 +27,7 @@ export function QueryEditor({ query, onChange, onRunQuery }: Props) {
       const n = nodeMetrics.find((d: {name: string}) => !d.name);
       const e = edgeMetrics.find((d: {name: string, type: string}) => !(d.name && d.type));
       if (e || n) {
-        return;
+        throw new Error('Bad data');
       }
       onRunQuery();
     } catch(e) {
diff --git a/src/datasource.ts b/src/datasource.ts
index 292e638..42a5af7 100644
--- a/src/datasource.ts
+++ b/src/datasource.ts
@@ -110,7 +110,7 @@ export class DataSource extends DataSourceApi<MyQuery, MyDataSourceOptions> {
         nodes,
         calls,
         nodeMetrics: nodeMetricsResp ? {...nodeMetricsResp, config: nodeMetrics} : undefined,
-        edgeMetrics: edgeMetricsResp ? {...edgeMetricsResp, config: serverMetrics} : undefined,
+        edgeMetrics: edgeMetricsResp ? {...edgeMetricsResp, config: edgeMetrics} : undefined,
       });
       const {nodeFieldTypes, edgeFieldTypes} = this.setFieldTypes({
         nodeMetrics: nodeMetricsResp ? {...nodeMetricsResp, config: nodeMetrics} : undefined,
@@ -173,7 +173,7 @@ export class DataSource extends DataSourceApi<MyQuery, MyDataSourceOptions> {
     return metrics;
   }
 
-  async queryMetrics(params: any[], ids: string[], duration: DurationTime) {
+  async queryMetrics(params: MetricData[], ids: string[], duration: DurationTime) {
     const names = params.map((d: MetricData) => d.name);
     const m = this.queryTopologyMetrics(names, ids, duration);
     const metricJson = await this.doRequest(m);
@@ -205,9 +205,9 @@ export class DataSource extends DataSourceApi<MyQuery, MyDataSourceOptions> {
 
   setFieldTypes(params: {nodeMetrics: Recordable, edgeMetrics: Recordable}) {
     const nodeMetrics = params.nodeMetrics || {config: [], data: {}};
-    const edgeServerMetrics = params.edgeMetrics || {config: [], data: {}};
+    const edgeMetrics = params.edgeMetrics || {config: [], data: {}};
     const nodeFieldTypes = this.getTypes(nodeMetrics);
-    const edgeFieldTypes = this.getTypes(edgeServerMetrics);
+    const edgeFieldTypes = this.getTypes(edgeMetrics);
 
     return {nodeFieldTypes, edgeFieldTypes};
   }
@@ -215,11 +215,12 @@ export class DataSource extends DataSourceApi<MyQuery, MyDataSourceOptions> {
   getTypes(metrics: Recordable) {
     const types = Object.keys(metrics.data).map((k: string, index: number) => {
       const c = metrics.config.find((d: MetricData) => d.name === k) || {};
+      const config = {displayName: c.label, unit: c.unit};
       if (index === 0) {
-        return { name: 'mainstat', type: FieldType.number, config: {unit: c.unit}};
+        return { name: 'mainstat', type: FieldType.number, config};
       }
       if (index === 1) {
-        return { name: 'secondarystat', type: FieldType.number, config: {unit: c.unit}};
+        return { name: 'secondarystat', type: FieldType.number, config};
       }
 
       return { name: `detail__${k}`, type: FieldType.number, config: {displayName: c.label || k, unit: c.unit} };
@@ -402,8 +403,7 @@ export class DataSource extends DataSourceApi<MyQuery, MyDataSourceOptions> {
     const utcTime = len + offset;
 
     return new Date(utcTime + 3600000 * utc);
-  };
-
+  }
   async testDatasource() {
     // Implement a health check for your data source.
     return {


[skywalking-grafana-plugins] 03/04: feat: validation

Posted by qi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

qiuxiafan pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/skywalking-grafana-plugins.git

commit cb58031db63b34a8a182335dbc82de7209a931b6
Author: Fine0830 <fa...@gmail.com>
AuthorDate: Mon May 8 16:28:54 2023 +0800

    feat: validation
---
 src/components/QueryEditor.tsx | 27 +++++++++++++++++++++++----
 src/datasource.ts              |  6 +++---
 2 files changed, 26 insertions(+), 7 deletions(-)

diff --git a/src/components/QueryEditor.tsx b/src/components/QueryEditor.tsx
index bbf02ec..757b33f 100644
--- a/src/components/QueryEditor.tsx
+++ b/src/components/QueryEditor.tsx
@@ -1,5 +1,5 @@
 import React, { ChangeEvent } from 'react';
-import { InlineField, Input } from '@grafana/ui';
+import { InlineField, Input, Alert } from '@grafana/ui';
 import { QueryEditorProps } from '@grafana/data';
 import { DataSource } from '../datasource';
 import { MyDataSourceOptions, MyQuery } from '../types';
@@ -20,7 +20,26 @@ export function QueryEditor({ query, onChange, onRunQuery }: Props) {
     onChange({ ...query, edgeMetrics: event.target.value });
   };
   const onRunQueryText = () => {
-    onRunQuery();
+    try {
+      const nodeMetrics = query.nodeMetrics && parseData(query.nodeMetrics) || [];
+      const edgeMetrics = query.edgeMetrics && parseData(query.edgeMetrics) || [];
+
+      const n = nodeMetrics.find((d: {name: string}) => !d.name);
+      const e = edgeMetrics.find((d: {name: string, type: string}) => !(d.name && d.type));
+      if (e || n) {
+        return;
+      }
+      onRunQuery();
+    } catch(e) {
+      console.error(e);
+    }
+  };
+  const parseData = (params: string) => {
+    const regex = /{[^}]+}/g;
+    const arr = params.match(regex);
+    const metrics = arr?.map((d: string) => JSON.parse(d)) || [];
+
+    return metrics;
   };
   const { service, nodeMetrics, edgeMetrics, layer } = query;
 
@@ -36,8 +55,8 @@ export function QueryEditor({ query, onChange, onRunQuery }: Props) {
         <Input onBlur={onRunQueryText} onChange={onNodeMetricsChange} value={nodeMetrics || ''} width={60} />
       </InlineField>
       <InlineField label="Edge Metrics"  tooltip="Not used yet" labelWidth={20}>
-          <Input onBlur={onRunQueryText} onChange={onEdgeMetricsChange} value={edgeMetrics || ''} width={60} />
-        </InlineField>
+        <Input onBlur={onRunQueryText} onChange={onEdgeMetricsChange} value={edgeMetrics || ''} width={60} />
+      </InlineField>
     </div> 
   );
 }
diff --git a/src/datasource.ts b/src/datasource.ts
index 5753383..292e638 100644
--- a/src/datasource.ts
+++ b/src/datasource.ts
@@ -216,13 +216,13 @@ export class DataSource extends DataSourceApi<MyQuery, MyDataSourceOptions> {
     const types = Object.keys(metrics.data).map((k: string, index: number) => {
       const c = metrics.config.find((d: MetricData) => d.name === k) || {};
       if (index === 0) {
-        return { name: 'mainstat', type: FieldType.number};
+        return { name: 'mainstat', type: FieldType.number, config: {unit: c.unit}};
       }
       if (index === 1) {
-        return { name: 'secondarystat', type: FieldType.number};
+        return { name: 'secondarystat', type: FieldType.number, config: {unit: c.unit}};
       }
 
-      return { name: `detail__${k}`, type: FieldType.number, config: {displayName:`${c.label || k} ${c.unit || ''}`} };
+      return { name: `detail__${k}`, type: FieldType.number, config: {displayName: c.label || k, unit: c.unit} };
     });
 
     return types;