mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +00:00
fix: Community nodes - setting page empty state (#15305)
This commit is contained in:
@@ -534,7 +534,7 @@ describe('CommunityPackagesService', () => {
|
|||||||
globalConfig.nodes.communityPackages.unverifiedEnabled = false;
|
globalConfig.nodes.communityPackages.unverifiedEnabled = false;
|
||||||
globalConfig.nodes.communityPackages.registry = 'https://registry.npmjs.org';
|
globalConfig.nodes.communityPackages.registry = 'https://registry.npmjs.org';
|
||||||
await expect(communityPackagesService.installPackage('package', '0.1.0')).rejects.toThrow(
|
await expect(communityPackagesService.installPackage('package', '0.1.0')).rejects.toThrow(
|
||||||
'Installation of non-vetted community packages is forbidden!',
|
'Installation of unverified community packages is forbidden!',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -352,7 +352,7 @@ export class CommunityPackagesService {
|
|||||||
if (isUpdate) return;
|
if (isUpdate) return;
|
||||||
|
|
||||||
if (!this.globalConfig.nodes.communityPackages.unverifiedEnabled && !checksumProvided) {
|
if (!this.globalConfig.nodes.communityPackages.unverifiedEnabled && !checksumProvided) {
|
||||||
throw new UnexpectedError('Installation of non-vetted community packages is forbidden!');
|
throw new UnexpectedError('Installation of unverified community packages is forbidden!');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ const emit = defineEmits<{
|
|||||||
const telemetry = useTelemetry();
|
const telemetry = useTelemetry();
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
|
|
||||||
const { userActivated } = useUsersStore();
|
const { userActivated, isInstanceOwner } = useUsersStore();
|
||||||
const { popViewStack, updateCurrentViewStack } = useViewStacks();
|
const { popViewStack, updateCurrentViewStack } = useViewStacks();
|
||||||
const { registerKeyHook } = useKeyboardNavigation();
|
const { registerKeyHook } = useKeyboardNavigation();
|
||||||
const {
|
const {
|
||||||
@@ -337,6 +337,7 @@ onMounted(() => {
|
|||||||
:class="$style.communityNodeFooter"
|
:class="$style.communityNodeFooter"
|
||||||
v-if="communityNodeDetails"
|
v-if="communityNodeDetails"
|
||||||
:package-name="communityNodeDetails.packageName"
|
:package-name="communityNodeDetails.packageName"
|
||||||
|
:show-manage="communityNodeDetails.installed && isInstanceOwner"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import { fireEvent } from '@testing-library/vue';
|
||||||
|
import { VIEWS } from '@/constants';
|
||||||
|
import { createTestingPinia } from '@pinia/testing';
|
||||||
|
import { setActivePinia } from 'pinia';
|
||||||
|
import CommunityNodeFooter from './CommunityNodeFooter.vue';
|
||||||
|
import { createComponentRenderer } from '@/__tests__/render';
|
||||||
|
import { vi } from 'vitest';
|
||||||
|
|
||||||
|
const push = vi.fn();
|
||||||
|
|
||||||
|
vi.mock('vue-router', async (importOriginal) => {
|
||||||
|
const actual = await importOriginal();
|
||||||
|
return {
|
||||||
|
...(actual as object),
|
||||||
|
useRouter: vi.fn(() => ({
|
||||||
|
push,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('CommunityNodeInfo - links & bugs URL', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
setActivePinia(createTestingPinia());
|
||||||
|
const fetchMock = vi.fn().mockResolvedValue({
|
||||||
|
ok: true,
|
||||||
|
json: async () => ({
|
||||||
|
bugs: {
|
||||||
|
url: 'https://github.com/n8n-io/n8n/issues',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
vi.stubGlobal('fetch', fetchMock);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.resetAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls router.push to open settings page when "Manage" is clicked', async () => {
|
||||||
|
const { getByText } = createComponentRenderer(CommunityNodeFooter)({
|
||||||
|
props: { packageName: 'n8n-nodes-test', showManage: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
const manageLink = getByText('Manage');
|
||||||
|
await fireEvent.click(manageLink);
|
||||||
|
|
||||||
|
expect(push).toHaveBeenCalledWith({ name: VIEWS.COMMUNITY_NODES });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Manage should not be in the footer', async () => {
|
||||||
|
const { queryByText } = createComponentRenderer(CommunityNodeFooter)({
|
||||||
|
props: { packageName: 'n8n-nodes-test', showManage: false },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(queryByText('Manage')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -8,6 +8,7 @@ import { N8nText, N8nLink } from '@n8n/design-system';
|
|||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
packageName: string;
|
packageName: string;
|
||||||
|
showManage: boolean;
|
||||||
}
|
}
|
||||||
const props = defineProps<Props>();
|
const props = defineProps<Props>();
|
||||||
|
|
||||||
@@ -54,10 +55,12 @@ onMounted(async () => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :class="$style.container">
|
<div :class="$style.container">
|
||||||
<N8nLink theme="text" @click="openSettingsPage">
|
<template v-if="props.showManage">
|
||||||
<N8nText size="small" color="primary" bold> Manage </N8nText>
|
<N8nLink theme="text" @click="openSettingsPage">
|
||||||
</N8nLink>
|
<N8nText size="small" color="primary" bold> Manage </N8nText>
|
||||||
<N8nText size="small" color="primary" bold>|</N8nText>
|
</N8nLink>
|
||||||
|
<N8nText size="small" color="primary" bold>|</N8nText>
|
||||||
|
</template>
|
||||||
<N8nLink theme="text" @click="openIssuesPage">
|
<N8nLink theme="text" @click="openIssuesPage">
|
||||||
<N8nText size="small" color="primary" bold> Report issue </N8nText>
|
<N8nText size="small" color="primary" bold> Report issue </N8nText>
|
||||||
</N8nLink>
|
</N8nLink>
|
||||||
|
|||||||
@@ -142,14 +142,14 @@ onMounted(async () => {
|
|||||||
</div>
|
</div>
|
||||||
<div v-if="!isOwner && !communityNodeDetails?.installed" :class="$style.contactOwnerHint">
|
<div v-if="!isOwner && !communityNodeDetails?.installed" :class="$style.contactOwnerHint">
|
||||||
<N8nIcon color="text-light" icon="info-circle" size="large" />
|
<N8nIcon color="text-light" icon="info-circle" size="large" />
|
||||||
<nN8nText color="text-base" size="medium">
|
<N8nText color="text-base" size="medium">
|
||||||
<div style="padding-bottom: 8px">
|
<div style="padding-bottom: 8px">
|
||||||
{{ i18n.baseText('communityNodeInfo.contact.admin') }}
|
{{ i18n.baseText('communityNodeInfo.contact.admin') }}
|
||||||
</div>
|
</div>
|
||||||
<N8nText bold v-if="ownerEmailList.length">
|
<N8nText bold v-if="ownerEmailList.length">
|
||||||
{{ ownerEmailList.join(', ') }}
|
{{ ownerEmailList.join(', ') }}
|
||||||
</N8nText>
|
</N8nText>
|
||||||
</nN8nText>
|
</N8nText>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import CommunityNodeDetails from './CommunityNodeDetails.vue';
|
|||||||
import CommunityNodeInfo from './CommunityNodeInfo.vue';
|
import CommunityNodeInfo from './CommunityNodeInfo.vue';
|
||||||
import CommunityNodeDocsLink from './CommunityNodeDocsLink.vue';
|
import CommunityNodeDocsLink from './CommunityNodeDocsLink.vue';
|
||||||
import CommunityNodeFooter from './CommunityNodeFooter.vue';
|
import CommunityNodeFooter from './CommunityNodeFooter.vue';
|
||||||
|
import { useUsersStore } from '@/stores/users.store';
|
||||||
|
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const { callDebounced } = useDebounce();
|
const { callDebounced } = useDebounce();
|
||||||
@@ -34,6 +35,8 @@ const { pushViewStack, popViewStack, updateCurrentViewStack } = useViewStacks();
|
|||||||
const { setActiveItemIndex, attachKeydownEvent, detachKeydownEvent } = useKeyboardNavigation();
|
const { setActiveItemIndex, attachKeydownEvent, detachKeydownEvent } = useKeyboardNavigation();
|
||||||
const nodeCreatorStore = useNodeCreatorStore();
|
const nodeCreatorStore = useNodeCreatorStore();
|
||||||
|
|
||||||
|
const { isInstanceOwner } = useUsersStore();
|
||||||
|
|
||||||
const activeViewStack = computed(() => useViewStacks().activeViewStack);
|
const activeViewStack = computed(() => useViewStacks().activeViewStack);
|
||||||
|
|
||||||
const communityNodeDetails = computed(() => activeViewStack.value.communityNodeDetails);
|
const communityNodeDetails = computed(() => activeViewStack.value.communityNodeDetails);
|
||||||
@@ -233,6 +236,7 @@ function onBackButton() {
|
|||||||
<CommunityNodeFooter
|
<CommunityNodeFooter
|
||||||
v-if="communityNodeDetails && !isCommunityNodeActionsMode"
|
v-if="communityNodeDetails && !isCommunityNodeActionsMode"
|
||||||
:package-name="communityNodeDetails.packageName"
|
:package-name="communityNodeDetails.packageName"
|
||||||
|
:show-manage="communityNodeDetails.installed && isInstanceOwner"
|
||||||
/>
|
/>
|
||||||
</aside>
|
</aside>
|
||||||
</transition>
|
</transition>
|
||||||
|
|||||||
@@ -1831,7 +1831,9 @@
|
|||||||
"settings": "Settings",
|
"settings": "Settings",
|
||||||
"settings.communityNodes": "Community nodes",
|
"settings.communityNodes": "Community nodes",
|
||||||
"settings.communityNodes.empty.title": "Supercharge your workflows with community nodes",
|
"settings.communityNodes.empty.title": "Supercharge your workflows with community nodes",
|
||||||
|
"settings.communityNodes.empty.verified.only.title": "Supercharge your workflows with verified community nodes",
|
||||||
"settings.communityNodes.empty.description": "Install over {count} node packages contributed by our community.",
|
"settings.communityNodes.empty.description": "Install over {count} node packages contributed by our community.",
|
||||||
|
"settings.communityNodes.empty.verified.only.description": "You can install community and partner built node packages that have been verified by n8n directly from the nodes panel. Installed packages will show up here.",
|
||||||
"settings.communityNodes.empty.description.no-packages": "Install node packages contributed by our community.",
|
"settings.communityNodes.empty.description.no-packages": "Install node packages contributed by our community.",
|
||||||
"settings.communityNodes.empty.installPackageLabel": "Install a community node",
|
"settings.communityNodes.empty.installPackageLabel": "Install a community node",
|
||||||
"settings.communityNodes.npmUnavailable.warning": "To use this feature, please <a href=\"{npmUrl}\" target=\"_blank\" title=\"How to install npm\">install npm</a> and restart n8n.",
|
"settings.communityNodes.npmUnavailable.warning": "To use this feature, please <a href=\"{npmUrl}\" target=\"_blank\" title=\"How to install npm\">install npm</a> and restart n8n.",
|
||||||
|
|||||||
@@ -36,7 +36,19 @@ const communityNodesStore = useCommunityNodesStore();
|
|||||||
const uiStore = useUIStore();
|
const uiStore = useUIStore();
|
||||||
const settingsStore = useSettingsStore();
|
const settingsStore = useSettingsStore();
|
||||||
|
|
||||||
|
const getEmptyStateTitle = computed(() => {
|
||||||
|
if (!settingsStore.isUnverifiedPackagesEnabled) {
|
||||||
|
return i18n.baseText('settings.communityNodes.empty.verified.only.title');
|
||||||
|
}
|
||||||
|
|
||||||
|
return i18n.baseText('settings.communityNodes.empty.title');
|
||||||
|
});
|
||||||
|
|
||||||
const getEmptyStateDescription = computed(() => {
|
const getEmptyStateDescription = computed(() => {
|
||||||
|
if (!settingsStore.isUnverifiedPackagesEnabled) {
|
||||||
|
return i18n.baseText('settings.communityNodes.empty.verified.only.description');
|
||||||
|
}
|
||||||
|
|
||||||
const packageCount = communityNodesStore.availablePackageCount;
|
const packageCount = communityNodesStore.availablePackageCount;
|
||||||
|
|
||||||
return packageCount < PACKAGE_COUNT_THRESHOLD
|
return packageCount < PACKAGE_COUNT_THRESHOLD
|
||||||
@@ -53,9 +65,10 @@ const getEmptyStateDescription = computed(() => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const getEmptyStateButtonText = computed(() =>
|
const getEmptyStateButtonText = computed(() => {
|
||||||
i18n.baseText('settings.communityNodes.empty.installPackageLabel'),
|
if (!settingsStore.isUnverifiedPackagesEnabled) return '';
|
||||||
);
|
return i18n.baseText('settings.communityNodes.empty.installPackageLabel');
|
||||||
|
});
|
||||||
|
|
||||||
const actionBoxConfig = computed(() => {
|
const actionBoxConfig = computed(() => {
|
||||||
return {
|
return {
|
||||||
@@ -163,9 +176,10 @@ onBeforeUnmount(() => {
|
|||||||
:class="$style.actionBoxContainer"
|
:class="$style.actionBoxContainer"
|
||||||
>
|
>
|
||||||
<n8n-action-box
|
<n8n-action-box
|
||||||
:heading="i18n.baseText('settings.communityNodes.empty.title')"
|
:heading="getEmptyStateTitle"
|
||||||
:description="getEmptyStateDescription"
|
:description="getEmptyStateDescription"
|
||||||
:button-text="getEmptyStateButtonText"
|
:button-text="getEmptyStateButtonText"
|
||||||
|
:button-disabled="!settingsStore.isUnverifiedPackagesEnabled"
|
||||||
:callout-text="actionBoxConfig.calloutText"
|
:callout-text="actionBoxConfig.calloutText"
|
||||||
:callout-theme="actionBoxConfig.calloutTheme"
|
:callout-theme="actionBoxConfig.calloutTheme"
|
||||||
@click:button="onClickEmptyStateButton"
|
@click:button="onClickEmptyStateButton"
|
||||||
|
|||||||
Reference in New Issue
Block a user