feat(editor): Allow to create new tags during evaluation edit

This commit is contained in:
Oleg Ivaniv 2024-12-05 12:00:14 +01:00
parent 0537524c3e
commit 2c4c457348
No known key found for this signature in database
3 changed files with 50 additions and 12 deletions

View file

@ -16,6 +16,8 @@ interface TagsDropdownProps {
allTags: ITag[];
isLoading: boolean;
tagsById: Record<string, ITag>;
createEnabled?: boolean;
manageEnabled?: boolean;
createTag?: (name: string) => Promise<ITag>;
}
@ -27,6 +29,9 @@ const props = withDefaults(defineProps<TagsDropdownProps>(), {
placeholder: '',
modelValue: () => [],
eventBus: null,
createEnabled: true,
manageEnabled: true,
createTag: undefined,
});
const emit = defineEmits<{
@ -59,6 +64,24 @@ const appliedTags = computed<string[]>(() => {
return props.modelValue.filter((id: string) => props.tagsById[id]);
});
const containerClasses = computed(() => {
return { 'tags-container': true, focused: focused.value };
});
const dropdownClasses = computed(() => {
const classes = ['tags-dropdown', 'tags-dropdown-' + dropdownId];
if (props.createEnabled) {
classes.push('tags-dropdown-create-enabled');
}
if (props.manageEnabled) {
classes.push('tags-dropdown-manage-enabled');
}
return classes.join(' ');
});
watch(
() => props.allTags,
() => {
@ -189,7 +212,7 @@ onClickOutside(
</script>
<template>
<div ref="container" :class="{ 'tags-container': true, focused }" @keydown.stop>
<div ref="container" :class="containerClasses" @keydown.stop>
<N8nSelect
ref="selectRef"
:teleported="true"
@ -201,14 +224,14 @@ onClickOutside(
multiple
:reserve-keyword="false"
loading-text="..."
:popper-class="['tags-dropdown', 'tags-dropdown-' + dropdownId].join(' ')"
:popper-class="dropdownClasses"
data-test-id="tags-dropdown"
@update:model-value="onTagsUpdated"
@visible-change="onVisibleChange"
@remove-tag="onRemoveTag"
>
<N8nOption
v-if="options.length === 0 && filter"
v-if="createEnabled && options.length === 0 && filter"
:key="CREATE_KEY"
ref="createRef"
:value="CREATE_KEY"
@ -220,7 +243,7 @@ onClickOutside(
</span>
</N8nOption>
<N8nOption v-else-if="options.length === 0" value="message" disabled>
<span>{{ i18n.baseText('tagsDropdown.typeToCreateATag') }}</span>
<span v-if="createEnabled">{{ i18n.baseText('tagsDropdown.typeToCreateATag') }}</span>
<span v-if="allTags.length > 0">{{
i18n.baseText('tagsDropdown.noMatchingTagsExist')
}}</span>
@ -237,7 +260,7 @@ onClickOutside(
data-test-id="tag"
/>
<N8nOption :key="MANAGE_KEY" :value="MANAGE_KEY" class="ops manage-tags">
<N8nOption v-if="manageEnabled" :key="MANAGE_KEY" :value="MANAGE_KEY" class="ops manage-tags">
<font-awesome-icon icon="cog" />
<span>{{ i18n.baseText('tagsDropdown.manageTags') }}</span>
</N8nOption>
@ -313,7 +336,7 @@ onClickOutside(
}
}
&:after {
.tags-dropdown-manage-enabled &:after {
content: ' ';
display: block;
min-height: $--item-height;

View file

@ -15,6 +15,7 @@ export interface TagsInputProps {
startEditing: (field: string) => void;
saveChanges: (field: string) => void;
cancelEditing: (field: string) => void;
createTag?: (name: string) => Promise<ITag>;
}
const props = withDefaults(defineProps<TagsInputProps>(), {
@ -22,6 +23,7 @@ const props = withDefaults(defineProps<TagsInputProps>(), {
isEditing: false,
appliedTagIds: [],
}),
createTag: undefined,
});
const emit = defineEmits<{ 'update:modelValue': [value: TagsInputProps['modelValue']] }>();
@ -70,20 +72,19 @@ function updateTags(tags: string[]) {
v-else
:model-value="modelValue.appliedTagIds"
:placeholder="locale.baseText('executionAnnotationView.chooseOrCreateATag')"
:create-enabled="false"
:create-enabled="modelValue.appliedTagIds.length === 0"
:all-tags="allTags"
:is-loading="isLoading"
:tags-by-id="tagsById"
data-test-id="workflow-tags-dropdown"
:event-bus="tagsEventBus"
:create-tag="createTag"
:manage-enabled="false"
@update:model-value="updateTags"
@esc="cancelEditing('tags')"
@blur="saveChanges('tags')"
/>
</n8n-input-label>
<n8n-text size="small" color="text-light">{{
locale.baseText('testDefinition.edit.tagsHelpText')
}}</n8n-text>
</div>
</template>

View file

@ -24,8 +24,11 @@ const route = useRoute();
const locale = useI18n();
const { debounce } = useDebounce();
const toast = useToast();
const { isLoading, allTags, tagsById, fetchAll } = useAnnotationTagsStore();
const tagsStore = useAnnotationTagsStore();
const isLoading = computed(() => tagsStore.isLoading);
const allTags = computed(() => tagsStore.allTags);
const tagsById = computed(() => tagsStore.tagsById);
const testId = computed(() => props.testId ?? (route.params.testId as string));
const currentWorkflowId = computed(() => route.params.name as string);
const buttonLabel = computed(() =>
@ -48,7 +51,7 @@ const {
} = useTestDefinitionForm();
onMounted(async () => {
await fetchAll();
await tagsStore.fetchAll();
if (testId.value) {
await loadTestData(testId.value);
} else {
@ -83,6 +86,16 @@ function hasIssues(key: string) {
return fieldsIssues.value.some((issue) => issue.field === key);
}
async function handleCreateTag(tagName: string) {
try {
const newTag = await tagsStore.create(tagName);
return newTag;
} catch (error) {
toast.showError(error, 'Error', error.message);
throw error;
}
}
watch(() => state.value, debounce(onSaveTest, { debounceTime: 400 }), { deep: true });
</script>
@ -126,6 +139,7 @@ watch(() => state.value, debounce(onSaveTest, { debounceTime: 400 }), { deep: tr
:start-editing="startEditing"
:save-changes="saveChanges"
:cancel-editing="cancelEditing"
:create-tag="handleCreateTag"
/>
</template>
</EvaluationStep>