import { f$ as useCssVar, fY as dateFormat, d as defineComponent, x as computed, fZ as watchEffect, h as createElementBlock, g as openBlock, n as normalizeClass, j as createBaseVNode, i as createVNode, e8 as N8nSelect, w as withCtx, F as Fragment, A as renderList, e as createBlock, e9 as _sfc_main$4, l as unref, _ as _export_sfc, m as N8nHeading, k as createTextVNode, t as toDisplayString, c as useI18n, g0 as TestTableBase, p as N8nText, N as N8nIcon, g1 as statusDictionary, a9 as Tooltip, ab as I18nT, g2 as getErrorBaseKey, br as createSlots, ef as mergeModels, b as useRouter, eg as useModel, g3 as convertToDisplayDate, V as VIEWS, a as useToast, ac as useEvaluationStore, r as ref, eS as orderBy, a7 as watch, q as N8nButton } from "./index--OJ5nhDf.js"; import { L as Line } from "./index-D2apwRup.js"; import { _ as __unplugin_components_1 } from "./AnimatedSpinner-CxbOZIWM.js"; function useMetricsChart() { const colors = { primary: useCssVar("--color-primary", document.body).value, textBase: useCssVar("--color-text-base", document.body).value, backgroundXLight: useCssVar("--color-background-xlight", document.body).value, foregroundLight: useCssVar("--color-foreground-light", document.body).value, foregroundBase: useCssVar("--color-foreground-base", document.body).value, foregroundDark: useCssVar("--color-foreground-dark", document.body).value }; function generateChartData(runs2, metric) { const data = { datasets: [ { data: runs2, parsing: { xAxisKey: "id", yAxisKey: `metrics.${metric}` }, borderColor: colors.primary, backgroundColor: colors.backgroundXLight, borderWidth: 1, pointRadius: 2, pointHoverRadius: 4, pointBackgroundColor: colors.backgroundXLight, pointHoverBackgroundColor: colors.backgroundXLight } ] }; return data; } function generateChartOptions({ metric, data }) { return { responsive: true, maintainAspectRatio: false, animation: false, devicePixelRatio: 2, interaction: { mode: "index", intersect: false }, scales: { y: { border: { display: false }, grid: { color: colors.foregroundBase }, ticks: { padding: 8, color: colors.textBase } }, x: { border: { display: false }, grid: { display: false }, ticks: { color: colors.textBase, // eslint-disable-next-line id-denylist callback(_tickValue, index) { return `#${data[index].index}`; } } } }, plugins: { tooltip: { backgroundColor: colors.backgroundXLight, titleColor: colors.textBase, titleFont: { weight: "600" }, bodyColor: colors.textBase, bodySpacing: 4, padding: 12, borderColor: colors.foregroundBase, borderWidth: 1, displayColors: true, callbacks: { title: (tooltipItems) => { return dateFormat(tooltipItems[0].raw.runAt, "yyyy-mm-dd HH:MM"); }, label: (context) => `${metric}: ${context.parsed.y.toFixed(2)}`, labelColor() { return { borderColor: "rgba(29, 21, 21, 0)", backgroundColor: colors.primary, borderWidth: 0, borderRadius: 5 }; } } }, legend: { display: false } } }; } return { generateChartData, generateChartOptions }; } const _sfc_main$3 = /* @__PURE__ */ defineComponent({ __name: "MetricsChart", props: { selectedMetric: {}, runs: {} }, emits: ["update:selectedMetric"], setup(__props, { emit: __emit }) { const emit = __emit; const props = __props; const metricsChart = useMetricsChart(); const availableMetrics = computed(() => { return props.runs.reduce((acc, run) => { const metricKeys = Object.keys(run.metrics ?? {}); return [.../* @__PURE__ */ new Set([...acc, ...metricKeys])]; }, []); }); const filteredRuns = computed( () => props.runs.filter((run) => run.metrics?.[props.selectedMetric] !== void 0) ); const chartData = computed( () => metricsChart.generateChartData(filteredRuns.value, props.selectedMetric) ); const chartOptions = computed( () => metricsChart.generateChartOptions({ metric: props.selectedMetric, data: filteredRuns.value }) ); watchEffect(() => { if (props.runs.length > 0 && !props.selectedMetric) { emit("update:selectedMetric", availableMetrics.value[0]); } }); return (_ctx, _cache) => { const _component_N8nOption = _sfc_main$4; const _component_N8nSelect = N8nSelect; return openBlock(), createElementBlock("div", { class: normalizeClass(_ctx.$style.metricsChartContainer) }, [ createBaseVNode("div", { class: normalizeClass(_ctx.$style.chartHeader) }, [ createVNode(_component_N8nSelect, { "model-value": _ctx.selectedMetric, class: normalizeClass(_ctx.$style.metricSelect), placeholder: "Select metric", size: "small", "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => emit("update:selectedMetric", $event)) }, { default: withCtx(() => [ (openBlock(true), createElementBlock(Fragment, null, renderList(availableMetrics.value, (metric) => { return openBlock(), createBlock(_component_N8nOption, { key: metric, label: metric, value: metric }, null, 8, ["label", "value"]); }), 128)) ]), _: 1 }, 8, ["model-value", "class"]) ], 2), createBaseVNode("div", { class: normalizeClass(_ctx.$style.chartWrapper) }, [ (openBlock(), createBlock(unref(Line), { key: _ctx.selectedMetric, data: chartData.value, options: chartOptions.value, class: normalizeClass(_ctx.$style.metricsChart) }, null, 8, ["data", "options", "class"])) ], 2) ], 2); }; } }); const metricsChartContainer = "_metricsChartContainer_1xhz2_123"; const chartHeader = "_chartHeader_1xhz2_128"; const chartTitle = "_chartTitle_1xhz2_131"; const metricSelect = "_metricSelect_1xhz2_136"; const chartWrapper = "_chartWrapper_1xhz2_139"; const style0$3 = { metricsChartContainer, chartHeader, chartTitle, metricSelect, chartWrapper }; const cssModules$3 = { "$style": style0$3 }; const MetricsChart = /* @__PURE__ */ _export_sfc(_sfc_main$3, [["__cssModules", cssModules$3]]); const _hoisted_1 = { style: { "display": "inline-flex", "gap": "12px", "text-transform": "capitalize", "align-items": "center" } }; const _sfc_main$2 = /* @__PURE__ */ defineComponent({ __name: "TestRunsTable", props: { runs: {}, columns: {} }, emits: ["rowClick"], setup(__props, { emit: __emit }) { const emit = __emit; const props = __props; const locale = useI18n(); const styledColumns = computed(() => { return props.columns.map((column) => { if (column.prop === "id") { return { ...column, width: 100 }; } if (column.prop === "runAt") { return { ...column, width: 150 }; } return column; }); }); const runSummaries = computed(() => { return props.runs.map(({ status, finalResult, errorDetails, ...run }) => { if (status === "completed" && finalResult && ["error", "warning"].includes(finalResult)) { status = "warning"; } return { ...run, status, finalResult, errorDetails }; }); }); return (_ctx, _cache) => { const _component_N8nHeading = N8nHeading; const _component_AnimatedSpinner = __unplugin_components_1; const _component_N8nTooltip = Tooltip; return openBlock(), createElementBlock("div", { class: normalizeClass(_ctx.$style.container) }, [ createVNode(_component_N8nHeading, { size: "large", bold: true, class: normalizeClass(_ctx.$style.runsTableHeading), color: "text-base" }, { default: withCtx(() => [ createTextVNode(toDisplayString(unref(locale).baseText("evaluation.listRuns.pastRuns.total", { adjustToNumber: _ctx.runs.length })) + " (" + toDisplayString(_ctx.runs.length) + ") ", 1) ]), _: 1 }, 8, ["class"]), createVNode(TestTableBase, { data: runSummaries.value, columns: styledColumns.value, "default-sort": { prop: "runAt", order: "descending" }, onRowClick: _cache[0] || (_cache[0] = (row) => row.status !== "error" ? emit("rowClick", row) : void 0) }, { id: withCtx(({ row }) => [ createTextVNode("#" + toDisplayString(row.index), 1) ]), status: withCtx(({ row }) => [ createBaseVNode("div", _hoisted_1, [ row.status === "running" ? (openBlock(), createBlock(unref(N8nText), { key: 0, color: "secondary" }, { default: withCtx(() => [ createVNode(_component_AnimatedSpinner) ]), _: 1 })) : (openBlock(), createBlock(unref(N8nIcon), { key: 1, icon: unref(statusDictionary)[row.status].icon, color: unref(statusDictionary)[row.status].color }, null, 8, ["icon", "color"])), row.status === "warning" ? (openBlock(), createBlock(unref(N8nText), { key: 2, color: "warning", class: normalizeClass([_ctx.$style.alertText, _ctx.$style.warningText]) }, { default: withCtx(() => [ createTextVNode(toDisplayString(unref(locale).baseText(`evaluation.runDetail.error.partialCasesFailed`)), 1) ]), _: 1 }, 8, ["class"])) : row.status === "error" ? (openBlock(), createBlock(_component_N8nTooltip, { key: 3, placement: "top", "show-after": 300 }, { content: withCtx(() => [ createVNode(unref(I18nT), { keypath: `${unref(getErrorBaseKey)(row.errorCode)}`, scope: "global" }, createSlots({ _: 2 }, [ unref(locale).exists(`${unref(getErrorBaseKey)(row.errorCode)}.description`) ? { name: "description", fn: withCtx(() => [ createTextVNode(toDisplayString(unref(locale).baseText( `${unref(getErrorBaseKey)(row.errorCode)}.description` ) && ". ") + " " + toDisplayString(unref(locale).baseText( `${unref(getErrorBaseKey)(row.errorCode)}.description` )), 1) ]), key: "0" } : void 0 ]), 1032, ["keypath"]) ]), default: withCtx(() => [ createVNode(unref(N8nText), { class: normalizeClass([_ctx.$style.alertText, _ctx.$style.errorText]) }, { default: withCtx(() => [ createVNode(unref(I18nT), { keypath: `${unref(getErrorBaseKey)(row.errorCode)}`, scope: "global" }, createSlots({ _: 2 }, [ unref(locale).exists(`${unref(getErrorBaseKey)(row.errorCode)}.description`) ? { name: "description", fn: withCtx(() => [ createBaseVNode("p", { class: normalizeClass(_ctx.$style.grayText) }, toDisplayString(unref(locale).baseText( `${unref(getErrorBaseKey)(row.errorCode)}.description` )), 3) ]), key: "0" } : void 0 ]), 1032, ["keypath"]) ]), _: 2 }, 1032, ["class"]) ]), _: 2 }, 1024)) : (openBlock(), createElementBlock(Fragment, { key: 4 }, [ createTextVNode(toDisplayString(row.status), 1) ], 64)) ]) ]), _: 1 }, 8, ["data", "columns"]) ], 2); }; } }); const container = "_container_okb74_123"; const grayText = "_grayText_okb74_129"; const alertText = "_alertText_okb74_133"; const warningText = "_warningText_okb74_151"; const errorText = "_errorText_okb74_155"; const style0$2 = { container, grayText, alertText, warningText, errorText }; const cssModules$2 = { "$style": style0$2 }; const TestRunsTable = /* @__PURE__ */ _export_sfc(_sfc_main$2, [["__cssModules", cssModules$2]]); const _sfc_main$1 = /* @__PURE__ */ defineComponent({ __name: "RunsSection", props: /* @__PURE__ */ mergeModels({ runs: {}, workflowId: {} }, { "selectedMetric": { required: true }, "selectedMetricModifiers": {} }), emits: ["update:selectedMetric"], setup(__props) { const props = __props; const locale = useI18n(); const router = useRouter(); const selectedMetric = useModel(__props, "selectedMetric"); const metrics = computed(() => { const metricKeys = props.runs.reduce((acc, run) => { Object.keys(run.metrics ?? {}).forEach((metric) => acc.add(metric)); return acc; }, /* @__PURE__ */ new Set()); return [...metricKeys]; }); const metricColumns = computed( () => metrics.value.map((metric) => ({ prop: `metrics.${metric}`, label: metric, sortable: true, showHeaderTooltip: true, sortMethod: (a, b) => (a.metrics?.[metric] ?? 0) - (b.metrics?.[metric] ?? 0), formatter: (row) => row.metrics?.[metric] !== void 0 ? (row.metrics?.[metric]).toFixed(2) : "" })) ); const columns = computed(() => [ { prop: "id", label: locale.baseText("evaluation.listRuns.runNumber"), showOverflowTooltip: true }, { prop: "runAt", label: "Run at", sortable: true, showOverflowTooltip: true, formatter: (row) => { const { date, time } = convertToDisplayDate(row.runAt); return [date, time].join(", "); }, sortMethod: (a, b) => new Date(a.runAt ?? a.createdAt).getTime() - new Date(b.runAt ?? b.createdAt).getTime() }, { prop: "status", label: locale.baseText("evaluation.listRuns.status"), sortable: true }, ...metricColumns.value ]); const handleRowClick = (row) => { void router.push({ name: VIEWS.EVALUATION_RUNS_DETAIL, params: { runId: row.id } }); }; return (_ctx, _cache) => { return openBlock(), createElementBlock("div", { class: normalizeClass(_ctx.$style.runs) }, [ createVNode(MetricsChart, { "selected-metric": selectedMetric.value, "onUpdate:selectedMetric": _cache[0] || (_cache[0] = ($event) => selectedMetric.value = $event), runs: _ctx.runs }, null, 8, ["selected-metric", "runs"]), createVNode(TestRunsTable, { class: normalizeClass(_ctx.$style.runsTable), runs: _ctx.runs, columns: columns.value, selectable: true, "data-test-id": "past-runs-table", onRowClick: handleRowClick }, null, 8, ["class", "runs", "columns"]) ], 2); }; } }); const runs$1 = "_runs_37xaf_123"; const style0$1 = { runs: runs$1 }; const cssModules$1 = { "$style": style0$1 }; const RunsSection = /* @__PURE__ */ _export_sfc(_sfc_main$1, [["__cssModules", cssModules$1]]); const _sfc_main = /* @__PURE__ */ defineComponent({ __name: "EvaluationsView", props: { name: {} }, setup(__props) { const props = __props; const locale = useI18n(); const toast = useToast(); const evaluationStore = useEvaluationStore(); const selectedMetric = ref(""); const cancellingTestRun = ref(false); const runningTestRun = computed(() => runs2.value.find((run) => run.status === "running")); async function runTest() { try { await evaluationStore.startTestRun(props.name); } catch (error) { toast.showError(error, locale.baseText("evaluation.listRuns.error.cantStartTestRun")); } try { await evaluationStore.fetchTestRuns(props.name); } catch (error) { toast.showError(error, locale.baseText("evaluation.listRuns.error.cantFetchTestRuns")); } } async function stopTest() { if (!runningTestRun.value) { return; } try { cancellingTestRun.value = true; await evaluationStore.cancelTestRun(runningTestRun.value.workflowId, runningTestRun.value.id); } catch (error) { toast.showError(error, locale.baseText("evaluation.listRuns.error.cantStopTestRun")); cancellingTestRun.value = false; } } const runs2 = computed(() => { const testRuns = Object.values(evaluationStore.testRunsById ?? {}).filter( ({ workflowId }) => workflowId === props.name ); return orderBy(testRuns, (record) => new Date(record.runAt), ["asc"]).map((record, index) => ({ ...record, index: index + 1 })); }); watch(runningTestRun, (run) => { if (!run) { cancellingTestRun.value = false; } }); return (_ctx, _cache) => { return openBlock(), createElementBlock("div", { class: normalizeClass(_ctx.$style.evaluationsView) }, [ createBaseVNode("div", { class: normalizeClass(_ctx.$style.header) }, [ runningTestRun.value ? (openBlock(), createBlock(unref(N8nButton), { key: 0, disabled: cancellingTestRun.value, class: normalizeClass(_ctx.$style.runOrStopTestButton), size: "small", "data-test-id": "stop-test-button", label: unref(locale).baseText("evaluation.stopTest"), type: "secondary", onClick: stopTest }, null, 8, ["disabled", "class", "label"])) : (openBlock(), createBlock(unref(N8nButton), { key: 1, class: normalizeClass(_ctx.$style.runOrStopTestButton), size: "small", "data-test-id": "run-test-button", label: unref(locale).baseText("evaluation.runTest"), type: "primary", onClick: runTest }, null, 8, ["class", "label"])) ], 2), createBaseVNode("div", { class: normalizeClass(_ctx.$style.wrapper) }, [ createBaseVNode("div", { class: normalizeClass(_ctx.$style.content) }, [ createVNode(RunsSection, { "selected-metric": selectedMetric.value, "onUpdate:selectedMetric": _cache[0] || (_cache[0] = ($event) => selectedMetric.value = $event), class: normalizeClass(_ctx.$style.runs), runs: runs2.value, "workflow-id": props.name }, null, 8, ["selected-metric", "class", "runs", "workflow-id"]) ], 2) ], 2) ], 2); }; } }); const evaluationsView = "_evaluationsView_1191q_123"; const content = "_content_1191q_127"; const header = "_header_1191q_134"; const wrapper = "_wrapper_1191q_148"; const runOrStopTestButton = "_runOrStopTestButton_1191q_153"; const runs = "_runs_1191q_157"; const style0 = { evaluationsView, content, header, wrapper, runOrStopTestButton, runs }; const cssModules = { "$style": style0 }; const EvaluationsView = /* @__PURE__ */ _export_sfc(_sfc_main, [["__cssModules", cssModules]]); export { EvaluationsView as default };