feat(editor): Add new /templates/search endpoint (#8227)
Updating n8n front-end to use the new search endpoint powered by TypeSense. Endpoint is deployed on staging API so, in order to test it, use this env var: ```export N8N_TEMPLATES_HOST=https://api-staging.n8n.io/api``` **NOTE**: This PR should not be merged until [backend changes](https://github.com/n8n-io/creators-site/pull/118) are merged and released. ## Related tickets and issues https://linear.app/n8n/issue/ADO-1555/update-in-app-search-to-work-with-the-new-back-end ## Review / Merge checklist - [x] PR title and summary are descriptive. **Remember, the title automatically goes into the changelog. Use `(no-changelog)` otherwise.** ([conventions](https://github.com/n8n-io/n8n/blob/master/.github/pull_request_title_conventions.md)) - [ ] Tests included. > A bug is not considered fixed, unless a test is added to prevent it from happening again. > A feature is not complete without tests.
This commit is contained in:
@@ -1,32 +1,35 @@
|
||||
<template>
|
||||
<div :class="$style.filters" class="template-filters">
|
||||
<div :class="$style.filters" class="template-filters" data-test-id="templates-filter-container">
|
||||
<div :class="$style.title" v-text="$locale.baseText('templates.categoriesHeading')" />
|
||||
<div v-if="loading" :class="$style.list">
|
||||
<n8n-loading :loading="loading" :rows="expandLimit" />
|
||||
</div>
|
||||
<ul v-if="!loading" :class="$style.categories">
|
||||
<li :class="$style.item">
|
||||
<el-checkbox
|
||||
:label="$locale.baseText('templates.allCategories')"
|
||||
:model-value="allSelected"
|
||||
@update:modelValue="(value) => resetCategories(value)"
|
||||
/>
|
||||
<li :class="$style.item" data-test-id="template-filter-all-categories">
|
||||
<el-checkbox :model-value="allSelected" @update:model-value="() => resetCategories()">
|
||||
{{ $locale.baseText('templates.allCategories') }}
|
||||
</el-checkbox>
|
||||
</li>
|
||||
<li
|
||||
v-for="category in collapsed ? sortedCategories.slice(0, expandLimit) : sortedCategories"
|
||||
:key="category.id"
|
||||
v-for="(category, index) in collapsed
|
||||
? sortedCategories.slice(0, expandLimit)
|
||||
: sortedCategories"
|
||||
:key="index"
|
||||
:class="$style.item"
|
||||
:data-test-id="`template-filter-${category.name.toLowerCase().replaceAll(' ', '-')}`"
|
||||
>
|
||||
<el-checkbox
|
||||
:label="category.name"
|
||||
:model-value="isSelected(category.id)"
|
||||
@update:modelValue="(value) => handleCheckboxChanged(value, category)"
|
||||
/>
|
||||
:model-value="isSelected(category)"
|
||||
@update:model-value="(value: boolean) => handleCheckboxChanged(value, category)"
|
||||
>
|
||||
{{ category.name }}
|
||||
</el-checkbox>
|
||||
</li>
|
||||
</ul>
|
||||
<div
|
||||
v-if="sortedCategories.length > expandLimit && collapsed && !loading"
|
||||
:class="$style.button"
|
||||
data-test-id="expand-categories-button"
|
||||
@click="collapseAction"
|
||||
>
|
||||
<n8n-text size="small" color="primary">
|
||||
@@ -39,17 +42,21 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import type { ITemplatesCategory } from '@/Interface';
|
||||
import type { PropType } from 'vue';
|
||||
import { useTemplatesStore } from '@/stores/templates.store';
|
||||
import { mapStores } from 'pinia';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'TemplateFilters',
|
||||
props: {
|
||||
categories: {
|
||||
type: Array as PropType<ITemplatesCategory[]>,
|
||||
default: () => [],
|
||||
},
|
||||
sortOnPopulate: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
categories: {
|
||||
type: Array,
|
||||
},
|
||||
expandLimit: {
|
||||
type: Number,
|
||||
default: 12,
|
||||
@@ -58,9 +65,11 @@ export default defineComponent({
|
||||
type: Boolean,
|
||||
},
|
||||
selected: {
|
||||
type: Array,
|
||||
type: Array as PropType<ITemplatesCategory[]>,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
emits: ['clearAll', 'select', 'clear'],
|
||||
data() {
|
||||
return {
|
||||
collapsed: true,
|
||||
@@ -68,34 +77,48 @@ export default defineComponent({
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapStores(useTemplatesStore),
|
||||
allSelected(): boolean {
|
||||
return this.selected.length === 0;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
sortOnPopulate: {
|
||||
handler(value: boolean) {
|
||||
if (value) {
|
||||
this.sortCategories();
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
categories: {
|
||||
handler(categories: ITemplatesCategory[]) {
|
||||
if (!this.sortOnPopulate) {
|
||||
this.sortedCategories = categories;
|
||||
} else {
|
||||
const selected = this.selected || [];
|
||||
const selectedCategories = categories.filter(({ id }) => selected.includes(id));
|
||||
const notSelectedCategories = categories.filter(({ id }) => !selected.includes(id));
|
||||
this.sortedCategories = selectedCategories.concat(notSelectedCategories);
|
||||
if (categories.length > 0) {
|
||||
this.sortCategories();
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
sortCategories() {
|
||||
if (!this.sortOnPopulate) {
|
||||
this.sortedCategories = this.categories;
|
||||
} else {
|
||||
const selected = this.selected || [];
|
||||
const selectedCategories = this.categories.filter((cat) => selected.includes(cat));
|
||||
const notSelectedCategories = this.categories.filter((cat) => !selected.includes(cat));
|
||||
this.sortedCategories = selectedCategories.concat(notSelectedCategories);
|
||||
}
|
||||
},
|
||||
collapseAction() {
|
||||
this.collapsed = false;
|
||||
},
|
||||
handleCheckboxChanged(value: boolean, selectedCategory: ITemplatesCategory) {
|
||||
this.$emit(value ? 'select' : 'clear', selectedCategory.id);
|
||||
this.$emit(value ? 'select' : 'clear', selectedCategory);
|
||||
},
|
||||
isSelected(categoryId: string) {
|
||||
return this.selected.includes(categoryId);
|
||||
isSelected(category: ITemplatesCategory) {
|
||||
return this.selected.includes(category);
|
||||
},
|
||||
resetCategories() {
|
||||
this.$emit('clearAll');
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<div v-if="!simpleView" :class="$style.header">
|
||||
<n8n-heading :bold="true" size="medium" color="text-light">
|
||||
{{ $locale.baseText('templates.workflows') }}
|
||||
<span v-if="totalCount > 0" data-test-id="template-count-label">({{ totalCount }})</span>
|
||||
<span v-if="!loading && totalWorkflows" v-text="`(${totalWorkflows})`" />
|
||||
</n8n-heading>
|
||||
</div>
|
||||
@@ -19,7 +20,7 @@
|
||||
@useWorkflow="(e) => onUseWorkflow(e, workflow.id)"
|
||||
/>
|
||||
<div v-if="infiniteScrollEnabled" ref="loader" />
|
||||
<div v-if="loading">
|
||||
<div v-if="loading" data-test-id="templates-loading-container">
|
||||
<TemplateCard
|
||||
v-for="n in 4"
|
||||
:key="'index-' + n"
|
||||
@@ -55,14 +56,20 @@ export default defineComponent({
|
||||
},
|
||||
workflows: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
totalWorkflows: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
simpleView: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
totalCount: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
if (this.infiniteScrollEnabled) {
|
||||
|
||||
Reference in New Issue
Block a user