From 69a9c6de13b87450c48515d859d9099877f0a84b Mon Sep 17 00:00:00 2001 From: Fine0830 Date: Thu, 14 Apr 2022 14:38:32 +0800 Subject: [PATCH] feat: add an avg calculation for metrics of list graphs (#65) --- src/assets/icons/timeline.svg | 1 - src/hooks/data.ts | 1 + src/hooks/useListConfig.ts | 34 ++++ src/hooks/useProcessor.ts | 50 ++++-- src/store/modules/topology.ts | 6 +- src/types/dashboard.ts | 1 + .../configuration/widget/metric/Index.vue | 2 +- src/views/dashboard/controls/Text.vue | 1 + src/views/dashboard/data.ts | 5 +- src/views/dashboard/graphs/Card.vue | 24 +-- src/views/dashboard/graphs/EndpointList.vue | 56 +----- src/views/dashboard/graphs/InstanceList.vue | 59 +------ src/views/dashboard/graphs/Line.vue | 5 +- src/views/dashboard/graphs/ServiceList.vue | 61 +------ .../graphs/components/ColumnGraph.vue | 161 ++++++++++++++++++ src/views/dashboard/panel/Layout.vue | 1 + 16 files changed, 279 insertions(+), 189 deletions(-) create mode 100644 src/hooks/useListConfig.ts create mode 100644 src/views/dashboard/graphs/components/ColumnGraph.vue diff --git a/src/assets/icons/timeline.svg b/src/assets/icons/timeline.svg index c7a074f0..64b37e5c 100644 --- a/src/assets/icons/timeline.svg +++ b/src/assets/icons/timeline.svg @@ -13,6 +13,5 @@ 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. --> -timeline diff --git a/src/hooks/data.ts b/src/hooks/data.ts index 00160b42..1909362d 100644 --- a/src/hooks/data.ts +++ b/src/hooks/data.ts @@ -33,6 +33,7 @@ export enum Calculations { ConvertSeconds = "convertSeconds", ConvertMilliseconds = "convertMilliseconds", MsTos = "msTos", + Average = "average", } export enum sizeEnum { XS = "XS", diff --git a/src/hooks/useListConfig.ts b/src/hooks/useListConfig.ts new file mode 100644 index 00000000..47e7269e --- /dev/null +++ b/src/hooks/useListConfig.ts @@ -0,0 +1,34 @@ +/** + * 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 { MetricQueryTypes, Calculations } from "./data"; +export function useListConfig(config: any, index: string) { + const i = Number(index); + const calculation = + config.metricConfig && + config.metricConfig[i] && + config.metricConfig[i].calculation; + const line = + config.metricTypes[i] === MetricQueryTypes.ReadMetricsValues && + calculation !== Calculations.Average; + const isAvg = + config.metricTypes[i] === MetricQueryTypes.ReadMetricsValues && + calculation === Calculations.Average; + return { + isLinear: line, + isAvg, + }; +} diff --git a/src/hooks/useProcessor.ts b/src/hooks/useProcessor.ts index 74d4f954..84ab0744 100644 --- a/src/hooks/useProcessor.ts +++ b/src/hooks/useProcessor.ts @@ -150,9 +150,7 @@ export function useSourceProcessor( const c = (config.metricConfig && config.metricConfig[index]) || {}; if (type === MetricQueryTypes.ReadMetricsValues) { - source[m] = resp.data[keys[index]].values.values.map( - (d: { value: number }) => aggregation(d.value, c) - ); + source[m] = calculateExp(resp.data[keys[index]].values.values, c); } if (type === MetricQueryTypes.ReadLabeledMetricsValues) { const resVal = Object.values(resp.data)[0] || []; @@ -166,7 +164,6 @@ export function useSourceProcessor( const values = item.values.values.map((d: { value: number }) => aggregation(Number(d.value), c) ); - const indexNum = labelsIdx.findIndex((d: string) => d === item.label); if (labels[indexNum] && indexNum > -1) { source[labels[indexNum]] = values; @@ -287,8 +284,12 @@ export function usePodsSource( d[name] = aggregation(resp.data[key], c); } if (config.metricTypes[index] === MetricQueryTypes.ReadMetricsValues) { - d[name] = resp.data[key].values.values.map((d: { value: number }) => - aggregation(d.value, c) + d[name] = {}; + if (c.calculation === Calculations.Average) { + d[name]["avg"] = calculateExp(resp.data[key].values.values, c); + } + d[name]["values"] = resp.data[key].values.values.map( + (val: { value: number }) => aggregation(val.value, c) ); } }); @@ -322,25 +323,48 @@ export function useQueryTopologyMetrics(metrics: string[], ids: string[]) { return { queryStr, conditions }; } +function calculateExp( + arr: any[], + config: { calculation: string } +): (number | string)[] { + let data: (number | string)[] = []; + switch (config.calculation) { + case Calculations.Average: + data = [ + ( + arr.map((d: { value: number }) => d.value).reduce((a, b) => a + b) / + arr.length + ).toFixed(2), + ]; + break; + default: + data = arr.map((d) => aggregation(d.value, config)); + break; + } + return data; +} -export function aggregation(val: number, config: any): number | string { +export function aggregation( + val: number, + config: { calculation: string } +): number | string { let data: number | string = Number(val); switch (config.calculation) { case Calculations.Percentage: - data = val / 100; + data = (val / 100).toFixed(2); break; case Calculations.ByteToKB: - data = val / 1024; + data = (val / 1024).toFixed(2); break; case Calculations.ByteToMB: - data = val / 1024 / 1024; + data = (val / 1024 / 1024).toFixed(2); break; case Calculations.ByteToGB: - data = val / 1024 / 1024 / 1024; + data = (val / 1024 / 1024 / 1024).toFixed(2); break; case Calculations.Apdex: - data = val / 10000; + data = (val / 10000).toFixed(2); break; case Calculations.ConvertSeconds: data = dayjs(val).format("YYYY-MM-DD HH:mm:ss"); @@ -352,7 +376,7 @@ export function aggregation(val: number, config: any): number | string { data = data.toFixed(2); break; case Calculations.MsTos: - data = val / 1000; + data = (val / 1000).toFixed(2); break; default: data; diff --git a/src/store/modules/topology.ts b/src/store/modules/topology.ts index 32da7b3f..c381e4bd 100644 --- a/src/store/modules/topology.ts +++ b/src/store/modules/topology.ts @@ -75,7 +75,7 @@ export const topologyStore = defineStore({ setTopology(data: { nodes: Node[]; calls: Call[] }) { const obj = {} as any; const services = useSelectorStore().services; - const nodes = data.nodes.reduce((prev: Node[], next: Node) => { + const nodes = (data.nodes || []).reduce((prev: Node[], next: Node) => { if (!obj[next.id]) { obj[next.id] = true; const s = services.filter((d: Service) => d.id === next.id)[0] || {}; @@ -84,7 +84,7 @@ export const topologyStore = defineStore({ } return prev; }, []); - const calls = data.calls.reduce((prev: Call[], next: Call) => { + const calls = (data.calls || []).reduce((prev: Call[], next: Call) => { if (!obj[next.id]) { obj[next.id] = true; next.value = next.value || 1; @@ -117,7 +117,7 @@ export const topologyStore = defineStore({ async getDepthServiceTopology(serviceIds: string[], depth: number) { const res = await this.getServicesTopology(serviceIds); if (depth > 1) { - const ids = res.nodes + const ids = (res.nodes || []) .map((item: Node) => item.id) .filter((d: string) => !serviceIds.includes(d)); if (!ids.length) { diff --git a/src/types/dashboard.ts b/src/types/dashboard.ts index f4086d9d..6f60b6a0 100644 --- a/src/types/dashboard.ts +++ b/src/types/dashboard.ts @@ -73,6 +73,7 @@ export interface LineConfig extends AreaConfig { showXAxis?: boolean; showYAxis?: boolean; smallTips?: boolean; + showlabels?: boolean; } export interface AreaConfig { diff --git a/src/views/dashboard/configuration/widget/metric/Index.vue b/src/views/dashboard/configuration/widget/metric/Index.vue index 14cb8363..e76324fa 100644 --- a/src/views/dashboard/configuration/widget/metric/Index.vue +++ b/src/views/dashboard/configuration/widget/metric/Index.vue @@ -466,7 +466,7 @@ function setMetricConfig(index: number) { .chart-types { span { display: inline-block; - padding: 5px 10px; + padding: 2px 10px; border: 1px solid #ccc; background-color: #fff; border-right: 0; diff --git a/src/views/dashboard/controls/Text.vue b/src/views/dashboard/controls/Text.vue index 98cc0db3..1e1a37bf 100644 --- a/src/views/dashboard/controls/Text.vue +++ b/src/views/dashboard/controls/Text.vue @@ -105,6 +105,7 @@ function editConfig() { cursor: pointer; display: flex; align-items: center; + overflow: auto; } .tools { diff --git a/src/views/dashboard/data.ts b/src/views/dashboard/data.ts index 50026a1c..5b80ca3c 100644 --- a/src/views/dashboard/data.ts +++ b/src/views/dashboard/data.ts @@ -266,11 +266,12 @@ export const CalculationOpts = [ { label: "Byte to KB", value: "byteToKB" }, { label: "Byte to MB", value: "byteToMB" }, { label: "Byte to GB", value: "byteToGB" }, + { label: "Average", value: "average" }, { - label: "Convert milliseconds to YYYY-MM-DD HH:mm:ss", + label: "Milliseconds to YYYY-MM-DD HH:mm:ss", value: "convertMilliseconds", }, - { label: "Convert seconds to YYYY-MM-DD HH:mm:ss", value: "convertSeconds" }, + { label: "Seconds to YYYY-MM-DD HH:mm:ss", value: "convertSeconds" }, { label: "Precision is 2", value: "precision" }, { label: "Milliseconds to seconds", value: "msTos" }, ]; diff --git a/src/views/dashboard/graphs/Card.vue b/src/views/dashboard/graphs/Card.vue index 241dacc1..234f8b9a 100644 --- a/src/views/dashboard/graphs/Card.vue +++ b/src/views/dashboard/graphs/Card.vue @@ -17,11 +17,13 @@ limitations under the License. -->
{{ singleVal.toFixed(2) }} - + {{ decodeURIComponent(unit) }}
@@ -59,19 +61,19 @@ const unit = computed( diff --git a/src/views/dashboard/graphs/EndpointList.vue b/src/views/dashboard/graphs/EndpointList.vue index 9dc8d1b6..5ab1b95f 100644 --- a/src/views/dashboard/graphs/EndpointList.vue +++ b/src/views/dashboard/graphs/EndpointList.vue @@ -42,34 +42,11 @@ limitations under the License. --> - - - + @@ -83,12 +60,11 @@ import { EndpointListConfig } from "@/types/dashboard"; import { Endpoint } from "@/types/selector"; import { useDashboardStore } from "@/store/modules/dashboard"; import { useQueryPodsMetrics, usePodsSource } from "@/hooks/useProcessor"; -import Line from "./Line.vue"; -import Card from "./Card.vue"; import { EntityType } from "../data"; import router from "@/router"; import getDashboard from "@/hooks/useDashboardsSession"; import { MetricConfigOpt } from "@/types/dashboard"; +import ColumnGraph from "./components/ColumnGraph.vue"; /*global defineProps */ const props = defineProps({ @@ -184,26 +160,6 @@ function clickEndpoint(scope: any) { async function searchList() { await queryEndpoints(); } -function getUnit(index: number) { - const u = - props.config.metricConfig && - props.config.metricConfig[index] && - props.config.metricConfig[index].unit; - if (u) { - return `(${encodeURIComponent(u)})`; - } - return encodeURIComponent(""); -} -function getLabel(metric: string, index: number) { - const label = - props.config.metricConfig && - props.config.metricConfig[index] && - props.config.metricConfig[index].label; - if (label) { - return encodeURIComponent(label); - } - return encodeURIComponent(metric); -} watch( () => [...(props.config.metricTypes || []), ...(props.config.metrics || [])], (data, old) => { diff --git a/src/views/dashboard/graphs/InstanceList.vue b/src/views/dashboard/graphs/InstanceList.vue index 4a1b367d..794b8804 100644 --- a/src/views/dashboard/graphs/InstanceList.vue +++ b/src/views/dashboard/graphs/InstanceList.vue @@ -42,35 +42,11 @@ limitations under the License. --> - - - + - - - + [...(props.config.metricTypes || []), ...(props.config.metrics || [])], @@ -318,10 +275,6 @@ watch( diff --git a/src/views/dashboard/panel/Layout.vue b/src/views/dashboard/panel/Layout.vue index 409df42b..39eb8a6d 100644 --- a/src/views/dashboard/panel/Layout.vue +++ b/src/views/dashboard/panel/Layout.vue @@ -66,6 +66,7 @@ export default defineComponent({ selectorStore.setCurrentService(null); selectorStore.setCurrentPod(null); dashboardStore.setEntity(""); + dashboardStore.setConfigPanel(false); }); return { dashboardStore,