mirror of
https://github.com/apache/skywalking-booster-ui.git
synced 2025-10-14 20:01:28 +00:00
test: implement unit tests for hooks and refactor some types (#493)
This commit is contained in:
@@ -24,7 +24,7 @@ limitations under the License. -->
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div class="widget-chart" :style="{ height: config.height - 60 + 'px' }">
|
||||
<div class="widget-chart" :style="{ height: (config.height || 0) - 60 + 'px' }">
|
||||
<component
|
||||
:is="graph.type"
|
||||
:intervalTime="appStoreWithOut.intervalTime"
|
||||
@@ -55,10 +55,12 @@ limitations under the License. -->
|
||||
import { useRoute } from "vue-router";
|
||||
import { useSelectorStore } from "@/store/modules/selectors";
|
||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||
import { useDashboardQueryProcessor } from "@/hooks/useExpressionsProcessor";
|
||||
import { useDashboardQueryProcessor, DashboardWidgetConfig } from "@/hooks/useExpressionsProcessor";
|
||||
import graphs from "./graphs";
|
||||
import { EntityType } from "./data";
|
||||
import timeFormat from "@/utils/timeFormat";
|
||||
import { LayoutConfig } from "@/types/dashboard";
|
||||
import { ExpressionsSourceResult } from "@/hooks/useExpressionsProcessor";
|
||||
|
||||
export default defineComponent({
|
||||
name: "WidgetPage",
|
||||
@@ -70,9 +72,11 @@ limitations under the License. -->
|
||||
const appStoreWithOut = useAppStoreWithOut();
|
||||
const selectorStore = useSelectorStore();
|
||||
const route = useRoute();
|
||||
const config = computed<any>(() => JSON.parse(decodeURIComponent(route.params.config as string) as string));
|
||||
const config = computed<LayoutConfig>(() =>
|
||||
JSON.parse(decodeURIComponent(route.params.config as string) as string),
|
||||
);
|
||||
const graph = computed(() => config.value.graph || {});
|
||||
const source = ref<unknown>({});
|
||||
const source = ref<ExpressionsSourceResult | {}>({});
|
||||
const loading = ref<boolean>(false);
|
||||
const dashboardStore = useDashboardStore();
|
||||
const title = computed(() => (config.value.widget && config.value.widget.title) || "");
|
||||
@@ -86,7 +90,7 @@ limitations under the License. -->
|
||||
const { auto, autoPeriod } = config.value;
|
||||
if (auto) {
|
||||
await setDuration();
|
||||
appStoreWithOut.setReloadTimer(setInterval(setDuration, autoPeriod * 1000));
|
||||
appStoreWithOut.setReloadTimer(setInterval(setDuration, (autoPeriod ?? 0) * 1000));
|
||||
} else {
|
||||
const duration = JSON.parse(route.params.duration as string);
|
||||
appStoreWithOut.setDuration(duration);
|
||||
@@ -95,7 +99,7 @@ limitations under the License. -->
|
||||
await queryMetrics();
|
||||
}
|
||||
async function setDuration() {
|
||||
const dates: Date[] = [new Date(new Date().getTime() - config.value.auto), new Date()];
|
||||
const dates: Date[] = [new Date(new Date().getTime() - (config.value.auto ?? 0)), new Date()];
|
||||
|
||||
appStoreWithOut.setDuration(timeFormat(dates));
|
||||
}
|
||||
@@ -130,17 +134,17 @@ limitations under the License. -->
|
||||
}
|
||||
async function queryMetrics() {
|
||||
loading.value = true;
|
||||
const metrics: { [key: string]: { source: { [key: string]: unknown }; typesOfMQE: string[] } } =
|
||||
await useDashboardQueryProcessor([
|
||||
{
|
||||
metrics: config.value.expressions || [],
|
||||
metricConfig: config.value.metricConfig || [],
|
||||
subExpressions: config.value.subExpressions || [],
|
||||
id: config.value.i,
|
||||
},
|
||||
]);
|
||||
const params = metrics[config.value.i];
|
||||
loading.value = false;
|
||||
const metrics = await useDashboardQueryProcessor([
|
||||
{
|
||||
metrics: config.value.expressions || [],
|
||||
metricConfig: config.value.metricConfig || [],
|
||||
subExpressions: (config.value.subExpressions || []) as string[],
|
||||
id: config.value.i,
|
||||
},
|
||||
] as DashboardWidgetConfig[]);
|
||||
const params: ExpressionsSourceResult = (metrics as Record<string, ExpressionsSourceResult>)[
|
||||
config.value.i as string
|
||||
];
|
||||
source.value = params.source || {};
|
||||
typesOfMQE.value = params.typesOfMQE;
|
||||
}
|
||||
|
@@ -165,7 +165,7 @@ limitations under the License. -->
|
||||
{ metricConfig: metricConfig.value || [], expressions, subExpressions },
|
||||
EntityType[2].value,
|
||||
);
|
||||
currentEndpoints.value = params.data;
|
||||
currentEndpoints.value = params.data as Endpoint[];
|
||||
colMetrics.value = params.names;
|
||||
colSubMetrics.value = params.subNames;
|
||||
metricConfig.value = params.metricConfigArr;
|
||||
|
@@ -91,6 +91,7 @@ limitations under the License. -->
|
||||
import getDashboard from "@/hooks/useDashboardsSession";
|
||||
import type { MetricConfigOpt } from "@/types/dashboard";
|
||||
import ColumnGraph from "./components/ColumnGraph.vue";
|
||||
import type { PodWithMetrics } from "@/hooks/useExpressionsProcessor";
|
||||
|
||||
/*global defineProps */
|
||||
const props = defineProps({
|
||||
@@ -176,11 +177,11 @@ limitations under the License. -->
|
||||
|
||||
if (expressions.length && expressions[0]) {
|
||||
const params = await useExpressionsQueryPodsMetrics(
|
||||
currentInstances,
|
||||
currentInstances as PodWithMetrics[],
|
||||
{ metricConfig: metricConfig.value || [], expressions, subExpressions },
|
||||
EntityType[3].value,
|
||||
);
|
||||
instances.value = params.data;
|
||||
instances.value = params.data as Instance[];
|
||||
colMetrics.value = params.names;
|
||||
colSubMetrics.value = params.subNames;
|
||||
typesOfMQE.value = params.metricTypesArr;
|
||||
|
@@ -78,14 +78,14 @@ limitations under the License. -->
|
||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||
import { useAppStoreWithOut } from "@/store/modules/app";
|
||||
import type { Service } from "@/types/selector";
|
||||
import { useExpressionsQueryPodsMetrics } from "@/hooks/useExpressionsProcessor";
|
||||
import { useExpressionsQueryPodsMetrics, PodWithMetrics } from "@/hooks/useExpressionsProcessor";
|
||||
import { EntityType } from "../data";
|
||||
import router from "@/router";
|
||||
import getDashboard from "@/hooks/useDashboardsSession";
|
||||
import type { MetricConfigOpt } from "@/types/dashboard";
|
||||
import ColumnGraph from "./components/ColumnGraph.vue";
|
||||
|
||||
interface ServiceWithGroup extends Service {
|
||||
export interface ServiceWithGroup extends Service {
|
||||
merge: boolean;
|
||||
group: string;
|
||||
}
|
||||
@@ -219,11 +219,11 @@ limitations under the License. -->
|
||||
|
||||
if (expressions.length && expressions[0]) {
|
||||
const params = await useExpressionsQueryPodsMetrics(
|
||||
currentServices,
|
||||
currentServices as PodWithMetrics[],
|
||||
{ metricConfig: metricConfig.value || [], expressions, subExpressions },
|
||||
EntityType[0].value,
|
||||
);
|
||||
services.value = params.data;
|
||||
services.value = params.data as ServiceWithGroup[];
|
||||
colMetrics.value = params.names;
|
||||
colSubMetrics.value = params.subNames;
|
||||
metricConfig.value = params.metricConfigArr;
|
||||
|
@@ -19,15 +19,15 @@ limitations under the License. -->
|
||||
v-for="(item, index) in columns"
|
||||
:key="index"
|
||||
:class="item.label"
|
||||
@click="selectLog(item.label, data[item.label])"
|
||||
@click="selectLog(item.label, getDataValue(item.label))"
|
||||
>
|
||||
<span v-if="item.label === 'tags'" :class="level.toLowerCase()"> > </span>
|
||||
<span class="blue" v-else-if="item.label === 'traceId'">
|
||||
<el-tooltip content="Trace Link" v-if="!noLink && data[item.label]">
|
||||
<el-tooltip content="Trace Link" v-if="!noLink && getDataValue(item.label)">
|
||||
<Icon iconName="merge" />
|
||||
</el-tooltip>
|
||||
</span>
|
||||
<span v-else v-html="highlightKeywords(data[item.label])"></span>
|
||||
<span v-else v-html="highlightKeywords(getDataValue(item.label))"></span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -40,10 +40,11 @@ limitations under the License. -->
|
||||
import type { LayoutConfig, DashboardItem } from "@/types/dashboard";
|
||||
import { WidgetType } from "@/views/dashboard/data";
|
||||
import { useLogStore } from "@/store/modules/log";
|
||||
import type { LogItem } from "@/types/log";
|
||||
|
||||
/*global defineProps, defineEmits */
|
||||
const props = defineProps({
|
||||
data: { type: Object as any, default: () => ({}) },
|
||||
data: { type: Object as PropType<LogItem>, default: () => ({}) },
|
||||
noLink: { type: Boolean, default: true },
|
||||
config: { type: Object as PropType<LayoutConfig>, default: () => ({}) },
|
||||
});
|
||||
@@ -64,6 +65,10 @@ limitations under the License. -->
|
||||
return `${content}`.replace(regex, (match) => `<span style="color: red">${match}</span>`);
|
||||
};
|
||||
|
||||
function getDataValue(label: string) {
|
||||
return props.data[label as keyof LogItem] as string;
|
||||
}
|
||||
|
||||
function selectLog(label: string, value: string) {
|
||||
if (label === "traceId") {
|
||||
if (!value) {
|
||||
|
@@ -62,20 +62,25 @@ export function layout(levels: Node[][], calls: Call[], radius: number) {
|
||||
|
||||
export function computeCallPos(calls: Call[], radius: number) {
|
||||
for (const [index, call] of calls.entries()) {
|
||||
const centrePoints = [call.sourceObj.x, call.sourceObj.y, call.targetObj.x, call.targetObj.y];
|
||||
const centrePoints = [
|
||||
call.sourceObj?.x || 0,
|
||||
call.sourceObj?.y || 0,
|
||||
call.targetObj?.x || 0,
|
||||
call.targetObj?.y || 0,
|
||||
];
|
||||
for (const [idx, link] of calls.entries()) {
|
||||
if (
|
||||
index < idx &&
|
||||
call.id !== link.id &&
|
||||
call.sourceObj.x === link.targetObj.x &&
|
||||
call.sourceObj.y === link.targetObj.y &&
|
||||
call.targetObj.x === link.sourceObj.x &&
|
||||
call.targetObj.y === link.sourceObj.y
|
||||
call.sourceObj?.x === link.targetObj?.x &&
|
||||
call.sourceObj?.y === link.targetObj?.y &&
|
||||
call.targetObj?.x === link.sourceObj?.x &&
|
||||
call.targetObj?.y === link.sourceObj?.y
|
||||
) {
|
||||
if (call.targetObj.y === call.sourceObj.y) {
|
||||
if (call.targetObj?.y === call.sourceObj?.y) {
|
||||
centrePoints[1] = centrePoints[1] - 8;
|
||||
centrePoints[3] = centrePoints[3] - 8;
|
||||
} else if (call.targetObj.x === call.sourceObj.x) {
|
||||
} else if (call.targetObj?.x === call.sourceObj?.x) {
|
||||
centrePoints[0] = centrePoints[0] - 8;
|
||||
centrePoints[2] = centrePoints[2] - 8;
|
||||
} else {
|
||||
@@ -127,14 +132,14 @@ function findMostFrequent(arr: Call[]) {
|
||||
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
const item = arr[i];
|
||||
count[item.sourceObj.id] = (count[item.sourceObj.id] || 0) + 1;
|
||||
if (count[item.sourceObj.id] > maxCount) {
|
||||
maxCount = count[item.sourceObj.id];
|
||||
count[item.sourceObj?.id || ""] = (count[item.sourceObj?.id || ""] || 0) + 1;
|
||||
if (count[item.sourceObj?.id || ""] > maxCount) {
|
||||
maxCount = count[item.sourceObj?.id || ""];
|
||||
maxItem = item.sourceObj;
|
||||
}
|
||||
count[item.targetObj.id] = (count[item.targetObj.id] || 0) + 1;
|
||||
if (count[item.targetObj.id] > maxCount) {
|
||||
maxCount = count[item.targetObj.id];
|
||||
count[item.targetObj?.id || ""] = (count[item.targetObj?.id || ""] || 0) + 1;
|
||||
if (count[item.targetObj?.id || ""] > maxCount) {
|
||||
maxCount = count[item.targetObj?.id || ""];
|
||||
maxItem = item.targetObj;
|
||||
}
|
||||
}
|
||||
@@ -156,7 +161,7 @@ export function computeLevels(calls: Call[], nodeList: Node[], arr: Node[][]) {
|
||||
const index = nodes.findIndex((n: Node) => n.type === "USER");
|
||||
let key = index;
|
||||
if (index < 0) {
|
||||
key = nodes.findIndex((n: Node) => n.id === node.id);
|
||||
key = nodes.findIndex((n: Node) => n.id === node?.id);
|
||||
}
|
||||
levels.push([nodes[key]]);
|
||||
nodes = nodes.filter((_: unknown, index: number) => index !== key);
|
||||
|
@@ -109,7 +109,7 @@ limitations under the License. -->
|
||||
return ` <div class="mb-5"><span class="grey">${opt.label || m}: </span>${metric?.value} ${opt.unit || ""}</div>`;
|
||||
});
|
||||
const html = [
|
||||
`<div>${data.sourceObj.serviceName} -> ${data.targetObj.serviceName}</div>`,
|
||||
`<div>${data.sourceObj?.serviceName} -> ${data.targetObj?.serviceName}</div>`,
|
||||
...htmlServer,
|
||||
...htmlClient,
|
||||
].join(" ");
|
||||
|
@@ -386,7 +386,10 @@ limitations under the License. -->
|
||||
}
|
||||
function handleLinkClick(event: MouseEvent, d: Call) {
|
||||
event.stopPropagation();
|
||||
if (!d.sourceObj.layers.includes(dashboardStore.layerId) || !d.targetObj.layers.includes(dashboardStore.layerId)) {
|
||||
if (
|
||||
!d.sourceObj?.layers?.includes(dashboardStore.layerId) ||
|
||||
!d.targetObj?.layers?.includes(dashboardStore.layerId)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
topologyStore.setNode(null);
|
||||
@@ -406,7 +409,10 @@ limitations under the License. -->
|
||||
return;
|
||||
}
|
||||
dashboardStore.setEntity(dashboard.entity);
|
||||
const path = `/dashboard/related/${dashboard.layer}/${e}Relation/${d.sourceObj.id}/${d.targetObj.id}/${dashboard.name}`;
|
||||
if (!d.sourceObj || !d.targetObj) {
|
||||
return;
|
||||
}
|
||||
const path = `/dashboard/related/${dashboard.layer}/${e}Relation/${d.sourceObj?.id}/${d.targetObj?.id}/${dashboard.name}`;
|
||||
const routeUrl = router.resolve({ path });
|
||||
window.open(routeUrl.href, "_blank");
|
||||
dashboardStore.setEntity(origin);
|
||||
|
@@ -49,12 +49,12 @@ limitations under the License. -->
|
||||
<span
|
||||
v-if="
|
||||
[FiltersKeys.minTraceDuration, FiltersKeys.maxTraceDuration].includes(key) &&
|
||||
!isNaN(traceStore.conditions[FiltersKeys[key]])
|
||||
!isNaN(getConditionValue(key) as number)
|
||||
"
|
||||
>
|
||||
{{ t(key) }}: {{ traceStore.conditions[FiltersKeys[key]] }}
|
||||
{{ t(key) }}: {{ getConditionValue(key) }}
|
||||
</span>
|
||||
<span v-else-if="key !== 'duration'"> {{ t(key) }}: {{ traceStore.conditions[FiltersKeys[key]] }} </span>
|
||||
<span v-else-if="key !== 'duration'"> {{ t(key) }}: {{ getConditionValue(key) }} </span>
|
||||
</div>
|
||||
</div>
|
||||
</el-popover>
|
||||
@@ -101,6 +101,16 @@ limitations under the License. -->
|
||||
minTraceDuration: "minTraceDuration",
|
||||
maxTraceDuration: "maxTraceDuration",
|
||||
};
|
||||
|
||||
// Type-safe function to get condition value
|
||||
const getConditionValue = (key: string): string | number | undefined => {
|
||||
const conditionKey = FiltersKeys[key];
|
||||
if (!conditionKey) return undefined;
|
||||
|
||||
// Type assertion for dynamic properties that are added at runtime
|
||||
return (traceStore.conditions as Recordable)[conditionKey];
|
||||
};
|
||||
|
||||
/*global defineProps, Recordable */
|
||||
const props = defineProps({
|
||||
needQuery: { type: Boolean, default: true },
|
||||
|
Reference in New Issue
Block a user