Skip to content

Commit

Permalink
feat: Implement the legend selector in metrics charts (#447)
Browse files Browse the repository at this point in the history
  • Loading branch information
Fine0830 authored Feb 20, 2025
1 parent 2b6f3ec commit 5bb4218
Show file tree
Hide file tree
Showing 16 changed files with 299 additions and 25 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "skywalking-booster-ui",
"version": "9.4.0",
"version": "10.2.0",
"private": true,
"scripts": {
"dev": "vite",
Expand Down
30 changes: 30 additions & 0 deletions src/components/Graph.vue → src/components/Graph/Graph.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ 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>
<SelectorLegend
:data="option.legend.data"
:show="legendSelector.isSelector"
:isConfigPage="legendSelector.isConfigPage"
:colors="option.color"
@change="changeLegend"
/>
<div class="chart" ref="chartRef" :style="`height:${height};width:${width};`">
<div v-if="!available" class="no-data">No Data</div>
<div
Expand Down Expand Up @@ -54,6 +61,7 @@ limitations under the License. -->
import Trace from "@/views/dashboard/related/trace/Index.vue";
import associateProcessor from "@/hooks/useAssociateProcessor";
import { WidgetType } from "@/views/dashboard/data";
import SelectorLegend from "./Legend.vue";

/*global Nullable, defineProps, defineEmits, Indexable*/
const emits = defineEmits(["select"]);
Expand Down Expand Up @@ -84,6 +92,10 @@ limitations under the License. -->
type: Array as PropType<{ widgetId: string }[]>,
default: () => [],
},
legendSelector: {
type: Object as PropType<Indexable>,
default: () => ({ isConfigPage: false, isSelector: false }),
},
});
const available = computed(
() =>
Expand All @@ -103,6 +115,7 @@ limitations under the License. -->
if (!instance) {
return;
}

instance.on("click", (params: EventParams) => {
currentParams.value = params;
if (props.option.series.type === "sankey") {
Expand Down Expand Up @@ -203,6 +216,23 @@ limitations under the License. -->
});
}

function changeLegend(names: string[]) {
const instance = getInstance();
for (const item of props.option.legend.data) {
if (names.includes(item.name)) {
instance.dispatchAction({
type: "legendSelect",
name: item.name,
});
} else {
instance.dispatchAction({
type: "legendUnSelect",
name: item.name,
});
}
}
}

watch(
() => props.option,
(newVal, oldVal) => {
Expand Down
74 changes: 74 additions & 0 deletions src/components/Graph/Legend.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<!-- 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>
<Selector
class="mb-10"
multiple
:value="legend"
size="small"
:options="Options"
@change="changeLegend"
filterable
collapseTags
collapseTagsTooltip
v-if="show"
/>
</template>
<script lang="ts" setup>
import { computed, ref, watch } from "vue";
import type { PropType } from "vue";
import type { Option } from "@/types/app";
import Selector from "./Selector.vue";

const props = defineProps({
data: {
type: Array as PropType<{ name: string }[]>,
default: () => [],
},
colors: {
type: Array as PropType<string[]>,
default: () => [],
},
show: {
type: Boolean,
default: false,
},
isConfigPage: {
type: Boolean,
default: false,
},
});
const emits = defineEmits(["change"]);
const legend = ref<string[]>([]);
const Options = computed(() =>
props.data.map((d: { name: string }, index: number) => ({
label: d.name,
value: d.name,
color: props.colors[index % props.colors.length],
})),
);

function changeLegend(opt: Option[]) {
legend.value = opt.map((d: Option) => d.value);
emits("change", legend.value);
}

watch(
() => props.data,
() => {
legend.value = props.data.map((d) => d.name);
},
);
</script>
107 changes: 107 additions & 0 deletions src/components/Graph/Selector.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
<!-- 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>
<el-select
:size="size"
v-model="selected"
:placeholder="placeholder"
@change="changeSelected"
:multiple="multiple"
:disabled="disabled"
:style="{ borderRadius }"
:clearable="clearable"
:remote="isRemote"
:reserve-keyword="isRemote"
:remote-method="remoteMethod"
:filterable="filterable"
:collapse-tags="collapseTags"
:collapse-tags-tooltip="collapseTagsTooltip"
>
<el-option
v-for="(item, index) in options"
:key="`${item.value}${index}`"
:label="item.label || ''"
:value="item.value || ''"
:disabled="item.disabled || false"
>
<div class="flex items-center">
<el-tag :color="item.color" class="mr-5" size="small" />
<span :style="{ color: item.color }">{{ item.label }}</span>
</div>
</el-option>
</el-select>
</template>
<script lang="ts" setup>
import { ref, watch } from "vue";
import type { PropType } from "vue";

/*global defineProps, defineEmits, Indexable*/
const emit = defineEmits(["change", "query"]);
const props = defineProps({
options: {
type: Array as PropType<
({
label: string | number;
value: string | number;
color: string;
} & { disabled?: boolean })[]
>,
default: () => [],
},
value: {
type: [Array, String, Number, undefined] as PropType<any>,
default: () => [],
},
size: { type: null, default: "default" },
placeholder: {
type: [String, undefined] as PropType<string>,
default: "Select a option",
},
borderRadius: { type: Number, default: 3 },
multiple: { type: Boolean, default: false },
disabled: { type: Boolean, default: false },
clearable: { type: Boolean, default: false },
isRemote: { type: Boolean, default: false },
filterable: { type: Boolean, default: true },
collapseTags: { type: Boolean, default: false },
collapseTagsTooltip: { type: Boolean, default: false },
});

const selected = ref<string[] | string>(props.value);
function changeSelected() {
const options = props.options.filter((d: Indexable) =>
props.multiple ? selected.value.includes(d.value) : selected.value === d.value,
);
emit("change", options);
}

function remoteMethod(query: string) {
if (props.isRemote) {
emit("query", query);
}
}

watch(
() => props.value,
(data) => {
selected.value = data;
},
);
</script>
<style lang="scss" scoped>
.el-input__inner {
border-radius: unset !important;
}
</style>
9 changes: 4 additions & 5 deletions src/components/Selector.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ limitations under the License. -->
:reserve-keyword="isRemote"
:remote-method="remoteMethod"
:filterable="filterable"
:collapse-tags="collapseTags"
:collapse-tags-tooltip="collapseTagsTooltip"
>
<el-option
v-for="(item, index) in options"
Expand All @@ -41,11 +43,6 @@ limitations under the License. -->
import { ref, watch } from "vue";
import type { PropType } from "vue";

// interface Option {
// label: string | number;
// value: string | number;
// }

/*global defineProps, defineEmits, Indexable*/
const emit = defineEmits(["change", "query"]);
const props = defineProps({
Expand Down Expand Up @@ -73,6 +70,8 @@ limitations under the License. -->
clearable: { type: Boolean, default: false },
isRemote: { type: Boolean, default: false },
filterable: { type: Boolean, default: true },
collapseTags: { type: Boolean, default: false },
collapseTagsTooltip: { type: Boolean, default: false },
});

const selected = ref<string[] | string>(props.value);
Expand Down
2 changes: 1 addition & 1 deletion src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import type { App } from "vue";
import Icon from "./Icon.vue";
import TimePicker from "./TimePicker.vue";
import Selector from "./Selector.vue";
import Graph from "./Graph.vue";
import Graph from "./Graph/Graph.vue";
import Radio from "./Radio.vue";
import SelectSingle from "./SelectSingle.vue";
import Tags from "./Tags.vue";
Expand Down
2 changes: 1 addition & 1 deletion src/hooks/useLegendProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export default function useLegendProcess(legend?: LegendOptions) {
if (keys.length === 1) {
return false;
}
if (legend && legend.asTable) {
if (legend && (legend.asTable || legend.asSelector)) {
return false;
}
return true;
Expand Down
1 change: 1 addition & 0 deletions src/locales/lang/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ const msg = {
legendOptions: "Legend Options",
showLegend: "Show Legend",
asTable: "As Table",
asSelector: "As Selector",
toTheRight: "To The Right",
legendValues: "Legend Values",
minDuration: "Minimal Request Duration",
Expand Down
1 change: 1 addition & 0 deletions src/locales/lang/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -397,5 +397,6 @@ const msg = {
instances: "Instances",
snapshot: "Snapshot",
expression: "Expression",
asSelector: "As Selector",
};
export default msg;
1 change: 1 addition & 0 deletions src/locales/lang/zh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ const msg = {
legendOptions: "图例选项",
showLegend: "显示图例",
asTable: "作为表格",
asSelector: "作为选择器",
toTheRight: "在右边",
legendValues: "图例值",
minDuration: "最小请求持续时间",
Expand Down
2 changes: 2 additions & 0 deletions src/styles/theme.scss
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@

html {
--el-color-primary: #409eff;
--el-color-info-light-9: #666;
--theme-background: #fff;
--font-color: #3d444f;
--disabled-color: #ccc;
Expand Down Expand Up @@ -74,6 +75,7 @@ html {

html.dark {
--el-color-primary: #409eff;
--el-color-info-light-9: #333;
--theme-background: #212224;
--font-color: #fafbfc;
--disabled-color: #999;
Expand Down
3 changes: 2 additions & 1 deletion src/types/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,9 @@ declare module 'vue' {
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
ElTag: typeof import('element-plus/es')['ElTag']
ElTooltip: typeof import('element-plus/es')['ElTooltip']
Graph: typeof import('./../components/Graph.vue')['default']
Graph: typeof import('./../components/Graph/Graph.vue')['default']
Icon: typeof import('./../components/Icon.vue')['default']
Legend: typeof import('./../components/Graph/Legend.vue')['default']
Radio: typeof import('./../components/Radio.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
Expand Down
2 changes: 1 addition & 1 deletion src/types/dashboard.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,6 @@ export interface LineConfig extends AreaConfig {
smallTips?: boolean;
showlabels?: boolean;
noTooltips?: boolean;
showLegend?: boolean;
}

export interface AreaConfig {
Expand Down Expand Up @@ -198,6 +197,7 @@ export type LegendOptions = {
asTable: boolean;
toTheRight: boolean;
width: number;
asSelector: boolean;
};
export type MetricsResults = {
metric: { labels: MetricLabel[] };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,15 @@ limitations under the License. -->
@change="updateLegendConfig({ show: legend.show })"
/>
</div>
<div>
<span class="label mr-5">{{ t("asSelector") }}</span>
<el-switch
v-model="legend.asSelector"
active-text="Yes"
inactive-text="No"
@change="updateLegendConfig({ asSelector: legend.asSelector })"
/>
</div>
<div>
<span class="label">{{ t("asTable") }}</span>
<el-switch
Expand Down Expand Up @@ -97,6 +106,7 @@ limitations under the License. -->
max: false,
mean: false,
asTable: false,
asSelector: false,
toTheRight: false,
width: 130,
...graph.value.legend,
Expand Down
Loading

0 comments on commit 5bb4218

Please sign in to comment.