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

@@ -36,6 +36,7 @@ limitations under the License. -->
metrics: dashboardStore.selectedGrid.metrics,
metricTypes: dashboardStore.selectedGrid.metricTypes,
metricConfig: dashboardStore.selectedGrid.metricConfig,
relatedTrace: dashboardStore.selectedGrid.relatedTrace,
}"
:needQuery="true"
/>
@@ -65,6 +66,13 @@ limitations under the License. -->
>
<AssociateOptions />
</el-collapse-item>
<el-collapse-item
:title="t('relatedTraceOptions')"
name="5"
v-if="hasAssociate"
>
<RelatedTraceOptions />
</el-collapse-item>
</el-collapse>
</div>
<div class="footer">

View File

@@ -0,0 +1,90 @@
<!-- 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="item">
<span class="label">{{ t("enableRelatedTrace") }}</span>
<el-switch
v-model="enableRelate"
active-text="Yes"
inactive-text="No"
@change="updateConfig({ enableRelate })"
/>
</div>
<div v-show="enableRelate">
<div class="item">
<span class="label">{{ t("status") }}</span>
<Selector
size="small"
:value="status"
:options="Status"
placeholder="Select a status"
@change="updateConfig({ status: $event[0].value })"
/>
</div>
<div class="item">
<span class="label">{{ t("queryOrder") }}</span>
<Selector
size="small"
:value="queryOrder"
:options="QueryOrders"
placeholder="Select a option"
@change="updateConfig({ queryOrder: $event[0].value })"
/>
</div>
<div class="item">
<span class="label">{{ t("setLatencyDuration") }}</span>
<el-switch
v-model="latency"
active-text="Yes"
inactive-text="No"
@change="updateConfig({ latency })"
/>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import { Status, QueryOrders } from "../../data";
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const traceOpt = dashboardStore.selectedGrid.relatedTrace || {};
const status = ref<string>(traceOpt.status || Status[0].value);
const queryOrder = ref<string>(traceOpt.queryOrder || QueryOrders[0].value);
const latency = ref<boolean>(traceOpt.setLatencyDuration || false);
const enableRelate = ref<boolean>(traceOpt.enableRelate || false);
function updateConfig(param: { [key: string]: unknown }) {
const relatedTrace = {
...dashboardStore.selectedGrid.relatedTrace,
...param,
};
dashboardStore.selectWidget({ ...dashboardStore.selectedGrid, relatedTrace });
}
</script>
<style lang="scss" scoped>
.label {
font-size: 13px;
font-weight: 500;
display: block;
margin-bottom: 5px;
}
.item {
margin-bottom: 10px;
}
</style>

View File

@@ -19,10 +19,12 @@ import StyleOptions from "./graph-styles";
import WidgetOptions from "./WidgetOptions.vue";
import MetricOptions from "./metric/Index.vue";
import AssociateOptions from "./AssociateOptions.vue";
import RelatedTraceOptions from "./RelatedTraceOptions.vue";
export default {
...StyleOptions,
WidgetOptions,
MetricOptions,
AssociateOptions,
RelatedTraceOptions,
};

View File

@@ -40,7 +40,7 @@ limitations under the License. -->
<span class="tab-icons">
<el-tooltip content="Copy Link" placement="bottom">
<i @click="copyLink">
<Icon size="middle" iconName="review-list" class="tab-icon" />
<Icon size="middle" iconName="copy" class="tab-icon" />
</i>
</el-tooltip>
</span>

View File

@@ -64,6 +64,7 @@ limitations under the License. -->
id: data.id,
metricConfig: data.metricConfig,
filters: data.filters || {},
relatedTrace: data.relatedTrace || {},
}"
:needQuery="needQuery"
@click="clickHandle"

View File

