n8n/packages/editor-ui/src/views/CredentialsView.vue
Alex Grozav 874c735d0a
feat: Improve workflow list performance using RecycleScroller and on-demand sharing data loading (#5181)
* feat(editor): Load workflow sharedWith info only when opening share modal (#5125)

* feat(editor): load workflow sharedWith info only when opening share modal

* fix(editor): update workflow share modal loading state at the end of initialize fn

* feat: initial recycle scroller commit

* feat: prepare recycle scroller for dynamic item sizes (no-changelog)

* feat: add recycle scroller with variable size support and caching

* feat: integrated recycle scroller with existing resources list

* feat: improve recycle scroller performance

* fix: fix recycle-scroller storybook

* fix: update recycle-scroller styles to fix scrollbar size

* chore: undo vite config changes

* chore: undo installed packages

* chore: remove commented code

* chore: remove vue-virtual-scroller code.

* feat: update size cache updating mechanism

* chore: remove console.log

* fix: adjust code for e2e tests

* fix: fix linting issues
2023-01-27 09:51:32 +02:00

170 lines
4.6 KiB
Vue

<template>
<resources-list-layout
ref="layout"
resource-key="credentials"
:resources="allCredentials"
:initialize="initialize"
:filters="filters"
:additional-filters-handler="onFilter"
:item-size="77"
@click:add="addCredential"
@update:filters="filters = $event"
>
<template #default="{ data }">
<credential-card data-test-id="resources-list-item" class="mb-2xs" :data="data" />
</template>
<template #filters="{ setKeyValue }">
<div class="mb-s">
<n8n-input-label
:label="$locale.baseText('credentials.filters.type')"
:bold="false"
size="small"
color="text-base"
class="mb-3xs"
/>
<n8n-select
:value="filters.type"
size="medium"
multiple
filterable
ref="typeInput"
:class="$style['type-input']"
@input="setKeyValue('type', $event)"
>
<n8n-option
v-for="credentialType in allCredentialTypes"
:key="credentialType.name"
:value="credentialType.name"
:label="credentialType.displayName"
/>
</n8n-select>
</div>
</template>
</resources-list-layout>
</template>
<script lang="ts">
import { showMessage } from '@/mixins/showMessage';
import { ICredentialsResponse, ICredentialTypeMap, IUser } from '@/Interface';
import mixins from 'vue-typed-mixins';
import SettingsView from './SettingsView.vue';
import ResourcesListLayout from '@/components/layouts/ResourcesListLayout.vue';
import PageViewLayout from '@/components/layouts/PageViewLayout.vue';
import PageViewLayoutList from '@/components/layouts/PageViewLayoutList.vue';
import CredentialCard from '@/components/CredentialCard.vue';
import { ICredentialType } from 'n8n-workflow';
import TemplateCard from '@/components/TemplateCard.vue';
import { debounceHelper } from '@/mixins/debounce';
import ResourceOwnershipSelect from '@/components/forms/ResourceOwnershipSelect.ee.vue';
import ResourceFiltersDropdown from '@/components/forms/ResourceFiltersDropdown.vue';
import { CREDENTIAL_SELECT_MODAL_KEY } from '@/constants';
import Vue from 'vue';
import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui';
import { useUsersStore } from '@/stores/users';
import { useNodeTypesStore } from '@/stores/nodeTypes';
import { useCredentialsStore } from '@/stores/credentials';
type IResourcesListLayoutInstance = Vue & { sendFiltersTelemetry: (source: string) => void };
export default mixins(showMessage, debounceHelper).extend({
name: 'SettingsPersonalView',
components: {
ResourcesListLayout,
TemplateCard,
PageViewLayout,
PageViewLayoutList,
SettingsView,
CredentialCard,
ResourceOwnershipSelect,
ResourceFiltersDropdown,
},
data() {
return {
filters: {
search: '',
ownedBy: '',
sharedWith: '',
type: '',
},
};
},
computed: {
...mapStores(useCredentialsStore, useNodeTypesStore, useUIStore, useUsersStore),
allCredentials(): ICredentialsResponse[] {
return this.credentialsStore.allCredentials;
},
allCredentialTypes(): ICredentialType[] {
return this.credentialsStore.allCredentialTypes;
},
credentialTypesById(): ICredentialTypeMap {
return this.credentialsStore.credentialTypesById;
},
},
methods: {
addCredential() {
this.uiStore.openModal(CREDENTIAL_SELECT_MODAL_KEY);
this.$telemetry.track('User clicked add cred button', {
source: 'Creds list',
});
},
async initialize() {
const loadPromises = [
this.credentialsStore.fetchAllCredentials(),
this.credentialsStore.fetchCredentialTypes(false),
];
if (this.nodeTypesStore.allNodeTypes.length === 0) {
loadPromises.push(this.nodeTypesStore.getNodeTypes());
}
await Promise.all(loadPromises);
this.usersStore.fetchUsers(); // Can be loaded in the background, used for filtering
},
onFilter(
resource: ICredentialsResponse,
filters: { type: string[]; search: string },
matches: boolean,
): boolean {
if (filters.type.length > 0) {
matches = matches && filters.type.includes(resource.type);
}
if (filters.search) {
const searchString = filters.search.toLowerCase();
matches =
matches ||
(this.credentialTypesById[resource.type] &&
this.credentialTypesById[resource.type].displayName
.toLowerCase()
.includes(searchString));
}
return matches;
},
sendFiltersTelemetry(source: string) {
(this.$refs.layout as IResourcesListLayoutInstance).sendFiltersTelemetry(source);
},
},
watch: {
'filters.type'() {
this.sendFiltersTelemetry('type');
},
},
});
</script>
<style lang="scss" module>
.type-input {
--max-width: 265px;
}
.sidebarContainer ul {
padding: 0 !important;
}
</style>