From dc22f8da6e5fe60fa0c4e90dc4e3f37361384b03 Mon Sep 17 00:00:00 2001 From: Fine0830 Date: Sun, 4 Jun 2023 14:09:36 +0800 Subject: [PATCH] feat: supporting expressions to query metrics data (#270) --- src/graphql/fragments/dashboard.ts | 9 + src/graphql/query/dashboard.ts | 3 + src/hooks/data.ts | 18 ++ src/hooks/useExpressionsProcessor.ts | 291 ++++++++++++++++++ src/hooks/useListConfig.ts | 9 + src/hooks/useMetricsProcessor.ts | 35 +-- src/store/modules/dashboard.ts | 13 +- src/types/dashboard.d.ts | 7 +- src/types/selector.d.ts | 3 + src/views/dashboard/Widget.vue | 22 +- src/views/dashboard/components/WidgetLink.vue | 17 +- src/views/dashboard/configuration/Widget.vue | 25 +- .../configuration/widget/metric/Index.vue | 287 +++++++++++++++-- .../configuration/widget/metric/Standard.vue | 30 +- src/views/dashboard/controls/Widget.vue | 30 +- src/views/dashboard/data.ts | 14 + src/views/dashboard/graphs/Bar.vue | 1 + src/views/dashboard/graphs/EndpointList.vue | 61 +++- src/views/dashboard/graphs/InstanceList.vue | 65 +++- src/views/dashboard/graphs/ServiceList.vue | 67 +++- src/views/dashboard/graphs/TopList.vue | 2 +- .../graphs/components/ColumnGraph.vue | 19 +- 22 files changed, 923 insertions(+), 105 deletions(-) create mode 100644 src/hooks/useExpressionsProcessor.ts diff --git a/src/graphql/fragments/dashboard.ts b/src/graphql/fragments/dashboard.ts index f453f3d8..9b673b08 100644 --- a/src/graphql/fragments/dashboard.ts +++ b/src/graphql/fragments/dashboard.ts @@ -68,3 +68,12 @@ export const deleteTemplate = { message }`, }; +export const TypeOfMQE = { + variable: "$expression: String!", + query: ` + metricType: returnTypeOfMQE(expression: $expression) { + type + error + } + `, +}; diff --git a/src/graphql/query/dashboard.ts b/src/graphql/query/dashboard.ts index 738fafa8..a60a464e 100644 --- a/src/graphql/query/dashboard.ts +++ b/src/graphql/query/dashboard.ts @@ -21,6 +21,7 @@ import { addTemplate, changeTemplate, deleteTemplate, + TypeOfMQE, } from "../fragments/dashboard"; export const queryTypeOfMetrics = `query typeOfMetrics(${TypeOfMetrics.variable}) {${TypeOfMetrics.query}}`; @@ -34,3 +35,5 @@ export const updateTemplate = `mutation template(${changeTemplate.variable}) {${ export const removeTemplate = `mutation template(${deleteTemplate.variable}) {${deleteTemplate.query}}`; export const getTemplates = `query templates {${getAllTemplates.query}}`; + +export const getTypeOfMQE = `query returnTypeOfMQE(${TypeOfMQE.variable}) {${TypeOfMQE.query}}`; diff --git a/src/hooks/data.ts b/src/hooks/data.ts index 195483f9..d74ba1b2 100644 --- a/src/hooks/data.ts +++ b/src/hooks/data.ts @@ -112,4 +112,22 @@ export const RespFields: Indexable = { value refId }`, + execExpression: `{ + type + results { + metric { + name + labels { + key + value + } + } + values { + id + value + traceID + } + } + error + }`, }; diff --git a/src/hooks/useExpressionsProcessor.ts b/src/hooks/useExpressionsProcessor.ts new file mode 100644 index 00000000..345fde92 --- /dev/null +++ b/src/hooks/useExpressionsProcessor.ts @@ -0,0 +1,291 @@ +/** + * 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 { RespFields, Calculations } from "./data"; +import { ExpressionResultType } from "@/views/dashboard/data"; +import { ElMessage } from "element-plus"; +import { useDashboardStore } from "@/store/modules/dashboard"; +import { useSelectorStore } from "@/store/modules/selectors"; +import { useAppStoreWithOut } from "@/store/modules/app"; +import type { MetricConfigOpt } from "@/types/dashboard"; +import type { Instance, Endpoint, Service } from "@/types/selector"; +import { calculateExp } from "./useMetricsProcessor"; + +export function useExpressionsQueryProcessor(config: Indexable) { + if (!(config.metrics && config.metrics[0])) { + return; + } + if (!(config.metricTypes && config.metricTypes[0])) { + return; + } + const appStore = useAppStoreWithOut(); + const dashboardStore = useDashboardStore(); + const selectorStore = useSelectorStore(); + + if (!selectorStore.currentService && dashboardStore.entity !== "All") { + return; + } + const conditions: Recordable = { + duration: appStore.durationTime, + }; + const variables: string[] = [`$duration: Duration!`]; + const isRelation = ["ServiceRelation", "ServiceInstanceRelation", "EndpointRelation", "ProcessRelation"].includes( + dashboardStore.entity, + ); + if (isRelation && !selectorStore.currentDestService) { + return; + } + const fragment = config.metrics.map((name: string, index: number) => { + variables.push(`$expression${index}: String!`, `$entity${index}: Entity!`); + conditions[`expression${index}`] = name; + const entity = { + serviceName: dashboardStore.entity === "All" ? undefined : selectorStore.currentService.value, + normal: dashboardStore.entity === "All" ? undefined : selectorStore.currentService.normal, + serviceInstanceName: ["ServiceInstance", "ServiceInstanceRelation", "ProcessRelation"].includes( + dashboardStore.entity, + ) + ? selectorStore.currentPod && selectorStore.currentPod.value + : undefined, + endpointName: dashboardStore.entity.includes("Endpoint") + ? selectorStore.currentPod && selectorStore.currentPod.value + : undefined, + processName: dashboardStore.entity.includes("Process") + ? selectorStore.currentProcess && selectorStore.currentProcess.value + : undefined, + destNormal: isRelation ? selectorStore.currentDestService.normal : undefined, + destServiceName: isRelation ? selectorStore.currentDestService.value : undefined, + destServiceInstanceName: ["ServiceInstanceRelation", "ProcessRelation"].includes(dashboardStore.entity) + ? selectorStore.currentDestPod && selectorStore.currentDestPod.value + : undefined, + destEndpointName: + dashboardStore.entity === "EndpointRelation" + ? selectorStore.currentDestPod && selectorStore.currentDestPod.value + : undefined, + destProcessName: dashboardStore.entity.includes("ProcessRelation") + ? selectorStore.currentDestProcess && selectorStore.currentDestProcess.value + : undefined, + }; + conditions[`entity${index}`] = entity; + + return `expression${index}: execExpression(expression: $expression${index}, entity: $entity${index}, duration: $duration)${RespFields.execExpression}`; + }); + const queryStr = `query queryData(${variables}) {${fragment}}`; + + return { + queryStr, + conditions, + }; +} + +export function useExpressionsSourceProcessor( + resp: { errors: string; data: Indexable }, + config: { + metrics: string[]; + metricTypes: string[]; + metricConfig: MetricConfigOpt[]; + }, +) { + if (resp.errors) { + ElMessage.error(resp.errors); + return {}; + } + if (!resp.data) { + ElMessage.error("The query is wrong"); + return {}; + } + const source: { [key: string]: unknown } = {}; + const keys = Object.keys(resp.data); + for (let i = 0; i < config.metricTypes.length; i++) { + const type = config.metricTypes[i]; + const c = (config.metricConfig && config.metricConfig[i]) || {}; + const results = (resp.data[keys[i]] && resp.data[keys[i]].results) || []; + const name = ((results[0] || {}).metric || {}).name; + + if (type === ExpressionResultType.TIME_SERIES_VALUES) { + if (results.length === 1) { + source[c.label || name] = results[0].values.map((d: { value: unknown }) => d.value) || []; + } else { + const labels = (c.label || "").split(",").map((item: string) => item.replace(/^\s*|\s*$/g, "")); + for (const item of results) { + const values = item.values.map((d: { value: unknown }) => d.value) || []; + const index = item.metric.labels[0].value; + const indexNum = labels.findIndex((_, i: number) => i === Number(index)); + if (labels[indexNum] && indexNum > -1) { + source[labels[indexNum]] = values; + } else { + source[index] = values; + } + } + } + } + if (type === ExpressionResultType.SINGLE_VALUE) { + source[c.label || name] = results[0].values[0].value; + } + if (([ExpressionResultType.RECORD_LIST, ExpressionResultType.SORTED_LIST] as string[]).includes(type)) { + source[name] = results[0].values; + } + } + + return source; +} + +export async function useExpressionsQueryPodsMetrics( + pods: Array<(Instance | Endpoint | Service) & Indexable>, + config: { + expressions: string[]; + typesOfMQE: string[]; + subExpressions: string[]; + metricConfig: MetricConfigOpt[]; + }, + scope: string, +) { + function expressionsGraphqlPods() { + const metrics: string[] = []; + const subMetrics: string[] = []; + const metricTypes: string[] = []; + config.expressions = config.expressions || []; + config.subExpressions = config.subExpressions || []; + config.typesOfMQE = config.typesOfMQE || []; + + for (let i = 0; i < config.expressions.length; i++) { + if (config.expressions[i]) { + metrics.push(config.expressions[i]); + subMetrics.push(config.subExpressions[i]); + metricTypes.push(config.typesOfMQE[i]); + } + } + if (!metrics.length) { + return; + } + const appStore = useAppStoreWithOut(); + const selectorStore = useSelectorStore(); + const conditions: { [key: string]: unknown } = { + duration: appStore.durationTime, + }; + const variables: string[] = [`$duration: Duration!`]; + const currentService = selectorStore.currentService || {}; + const fragmentList = pods.map((d: (Instance | Endpoint | Service) & Indexable, index: number) => { + const entity = { + serviceName: scope === "Service" ? d.label : currentService.label, + serviceInstanceName: scope === "ServiceInstance" ? d.label : undefined, + endpointName: scope === "Endpoint" ? d.label : undefined, + normal: scope === "Service" ? d.normal : currentService.normal, + }; + variables.push(`$entity${index}: Entity!`); + conditions[`entity${index}`] = entity; + const f = metrics.map((name: string, idx: number) => { + variables.push(`$expression${index}${idx}: String!`); + conditions[`expression${index}${idx}`] = name; + let str = ""; + if (config.subExpressions[idx]) { + variables.push(`$subExpression${index}${idx}: String!`); + conditions[`subExpression${index}${idx}`] = config.subExpressions[idx]; + str = `subexpression${index}${idx}: execExpression(expression: $subExpression${index}${idx}, entity: $entity${index}, duration: $duration)${RespFields.execExpression}`; + } + + return ( + str + + `expression${index}${idx}: execExpression(expression: $expression${index}${idx}, entity: $entity${index}, duration: $duration)${RespFields.execExpression}` + ); + }); + return f; + }); + const fragment = fragmentList.flat(1).join(" "); + const queryStr = `query queryData(${variables}) {${fragment}}`; + + return { queryStr, conditions }; + } + + function expressionsPodsSource(resp: { errors: string; data: Indexable }): Indexable { + if (resp.errors) { + ElMessage.error(resp.errors); + return {}; + } + const names: string[] = []; + const metricConfigArr: MetricConfigOpt[] = []; + const metricTypesArr: string[] = []; + const data = pods.map((d: any, idx: number) => { + for (let index = 0; index < config.expressions.length; index++) { + const c: any = (config.metricConfig && config.metricConfig[index]) || {}; + const k = "expression" + idx + index; + const sub = "subexpression" + idx + index; + const results = (resp.data[k] && resp.data[k].results) || []; + const subResults = (resp.data[sub] && resp.data[sub].results) || []; + + if (results.length > 1) { + const labels = (c.label || "").split(",").map((item: string) => item.replace(/^\s*|\s*$/g, "")); + const labelsIdx = (c.labelsIndex || "").split(",").map((item: string) => item.replace(/^\s*|\s*$/g, "")); + for (let i = 0; i < results.length; i++) { + let name = results[i].metric.labels[0].value || ""; + const subValues = subResults[i] && subResults[i].values.map((d: { value: unknown }) => d.value); + const num = labelsIdx.findIndex((d: string) => d === results[i].metric.labels[0].value); + + if (labels[num]) { + name = labels[num]; + } + if (!d[name]) { + d[name] = {}; + } + if (subValues) { + d[name]["values"] = subValues; + } + d[name]["avg"] = results[i].values[0].value; + + const j = names.find((d: string) => d === name); + + if (!j) { + names.push(name); + metricConfigArr.push({ ...c, index: i }); + metricTypesArr.push(config.typesOfMQE[index]); + } + } + } else { + if (!results[0]) { + return d; + } + const name = results[0].metric.name || ""; + if (!d[name]) { + d[name] = {}; + } + d[name]["avg"] = [results[0].values[0].value]; + if (subResults[0]) { + d[name]["values"] = subResults[0].values.map((d: { value: number }) => d.value); + } + const j = names.find((d: string) => d === name); + if (!j) { + names.push(name); + metricConfigArr.push(c); + metricTypesArr.push(config.typesOfMQE[index]); + } + } + } + return d; + }); + + return { data, names, metricConfigArr, metricTypesArr }; + } + const dashboardStore = useDashboardStore(); + const params = await expressionsGraphqlPods(); + const json = await dashboardStore.fetchMetricValue(params); + + if (json.errors) { + ElMessage.error(json.errors); + return {}; + } + const { data, names, metricTypesArr, metricConfigArr } = expressionsPodsSource(json); + + return { data, names, metricTypesArr, metricConfigArr }; +} diff --git a/src/hooks/useListConfig.ts b/src/hooks/useListConfig.ts index 8c199a07..004cd594 100644 --- a/src/hooks/useListConfig.ts +++ b/src/hooks/useListConfig.ts @@ -15,7 +15,15 @@ * limitations under the License. */ import { MetricQueryTypes, Calculations } from "./data"; +import { MetricModes } from "@/views/dashboard/data"; + export function useListConfig(config: Indexable, index: string) { + if (config.metricModes === MetricModes.Expression) { + return { + isLinear: false, + isAvg: true, + }; + } const i = Number(index); const types = [Calculations.Average, Calculations.ApdexAvg, Calculations.PercentageAvg]; const calculation = config.metricConfig && config.metricConfig[i] && config.metricConfig[i].calculation; @@ -25,6 +33,7 @@ export function useListConfig(config: Indexable, index: string) { const isAvg = [MetricQueryTypes.ReadMetricsValues, MetricQueryTypes.ReadLabeledMetricsValues].includes(config.metricTypes[i]) && types.includes(calculation); + return { isLinear, isAvg, diff --git a/src/hooks/useMetricsProcessor.ts b/src/hooks/useMetricsProcessor.ts index 0a1265e5..b0bbaf71 100644 --- a/src/hooks/useMetricsProcessor.ts +++ b/src/hooks/useMetricsProcessor.ts @@ -22,7 +22,7 @@ import { useSelectorStore } from "@/store/modules/selectors"; import { useAppStoreWithOut } from "@/store/modules/app"; import type { Instance, Endpoint, Service } from "@/types/selector"; import type { MetricConfigOpt } from "@/types/dashboard"; -import { MetricCatalog } from "@/views/dashboard/data"; +import type { E } from "vitest/dist/types-c441ef31"; export function useQueryProcessor(config: Indexable) { if (!(config.metrics && config.metrics[0])) { @@ -57,13 +57,11 @@ export function useQueryProcessor(config: Indexable) { name, parentService: ["All"].includes(dashboardStore.entity) ? null : selectorStore.currentService.value, normal: selectorStore.currentService ? selectorStore.currentService.normal : true, - scope: config.catalog, topN: Number(c.topN) || 10, order: c.sortOrder || "DES", }; } else { const entity = { - scope: config.catalog, serviceName: dashboardStore.entity === "All" ? undefined : selectorStore.currentService.value, normal: dashboardStore.entity === "All" ? undefined : selectorStore.currentService.normal, serviceInstanceName: ["ServiceInstance", "ServiceInstanceRelation", "ProcessRelation"].includes( @@ -99,7 +97,6 @@ export function useQueryProcessor(config: Indexable) { order: c.sortOrder || "DES", }; } else { - entity.scope = dashboardStore.entity; if (metricType === MetricQueryTypes.ReadLabeledMetricsValues) { const labels = (c.labelsIndex || "").split(",").map((item: string) => item.replace(/^\s*|\s*$/g, "")); variables.push(`$labels${index}: [String!]!`); @@ -233,7 +230,6 @@ export function useQueryPodsMetrics( const currentService = selectorStore.currentService || {}; const fragmentList = pods.map((d: (Instance | Endpoint | Service) & Indexable, index: number) => { const param = { - scope, serviceName: scope === "Service" ? d.label : currentService.label, serviceInstanceName: scope === "ServiceInstance" ? d.label : undefined, endpointName: scope === "Endpoint" ? d.label : undefined, @@ -282,7 +278,7 @@ export function usePodsSource( const names: string[] = []; const metricConfigArr: MetricConfigOpt[] = []; const metricTypesArr: string[] = []; - const data = pods.map((d: Instance & Indexable, idx: number) => { + const data = pods.map((d: any, idx: number) => { config.metrics.map((name: string, index: number) => { const c: any = (config.metricConfig && config.metricConfig[index]) || {}; const key = name + idx + index; @@ -367,12 +363,12 @@ export function useQueryTopologyMetrics(metrics: string[], ids: string[]) { return { queryStr, conditions }; } -function calculateExp( +export function calculateExp( list: { value: number; isEmptyValue: boolean }[], config: { calculation?: string }, ): (number | string)[] { const arr = list.filter((d: { value: number; isEmptyValue: boolean }) => !d.isEmptyValue); - const sum = arr.length ? arr.map((d: { value: number }) => d.value).reduce((a, b) => a + b) : 0; + const sum = arr.length ? arr.map((d: { value: number }) => Number(d.value)).reduce((a, b) => a + b) : 0; let data: (number | string)[] = []; switch (config.calculation) { case Calculations.Average: @@ -440,26 +436,3 @@ export function aggregation(val: number, config: { calculation?: string }): numb return data; } - -export async function useGetMetricEntity(metric: string, metricType: string) { - if (!metric || !metricType) { - return; - } - let catalog = ""; - const dashboardStore = useDashboardStore(); - if ( - ([MetricQueryTypes.ReadSampledRecords, MetricQueryTypes.SortMetrics, MetricQueryTypes.ReadRecords] as any).includes( - metricType, - ) - ) { - const res = await dashboardStore.fetchMetricList(metric); - if (res.errors) { - ElMessage.error(res.errors); - return; - } - const c: string = res.data.metrics[0].catalog; - catalog = (MetricCatalog as Indexable)[c]; - } - - return catalog; -} diff --git a/src/store/modules/dashboard.ts b/src/store/modules/dashboard.ts index 47051f30..c1bc22f9 100644 --- a/src/store/modules/dashboard.ts +++ b/src/store/modules/dashboard.ts @@ -25,7 +25,7 @@ import { NewControl, TextConfig, TimeRangeConfig, ControlsTypes } from "../data" import type { AxiosResponse } from "axios"; import { ElMessage } from "element-plus"; import { useI18n } from "vue-i18n"; -import { EntityType } from "@/views/dashboard/data"; +import { EntityType, MetricModes } from "@/views/dashboard/data"; interface DashboardState { showConfig: boolean; layout: LayoutConfig[]; @@ -92,6 +92,9 @@ export const dashboardStore = defineStore({ metricTypes: [""], metrics: [""], }; + if (type === "Widget") { + newItem.metricMode = MetricModes.Expression; + } if (type === "Tab") { newItem.h = 36; newItem.activedTabIndex = 0; @@ -167,6 +170,9 @@ export const dashboardStore = defineStore({ metricTypes: [""], metrics: [""], }; + if (type === "Widget") { + newItem.metricMode = MetricModes.Expression; + } if (type === "Topology") { newItem.h = 32; newItem.graph = { @@ -309,6 +315,11 @@ export const dashboardStore = defineStore({ return res.data; }, + async getTypeOfMQE(expression: string) { + const res: AxiosResponse = await graphql.query("getTypeOfMQE").params({ expression }); + + return res.data; + }, async fetchMetricList(regex: string) { const res: AxiosResponse = await graphql.query("queryMetrics").params({ regex }); diff --git a/src/types/dashboard.d.ts b/src/types/dashboard.d.ts index b4c151ef..636db486 100644 --- a/src/types/dashboard.d.ts +++ b/src/types/dashboard.d.ts @@ -28,11 +28,14 @@ export interface LayoutConfig { w: number; h: number; i: string; + type: string; + metricMode?: string; widget?: WidgetConfig; graph?: GraphConfig; metrics?: string[]; - type: string; + expressions?: string[]; metricTypes?: string[]; + typesOfMQE?: string[]; children?: { name: string; children: LayoutConfig[] }[]; activedTabIndex?: number; metricConfig?: MetricConfigOpt[]; @@ -41,6 +44,8 @@ export interface LayoutConfig { eventAssociate?: boolean; filters?: Filters; relatedTrace?: RelatedTrace; + subExpressions?: string[]; + subTypesOfMQE?: string[]; } export type RelatedTrace = { duration: DurationTime; diff --git a/src/types/selector.d.ts b/src/types/selector.d.ts index f4a63a28..f1b4a3f4 100644 --- a/src/types/selector.d.ts +++ b/src/types/selector.d.ts @@ -21,6 +21,7 @@ export type Service = { layers?: string[]; normal?: boolean; group?: string; + merge?: string; }; export type Instance = { @@ -30,12 +31,14 @@ export type Instance = { instanceUUID?: string; attributes?: { name: string; value: string }[]; id?: string; + merge?: boolean; }; export type Endpoint = { id?: string; label: string; value: string; + merge?: string; }; export type Service = { diff --git a/src/views/dashboard/Widget.vue b/src/views/dashboard/Widget.vue index e3f480bb..b7a2c50e 100644 --- a/src/views/dashboard/Widget.vue +++ b/src/views/dashboard/Widget.vue @@ -35,6 +35,11 @@ limitations under the License. --> metrics: config.metrics, metricTypes: config.metricTypes, metricConfig: config.metricConfig, + metricMode: config.metricMode, + expressions: config.expressions || [], + typesOfMQE: config.typesOfMQE || [], + subExpressions: config.subExpressions || [], + subTypesOfMQE: config.subTypesOfMQE || [], }" :needQuery="true" /> @@ -51,10 +56,12 @@ limitations under the License. --> import { useRoute } from "vue-router"; import { useSelectorStore } from "@/store/modules/selectors"; import { useDashboardStore } from "@/store/modules/dashboard"; - import { useQueryProcessor, useSourceProcessor, useGetMetricEntity } from "@/hooks/useMetricsProcessor"; + import { useQueryProcessor, useSourceProcessor } from "@/hooks/useMetricsProcessor"; + import { useExpressionsQueryProcessor, useExpressionsSourceProcessor } from "@/hooks/useExpressionsProcessor"; import graphs from "./graphs"; import { EntityType } from "./data"; import timeFormat from "@/utils/timeFormat"; + import { MetricModes } from "./data"; export default defineComponent({ name: "WidgetPage", @@ -122,10 +129,12 @@ limitations under the License. --> } } async function queryMetrics() { - const metricTypes = config.value.metricTypes || []; - const metrics = config.value.metrics || []; - const catalog = await useGetMetricEntity(metrics[0], metricTypes[0]); - const params = await useQueryProcessor({ ...config.value, catalog }); + const isExpression = config.value.metricMode === MetricModes.Expression; + const params = isExpression + ? await useExpressionsQueryProcessor({ + ...config.value, + }) + : await useQueryProcessor({ ...config.value }); if (!params) { source.value = {}; return; @@ -140,8 +149,9 @@ limitations under the License. --> metrics: config.value.metrics || [], metricTypes: config.value.metricTypes || [], metricConfig: config.value.metricConfig || [], + subExpressions: config.value.subExpressions || [], }; - source.value = useSourceProcessor(json, d); + source.value = isExpression ? await useExpressionsSourceProcessor(json, d) : await useSourceProcessor(json, d); } watch( () => appStoreWithOut.durationTime, diff --git a/src/views/dashboard/components/WidgetLink.vue b/src/views/dashboard/components/WidgetLink.vue index 584c24a4..c92d4411 100644 --- a/src/views/dashboard/components/WidgetLink.vue +++ b/src/views/dashboard/components/WidgetLink.vue @@ -59,6 +59,7 @@ limitations under the License. --> import copy from "@/utils/copy"; import { RefreshOptions } from "@/views/dashboard/data"; import { TimeType } from "@/constants/data"; + import { MetricModes } from "../data"; const { t } = useI18n(); const appStore = useAppStoreWithOut(); @@ -91,7 +92,8 @@ limitations under the License. --> step: appStore.durationRow.step, utc: appStore.utc, }); - const { widget, graph, metrics, metricTypes, metricConfig } = dashboardStore.selectedGrid; + const { widget, graph, metrics, metricTypes, metricConfig, metricMode, expressions, typesOfMQE, subExpressions } = + dashboardStore.selectedGrid; const c = (metricConfig || []).map((d: any) => { const t: any = {}; if (d.label) { @@ -105,11 +107,20 @@ limitations under the License. --> const opt: any = { type: dashboardStore.selectedGrid.type, graph: graph, - metrics: metrics, - metricTypes: metricTypes, + metricMode, metricConfig: c, height: dashboardStore.selectedGrid.h * 20 + 60, }; + if (metricMode === MetricModes.Expression) { + opt.expressions = expressions; + opt.typesOfMQE = typesOfMQE; + if (subExpressions && subExpressions.length) { + opt.subExpressions = subExpressions; + } + } else { + opt.metrics = metrics; + opt.metricTypes = metricTypes; + } if (widget) { opt.widget = { title: encodeURIComponent(widget.title || ""), diff --git a/src/views/dashboard/configuration/Widget.vue b/src/views/dashboard/configuration/Widget.vue index 812b4128..772f2f8d 100644 --- a/src/views/dashboard/configuration/Widget.vue +++ b/src/views/dashboard/configuration/Widget.vue @@ -38,6 +38,11 @@ limitations under the License. --> metricTypes: dashboardStore.selectedGrid.metricTypes, metricConfig: dashboardStore.selectedGrid.metricConfig, relatedTrace: dashboardStore.selectedGrid.relatedTrace, + metricMode: dashboardStore.selectedGrid.metricMode, + expressions: dashboardStore.selectedGrid.expressions || [], + typesOfMQE: dashboardStore.selectedGrid.typesOfMQE || [], + subExpressions: dashboardStore.selectedGrid.subExpressions || [], + subTypesOfMQE: dashboardStore.selectedGrid.subTypesOfMQE || [], }" :needQuery="true" /> @@ -83,6 +88,7 @@ limitations under the License. --> import type { Option } from "@/types/app"; import graphs from "../graphs"; import CustomOptions from "./widget/index"; + import { MetricModes } from "../data"; export default defineComponent({ name: "WidgetEdit", @@ -128,6 +134,23 @@ limitations under the License. --> function applyConfig() { dashboardStore.setConfigPanel(false); + const { metricMode } = dashboardStore.selectedGrid; + let p = {}; + if (metricMode === MetricModes.Expression) { + p = { + metrics: [], + metricTypes: [], + }; + } else { + p = { + expressions: [], + typesOfMQE: [], + }; + } + dashboardStore.selectWidget({ + ...dashboardStore.selectedGrid, + ...p, + }); dashboardStore.setConfigs(dashboardStore.selectedGrid); } @@ -188,7 +211,7 @@ limitations under the License. --> .render-chart { padding: 5px; - height: 400px; + height: 420px; width: 100%; } diff --git a/src/views/dashboard/configuration/widget/metric/Index.vue b/src/views/dashboard/configuration/widget/metric/Index.vue index c88b25be..c54cdfda 100644 --- a/src/views/dashboard/configuration/widget/metric/Index.vue +++ b/src/views/dashboard/configuration/widget/metric/Index.vue @@ -26,23 +26,50 @@ limitations under the License. --> />
{{ t("metrics") }}
-
- - + +
+ Summary + Detail +
+
+ +
+ {{ metric }} +
+
+ {{ states.subMetrics[index] }} +
+
+ + + + - + /> +
{{ states.tips[index] }}
+
{{ states.tips[index] }}
{{ t("visualization") }}
@@ -89,10 +123,13 @@ limitations under the License. --> PodsChartTypes, MetricsType, ProtocolTypes, + ExpressionResultType, + MetricModes, } from "../../../data"; import { ElMessage } from "element-plus"; import Icon from "@/components/Icon.vue"; - import { useQueryProcessor, useSourceProcessor, useGetMetricEntity } from "@/hooks/useMetricsProcessor"; + import { useQueryProcessor, useSourceProcessor } from "@/hooks/useMetricsProcessor"; + import { useExpressionsQueryProcessor, useExpressionsSourceProcessor } from "@/hooks/useExpressionsProcessor"; import { useI18n } from "vue-i18n"; import type { DashboardItem, MetricConfigOpt } from "@/types/dashboard"; import Standard from "./Standard.vue"; @@ -101,17 +138,28 @@ limitations under the License. --> const { t } = useI18n(); const emit = defineEmits(["update", "loading"]); const dashboardStore = useDashboardStore(); - const metrics = computed(() => dashboardStore.selectedGrid.metrics || []); + const isExpression = ref(dashboardStore.selectedGrid.metricMode === MetricModes.Expression ? true : false); + const metrics = computed( + () => (isExpression.value ? dashboardStore.selectedGrid.expressions : dashboardStore.selectedGrid.metrics) || [], + ); + const subMetrics = computed(() => (isExpression.value ? dashboardStore.selectedGrid.subExpressions : []) || []); + const subMetricTypes = computed(() => (isExpression.value ? dashboardStore.selectedGrid.subTypesOfMQE : []) || []); const graph = computed(() => dashboardStore.selectedGrid.graph || {}); - const metricTypes = computed(() => dashboardStore.selectedGrid.metricTypes || []); + const metricTypes = computed( + () => (isExpression.value ? dashboardStore.selectedGrid.typesOfMQE : dashboardStore.selectedGrid.metricTypes) || [], + ); const states = reactive<{ metrics: string[]; + subMetrics: string[]; + subMetricTypes: string[]; metricTypes: string[]; metricTypeList: Option[][]; isList: boolean; metricList: (Option & { type: string })[]; dashboardName: string; dashboardList: ((DashboardItem & { label: string; value: string }) | any)[]; + tips: string[]; + subTips: string[]; }>({ metrics: metrics.value.length ? metrics.value : [""], metricTypes: metricTypes.value.length ? metricTypes.value : [""], @@ -120,6 +168,10 @@ limitations under the License. --> metricList: [], dashboardName: graph.value.dashboardName, dashboardList: [{ label: "", value: "" }], + tips: [], + subTips: [], + subMetrics: subMetrics.value.length ? subMetrics.value : [""], + subMetricTypes: subMetricTypes.value.length ? subMetricTypes.value : [""], }); const currentMetricConfig = ref({ unit: "", @@ -131,6 +183,8 @@ limitations under the License. --> states.isList = ListChartTypes.includes(graph.value.type); const defaultLen = ref(states.isList ? 5 : 20); + const backupMetricConfig = ref([]); + setDashboards(); setMetricType(); @@ -158,7 +212,7 @@ limitations under the License. --> } arr = json.data.metrics; } - states.metricList = (arr || []).filter((d: { catalog: string; type: string }) => { + states.metricList = (arr || []).filter((d: { type: string }) => { if (states.isList) { if (d.type === MetricsType.REGULAR_VALUE || d.type === MetricsType.LABELED_VALUE) { return d; @@ -171,6 +225,14 @@ limitations under the License. --> return d; } }); + if (isExpression.value) { + if (states.metrics && states.metrics[0]) { + queryMetrics(); + } else { + emit("update", {}); + } + return; + } const metrics: any = states.metricList.filter((d: { value: string; type: string }) => states.metrics.includes(d.value), ); @@ -234,12 +296,22 @@ limitations under the License. --> ...dashboardStore.selectedGrid, metrics: [""], metricTypes: [""], + expressions: [""], + typesOfMQE: [""], }); states.metrics = [""]; states.metricTypes = [""]; defaultLen.value = 5; } - setMetricType(chart); + + if (isExpression.value) { + dashboardStore.selectWidget({ + ...dashboardStore.selectedGrid, + graph: chart, + }); + } else { + setMetricType(chart); + } setDashboards(chart.type); states.dashboardName = ""; defaultLen.value = 10; @@ -300,12 +372,15 @@ limitations under the License. --> if (states.isList) { return; } - const { metricConfig, metricTypes, metrics } = dashboardStore.selectedGrid; - if (!(metrics && metrics[0] && metricTypes && metricTypes[0])) { + if (isExpression.value) { + queryMetricsWithExpressions(); return; } - const catalog = await useGetMetricEntity(metrics[0], metricTypes[0]); - const params = useQueryProcessor({ ...states, metricConfig, catalog }); + const { metricConfig, metricTypes, metrics } = dashboardStore.selectedGrid; + if (!(metrics && metrics[0] && metricTypes && metricTypes[0])) { + return emit("update", {}); + } + const params = useQueryProcessor({ ...states, metricConfig }); if (!params) { emit("update", {}); return; @@ -322,6 +397,29 @@ limitations under the License. --> emit("update", source); } + async function queryMetricsWithExpressions() { + const { metricConfig, typesOfMQE, expressions } = dashboardStore.selectedGrid; + if (!(expressions && expressions[0] && typesOfMQE && typesOfMQE[0])) { + return emit("update", {}); + } + + const params = useExpressionsQueryProcessor({ ...states, metricConfig }); + if (!params) { + emit("update", {}); + return; + } + + emit("loading", true); + const json = await dashboardStore.fetchMetricValue(params); + emit("loading", false); + if (json.errors) { + ElMessage.error(json.errors); + return; + } + const source = useExpressionsSourceProcessor(json, { ...states, metricConfig }); + emit("update", source); + } + function changeDashboard(opt: any) { if (!opt[0]) { states.dashboardName = ""; @@ -339,31 +437,76 @@ limitations under the License. --> } function addMetric() { states.metrics.push(""); + states.tips.push(""); + if (isExpression.value && states.isList) { + states.subMetrics.push(""); + states.subTips.push(""); + } + if (!states.isList) { states.metricTypes.push(states.metricTypes[0]); - states.metricTypeList.push(states.metricTypeList[0]); + if (!isExpression.value) { + states.metricTypeList.push(states.metricTypeList[0]); + } return; } states.metricTypes.push(""); + if (isExpression.value && states.isList) { + states.subMetricTypes.push(""); + } } function deleteMetric(index: number) { if (states.metrics.length === 1) { states.metrics = [""]; states.metricTypes = [""]; + states.tips = [""]; + let v = {}; + if (isExpression.value) { + v = { typesOfMQE: states.metricTypes, expressions: states.metrics }; + if (states.isList) { + states.subMetrics = [""]; + states.subMetricTypes = [""]; + states.subTips = [""]; + v = { + ...v, + subTypesOfMQE: states.subMetricTypes, + subExpressions: states.subMetrics, + }; + } + } else { + v = { metricTypes: states.metricTypes, metrics: states.metrics }; + } dashboardStore.selectWidget({ ...dashboardStore.selectedGrid, - ...{ metricTypes: states.metricTypes, metrics: states.metrics }, + ...v, metricConfig: [], }); return; } states.metrics.splice(index, 1); states.metricTypes.splice(index, 1); + states.tips.splice(index, 1); const config = dashboardStore.selectedGrid.metricConfig || []; const metricConfig = config[index] ? config.splice(index, 1) : config; + let p = {}; + if (isExpression.value) { + p = { typesOfMQE: states.metricTypes, expressions: states.metrics }; + if (states.isList) { + states.subMetrics = [""]; + states.subMetricTypes = [""]; + states.subTips = [""]; + p = { + ...p, + subTypesOfMQE: states.subMetricTypes, + subExpressions: states.subMetrics, + }; + } + } else { + p = { metricTypes: states.metricTypes, metrics: states.metrics }; + } dashboardStore.selectWidget({ ...dashboardStore.selectedGrid, - ...{ metricTypes: states.metricTypes, metrics: states.metrics }, + ...p, metricConfig, }); } @@ -393,6 +536,66 @@ limitations under the License. --> ...dashboardStore.selectedGrid.metricConfig[index], }; } + function changeMetricMode() { + states.metrics = metrics.value.length ? metrics.value : [""]; + states.subMetrics = subMetrics.value.length ? subMetrics.value : [""]; + states.metricTypes = metricTypes.value.length ? metricTypes.value : [""]; + states.subMetricTypes = subMetricTypes.value.length ? subMetricTypes.value : [""]; + const config = dashboardStore.selectedGrid.metricConfig; + dashboardStore.selectWidget({ + ...dashboardStore.selectedGrid, + metricMode: isExpression.value ? MetricModes.Expression : MetricModes.General, + metricConfig: backupMetricConfig.value, + }); + backupMetricConfig.value = config; + queryMetrics(); + } + async function changeExpression(event: any, index: number) { + const params = (event.target.textContent || "").replace(/\s+/g, ""); + + if (params) { + const resp = await dashboardStore.getTypeOfMQE(params); + states.metrics[index] = params; + states.metricTypes[index] = resp.data.metricType.type; + states.tips[index] = resp.data.metricType.error || ""; + } else { + states.metrics[index] = params; + states.metricTypes[index] = ""; + states.tips[index] = ""; + } + + dashboardStore.selectWidget({ + ...dashboardStore.selectedGrid, + expressions: states.metrics, + typesOfMQE: states.metricTypes, + }); + if (params) { + await queryMetrics(); + } + } + async function changeSubExpression(event: any, index: number) { + const params = (event.target.textContent || "").replace(/\s+/g, ""); + + if (params) { + const resp = await dashboardStore.getTypeOfMQE(params); + states.subMetrics[index] = params; + states.subMetricTypes[index] = resp.data.metricType.type; + states.subTips[index] = resp.data.metricType.error || ""; + } else { + states.subMetrics[index] = params; + states.subMetricTypes[index] = ""; + states.subTips[index] = ""; + } + + dashboardStore.selectWidget({ + ...dashboardStore.selectedGrid, + subExpressions: states.subMetrics, + subTypesOfMQE: states.subMetricTypes, + }); + if (params) { + await queryMetrics(); + } + } diff --git a/src/views/dashboard/configuration/widget/metric/Standard.vue b/src/views/dashboard/configuration/widget/metric/Standard.vue index 1172f631..8ba76a6a 100644 --- a/src/views/dashboard/configuration/widget/metric/Standard.vue +++ b/src/views/dashboard/configuration/widget/metric/Standard.vue @@ -42,7 +42,13 @@ limitations under the License. --> " />
-
+
{{ t("labelsIndex") }} " />
-
+
{{ t("aggregation") }} import { ref, watch, computed } from "vue"; import type { PropType } from "vue"; import { useI18n } from "vue-i18n"; - import { SortOrder, CalculationOpts } from "../../../data"; + import { SortOrder, CalculationOpts, MetricModes } from "../../../data"; import { useDashboardStore } from "@/store/modules/dashboard"; import type { MetricConfigOpt } from "@/types/dashboard"; - import { ListChartTypes, ProtocolTypes } from "../../../data"; + import { ListChartTypes, ProtocolTypes, ExpressionResultType } from "../../../data"; /*global defineEmits, defineProps */ const props = defineProps({ @@ -110,24 +116,32 @@ limitations under the License. --> const { t } = useI18n(); const emit = defineEmits(["update"]); const dashboardStore = useDashboardStore(); + const isExpression = ref(dashboardStore.selectedGrid.metricMode === MetricModes.Expression); const currentMetric = ref({ ...props.currentMetricConfig, topN: props.currentMetricConfig.topN || 10, }); - const metricTypes = dashboardStore.selectedGrid.metricTypes || []; - const metricType = computed(() => (dashboardStore.selectedGrid.metricTypes || [])[props.index]); + const metricTypes = computed( + () => (isExpression.value ? dashboardStore.selectedGrid.typesOfMQE : dashboardStore.selectedGrid.metricTypes) || [], + ); + const metricType = computed(() => metricTypes.value[props.index]); const hasLabel = computed(() => { const graph = dashboardStore.selectedGrid.graph || {}; return ( ListChartTypes.includes(graph.type) || - [ProtocolTypes.ReadLabeledMetricsValues, ProtocolTypes.ReadMetricsValues].includes(metricType.value) + [ + ProtocolTypes.ReadLabeledMetricsValues, + ProtocolTypes.ReadMetricsValues, + ExpressionResultType.TIME_SERIES_VALUES, + ].includes(metricType.value) ); }); const isTopn = computed(() => [ProtocolTypes.SortMetrics, ProtocolTypes.ReadSampledRecords, ProtocolTypes.ReadRecords].includes( - metricTypes[props.index], + metricTypes.value[props.index], ), ); + const isExec = computed(() => dashboardStore.selectedGrid.metricMode === MetricModes.General); function updateConfig(index: number, param: { [key: string]: string }) { const key = Object.keys(param)[0]; if (!key) { diff --git a/src/views/dashboard/controls/Widget.vue b/src/views/dashboard/controls/Widget.vue index d52ebc96..0f36affd 100644 --- a/src/views/dashboard/controls/Widget.vue +++ b/src/views/dashboard/controls/Widget.vue @@ -59,6 +59,11 @@ limitations under the License. --> filters: data.filters || {}, relatedTrace: data.relatedTrace || {}, associate: data.associate || [], + metricMode: data.metricMode, + expressions: data.expressions || [], + typesOfMQE: data.typesOfMQE || [], + subExpressions: data.subExpressions || [], + subTypesOfMQE: data.subTypesOfMQE || [], }" :needQuery="needQuery" @click="clickHandle" @@ -76,10 +81,12 @@ limitations under the License. --> import { useSelectorStore } from "@/store/modules/selectors"; import graphs from "../graphs"; import { useI18n } from "vue-i18n"; - import { useQueryProcessor, useSourceProcessor, useGetMetricEntity } from "@/hooks/useMetricsProcessor"; + import { useQueryProcessor, useSourceProcessor } from "@/hooks/useMetricsProcessor"; + import { useExpressionsQueryProcessor, useExpressionsSourceProcessor } from "@/hooks/useExpressionsProcessor"; import { EntityType, ListChartTypes } from "../data"; import type { EventParams } from "@/types/dashboard"; import getDashboard from "@/hooks/useDashboardsSession"; + import { MetricModes } from "../data"; const props = { data: { @@ -113,10 +120,14 @@ limitations under the License. --> } async function queryMetrics() { - const metricTypes: string[] = props.data.metricTypes || []; - const metrics = props.data.metrics || []; - const catalog = await useGetMetricEntity(metrics[0], metricTypes[0]); - const params = await useQueryProcessor({ ...props.data, catalog }); + const isExpression = props.data.metricMode === MetricModes.Expression; + const params = isExpression + ? await useExpressionsQueryProcessor({ + metrics: props.data.expressions, + metricTypes: props.data.typesOfMQE, + metricConfig: props.data.metricConfig, + }) + : await useQueryProcessor({ ...props.data }); if (!params) { state.source = {}; @@ -133,7 +144,12 @@ limitations under the License. --> metricTypes: props.data.metricTypes || [], metricConfig: props.data.metricConfig || [], }; - state.source = useSourceProcessor(json, d); + const e = { + metrics: props.data.expressions || [], + metricTypes: props.data.typesOfMQE || [], + metricConfig: props.data.metricConfig || [], + }; + state.source = isExpression ? await useExpressionsSourceProcessor(json, e) : await useSourceProcessor(json, d); } function removeWidget() { @@ -169,7 +185,7 @@ limitations under the License. --> dashboardStore.selectWidget(props.data); } watch( - () => [props.data.metricTypes, props.data.metrics], + () => [props.data.metricTypes, props.data.metrics, props.data.expressions], () => { if (!dashboardStore.selectedGrid) { return; diff --git a/src/views/dashboard/data.ts b/src/views/dashboard/data.ts index 3e68f50e..5abd837f 100644 --- a/src/views/dashboard/data.ts +++ b/src/views/dashboard/data.ts @@ -56,6 +56,15 @@ export enum ProtocolTypes { ReadMetricsValues = "readMetricsValues", ReadMetricsValue = "readMetricsValue", } + +export enum ExpressionResultType { + UNKNOWN = "UNKNOWN", + SINGLE_VALUE = "SINGLE_VALUE", + TIME_SERIES_VALUES = "TIME_SERIES_VALUES", + SORTED_LIST = "SORTED_LIST", + RECORD_LIST = "RECORD_LIST", +} + export const DefaultGraphConfig: { [key: string]: any } = { Bar: { type: "Bar", @@ -322,3 +331,8 @@ export const RefreshOptions = [ { label: "Last 8 hours", value: "8", step: "HOUR" }, { label: "Last 7 days", value: "7", step: "DAY" }, ]; + +export enum MetricModes { + Expression = "Expression", + General = "General", +} diff --git a/src/views/dashboard/graphs/Bar.vue b/src/views/dashboard/graphs/Bar.vue index 302c4c69..98c2bd02 100644 --- a/src/views/dashboard/graphs/Bar.vue +++ b/src/views/dashboard/graphs/Bar.vue @@ -23,6 +23,7 @@ limitations under the License. --> import type { PropType } from "vue"; import type { BarConfig, EventParams, RelatedTrace, Filters } from "@/types/dashboard"; import useLegendProcess from "@/hooks/useLegendProcessor"; + import Legend from "./components/Legend.vue"; /*global defineProps, defineEmits */ const emits = defineEmits(["click"]); diff --git a/src/views/dashboard/graphs/EndpointList.vue b/src/views/dashboard/graphs/EndpointList.vue index 7cc95035..6781886a 100644 --- a/src/views/dashboard/graphs/EndpointList.vue +++ b/src/views/dashboard/graphs/EndpointList.vue @@ -41,6 +41,7 @@ limitations under the License. --> metrics: colMetrics, metricConfig, metricTypes, + metricMode, }" v-if="colMetrics.length" /> @@ -58,7 +59,8 @@ limitations under the License. --> import type { Endpoint } from "@/types/selector"; import { useDashboardStore } from "@/store/modules/dashboard"; import { useQueryPodsMetrics, usePodsSource } from "@/hooks/useMetricsProcessor"; - import { EntityType } from "../data"; + import { useExpressionsQueryPodsMetrics } from "@/hooks/useExpressionsProcessor"; + import { EntityType, MetricModes } from "../data"; import router from "@/router"; import getDashboard from "@/hooks/useDashboardsSession"; import type { MetricConfigOpt } from "@/types/dashboard"; @@ -75,6 +77,11 @@ limitations under the License. --> i: string; metrics: string[]; metricTypes: string[]; + metricMode: string; + expressions: string[]; + typesOfMQE: string[]; + subExpressions: string[]; + subTypesOfMQE: string[]; } & { metricConfig: MetricConfigOpt[] } >, default: () => ({ @@ -98,6 +105,7 @@ limitations under the License. --> const colMetrics = ref([]); const metricConfig = ref(props.config.metricConfig || []); const metricTypes = ref(props.config.metricTypes || []); + const metricMode = ref(props.config.metricMode); if (props.needQuery) { queryEndpoints(); @@ -116,8 +124,20 @@ limitations under the License. --> endpoints.value = resp.data.pods || []; queryEndpointMetrics(endpoints.value); } - async function queryEndpointMetrics(currentPods: Endpoint[]) { - if (!currentPods.length) { + async function queryEndpointMetrics(arr: Endpoint[]) { + if (!arr.length) { + return; + } + const currentPods = arr.map((d: Endpoint) => { + return { + id: d.id, + value: d.value, + label: d.label, + merge: d.merge, + }; + }); + if (props.config.metricMode === MetricModes.Expression) { + queryEndpointExpressions(currentPods); return; } const metrics = props.config.metrics || []; @@ -141,6 +161,32 @@ limitations under the License. --> return; } endpoints.value = currentPods; + colMetrics.value = []; + metricTypes.value = []; + metricConfig.value = []; + } + async function queryEndpointExpressions(currentPods: Endpoint[]) { + const expressions = props.config.expressions || []; + const typesOfMQE = props.config.typesOfMQE || []; + const subExpressions = props.config.subExpressions || []; + + if (expressions.length && expressions[0] && typesOfMQE.length && typesOfMQE[0]) { + const params = await useExpressionsQueryPodsMetrics( + currentPods, + { ...props.config, metricConfig: metricConfig.value || [], typesOfMQE, expressions, subExpressions }, + EntityType[2].value, + ); + endpoints.value = params.data; + colMetrics.value = params.names; + metricTypes.value = params.metricTypesArr; + metricConfig.value = params.metricConfigArr; + + return; + } + endpoints.value = currentPods; + colMetrics.value = []; + metricTypes.value = []; + metricConfig.value = []; } function clickEndpoint(scope: any) { const { dashboard } = getDashboard({ @@ -160,12 +206,19 @@ limitations under the License. --> await queryEndpoints(); } watch( - () => [...(props.config.metricTypes || []), ...(props.config.metrics || []), ...(props.config.metricConfig || [])], + () => [ + ...(props.config.metricTypes || []), + ...(props.config.metrics || []), + ...(props.config.metricConfig || []), + ...(props.config.expressions || []), + props.config.metricMode, + ], (data, old) => { if (JSON.stringify(data) === JSON.stringify(old)) { return; } metricConfig.value = props.config.metricConfig; + metricMode.value = props.config.metricMode; queryEndpointMetrics(endpoints.value); }, ); diff --git a/src/views/dashboard/graphs/InstanceList.vue b/src/views/dashboard/graphs/InstanceList.vue index 0be09631..5b754ac1 100644 --- a/src/views/dashboard/graphs/InstanceList.vue +++ b/src/views/dashboard/graphs/InstanceList.vue @@ -40,6 +40,7 @@ limitations under the License. --> metrics: colMetrics, metricConfig, metricTypes, + metricMode, }" v-if="colMetrics.length" /> @@ -87,7 +88,8 @@ limitations under the License. --> import type { InstanceListConfig } from "@/types/dashboard"; import type { Instance } from "@/types/selector"; import { useQueryPodsMetrics, usePodsSource } from "@/hooks/useMetricsProcessor"; - import { EntityType } from "../data"; + import { useExpressionsQueryPodsMetrics } from "@/hooks/useExpressionsProcessor"; + import { EntityType, MetricModes } from "../data"; import router from "@/router"; import getDashboard from "@/hooks/useDashboardsSession"; import type { MetricConfigOpt } from "@/types/dashboard"; @@ -102,6 +104,11 @@ limitations under the License. --> metrics: string[]; metricTypes: string[]; isEdit: boolean; + metricMode: string; + expressions: string[]; + typesOfMQE: string[]; + subExpressions: string[]; + subTypesOfMQE: string[]; } & { metricConfig: MetricConfigOpt[] } >, default: () => ({ @@ -126,6 +133,7 @@ limitations under the License. --> const metricConfig = ref(props.config.metricConfig || []); const metricTypes = ref(props.config.metricTypes || []); const pods = ref([]); // all instances + const metricMode = ref(props.config.metricMode); if (props.needQuery) { queryInstance(); } @@ -146,8 +154,23 @@ limitations under the License. --> queryInstanceMetrics(instances.value); } - async function queryInstanceMetrics(currentInstances: Instance[]) { - if (!currentInstances.length) { + async function queryInstanceMetrics(arr: Instance[]) { + if (!arr.length) { + return; + } + const currentInstances = arr.map((d: Instance) => { + return { + id: d.id, + value: d.value, + label: d.label, + merge: d.merge, + language: d.language, + instanceUUID: d.instanceUUID, + attributes: d.attributes, + }; + }); + if (props.config.metricMode === MetricModes.Expression) { + queryInstanceExpressions(currentInstances); return; } const metrics = props.config.metrics || []; @@ -172,6 +195,33 @@ limitations under the License. --> return; } instances.value = currentInstances; + colMetrics.value = []; + metricTypes.value = []; + metricConfig.value = []; + } + + async function queryInstanceExpressions(currentInstances: Instance[]) { + const expressions = props.config.expressions || []; + const typesOfMQE = props.config.typesOfMQE || []; + const subExpressions = props.config.subExpressions || []; + + if (expressions.length && expressions[0] && typesOfMQE.length && typesOfMQE[0]) { + const params = await useExpressionsQueryPodsMetrics( + currentInstances, + { ...props.config, metricConfig: metricConfig.value || [], typesOfMQE, expressions, subExpressions }, + EntityType[3].value, + ); + instances.value = params.data; + colMetrics.value = params.names; + metricTypes.value = params.metricTypesArr; + metricConfig.value = params.metricConfigArr; + + return; + } + instances.value = currentInstances; + colMetrics.value = []; + metricTypes.value = []; + metricConfig.value = []; } function clickInstance(scope: any) { @@ -207,12 +257,19 @@ limitations under the License. --> } watch( - () => [...(props.config.metricTypes || []), ...(props.config.metrics || []), ...(props.config.metricConfig || [])], + () => [ + ...(props.config.metricTypes || []), + ...(props.config.metrics || []), + ...(props.config.metricConfig || []), + ...(props.config.expressions || []), + props.config.metricMode, + ], (data, old) => { if (JSON.stringify(data) === JSON.stringify(old)) { return; } metricConfig.value = props.config.metricConfig; + metricMode.value = props.config.metricMode; queryInstanceMetrics(instances.value); }, ); diff --git a/src/views/dashboard/graphs/ServiceList.vue b/src/views/dashboard/graphs/ServiceList.vue index 44b1a512..31a7e1fa 100644 --- a/src/views/dashboard/graphs/ServiceList.vue +++ b/src/views/dashboard/graphs/ServiceList.vue @@ -52,6 +52,7 @@ limitations under the License. --> metrics: colMetrics, metricConfig, metricTypes, + metricMode, }" v-if="colMetrics.length" /> @@ -80,7 +81,8 @@ limitations under the License. --> import { useAppStoreWithOut } from "@/store/modules/app"; import type { Service } from "@/types/selector"; import { useQueryPodsMetrics, usePodsSource } from "@/hooks/useMetricsProcessor"; - import { EntityType } from "../data"; + import { useExpressionsQueryPodsMetrics } from "@/hooks/useExpressionsProcessor"; + import { EntityType, MetricModes } from "../data"; import router from "@/router"; import getDashboard from "@/hooks/useDashboardsSession"; import type { MetricConfigOpt } from "@/types/dashboard"; @@ -100,6 +102,11 @@ limitations under the License. --> isEdit: boolean; names: string[]; metricConfig: MetricConfigOpt[]; + metricMode: string; + expressions: string[]; + typesOfMQE: string[]; + subExpressions: string[]; + subTypesOfMQE: string[]; } >, default: () => ({ dashboardName: "", fontSize: 12 }), @@ -119,6 +126,7 @@ limitations under the License. --> const sortServices = ref<(Service & { merge: boolean })[]>([]); const metricConfig = ref(props.config.metricConfig || []); const metricTypes = ref(props.config.metricTypes || []); + const metricMode = ref(props.config.metricMode); queryServices(); @@ -187,8 +195,23 @@ limitations under the License. --> router.push(path); } - async function queryServiceMetrics(currentServices: Service[]) { - if (!currentServices.length) { + async function queryServiceMetrics(arr: Service[]) { + if (!arr.length) { + return; + } + const currentServices = arr.map((d: Service) => { + return { + id: d.id, + value: d.value, + label: d.label, + layers: d.layers, + group: d.group, + normal: d.normal, + merge: d.merge, + }; + }); + if (props.config.metricMode === MetricModes.Expression) { + queryServiceExpressions(currentServices); return; } const metrics = props.config.metrics || []; @@ -211,6 +234,7 @@ limitations under the License. --> ...props.config, metricConfig: metricConfig.value || [], }); + services.value = data; colMetrics.value = names; metricTypes.value = metricTypesArr; @@ -219,6 +243,32 @@ limitations under the License. --> return; } services.value = currentServices; + colMetrics.value = []; + colMetrics.value = []; + metricTypes.value = []; + metricConfig.value = []; + } + async function queryServiceExpressions(currentServices: Service[]) { + const expressions = props.config.expressions || []; + const typesOfMQE = props.config.typesOfMQE || []; + const subExpressions = props.config.subExpressions || []; + + if (expressions.length && expressions[0] && typesOfMQE.length && typesOfMQE[0]) { + const params = await useExpressionsQueryPodsMetrics( + currentServices, + { ...props.config, metricConfig: metricConfig.value || [], typesOfMQE, expressions, subExpressions }, + EntityType[0].value, + ); + services.value = params.data; + colMetrics.value = params.names; + metricTypes.value = params.metricTypesArr; + metricConfig.value = params.metricConfigArr; + return; + } + services.value = currentServices; + colMetrics.value = []; + metricTypes.value = []; + metricConfig.value = []; } function objectSpanMethod(param: any): any { if (!props.config.showGroup) { @@ -251,15 +301,24 @@ limitations under the License. --> } watch( - () => [...(props.config.metricTypes || []), ...(props.config.metrics || []), ...(props.config.metricConfig || [])], + () => [ + ...(props.config.metricTypes || []), + ...(props.config.metrics || []), + ...(props.config.metricConfig || []), + ...(props.config.expressions || []), + ...(props.config.subExpressions || []), + props.config.metricMode, + ], (data, old) => { if (JSON.stringify(data) === JSON.stringify(old)) { return; } metricConfig.value = props.config.metricConfig; + metricMode.value = props.config.metricMode; queryServiceMetrics(services.value); }, ); + watch( () => appStore.durationTime, () => { diff --git a/src/views/dashboard/graphs/TopList.vue b/src/views/dashboard/graphs/TopList.vue index 1da016ed..ed4fbe9b 100644 --- a/src/views/dashboard/graphs/TopList.vue +++ b/src/views/dashboard/graphs/TopList.vue @@ -20,7 +20,7 @@ limitations under the License. -->
{{ i.value }} - {{ i.name }} + {{ i.name || i.id }}
diff --git a/src/views/dashboard/graphs/components/ColumnGraph.vue b/src/views/dashboard/graphs/components/ColumnGraph.vue index 06a4bef3..43299417 100644 --- a/src/views/dashboard/graphs/components/ColumnGraph.vue +++ b/src/views/dashboard/graphs/components/ColumnGraph.vue @@ -23,7 +23,7 @@ limitations under the License. -->