From aeea5b62415120ece23c8ab2a9da9ef8e63fc915 Mon Sep 17 00:00:00 2001 From: Fine Date: Sat, 3 Jun 2023 10:41:28 +0800 Subject: [PATCH] feat: add expressions for service list --- src/hooks/useExpressionsProcessor.ts | 140 +++++++++++++++++- src/hooks/useMetricsProcessor.ts | 2 +- src/views/dashboard/Widget.vue | 3 + src/views/dashboard/configuration/Widget.vue | 3 + .../configuration/widget/metric/Standard.vue | 11 +- src/views/dashboard/controls/Widget.vue | 3 + src/views/dashboard/graphs/ServiceList.vue | 35 ++++- 7 files changed, 188 insertions(+), 9 deletions(-) diff --git a/src/hooks/useExpressionsProcessor.ts b/src/hooks/useExpressionsProcessor.ts index c8d0c23e..023ef055 100644 --- a/src/hooks/useExpressionsProcessor.ts +++ b/src/hooks/useExpressionsProcessor.ts @@ -14,13 +14,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { RespFields } from "./data"; +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])) { @@ -117,11 +119,13 @@ export function useExpressionsSourceProcessor( 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, "")); + const labelsIdx = (c.labelsIndex || "").split(",").map((item: string) => item.replace(/^\s*|\s*$/g, "")); for (const item of results) { - const values = item.values.map((d: { value: unknown }) => Number(d.value)) || []; - const index = Number(item.metric.labels[0].value); - if (labels[index]) { - source[labels[index]] = values; + const values = item.values.map((d: { value: unknown }) => d.value) || []; + const index = item.metric.labels[0].value; + const indexNum = labelsIdx.findIndex((d: string) => d === index); + if (labels[indexNum] && indexNum > -1) { + source[labels[indexNum]] = values; } else { source[index] = values; } @@ -138,3 +142,129 @@ export function useExpressionsSourceProcessor( return source; } + +export async function useExpressionsQueryPodsMetrics( + pods: Array<(Instance | Endpoint | Service) & Indexable>, + config: { + expressions: string[]; + typesOfMQE: string[]; + metricConfig: MetricConfigOpt[]; + }, + scope: string, +) { + function expressionsGraphqlPods() { + const metricTypes = (config.typesOfMQE || []).filter((m: string) => m); + if (!metricTypes.length) { + return; + } + const metrics = (config.expressions || []).filter((m: string) => m); + 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, + }; + const f = metrics.map((name: string, idx: number) => { + variables.push(`$expression${index}${idx}: String!`, `$entity${index}${idx}: Entity!`); + conditions[`entity${index}${idx}`] = entity; + conditions[`expression${index}${idx}`] = name; + + return `expression${index}${idx}: execExpression(expression: $expression${index}${idx}, entity: $entity${index}${idx}, 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: Instance & Indexable, idx: number) => { + config.expressions.map((exp: string, index: number) => { + const c: any = (config.metricConfig && config.metricConfig[index]) || {}; + const k = "expression" + idx + index; + const results = (resp.data[k] && resp.data[k].results) || []; + if (config.typesOfMQE[index] === ExpressionResultType.SINGLE_VALUE) { + const name = results[0].metric.name || ""; + d[name] = results[0].values[0].value; + if (idx === 0) { + names.push(name); + metricConfigArr.push(c); + metricTypesArr.push(config.typesOfMQE[index]); + } + } + if (config.typesOfMQE[index] === ExpressionResultType.TIME_SERIES_VALUES) { + 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.name || ""; + const values = results[i].values.map((d: { value: unknown }) => d.value); + const indexNum = labelsIdx.findIndex((d: string) => d === results[i].metric.labels[0].value); + if (labels[indexNum] && indexNum > -1) { + name = labels[indexNum]; + } + if (!d[name]) { + d[name] = {}; + } + if ([Calculations.Average, Calculations.ApdexAvg, Calculations.PercentageAvg].includes(c.calculation)) { + d[name]["avg"] = calculateExp(results[i].values, c); + } + d[name]["values"] = values; + if (idx === 0) { + names.push(name); + metricConfigArr.push({ ...c, index: i }); + metricTypesArr.push(config.typesOfMQE[index]); + } + } + return; + } + const name = results[0].metric.name || ""; + d[name] = {}; + if ([Calculations.Average, Calculations.ApdexAvg, Calculations.PercentageAvg].includes(c.calculation)) { + d[name]["avg"] = calculateExp(results[0].values, c); + } + d[name]["values"] = results[0].values.map((d: { value: number }) => d.value); + if (idx === 0) { + 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/useMetricsProcessor.ts b/src/hooks/useMetricsProcessor.ts index e4ad5378..ee083b76 100644 --- a/src/hooks/useMetricsProcessor.ts +++ b/src/hooks/useMetricsProcessor.ts @@ -367,7 +367,7 @@ export function calculateExp( 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: diff --git a/src/views/dashboard/Widget.vue b/src/views/dashboard/Widget.vue index 7c9216b4..c0980d70 100644 --- a/src/views/dashboard/Widget.vue +++ b/src/views/dashboard/Widget.vue @@ -35,6 +35,9 @@ limitations under the License. --> metrics: config.metrics, metricTypes: config.metricTypes, metricConfig: config.metricConfig, + metricMode: config.metricMode, + expressions: config.expressions || [], + typesOfMQE: config.typesOfMQE || [], }" :needQuery="true" /> diff --git a/src/views/dashboard/configuration/Widget.vue b/src/views/dashboard/configuration/Widget.vue index 2a2ce3d8..533db63f 100644 --- a/src/views/dashboard/configuration/Widget.vue +++ b/src/views/dashboard/configuration/Widget.vue @@ -38,6 +38,9 @@ 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 || [], }" :needQuery="true" /> diff --git a/src/views/dashboard/configuration/widget/metric/Standard.vue b/src/views/dashboard/configuration/widget/metric/Standard.vue index 26e75b99..38a060fd 100644 --- a/src/views/dashboard/configuration/widget/metric/Standard.vue +++ b/src/views/dashboard/configuration/widget/metric/Standard.vue @@ -42,7 +42,10 @@ limitations under the License. --> " /> -
+
{{ t("labelsIndex") }} " />
-
+
{{ t("aggregation") }} metricTypes.value[props.index], ), ); + const isExec = computed(() => { + const graph = dashboardStore.selectedGrid.graph || {}; + return dashboardStore.selectedGrid.metricMode !== "Expression" || ListChartTypes.includes(graph.type); + }); 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 60698d5f..92655789 100644 --- a/src/views/dashboard/controls/Widget.vue +++ b/src/views/dashboard/controls/Widget.vue @@ -59,6 +59,9 @@ limitations under the License. --> filters: data.filters || {}, relatedTrace: data.relatedTrace || {}, associate: data.associate || [], + metricMode: data.metricMode, + expressions: data.expressions || [], + typesOfMQE: data.typesOfMQE || [], }" :needQuery="needQuery" @click="clickHandle" diff --git a/src/views/dashboard/graphs/ServiceList.vue b/src/views/dashboard/graphs/ServiceList.vue index 44b1a512..66d1808d 100644 --- a/src/views/dashboard/graphs/ServiceList.vue +++ b/src/views/dashboard/graphs/ServiceList.vue @@ -80,6 +80,7 @@ limitations under the License. --> import { useAppStoreWithOut } from "@/store/modules/app"; import type { Service } from "@/types/selector"; import { useQueryPodsMetrics, usePodsSource } from "@/hooks/useMetricsProcessor"; + import { useExpressionsQueryPodsMetrics } from "@/hooks/useExpressionsProcessor"; import { EntityType } from "../data"; import router from "@/router"; import getDashboard from "@/hooks/useDashboardsSession"; @@ -100,6 +101,9 @@ limitations under the License. --> isEdit: boolean; names: string[]; metricConfig: MetricConfigOpt[]; + metricMode: string; + expressions: string[]; + typesOfMQE: string[]; } >, default: () => ({ dashboardName: "", fontSize: 12 }), @@ -191,6 +195,10 @@ limitations under the License. --> if (!currentServices.length) { return; } + if (props.config.metricMode === "Expression") { + queryServiceExpressions(currentServices); + return; + } const metrics = props.config.metrics || []; const types = props.config.metricTypes || []; @@ -220,6 +228,25 @@ limitations under the License. --> } services.value = currentServices; } + async function queryServiceExpressions(currentServices: Service[]) { + const expressions = props.config.expressions || []; + const typesOfMQE = props.config.typesOfMQE || []; + + if (expressions.length && expressions[0] && typesOfMQE.length && typesOfMQE[0]) { + const params = await useExpressionsQueryPodsMetrics( + currentServices, + { ...props.config, metricConfig: metricConfig.value || [], typesOfMQE, expressions }, + EntityType[0].value, + ); + services.value = params.data; + colMetrics.value = params.names; + metricTypes.value = params.metricTypesArr; + metricConfig.value = params.metricConfigArr; + + return; + } + services.value = currentServices; + } function objectSpanMethod(param: any): any { if (!props.config.showGroup) { return; @@ -251,7 +278,12 @@ 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 || []), + ], (data, old) => { if (JSON.stringify(data) === JSON.stringify(old)) { return; @@ -260,6 +292,7 @@ limitations under the License. --> queryServiceMetrics(services.value); }, ); + watch( () => appStore.durationTime, () => {