mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-19 11:01:15 +00:00
235 lines
6.1 KiB
TypeScript
235 lines
6.1 KiB
TypeScript
import { ref, computed } from 'vue';
|
|
import type { ComponentPublicInstance, ComputedRef } from 'vue';
|
|
import { useTestDefinitionStore } from '@/stores/testDefinition.store.ee';
|
|
import type AnnotationTagsDropdownEe from '@/components/AnnotationTagsDropdown.ee.vue';
|
|
import type { N8nInput } from 'n8n-design-system';
|
|
import type { UpdateTestDefinitionParams } from '@/api/testDefinition.ee';
|
|
import type { EditableField, EditableFormState, EvaluationFormState } from '../types';
|
|
|
|
type FormRefs = {
|
|
nameInput: ComponentPublicInstance<typeof N8nInput>;
|
|
tagsInput: ComponentPublicInstance<typeof AnnotationTagsDropdownEe>;
|
|
};
|
|
|
|
export function useTestDefinitionForm() {
|
|
const evaluationsStore = useTestDefinitionStore();
|
|
|
|
// State initialization
|
|
const state = ref<EvaluationFormState>({
|
|
name: {
|
|
value: `My Test ${evaluationsStore.allTestDefinitions.length + 1}`,
|
|
tempValue: '',
|
|
isEditing: false,
|
|
},
|
|
tags: {
|
|
value: [],
|
|
tempValue: [],
|
|
isEditing: false,
|
|
},
|
|
description: '',
|
|
evaluationWorkflow: {
|
|
mode: 'list',
|
|
value: '',
|
|
__rl: true,
|
|
},
|
|
metrics: [],
|
|
mockedNodes: [],
|
|
});
|
|
|
|
const isSaving = ref(false);
|
|
const fieldsIssues = ref<Array<{ field: string; message: string }>>([]);
|
|
const fields = ref<FormRefs>({} as FormRefs);
|
|
|
|
// A computed mapping of editable fields to their states
|
|
// This ensures TS knows the exact type of each field.
|
|
const editableFields: ComputedRef<{
|
|
name: EditableField<string>;
|
|
tags: EditableField<string[]>;
|
|
}> = computed(() => ({
|
|
name: state.value.name,
|
|
tags: state.value.tags,
|
|
}));
|
|
|
|
/**
|
|
* Load test data including metrics.
|
|
*/
|
|
const loadTestData = async (testId: string) => {
|
|
try {
|
|
await evaluationsStore.fetchAll({ force: true });
|
|
const testDefinition = evaluationsStore.testDefinitionsById[testId];
|
|
|
|
if (testDefinition) {
|
|
const metrics = await evaluationsStore.fetchMetrics(testId);
|
|
|
|
state.value.description = testDefinition.description ?? '';
|
|
state.value.name = {
|
|
value: testDefinition.name ?? '',
|
|
isEditing: false,
|
|
tempValue: '',
|
|
};
|
|
state.value.tags = {
|
|
isEditing: false,
|
|
value: testDefinition.annotationTagId ? [testDefinition.annotationTagId] : [],
|
|
tempValue: [],
|
|
};
|
|
state.value.evaluationWorkflow = {
|
|
mode: 'list',
|
|
value: testDefinition.evaluationWorkflowId ?? '',
|
|
__rl: true,
|
|
};
|
|
state.value.metrics = metrics;
|
|
state.value.mockedNodes = testDefinition.mockedNodes ?? [];
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to load test data', error);
|
|
}
|
|
};
|
|
|
|
const createTest = async (workflowId: string) => {
|
|
if (isSaving.value) return;
|
|
|
|
isSaving.value = true;
|
|
fieldsIssues.value = [];
|
|
|
|
try {
|
|
const params = {
|
|
name: state.value.name.value,
|
|
workflowId,
|
|
description: state.value.description,
|
|
};
|
|
return await evaluationsStore.create(params);
|
|
} finally {
|
|
isSaving.value = false;
|
|
}
|
|
};
|
|
|
|
const deleteMetric = async (metricId: string, testId: string) => {
|
|
await evaluationsStore.deleteMetric({ id: metricId, testDefinitionId: testId });
|
|
state.value.metrics = state.value.metrics.filter((metric) => metric.id !== metricId);
|
|
};
|
|
|
|
const updateMetrics = async (testId: string) => {
|
|
const promises = state.value.metrics.map(async (metric) => {
|
|
if (!metric.name) return;
|
|
if (!metric.id) {
|
|
const createdMetric = await evaluationsStore.createMetric({
|
|
name: metric.name,
|
|
testDefinitionId: testId,
|
|
});
|
|
metric.id = createdMetric.id;
|
|
} else {
|
|
await evaluationsStore.updateMetric({
|
|
name: metric.name,
|
|
id: metric.id,
|
|
testDefinitionId: testId,
|
|
});
|
|
}
|
|
});
|
|
|
|
await Promise.all(promises);
|
|
};
|
|
|
|
const updateTest = async (testId: string) => {
|
|
if (isSaving.value) return;
|
|
|
|
isSaving.value = true;
|
|
fieldsIssues.value = [];
|
|
|
|
try {
|
|
if (!testId) {
|
|
throw new Error('Test ID is required for updating a test');
|
|
}
|
|
|
|
const params: UpdateTestDefinitionParams = {
|
|
name: state.value.name.value,
|
|
description: state.value.description,
|
|
};
|
|
|
|
if (state.value.evaluationWorkflow.value) {
|
|
params.evaluationWorkflowId = state.value.evaluationWorkflow.value.toString();
|
|
}
|
|
|
|
const annotationTagId = state.value.tags.value[0];
|
|
if (annotationTagId) {
|
|
params.annotationTagId = annotationTagId;
|
|
}
|
|
if (state.value.mockedNodes.length > 0) {
|
|
params.mockedNodes = state.value.mockedNodes;
|
|
}
|
|
|
|
return await evaluationsStore.update({ ...params, id: testId });
|
|
} finally {
|
|
isSaving.value = false;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Start editing an editable field by copying `value` to `tempValue`.
|
|
*/
|
|
function startEditing<T extends keyof EditableFormState>(field: T) {
|
|
const fieldObj = editableFields.value[field];
|
|
if (fieldObj.isEditing) {
|
|
// Already editing, do nothing
|
|
return;
|
|
}
|
|
|
|
if (Array.isArray(fieldObj.value)) {
|
|
fieldObj.tempValue = [...fieldObj.value];
|
|
} else {
|
|
fieldObj.tempValue = fieldObj.value;
|
|
}
|
|
fieldObj.isEditing = true;
|
|
}
|
|
/**
|
|
* Save changes by copying `tempValue` back into `value`.
|
|
*/
|
|
function saveChanges<T extends keyof EditableFormState>(field: T) {
|
|
const fieldObj = editableFields.value[field];
|
|
fieldObj.value = Array.isArray(fieldObj.tempValue)
|
|
? [...fieldObj.tempValue]
|
|
: fieldObj.tempValue;
|
|
fieldObj.isEditing = false;
|
|
}
|
|
|
|
/**
|
|
* Cancel editing and revert `tempValue` from `value`.
|
|
*/
|
|
function cancelEditing<T extends keyof EditableFormState>(field: T) {
|
|
const fieldObj = editableFields.value[field];
|
|
if (Array.isArray(fieldObj.value)) {
|
|
fieldObj.tempValue = [...fieldObj.value];
|
|
} else {
|
|
fieldObj.tempValue = fieldObj.value;
|
|
}
|
|
fieldObj.isEditing = false;
|
|
}
|
|
|
|
/**
|
|
* Handle keyboard events during editing.
|
|
*/
|
|
function handleKeydown<T extends keyof EditableFormState>(event: KeyboardEvent, field: T) {
|
|
if (event.key === 'Escape') {
|
|
cancelEditing(field);
|
|
} else if (event.key === 'Enter' && !event.shiftKey) {
|
|
event.preventDefault();
|
|
saveChanges(field);
|
|
}
|
|
}
|
|
|
|
return {
|
|
state,
|
|
fields,
|
|
isSaving: computed(() => isSaving.value),
|
|
fieldsIssues: computed(() => fieldsIssues.value),
|
|
deleteMetric,
|
|
updateMetrics,
|
|
loadTestData,
|
|
createTest,
|
|
updateTest,
|
|
startEditing,
|
|
saveChanges,
|
|
cancelEditing,
|
|
handleKeydown,
|
|
};
|
|
}
|