mirror of
https://github.com/apache/skywalking-booster-ui.git
synced 2025-05-12 15:52:57 +00:00
feat: add instance hierarchy topology
This commit is contained in:
parent
b18f206622
commit
0cc7329e5b
@ -1,3 +1,4 @@
|
||||
import { dashboardStore } from "./dashboard";
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
@ -20,6 +21,7 @@ import type { Service } from "@/types/selector";
|
||||
import type { Node, Call, HierarchyNode, ServiceHierarchy, InstanceHierarchy } from "@/types/topology";
|
||||
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";
|
||||
@ -43,6 +45,7 @@ interface TopologyState {
|
||||
linkServerMetrics: MetricVal;
|
||||
linkClientMetrics: MetricVal;
|
||||
hierarchyNodeMetrics: { [key: string]: MetricVal };
|
||||
hierarchyInstanceNodeMetrics: { [key: string]: MetricVal };
|
||||
}
|
||||
|
||||
export const topologyStore = defineStore({
|
||||
@ -60,6 +63,7 @@ export const topologyStore = defineStore({
|
||||
linkServerMetrics: {},
|
||||
linkClientMetrics: {},
|
||||
hierarchyNodeMetrics: {},
|
||||
hierarchyInstanceNodeMetrics: {},
|
||||
}),
|
||||
actions: {
|
||||
setNode(node: Node) {
|
||||
@ -183,6 +187,9 @@ export const topologyStore = defineStore({
|
||||
setHierarchyNodeMetricValue(m: MetricVal, layer: string) {
|
||||
this.hierarchyNodeMetrics[layer] = m;
|
||||
},
|
||||
setHierarchyInstanceNodeMetricValue(m: MetricVal, layer: string) {
|
||||
this.hierarchyInstanceNodeMetrics[layer] = m;
|
||||
},
|
||||
setLinkServerMetrics(m: MetricVal) {
|
||||
this.linkServerMetrics = m;
|
||||
},
|
||||
@ -574,17 +581,33 @@ export const topologyStore = defineStore({
|
||||
this.setHierarchyServiceTopology(res.data.data.hierarchyServiceTopology || {});
|
||||
return res.data;
|
||||
},
|
||||
async getHierarchyInstanceTopology(params: { instanceId: string; layer: string }) {
|
||||
if (!(params.instanceId && params.layer)) {
|
||||
async getHierarchyInstanceTopology() {
|
||||
const { currentPod } = useSelectorStore();
|
||||
const dashboardStore = useDashboardStore();
|
||||
|
||||
if (!(currentPod && dashboardStore.layerId)) {
|
||||
return new Promise((resolve) => resolve({}));
|
||||
}
|
||||
const res: AxiosResponse = await graphql.query("getHierarchyInstanceTopology").params(params);
|
||||
const res: AxiosResponse = await graphql
|
||||
.query("getHierarchyInstanceTopology")
|
||||
.params({ instanceId: currentPod.id, layer: dashboardStore.layerId });
|
||||
if (res.data.errors) {
|
||||
return res.data;
|
||||
}
|
||||
this.setInstanceTopology(res.data.data.hierarchyInstanceTopology || {});
|
||||
this.setHierarchyInstanceTopology(res.data.data.hierarchyInstanceTopology || {});
|
||||
return res.data;
|
||||
},
|
||||
async queryHierarchyExpressions(expressions: string[], nodes: Node[]) {
|
||||
const { getExpressionQuery, handleExpressionValues } = useQueryTopologyExpressionsProcessor(expressions, nodes);
|
||||
const param = getExpressionQuery();
|
||||
const res = await this.getNodeExpressionValue(param);
|
||||
if (res.errors) {
|
||||
ElMessage.error(res.errors);
|
||||
return;
|
||||
}
|
||||
const metrics = handleExpressionValues(res.data);
|
||||
return metrics;
|
||||
},
|
||||
async queryHierarchyNodeExpressions(expressions: string[], layer: string) {
|
||||
const nodes = this.hierarchyServiceNodes.filter((n: Node) => n.layer === layer);
|
||||
if (!nodes.length) {
|
||||
@ -598,15 +621,24 @@ export const topologyStore = defineStore({
|
||||
this.setHierarchyNodeMetricValue({}, layer);
|
||||
return;
|
||||
}
|
||||
const { getExpressionQuery, handleExpressionValues } = useQueryTopologyExpressionsProcessor(expressions, nodes);
|
||||
const param = getExpressionQuery();
|
||||
const res = await this.getNodeExpressionValue(param);
|
||||
if (res.errors) {
|
||||
ElMessage.error(res.errors);
|
||||
const metrics = await this.queryHierarchyExpressions(expressions, nodes);
|
||||
this.setHierarchyNodeMetricValue(metrics, layer);
|
||||
},
|
||||
async queryHierarchyInstanceNodeExpressions(expressions: string[], layer: string) {
|
||||
const nodes = this.hierarchyInstanceNodes.filter((n: Node) => n.layer === layer);
|
||||
if (!nodes.length) {
|
||||
return;
|
||||
}
|
||||
const metrics = handleExpressionValues(res.data);
|
||||
this.setHierarchyNodeMetricValue(metrics, layer);
|
||||
if (!expressions.length) {
|
||||
this.setHierarchyInstanceNodeMetricValue({}, layer);
|
||||
return;
|
||||
}
|
||||
if (!this.hierarchyServiceNodes.length) {
|
||||
this.setHierarchyInstanceNodeMetricValue({}, layer);
|
||||
return;
|
||||
}
|
||||
const metrics = await this.queryHierarchyExpressions(expressions, nodes);
|
||||
this.setHierarchyInstanceNodeMetricValue(metrics, layer);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -134,12 +134,15 @@ limitations under the License. -->
|
||||
</div>
|
||||
</div>
|
||||
<el-dialog v-model="showHierarchy" :destroy-on-close="true" @closed="showHierarchy = false" width="640px">
|
||||
<div class="hierarchy-related"> hierarchy-related </div>
|
||||
<div class="hierarchy-related">
|
||||
<instance-map />
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, computed, watch } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||
import { useAppStoreWithOut } from "@/store/modules/app";
|
||||
import {
|
||||
@ -158,7 +161,7 @@ limitations under the License. -->
|
||||
import { useSelectorStore } from "@/store/modules/selectors";
|
||||
import { ElMessage } from "element-plus";
|
||||
import type { Option } from "@/types/app";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import InstanceMap from "@/views/dashboard/related/topology/pod/InstanceMap.vue";
|
||||
|
||||
const { t } = useI18n();
|
||||
const dashboardStore = useDashboardStore();
|
||||
@ -726,4 +729,10 @@ limitations under the License. -->
|
||||
text-align: center;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.hierarchy-related {
|
||||
height: 400px;
|
||||
width: 600px;
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
||||
|
@ -75,13 +75,10 @@ limitations under the License. -->
|
||||
import { ref, computed, watch } from "vue";
|
||||
import * as d3 from "d3";
|
||||
import type { Node, Call } from "@/types/topology";
|
||||
import { useTopologyStore } from "@/store/modules/topology";
|
||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||
import { EntityType, ConfigFieldTypes } from "@/views/dashboard/data";
|
||||
import icons from "@/assets/img/icons";
|
||||
import { layout, changeNode, computeLevels } from "./utils/layout";
|
||||
import zoom from "@/views/dashboard/related/components/utils/zoom";
|
||||
import getDashboard from "@/hooks/useDashboardsSession";
|
||||
|
||||
/*global Nullable, defineProps */
|
||||
const props = defineProps({
|
||||
@ -102,8 +99,7 @@ limitations under the License. -->
|
||||
default: () => [],
|
||||
},
|
||||
});
|
||||
const emits = defineEmits(["showNodeTip", "handleNodeClick", "hideTip"]);
|
||||
const topologyStore = useTopologyStore();
|
||||
const emits = defineEmits(["showNodeTip", "handleNodeClick", "hideTip", "getNodeMetrics"]);
|
||||
const dashboardStore = useDashboardStore();
|
||||
const height = ref<number>(100);
|
||||
const width = ref<number>(100);
|
||||
@ -124,37 +120,9 @@ limitations under the License. -->
|
||||
width.value = dom.width;
|
||||
svg.value = d3.select(".hierarchy-services-svg");
|
||||
graph.value = d3.select(".hierarchy-services-graph");
|
||||
await update();
|
||||
svg.value.call(zoom(d3, graph.value, diff.value));
|
||||
}
|
||||
|
||||
async function update() {
|
||||
const layerList = [];
|
||||
const layerMap = new Map();
|
||||
for (const n of props.nodes) {
|
||||
if (layerMap.get(n.layer)) {
|
||||
const arr = layerMap.get(n.layer);
|
||||
arr.push(n);
|
||||
layerMap.set(n.layer, arr);
|
||||
} else {
|
||||
layerMap.set(n.layer, [n]);
|
||||
}
|
||||
}
|
||||
for (const d of layerMap.values()) {
|
||||
layerList.push(d);
|
||||
}
|
||||
for (const list of layerList) {
|
||||
const { dashboard } = getDashboard(
|
||||
{
|
||||
layer: list[0].layer || "",
|
||||
entity: EntityType[0].value,
|
||||
},
|
||||
ConfigFieldTypes.ISDEFAULT,
|
||||
);
|
||||
const exp = (dashboard && dashboard.expressions) || [];
|
||||
await topologyStore.queryHierarchyNodeExpressions(exp, list[0].layer);
|
||||
}
|
||||
emits("getNodeMetrics");
|
||||
draw();
|
||||
svg.value.call(zoom(d3, graph.value, diff.value));
|
||||
}
|
||||
|
||||
function draw() {
|
||||
|
@ -20,10 +20,10 @@ limitations under the License. -->
|
||||
:style="`height: ${height}px`"
|
||||
>
|
||||
<Graph
|
||||
:config="config"
|
||||
:nodes="topologyStore.hierarchyServiceNodes"
|
||||
:calls="topologyStore.hierarchyServiceCalls"
|
||||
:entity="EntityType[0].value"
|
||||
:nodes="topologyStore.hierarchyInstanceNodes"
|
||||
:calls="topologyStore.hierarchyInstanceCalls"
|
||||
:entity="EntityType[3].value"
|
||||
@getNodeMetrics="getNodeMetrics"
|
||||
@showNodeTip="showNodeTip"
|
||||
@handleNodeClick="handleNodeClick"
|
||||
@hideTip="hideTip"
|
||||
@ -32,7 +32,6 @@ limitations under the License. -->
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import type { PropType } from "vue";
|
||||
import { ref, onMounted, nextTick } from "vue";
|
||||
import * as d3 from "d3";
|
||||
import type { Node } from "@/types/topology";
|
||||
@ -46,13 +45,7 @@ limitations under the License. -->
|
||||
import getDashboard from "@/hooks/useDashboardsSession";
|
||||
import Graph from "../components/Graph.vue";
|
||||
|
||||
/*global Nullable, defineProps */
|
||||
defineProps({
|
||||
config: {
|
||||
type: Object as PropType<any>,
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
/*global Nullable*/
|
||||
const topologyStore = useTopologyStore();
|
||||
const dashboardStore = useDashboardStore();
|
||||
const height = ref<number>(100);
|
||||
@ -77,7 +70,7 @@ limitations under the License. -->
|
||||
|
||||
async function getTopology() {
|
||||
loading.value = true;
|
||||
const resp = await topologyStore.getHierarchyServiceTopology();
|
||||
const resp = await topologyStore.getHierarchyInstanceTopology();
|
||||
loading.value = false;
|
||||
|
||||
if (resp && resp.errors) {
|
||||
@ -85,6 +78,34 @@ limitations under the License. -->
|
||||
}
|
||||
}
|
||||
|
||||
async function getNodeMetrics() {
|
||||
const layerList = [];
|
||||
const layerMap = new Map();
|
||||
for (const n of topologyStore.hierarchyInstanceNodes) {
|
||||
if (layerMap.get(n.layer)) {
|
||||
const arr = layerMap.get(n.layer);
|
||||
arr.push(n);
|
||||
layerMap.set(n.layer, arr);
|
||||
} else {
|
||||
layerMap.set(n.layer, [n]);
|
||||
}
|
||||
}
|
||||
for (const d of layerMap.values()) {
|
||||
layerList.push(d);
|
||||
}
|
||||
for (const list of layerList) {
|
||||
const { dashboard } = getDashboard(
|
||||
{
|
||||
layer: list[0].layer || "",
|
||||
entity: EntityType[3].value,
|
||||
},
|
||||
ConfigFieldTypes.ISDEFAULT,
|
||||
);
|
||||
const exp = (dashboard && dashboard.expressions) || [];
|
||||
await topologyStore.queryHierarchyInstanceNodeExpressions(exp, list[0].layer);
|
||||
}
|
||||
}
|
||||
|
||||
function showNodeTip(event: MouseEvent, data: Node) {
|
||||
if (!data) {
|
||||
return;
|
||||
@ -96,7 +117,7 @@ limitations under the License. -->
|
||||
getDashboard(
|
||||
{
|
||||
layer: data.layer || "",
|
||||
entity: EntityType[0].value,
|
||||
entity: EntityType[3].value,
|
||||
},
|
||||
ConfigFieldTypes.ISDEFAULT,
|
||||
).dashboard || {};
|
||||
@ -104,7 +125,7 @@ limitations under the License. -->
|
||||
const nodeMetricConfig = dashboard.expressionsConfig || [];
|
||||
const html = exprssions.map((m: string, index: number) => {
|
||||
const metric =
|
||||
topologyStore.hierarchyNodeMetrics[data.layer || ""][m].values.find(
|
||||
topologyStore.hierarchyInstanceNodeMetrics[data.layer || ""][m].values.find(
|
||||
(val: { id: string; value: unknown }) => val.id === data.id,
|
||||
) || {};
|
||||
const opt: MetricConfigOpt = nodeMetricConfig[index] || {};
|
||||
@ -135,7 +156,7 @@ limitations under the License. -->
|
||||
getDashboard(
|
||||
{
|
||||
layer: d.layer || "",
|
||||
entity: EntityType[0].value,
|
||||
entity: EntityType[3].value,
|
||||
},
|
||||
ConfigFieldTypes.ISDEFAULT,
|
||||
).dashboard || {};
|
||||
|
@ -24,6 +24,7 @@ limitations under the License. -->
|
||||
:nodes="topologyStore.hierarchyServiceNodes"
|
||||
:calls="topologyStore.hierarchyServiceCalls"
|
||||
:entity="EntityType[0].value"
|
||||
@getNodeMetrics="getNodeMetrics"
|
||||
@showNodeTip="showNodeTip"
|
||||
@handleNodeClick="handleNodeClick"
|
||||
@hideTip="hideTip"
|
||||
@ -85,6 +86,34 @@ limitations under the License. -->
|
||||
}
|
||||
}
|
||||
|
||||
async function getNodeMetrics() {
|
||||
const layerList = [];
|
||||
const layerMap = new Map();
|
||||
for (const n of topologyStore.hierarchyServiceNodes) {
|
||||
if (layerMap.get(n.layer)) {
|
||||
const arr = layerMap.get(n.layer);
|
||||
arr.push(n);
|
||||
layerMap.set(n.layer, arr);
|
||||
} else {
|
||||
layerMap.set(n.layer, [n]);
|
||||
}
|
||||
}
|
||||
for (const d of layerMap.values()) {
|
||||
layerList.push(d);
|
||||
}
|
||||
for (const list of layerList) {
|
||||
const { dashboard } = getDashboard(
|
||||
{
|
||||
layer: list[0].layer || "",
|
||||
entity: EntityType[0].value,
|
||||
},
|
||||
ConfigFieldTypes.ISDEFAULT,
|
||||
);
|
||||
const exp = (dashboard && dashboard.expressions) || [];
|
||||
await topologyStore.queryHierarchyNodeExpressions(exp, list[0].layer);
|
||||
}
|
||||
}
|
||||
|
||||
function showNodeTip(event: MouseEvent, data: Node) {
|
||||
if (!data) {
|
||||
return;
|
||||
|
Loading…
Reference in New Issue
Block a user