refactor(editor): ResourceListLayout to script setup (#11526)

This commit is contained in:
Raúl Gómez Morales 2024-11-04 13:07:21 +01:00 committed by GitHub
parent e10968b26f
commit 5f3deea60f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 336 additions and 380 deletions

View file

@ -1,6 +1,5 @@
<script lang="ts">
import { computed, defineComponent, nextTick, ref, onMounted, watch } from 'vue';
import type { PropType } from 'vue';
<script lang="ts" setup>
import { computed, nextTick, ref, onMounted, watch } from 'vue';
import { type ProjectSharingData, ProjectTypes } from '@/types/projects.types';
import PageViewLayout from '@/components/layouts/PageViewLayout.vue';
@ -15,7 +14,6 @@ import { useTelemetry } from '@/composables/useTelemetry';
import { useRoute } from 'vue-router';
import { useProjectsStore } from '@/stores/projects.store';
// eslint-disable-next-line unused-imports/no-unused-imports, @typescript-eslint/no-unused-vars
import type { BaseTextKey } from '@/plugins/i18n';
import type { Scope } from '@n8n/permissions';
@ -32,86 +30,71 @@ export type IResource = {
sharedWithProjects?: ProjectSharingData[];
};
interface IFilters {
export interface IFilters {
search: string;
homeProject: string;
[key: string]: boolean | string | string[];
}
type IResourceKeyType = 'credentials' | 'workflows';
type IResourceKeyType = 'credentials' | 'workflows' | 'variables';
export default defineComponent({
name: 'ResourcesListLayout',
components: {
PageViewLayout,
PageViewLayoutList,
ResourceFiltersDropdown,
ResourceListHeader,
const props = withDefaults(
defineProps<{
resourceKey: IResourceKeyType;
displayName?: (resource: IResource) => string;
resources: IResource[];
disabled: boolean;
initialize: () => Promise<void>;
filters?: IFilters;
additionalFiltersHandler?: (
resource: IResource,
filters: IFilters,
matches: boolean,
) => boolean;
shareable?: boolean;
showFiltersDropdown?: boolean;
sortFns?: Record<string, (a: IResource, b: IResource) => number>;
sortOptions?: string[];
type?: 'datatable' | 'list';
typeProps: { itemSize: number } | { columns: DatatableColumn[] };
loading: boolean;
}>(),
{
displayName: (resource: IResource) => resource.name,
initialize: async () => {},
filters: () => ({ search: '', homeProject: '' }),
sortFns: () => ({}),
sortOptions: () => ['lastUpdated', 'lastCreated', 'nameAsc', 'nameDesc'],
type: 'list',
typeProps: () => ({ itemSize: 80 }),
loading: true,
additionalFiltersHandler: undefined,
showFiltersDropdown: true,
shareable: true,
},
props: {
resourceKey: {
type: String,
default: '' as IResourceKeyType,
},
displayName: {
type: Function as PropType<(resource: IResource) => string>,
default: (resource: IResource) => resource.name,
},
resources: {
type: Array as PropType<IResource[]>,
default: (): IResource[] => [],
},
disabled: {
type: Boolean,
default: false,
},
initialize: {
type: Function as PropType<() => Promise<void>>,
default: () => async () => {},
},
filters: {
type: Object,
default: (): IFilters => ({ search: '', homeProject: '' }),
},
additionalFiltersHandler: {
type: Function,
required: false,
default: undefined,
},
shareable: {
type: Boolean,
default: true,
},
showFiltersDropdown: {
type: Boolean,
default: true,
},
sortFns: {
type: Object as PropType<Record<string, (a: IResource, b: IResource) => number>>,
default: (): Record<string, (a: IResource, b: IResource) => number> => ({}),
},
sortOptions: {
type: Array as PropType<string[]>,
default: () => ['lastUpdated', 'lastCreated', 'nameAsc', 'nameDesc'],
},
type: {
type: String as PropType<'datatable' | 'list'>,
default: 'list',
},
typeProps: {
type: Object as PropType<{ itemSize: number } | { columns: DatatableColumn[] }>,
default: () => ({
itemSize: 80,
}),
},
loading: {
type: Boolean,
required: false,
default: true,
},
},
emits: ['update:filters', 'click:add', 'sort'],
setup(props, { emit }) {
);
const emit = defineEmits<{
'update:filters': [value: IFilters];
'click:add': [event: Event];
sort: [value: string];
}>();
defineSlots<{
header(): unknown;
empty(): unknown;
preamble(): unknown;
postamble(): unknown;
'add-button'(props: { disabled: boolean }): unknown;
callout(): unknown;
filters(props: {
filters: Record<string, boolean | string | string[]>;
setKeyValue: (key: string, value: unknown) => void;
}): unknown;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
default(props: { data: any; updateItemSize: (data: any) => void }): unknown;
}>();
const route = useRoute();
const i18n = useI18n();
const { callDebounced } = useDebounce();
@ -205,6 +188,11 @@ export default defineComponent({
currentPage.value = page;
};
defineExpose({
currentPage,
setCurrentPage,
});
const sendFiltersTelemetry = (source: string) => {
// Prevent sending multiple telemetry events when resetting filters
// Timeout is required to wait for search debounce to be over
@ -239,7 +227,7 @@ export default defineComponent({
emit('click:add', e);
};
const onUpdateFilters = (e: Event) => {
const onUpdateFilters = (e: IFilters) => {
emit('update:filters', e);
};
@ -362,36 +350,6 @@ export default defineComponent({
return projectsStore.currentProject.name;
}
});
return {
i18n,
search,
usersStore,
projectsStore,
filterKeys,
currentPage,
rowsPerPage,
filteredAndSortedResources,
hasFilters,
sortBy,
resettingFilters,
filtersModel,
sendFiltersTelemetry,
getColumns,
itemSize,
onAddButtonClick,
onUpdateFiltersLength,
onUpdateFilters,
resetFilters,
callDebounced,
setCurrentPage,
setRowsPerPage,
onSearch,
headerIcon,
projectName,
};
},
});
</script>
<template>

View file

@ -2,8 +2,10 @@
import { ref, computed, onMounted, watch } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import type { ICredentialsResponse, ICredentialTypeMap } from '@/Interface';
import type { IResource } from '@/components/layouts/ResourcesListLayout.vue';
import ResourcesListLayout from '@/components/layouts/ResourcesListLayout.vue';
import ResourcesListLayout, {
type IResource,
type IFilters,
} from '@/components/layouts/ResourcesListLayout.vue';
import CredentialCard from '@/components/CredentialCard.vue';
import type { ICredentialType } from 'n8n-workflow';
import {
@ -43,10 +45,10 @@ const router = useRouter();
const telemetry = useTelemetry();
const i18n = useI18n();
const filters = ref({
const filters = ref<IFilters>({
search: '',
homeProject: '',
type: '',
type: [],
});
const loading = ref(false);
@ -123,13 +125,11 @@ watch(
},
);
const onFilter = (
resource: ICredentialsResponse,
filtersToApply: { type: string[]; search: string },
matches: boolean,
): boolean => {
const onFilter = (resource: IResource, newFilters: IFilters, matches: boolean): boolean => {
const iResource = resource as ICredentialsResponse;
const filtersToApply = newFilters as IFilters & { type: string[] };
if (filtersToApply.type.length > 0) {
matches = matches && filtersToApply.type.includes(resource.type);
matches = matches && filtersToApply.type.includes(iResource.type);
}
if (filtersToApply.search) {
@ -137,8 +137,8 @@ const onFilter = (
matches =
matches ||
(credentialTypesById.value[resource.type] &&
credentialTypesById.value[resource.type].displayName.toLowerCase().includes(searchString));
(credentialTypesById.value[iResource.type] &&
credentialTypesById.value[iResource.type].displayName.toLowerCase().includes(searchString));
}
return matches;

View file

@ -1,10 +1,13 @@
<script lang="ts" setup>
import { computed, onMounted, watch, ref } from 'vue';
import ResourcesListLayout, { type IResource } from '@/components/layouts/ResourcesListLayout.vue';
import ResourcesListLayout, {
type IResource,
type IFilters,
} from '@/components/layouts/ResourcesListLayout.vue';
import WorkflowCard from '@/components/WorkflowCard.vue';
import WorkflowTagsDropdown from '@/components/WorkflowTagsDropdown.vue';
import { EnterpriseEditionFeature, MORE_ONBOARDING_OPTIONS_EXPERIMENT, VIEWS } from '@/constants';
import type { ITag, IUser, IWorkflowDb } from '@/Interface';
import type { IUser, IWorkflowDb } from '@/Interface';
import { useUIStore } from '@/stores/ui.store';
import { useSettingsStore } from '@/stores/settings.store';
import { useUsersStore } from '@/stores/users.store';
@ -49,9 +52,7 @@ const uiStore = useUIStore();
const tagsStore = useTagsStore();
const documentTitle = useDocumentTitle();
interface Filters {
search: string;
homeProject: string;
interface Filters extends IFilters {
status: string | boolean;
tags: string[];
}
@ -137,16 +138,13 @@ const emptyListDescription = computed(() => {
}
});
const onFilter = (
resource: IWorkflowDb,
newFilters: { tags: string[]; search: string; status: string | boolean },
matches: boolean,
): boolean => {
if (settingsStore.areTagsEnabled && newFilters.tags.length > 0) {
const onFilter = (resource: IResource, newFilters: IFilters, matches: boolean): boolean => {
const iFilters = newFilters as Filters;
if (settingsStore.areTagsEnabled && iFilters.tags.length > 0) {
matches =
matches &&
newFilters.tags.every((tag) =>
(resource.tags as ITag[])?.find((resourceTag) =>
iFilters.tags.every((tag) =>
(resource as IWorkflowDb).tags?.find((resourceTag) =>
typeof resourceTag === 'object'
? `${resourceTag.id}` === `${tag}`
: `${resourceTag}` === `${tag}`,
@ -155,14 +153,14 @@ const onFilter = (
}
if (newFilters.status !== '') {
matches = matches && resource.active === newFilters.status;
matches = matches && (resource as IWorkflowDb).active === newFilters.status;
}
return matches;
};
// Methods
const onFiltersUpdated = (newFilters: Filters) => {
const onFiltersUpdated = (newFilters: IFilters) => {
Object.assign(filters.value, newFilters);
};