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:
Oleg Ivaniv 2024-11-12 19:19:39 +01:00
parent 3dff63fe93
commit b8073c70fb
No known key found for this signature in database
2 changed files with 45 additions and 37 deletions

View file

@ -1,7 +1,6 @@
import { ref, computed } from 'vue';
import type { ComponentPublicInstance } from 'vue';
import type { INodeParameterResourceLocator } from 'n8n-workflow';
import { useAnnotationTagsStore } from '@/stores/tags.store';
import { useEvaluationsStore } from '@/stores/evaluations.store.ee';
import type AnnotationTagsDropdownEe from '@/components/AnnotationTagsDropdown.ee.vue';
import type { N8nInput } from 'n8n-design-system';
@ -28,9 +27,8 @@ type FormRefs = {
tagsInput: ComponentPublicInstance<typeof AnnotationTagsDropdownEe>;
};
export function useEvaluationForm(testId?: number) {
export function useEvaluationForm() {
// Stores
const tagsStore = useAnnotationTagsStore();
const evaluationsStore = useEvaluationsStore();
// Form state
@ -55,20 +53,16 @@ export function useEvaluationForm(testId?: number) {
// Loading states
const isSaving = ref(false);
const isLoading = computed(() => tagsStore.isLoading);
const fieldsIssues = ref<Array<{ field: string; message: string }>>([]);
// Computed
const isEditing = computed(() => !!testId);
const allTags = computed(() => tagsStore.allTags);
const tagsById = computed(() => tagsStore.tagsById);
// const isEditing = computed(() => !!testId);
// Field refs
const fields = ref<FormRefs>({} as FormRefs);
// Methods
const loadTestData = async () => {
if (!testId) return;
const loadTestData = async (testId: number) => {
try {
await evaluationsStore.fetchAll({ force: true });
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;
isSaving.value = true;
fieldsIssues.value = [];
try {
if (!state.value.evaluationWorkflow.value) {
addFieldIssue('evaluationWorkflow', 'Evaluation workflow is required');
}
const params = {
name: state.value.name.value,
...(state.value.tags.appliedTagIds[0] && {
@ -114,7 +113,7 @@ export function useEvaluationForm(testId?: number) {
}),
};
if (isEditing.value && testId) {
if (testId) {
await evaluationsStore.update({
id: testId,
...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) => {
if (field === 'name') {
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 {
state,
fields,
isEditing,
isLoading,
isSaving,
allTags,
tagsById,
init,
isSaving: computed(() => isSaving.value),
fieldsIssues: computed(() => fieldsIssues.value),
loadTestData,
saveTest,
startEditing,
saveChanges,

View file

@ -10,6 +10,7 @@ import WorkflowSelector from '@/components/WorkflowEvaluation/EditEvaluation/Wor
import MetricsInput from '@/components/WorkflowEvaluation/EditEvaluation/MetricsInput.vue';
import { useEvaluationForm } from '@/components/WorkflowEvaluation/composables/useEvaluationForm';
import { useI18n } from '@/composables/useI18n';
import { useAnnotationTagsStore } from '@/stores/tags.store';
const props = defineProps<{
testId?: number;
@ -20,33 +21,36 @@ const route = useRoute();
const locale = useI18n();
const testId = computed(() => props.testId ?? (route.params.testId as unknown as number));
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.saveTest'),
);
const toast = useToast();
const {
state,
isEditing,
isLoading,
fieldsIssues,
isSaving,
allTags,
tagsById,
init,
loadTestData,
saveTest,
startEditing,
saveChanges,
cancelEditing,
handleKeydown,
} = useEvaluationForm(testId.value);
} = useEvaluationForm();
onMounted(() => {
void init();
const { isLoading, allTags, tagsById, fetchAll } = useAnnotationTagsStore();
onMounted(async () => {
await fetchAll();
if (testId.value) {
await loadTestData(testId.value);
}
});
async function onSaveTest() {
try {
await saveTest();
await saveTest(testId.value);
toast.showMessage({
title: locale.baseText('workflowEvaluation.edit.testSaved'),
type: 'success',
@ -56,12 +60,19 @@ async function onSaveTest() {
toast.showError(e, locale.baseText('workflowEvaluation.edit.testSaveFailed'));
}
}
function hasIssues(key: string) {
const result = fieldsIssues.value.some((issue) => issue.field === key);
return result;
}
</script>
<template>
<div :class="$style.container">
<EvaluationHeader
v-model="state.name"
:class="{ 'has-issues': hasIssues('name') }"
:start-editing="startEditing"
:save-changes="saveChanges"
:handle-keydown="handleKeydown"
@ -71,6 +82,7 @@ async function onSaveTest() {
<TagsInput
v-model="state.tags"
:class="{ 'has-issues': hasIssues('tags') }"
:all-tags="allTags"
:tags-by-id="tagsById"
:is-loading="isLoading"
@ -79,9 +91,12 @@ async function onSaveTest() {
: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">
<n8n-button type="primary" :label="buttonLabel" :loading="isSaving" @click="onSaveTest" />