Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable community nodes based on npm availability #3871

Merged
merged 4 commits into from
Aug 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/cli/src/Interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,7 @@ export interface IN8nUISettings {
missingPackages?: boolean;
executionMode: 'regular' | 'queue';
communityNodesEnabled: boolean;
isNpmAvailable: boolean;
}

export interface IPersonalizationSurveyAnswers {
Expand Down
9 changes: 9 additions & 0 deletions packages/cli/src/Server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import express from 'express';
import { readFileSync, promises } from 'fs';
import { readFile } from 'fs/promises';
import { exec as callbackExec } from 'child_process';
import _, { cloneDeep } from 'lodash';
import { dirname as pathDirname, join as pathJoin, resolve as pathResolve } from 'path';
import {
Expand Down Expand Up @@ -86,6 +87,7 @@ import jwks from 'jwks-rsa';
import timezones from 'google-timezones-json';
import parseUrl from 'parseurl';
import promClient, { Registry } from 'prom-client';
import { promisify } from 'util';
import * as Queue from './Queue';
import {
ActiveExecutions,
Expand Down Expand Up @@ -167,6 +169,8 @@ import { loadPublicApiVersions } from './PublicApi';

require('body-parser-xml')(bodyParser);

const exec = promisify(callbackExec);

export const externalHooks: IExternalHooksClass = ExternalHooks();

class App {
Expand Down Expand Up @@ -330,6 +334,7 @@ class App {
onboardingCallPromptEnabled: config.getEnv('onboardingCallPrompt.enabled'),
executionMode: config.getEnv('executions.mode'),
communityNodesEnabled: config.getEnv('nodes.communityPackages.enabled'),
isNpmAvailable: false,
};
}

Expand Down Expand Up @@ -374,6 +379,10 @@ class App {
promClient.collectDefaultMetrics({ register });
}

this.frontendSettings.isNpmAvailable = await exec('npm --version')
.then(() => true)
.catch(() => false);

this.versions = await GenericHelpers.getVersions();
this.frontendSettings.versionCli = this.versions.cli;

Expand Down
2 changes: 2 additions & 0 deletions packages/editor-ui/src/Interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -719,6 +719,7 @@ export interface IN8nUISettings {
};
executionMode: string;
communityNodesEnabled: boolean;
isNpmAvailable: boolean;
publicApi: {
enabled: boolean;
latestVersion: number;
Expand Down Expand Up @@ -881,6 +882,7 @@ export interface IRootState {
sidebarMenuItems: IMenuItem[];
instanceId: string;
nodeMetadata: {[nodeName: string]: INodeMetadata};
isNpmAvailable: boolean;
}

export interface ICommunityPackageMap {
Expand Down
1 change: 1 addition & 0 deletions packages/editor-ui/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export const NPM_PACKAGE_DOCS_BASE_URL = `https://www.npmjs.com/package/`;
export const NPM_KEYWORD_SEARCH_URL = `https://www.npmjs.com/search?q=keywords%3An8n-community-node-package`;
export const N8N_QUEUE_MODE_DOCS_URL = `https://docs.n8n.io/hosting/scaling/queue-mode/`;
export const COMMUNITY_NODES_INSTALLATION_DOCS_URL = `https://docs.n8n.io/integrations/community-nodes/installation/`;
export const COMMUNITY_NODES_NPM_INSTALLATION_URL = 'https://docs.npmjs.com/downloading-and-installing-node-js-and-npm';
export const COMMUNITY_NODES_RISKS_DOCS_URL = `https://docs.n8n.io/integrations/community-nodes/risks/`;
export const COMMUNITY_NODES_BLOCKLIST_DOCS_URL = `https://docs.n8n.io/integrations/community-nodes/blocklist/`;
export const CUSTOM_NODES_DOCS_URL = `https://docs.n8n.io/integrations/creating-nodes/code/create-n8n-nodes-module/`;
Expand Down
4 changes: 4 additions & 0 deletions packages/editor-ui/src/modules/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ const module: Module<ISettingsState, IRootState> = {
isCommunityNodesFeatureEnabled: (state): boolean => {
return state.settings.communityNodesEnabled;
},
isNpmAvailable: (state): boolean => {
return state.settings.isNpmAvailable;
},
isQueueModeEnabled: (state): boolean => {
return state.settings.executionMode === 'queue';
},
Expand Down Expand Up @@ -138,6 +141,7 @@ const module: Module<ISettingsState, IRootState> = {
context.commit('setOauthCallbackUrls', settings.oauthCallbackUrls, {root: true});
context.commit('setN8nMetadata', settings.n8nMetadata || {}, {root: true});
context.commit('setDefaultLocale', settings.defaultLocale, {root: true});
context.commit('setIsNpmAvailable', settings.isNpmAvailable, {root: true});
context.commit('versions/setVersionNotificationSettings', settings.versionNotifications, {root: true});
context.commit('setCommunityNodesFeatureEnabled', settings.communityNodesEnabled === true);
},
Expand Down
1 change: 1 addition & 0 deletions packages/editor-ui/src/plugins/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -725,6 +725,7 @@
"settings.communityNodes.empty.description.no-packages": "Install node packages contributed by our community. <br /><a href=\"{docURL}\" target=\"_blank\" title=\"Read the n8n docs\">More info</a>",
"settings.communityNodes.empty.installPackageLabel": "Install a community node",
"settings.communityNodes.queueMode.warning": "You need to install community nodes manually because your instance is running in queue mode. <a href=\"{docURL}\" target=\"_blank\" title=\"Read the n8n docs\">More info</a>",
"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.packageNodes.label": "{count} node | {count} nodes",
"settings.communityNodes.updateAvailable.tooltip": "A newer version is available",
"settings.communityNodes.viewDocsAction.label": "Documentation",
Expand Down
4 changes: 4 additions & 0 deletions packages/editor-ui/src/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ const state: IRootState = {
selectedNodes: [],
sessionId: Math.random().toString(36).substring(2, 15),
urlBaseWebhook: 'http://localhost:5678/',
isNpmAvailable: false,
workflow: {
id: PLACEHOLDER_EMPTY_WORKFLOW_ID,
name: '',
Expand Down Expand Up @@ -600,6 +601,9 @@ export const store = new Vuex.Store({
setDefaultLocale(state, locale: string) {
Vue.set(state, 'defaultLocale', locale);
},
setIsNpmAvailable(state, isNpmAvailable: boolean) {
Vue.set(state, 'isNpmAvailable', isNpmAvailable);
},
setActiveNode(state, nodeName: string) {
state.activeNode = nodeName;
},
Expand Down
44 changes: 34 additions & 10 deletions packages/editor-ui/src/views/SettingsCommunityNodesView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,11 @@
<n8n-action-box
:heading="$locale.baseText('settings.communityNodes.empty.title')"
:description="getEmptyStateDescription"
:buttonText="$locale.baseText('settings.communityNodes.empty.installPackageLabel')"
:buttonText="
isNpmAvailable
? $locale.baseText('settings.communityNodes.empty.installPackageLabel')
: ''
"
:calloutText="actionBoxConfig.calloutText"
:calloutTheme="actionBoxConfig.calloutTheme"
@click="openInstallModal"
Expand All @@ -63,7 +67,11 @@ import SettingsView from './SettingsView.vue';
import CommunityPackageCard from '../components/CommunityPackageCard.vue';
import { showMessage } from '@/components/mixins/showMessage';
import mixins from 'vue-typed-mixins';
import { COMMUNITY_PACKAGE_INSTALL_MODAL_KEY, COMMUNITY_NODES_INSTALLATION_DOCS_URL } from '../constants';
import {
COMMUNITY_PACKAGE_INSTALL_MODAL_KEY,
COMMUNITY_NODES_INSTALLATION_DOCS_URL,
COMMUNITY_NODES_NPM_INSTALLATION_URL,
} from '../constants';
import { PublicInstalledPackage } from 'n8n-workflow';

const PACKAGE_COUNT_THRESHOLD = 31;
Expand Down Expand Up @@ -123,7 +131,7 @@ export default mixins(
}
},
computed: {
...mapGetters('settings', ['isQueueModeEnabled']),
...mapGetters('settings', ['isNpmAvailable', 'isQueueModeEnabled']),
...mapGetters('communityNodes', ['getInstalledPackages']),
getEmptyStateDescription() {
const packageCount = this.$store.getters['communityNodes/availablePackageCount'];
Expand All @@ -141,13 +149,29 @@ export default mixins(
});
},
actionBoxConfig() {
return this.isQueueModeEnabled ? {
calloutText: this.$locale.baseText('settings.communityNodes.queueMode.warning', {
interpolate: { docURL: COMMUNITY_NODES_INSTALLATION_DOCS_URL },
}),
calloutTheme: 'warning',
hideButton: true,
} : {
if (!this.isNpmAvailable) {
return {
calloutText: this.$locale.baseText(
'settings.communityNodes.npmUnavailable.warning',
{ interpolate: { npmUrl: COMMUNITY_NODES_NPM_INSTALLATION_URL } },
),
calloutTheme: 'warning',
hideButton: true,
};
}

if (this.isQueueModeEnabled) {
return {
calloutText: this.$locale.baseText(
'settings.communityNodes.queueMode.warning',
{ interpolate: { docURL: COMMUNITY_NODES_INSTALLATION_DOCS_URL } },
),
calloutTheme: 'warning',
hideButton: true,
};
}

return {
calloutText: '',
calloutTheme: '',
hideButton: false,
Expand Down