mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
Merge remote-tracking branch 'origin/master' into pay-2000-update-variable-api
This commit is contained in:
commit
64e5f44926
54
CHANGELOG.md
54
CHANGELOG.md
|
@ -1,3 +1,57 @@
|
|||
# [1.65.0](https://github.com/n8n-io/n8n/compare/n8n@1.64.0...n8n@1.65.0) (2024-10-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **AI Agent Node:** Preserve `intermediateSteps` when using output parser with non-tool agent ([#11363](https://github.com/n8n-io/n8n/issues/11363)) ([e61a853](https://github.com/n8n-io/n8n/commit/e61a8535aa39653b9a87575ea911a65318282167))
|
||||
* **API:** `PUT /credentials/:id` should move the specified credential, not the first one in the database ([#11365](https://github.com/n8n-io/n8n/issues/11365)) ([e6b2f8e](https://github.com/n8n-io/n8n/commit/e6b2f8e7e6ebbb6e3776a976297d519e99ac6c64))
|
||||
* **API:** Correct credential schema for response in `POST /credentials` ([#11340](https://github.com/n8n-io/n8n/issues/11340)) ([f495875](https://github.com/n8n-io/n8n/commit/f4958756b4976e0b608b9155dab84564f7e8804e))
|
||||
* **core:** Account for waiting jobs during shutdown ([#11338](https://github.com/n8n-io/n8n/issues/11338)) ([c863abd](https://github.com/n8n-io/n8n/commit/c863abd08300b53ea898fc4d06aae97dec7afa9b))
|
||||
* **core:** Add missing primary key to execution annotation tags table ([#11168](https://github.com/n8n-io/n8n/issues/11168)) ([b4b543d](https://github.com/n8n-io/n8n/commit/b4b543d41daa07753eca24ab93bf7445f672361d))
|
||||
* **core:** Change dedupe value column type from varchar(255) to text ([#11357](https://github.com/n8n-io/n8n/issues/11357)) ([7a71cff](https://github.com/n8n-io/n8n/commit/7a71cff4d75fe4e7282a398b4843428e0161ba8c))
|
||||
* **core:** Do not debounce webhooks, triggers and pollers activation ([#11306](https://github.com/n8n-io/n8n/issues/11306)) ([64bddf8](https://github.com/n8n-io/n8n/commit/64bddf86536ddd688638a643d24f80c947a12f31))
|
||||
* **core:** Enforce nodejs version consistently ([#11323](https://github.com/n8n-io/n8n/issues/11323)) ([0fa2e8c](https://github.com/n8n-io/n8n/commit/0fa2e8ca85005362d9043d82469f3c3525f4c4ef))
|
||||
* **core:** Fix memory issue with empty model response ([#11300](https://github.com/n8n-io/n8n/issues/11300)) ([216b119](https://github.com/n8n-io/n8n/commit/216b119350949de70f15cf2d61f474770803ad7a))
|
||||
* **core:** Fix race condition when resolving post-execute promise ([#11360](https://github.com/n8n-io/n8n/issues/11360)) ([4f1816e](https://github.com/n8n-io/n8n/commit/4f1816e03db00219bc2e723e3048848aef7f8fe1))
|
||||
* **core:** Sanitise IdP provided information in SAML test pages ([#11171](https://github.com/n8n-io/n8n/issues/11171)) ([74fc388](https://github.com/n8n-io/n8n/commit/74fc3889b946e8f224e65ef8d3d44125404aa4fc))
|
||||
* Don't show pin button in input panel when there's binary data ([#11267](https://github.com/n8n-io/n8n/issues/11267)) ([c0b5b92](https://github.com/n8n-io/n8n/commit/c0b5b92f62a2d7ba60492eb27daced268b654fe9))
|
||||
* **editor:** Add Personal project to main navigation ([#11161](https://github.com/n8n-io/n8n/issues/11161)) ([1f441f9](https://github.com/n8n-io/n8n/commit/1f441f97528f58e905eaf8930577bbcd08debf06))
|
||||
* **editor:** Fix Cannot read properties of undefined (reading 'finished') ([#11367](https://github.com/n8n-io/n8n/issues/11367)) ([475d72e](https://github.com/n8n-io/n8n/commit/475d72e0bc9e13c6dc56129902f6f89c67547f78))
|
||||
* **editor:** Fix delete all existing executions ([#11352](https://github.com/n8n-io/n8n/issues/11352)) ([3ec103f](https://github.com/n8n-io/n8n/commit/3ec103f8baaa89e579844947d945f00bec9e498e))
|
||||
* **editor:** Fix pin data button disappearing after reload ([#11198](https://github.com/n8n-io/n8n/issues/11198)) ([3b2f63e](https://github.com/n8n-io/n8n/commit/3b2f63e248cd0cba04087e2f40e13d670073707d))
|
||||
* **editor:** Fix RunData non-binary pagination when binary data is present ([#11309](https://github.com/n8n-io/n8n/issues/11309)) ([901888d](https://github.com/n8n-io/n8n/commit/901888d5b1027098653540c72f787f176941f35a))
|
||||
* **editor:** Fix sorting problem in older browsers that don't support `toSorted` ([#11204](https://github.com/n8n-io/n8n/issues/11204)) ([c728a2f](https://github.com/n8n-io/n8n/commit/c728a2ffe01f510a237979a54897c4680a407800))
|
||||
* **editor:** Follow-up fixes to projects side menu ([#11327](https://github.com/n8n-io/n8n/issues/11327)) ([4dde772](https://github.com/n8n-io/n8n/commit/4dde772814c55e66efcc9b369ae443328af21b14))
|
||||
* **editor:** Keep always focus on the first item on the node's search panel ([#11193](https://github.com/n8n-io/n8n/issues/11193)) ([c57cac9](https://github.com/n8n-io/n8n/commit/c57cac9e4d447c3a4240a565f9f2de8aa3b7c513))
|
||||
* **editor:** Open Community+ enrollment modal only for the instance owner ([#11292](https://github.com/n8n-io/n8n/issues/11292)) ([76724c3](https://github.com/n8n-io/n8n/commit/76724c3be6e001792433045c2b2aac0ef16d4b8a))
|
||||
* **editor:** Record sessionStarted telemetry event in Setting Store ([#11334](https://github.com/n8n-io/n8n/issues/11334)) ([1b734dd](https://github.com/n8n-io/n8n/commit/1b734dd9f42885594ce02400cfb395a4f5e7e088))
|
||||
* Ensure NDV params don't get cut off early and scrolled to the top ([#11252](https://github.com/n8n-io/n8n/issues/11252)) ([054fe97](https://github.com/n8n-io/n8n/commit/054fe9745ff6864f9088aa4cd66ed9e7869520d5))
|
||||
* **HTTP Request Tool Node:** Fix the undefined response issue when authentication is enabled ([#11343](https://github.com/n8n-io/n8n/issues/11343)) ([094ec68](https://github.com/n8n-io/n8n/commit/094ec68d4c00848013aa4eec4ac5efbd2c92afc5))
|
||||
* Include error in the message in JS task runner sandbox ([#11359](https://github.com/n8n-io/n8n/issues/11359)) ([0708b3a](https://github.com/n8n-io/n8n/commit/0708b3a1f8097af829c92fe106ea6ba375d6c500))
|
||||
* **Microsoft SQL Node:** Fix execute query to allow for non select query to run ([#11335](https://github.com/n8n-io/n8n/issues/11335)) ([ba158b4](https://github.com/n8n-io/n8n/commit/ba158b4f8533bd3430db8766d4921f75db5c1a11))
|
||||
* **OpenAI Chat Model Node, Ollama Chat Model Node:** Change default model to a more up-to-date option ([#11293](https://github.com/n8n-io/n8n/issues/11293)) ([0be04c6](https://github.com/n8n-io/n8n/commit/0be04c6348d8c059a96c3d37a6d6cd587bfb97f3))
|
||||
* **Pinecone Vector Store Node:** Prevent populating of vectors after manually stopping the execution ([#11288](https://github.com/n8n-io/n8n/issues/11288)) ([fbae17d](https://github.com/n8n-io/n8n/commit/fbae17d8fb35a5197fa183e3639bb36762dc73d2))
|
||||
* **Postgres Node:** Special datetime values cause errors ([#11225](https://github.com/n8n-io/n8n/issues/11225)) ([3c57f46](https://github.com/n8n-io/n8n/commit/3c57f46aaeb968d2974f2dc9790317a6a6fab624))
|
||||
* Resend invite operation on users list ([#11351](https://github.com/n8n-io/n8n/issues/11351)) ([e4218de](https://github.com/n8n-io/n8n/commit/e4218debd18812fa3aa508339afd3de03c4d69dc))
|
||||
* **SSH Node:** Cleanup temporary binary files as soon as possible ([#11305](https://github.com/n8n-io/n8n/issues/11305)) ([08a7b5b](https://github.com/n8n-io/n8n/commit/08a7b5b7425663ec6593114921c2e22ab37d039e))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add report bug buttons ([#11304](https://github.com/n8n-io/n8n/issues/11304)) ([296f68f](https://github.com/n8n-io/n8n/commit/296f68f041b93fd32ac7be2b53c2b41d58c2998a))
|
||||
* **AI Agent Node:** Make tools optional when using OpenAI model with Tools agent ([#11212](https://github.com/n8n-io/n8n/issues/11212)) ([fed7c3e](https://github.com/n8n-io/n8n/commit/fed7c3ec1fb0553adaa9a933f91aabfd54fe83a3))
|
||||
* **core:** introduce JWT API keys for the public API ([#11005](https://github.com/n8n-io/n8n/issues/11005)) ([679fa4a](https://github.com/n8n-io/n8n/commit/679fa4a10a85fc96e12ca66fe12cdb32368bc12b))
|
||||
* **core:** Enforce config file permissions on startup ([#11328](https://github.com/n8n-io/n8n/issues/11328)) ([c078a51](https://github.com/n8n-io/n8n/commit/c078a516bec857831cc904ef807d0791b889f3a2))
|
||||
* **core:** Handle cycles in workflows when partially executing them ([#11187](https://github.com/n8n-io/n8n/issues/11187)) ([321d6de](https://github.com/n8n-io/n8n/commit/321d6deef18806d88d97afef2f2c6f29e739ccb4))
|
||||
* **editor:** Separate node output execution tooltip from status icon ([#11196](https://github.com/n8n-io/n8n/issues/11196)) ([cd15e95](https://github.com/n8n-io/n8n/commit/cd15e959c7af82a7d8c682e94add2b2640624a70))
|
||||
* **GitHub Node:** Add workflow resource operations ([#10744](https://github.com/n8n-io/n8n/issues/10744)) ([d309112](https://github.com/n8n-io/n8n/commit/d3091126472faa2c8f270650e54027d19dc56bb6))
|
||||
* **n8n Form Page Node:** New node ([#10390](https://github.com/n8n-io/n8n/issues/10390)) ([643d66c](https://github.com/n8n-io/n8n/commit/643d66c0ae084a0d93dac652703adc0a32cab8de))
|
||||
* **n8n Google My Business Node:** New node ([#10504](https://github.com/n8n-io/n8n/issues/10504)) ([bf28fbe](https://github.com/n8n-io/n8n/commit/bf28fbefe5e8ba648cba1555a2d396b75ee32bbb))
|
||||
* Run `mfa.beforeSetup` hook before enabling MFA ([#11116](https://github.com/n8n-io/n8n/issues/11116)) ([25c1c32](https://github.com/n8n-io/n8n/commit/25c1c3218cf1075ca3abd961236f3b2fbd9d6ba9))
|
||||
* **Structured Output Parser Node:** Refactor Output Parsers and Improve Error Handling ([#11148](https://github.com/n8n-io/n8n/issues/11148)) ([45274f2](https://github.com/n8n-io/n8n/commit/45274f2e7f081e194e330e1c9e6a5c26fca0b141))
|
||||
|
||||
|
||||
|
||||
# [1.64.0](https://github.com/n8n-io/n8n/compare/n8n@1.63.0...n8n@1.64.0) (2024-10-16)
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "n8n-monorepo",
|
||||
"version": "1.64.0",
|
||||
"version": "1.65.0",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": ">=20.15",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@n8n/n8n-benchmark",
|
||||
"version": "1.7.0",
|
||||
"version": "1.8.0",
|
||||
"description": "Cli for running benchmark tests for n8n",
|
||||
"main": "dist/index",
|
||||
"scripts": {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@n8n/config",
|
||||
"version": "1.14.0",
|
||||
"version": "1.15.0",
|
||||
"scripts": {
|
||||
"clean": "rimraf dist .turbo",
|
||||
"dev": "pnpm watch",
|
||||
|
|
|
@ -4,6 +4,7 @@ import { StringArray } from '../utils';
|
|||
/** Scopes (areas of functionality) to filter logs by. */
|
||||
export const LOG_SCOPES = [
|
||||
'concurrency',
|
||||
'external-secrets',
|
||||
'license',
|
||||
'multi-main-setup',
|
||||
'pubsub',
|
||||
|
@ -64,6 +65,7 @@ export class LoggingConfig {
|
|||
* Supported log scopes:
|
||||
*
|
||||
* - `concurrency`
|
||||
* - `external-secrets`
|
||||
* - `license`
|
||||
* - `multi-main-setup`
|
||||
* - `pubsub`
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@n8n/json-schema-to-zod",
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.0",
|
||||
"description": "Converts JSON schema objects into Zod schemas",
|
||||
"types": "./dist/types/index.d.ts",
|
||||
"main": "./dist/cjs/index.js",
|
||||
|
|
|
@ -251,7 +251,7 @@ export class Agent implements INodeType {
|
|||
icon: 'fa:robot',
|
||||
iconColor: 'black',
|
||||
group: ['transform'],
|
||||
version: [1, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6],
|
||||
version: [1, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7],
|
||||
description: 'Generates an action plan and executes it. Can use external tools.',
|
||||
subtitle:
|
||||
"={{ { toolsAgent: 'Tools Agent', conversationalAgent: 'Conversational Agent', openAiFunctionsAgent: 'OpenAI Functions Agent', reActAgent: 'ReAct Agent', sqlAgent: 'SQL Agent', planAndExecuteAgent: 'Plan and Execute Agent' }[$parameter.agent] }}",
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
import { getOptionalOutputParsers } from '../../../../../utils/output_parsers/N8nOutputParser';
|
||||
import { throwIfToolSchema } from '../../../../../utils/schemaParsing';
|
||||
import { getTracingConfig } from '../../../../../utils/tracing';
|
||||
import { extractParsedOutput } from '../utils';
|
||||
|
||||
export async function conversationalAgentExecute(
|
||||
this: IExecuteFunctions,
|
||||
|
@ -102,12 +103,12 @@ export async function conversationalAgentExecute(
|
|||
input = (await prompt.invoke({ input })).value;
|
||||
}
|
||||
|
||||
let response = await agentExecutor
|
||||
const response = await agentExecutor
|
||||
.withConfig(getTracingConfig(this))
|
||||
.invoke({ input, outputParsers });
|
||||
|
||||
if (outputParser) {
|
||||
response = { output: await outputParser.parse(response.output as string) };
|
||||
response.output = await extractParsedOutput(this, outputParser, response.output as string);
|
||||
}
|
||||
|
||||
returnData.push({ json: response });
|
||||
|
|
|
@ -15,6 +15,7 @@ import {
|
|||
import { getConnectedTools, getPromptInputByType } from '../../../../../utils/helpers';
|
||||
import { getOptionalOutputParsers } from '../../../../../utils/output_parsers/N8nOutputParser';
|
||||
import { getTracingConfig } from '../../../../../utils/tracing';
|
||||
import { extractParsedOutput } from '../utils';
|
||||
|
||||
export async function openAiFunctionsAgentExecute(
|
||||
this: IExecuteFunctions,
|
||||
|
@ -103,12 +104,12 @@ export async function openAiFunctionsAgentExecute(
|
|||
input = (await prompt.invoke({ input })).value;
|
||||
}
|
||||
|
||||
let response = await agentExecutor
|
||||
const response = await agentExecutor
|
||||
.withConfig(getTracingConfig(this))
|
||||
.invoke({ input, outputParsers });
|
||||
|
||||
if (outputParser) {
|
||||
response = { output: await outputParser.parse(response.output as string) };
|
||||
response.output = await extractParsedOutput(this, outputParser, response.output as string);
|
||||
}
|
||||
|
||||
returnData.push({ json: response });
|
||||
|
|
|
@ -14,6 +14,7 @@ import { getConnectedTools, getPromptInputByType } from '../../../../../utils/he
|
|||
import { getOptionalOutputParsers } from '../../../../../utils/output_parsers/N8nOutputParser';
|
||||
import { throwIfToolSchema } from '../../../../../utils/schemaParsing';
|
||||
import { getTracingConfig } from '../../../../../utils/tracing';
|
||||
import { extractParsedOutput } from '../utils';
|
||||
|
||||
export async function planAndExecuteAgentExecute(
|
||||
this: IExecuteFunctions,
|
||||
|
@ -79,12 +80,12 @@ export async function planAndExecuteAgentExecute(
|
|||
input = (await prompt.invoke({ input })).value;
|
||||
}
|
||||
|
||||
let response = await agentExecutor
|
||||
const response = await agentExecutor
|
||||
.withConfig(getTracingConfig(this))
|
||||
.invoke({ input, outputParsers });
|
||||
|
||||
if (outputParser) {
|
||||
response = { output: await outputParser.parse(response.output as string) };
|
||||
response.output = await extractParsedOutput(this, outputParser, response.output as string);
|
||||
}
|
||||
|
||||
returnData.push({ json: response });
|
||||
|
|
|
@ -19,6 +19,7 @@ import {
|
|||
import { getOptionalOutputParsers } from '../../../../../utils/output_parsers/N8nOutputParser';
|
||||
import { throwIfToolSchema } from '../../../../../utils/schemaParsing';
|
||||
import { getTracingConfig } from '../../../../../utils/tracing';
|
||||
import { extractParsedOutput } from '../utils';
|
||||
|
||||
export async function reActAgentAgentExecute(
|
||||
this: IExecuteFunctions,
|
||||
|
@ -103,12 +104,12 @@ export async function reActAgentAgentExecute(
|
|||
input = (await prompt.invoke({ input })).value;
|
||||
}
|
||||
|
||||
let response = await agentExecutor
|
||||
const response = await agentExecutor
|
||||
.withConfig(getTracingConfig(this))
|
||||
.invoke({ input, outputParsers });
|
||||
|
||||
if (outputParser) {
|
||||
response = { output: await outputParser.parse(response.output as string) };
|
||||
response.output = await extractParsedOutput(this, outputParser, response.output as string);
|
||||
}
|
||||
|
||||
returnData.push({ json: response });
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
import type { BaseOutputParser } from '@langchain/core/output_parsers';
|
||||
import type { IExecuteFunctions } from 'n8n-workflow';
|
||||
|
||||
export async function extractParsedOutput(
|
||||
ctx: IExecuteFunctions,
|
||||
outputParser: BaseOutputParser<unknown>,
|
||||
output: string,
|
||||
): Promise<Record<string, unknown> | undefined> {
|
||||
const parsedOutput = (await outputParser.parse(output)) as {
|
||||
output: Record<string, unknown>;
|
||||
};
|
||||
|
||||
if (ctx.getNode().typeVersion <= 1.6) {
|
||||
return parsedOutput;
|
||||
}
|
||||
// For 1.7 and above, we try to extract the output from the parsed output
|
||||
// with fallback to the original output if it's not present
|
||||
return parsedOutput?.output ?? parsedOutput;
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@n8n/n8n-nodes-langchain",
|
||||
"version": "1.64.0",
|
||||
"version": "1.65.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@n8n/permissions",
|
||||
"version": "0.14.0",
|
||||
"version": "0.15.0",
|
||||
"scripts": {
|
||||
"clean": "rimraf dist .turbo",
|
||||
"dev": "pnpm watch",
|
||||
|
|
|
@ -5,45 +5,33 @@ export type Resource = keyof typeof RESOURCES;
|
|||
|
||||
export type ResourceScope<
|
||||
R extends Resource,
|
||||
Operation extends string = DefaultOperations,
|
||||
Operation extends (typeof RESOURCES)[R][number] = (typeof RESOURCES)[R][number],
|
||||
> = `${R}:${Operation}`;
|
||||
|
||||
export type WildcardScope = `${Resource}:*` | '*';
|
||||
|
||||
export type AnnotationTagScope = ResourceScope<'annotationTag'>;
|
||||
export type AuditLogsScope = ResourceScope<'auditLogs', 'manage'>;
|
||||
export type BannerScope = ResourceScope<'banner', 'dismiss'>;
|
||||
export type CommunityScope = ResourceScope<'community', 'register'>;
|
||||
export type CommunityPackageScope = ResourceScope<
|
||||
'communityPackage',
|
||||
'install' | 'uninstall' | 'update' | 'list' | 'manage'
|
||||
>;
|
||||
export type CredentialScope = ResourceScope<'credential', DefaultOperations | 'share' | 'move'>;
|
||||
export type ExternalSecretScope = ResourceScope<'externalSecret', 'list' | 'use'>;
|
||||
export type ExternalSecretProviderScope = ResourceScope<
|
||||
'externalSecretsProvider',
|
||||
DefaultOperations | 'sync'
|
||||
>;
|
||||
export type EventBusDestinationScope = ResourceScope<
|
||||
'eventBusDestination',
|
||||
DefaultOperations | 'test'
|
||||
>;
|
||||
export type LdapScope = ResourceScope<'ldap', 'manage' | 'sync'>;
|
||||
export type LicenseScope = ResourceScope<'license', 'manage'>;
|
||||
export type LogStreamingScope = ResourceScope<'logStreaming', 'manage'>;
|
||||
export type OrchestrationScope = ResourceScope<'orchestration', 'read' | 'list'>;
|
||||
export type AuditLogsScope = ResourceScope<'auditLogs'>;
|
||||
export type BannerScope = ResourceScope<'banner'>;
|
||||
export type CommunityScope = ResourceScope<'community'>;
|
||||
export type CommunityPackageScope = ResourceScope<'communityPackage'>;
|
||||
export type CredentialScope = ResourceScope<'credential'>;
|
||||
export type ExternalSecretScope = ResourceScope<'externalSecret'>;
|
||||
export type ExternalSecretProviderScope = ResourceScope<'externalSecretsProvider'>;
|
||||
export type EventBusDestinationScope = ResourceScope<'eventBusDestination'>;
|
||||
export type LdapScope = ResourceScope<'ldap'>;
|
||||
export type LicenseScope = ResourceScope<'license'>;
|
||||
export type LogStreamingScope = ResourceScope<'logStreaming'>;
|
||||
export type OrchestrationScope = ResourceScope<'orchestration'>;
|
||||
export type ProjectScope = ResourceScope<'project'>;
|
||||
export type SamlScope = ResourceScope<'saml', 'manage'>;
|
||||
export type SecurityAuditScope = ResourceScope<'securityAudit', 'generate'>;
|
||||
export type SourceControlScope = ResourceScope<'sourceControl', 'pull' | 'push' | 'manage'>;
|
||||
export type SamlScope = ResourceScope<'saml'>;
|
||||
export type SecurityAuditScope = ResourceScope<'securityAudit'>;
|
||||
export type SourceControlScope = ResourceScope<'sourceControl'>;
|
||||
export type TagScope = ResourceScope<'tag'>;
|
||||
export type UserScope = ResourceScope<'user', DefaultOperations | 'resetPassword' | 'changeRole'>;
|
||||
export type UserScope = ResourceScope<'user'>;
|
||||
export type VariableScope = ResourceScope<'variable'>;
|
||||
export type WorkersViewScope = ResourceScope<'workersView', 'manage'>;
|
||||
export type WorkflowScope = ResourceScope<
|
||||
'workflow',
|
||||
DefaultOperations | 'share' | 'execute' | 'move'
|
||||
>;
|
||||
export type WorkersViewScope = ResourceScope<'workersView'>;
|
||||
export type WorkflowScope = ResourceScope<'workflow'>;
|
||||
|
||||
export type Scope =
|
||||
| AnnotationTagScope
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@n8n/task-runner",
|
||||
"version": "1.2.0",
|
||||
"version": "1.3.0",
|
||||
"scripts": {
|
||||
"clean": "rimraf dist .turbo",
|
||||
"start": "node dist/start.js",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "n8n",
|
||||
"version": "1.64.0",
|
||||
"version": "1.65.0",
|
||||
"description": "n8n Workflow Automation Tool",
|
||||
"main": "dist/index",
|
||||
"types": "dist/index.d.ts",
|
||||
|
|
|
@ -13,7 +13,7 @@ import {
|
|||
FailedProvider,
|
||||
MockProviders,
|
||||
} from '@test/external-secrets/utils';
|
||||
import { mockInstance } from '@test/mocking';
|
||||
import { mockInstance, mockLogger } from '@test/mocking';
|
||||
|
||||
describe('External Secrets Manager', () => {
|
||||
const connectedDate = '2023-08-01T12:32:29.000Z';
|
||||
|
@ -49,7 +49,7 @@ describe('External Secrets Manager', () => {
|
|||
license.isExternalSecretsEnabled.mockReturnValue(true);
|
||||
settingsRepo.getEncryptedSecretsProviderSettings.mockResolvedValue(settings);
|
||||
manager = new ExternalSecretsManager(
|
||||
mock(),
|
||||
mockLogger(),
|
||||
settingsRepo,
|
||||
license,
|
||||
providersMock,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Cipher } from 'n8n-core';
|
||||
import { jsonParse, type IDataObject, ApplicationError } from 'n8n-workflow';
|
||||
import { jsonParse, type IDataObject, ApplicationError, ensureError } from 'n8n-workflow';
|
||||
import { Service } from 'typedi';
|
||||
|
||||
import { SettingsRepository } from '@/databases/repositories/settings.repository';
|
||||
|
@ -39,7 +39,9 @@ export class ExternalSecretsManager {
|
|||
private readonly cipher: Cipher,
|
||||
private readonly eventService: EventService,
|
||||
private readonly publisher: Publisher,
|
||||
) {}
|
||||
) {
|
||||
this.logger = this.logger.scoped('external-secrets');
|
||||
}
|
||||
|
||||
async init(): Promise<void> {
|
||||
if (!this.initialized) {
|
||||
|
@ -57,6 +59,8 @@ export class ExternalSecretsManager {
|
|||
}
|
||||
return await this.initializingPromise;
|
||||
}
|
||||
|
||||
this.logger.debug('External secrets manager initialized');
|
||||
}
|
||||
|
||||
shutdown() {
|
||||
|
@ -66,6 +70,8 @@ export class ExternalSecretsManager {
|
|||
void p.disconnect().catch(() => {});
|
||||
});
|
||||
Object.values(this.initRetryTimeouts).forEach((v) => clearTimeout(v));
|
||||
|
||||
this.logger.debug('External secrets manager shut down');
|
||||
}
|
||||
|
||||
async reloadAllProviders(backoff?: number) {
|
||||
|
@ -77,6 +83,8 @@ export class ExternalSecretsManager {
|
|||
for (const provider of providers) {
|
||||
await this.reloadProvider(provider, backoff);
|
||||
}
|
||||
|
||||
this.logger.debug('External secrets managed reloaded all providers');
|
||||
}
|
||||
|
||||
broadcastReloadExternalSecretsProviders() {
|
||||
|
@ -191,6 +199,8 @@ export class ExternalSecretsManager {
|
|||
}
|
||||
}),
|
||||
);
|
||||
|
||||
this.logger.debug('External secrets manager updated secrets');
|
||||
}
|
||||
|
||||
getProvider(provider: string): SecretsProvider | undefined {
|
||||
|
@ -261,6 +271,8 @@ export class ExternalSecretsManager {
|
|||
if (newProvider) {
|
||||
this.providers[provider] = newProvider;
|
||||
}
|
||||
|
||||
this.logger.debug(`External secrets manager reloaded provider ${provider}`);
|
||||
}
|
||||
|
||||
async setProviderSettings(provider: string, data: IDataObject, userId?: string) {
|
||||
|
@ -382,8 +394,12 @@ export class ExternalSecretsManager {
|
|||
try {
|
||||
await this.providers[provider].update();
|
||||
this.broadcastReloadExternalSecretsProviders();
|
||||
this.logger.debug(`External secrets manager updated provider ${provider}`);
|
||||
return true;
|
||||
} catch {
|
||||
} catch (error) {
|
||||
this.logger.debug(`External secrets manager failed to update provider ${provider}`, {
|
||||
error: ensureError(error),
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import type { INodeProperties } from 'n8n-workflow';
|
||||
import Container from 'typedi';
|
||||
|
||||
import { UnknownAuthTypeError } from '@/errors/unknown-auth-type.error';
|
||||
import { DOCS_HELP_NOTICE, EXTERNAL_SECRETS_NAME_REGEX } from '@/external-secrets/constants';
|
||||
import type { SecretsProvider, SecretsProviderState } from '@/interfaces';
|
||||
import { Logger } from '@/logging/logger.service';
|
||||
|
||||
import { AwsSecretsClient } from './aws-secrets-client';
|
||||
import type { AwsSecretsManagerContext } from './types';
|
||||
|
@ -76,10 +78,16 @@ export class AwsSecretsManager implements SecretsProvider {
|
|||
|
||||
private client: AwsSecretsClient;
|
||||
|
||||
constructor(private readonly logger = Container.get(Logger)) {
|
||||
this.logger = this.logger.scoped('external-secrets');
|
||||
}
|
||||
|
||||
async init(context: AwsSecretsManagerContext) {
|
||||
this.assertAuthType(context);
|
||||
|
||||
this.client = new AwsSecretsClient(context.settings);
|
||||
|
||||
this.logger.debug('AWS Secrets Manager provider initialized');
|
||||
}
|
||||
|
||||
async test() {
|
||||
|
@ -87,9 +95,15 @@ export class AwsSecretsManager implements SecretsProvider {
|
|||
}
|
||||
|
||||
async connect() {
|
||||
const [wasSuccessful] = await this.test();
|
||||
const [wasSuccessful, errorMsg] = await this.test();
|
||||
|
||||
this.state = wasSuccessful ? 'connected' : 'error';
|
||||
|
||||
if (wasSuccessful) {
|
||||
this.logger.debug('AWS Secrets Manager provider connected');
|
||||
} else {
|
||||
this.logger.error('AWS Secrets Manager provider failed to connect', { errorMsg });
|
||||
}
|
||||
}
|
||||
|
||||
async disconnect() {
|
||||
|
@ -104,6 +118,8 @@ export class AwsSecretsManager implements SecretsProvider {
|
|||
this.cachedSecrets = Object.fromEntries(
|
||||
supportedSecrets.map((s) => [s.secretName, s.secretValue]),
|
||||
);
|
||||
|
||||
this.logger.debug('AWS Secrets Manager provider secrets updated');
|
||||
}
|
||||
|
||||
getSecret(name: string) {
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
import type { SecretClient } from '@azure/keyvault-secrets';
|
||||
import { ensureError } from 'n8n-workflow';
|
||||
import type { INodeProperties } from 'n8n-workflow';
|
||||
import Container from 'typedi';
|
||||
|
||||
import { DOCS_HELP_NOTICE, EXTERNAL_SECRETS_NAME_REGEX } from '@/external-secrets/constants';
|
||||
import type { SecretsProvider, SecretsProviderState } from '@/interfaces';
|
||||
import { Logger } from '@/logging/logger.service';
|
||||
|
||||
import type { AzureKeyVaultContext } from './types';
|
||||
|
||||
|
@ -64,8 +67,14 @@ export class AzureKeyVault implements SecretsProvider {
|
|||
|
||||
private settings: AzureKeyVaultContext['settings'];
|
||||
|
||||
constructor(private readonly logger = Container.get(Logger)) {
|
||||
this.logger = this.logger.scoped('external-secrets');
|
||||
}
|
||||
|
||||
async init(context: AzureKeyVaultContext) {
|
||||
this.settings = context.settings;
|
||||
|
||||
this.logger.debug('Azure Key Vault provider initialized');
|
||||
}
|
||||
|
||||
async connect() {
|
||||
|
@ -78,8 +87,12 @@ export class AzureKeyVault implements SecretsProvider {
|
|||
const credential = new ClientSecretCredential(tenantId, clientId, clientSecret);
|
||||
this.client = new SecretClient(`https://${vaultName}.vault.azure.net/`, credential);
|
||||
this.state = 'connected';
|
||||
} catch {
|
||||
this.logger.debug('Azure Key Vault provider connected');
|
||||
} catch (error) {
|
||||
this.state = 'error';
|
||||
this.logger.error('Azure Key Vault provider failed to connect', {
|
||||
error: ensureError(error),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -119,6 +132,8 @@ export class AzureKeyVault implements SecretsProvider {
|
|||
acc[cur.name] = cur.value;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
this.logger.debug('Azure Key Vault provider secrets updated');
|
||||
}
|
||||
|
||||
getSecret(name: string) {
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import type { SecretManagerServiceClient as GcpClient } from '@google-cloud/secret-manager';
|
||||
import { jsonParse, type INodeProperties } from 'n8n-workflow';
|
||||
import { ensureError, jsonParse, type INodeProperties } from 'n8n-workflow';
|
||||
import Container from 'typedi';
|
||||
|
||||
import { DOCS_HELP_NOTICE, EXTERNAL_SECRETS_NAME_REGEX } from '@/external-secrets/constants';
|
||||
import type { SecretsProvider, SecretsProviderState } from '@/interfaces';
|
||||
import { Logger } from '@/logging/logger.service';
|
||||
|
||||
import type {
|
||||
GcpSecretsManagerContext,
|
||||
|
@ -38,6 +40,10 @@ export class GcpSecretsManager implements SecretsProvider {
|
|||
|
||||
private settings: GcpSecretAccountKey;
|
||||
|
||||
constructor(private readonly logger = Container.get(Logger)) {
|
||||
this.logger = this.logger.scoped('external-secrets');
|
||||
}
|
||||
|
||||
async init(context: GcpSecretsManagerContext) {
|
||||
this.settings = this.parseSecretAccountKey(context.settings.serviceAccountKey);
|
||||
}
|
||||
|
@ -53,8 +59,12 @@ export class GcpSecretsManager implements SecretsProvider {
|
|||
projectId,
|
||||
});
|
||||
this.state = 'connected';
|
||||
} catch {
|
||||
this.logger.debug('GCP Secrets Manager provider connected');
|
||||
} catch (error) {
|
||||
this.state = 'error';
|
||||
this.logger.debug('GCP Secrets Manager provider failed to connect', {
|
||||
error: ensureError(error),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -114,6 +124,8 @@ export class GcpSecretsManager implements SecretsProvider {
|
|||
if (cur) acc[cur.name] = cur.value;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
this.logger.debug('GCP Secrets Manager provider secrets updated');
|
||||
}
|
||||
|
||||
getSecret(name: string) {
|
||||
|
|
|
@ -237,6 +237,7 @@ export class VaultProvider extends SecretsProvider {
|
|||
|
||||
constructor(readonly logger = Container.get(Logger)) {
|
||||
super();
|
||||
this.logger = this.logger.scoped('external-secrets');
|
||||
}
|
||||
|
||||
async init(settings: SecretsProviderSettings): Promise<void> {
|
||||
|
@ -257,6 +258,8 @@ export class VaultProvider extends SecretsProvider {
|
|||
}
|
||||
return config;
|
||||
});
|
||||
|
||||
this.logger.debug('Vault provider initialized');
|
||||
}
|
||||
|
||||
async connect(): Promise<void> {
|
||||
|
@ -408,6 +411,7 @@ export class VaultProvider extends SecretsProvider {
|
|||
kvVersion: string,
|
||||
path: string,
|
||||
): Promise<[string, IDataObject] | null> {
|
||||
this.logger.debug(`Getting kv secrets from ${mountPath}${path} (version ${kvVersion})`);
|
||||
let listPath = mountPath;
|
||||
if (kvVersion === '2') {
|
||||
listPath += 'metadata/';
|
||||
|
@ -441,6 +445,7 @@ export class VaultProvider extends SecretsProvider {
|
|||
secretPath += path + key;
|
||||
try {
|
||||
const secretResp = await this.#http.get<VaultResponse<IDataObject>>(secretPath);
|
||||
this.logger.debug(`Vault provider retrieved secrets from ${secretPath}`);
|
||||
return [
|
||||
key,
|
||||
kvVersion === '2'
|
||||
|
@ -457,6 +462,7 @@ export class VaultProvider extends SecretsProvider {
|
|||
.filter((v): v is [string, IDataObject] => v !== null),
|
||||
);
|
||||
const name = path.substring(0, path.length - 1);
|
||||
this.logger.debug(`Vault provider retrieved kv secrets from ${name}`);
|
||||
return [name, data];
|
||||
}
|
||||
|
||||
|
@ -479,6 +485,7 @@ export class VaultProvider extends SecretsProvider {
|
|||
).filter((v): v is [string, IDataObject] => v !== null),
|
||||
);
|
||||
this.cachedSecrets = secrets;
|
||||
this.logger.debug('Vault provider secrets updated');
|
||||
}
|
||||
|
||||
async test(): Promise<[boolean] | [boolean, string]> {
|
||||
|
|
|
@ -1,10 +1,42 @@
|
|||
jest.mock('n8n-workflow', () => ({
|
||||
...jest.requireActual('n8n-workflow'),
|
||||
LoggerProxy: { init: jest.fn() },
|
||||
}));
|
||||
|
||||
import type { GlobalConfig } from '@n8n/config';
|
||||
import { mock } from 'jest-mock-extended';
|
||||
import type { InstanceSettings } from 'n8n-core';
|
||||
import { LoggerProxy } from 'n8n-workflow';
|
||||
|
||||
import { Logger } from '@/logging/logger.service';
|
||||
|
||||
describe('Logger', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
const globalConfig = mock<GlobalConfig>({
|
||||
logging: {
|
||||
level: 'info',
|
||||
outputs: ['console'],
|
||||
scopes: [],
|
||||
},
|
||||
});
|
||||
|
||||
test('if root, should initialize `LoggerProxy` with instance', () => {
|
||||
const logger = new Logger(globalConfig, mock<InstanceSettings>(), { isRoot: true });
|
||||
|
||||
expect(LoggerProxy.init).toHaveBeenCalledWith(logger);
|
||||
});
|
||||
|
||||
test('if scoped, should not initialize `LoggerProxy`', () => {
|
||||
new Logger(globalConfig, mock<InstanceSettings>(), { isRoot: false });
|
||||
|
||||
expect(LoggerProxy.init).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('transports', () => {
|
||||
test('if `console` selected, should set console transport', () => {
|
||||
const globalConfig = mock<GlobalConfig>({
|
||||
|
|
|
@ -30,6 +30,7 @@ export class Logger {
|
|||
constructor(
|
||||
private readonly globalConfig: GlobalConfig,
|
||||
private readonly instanceSettings: InstanceSettings,
|
||||
{ isRoot }: { isRoot?: boolean } = { isRoot: true },
|
||||
) {
|
||||
this.level = this.globalConfig.logging.level;
|
||||
|
||||
|
@ -51,7 +52,7 @@ export class Logger {
|
|||
this.scopes = new Set(scopes);
|
||||
}
|
||||
|
||||
LoggerProxy.init(this);
|
||||
if (isRoot) LoggerProxy.init(this);
|
||||
}
|
||||
|
||||
private setInternalLogger(internalLogger: winston.Logger) {
|
||||
|
@ -61,7 +62,7 @@ export class Logger {
|
|||
/** Create a logger that injects the given scopes into its log metadata. */
|
||||
scoped(scopes: LogScope | LogScope[]) {
|
||||
scopes = Array.isArray(scopes) ? scopes : [scopes];
|
||||
const scopedLogger = new Logger(this.globalConfig, this.instanceSettings);
|
||||
const scopedLogger = new Logger(this.globalConfig, this.instanceSettings, { isRoot: false });
|
||||
const childLogger = this.internalLogger.child({ scopes });
|
||||
|
||||
scopedLogger.setInternalLogger(childLogger);
|
||||
|
|
|
@ -44,6 +44,16 @@ describe('Publisher', () => {
|
|||
});
|
||||
|
||||
describe('publishCommand', () => {
|
||||
it('should do nothing if not in scaling mode', async () => {
|
||||
config.set('executions.mode', 'regular');
|
||||
const publisher = new Publisher(logger, redisClientService, instanceSettings);
|
||||
const msg = mock<PubSub.Command>({ command: 'reload-license' });
|
||||
|
||||
await publisher.publishCommand(msg);
|
||||
|
||||
expect(client.publish).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should publish command into `n8n.commands` pubsub channel', async () => {
|
||||
const publisher = new Publisher(logger, redisClientService, instanceSettings);
|
||||
const msg = mock<PubSub.Command>({ command: 'reload-license' });
|
||||
|
|
|
@ -23,7 +23,7 @@ export class Publisher {
|
|||
private readonly redisClientService: RedisClientService,
|
||||
private readonly instanceSettings: InstanceSettings,
|
||||
) {
|
||||
// @TODO: Once this class is only ever initialized in scaling mode, throw in the next line instead.
|
||||
// @TODO: Once this class is only ever initialized in scaling mode, assert in the next line.
|
||||
if (config.getEnv('executions.mode') !== 'queue') return;
|
||||
|
||||
this.logger = this.logger.scoped(['scaling', 'pubsub']);
|
||||
|
@ -46,6 +46,9 @@ export class Publisher {
|
|||
|
||||
/** Publish a command into the `n8n.commands` channel. */
|
||||
async publishCommand(msg: Omit<PubSub.Command, 'senderId'>) {
|
||||
// @TODO: Once this class is only ever used in scaling mode, remove next line.
|
||||
if (config.getEnv('executions.mode') !== 'queue') return;
|
||||
|
||||
await this.client.publish(
|
||||
'n8n.commands',
|
||||
JSON.stringify({
|
||||
|
|
|
@ -18,7 +18,7 @@ import {
|
|||
MockProviders,
|
||||
TestFailProvider,
|
||||
} from '../../shared/external-secrets/utils';
|
||||
import { mockInstance } from '../../shared/mocking';
|
||||
import { mockInstance, mockLogger } from '../../shared/mocking';
|
||||
import { createOwner, createUser } from '../shared/db/users';
|
||||
import type { SuperAgentTest } from '../shared/types';
|
||||
import { setupTestServer } from '../shared/utils';
|
||||
|
@ -52,12 +52,14 @@ async function getExternalSecretsSettings(): Promise<ExternalSecretsSettings | n
|
|||
|
||||
const eventService = mock<EventService>();
|
||||
|
||||
const logger = mockLogger();
|
||||
|
||||
const resetManager = async () => {
|
||||
Container.get(ExternalSecretsManager).shutdown();
|
||||
Container.set(
|
||||
ExternalSecretsManager,
|
||||
new ExternalSecretsManager(
|
||||
mock(),
|
||||
logger,
|
||||
Container.get(SettingsRepository),
|
||||
Container.get(License),
|
||||
mockProvidersInstance,
|
||||
|
@ -108,6 +110,18 @@ beforeAll(async () => {
|
|||
const member = await createUser();
|
||||
authMemberAgent = testServer.authAgentFor(member);
|
||||
config.set('userManagement.isInstanceOwnerSetUp', true);
|
||||
Container.set(
|
||||
ExternalSecretsManager,
|
||||
new ExternalSecretsManager(
|
||||
logger,
|
||||
Container.get(SettingsRepository),
|
||||
Container.get(License),
|
||||
mockProvidersInstance,
|
||||
Container.get(Cipher),
|
||||
eventService,
|
||||
mock(),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "n8n-core",
|
||||
"version": "1.64.0",
|
||||
"version": "1.65.0",
|
||||
"description": "Core functionality of n8n",
|
||||
"main": "dist/index",
|
||||
"types": "dist/index.d.ts",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "n8n-design-system",
|
||||
"version": "1.54.0",
|
||||
"version": "1.55.0",
|
||||
"main": "src/main.ts",
|
||||
"import": "src/main.ts",
|
||||
"scripts": {
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
import '@testing-library/jest-dom';
|
||||
import { configure } from '@testing-library/vue';
|
||||
import { config } from '@vue/test-utils';
|
||||
|
||||
import { N8nPlugin } from 'n8n-design-system/plugin';
|
||||
|
||||
configure({ testIdAttribute: 'data-test-id' });
|
||||
|
||||
config.global.plugins = [N8nPlugin];
|
||||
|
||||
window.ResizeObserver =
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "n8n-editor-ui",
|
||||
"version": "1.64.0",
|
||||
"version": "1.65.0",
|
||||
"description": "Workflow Editor UI for n8n",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
<script setup lang="ts">
|
||||
import { useI18n } from '@/composables/useI18n';
|
||||
import { useNDVStore } from '@/stores/ndv.store';
|
||||
import { computed, onBeforeUnmount, ref, watch } from 'vue';
|
||||
import { EditorSelection, EditorState, type SelectionRange } from '@codemirror/state';
|
||||
import { type Completion, CompletionContext } from '@codemirror/autocomplete';
|
||||
import { datatypeCompletions } from '@/plugins/codemirror/completions/datatype.completions';
|
||||
import { watchDebounced } from '@vueuse/core';
|
||||
import { FIELDS_SECTION } from '@/plugins/codemirror/completions/constants';
|
||||
import { datatypeCompletions } from '@/plugins/codemirror/completions/datatype.completions';
|
||||
import { isCompletionSection } from '@/plugins/codemirror/completions/utils';
|
||||
import { useNDVStore } from '@/stores/ndv.store';
|
||||
import { type Completion, CompletionContext } from '@codemirror/autocomplete';
|
||||
import { EditorSelection, EditorState, type SelectionRange } from '@codemirror/state';
|
||||
import { watchDebounced } from '@vueuse/core';
|
||||
import { computed, onBeforeUnmount, ref, watch } from 'vue';
|
||||
|
||||
type TipId = 'executePrevious' | 'drag' | 'default' | 'dotObject' | 'dotPrimitive';
|
||||
|
||||
|
@ -36,7 +36,11 @@ const canDragToFocusedInput = computed(
|
|||
const emptyExpression = computed(() => props.unresolvedExpression.trim().length === 0);
|
||||
|
||||
const tip = computed<TipId>(() => {
|
||||
if (!ndvStore.hasInputData && ndvStore.isInputParentOfActiveNode) {
|
||||
if (
|
||||
!ndvStore.hasInputData &&
|
||||
ndvStore.isInputParentOfActiveNode &&
|
||||
ndvStore.focusedMappableInput
|
||||
) {
|
||||
return 'executePrevious';
|
||||
}
|
||||
|
||||
|
|
|
@ -539,7 +539,8 @@ const showDragnDropTip = computed(
|
|||
!isDropDisabled.value &&
|
||||
(!ndvStore.hasInputData || !isInputDataEmpty.value) &&
|
||||
!ndvStore.isMappingOnboarded &&
|
||||
ndvStore.isInputParentOfActiveNode,
|
||||
ndvStore.isInputParentOfActiveNode &&
|
||||
!props.isForCredential,
|
||||
);
|
||||
|
||||
const shouldCaptureForPosthog = computed(() => {
|
||||
|
|
|
@ -8,7 +8,13 @@ import {
|
|||
WAIT_NODE_TYPE,
|
||||
} from '@/constants';
|
||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||
import type { IExecutionResponse, INodeUi, IWorkflowDb, IWorkflowSettings } from '@/Interface';
|
||||
import type {
|
||||
IExecutionResponse,
|
||||
IExecutionsCurrentSummaryExtended,
|
||||
INodeUi,
|
||||
IWorkflowDb,
|
||||
IWorkflowSettings,
|
||||
} from '@/Interface';
|
||||
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||
|
||||
import {
|
||||
|
@ -594,6 +600,50 @@ describe('useWorkflowsStore', () => {
|
|||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('finishActiveExecution', () => {
|
||||
it('should update execution', async () => {
|
||||
const cursor = 1;
|
||||
const ids = ['0', '1', '2'];
|
||||
workflowsStore.setActiveExecutions(
|
||||
ids.map((id) => ({ id })) as IExecutionsCurrentSummaryExtended[],
|
||||
);
|
||||
|
||||
const stoppedAt = new Date();
|
||||
|
||||
workflowsStore.finishActiveExecution({
|
||||
executionId: ids[cursor],
|
||||
data: {
|
||||
finished: true,
|
||||
stoppedAt,
|
||||
},
|
||||
} as PushPayload<'executionFinished'>);
|
||||
|
||||
expect(workflowsStore.activeExecutions[cursor]).toStrictEqual({
|
||||
id: ids[cursor],
|
||||
finished: true,
|
||||
stoppedAt,
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle parameter casting', async () => {
|
||||
const cursor = 1;
|
||||
const ids = ['0', '1', '2'];
|
||||
workflowsStore.setActiveExecutions(
|
||||
ids.map((id) => ({ id })) as IExecutionsCurrentSummaryExtended[],
|
||||
);
|
||||
|
||||
workflowsStore.finishActiveExecution({
|
||||
executionId: ids[cursor],
|
||||
} as PushPayload<'executionFinished'>);
|
||||
|
||||
expect(workflowsStore.activeExecutions[cursor]).toStrictEqual({
|
||||
id: ids[cursor],
|
||||
finished: undefined,
|
||||
stoppedAt: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function getMockEditFieldsNode() {
|
||||
|
|
|
@ -47,7 +47,6 @@ import type {
|
|||
INodeParameters,
|
||||
INodeTypes,
|
||||
IPinData,
|
||||
IRun,
|
||||
IRunData,
|
||||
IRunExecutionData,
|
||||
ITaskData,
|
||||
|
@ -1344,23 +1343,16 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => {
|
|||
return;
|
||||
}
|
||||
|
||||
const activeExecution = activeExecutions.value[activeExecutionIndex];
|
||||
Object.assign(activeExecutions.value[activeExecutionIndex], {
|
||||
...(finishedActiveExecution.executionId !== undefined
|
||||
? { id: finishedActiveExecution.executionId }
|
||||
: {}),
|
||||
finished: finishedActiveExecution.data?.finished,
|
||||
stoppedAt: finishedActiveExecution.data?.stoppedAt,
|
||||
});
|
||||
|
||||
activeExecutions.value = [
|
||||
...activeExecutions.value.slice(0, activeExecutionIndex),
|
||||
{
|
||||
...activeExecution,
|
||||
...(finishedActiveExecution.executionId !== undefined
|
||||
? { id: finishedActiveExecution.executionId }
|
||||
: {}),
|
||||
finished: finishedActiveExecution.data.finished,
|
||||
stoppedAt: finishedActiveExecution.data.stoppedAt,
|
||||
},
|
||||
...activeExecutions.value.slice(activeExecutionIndex + 1),
|
||||
];
|
||||
|
||||
if (finishedActiveExecution.data && (finishedActiveExecution.data as IRun).data) {
|
||||
setWorkflowExecutionRunData((finishedActiveExecution.data as IRun).data);
|
||||
if (finishedActiveExecution.data?.data) {
|
||||
setWorkflowExecutionRunData(finishedActiveExecution.data.data);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "n8n-node-dev",
|
||||
"version": "1.64.0",
|
||||
"version": "1.65.0",
|
||||
"description": "CLI to simplify n8n credentials/node development",
|
||||
"main": "dist/src/index",
|
||||
"types": "dist/src/index.d.ts",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "n8n-nodes-base",
|
||||
"version": "1.64.0",
|
||||
"version": "1.65.0",
|
||||
"description": "Base nodes of n8n",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "n8n-workflow",
|
||||
"version": "1.63.0",
|
||||
"version": "1.64.0",
|
||||
"description": "Workflow base code of n8n",
|
||||
"main": "dist/index.js",
|
||||
"module": "src/index.ts",
|
||||
|
|
Loading…
Reference in a new issue