feat: Implement tags auto-complete for Trace and Log (#85)

This commit is contained in:
Fine0830 2022-05-12 17:00:07 +08:00 committed by GitHub
parent 0e0f2704b3
commit b492787027
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 244 additions and 84 deletions

View File

@ -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)`,
};

View File

@ -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)`,
};

View File

@ -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}}`;

View File

@ -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}}`;

View File

@ -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;

View File

@ -327,7 +327,7 @@ const msg = {
addExcludingKeywordsOfContent: "请输入一个内容不包含的关键词",
noticeTag: "请输入一个标签(key=value)之后回车",
conditionNotice:
"请输入一个标签、内容关键词或者内容不包含的关键词(key=value)之后回车",
"请输入一个内容关键词或者内容不包含的关键词(key=value)之后回车",
language: "语言",
};
export default msg;

View File

@ -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;
},
},
});

View File

@ -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;
},
},
});

View File

@ -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;

View File

@ -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 {

View File

@ -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>

View File

@ -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);
}