mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
fix(editor): Add back credential type icon (no-changelog) (#9704)
This commit is contained in:
parent
daf85b4439
commit
f9aa340015
|
@ -68,6 +68,11 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
||||||
|
|
||||||
credentialsModal.actions.close();
|
credentialsModal.actions.close();
|
||||||
credentialsPage.getters.credentialCards().should('have.length', 1);
|
credentialsPage.getters.credentialCards().should('have.length', 1);
|
||||||
|
credentialsPage.getters
|
||||||
|
.credentialCards()
|
||||||
|
.first()
|
||||||
|
.find('.n8n-node-icon img')
|
||||||
|
.should('be.visible');
|
||||||
|
|
||||||
projects.getProjectTabWorkflows().click();
|
projects.getProjectTabWorkflows().click();
|
||||||
workflowsPage.getters.workflowCards().should('have.length', 1);
|
workflowsPage.getters.workflowCards().should('have.length', 1);
|
||||||
|
|
|
@ -130,7 +130,7 @@ function moveResource() {
|
||||||
<template>
|
<template>
|
||||||
<n8n-card :class="$style.cardLink" @click="onClick">
|
<n8n-card :class="$style.cardLink" @click="onClick">
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<CredentialIcon :credential-type-name="credentialType ? credentialType.name : ''" />
|
<CredentialIcon :credential-type-name="credentialType?.name ?? ''" />
|
||||||
</template>
|
</template>
|
||||||
<template #header>
|
<template #header>
|
||||||
<n8n-heading tag="h2" bold :class="$style.cardHeading">
|
<n8n-heading tag="h2" bold :class="$style.cardHeading">
|
||||||
|
|
|
@ -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>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<img v-if="filePath" :class="$style.credIcon" :src="filePath" />
|
<img v-if="filePath" :class="$style.credIcon" :src="filePath" />
|
||||||
|
@ -6,95 +83,6 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</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>
|
<style lang="scss" module>
|
||||||
.credIcon {
|
.credIcon {
|
||||||
height: 26px;
|
height: 26px;
|
||||||
|
|
|
@ -169,6 +169,8 @@ export type IResource = {
|
||||||
createdAt?: string;
|
createdAt?: string;
|
||||||
homeProject?: ProjectSharingData;
|
homeProject?: ProjectSharingData;
|
||||||
scopes?: Scope[];
|
scopes?: Scope[];
|
||||||
|
type?: string;
|
||||||
|
sharedWithProjects?: ProjectSharingData[];
|
||||||
};
|
};
|
||||||
|
|
||||||
interface IFilters {
|
interface IFilters {
|
||||||
|
|
80
packages/editor-ui/src/views/CredentialsView.test.ts
Normal file
80
packages/editor-ui/src/views/CredentialsView.test.ts
Normal 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,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -116,6 +116,8 @@ export default defineComponent({
|
||||||
createdAt: credential.createdAt,
|
createdAt: credential.createdAt,
|
||||||
homeProject: credential.homeProject,
|
homeProject: credential.homeProject,
|
||||||
scopes: credential.scopes,
|
scopes: credential.scopes,
|
||||||
|
type: credential.type,
|
||||||
|
sharedWithProjects: credential.sharedWithProjects,
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
allCredentialTypes(): ICredentialType[] {
|
allCredentialTypes(): ICredentialType[] {
|
||||||
|
|
Loading…
Reference in a new issue