mirror of
https://github.com/apache/skywalking-booster-ui.git
synced 2025-05-01 16:23:14 +00:00
feat: Implement tags auto-complete for Trace and Log (#85)
This commit is contained in:
parent
0e0f2704b3
commit
b492787027
@ -63,3 +63,15 @@ export const QueryLogsByKeywords = {
|
||||
query: `
|
||||
support: supportQueryLogsByKeywords`,
|
||||
};
|
||||
|
||||
export const LogTagKeys = {
|
||||
variable: "$duration: Duration!",
|
||||
query: `
|
||||
tagKeys: queryLogTagAutocompleteKeys(duration: $duration)`,
|
||||
};
|
||||
|
||||
export const LogTagValues = {
|
||||
variable: "$tagKey: String!, $duration: Duration!",
|
||||
query: `
|
||||
tagValues: queryLogTagAutocompleteValues(tagKey: $tagKey, duration: $duration)`,
|
||||
};
|
||||
|
@ -74,3 +74,14 @@ export const TraceSpans = {
|
||||
}
|
||||
`,
|
||||
};
|
||||
export const TraceTagKeys = {
|
||||
variable: "$duration: Duration!",
|
||||
query: `
|
||||
tagKeys: queryTraceTagAutocompleteKeys(duration: $duration)`,
|
||||
};
|
||||
|
||||
export const TraceTagValues = {
|
||||
variable: "$tagKey: String!, $duration: Duration!",
|
||||
query: `
|
||||
tagValues: queryTraceTagAutocompleteValues(tagKey: $tagKey, duration: $duration)`,
|
||||
};
|
||||
|
@ -19,9 +19,13 @@ import {
|
||||
QueryBrowserErrorLogs,
|
||||
QueryServiceLogs,
|
||||
QueryLogsByKeywords,
|
||||
LogTagValues,
|
||||
LogTagKeys,
|
||||
} from "../fragments/log";
|
||||
|
||||
export const queryBrowserErrorLogs = `query queryBrowserErrorLogs(${QueryBrowserErrorLogs.variable}) {
|
||||
${QueryBrowserErrorLogs.query}}`;
|
||||
export const queryServiceLogs = `query queryLogs(${QueryServiceLogs.variable}) {${QueryServiceLogs.query}}`;
|
||||
export const queryLogsByKeywords = `query queryLogsByKeywords {${QueryLogsByKeywords.query}}`;
|
||||
export const queryLogTagValues = `query queryTagValues(${LogTagValues.variable}) {${LogTagValues.query}}`;
|
||||
export const queryLogTagKeys = `query queryTagKeys(${LogTagKeys.variable}) {${LogTagKeys.query}}`;
|
||||
|
@ -15,8 +15,17 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Traces, TraceSpans } from "../fragments/trace";
|
||||
import {
|
||||
Traces,
|
||||
TraceSpans,
|
||||
TraceTagKeys,
|
||||
TraceTagValues,
|
||||
} from "../fragments/trace";
|
||||
|
||||
export const queryTraces = `query queryTraces(${Traces.variable}) {${Traces.query}}`;
|
||||
|
||||
export const queryTrace = `query queryTrace(${TraceSpans.variable}) {${TraceSpans.query}}`;
|
||||
|
||||
export const queryTraceTagKeys = `query queryTraceTagKeys(${TraceTagKeys.variable}) {${TraceTagKeys.query}}`;
|
||||
|
||||
export const queryTraceTagValues = `query queryTraceTagValues(${TraceTagValues.variable}) {${TraceTagValues.query}}`;
|
||||
|
@ -326,7 +326,7 @@ const msg = {
|
||||
addExcludingKeywordsOfContent: "Please input a keyword of excluding content",
|
||||
noticeTag: "Please press Enter after inputting a tag(key=value).",
|
||||
conditionNotice:
|
||||
"Notice: Please press Enter after inputting a tag, key of content, exclude key of content(key=value).",
|
||||
"Notice: Please press Enter after inputting a key of content, exclude key of content(key=value).",
|
||||
language: "Language",
|
||||
};
|
||||
export default msg;
|
||||
|
@ -327,7 +327,7 @@ const msg = {
|
||||
addExcludingKeywordsOfContent: "请输入一个内容不包含的关键词",
|
||||
noticeTag: "请输入一个标签(key=value)之后回车",
|
||||
conditionNotice:
|
||||
"请输入一个标签、内容关键词或者内容不包含的关键词(key=value)之后回车",
|
||||
"请输入一个内容关键词或者内容不包含的关键词(key=value)之后回车",
|
||||
language: "语言",
|
||||
};
|
||||
export default msg;
|
||||
|
@ -151,6 +151,20 @@ export const logStore = defineStore({
|
||||
this.logsTotal = res.data.data.queryBrowserErrorLogs.total;
|
||||
return res.data;
|
||||
},
|
||||
async getLogTagKeys() {
|
||||
const res: AxiosResponse = await graphql
|
||||
.query("queryLogTagKeys")
|
||||
.params({ duration: useAppStoreWithOut().durationTime });
|
||||
|
||||
return res.data;
|
||||
},
|
||||
async getLogTagValues(tagKey: string) {
|
||||
const res: AxiosResponse = await graphql
|
||||
.query("queryLogTagValues")
|
||||
.params({ tagKey, duration: useAppStoreWithOut().durationTime });
|
||||
|
||||
return res.data;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -161,6 +161,20 @@ export const traceStore = defineStore({
|
||||
this.traceSpanLogsTotal = res.data.data.queryLogs.total;
|
||||
return res.data;
|
||||
},
|
||||
async getTagKeys() {
|
||||
const res: AxiosResponse = await graphql
|
||||
.query("queryTraceTagKeys")
|
||||
.params({ duration: useAppStoreWithOut().durationTime });
|
||||
|
||||
return res.data;
|
||||
},
|
||||
async getTagValues(tagKey: string) {
|
||||
const res: AxiosResponse = await graphql
|
||||
.query("queryTraceTagValues")
|
||||
.params({ tagKey, duration: useAppStoreWithOut().durationTime });
|
||||
|
||||
return res.data;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -13,65 +13,97 @@ 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="flex-h" :class="{ light: theme === 'light' }">
|
||||
<div class="mr-5">
|
||||
<span class="sm grey" v-show="theme === 'dark'">{{ t("tags") }}: </span>
|
||||
<span
|
||||
class="trace-tags"
|
||||
:style="type === 'LOG' ? `min-width: 122px;` : ''"
|
||||
>
|
||||
<span class="selected" v-for="(item, index) in tagsList" :key="index">
|
||||
<span>{{ item }}</span>
|
||||
<span class="remove-icon" @click="removeTags(index)">×</span>
|
||||
</span>
|
||||
<div>
|
||||
<span class="grey">{{ t("tags") }}: </span>
|
||||
<span
|
||||
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>{{ item }}</span>
|
||||
<span class="remove-icon" @click="removeTags(index)">×</span>
|
||||
</span>
|
||||
</span>
|
||||
<el-input
|
||||
v-if="type === 'ALARM'"
|
||||
size="small"
|
||||
v-model="tags"
|
||||
class="trace-new-tag"
|
||||
@change="addLabels"
|
||||
:placeholder="t('addTags')"
|
||||
/>
|
||||
<span v-else>
|
||||
<el-input
|
||||
size="small"
|
||||
v-model="tags"
|
||||
class="trace-new-tag"
|
||||
@change="addLabels"
|
||||
:placeholder="t('addTags')"
|
||||
@click="showClick"
|
||||
/>
|
||||
<span class="tags-tip">
|
||||
<a
|
||||
target="blank"
|
||||
href="https://github.com/apache/skywalking/blob/master/docs/en/setup/backend/configuration-vocabulary.md"
|
||||
>
|
||||
{{ t("tagsLink") }}
|
||||
</a>
|
||||
<el-tooltip
|
||||
:content="
|
||||
t(
|
||||
type === 'LOG'
|
||||
? 'logTagsTip'
|
||||
: type === 'TRACE'
|
||||
? 'traceTagsTip'
|
||||
: 'alarmTagsTip'
|
||||
)
|
||||
"
|
||||
>
|
||||
<span>
|
||||
<Icon class="icon-help mr-5" iconName="help" size="middle" />
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<b v-if="type !== 'LOG'">{{ t("noticeTag") }}</b>
|
||||
</span>
|
||||
</div>
|
||||
<el-dropdown
|
||||
ref="dropdownTag"
|
||||
trigger="contextmenu"
|
||||
:hide-on-click="false"
|
||||
style="margin: 20px 0 0 -130px"
|
||||
v-if="tagArr.length"
|
||||
>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item v-for="(item, index) in tagArr" :key="index">
|
||||
<span @click="selectTag(item)" class="tag-item">
|
||||
{{ item }}
|
||||
</span>
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</span>
|
||||
<span
|
||||
class="tags-tip"
|
||||
:class="type !== 'ALARM' && tagArr.length ? 'link-tips' : ''"
|
||||
>
|
||||
<a
|
||||
target="blank"
|
||||
href="https://github.com/apache/skywalking/blob/master/docs/en/setup/backend/configuration-vocabulary.md"
|
||||
>
|
||||
{{ t("tagsLink") }}
|
||||
</a>
|
||||
<el-tooltip :content="t(tipsMap[type])">
|
||||
<span>
|
||||
<Icon class="icon-help mr-5" iconName="help" size="middle" />
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<b v-if="type === 'AL'">{{ t("noticeTag") }}</b>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useTraceStore } from "@/store/modules/trace";
|
||||
import { useLogStore } from "@/store/modules/log";
|
||||
import { ElMessage } from "element-plus";
|
||||
|
||||
/*global defineEmits, defineProps */
|
||||
/*global Nullable, defineEmits, defineProps */
|
||||
const emit = defineEmits(["update"]);
|
||||
defineProps({
|
||||
const props = defineProps({
|
||||
type: { type: String, default: "TRACE" },
|
||||
});
|
||||
const traceStore = useTraceStore();
|
||||
const logStore = useLogStore();
|
||||
const { t } = useI18n();
|
||||
const theme = ref<string>("dark");
|
||||
const tags = ref<string>("");
|
||||
const tagsList = ref<string[]>([]);
|
||||
const tagArr = ref<string[]>([]);
|
||||
const tagKeys = ref<string[]>([]);
|
||||
const tipsMap = {
|
||||
LOG: "logTagsTip",
|
||||
TRACE: "traceTagsTip",
|
||||
ALARM: "alarmTagsTip",
|
||||
};
|
||||
const dropdownTag = ref<Nullable<any>>(null);
|
||||
|
||||
fetchTagKeys();
|
||||
|
||||
function removeTags(index: number) {
|
||||
tagsList.value.splice(index, 1);
|
||||
@ -95,6 +127,55 @@ function updateTags() {
|
||||
});
|
||||
emit("update", { tagsMap, tagsList: tagsList.value });
|
||||
}
|
||||
async function fetchTagKeys() {
|
||||
let resp: any = {};
|
||||
if (props.type === "TRACE") {
|
||||
resp = await traceStore.getTagKeys();
|
||||
} else {
|
||||
resp = await logStore.getLogTagKeys();
|
||||
}
|
||||
|
||||
if (resp.errors) {
|
||||
ElMessage.error(resp.errors);
|
||||
return;
|
||||
}
|
||||
tagArr.value = resp.data.tagKeys;
|
||||
tagKeys.value = resp.data.tagKeys;
|
||||
}
|
||||
|
||||
async function fetchTagValues() {
|
||||
const param = tags.value.split("=")[0];
|
||||
let resp: any = {};
|
||||
if (props.type === "TRACE") {
|
||||
resp = await traceStore.getTagValues(param);
|
||||
} else {
|
||||
resp = await logStore.getLogTagValues(param);
|
||||
}
|
||||
|
||||
if (resp.errors) {
|
||||
ElMessage.error(resp.errors);
|
||||
return;
|
||||
}
|
||||
tagArr.value = resp.data.tagValues;
|
||||
}
|
||||
|
||||
function selectTag(item: string) {
|
||||
if (tags.value.includes("=")) {
|
||||
tags.value += item;
|
||||
addLabels();
|
||||
tagArr.value = tagKeys.value;
|
||||
dropdownTag.value.handleClose();
|
||||
return;
|
||||
}
|
||||
tags.value = item + "=";
|
||||
fetchTagValues();
|
||||
}
|
||||
|
||||
function showClick() {
|
||||
if (dropdownTag.value) {
|
||||
dropdownTag.value.handleOpen();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.trace-tags {
|
||||
@ -121,7 +202,6 @@ function updateTags() {
|
||||
padding: 2px 5px;
|
||||
border-radius: 3px;
|
||||
width: 250px;
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
.remove-icon {
|
||||
@ -130,10 +210,20 @@ function updateTags() {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tag-item {
|
||||
display: inline-block;
|
||||
min-width: 210px;
|
||||
}
|
||||
|
||||
.tags-tip {
|
||||
color: #a7aebb;
|
||||
}
|
||||
|
||||
.link-tips {
|
||||
display: inline-block;
|
||||
margin-left: 130px;
|
||||
}
|
||||
|
||||
.light {
|
||||
color: #3d444f;
|
||||
|
||||
|
@ -50,9 +50,14 @@ limitations under the License. -->
|
||||
@query="searchEndpoints"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row tips">
|
||||
<b>{{ t("conditionNotice") }}</b>
|
||||
<el-button
|
||||
class="search-btn"
|
||||
size="small"
|
||||
type="primary"
|
||||
@click="searchLogs"
|
||||
>
|
||||
{{ t("search") }}
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="flex-h row">
|
||||
<div class="mr-5 traceId" v-show="!isBrowser">
|
||||
@ -61,6 +66,9 @@ limitations under the License. -->
|
||||
</div>
|
||||
<ConditionTags :type="'LOG'" @update="updateTags" />
|
||||
</div>
|
||||
<div class="row tips">
|
||||
<b>{{ t("conditionNotice") }}</b>
|
||||
</div>
|
||||
<div class="flex-h" v-show="!isBrowser">
|
||||
<div class="mr-5" v-show="logStore.supportQueryLogsByKeywords">
|
||||
<span class="mr-5 grey">{{ t("keywordsOfContent") }}:</span>
|
||||
@ -109,14 +117,6 @@ limitations under the License. -->
|
||||
</span>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<el-button
|
||||
class="search-btn"
|
||||
size="small"
|
||||
type="primary"
|
||||
@click="searchLogs"
|
||||
>
|
||||
{{ t("search") }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
@ -326,6 +326,7 @@ watch(
|
||||
|
||||
.row {
|
||||
margin-bottom: 5px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.inputs-max {
|
||||
@ -337,8 +338,11 @@ watch(
|
||||
}
|
||||
|
||||
.search-btn {
|
||||
margin-left: 20px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 10px;
|
||||
cursor: pointer;
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.tips {
|
||||
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License. -->
|
||||
<template>
|
||||
<div class="flex-h row">
|
||||
<div class="mr-5" v-if="dashboardStore.entity === EntityType[1].value">
|
||||
<div class="mr-10" v-if="dashboardStore.entity === EntityType[1].value">
|
||||
<span class="grey mr-5">{{ t("service") }}:</span>
|
||||
<Selector
|
||||
size="small"
|
||||
@ -24,7 +24,7 @@ limitations under the License. -->
|
||||
@change="changeField('service', $event)"
|
||||
/>
|
||||
</div>
|
||||
<div class="mr-5" v-if="dashboardStore.entity !== EntityType[3].value">
|
||||
<div class="mr-10" v-if="dashboardStore.entity !== EntityType[3].value">
|
||||
<span class="grey mr-5">{{ t("instance") }}:</span>
|
||||
<Selector
|
||||
size="small"
|
||||
@ -34,7 +34,7 @@ limitations under the License. -->
|
||||
@change="changeField('instance', $event)"
|
||||
/>
|
||||
</div>
|
||||
<div class="mr-5" v-if="dashboardStore.entity !== EntityType[2].value">
|
||||
<div class="mr-10" v-if="dashboardStore.entity !== EntityType[2].value">
|
||||
<span class="grey mr-5">{{ t("endpoint") }}:</span>
|
||||
<Selector
|
||||
size="small"
|
||||
@ -46,7 +46,7 @@ limitations under the License. -->
|
||||
@query="searchEndpoints"
|
||||
/>
|
||||
</div>
|
||||
<div class="mr-5">
|
||||
<div class="mr-10">
|
||||
<span class="grey mr-5">{{ t("status") }}:</span>
|
||||
<Selector
|
||||
size="small"
|
||||
@ -56,27 +56,29 @@ limitations under the License. -->
|
||||
@change="changeField('status', $event)"
|
||||
/>
|
||||
</div>
|
||||
<div class="mr-5">
|
||||
<el-button
|
||||
size="small"
|
||||
type="primary"
|
||||
@click="searchTraces"
|
||||
class="search-btn"
|
||||
>
|
||||
{{ t("search") }}
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="flex-h row">
|
||||
<div class="mr-10">
|
||||
<span class="grey mr-5">{{ t("traceID") }}:</span>
|
||||
<el-input size="small" v-model="traceId" class="traceId" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-h">
|
||||
<div class="mr-5">
|
||||
<div class="mr-10">
|
||||
<span class="sm b grey mr-5">{{ t("duration") }}:</span>
|
||||
<el-input size="small" class="inputs mr-5" v-model="minTraceDuration" />
|
||||
<span class="grey mr-5">-</span>
|
||||
<el-input size="small" class="inputs" v-model="maxTraceDuration" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-h">
|
||||
<ConditionTags :type="'TRACE'" @update="updateTags" />
|
||||
<el-button
|
||||
class="search-btn"
|
||||
size="small"
|
||||
type="primary"
|
||||
@click="searchTraces"
|
||||
>
|
||||
{{ t("search") }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
@ -109,10 +111,6 @@ const state = reactive<any>({
|
||||
service: { value: "", label: "" },
|
||||
});
|
||||
|
||||
// const dateTime = computed(() => [
|
||||
// appStore.durationRow.start,
|
||||
// appStore.durationRow.end,
|
||||
// ]);
|
||||
init();
|
||||
async function init() {
|
||||
if (dashboardStore.entity === EntityType[1].value) {
|
||||
@ -241,6 +239,7 @@ watch(
|
||||
|
||||
.row {
|
||||
margin-bottom: 5px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.traceId {
|
||||
@ -248,7 +247,10 @@ watch(
|
||||
}
|
||||
|
||||
.search-btn {
|
||||
margin-left: 20px;
|
||||
cursor: pointer;
|
||||
width: 120px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 10px;
|
||||
}
|
||||
</style>
|
||||
|
@ -200,9 +200,9 @@ export default defineComponent({
|
||||
dom.style.background = "rgba(0, 0, 0, 0.1)";
|
||||
}
|
||||
function selectSpan(event: any) {
|
||||
const dom = event.path.find((d: any) =>
|
||||
d.className.includes("trace-item")
|
||||
);
|
||||
const dom = event
|
||||
.composedPath()
|
||||
.find((d: any) => d.className.includes("trace-item"));
|
||||
|
||||
emit("select", props.data);
|
||||
if (props.headerType === "profile") {
|
||||
@ -212,9 +212,9 @@ export default defineComponent({
|
||||
viewSpanDetail(dom);
|
||||
}
|
||||
function viewSpan(event: any) {
|
||||
const dom = event.path.find((d: any) =>
|
||||
d.className.includes("trace-item")
|
||||
);
|
||||
const dom = event
|
||||
.composedPath()
|
||||
.find((d: any) => d.className.includes("trace-item"));
|
||||
emit("select", props.data);
|
||||
viewSpanDetail(dom);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user