From e16fbaa4a4ee975530c9ab04edfb70026249c87b Mon Sep 17 00:00:00 2001
From: Shireen Missi <94372015+ShireenMissi@users.noreply.github.com>
Date: Wed, 28 May 2025 16:52:39 +0100
Subject: [PATCH 1/3] fix(Microsoft SharePoint Node): Add back the support for
cred only node (#15806)
---
...icrosoftSharePointOAuth2Api.credentials.ts | 13 +++-
.../credentials/icons/microsoftSharePoint.svg | 59 +++++++++++++++++++
2 files changed, 71 insertions(+), 1 deletion(-)
create mode 100644 packages/nodes-base/credentials/icons/microsoftSharePoint.svg
diff --git a/packages/nodes-base/credentials/MicrosoftSharePointOAuth2Api.credentials.ts b/packages/nodes-base/credentials/MicrosoftSharePointOAuth2Api.credentials.ts
index 76b1d3fedc2ff..09abbe2ecda17 100644
--- a/packages/nodes-base/credentials/MicrosoftSharePointOAuth2Api.credentials.ts
+++ b/packages/nodes-base/credentials/MicrosoftSharePointOAuth2Api.credentials.ts
@@ -1,14 +1,25 @@
-import type { ICredentialType, INodeProperties } from 'n8n-workflow';
+import type { Icon, ICredentialType, INodeProperties } from 'n8n-workflow';
export class MicrosoftSharePointOAuth2Api implements ICredentialType {
name = 'microsoftSharePointOAuth2Api';
extends = ['microsoftOAuth2Api'];
+ icon: Icon = {
+ light: 'file:icons/microsoftSharePoint.svg',
+ dark: 'file:icons/microsoftSharePoint.svg',
+ };
+
displayName = 'Microsoft SharePoint OAuth2 API';
documentationUrl = 'microsoft';
+ httpRequestNode = {
+ name: 'Microsoft SharePoint',
+ docsUrl: 'https://learn.microsoft.com/en-us/sharepoint/dev/apis/sharepoint-rest-graph',
+ apiBaseUrlPlaceholder: 'https://{subdomain}.sharepoint.com/_api/v2.0/',
+ };
+
properties: INodeProperties[] = [
{
displayName: 'Scope',
diff --git a/packages/nodes-base/credentials/icons/microsoftSharePoint.svg b/packages/nodes-base/credentials/icons/microsoftSharePoint.svg
new file mode 100644
index 0000000000000..8baca491054bb
--- /dev/null
+++ b/packages/nodes-base/credentials/icons/microsoftSharePoint.svg
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
+]>
+
From 8b76694769302a1c388707ac80c3f03d90c4f749 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ra=C3=BAl=20G=C3=B3mez=20Morales?=
Date: Tue, 27 May 2025 14:30:18 +0200
Subject: [PATCH 2/3] fix(editor): Handle Insights calculations to prevent
Infinity numbers (#15727)
---
.../features/insights/insights.utils.test.ts | 4 ++--
.../src/features/insights/insights.utils.ts | 17 ++++++++++++-----
2 files changed, 14 insertions(+), 7 deletions(-)
diff --git a/packages/frontend/editor-ui/src/features/insights/insights.utils.test.ts b/packages/frontend/editor-ui/src/features/insights/insights.utils.test.ts
index 4468eef47ec29..e3fe15b2de1d7 100644
--- a/packages/frontend/editor-ui/src/features/insights/insights.utils.test.ts
+++ b/packages/frontend/editor-ui/src/features/insights/insights.utils.test.ts
@@ -81,8 +81,8 @@ describe('Insights Transformers', () => {
});
it('should return Infinity if value equals deviation (and thus previous value is 0)', () => {
- expect(transformInsightsDeviation.total(10, 10)).toBe(Infinity);
- expect(transformInsightsDeviation.failed(5, 5)).toBe(Infinity);
+ expect(transformInsightsDeviation.total(10, 10)).toBe(null);
+ expect(transformInsightsDeviation.failed(5, 5)).toBe(null);
});
it('should return 0 if deviation is 0 and value is not 0', () => {
diff --git a/packages/frontend/editor-ui/src/features/insights/insights.utils.ts b/packages/frontend/editor-ui/src/features/insights/insights.utils.ts
index bb493e572bc49..88986285b90df 100644
--- a/packages/frontend/editor-ui/src/features/insights/insights.utils.ts
+++ b/packages/frontend/editor-ui/src/features/insights/insights.utils.ts
@@ -21,14 +21,21 @@ export const transformInsightsValues: Record value - deviation;
+const getDeviation = (value: number, deviation: number): number | null => {
+ if (value === 0 && deviation === 0) return 0;
+
+ const previousValue = getPreviousValue(value, deviation);
+ if (previousValue === 0) return null; // avoid division by zero
+
+ return (deviation / previousValue) * 100;
+};
+
export const transformInsightsDeviation: Record<
InsightsSummaryType,
- (value: number, deviation: number) => number
+ (value: number, deviation: number) => number | null
> = {
- total: (value: number, deviation: number) =>
- value === 0 && deviation === 0 ? 0 : (deviation / getPreviousValue(value, deviation)) * 100,
- failed: (value: number, deviation: number) =>
- value === 0 && deviation === 0 ? 0 : (deviation / getPreviousValue(value, deviation)) * 100,
+ total: getDeviation,
+ failed: getDeviation,
timeSaved: (_: number, deviation: number) => transformInsightsTimeSaved(deviation),
averageRunTime: (_: number, deviation: number) => transformInsightsAverageRunTime(deviation),
failureRate: (_: number, deviation: number) => transformInsightsFailureRate(deviation),
From ea890bc20445596d518255f72e7c40d62c87ebbb Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Thu, 29 May 2025 17:10:44 +0100
Subject: [PATCH 3/3] :rocket: Release 1.95.2 (#15823)
Co-authored-by: ShireenMissi <94372015+ShireenMissi@users.noreply.github.com>
Co-authored-by: shortstacked
Co-authored-by: Eugene Molodkin
---
CHANGELOG.md | 10 +++
cypress/composables/ndv.ts | 16 +++++
cypress/composables/workflow.ts | 23 ++++++-
.../e2e/233-AI-switch-to-logs-on-error.cy.ts | 2 +-
cypress/e2e/30-langchain.cy.ts | 63 ++++++++++---------
cypress/e2e/4-node-creator.cy.ts | 2 +-
cypress/e2e/48-subworkflow-inputs.cy.ts | 2 +-
package.json | 2 +-
packages/@n8n/nodes-langchain/package.json | 2 +-
packages/cli/package.json | 2 +-
packages/frontend/editor-ui/package.json | 2 +-
packages/nodes-base/package.json | 2 +-
12 files changed, 88 insertions(+), 40 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2fc722a4bb11f..47c4c103def07 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,13 @@
+## [1.95.2](https://github.com/n8n-io/n8n/compare/n8n@1.95.1...n8n@1.95.2) (2025-05-29)
+
+
+### Bug Fixes
+
+* **editor:** Handle Insights calculations to prevent Infinity numbers ([#15727](https://github.com/n8n-io/n8n/issues/15727)) ([8b76694](https://github.com/n8n-io/n8n/commit/8b76694769302a1c388707ac80c3f03d90c4f749))
+* **Microsoft SharePoint Node:** Add back the support for cred only node ([#15806](https://github.com/n8n-io/n8n/issues/15806)) ([e16fbaa](https://github.com/n8n-io/n8n/commit/e16fbaa4a4ee975530c9ab04edfb70026249c87b))
+
+
+
## [1.95.1](https://github.com/n8n-io/n8n/compare/n8n@1.95.0...n8n@1.95.1) (2025-05-27)
diff --git a/cypress/composables/ndv.ts b/cypress/composables/ndv.ts
index 7fbf07e3a5713..22eb423b9ae6e 100644
--- a/cypress/composables/ndv.ts
+++ b/cypress/composables/ndv.ts
@@ -36,6 +36,10 @@ export function getInputSelect() {
return cy.getByTestId('ndv-input-select').find('input');
}
+export function getInputLinkRun() {
+ return getInputPanel().findChildByTestId('link-run');
+}
+
export function getMainPanel() {
return cy.getByTestId('node-parameters');
}
@@ -68,6 +72,14 @@ export function getInputTbodyCell(row: number, col: number) {
return getInputTableRows().eq(row).find('td').eq(col);
}
+export function getInputRunSelector() {
+ return getInputPanel().findChildByTestId('run-selector');
+}
+
+export function getInputPanelItemsCount() {
+ return getInputPanel().getByTestId('ndv-items-count');
+}
+
export function getOutputPanelDataContainer() {
return getOutputPanel().findChildByTestId('ndv-data-container');
}
@@ -329,3 +341,7 @@ export function resetHoverState() {
export function setInputDisplayMode(mode: 'Schema' | 'Table' | 'JSON' | 'Binary') {
getInputPanel().findChildByTestId('ndv-run-data-display-mode').contains(mode).click();
}
+
+export function toggleInputRunLinking() {
+ getInputLinkRun().click();
+}
diff --git a/cypress/composables/workflow.ts b/cypress/composables/workflow.ts
index a641379f9bebb..ff00150e88d3b 100644
--- a/cypress/composables/workflow.ts
+++ b/cypress/composables/workflow.ts
@@ -221,35 +221,52 @@ export function executeWorkflowAndWait(waitForSuccessBannerToDisappear = true) {
}
}
+/**
+ * @param nodeDisplayName - The name of the node to add to the canvas
+ * @param plusButtonClick - Whether to click the plus button to open the node creator
+ * @param preventNdvClose - Whether to prevent the Ndv from closing
+ * @param action - The action to select in the node creator
+ * @param useExactMatch - Whether to use an exact match for the node name will use selector instead of enter key
+ */
export function addNodeToCanvas(
nodeDisplayName: string,
plusButtonClick = true,
preventNdvClose?: boolean,
action?: string,
+ useExactMatch = false,
) {
if (plusButtonClick) {
getNodeCreatorPlusButton().click();
}
getNodeCreatorSearchBar().type(nodeDisplayName);
- getNodeCreatorSearchBar().type('{enter}');
+
+ if (useExactMatch) {
+ cy.getByTestId('node-creator-item-name').contains(nodeDisplayName).click();
+ } else {
+ getNodeCreatorSearchBar().type('{enter}');
+ }
+
cy.wait(500);
+
cy.get('body').then((body) => {
if (body.find('[data-test-id=node-creator]').length > 0) {
if (action) {
cy.contains(action).click();
} else {
- // Select the first action
cy.get('[data-keyboard-nav-type="action"]').eq(0).click();
}
}
});
- if (!preventNdvClose) cy.get('body').type('{esc}');
+ if (!preventNdvClose) {
+ cy.get('body').type('{esc}');
+ }
}
export function navigateToNewWorkflowPage(preventNodeViewUnload = true) {
cy.visit(ROUTES.NEW_WORKFLOW_PAGE);
+ cy.getByTestId('node-creator-plus-button').should('be.visible');
cy.waitForLoad();
cy.window().then((win) => {
win.preventNodeViewBeforeUnload = preventNodeViewUnload;
diff --git a/cypress/e2e/233-AI-switch-to-logs-on-error.cy.ts b/cypress/e2e/233-AI-switch-to-logs-on-error.cy.ts
index e637b4ecd8c55..508f606cb6be9 100644
--- a/cypress/e2e/233-AI-switch-to-logs-on-error.cy.ts
+++ b/cypress/e2e/233-AI-switch-to-logs-on-error.cy.ts
@@ -139,7 +139,7 @@ function createRunDataWithError(inputMessage: string) {
function setupTestWorkflow(chatTrigger: boolean = false) {
// Setup test workflow with AI Agent, Postgres Memory Node (source of error), Calculator Tool, and OpenAI Chat Model
if (chatTrigger) {
- addNodeToCanvas(MANUAL_CHAT_TRIGGER_NODE_NAME, true);
+ addNodeToCanvas(MANUAL_CHAT_TRIGGER_NODE_NAME, true, false, undefined, true);
} else {
addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME, true);
}
diff --git a/cypress/e2e/30-langchain.cy.ts b/cypress/e2e/30-langchain.cy.ts
index ccbe4f141ccfa..9b6daf2a19ec6 100644
--- a/cypress/e2e/30-langchain.cy.ts
+++ b/cypress/e2e/30-langchain.cy.ts
@@ -21,6 +21,7 @@ import {
sendManualChatMessage,
} from '../composables/modals/chat-modal';
import { setCredentialValues } from '../composables/modals/credential-modal';
+import * as ndv from '../composables/ndv';
import {
clickCreateNewCredential,
clickExecuteNode,
@@ -29,6 +30,7 @@ import {
getOutputPanelTable,
checkParameterCheckboxInputByName,
} from '../composables/ndv';
+import * as workflow from '../composables/workflow';
import {
addLanguageModelNodeToParent,
addMemoryNodeToParent,
@@ -44,7 +46,7 @@ import {
disableNode,
getExecuteWorkflowButton,
} from '../composables/workflow';
-import { NDV, WorkflowPage } from '../pages';
+import { WorkflowPage } from '../pages';
import { createMockNodeExecutionData, runMockWorkflowExecution } from '../utils';
describe('Langchain Integration', () => {
@@ -132,7 +134,7 @@ describe('Langchain Integration', () => {
});
it('should be able to open and execute Basic LLM Chain node', () => {
- addNodeToCanvas(MANUAL_CHAT_TRIGGER_NODE_NAME, true);
+ addNodeToCanvas(MANUAL_CHAT_TRIGGER_NODE_NAME, true, false, undefined, true);
addNodeToCanvas(BASIC_LLM_CHAIN_NODE_NAME, true);
addLanguageModelNodeToParent(
@@ -171,7 +173,7 @@ describe('Langchain Integration', () => {
});
it('should be able to open and execute Agent node', () => {
- addNodeToCanvas(MANUAL_CHAT_TRIGGER_NODE_NAME, true);
+ addNodeToCanvas(MANUAL_CHAT_TRIGGER_NODE_NAME, true, false, undefined, true);
addNodeToCanvas(AGENT_NODE_NAME, true);
addLanguageModelNodeToParent(
@@ -211,7 +213,7 @@ describe('Langchain Integration', () => {
});
it('should add and use Manual Chat Trigger node together with Agent node', () => {
- addNodeToCanvas(MANUAL_CHAT_TRIGGER_NODE_NAME, true);
+ addNodeToCanvas(MANUAL_CHAT_TRIGGER_NODE_NAME, true, false, undefined, true);
addNodeToCanvas(AGENT_NODE_NAME, true);
addLanguageModelNodeToParent(
@@ -362,7 +364,7 @@ describe('Langchain Integration', () => {
getNodes().should('have.length', 3);
});
it('should not auto-add nodes if ChatTrigger is already present', () => {
- addNodeToCanvas(MANUAL_CHAT_TRIGGER_NODE_NAME, true);
+ addNodeToCanvas(MANUAL_CHAT_TRIGGER_NODE_NAME, true, false, undefined, true);
addNodeToCanvas(AGENT_NODE_NAME, true);
addNodeToCanvas(AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, true);
@@ -372,7 +374,6 @@ describe('Langchain Integration', () => {
it('should render runItems for sub-nodes and allow switching between them', () => {
const workflowPage = new WorkflowPage();
- const ndv = new NDV();
cy.visit(workflowPage.url);
cy.createFixtureWorkflow('In_memory_vector_store_fake_embeddings.json');
@@ -380,51 +381,56 @@ describe('Langchain Integration', () => {
workflowPage.actions.deselectAll();
workflowPage.actions.executeNode('Populate VS');
+ workflow.getNodesWithSpinner().should('not.exist');
const assertInputOutputText = (text: string, assertion: 'exist' | 'not.exist') => {
- ndv.getters.outputPanel().contains(text).should(assertion);
- ndv.getters.inputPanel().contains(text).should(assertion);
+ ndv.getOutputPanel().contains(text).should(assertion);
+ ndv.getOutputPanel().contains(text).should(assertion);
};
workflowPage.actions.openNode('Character Text Splitter');
- ndv.getters.outputRunSelector().should('exist');
- ndv.getters.inputRunSelector().should('exist');
- ndv.getters.inputRunSelector().find('input').should('include.value', '3 of 3');
- ndv.getters.outputRunSelector().find('input').should('include.value', '3 of 3');
+
+ // Wait for the input panel to switch to Debugging mode
+ ndv.getInputPanelItemsCount().should('not.exist');
+
+ ndv.getOutputRunSelector().should('exist');
+ ndv.getInputRunSelector().should('exist');
+ ndv.getInputRunSelector().find('input').should('include.value', '3 of 3');
+ ndv.getOutputRunSelector().find('input').should('include.value', '3 of 3');
assertInputOutputText('Kyiv', 'exist');
assertInputOutputText('Berlin', 'not.exist');
assertInputOutputText('Prague', 'not.exist');
- ndv.actions.changeOutputRunSelector('2 of 3');
+ ndv.changeOutputRunSelector('2 of 3');
assertInputOutputText('Berlin', 'exist');
assertInputOutputText('Kyiv', 'not.exist');
assertInputOutputText('Prague', 'not.exist');
- ndv.actions.changeOutputRunSelector('1 of 3');
+ ndv.changeOutputRunSelector('1 of 3');
assertInputOutputText('Prague', 'exist');
assertInputOutputText('Berlin', 'not.exist');
assertInputOutputText('Kyiv', 'not.exist');
- ndv.actions.toggleInputRunLinking();
- ndv.actions.changeOutputRunSelector('2 of 3');
- ndv.getters.inputRunSelector().find('input').should('include.value', '1 of 3');
- ndv.getters.outputRunSelector().find('input').should('include.value', '2 of 3');
- ndv.getters.inputPanel().contains('Prague').should('exist');
- ndv.getters.inputPanel().contains('Berlin').should('not.exist');
+ ndv.toggleInputRunLinking();
+ ndv.changeOutputRunSelector('2 of 3');
+ ndv.getInputRunSelector().find('input').should('include.value', '1 of 3');
+ ndv.getOutputRunSelector().find('input').should('include.value', '2 of 3');
+ ndv.getInputPanel().contains('Prague').should('exist');
+ ndv.getInputPanel().contains('Berlin').should('not.exist');
- ndv.getters.outputPanel().contains('Berlin').should('exist');
- ndv.getters.outputPanel().contains('Prague').should('not.exist');
+ ndv.getOutputPanel().contains('Berlin').should('exist');
+ ndv.getOutputPanel().contains('Prague').should('not.exist');
- ndv.actions.toggleInputRunLinking();
- ndv.getters.inputRunSelector().find('input').should('include.value', '1 of 3');
- ndv.getters.outputRunSelector().find('input').should('include.value', '1 of 3');
+ ndv.toggleInputRunLinking();
+ ndv.getInputRunSelector().find('input').should('include.value', '1 of 3');
+ ndv.getOutputRunSelector().find('input').should('include.value', '1 of 3');
assertInputOutputText('Prague', 'exist');
assertInputOutputText('Berlin', 'not.exist');
assertInputOutputText('Kyiv', 'not.exist');
});
it('should show tool info notice if no existing tools were used during execution', () => {
- addNodeToCanvas(MANUAL_CHAT_TRIGGER_NODE_NAME, true);
+ addNodeToCanvas(MANUAL_CHAT_TRIGGER_NODE_NAME, true, false, undefined, true);
addNodeToCanvas(AGENT_NODE_NAME, true);
addLanguageModelNodeToParent(
@@ -469,7 +475,7 @@ describe('Langchain Integration', () => {
});
it('should not show tool info notice if tools were used during execution', () => {
- addNodeToCanvas(MANUAL_CHAT_TRIGGER_NODE_NAME, true);
+ addNodeToCanvas(MANUAL_CHAT_TRIGGER_NODE_NAME, true, false, undefined, true);
addNodeToCanvas(AGENT_NODE_NAME, true, true);
getRunDataInfoCallout().should('not.exist');
clickGetBackToCanvas();
@@ -523,7 +529,6 @@ describe('Langchain Integration', () => {
it('should execute up to Node 1 when using partial execution', () => {
const workflowPage = new WorkflowPage();
- const ndv = new NDV();
cy.visit(workflowPage.url);
cy.createFixtureWorkflow('Test_workflow_chat_partial_execution.json');
@@ -531,7 +536,7 @@ describe('Langchain Integration', () => {
getManualChatModal().find('main').should('not.exist');
openNode('Node 1');
- ndv.actions.execute();
+ ndv.clickExecuteNode();
getManualChatModal().find('main').should('exist');
sendManualChatMessage('Test');
diff --git a/cypress/e2e/4-node-creator.cy.ts b/cypress/e2e/4-node-creator.cy.ts
index 77e8167080809..995b77ab3ed53 100644
--- a/cypress/e2e/4-node-creator.cy.ts
+++ b/cypress/e2e/4-node-creator.cy.ts
@@ -565,7 +565,7 @@ describe('Node Creator', () => {
});
it('should add node directly for sub-connection as tool', () => {
- addNodeToCanvas(MANUAL_CHAT_TRIGGER_NODE_NAME, true);
+ addNodeToCanvas(MANUAL_CHAT_TRIGGER_NODE_NAME, true, false, undefined, true);
addNodeToCanvas(AGENT_NODE_NAME, true, true);
clickGetBackToCanvas();
diff --git a/cypress/e2e/48-subworkflow-inputs.cy.ts b/cypress/e2e/48-subworkflow-inputs.cy.ts
index fa8d3e45106c9..9b994c6ab0833 100644
--- a/cypress/e2e/48-subworkflow-inputs.cy.ts
+++ b/cypress/e2e/48-subworkflow-inputs.cy.ts
@@ -70,7 +70,7 @@ describe('Sub-workflow creation and typed usage', () => {
// NAVIGATE TO CHILD WORKFLOW
// **************************
// Close NDV before opening the node creator
- cy.get('body').type('{esc}');
+ clickGetBackToCanvas();
openNode('When Executed by Another Workflow');
});
diff --git a/package.json b/package.json
index 89e917fe0b879..86a7d71f0a792 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "n8n-monorepo",
- "version": "1.95.1",
+ "version": "1.95.2",
"private": true,
"engines": {
"node": ">=20.15",
diff --git a/packages/@n8n/nodes-langchain/package.json b/packages/@n8n/nodes-langchain/package.json
index 6cdc46d792086..0a8f882854ddc 100644
--- a/packages/@n8n/nodes-langchain/package.json
+++ b/packages/@n8n/nodes-langchain/package.json
@@ -1,6 +1,6 @@
{
"name": "@n8n/n8n-nodes-langchain",
- "version": "1.95.0",
+ "version": "1.95.1",
"description": "",
"main": "index.js",
"scripts": {
diff --git a/packages/cli/package.json b/packages/cli/package.json
index d859eed3350e1..c1e60c5ebb082 100644
--- a/packages/cli/package.json
+++ b/packages/cli/package.json
@@ -1,6 +1,6 @@
{
"name": "n8n",
- "version": "1.95.1",
+ "version": "1.95.2",
"description": "n8n Workflow Automation Tool",
"main": "dist/index",
"types": "dist/index.d.ts",
diff --git a/packages/frontend/editor-ui/package.json b/packages/frontend/editor-ui/package.json
index e9c296e7d324b..ec6086231980e 100644
--- a/packages/frontend/editor-ui/package.json
+++ b/packages/frontend/editor-ui/package.json
@@ -1,6 +1,6 @@
{
"name": "n8n-editor-ui",
- "version": "1.95.1",
+ "version": "1.95.2",
"description": "Workflow Editor UI for n8n",
"main": "index.js",
"scripts": {
diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json
index 68b53e6e0383e..8b517bb491bc6 100644
--- a/packages/nodes-base/package.json
+++ b/packages/nodes-base/package.json
@@ -1,6 +1,6 @@
{
"name": "n8n-nodes-base",
- "version": "1.94.0",
+ "version": "1.94.1",
"description": "Base nodes of n8n",
"main": "index.js",
"scripts": {