feat: add instance hierarchy topology

This commit is contained in:
Fine 2024-01-11 19:40:23 +08:00
parent b18f206622
commit 0cc7329e5b
5 changed files with 123 additions and 64 deletions

View File

@ -1,3 +1,4 @@
import { dashboardStore } from "./dashboard";
/** /**
* Licensed to the Apache Software Foundation (ASF) under one or more * Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with * 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 type { Node, Call, HierarchyNode, ServiceHierarchy, InstanceHierarchy } from "@/types/topology";
import graphql from "@/graphql"; import graphql from "@/graphql";
import { useSelectorStore } from "@/store/modules/selectors"; import { useSelectorStore } from "@/store/modules/selectors";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app"; import { useAppStoreWithOut } from "@/store/modules/app";
import type { AxiosResponse } from "axios"; import type { AxiosResponse } from "axios";
import query from "@/graphql/fetch"; import query from "@/graphql/fetch";
@ -43,6 +45,7 @@ interface TopologyState {
linkServerMetrics: MetricVal; linkServerMetrics: MetricVal;
linkClientMetrics: MetricVal; linkClientMetrics: MetricVal;
hierarchyNodeMetrics: { [key: string]: MetricVal }; hierarchyNodeMetrics: { [key: string]: MetricVal };
hierarchyInstanceNodeMetrics: { [key: string]: MetricVal };
} }
export const topologyStore = defineStore({ export const topologyStore = defineStore({
@ -60,6 +63,7 @@ export const topologyStore = defineStore({
linkServerMetrics: {}, linkServerMetrics: {},
linkClientMetrics: {}, linkClientMetrics: {},
hierarchyNodeMetrics: {}, hierarchyNodeMetrics: {},
hierarchyInstanceNodeMetrics: {},
}), }),
actions: { actions: {
setNode(node: Node) { setNode(node: Node) {
@ -183,6 +187,9 @@ export const topologyStore = defineStore({
setHierarchyNodeMetricValue(m: MetricVal, layer: string) { setHierarchyNodeMetricValue(m: MetricVal, layer: string) {
this.hierarchyNodeMetrics[layer] = m; this.hierarchyNodeMetrics[layer] = m;
}, },
setHierarchyInstanceNodeMetricValue(m: MetricVal, layer: string) {
this.hierarchyInstanceNodeMetrics[layer] = m;
},
setLinkServerMetrics(m: MetricVal) { setLinkServerMetrics(m: MetricVal) {
this.linkServerMetrics = m; this.linkServerMetrics = m;
}, },
@ -574,17 +581,33 @@ export const topologyStore = defineStore({
this.setHierarchyServiceTopology(res.data.data.hierarchyServiceTopology || {}); this.setHierarchyServiceTopology(res.data.data.hierarchyServiceTopology || {});
return res.data; return res.data;
}, },
async getHierarchyInstanceTopology(params: { instanceId: string; layer: string }) { async getHierarchyInstanceTopology() {
if (!(params.instanceId && params.layer)) { const { currentPod } = useSelectorStore();
const dashboardStore = useDashboardStore();
if (!(currentPod && dashboardStore.layerId)) {
return new Promise((resolve) => resolve({})); 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) { if (res.data.errors) {
return res.data; return res.data;
} }
this.setInstanceTopology(res.data.data.hierarchyInstanceTopology || {}); this.setHierarchyInstanceTopology(res.data.data.hierarchyInstanceTopology || {});
return res.data; 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) { async queryHierarchyNodeExpressions(expressions: string[], layer: string) {
const nodes = this.hierarchyServiceNodes.filter((n: Node) => n.layer === layer); const nodes = this.hierarchyServiceNodes.filter((n: Node) => n.layer === layer);
if (!nodes.length) { if (!nodes.length) {
@ -598,15 +621,24 @@ export const topologyStore = defineStore({
this.setHierarchyNodeMetricValue({}, layer); this.setHierarchyNodeMetricValue({}, layer);
return; return;
} }
const { getExpressionQuery, handleExpressionValues } = useQueryTopologyExpressionsProcessor(expressions, nodes); const metrics = await this.queryHierarchyExpressions(expressions, nodes);
const param = getExpressionQuery(); this.setHierarchyNodeMetricValue(metrics, layer);
const res = await this.getNodeExpressionValue(param); },
if (res.errors) { async queryHierarchyInstanceNodeExpressions(expressions: string[], layer: string) {
ElMessage.error(res.errors); const nodes = this.hierarchyInstanceNodes.filter((n: Node) => n.layer === layer);
if (!nodes.length) {
return; return;
} }
const metrics = handleExpressionValues(res.data); if (!expressions.length) {
this.setHierarchyNodeMetricValue(metrics, layer); this.setHierarchyInstanceNodeMetricValue({}, layer);
return;
}
if (!this.hierarchyServiceNodes.length) {
this.setHierarchyInstanceNodeMetricValue({}, layer);
return;
}
const metrics = await this.queryHierarchyExpressions(expressions, nodes);
this.setHierarchyInstanceNodeMetricValue(metrics, layer);
}, },
}, },
}); });

View File

@ -134,12 +134,15 @@ limitations under the License. -->
</div> </div>
</div> </div>
<el-dialog v-model="showHierarchy" :destroy-on-close="true" @closed="showHierarchy = false" width="640px"> <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> </el-dialog>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { reactive, ref, computed, watch } from "vue"; import { reactive, ref, computed, watch } from "vue";
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard"; import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app"; import { useAppStoreWithOut } from "@/store/modules/app";
import { import {
@ -158,7 +161,7 @@ limitations under the License. -->
import { useSelectorStore } from "@/store/modules/selectors"; import { useSelectorStore } from "@/store/modules/selectors";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import type { Option } from "@/types/app"; 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 { t } = useI18n();
const dashboardStore = useDashboardStore(); const dashboardStore = useDashboardStore();
@ -726,4 +729,10 @@ limitations under the License. -->
text-align: center; text-align: center;
color: #aaa; color: #aaa;
} }
.hierarchy-related {
height: 400px;
width: 600px;
overflow: auto;
}
</style> </style>

View File

@ -75,13 +75,10 @@ limitations under the License. -->
import { ref, computed, watch } from "vue"; import { ref, computed, watch } from "vue";
import * as d3 from "d3"; import * as d3 from "d3";
import type { Node, Call } from "@/types/topology"; import type { Node, Call } from "@/types/topology";
import { useTopologyStore } from "@/store/modules/topology";
import { useDashboardStore } from "@/store/modules/dashboard"; import { useDashboardStore } from "@/store/modules/dashboard";
import { EntityType, ConfigFieldTypes } from "@/views/dashboard/data";
import icons from "@/assets/img/icons"; import icons from "@/assets/img/icons";
import { layout, changeNode, computeLevels } from "./utils/layout"; import { layout, changeNode, computeLevels } from "./utils/layout";
import zoom from "@/views/dashboard/related/components/utils/zoom"; import zoom from "@/views/dashboard/related/components/utils/zoom";
import getDashboard from "@/hooks/useDashboardsSession";
/*global Nullable, defineProps */ /*global Nullable, defineProps */
const props = defineProps({ const props = defineProps({
@ -102,8 +99,7 @@ limitations under the License. -->
default: () => [], default: () => [],
}, },
}); });
const emits = defineEmits(["showNodeTip", "handleNodeClick", "hideTip"]); const emits = defineEmits(["showNodeTip", "handleNodeClick", "hideTip", "getNodeMetrics"]);
const topologyStore = useTopologyStore();
const dashboardStore = useDashboardStore(); const dashboardStore = useDashboardStore();
const height = ref<number>(100); const height = ref<number>(100);
const width = ref<number>(100); const width = ref<number>(100);
@ -124,37 +120,9 @@ limitations under the License. -->
width.value = dom.width; width.value = dom.width;
svg.value = d3.select(".hierarchy-services-svg"); svg.value = d3.select(".hierarchy-services-svg");
graph.value = d3.select(".hierarchy-services-graph"); graph.value = d3.select(".hierarchy-services-graph");
await update(); emits("getNodeMetrics");
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);
}
draw(); draw();
svg.value.call(zoom(d3, graph.value, diff.value));
} }
function draw() { function draw() {

View File

@ -20,10 +20,10 @@ limitations under the License. -->
:style="`height: ${height}px`" :style="`height: ${height}px`"
> >
<Graph <Graph
:config="config" :nodes="topologyStore.hierarchyInstanceNodes"
:nodes="topologyStore.hierarchyServiceNodes" :calls="topologyStore.hierarchyInstanceCalls"
:calls="topologyStore.hierarchyServiceCalls" :entity="EntityType[3].value"
:entity="EntityType[0].value" @getNodeMetrics="getNodeMetrics"
@showNodeTip="showNodeTip" @showNodeTip="showNodeTip"
@handleNodeClick="handleNodeClick" @handleNodeClick="handleNodeClick"
@hideTip="hideTip" @hideTip="hideTip"
@ -32,7 +32,6 @@ limitations under the License. -->
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import type { PropType } from "vue";
import { ref, onMounted, nextTick } from "vue"; import { ref, onMounted, nextTick } from "vue";
import * as d3 from "d3"; import * as d3 from "d3";
import type { Node } from "@/types/topology"; import type { Node } from "@/types/topology";
@ -46,13 +45,7 @@ limitations under the License. -->
import getDashboard from "@/hooks/useDashboardsSession"; import getDashboard from "@/hooks/useDashboardsSession";
import Graph from "../components/Graph.vue"; import Graph from "../components/Graph.vue";
/*global Nullable, defineProps */ /*global Nullable*/
defineProps({
config: {
type: Object as PropType<any>,
default: () => ({}),
},
});
const topologyStore = useTopologyStore(); const topologyStore = useTopologyStore();
const dashboardStore = useDashboardStore(); const dashboardStore = useDashboardStore();
const height = ref<number>(100); const height = ref<number>(100);
@ -77,7 +70,7 @@ limitations under the License. -->
async function getTopology() { async function getTopology() {
loading.value = true; loading.value = true;
const resp = await topologyStore.getHierarchyServiceTopology(); const resp = await topologyStore.getHierarchyInstanceTopology();
loading.value = false; loading.value = false;
if (resp && resp.errors) { 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) { function showNodeTip(event: MouseEvent, data: Node) {
if (!data) { if (!data) {
return; return;
@ -96,7 +117,7 @@ limitations under the License. -->
getDashboard( getDashboard(
{ {
layer: data.layer || "", layer: data.layer || "",
entity: EntityType[0].value, entity: EntityType[3].value,
}, },
ConfigFieldTypes.ISDEFAULT, ConfigFieldTypes.ISDEFAULT,
).dashboard || {}; ).dashboard || {};
@ -104,7 +125,7 @@ limitations under the License. -->
const nodeMetricConfig = dashboard.expressionsConfig || []; const nodeMetricConfig = dashboard.expressionsConfig || [];
const html = exprssions.map((m: string, index: number) => { const html = exprssions.map((m: string, index: number) => {
const metric = 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, (val: { id: string; value: unknown }) => val.id === data.id,
) || {}; ) || {};
const opt: MetricConfigOpt = nodeMetricConfig[index] || {}; const opt: MetricConfigOpt = nodeMetricConfig[index] || {};
@ -135,7 +156,7 @@ limitations under the License. -->
getDashboard( getDashboard(
{ {
layer: d.layer || "", layer: d.layer || "",
entity: EntityType[0].value, entity: EntityType[3].value,
}, },
ConfigFieldTypes.ISDEFAULT, ConfigFieldTypes.ISDEFAULT,
).dashboard || {}; ).dashboard || {};

View File

@ -24,6 +24,7 @@ limitations under the License. -->
:nodes="topologyStore.hierarchyServiceNodes" :nodes="topologyStore.hierarchyServiceNodes"
:calls="topologyStore.hierarchyServiceCalls" :calls="topologyStore.hierarchyServiceCalls"
:entity="EntityType[0].value" :entity="EntityType[0].value"
@getNodeMetrics="getNodeMetrics"
@showNodeTip="showNodeTip" @showNodeTip="showNodeTip"
@handleNodeClick="handleNodeClick" @handleNodeClick="handleNodeClick"
@hideTip="hideTip" @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) { function showNodeTip(event: MouseEvent, data: Node) {
if (!data) { if (!data) {
return; return;