mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
Merge branch 'master' of github.com:n8n-io/n8n into ado-2808-1
This commit is contained in:
commit
cc97c6c026
2
.github/pull_request_template.md
vendored
2
.github/pull_request_template.md
vendored
|
@ -11,6 +11,8 @@ Photos and videos are recommended.
|
||||||
Include links to **Linear ticket** or Github issue or Community forum post.
|
Include links to **Linear ticket** or Github issue or Community forum post.
|
||||||
Important in order to close *automatically* and provide context to reviewers.
|
Important in order to close *automatically* and provide context to reviewers.
|
||||||
-->
|
-->
|
||||||
|
<!-- Use "closes #<issue-number>", "fixes #<issue-number>", or "resolves #<issue-number>" to automatically close issues when the PR is merged. -->
|
||||||
|
|
||||||
|
|
||||||
## Review / Merge checklist
|
## Review / Merge checklist
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ import {
|
||||||
import { getOptionalOutputParsers } from '../../../../../utils/output_parsers/N8nOutputParser';
|
import { getOptionalOutputParsers } from '../../../../../utils/output_parsers/N8nOutputParser';
|
||||||
import { throwIfToolSchema } from '../../../../../utils/schemaParsing';
|
import { throwIfToolSchema } from '../../../../../utils/schemaParsing';
|
||||||
import { getTracingConfig } from '../../../../../utils/tracing';
|
import { getTracingConfig } from '../../../../../utils/tracing';
|
||||||
import { extractParsedOutput } from '../utils';
|
import { checkForStructuredTools, extractParsedOutput } from '../utils';
|
||||||
|
|
||||||
export async function conversationalAgentExecute(
|
export async function conversationalAgentExecute(
|
||||||
this: IExecuteFunctions,
|
this: IExecuteFunctions,
|
||||||
|
@ -34,6 +34,8 @@ export async function conversationalAgentExecute(
|
||||||
const tools = await getConnectedTools(this, nodeVersion >= 1.5);
|
const tools = await getConnectedTools(this, nodeVersion >= 1.5);
|
||||||
const outputParsers = await getOptionalOutputParsers(this);
|
const outputParsers = await getOptionalOutputParsers(this);
|
||||||
|
|
||||||
|
await checkForStructuredTools(tools, this.getNode(), 'Conversational Agent');
|
||||||
|
|
||||||
// TODO: Make it possible in the future to use values for other items than just 0
|
// TODO: Make it possible in the future to use values for other items than just 0
|
||||||
const options = this.getNodeParameter('options', 0, {}) as {
|
const options = this.getNodeParameter('options', 0, {}) as {
|
||||||
systemMessage?: string;
|
systemMessage?: string;
|
||||||
|
|
|
@ -14,7 +14,7 @@ import { getConnectedTools, getPromptInputByType } from '../../../../../utils/he
|
||||||
import { getOptionalOutputParsers } from '../../../../../utils/output_parsers/N8nOutputParser';
|
import { getOptionalOutputParsers } from '../../../../../utils/output_parsers/N8nOutputParser';
|
||||||
import { throwIfToolSchema } from '../../../../../utils/schemaParsing';
|
import { throwIfToolSchema } from '../../../../../utils/schemaParsing';
|
||||||
import { getTracingConfig } from '../../../../../utils/tracing';
|
import { getTracingConfig } from '../../../../../utils/tracing';
|
||||||
import { extractParsedOutput } from '../utils';
|
import { checkForStructuredTools, extractParsedOutput } from '../utils';
|
||||||
|
|
||||||
export async function planAndExecuteAgentExecute(
|
export async function planAndExecuteAgentExecute(
|
||||||
this: IExecuteFunctions,
|
this: IExecuteFunctions,
|
||||||
|
@ -28,6 +28,7 @@ export async function planAndExecuteAgentExecute(
|
||||||
|
|
||||||
const tools = await getConnectedTools(this, nodeVersion >= 1.5);
|
const tools = await getConnectedTools(this, nodeVersion >= 1.5);
|
||||||
|
|
||||||
|
await checkForStructuredTools(tools, this.getNode(), 'Plan & Execute Agent');
|
||||||
const outputParsers = await getOptionalOutputParsers(this);
|
const outputParsers = await getOptionalOutputParsers(this);
|
||||||
|
|
||||||
const options = this.getNodeParameter('options', 0, {}) as {
|
const options = this.getNodeParameter('options', 0, {}) as {
|
||||||
|
|
|
@ -19,7 +19,7 @@ import {
|
||||||
import { getOptionalOutputParsers } from '../../../../../utils/output_parsers/N8nOutputParser';
|
import { getOptionalOutputParsers } from '../../../../../utils/output_parsers/N8nOutputParser';
|
||||||
import { throwIfToolSchema } from '../../../../../utils/schemaParsing';
|
import { throwIfToolSchema } from '../../../../../utils/schemaParsing';
|
||||||
import { getTracingConfig } from '../../../../../utils/tracing';
|
import { getTracingConfig } from '../../../../../utils/tracing';
|
||||||
import { extractParsedOutput } from '../utils';
|
import { checkForStructuredTools, extractParsedOutput } from '../utils';
|
||||||
|
|
||||||
export async function reActAgentAgentExecute(
|
export async function reActAgentAgentExecute(
|
||||||
this: IExecuteFunctions,
|
this: IExecuteFunctions,
|
||||||
|
@ -33,6 +33,8 @@ export async function reActAgentAgentExecute(
|
||||||
|
|
||||||
const tools = await getConnectedTools(this, nodeVersion >= 1.5);
|
const tools = await getConnectedTools(this, nodeVersion >= 1.5);
|
||||||
|
|
||||||
|
await checkForStructuredTools(tools, this.getNode(), 'ReAct Agent');
|
||||||
|
|
||||||
const outputParsers = await getOptionalOutputParsers(this);
|
const outputParsers = await getOptionalOutputParsers(this);
|
||||||
|
|
||||||
const options = this.getNodeParameter('options', 0, {}) as {
|
const options = this.getNodeParameter('options', 0, {}) as {
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
|
import type { ZodObjectAny } from '@langchain/core/dist/types/zod';
|
||||||
import type { BaseOutputParser } from '@langchain/core/output_parsers';
|
import type { BaseOutputParser } from '@langchain/core/output_parsers';
|
||||||
import type { IExecuteFunctions } from 'n8n-workflow';
|
import type { DynamicStructuredTool, Tool } from 'langchain/tools';
|
||||||
|
import { NodeOperationError, type IExecuteFunctions, type INode } from 'n8n-workflow';
|
||||||
|
|
||||||
export async function extractParsedOutput(
|
export async function extractParsedOutput(
|
||||||
ctx: IExecuteFunctions,
|
ctx: IExecuteFunctions,
|
||||||
|
@ -17,3 +19,24 @@ export async function extractParsedOutput(
|
||||||
// with fallback to the original output if it's not present
|
// with fallback to the original output if it's not present
|
||||||
return parsedOutput?.output ?? parsedOutput;
|
return parsedOutput?.output ?? parsedOutput;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function checkForStructuredTools(
|
||||||
|
tools: Array<Tool | DynamicStructuredTool<ZodObjectAny>>,
|
||||||
|
node: INode,
|
||||||
|
currentAgentType: string,
|
||||||
|
) {
|
||||||
|
const dynamicStructuredTools = tools.filter(
|
||||||
|
(tool) => tool.constructor.name === 'DynamicStructuredTool',
|
||||||
|
);
|
||||||
|
if (dynamicStructuredTools.length > 0) {
|
||||||
|
const getToolName = (tool: Tool | DynamicStructuredTool) => `"${tool.name}"`;
|
||||||
|
throw new NodeOperationError(
|
||||||
|
node,
|
||||||
|
`The selected tools are not supported by "${currentAgentType}", please use "Tools Agent" instead`,
|
||||||
|
{
|
||||||
|
itemIndex: 0,
|
||||||
|
description: `Incompatible connected tools: ${dynamicStructuredTools.map(getToolName).join(', ')}`,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,106 @@
|
||||||
|
import type { Tool } from 'langchain/tools';
|
||||||
|
import { DynamicStructuredTool } from 'langchain/tools';
|
||||||
|
import { NodeOperationError } from 'n8n-workflow';
|
||||||
|
import type { INode } from 'n8n-workflow';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import { checkForStructuredTools } from '../agents/utils';
|
||||||
|
|
||||||
|
describe('checkForStructuredTools', () => {
|
||||||
|
let mockNode: INode;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mockNode = {
|
||||||
|
id: 'test-node',
|
||||||
|
name: 'Test Node',
|
||||||
|
type: 'test',
|
||||||
|
typeVersion: 1,
|
||||||
|
position: [0, 0],
|
||||||
|
parameters: {},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not throw error when no DynamicStructuredTools are present', async () => {
|
||||||
|
const tools = [
|
||||||
|
{
|
||||||
|
name: 'regular-tool',
|
||||||
|
constructor: { name: 'Tool' },
|
||||||
|
} as Tool,
|
||||||
|
];
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
checkForStructuredTools(tools, mockNode, 'Conversation Agent'),
|
||||||
|
).resolves.not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw NodeOperationError when DynamicStructuredTools are present', async () => {
|
||||||
|
const dynamicTool = new DynamicStructuredTool({
|
||||||
|
name: 'dynamic-tool',
|
||||||
|
description: 'test tool',
|
||||||
|
schema: z.object({}),
|
||||||
|
func: async () => 'result',
|
||||||
|
});
|
||||||
|
|
||||||
|
const tools: Array<Tool | DynamicStructuredTool> = [dynamicTool];
|
||||||
|
|
||||||
|
await expect(checkForStructuredTools(tools, mockNode, 'Conversation Agent')).rejects.toThrow(
|
||||||
|
NodeOperationError,
|
||||||
|
);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
checkForStructuredTools(tools, mockNode, 'Conversation Agent'),
|
||||||
|
).rejects.toMatchObject({
|
||||||
|
message:
|
||||||
|
'The selected tools are not supported by "Conversation Agent", please use "Tools Agent" instead',
|
||||||
|
description: 'Incompatible connected tools: "dynamic-tool"',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should list multiple dynamic tools in error message', async () => {
|
||||||
|
const dynamicTool1 = new DynamicStructuredTool({
|
||||||
|
name: 'dynamic-tool-1',
|
||||||
|
description: 'test tool 1',
|
||||||
|
schema: z.object({}),
|
||||||
|
func: async () => 'result',
|
||||||
|
});
|
||||||
|
|
||||||
|
const dynamicTool2 = new DynamicStructuredTool({
|
||||||
|
name: 'dynamic-tool-2',
|
||||||
|
description: 'test tool 2',
|
||||||
|
schema: z.object({}),
|
||||||
|
func: async () => 'result',
|
||||||
|
});
|
||||||
|
|
||||||
|
const tools = [dynamicTool1, dynamicTool2];
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
checkForStructuredTools(tools, mockNode, 'Conversation Agent'),
|
||||||
|
).rejects.toMatchObject({
|
||||||
|
description: 'Incompatible connected tools: "dynamic-tool-1", "dynamic-tool-2"',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw error with mixed tool types and list only dynamic tools in error message', async () => {
|
||||||
|
const regularTool = {
|
||||||
|
name: 'regular-tool',
|
||||||
|
constructor: { name: 'Tool' },
|
||||||
|
} as Tool;
|
||||||
|
|
||||||
|
const dynamicTool = new DynamicStructuredTool({
|
||||||
|
name: 'dynamic-tool',
|
||||||
|
description: 'test tool',
|
||||||
|
schema: z.object({}),
|
||||||
|
func: async () => 'result',
|
||||||
|
});
|
||||||
|
|
||||||
|
const tools = [regularTool, dynamicTool];
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
checkForStructuredTools(tools, mockNode, 'Conversation Agent'),
|
||||||
|
).rejects.toMatchObject({
|
||||||
|
message:
|
||||||
|
'The selected tools are not supported by "Conversation Agent", please use "Tools Agent" instead',
|
||||||
|
description: 'Incompatible connected tools: "dynamic-tool"',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -390,7 +390,15 @@ export class LoadNodesAndCredentials {
|
||||||
const toWatch = loader.isLazyLoaded
|
const toWatch = loader.isLazyLoaded
|
||||||
? ['**/nodes.json', '**/credentials.json']
|
? ['**/nodes.json', '**/credentials.json']
|
||||||
: ['**/*.js', '**/*.json'];
|
: ['**/*.js', '**/*.json'];
|
||||||
watch(toWatch, { cwd: realModulePath }).on('change', reloader);
|
const files = await glob(toWatch, {
|
||||||
|
cwd: realModulePath,
|
||||||
|
ignore: ['node_modules/**'],
|
||||||
|
});
|
||||||
|
const watcher = watch(files, {
|
||||||
|
cwd: realModulePath,
|
||||||
|
ignoreInitial: true,
|
||||||
|
});
|
||||||
|
watcher.on('add', reloader).on('change', reloader).on('unlink', reloader);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,18 +5,22 @@ import type { TaskRunner } from '../task-broker.service';
|
||||||
export class TaskRunnerOomError extends ApplicationError {
|
export class TaskRunnerOomError extends ApplicationError {
|
||||||
public description: string;
|
public description: string;
|
||||||
|
|
||||||
constructor(runnerId: TaskRunner['id'], isCloudDeployment: boolean) {
|
constructor(
|
||||||
super(`Task runner (${runnerId}) ran out of memory.`, { level: 'error' });
|
public readonly runnerId: TaskRunner['id'],
|
||||||
|
isCloudDeployment: boolean,
|
||||||
|
) {
|
||||||
|
super('Node ran out of memory.', { level: 'error' });
|
||||||
|
|
||||||
const fixSuggestions = {
|
const fixSuggestions = {
|
||||||
reduceItems: 'Reduce the number of items processed at a time by batching the input.',
|
reduceItems:
|
||||||
|
'Reduce the number of items processed at a time, by batching them using a loop node',
|
||||||
increaseMemory:
|
increaseMemory:
|
||||||
"Increase the memory available to the task runner with 'N8N_RUNNERS_MAX_OLD_SPACE_SIZE' environment variable.",
|
"Increase the memory available to the task runner with 'N8N_RUNNERS_MAX_OLD_SPACE_SIZE' environment variable",
|
||||||
upgradePlan: 'Upgrade your cloud plan to increase the available memory.',
|
upgradePlan: 'Upgrade your cloud plan to increase the available memory',
|
||||||
};
|
};
|
||||||
|
|
||||||
const subtitle =
|
const subtitle =
|
||||||
'The runner executing the code ran out of memory. This usually happens when there are too many items to process. You can try the following:';
|
'This usually happens when there are too many items to process. You can try the following:';
|
||||||
const suggestions = isCloudDeployment
|
const suggestions = isCloudDeployment
|
||||||
? [fixSuggestions.reduceItems, fixSuggestions.upgradePlan]
|
? [fixSuggestions.reduceItems, fixSuggestions.upgradePlan]
|
||||||
: [fixSuggestions.reduceItems, fixSuggestions.increaseMemory];
|
: [fixSuggestions.reduceItems, fixSuggestions.increaseMemory];
|
||||||
|
|
|
@ -635,7 +635,6 @@ export type WorkflowCallerPolicyDefaultOption = 'any' | 'none' | 'workflowsFromA
|
||||||
|
|
||||||
export interface IWorkflowSettings extends IWorkflowSettingsWorkflow {
|
export interface IWorkflowSettings extends IWorkflowSettingsWorkflow {
|
||||||
errorWorkflow?: string;
|
errorWorkflow?: string;
|
||||||
saveManualExecutions?: boolean;
|
|
||||||
timezone?: string;
|
timezone?: string;
|
||||||
executionTimeout?: number;
|
executionTimeout?: number;
|
||||||
maxExecutionTimeout?: number;
|
maxExecutionTimeout?: number;
|
||||||
|
@ -1594,3 +1593,44 @@ export type ApiKey = {
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type InputPanel = {
|
||||||
|
displayMode: IRunDataDisplayMode;
|
||||||
|
nodeName?: string;
|
||||||
|
run?: number;
|
||||||
|
branch?: number;
|
||||||
|
data: {
|
||||||
|
isEmpty: boolean;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type OutputPanel = {
|
||||||
|
branch?: number;
|
||||||
|
displayMode: IRunDataDisplayMode;
|
||||||
|
data: {
|
||||||
|
isEmpty: boolean;
|
||||||
|
};
|
||||||
|
editMode: {
|
||||||
|
enabled: boolean;
|
||||||
|
value: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Draggable = {
|
||||||
|
isDragging: boolean;
|
||||||
|
type: string;
|
||||||
|
data: string;
|
||||||
|
dimensions: DOMRect | null;
|
||||||
|
activeTarget: { id: string; stickyPosition: null | XYPosition } | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type MainPanelType = 'regular' | 'dragless' | 'inputless' | 'unknown' | 'wide';
|
||||||
|
|
||||||
|
export type MainPanelDimensions = Record<
|
||||||
|
MainPanelType,
|
||||||
|
{
|
||||||
|
relativeLeft: number;
|
||||||
|
relativeRight: number;
|
||||||
|
relativeWidth: number;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
|
|
@ -63,8 +63,8 @@ export const defaultSettings: FrontendSettings = {
|
||||||
},
|
},
|
||||||
publicApi: { enabled: false, latestVersion: 0, path: '', swaggerUi: { enabled: false } },
|
publicApi: { enabled: false, latestVersion: 0, path: '', swaggerUi: { enabled: false } },
|
||||||
pushBackend: 'websocket',
|
pushBackend: 'websocket',
|
||||||
saveDataErrorExecution: 'DEFAULT',
|
saveDataErrorExecution: 'all',
|
||||||
saveDataSuccessExecution: 'DEFAULT',
|
saveDataSuccessExecution: 'all',
|
||||||
saveManualExecutions: false,
|
saveManualExecutions: false,
|
||||||
saveExecutionProgress: false,
|
saveExecutionProgress: false,
|
||||||
sso: {
|
sso: {
|
||||||
|
|
|
@ -26,7 +26,8 @@ describe('InlineExpressionTip.vue', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockNdvState = {
|
mockNdvState = {
|
||||||
hasInputData: true,
|
hasInputData: true,
|
||||||
isNDVDataEmpty: vi.fn(() => true),
|
isInputPanelEmpty: true,
|
||||||
|
isOutputPanelEmpty: true,
|
||||||
setHighlightDraggables: vi.fn(),
|
setHighlightDraggables: vi.fn(),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -42,7 +43,8 @@ describe('InlineExpressionTip.vue', () => {
|
||||||
test('should show the drag-n-drop tip', async () => {
|
test('should show the drag-n-drop tip', async () => {
|
||||||
mockNdvState = {
|
mockNdvState = {
|
||||||
hasInputData: true,
|
hasInputData: true,
|
||||||
isNDVDataEmpty: vi.fn(() => false),
|
isInputPanelEmpty: false,
|
||||||
|
isOutputPanelEmpty: false,
|
||||||
focusedMappableInput: 'Some Input',
|
focusedMappableInput: 'Some Input',
|
||||||
setHighlightDraggables: vi.fn(),
|
setHighlightDraggables: vi.fn(),
|
||||||
};
|
};
|
||||||
|
@ -62,7 +64,8 @@ describe('InlineExpressionTip.vue', () => {
|
||||||
mockNdvState = {
|
mockNdvState = {
|
||||||
hasInputData: false,
|
hasInputData: false,
|
||||||
isInputParentOfActiveNode: true,
|
isInputParentOfActiveNode: true,
|
||||||
isNDVDataEmpty: vi.fn(() => false),
|
isInputPanelEmpty: false,
|
||||||
|
isOutputPanelEmpty: false,
|
||||||
focusedMappableInput: 'Some Input',
|
focusedMappableInput: 'Some Input',
|
||||||
setHighlightDraggables: vi.fn(),
|
setHighlightDraggables: vi.fn(),
|
||||||
};
|
};
|
||||||
|
@ -77,7 +80,8 @@ describe('InlineExpressionTip.vue', () => {
|
||||||
test('should show the correct tip for objects', async () => {
|
test('should show the correct tip for objects', async () => {
|
||||||
mockNdvState = {
|
mockNdvState = {
|
||||||
hasInputData: true,
|
hasInputData: true,
|
||||||
isNDVDataEmpty: vi.fn(() => false),
|
isInputPanelEmpty: false,
|
||||||
|
isOutputPanelEmpty: false,
|
||||||
focusedMappableInput: 'Some Input',
|
focusedMappableInput: 'Some Input',
|
||||||
setHighlightDraggables: vi.fn(),
|
setHighlightDraggables: vi.fn(),
|
||||||
};
|
};
|
||||||
|
@ -106,7 +110,8 @@ describe('InlineExpressionTip.vue', () => {
|
||||||
test('should show the correct tip for primitives', async () => {
|
test('should show the correct tip for primitives', async () => {
|
||||||
mockNdvState = {
|
mockNdvState = {
|
||||||
hasInputData: true,
|
hasInputData: true,
|
||||||
isNDVDataEmpty: vi.fn(() => false),
|
isInputPanelEmpty: false,
|
||||||
|
isOutputPanelEmpty: false,
|
||||||
focusedMappableInput: 'Some Input',
|
focusedMappableInput: 'Some Input',
|
||||||
setHighlightDraggables: vi.fn(),
|
setHighlightDraggables: vi.fn(),
|
||||||
};
|
};
|
||||||
|
|
|
@ -30,7 +30,7 @@ const canAddDotToExpression = ref(false);
|
||||||
const resolvedExpressionHasFields = ref(false);
|
const resolvedExpressionHasFields = ref(false);
|
||||||
|
|
||||||
const canDragToFocusedInput = computed(
|
const canDragToFocusedInput = computed(
|
||||||
() => !ndvStore.isNDVDataEmpty('input') && ndvStore.focusedMappableInput,
|
() => !ndvStore.isInputPanelEmpty && ndvStore.focusedMappableInput,
|
||||||
);
|
);
|
||||||
|
|
||||||
const emptyExpression = computed(() => props.unresolvedExpression.trim().length === 0);
|
const emptyExpression = computed(() => props.unresolvedExpression.trim().length === 0);
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { useNDVStore } from '@/stores/ndv.store';
|
||||||
import { ndvEventBus } from '@/event-bus';
|
import { ndvEventBus } from '@/event-bus';
|
||||||
import NDVFloatingNodes from '@/components/NDVFloatingNodes.vue';
|
import NDVFloatingNodes from '@/components/NDVFloatingNodes.vue';
|
||||||
import { useDebounce } from '@/composables/useDebounce';
|
import { useDebounce } from '@/composables/useDebounce';
|
||||||
import type { XYPosition } from '@/Interface';
|
import type { MainPanelType, XYPosition } from '@/Interface';
|
||||||
import { ref, onMounted, onBeforeUnmount, computed, watch } from 'vue';
|
import { ref, onMounted, onBeforeUnmount, computed, watch } from 'vue';
|
||||||
import { useUIStore } from '@/stores/ui.store';
|
import { useUIStore } from '@/stores/ui.store';
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ const PANEL_WIDTH = 350;
|
||||||
const PANEL_WIDTH_LARGE = 420;
|
const PANEL_WIDTH_LARGE = 420;
|
||||||
const MIN_WINDOW_WIDTH = 2 * (SIDE_MARGIN + SIDE_PANELS_MARGIN) + MIN_PANEL_WIDTH;
|
const MIN_WINDOW_WIDTH = 2 * (SIDE_MARGIN + SIDE_PANELS_MARGIN) + MIN_PANEL_WIDTH;
|
||||||
|
|
||||||
const initialMainPanelWidth: { [key: string]: number } = {
|
const initialMainPanelWidth: Record<MainPanelType, number> = {
|
||||||
regular: MAIN_NODE_PANEL_WIDTH,
|
regular: MAIN_NODE_PANEL_WIDTH,
|
||||||
dragless: MAIN_NODE_PANEL_WIDTH,
|
dragless: MAIN_NODE_PANEL_WIDTH,
|
||||||
unknown: MAIN_NODE_PANEL_WIDTH,
|
unknown: MAIN_NODE_PANEL_WIDTH,
|
||||||
|
@ -106,22 +106,16 @@ watch(containerWidth, (width) => {
|
||||||
setPositions(mainPanelDimensions.value.relativeLeft);
|
setPositions(mainPanelDimensions.value.relativeLeft);
|
||||||
});
|
});
|
||||||
|
|
||||||
const currentNodePaneType = computed((): string => {
|
const currentNodePaneType = computed((): MainPanelType => {
|
||||||
if (!hasInputSlot.value) return 'inputless';
|
if (!hasInputSlot.value) return 'inputless';
|
||||||
if (!props.isDraggable) return 'dragless';
|
if (!props.isDraggable) return 'dragless';
|
||||||
if (props.nodeType === null) return 'unknown';
|
if (props.nodeType === null) return 'unknown';
|
||||||
return props.nodeType.parameterPane ?? 'regular';
|
return props.nodeType.parameterPane ?? 'regular';
|
||||||
});
|
});
|
||||||
|
|
||||||
const mainPanelDimensions = computed(
|
const mainPanelDimensions = computed(() => {
|
||||||
(): {
|
return ndvStore.mainPanelDimensions[currentNodePaneType.value];
|
||||||
relativeWidth: number;
|
});
|
||||||
relativeLeft: number;
|
|
||||||
relativeRight: number;
|
|
||||||
} => {
|
|
||||||
return ndvStore.getMainPanelDimensions(currentNodePaneType.value);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const calculatedPositions = computed(
|
const calculatedPositions = computed(
|
||||||
(): { inputPanelRelativeRight: number; outputPanelRelativeLeft: number } => {
|
(): { inputPanelRelativeRight: number; outputPanelRelativeLeft: number } => {
|
||||||
|
|
|
@ -141,6 +141,10 @@ export function AIView(_nodes: SimplifiedNodeType[]): NodeView {
|
||||||
const chainNodes = getAiNodesBySubcategory(nodeTypesStore.allLatestNodeTypes, AI_CATEGORY_CHAINS);
|
const chainNodes = getAiNodesBySubcategory(nodeTypesStore.allLatestNodeTypes, AI_CATEGORY_CHAINS);
|
||||||
const agentNodes = getAiNodesBySubcategory(nodeTypesStore.allLatestNodeTypes, AI_CATEGORY_AGENTS);
|
const agentNodes = getAiNodesBySubcategory(nodeTypesStore.allLatestNodeTypes, AI_CATEGORY_AGENTS);
|
||||||
|
|
||||||
|
const websiteCategoryURL = templatesStore.websiteTemplateRepositoryParameters;
|
||||||
|
|
||||||
|
websiteCategoryURL.append('utm_user_role', 'AdvancedAI');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
value: AI_NODE_CREATOR_VIEW,
|
value: AI_NODE_CREATOR_VIEW,
|
||||||
title: i18n.baseText('nodeCreator.aiPanel.aiNodes'),
|
title: i18n.baseText('nodeCreator.aiPanel.aiNodes'),
|
||||||
|
@ -154,7 +158,7 @@ export function AIView(_nodes: SimplifiedNodeType[]): NodeView {
|
||||||
icon: 'box-open',
|
icon: 'box-open',
|
||||||
description: i18n.baseText('nodeCreator.aiPanel.linkItem.description'),
|
description: i18n.baseText('nodeCreator.aiPanel.linkItem.description'),
|
||||||
name: 'ai_templates_root',
|
name: 'ai_templates_root',
|
||||||
url: templatesStore.getWebsiteCategoryURL(undefined, 'AdvancedAI'),
|
url: websiteCategoryURL.toString(),
|
||||||
tag: {
|
tag: {
|
||||||
type: 'info',
|
type: 'info',
|
||||||
text: i18n.baseText('nodeCreator.triggerHelperPanel.manualTriggerTag'),
|
text: i18n.baseText('nodeCreator.triggerHelperPanel.manualTriggerTag'),
|
||||||
|
|
|
@ -316,7 +316,7 @@ async function onClick() {
|
||||||
codeGenerationInProgress.value = false;
|
codeGenerationInProgress.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isChatNode.value || (isChatChild.value && ndvStore.isNDVDataEmpty('input'))) {
|
if (isChatNode.value || (isChatChild.value && ndvStore.isInputPanelEmpty)) {
|
||||||
ndvStore.setActiveNodeName(null);
|
ndvStore.setActiveNodeName(null);
|
||||||
nodeViewEventBus.emit('openChat');
|
nodeViewEventBus.emit('openChat');
|
||||||
} else if (isListeningForEvents.value) {
|
} else if (isListeningForEvents.value) {
|
||||||
|
|
|
@ -78,7 +78,7 @@ const { isSubNodeType } = useNodeType({
|
||||||
});
|
});
|
||||||
const pinnedData = usePinnedData(activeNode, {
|
const pinnedData = usePinnedData(activeNode, {
|
||||||
runIndex: props.runIndex,
|
runIndex: props.runIndex,
|
||||||
displayMode: ndvStore.getPanelDisplayMode('output'),
|
displayMode: ndvStore.outputPanelDisplayMode,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Data
|
// Data
|
||||||
|
|
|
@ -54,7 +54,8 @@ describe('ParameterInput.vue', () => {
|
||||||
type: 'test',
|
type: 'test',
|
||||||
typeVersion: 1,
|
typeVersion: 1,
|
||||||
},
|
},
|
||||||
isNDVDataEmpty: vi.fn(() => false),
|
isInputPanelEmpty: false,
|
||||||
|
isOutputPanelEmpty: false,
|
||||||
};
|
};
|
||||||
mockNodeTypesState = {
|
mockNodeTypesState = {
|
||||||
allNodeTypes: [],
|
allNodeTypes: [],
|
||||||
|
|
|
@ -523,7 +523,7 @@ const isHtmlNode = computed(() => !!node.value && node.value.type === HTML_NODE_
|
||||||
const isInputTypeString = computed(() => props.parameter.type === 'string');
|
const isInputTypeString = computed(() => props.parameter.type === 'string');
|
||||||
const isInputTypeNumber = computed(() => props.parameter.type === 'number');
|
const isInputTypeNumber = computed(() => props.parameter.type === 'number');
|
||||||
|
|
||||||
const isInputDataEmpty = computed(() => ndvStore.isNDVDataEmpty('input'));
|
const isInputDataEmpty = computed(() => ndvStore.isInputPanelEmpty);
|
||||||
const isDropDisabled = computed(
|
const isDropDisabled = computed(
|
||||||
() =>
|
() =>
|
||||||
props.parameter.noDataExpression ||
|
props.parameter.noDataExpression ||
|
||||||
|
|
|
@ -187,12 +187,17 @@ const node = toRef(props, 'node');
|
||||||
|
|
||||||
const pinnedData = usePinnedData(node, {
|
const pinnedData = usePinnedData(node, {
|
||||||
runIndex: props.runIndex,
|
runIndex: props.runIndex,
|
||||||
displayMode: ndvStore.getPanelDisplayMode(props.paneType),
|
displayMode:
|
||||||
|
props.paneType === 'input' ? ndvStore.inputPanelDisplayMode : ndvStore.outputPanelDisplayMode,
|
||||||
});
|
});
|
||||||
const { isSubNodeType } = useNodeType({
|
const { isSubNodeType } = useNodeType({
|
||||||
node,
|
node,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const displayMode = computed(() =>
|
||||||
|
props.paneType === 'input' ? ndvStore.inputPanelDisplayMode : ndvStore.outputPanelDisplayMode,
|
||||||
|
);
|
||||||
|
|
||||||
const isReadOnlyRoute = computed(() => route.meta.readOnlyCanvas === true);
|
const isReadOnlyRoute = computed(() => route.meta.readOnlyCanvas === true);
|
||||||
const isWaitNodeWaiting = computed(
|
const isWaitNodeWaiting = computed(
|
||||||
() =>
|
() =>
|
||||||
|
@ -202,7 +207,6 @@ const isWaitNodeWaiting = computed(
|
||||||
);
|
);
|
||||||
|
|
||||||
const { activeNode } = storeToRefs(ndvStore);
|
const { activeNode } = storeToRefs(ndvStore);
|
||||||
const displayMode = computed(() => ndvStore.getPanelDisplayMode(props.paneType));
|
|
||||||
const nodeType = computed(() => {
|
const nodeType = computed(() => {
|
||||||
if (!node.value) return null;
|
if (!node.value) return null;
|
||||||
|
|
||||||
|
|
|
@ -145,4 +145,50 @@ describe('WorkflowSettingsVue', () => {
|
||||||
|
|
||||||
expect(getByTestId('workflow-caller-policy-workflow-ids')).toHaveValue(cleanedUpWorkflowList);
|
expect(getByTestId('workflow-caller-policy-workflow-ids')).toHaveValue(cleanedUpWorkflowList);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test.each([
|
||||||
|
['workflow-settings-save-failed-executions', 'Default - Save', () => {}],
|
||||||
|
[
|
||||||
|
'workflow-settings-save-failed-executions',
|
||||||
|
'Default - Do not save',
|
||||||
|
() => {
|
||||||
|
settingsStore.saveDataErrorExecution = 'none';
|
||||||
|
},
|
||||||
|
],
|
||||||
|
['workflow-settings-save-success-executions', 'Default - Save', () => {}],
|
||||||
|
[
|
||||||
|
'workflow-settings-save-success-executions',
|
||||||
|
'Default - Do not save',
|
||||||
|
() => {
|
||||||
|
settingsStore.saveDataSuccessExecution = 'none';
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'workflow-settings-save-manual-executions',
|
||||||
|
'Default - Save',
|
||||||
|
() => {
|
||||||
|
settingsStore.saveManualExecutions = true;
|
||||||
|
},
|
||||||
|
],
|
||||||
|
['workflow-settings-save-manual-executions', 'Default - Do not save', () => {}],
|
||||||
|
[
|
||||||
|
'workflow-settings-save-execution-progress',
|
||||||
|
'Default - Save',
|
||||||
|
() => {
|
||||||
|
settingsStore.saveDataProgressExecution = true;
|
||||||
|
},
|
||||||
|
],
|
||||||
|
['workflow-settings-save-execution-progress', 'Default - Do not save', () => {}],
|
||||||
|
])(
|
||||||
|
'should show %s dropdown correct default value as %s',
|
||||||
|
async (testId, optionText, storeSetter) => {
|
||||||
|
storeSetter();
|
||||||
|
const { getByTestId } = createComponent({ pinia });
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
const dropdownItems = await getDropdownItems(getByTestId(testId));
|
||||||
|
|
||||||
|
expect(dropdownItems[0]).toHaveTextContent(optionText);
|
||||||
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -70,14 +70,14 @@ const helpTexts = computed(() => ({
|
||||||
workflowCallerPolicy: i18n.baseText('workflowSettings.helpTexts.workflowCallerPolicy'),
|
workflowCallerPolicy: i18n.baseText('workflowSettings.helpTexts.workflowCallerPolicy'),
|
||||||
workflowCallerIds: i18n.baseText('workflowSettings.helpTexts.workflowCallerIds'),
|
workflowCallerIds: i18n.baseText('workflowSettings.helpTexts.workflowCallerIds'),
|
||||||
}));
|
}));
|
||||||
const defaultValues = computed(() => ({
|
const defaultValues = ref({
|
||||||
timezone: 'America/New_York',
|
timezone: 'America/New_York',
|
||||||
saveDataErrorExecution: 'all',
|
saveDataErrorExecution: 'all',
|
||||||
saveDataSuccessExecution: 'all',
|
saveDataSuccessExecution: 'all',
|
||||||
saveExecutionProgress: false,
|
saveExecutionProgress: false,
|
||||||
saveManualExecutions: false,
|
saveManualExecutions: false,
|
||||||
workflowCallerPolicy: 'workflowsFromSameOwner',
|
workflowCallerPolicy: 'workflowsFromSameOwner',
|
||||||
}));
|
});
|
||||||
const readOnlyEnv = computed(() => sourceControlStore.preferences.branchReadOnly);
|
const readOnlyEnv = computed(() => sourceControlStore.preferences.branchReadOnly);
|
||||||
const workflowName = computed(() => workflowsStore.workflowName);
|
const workflowName = computed(() => workflowsStore.workflowName);
|
||||||
const workflowId = computed(() => workflowsStore.workflowId);
|
const workflowId = computed(() => workflowsStore.workflowId);
|
||||||
|
@ -145,8 +145,7 @@ const loadWorkflowCallerPolicyOptions = async () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadSaveDataErrorExecutionOptions = async () => {
|
const loadSaveDataErrorExecutionOptions = async () => {
|
||||||
saveDataErrorExecutionOptions.value.length = 0;
|
saveDataErrorExecutionOptions.value = [
|
||||||
saveDataErrorExecutionOptions.value.push.apply(saveDataErrorExecutionOptions.value, [
|
|
||||||
{
|
{
|
||||||
key: 'DEFAULT',
|
key: 'DEFAULT',
|
||||||
value: i18n.baseText('workflowSettings.saveDataErrorExecutionOptions.defaultSave', {
|
value: i18n.baseText('workflowSettings.saveDataErrorExecutionOptions.defaultSave', {
|
||||||
|
@ -166,12 +165,11 @@ const loadSaveDataErrorExecutionOptions = async () => {
|
||||||
key: 'none',
|
key: 'none',
|
||||||
value: i18n.baseText('workflowSettings.saveDataErrorExecutionOptions.doNotSave'),
|
value: i18n.baseText('workflowSettings.saveDataErrorExecutionOptions.doNotSave'),
|
||||||
},
|
},
|
||||||
]);
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadSaveDataSuccessExecutionOptions = async () => {
|
const loadSaveDataSuccessExecutionOptions = async () => {
|
||||||
saveDataSuccessExecutionOptions.value.length = 0;
|
saveDataSuccessExecutionOptions.value = [
|
||||||
saveDataSuccessExecutionOptions.value.push.apply(saveDataSuccessExecutionOptions.value, [
|
|
||||||
{
|
{
|
||||||
key: 'DEFAULT',
|
key: 'DEFAULT',
|
||||||
value: i18n.baseText('workflowSettings.saveDataSuccessExecutionOptions.defaultSave', {
|
value: i18n.baseText('workflowSettings.saveDataSuccessExecutionOptions.defaultSave', {
|
||||||
|
@ -191,12 +189,11 @@ const loadSaveDataSuccessExecutionOptions = async () => {
|
||||||
key: 'none',
|
key: 'none',
|
||||||
value: i18n.baseText('workflowSettings.saveDataSuccessExecutionOptions.doNotSave'),
|
value: i18n.baseText('workflowSettings.saveDataSuccessExecutionOptions.doNotSave'),
|
||||||
},
|
},
|
||||||
]);
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadSaveExecutionProgressOptions = async () => {
|
const loadSaveExecutionProgressOptions = async () => {
|
||||||
saveExecutionProgressOptions.value.length = 0;
|
saveExecutionProgressOptions.value = [
|
||||||
saveExecutionProgressOptions.value.push.apply(saveExecutionProgressOptions.value, [
|
|
||||||
{
|
{
|
||||||
key: 'DEFAULT',
|
key: 'DEFAULT',
|
||||||
value: i18n.baseText('workflowSettings.saveExecutionProgressOptions.defaultSave', {
|
value: i18n.baseText('workflowSettings.saveExecutionProgressOptions.defaultSave', {
|
||||||
|
@ -215,12 +212,12 @@ const loadSaveExecutionProgressOptions = async () => {
|
||||||
key: false,
|
key: false,
|
||||||
value: i18n.baseText('workflowSettings.saveExecutionProgressOptions.doNotSave'),
|
value: i18n.baseText('workflowSettings.saveExecutionProgressOptions.doNotSave'),
|
||||||
},
|
},
|
||||||
]);
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadSaveManualOptions = async () => {
|
const loadSaveManualOptions = async () => {
|
||||||
saveManualOptions.value.length = 0;
|
saveManualOptions.value = [
|
||||||
saveManualOptions.value.push({
|
{
|
||||||
key: 'DEFAULT',
|
key: 'DEFAULT',
|
||||||
value: i18n.baseText('workflowSettings.saveManualOptions.defaultSave', {
|
value: i18n.baseText('workflowSettings.saveManualOptions.defaultSave', {
|
||||||
interpolate: {
|
interpolate: {
|
||||||
|
@ -229,15 +226,16 @@ const loadSaveManualOptions = async () => {
|
||||||
: i18n.baseText('workflowSettings.saveManualOptions.doNotSave'),
|
: i18n.baseText('workflowSettings.saveManualOptions.doNotSave'),
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
});
|
},
|
||||||
saveManualOptions.value.push({
|
{
|
||||||
key: true,
|
key: true,
|
||||||
value: i18n.baseText('workflowSettings.saveManualOptions.save'),
|
value: i18n.baseText('workflowSettings.saveManualOptions.save'),
|
||||||
});
|
},
|
||||||
saveManualOptions.value.push({
|
{
|
||||||
key: false,
|
key: false,
|
||||||
value: i18n.baseText('workflowSettings.saveManualOptions.doNotSave'),
|
value: i18n.baseText('workflowSettings.saveManualOptions.doNotSave'),
|
||||||
});
|
},
|
||||||
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadTimezones = async () => {
|
const loadTimezones = async () => {
|
||||||
|
@ -400,6 +398,7 @@ onMounted(async () => {
|
||||||
defaultValues.value.saveDataErrorExecution = settingsStore.saveDataErrorExecution;
|
defaultValues.value.saveDataErrorExecution = settingsStore.saveDataErrorExecution;
|
||||||
defaultValues.value.saveDataSuccessExecution = settingsStore.saveDataSuccessExecution;
|
defaultValues.value.saveDataSuccessExecution = settingsStore.saveDataSuccessExecution;
|
||||||
defaultValues.value.saveManualExecutions = settingsStore.saveManualExecutions;
|
defaultValues.value.saveManualExecutions = settingsStore.saveManualExecutions;
|
||||||
|
defaultValues.value.saveExecutionProgress = settingsStore.saveDataProgressExecution;
|
||||||
defaultValues.value.timezone = rootStore.timezone;
|
defaultValues.value.timezone = rootStore.timezone;
|
||||||
defaultValues.value.workflowCallerPolicy = settingsStore.workflowCallerPolicyDefaultOption;
|
defaultValues.value.workflowCallerPolicy = settingsStore.workflowCallerPolicyDefaultOption;
|
||||||
|
|
||||||
|
@ -423,7 +422,7 @@ onMounted(async () => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const workflowSettingsData = deepCopy(workflowsStore.workflowSettings) as IWorkflowSettings;
|
const workflowSettingsData = deepCopy(workflowsStore.workflowSettings);
|
||||||
|
|
||||||
if (workflowSettingsData.timezone === undefined) {
|
if (workflowSettingsData.timezone === undefined) {
|
||||||
workflowSettingsData.timezone = 'DEFAULT';
|
workflowSettingsData.timezone = 'DEFAULT';
|
||||||
|
@ -438,7 +437,7 @@ onMounted(async () => {
|
||||||
workflowSettingsData.saveExecutionProgress = 'DEFAULT';
|
workflowSettingsData.saveExecutionProgress = 'DEFAULT';
|
||||||
}
|
}
|
||||||
if (workflowSettingsData.saveManualExecutions === undefined) {
|
if (workflowSettingsData.saveManualExecutions === undefined) {
|
||||||
workflowSettingsData.saveManualExecutions = defaultValues.value.saveManualExecutions;
|
workflowSettingsData.saveManualExecutions = 'DEFAULT';
|
||||||
}
|
}
|
||||||
if (workflowSettingsData.callerPolicy === undefined) {
|
if (workflowSettingsData.callerPolicy === undefined) {
|
||||||
workflowSettingsData.callerPolicy = defaultValues.value
|
workflowSettingsData.callerPolicy = defaultValues.value
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
import type {
|
import type {
|
||||||
INodeUi,
|
Draggable,
|
||||||
|
InputPanel,
|
||||||
IRunDataDisplayMode,
|
IRunDataDisplayMode,
|
||||||
|
MainPanelDimensions,
|
||||||
|
MainPanelType,
|
||||||
NDVState,
|
NDVState,
|
||||||
NodePanelType,
|
NodePanelType,
|
||||||
|
OutputPanel,
|
||||||
TargetItem,
|
TargetItem,
|
||||||
XYPosition,
|
|
||||||
} from '@/Interface';
|
} from '@/Interface';
|
||||||
import { useStorage } from '@/composables/useStorage';
|
import { useStorage } from '@/composables/useStorage';
|
||||||
import {
|
import {
|
||||||
|
@ -13,18 +16,34 @@ import {
|
||||||
LOCAL_STORAGE_TABLE_HOVER_IS_ONBOARDED,
|
LOCAL_STORAGE_TABLE_HOVER_IS_ONBOARDED,
|
||||||
STORES,
|
STORES,
|
||||||
} from '@/constants';
|
} from '@/constants';
|
||||||
import type { INodeExecutionData, INodeIssues } from 'n8n-workflow';
|
import type { INodeIssues } from 'n8n-workflow';
|
||||||
import { NodeConnectionType } from 'n8n-workflow';
|
import { NodeConnectionType } from 'n8n-workflow';
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import { useWorkflowsStore } from './workflows.store';
|
import { useWorkflowsStore } from './workflows.store';
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
export const useNDVStore = defineStore(STORES.NDV, {
|
const DEFAULT_MAIN_PANEL_DIMENSIONS = {
|
||||||
state: (): NDVState => ({
|
relativeLeft: 1,
|
||||||
activeNodeName: null,
|
relativeRight: 1,
|
||||||
mainPanelDimensions: {},
|
relativeWidth: 1,
|
||||||
pushRef: '',
|
};
|
||||||
input: {
|
|
||||||
|
export const useNDVStore = defineStore(STORES.NDV, () => {
|
||||||
|
const localStorageMappingIsOnboarded = useStorage(LOCAL_STORAGE_MAPPING_IS_ONBOARDED);
|
||||||
|
const localStorageTableHoverIsOnboarded = useStorage(LOCAL_STORAGE_TABLE_HOVER_IS_ONBOARDED);
|
||||||
|
const localStorageAutoCompleteIsOnboarded = useStorage(LOCAL_STORAGE_AUTOCOMPLETE_IS_ONBOARDED);
|
||||||
|
|
||||||
|
const activeNodeName = ref<string | null>(null);
|
||||||
|
const mainPanelDimensions = ref<MainPanelDimensions>({
|
||||||
|
unknown: { ...DEFAULT_MAIN_PANEL_DIMENSIONS },
|
||||||
|
regular: { ...DEFAULT_MAIN_PANEL_DIMENSIONS },
|
||||||
|
dragless: { ...DEFAULT_MAIN_PANEL_DIMENSIONS },
|
||||||
|
inputless: { ...DEFAULT_MAIN_PANEL_DIMENSIONS },
|
||||||
|
wide: { ...DEFAULT_MAIN_PANEL_DIMENSIONS },
|
||||||
|
});
|
||||||
|
const pushRef = ref('');
|
||||||
|
const input = ref<InputPanel>({
|
||||||
displayMode: 'schema',
|
displayMode: 'schema',
|
||||||
nodeName: undefined,
|
nodeName: undefined,
|
||||||
run: undefined,
|
run: undefined,
|
||||||
|
@ -32,8 +51,8 @@ export const useNDVStore = defineStore(STORES.NDV, {
|
||||||
data: {
|
data: {
|
||||||
isEmpty: true,
|
isEmpty: true,
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
output: {
|
const output = ref<OutputPanel>({
|
||||||
displayMode: 'table',
|
displayMode: 'table',
|
||||||
branch: undefined,
|
branch: undefined,
|
||||||
data: {
|
data: {
|
||||||
|
@ -43,35 +62,37 @@ export const useNDVStore = defineStore(STORES.NDV, {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
value: '',
|
value: '',
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
focusedMappableInput: '',
|
const focusedMappableInput = ref('');
|
||||||
focusedInputPath: '',
|
const focusedInputPath = ref('');
|
||||||
mappingTelemetry: {},
|
const mappingTelemetry = ref<Record<string, string | number | boolean>>({});
|
||||||
hoveringItem: null,
|
const hoveringItem = ref<null | TargetItem>(null);
|
||||||
expressionOutputItemIndex: 0,
|
const expressionOutputItemIndex = ref(0);
|
||||||
draggable: {
|
const draggable = ref<Draggable>({
|
||||||
isDragging: false,
|
isDragging: false,
|
||||||
type: '',
|
type: '',
|
||||||
data: '',
|
data: '',
|
||||||
dimensions: null,
|
dimensions: null,
|
||||||
activeTarget: null,
|
activeTarget: null,
|
||||||
},
|
});
|
||||||
isMappingOnboarded: useStorage(LOCAL_STORAGE_MAPPING_IS_ONBOARDED).value === 'true',
|
const isMappingOnboarded = ref(localStorageMappingIsOnboarded.value === 'true');
|
||||||
isTableHoverOnboarded: useStorage(LOCAL_STORAGE_TABLE_HOVER_IS_ONBOARDED).value === 'true',
|
const isTableHoverOnboarded = ref(localStorageTableHoverIsOnboarded.value === 'true');
|
||||||
isAutocompleteOnboarded: useStorage(LOCAL_STORAGE_AUTOCOMPLETE_IS_ONBOARDED).value === 'true',
|
|
||||||
highlightDraggables: false,
|
const isAutocompleteOnboarded = ref(localStorageAutoCompleteIsOnboarded.value === 'true');
|
||||||
}),
|
|
||||||
getters: {
|
const highlightDraggables = ref(false);
|
||||||
activeNode(): INodeUi | null {
|
|
||||||
const workflowsStore = useWorkflowsStore();
|
|
||||||
return workflowsStore.getNodeByName(this.activeNodeName || '');
|
|
||||||
},
|
|
||||||
ndvInputData(): INodeExecutionData[] {
|
|
||||||
const workflowsStore = useWorkflowsStore();
|
const workflowsStore = useWorkflowsStore();
|
||||||
|
|
||||||
|
const activeNode = computed(() => {
|
||||||
|
return workflowsStore.getNodeByName(activeNodeName.value || '');
|
||||||
|
});
|
||||||
|
|
||||||
|
const ndvInputData = computed(() => {
|
||||||
const executionData = workflowsStore.getWorkflowExecution;
|
const executionData = workflowsStore.getWorkflowExecution;
|
||||||
const inputNodeName: string | undefined = this.input.nodeName;
|
const inputNodeName: string | undefined = input.value.nodeName;
|
||||||
const inputRunIndex: number = this.input.run ?? 0;
|
const inputRunIndex: number = input.value.run ?? 0;
|
||||||
const inputBranchIndex: number = this.input.branch ?? 0;
|
const inputBranchIndex: number = input.value.branch ?? 0;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!executionData ||
|
!executionData ||
|
||||||
|
@ -87,101 +108,44 @@ export const useNDVStore = defineStore(STORES.NDV, {
|
||||||
inputBranchIndex
|
inputBranchIndex
|
||||||
] ?? []
|
] ?? []
|
||||||
);
|
);
|
||||||
},
|
});
|
||||||
ndvInputDataWithPinnedData(): INodeExecutionData[] {
|
|
||||||
const data = this.ndvInputData;
|
const ndvInputNodeName = computed(() => {
|
||||||
return this.ndvInputNodeName
|
return input.value.nodeName;
|
||||||
? (useWorkflowsStore().pinDataByNodeName(this.ndvInputNodeName) ?? data)
|
});
|
||||||
|
|
||||||
|
const ndvInputDataWithPinnedData = computed(() => {
|
||||||
|
const data = ndvInputData.value;
|
||||||
|
return ndvInputNodeName.value
|
||||||
|
? (workflowsStore.pinDataByNodeName(ndvInputNodeName.value) ?? data)
|
||||||
: data;
|
: data;
|
||||||
},
|
});
|
||||||
hasInputData(): boolean {
|
|
||||||
return this.ndvInputDataWithPinnedData.length > 0;
|
|
||||||
},
|
|
||||||
getPanelDisplayMode() {
|
|
||||||
return (panel: NodePanelType) => this[panel].displayMode;
|
|
||||||
},
|
|
||||||
inputPanelDisplayMode(): IRunDataDisplayMode {
|
|
||||||
return this.input.displayMode;
|
|
||||||
},
|
|
||||||
outputPanelDisplayMode(): IRunDataDisplayMode {
|
|
||||||
return this.output.displayMode;
|
|
||||||
},
|
|
||||||
isDraggableDragging(): boolean {
|
|
||||||
return this.draggable.isDragging;
|
|
||||||
},
|
|
||||||
draggableType(): string {
|
|
||||||
return this.draggable.type;
|
|
||||||
},
|
|
||||||
draggableData(): string {
|
|
||||||
return this.draggable.data;
|
|
||||||
},
|
|
||||||
canDraggableDrop(): boolean {
|
|
||||||
return this.draggable.activeTarget !== null;
|
|
||||||
},
|
|
||||||
outputPanelEditMode(): NDVState['output']['editMode'] {
|
|
||||||
return this.output.editMode;
|
|
||||||
},
|
|
||||||
getMainPanelDimensions() {
|
|
||||||
return (panelType: string) => {
|
|
||||||
const defaults = { relativeRight: 1, relativeLeft: 1, relativeWidth: 1 };
|
|
||||||
return { ...defaults, ...this.mainPanelDimensions[panelType] };
|
|
||||||
};
|
|
||||||
},
|
|
||||||
draggableStickyPos(): XYPosition | null {
|
|
||||||
return this.draggable.activeTarget?.stickyPosition ?? null;
|
|
||||||
},
|
|
||||||
ndvInputNodeName(): string | undefined {
|
|
||||||
return this.input.nodeName;
|
|
||||||
},
|
|
||||||
ndvInputRunIndex(): number | undefined {
|
|
||||||
return this.input.run;
|
|
||||||
},
|
|
||||||
ndvInputBranchIndex(): number | undefined {
|
|
||||||
return this.input.branch;
|
|
||||||
},
|
|
||||||
isNDVDataEmpty() {
|
|
||||||
return (panel: 'input' | 'output'): boolean => this[panel].data.isEmpty;
|
|
||||||
},
|
|
||||||
isInputParentOfActiveNode(): boolean {
|
|
||||||
const inputNodeName = this.ndvInputNodeName;
|
|
||||||
if (!this.activeNode || !inputNodeName) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const workflow = useWorkflowsStore().getCurrentWorkflow();
|
|
||||||
const parentNodes = workflow.getParentNodes(this.activeNode.name, NodeConnectionType.Main, 1);
|
|
||||||
return parentNodes.includes(inputNodeName);
|
|
||||||
},
|
|
||||||
getHoveringItem(): TargetItem | null {
|
|
||||||
if (this.isInputParentOfActiveNode) {
|
|
||||||
return this.hoveringItem;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
const hasInputData = computed(() => {
|
||||||
},
|
return ndvInputDataWithPinnedData.value.length > 0;
|
||||||
expressionTargetItem(): TargetItem | null {
|
});
|
||||||
if (this.getHoveringItem) {
|
|
||||||
return this.getHoveringItem;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.expressionOutputItemIndex && this.ndvInputNodeName) {
|
const inputPanelDisplayMode = computed(() => input.value.displayMode);
|
||||||
return {
|
|
||||||
nodeName: this.ndvInputNodeName,
|
|
||||||
runIndex: this.ndvInputRunIndex ?? 0,
|
|
||||||
outputIndex: this.ndvInputBranchIndex ?? 0,
|
|
||||||
itemIndex: this.expressionOutputItemIndex,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
const outputPanelDisplayMode = computed(() => output.value.displayMode);
|
||||||
},
|
|
||||||
isNDVOpen(): boolean {
|
const isDraggableDragging = computed(() => draggable.value.isDragging);
|
||||||
return this.activeNodeName !== null;
|
|
||||||
},
|
const draggableType = computed(() => draggable.value.type);
|
||||||
ndvNodeInputNumber() {
|
|
||||||
|
const draggableData = computed(() => draggable.value.data);
|
||||||
|
|
||||||
|
const canDraggableDrop = computed(() => draggable.value.activeTarget !== null);
|
||||||
|
|
||||||
|
const outputPanelEditMode = computed(() => output.value.editMode);
|
||||||
|
|
||||||
|
const draggableStickyPos = computed(() => draggable.value.activeTarget?.stickyPosition ?? null);
|
||||||
|
|
||||||
|
const ndvNodeInputNumber = computed(() => {
|
||||||
const returnData: { [nodeName: string]: number[] } = {};
|
const returnData: { [nodeName: string]: number[] } = {};
|
||||||
const workflow = useWorkflowsStore().getCurrentWorkflow();
|
const workflow = workflowsStore.getCurrentWorkflow();
|
||||||
const activeNodeConections = (
|
const activeNodeConections = (
|
||||||
workflow.connectionsByDestinationNode[this.activeNode?.name || ''] ?? {}
|
workflow.connectionsByDestinationNode[activeNode.value?.name || ''] ?? {}
|
||||||
).main;
|
).main;
|
||||||
|
|
||||||
if (!activeNodeConections || activeNodeConections.length < 2) return returnData;
|
if (!activeNodeConections || activeNodeConections.length < 2) return returnData;
|
||||||
|
@ -196,117 +160,187 @@ export const useNDVStore = defineStore(STORES.NDV, {
|
||||||
}
|
}
|
||||||
|
|
||||||
return returnData;
|
return returnData;
|
||||||
},
|
});
|
||||||
},
|
|
||||||
actions: {
|
const ndvInputRunIndex = computed(() => input.value.run);
|
||||||
setActiveNodeName(nodeName: string | null): void {
|
|
||||||
this.activeNodeName = nodeName;
|
const ndvInputBranchIndex = computed(() => input.value.branch);
|
||||||
},
|
|
||||||
setInputNodeName(nodeName: string | undefined): void {
|
const isInputPanelEmpty = computed(() => input.value.data.isEmpty);
|
||||||
this.input = {
|
|
||||||
...this.input,
|
const isOutputPanelEmpty = computed(() => output.value.data.isEmpty);
|
||||||
nodeName,
|
|
||||||
|
const isInputParentOfActiveNode = computed(() => {
|
||||||
|
const inputNodeName = ndvInputNodeName.value;
|
||||||
|
if (!activeNode.value || !inputNodeName) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const workflow = workflowsStore.getCurrentWorkflow();
|
||||||
|
const parentNodes = workflow.getParentNodes(activeNode.value.name, NodeConnectionType.Main, 1);
|
||||||
|
return parentNodes.includes(inputNodeName);
|
||||||
|
});
|
||||||
|
|
||||||
|
const getHoveringItem = computed(() => {
|
||||||
|
if (isInputParentOfActiveNode.value) {
|
||||||
|
return hoveringItem.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
const expressionTargetItem = computed(() => {
|
||||||
|
if (getHoveringItem.value) {
|
||||||
|
return getHoveringItem.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expressionOutputItemIndex.value && ndvInputNodeName.value) {
|
||||||
|
return {
|
||||||
|
nodeName: ndvInputNodeName.value,
|
||||||
|
runIndex: ndvInputRunIndex.value ?? 0,
|
||||||
|
outputIndex: ndvInputBranchIndex.value ?? 0,
|
||||||
|
itemIndex: expressionOutputItemIndex.value,
|
||||||
};
|
};
|
||||||
},
|
}
|
||||||
setInputRunIndex(run?: number): void {
|
|
||||||
this.input = {
|
return null;
|
||||||
...this.input,
|
});
|
||||||
run,
|
|
||||||
|
const isNDVOpen = computed(() => activeNodeName.value !== null);
|
||||||
|
|
||||||
|
const setActiveNodeName = (nodeName: string | null): void => {
|
||||||
|
activeNodeName.value = nodeName;
|
||||||
};
|
};
|
||||||
},
|
|
||||||
setMainPanelDimensions(params: {
|
const setInputNodeName = (nodeName: string | undefined): void => {
|
||||||
panelType: string;
|
input.value.nodeName = nodeName;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setInputRunIndex = (run?: number): void => {
|
||||||
|
input.value.run = run;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setMainPanelDimensions = (params: {
|
||||||
|
panelType: MainPanelType;
|
||||||
dimensions: { relativeLeft?: number; relativeRight?: number; relativeWidth?: number };
|
dimensions: { relativeLeft?: number; relativeRight?: number; relativeWidth?: number };
|
||||||
}): void {
|
}): void => {
|
||||||
this.mainPanelDimensions = {
|
mainPanelDimensions.value[params.panelType] = {
|
||||||
...this.mainPanelDimensions,
|
...mainPanelDimensions.value[params.panelType],
|
||||||
[params.panelType]: {
|
|
||||||
...this.mainPanelDimensions[params.panelType],
|
|
||||||
...params.dimensions,
|
...params.dimensions,
|
||||||
},
|
|
||||||
};
|
};
|
||||||
},
|
};
|
||||||
setNDVPushRef(): void {
|
|
||||||
this.pushRef = `ndv-${uuid()}`;
|
const setNDVPushRef = (): void => {
|
||||||
},
|
pushRef.value = `ndv-${uuid()}`;
|
||||||
resetNDVPushRef(): void {
|
};
|
||||||
this.pushRef = '';
|
|
||||||
},
|
const resetNDVPushRef = (): void => {
|
||||||
setPanelDisplayMode(params: { pane: NodePanelType; mode: IRunDataDisplayMode }): void {
|
pushRef.value = '';
|
||||||
this[params.pane].displayMode = params.mode;
|
};
|
||||||
},
|
|
||||||
setOutputPanelEditModeEnabled(isEnabled: boolean): void {
|
const setPanelDisplayMode = (params: {
|
||||||
this.output.editMode.enabled = isEnabled;
|
pane: NodePanelType;
|
||||||
},
|
mode: IRunDataDisplayMode;
|
||||||
setOutputPanelEditModeValue(payload: string): void {
|
}): void => {
|
||||||
this.output.editMode.value = payload;
|
if (params.pane === 'input') {
|
||||||
},
|
input.value.displayMode = params.mode;
|
||||||
setMappableNDVInputFocus(paramName: string): void {
|
} else {
|
||||||
this.focusedMappableInput = paramName;
|
output.value.displayMode = params.mode;
|
||||||
},
|
}
|
||||||
draggableStartDragging({
|
};
|
||||||
|
|
||||||
|
const setOutputPanelEditModeEnabled = (isEnabled: boolean): void => {
|
||||||
|
output.value.editMode.enabled = isEnabled;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setOutputPanelEditModeValue = (payload: string): void => {
|
||||||
|
output.value.editMode.value = payload;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setMappableNDVInputFocus = (paramName: string): void => {
|
||||||
|
focusedMappableInput.value = paramName;
|
||||||
|
};
|
||||||
|
|
||||||
|
const draggableStartDragging = ({
|
||||||
type,
|
type,
|
||||||
data,
|
data,
|
||||||
dimensions,
|
dimensions,
|
||||||
}: {
|
}: { type: string; data: string; dimensions: DOMRect | null }): void => {
|
||||||
type: string;
|
draggable.value = {
|
||||||
data: string;
|
|
||||||
dimensions: DOMRect | null;
|
|
||||||
}): void {
|
|
||||||
this.draggable = {
|
|
||||||
isDragging: true,
|
isDragging: true,
|
||||||
type,
|
type,
|
||||||
data,
|
data,
|
||||||
dimensions,
|
dimensions,
|
||||||
activeTarget: null,
|
activeTarget: null,
|
||||||
};
|
};
|
||||||
},
|
};
|
||||||
draggableStopDragging(): void {
|
|
||||||
this.draggable = {
|
const draggableStopDragging = (): void => {
|
||||||
|
draggable.value = {
|
||||||
isDragging: false,
|
isDragging: false,
|
||||||
type: '',
|
type: '',
|
||||||
data: '',
|
data: '',
|
||||||
dimensions: null,
|
dimensions: null,
|
||||||
activeTarget: null,
|
activeTarget: null,
|
||||||
};
|
};
|
||||||
},
|
};
|
||||||
setDraggableTarget(target: NDVState['draggable']['activeTarget']): void {
|
|
||||||
this.draggable.activeTarget = target;
|
const setDraggableTarget = (target: NDVState['draggable']['activeTarget']): void => {
|
||||||
},
|
draggable.value.activeTarget = target;
|
||||||
setMappingTelemetry(telemetry: { [key: string]: string | number | boolean }): void {
|
};
|
||||||
this.mappingTelemetry = { ...this.mappingTelemetry, ...telemetry };
|
|
||||||
},
|
const setMappingTelemetry = (telemetry: { [key: string]: string | number | boolean }): void => {
|
||||||
resetMappingTelemetry(): void {
|
mappingTelemetry.value = { ...mappingTelemetry.value, ...telemetry };
|
||||||
this.mappingTelemetry = {};
|
};
|
||||||
},
|
|
||||||
setHoveringItem(item: null | NDVState['hoveringItem']): void {
|
const resetMappingTelemetry = (): void => {
|
||||||
if (item) this.setTableHoverOnboarded();
|
mappingTelemetry.value = {};
|
||||||
this.hoveringItem = item;
|
};
|
||||||
},
|
|
||||||
setNDVBranchIndex(e: { pane: 'input' | 'output'; branchIndex: number }): void {
|
const setHoveringItem = (item: TargetItem | null): void => {
|
||||||
this[e.pane].branch = e.branchIndex;
|
if (item) setTableHoverOnboarded();
|
||||||
},
|
hoveringItem.value = item;
|
||||||
setNDVPanelDataIsEmpty(payload: { panel: 'input' | 'output'; isEmpty: boolean }): void {
|
};
|
||||||
this[payload.panel].data.isEmpty = payload.isEmpty;
|
|
||||||
},
|
const setNDVBranchIndex = (e: { pane: NodePanelType; branchIndex: number }): void => {
|
||||||
setMappingOnboarded() {
|
if (e.pane === 'input') {
|
||||||
this.isMappingOnboarded = true;
|
input.value.branch = e.branchIndex;
|
||||||
useStorage(LOCAL_STORAGE_MAPPING_IS_ONBOARDED).value = 'true';
|
} else {
|
||||||
},
|
output.value.branch = e.branchIndex;
|
||||||
setTableHoverOnboarded() {
|
}
|
||||||
this.isTableHoverOnboarded = true;
|
};
|
||||||
useStorage(LOCAL_STORAGE_TABLE_HOVER_IS_ONBOARDED).value = 'true';
|
|
||||||
},
|
const setNDVPanelDataIsEmpty = (params: {
|
||||||
setAutocompleteOnboarded() {
|
panel: NodePanelType;
|
||||||
this.isAutocompleteOnboarded = true;
|
isEmpty: boolean;
|
||||||
useStorage(LOCAL_STORAGE_AUTOCOMPLETE_IS_ONBOARDED).value = 'true';
|
}): void => {
|
||||||
},
|
if (params.panel === 'input') {
|
||||||
setHighlightDraggables(highlight: boolean) {
|
input.value.data.isEmpty = params.isEmpty;
|
||||||
this.highlightDraggables = highlight;
|
} else {
|
||||||
},
|
output.value.data.isEmpty = params.isEmpty;
|
||||||
updateNodeParameterIssues(issues: INodeIssues): void {
|
}
|
||||||
const workflowsStore = useWorkflowsStore();
|
};
|
||||||
const activeNode = workflowsStore.getNodeByName(this.activeNodeName || '');
|
|
||||||
|
const setMappingOnboarded = () => {
|
||||||
|
isMappingOnboarded.value = true;
|
||||||
|
localStorageMappingIsOnboarded.value = 'true';
|
||||||
|
};
|
||||||
|
|
||||||
|
const setTableHoverOnboarded = () => {
|
||||||
|
isTableHoverOnboarded.value = true;
|
||||||
|
localStorageTableHoverIsOnboarded.value = 'true';
|
||||||
|
};
|
||||||
|
|
||||||
|
const setAutocompleteOnboarded = () => {
|
||||||
|
isAutocompleteOnboarded.value = true;
|
||||||
|
localStorageAutoCompleteIsOnboarded.value = 'true';
|
||||||
|
};
|
||||||
|
|
||||||
|
const setHighlightDraggables = (highlight: boolean) => {
|
||||||
|
highlightDraggables.value = highlight;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateNodeParameterIssues = (issues: INodeIssues): void => {
|
||||||
|
const activeNode = workflowsStore.getNodeByName(activeNodeName.value || '');
|
||||||
|
|
||||||
if (activeNode) {
|
if (activeNode) {
|
||||||
const nodeIndex = workflowsStore.workflow.nodes.findIndex((node) => {
|
const nodeIndex = workflowsStore.workflow.nodes.findIndex((node) => {
|
||||||
|
@ -320,9 +354,73 @@ export const useNDVStore = defineStore(STORES.NDV, {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
setFocusedInputPath(path: string) {
|
|
||||||
this.focusedInputPath = path;
|
const setFocusedInputPath = (path: string) => {
|
||||||
},
|
focusedInputPath.value = path;
|
||||||
},
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
activeNode,
|
||||||
|
ndvInputData,
|
||||||
|
ndvInputNodeName,
|
||||||
|
ndvInputDataWithPinnedData,
|
||||||
|
hasInputData,
|
||||||
|
inputPanelDisplayMode,
|
||||||
|
outputPanelDisplayMode,
|
||||||
|
isDraggableDragging,
|
||||||
|
draggableType,
|
||||||
|
draggableData,
|
||||||
|
canDraggableDrop,
|
||||||
|
outputPanelEditMode,
|
||||||
|
draggableStickyPos,
|
||||||
|
ndvNodeInputNumber,
|
||||||
|
ndvInputRunIndex,
|
||||||
|
ndvInputBranchIndex,
|
||||||
|
isInputParentOfActiveNode,
|
||||||
|
getHoveringItem,
|
||||||
|
expressionTargetItem,
|
||||||
|
isNDVOpen,
|
||||||
|
isInputPanelEmpty,
|
||||||
|
isOutputPanelEmpty,
|
||||||
|
focusedMappableInput,
|
||||||
|
isMappingOnboarded,
|
||||||
|
pushRef,
|
||||||
|
activeNodeName,
|
||||||
|
focusedInputPath,
|
||||||
|
input,
|
||||||
|
output,
|
||||||
|
hoveringItem,
|
||||||
|
highlightDraggables,
|
||||||
|
mappingTelemetry,
|
||||||
|
draggable,
|
||||||
|
isAutocompleteOnboarded,
|
||||||
|
expressionOutputItemIndex,
|
||||||
|
isTableHoverOnboarded,
|
||||||
|
mainPanelDimensions,
|
||||||
|
setActiveNodeName,
|
||||||
|
setInputNodeName,
|
||||||
|
setInputRunIndex,
|
||||||
|
setMainPanelDimensions,
|
||||||
|
setNDVPushRef,
|
||||||
|
resetNDVPushRef,
|
||||||
|
setPanelDisplayMode,
|
||||||
|
setOutputPanelEditModeEnabled,
|
||||||
|
setOutputPanelEditModeValue,
|
||||||
|
setMappableNDVInputFocus,
|
||||||
|
draggableStartDragging,
|
||||||
|
draggableStopDragging,
|
||||||
|
setDraggableTarget,
|
||||||
|
setMappingTelemetry,
|
||||||
|
resetMappingTelemetry,
|
||||||
|
setHoveringItem,
|
||||||
|
setNDVBranchIndex,
|
||||||
|
setNDVPanelDataIsEmpty,
|
||||||
|
setMappingOnboarded,
|
||||||
|
setTableHoverOnboarded,
|
||||||
|
setAutocompleteOnboarded,
|
||||||
|
setHighlightDraggables,
|
||||||
|
updateNodeParameterIssues,
|
||||||
|
setFocusedInputPath,
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,24 +6,17 @@ import type {
|
||||||
ITemplatesCollection,
|
ITemplatesCollection,
|
||||||
ITemplatesCollectionFull,
|
ITemplatesCollectionFull,
|
||||||
ITemplatesQuery,
|
ITemplatesQuery,
|
||||||
ITemplateState,
|
|
||||||
ITemplatesWorkflow,
|
ITemplatesWorkflow,
|
||||||
ITemplatesWorkflowFull,
|
ITemplatesWorkflowFull,
|
||||||
IWorkflowTemplate,
|
IWorkflowTemplate,
|
||||||
} from '@/Interface';
|
} from '@/Interface';
|
||||||
import { useSettingsStore } from './settings.store';
|
import { useSettingsStore } from './settings.store';
|
||||||
import {
|
import * as templatesApi from '@/api/templates';
|
||||||
getCategories,
|
|
||||||
getCollectionById,
|
|
||||||
getCollections,
|
|
||||||
getTemplateById,
|
|
||||||
getWorkflows,
|
|
||||||
getWorkflowTemplate,
|
|
||||||
} from '@/api/templates';
|
|
||||||
import { getFixedNodesList } from '@/utils/nodeViewUtils';
|
import { getFixedNodesList } from '@/utils/nodeViewUtils';
|
||||||
import { useRootStore } from '@/stores/root.store';
|
import { useRootStore } from '@/stores/root.store';
|
||||||
import { useUsersStore } from './users.store';
|
import { useUsersStore } from './users.store';
|
||||||
import { useWorkflowsStore } from './workflows.store';
|
import { useWorkflowsStore } from './workflows.store';
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
const TEMPLATES_PAGE_SIZE = 20;
|
const TEMPLATES_PAGE_SIZE = 20;
|
||||||
|
|
||||||
|
@ -33,101 +26,124 @@ function getSearchKey(query: ITemplatesQuery): string {
|
||||||
|
|
||||||
export type TemplatesStore = ReturnType<typeof useTemplatesStore>;
|
export type TemplatesStore = ReturnType<typeof useTemplatesStore>;
|
||||||
|
|
||||||
export const useTemplatesStore = defineStore(STORES.TEMPLATES, {
|
export const useTemplatesStore = defineStore(STORES.TEMPLATES, () => {
|
||||||
state: (): ITemplateState => ({
|
const categories = ref<ITemplatesCategory[]>([]);
|
||||||
categories: [],
|
const collections = ref<Record<string, ITemplatesCollection>>({});
|
||||||
collections: {},
|
const workflows = ref<Record<string, ITemplatesWorkflow | ITemplatesWorkflowFull>>({});
|
||||||
workflows: {},
|
const workflowSearches = ref<
|
||||||
collectionSearches: {},
|
Record<
|
||||||
workflowSearches: {},
|
string,
|
||||||
currentSessionId: '',
|
{
|
||||||
previousSessionId: '',
|
workflowIds: string[];
|
||||||
currentN8nPath: `${window.location.protocol}//${window.location.host}${window.BASE_PATH}`,
|
totalWorkflows: number;
|
||||||
}),
|
loadingMore?: boolean;
|
||||||
getters: {
|
categories?: ITemplatesCategory[];
|
||||||
allCategories(): ITemplatesCategory[] {
|
}
|
||||||
return Object.values(this.categories).sort((a: ITemplatesCategory, b: ITemplatesCategory) =>
|
>
|
||||||
|
>({});
|
||||||
|
const collectionSearches = ref<
|
||||||
|
Record<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
collectionIds: string[];
|
||||||
|
}
|
||||||
|
>
|
||||||
|
>({});
|
||||||
|
const currentSessionId = ref<string>('');
|
||||||
|
const previousSessionId = ref<string>('');
|
||||||
|
const currentN8nPath = ref<string>(
|
||||||
|
`${window.location.protocol}//${window.location.host}${window.BASE_PATH}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
const settingsStore = useSettingsStore();
|
||||||
|
const rootStore = useRootStore();
|
||||||
|
const userStore = useUsersStore();
|
||||||
|
const workflowsStore = useWorkflowsStore();
|
||||||
|
|
||||||
|
const allCategories = computed(() => {
|
||||||
|
return categories.value.sort((a: ITemplatesCategory, b: ITemplatesCategory) =>
|
||||||
a.name > b.name ? 1 : -1,
|
a.name > b.name ? 1 : -1,
|
||||||
);
|
);
|
||||||
},
|
});
|
||||||
getTemplateById() {
|
|
||||||
return (id: string): null | ITemplatesWorkflow => this.workflows[id];
|
const getTemplatesById = computed(() => {
|
||||||
},
|
return (id: string): null | ITemplatesWorkflow => workflows.value[id];
|
||||||
getFullTemplateById() {
|
});
|
||||||
|
|
||||||
|
const getFullTemplateById = computed(() => {
|
||||||
return (id: string): null | ITemplatesWorkflowFull => {
|
return (id: string): null | ITemplatesWorkflowFull => {
|
||||||
const template = this.workflows[id];
|
const template = workflows.value[id];
|
||||||
return template && 'full' in template && template.full ? template : null;
|
return template && 'full' in template && template.full ? template : null;
|
||||||
};
|
};
|
||||||
},
|
});
|
||||||
getCollectionById() {
|
|
||||||
return (id: string): null | ITemplatesCollection => this.collections[id];
|
const getCollectionById = computed(() => collections.value);
|
||||||
},
|
|
||||||
getCategoryById() {
|
const getCategoryById = computed(() => {
|
||||||
return (id: string): null | ITemplatesCategory => this.categories[id as unknown as number];
|
return (id: string): null | ITemplatesCategory => categories.value[id as unknown as number];
|
||||||
},
|
});
|
||||||
getSearchedCollections() {
|
|
||||||
|
const getSearchedCollections = computed(() => {
|
||||||
return (query: ITemplatesQuery) => {
|
return (query: ITemplatesQuery) => {
|
||||||
const searchKey = getSearchKey(query);
|
const searchKey = getSearchKey(query);
|
||||||
const search = this.collectionSearches[searchKey];
|
const search = collectionSearches.value[searchKey];
|
||||||
if (!search) {
|
if (!search) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return search.collectionIds.map((collectionId: string) => this.collections[collectionId]);
|
return search.collectionIds.map((collectionId: string) => collections.value[collectionId]);
|
||||||
};
|
};
|
||||||
},
|
});
|
||||||
getSearchedWorkflows() {
|
|
||||||
|
const getSearchedWorkflows = computed(() => {
|
||||||
return (query: ITemplatesQuery) => {
|
return (query: ITemplatesQuery) => {
|
||||||
const searchKey = getSearchKey(query);
|
const searchKey = getSearchKey(query);
|
||||||
const search = this.workflowSearches[searchKey];
|
const search = workflowSearches.value[searchKey];
|
||||||
if (!search) {
|
if (!search) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return search.workflowIds.map((workflowId: string) => this.workflows[workflowId]);
|
return search.workflowIds.map((workflowId: string) => workflows.value[workflowId]);
|
||||||
};
|
};
|
||||||
},
|
});
|
||||||
getSearchedWorkflowsTotal() {
|
|
||||||
|
const getSearchedWorkflowsTotal = computed(() => {
|
||||||
return (query: ITemplatesQuery) => {
|
return (query: ITemplatesQuery) => {
|
||||||
const searchKey = getSearchKey(query);
|
const searchKey = getSearchKey(query);
|
||||||
const search = this.workflowSearches[searchKey];
|
const search = workflowSearches.value[searchKey];
|
||||||
|
|
||||||
return search ? search.totalWorkflows : 0;
|
return search ? search.totalWorkflows : 0;
|
||||||
};
|
};
|
||||||
},
|
});
|
||||||
isSearchLoadingMore() {
|
|
||||||
|
const isSearchLoadingMore = computed(() => {
|
||||||
return (query: ITemplatesQuery) => {
|
return (query: ITemplatesQuery) => {
|
||||||
const searchKey = getSearchKey(query);
|
const searchKey = getSearchKey(query);
|
||||||
const search = this.workflowSearches[searchKey];
|
const search = workflowSearches.value[searchKey];
|
||||||
|
|
||||||
return Boolean(search && search.loadingMore);
|
return Boolean(search && search.loadingMore);
|
||||||
};
|
};
|
||||||
},
|
});
|
||||||
isSearchFinished() {
|
|
||||||
|
const isSearchFinished = computed(() => {
|
||||||
return (query: ITemplatesQuery) => {
|
return (query: ITemplatesQuery) => {
|
||||||
const searchKey = getSearchKey(query);
|
const searchKey = getSearchKey(query);
|
||||||
const search = this.workflowSearches[searchKey];
|
const search = workflowSearches.value[searchKey];
|
||||||
|
|
||||||
return Boolean(
|
return Boolean(
|
||||||
search && !search.loadingMore && search.totalWorkflows === search.workflowIds.length,
|
search && !search.loadingMore && search.totalWorkflows === search.workflowIds.length,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
},
|
});
|
||||||
hasCustomTemplatesHost(): boolean {
|
|
||||||
const settingsStore = useSettingsStore();
|
const hasCustomTemplatesHost = computed(() => {
|
||||||
return settingsStore.templatesHost !== TEMPLATES_URLS.DEFAULT_API_HOST;
|
return settingsStore.templatesHost !== TEMPLATES_URLS.DEFAULT_API_HOST;
|
||||||
},
|
});
|
||||||
/**
|
|
||||||
* Constructs URLSearchParams object based on the default parameters for the template repository
|
const websiteTemplateRepositoryParameters = computed(() => {
|
||||||
* and provided additional parameters
|
|
||||||
*/
|
|
||||||
websiteTemplateRepositoryParameters(_roleOverride?: string) {
|
|
||||||
const rootStore = useRootStore();
|
|
||||||
const userStore = useUsersStore();
|
|
||||||
const workflowsStore = useWorkflowsStore();
|
|
||||||
const defaultParameters: Record<string, string> = {
|
const defaultParameters: Record<string, string> = {
|
||||||
...TEMPLATES_URLS.UTM_QUERY,
|
...TEMPLATES_URLS.UTM_QUERY,
|
||||||
utm_instance: this.currentN8nPath,
|
utm_instance: currentN8nPath.value,
|
||||||
utm_n8n_version: rootStore.versionCli,
|
utm_n8n_version: rootStore.versionCli,
|
||||||
utm_awc: String(workflowsStore.activeWorkflows.length),
|
utm_awc: String(workflowsStore.activeWorkflows.length),
|
||||||
};
|
};
|
||||||
|
@ -141,158 +157,119 @@ export const useTemplatesStore = defineStore(STORES.TEMPLATES, {
|
||||||
if (userRole) {
|
if (userRole) {
|
||||||
defaultParameters.utm_user_role = userRole;
|
defaultParameters.utm_user_role = userRole;
|
||||||
}
|
}
|
||||||
return (additionalParameters: Record<string, string> = {}) => {
|
|
||||||
return new URLSearchParams({
|
return new URLSearchParams({
|
||||||
...defaultParameters,
|
...defaultParameters,
|
||||||
...additionalParameters,
|
|
||||||
});
|
});
|
||||||
};
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Construct the URL for the template repository on the website
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
websiteTemplateRepositoryURL(): string {
|
|
||||||
return `${
|
|
||||||
TEMPLATES_URLS.BASE_WEBSITE_URL
|
|
||||||
}?${this.websiteTemplateRepositoryParameters().toString()}`;
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Construct the URL for the template category page on the website for a given category id
|
|
||||||
*/
|
|
||||||
getWebsiteCategoryURL() {
|
|
||||||
return (id?: string, roleOverride?: string) => {
|
|
||||||
const payload: Record<string, string> = {};
|
|
||||||
if (id) {
|
|
||||||
payload.categories = id;
|
|
||||||
}
|
|
||||||
if (roleOverride) {
|
|
||||||
payload.utm_user_role = roleOverride;
|
|
||||||
}
|
|
||||||
return `${TEMPLATES_URLS.BASE_WEBSITE_URL}/?${this.websiteTemplateRepositoryParameters(payload).toString()}`;
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
actions: {
|
|
||||||
addCategories(categories: ITemplatesCategory[]): void {
|
|
||||||
categories.forEach((category: ITemplatesCategory) => {
|
|
||||||
this.categories = {
|
|
||||||
...this.categories,
|
|
||||||
[category.id]: category,
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
},
|
|
||||||
addCollections(collections: Array<ITemplatesCollection | ITemplatesCollectionFull>): void {
|
|
||||||
collections.forEach((collection) => {
|
|
||||||
const workflows = (collection.workflows || []).map((workflow) => ({ id: workflow.id }));
|
|
||||||
const cachedCollection = this.collections[collection.id] || {};
|
|
||||||
|
|
||||||
this.collections = {
|
const websiteTemplateRepositoryURL = computed(
|
||||||
...this.collections,
|
() =>
|
||||||
[collection.id]: {
|
`${TEMPLATES_URLS.BASE_WEBSITE_URL}?${websiteTemplateRepositoryParameters.value.toString()}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
const addCategories = (_categories: ITemplatesCategory[]): void => {
|
||||||
|
categories.value = _categories;
|
||||||
|
};
|
||||||
|
|
||||||
|
const addCollections = (
|
||||||
|
_collections: Array<ITemplatesCollection | ITemplatesCollectionFull>,
|
||||||
|
): void => {
|
||||||
|
_collections.forEach((collection) => {
|
||||||
|
const workflows = (collection.workflows || []).map((workflow) => ({ id: workflow.id }));
|
||||||
|
const cachedCollection = collections.value[collection.id] || {};
|
||||||
|
|
||||||
|
collections.value[collection.id] = {
|
||||||
...cachedCollection,
|
...cachedCollection,
|
||||||
...collection,
|
...collection,
|
||||||
workflows,
|
workflows,
|
||||||
},
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
},
|
};
|
||||||
addWorkflows(workflows: Array<ITemplatesWorkflow | ITemplatesWorkflowFull>): void {
|
|
||||||
workflows.forEach((workflow: ITemplatesWorkflow) => {
|
|
||||||
const cachedWorkflow = this.workflows[workflow.id] || {};
|
|
||||||
|
|
||||||
this.workflows = {
|
const addWorkflows = (_workflows: Array<ITemplatesWorkflow | ITemplatesWorkflowFull>): void => {
|
||||||
...this.workflows,
|
_workflows.forEach((workflow) => {
|
||||||
[workflow.id]: {
|
const cachedWorkflow = workflows.value[workflow.id] || {};
|
||||||
...cachedWorkflow,
|
workflows.value[workflow.id.toString()] = { ...cachedWorkflow, ...workflow };
|
||||||
...workflow,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
},
|
};
|
||||||
addCollectionSearch(data: {
|
|
||||||
collections: ITemplatesCollection[];
|
const addCollectionsSearch = (data: {
|
||||||
|
_collections: ITemplatesCollection[];
|
||||||
query: ITemplatesQuery;
|
query: ITemplatesQuery;
|
||||||
}): void {
|
}) => {
|
||||||
const collectionIds = data.collections.map((collection) => String(collection.id));
|
const collectionIds = data._collections.map((collection) => String(collection.id));
|
||||||
const searchKey = getSearchKey(data.query);
|
const searchKey = getSearchKey(data.query);
|
||||||
|
|
||||||
this.collectionSearches = {
|
collectionSearches.value[searchKey] = {
|
||||||
...this.collectionSearches,
|
|
||||||
[searchKey]: {
|
|
||||||
collectionIds,
|
collectionIds,
|
||||||
},
|
|
||||||
};
|
};
|
||||||
},
|
};
|
||||||
addWorkflowsSearch(data: {
|
|
||||||
|
const addWorkflowsSearch = (data: {
|
||||||
totalWorkflows: number;
|
totalWorkflows: number;
|
||||||
workflows: ITemplatesWorkflow[];
|
workflows: ITemplatesWorkflow[];
|
||||||
query: ITemplatesQuery;
|
query: ITemplatesQuery;
|
||||||
}): void {
|
}) => {
|
||||||
const workflowIds = data.workflows.map((workflow) => workflow.id);
|
const workflowIds = data.workflows.map((workflow) => workflow.id);
|
||||||
const searchKey = getSearchKey(data.query);
|
const searchKey = getSearchKey(data.query);
|
||||||
const cachedResults = this.workflowSearches[searchKey];
|
const cachedResults = workflowSearches.value[searchKey];
|
||||||
if (!cachedResults) {
|
if (!cachedResults) {
|
||||||
this.workflowSearches = {
|
workflowSearches.value[searchKey] = {
|
||||||
...this.workflowSearches,
|
|
||||||
[searchKey]: {
|
|
||||||
workflowIds: workflowIds as unknown as string[],
|
workflowIds: workflowIds as unknown as string[],
|
||||||
totalWorkflows: data.totalWorkflows,
|
totalWorkflows: data.totalWorkflows,
|
||||||
categories: this.categories,
|
categories: categories.value,
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.workflowSearches = {
|
workflowSearches.value[searchKey] = {
|
||||||
...this.workflowSearches,
|
|
||||||
[searchKey]: {
|
|
||||||
workflowIds: [...cachedResults.workflowIds, ...workflowIds] as string[],
|
workflowIds: [...cachedResults.workflowIds, ...workflowIds] as string[],
|
||||||
totalWorkflows: data.totalWorkflows,
|
totalWorkflows: data.totalWorkflows,
|
||||||
categories: this.categories,
|
categories: categories.value,
|
||||||
},
|
|
||||||
};
|
};
|
||||||
},
|
};
|
||||||
setWorkflowSearchLoading(query: ITemplatesQuery): void {
|
|
||||||
|
const setWorkflowSearchLoading = (query: ITemplatesQuery): void => {
|
||||||
const searchKey = getSearchKey(query);
|
const searchKey = getSearchKey(query);
|
||||||
const cachedResults = this.workflowSearches[searchKey];
|
const cachedResults = workflowSearches.value[searchKey];
|
||||||
if (!cachedResults) {
|
if (!cachedResults) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.workflowSearches[searchKey] = {
|
workflowSearches.value[searchKey] = {
|
||||||
...this.workflowSearches[searchKey],
|
...workflowSearches.value[searchKey],
|
||||||
loadingMore: true,
|
loadingMore: true,
|
||||||
};
|
};
|
||||||
},
|
};
|
||||||
setWorkflowSearchLoaded(query: ITemplatesQuery): void {
|
|
||||||
|
const setWorkflowSearchLoaded = (query: ITemplatesQuery): void => {
|
||||||
const searchKey = getSearchKey(query);
|
const searchKey = getSearchKey(query);
|
||||||
const cachedResults = this.workflowSearches[searchKey];
|
const cachedResults = workflowSearches.value[searchKey];
|
||||||
if (!cachedResults) {
|
if (!cachedResults) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.workflowSearches[searchKey] = {
|
workflowSearches.value[searchKey] = {
|
||||||
...this.workflowSearches[searchKey],
|
...workflowSearches.value[searchKey],
|
||||||
loadingMore: false,
|
loadingMore: false,
|
||||||
};
|
};
|
||||||
},
|
};
|
||||||
resetSessionId(): void {
|
|
||||||
this.previousSessionId = this.currentSessionId;
|
const resetSessionId = (): void => {
|
||||||
this.currentSessionId = '';
|
previousSessionId.value = currentSessionId.value;
|
||||||
},
|
currentSessionId.value = '';
|
||||||
setSessionId(): void {
|
};
|
||||||
if (!this.currentSessionId) {
|
|
||||||
this.currentSessionId = `templates-${Date.now()}`;
|
const setSessionId = (): void => {
|
||||||
|
if (!currentSessionId.value) {
|
||||||
|
currentSessionId.value = `templates-${Date.now()}`;
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
async fetchTemplateById(templateId: string): Promise<ITemplatesWorkflowFull> {
|
|
||||||
const settingsStore = useSettingsStore();
|
const fetchTemplateById = async (templateId: string): Promise<ITemplatesWorkflowFull> => {
|
||||||
const rootStore = useRootStore();
|
|
||||||
const apiEndpoint: string = settingsStore.templatesHost;
|
const apiEndpoint: string = settingsStore.templatesHost;
|
||||||
const versionCli: string = rootStore.versionCli;
|
const versionCli: string = rootStore.versionCli;
|
||||||
const response = await getTemplateById(apiEndpoint, templateId, {
|
const response = await templatesApi.getTemplateById(apiEndpoint, templateId, {
|
||||||
'n8n-version': versionCli,
|
'n8n-version': versionCli,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -300,16 +277,17 @@ export const useTemplatesStore = defineStore(STORES.TEMPLATES, {
|
||||||
...response.workflow,
|
...response.workflow,
|
||||||
full: true,
|
full: true,
|
||||||
};
|
};
|
||||||
this.addWorkflows([template]);
|
addWorkflows([template]);
|
||||||
|
|
||||||
return template;
|
return template;
|
||||||
},
|
};
|
||||||
async fetchCollectionById(collectionId: string): Promise<ITemplatesCollection | null> {
|
|
||||||
const settingsStore = useSettingsStore();
|
const fetchCollectionById = async (
|
||||||
const rootStore = useRootStore();
|
collectionId: string,
|
||||||
|
): Promise<ITemplatesCollection | null> => {
|
||||||
const apiEndpoint: string = settingsStore.templatesHost;
|
const apiEndpoint: string = settingsStore.templatesHost;
|
||||||
const versionCli: string = rootStore.versionCli;
|
const versionCli: string = rootStore.versionCli;
|
||||||
const response = await getCollectionById(apiEndpoint, collectionId, {
|
const response = await templatesApi.getCollectionById(apiEndpoint, collectionId, {
|
||||||
'n8n-version': versionCli,
|
'n8n-version': versionCli,
|
||||||
});
|
});
|
||||||
const collection: ITemplatesCollectionFull = {
|
const collection: ITemplatesCollectionFull = {
|
||||||
|
@ -317,104 +295,105 @@ export const useTemplatesStore = defineStore(STORES.TEMPLATES, {
|
||||||
full: true,
|
full: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.addCollections([collection]);
|
addCollections([collection]);
|
||||||
this.addWorkflows(response.collection.workflows);
|
addWorkflows(response.collection.workflows);
|
||||||
return this.getCollectionById(collectionId);
|
return getCollectionById.value[collectionId];
|
||||||
},
|
};
|
||||||
async getCategories(): Promise<ITemplatesCategory[]> {
|
|
||||||
const cachedCategories = this.allCategories;
|
const getCategories = async (): Promise<ITemplatesCategory[]> => {
|
||||||
|
const cachedCategories = allCategories.value;
|
||||||
if (cachedCategories.length) {
|
if (cachedCategories.length) {
|
||||||
return cachedCategories;
|
return cachedCategories;
|
||||||
}
|
}
|
||||||
const settingsStore = useSettingsStore();
|
|
||||||
const rootStore = useRootStore();
|
|
||||||
const apiEndpoint: string = settingsStore.templatesHost;
|
const apiEndpoint: string = settingsStore.templatesHost;
|
||||||
const versionCli: string = rootStore.versionCli;
|
const versionCli: string = rootStore.versionCli;
|
||||||
const response = await getCategories(apiEndpoint, { 'n8n-version': versionCli });
|
const response = await templatesApi.getCategories(apiEndpoint, {
|
||||||
|
'n8n-version': versionCli,
|
||||||
|
});
|
||||||
const categories = response.categories;
|
const categories = response.categories;
|
||||||
|
|
||||||
this.addCategories(categories);
|
addCategories(categories);
|
||||||
return categories;
|
return categories;
|
||||||
},
|
};
|
||||||
async getCollections(query: ITemplatesQuery): Promise<ITemplatesCollection[]> {
|
|
||||||
const cachedResults = this.getSearchedCollections(query);
|
const getCollections = async (query: ITemplatesQuery): Promise<ITemplatesCollection[]> => {
|
||||||
|
const cachedResults = getSearchedCollections.value(query);
|
||||||
if (cachedResults) {
|
if (cachedResults) {
|
||||||
return cachedResults;
|
return cachedResults;
|
||||||
}
|
}
|
||||||
|
|
||||||
const settingsStore = useSettingsStore();
|
|
||||||
const rootStore = useRootStore();
|
|
||||||
const apiEndpoint: string = settingsStore.templatesHost;
|
const apiEndpoint: string = settingsStore.templatesHost;
|
||||||
const versionCli: string = rootStore.versionCli;
|
const versionCli: string = rootStore.versionCli;
|
||||||
const response = await getCollections(apiEndpoint, query, { 'n8n-version': versionCli });
|
const response = await templatesApi.getCollections(apiEndpoint, query, {
|
||||||
|
'n8n-version': versionCli,
|
||||||
|
});
|
||||||
const collections = response.collections;
|
const collections = response.collections;
|
||||||
|
|
||||||
this.addCollections(collections);
|
addCollections(collections);
|
||||||
this.addCollectionSearch({ query, collections });
|
addCollectionsSearch({ query, _collections: collections });
|
||||||
collections.forEach((collection) =>
|
collections.forEach((collection) => addWorkflows(collection.workflows as ITemplatesWorkflow[]));
|
||||||
this.addWorkflows(collection.workflows as ITemplatesWorkflowFull[]),
|
|
||||||
);
|
|
||||||
|
|
||||||
return collections;
|
return collections;
|
||||||
},
|
};
|
||||||
async getWorkflows(query: ITemplatesQuery): Promise<ITemplatesWorkflow[]> {
|
|
||||||
const cachedResults = this.getSearchedWorkflows(query);
|
const getWorkflows = async (query: ITemplatesQuery): Promise<ITemplatesWorkflow[]> => {
|
||||||
|
const cachedResults = getSearchedWorkflows.value(query);
|
||||||
if (cachedResults) {
|
if (cachedResults) {
|
||||||
this.categories = this.workflowSearches[getSearchKey(query)].categories ?? [];
|
categories.value = workflowSearches.value[getSearchKey(query)].categories ?? [];
|
||||||
return cachedResults;
|
return cachedResults;
|
||||||
}
|
}
|
||||||
|
|
||||||
const settingsStore = useSettingsStore();
|
|
||||||
const rootStore = useRootStore();
|
|
||||||
const apiEndpoint: string = settingsStore.templatesHost;
|
const apiEndpoint: string = settingsStore.templatesHost;
|
||||||
const versionCli: string = rootStore.versionCli;
|
const versionCli: string = rootStore.versionCli;
|
||||||
|
const payload = await templatesApi.getWorkflows(
|
||||||
const payload = await getWorkflows(
|
|
||||||
apiEndpoint,
|
apiEndpoint,
|
||||||
{ ...query, page: 1, limit: TEMPLATES_PAGE_SIZE },
|
{ ...query, page: 1, limit: TEMPLATES_PAGE_SIZE },
|
||||||
{ 'n8n-version': versionCli },
|
{ 'n8n-version': versionCli },
|
||||||
);
|
);
|
||||||
|
|
||||||
this.addWorkflows(payload.workflows);
|
addWorkflows(payload.workflows);
|
||||||
this.addWorkflowsSearch({ ...payload, query });
|
addWorkflowsSearch({ ...payload, query });
|
||||||
return this.getSearchedWorkflows(query) || [];
|
return getSearchedWorkflows.value(query) || [];
|
||||||
},
|
};
|
||||||
async getMoreWorkflows(query: ITemplatesQuery): Promise<ITemplatesWorkflow[]> {
|
|
||||||
if (this.isSearchLoadingMore(query) && !this.isSearchFinished(query)) {
|
const getMoreWorkflows = async (query: ITemplatesQuery): Promise<ITemplatesWorkflow[]> => {
|
||||||
|
if (isSearchLoadingMore.value(query) && !isSearchFinished.value(query)) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
const cachedResults = this.getSearchedWorkflows(query) || [];
|
const cachedResults = getSearchedWorkflows.value(query) || [];
|
||||||
const settingsStore = useSettingsStore();
|
|
||||||
const apiEndpoint: string = settingsStore.templatesHost;
|
const apiEndpoint: string = settingsStore.templatesHost;
|
||||||
|
|
||||||
this.setWorkflowSearchLoading(query);
|
setWorkflowSearchLoading(query);
|
||||||
try {
|
try {
|
||||||
const payload = await getWorkflows(apiEndpoint, {
|
const payload = await templatesApi.getWorkflows(apiEndpoint, {
|
||||||
...query,
|
...query,
|
||||||
page: cachedResults.length / TEMPLATES_PAGE_SIZE + 1,
|
page: cachedResults.length / TEMPLATES_PAGE_SIZE + 1,
|
||||||
limit: TEMPLATES_PAGE_SIZE,
|
limit: TEMPLATES_PAGE_SIZE,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.setWorkflowSearchLoaded(query);
|
setWorkflowSearchLoaded(query);
|
||||||
this.addWorkflows(payload.workflows);
|
addWorkflows(payload.workflows);
|
||||||
this.addWorkflowsSearch({ ...payload, query });
|
addWorkflowsSearch({ ...payload, query });
|
||||||
|
|
||||||
return this.getSearchedWorkflows(query) || [];
|
return getSearchedWorkflows.value(query) || [];
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.setWorkflowSearchLoaded(query);
|
setWorkflowSearchLoaded(query);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
async getWorkflowTemplate(templateId: string): Promise<IWorkflowTemplate> {
|
|
||||||
const settingsStore = useSettingsStore();
|
const getWorkflowTemplate = async (templateId: string): Promise<IWorkflowTemplate> => {
|
||||||
const rootStore = useRootStore();
|
|
||||||
const apiEndpoint: string = settingsStore.templatesHost;
|
const apiEndpoint: string = settingsStore.templatesHost;
|
||||||
const versionCli: string = rootStore.versionCli;
|
const versionCli: string = rootStore.versionCli;
|
||||||
return await getWorkflowTemplate(apiEndpoint, templateId, { 'n8n-version': versionCli });
|
return await templatesApi.getWorkflowTemplate(apiEndpoint, templateId, {
|
||||||
},
|
'n8n-version': versionCli,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
async getFixedWorkflowTemplate(templateId: string): Promise<IWorkflowTemplate | undefined> {
|
const getFixedWorkflowTemplate = async (
|
||||||
const template = await this.getWorkflowTemplate(templateId);
|
templateId: string,
|
||||||
|
): Promise<IWorkflowTemplate | undefined> => {
|
||||||
|
const template = await getWorkflowTemplate(templateId);
|
||||||
if (template?.workflow?.nodes) {
|
if (template?.workflow?.nodes) {
|
||||||
template.workflow.nodes = getFixedNodesList(template.workflow.nodes) as INodeUi[];
|
template.workflow.nodes = getFixedNodesList(template.workflow.nodes) as INodeUi[];
|
||||||
template.workflow.nodes?.forEach((node) => {
|
template.workflow.nodes?.forEach((node) => {
|
||||||
|
@ -425,6 +404,46 @@ export const useTemplatesStore = defineStore(STORES.TEMPLATES, {
|
||||||
}
|
}
|
||||||
|
|
||||||
return template;
|
return template;
|
||||||
},
|
};
|
||||||
},
|
|
||||||
|
return {
|
||||||
|
categories,
|
||||||
|
collections,
|
||||||
|
workflows,
|
||||||
|
workflowSearches,
|
||||||
|
collectionSearches,
|
||||||
|
currentSessionId,
|
||||||
|
previousSessionId,
|
||||||
|
currentN8nPath,
|
||||||
|
allCategories,
|
||||||
|
getTemplatesById,
|
||||||
|
getFullTemplateById,
|
||||||
|
getCollectionById,
|
||||||
|
getCategoryById,
|
||||||
|
getSearchedCollections,
|
||||||
|
getSearchedWorkflows,
|
||||||
|
getSearchedWorkflowsTotal,
|
||||||
|
isSearchLoadingMore,
|
||||||
|
isSearchFinished,
|
||||||
|
hasCustomTemplatesHost,
|
||||||
|
websiteTemplateRepositoryURL,
|
||||||
|
websiteTemplateRepositoryParameters,
|
||||||
|
addCategories,
|
||||||
|
addCollections,
|
||||||
|
addWorkflows,
|
||||||
|
addCollectionsSearch,
|
||||||
|
addWorkflowsSearch,
|
||||||
|
setWorkflowSearchLoading,
|
||||||
|
setWorkflowSearchLoaded,
|
||||||
|
resetSessionId,
|
||||||
|
setSessionId,
|
||||||
|
fetchTemplateById,
|
||||||
|
fetchCollectionById,
|
||||||
|
getCategories,
|
||||||
|
getCollections,
|
||||||
|
getWorkflows,
|
||||||
|
getMoreWorkflows,
|
||||||
|
getWorkflowTemplate,
|
||||||
|
getFixedWorkflowTemplate,
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
|
@ -35,14 +35,14 @@ const collectionId = computed(() => {
|
||||||
return Array.isArray(id) ? id[0] : id;
|
return Array.isArray(id) ? id[0] : id;
|
||||||
});
|
});
|
||||||
|
|
||||||
const collection = computed(() => templatesStore.getCollectionById(collectionId.value));
|
const collection = computed(() => templatesStore.getCollectionById[collectionId.value]);
|
||||||
|
|
||||||
const collectionWorkflows = computed(() => {
|
const collectionWorkflows = computed(() => {
|
||||||
if (!collection.value || loading.value) {
|
if (!collection.value || loading.value) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
return collection.value.workflows
|
return collection.value.workflows
|
||||||
.map(({ id }) => templatesStore.getTemplateById(id.toString()))
|
.map(({ id }) => templatesStore.getTemplatesById(id.toString()))
|
||||||
.filter((workflow): workflow is ITemplatesWorkflow => !!workflow);
|
.filter((workflow): workflow is ITemplatesWorkflow => !!workflow);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -124,14 +124,16 @@ export class Supabase implements INodeType {
|
||||||
const items = this.getInputData();
|
const items = this.getInputData();
|
||||||
const returnData: INodeExecutionData[] = [];
|
const returnData: INodeExecutionData[] = [];
|
||||||
const length = items.length;
|
const length = items.length;
|
||||||
const qs: IDataObject = {};
|
let qs: IDataObject = {};
|
||||||
const resource = this.getNodeParameter('resource', 0);
|
const resource = this.getNodeParameter('resource', 0);
|
||||||
const operation = this.getNodeParameter('operation', 0);
|
const operation = this.getNodeParameter('operation', 0);
|
||||||
|
|
||||||
if (resource === 'row') {
|
if (resource === 'row') {
|
||||||
|
const tableId = this.getNodeParameter('tableId', 0) as string;
|
||||||
|
|
||||||
if (operation === 'create') {
|
if (operation === 'create') {
|
||||||
const records: IDataObject[] = [];
|
const records: IDataObject[] = [];
|
||||||
const tableId = this.getNodeParameter('tableId', 0) as string;
|
|
||||||
for (let i = 0; i < length; i++) {
|
for (let i = 0; i < length; i++) {
|
||||||
const record: IDataObject = {};
|
const record: IDataObject = {};
|
||||||
const dataToSend = this.getNodeParameter('dataToSend', 0) as
|
const dataToSend = this.getNodeParameter('dataToSend', 0) as
|
||||||
|
@ -185,7 +187,6 @@ export class Supabase implements INodeType {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (operation === 'delete') {
|
if (operation === 'delete') {
|
||||||
const tableId = this.getNodeParameter('tableId', 0) as string;
|
|
||||||
const filterType = this.getNodeParameter('filterType', 0) as string;
|
const filterType = this.getNodeParameter('filterType', 0) as string;
|
||||||
for (let i = 0; i < length; i++) {
|
for (let i = 0; i < length; i++) {
|
||||||
let endpoint = `/${tableId}`;
|
let endpoint = `/${tableId}`;
|
||||||
|
@ -241,7 +242,6 @@ export class Supabase implements INodeType {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (operation === 'get') {
|
if (operation === 'get') {
|
||||||
const tableId = this.getNodeParameter('tableId', 0) as string;
|
|
||||||
const endpoint = `/${tableId}`;
|
const endpoint = `/${tableId}`;
|
||||||
|
|
||||||
for (let i = 0; i < length; i++) {
|
for (let i = 0; i < length; i++) {
|
||||||
|
@ -281,11 +281,13 @@ export class Supabase implements INodeType {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (operation === 'getAll') {
|
if (operation === 'getAll') {
|
||||||
const tableId = this.getNodeParameter('tableId', 0) as string;
|
|
||||||
const returnAll = this.getNodeParameter('returnAll', 0);
|
const returnAll = this.getNodeParameter('returnAll', 0);
|
||||||
const filterType = this.getNodeParameter('filterType', 0) as string;
|
const filterType = this.getNodeParameter('filterType', 0) as string;
|
||||||
|
|
||||||
let endpoint = `/${tableId}`;
|
let endpoint = `/${tableId}`;
|
||||||
for (let i = 0; i < length; i++) {
|
for (let i = 0; i < length; i++) {
|
||||||
|
qs = {}; // reset qs
|
||||||
|
|
||||||
if (filterType === 'manual') {
|
if (filterType === 'manual') {
|
||||||
const matchType = this.getNodeParameter('matchType', 0) as string;
|
const matchType = this.getNodeParameter('matchType', 0) as string;
|
||||||
const keys = this.getNodeParameter('filters.conditions', i, []) as IDataObject[];
|
const keys = this.getNodeParameter('filters.conditions', i, []) as IDataObject[];
|
||||||
|
@ -342,7 +344,6 @@ export class Supabase implements INodeType {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (operation === 'update') {
|
if (operation === 'update') {
|
||||||
const tableId = this.getNodeParameter('tableId', 0) as string;
|
|
||||||
const filterType = this.getNodeParameter('filterType', 0) as string;
|
const filterType = this.getNodeParameter('filterType', 0) as string;
|
||||||
let endpoint = `/${tableId}`;
|
let endpoint = `/${tableId}`;
|
||||||
for (let i = 0; i < length; i++) {
|
for (let i = 0; i < length; i++) {
|
||||||
|
|
|
@ -408,7 +408,8 @@ export function convertNodeToAiTool<
|
||||||
};
|
};
|
||||||
|
|
||||||
const noticeProp: INodeProperties = {
|
const noticeProp: INodeProperties = {
|
||||||
displayName: 'Use the expression {{ $fromAI() }} for any data to be filled by the model',
|
displayName:
|
||||||
|
"Use the expression {{ $fromAI('placeholder_name') }} for any data to be filled by the model",
|
||||||
name: 'notice',
|
name: 'notice',
|
||||||
type: 'notice',
|
type: 'notice',
|
||||||
default: '',
|
default: '',
|
||||||
|
|
|
@ -946,7 +946,7 @@ export class WorkflowDataProxy {
|
||||||
defaultValue?: unknown,
|
defaultValue?: unknown,
|
||||||
) => {
|
) => {
|
||||||
if (!name || name === '') {
|
if (!name || name === '') {
|
||||||
throw new ExpressionError('Please provide a key', {
|
throw new ExpressionError("Add a key, e.g. $fromAI('placeholder_name')", {
|
||||||
runIndex: that.runIndex,
|
runIndex: that.runIndex,
|
||||||
itemIndex: that.itemIndex,
|
itemIndex: that.itemIndex,
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue