Compare commits

...

11 Commits

Author SHA1 Message Date
dependabot[bot]
0c2cfa5630
build(deps-dev): bump vite from 6.2.6 to 6.3.4 (#468) 2025-05-01 16:42:57 +08:00
Fine0830
5e6e5aa737
Fix: correct the same labels for metrics (#467) 2025-04-24 11:58:29 +08:00
Fine0830
a4cd265d45
refactor: use the Fetch API to instead of Axios (#466) 2025-04-22 11:41:29 +08:00
peachisai
0ef6b57cae
Add the Flink icon and descriptions (#465) 2025-04-17 19:27:58 +08:00
Fine0830
687ae07bb0
feat: enhance the trace table graph for multiple refs (#464) 2025-04-14 21:12:48 +08:00
dependabot[bot]
0775bf0034
build(deps-dev): bump vite from 6.2.5 to 6.2.6 (#463) 2025-04-12 10:39:21 +08:00
Fine0830
5c322d960f
feat: enhance the trace tree to support displaying multiple refs of spans and distinguishing different parents. (#462) 2025-04-08 20:44:36 +08:00
dependabot[bot]
df2d07f508
build(deps-dev): bump vite from 6.2.4 to 6.2.5 (#461) 2025-04-07 09:44:10 +08:00
dependabot[bot]
105450071e
build(deps-dev): bump vite from 6.2.3 to 6.2.4 (#460)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 6.2.3 to 6.2.4.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v6.2.4/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v6.2.4/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-01 07:32:25 +08:00
Fine0830
39b4626317
feat: enhance trace list graph (#459) 2025-03-28 10:34:01 +08:00
dependabot[bot]
0ea8335fee
build(deps-dev): bump vite from 6.1.1 to 6.2.3 (#458) 2025-03-26 09:39:39 +08:00
48 changed files with 1425 additions and 1088 deletions

553
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -18,7 +18,6 @@
"check-components-types": "if (! git diff --quiet -U0 ./src/types); then echo 'type files are not updated correctly'; git diff -U0 ./src/types; exit 1; fi"
},
"dependencies": {
"axios": "^1.8.2",
"d3": "^7.3.0",
"d3-flame-graph": "^4.1.3",
"d3-tip": "^0.9.1",
@ -72,7 +71,7 @@
"typescript": "^5.7.3",
"unplugin-auto-import": "^0.18.2",
"unplugin-vue-components": "^0.27.3",
"vite": "^6.1.0",
"vite": "^6.3.4",
"vite-plugin-monaco-editor": "^1.1.0",
"vite-plugin-svg-icons": "^2.0.1",
"vitest": "^3.0.5",

View File

@ -0,0 +1,38 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<svg width="2400" height="2400" viewBox="0 0 200 100" version="1.1" xmlns="http://www.w3.org/2000/svg">
<defs>
<marker id="arrowhead" markerWidth="8" markerHeight="8" refX="2" refY="2.5" orient="auto">
<polygon points="0 0, 3 2.5, 0 5" fill="white" />
</marker>
</defs>
<line x1="0" y1="20" x2="42" y2="20" stroke="white" stroke-width="7" marker-end="url(#arrowhead)" />
<line x1="0" y1="50" x2="42" y2="50" stroke="white" stroke-width="7" marker-end="url(#arrowhead)" />
<line x1="0" y1="80" x2="42" y2="80" stroke="white" stroke-width="7" marker-end="url(#arrowhead)" />
<line x1="49" y1="10" x2="139" y2="10" stroke="white" stroke-width="7" />
<line x1="49" y1="90" x2="139" y2="90" stroke="white" stroke-width="7" />
<line x1="49" y1="10" x2="50" y2="90" stroke="white" stroke-width="7" />
<ellipse cx="140" cy="50" rx="10" ry="40" fill="none" stroke="white" stroke-width="7" />
<line x1="147" y1="20" x2="190" y2="20" stroke="white" stroke-width="7" marker-end="url(#arrowhead)" />
<line x1="149" y1="50" x2="190" y2="50" stroke="white" stroke-width="7" marker-end="url(#arrowhead)" />
<line x1="147" y1="80" x2="190" y2="80" stroke="white" stroke-width="7" marker-end="url(#arrowhead)" />
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

75
src/graphql/base.ts Normal file
View File

@ -0,0 +1,75 @@
/*
* Licensed to 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. Apache Software Foundation (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.
*/
const Timeout = 2 * 60 * 1000;
export let globalAbortController = new AbortController();
export function abortRequestsAndUpdate() {
globalAbortController.abort(`Request timeout ${Timeout}ms`);
globalAbortController = new AbortController();
}
class HTTPError extends Error {
response;
constructor(response: Response, detailText = "") {
super(detailText || response.statusText);
this.name = "HTTPError";
this.response = response;
}
}
const BasePath = `/graphql`;
export async function httpQuery({
path = "",
method = "GET",
json,
headers = {},
}: {
path?: string;
method: string;
json: unknown;
headers: Recordable;
}) {
const timeoutId = setTimeout(() => {
abortRequestsAndUpdate();
}, Timeout);
const url = `${BasePath}${path}`;
const response: Response = await fetch(url, {
method,
headers: {
"Content-Type": "application/json",
accept: "application/json",
...headers,
},
body: JSON.stringify(json),
signal: globalAbortController.signal,
})
.catch((error) => {
throw new HTTPError(error);
})
.finally(() => {
clearTimeout(timeoutId);
});
if (response.ok) {
return response.json();
} else {
console.error(new HTTPError(response));
}
}

View File

@ -14,20 +14,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { AxiosResponse } from "axios";
import axios from "axios";
import { cancelToken } from "@/utils/cancelToken";
import { httpQuery } from "./base";
async function query(param: { queryStr: string; conditions: { [key: string]: unknown } }) {
const res: AxiosResponse = await axios.post(
"/graphql",
{ query: param.queryStr, variables: { ...param.conditions } },
{ cancelToken: cancelToken() },
);
if (res.data.errors) {
res.data.errors = res.data.errors.map((e: { message: string }) => e.message).join(" ");
async function fetchQuery(param: { queryStr: string; conditions: { [key: string]: unknown } }) {
const response = await httpQuery({
method: "post",
json: { query: param.queryStr, variables: { ...param.conditions } },
headers: {},
});
if (response.errors) {
response.errors = response.errors.map((e: { message: string }) => e.message).join(" ");
}
return res;
return response;
}
export default query;
export default fetchQuery;

View File

@ -14,22 +14,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export const TypeOfMetrics = {
variable: "$name: String!",
query: `typeOfMetrics(name: $name)`,
};
export const listMetrics = {
variable: "$regex: String",
query: `
metrics: listMetrics(regex: $regex) {
value: name
label: name
type
catalog
}
`,
};
export const getAllTemplates = {
query: `

View File

@ -14,9 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { AxiosPromise, AxiosResponse } from "axios";
import axios from "axios";
import { cancelToken } from "@/utils/cancelToken";
import { httpQuery } from "./base";
import * as app from "./query/app";
import * as selector from "./query/selector";
import * as dashboard from "./query/dashboard";
@ -45,30 +43,24 @@ const query: { [key: string]: string } = {
...asyncProfile,
};
class Graphql {
private queryData = "";
public query(queryData: string) {
this.queryData = queryData;
queryData = "";
query(data: string) {
this.queryData = data;
return this;
}
public params(variablesData: unknown): AxiosPromise<void> {
return axios
.post(
"/graphql",
{
query: query[this.queryData],
variables: variablesData,
},
{ cancelToken: cancelToken() },
)
.then((res: AxiosResponse) => {
if (res.data.errors) {
res.data.errors = res.data.errors.map((e: { message: string }) => e.message).join(" ");
}
return res;
})
.catch((err: Error) => {
throw err;
});
async params(variables: unknown) {
const response = await httpQuery({
method: "post",
headers: {},
json: {
query: query[this.queryData],
variables,
},
});
if (response.errors) {
response.errors = response.errors.map((e: { message: string }) => e.message).join(" ");
}
return response;
}
}

View File

@ -14,18 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
TypeOfMetrics,
listMetrics,
getAllTemplates,
addTemplate,
changeTemplate,
deleteTemplate,
} from "../fragments/dashboard";
export const queryTypeOfMetrics = `query typeOfMetrics(${TypeOfMetrics.variable}) {${TypeOfMetrics.query}}`;
export const queryMetrics = `query queryData(${listMetrics.variable}) {${listMetrics.query}}`;
import { getAllTemplates, addTemplate, changeTemplate, deleteTemplate } from "../fragments/dashboard";
export const addNewTemplate = `mutation template(${addTemplate.variable}) {${addTemplate.query}}`;

View File

@ -109,12 +109,12 @@ export async function useDashboardQueryProcessor(configList: Indexable[]) {
return { source: {}, tips: [], typesOfMQE: [] };
}
const tips: string[] = [];
const source: { [key: string]: unknown } = {};
const source: Indexable<unknown> = {};
const keys = Object.keys(resp.data);
const typesOfMQE: string[] = [];
for (let i = 0; i < config.metrics.length; i++) {
const c: MetricConfigOpt = (config.metricConfig && config.metricConfig[i]) || {};
const metricConfig: MetricConfigOpt = (config.metricConfig && config.metricConfig[i]) || {};
const obj = resp.data[keys[i]] || {};
const results = obj.results || [];
const name = config.metrics[i];
@ -125,15 +125,15 @@ export async function useDashboardQueryProcessor(configList: Indexable[]) {
if (!obj.error) {
if ([ExpressionResultType.SINGLE_VALUE, ExpressionResultType.TIME_SERIES_VALUES].includes(type)) {
for (const item of results) {
const label =
let label =
item.metric &&
item.metric.labels.map((d: { key: string; value: string }) => `${d.key}=${d.value}`).join(",");
const values = item.values.map((d: { value: unknown }) => d.value) || [];
if (results.length === 1) {
source[label || c.label || name] = values;
} else {
source[label] = values;
// If the metrics label does not exist, use the configuration label or expression
label = label ? `${metricConfig.label || name}, ${label}` : metricConfig.label || name;
}
source[label] = values;
}
}
if (([ExpressionResultType.RECORD_LIST, ExpressionResultType.SORTED_LIST] as string[]).includes(type)) {
@ -148,7 +148,7 @@ export async function useDashboardQueryProcessor(configList: Indexable[]) {
const appStore = useAppStoreWithOut();
const variables: string[] = [`$duration: Duration!`];
let fragments = "";
let conditions: Recordable = {
let conditions: Recordable<unknown> = {
duration: appStore.durationTime,
};
for (let i = 0; i < configArr.length; i++) {
@ -465,7 +465,7 @@ export function useQueryTopologyExpressionsProcessor(metrics: string[], instance
return { queryStr, conditions };
}
function handleExpressionValues(partMetrics: string[], resp: { [key: string]: any }) {
const obj: any = {};
const obj: Indexable = {};
for (let idx = 0; idx < instances.length; idx++) {
for (let index = 0; index < partMetrics.length; index++) {
const k = "expression" + idx + index;

View File

@ -136,6 +136,12 @@ const titles = {
"Cilium is a CNI plugin for Kubernetes that provides eBPF-based networking, security, and load balancing.",
cilium_service: "Cilium Service",
cilium_service_desc: "Observe Service status and resources from Cilium Hubble.",
data_processing_engine: "Data Processing Engine",
data_processing_engine_desc:
"A data processing engine is a system designed to efficiently process, transform, and analyze large-scale data in real time or batch mode.",
data_processing_engine_flink: "Flink",
data_processing_engine_flink_desc:
"Apache Flink is a framework and distributed processing engine for stateful computations over unbounded and bounded data streams. Flink has been designed to run in all common cluster environments, perform computations at in-memory speed and at any scale.",
};
export default titles;

View File

@ -137,6 +137,12 @@ const titles = {
"Cilium es un complemento CNI para Kubernetes que proporciona redes, seguridad y equilibrio de carga basados en eBPF.",
cilium_service: "Cilium Service",
cilium_service_desc: "Observe el estado del servicio y los recursos de Cilium Hubble.",
data_processing_engine: "Data Processing Engine",
data_processing_engine_desc:
"A data processing engine is a system designed to efficiently process, transform, and analyze large-scale data in real time or batch mode.",
data_processing_engine_flink: "Flink",
data_processing_engine_flink_desc:
"Apache Flink is a framework and distributed processing engine for stateful computations over unbounded and bounded data streams. Flink has been designed to run in all common cluster environments, perform computations at in-memory speed and at any scale.",
};
export default titles;

View File

@ -119,6 +119,11 @@ const titles = {
cilium_desc: "Cilium是Kubernetes上的CNI插件提供基于eBPF的网络、安全和负载均衡。",
cilium_service: "Cilium服务",
cilium_service_desc: "通过Cilium Hubble收集的遥测数据观察服务。",
data_processing_engine: "数据处理引擎",
data_processing_engine_desc: "数据处理引擎是一个用于高效地在实时或批处理模式下处理、转换和分析大规模数据的系统。",
data_processing_engine_flink: "Flink",
data_processing_engine_flink_desc:
"Apache Flink 是一个框架和分布式处理引擎用于在无边界和有边界数据流上进行有状态的计算。Flink 能在所有常见集群环境中运行,并能以内存速度和任意规模进行计算。",
};
export default titles;

View File

@ -37,16 +37,8 @@ const router = createRouter({
routes,
});
(window as any).axiosCancel = [];
router.beforeEach((to, from, next) => {
router.beforeEach((to, _, next) => {
// const token = window.localStorage.getItem("skywalking-authority");
if ((window as any).axiosCancel.length !== 0) {
for (const func of (window as any).axiosCancel) {
setTimeout(func(), 0);
}
(window as any).axiosCancel = [];
}
if (to.path === "/") {
let defaultPath = "";

View File

@ -17,7 +17,6 @@
import { defineStore } from "pinia";
import { store } from "@/store";
import graphql from "@/graphql";
import type { AxiosResponse } from "axios";
import type { Alarm } from "@/types/alarm";
import { useAppStoreWithOut } from "@/store/modules/app";
@ -37,30 +36,22 @@ export const alarmStore = defineStore({
actions: {
async getAlarms(params: Recordable) {
this.loading = true;
const res: AxiosResponse = await graphql.query("queryAlarms").params(params);
const res = await graphql.query("queryAlarms").params(params);
this.loading = false;
if (res.data.errors) {
return res.data;
if (res.errors) {
return res;
}
if (res.data.data.getAlarm.items) {
this.alarms = res.data.data.getAlarm.items;
this.total = res.data.data.getAlarm.total;
if (res.data.getAlarm.items) {
this.alarms = res.data.getAlarm.items;
this.total = res.data.getAlarm.total;
}
return res.data;
},
async getAlarmTagKeys() {
const res: AxiosResponse = await graphql
.query("queryAlarmTagKeys")
.params({ duration: useAppStoreWithOut().durationTime });
return res.data;
return await graphql.query("queryAlarmTagKeys").params({ duration: useAppStoreWithOut().durationTime });
},
async getAlarmTagValues(tagKey: string) {
const res: AxiosResponse = await graphql
.query("queryAlarmTagValues")
.params({ tagKey, duration: useAppStoreWithOut().durationTime });
return res.data;
return await graphql.query("queryAlarmTagValues").params({ tagKey, duration: useAppStoreWithOut().durationTime });
},
},
});

View File

@ -19,7 +19,6 @@ import { store } from "@/store";
import graphql from "@/graphql";
import type { Duration, DurationTime } from "@/types/app";
import getLocalTime from "@/utils/localtime";
import type { AxiosResponse } from "axios";
import dateFormatStep, { dateFormatTime } from "@/utils/dateFormat";
import { TimeType } from "@/constants/data";
import type { MenuOptions, SubItem } from "@/types/app";
@ -118,12 +117,6 @@ export const appStore = defineStore({
actions: {
setDuration(data: Duration): void {
this.durationRow = data;
if ((window as any).axiosCancel.length !== 0) {
for (const event of (window as any).axiosCancel) {
setTimeout(event(), 0);
}
(window as any).axiosCancel = [];
}
this.runEventStack();
},
updateDurationRow(data: Duration) {
@ -185,11 +178,11 @@ export const appStore = defineStore({
});
},
async queryOAPTimeInfo() {
const res: AxiosResponse = await graphql.query("queryOAPTimeInfo").params({});
if (res.data.errors) {
const res = await graphql.query("queryOAPTimeInfo").params({});
if (res.errors) {
this.utc = -(new Date().getTimezoneOffset() / 60) + ":0";
} else {
this.utc = res.data.data.getTimeInfo.timezone / 100 + ":0";
this.utc = res.data.getTimeInfo.timezone / 100 + ":0";
}
const utcArr = this.utc.split(":");
this.utcHour = isNaN(Number(utcArr[0])) ? 0 : Number(utcArr[0]);
@ -197,21 +190,21 @@ export const appStore = defineStore({
return res.data;
},
async fetchVersion(): Promise<void> {
const res: AxiosResponse = await graphql.query("queryOAPVersion").params({});
if (res.data.errors) {
return res.data;
async fetchVersion() {
const res = await graphql.query("queryOAPVersion").params({});
if (res.errors) {
return res;
}
this.version = res.data.data.version;
this.version = res.data.version;
return res.data;
},
async queryMenuItems() {
const res: AxiosResponse = await graphql.query("queryMenuItems").params({});
if (res.data.errors) {
return res.data;
const res = await graphql.query("queryMenuItems").params({});
if (res.errors) {
return res;
}
return res.data.data;
return res.data;
},
setReloadTimer(timer: IntervalHandle) {
this.reloadTimer = timer;

View File

@ -25,7 +25,6 @@ import { store } from "@/store";
import graphql from "@/graphql";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useSelectorStore } from "@/store/modules/selectors";
import type { AxiosResponse } from "axios";
import type { Instance } from "@/types/selector";
interface AsyncProfilingState {
@ -59,79 +58,77 @@ export const asyncProfilingStore = defineStore({
async getTaskList() {
const selectorStore = useSelectorStore();
this.loadingTasks = true;
const res: AxiosResponse = await graphql.query("getAsyncTaskList").params({
const response = await graphql.query("getAsyncTaskList").params({
request: {
serviceId: selectorStore.currentService.id,
limit: 10000,
},
});
this.loadingTasks = false;
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
this.taskList = res.data.data.asyncTaskList.tasks || [];
this.taskList = response.data.asyncTaskList.tasks || [];
this.selectedTask = this.taskList[0] || {};
this.setAnalyzeTrees([]);
this.setSelectedTask(this.selectedTask);
if (!this.taskList.length) {
return res.data;
return response;
}
return res.data;
return response;
},
async getTaskLogs(param: { taskID: string }) {
const res: AxiosResponse = await graphql.query("getAsyncProfileTaskProcess").params(param);
const response = await graphql.query("getAsyncProfileTaskProcess").params(param);
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
this.taskProgress = res.data.data.taskProgress;
return res.data;
this.taskProgress = response.data.taskProgress;
return response;
},
async getServiceInstances(param: { serviceId: string; isRelation: boolean }): Promise<Nullable<AxiosResponse>> {
async getServiceInstances(param: { serviceId: string; isRelation: boolean }) {
if (!param.serviceId) {
return null;
}
const res: AxiosResponse = await graphql.query("queryInstances").params({
const response = await graphql.query("queryInstances").params({
serviceId: param.serviceId,
duration: useAppStoreWithOut().durationTime,
});
if (!res.data.errors) {
this.instances = (res.data.data.pods || []).map((d: Instance) => {
if (!response.errors) {
this.instances = (response.data.pods || []).map((d: Instance) => {
d.value = d.id || "";
return d;
});
}
return res.data;
return response;
},
async createTask(param: AsyncProfileTaskCreationRequest) {
const res: AxiosResponse = await graphql
.query("saveAsyncProfileTask")
.params({ asyncProfilerTaskCreationRequest: param });
const response = await graphql.query("saveAsyncProfileTask").params({ asyncProfilerTaskCreationRequest: param });
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
this.getTaskList();
return res.data;
return response;
},
async getAsyncProfilingAnalyze(params: { taskId: string; instanceIds: Array<string>; eventType: string }) {
if (!params.instanceIds.length) {
return new Promise((resolve) => resolve({}));
}
this.loadingTree = true;
const res: AxiosResponse = await graphql.query("getAsyncProfileAnalyze").params({ request: params });
const response = await graphql.query("getAsyncProfileAnalyze").params({ request: params });
this.loadingTree = false;
if (res.data.errors) {
if (response.errors) {
this.analyzeTrees = [];
return res.data;
return response;
}
const { analysisResult } = res.data.data;
const { analysisResult } = response.data;
if (!analysisResult) {
this.analyzeTrees = [];
return res.data;
return response;
}
this.analyzeTrees = [analysisResult.tree];
return res.data;
return response;
},
},
});

View File

@ -21,7 +21,6 @@ import type { Instance } from "@/types/selector";
import { store } from "@/store";
import graphql from "@/graphql";
import type { MonitorInstance, MonitorProcess } from "@/types/continous-profiling";
import type { AxiosResponse } from "axios";
import { dateFormat } from "@/utils/dateFormat";
interface ContinousProfilingState {
@ -84,37 +83,37 @@ export const continousProfilingStore = defineStore({
checkItems: CheckItems[];
}[],
) {
const res: AxiosResponse = await graphql.query("editStrategy").params({
const response = await graphql.query("editStrategy").params({
request: {
serviceId,
targets,
},
});
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
return res.data;
return response;
},
async getStrategyList(params: { serviceId: string }) {
if (!params.serviceId) {
return new Promise((resolve) => resolve({}));
}
this.policyLoading = true;
const res: AxiosResponse = await graphql.query("getStrategyList").params(params);
const response = await graphql.query("getStrategyList").params(params);
this.policyLoading = false;
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
const list = res.data.data.strategyList || [];
const list = response.data.strategyList || [];
if (!list.length) {
this.taskList = [];
this.instances = [];
this.instance = null;
}
const arr = list.length ? res.data.data.strategyList : [{ type: "", checkItems: [{ type: "" }] }];
const arr = list.length ? response.data.strategyList : [{ type: "", checkItems: [{ type: "" }] }];
this.strategyList = arr.map((d: StrategyItem, index: number) => {
return {
...d,
@ -123,25 +122,25 @@ export const continousProfilingStore = defineStore({
});
this.setSelectedStrategy(this.strategyList[0]);
if (!this.selectedStrategy.type) {
return res.data;
return response;
}
this.getMonitoringInstances(params.serviceId);
return res.data;
return response;
},
async getMonitoringInstances(serviceId: string): Promise<Nullable<AxiosResponse>> {
async getMonitoringInstances(serviceId: string) {
this.instancesLoading = true;
if (!serviceId) {
return null;
}
const res: AxiosResponse = await graphql.query("getMonitoringInstances").params({
const response = await graphql.query("getMonitoringInstances").params({
serviceId,
target: this.selectedStrategy.type,
});
this.instancesLoading = false;
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
this.instances = (res.data.data.instances || [])
this.instances = (response.data.instances || [])
.map((d: MonitorInstance) => {
const processes = (d.processes || [])
.sort((c: MonitorProcess, d: MonitorProcess) => d.lastTriggerTimestamp - c.lastTriggerTimestamp)
@ -161,7 +160,7 @@ export const continousProfilingStore = defineStore({
})
.sort((a: MonitorInstance, b: MonitorInstance) => b.lastTriggerTimestamp - a.lastTriggerTimestamp);
this.instance = this.instances[0] || null;
return res.data;
return response;
},
},
});

View File

@ -18,11 +18,10 @@ import { defineStore } from "pinia";
import { store } from "@/store";
import type { LayoutConfig } from "@/types/dashboard";
import graphql from "@/graphql";
import query from "@/graphql/fetch";
import fetchQuery from "@/graphql/fetch";
import type { DashboardItem } from "@/types/dashboard";
import { useSelectorStore } from "@/store/modules/selectors";
import { NewControl, TextConfig, TimeRangeConfig, ControlsTypes } from "../data";
import type { AxiosResponse } from "axios";
import { ElMessage } from "element-plus";
import { EntityType, WidgetType } from "@/views/dashboard/data";
interface DashboardState {
@ -299,37 +298,20 @@ export const dashboardStore = defineStore({
}
}
},
async fetchMetricType(item: string) {
const res: AxiosResponse = await graphql.query("queryTypeOfMetrics").params({ name: item });
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 });
return res.data;
},
async fetchMetricValue(param: { queryStr: string; conditions: { [key: string]: unknown } }) {
const res: AxiosResponse = await query(param);
return res.data;
return await fetchQuery(param);
},
async fetchTemplates() {
const res: AxiosResponse = await graphql.query("getTemplates").params({});
const res = await graphql.query("getTemplates").params({});
if (res.data.errors) {
return res.data;
if (res.errors) {
return res;
}
const data = res.data.data.getAllTemplates;
const data = res.data.getAllTemplates;
let list = [];
for (const t of data) {
const c = JSON.parse(t.configuration);
const key = [c.layer, c.entity, c.name].join("_");
list.push({
...c,
id: t.id,
@ -372,20 +354,20 @@ export const dashboardStore = defineStore({
this.dashboards = JSON.parse(sessionStorage.getItem("dashboards") || "[]");
},
async updateDashboard(setting: { id: string; configuration: string }) {
const res: AxiosResponse = await graphql.query("updateTemplate").params({
const resp = await graphql.query("updateTemplate").params({
setting,
});
if (res.data.errors) {
ElMessage.error(res.data.errors);
return res.data;
if (resp.errors) {
ElMessage.error(resp.errors);
return resp;
}
const json = res.data.data.changeTemplate;
const json = resp.data.changeTemplate;
if (!json.status) {
ElMessage.error(json.message);
return res.data;
return resp;
}
ElMessage.success("Saved successfully");
return res.data;
return resp;
},
async saveDashboard() {
if (!this.currentDashboard?.name) {
@ -419,14 +401,14 @@ export const dashboardStore = defineStore({
}
res = await graphql.query("addNewTemplate").params({ setting: { configuration: JSON.stringify(c) } });
json = res.data.data.addTemplate;
json = res.data.addTemplate;
if (!json.status) {
ElMessage.error(json.message);
}
}
if (res.data.errors || res.errors) {
ElMessage.error(res.data.errors);
return res.data;
if (res.errors) {
ElMessage.error(res.errors);
return res;
}
if (!json.status) {
return json;
@ -448,16 +430,16 @@ export const dashboardStore = defineStore({
return json;
},
async deleteDashboard() {
const res: AxiosResponse = await graphql.query("removeTemplate").params({ id: this.currentDashboard?.id });
const res = await graphql.query("removeTemplate").params({ id: this.currentDashboard?.id });
if (res.data.errors) {
ElMessage.error(res.data.errors);
return res.data;
if (res.errors) {
ElMessage.error(res.errors);
return res;
}
const json = res.data.data.disableTemplate;
const json = res.data.disableTemplate;
if (!json.status) {
ElMessage.error(json.message);
return res.data;
return res;
}
this.dashboards = this.dashboards.filter((d: Recordable) => d.id !== this.currentDashboard?.id);
const key = [this.currentDashboard?.layer, this.currentDashboard?.entity, this.currentDashboard?.name].join("_");

View File

@ -18,7 +18,6 @@ import { defineStore } from "pinia";
import type { Instance } from "@/types/selector";
import { store } from "@/store";
import graphql from "@/graphql";
import type { AxiosResponse } from "axios";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useSelectorStore } from "@/store/modules/selectors";
import type { Conditions, Log } from "@/types/demand-log";
@ -60,16 +59,16 @@ export const demandLogStore = defineStore({
},
async getInstances(id: string) {
const serviceId = this.selectorStore.currentService ? this.selectorStore.currentService.id : id;
const res: AxiosResponse = await graphql.query("queryInstances").params({
const response = await graphql.query("queryInstances").params({
serviceId,
duration: useAppStoreWithOut().durationTime,
});
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
this.instances = res.data.data.pods || [];
return res.data;
this.instances = response.data.pods || [];
return response;
},
async getContainers(serviceInstanceId: string) {
if (!serviceInstanceId) {
@ -78,35 +77,35 @@ export const demandLogStore = defineStore({
const condition = {
serviceInstanceId,
};
const res: AxiosResponse = await graphql.query("fetchContainers").params({ condition });
const response = await graphql.query("fetchContainers").params({ condition });
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
if (res.data.data.containers.errorReason) {
if (response.data.containers.errorReason) {
this.containers = [{ label: "", value: "" }];
return res.data;
return response;
}
this.containers = res.data.data.containers.containers.map((d: string) => {
this.containers = response.data.containers.containers.map((d: string) => {
return { label: d, value: d };
});
return res.data;
return response;
},
async getDemandLogs() {
this.loadLogs = true;
const res: AxiosResponse = await graphql.query("fetchDemandPodLogs").params({ condition: this.conditions });
const response = await graphql.query("fetchDemandPodLogs").params({ condition: this.conditions });
this.loadLogs = false;
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
if (res.data.data.logs.errorReason) {
this.setLogs([], res.data.data.logs.errorReason);
return res.data;
if (response.data.logs.errorReason) {
this.setLogs([], response.data.logs.errorReason);
return response;
}
this.total = res.data.data.logs.logs.length;
const logs = res.data.data.logs.logs.map((d: Log) => d.content).join("\n");
this.total = response.data.logs.logs.length;
const logs = response.data.logs.logs.map((d: Log) => d.content).join("\n");
this.setLogs(logs);
return res.data;
return response;
},
},
});

View File

@ -19,7 +19,6 @@ import type { Option } from "@/types/app";
import type { EBPFTaskCreationRequest, EBPFProfilingSchedule, EBPFTaskList, AnalyzationTrees } from "@/types/ebpf";
import { store } from "@/store";
import graphql from "@/graphql";
import type { AxiosResponse } from "axios";
import { EBPFProfilingTriggerType } from "../data";
interface EbpfState {
taskList: Array<Recordable<EBPFTaskList>>;
@ -57,70 +56,70 @@ export const ebpfStore = defineStore({
this.analyzeTrees = tree;
},
async getCreateTaskData(serviceId: string) {
const res: AxiosResponse = await graphql.query("getCreateTaskData").params({ serviceId });
const response = await graphql.query("getCreateTaskData").params({ serviceId });
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
const json = res.data.data.createTaskData;
const json = response.data.createTaskData;
this.couldProfiling = json.couldProfiling || false;
this.labels = json.processLabels.map((d: string) => {
return { label: d, value: d };
});
return res.data;
return response;
},
async createTask(param: EBPFTaskCreationRequest) {
const res: AxiosResponse = await graphql.query("saveEBPFTask").params({ request: param });
const response = await graphql.query("saveEBPFTask").params({ request: param });
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
this.getTaskList({
serviceId: param.serviceId,
targets: ["ON_CPU", "OFF_CPU"],
triggerType: EBPFProfilingTriggerType.FIXED_TIME,
});
return res.data;
return response;
},
async getTaskList(params: { serviceId: string; targets: string[] }) {
if (!params.serviceId) {
return new Promise((resolve) => resolve({}));
}
const res: AxiosResponse = await graphql.query("getEBPFTasks").params(params);
const response = await graphql.query("getEBPFTasks").params(params);
this.ebpfTips = "";
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
this.taskList = res.data.data.queryEBPFTasks || [];
this.taskList = response.data.queryEBPFTasks || [];
this.selectedTask = this.taskList[0] || {};
this.setSelectedTask(this.selectedTask);
if (!this.taskList.length) {
return res.data;
return response;
}
this.getEBPFSchedules({ taskId: String(this.taskList[0].taskId) });
return res.data;
return response;
},
async getEBPFSchedules(params: { taskId: string }) {
if (!params.taskId) {
return new Promise((resolve) => resolve({}));
}
const res: AxiosResponse = await graphql.query("getEBPFSchedules").params({ ...params });
const response = await graphql.query("getEBPFSchedules").params({ ...params });
if (res.data.errors) {
if (response.errors) {
this.eBPFSchedules = [];
return res.data;
return response;
}
this.ebpfTips = "";
const { eBPFSchedules } = res.data.data;
const { eBPFSchedules } = response.data;
this.eBPFSchedules = eBPFSchedules;
if (!eBPFSchedules.length) {
this.eBPFSchedules = [];
this.analyzeTrees = [];
}
return res.data;
return response;
},
async getEBPFAnalyze(params: {
scheduleIdList: string[];
@ -134,24 +133,24 @@ export const ebpfStore = defineStore({
if (!params.timeRanges.length) {
return new Promise((resolve) => resolve({}));
}
const res: AxiosResponse = await graphql.query("getEBPFResult").params(params);
const response = await graphql.query("getEBPFResult").params(params);
if (res.data.errors) {
if (response.errors) {
this.analyzeTrees = [];
return res.data;
return response;
}
const { analysisEBPFResult } = res.data.data;
const { analysisEBPFResult } = response.data;
this.ebpfTips = analysisEBPFResult.tip;
if (!analysisEBPFResult) {
this.analyzeTrees = [];
return res.data;
return response;
}
if (analysisEBPFResult.tip) {
this.analyzeTrees = [];
return res.data;
return response;
}
this.analyzeTrees = analysisEBPFResult.trees;
return res.data;
return response;
},
},
});

View File

@ -17,7 +17,6 @@
import { defineStore } from "pinia";
import { store } from "@/store";
import graphql from "@/graphql";
import type { AxiosResponse } from "axios";
import type { Event, QueryEventCondition } from "@/types/events";
import type { Instance, Endpoint } from "@/types/selector";
import { useAppStoreWithOut } from "@/store/modules/app";
@ -47,48 +46,48 @@ export const eventStore = defineStore({
},
async getInstances() {
const serviceId = useSelectorStore().currentService ? useSelectorStore().currentService.id : "";
const res: AxiosResponse = await graphql.query("queryInstances").params({
const response = await graphql.query("queryInstances").params({
serviceId,
duration: useAppStoreWithOut().durationTime,
});
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
this.instances = [{ value: "", label: "All" }, ...res.data.data.pods];
return res.data;
this.instances = [{ value: "", label: "All" }, ...response.data.pods];
return response;
},
async getEndpoints(keyword: string) {
const serviceId = useSelectorStore().currentService ? useSelectorStore().currentService.id : "";
if (!serviceId) {
return;
}
const res: AxiosResponse = await graphql.query("queryEndpoints").params({
const response = await graphql.query("queryEndpoints").params({
serviceId,
duration: useAppStoreWithOut().durationTime,
keyword: keyword || "",
limit: EndpointsTopNDefault,
});
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
this.endpoints = [{ value: "", label: "All" }, ...res.data.data.pods];
return res.data;
this.endpoints = [{ value: "", label: "All" }, ...response.data.pods];
return response;
},
async getEvents() {
this.loading = true;
const res: AxiosResponse = await graphql.query("queryEvents").params({
const response = await graphql.query("queryEvents").params({
condition: {
...this.condition,
time: useAppStoreWithOut().durationTime,
},
});
this.loading = false;
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
if (res.data.data.fetchEvents) {
this.events = (res.data.data.fetchEvents.events || []).map((item: Event) => {
if (response.data.fetchEvents) {
this.events = (response.data.fetchEvents.events || []).map((item: Event) => {
let scope = "Service";
if (item.source.serviceInstance) {
scope = "ServiceInstance";
@ -103,7 +102,7 @@ export const eventStore = defineStore({
return item;
});
}
return res.data;
return response;
},
},
});

View File

@ -18,7 +18,6 @@ import { defineStore } from "pinia";
import type { Instance, Endpoint, Service } from "@/types/selector";
import { store } from "@/store";
import graphql from "@/graphql";
import type { AxiosResponse } from "axios";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useSelectorStore } from "@/store/modules/selectors";
import { useDashboardStore } from "@/store/modules/dashboard";
@ -62,51 +61,51 @@ export const logStore = defineStore({
};
},
async getServices(layer: string) {
const res: AxiosResponse = await graphql.query("queryServices").params({
const response = await graphql.query("queryServices").params({
layer,
});
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
this.services = res.data.data.services;
return res.data;
this.services = response.data.services;
return response;
},
async getInstances(id: string) {
const serviceId = this.selectorStore.currentService ? this.selectorStore.currentService.id : id;
const res: AxiosResponse = await graphql.query("queryInstances").params({
const response = await graphql.query("queryInstances").params({
serviceId,
duration: useAppStoreWithOut().durationTime,
});
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
this.instances = [{ value: "0", label: "All" }, ...res.data.data.pods];
return res.data;
this.instances = [{ value: "0", label: "All" }, ...response.data.pods];
return response;
},
async getEndpoints(id: string, keyword?: string) {
const serviceId = this.selectorStore.currentService ? this.selectorStore.currentService.id : id;
const res: AxiosResponse = await graphql.query("queryEndpoints").params({
const response = await graphql.query("queryEndpoints").params({
serviceId,
duration: useAppStoreWithOut().durationTime,
keyword: keyword || "",
limit: EndpointsTopNDefault,
});
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
this.endpoints = [{ value: "0", label: "All" }, ...res.data.data.pods];
return res.data;
this.endpoints = [{ value: "0", label: "All" }, ...response.data.pods];
return response;
},
async getLogsByKeywords() {
const res: AxiosResponse = await graphql.query("queryLogsByKeywords").params({});
const response = await graphql.query("queryLogsByKeywords").params({});
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
this.supportQueryLogsByKeywords = res.data.data.support;
return res.data;
this.supportQueryLogsByKeywords = response.data.support;
return response;
},
async getLogs() {
const dashboardStore = useDashboardStore();
@ -117,39 +116,31 @@ export const logStore = defineStore({
},
async getServiceLogs() {
this.loadLogs = true;
const res: AxiosResponse = await graphql.query("queryServiceLogs").params({ condition: this.conditions });
const response = await graphql.query("queryServiceLogs").params({ condition: this.conditions });
this.loadLogs = false;
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
this.logs = res.data.data.queryLogs.logs;
return res.data;
this.logs = response.data.queryLogs.logs;
return response;
},
async getBrowserLogs() {
this.loadLogs = true;
const res: AxiosResponse = await graphql.query("queryBrowserErrorLogs").params({ condition: this.conditions });
const response = await graphql.query("queryBrowserErrorLogs").params({ condition: this.conditions });
this.loadLogs = false;
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
this.logs = res.data.data.queryBrowserErrorLogs.logs;
return res.data;
this.logs = response.data.queryBrowserErrorLogs.logs;
return response;
},
async getLogTagKeys() {
const res: AxiosResponse = await graphql
.query("queryLogTagKeys")
.params({ duration: useAppStoreWithOut().durationTime });
return res.data;
return await graphql.query("queryLogTagKeys").params({ duration: useAppStoreWithOut().durationTime });
},
async getLogTagValues(tagKey: string) {
const res: AxiosResponse = await graphql
.query("queryLogTagValues")
.params({ tagKey, duration: useAppStoreWithOut().durationTime });
return res.data;
return await graphql.query("queryLogTagValues").params({ tagKey, duration: useAppStoreWithOut().durationTime });
},
},
});

View File

@ -18,7 +18,6 @@ import { defineStore } from "pinia";
import type { EBPFTaskList, ProcessNode } from "@/types/ebpf";
import { store } from "@/store";
import graphql from "@/graphql";
import type { AxiosResponse } from "axios";
import type { Call } from "@/types/topology";
import type { LayoutConfig } from "@/types/dashboard";
import { ElMessage } from "element-plus";
@ -126,65 +125,65 @@ export const networkProfilingStore = defineStore({
minDuration: number;
}[],
) {
const res: AxiosResponse = await graphql.query("newNetworkProfiling").params({
const response = await graphql.query("newNetworkProfiling").params({
request: {
instanceId,
samplings: params,
},
});
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
return res.data;
return response;
},
async getTaskList(params: { serviceId: string; serviceInstanceId: string; targets: string[] }) {
if (!params.serviceId) {
return new Promise((resolve) => resolve({}));
}
const res: AxiosResponse = await graphql.query("getEBPFTasks").params(params);
const response = await graphql.query("getEBPFTasks").params(params);
this.networkTip = "";
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
this.networkTasks = res.data.data.queryEBPFTasks || [];
this.networkTasks = response.data.queryEBPFTasks || [];
this.selectedNetworkTask = this.networkTasks[0] || {};
this.setSelectedNetworkTask(this.selectedNetworkTask);
if (!this.networkTasks.length) {
this.nodes = [];
this.calls = [];
}
return res.data;
return response;
},
async keepNetworkProfiling(taskId: string) {
if (!taskId) {
return new Promise((resolve) => resolve({}));
}
const res: AxiosResponse = await graphql.query("aliveNetworkProfiling").params({ taskId });
const response = await graphql.query("aliveNetworkProfiling").params({ taskId });
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
this.aliveNetwork = res.data.data.keepEBPFNetworkProfiling.status;
this.aliveNetwork = response.data.keepEBPFNetworkProfiling.status;
if (!this.aliveNetwork) {
ElMessage.warning(res.data.data.keepEBPFNetworkProfiling.errorReason);
ElMessage.warning(response.data.keepEBPFNetworkProfiling.errorReason);
}
return res.data;
return response;
},
async getProcessTopology(params: { duration: DurationTime; serviceInstanceId: string }) {
this.loadNodes = true;
const res: AxiosResponse = await graphql.query("getProcessTopology").params(params);
const response = await graphql.query("getProcessTopology").params(params);
this.loadNodes = false;
if (res.data.errors) {
if (response.errors) {
this.nodes = [];
this.calls = [];
return res.data;
return response;
}
const { topology } = res.data.data;
const { topology } = response.data;
this.setTopology(topology);
return res.data;
return response;
},
},
});

View File

@ -26,7 +26,6 @@ import type {
import type { Trace } from "@/types/trace";
import { store } from "@/store";
import graphql from "@/graphql";
import type { AxiosResponse } from "axios";
import { useAppStoreWithOut } from "@/store/modules/app";
import { EndpointsTopNDefault } from "../data";
@ -94,38 +93,38 @@ export const profileStore = defineStore({
this.highlightTop = !this.highlightTop;
},
async getEndpoints(serviceId: string, keyword?: string) {
const res: AxiosResponse = await graphql.query("queryEndpoints").params({
const response = await graphql.query("queryEndpoints").params({
serviceId,
duration: useAppStoreWithOut().durationTime,
keyword: keyword || "",
limit: EndpointsTopNDefault,
});
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
this.endpoints = res.data.data.pods || [];
return res.data;
this.endpoints = response.data.pods || [];
return response.data;
},
async getTaskEndpoints(serviceId: string, keyword?: string) {
const res: AxiosResponse = await graphql.query("queryEndpoints").params({
const response = await graphql.query("queryEndpoints").params({
serviceId,
duration: useAppStoreWithOut().durationTime,
keyword: keyword || "",
limit: EndpointsTopNDefault,
});
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
this.taskEndpoints = [{ value: "", label: "All" }, ...res.data.data.pods];
return res.data;
this.taskEndpoints = [{ value: "", label: "All" }, ...response.data.pods];
return response;
},
async getTaskList() {
const res: AxiosResponse = await graphql.query("getProfileTaskList").params(this.condition);
const response = await graphql.query("getProfileTaskList").params(this.condition);
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
const list = res.data.data.taskList || [];
const list = response.data.taskList || [];
this.taskList = list;
this.currentTask = list[0] || {};
if (!list.length) {
@ -133,29 +132,29 @@ export const profileStore = defineStore({
this.segmentSpans = [];
this.analyzeTrees = [];
return res.data;
return response;
}
this.getSegmentList({ taskID: list[0].id });
return res.data;
return response;
},
async getSegmentList(params: { taskID: string }) {
if (!params.taskID) {
return new Promise((resolve) => resolve({}));
}
const res: AxiosResponse = await graphql.query("getProfileTaskSegmentList").params(params);
const response = await graphql.query("getProfileTaskSegmentList").params(params);
if (res.data.errors) {
if (response.errors) {
this.segmentList = [];
return res.data;
return response;
}
const { segmentList } = res.data.data;
const { segmentList } = response.data;
this.segmentList = segmentList || [];
if (!segmentList.length) {
this.segmentSpans = [];
this.analyzeTrees = [];
return res.data;
return response;
}
if (segmentList[0]) {
this.setCurrentSegment(segmentList[0]);
@ -163,22 +162,22 @@ export const profileStore = defineStore({
} else {
this.setCurrentSegment({});
}
return res.data;
return response;
},
async getSegmentSpans(params: { segmentId: string }) {
if (!(params && params.segmentId)) {
return new Promise((resolve) => resolve({}));
}
const res: AxiosResponse = await graphql.query("queryProfileSegment").params(params);
if (res.data.errors) {
const response = await graphql.query("queryProfileSegment").params(params);
if (response.errors) {
this.segmentSpans = [];
return res.data;
return response;
}
const { segment } = res.data.data;
const { segment } = response.data;
if (!segment) {
this.segmentSpans = [];
this.analyzeTrees = [];
return res.data;
return response;
}
this.segmentSpans = segment.spans.map((d: SegmentSpan) => {
return {
@ -189,52 +188,52 @@ export const profileStore = defineStore({
});
if (!(segment.spans && segment.spans.length)) {
this.analyzeTrees = [];
return res.data;
return response;
}
const index = segment.spans.length - 1 || 0;
this.currentSpan = segment.spans[index];
return res.data;
return response;
},
async getProfileAnalyze(params: Array<{ segmentId: string; timeRange: { start: number; end: number } }>) {
if (!params.length) {
return new Promise((resolve) => resolve({}));
}
const res: AxiosResponse = await graphql.query("getProfileAnalyze").params({ queries: params });
const response = await graphql.query("getProfileAnalyze").params({ queries: params });
if (res.data.errors) {
if (response.errors) {
this.analyzeTrees = [];
return res.data;
return response;
}
const { analyze, tip } = res.data.data;
const { analyze, tip } = response.data;
if (tip) {
this.analyzeTrees = [];
return res.data;
return response;
}
if (!analyze) {
this.analyzeTrees = [];
return res.data;
return response;
}
this.analyzeTrees = analyze.trees;
return res.data;
return response;
},
async createTask(param: ProfileTaskCreationRequest) {
const res: AxiosResponse = await graphql.query("saveProfileTask").params({ creationRequest: param });
const response = await graphql.query("saveProfileTask").params({ creationRequest: param });
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
this.getTaskList();
return res.data;
return response;
},
async getTaskLogs(param: { taskID: string }) {
const res: AxiosResponse = await graphql.query("getProfileTaskLogs").params(param);
const response = await graphql.query("getProfileTaskLogs").params(param);
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
this.taskLogs = res.data.data.taskLogs;
return res.data;
this.taskLogs = response.data.taskLogs;
return response;
},
},
});

View File

@ -18,7 +18,6 @@ import { defineStore } from "pinia";
import type { Service, Instance, Endpoint, Process } from "@/types/selector";
import { store } from "@/store";
import graphql from "@/graphql";
import type { AxiosResponse } from "axios";
import { useAppStoreWithOut } from "@/store/modules/app";
import { EndpointsTopNDefault } from "../data";
interface SelectorState {
@ -77,62 +76,55 @@ export const selectorStore = defineStore({
setDestProcesses(processes: Array<Process>) {
this.destProcesses = processes;
},
async fetchLayers(): Promise<AxiosResponse> {
const res: AxiosResponse = await graphql.query("queryLayers").params({});
return res.data || {};
async fetchLayers() {
return await graphql.query("queryLayers").params({});
},
async fetchServices(layer: string): Promise<AxiosResponse> {
const res: AxiosResponse = await graphql.query("queryServices").params({ layer });
async fetchServices(layer: string) {
const res = await graphql.query("queryServices").params({ layer });
if (!res.data.errors) {
this.services = res.data.data.services || [];
this.destServices = res.data.data.services || [];
if (!res.errors) {
this.services = res.data.services || [];
this.destServices = res.data.services || [];
}
return res.data;
},
async getServiceInstances(param?: { serviceId: string; isRelation: boolean }): Promise<Nullable<AxiosResponse>> {
async getServiceInstances(param?: { serviceId: string; isRelation: boolean }) {
const serviceId = param ? param.serviceId : this.currentService?.id;
if (!serviceId) {
return null;
}
const res: AxiosResponse = await graphql.query("queryInstances").params({
const resp = await graphql.query("queryInstances").params({
serviceId,
duration: useAppStoreWithOut().durationTime,
});
if (!res.data.errors) {
if (!resp.errors) {
if (param && param.isRelation) {
this.destPods = res.data.data.pods || [];
return res.data;
this.destPods = resp.data.pods || [];
return resp;
}
this.pods = res.data.data.pods || [];
this.pods = resp.data.pods || [];
}
return res.data;
return resp;
},
async getProcesses(param?: { instanceId: string; isRelation: boolean }): Promise<Nullable<AxiosResponse>> {
async getProcesses(param?: { instanceId: string; isRelation: boolean }) {
const instanceId = param ? param.instanceId : this.currentPod?.id;
if (!instanceId) {
return null;
}
const res: AxiosResponse = await graphql.query("queryProcesses").params({
const res = await graphql.query("queryProcesses").params({
instanceId,
duration: useAppStoreWithOut().durationTime,
});
if (!res.data.errors) {
if (!res.errors) {
if (param && param.isRelation) {
this.destProcesses = res.data.data.processes || [];
return res.data;
this.destProcesses = res.data.processes || [];
return res;
}
this.processes = res.data.data.processes || [];
this.processes = res.data.processes || [];
}
return res.data;
return res;
},
async getEndpoints(params: {
keyword?: string;
serviceId?: string;
isRelation?: boolean;
limit?: number;
}): Promise<Nullable<AxiosResponse>> {
async getEndpoints(params: { keyword?: string; serviceId?: string; isRelation?: boolean; limit?: number }) {
if (!params) {
params = {};
}
@ -140,96 +132,96 @@ export const selectorStore = defineStore({
if (!serviceId) {
return null;
}
const res: AxiosResponse = await graphql.query("queryEndpoints").params({
const res = await graphql.query("queryEndpoints").params({
serviceId,
duration: useAppStoreWithOut().durationTime,
keyword: params.keyword || "",
limit: params.limit || EndpointsTopNDefault,
});
if (!res.data.errors) {
if (!res.errors) {
if (params.isRelation) {
this.destPods = res.data.data.pods || [];
return res.data;
this.destPods = res.data.pods || [];
return res;
}
this.pods = res.data.data.pods || [];
this.pods = res.data.pods || [];
}
return res.data;
return res;
},
async getService(serviceId: string, isRelation: boolean) {
if (!serviceId) {
return;
}
const res: AxiosResponse = await graphql.query("queryService").params({
const res = await graphql.query("queryService").params({
serviceId,
});
if (!res.data.errors) {
if (!res.errors) {
if (isRelation) {
this.setCurrentDestService(res.data.data.service);
this.destServices = [res.data.data.service];
return res.data;
this.setCurrentDestService(res.data.service);
this.destServices = [res.data.service];
return res;
}
this.setCurrentService(res.data.data.service);
this.services = [res.data.data.service];
this.setCurrentService(res.data.service);
this.services = [res.data.service];
}
return res.data;
return res;
},
async getInstance(instanceId: string, isRelation?: boolean) {
if (!instanceId) {
return;
}
const res: AxiosResponse = await graphql.query("queryInstance").params({
const res = await graphql.query("queryInstance").params({
instanceId,
});
if (!res.data.errors) {
if (!res.errors) {
if (isRelation) {
this.currentDestPod = res.data.data.instance || null;
this.destPods = [res.data.data.instance];
return res.data;
this.currentDestPod = res.data.instance || null;
this.destPods = [res.data.instance];
return res;
}
this.currentPod = res.data.data.instance || null;
this.pods = [res.data.data.instance];
this.currentPod = res.data.instance || null;
this.pods = [res.data.instance];
}
return res.data;
return res;
},
async getEndpoint(endpointId: string, isRelation?: string) {
if (!endpointId) {
return;
}
const res: AxiosResponse = await graphql.query("queryEndpoint").params({
const res = await graphql.query("queryEndpoint").params({
endpointId,
});
if (res.data.errors) {
return res.data;
if (res.errors) {
return res;
}
if (isRelation) {
this.currentDestPod = res.data.data.endpoint || null;
this.destPods = [res.data.data.endpoint];
return res.data;
this.currentDestPod = res.data.endpoint || null;
this.destPods = [res.data.endpoint];
return res;
}
this.currentPod = res.data.data.endpoint || null;
this.pods = [res.data.data.endpoint];
return res.data;
this.currentPod = res.data.endpoint || null;
this.pods = [res.data.endpoint];
return res;
},
async getProcess(processId: string, isRelation?: boolean) {
if (!processId) {
return;
}
const res: AxiosResponse = await graphql.query("queryProcess").params({
const res = await graphql.query("queryProcess").params({
processId,
});
if (!res.data.errors) {
if (!res.errors) {
if (isRelation) {
this.currentDestProcess = res.data.data.process || null;
this.destProcesses = [res.data.data.process];
this.currentDestProcess = res.data.process || null;
this.destProcesses = [res.data.process];
return res.data;
}
this.currentProcess = res.data.data.process || null;
this.processes = [res.data.data.process];
this.currentProcess = res.data.process || null;
this.processes = [res.data.process];
}
return res.data;
return res;
},
},
});

View File

@ -18,7 +18,6 @@ import { defineStore } from "pinia";
import { ElMessage } from "element-plus";
import { store } from "@/store";
import graphql from "@/graphql";
import type { AxiosResponse } from "axios";
import { useAppStoreWithOut } from "@/store/modules/app";
import type { EBPFTaskList } from "@/types/ebpf";
import { useNetworkProfilingStore } from "@/store/modules/network-profiling";
@ -57,20 +56,18 @@ export const taskTimelineStore = defineStore({
return new Promise((resolve) => resolve({}));
}
this.loading = true;
const res: AxiosResponse = await graphql.query("getEBPFTasks").params(params);
const response = await graphql.query("getEBPFTasks").params(params);
this.loading = false;
this.errorTip = "";
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
const selectorStore = useSelectorStore();
this.taskList = (res.data.data.queryEBPFTasks || []).filter(
this.taskList = (response.data.queryEBPFTasks || []).filter(
(d: EBPFTaskList) => selectorStore.currentProcess && d.processId === selectorStore.currentProcess.id,
);
// this.selectedTask = this.taskList[0] || {};
// await this.getGraphData();
return res.data;
return response;
},
async getGraphData() {
let res: any = {};

View File

@ -21,8 +21,7 @@ import graphql from "@/graphql";
import { useSelectorStore } from "@/store/modules/selectors";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app";
import type { AxiosResponse } from "axios";
import query from "@/graphql/fetch";
import fetchQuery from "@/graphql/fetch";
import { useQueryTopologyExpressionsProcessor } from "@/hooks/useExpressionsProcessor";
interface MetricVal {
@ -305,14 +304,14 @@ export const topologyStore = defineStore({
return new Promise((resolve) => resolve({}));
}
const duration = useAppStoreWithOut().durationTime;
const res: AxiosResponse = await graphql.query("getServicesTopology").params({
const res = await graphql.query("getServicesTopology").params({
serviceIds,
duration,
});
if (res.data.errors) {
return res.data;
if (res.errors) {
return res;
}
return res.data.data.topology;
return res.data.topology;
},
async getInstanceTopology() {
const { currentService, currentDestService } = useSelectorStore();
@ -322,15 +321,15 @@ export const topologyStore = defineStore({
if (!(serverServiceId && clientServiceId)) {
return new Promise((resolve) => resolve({}));
}
const res: AxiosResponse = await graphql.query("getInstanceTopology").params({
const res = await graphql.query("getInstanceTopology").params({
clientServiceId,
serverServiceId,
duration,
});
if (!res.data.errors) {
this.setInstanceTopology(res.data.data.topology);
if (!res.errors) {
this.setInstanceTopology(res.data.topology);
}
return res.data;
return res;
},
async updateEndpointTopology(endpointIds: string[], depth: number) {
if (!endpointIds.length) {
@ -432,12 +431,12 @@ export const topologyStore = defineStore({
});
const queryStr = `query queryData(${variables}) {${fragment}}`;
const conditions = { duration };
const res: AxiosResponse = await query({ queryStr, conditions });
const res = await fetchQuery({ queryStr, conditions });
if (res.data.errors) {
return res.data;
if (res.errors) {
return res;
}
const topo = res.data.data;
const topo = res.data;
const calls = [] as Call[];
const nodes = [] as Node[];
for (const key of Object.keys(topo)) {
@ -449,13 +448,13 @@ export const topologyStore = defineStore({
return { calls, nodes };
},
async getTopologyExpressionValue(param: { queryStr: string; conditions: { [key: string]: unknown } }) {
const res: AxiosResponse = await query(param);
const res = await fetchQuery(param);
if (res.data.errors) {
return res.data;
if (res.errors) {
return res;
}
return res.data;
return res;
},
async getLinkExpressions(expressions: string[], type: string) {
if (!expressions.length) {
@ -503,22 +502,20 @@ export const topologyStore = defineStore({
if (!(id && layer)) {
return new Promise((resolve) => resolve({}));
}
const res: AxiosResponse = await graphql
.query("getHierarchyServiceTopology")
.params({ serviceId: id, layer: layer });
if (res.data.errors) {
return res.data;
const res = await graphql.query("getHierarchyServiceTopology").params({ serviceId: id, layer: layer });
if (res.errors) {
return res;
}
const resp = await this.getListLayerLevels();
if (resp.errors) {
return resp;
}
const levels = resp.data.levels || [];
this.setHierarchyServiceTopology(res.data.data.hierarchyServiceTopology || {}, levels);
return res.data;
const levels = resp.levels || [];
this.setHierarchyServiceTopology(res.data.hierarchyServiceTopology || {}, levels);
return res;
},
async getListLayerLevels() {
const res: AxiosResponse = await graphql.query("queryListLayerLevels").params({});
const res = await graphql.query("queryListLayerLevels").params({});
return res.data;
},
@ -529,19 +526,19 @@ export const topologyStore = defineStore({
if (!(currentPod && dashboardStore.layerId)) {
return new Promise((resolve) => resolve({}));
}
const res: AxiosResponse = await graphql
const res = await graphql
.query("getHierarchyInstanceTopology")
.params({ instanceId: currentPod.id, layer: dashboardStore.layerId });
if (res.data.errors) {
return res.data;
if (res.errors) {
return res;
}
const resp = await this.getListLayerLevels();
if (resp.errors) {
return resp;
}
const levels = resp.data.levels || [];
this.setHierarchyInstanceTopology(res.data.data.hierarchyInstanceTopology || {}, levels);
return res.data;
const levels = resp.levels || [];
this.setHierarchyInstanceTopology(res.data.hierarchyInstanceTopology || {}, levels);
return res;
},
async queryHierarchyNodeExpressions(expressions: string[], layer: string) {
const nodes = this.hierarchyServiceNodes.filter((n: HierarchyNode) => n.layer === layer);

View File

@ -19,7 +19,6 @@ import type { Instance, Endpoint, Service } from "@/types/selector";
import type { Trace, Span } from "@/types/trace";
import { store } from "@/store";
import graphql from "@/graphql";
import type { AxiosResponse } from "axios";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useSelectorStore } from "@/store/modules/selectors";
import { QueryOrders } from "@/views/dashboard/data";
@ -34,6 +33,7 @@ interface TraceState {
conditions: Recordable;
traceSpanLogs: Recordable[];
selectorStore: Recordable;
selectedSpan: Recordable<Span>;
}
export const traceStore = defineStore({
@ -45,6 +45,7 @@ export const traceStore = defineStore({
traceList: [],
traceSpans: [],
currentTrace: {},
selectedSpan: {},
conditions: {
queryDuration: useAppStoreWithOut().durationTime,
traceState: "ALL",
@ -64,6 +65,9 @@ export const traceStore = defineStore({
setTraceSpans(spans: Span[]) {
this.traceSpans = spans;
},
setSelectedSpan(span: Span) {
this.selectedSpan = span;
},
resetState() {
this.traceSpans = [];
this.traceList = [];
@ -76,125 +80,115 @@ export const traceStore = defineStore({
};
},
async getServices(layer: string) {
const res: AxiosResponse = await graphql.query("queryServices").params({
const response = await graphql.query("queryServices").params({
layer,
});
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
this.services = res.data.data.services;
return res.data;
this.services = response.data.services;
return response;
},
async getService(serviceId: string) {
if (!serviceId) {
return;
}
const res: AxiosResponse = await graphql.query("queryService").params({
const response = await graphql.query("queryService").params({
serviceId,
});
return res.data;
return response;
},
async getInstance(instanceId: string) {
if (!instanceId) {
return;
}
const res: AxiosResponse = await graphql.query("queryInstance").params({
const response = await graphql.query("queryInstance").params({
instanceId,
});
return res.data;
return response;
},
async getEndpoint(endpointId: string) {
if (!endpointId) {
return;
}
const res: AxiosResponse = await graphql.query("queryEndpoint").params({
return await graphql.query("queryEndpoint").params({
endpointId,
});
return res.data;
},
async getInstances(id: string) {
const serviceId = this.selectorStore.currentService ? this.selectorStore.currentService.id : id;
const res: AxiosResponse = await graphql.query("queryInstances").params({
const response = await graphql.query("queryInstances").params({
serviceId: serviceId,
duration: useAppStoreWithOut().durationTime,
});
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
this.instances = [{ value: "0", label: "All" }, ...res.data.data.pods];
return res.data;
this.instances = [{ value: "0", label: "All" }, ...response.data.pods];
return response;
},
async getEndpoints(id: string, keyword?: string) {
const serviceId = this.selectorStore.currentService ? this.selectorStore.currentService.id : id;
const res: AxiosResponse = await graphql.query("queryEndpoints").params({
const response = await graphql.query("queryEndpoints").params({
serviceId,
duration: useAppStoreWithOut().durationTime,
keyword: keyword || "",
limit: EndpointsTopNDefault,
});
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
this.endpoints = [{ value: "0", label: "All" }, ...res.data.data.pods];
return res.data;
this.endpoints = [{ value: "0", label: "All" }, ...response.data.pods];
return response;
},
async getTraces() {
const res: AxiosResponse = await graphql.query("queryTraces").params({ condition: this.conditions });
if (res.data.errors) {
return res.data;
const response = await graphql.query("queryTraces").params({ condition: this.conditions });
if (response.errors) {
return response;
}
if (!res.data.data.data.traces.length) {
if (!response.data.data.traces.length) {
this.traceList = [];
this.setCurrentTrace({});
this.setTraceSpans([]);
return res.data;
return response;
}
this.getTraceSpans({ traceId: res.data.data.data.traces[0].traceIds[0] });
this.traceList = res.data.data.data.traces.map((d: Trace) => {
this.getTraceSpans({ traceId: response.data.data.traces[0].traceIds[0] });
this.traceList = response.data.data.traces.map((d: Trace) => {
d.traceIds = d.traceIds.map((id: string) => {
return { value: id, label: id };
});
return d;
});
this.setCurrentTrace(res.data.data.data.traces[0] || {});
return res.data;
this.setCurrentTrace(response.data.data.traces[0] || {});
return response;
},
async getTraceSpans(params: { traceId: string }) {
const res: AxiosResponse = await graphql.query("queryTrace").params(params);
if (res.data.errors) {
return res.data;
const response = await graphql.query("queryTrace").params(params);
if (response.errors) {
return response;
}
const data = res.data.data.trace.spans;
const data = response.data.trace.spans;
this.setTraceSpans(data || []);
return res.data;
return response;
},
async getSpanLogs(params: Recordable) {
const res: AxiosResponse = await graphql.query("queryServiceLogs").params(params);
if (res.data.errors) {
const response = await graphql.query("queryServiceLogs").params(params);
if (response.errors) {
this.traceSpanLogs = [];
return res.data;
return response;
}
this.traceSpanLogs = res.data.data.queryLogs.logs || [];
return res.data;
this.traceSpanLogs = response.data.queryLogs.logs || [];
return response;
},
async getTagKeys() {
const res: AxiosResponse = await graphql
.query("queryTraceTagKeys")
.params({ duration: useAppStoreWithOut().durationTime });
return res.data;
return await graphql.query("queryTraceTagKeys").params({ duration: useAppStoreWithOut().durationTime });
},
async getTagValues(tagKey: string) {
const res: AxiosResponse = await graphql
.query("queryTraceTagValues")
.params({ tagKey, duration: useAppStoreWithOut().durationTime });
return res.data;
return await graphql.query("queryTraceTagValues").params({ tagKey, duration: useAppStoreWithOut().durationTime });
},
},
});

10
src/types/trace.d.ts vendored
View File

@ -48,9 +48,10 @@ export interface Span {
logs?: log[];
parentSegmentId?: string;
refs?: Ref[];
key?: string;
}
export type Ref = {
type: string;
type?: string;
parentSegmentId: string;
parentSpanId: number;
traceId: string;
@ -60,13 +61,6 @@ export interface log {
data: Map<string, string>;
}
export interface Ref {
traceId: string;
parentSegmentId: string;
parentSpanId: number;
type: string;
}
export interface StatisticsSpan {
groupRef: StatisticsGroupRef;
maxTime: number;

View File

@ -77,6 +77,7 @@ limitations under the License. -->
border-bottom: 1px solid $border-color-primary;
width: 100%;
overflow: auto;
min-height: 350px;
}
.log-header {

View File

@ -43,8 +43,7 @@ limitations under the License. -->
<Table
:data="profileStore.segmentSpans"
:traceId="profileStore.currentSegment.traceId"
:showBtnDetail="true"
headerType="profile"
:headerType="WidgetType.Profile"
@select="selectSpan"
/>
</div>
@ -53,13 +52,14 @@ limitations under the License. -->
<script lang="ts" setup>
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import Table from "../../trace/components/Table/Index.vue";
import Table from "../../trace/components/Table.vue";
import { useProfileStore } from "@/store/modules/profile";
import Selector from "@/components/Selector.vue";
import type { Span } from "@/types/trace";
import type { Option } from "@/types/app";
import { ElMessage } from "element-plus";
import { ProfileDataMode, ProfileDisplayMode } from "./data";
import { WidgetType } from "@/views/dashboard/data";
/* global defineEmits*/
const emits = defineEmits(["loading", "displayMode"]);

View File

@ -148,7 +148,7 @@ limitations under the License. -->
</div>
<div class="label">Unhealthy Description</div>
<el-input v-model="description.unhealthy" placeholder="Please input description" size="small" class="mt-5" />
<el-button @click="setLegend" class="mt-20" size="small" type="primary">
<el-button @click="setLegend" class="mt-20 mb-20" size="small" type="primary">
{{ t("setLegend") }}
</el-button>
</div>
@ -208,11 +208,6 @@ limitations under the License. -->
getDashboardList();
async function getDashboardList() {
const list = JSON.parse(sessionStorage.getItem("dashboards") || "[]");
const json = await dashboardStore.fetchMetricList();
if (json.errors) {
ElMessage.error(json.errors);
return;
}
const entity =
dashboardStore.entity === EntityType[1].value
? EntityType[0].value

View File

@ -84,7 +84,7 @@ limitations under the License. -->
:data="traceStore.traceSpans"
:traceId="traceStore.currentTrace.traceIds[0].value"
:showBtnDetail="false"
HeaderType="trace"
:headerType="WidgetType.Trace"
/>
</div>
</div>
@ -160,6 +160,7 @@ limitations under the License. -->
appStore,
loading,
traceId,
WidgetType,
};
},
});

View File

@ -14,8 +14,35 @@ limitations under the License. -->
<div class="trace-t-loading" v-show="loading">
<Icon iconName="spinner" size="sm" />
</div>
<div ref="traceGraph" class="d3-graph"></div>
<el-dialog v-model="showDetail" :destroy-on-close="true" fullscreen @closed="showDetail = false">
<TableContainer
v-if="type === TraceGraphType.TABLE"
:tableData="segmentId"
:type="type"
:headerType="headerType"
:traceId="traceId"
@select="handleSelectSpan"
>
<div class="trace-tips" v-if="!segmentId.length">{{ $t("noData") }}</div>
</TableContainer>
<div v-else ref="traceGraph" class="d3-graph"></div>
<div id="trace-action-box">
<div @click="viewSpanDetails">Span details</div>
<div v-for="span in parentSpans" :key="span.segmentId" @click="viewParentSpan(span)">
{{ `Parent span: ${span.endpointName} -> Start time: ${visDate(span.startTime)}` }}
</div>
<div v-for="span in refParentSpans" :key="span.segmentId" @click="viewParentSpan(span)">
{{ `Ref to span: ${span.endpointName} -> Start time: ${visDate(span.startTime)}` }}
</div>
</div>
<el-dialog
v-model="showDetail"
width="60%"
center
align-center
:destroy-on-close="true"
@closed="showDetail = false"
v-if="currentSpan?.segmentId"
>
<SpanDetail :currentSpan="currentSpan" />
</el-dialog>
</template>
@ -23,70 +50,138 @@ limitations under the License. -->
import { ref, watch, onBeforeUnmount, onMounted } from "vue";
import type { PropType } from "vue";
import * as d3 from "d3";
import dayjs from "dayjs";
import ListGraph from "../../utils/d3-trace-list";
import TreeGraph from "../../utils/d3-trace-tree";
import type { Span, Ref } from "@/types/trace";
import SpanDetail from "./SpanDetail.vue";
import TableContainer from "../Table/TableContainer.vue";
import { useAppStoreWithOut } from "@/store/modules/app";
import { debounce } from "@/utils/debounce";
import { mutationObserver } from "@/utils/mutation";
import { TraceGraphType } from "../constant";
import { Themes } from "@/constants/data";
/* global defineProps, Nullable, defineExpose, Recordable */
/* global Recordable, Nullable */
const props = defineProps({
data: { type: Array as PropType<Span[]>, default: () => [] },
traceId: { type: String, default: "" },
type: { type: String, default: "List" },
type: { type: String, default: TraceGraphType.LIST },
headerType: { type: String, default: "" },
});
const appStore = useAppStoreWithOut();
const loading = ref<boolean>(false);
const showDetail = ref<boolean>(false);
const fixSpansSize = ref<number>(0);
const segmentId = ref<Recordable[]>([]);
const currentSpan = ref<Array<Span>>([]);
const currentSpan = ref<Nullable<Span>>(null);
const refSpans = ref<Array<Ref>>([]);
const tree = ref<Nullable<any>>(null);
const traceGraph = ref<Nullable<HTMLDivElement>>(null);
const parentSpans = ref<Array<Span>>([]);
const refParentSpans = ref<Array<Span>>([]);
const debounceFunc = debounce(draw, 500);
const visDate = (date: number, pattern = "YYYY-MM-DD HH:mm:ss:SSS") => dayjs(date).format(pattern);
defineExpose({
tree,
});
onMounted(() => {
loading.value = true;
changeTree();
if (!traceGraph.value) {
loading.value = false;
return;
}
draw();
loading.value = false;
// monitor segment list width changes.
mutationObserver.create("trigger-resize", () => {
d3.selectAll(".d3-tip").remove();
debounceFunc();
});
window.addEventListener("resize", debounceFunc);
});
function draw() {
if (props.type === TraceGraphType.TABLE) {
segmentId.value = setLevel(segmentId.value);
return;
}
if (!traceGraph.value) {
return;
}
d3.selectAll(".d3-tip").remove();
if (props.type === "List") {
if (props.type === TraceGraphType.LIST) {
tree.value = new ListGraph(traceGraph.value, handleSelectSpan);
tree.value.init({ label: "TRACE_ROOT", children: segmentId.value }, props.data, fixSpansSize.value);
tree.value.init(
{ label: "TRACE_ROOT", children: segmentId.value },
getRefsAllNodes({ label: "TRACE_ROOT", children: segmentId.value }),
fixSpansSize.value,
);
tree.value.draw();
} else {
return;
}
if (props.type === TraceGraphType.TREE) {
tree.value = new TreeGraph(traceGraph.value, handleSelectSpan);
tree.value.init({ label: `${props.traceId}`, children: segmentId.value }, props.data);
tree.value.init(
{ label: `${props.traceId}`, children: segmentId.value },
getRefsAllNodes({ label: "TRACE_ROOT", children: segmentId.value }),
);
}
}
function handleSelectSpan(i: Recordable) {
currentSpan.value = i.data;
function handleSelectSpan(i: any) {
const spans = [];
const refSpans = [];
parentSpans.value = [];
refParentSpans.value = [];
if (props.type === TraceGraphType.TABLE) {
currentSpan.value = i;
} else {
currentSpan.value = i.data;
}
if (!currentSpan.value) {
return;
}
for (const ref of currentSpan.value.refs || []) {
refSpans.push(ref);
}
if (currentSpan.value.parentSpanId > -1) {
spans.push({
parentSegmentId: currentSpan.value.segmentId,
parentSpanId: currentSpan.value.parentSpanId,
traceId: currentSpan.value.traceId,
});
}
for (const span of refSpans) {
const item = props.data.find(
(d) => d.segmentId === span.parentSegmentId && d.spanId === span.parentSpanId && d.traceId === span.traceId,
);
item && refParentSpans.value.push(item);
}
for (const span of spans) {
const item = props.data.find(
(d) => d.segmentId === span.parentSegmentId && d.spanId === span.parentSpanId && d.traceId === span.traceId,
);
item && parentSpans.value.push(item);
}
}
function viewParentSpan(span: Recordable) {
if (props.type === TraceGraphType.TABLE) {
setTableSpanStyle(span);
return;
}
tree.value.highlightParents(span);
}
function viewSpanDetails() {
showDetail.value = true;
hideActionBox();
}
function setTableSpanStyle(span: Recordable) {
const itemDom: any = document.querySelector(`.trace-item-${span.key}`);
const items: any = document.querySelectorAll(".trace-item");
for (const item of items) {
item.style.background = appStore.theme === Themes.Dark ? "#212224" : "#fff";
}
itemDom.style.background = appStore.theme === Themes.Dark ? "rgba(255, 255, 255, 0.1)" : "rgba(0, 0, 0, 0.1)";
hideActionBox();
}
function hideActionBox() {
const box: any = document.querySelector("#trace-action-box");
box.style.display = "none";
}
function traverseTree(node: Recordable, spanId: string, segmentId: string, data: Recordable) {
if (!node || node.isBroken) {
@ -229,6 +324,7 @@ limitations under the License. -->
}
for (const i of [...fixSpans, ...props.data]) {
i.label = i.endpointName || "no operation name";
i.key = Math.random().toString(36).substring(2, 36);
i.children = [];
if (segmentGroup[i.segmentId]) {
segmentGroup[i.segmentId].push(i);
@ -272,21 +368,12 @@ limitations under the License. -->
}
}
for (const i in segmentGroup) {
if (segmentGroup[i].refs.length) {
let exit = null;
for (const ref of segmentGroup[i].refs) {
const e = props.data.find(
(i: Recordable) =>
ref.traceId === i.traceId && ref.parentSegmentId === i.segmentId && ref.parentSpanId === i.spanId,
);
if (e) {
exit = e;
}
}
if (exit) {
for (const ref of segmentGroup[i].refs) {
if (!segmentGroup[ref.parentSegmentId]) {
segmentId.value.push(segmentGroup[i]);
}
} else {
}
if (!segmentGroup[i].refs.length && segmentGroup[i].parentSpanId === -1) {
segmentId.value.push(segmentGroup[i]);
}
}
@ -310,6 +397,34 @@ limitations under the License. -->
}
}
}
function setLevel(arr: Recordable[], level = 1, totalExec?: number) {
for (const item of arr) {
item.level = level;
totalExec = totalExec || item.endTime - item.startTime;
item.totalExec = totalExec;
if (item.children && item.children.length > 0) {
setLevel(item.children, level + 1, totalExec);
}
}
return arr;
}
function getRefsAllNodes(tree: Recordable) {
let nodes = [];
let stack = [tree];
while (stack.length > 0) {
const node = stack.pop();
nodes.push(node);
if (node?.children && node.children.length > 0) {
for (let i = node.children.length - 1; i >= 0; i--) {
stack.push(node.children[i]);
}
}
}
return nodes;
}
function compare(p: string) {
return (m: Recordable, n: Recordable) => {
const a = m[p];
@ -346,7 +461,7 @@ limitations under the License. -->
},
);
</script>
<style lang="scss" scoped>
<style lang="scss">
.d3-graph {
height: 100%;
}
@ -356,36 +471,50 @@ limitations under the License. -->
fill-opacity: 0;
}
.trace-node-container {
fill: rgb(0 0 0 / 0%);
stroke-width: 5px;
cursor: pointer;
&:hover {
fill: rgb(0 0 0 / 5%);
}
}
.trace-node .node-text {
font: 12.5px sans-serif;
font: 12px sans-serif;
pointer-events: none;
}
.domain {
.trace-node.highlighted .node-text,
.trace-node.highlightedParent .node-text {
font-weight: bold;
fill: #409eff;
}
.highlightedParent .node,
.highlighted .node {
stroke-width: 4;
fill: var(--font-color);
stroke: var(--font-color);
}
.trace-node.highlighted .trace-node-text,
.trace-node.highlightedParent .trace-node-text {
font-weight: bold;
fill: #409eff;
}
#trace-action-box {
position: absolute;
color: $font-color;
cursor: pointer;
border: var(--sw-topology-border);
border-radius: 3px;
background-color: $theme-background;
padding: 10px 0;
display: none;
}
.time-charts-item {
display: inline-block;
padding: 2px 8px;
border: 1px solid;
font-size: 11px;
border-radius: 4px;
}
div {
height: 30px;
line-height: 30px;
text-align: left;
padding: 0 15px;
}
.dialog-c-text {
white-space: pre;
overflow: auto;
font-family: monospace;
div:hover {
color: $active-color;
background-color: $popper-hover-bg-color;
}
}
</style>

View File

@ -87,7 +87,14 @@ limitations under the License. -->
{{ t("relatedTraceLogs") }}
</el-button>
</div>
<el-dialog v-model="showEventDetail" :destroy-on-close="true" fullscreen @closed="showEventDetail = false">
<el-dialog
v-model="showEventDetail"
width="60%"
center
align-center
:destroy-on-close="true"
@closed="showEventDetail = false"
>
<div>
<div class="mb-10">
<span class="grey title">Name:</span>
@ -115,7 +122,14 @@ limitations under the License. -->
</div>
</div>
</el-dialog>
<el-dialog v-model="showRelatedLogs" :destroy-on-close="true" fullscreen @closed="showRelatedLogs = false">
<el-dialog
v-model="showRelatedLogs"
width="60%"
center
align-center
:destroy-on-close="true"
@closed="showRelatedLogs = false"
>
<el-pagination
v-model="pageNum"
:page-size="pageSize"
@ -295,4 +309,10 @@ limitations under the License. -->
.link {
text-decoration: underline;
}
.log-tips {
width: 100%;
text-align: center;
margin: 50px 0;
}
</style>

View File

@ -22,7 +22,7 @@ limitations under the License. -->
</el-button>
</div>
<div class="list">
<Graph :data="data" :traceId="traceId" type="List" />
<Graph :data="data" :traceId="traceId" :type="TraceGraphType.LIST" />
</div>
</div>
</template>
@ -31,8 +31,11 @@ limitations under the License. -->
import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
import * as d3 from "d3";
import { useAppStoreWithOut } from "@/store/modules/app";
import type { Span } from "@/types/trace";
import Graph from "./D3Graph/Index.vue";
import { Themes } from "@/constants/data";
import { TraceGraphType } from "./constant";
/* global defineProps, Recordable*/
const props = defineProps({
@ -40,6 +43,7 @@ limitations under the License. -->
traceId: { type: String, default: "" },
});
const { t } = useI18n();
const appStore = useAppStoreWithOut();
const list = computed(() => Array.from(new Set(props.data.map((i: Span) => i.serviceCode))));
function computedScale(i: number) {
@ -52,13 +56,13 @@ limitations under the License. -->
function downloadTrace() {
const serializer = new XMLSerializer();
const svgNode: any = d3.select(".trace-list-dowanload").node();
const svgNode: any = d3.select(".trace-list").node();
const source = `<?xml version="1.0" standalone="no"?>\r\n${serializer.serializeToString(svgNode)}`;
const canvas = document.createElement("canvas");
const context: any = canvas.getContext("2d");
canvas.width = (d3.select(".trace-list-dowanload") as Recordable)._groups[0][0].clientWidth;
canvas.height = (d3.select(".trace-list-dowanload") as Recordable)._groups[0][0].clientHeight;
context.fillStyle = "#fff";
canvas.width = (d3.select(".trace-list") as Recordable)._groups[0][0].clientWidth;
canvas.height = (d3.select(".trace-list") as Recordable)._groups[0][0].clientHeight;
context.fillStyle = appStore.theme === Themes.Dark ? "#212224" : `#fff`;
context.fillRect(0, 0, canvas.width, canvas.height);
const image = new Image();
image.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(source)}`;
@ -93,6 +97,7 @@ limitations under the License. -->
.list {
height: calc(100% - 150px);
position: relative;
}
.event-tag {

View File

@ -17,7 +17,7 @@ limitations under the License. -->
<div class="trace-t-loading" v-show="loading">
<Icon iconName="spinner" size="sm" />
</div>
<TableContainer :tableData="tableData" type="statistics" :HeaderType="HeaderType">
<TableContainer :tableData="tableData" :type="TraceGraphType.STATISTICS" :headerType="headerType">
<div class="trace-tips" v-if="!tableData.length">{{ $t("noData") }}</div>
</TableContainer>
</div>
@ -28,13 +28,14 @@ limitations under the License. -->
import TableContainer from "./Table/TableContainer.vue";
import traceTable from "../utils/trace-table";
import type { StatisticsSpan, Span, StatisticsGroupRef } from "@/types/trace";
import { TraceGraphType } from "./constant";
/* global defineProps, defineEmits, Recordable*/
const props = defineProps({
data: { type: Array as PropType<Span[]>, default: () => [] },
traceId: { type: String, default: "" },
showBtnDetail: { type: Boolean, default: false },
HeaderType: { type: String, default: "" },
headerType: { type: String, default: "" },
});
const emit = defineEmits(["load"]);
const loading = ref<boolean>(true);

View File

@ -0,0 +1,38 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div class="trace-table-charts">
<Graph :data="data" :traceId="traceId" :type="TraceGraphType.TABLE" :headerType="headerType" />
</div>
</template>
<script lang="ts" setup>
import type { PropType } from "vue";
import type { Span } from "@/types/trace";
import Graph from "./D3Graph/Index.vue";
import { TraceGraphType } from "./constant";
defineProps({
data: { type: Array as PropType<Span[]>, default: () => [] },
traceId: { type: String, default: "" },
headerType: { type: String, default: "" },
});
</script>
<style lang="scss" scoped>
.trace-table-charts {
overflow: auto;
padding: 10px;
height: 100%;
width: 100%;
position: relative;
}
</style>

View File

@ -1,108 +0,0 @@
<!-- 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. -->
<template>
<div class="trace-table">
<div class="trace-t-loading" v-show="loading">
<Icon iconName="spinner" size="sm" />
</div>
<TableContainer
:tableData="tableData"
type="table"
:headerType="headerType"
:traceId="traceId"
@select="handleSelectSpan"
>
<div class="trace-tips" v-if="!tableData.length">{{ $t("noData") }}</div>
</TableContainer>
</div>
</template>
<script lang="ts" setup>
import { ref, watch, onMounted } from "vue";
import type { PropType } from "vue";
import TableContainer from "./TableContainer.vue";
import traceTable from "../../utils/trace-table";
import type { Span } from "@/types/trace";
/* global defineProps, defineEmits, Recordable */
const props = defineProps({
data: { type: Array as PropType<Span[]>, default: () => [] },
traceId: { type: String, default: "" },
showBtnDetail: { type: Boolean, default: false },
headerType: { type: String, default: "" },
});
const emit = defineEmits(["select", "view", "load"]);
const loading = ref<boolean>(true);
const tableData = ref<Recordable[]>([]);
const showDetail = ref<boolean>(false);
const currentSpan = ref<Span | Recordable>({});
onMounted(() => {
tableData.value = formatData(traceTable.changeTree(props.data, props.traceId));
loading.value = false;
emit("load", () => {
loading.value = true;
});
});
function formatData(arr: Recordable[], level = 1, totalExec?: number) {
for (const item of arr) {
item.level = level;
totalExec = totalExec || item.endTime - item.startTime;
item.totalExec = totalExec;
if (item.children && item.children.length > 0) {
formatData(item.children, level + 1, totalExec);
}
}
return arr;
}
function handleSelectSpan(data: Span) {
currentSpan.value = data;
if (!props.showBtnDetail) {
showDetail.value = true;
}
emit("select", data);
}
watch(
() => props.data,
() => {
if (!props.data.length) {
tableData.value = [];
return;
}
tableData.value = formatData(traceTable.changeTree(props.data, props.traceId));
loading.value = false;
},
);
</script>
<style lang="scss" scoped>
.dialog-c-text {
white-space: pre;
overflow: auto;
font-family: monospace;
}
.trace-tips {
width: 100%;
text-align: center;
margin-top: 10px;
}
.trace-table {
height: 100%;
width: 100%;
}
</style>

View File

@ -14,8 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div class="trace">
<div class="trace-header" v-if="type === 'statistics'">
<div class="trace-table">
<div class="trace-table-header" v-if="type === TraceGraphType.STATISTICS">
<div :class="item.label" v-for="(item, index) in headerData" :key="index">
{{ item.value }}
<span
@ -28,7 +28,7 @@ limitations under the License. -->
</span>
</div>
</div>
<div class="trace-header" v-else>
<div class="trace-table-header" v-else>
<div class="method" :style="`width: ${method}px`">
<span class="cp dragger" ref="dragger">
<Icon iconName="settings_ethernet" size="sm" />
@ -44,10 +44,10 @@ limitations under the License. -->
:traceId="traceId"
v-for="(item, index) in tableData"
:data="item"
:key="'key' + index"
:key="`key${index}`"
:type="type"
:headerType="headerType"
@select="selectItem"
@click="selectItem"
/>
<slot></slot>
</div>
@ -55,9 +55,11 @@ limitations under the License. -->
<script lang="ts" setup>
import { ref, onMounted } from "vue";
import type { PropType } from "vue";
import type { Span } from "@/types/trace";
import { useTraceStore } from "@/store/modules/trace";
import TableItem from "./TableItem.vue";
import { ProfileConstant, TraceConstant, StatisticsConstant } from "./data";
import { TraceGraphType } from "../constant";
import { WidgetType } from "@/views/dashboard/data";
/* global defineProps, Nullable, defineEmits, Recordable*/
const props = defineProps({
@ -67,20 +69,21 @@ limitations under the License. -->
traceId: { type: String, default: "" },
});
const emits = defineEmits(["select"]);
const traceStore = useTraceStore();
const method = ref<number>(300);
const componentKey = ref<number>(300);
const flag = ref<boolean>(true);
const dragger = ref<Nullable<HTMLSpanElement>>(null);
let headerData: Recordable[] = TraceConstant;
if (props.headerType === "profile") {
if (props.headerType === WidgetType.Profile) {
headerData = ProfileConstant;
}
if (props.type === "statistics") {
if (props.type === TraceGraphType.STATISTICS) {
headerData = StatisticsConstant;
}
onMounted(() => {
if (props.type === "statistics") {
if (props.type === TraceGraphType.STATISTICS) {
return;
}
const drag: Nullable<HTMLSpanElement> = dragger.value;
@ -101,8 +104,24 @@ limitations under the License. -->
};
};
});
function selectItem(span: Span) {
emits("select", span);
function selectItem(event: MouseEvent) {
emits("select", traceStore.selectedSpan);
if (props.headerType === WidgetType.Profile) {
return;
}
if (props.type === TraceGraphType.STATISTICS) {
return;
}
const item: any = document.querySelector("#trace-action-box");
const tableBox = document.querySelector(".trace-table-charts")?.getBoundingClientRect();
if (!tableBox) {
return;
}
const offsetX = event.x - tableBox.x;
const offsetY = event.y - tableBox.y;
item.style.display = "block";
item.style.top = `${offsetY + 20}px`;
item.style.left = `${offsetX + 10}px`;
}
function sortStatistics(key: string) {
const element = props.tableData;
@ -152,7 +171,7 @@ limitations under the License. -->
<style lang="scss" scoped>
@import url("./table.scss");
.trace {
.trace-table {
font-size: $font-size-smaller;
height: 100%;
overflow: auto;
@ -163,7 +182,7 @@ limitations under the License. -->
float: right;
}
.trace-header {
.trace-table-header {
white-space: nowrap;
user-select: none;
border-left: 0;
@ -171,7 +190,7 @@ limitations under the License. -->
border-bottom: 1px solid var(--sw-trace-list-border);
}
.trace-header div {
.trace-table-header div {
display: inline-block;
background-color: var(--sw-table-header);
padding: 0 4px;

View File

@ -14,17 +14,17 @@ See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div v-if="type === 'statistics'">
<div v-if="type === TraceGraphType.STATISTICS">
<div class="trace-item">
<div :class="['method']">
<el-tooltip :content="data.groupRef.endpointName" placement="bottom" :show-after="300">
<el-tooltip :content="data.groupRef.endpointName" placement="top" :show-after="300">
<span>
{{ data.groupRef.endpointName }}
</span>
</el-tooltip>
</div>
<div :class="['type']">
<el-tooltip :content="data.groupRef.type" placement="bottom" :show-after="300">
<el-tooltip :content="data.groupRef.type" placement="top" :show-after="300">
<span>
{{ data.groupRef.type }}
</span>
@ -55,6 +55,7 @@ limitations under the License. -->
'level' + (data.level - 1),
{ 'trace-item-error': data.isError },
{ profiled: data.profiled === false },
`trace-item-${data.key}`,
]"
:data-text="data.profiled === false ? 'No Thread Dump' : ''"
>
@ -75,7 +76,7 @@ limitations under the License. -->
/>
<el-tooltip
:content="data.type === 'Entry' ? 'Entry' : 'Exit'"
placement="bottom"
placement="top"
:show-after="300"
v-if="['Entry', 'Exit'].includes(data.type)"
>
@ -83,12 +84,12 @@ limitations under the License. -->
<Icon :iconName="data.type === 'Entry' ? 'entry' : 'exit'" size="sm" class="mr-5" />
</span>
</el-tooltip>
<el-tooltip v-if="isCrossThread" content="CROSS_THREAD" placement="bottom" :show-after="300">
<el-tooltip v-if="isCrossThread" content="CROSS_THREAD" placement="top" :show-after="300">
<span>
<Icon iconName="cross" size="sm" class="mr-5" />
</span>
</el-tooltip>
<el-tooltip :content="data.endpointName" placement="bottom" :show-after="300">
<el-tooltip :content="data.endpointName" placement="top" :show-after="300">
<span>
{{ data.endpointName }}
</span>
@ -109,19 +110,19 @@ limitations under the License. -->
{{ data.dur ? data.dur + "" : "0" }}
</div>
<div class="api">
<el-tooltip :show-after="300" :content="data.component || '-'" placement="bottom">
<el-tooltip :show-after="300" :content="data.component || '-'" placement="top">
<span>{{ data.component || "-" }}</span>
</el-tooltip>
</div>
<div class="application">
<el-tooltip :show-after="300" :content="data.serviceCode || '-'" placement="bottom">
<el-tooltip :show-after="300" :content="data.serviceCode || '-'" placement="top">
<span>{{ data.serviceCode }}</span>
</el-tooltip>
</div>
<div class="application" v-show="headerType === 'profile'" @click="viewSpan($event)">
<div class="application" v-show="headerType === WidgetType.Profile" @click="viewSpan($event)">
<span>{{ t("view") }}</span>
</div>
<div class="application" v-show="headerType !== 'profile'">
<div class="application" v-show="headerType !== WidgetType.Profile">
<span>{{ data.attachedEvents && data.attachedEvents.length }}</span>
</div>
</div>
@ -133,7 +134,6 @@ limitations under the License. -->
:data="child"
:type="type"
:headerType="headerType"
@select="selectedItem(child)"
/>
</div>
<el-dialog v-model="showDetail" :destroy-on-close="true" fullscreen @closed="showDetail = false">
@ -148,7 +148,10 @@ limitations under the License. -->
import SpanDetail from "../D3Graph/SpanDetail.vue";
import { dateFormat } from "@/utils/dateFormat";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useTraceStore } from "@/store/modules/trace";
import { Themes } from "@/constants/data";
import { TraceGraphType } from "../constant";
import { WidgetType } from "@/views/dashboard/data";
/*global Recordable*/
const props = {
@ -161,10 +164,10 @@ limitations under the License. -->
export default defineComponent({
name: "TableItem",
props,
emits: ["select"],
components: { SpanDetail },
setup(props, { emit }) {
setup(props) {
const appStore = useAppStoreWithOut();
const traceStore = useTraceStore();
const displayChildren = ref<boolean>(true);
const showDetail = ref<boolean>(false);
const { t } = useI18n();
@ -193,7 +196,6 @@ limitations under the License. -->
const key = props.data.refs.findIndex((d: { type: string }) => d.type === "CROSS_THREAD");
return key > -1 ? true : false;
});
function toggle() {
displayChildren.value = !displayChildren.value;
}
@ -213,26 +215,27 @@ limitations under the License. -->
}
function selectSpan(event: Recordable) {
const dom = event.composedPath().find((d: Recordable) => d.className.includes("trace-item"));
emit("select", props.data);
if (props.headerType === "profile") {
selectedItem(props.data);
if (props.headerType === WidgetType.Profile) {
showSelectSpan(dom);
return;
}
viewSpanDetail(dom);
}
function viewSpan(event: Recordable) {
showDetail.value = true;
const dom = event.composedPath().find((d: Recordable) => d.className.includes("trace-item"));
emit("select", props.data);
selectedItem(props.data);
viewSpanDetail(dom);
}
function selectedItem(data: HTMLSpanElement) {
emit("select", data);
function selectedItem(span: Recordable) {
traceStore.setSelectedSpan(span);
}
function viewSpanDetail(dom: HTMLSpanElement) {
showSelectSpan(dom);
showDetail.value = true;
if (props.type === TraceGraphType.STATISTICS) {
showDetail.value = true;
}
}
watch(
() => appStore.theme,
@ -262,6 +265,8 @@ limitations under the License. -->
viewSpan,
t,
appStore,
TraceGraphType,
WidgetType,
};
},
});
@ -279,8 +284,6 @@ limitations under the License. -->
}
.trace-item.level0 {
color: #448dfe;
&:hover {
background: rgb(0 0 0 / 4%);
}

View File

@ -35,7 +35,7 @@ limitations under the License. -->
</a>
</div>
<div class="trace-tree">
<Graph ref="charts" :data="data" :traceId="traceId" type="Tree" />
<Graph ref="charts" :data="data" :traceId="traceId" :type="TraceGraphType.TREE" />
</div>
</div>
</template>
@ -46,6 +46,7 @@ limitations under the License. -->
import type { Span } from "@/types/trace";
import { useI18n } from "vue-i18n";
import { ref, computed } from "vue";
import { TraceGraphType } from "./constant";
/* global defineProps */
const props = defineProps({

View File

@ -15,11 +15,9 @@
* limitations under the License.
*/
import axios from "axios";
const CancelToken = axios.CancelToken;
export const cancelToken = (): any =>
new CancelToken(function executor(c) {
const w = window as any;
(w.axiosCancel || []).push(c);
});
export enum TraceGraphType {
TREE = "Tree",
LIST = "List",
TABLE = "Table",
STATISTICS = "Statistics",
}

View File

@ -17,7 +17,7 @@
import List from "./List.vue";
import Tree from "./Tree.vue";
import Table from "./Table/Index.vue";
import Table from "./Table.vue";
import Statistics from "./Statistics.vue";
export default {

View File

@ -42,16 +42,17 @@ export default class ListGraph {
private xAxis: any = null;
private sequentialScale: any = null;
private root: any = null;
private selectedNode: any = null;
constructor(el: HTMLDivElement, handleSelectSpan: (i: Trace) => void) {
this.handleSelectSpan = handleSelectSpan;
this.el = el;
this.width = el.getBoundingClientRect().width - 10;
this.height = el.getBoundingClientRect().height - 10;
d3.select(".trace-list-dowanload").remove();
d3.select(`.${this.el?.className} .trace-list`).remove();
this.svg = d3
.select(this.el)
.append("svg")
.attr("class", "trace-list-dowanload")
.attr("class", "trace-list")
.attr("width", this.width > 0 ? this.width : 10)
.attr("height", this.height > 0 ? this.height : 10)
.attr("transform", `translate(-5, 0)`);
@ -85,7 +86,8 @@ export default class ListGraph {
L${d.target.y} ${d.target.x - 5}`;
}
init(data: Recordable, row: Recordable[], fixSpansSize: number) {
d3.select(".trace-xaxis").remove();
d3.select(`.${this.el?.className} .trace-xaxis`).remove();
d3.select("#trace-action-box").style("display", "none");
this.row = row;
this.data = data;
this.min = d3.min(this.row.map((i) => i.startTime));
@ -113,6 +115,14 @@ export default class ListGraph {
this.root = d3.hierarchy(this.data, (d) => d.children);
this.root.x0 = 0;
this.root.y0 = 0;
const t = this;
d3.select("svg.trace-list").on("click", function (event: MouseEvent) {
if (event.target === this) {
d3.select("#trace-action-box").style("display", "none");
t.selectedNode && t.selectedNode.classed("highlighted", false);
t.clearParentHighlight();
}
});
}
draw(callback: Function) {
this.update(this.root, callback);
@ -142,19 +152,35 @@ export default class ListGraph {
.enter()
.append("g")
.attr("transform", `translate(${source.y0},${source.x0})`)
.attr("id", (d: Recordable) => `list-node-${d.id}`)
.attr("class", "trace-node")
.attr("style", "cursor: pointer")
.style("opacity", 0)
.on("mouseover", function (event: MouseEvent, d: Trace) {
t.tip.show(d, this);
})
.on("mouseout", function (event: MouseEvent, d: Trace) {
t.tip.hide(d, this);
})
.on("click", (event: MouseEvent, d: Trace) => {
if (this.handleSelectSpan) {
this.handleSelectSpan(d);
.on("click", function (event: MouseEvent, d: Trace & { id: string }) {
event.stopPropagation();
t.tip.hide(d, this);
d3.select(this).classed("highlighted", true);
const nodeBox = this.getBoundingClientRect();
const svgBox = (d3.select(`.${t.el?.className} .trace-list`) as any).node().getBoundingClientRect();
const offsetX = nodeBox.x - svgBox.x;
const offsetY = nodeBox.y - svgBox.y;
d3.select("#trace-action-box")
.style("display", "block")
.style("left", `${offsetX + 30}px`)
.style("top", `${offsetY + 40}px`);
t.selectedNode = d3.select(this);
if (t.handleSelectSpan) {
t.handleSelectSpan(d);
}
t.root.descendants().map((node: { id: number }) => {
d3.select(`#list-node-${node.id}`).classed("highlightedParent", false);
return node;
});
});
nodeEnter
.append("rect")
@ -239,14 +265,14 @@ export default class ListGraph {
.attr("cx", (d: Recordable) => {
const events = d.data.attachedEvents;
if (events && events.length > 9) {
return 272;
return 273;
} else {
return 270;
}
})
.attr("cy", -5)
.attr("fill", "none")
.attr("stroke", appStore.theme === Themes.Dark ? "#666" : "#e66")
.attr("stroke", "#e66")
.style("opacity", (d: Recordable) => {
const events = d.data.attachedEvents;
if (events && events.length) {
@ -259,7 +285,7 @@ export default class ListGraph {
.append("text")
.attr("x", 267)
.attr("y", -1)
.attr("fill", appStore.theme === Themes.Dark ? "#666" : "#e66")
.attr("fill", "#e66")
.style("font-size", "10px")
.text((d: Recordable) => {
const events = d.data.attachedEvents;
@ -324,16 +350,6 @@ export default class ListGraph {
if (d.data.children.length === 0) return;
this.click(d, this);
});
nodeUpdate
.transition()
.duration(400)
.attr("transform", (d: Recordable) => `translate(${d.y + 3},${d.x})`)
.style("opacity", 1)
.select("circle")
.style("fill", (d: Recordable) =>
d._children ? `${this.sequentialScale(this.list.indexOf(d.data.serviceCode))}` : "#eee",
);
// Transition exiting nodes to the parent's new position.
node
.exit()
@ -381,6 +397,39 @@ export default class ListGraph {
callback();
}
}
clearParentHighlight() {
return this.root.descendants().map((node: { id: number }) => {
d3.select(`#list-node-${node.id}`).classed("highlightedParent", false);
return node;
});
}
highlightParents(span: Recordable) {
if (!span) {
return;
}
const nodes = this.clearParentHighlight();
const parentSpan = nodes.find(
(node: Recordable) =>
span.spanId === node.data.spanId &&
span.segmentId === node.data.segmentId &&
span.traceId === node.data.traceId,
);
if (!parentSpan) return;
d3.select(`#list-node-${parentSpan.id}`).classed("highlightedParent", true);
d3.select("#trace-action-box").style("display", "none");
this.selectedNode.classed("highlighted", false);
const container = document.querySelector(".trace-chart .charts");
const containerRect = container?.getBoundingClientRect();
if (!containerRect) return;
const targetElement = document.querySelector(`#list-node-${parentSpan.id}`);
if (!targetElement) return;
const targetRect = targetElement.getBoundingClientRect();
container?.scrollTo({
left: targetRect.left - containerRect.left + container?.scrollLeft,
top: targetRect.top - containerRect.top + container?.scrollTop - 100,
behavior: "smooth",
});
}
visDate(date: number, pattern = "YYYY-MM-DD HH:mm:ss:SSS") {
return dayjs(date).format(pattern);
}

View File

@ -46,6 +46,7 @@ export default class TraceMap {
private topChildMax: number[] = [];
private topChildMin: number[] = [];
private nodeUpdate: Nullable<any> = null;
private selectedNode: any = null;
constructor(el: HTMLDivElement, handleSelectSpan: (i: Trace) => void) {
this.el = el;
@ -55,7 +56,7 @@ export default class TraceMap {
this.topChild = [];
this.width = el.clientWidth - 20;
this.height = el.clientHeight - 30;
d3.select(".d3-trace-tree").remove();
d3.select(`.${this.el?.className} .d3-trace-tree`).remove();
this.body = d3
.select(this.el)
.append("svg")
@ -80,6 +81,7 @@ export default class TraceMap {
this.svg.call(this.tip);
}
init(data: Recordable, row: Recordable) {
d3.select("#trace-action-box").style("display", "none");
this.treemap = d3.tree().size([row.length * 35, this.width]);
this.row = row;
this.data = data;
@ -124,24 +126,33 @@ export default class TraceMap {
this.update(this.root);
}
update(source: Recordable) {
const t = this;
const appStore = useAppStoreWithOut();
const that: any = this;
const treeData = this.treemap(this.root);
const nodes = treeData.descendants(),
links = treeData.descendants().slice(1);
const nodes = treeData.descendants();
const links = treeData.descendants().slice(1);
nodes.forEach(function (d: Recordable) {
d.y = d.depth * 140;
});
const node = this.svg.selectAll("g.node").data(nodes, (d: Recordable) => {
const node = this.svg.selectAll("g.trace-node").data(nodes, (d: Recordable) => {
return d.id || (d.id = ++this.i);
});
d3.select("svg.d3-trace-tree").on("click", function (event: MouseEvent) {
if (event.target === this) {
d3.select("#trace-action-box").style("display", "none");
t.selectedNode && t.selectedNode.classed("highlighted", false);
t.clearParentHighlight();
}
});
const nodeEnter = node
.enter()
.append("g")
.attr("class", "node")
.attr("class", "trace-node")
.attr("id", (d: Recordable) => `trace-node-${d.id}`)
.attr("cursor", "pointer")
.attr("transform", function () {
return "translate(" + source.y0 + "," + source.x0 + ")";
@ -165,9 +176,6 @@ export default class TraceMap {
if (_node.length) {
that.timeTip.hide(d, _node[0].children[1]);
}
})
.on("click", function (event: MouseEvent, d: Recordable) {
that.handleSelectSpan(d);
});
nodeEnter
.append("circle")
@ -208,15 +216,15 @@ export default class TraceMap {
nodeEnter
.append("circle")
.attr("class", "node")
.attr("r", 1e-6)
.style("fill", (d: Recordable) =>
d._children ? this.sequentialScale(this.list.indexOf(d.data.serviceCode)) : "#fff",
)
.attr("r", 2)
.attr("stroke", (d: Recordable) => this.sequentialScale(this.list.indexOf(d.data.serviceCode)))
.attr("stroke-width", 2.5);
.attr("stroke-width", 2.5)
.attr("fill", (d: Recordable) =>
d.data.children.length ? this.sequentialScale(this.list.indexOf(d.data.serviceCode)) : "#fff",
);
nodeEnter
.append("text")
.attr("class", "trace-node-text")
.attr("font-size", 11)
.attr("dy", "-0.5em")
.attr("x", function (d: Recordable) {
@ -230,24 +238,27 @@ export default class TraceMap {
? (d.data.isError ? "◉ " : "") + d.data.label.slice(0, 10) + "..."
: (d.data.isError ? "◉ " : "") + d.data.label,
)
.style("fill", (d: Recordable) =>
.attr("fill", (d: Recordable) =>
!d.data.isError ? (appStore.theme === Themes.Dark ? "#eee" : "#3d444f") : "#E54C17",
);
nodeEnter
.append("text")
.attr("class", "node-text")
.attr("x", function (d: Recordable) {
return d.children || d._children ? -45 : 15;
return d.children || d._children ? -30 : 15;
})
.attr("dy", "1em")
.attr("dy", "1.5em")
.attr("fill", appStore.theme === Themes.Dark ? "#888" : "#bbb")
.attr("text-anchor", function (d: Recordable) {
return d.children || d._children ? "end" : "start";
})
.style("font-size", "10px")
.text(
(d: Recordable) => `${d.data.layer || ""}${d.data.component ? "-" + d.data.component : d.data.component || ""}`,
);
.text((d: Recordable) => {
const label = d.data.component
? " - " + (d.data.component.length > 10 ? d.data.label.slice(0, 10) + "..." : d.data.component)
: "";
return `${d.data.layer || ""}${label}`;
});
nodeEnter
.append("rect")
.attr("rx", 1)
@ -290,12 +301,37 @@ export default class TraceMap {
nodeUpdate
.select("circle.node")
.attr("r", 5)
.style("fill", (d: Recordable) =>
d._children ? this.sequentialScale(this.list.indexOf(d.data.serviceCode)) : "#fff",
)
.attr("cursor", "pointer")
.on("click", (event: any, d: Recordable) => {
.on("click", function (event: MouseEvent, d: Trace & { id: string }) {
event.stopPropagation();
t.tip.hide(d, this);
d3.select(this.parentNode).classed("highlighted", true);
const nodeBox = this.getBoundingClientRect();
const svgBox = (d3.select(`.${t.el?.className} .d3-trace-tree`) as any).node().getBoundingClientRect();
const offsetX = nodeBox.x - svgBox.x;
const offsetY = nodeBox.y - svgBox.y;
d3.select("#trace-action-box")
.style("display", "block")
.style("left", `${offsetX + 30}px`)
.style("top", `${offsetY + 40}px`);
t.selectedNode = d3.select(this.parentNode);
if (t.handleSelectSpan) {
t.handleSelectSpan(d);
}
t.root.descendants().map((node: { id: number }) => {
d3.select(`#trace-node-${node.id}`).classed("highlightedParent", false);
return node;
});
})
.on("dblclick", function (event: MouseEvent, d: Recordable) {
event.stopPropagation();
t.tip.hide(d, this);
if (d.data.children.length === 0) return;
click(d);
})
.on("contextmenu", function (event: MouseEvent, d: Recordable) {
event.stopPropagation();
t.tip.hide(d, this);
if (d.data.children.length === 0) return;
click(d);
});
@ -369,6 +405,39 @@ export default class TraceMap {
that.update(d);
}
}
clearParentHighlight() {
return this.root.descendants().map((node: { id: number }) => {
d3.select(`#trace-node-${node.id}`).classed("highlightedParent", false);
return node;
});
}
highlightParents(span: Recordable) {
if (!span) {
return;
}
const nodes = this.clearParentHighlight();
const parentSpan = nodes.find(
(node: Recordable) =>
span.spanId === node.data.spanId &&
span.segmentId === node.data.segmentId &&
span.traceId === node.data.traceId,
);
if (!parentSpan) return;
d3.select(`#trace-node-${parentSpan.id}`).classed("highlightedParent", true);
d3.select("#trace-action-box").style("display", "none");
this.selectedNode.classed("highlighted", false);
const container = document.querySelector(".trace-chart .charts");
const containerRect = container?.getBoundingClientRect();
if (!containerRect) return;
const targetElement = document.querySelector(`#trace-node-${parentSpan.id}`);
if (!targetElement) return;
const targetRect = targetElement.getBoundingClientRect();
container?.scrollTo({
left: targetRect.left - containerRect.left + container?.scrollLeft,
top: targetRect.top - containerRect.top + container?.scrollTop - 100,
behavior: "smooth",
});
}
setDefault() {
d3.selectAll(".time-inner").style("opacity", 1);
d3.selectAll(".time-inner-duration").style("opacity", 0);

View File

@ -15,42 +15,13 @@
* limitations under the License.
*/
import type { Ref, Span, StatisticsSpan, StatisticsGroupRef, TraceTreeRef } from "@/types/trace";
import type { Span, TraceTreeRef } from "@/types/trace";
export default class TraceUtil {
public static buildTraceDataList(data: Span[]): string[] {
return Array.from(new Set(data.map((span: Span) => span.serviceCode)));
}
public static changeTree(data: Span[], currentTraceId: string) {
const segmentIdList: Span[] = [];
const traceTreeRef: Recordable = this.changeTreeCore(data);
traceTreeRef.segmentIdGroup.forEach((segmentId: string) => {
if (traceTreeRef.segmentMap.get(segmentId).refs) {
traceTreeRef.segmentMap.get(segmentId).refs.forEach((ref: Ref) => {
if (ref.traceId === currentTraceId) {
this.traverseTree(
traceTreeRef.segmentMap.get(ref.parentSegmentId) as Span,
ref.parentSpanId,
ref.parentSegmentId,
traceTreeRef.segmentMap.get(segmentId) as Span,
);
}
});
}
});
// set a breakpoint at this line
traceTreeRef.segmentMap.forEach((value: Span) => {
if ((value.refs && value.refs.length === 0) || !value.refs) {
segmentIdList.push(value as Span);
}
});
segmentIdList.forEach((segmentId: Span) => {
this.collapse(segmentId);
});
return segmentIdList;
}
public static changeStatisticsTree(data: Span[]): Map<string, Span[]> {
const result = new Map<string, Span[]>();
const traceTreeRef = this.changeTreeCore(data);
@ -255,47 +226,6 @@ export default class TraceUtil {
}
}
private static traverseTree(node: Span, spanId: number, segmentId: string, childNode: Span) {
if (!node || node.isBroken) {
return;
}
if (node.spanId === spanId && node.segmentId === segmentId) {
node.children!.push(childNode);
return;
}
if (node.children && node.children.length > 0) {
for (const grandchild of node.children) {
this.traverseTree(grandchild, spanId, segmentId, childNode);
}
}
}
private static getSpanGroupData(groupspans: Span[], groupRef: StatisticsGroupRef): StatisticsSpan {
let maxTime = 0;
let minTime = 0;
let sumTime = 0;
const count = groupspans.length;
groupspans.forEach((groupspan: Span) => {
const duration = groupspan.dur || 0;
if (duration > maxTime) {
maxTime = duration;
}
if (duration < minTime) {
minTime = duration;
}
sumTime = sumTime + duration;
});
const avgTime = count === 0 ? 0 : sumTime / count;
return {
groupRef,
maxTime,
minTime,
sumTime,
avgTime,
count,
};
}
private static calculationChildren(nodes: Span[], result: Map<string, Span[]>): void {
nodes.forEach((node: Span) => {
const groupRef = node.endpointName + ":" + node.type;

1
src/vite-env.d.ts vendored
View File

@ -26,6 +26,5 @@ declare global {
interface Window {
Promise: any;
moment: any;
axiosCancel: any;
}
}