@@ -174,7 +174,7 @@ export const SortOrder = [
];
export const AllTools = [
{ name: "playlist_add", content: "Add Widget", id: "addWidget" },
{ name: "all_inbox", content: "Add Tab", id: "addTab" },
{ name: "all_inbox", content: "Add Tabs", id: "addTab" },
{ name: "library_books", content: "Add Text", id: "addText" },
{ name: "device_hub", content: "Add Topology", id: "addTopology" },
{ name: "merge", content: "Add Trace", id: "addTrace" },
@@ -182,7 +182,7 @@ export const AllTools = [
];
export const ServiceTools = [
{ name: "playlist_add", content: "Add Widget", id: "addWidget" },
{ name: "all_inbox", content: "Add Tab", id: "addTab" },
{ name: "all_inbox", content: "Add Tabs", id: "addTab" },
{ name: "library_books", content: "Add Text", id: "addText" },
{ name: "device_hub", content: "Add Topology", id: "addTopology" },
{ name: "merge", content: "Add Trace", id: "addTrace" },
@@ -194,7 +194,7 @@ export const ServiceTools = [
];
export const InstanceTools = [
{ name: "playlist_add", content: "Add Widget", id: "addWidget" },
{ name: "all_inbox", content: "Add Tab", id: "addTab" },
{ name: "all_inbox", content: "Add Tabs", id: "addTab" },
{ name: "library_books", content: "Add Text", id: "addText" },
{ name: "merge", content: "Add Trace", id: "addTrace" },
{ name: "assignment", content: "Add Log", id: "addLog" },
@@ -208,7 +208,7 @@ export const InstanceTools = [
];
export const EndpointTools = [
{ name: "playlist_add", content: "Add Widget", id: "addWidget" },
{ name: "all_inbox", content: "Add Tab", id: "addTab" },
{ name: "all_inbox", content: "Add Tabs", id: "addTab" },
{ name: "library_books", content: "Add Text", id: "addText" },
{ name: "device_hub", content: "Add Topology", id: "addTopology" },
{ name: "merge", content: "Add Trace", id: "addTrace" },
@@ -217,25 +217,25 @@ export const EndpointTools = [
];
export const ProcessTools = [
{ name: "playlist_add", content: "Add Widget", id: "addWidget" },
{ name: "all_inbox", content: "Add Tab", id: "addTab" },
{ name: "all_inbox", content: "Add Tabs", id: "addTab" },
{ name: "library_books", content: "Add Text", id: "addText" },
{ name: "time_range", content: "Add Time Range Text", id: "addTimeRange" },
];
export const ServiceRelationTools = [
{ name: "playlist_add", content: "Add Widget", id: "addWidget" },
{ name: "all_inbox", content: "Add Tab", id: "addTab" },
{ name: "all_inbox", content: "Add Tabs", id: "addTab" },
{ name: "library_books", content: "Add Text", id: "addText" },
{ name: "device_hub", content: "Add Topology", id: "addTopology" },
];
export const EndpointRelationTools = [
{ name: "playlist_add", content: "Add Widget", id: "addWidget" },
{ name: "all_inbox", content: "Add Tab", id: "addTab" },
{ name: "all_inbox", content: "Add Tabs", id: "addTab" },
{ name: "library_books", content: "Add Text", id: "addText" },
];
export const InstanceRelationTools = [
{ name: "playlist_add", content: "Add Widget", id: "addWidget" },
{ name: "all_inbox", content: "Add Tab", id: "addTab" },
{ name: "all_inbox", content: "Add Tabs", id: "addTab" },
{ name: "library_books", content: "Add Text", id: "addText" },
{ name: "device_hub", content: "Add Topology", id: "addTopology" },
];

View File

@@ -24,7 +24,12 @@ limitations under the License. -->
<script lang="ts" setup>
import type { PropType } from "vue";
import Line from "./Line.vue";
import { AreaConfig, EventParams } from "@/types/dashboard";
import {
AreaConfig,
EventParams,
RelatedTrace,
Filters,
} from "@/types/dashboard";
/*global defineProps, defineEmits */
const emits = defineEmits(["click"]);
@@ -37,15 +42,8 @@ defineProps({
config: {
type: Object as PropType<
AreaConfig & {
filters: {
sourceId: string;
duration: {
startTime: string;
endTime: string;
};
isRange: boolean;
dataIndex?: number;
};
filters: Filters;
relatedTrace: RelatedTrace;
} & { id: string }
>,
default: () => ({}),

View File

@@ -18,7 +18,12 @@ limitations under the License. -->
<script lang="ts" setup>
import { computed } from "vue";
import type { PropType } from "vue";
import { BarConfig, EventParams } from "@/types/dashboard";
import {
BarConfig,
EventParams,
RelatedTrace,
Filters,
} from "@/types/dashboard";
/*global defineProps, defineEmits */
const emits = defineEmits(["click"]);
@@ -32,15 +37,8 @@ const props = defineProps({
config: {
type: Object as PropType<
BarConfig & {
filters: {
sourceId: string;
duration: {
startTime: string;
endTime: string;
};
isRange: boolean;
dataIndex?: number;
};
filters: Filters;
relatedTrace: RelatedTrace;
} & { id: string }
>,
default: () => ({}),
@@ -107,16 +105,12 @@ function getOption() {
return {
color,
tooltip: {
trigger: "axis",
zlevel: 1000,
z: 60,
confine: true,
textStyle: {
fontSize: 13,
trigger: "none",
axisPointer: {
type: "cross",
color: "#333",
backgroundColor: "rgba(255, 255, 255, 0.8)",
},
enterable: true,
extraCssText: "max-height: 300px; overflow: auto; border: none",
},
legend: {
type: "scroll",
@@ -136,6 +130,12 @@ function getOption() {
bottom: 5,
containLabel: true,
},
axisPointer: {
label: {
color: "#333",
backgroundColor: "rgba(255, 255, 255, 0.8)",
},
},
xAxis: {
type: "category",
axisTick: {

View File

@@ -13,12 +13,22 @@ 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>
<Graph :option="option" @select="clickEvent" :filters="config.filters" />
<Graph
:option="option"
@select="clickEvent"
:filters="config.filters"
:relatedTrace="config.relatedTrace"
/>
</template>
<script lang="ts" setup>
import { computed } from "vue";
import type { PropType } from "vue";
import { LineConfig, EventParams } from "@/types/dashboard";
import {
LineConfig,
EventParams,
RelatedTrace,
Filters,
} from "@/types/dashboard";
/*global defineProps, defineEmits */
const emits = defineEmits(["click"]);
@@ -32,15 +42,8 @@ const props = defineProps({
config: {
type: Object as PropType<
LineConfig & {
filters: {
sourceId: string;
duration: {
startTime: string;
endTime: string;
};
isRange: boolean;
dataIndex?: number;
};
filters: Filters;
relatedTrace: RelatedTrace;
} & { id: string }
>,
default: () => ({
@@ -115,13 +118,19 @@ function getOption() {
break;
}
const tooltip = {
trigger: "axis",
textStyle: {
fontSize: 12,
trigger: "none",
axisPointer: {
type: "cross",
color: "#333",
backgroundColor: "rgba(255, 255, 255, 0.8)",
},
enterable: true,
confine: true,
// trigger: "axis",
// textStyle: {
// fontSize: 12,
// color: "#333",
// },
// enterable: true,
// confine: true,
extraCssText: "max-height: 300px; overflow: auto; border: none;",
};
const tips = {
@@ -151,6 +160,12 @@ function getOption() {
color: props.theme === "dark" ? "#fff" : "#333",
},
},
axisPointer: {
label: {
color: "#333",
backgroundColor: "rgba(255, 255, 255, 0.8)",
},
},
grid: {
top: keys.length === 1 ? 15 : 55,
left: 0,

View File

@@ -23,14 +23,19 @@ limitations under the License. -->
{{ i.name }}
</span>
</div>
<div class="copy">
<Icon
iconName="review-list"
size="middle"
class="cp"
@click="handleClick(i.name)"
/>
</div>
<el-popover placement="bottom" trigger="click">
<template #reference>
<div class="operation-icon cp ml-10">
<Icon iconName="ellipsis_v" size="middle" />
</div>
</template>
<div class="operation" @click="handleClick(i.name)">
<span>{{ t("copy") }}</span>
</div>
<div class="operation" @click="viewTrace(i)">
<span>{{ t("viewTrace") }}</span>
</div>
</el-popover>
</div>
<el-progress
:stroke-width="6"
@@ -39,28 +44,45 @@ limitations under the License. -->
:show-text="false"
/>
</div>
<el-drawer
v-model="showTrace"
size="100%"
:destroy-on-close="true"
:before-close="() => (showTrace = false)"
:append-to-body="true"
>
<Trace :data="traceOptions" />
</el-drawer>
</div>
<div class="center no-data" v-else>No Data</div>
</template>
<script lang="ts" setup>
import type { PropType } from "vue";
import { computed } from "vue";
import { useI18n } from "vue-i18n";
import { computed, ref } from "vue";
import copy from "@/utils/copy";
import { TextColors } from "@/views/dashboard/data";
import Trace from "@/views/dashboard/related/trace/Index.vue";
import { QueryOrders, Status } from "../data";
/*global defineProps */
const props = defineProps({
data: {
type: Object as PropType<{
[key: string]: { name: string; value: number; traceIds: string[] }[];
[key: string]: { name: string; value: number; id: string }[];
}>,
default: () => ({}),
},
config: {
type: Object as PropType<{ color: string }>,
type: Object as PropType<{ color: string; metrics: string[] }>,
default: () => ({ color: "purple" }),
},
intervalTime: { type: Array as PropType<string[]>, default: () => [] },
});
const { t } = useI18n();
const showTrace = ref<boolean>(false);
const traceOptions = ref<{ type: string; filters?: unknown }>({
type: "Trace",
});
const key = computed(() => Object.keys(props.data)[0] || "");
const available = computed(
() =>
@@ -78,6 +100,21 @@ const maxValue = computed(() => {
function handleClick(i: string) {
copy(i);
}
function viewTrace(item: { name: string; id: string; value: unknown }) {
const filters = {
...item,
queryOrder: QueryOrders[1].value,
status: Status[2].value,
metricValue: [
{ label: props.config.metrics[0], data: item.value, value: item.name },
],
};
traceOptions.value = {
...traceOptions.value,
filters,
};
showTrace.value = true;
}
</script>
<style lang="scss" scoped>
.top-list {
@@ -109,10 +146,6 @@ function handleClick(i: string) {
text-overflow: ellipsis;
}
.copy {
width: 30px;
}
.calls {
font-size: 12px;
padding: 0 5px;
@@ -141,4 +174,22 @@ function handleClick(i: string) {
-webkit-box-pack: center;
-webkit-box-align: center;
}
.operation-icon {
color: #333;
}
.operation {
padding: 5px 0;
color: #333;
cursor: pointer;
position: relative;
text-align: center;
font-size: 12px;
&:hover {
color: #409eff;
background-color: #eee;
}
}
</style>

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>