feat(editor): Add workflow filters to querystring (#7456)

fixes:
https://linear.app/n8n/issue/ADO-1222/feature-save-filters-in-workflows
This commit is contained in:
Ricardo Espinoza
2023-11-08 08:42:53 -05:00
committed by GitHub
parent 8171ad4fa8
commit afd637b5ea
7 changed files with 228 additions and 4 deletions

View File

@@ -17,6 +17,7 @@
ref="selectRef"
loading-text="..."
popper-class="tags-dropdown"
data-test-id="tags-dropdown"
@update:modelValue="onTagsUpdated"
@visible-change="onVisibleChange"
@remove-tag="onRemoveTag"
@@ -48,6 +49,7 @@
:key="tag.id + '_' + i"
:label="tag.name"
class="tag"
data-test-id="tag"
ref="tagRefs"
/>

View File

@@ -107,7 +107,7 @@
<div v-if="showFiltersDropdown" v-show="hasFilters" class="mt-xs">
<n8n-info-tip :bold="false">
{{ i18n.baseText(`${resourceKey}.filters.active`) }}
<n8n-link @click="resetFilters" size="small">
<n8n-link data-test-id="workflows-filter-reset" @click="resetFilters" size="small">
{{ i18n.baseText(`${resourceKey}.filters.active.reset`) }}
</n8n-link>
</n8n-info-tip>
@@ -392,6 +392,19 @@ export default defineComponent({
this.loading = false;
await this.$nextTick();
this.focusSearchInput();
if (this.hasAppliedFilters()) {
this.hasFilters = true;
}
},
hasAppliedFilters(): boolean {
return !!this.filterKeys.find(
(key) =>
key !== 'search' &&
(Array.isArray(this.filters[key])
? this.filters[key].length > 0
: this.filters[key] !== ''),
);
},
setCurrentPage(page: number) {
this.currentPage = page;

View File

@@ -101,12 +101,17 @@
color="text-base"
class="mb-3xs"
/>
<n8n-select :modelValue="filters.status" @update:modelValue="setKeyValue('status', $event)">
<n8n-select
data-test-id="status-dropdown"
:modelValue="filters.status"
@update:modelValue="setKeyValue('status', $event)"
>
<n8n-option
v-for="option in statusFilterOptions"
:key="option.label"
:label="option.label"
:value="option.value"
data-test-id="status"
>
</n8n-option>
</n8n-select>
@@ -130,6 +135,7 @@ import { useWorkflowsStore } from '@/stores/workflows.store';
import { useCredentialsStore } from '@/stores/credentials.store';
import { useSourceControlStore } from '@/stores/sourceControl.store';
import { genericHelpers } from '@/mixins/genericHelpers';
import { useTagsStore } from '@/stores';
type IResourcesListLayoutInstance = InstanceType<typeof ResourcesListLayout>;
@@ -153,7 +159,7 @@ const WorkflowsView = defineComponent({
search: '',
ownedBy: '',
sharedWith: '',
status: StatusFilter.ALL,
status: StatusFilter.ALL as string | boolean,
tags: [] as string[],
},
sourceControlStoreUnsubscribe: () => {},
@@ -167,6 +173,7 @@ const WorkflowsView = defineComponent({
useWorkflowsStore,
useCredentialsStore,
useSourceControlStore,
useTagsStore,
),
currentUser(): IUser {
return this.usersStore.currentUser || ({} as IUser);
@@ -221,6 +228,8 @@ const WorkflowsView = defineComponent({
filters: { tags: string[]; search: string; status: string | boolean },
matches: boolean,
): boolean {
this.saveFiltersOnQueryString();
if (this.settingsStore.areTagsEnabled && filters.tags.length > 0) {
matches =
matches &&
@@ -243,6 +252,77 @@ const WorkflowsView = defineComponent({
sendFiltersTelemetry(source: string) {
(this.$refs.layout as IResourcesListLayoutInstance).sendFiltersTelemetry(source);
},
saveFiltersOnQueryString() {
const query: { [key: string]: string } = {};
if (this.filters.search) {
query.search = this.filters.search;
}
if (typeof this.filters.status !== 'string') {
query.status = this.filters.status.toString();
}
if (this.filters.tags.length) {
query.tags = this.filters.tags.join(',');
}
if (this.filters.ownedBy) {
query.ownedBy = this.filters.ownedBy;
}
if (this.filters.sharedWith) {
query.sharedWith = this.filters.sharedWith;
}
void this.$router.replace({
name: VIEWS.WORKFLOWS,
query,
});
},
isValidUserId(userId: string) {
return Object.keys(this.usersStore.users).includes(userId);
},
setFiltersFromQueryString() {
const { tags, status, search, ownedBy, sharedWith } = this.$route.query;
const filtersToApply: { [key: string]: string | string[] | boolean } = {};
if (ownedBy && typeof ownedBy === 'string' && this.isValidUserId(ownedBy)) {
filtersToApply.ownedBy = ownedBy;
}
if (sharedWith && typeof sharedWith === 'string' && this.isValidUserId(sharedWith)) {
filtersToApply.sharedWith = sharedWith;
}
if (search && typeof search === 'string') {
filtersToApply.search = search;
}
if (tags && typeof tags === 'string') {
const currentTags = this.tagsStore.allTags.map((tag) => tag.id);
const savedTags = tags.split(',').filter((tag) => currentTags.includes(tag));
if (savedTags.length) {
filtersToApply.tags = savedTags;
}
}
if (
status &&
typeof status === 'string' &&
[StatusFilter.ACTIVE.toString(), StatusFilter.DEACTIVATED.toString()].includes(status)
) {
filtersToApply.status = status === 'true';
}
if (Object.keys(filtersToApply).length) {
this.filters = {
...this.filters,
...filtersToApply,
};
}
},
},
watch: {
'filters.tags'() {
@@ -250,6 +330,8 @@ const WorkflowsView = defineComponent({
},
},
mounted() {
this.setFiltersFromQueryString();
void this.usersStore.showPersonalizationSurvey();
this.sourceControlStoreUnsubscribe = this.sourceControlStore.$onAction(({ name, after }) => {