UNPKG

n8n-editor-ui

Version:

Workflow Editor UI for n8n

468 lines (467 loc) 18.7 kB
import { o as __toESM } from "./chunk-6z4oVpB-.js"; import { C as computed, Cn as toDisplayString, D as createElementBlock, Gt as unref, It as ref, M as createVNode, P as defineComponent, T as createBlock, U as mergeModels, _ as Fragment, _t as watch, bt as withCtx, et as openBlock, ft as useModel, it as renderList, j as createTextVNode, k as createSlots, vn as normalizeClass, vt as watchEffect, w as createBaseVNode } from "./vue.runtime.esm-bundler-tP5dCd7J.js"; import { C as useCssVar, _t as useI18n, vt as I18nT } from "./_MapCache-ficiegRb.js"; import { J as N8nSelect_default, Sn as N8nButton_default, Y as N8nOption_default, ht as N8nTooltip_default, wn as N8nIcon_default, xn as N8nText_default, yn as N8nHeading_default } from "./src-DgvumQTi.js"; import "./en-CF30SCh2.js"; import "./preload-helper-CR0ecmWK.js"; import { t as __plugin_vue_export_helper_default } from "./_plugin-vue_export-helper-BwBpWJRZ.js"; import { y as useRouter } from "./truncate-B1HVeveJ.js"; import "./_initCloneObject-Al9ZJ6PX.js"; import "./table-DzZuElSR.js"; import { Ft as useEvaluationStore, v as useToast } from "./builder.store-sBTWwxRU.js"; import "./empty-BuGRxzl4.js"; import "./sanitize-html-Cc45ZKm8.js"; import "./CalendarDate-zWqgZMlk.js"; import "./path-browserify-BtCDmZ3_.js"; import { Ro as VIEWS } from "./constants-BbpucWL4.js"; import "./merge-k8vSyyXH.js"; import "./_baseOrderBy-B_6CV8x6.js"; import { t as require_dateformat } from "./dateformat-CM9k0--B.js"; import "./useDebounce-Bnb8W2LR.js"; import { t as convertToDisplayDate } from "./dateFormatter-DDfPHI9I.js"; import { t as require_orderBy } from "./orderBy-EuatZUAl.js"; import "./chart-CE3HWIkr.js"; import { r as Line } from "./dist-DP_8je0B.js"; import { n as statusDictionary, r as TestTableBase_default, t as getErrorBaseKey } from "./evaluation.constants-CJqUkQ3G.js"; import { t as AnimatedSpinner_default } from "./AnimatedSpinner-wiGFgffA.js"; var import_dateformat = /* @__PURE__ */ __toESM(require_dateformat()); function useMetricsChart() { const colors = { primary: useCssVar("--color--primary", document.body).value, textBase: useCssVar("--color--text", document.body).value, backgroundXLight: useCssVar("--color--background--light-3", document.body).value, foregroundLight: useCssVar("--color--foreground--tint-1", document.body).value, foregroundBase: useCssVar("--color--foreground", document.body).value, foregroundDark: useCssVar("--color--foreground--shade-1", document.body).value }; function generateChartData(runs$2, metric) { return { datasets: [{ data: runs$2, parsing: { xAxisKey: "id", yAxisKey: `metrics.${metric}` }, borderColor: colors.primary, backgroundColor: colors.backgroundXLight, borderWidth: 1, pointRadius: 2, pointHoverRadius: 4, pointBackgroundColor: colors.backgroundXLight, pointHoverBackgroundColor: colors.backgroundXLight }] }; } 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, 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 (0, import_dateformat.default)(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 }; } var MetricsChart_vue_vue_type_script_setup_true_lang_default = /* @__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 [...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) => { return openBlock(), createElementBlock("div", { class: normalizeClass(_ctx.$style.metricsChartContainer) }, [createBaseVNode("div", { class: normalizeClass(_ctx.$style.chartHeader) }, [createVNode(unref(N8nSelect_default), { "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(unref(N8nOption_default), { 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); }; } }); var MetricsChart_vue_vue_type_style_index_0_lang_module_default = { metricsChartContainer: "_metricsChartContainer_vfsn9_123", chartHeader: "_chartHeader_vfsn9_128", chartTitle: "_chartTitle_vfsn9_131", metricSelect: "_metricSelect_vfsn9_136", chartWrapper: "_chartWrapper_vfsn9_139" }; var MetricsChart_default = /* @__PURE__ */ __plugin_vue_export_helper_default(MetricsChart_vue_vue_type_script_setup_true_lang_default, [["__cssModules", { "$style": MetricsChart_vue_vue_type_style_index_0_lang_module_default }]]); var _hoisted_1 = { style: { "display": "inline-flex", "gap": "12px", "text-transform": "capitalize", "align-items": "center" } }; var TestRunsTable_vue_vue_type_script_setup_true_lang_default = /* @__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) => { return openBlock(), createElementBlock("div", { class: normalizeClass(_ctx.$style.container) }, [createVNode(unref(N8nHeading_default), { 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_default, { 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_default), { key: 0, color: "secondary" }, { default: withCtx(() => [createVNode(AnimatedSpinner_default)]), _: 1 })) : (openBlock(), createBlock(unref(N8nIcon_default), { 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_default), { 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(unref(N8nTooltip_default), { 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_default), { 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); }; } }); var TestRunsTable_vue_vue_type_style_index_0_lang_module_default = { container: "_container_o1fe0_123", grayText: "_grayText_o1fe0_129", alertText: "_alertText_o1fe0_133", warningText: "_warningText_o1fe0_151", errorText: "_errorText_o1fe0_155" }; var TestRunsTable_default = /* @__PURE__ */ __plugin_vue_export_helper_default(TestRunsTable_vue_vue_type_script_setup_true_lang_default, [["__cssModules", { "$style": TestRunsTable_vue_vue_type_style_index_0_lang_module_default }]]); var RunsSection_vue_vue_type_script_setup_true_lang_default = /* @__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(() => { return [...props.runs.reduce((acc, run) => { Object.keys(run.metrics ?? {}).forEach((metric) => acc.add(metric)); return acc; }, /* @__PURE__ */ new Set())]; }); 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) => { 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_default, { "selected-metric": selectedMetric.value, "onUpdate:selectedMetric": _cache[0] || (_cache[0] = ($event) => selectedMetric.value = $event), runs: _ctx.runs }, null, 8, ["selected-metric", "runs"]), createVNode(TestRunsTable_default, { 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); }; } }); var RunsSection_vue_vue_type_style_index_0_lang_module_default = { runs: "_runs_16gb6_123" }; var RunsSection_default = /* @__PURE__ */ __plugin_vue_export_helper_default(RunsSection_vue_vue_type_script_setup_true_lang_default, [["__cssModules", { "$style": RunsSection_vue_vue_type_style_index_0_lang_module_default }]]); var import_orderBy = /* @__PURE__ */ __toESM(require_orderBy()); var EvaluationsView_vue_vue_type_script_setup_true_lang_default = /* @__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(() => runs$2.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 runs$2 = computed(() => { return (0, import_orderBy.default)(Object.values(evaluationStore.testRunsById ?? {}).filter(({ workflowId }) => workflowId === props.name), (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_default), { 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_default), { 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_default, { "selected-metric": selectedMetric.value, "onUpdate:selectedMetric": _cache[0] || (_cache[0] = ($event) => selectedMetric.value = $event), class: normalizeClass(_ctx.$style.runs), runs: runs$2.value, "workflow-id": props.name }, null, 8, [ "selected-metric", "class", "runs", "workflow-id" ])], 2)], 2)], 2); }; } }); var EvaluationsView_vue_vue_type_style_index_0_lang_module_default = { evaluationsView: "_evaluationsView_ilj3k_123", content: "_content_ilj3k_127", header: "_header_ilj3k_134", wrapper: "_wrapper_ilj3k_148", runOrStopTestButton: "_runOrStopTestButton_ilj3k_153", runs: "_runs_ilj3k_157" }; var EvaluationsView_default = /* @__PURE__ */ __plugin_vue_export_helper_default(EvaluationsView_vue_vue_type_script_setup_true_lang_default, [["__cssModules", { "$style": EvaluationsView_vue_vue_type_style_index_0_lang_module_default }]]); export { EvaluationsView_default as default };