feat: associate metrics with trace widget on dashboards (#174)

This commit is contained in:
Fine0830
2022-10-25 11:36:49 +08:00
committed by GitHub
parent 78f0096c00
commit eda44db0cd
33 changed files with 1041 additions and 192 deletions

View File

@@ -34,7 +34,7 @@ limitations under the License. -->
</el-button>
</div>
</h5>
<div class="mb-5 blue sm">
<div class="mb-5 blue">
<Selector
size="small"
:value="
@@ -46,12 +46,7 @@ limitations under the License. -->
@change="changeTraceId"
class="trace-detail-ids"
/>
<Icon
size="sm"
class="icon grey link-hover cp ml-5"
iconName="review-list"
@click="handleClick"
/>
<Icon class="cp ml-5" iconName="copy" @click="handleClick" />
</div>
<div class="flex-h item">
<div>

View File

@@ -95,17 +95,15 @@ limitations under the License. -->
import { ref, reactive, watch, onUnmounted } from "vue";
import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
import { Option } from "@/types/app";
import { Status } from "../../data";
import { Option, DurationTime } from "@/types/app";
import { useTraceStore } from "@/store/modules/trace";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useSelectorStore } from "@/store/modules/selectors";
import ConditionTags from "@/views/components/ConditionTags.vue";
import { ElMessage } from "element-plus";
import { EntityType } from "../../data";
import { EntityType, QueryOrders, Status } from "../../data";
import { LayoutConfig } from "@/types/dashboard";
import { DurationTime } from "@/types/app";
/*global defineProps, Recordable */
const props = defineProps({
@@ -115,28 +113,29 @@ const props = defineProps({
default: () => ({ graph: {} }),
},
});
const traceId = ref<string>(
(props.data.filters && props.data.filters.traceId) || ""
);
const filters = reactive<Recordable>(props.data.filters || {});
const traceId = ref<string>(filters.traceId || "");
const { t } = useI18n();
const appStore = useAppStoreWithOut();
const selectorStore = useSelectorStore();
const dashboardStore = useDashboardStore();
const traceStore = useTraceStore();
const duration = ref<DurationTime>(
(props.data.filters && props.data.filters.duration) || appStore.durationTime
);
const duration = ref<DurationTime>(filters.duration || appStore.durationTime);
const minTraceDuration = ref<number>();
const maxTraceDuration = ref<number>();
const tagsList = ref<string[]>([]);
const tagsMap = ref<Option[]>([]);
const state = reactive<Recordable>({
status: { label: "All", value: "ALL" },
status: filters.status === "ERROR" ? Status[2] : Status[0],
instance: { value: "0", label: "All" },
endpoint: { value: "0", label: "All" },
service: { value: "", label: "" },
});
if (filters.queryOrder) {
traceStore.setTraceCondition({
queryOrder: filters.queryOrder,
});
}
if (props.needQuery) {
init();
}
@@ -164,7 +163,7 @@ async function getServices() {
ElMessage.error(resp.errors);
return;
}
state.service = traceStore.services[0];
state.service = getCurrentNode(traceStore.services) || traceStore.services[0];
getEndpoints(state.service.id);
getInstances(state.service.id);
}
@@ -175,7 +174,8 @@ async function getEndpoints(id?: string, keyword?: string) {
ElMessage.error(resp.errors);
return;
}
state.endpoint = traceStore.endpoints[0];
state.endpoint =
getCurrentNode(traceStore.endpoints) || traceStore.endpoints[0];
}
async function getInstances(id?: string) {
const resp = await traceStore.getInstances(id);
@@ -183,9 +183,39 @@ async function getInstances(id?: string) {
ElMessage.error(resp.errors);
return;
}
state.instance = traceStore.instances[0];
state.instance =
getCurrentNode(traceStore.instances) || traceStore.instances[0];
}
function searchTraces() {
function getCurrentNode(arr: { id: string }[]) {
let item;
if (!props.data.filters) {
return item;
}
if (props.data.filters.id) {
item = arr.find((d: { id: string }) => d.id === props.data.filters?.id);
}
return item;
}
function setCondition() {
let param: any = {
traceState: state.status.value || "ALL",
tags: tagsMap.value.length ? tagsMap.value : undefined,
queryOrder: traceStore.conditions.queryOrder || QueryOrders[1].value,
queryDuration: duration.value,
minTraceDuration: Number(minTraceDuration.value),
maxTraceDuration: Number(maxTraceDuration.value),
traceId: traceId.value || undefined,
paging: { pageNum: 1, pageSize: 20 },
};
if (props.data.filters && props.data.filters.id) {
param = {
...param,
serviceId: selectorStore.currentService.id,
endpointId: state.endpoint.id || undefined,
serviceInstanceId: state.instance.id || undefined,
};
return param;
}
let endpoint = "",
instance = "";
if (dashboardStore.entity === EntityType[2].value) {
@@ -194,21 +224,18 @@ function searchTraces() {
if (dashboardStore.entity === EntityType[3].value) {
instance = selectorStore.currentPod.id;
}
traceStore.setTraceCondition({
param = {
...param,
serviceId: selectorStore.currentService
? selectorStore.currentService.id
: state.service.id,
traceId: traceId.value || undefined,
endpointId: endpoint || state.endpoint.id || undefined,
serviceInstanceId: instance || state.instance.id || undefined,
traceState: state.status.value || "ALL",
queryDuration: duration.value,
minTraceDuration: Number(minTraceDuration.value),
maxTraceDuration: Number(maxTraceDuration.value),
queryOrder: traceStore.conditions.queryOrder || "BY_DURATION",
tags: tagsMap.value.length ? tagsMap.value : undefined,
paging: { pageNum: 1, pageSize: 20 },
});
};
return param;
}
function searchTraces() {
traceStore.setTraceCondition(setCondition());
queryTraces();
}
async function queryTraces() {
@@ -263,17 +290,19 @@ watch(
}
}
);
// Event widget associate with trace widget
watch(
() => props.data.filters,
(newJson, oldJson) => {
if (props.data.filters) {
if (JSON.stringify(newJson) === JSON.stringify(oldJson)) {
return;
}
traceId.value = props.data.filters.traceId || "";
duration.value = props.data.filters.duration || appStore.durationTime;
init();
if (!props.data.filters) {
return;
}
if (JSON.stringify(newJson) === JSON.stringify(oldJson)) {
return;
}
traceId.value = props.data.filters.traceId || "";
duration.value = props.data.filters.duration || appStore.durationTime;
init();
}
);
</script>

View File

@@ -0,0 +1,267 @@
<!-- 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="conditions flex-h">
<el-radio-group v-model="conditions" @change="changeCondition">
<el-radio-button
v-for="(item, index) in items"
:label="item.label"
:key="item.label + index"
border
>
{{ t(item.label) }}
</el-radio-button>
</el-radio-group>
<Selector
v-if="conditions === 'latency' && filters.latency.length > 1"
:value="filters.latency[0].value"
:options="filters.latency"
placeholder="Select a option"
@change="changeLatency"
class="ml-10"
/>
<el-popover trigger="hover" width="250" placement="bottom" effect="light">
<template #reference>
<div class="cp conditions-popup">
<Icon iconName="conditions" size="middle" />
</div>
</template>
<div>
<div class="title">{{ t("queryConditions") }}</div>
<div
v-for="key in Object.keys(FiltersKeys)"
:key="key"
v-show="traceStore.conditions[FiltersKeys[key]]"
>
<span v-if="key !== 'duration'">
{{ t(key) }}: {{ traceStore.conditions[FiltersKeys[key]] }}
</span>
</div>
</div>
</el-popover>
<el-popover trigger="hover" width="250" placement="bottom" effect="light">
<template #reference>
<div class="cp metric-value">
<Icon iconName="info_outline" size="middle" />
</div>
</template>
<div>
<div class="title">{{ t("metricValues") }}</div>
<div v-for="metric in filters.metricValue" :key="metric.value">
{{ metric.label }}: {{ metric.data }}
</div>
</div>
</el-popover>
</div>
<div class="flex-h">
<ConditionTags :type="'TRACE'" @update="updateTags" />
<div class="search-btn">
<el-button size="small" type="primary" @click="queryTraces">
{{ t("search") }}
</el-button>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, reactive, onUnmounted } from "vue";
import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
import { Option, DurationTime } from "@/types/app";
import { useTraceStore } from "@/store/modules/trace";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useSelectorStore } from "@/store/modules/selectors";
import ConditionTags from "@/views/components/ConditionTags.vue";
import { ElMessage } from "element-plus";
import { EntityType, QueryOrders, Status } from "../../data";
import { LayoutConfig } from "@/types/dashboard";
const FiltersKeys: { [key: string]: string } = {
status: "traceState",
queryOrder: "queryOrder",
duration: "queryDuration",
minTraceDuration: "minTraceDuration",
maxTraceDuration: "maxTraceDuration",
};
/*global defineProps, Recordable */
const props = defineProps({
needQuery: { type: Boolean, default: true },
data: {
type: Object as PropType<LayoutConfig>,
default: () => ({ graph: {} }),
},
});
const { t } = useI18n();
const filters = reactive<Recordable>(props.data.filters || {});
const appStore = useAppStoreWithOut();
const selectorStore = useSelectorStore();
const dashboardStore = useDashboardStore();
const traceStore = useTraceStore();
const tagsList = ref<string[]>([]);
const tagsMap = ref<Option[]>([]);
const duration = ref<DurationTime>(filters.duration || appStore.durationTime);
const state = reactive<Recordable>({
instance: "",
endpoint: "",
service: "",
});
const conditions = ref<string>("");
const items = ref<{ label: string; value: string }[]>([]);
const currentLatency = ref<number[]>(
filters.latency ? filters.latency[0].data : []
);
init();
async function init() {
for (const d of Object.keys(filters)) {
if (filters[d] && ["status", "queryOrder", "latency"].includes(d)) {
items.value.push({ label: d, value: FiltersKeys[d] });
}
}
conditions.value = (items.value[0] && items.value[0].label) || "";
if (!filters.id) {
state.service = selectorStore.currentService.id;
if (dashboardStore.entity === EntityType[2].value) {
state.instance = selectorStore.currentPod.id;
}
if (dashboardStore.entity === EntityType[3].value) {
state.endpoint = selectorStore.currentPod.id;
}
await queryTraces();
return;
}
if (dashboardStore.entity === EntityType[1].value) {
await getService();
}
if (dashboardStore.entity === EntityType[0].value) {
state.service = selectorStore.currentService.id;
await getInstance();
if (!state.instance) {
await getEndpoint();
}
}
await queryTraces();
}
function changeCondition() {
if (conditions.value === "latency") {
currentLatency.value = filters.latency ? filters.latency[0].data : [];
}
queryTraces();
}
function changeLatency(options: any[]) {
currentLatency.value = options[0].data;
queryTraces();
}
async function getService() {
const resp = await traceStore.getService(filters.id);
if (resp.errors) {
ElMessage.error(resp.errors);
return;
}
state.service = (resp.data.service && resp.data.service) || "";
}
async function getEndpoint() {
const resp = await traceStore.getEndpoint(filters.id);
if (resp.errors) {
ElMessage.error(resp.errors);
return;
}
state.endpoint = (resp.data.endpoint && resp.data.endpoint.id) || "";
}
async function getInstance() {
const resp = await traceStore.getInstance(filters.id);
if (resp.errors) {
ElMessage.error(resp.errors);
return;
}
state.instance = (resp.data.instance && resp.data.instance.id) || "";
}
function setCondition() {
let params: any = {
traceState: Status[0].value,
queryOrder: QueryOrders[0].value,
queryDuration: duration.value,
minTraceDuration: isNaN(currentLatency.value[0])
? undefined
: currentLatency.value[0] === currentLatency.value[1]
? currentLatency.value[0] - 10
: currentLatency.value[0],
maxTraceDuration:
isNaN(currentLatency.value[1]) || currentLatency.value[1] === Infinity
? undefined
: currentLatency.value[1],
tags: tagsMap.value.length ? tagsMap.value : undefined,
paging: { pageNum: 1, pageSize: 20 },
serviceId: state.service || undefined,
endpointId: state.endpoint || undefined,
serviceInstanceId: state.instance || undefined,
};
for (const k of items.value) {
if (k.label === conditions.value && FiltersKeys[k.label]) {
params[k.value] = filters[k.label];
}
}
if (!isNaN(params.minTraceDuration)) {
params.queryOrder = QueryOrders[1].value;
}
return params;
}
async function queryTraces() {
traceStore.setTraceCondition(setCondition());
const res = await traceStore.getTraces();
if (res && res.errors) {
ElMessage.error(res.errors);
}
}
function updateTags(data: { tagsMap: Array<Option>; tagsList: string[] }) {
tagsList.value = data.tagsList;
tagsMap.value = data.tagsMap;
}
onUnmounted(() => {
traceStore.resetState();
});
</script>
<style lang="scss" scoped>
.row {
margin-bottom: 5px;
position: relative;
}
.conditions {
margin-bottom: 10px;
}
.search-btn {
margin-top: 2px;
}
.metric-value {
padding: 0 5px;
line-height: 32px;
}
.conditions-popup {
padding-left: 10px;
line-height: 32px;
}
.title {
margin-bottom: 10px;
font-weight: bold;
}
</style>

View File

@@ -0,0 +1,63 @@
<!-- 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-wrapper flex-v">
<div class="header">
<Header :data="data" />
</div>
<div class="trace flex-h">
<TraceList />
<TraceDetail />
</div>
</div>
</template>
<script lang="ts" setup>
import { provide } from "vue";
import type { PropType } from "vue";
import Header from "./Header.vue";
import TraceList from "./TraceList.vue";
import TraceDetail from "./Detail.vue";
/*global defineProps */
const props = defineProps({
data: {
type: Object as PropType<any>,
default: () => ({ graph: {} }),
},
});
provide("options", props.data);
</script>
<style lang="scss" scoped>
.trace-wrapper {
width: 100%;
height: 100%;
font-size: 12px;
position: relative;
overflow: auto;
}
.header {
padding: 10px;
font-size: 12px;
border-bottom: 1px solid #dcdfe6;
min-width: 1200px;
}
.trace {
width: 100%;
overflow: auto;
min-width: 1200px;
}
</style>

View File

@@ -58,8 +58,8 @@ limitations under the License. -->
<span class="b">{{ i.endpointNames[0] }}</span>
</div>
<div class="grey ell sm">
<span class="tag mr-10 sm">{{ i.duration }} ms</span
>{{ dateFormat(parseInt(i.start, 10)) }}
<span class="tag mr-10 sm"> {{ i.duration }} ms </span>
{{ dateFormat(parseInt(i.start, 10)) }}
</div>
</td>
</tr>