Support clicking the service name in the chart to link to the tracking page. (#530)
* Support service metrics link to trace query
* Support service metrics link to trace query
* Modify the hard code.
* Support service metrics link to log or trace
* Clean up useless code and modify styles
* Clean up useless code and modify styles
* Clean up useless code
* Modify style and copywriting
* fix undefined bug
* fix undefined bug
Co-authored-by: 吴晟 Wu Sheng <wu.sheng@foxmail.com>
diff --git a/src/assets/lang/en.ts b/src/assets/lang/en.ts
index 8529304..0e0ced9 100644
--- a/src/assets/lang/en.ts
+++ b/src/assets/lang/en.ts
@@ -230,6 +230,8 @@
destService: 'Destination Service',
destServiceInstance: 'Destination Service Instance',
eventSource: 'Event Source',
+ modalTitle: 'Inspection',
+ selectRedirectPage: 'Do you want to inspect Traces or Logs of %s service?',
};
export default m;
diff --git a/src/assets/lang/zh.ts b/src/assets/lang/zh.ts
index 84ec613..5e13722 100644
--- a/src/assets/lang/zh.ts
+++ b/src/assets/lang/zh.ts
@@ -228,6 +228,8 @@
destService: '终点服务',
destServiceInstance: '终点实例',
eventSource: '事件资源',
+ modalTitle: '查看',
+ selectRedirectPage: '查看 %s 服务的追踪或日志?',
};
export default m;
diff --git a/src/components/index.ts b/src/components/index.ts
index 67d83ec..a5f02e8 100644
--- a/src/components/index.ts
+++ b/src/components/index.ts
@@ -24,6 +24,7 @@
import RkDate from './rk-date.vue';
import RkPanel from './rk-panel.vue';
import RkSidebox from './rk-sidebox.vue';
+import RkModal from './rk-modal.vue';
import RkEcharts from './rk-echarts.vue';
import RkSelect from './rk-select.vue';
import RkPopper from './rk-popper.vue';
@@ -42,6 +43,7 @@
RkEcharts,
RkPage,
RkSidebox,
+ RkModal,
RkFooterTime,
RkSelect,
RkPopper,
diff --git a/src/components/rk-modal.vue b/src/components/rk-modal.vue
new file mode 100644
index 0000000..93357f2
--- /dev/null
+++ b/src/components/rk-modal.vue
@@ -0,0 +1,164 @@
+<!-- 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="rk-modal-bg" v-show="show" @mousemove="modalMove" @mouseup="cancelMove">
+ <div class="rk-modal-container">
+ <div class="rk-modal-header" @mousedown="setStartingPoint">
+ <span class="title">{{ this.title }}</span>
+ <div class="r rk-modal-close" @click="cancel">
+ <svg class="icon">
+ <use xlink:href="#close"></use>
+ </svg>
+ </div>
+ </div>
+ <div class="rk-modal-main">
+ <slot></slot>
+ </div>
+ <div v-if="showButton" class="rk-modal-footer">
+ <rk-button class="cancel" @click="cancel">{{ $t('cancel') }}</rk-button>
+ <rk-button @click="confirm">{{ $t('confirm') }}</rk-button>
+ </div>
+ </div>
+ </div>
+</template>
+<script lang="js">
+ import RkButton from '@/components/rk-button';
+ export default {
+ name: 'RkModal',
+ components: {RkButton},
+ props: {
+ show: {
+ type: Boolean,
+ default: false,
+ },
+ showButton: {
+ type: Boolean,
+ default: false,
+ },
+ title: {
+ type: String,
+ default: '',
+ },
+ },
+ data() {
+ return {
+ x: 0,
+ y: 0,
+ node: null,
+ isCanMove: false,
+ };
+ },
+ mounted() {
+ this.node = document.querySelector('.rk-modal-container');
+ },
+ methods: {
+ cancel() {
+ this.$emit('update:show', false);
+ this.$emit('cancelModalCallback');
+ },
+
+ confirm() {
+ this.$emit('update:show', false);
+ this.$emit('confirmModalCallback');
+ },
+
+ setStartingPoint(e) {
+ this.x = e.clientX - this.node.offsetLeft;
+ this.y = e.clientY - this.node.offsetTop;
+ this.isCanMove = true;
+ },
+
+ modalMove(e) {
+ if (this.isCanMove) {
+ this.node.style.left = e.clientX - this.x + 'px';
+ this.node.style.top = e.clientY - this.y + 'px';
+ }
+ },
+
+ cancelMove() {
+ this.isCanMove = false;
+ },
+ },
+ };
+</script>
+
+<style lang="scss">
+ .rk-modal-bg {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: rgba(0, 0, 0, .5);
+ z-index: 10;
+ }
+
+ .rk-modal-container {
+ background: #fff;
+ border-radius: 10px;
+ overflow: hidden;
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ }
+
+ .rk-modal-header {
+ height: 35px;
+ background: #333840;
+ color: #efefef;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ cursor: move;
+ }
+
+ .rk-modal-close {
+ position: absolute;
+ right: 10px;
+ top: 5px;
+ cursor: pointer;
+ color: #d8d8d8;
+ transition: color 0.3s;
+
+ .icon {
+ width: 18px;
+ height: 20px;
+ }
+
+ &:hover {
+ color: #3d92ff;
+ }
+ }
+
+ .rk-modal-main {
+ padding: 15px 40px;
+ }
+
+ .rk-modal-footer {
+ display: flex;
+ align-items: center;
+ justify-content: right;
+ height: 35px;
+ border-top: 1px solid #ddd;
+ padding-right: 10px;
+ }
+ .rk-modal-footer .cancel{
+ margin-right: 10px;
+ border: 1px solid #ddd;
+ color: #333;
+ background-color: #fff;
+ }
+</style>
diff --git a/src/views/components/dashboard/charts/chart-slow.vue b/src/views/components/dashboard/charts/chart-slow.vue
index 9f905e5..23bf084 100644
--- a/src/views/components/dashboard/charts/chart-slow.vue
+++ b/src/views/components/dashboard/charts/chart-slow.vue
@@ -22,11 +22,20 @@
</svg>
<div class="mb-5 ell" v-tooltip:top.ellipsis="i.name || ''">
<span class="calls sm mr-10">{{ i.value }}</span>
- <span class="cp link-hover">{{ i.name + getTraceId(i) }}</span>
+ <span class="cp link-hover" @click="handleLink(i)">{{ i.name + getTraceId(i) }}</span>
</div>
<RkProgress :precent="(i.value / maxValue) * 100" color="#bf99f8" />
</div>
</div>
+ <rk-modal :show.sync="showModal" :title="$t('modalTitle')">
+ <div>
+ {{ $t('selectRedirectPage').replace('%s', redirectData.name) }}
+ </div>
+ <div class="mt-15">
+ <router-link :to="redirectData.log" class="rk-chart-slow-link mr-20">{{ $t('log') }}</router-link>
+ <router-link :to="redirectData.trace" class="rk-chart-slow-link mr-20">{{ $t('trace') }}</router-link>
+ </div>
+ </rk-modal>
</div>
</template>
@@ -34,6 +43,7 @@
import Vue from 'vue';
import { Component, Prop } from 'vue-property-decorator';
import copy from '@/utils/copy';
+ import {MetricsName} from '@/views/components/dashboard/charts/constant';
@Component({})
export default class ChartSlow extends Vue {
@@ -41,6 +51,37 @@
@Prop() private item!: any;
@Prop() private type!: any;
@Prop() private intervalTime!: any;
+ private showModal: boolean = false;
+ private redirectData: any = {
+ name: '',
+ log: {
+ path: '',
+ query: {
+ service: '',
+ },
+ },
+ trace: {
+ path: '',
+ query: {
+ service: '',
+ },
+ },
+ };
+ private isServiceChart: boolean = false;
+
+ private created() {
+ const serviceMetricNames = [
+ MetricsName.SERVICE_RESP_TIME,
+ MetricsName.SERVICE_SLA,
+ MetricsName.SERVICE_CPM,
+ MetricsName.SERVICE_PERCENTILE,
+ MetricsName.SERVICE_APDEX,
+ ];
+ this.isServiceChart = 'Service' === this.item.entityType
+ && 'sortMetrics' === this.item.queryMetricType
+ && serviceMetricNames.includes(this.item.metricName);
+ }
+
get maxValue() {
if (!this.data.length) {
return null;
@@ -54,6 +95,24 @@
private handleClick(i: any) {
copy(i);
}
+ private handleLink(i: any) {
+ if (this.isServiceChart && i.name) {
+ this.redirectData.name = i.name;
+ this.redirectData.log = {
+ path: 'log',
+ query: {
+ service: encodeURIComponent(i.name),
+ },
+ };
+ this.redirectData.trace = {
+ path: 'trace',
+ query: {
+ service: encodeURIComponent(i.name),
+ },
+ };
+ this.showModal = true;
+ }
+ }
get datas() {
if (!this.data.length) {
return [];
@@ -88,4 +147,13 @@
.rk-chart-slow-i {
padding: 6px 0;
}
+ .rk-chart-slow-link {
+ padding: 4px 10px 7px 10px;
+ border-radius: 4px;
+ border: 1px solid #ddd;
+ color: #333;
+ background-color: #fff;
+ will-change: opacity, background-color;
+ transition: opacity 0.3s, background-color 0.3s;
+ }
</style>
diff --git a/src/views/components/dashboard/charts/constant.ts b/src/views/components/dashboard/charts/constant.ts
index 5aa812f..ef2f80a 100644
--- a/src/views/components/dashboard/charts/constant.ts
+++ b/src/views/components/dashboard/charts/constant.ts
@@ -86,3 +86,11 @@
];
export const MaxItemNum = 10;
+
+export enum MetricsName {
+ SERVICE_RESP_TIME = 'service_resp_time',
+ SERVICE_SLA = 'service_sla',
+ SERVICE_CPM = 'service_cpm',
+ SERVICE_PERCENTILE = 'service_percentile',
+ SERVICE_APDEX = 'service_apdex',
+}
diff --git a/src/views/components/log/log-bar.vue b/src/views/components/log/log-bar.vue
index 8216294..fe369e5 100644
--- a/src/views/components/log/log-bar.vue
+++ b/src/views/components/log/log-bar.vue
@@ -127,6 +127,15 @@
this.QUERY_LOGS_BYKEYWORDS();
})
.then(() => {
+ const serviceName = this.$route.query.service ? this.$route.query.service.toString() : undefined;
+ if (serviceName) {
+ for (const s of this.rocketOption.services) {
+ if (s.label === serviceName) {
+ this.selectService(s);
+ break;
+ }
+ }
+ }
this.queryLogs();
});
}
diff --git a/src/views/components/trace/trace-search.vue b/src/views/components/trace/trace-search.vue
index 92b0c4f..1882460 100644
--- a/src/views/components/trace/trace-search.vue
+++ b/src/views/components/trace/trace-search.vue
@@ -132,19 +132,32 @@
private tagsMap: Array<{ key: string; value: string }> = [];
private tagsList: string[] = [];
private clearTags: boolean = false;
+ private serviceName: string = '';
private created() {
this.traceId = this.$route.query.traceid ? this.$route.query.traceid.toString() : this.traceId;
+ this.serviceName = this.$route.query.service ? this.$route.query.service.toString() : this.serviceName;
this.time = [this.rocketbotGlobal.durationRow.start, this.rocketbotGlobal.durationRow.end];
}
private mounted() {
- this.getTraceList();
- if (this.service && this.service.key) {
- this.GET_INSTANCES({
- duration: this.durationTime,
- serviceId: this.service.key,
+ this.GET_SERVICES({ duration: this.durationTime })
+ .then(() => {
+ if (this.serviceName) {
+ for (const s of this.rocketTrace.services) {
+ if (s.label === this.serviceName) {
+ this.service = s;
+ break;
+ }
+ }
+ }
+ this.getTraceList();
+ if (this.service && this.service.key) {
+ this.GET_INSTANCES({
+ duration: this.durationTime,
+ serviceId: this.service.key,
+ });
+ }
});
- }
}
private globalTimeFormat(time: Date[]) {
const step = 'SECOND';
@@ -195,7 +208,6 @@
this.tagsMap = data.tagsMap;
}
private getTraceList() {
- this.GET_SERVICES({ duration: this.durationTime });
const temp: any = {
queryDuration: this.globalTimeFormat([
new Date(
@@ -259,6 +271,7 @@
localStorage.removeItem('traceId');
this.traceState = { label: 'All', key: 'ALL' };
this.SET_TRACE_FORM_ITEM({ type: 'queryOrder', data: '' });
+ this.GET_SERVICES({ duration: this.durationTime });
this.getTraceList();
}