feat: the log widget and the trace widget associate with each other, remove log tables on the trace widget (#128)

This commit is contained in:
Fine0830 2022-07-27 16:24:34 +08:00 committed by GitHub
parent 673b1a41a8
commit 2ba3c67d31
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 230 additions and 168 deletions

View File

@ -14,24 +14,27 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import { ElMessage } from "element-plus";
import { useDashboardStore } from "@/store/modules/dashboard"; import { useDashboardStore } from "@/store/modules/dashboard";
export default function getDashboard(param: { import { LayoutConfig } from "@/types/dashboard";
export default function getDashboard(param?: {
name: string; name: string;
layer: string; layer: string;
entity: string; entity: string;
}) { }) {
const dashboardStore = useDashboardStore(); const dashboardStore = useDashboardStore();
const opt = param || dashboardStore.currentDashboard;
const list = JSON.parse(sessionStorage.getItem("dashboards") || "[]"); const list = JSON.parse(sessionStorage.getItem("dashboards") || "[]");
const dashboard = list.find( const dashboard = list.find(
(d: { name: string; layer: string; entity: string }) => (d: { name: string; layer: string; entity: string }) =>
d.name === param.name && d.name === opt.name && d.entity === opt.entity && d.layer === opt.layer
d.entity === param.entity &&
d.layer === param.layer
); );
const all = dashboardStore.layout; const all = dashboardStore.layout;
const widgets = []; const widgets: LayoutConfig[] = [];
for (const item of all) { for (const item of all) {
if (item.type === "Tab") { if (item.type === "Tab") {
widgets.push(item);
if (item.children && item.children.length) { if (item.children && item.children.length) {
for (const child of item.children) { for (const child of item.children) {
if (child.children && child.children.length) { if (child.children && child.children.length) {
@ -43,5 +46,36 @@ export default function getDashboard(param: {
widgets.push(item); widgets.push(item);
} }
} }
return { dashboard, widgets }; function associationWidget(sourceId: string, filters: unknown, type: string) {
const widget = widgets.find((d: { type: string }) => d.type === type);
if (!widget) {
return ElMessage.info(`There has no a ${type} widget in the dashboard`);
}
const item = {
...widget,
filters,
};
dashboardStore.setWidget(item);
const targetTabIndex = (widget.id || "").split("-");
const sourceTabindex = (sourceId || "").split("-") || [];
let container: Nullable<Element>;
if (targetTabIndex[1] === undefined) {
container = document.querySelector(".ds-main");
} else {
const w = widgets.find((d: any) => d.id === targetTabIndex[0]);
container = document.querySelector(".tab-layout");
const layout: Nullable<Element> = document.querySelector(".ds-main");
if (w && layout) {
layout.scrollTop = w.y * 10 + w.h * 5;
}
}
if (targetTabIndex[1] && targetTabIndex[1] !== sourceTabindex[1]) {
dashboardStore.setActiveTabIndex(Number(targetTabIndex[1]));
}
if (container && widget) {
container.scrollTop = widget.y * 10 + widget.h * 5;
}
}
return { dashboard, widgets, associationWidget };
} }

View File

@ -39,6 +39,7 @@ interface DashboardState {
dashboards: DashboardItem[]; dashboards: DashboardItem[];
currentDashboard: Nullable<DashboardItem>; currentDashboard: Nullable<DashboardItem>;
editMode: boolean; editMode: boolean;
currentTabIndex: number;
} }
export const dashboardStore = defineStore({ export const dashboardStore = defineStore({
@ -56,6 +57,7 @@ export const dashboardStore = defineStore({
dashboards: [], dashboards: [],
currentDashboard: null, currentDashboard: null,
editMode: false, editMode: false,
currentTabIndex: 0,
}), }),
actions: { actions: {
setLayout(data: LayoutConfig[]) { setLayout(data: LayoutConfig[]) {
@ -189,6 +191,7 @@ export const dashboardStore = defineStore({
this.activedGridItem = index; this.activedGridItem = index;
}, },
setActiveTabIndex(index: number, target?: number) { setActiveTabIndex(index: number, target?: number) {
this.currentTabIndex = index;
const m = target || this.activedGridItem; const m = target || this.activedGridItem;
const idx = this.layout.findIndex((d: LayoutConfig) => d.i === m); const idx = this.layout.findIndex((d: LayoutConfig) => d.i === m);
if (idx < 0) { if (idx < 0) {

View File

@ -63,6 +63,14 @@ export const traceStore = defineStore({
setTraceSpans(spans: Span) { setTraceSpans(spans: Span) {
this.traceSpans = spans; this.traceSpans = spans;
}, },
resetCondition() {
this.conditions = {
queryDuration: useAppStoreWithOut().durationTime,
paging: { pageNum: 1, pageSize: 20 },
traceState: "ALL",
queryOrder: "BY_START_TIME",
};
},
async getServices(layer: string) { async getServices(layer: string) {
const res: AxiosResponse = await graphql.query("queryServices").params({ const res: AxiosResponse = await graphql.query("queryServices").params({
layer, layer,

View File

@ -47,6 +47,9 @@ export interface LayoutConfig {
startTime: string; startTime: string;
endTime: string; endTime: string;
}; };
traceId?: string;
spanId?: string;
segmentId?: string;
}; };
} }

View File

@ -15,11 +15,7 @@ limitations under the License. -->
<template> <template>
<div> <div>
<span class="grey">{{ t("tags") }}: </span> <span class="grey">{{ t("tags") }}: </span>
<span <span v-if="tagsList.length" class="trace-tags">
v-if="tagsList.length"
class="trace-tags"
:style="type === 'LOG' ? `min-width: 122px;` : ''"
>
<span class="selected" v-for="(item, index) in tagsList" :key="index"> <span class="selected" v-for="(item, index) in tagsList" :key="index">
<span>{{ item }}</span> <span>{{ item }}</span>
<span class="remove-icon" @click="removeTags(index)">×</span> <span class="remove-icon" @click="removeTags(index)">×</span>
@ -33,13 +29,7 @@ limitations under the License. -->
@change="addLabels" @change="addLabels"
:placeholder="t('addTags')" :placeholder="t('addTags')"
/> />
<el-popover <el-popover v-else trigger="click" :visible="visible" width="300px">
v-else
trigger="click"
style="margin: 10px 0 0 -130px"
:visible="visible"
width="300px"
>
<template #reference> <template #reference>
<el-input <el-input
size="small" size="small"
@ -226,7 +216,6 @@ watch(
padding: 2px 5px; padding: 2px 5px;
border-radius: 3px; border-radius: 3px;
width: 250px; width: 250px;
z-index: 999;
} }
.remove-icon { .remove-icon {

View File

@ -30,7 +30,7 @@ limitations under the License. -->
</div> </div>
</el-popover> </el-popover>
<div class="header"> <div class="header">
<Header :needQuery="needQuery" /> <Header :needQuery="needQuery" :data="data" />
</div> </div>
<div class="log"> <div class="log">
<List /> <List />
@ -38,20 +38,24 @@ limitations under the License. -->
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { provide } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard"; import { useDashboardStore } from "@/store/modules/dashboard";
import Header from "../related/log/Header.vue"; import Header from "../related/log/Header.vue";
import List from "../related/log/List.vue"; import List from "../related/log/List.vue";
import { LayoutConfig } from "@/types/dashboard";
import type { PropType } from "vue";
/*global defineProps */ /*global defineProps */
const props = defineProps({ const props = defineProps({
data: { data: {
type: Object, type: Object as PropType<LayoutConfig>,
default: () => ({}), default: () => ({ graph: {} }),
}, },
activeIndex: { type: String, default: "" }, activeIndex: { type: String, default: "" },
needQuery: { type: Boolean, default: true }, needQuery: { type: Boolean, default: true },
}); });
provide("options", props.data);
const { t } = useI18n(); const { t } = useI18n();
const dashboardStore = useDashboardStore(); const dashboardStore = useDashboardStore();
@ -72,6 +76,7 @@ function removeWidget() {
position: absolute; position: absolute;
top: 5px; top: 5px;
right: 3px; right: 3px;
z-index: 1000;
} }
.header { .header {

View File

@ -259,6 +259,26 @@ export default defineComponent({
} }
} }
); );
watch(
() => dashboardStore.currentTabIndex,
() => {
activeTabIndex.value = dashboardStore.currentTabIndex;
dashboardStore.activeGridItem(props.data.i);
dashboardStore.selectWidget(props.data);
const l = dashboardStore.layout.findIndex(
(d: LayoutConfig) => d.i === props.data.i
);
dashboardStore.setCurrentTabItems(
dashboardStore.layout[l].children[activeTabIndex.value].children
);
needQuery.value = true;
if (route.params.activeTabIndex) {
let p = location.href.split("/tab/")[0];
p = p + "/tab/" + activeTabIndex.value;
history.replaceState({}, "", p);
}
}
);
return { return {
handleClick, handleClick,
layoutUpdatedEvent, layoutUpdatedEvent,

View File

@ -30,7 +30,7 @@ limitations under the License. -->
</div> </div>
</el-popover> </el-popover>
<div class="header"> <div class="header">
<Filter :needQuery="needQuery" /> <Filter :needQuery="needQuery" :data="data" />
</div> </div>
<div class="trace flex-h"> <div class="trace flex-h">
<TraceList /> <TraceList />
@ -39,6 +39,7 @@ limitations under the License. -->
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { provide } from "vue";
import type { PropType } from "vue"; import type { PropType } from "vue";
import Filter from "../related/trace/Filter.vue"; import Filter from "../related/trace/Filter.vue";
import TraceList from "../related/trace/TraceList.vue"; import TraceList from "../related/trace/TraceList.vue";
@ -55,6 +56,7 @@ const props = defineProps({
activeIndex: { type: String, default: "" }, activeIndex: { type: String, default: "" },
needQuery: { type: Boolean, default: true }, needQuery: { type: Boolean, default: true },
}); });
provide("options", props.data);
const { t } = useI18n(); const { t } = useI18n();
const dashboardStore = useDashboardStore(); const dashboardStore = useDashboardStore();
function removeWidget() { function removeWidget() {

View File

@ -168,7 +168,7 @@ export default defineComponent({
for (const item of associate) { for (const item of associate) {
const widget = widgets.find( const widget = widgets.find(
(d: { id: string }) => d.id === item.widgetId (d: LayoutConfig) => d.id === item.widgetId
); );
if (widget) { if (widget) {
widget.filters = { widget.filters = {

View File

@ -131,6 +131,7 @@ limitations under the License. -->
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, reactive, watch, onUnmounted } from "vue"; import { ref, reactive, watch, onUnmounted } from "vue";
import type { PropType } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { Option } from "@/types/app"; import { Option } from "@/types/app";
import { useLogStore } from "@/store/modules/log"; import { useLogStore } from "@/store/modules/log";
@ -141,17 +142,24 @@ import ConditionTags from "@/views/components/ConditionTags.vue";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import { EntityType } from "../../data"; import { EntityType } from "../../data";
import { ErrorCategory } from "./data"; import { ErrorCategory } from "./data";
import { LayoutConfig } from "@/types/dashboard";
/*global defineProps */ /*global defineProps, Recordable */
const props = defineProps({ const props = defineProps({
needQuery: { type: Boolean, default: true }, needQuery: { type: Boolean, default: true },
data: {
type: Object as PropType<LayoutConfig>,
default: () => ({ graph: {} }),
},
}); });
const { t } = useI18n(); const { t } = useI18n();
const appStore = useAppStoreWithOut(); const appStore = useAppStoreWithOut();
const selectorStore = useSelectorStore(); const selectorStore = useSelectorStore();
const dashboardStore = useDashboardStore(); const dashboardStore = useDashboardStore();
const logStore = useLogStore(); const logStore = useLogStore();
const traceId = ref<string>(""); const traceId = ref<string>(
(props.data.filters && props.data.filters.traceId) || ""
);
const keywordsOfContent = ref<string[]>([]); const keywordsOfContent = ref<string[]>([]);
const excludingKeywordsOfContent = ref<string[]>([]); const excludingKeywordsOfContent = ref<string[]>([]);
const tagsList = ref<string[]>([]); const tagsList = ref<string[]>([]);
@ -159,7 +167,7 @@ const tagsMap = ref<Option[]>([]);
const contentStr = ref<string>(""); const contentStr = ref<string>("");
const excludingContentStr = ref<string>(""); const excludingContentStr = ref<string>("");
const isBrowser = ref<boolean>(dashboardStore.layerId === "BROWSER"); const isBrowser = ref<boolean>(dashboardStore.layerId === "BROWSER");
const state = reactive<any>({ const state = reactive<Recordable>({
instance: { value: "0", label: "All" }, instance: { value: "0", label: "All" },
endpoint: { value: "0", label: "All" }, endpoint: { value: "0", label: "All" },
service: { value: "", label: "" }, service: { value: "", label: "" },
@ -248,6 +256,11 @@ function searchLogs() {
category: state.category.value, category: state.category.value,
}); });
} else { } else {
let segmentId, spanId;
if (props.data.filters) {
segmentId = props.data.filters.segmentId;
spanId = props.data.filters.spanId;
}
logStore.setLogCondition({ logStore.setLogCondition({
serviceId: selectorStore.currentService serviceId: selectorStore.currentService
? selectorStore.currentService.id ? selectorStore.currentService.id
@ -259,7 +272,9 @@ function searchLogs() {
excludingKeywordsOfContent: excludingKeywordsOfContent.value, excludingKeywordsOfContent: excludingKeywordsOfContent.value,
tags: tagsMap.value.length ? tagsMap.value : undefined, tags: tagsMap.value.length ? tagsMap.value : undefined,
paging: { pageNum: 1, pageSize: 15 }, paging: { pageNum: 1, pageSize: 15 },
relatedTrace: traceId.value ? { traceId: traceId.value } : undefined, relatedTrace: traceId.value
? { traceId: traceId.value, segmentId, spanId }
: undefined,
}); });
} }
queryLogs(); queryLogs();
@ -325,6 +340,12 @@ function removeExcludeContent(index: number) {
} }
onUnmounted(() => { onUnmounted(() => {
logStore.resetCondition(); logStore.resetCondition();
const item = {
...props.data,
filters: undefined,
};
dashboardStore.setWidget(item);
traceId.value = "";
}); });
watch( watch(
() => selectorStore.currentService, () => selectorStore.currentService,
@ -351,6 +372,19 @@ watch(
} }
} }
); );
watch(
() => props.data.filters,
(newJson, oldJson) => {
console.log(props.data.filters);
if (props.data.filters) {
if (JSON.stringify(newJson) === JSON.stringify(oldJson)) {
return;
}
traceId.value = props.data.filters.traceId || "";
init();
}
}
);
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.inputs { .inputs {

View File

@ -18,7 +18,7 @@ limitations under the License. -->
v-loading="logStore.loadLogs" v-loading="logStore.loadLogs"
:tableData="logStore.logs || []" :tableData="logStore.logs || []"
:type="type" :type="type"
:noLink="true" :noLink="false"
> >
<div class="log-tips" v-if="!logStore.logs.length">{{ t("noData") }}</div> <div class="log-tips" v-if="!logStore.logs.length">{{ t("noData") }}</div>
</LogTable> </LogTable>
@ -39,7 +39,7 @@ limitations under the License. -->
<script lang="ts" setup> <script lang="ts" setup>
import { ref, computed } from "vue"; import { ref, computed } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import LogTable from "@/views/dashboard/related/components/LogTable/Index.vue"; import LogTable from "./LogTable/Index.vue";
import { useLogStore } from "@/store/modules/log"; import { useLogStore } from "@/store/modules/log";
import { useDashboardStore } from "@/store/modules/dashboard"; import { useDashboardStore } from "@/store/modules/dashboard";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";

View File

@ -14,46 +14,68 @@ See the License for the specific language governing permissions and
limitations under the License. --> limitations under the License. -->
<template> <template>
<div @click="showSelectSpan" class="log-item"> <div class="log-item">
<div v-for="(item, index) in columns" :key="index" :class="item.label"> <div v-for="(item, index) in columns" :key="index" :class="item.label">
<span v-if="item.label === 'timestamp'"> <span v-if="item.label === 'timestamp'" @click="showSelectSpan">
{{ dateFormat(data.timestamp) }} {{ dateFormat(data.timestamp) }}
</span> </span>
<span v-else-if="item.label === 'tags'"> <span v-else-if="item.label === 'tags'" @click="showSelectSpan">
{{ tags }} {{ tags }}
</span> </span>
<!-- <router-link <span
v-else-if="item.label === 'traceId' && !noLink" v-else-if="item.label === 'traceId' && !noLink"
:to="{ name: 'trace', query: { traceid: data[item.label] } }" :class="noLink ? '' : 'blue'"
@click="linkTrace(data[item.label])"
> >
<span :class="noLink ? '' : 'blue'">{{ data[item.label] }}</span> {{ data[item.label] }}
</router-link> --> </span>
<span v-else>{{ data[item.label] }}</span> <span v-else @click="showSelectSpan">{{ data[item.label] }}</span>
</div> </div>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed } from "vue"; import { computed, inject } from "vue";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { ServiceLogConstants } from "./data"; import { ServiceLogConstants } from "./data";
/*global defineProps, defineEmits */ import getDashboard from "@/hooks/useDashboardsSession";
import { useDashboardStore } from "@/store/modules/dashboard";
import { LayoutConfig } from "@/types/dashboard";
/*global defineProps, defineEmits, Recordable */
const props = defineProps({ const props = defineProps({
data: { type: Object as any, default: () => ({}) }, data: { type: Object as any, default: () => ({}) },
noLink: { type: Boolean, default: true }, noLink: { type: Boolean, default: true },
}); });
const dashboardStore = useDashboardStore();
const options: Recordable<LayoutConfig> = inject("options") || {};
const emit = defineEmits(["select"]); const emit = defineEmits(["select"]);
const columns = ServiceLogConstants; const columns = ServiceLogConstants;
const tags = computed(() => { const tags = computed(() => {
if (!props.data.tags) { if (!props.data.tags) {
return ""; return "";
} }
return String(props.data.tags.map((d: any) => `${d.key}=${d.value}`)); return String(
props.data.tags.map(
(d: { key: string; value: string }) => `${d.key}=${d.value}`
)
);
}); });
const dateFormat = (date: number, pattern = "YYYY-MM-DD HH:mm:ss") => const dateFormat = (date: number, pattern = "YYYY-MM-DD HH:mm:ss") =>
dayjs(date).format(pattern); dayjs(date).format(pattern);
function showSelectSpan() { function showSelectSpan() {
emit("select", props.data); emit("select", props.data);
} }
function linkTrace(id: string) {
const { associationWidget } = getDashboard(dashboardStore.currentDashboard);
associationWidget(
(options.id as any) || "",
{
sourceId: options.id || "",
traceId: id,
},
"Trace"
);
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.log-item { .log-item {

View File

@ -40,14 +40,14 @@ export const ServiceLogConstants = [
label: "tags", label: "tags",
value: "tags", value: "tags",
}, },
{
label: "content",
value: "content",
},
{ {
label: "traceId", label: "traceId",
value: "traceID", value: "traceID",
}, },
{
label: "content",
value: "content",
},
]; ];
export const ServiceLogDetail = [ export const ServiceLogDetail = [
{ {

View File

@ -33,33 +33,6 @@ limitations under the License. -->
{{ t("viewLogs") }} {{ t("viewLogs") }}
</el-button> </el-button>
</div> </div>
<el-dialog
v-model="showTraceLogs"
:destroy-on-close="true"
fullscreen
@closed="showTraceLogs = false"
>
<div>
<el-pagination
v-model:currentPage="pageNum"
v-model:page-size="pageSize"
:small="true"
layout="prev, pager, next"
:pager-count="5"
:total="total"
@current-change="turnLogsPage"
/>
<LogTable
:tableData="traceStore.traceSpanLogs || []"
:type="`service`"
:noLink="true"
>
<div class="log-tips" v-if="!traceStore.traceSpanLogs.length">
{{ t("noData") }}
</div>
</LogTable>
</div>
</el-dialog>
</h5> </h5>
<div class="mb-5 blue sm"> <div class="mb-5 blue sm">
<Selector <Selector
@ -148,37 +121,31 @@ limitations under the License. -->
</template> </template>
<script lang="ts"> <script lang="ts">
import dayjs from "dayjs"; import dayjs from "dayjs";
import { ref, defineComponent, computed } from "vue"; import { ref, defineComponent, inject } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { useTraceStore } from "@/store/modules/trace"; import { useTraceStore } from "@/store/modules/trace";
import { Option } from "@/types/app"; import { Option } from "@/types/app";
import copy from "@/utils/copy"; import copy from "@/utils/copy";
import graphs from "./components/index"; import graphs from "./components/index";
import LogTable from "@/views/dashboard/related/components/LogTable/Index.vue";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import getDashboard from "@/hooks/useDashboardsSession";
import { LayoutConfig } from "@/types/dashboard";
export default defineComponent({ export default defineComponent({
name: "TraceDetail", name: "TraceDetail",
components: { components: {
...graphs, ...graphs,
LogTable,
}, },
setup() { setup() {
/*global Recordable */
const options: Recordable<LayoutConfig> = inject("options") || {};
const { t } = useI18n(); const { t } = useI18n();
const traceStore = useTraceStore(); const traceStore = useTraceStore();
const loading = ref<boolean>(false); const loading = ref<boolean>(false);
const traceId = ref<string>(""); const traceId = ref<string>("");
const displayMode = ref<string>("List"); const displayMode = ref<string>("List");
const pageNum = ref<number>(1);
const pageSize = 10;
const total = computed(() =>
traceStore.traceList.length === pageSize
? pageSize * pageNum.value + 1
: pageSize * pageNum.value
);
const dateFormat = (date: number, pattern = "YYYY-MM-DD HH:mm:ss") => const dateFormat = (date: number, pattern = "YYYY-MM-DD HH:mm:ss") =>
dayjs(date).format(pattern); dayjs(date).format(pattern);
const showTraceLogs = ref<boolean>(false);
function handleClick() { function handleClick() {
copy(traceId.value || traceStore.currentTrace.traceIds[0].value); copy(traceId.value || traceStore.currentTrace.traceIds[0].value);
@ -195,23 +162,15 @@ export default defineComponent({
} }
async function searchTraceLogs() { async function searchTraceLogs() {
showTraceLogs.value = true; const { associationWidget } = getDashboard();
const res = await traceStore.getSpanLogs({ associationWidget(
condition: { (options.id as any) || "",
relatedTrace: { {
sourceId: options?.id || "",
traceId: traceId.value || traceStore.currentTrace.traceIds[0].value, traceId: traceId.value || traceStore.currentTrace.traceIds[0].value,
}, },
paging: { pageNum: pageNum.value, pageSize }, "Log"
}, );
});
if (res.errors) {
ElMessage.error(res.errors);
}
}
function turnLogsPage(page: number) {
pageNum.value = page;
searchTraceLogs();
} }
return { return {
traceStore, traceStore,
@ -221,12 +180,7 @@ export default defineComponent({
handleClick, handleClick,
t, t,
searchTraceLogs, searchTraceLogs,
showTraceLogs,
turnLogsPage,
pageSize,
pageNum,
loading, loading,
total,
traceId, traceId,
}; };
}, },

View File

@ -92,7 +92,8 @@ limitations under the License. -->
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, reactive, watch } from "vue"; import { ref, reactive, watch, onUnmounted } from "vue";
import type { PropType } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { Option } from "@/types/app"; import { Option } from "@/types/app";
import { Status } from "../../data"; import { Status } from "../../data";
@ -103,22 +104,29 @@ import { useSelectorStore } from "@/store/modules/selectors";
import ConditionTags from "@/views/components/ConditionTags.vue"; import ConditionTags from "@/views/components/ConditionTags.vue";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import { EntityType } from "../../data"; import { EntityType } from "../../data";
import { LayoutConfig } from "@/types/dashboard";
/*global defineProps */ /*global defineProps, Recordable */
const props = defineProps({ const props = defineProps({
needQuery: { type: Boolean, default: true }, needQuery: { type: Boolean, default: true },
data: {
type: Object as PropType<LayoutConfig>,
default: () => ({ graph: {} }),
},
}); });
const traceId = ref<string>(
(props.data.filters && props.data.filters.traceId) || ""
);
const { t } = useI18n(); const { t } = useI18n();
const appStore = useAppStoreWithOut(); const appStore = useAppStoreWithOut();
const selectorStore = useSelectorStore(); const selectorStore = useSelectorStore();
const dashboardStore = useDashboardStore(); const dashboardStore = useDashboardStore();
const traceStore = useTraceStore(); const traceStore = useTraceStore();
const traceId = ref<string>("");
const minTraceDuration = ref<number>(); const minTraceDuration = ref<number>();
const maxTraceDuration = ref<number>(); const maxTraceDuration = ref<number>();
const tagsList = ref<string[]>([]); const tagsList = ref<string[]>([]);
const tagsMap = ref<Option[]>([]); const tagsMap = ref<Option[]>([]);
const state = reactive<any>({ const state = reactive<Recordable>({
status: { label: "All", value: "ALL" }, status: { label: "All", value: "ALL" },
instance: { value: "0", label: "All" }, instance: { value: "0", label: "All" },
endpoint: { value: "0", label: "All" }, endpoint: { value: "0", label: "All" },
@ -222,6 +230,15 @@ async function searchEndpoints(keyword: string) {
ElMessage.error(resp.errors); ElMessage.error(resp.errors);
} }
} }
onUnmounted(() => {
traceStore.resetCondition();
const item = {
...props.data,
filters: undefined,
};
dashboardStore.setWidget(item);
traceId.value = "";
});
watch( watch(
() => [selectorStore.currentPod], () => [selectorStore.currentPod],
() => { () => {
@ -248,6 +265,18 @@ watch(
} }
} }
); );
watch(
() => props.data.filters,
(newJson, oldJson) => {
if (props.data.filters) {
if (JSON.stringify(newJson) === JSON.stringify(oldJson)) {
return;
}
traceId.value = props.data.filters.traceId || "";
init();
}
}
);
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.inputs { .inputs {

View File

@ -81,77 +81,36 @@ limitations under the License. -->
{{ t("relatedTraceLogs") }} {{ t("relatedTraceLogs") }}
</el-button> </el-button>
</div> </div>
<el-dialog
v-model="showRelatedLogs"
:destroy-on-close="true"
fullscreen
@closed="showRelatedLogs = false"
>
<el-pagination
v-model:currentPage="pageNum"
v-model:page-size="pageSize"
:small="true"
layout="prev, pager, next"
:pager-count="5"
:total="total"
@current-change="turnPage"
/>
<LogTable
:tableData="traceStore.traceSpanLogs || []"
:type="`service`"
:noLink="true"
>
<div class="log-tips" v-if="!traceStore.traceSpanLogs.length">
{{ t("noData") }}
</div>
</LogTable>
</el-dialog>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, computed } from "vue"; import { inject } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import type { PropType } from "vue"; import type { PropType } from "vue";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { useTraceStore } from "@/store/modules/trace";
import copy from "@/utils/copy"; import copy from "@/utils/copy";
import { ElMessage } from "element-plus"; import getDashboard from "@/hooks/useDashboardsSession";
import LogTable from "@/views/dashboard/related/components/LogTable/Index.vue"; import { LayoutConfig } from "@/types/dashboard";
/* global defineProps */ /*global defineProps, Recordable */
const options: Recordable<LayoutConfig> = inject("options") || {};
const props = defineProps({ const props = defineProps({
currentSpan: { type: Object as PropType<any>, default: () => ({}) }, currentSpan: { type: Object as PropType<any>, default: () => ({}) },
}); });
const { t } = useI18n(); const { t } = useI18n();
const traceStore = useTraceStore();
const pageNum = ref<number>(1);
const showRelatedLogs = ref<boolean>(false);
const pageSize = 10;
const total = computed(() =>
traceStore.traceList.length === pageSize
? pageSize * pageNum.value + 1
: pageSize * pageNum.value
);
const dateFormat = (date: number, pattern = "YYYY-MM-DD HH:mm:ss") => const dateFormat = (date: number, pattern = "YYYY-MM-DD HH:mm:ss") =>
dayjs(date).format(pattern); dayjs(date).format(pattern);
async function getTaceLogs() { async function getTaceLogs() {
showRelatedLogs.value = true; const { associationWidget } = getDashboard();
const res = await traceStore.getSpanLogs({ associationWidget(
condition: { (options.id as any) || "",
relatedTrace: { {
sourceId: options?.id || "",
traceId: props.currentSpan.traceId, traceId: props.currentSpan.traceId,
segmentId: props.currentSpan.segmentId, segmentId: props.currentSpan.segmentId,
spanId: props.currentSpan.spanId, spanId: props.currentSpan.spanId,
}, },
paging: { pageNum: pageNum.value, pageSize }, "Log"
}, );
});
if (res.errors) {
ElMessage.error(res.errors);
}
}
function turnPage(p: number) {
pageNum.value = p;
getTaceLogs();
} }
function showCurrentSpanDetail(text: string) { function showCurrentSpanDetail(text: string) {
copy(text); copy(text);

View File

@ -46,8 +46,8 @@ export default class ListGraph {
.select(this.el) .select(this.el)
.append("svg") .append("svg")
.attr("class", "trace-list-dowanload") .attr("class", "trace-list-dowanload")
.attr("width", this.width) .attr("width", this.width > 0 ? this.width : 10)
.attr("height", this.height) .attr("height", this.height > 0 ? this.height : 10)
.attr("transform", `translate(-5, 0)`); .attr("transform", `translate(-5, 0)`);
this.tip = (d3tip as any)() this.tip = (d3tip as any)()
.attr("class", "d3-tip") .attr("class", "d3-tip")

View File

@ -57,8 +57,8 @@ export default class TraceMap {
.select(this.el) .select(this.el)
.append("svg") .append("svg")
.attr("class", "d3-trace-tree") .attr("class", "d3-trace-tree")
.attr("width", this.width) .attr("width", this.width > 0 ? this.width : 10)
.attr("height", this.height); .attr("height", this.height > 0 ? this.height : 10);
this.tip = (d3tip as any)() this.tip = (d3tip as any)()
.attr("class", "d3-tip") .attr("class", "d3-tip")
.offset([-8, 0]) .offset([-8, 0])