mirror of
https://github.com/n8n-io/n8n.git
synced 2025-01-11 12:57:29 -08:00
feat(editor): Allow to create new tags during evaluation edit
This commit is contained in:
parent
0537524c3e
commit
2c4c457348
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue