mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
Refactor evaluation form and improve error handling in workflow evaluation
• Remove dependency on tagsStore • Add field-specific error handling • Simplify loadTestData and saveTest methods • Update EvaluationEditView component • Improve UI feedback for form errors
This commit is contained in:
parent
3dff63fe93
commit
b8073c70fb
|
@ -1,7 +1,6 @@
|
||||||
import { ref, computed } from 'vue';
|
import { ref, computed } from 'vue';
|
||||||
import type { ComponentPublicInstance } from 'vue';
|
import type { ComponentPublicInstance } from 'vue';
|
||||||
import type { INodeParameterResourceLocator } from 'n8n-workflow';
|
import type { INodeParameterResourceLocator } from 'n8n-workflow';
|
||||||
import { useAnnotationTagsStore } from '@/stores/tags.store';
|
|
||||||
import { useEvaluationsStore } from '@/stores/evaluations.store.ee';
|
import { useEvaluationsStore } from '@/stores/evaluations.store.ee';
|
||||||
import type AnnotationTagsDropdownEe from '@/components/AnnotationTagsDropdown.ee.vue';
|
import type AnnotationTagsDropdownEe from '@/components/AnnotationTagsDropdown.ee.vue';
|
||||||
import type { N8nInput } from 'n8n-design-system';
|
import type { N8nInput } from 'n8n-design-system';
|
||||||
|
@ -28,9 +27,8 @@ type FormRefs = {
|
||||||
tagsInput: ComponentPublicInstance<typeof AnnotationTagsDropdownEe>;
|
tagsInput: ComponentPublicInstance<typeof AnnotationTagsDropdownEe>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function useEvaluationForm(testId?: number) {
|
export function useEvaluationForm() {
|
||||||
// Stores
|
// Stores
|
||||||
const tagsStore = useAnnotationTagsStore();
|
|
||||||
const evaluationsStore = useEvaluationsStore();
|
const evaluationsStore = useEvaluationsStore();
|
||||||
|
|
||||||
// Form state
|
// Form state
|
||||||
|
@ -55,20 +53,16 @@ export function useEvaluationForm(testId?: number) {
|
||||||
|
|
||||||
// Loading states
|
// Loading states
|
||||||
const isSaving = ref(false);
|
const isSaving = ref(false);
|
||||||
const isLoading = computed(() => tagsStore.isLoading);
|
const fieldsIssues = ref<Array<{ field: string; message: string }>>([]);
|
||||||
|
|
||||||
// Computed
|
// Computed
|
||||||
const isEditing = computed(() => !!testId);
|
// const isEditing = computed(() => !!testId);
|
||||||
const allTags = computed(() => tagsStore.allTags);
|
|
||||||
const tagsById = computed(() => tagsStore.tagsById);
|
|
||||||
|
|
||||||
// Field refs
|
// Field refs
|
||||||
const fields = ref<FormRefs>({} as FormRefs);
|
const fields = ref<FormRefs>({} as FormRefs);
|
||||||
|
|
||||||
// Methods
|
// Methods
|
||||||
const loadTestData = async () => {
|
const loadTestData = async (testId: number) => {
|
||||||
if (!testId) return;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await evaluationsStore.fetchAll({ force: true });
|
await evaluationsStore.fetchAll({ force: true });
|
||||||
const testDefinition = evaluationsStore.testDefinitionsById[testId];
|
const testDefinition = evaluationsStore.testDefinitionsById[testId];
|
||||||
|
@ -99,11 +93,16 @@ export function useEvaluationForm(testId?: number) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const saveTest = async () => {
|
const saveTest = async (testId?: number) => {
|
||||||
if (isSaving.value) return;
|
if (isSaving.value) return;
|
||||||
|
|
||||||
isSaving.value = true;
|
isSaving.value = true;
|
||||||
|
fieldsIssues.value = [];
|
||||||
try {
|
try {
|
||||||
|
if (!state.value.evaluationWorkflow.value) {
|
||||||
|
addFieldIssue('evaluationWorkflow', 'Evaluation workflow is required');
|
||||||
|
}
|
||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
name: state.value.name.value,
|
name: state.value.name.value,
|
||||||
...(state.value.tags.appliedTagIds[0] && {
|
...(state.value.tags.appliedTagIds[0] && {
|
||||||
|
@ -114,7 +113,7 @@ export function useEvaluationForm(testId?: number) {
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isEditing.value && testId) {
|
if (testId) {
|
||||||
await evaluationsStore.update({
|
await evaluationsStore.update({
|
||||||
id: testId,
|
id: testId,
|
||||||
...params,
|
...params,
|
||||||
|
@ -132,6 +131,10 @@ export function useEvaluationForm(testId?: number) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const addFieldIssue = (field: string, message: string) => {
|
||||||
|
fieldsIssues.value.push({ field, message });
|
||||||
|
};
|
||||||
|
|
||||||
const startEditing = async (field: string) => {
|
const startEditing = async (field: string) => {
|
||||||
if (field === 'name') {
|
if (field === 'name') {
|
||||||
state.value.name.tempValue = state.value.name.value;
|
state.value.name.tempValue = state.value.name.value;
|
||||||
|
@ -167,22 +170,12 @@ export function useEvaluationForm(testId?: number) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const init = async () => {
|
|
||||||
await tagsStore.fetchAll();
|
|
||||||
if (testId) {
|
|
||||||
await loadTestData();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
state,
|
state,
|
||||||
fields,
|
fields,
|
||||||
isEditing,
|
isSaving: computed(() => isSaving.value),
|
||||||
isLoading,
|
fieldsIssues: computed(() => fieldsIssues.value),
|
||||||
isSaving,
|
loadTestData,
|
||||||
allTags,
|
|
||||||
tagsById,
|
|
||||||
init,
|
|
||||||
saveTest,
|
saveTest,
|
||||||
startEditing,
|
startEditing,
|
||||||
saveChanges,
|
saveChanges,
|
||||||
|
|
|
@ -10,6 +10,7 @@ import WorkflowSelector from '@/components/WorkflowEvaluation/EditEvaluation/Wor
|
||||||
import MetricsInput from '@/components/WorkflowEvaluation/EditEvaluation/MetricsInput.vue';
|
import MetricsInput from '@/components/WorkflowEvaluation/EditEvaluation/MetricsInput.vue';
|
||||||
import { useEvaluationForm } from '@/components/WorkflowEvaluation/composables/useEvaluationForm';
|
import { useEvaluationForm } from '@/components/WorkflowEvaluation/composables/useEvaluationForm';
|
||||||
import { useI18n } from '@/composables/useI18n';
|
import { useI18n } from '@/composables/useI18n';
|
||||||
|
import { useAnnotationTagsStore } from '@/stores/tags.store';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
testId?: number;
|
testId?: number;
|
||||||
|
@ -20,33 +21,36 @@ const route = useRoute();
|
||||||
const locale = useI18n();
|
const locale = useI18n();
|
||||||
const testId = computed(() => props.testId ?? (route.params.testId as unknown as number));
|
const testId = computed(() => props.testId ?? (route.params.testId as unknown as number));
|
||||||
const buttonLabel = computed(() =>
|
const buttonLabel = computed(() =>
|
||||||
isEditing.value
|
// No testId means we're creating a new one
|
||||||
|
testId.value
|
||||||
? locale.baseText('workflowEvaluation.edit.updateTest')
|
? locale.baseText('workflowEvaluation.edit.updateTest')
|
||||||
: locale.baseText('workflowEvaluation.edit.saveTest'),
|
: locale.baseText('workflowEvaluation.edit.saveTest'),
|
||||||
);
|
);
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
const {
|
const {
|
||||||
state,
|
state,
|
||||||
isEditing,
|
fieldsIssues,
|
||||||
isLoading,
|
|
||||||
isSaving,
|
isSaving,
|
||||||
allTags,
|
loadTestData,
|
||||||
tagsById,
|
|
||||||
init,
|
|
||||||
saveTest,
|
saveTest,
|
||||||
startEditing,
|
startEditing,
|
||||||
saveChanges,
|
saveChanges,
|
||||||
cancelEditing,
|
cancelEditing,
|
||||||
handleKeydown,
|
handleKeydown,
|
||||||
} = useEvaluationForm(testId.value);
|
} = useEvaluationForm();
|
||||||
|
|
||||||
onMounted(() => {
|
const { isLoading, allTags, tagsById, fetchAll } = useAnnotationTagsStore();
|
||||||
void init();
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await fetchAll();
|
||||||
|
if (testId.value) {
|
||||||
|
await loadTestData(testId.value);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
async function onSaveTest() {
|
async function onSaveTest() {
|
||||||
try {
|
try {
|
||||||
await saveTest();
|
await saveTest(testId.value);
|
||||||
toast.showMessage({
|
toast.showMessage({
|
||||||
title: locale.baseText('workflowEvaluation.edit.testSaved'),
|
title: locale.baseText('workflowEvaluation.edit.testSaved'),
|
||||||
type: 'success',
|
type: 'success',
|
||||||
|
@ -56,12 +60,19 @@ async function onSaveTest() {
|
||||||
toast.showError(e, locale.baseText('workflowEvaluation.edit.testSaveFailed'));
|
toast.showError(e, locale.baseText('workflowEvaluation.edit.testSaveFailed'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hasIssues(key: string) {
|
||||||
|
const result = fieldsIssues.value.some((issue) => issue.field === key);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :class="$style.container">
|
<div :class="$style.container">
|
||||||
<EvaluationHeader
|
<EvaluationHeader
|
||||||
v-model="state.name"
|
v-model="state.name"
|
||||||
|
:class="{ 'has-issues': hasIssues('name') }"
|
||||||
:start-editing="startEditing"
|
:start-editing="startEditing"
|
||||||
:save-changes="saveChanges"
|
:save-changes="saveChanges"
|
||||||
:handle-keydown="handleKeydown"
|
:handle-keydown="handleKeydown"
|
||||||
|
@ -71,6 +82,7 @@ async function onSaveTest() {
|
||||||
|
|
||||||
<TagsInput
|
<TagsInput
|
||||||
v-model="state.tags"
|
v-model="state.tags"
|
||||||
|
:class="{ 'has-issues': hasIssues('tags') }"
|
||||||
:all-tags="allTags"
|
:all-tags="allTags"
|
||||||
:tags-by-id="tagsById"
|
:tags-by-id="tagsById"
|
||||||
:is-loading="isLoading"
|
:is-loading="isLoading"
|
||||||
|
@ -79,9 +91,12 @@ async function onSaveTest() {
|
||||||
:cancel-editing="cancelEditing"
|
:cancel-editing="cancelEditing"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<WorkflowSelector v-model="state.evaluationWorkflow" />
|
<WorkflowSelector
|
||||||
|
v-model="state.evaluationWorkflow"
|
||||||
|
:class="{ 'has-issues': hasIssues('evaluationWorkflow') }"
|
||||||
|
/>
|
||||||
|
|
||||||
<MetricsInput v-model="state.metrics" />
|
<MetricsInput v-model="state.metrics" :class="{ 'has-issues': hasIssues('metrics') }" />
|
||||||
|
|
||||||
<div :class="$style.footer">
|
<div :class="$style.footer">
|
||||||
<n8n-button type="primary" :label="buttonLabel" :loading="isSaving" @click="onSaveTest" />
|
<n8n-button type="primary" :label="buttonLabel" :loading="isSaving" @click="onSaveTest" />
|
||||||
|
|
Loading…
Reference in a new issue