fix(editor): Add back credential type icon (no-changelog) (#9704)

This commit is contained in:
Csaba Tuncsik 2024-06-11 20:16:27 +02:00 committed by GitHub
parent daf85b4439
commit f9aa340015
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 167 additions and 90 deletions

View file

@ -68,6 +68,11 @@ describe('Projects', { disableAutoLogin: true }, () => {
credentialsModal.actions.close();
credentialsPage.getters.credentialCards().should('have.length', 1);
credentialsPage.getters
.credentialCards()
.first()
.find('.n8n-node-icon img')
.should('be.visible');
projects.getProjectTabWorkflows().click();
workflowsPage.getters.workflowCards().should('have.length', 1);

View file

@ -130,7 +130,7 @@ function moveResource() {
<template>
<n8n-card :class="$style.cardLink" @click="onClick">
<template #prepend>
<CredentialIcon :credential-type-name="credentialType ? credentialType.name : ''" />
<CredentialIcon :credential-type-name="credentialType?.name ?? ''" />
</template>
<template #header>
<n8n-heading tag="h2" bold :class="$style.cardHeading">

View file

@ -1,3 +1,80 @@
<script setup lang="ts">
import { computed } from 'vue';
import { useCredentialsStore } from '@/stores/credentials.store';
import { useRootStore } from '@/stores/n8nRoot.store';
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
import type { ICredentialType } from 'n8n-workflow';
import NodeIcon from '@/components/NodeIcon.vue';
import { getThemedValue } from '@/utils/nodeTypesUtils';
import { useUIStore } from '@/stores/ui.store';
const props = defineProps<{
credentialTypeName: string | null;
}>();
const credentialsStore = useCredentialsStore();
const nodeTypesStore = useNodeTypesStore();
const rootStore = useRootStore();
const uiStore = useUIStore();
const credentialWithIcon = computed(() => getCredentialWithIcon(props.credentialTypeName));
const filePath = computed(() => {
const themeIconUrl = getThemedValue(credentialWithIcon.value?.iconUrl, uiStore.appliedTheme);
if (!themeIconUrl) {
return null;
}
return rootStore.getBaseUrl + themeIconUrl;
});
const relevantNode = computed(() => {
const icon = credentialWithIcon.value?.icon;
if (typeof icon === 'string' && icon.startsWith('node:')) {
const nodeType = icon.replace('node:', '');
return nodeTypesStore.getNodeType(nodeType);
}
if (!props.credentialTypeName) {
return null;
}
const nodesWithAccess = credentialsStore.getNodesWithAccess(props.credentialTypeName);
if (nodesWithAccess.length) {
return nodesWithAccess[0];
}
return null;
});
function getCredentialWithIcon(name: string | null): ICredentialType | null {
if (!name) {
return null;
}
const type = credentialsStore.getCredentialTypeByName(name);
if (!type) {
return null;
}
if (type.icon ?? type.iconUrl) {
return type;
}
if (type.extends) {
let parentCred = null;
type.extends.forEach((iconName) => {
parentCred = getCredentialWithIcon(iconName);
if (parentCred !== null) return;
});
return parentCred;
}
return null;
}
</script>
<template>
<div>
<img v-if="filePath" :class="$style.credIcon" :src="filePath" />
@ -6,95 +83,6 @@
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { mapStores } from 'pinia';
import { useCredentialsStore } from '@/stores/credentials.store';
import { useRootStore } from '@/stores/n8nRoot.store';
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
import type { ICredentialType, INodeTypeDescription } from 'n8n-workflow';
import NodeIcon from '@/components/NodeIcon.vue';
import { getThemedValue } from '@/utils/nodeTypesUtils';
import { useUIStore } from '@/stores/ui.store';
export default defineComponent({
components: {
NodeIcon,
},
props: {
credentialTypeName: {
type: String,
},
},
computed: {
...mapStores(useCredentialsStore, useNodeTypesStore, useRootStore, useUIStore),
credentialWithIcon(): ICredentialType | null {
return this.credentialTypeName ? this.getCredentialWithIcon(this.credentialTypeName) : null;
},
filePath(): string | null {
const themeIconUrl = getThemedValue(
this.credentialWithIcon?.iconUrl,
this.uiStore.appliedTheme,
);
if (!themeIconUrl) {
return null;
}
return this.rootStore.getBaseUrl + themeIconUrl;
},
relevantNode(): INodeTypeDescription | null {
const icon = this.credentialWithIcon?.icon;
if (typeof icon === 'string' && icon.startsWith('node:')) {
const nodeType = icon.replace('node:', '');
return this.nodeTypesStore.getNodeType(nodeType);
}
if (!this.credentialTypeName) {
return null;
}
const nodesWithAccess = this.credentialsStore.getNodesWithAccess(this.credentialTypeName);
if (nodesWithAccess.length) {
return nodesWithAccess[0];
}
return null;
},
},
methods: {
getCredentialWithIcon(name: string | null): ICredentialType | null {
if (!name) {
return null;
}
const type = this.credentialsStore.getCredentialTypeByName(name);
if (!type) {
return null;
}
if (type.icon || type.iconUrl) {
return type;
}
if (type.extends) {
let parentCred = null;
type.extends.forEach((name) => {
parentCred = this.getCredentialWithIcon(name);
if (parentCred !== null) return;
});
return parentCred;
}
return null;
},
},
});
</script>
<style lang="scss" module>
.credIcon {
height: 26px;

View file

@ -169,6 +169,8 @@ export type IResource = {
createdAt?: string;
homeProject?: ProjectSharingData;
scopes?: Scope[];
type?: string;
sharedWithProjects?: ProjectSharingData[];
};
interface IFilters {

View file

@ -0,0 +1,80 @@
import { createComponentRenderer } from '@/__tests__/render';
import { createTestingPinia } from '@pinia/testing';
import type { Scope } from '@n8n/permissions';
import { useCredentialsStore } from '@/stores/credentials.store';
import type { ProjectSharingData } from '@/types/projects.types';
import CredentialsView from '@/views/CredentialsView.vue';
import ResourcesListLayout from '@/components/layouts/ResourcesListLayout.vue';
vi.mock('@/components/layouts/ResourcesListLayout.vue', async (importOriginal) => {
const original = await importOriginal<typeof ResourcesListLayout>();
return {
default: {
...original.default,
render: vi.fn(),
setup: vi.fn(),
},
};
});
const renderComponent = createComponentRenderer(CredentialsView);
describe('CredentialsView', () => {
describe('with fake stores', () => {
let credentialsStore: ReturnType<typeof useCredentialsStore>;
beforeEach(() => {
createTestingPinia();
credentialsStore = useCredentialsStore();
});
afterAll(() => {
vi.resetAllMocks();
});
it('should have ResourcesListLayout render with necessary keys from credential object', () => {
const homeProject: ProjectSharingData = {
id: '1',
name: 'test',
type: 'personal',
createdAt: '2021-05-05T00:00:00Z',
updatedAt: '2021-05-05T00:00:00Z',
};
const scopes: Scope[] = ['credential:move', 'credential:delete'];
const sharedWithProjects: ProjectSharingData[] = [
{
id: '2',
name: 'test 2',
type: 'personal',
createdAt: '2021-05-05T00:00:00Z',
updatedAt: '2021-05-05T00:00:00Z',
},
];
vi.spyOn(credentialsStore, 'allCredentials', 'get').mockReturnValue([
{
id: '1',
name: 'test',
type: 'test',
createdAt: '2021-05-05T00:00:00Z',
updatedAt: '2021-05-05T00:00:00Z',
homeProject,
scopes,
sharedWithProjects,
},
]);
renderComponent();
expect(ResourcesListLayout.setup).toHaveBeenCalledWith(
expect.objectContaining({
resources: [
expect.objectContaining({
type: 'test',
homeProject,
scopes,
sharedWithProjects,
}),
],
}),
null,
);
});
});
});

View file

@ -116,6 +116,8 @@ export default defineComponent({
createdAt: credential.createdAt,
homeProject: credential.homeProject,
scopes: credential.scopes,
type: credential.type,
sharedWithProjects: credential.sharedWithProjects,
}));
},
allCredentialTypes(): ICredentialType[] {