feat(editor): Allow users to update verified nodes from the node settings panel (#16447)

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
Co-authored-by: Michael Kret <michael.k@radency.com>
This commit is contained in:
Dana
2025-06-30 15:43:26 +02:00
committed by GitHub
parent 585295c89f
commit 6edd47dd65
13 changed files with 374 additions and 37 deletions

View File

@@ -6,8 +6,11 @@ import { useCommunityNodesStore } from '@/stores/communityNodes.store';
import { createEventBus } from '@n8n/utils/event-bus';
import { useI18n } from '@n8n/i18n';
import { useTelemetry } from '@/composables/useTelemetry';
import { computed, ref } from 'vue';
import { computed, onMounted, ref } from 'vue';
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
import type { CommunityNodeType } from '@n8n/api-types';
import { useSettingsStore } from '@/stores/settings.store';
import semver from 'semver';
export type CommunityPackageManageMode = 'uninstall' | 'update' | 'view-documentation';
@@ -20,6 +23,8 @@ interface Props {
const props = defineProps<Props>();
const communityNodesStore = useCommunityNodesStore();
const nodeTypesStore = useNodeTypesStore();
const settingsStore = useSettingsStore();
const modalBus = createEventBus();
@@ -29,9 +34,24 @@ const telemetry = useTelemetry();
const loading = ref(false);
const activePackage = computed(
const isUsingVerifiedAndUnverifiedPackages =
settingsStore.isCommunityNodesFeatureEnabled && settingsStore.isUnverifiedPackagesEnabled;
const isUsingVerifiedPackagesOnly =
settingsStore.isCommunityNodesFeatureEnabled && !settingsStore.isUnverifiedPackagesEnabled;
const communityStorePackage = computed(
() => communityNodesStore.installedPackages[props.activePackageName],
);
const updateVersion = computed(() => {
return settingsStore.isUnverifiedPackagesEnabled
? communityStorePackage.value.updateAvailable
: nodeTypeStorePackage.value?.npmVersion;
});
const nodeTypeStorePackage = ref<CommunityNodeType>();
const isLatestPackageVerified = ref<boolean>(true);
const packageVersion = ref<string>(communityStorePackage.value.updateAvailable ?? '');
const getModalContent = computed(() => {
if (props.mode === COMMUNITY_PACKAGE_MANAGE_ACTIONS.UNINSTALL) {
@@ -55,10 +75,11 @@ const getModalContent = computed(() => {
},
}),
description: i18n.baseText('settings.communityNodes.confirmModal.update.description'),
warning: i18n.baseText('settings.communityNodes.confirmModal.update.warning'),
message: i18n.baseText('settings.communityNodes.confirmModal.update.message', {
interpolate: {
packageName: props.activePackageName,
version: activePackage.value.updateAvailable ?? '',
version: packageVersion.value,
},
}),
buttonLabel: i18n.baseText('settings.communityNodes.confirmModal.update.buttonLabel'),
@@ -83,11 +104,11 @@ const onConfirmButtonClick = async () => {
const onUninstall = async () => {
try {
telemetry.track('user started cnr package deletion', {
package_name: activePackage.value.packageName,
package_node_names: activePackage.value.installedNodes.map((node) => node.name),
package_version: activePackage.value.installedVersion,
package_author: activePackage.value.authorName,
package_author_email: activePackage.value.authorEmail,
package_name: communityStorePackage.value.packageName,
package_node_names: communityStorePackage.value.installedNodes.map((node) => node.name),
package_version: communityStorePackage.value.installedVersion,
package_author: communityStorePackage.value.authorName,
package_author_email: communityStorePackage.value.authorEmail,
});
loading.value = true;
await communityNodesStore.uninstallPackage(props.activePackageName);
@@ -107,23 +128,34 @@ const onUninstall = async () => {
const onUpdate = async () => {
try {
telemetry.track('user started cnr package update', {
package_name: activePackage.value.packageName,
package_node_names: activePackage.value.installedNodes.map((node) => node.name),
package_version_current: activePackage.value.installedVersion,
package_version_new: activePackage.value.updateAvailable,
package_author: activePackage.value.authorName,
package_author_email: activePackage.value.authorEmail,
package_name: communityStorePackage.value.packageName,
package_node_names: communityStorePackage.value.installedNodes.map((node) => node.name),
package_version_current: communityStorePackage.value.installedVersion,
package_version_new: communityStorePackage.value.updateAvailable,
package_author: communityStorePackage.value.authorName,
package_author_email: communityStorePackage.value.authorEmail,
});
loading.value = true;
const updatedVersion = activePackage.value.updateAvailable;
await communityNodesStore.updatePackage(props.activePackageName);
if (settingsStore.isUnverifiedPackagesEnabled) {
await communityNodesStore.updatePackage(props.activePackageName);
} else if (settingsStore.isCommunityNodesFeatureEnabled) {
await communityNodesStore.updatePackage(
props.activePackageName,
updateVersion.value,
nodeTypeStorePackage.value?.checksum,
);
} else {
throw new Error('Community nodes feature is not correctly enabled.');
}
await useNodeTypesStore().getNodeTypes();
toast.showMessage({
title: i18n.baseText('settings.communityNodes.messages.update.success.title'),
message: i18n.baseText('settings.communityNodes.messages.update.success.message', {
interpolate: {
packageName: props.activePackageName,
version: updatedVersion ?? '',
version: updateVersion.value ?? '',
},
}),
type: 'success',
@@ -135,6 +167,47 @@ const onUpdate = async () => {
modalBus.emit('close');
}
};
async function fetchPackageInfo(packageName: string) {
await nodeTypesStore.loadNodeTypesIfNotLoaded();
const nodeType = nodeTypesStore.visibleNodeTypes.find((nodeType) =>
nodeType.name.includes(packageName),
);
if (nodeType) {
const communityNodeAttributes = await nodeTypesStore.getCommunityNodeAttributes(nodeType?.name);
nodeTypeStorePackage.value = communityNodeAttributes ?? undefined;
}
}
function setIsVerifiedLatestPackage() {
if (
isUsingVerifiedAndUnverifiedPackages &&
nodeTypeStorePackage.value?.npmVersion &&
communityStorePackage.value.updateAvailable
) {
isLatestPackageVerified.value = semver.eq(
nodeTypeStorePackage.value.npmVersion,
communityStorePackage.value.updateAvailable,
);
}
}
function setPackageVersion() {
if (isUsingVerifiedPackagesOnly) {
packageVersion.value = nodeTypeStorePackage.value?.npmVersion ?? packageVersion.value;
}
}
onMounted(async () => {
if (props.activePackageName) {
await fetchPackageInfo(props.activePackageName);
}
setIsVerifiedLatestPackage();
setPackageVersion();
});
</script>
<template>
@@ -156,6 +229,11 @@ const onUpdate = async () => {
<n8n-info-tip theme="info" type="note" :bold="false">
<span v-text="getModalContent.description"></span>
</n8n-info-tip>
<n8n-notice
data-test-id="communityPackageManageConfirmModal-warning"
v-if="!isLatestPackageVerified"
:content="getModalContent.warning"
/>
</div>
</template>
<template #footer>
@@ -175,6 +253,7 @@ const onUpdate = async () => {
.descriptionContainer {
display: flex;
margin: var(--spacing-s) 0;
flex-direction: column;
}
.descriptionIcon {