mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-25 20:54:07 -08:00
refactor: Fix type issues for parameter input components (#9449)
This commit is contained in:
parent
cd751e7cc8
commit
711c46f205
|
@ -37,4 +37,4 @@ export namespace n8n {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ExtendedValidationResult = Partial<ValidationResult> & { fieldName?: string };
|
export type ExtendedValidationResult = ValidationResult & { fieldName?: string };
|
||||||
|
|
|
@ -99,6 +99,7 @@ import type {
|
||||||
WorkflowActivateMode,
|
WorkflowActivateMode,
|
||||||
WorkflowExecuteMode,
|
WorkflowExecuteMode,
|
||||||
CallbackManager,
|
CallbackManager,
|
||||||
|
INodeParameters,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import {
|
import {
|
||||||
ExpressionError,
|
ExpressionError,
|
||||||
|
@ -2121,13 +2122,12 @@ export function cleanupParameterData(inputData: NodeParameterValueType): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof inputData === 'object') {
|
if (typeof inputData === 'object') {
|
||||||
type Key = keyof typeof inputData;
|
Object.keys(inputData).forEach((key) => {
|
||||||
(Object.keys(inputData) as Key[]).forEach((key) => {
|
const value = (inputData as INodeParameters)[key];
|
||||||
const value = inputData[key];
|
|
||||||
if (typeof value === 'object') {
|
if (typeof value === 'object') {
|
||||||
if (DateTime.isDateTime(value)) {
|
if (DateTime.isDateTime(value)) {
|
||||||
// Is a special luxon date so convert to string
|
// Is a special luxon date so convert to string
|
||||||
inputData[key] = value.toString();
|
(inputData as INodeParameters)[key] = value.toString();
|
||||||
} else {
|
} else {
|
||||||
cleanupParameterData(value);
|
cleanupParameterData(value);
|
||||||
}
|
}
|
||||||
|
@ -2230,6 +2230,7 @@ const validateCollection = (
|
||||||
return validationResult;
|
return validationResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (validationResult.valid) {
|
||||||
for (const value of Array.isArray(validationResult.newValue)
|
for (const value of Array.isArray(validationResult.newValue)
|
||||||
? (validationResult.newValue as IDataObject[])
|
? (validationResult.newValue as IDataObject[])
|
||||||
: [validationResult.newValue as IDataObject]) {
|
: [validationResult.newValue as IDataObject]) {
|
||||||
|
@ -2254,6 +2255,7 @@ const validateCollection = (
|
||||||
value[key] = fieldValidationResult.newValue;
|
value[key] = fieldValidationResult.newValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return validationResult;
|
return validationResult;
|
||||||
};
|
};
|
||||||
|
|
|
@ -130,15 +130,17 @@ export type EndpointStyle = {
|
||||||
hoverMessage?: string;
|
hoverMessage?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface IUpdateInformation {
|
export interface IUpdateInformation<
|
||||||
name: string;
|
T extends NodeParameterValueType =
|
||||||
key?: string;
|
|
||||||
value:
|
|
||||||
| string
|
| string
|
||||||
| number
|
| number
|
||||||
| { [key: string]: string | number | boolean }
|
| { [key: string]: string | number | boolean }
|
||||||
| NodeParameterValueType
|
| NodeParameterValueType
|
||||||
| INodeParameters; // with null makes problems in NodeSettings.vue
|
| INodeParameters,
|
||||||
|
> {
|
||||||
|
name: string;
|
||||||
|
key?: string;
|
||||||
|
value: T;
|
||||||
node?: string;
|
node?: string;
|
||||||
oldValue?: string | number;
|
oldValue?: string | number;
|
||||||
type?: 'optionsOrderChanged';
|
type?: 'optionsOrderChanged';
|
||||||
|
|
|
@ -1,20 +1,27 @@
|
||||||
import { defineComponent } from 'vue';
|
|
||||||
import type { Diagnostic } from '@codemirror/lint';
|
import type { Diagnostic } from '@codemirror/lint';
|
||||||
import { linter as createLinter } from '@codemirror/lint';
|
import { linter as createLinter } from '@codemirror/lint';
|
||||||
import type { EditorView } from '@codemirror/view';
|
import type { EditorView } from '@codemirror/view';
|
||||||
import * as esprima from 'esprima-next';
|
import * as esprima from 'esprima-next';
|
||||||
import type { Node } from 'estree';
|
import type { Node } from 'estree';
|
||||||
import type { CodeNodeEditorLanguage } from 'n8n-workflow';
|
import type { CodeExecutionMode, CodeNodeEditorLanguage } from 'n8n-workflow';
|
||||||
|
import { type PropType, defineComponent } from 'vue';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DEFAULT_LINTER_DELAY_IN_MS,
|
DEFAULT_LINTER_DELAY_IN_MS,
|
||||||
DEFAULT_LINTER_SEVERITY,
|
DEFAULT_LINTER_SEVERITY,
|
||||||
OFFSET_FOR_SCRIPT_WRAPPER,
|
OFFSET_FOR_SCRIPT_WRAPPER,
|
||||||
} from './constants';
|
} from './constants';
|
||||||
import { walk } from './utils';
|
|
||||||
import type { RangeNode } from './types';
|
import type { RangeNode } from './types';
|
||||||
|
import { walk } from './utils';
|
||||||
|
|
||||||
export const linterExtension = defineComponent({
|
export const linterExtension = defineComponent({
|
||||||
|
props: {
|
||||||
|
mode: {
|
||||||
|
type: String as PropType<CodeExecutionMode>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
editor: { type: Object as PropType<EditorView | null>, default: null },
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
createLinter(language: CodeNodeEditorLanguage) {
|
createLinter(language: CodeNodeEditorLanguage) {
|
||||||
switch (language) {
|
switch (language) {
|
||||||
|
|
|
@ -72,7 +72,7 @@ export interface Props {
|
||||||
nodeValues: INodeParameters;
|
nodeValues: INodeParameters;
|
||||||
parameter: INodeProperties;
|
parameter: INodeProperties;
|
||||||
path: string;
|
path: string;
|
||||||
values: INodeProperties;
|
values: INodeParameters;
|
||||||
isReadOnly?: boolean;
|
isReadOnly?: boolean;
|
||||||
}
|
}
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
|
|
|
@ -25,14 +25,18 @@ export default defineComponent({
|
||||||
name: 'ExpandableInputEdit',
|
name: 'ExpandableInputEdit',
|
||||||
components: { ExpandableInputBase },
|
components: { ExpandableInputBase },
|
||||||
props: {
|
props: {
|
||||||
modelValue: {},
|
modelValue: {
|
||||||
placeholder: {},
|
type: String,
|
||||||
maxlength: {},
|
required: true,
|
||||||
autofocus: {},
|
},
|
||||||
|
placeholder: { type: String, required: true },
|
||||||
|
maxlength: { type: Number },
|
||||||
|
autofocus: { type: Boolean },
|
||||||
eventBus: {
|
eventBus: {
|
||||||
type: Object as PropType<EventBus>,
|
type: Object as PropType<EventBus>,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
emits: ['update:modelValue', 'enter', 'blur', 'esc'],
|
||||||
mounted() {
|
mounted() {
|
||||||
// autofocus on input element is not reliable
|
// autofocus on input element is not reliable
|
||||||
if (this.autofocus && this.$refs.input) {
|
if (this.autofocus && this.$refs.input) {
|
||||||
|
|
|
@ -80,7 +80,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { type PropType, defineComponent } from 'vue';
|
||||||
import { mapStores } from 'pinia';
|
import { mapStores } from 'pinia';
|
||||||
import ExpressionEditorModalInput from '@/components/ExpressionEditorModal/ExpressionEditorModalInput.vue';
|
import ExpressionEditorModalInput from '@/components/ExpressionEditorModal/ExpressionEditorModalInput.vue';
|
||||||
import VariableSelector from '@/components/VariableSelector.vue';
|
import VariableSelector from '@/components/VariableSelector.vue';
|
||||||
|
@ -98,6 +98,7 @@ import { useDebounce } from '@/composables/useDebounce';
|
||||||
import type { Segment } from '@/types/expressions';
|
import type { Segment } from '@/types/expressions';
|
||||||
import ExpressionOutput from './InlineExpressionEditor/ExpressionOutput.vue';
|
import ExpressionOutput from './InlineExpressionEditor/ExpressionOutput.vue';
|
||||||
import { outputTheme } from './ExpressionEditorModal/theme';
|
import { outputTheme } from './ExpressionEditorModal/theme';
|
||||||
|
import type { INodeProperties } from 'n8n-workflow';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'ExpressionEdit',
|
name: 'ExpressionEdit',
|
||||||
|
@ -112,7 +113,7 @@ export default defineComponent({
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
parameter: {
|
parameter: {
|
||||||
type: Object,
|
type: Object as PropType<INodeProperties>,
|
||||||
default: () => ({}),
|
default: () => ({}),
|
||||||
},
|
},
|
||||||
path: {
|
path: {
|
||||||
|
|
|
@ -23,16 +23,17 @@ const inlineInput = ref<InstanceType<typeof InlineExpressionEditorInput>>();
|
||||||
type Props = {
|
type Props = {
|
||||||
path: string;
|
path: string;
|
||||||
modelValue: string;
|
modelValue: string;
|
||||||
isReadOnly: boolean;
|
rows?: number;
|
||||||
rows: number;
|
additionalExpressionData?: IDataObject;
|
||||||
isAssignment: boolean;
|
eventBus?: EventBus;
|
||||||
additionalExpressionData: IDataObject;
|
isReadOnly?: boolean;
|
||||||
eventBus: EventBus;
|
isAssignment?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
rows: 5,
|
rows: 5,
|
||||||
isAssignment: false,
|
isAssignment: false,
|
||||||
|
isReadOnly: false,
|
||||||
additionalExpressionData: () => ({}),
|
additionalExpressionData: () => ({}),
|
||||||
eventBus: () => createEventBus(),
|
eventBus: () => createEventBus(),
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,10 +5,11 @@ import ParameterInputFull from '@/components/ParameterInputFull.vue';
|
||||||
import ParameterIssues from '@/components/ParameterIssues.vue';
|
import ParameterIssues from '@/components/ParameterIssues.vue';
|
||||||
import { useI18n } from '@/composables/useI18n';
|
import { useI18n } from '@/composables/useI18n';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
import {
|
import type {
|
||||||
type FilterConditionValue,
|
FilterConditionValue,
|
||||||
type FilterOptionsValue,
|
FilterOptionsValue,
|
||||||
type INodeProperties,
|
INodeProperties,
|
||||||
|
NodeParameterValue,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import OperatorSelect from './OperatorSelect.vue';
|
import OperatorSelect from './OperatorSelect.vue';
|
||||||
|
@ -100,11 +101,11 @@ const rightParameter = computed<INodeProperties>(() => {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const onLeftValueChange = (update: IUpdateInformation): void => {
|
const onLeftValueChange = (update: IUpdateInformation<NodeParameterValue>): void => {
|
||||||
condition.value.leftValue = update.value;
|
condition.value.leftValue = update.value;
|
||||||
};
|
};
|
||||||
|
|
||||||
const onRightValueChange = (update: IUpdateInformation): void => {
|
const onRightValueChange = (update: IUpdateInformation<NodeParameterValue>): void => {
|
||||||
condition.value.rightValue = update.value;
|
condition.value.rightValue = update.value;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -17,12 +17,24 @@ import type { ConditionResult, FilterOperator } from './types';
|
||||||
export const getFilterOperator = (key: string) =>
|
export const getFilterOperator = (key: string) =>
|
||||||
OPERATORS_BY_ID[key as FilterOperatorId] as FilterOperator;
|
OPERATORS_BY_ID[key as FilterOperatorId] as FilterOperator;
|
||||||
|
|
||||||
const convertToType = (value: unknown, type: FilterOperatorType): unknown => {
|
const getTargetType = (type: FilterOperatorType) => {
|
||||||
|
if (type === 'number') return 'number';
|
||||||
|
if (type === 'boolean') return 'boolean';
|
||||||
|
return 'string';
|
||||||
|
};
|
||||||
|
|
||||||
|
const convertToType = (value: NodeParameterValue, type: FilterOperatorType): NodeParameterValue => {
|
||||||
if (type === 'any') return value;
|
if (type === 'any') return value;
|
||||||
|
|
||||||
const fallback = type === 'boolean' ? false : value;
|
const fallback = type === 'boolean' ? false : value;
|
||||||
|
|
||||||
return validateFieldType('filter', value, type, { parseStrings: true }).newValue ?? fallback;
|
const validationResult = validateFieldType('filter', value, getTargetType(type), {
|
||||||
|
parseStrings: true,
|
||||||
|
});
|
||||||
|
if (!validationResult.valid) {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
return validationResult.newValue ?? fallback;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const handleOperatorChange = ({
|
export const handleOperatorChange = ({
|
||||||
|
@ -42,6 +54,7 @@ export const handleOperatorChange = ({
|
||||||
if (leftTypeChanged && !isExpression(condition.leftValue)) {
|
if (leftTypeChanged && !isExpression(condition.leftValue)) {
|
||||||
condition.leftValue = convertToType(condition.leftValue, newOperator.type);
|
condition.leftValue = convertToType(condition.leftValue, newOperator.type);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rightTypeChanged && !newOperator.singleValue && !isExpression(condition.rightValue)) {
|
if (rightTypeChanged && !newOperator.singleValue && !isExpression(condition.rightValue)) {
|
||||||
condition.rightValue = convertToType(condition.rightValue, newRightType);
|
condition.rightValue = convertToType(condition.rightValue, newRightType);
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,7 +95,7 @@
|
||||||
|
|
||||||
<div v-if="parameterOptions.length > 0 && !isReadOnly" class="controls">
|
<div v-if="parameterOptions.length > 0 && !isReadOnly" class="controls">
|
||||||
<n8n-button
|
<n8n-button
|
||||||
v-if="parameter.options.length === 1"
|
v-if="parameter.options && parameter.options.length === 1"
|
||||||
type="tertiary"
|
type="tertiary"
|
||||||
block
|
block
|
||||||
:label="getPlaceholderText"
|
:label="getPlaceholderText"
|
||||||
|
@ -126,12 +126,7 @@ import { defineComponent } from 'vue';
|
||||||
import type { PropType } from 'vue';
|
import type { PropType } from 'vue';
|
||||||
import type { IUpdateInformation } from '@/Interface';
|
import type { IUpdateInformation } from '@/Interface';
|
||||||
|
|
||||||
import type {
|
import type { INodeParameters, INodeProperties, INodePropertyCollection } from 'n8n-workflow';
|
||||||
INodeParameters,
|
|
||||||
INodeProperties,
|
|
||||||
INodePropertyCollection,
|
|
||||||
NodeParameterValue,
|
|
||||||
} from 'n8n-workflow';
|
|
||||||
import { deepCopy, isINodePropertyCollectionList } from 'n8n-workflow';
|
import { deepCopy, isINodePropertyCollectionList } from 'n8n-workflow';
|
||||||
|
|
||||||
import { get } from 'lodash-es';
|
import { get } from 'lodash-es';
|
||||||
|
@ -140,7 +135,7 @@ export default defineComponent({
|
||||||
name: 'FixedCollectionParameter',
|
name: 'FixedCollectionParameter',
|
||||||
props: {
|
props: {
|
||||||
nodeValues: {
|
nodeValues: {
|
||||||
type: Object as PropType<Record<string, INodeParameters[]>>,
|
type: Object as PropType<INodeParameters>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
parameter: {
|
parameter: {
|
||||||
|
@ -297,23 +292,18 @@ export default defineComponent({
|
||||||
optionParameter.typeOptions.multipleValues === true
|
optionParameter.typeOptions.multipleValues === true
|
||||||
) {
|
) {
|
||||||
// Multiple values are allowed so append option to array
|
// Multiple values are allowed so append option to array
|
||||||
newParameterValue[optionParameter.name] = get(
|
const multiValue = get(this.nodeValues, [this.path, optionParameter.name], []);
|
||||||
this.nodeValues,
|
|
||||||
[this.path, optionParameter.name],
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
if (Array.isArray(optionParameter.default)) {
|
if (Array.isArray(optionParameter.default)) {
|
||||||
(newParameterValue[optionParameter.name] as INodeParameters[]).push(
|
multiValue.push(...deepCopy(optionParameter.default));
|
||||||
...deepCopy(optionParameter.default as INodeParameters[]),
|
|
||||||
);
|
|
||||||
} else if (
|
} else if (
|
||||||
optionParameter.default !== '' &&
|
optionParameter.default !== '' &&
|
||||||
typeof optionParameter.default !== 'object'
|
typeof optionParameter.default !== 'object'
|
||||||
) {
|
) {
|
||||||
(newParameterValue[optionParameter.name] as NodeParameterValue[]).push(
|
multiValue.push(deepCopy(optionParameter.default));
|
||||||
deepCopy(optionParameter.default),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
newParameterValue[optionParameter.name] = multiValue;
|
||||||
} else {
|
} else {
|
||||||
// Add a new option
|
// Add a new option
|
||||||
newParameterValue[optionParameter.name] = deepCopy(optionParameter.default);
|
newParameterValue[optionParameter.name] = deepCopy(optionParameter.default);
|
||||||
|
@ -322,7 +312,7 @@ export default defineComponent({
|
||||||
|
|
||||||
let newValue;
|
let newValue;
|
||||||
if (this.multipleValues) {
|
if (this.multipleValues) {
|
||||||
newValue = get(this.nodeValues, name, [] as INodeParameters[]);
|
newValue = get(this.nodeValues, name, []) as INodeParameters[];
|
||||||
|
|
||||||
newValue.push(newParameterValue);
|
newValue.push(newParameterValue);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -102,7 +102,7 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
nodeValues: {
|
nodeValues: {
|
||||||
type: Object as PropType<Record<string, INodeParameters[]>>,
|
type: Object as PropType<INodeParameters>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
parameter: {
|
parameter: {
|
||||||
|
@ -159,7 +159,7 @@ export default defineComponent({
|
||||||
methods: {
|
methods: {
|
||||||
addItem() {
|
addItem() {
|
||||||
const name = this.getPath();
|
const name = this.getPath();
|
||||||
const currentValue = get(this.nodeValues, name, [] as INodeParameters[]);
|
const currentValue = get(this.nodeValues, name, []) as INodeParameters[];
|
||||||
|
|
||||||
currentValue.push(deepCopy(this.parameter.default as INodeParameters));
|
currentValue.push(deepCopy(this.parameter.default as INodeParameters));
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,12 @@ export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
nodeType: {
|
nodeType: {
|
||||||
type: Object as PropType<
|
type: Object as PropType<
|
||||||
INodeTypeDescription | IVersionNode | SimplifiedNodeType | ActionTypeDescription | null
|
| INodeTypeDescription
|
||||||
|
| IVersionNode
|
||||||
|
| SimplifiedNodeType
|
||||||
|
| ActionTypeDescription
|
||||||
|
| null
|
||||||
|
| undefined
|
||||||
>,
|
>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
<template>
|
<template>
|
||||||
<span :class="$style.container" data-test-id="node-title-container" @click="onEdit">
|
<span :class="$style.container" data-test-id="node-title-container" @click="onEdit">
|
||||||
<span :class="$style.iconWrapper"><NodeIcon :node-type="nodeType" :size="18" /></span>
|
<span :class="$style.iconWrapper">
|
||||||
|
<NodeIcon :node-type="nodeType" :size="18" />
|
||||||
|
</span>
|
||||||
<n8n-popover placement="right" width="200" :visible="editName" :disabled="!editable">
|
<n8n-popover placement="right" width="200" :visible="editName" :disabled="!editable">
|
||||||
<div
|
<div
|
||||||
:class="$style.editContainer"
|
:class="$style.editContainer"
|
||||||
|
@ -39,56 +41,47 @@
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import NodeIcon from '@/components/NodeIcon.vue';
|
import NodeIcon from '@/components/NodeIcon.vue';
|
||||||
import { defineComponent } from 'vue';
|
import type { INodeTypeDescription } from 'n8n-workflow';
|
||||||
|
import { computed, nextTick, ref } from 'vue';
|
||||||
|
|
||||||
export default defineComponent({
|
type Props = {
|
||||||
name: 'NodeTitle',
|
modelValue: string;
|
||||||
components: {
|
nodeType?: INodeTypeDescription;
|
||||||
NodeIcon,
|
readOnly?: boolean;
|
||||||
},
|
|
||||||
props: {
|
|
||||||
modelValue: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
nodeType: {},
|
|
||||||
readOnly: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
editName: false,
|
|
||||||
newName: '',
|
|
||||||
};
|
};
|
||||||
},
|
|
||||||
computed: {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
editable(): boolean {
|
modelValue: '',
|
||||||
return !this.readOnly && window === window.parent;
|
nodeType: undefined,
|
||||||
},
|
readOnly: false,
|
||||||
},
|
});
|
||||||
methods: {
|
const emit = defineEmits<{
|
||||||
async onEdit() {
|
(event: 'update:model-value', value: string): void;
|
||||||
this.newName = this.modelValue;
|
}>();
|
||||||
this.editName = true;
|
const editName = ref(false);
|
||||||
await this.$nextTick();
|
const newName = ref('');
|
||||||
const inputRef = this.$refs.input as HTMLInputElement | undefined;
|
const input = ref<HTMLInputElement>();
|
||||||
if (inputRef) {
|
|
||||||
inputRef.focus();
|
const editable = computed(() => !props.readOnly && window === window.parent);
|
||||||
|
|
||||||
|
async function onEdit() {
|
||||||
|
newName.value = props.modelValue;
|
||||||
|
editName.value = true;
|
||||||
|
await nextTick();
|
||||||
|
if (input.value) {
|
||||||
|
input.value.focus();
|
||||||
}
|
}
|
||||||
},
|
|
||||||
onRename() {
|
|
||||||
if (this.newName.trim() !== '') {
|
|
||||||
this.$emit('update:modelValue', this.newName.trim());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.editName = false;
|
function onRename() {
|
||||||
},
|
if (newName.value.trim() !== '') {
|
||||||
},
|
emit('update:model-value', newName.value.trim());
|
||||||
});
|
}
|
||||||
|
|
||||||
|
editName.value = false;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
|
|
@ -63,7 +63,7 @@ export default defineComponent({
|
||||||
props: ['modalName'],
|
props: ['modalName'],
|
||||||
setup() {
|
setup() {
|
||||||
return {
|
return {
|
||||||
...useToast(),
|
toast: useToast(),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
@ -94,7 +94,7 @@ export default defineComponent({
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.uiStore.applyForOnboardingCall(this.email);
|
await this.uiStore.applyForOnboardingCall(this.email);
|
||||||
this.showMessage({
|
this.toast.showMessage({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
title: this.$locale.baseText('onboardingCallSignupSucess.title'),
|
title: this.$locale.baseText('onboardingCallSignupSucess.title'),
|
||||||
message: this.$locale.baseText('onboardingCallSignupSucess.message'),
|
message: this.$locale.baseText('onboardingCallSignupSucess.message'),
|
||||||
|
@ -102,7 +102,7 @@ export default defineComponent({
|
||||||
this.okToClose = true;
|
this.okToClose = true;
|
||||||
this.modalBus.emit('close');
|
this.modalBus.emit('close');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.showError(
|
this.toast.showError(
|
||||||
e,
|
e,
|
||||||
this.$locale.baseText('onboardingCallSignupFailed.title'),
|
this.$locale.baseText('onboardingCallSignupFailed.title'),
|
||||||
this.$locale.baseText('onboardingCallSignupFailed.message'),
|
this.$locale.baseText('onboardingCallSignupFailed.message'),
|
||||||
|
|
|
@ -80,7 +80,7 @@
|
||||||
</n8n-text>
|
</n8n-text>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-if="outputMode === 'logs'" #content>
|
<template v-if="outputMode === 'logs' && node" #content>
|
||||||
<RunDataAi :node="node" :run-index="runIndex" />
|
<RunDataAi :node="node" :run-index="runIndex" />
|
||||||
</template>
|
</template>
|
||||||
<template #recovered-artificial-output-data>
|
<template #recovered-artificial-output-data>
|
||||||
|
@ -123,6 +123,9 @@ const OUTPUT_TYPE = {
|
||||||
LOGS: 'logs',
|
LOGS: 'logs',
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
type OutputTypeKey = keyof typeof OUTPUT_TYPE;
|
||||||
|
type OutputType = (typeof OUTPUT_TYPE)[OutputTypeKey];
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'OutputPanel',
|
name: 'OutputPanel',
|
||||||
components: { RunData, RunInfo, RunDataAi },
|
components: { RunData, RunInfo, RunDataAi },
|
||||||
|
@ -183,8 +186,8 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapStores(useNodeTypesStore, useNDVStore, useUIStore, useWorkflowsStore),
|
...mapStores(useNodeTypesStore, useNDVStore, useUIStore, useWorkflowsStore),
|
||||||
node(): INodeUi | null {
|
node(): INodeUi | undefined {
|
||||||
return this.ndvStore.activeNode;
|
return this.ndvStore.activeNode ?? undefined;
|
||||||
},
|
},
|
||||||
nodeType(): INodeTypeDescription | null {
|
nodeType(): INodeTypeDescription | null {
|
||||||
if (this.node) {
|
if (this.node) {
|
||||||
|
@ -193,7 +196,7 @@ export default defineComponent({
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
isTriggerNode(): boolean {
|
isTriggerNode(): boolean {
|
||||||
return this.nodeTypesStore.isTriggerNode(this.node?.type ?? '');
|
return !!this.node && this.nodeTypesStore.isTriggerNode(this.node.type);
|
||||||
},
|
},
|
||||||
hasAiMetadata(): boolean {
|
hasAiMetadata(): boolean {
|
||||||
if (this.node) {
|
if (this.node) {
|
||||||
|
@ -263,11 +266,11 @@ export default defineComponent({
|
||||||
|
|
||||||
const runData: IRunData | null = this.workflowRunData;
|
const runData: IRunData | null = this.workflowRunData;
|
||||||
|
|
||||||
if (runData === null || !runData.hasOwnProperty(this.node.name)) {
|
if (runData === null || (this.node && !runData.hasOwnProperty(this.node.name))) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (runData[this.node.name].length) {
|
if (this.node && runData[this.node.name].length) {
|
||||||
return runData[this.node.name].length;
|
return runData[this.node.name].length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -327,7 +330,7 @@ export default defineComponent({
|
||||||
onRunIndexChange(run: number) {
|
onRunIndexChange(run: number) {
|
||||||
this.$emit('runChange', run);
|
this.$emit('runChange', run);
|
||||||
},
|
},
|
||||||
onUpdateOutputMode(outputMode: (typeof OUTPUT_TYPE)[keyof typeof OUTPUT_TYPE]) {
|
onUpdateOutputMode(outputMode: OutputType) {
|
||||||
if (outputMode === OUTPUT_TYPE.LOGS) {
|
if (outputMode === OUTPUT_TYPE.LOGS) {
|
||||||
ndvEventBus.emit('setPositionByName', 'minLeft');
|
ndvEventBus.emit('setPositionByName', 'minLeft');
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import type { NotificationInstance } from 'element-plus';
|
import type { NotificationHandle } from 'element-plus';
|
||||||
import { sanitizeHtml } from '@/utils/htmlUtils';
|
import { sanitizeHtml } from '@/utils/htmlUtils';
|
||||||
import { useToast } from '@/composables/useToast';
|
import { useToast } from '@/composables/useToast';
|
||||||
|
|
||||||
|
@ -21,16 +21,16 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
return {
|
return {
|
||||||
...useToast(),
|
toast: useToast(),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
alert: null as null | NotificationInstance,
|
alert: null as NotificationHandle | null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.alert = this.showAlert({
|
this.alert = this.toast.showAlert({
|
||||||
title: '',
|
title: '',
|
||||||
message: sanitizeHtml(this.message),
|
message: sanitizeHtml(this.message),
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
|
|
|
@ -537,7 +537,7 @@ type Props = {
|
||||||
hint?: string;
|
hint?: string;
|
||||||
inputSize?: InputSize;
|
inputSize?: InputSize;
|
||||||
eventSource?: string;
|
eventSource?: string;
|
||||||
expressionEvaluated?: string;
|
expressionEvaluated: unknown;
|
||||||
documentationUrl?: string;
|
documentationUrl?: string;
|
||||||
isAssignment?: boolean;
|
isAssignment?: boolean;
|
||||||
isReadOnly?: boolean;
|
isReadOnly?: boolean;
|
||||||
|
@ -555,7 +555,6 @@ const props = withDefaults(defineProps<Props>(), {
|
||||||
hint: undefined,
|
hint: undefined,
|
||||||
inputSize: undefined,
|
inputSize: undefined,
|
||||||
eventSource: undefined,
|
eventSource: undefined,
|
||||||
expressionEvaluated: undefined,
|
|
||||||
documentationUrl: undefined,
|
documentationUrl: undefined,
|
||||||
isReadOnly: false,
|
isReadOnly: false,
|
||||||
isAssignment: false,
|
isAssignment: false,
|
||||||
|
@ -1266,7 +1265,7 @@ async function optionSelected(command: string) {
|
||||||
|
|
||||||
await setFocus();
|
await setFocus();
|
||||||
} else if (command === 'removeExpression') {
|
} else if (command === 'removeExpression') {
|
||||||
let value: NodeParameterValueType = props.expressionEvaluated;
|
let value = props.expressionEvaluated;
|
||||||
|
|
||||||
isFocused.value = false;
|
isFocused.value = false;
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
:error-highlight="showRequiredErrors"
|
:error-highlight="showRequiredErrors"
|
||||||
:is-for-credential="true"
|
:is-for-credential="true"
|
||||||
:event-source="eventSource"
|
:event-source="eventSource"
|
||||||
:hint="!showRequiredErrors ? hint : ''"
|
:hint="!showRequiredErrors && hint ? hint : ''"
|
||||||
:event-bus="eventBus"
|
:event-bus="eventBus"
|
||||||
@focus="onFocus"
|
@focus="onFocus"
|
||||||
@blur="onBlur"
|
@blur="onBlur"
|
||||||
|
@ -61,7 +61,12 @@ import { defineComponent } from 'vue';
|
||||||
import type { PropType } from 'vue';
|
import type { PropType } from 'vue';
|
||||||
import ParameterInputWrapper from './ParameterInputWrapper.vue';
|
import ParameterInputWrapper from './ParameterInputWrapper.vue';
|
||||||
import { isValueExpression } from '@/utils/nodeTypesUtils';
|
import { isValueExpression } from '@/utils/nodeTypesUtils';
|
||||||
import type { INodeParameterResourceLocator, INodeProperties, IParameterLabel } from 'n8n-workflow';
|
import type {
|
||||||
|
INodeParameterResourceLocator,
|
||||||
|
INodeProperties,
|
||||||
|
IParameterLabel,
|
||||||
|
NodeParameterValueType,
|
||||||
|
} from 'n8n-workflow';
|
||||||
import { mapStores } from 'pinia';
|
import { mapStores } from 'pinia';
|
||||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||||
import { createEventBus } from 'n8n-design-system/utils';
|
import { createEventBus } from 'n8n-design-system/utils';
|
||||||
|
@ -75,8 +80,11 @@ export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
parameter: {
|
parameter: {
|
||||||
type: Object as PropType<INodeProperties>,
|
type: Object as PropType<INodeProperties>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: Object as PropType<NodeParameterValueType>,
|
||||||
},
|
},
|
||||||
value: {},
|
|
||||||
showValidationWarnings: {
|
showValidationWarnings: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
},
|
},
|
||||||
|
|
|
@ -150,6 +150,7 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
path: {
|
path: {
|
||||||
type: String,
|
type: String,
|
||||||
|
required: true,
|
||||||
},
|
},
|
||||||
value: {
|
value: {
|
||||||
type: [Number, String, Boolean, Array, Object] as PropType<NodeParameterValueType>,
|
type: [Number, String, Boolean, Array, Object] as PropType<NodeParameterValueType>,
|
||||||
|
@ -187,7 +188,7 @@ export default defineComponent({
|
||||||
node(): INodeUi | null {
|
node(): INodeUi | null {
|
||||||
return this.ndvStore.activeNode;
|
return this.ndvStore.activeNode;
|
||||||
},
|
},
|
||||||
hint(): string | null {
|
hint(): string {
|
||||||
return this.i18n.nodeText().hint(this.parameter, this.path);
|
return this.i18n.nodeText().hint(this.parameter, this.path);
|
||||||
},
|
},
|
||||||
isInputTypeString(): boolean {
|
isInputTypeString(): boolean {
|
||||||
|
@ -260,8 +261,10 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onDrop(newParamValue: string) {
|
onDrop(newParamValue: string) {
|
||||||
const updatedValue = getMappedResult(this.parameter, newParamValue, this.value);
|
const value = this.value;
|
||||||
const prevValue = this.isResourceLocator ? this.value.value : this.value;
|
const updatedValue = getMappedResult(this.parameter, newParamValue, value);
|
||||||
|
const prevValue =
|
||||||
|
this.isResourceLocator && isResourceLocatorValue(value) ? value.value : value;
|
||||||
|
|
||||||
if (updatedValue.startsWith('=')) {
|
if (updatedValue.startsWith('=')) {
|
||||||
this.forceShowExpression = true;
|
this.forceShowExpression = true;
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
>
|
>
|
||||||
<MultipleParameter
|
<MultipleParameter
|
||||||
:parameter="parameter"
|
:parameter="parameter"
|
||||||
:values="nodeHelpers.getParameterValue(nodeValues, parameter.name, path)"
|
:values="getParameterValue(parameter.name)"
|
||||||
:node-values="nodeValues"
|
:node-values="nodeValues"
|
||||||
:path="getPath(parameter.name)"
|
:path="getPath(parameter.name)"
|
||||||
:is-read-only="isReadOnly"
|
:is-read-only="isReadOnly"
|
||||||
|
@ -70,7 +70,7 @@
|
||||||
<CollectionParameter
|
<CollectionParameter
|
||||||
v-if="parameter.type === 'collection'"
|
v-if="parameter.type === 'collection'"
|
||||||
:parameter="parameter"
|
:parameter="parameter"
|
||||||
:values="nodeHelpers.getParameterValue(nodeValues, parameter.name, path)"
|
:values="getParameterValue(parameter.name)"
|
||||||
:node-values="nodeValues"
|
:node-values="nodeValues"
|
||||||
:path="getPath(parameter.name)"
|
:path="getPath(parameter.name)"
|
||||||
:is-read-only="isReadOnly"
|
:is-read-only="isReadOnly"
|
||||||
|
@ -79,7 +79,7 @@
|
||||||
<FixedCollectionParameter
|
<FixedCollectionParameter
|
||||||
v-else-if="parameter.type === 'fixedCollection'"
|
v-else-if="parameter.type === 'fixedCollection'"
|
||||||
:parameter="parameter"
|
:parameter="parameter"
|
||||||
:values="nodeHelpers.getParameterValue(nodeValues, parameter.name, path)"
|
:values="getParameterValue(parameter.name)"
|
||||||
:node-values="nodeValues"
|
:node-values="nodeValues"
|
||||||
:path="getPath(parameter.name)"
|
:path="getPath(parameter.name)"
|
||||||
:is-read-only="isReadOnly"
|
:is-read-only="isReadOnly"
|
||||||
|
@ -111,7 +111,7 @@
|
||||||
<FilterConditions
|
<FilterConditions
|
||||||
v-else-if="parameter.type === 'filter'"
|
v-else-if="parameter.type === 'filter'"
|
||||||
:parameter="parameter"
|
:parameter="parameter"
|
||||||
:value="nodeHelpers.getParameterValue(nodeValues, parameter.name, path)"
|
:value="getParameterValue(parameter.name)"
|
||||||
:path="getPath(parameter.name)"
|
:path="getPath(parameter.name)"
|
||||||
:node="node"
|
:node="node"
|
||||||
:read-only="isReadOnly"
|
:read-only="isReadOnly"
|
||||||
|
@ -120,7 +120,7 @@
|
||||||
<AssignmentCollection
|
<AssignmentCollection
|
||||||
v-else-if="parameter.type === 'assignmentCollection'"
|
v-else-if="parameter.type === 'assignmentCollection'"
|
||||||
:parameter="parameter"
|
:parameter="parameter"
|
||||||
:value="nodeHelpers.getParameterValue(nodeValues, parameter.name, path)"
|
:value="getParameterValue(parameter.name)"
|
||||||
:path="getPath(parameter.name)"
|
:path="getPath(parameter.name)"
|
||||||
:node="node"
|
:node="node"
|
||||||
:is-read-only="isReadOnly"
|
:is-read-only="isReadOnly"
|
||||||
|
@ -144,7 +144,7 @@
|
||||||
<ParameterInputFull
|
<ParameterInputFull
|
||||||
:parameter="parameter"
|
:parameter="parameter"
|
||||||
:hide-issues="hiddenIssuesInputs.includes(parameter.name)"
|
:hide-issues="hiddenIssuesInputs.includes(parameter.name)"
|
||||||
:value="nodeHelpers.getParameterValue(nodeValues, parameter.name, path)"
|
:value="getParameterValue(parameter.name)"
|
||||||
:display-options="shouldShowOptions(parameter)"
|
:display-options="shouldShowOptions(parameter)"
|
||||||
:path="getPath(parameter.name)"
|
:path="getPath(parameter.name)"
|
||||||
:is-read-only="isReadOnly"
|
:is-read-only="isReadOnly"
|
||||||
|
@ -167,6 +167,7 @@ import type {
|
||||||
INodeProperties,
|
INodeProperties,
|
||||||
INodeTypeDescription,
|
INodeTypeDescription,
|
||||||
NodeParameterValue,
|
NodeParameterValue,
|
||||||
|
NodeParameterValueType,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import { deepCopy } from 'n8n-workflow';
|
import { deepCopy } from 'n8n-workflow';
|
||||||
import { mapStores } from 'pinia';
|
import { mapStores } from 'pinia';
|
||||||
|
@ -385,10 +386,7 @@ export default defineComponent({
|
||||||
return dependencies;
|
return dependencies;
|
||||||
},
|
},
|
||||||
multipleValues(parameter: INodeProperties): boolean {
|
multipleValues(parameter: INodeProperties): boolean {
|
||||||
if (this.getArgument('multipleValues', parameter) === true) {
|
return this.getArgument('multipleValues', parameter) === true;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
},
|
},
|
||||||
getArgument(
|
getArgument(
|
||||||
argumentName: string,
|
argumentName: string,
|
||||||
|
@ -460,7 +458,7 @@ export default defineComponent({
|
||||||
const nodeValues: INodeParameters = {};
|
const nodeValues: INodeParameters = {};
|
||||||
let rawValues = this.nodeValues;
|
let rawValues = this.nodeValues;
|
||||||
if (this.path) {
|
if (this.path) {
|
||||||
rawValues = get(this.nodeValues, this.path);
|
rawValues = get(this.nodeValues, this.path) as INodeParameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!rawValues) {
|
if (!rawValues) {
|
||||||
|
@ -473,11 +471,12 @@ export default defineComponent({
|
||||||
let parameterGotResolved = false;
|
let parameterGotResolved = false;
|
||||||
do {
|
do {
|
||||||
key = resolveKeys.shift() as string;
|
key = resolveKeys.shift() as string;
|
||||||
if (typeof rawValues[key] === 'string' && rawValues[key].charAt(0) === '=') {
|
const value = rawValues[key];
|
||||||
|
if (typeof value === 'string' && value?.charAt(0) === '=') {
|
||||||
// Contains an expression that
|
// Contains an expression that
|
||||||
if (
|
if (
|
||||||
rawValues[key].includes('$parameter') &&
|
value.includes('$parameter') &&
|
||||||
resolveKeys.some((parameterName) => rawValues[key].includes(parameterName))
|
resolveKeys.some((parameterName) => value.includes(parameterName))
|
||||||
) {
|
) {
|
||||||
// Contains probably an expression of a missing parameter so skip
|
// Contains probably an expression of a missing parameter so skip
|
||||||
resolveKeys.push(key);
|
resolveKeys.push(key);
|
||||||
|
@ -486,7 +485,7 @@ export default defineComponent({
|
||||||
// Contains probably no expression with a missing parameter so resolve
|
// Contains probably no expression with a missing parameter so resolve
|
||||||
try {
|
try {
|
||||||
nodeValues[key] = this.workflowHelpers.resolveExpression(
|
nodeValues[key] = this.workflowHelpers.resolveExpression(
|
||||||
rawValues[key],
|
value,
|
||||||
nodeValues,
|
nodeValues,
|
||||||
) as NodeParameterValue;
|
) as NodeParameterValue;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -574,6 +573,9 @@ export default defineComponent({
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
getParameterValue<T extends NodeParameterValueType = NodeParameterValueType>(name: string): T {
|
||||||
|
return this.nodeHelpers.getParameterValue(this.nodeValues, name, this.path) as T;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -50,7 +50,7 @@ import { mapStores } from 'pinia';
|
||||||
import type { PropType } from 'vue';
|
import type { PropType } from 'vue';
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
import type { INodeUi, IUpdateInformation, TargetItem } from '@/Interface';
|
import type { INodeUi, IUpdateInformation, InputSize, TargetItem } from '@/Interface';
|
||||||
import ParameterInput from '@/components/ParameterInput.vue';
|
import ParameterInput from '@/components/ParameterInput.vue';
|
||||||
import InputHint from '@/components/ParameterInputHint.vue';
|
import InputHint from '@/components/ParameterInputHint.vue';
|
||||||
import { useEnvironmentsStore } from '@/stores/environments.ee.store';
|
import { useEnvironmentsStore } from '@/stores/environments.ee.store';
|
||||||
|
@ -96,9 +96,11 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
parameter: {
|
parameter: {
|
||||||
type: Object as PropType<INodeProperties>,
|
type: Object as PropType<INodeProperties>,
|
||||||
|
required: true,
|
||||||
},
|
},
|
||||||
path: {
|
path: {
|
||||||
type: String,
|
type: String,
|
||||||
|
required: true,
|
||||||
},
|
},
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: [String, Number, Boolean, Array, Object] as PropType<NodeParameterValueType>,
|
type: [String, Number, Boolean, Array, Object] as PropType<NodeParameterValueType>,
|
||||||
|
@ -121,7 +123,7 @@ export default defineComponent({
|
||||||
required: false,
|
required: false,
|
||||||
},
|
},
|
||||||
inputSize: {
|
inputSize: {
|
||||||
type: String,
|
type: String as PropType<InputSize>,
|
||||||
},
|
},
|
||||||
hideIssues: {
|
hideIssues: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
@ -202,15 +204,14 @@ export default defineComponent({
|
||||||
? this.modelValue.value
|
? this.modelValue.value
|
||||||
: this.modelValue;
|
: this.modelValue;
|
||||||
|
|
||||||
if (
|
if (!this.activeNode || !this.isValueExpression || typeof value !== 'string') {
|
||||||
!this.isForCredential &&
|
|
||||||
(!this.activeNode || !this.isValueExpression || typeof value !== 'string')
|
|
||||||
) {
|
|
||||||
return { ok: false, error: new Error() };
|
return { ok: false, error: new Error() };
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let opts = { isForCredential: this.isForCredential };
|
let opts: Parameters<typeof this.workflowHelpers.resolveExpression>[2] = {
|
||||||
|
isForCredential: this.isForCredential,
|
||||||
|
};
|
||||||
if (this.ndvStore.isInputParentOfActiveNode) {
|
if (this.ndvStore.isInputParentOfActiveNode) {
|
||||||
opts = {
|
opts = {
|
||||||
...opts,
|
...opts,
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
icon-size="small"
|
icon-size="small"
|
||||||
:actions="actions"
|
:actions="actions"
|
||||||
:icon-orientation="iconOrientation"
|
:icon-orientation="iconOrientation"
|
||||||
@action="(action) => $emit('update:modelValue', action)"
|
@action="(action: string) => $emit('update:modelValue', action)"
|
||||||
@visible-change="onMenuToggle"
|
@visible-change="onMenuToggle"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -40,7 +40,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { NodeParameterValueType } from 'n8n-workflow';
|
import type { INodeProperties, NodeParameterValueType } from 'n8n-workflow';
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import type { PropType } from 'vue';
|
import type { PropType } from 'vue';
|
||||||
import { isResourceLocatorValue } from '@/utils/typeGuards';
|
import { isResourceLocatorValue } from '@/utils/typeGuards';
|
||||||
|
@ -51,7 +51,8 @@ export default defineComponent({
|
||||||
name: 'ParameterOptions',
|
name: 'ParameterOptions',
|
||||||
props: {
|
props: {
|
||||||
parameter: {
|
parameter: {
|
||||||
type: Object,
|
type: Object as PropType<INodeProperties>,
|
||||||
|
required: true,
|
||||||
},
|
},
|
||||||
isReadOnly: {
|
isReadOnly: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
@ -87,6 +88,7 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
emits: ['update:modelValue', 'menu-expanded'],
|
||||||
computed: {
|
computed: {
|
||||||
isDefault(): boolean {
|
isDefault(): boolean {
|
||||||
return this.parameter.default === this.value;
|
return this.parameter.default === this.value;
|
||||||
|
@ -109,7 +111,7 @@ export default defineComponent({
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (['codeNodeEditor', 'sqlEditor'].includes(this.parameter.typeOptions?.editor)) {
|
if (['codeNodeEditor', 'sqlEditor'].includes(this.parameter.typeOptions?.editor ?? '')) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import type { PropType } from 'vue';
|
import type { PropType } from 'vue';
|
||||||
import { computed, defineComponent } from 'vue';
|
import { computed, defineComponent } from 'vue';
|
||||||
import { useRBACStore } from '@/stores/rbac.store';
|
import { useRBACStore } from '@/stores/rbac.store';
|
||||||
import type { HasScopeMode, Scope, Resource } from '@n8n/permissions';
|
import type { ScopeMode, Scope, Resource } from '@n8n/permissions';
|
||||||
import {
|
import {
|
||||||
inferProjectIdFromRoute,
|
inferProjectIdFromRoute,
|
||||||
inferResourceIdFromRoute,
|
inferResourceIdFromRoute,
|
||||||
|
@ -17,7 +17,7 @@ export default defineComponent({
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
mode: {
|
mode: {
|
||||||
type: String as PropType<HasScopeMode>,
|
type: String as PropType<ScopeMode>,
|
||||||
default: 'allOf',
|
default: 'allOf',
|
||||||
},
|
},
|
||||||
resourceType: {
|
resourceType: {
|
||||||
|
|
|
@ -144,7 +144,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { IResourceLocatorReqParams, IResourceLocatorResultExpanded } from '@/Interface';
|
import type { DynamicNodeParameters, IResourceLocatorResultExpanded } from '@/Interface';
|
||||||
import DraggableTarget from '@/components/DraggableTarget.vue';
|
import DraggableTarget from '@/components/DraggableTarget.vue';
|
||||||
import ExpressionParameterInput from '@/components/ExpressionParameterInput.vue';
|
import ExpressionParameterInput from '@/components/ExpressionParameterInput.vue';
|
||||||
import ParameterIssues from '@/components/ParameterIssues.vue';
|
import ParameterIssues from '@/components/ParameterIssues.vue';
|
||||||
|
@ -197,9 +197,7 @@ export default defineComponent({
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: [Object, String] as PropType<
|
type: Object as PropType<INodeParameterResourceLocator>,
|
||||||
INodeParameterResourceLocator | NodeParameterValue | undefined
|
|
||||||
>,
|
|
||||||
},
|
},
|
||||||
inputSize: {
|
inputSize: {
|
||||||
type: String,
|
type: String,
|
||||||
|
@ -221,8 +219,7 @@ export default defineComponent({
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
expressionComputedValue: {
|
expressionComputedValue: {
|
||||||
type: String,
|
type: {} as PropType<unknown>,
|
||||||
default: '',
|
|
||||||
},
|
},
|
||||||
isReadOnly: {
|
isReadOnly: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
@ -230,6 +227,7 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
expressionDisplayValue: {
|
expressionDisplayValue: {
|
||||||
type: String,
|
type: String,
|
||||||
|
default: '',
|
||||||
},
|
},
|
||||||
forceShowExpression: {
|
forceShowExpression: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
@ -245,9 +243,11 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
node: {
|
node: {
|
||||||
type: Object as PropType<INode>,
|
type: Object as PropType<INode>,
|
||||||
|
required: true,
|
||||||
},
|
},
|
||||||
path: {
|
path: {
|
||||||
type: String,
|
type: String,
|
||||||
|
required: true,
|
||||||
},
|
},
|
||||||
loadOptionsMethod: {
|
loadOptionsMethod: {
|
||||||
type: String,
|
type: String,
|
||||||
|
@ -506,10 +506,10 @@ export default defineComponent({
|
||||||
this.width = containerRef?.offsetWidth;
|
this.width = containerRef?.offsetWidth;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getLinkAlt(entity: string) {
|
getLinkAlt(entity: NodeParameterValue) {
|
||||||
if (this.selectedMode === 'list' && entity) {
|
if (this.selectedMode === 'list' && entity) {
|
||||||
return this.$locale.baseText('resourceLocator.openSpecificResource', {
|
return this.$locale.baseText('resourceLocator.openSpecificResource', {
|
||||||
interpolate: { entity, appName: this.appName },
|
interpolate: { entity: entity.toString(), appName: this.appName },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return this.$locale.baseText('resourceLocator.openResource', {
|
return this.$locale.baseText('resourceLocator.openResource', {
|
||||||
|
@ -520,7 +520,7 @@ export default defineComponent({
|
||||||
this.cachedResponses = {};
|
this.cachedResponses = {};
|
||||||
this.trackEvent('User refreshed resource locator list');
|
this.trackEvent('User refreshed resource locator list');
|
||||||
},
|
},
|
||||||
onKeyDown(e: MouseEvent) {
|
onKeyDown(e: KeyboardEvent) {
|
||||||
if (this.resourceDropdownVisible && !this.isSearchable) {
|
if (this.resourceDropdownVisible && !this.isSearchable) {
|
||||||
this.eventBus.emit('keyDown', e);
|
this.eventBus.emit('keyDown', e);
|
||||||
}
|
}
|
||||||
|
@ -555,6 +555,9 @@ export default defineComponent({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const id = node.credentials[credentialKey].id;
|
const id = node.credentials[credentialKey].id;
|
||||||
|
if (!id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.uiStore.openExistingCredential(id);
|
this.uiStore.openExistingCredential(id);
|
||||||
},
|
},
|
||||||
createNewCredential(): void {
|
createNewCredential(): void {
|
||||||
|
@ -664,11 +667,11 @@ export default defineComponent({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let paginationToken: unknown = null;
|
let paginationToken: string | undefined;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (cachedResponse) {
|
if (cachedResponse) {
|
||||||
const nextPageToken = cachedResponse.nextPageToken;
|
const nextPageToken = cachedResponse.nextPageToken as string;
|
||||||
if (nextPageToken) {
|
if (nextPageToken) {
|
||||||
paginationToken = nextPageToken;
|
paginationToken = nextPageToken;
|
||||||
this.setResponse(paramsKey, { loading: true });
|
this.setResponse(paramsKey, { loading: true });
|
||||||
|
@ -690,11 +693,12 @@ export default defineComponent({
|
||||||
this.parameter,
|
this.parameter,
|
||||||
params.parameters,
|
params.parameters,
|
||||||
) as INodeParameters;
|
) as INodeParameters;
|
||||||
const loadOptionsMethod = this.getPropertyArgument(this.currentMode, 'searchListMethod') as
|
const loadOptionsMethod = this.getPropertyArgument(
|
||||||
| string
|
this.currentMode,
|
||||||
| undefined;
|
'searchListMethod',
|
||||||
|
) as string;
|
||||||
|
|
||||||
const requestParams: IResourceLocatorReqParams = {
|
const requestParams: DynamicNodeParameters.ResourceLocatorResultsRequest = {
|
||||||
nodeTypeAndVersion: {
|
nodeTypeAndVersion: {
|
||||||
name: this.node.type,
|
name: this.node.type,
|
||||||
version: this.node.typeVersion,
|
version: this.node.typeVersion,
|
||||||
|
@ -703,10 +707,16 @@ export default defineComponent({
|
||||||
methodName: loadOptionsMethod,
|
methodName: loadOptionsMethod,
|
||||||
currentNodeParameters: resolvedNodeParameters,
|
currentNodeParameters: resolvedNodeParameters,
|
||||||
credentials: this.node.credentials,
|
credentials: this.node.credentials,
|
||||||
...(params.filter ? { filter: params.filter } : {}),
|
|
||||||
...(paginationToken ? { paginationToken } : {}),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (params.filter) {
|
||||||
|
requestParams.filter = params.filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (paginationToken) {
|
||||||
|
requestParams.paginationToken = paginationToken;
|
||||||
|
}
|
||||||
|
|
||||||
const response = await this.nodeTypesStore.getResourceLocatorResults(requestParams);
|
const response = await this.nodeTypesStore.getResourceLocatorResults(requestParams);
|
||||||
|
|
||||||
this.setResponse(paramsKey, {
|
this.setResponse(paramsKey, {
|
||||||
|
|
|
@ -41,7 +41,7 @@
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-for="(result, i) in sortedResources"
|
v-for="(result, i) in sortedResources"
|
||||||
:key="result.value"
|
:key="result.value.toString()"
|
||||||
:ref="`item-${i}`"
|
:ref="`item-${i}`"
|
||||||
:class="{
|
:class="{
|
||||||
[$style.resourceItem]: true,
|
[$style.resourceItem]: true,
|
||||||
|
@ -83,6 +83,7 @@ import { defineComponent } from 'vue';
|
||||||
import type { PropType } from 'vue';
|
import type { PropType } from 'vue';
|
||||||
import type { EventBus } from 'n8n-design-system/utils';
|
import type { EventBus } from 'n8n-design-system/utils';
|
||||||
import { createEventBus } from 'n8n-design-system/utils';
|
import { createEventBus } from 'n8n-design-system/utils';
|
||||||
|
import type { NodeParameterValue } from 'n8n-workflow';
|
||||||
|
|
||||||
const SEARCH_BAR_HEIGHT_PX = 40;
|
const SEARCH_BAR_HEIGHT_PX = 40;
|
||||||
const SCROLL_MARGIN_PX = 10;
|
const SCROLL_MARGIN_PX = 10;
|
||||||
|
@ -91,7 +92,7 @@ export default defineComponent({
|
||||||
name: 'ResourceLocatorDropdown',
|
name: 'ResourceLocatorDropdown',
|
||||||
props: {
|
props: {
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: [String, Number],
|
type: [String, Number] as PropType<NodeParameterValue>,
|
||||||
},
|
},
|
||||||
show: {
|
show: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
@ -126,6 +127,7 @@ export default defineComponent({
|
||||||
default: () => createEventBus(),
|
default: () => createEventBus(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
emits: ['update:modelValue', 'loadMore', 'filter'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
hoverIndex: 0,
|
hoverIndex: 0,
|
||||||
|
@ -135,7 +137,7 @@ export default defineComponent({
|
||||||
computed: {
|
computed: {
|
||||||
sortedResources(): IResourceLocatorResultExpanded[] {
|
sortedResources(): IResourceLocatorResultExpanded[] {
|
||||||
const seen = new Set();
|
const seen = new Set();
|
||||||
const { selected, notSelected } = this.resources.reduce(
|
const { selected, notSelected } = (this.resources ?? []).reduce(
|
||||||
(acc, item: IResourceLocatorResultExpanded) => {
|
(acc, item: IResourceLocatorResultExpanded) => {
|
||||||
if (seen.has(item.value)) {
|
if (seen.has(item.value)) {
|
||||||
return acc;
|
return acc;
|
||||||
|
@ -232,7 +234,7 @@ export default defineComponent({
|
||||||
onFilterInput(value: string) {
|
onFilterInput(value: string) {
|
||||||
this.$emit('filter', value);
|
this.$emit('filter', value);
|
||||||
},
|
},
|
||||||
onItemClick(selected: string) {
|
onItemClick(selected: string | number | boolean) {
|
||||||
this.$emit('update:modelValue', selected);
|
this.$emit('update:modelValue', selected);
|
||||||
},
|
},
|
||||||
onItemHover(index: number) {
|
onItemHover(index: number) {
|
||||||
|
|
|
@ -67,7 +67,7 @@ function markAsReadOnly(field: ResourceMapperField): boolean {
|
||||||
return field.readOnly || false;
|
return field.readOnly || false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fieldsUi = computed<Array<Partial<INodeProperties> & { readOnly?: boolean }>>(() => {
|
const fieldsUi = computed<Array<INodeProperties & { readOnly: boolean }>>(() => {
|
||||||
return props.fieldsToMap
|
return props.fieldsToMap
|
||||||
.filter((field) => field.display && field.removed !== true)
|
.filter((field) => field.display && field.removed !== true)
|
||||||
.map((field) => {
|
.map((field) => {
|
||||||
|
@ -85,11 +85,11 @@ const fieldsUi = computed<Array<Partial<INodeProperties> & { readOnly?: boolean
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const orderedFields = computed<Array<Partial<INodeProperties> & { readOnly?: boolean }>>(() => {
|
const orderedFields = computed<Array<INodeProperties & { readOnly: boolean }>>(() => {
|
||||||
// Sort so that matching columns are first
|
// Sort so that matching columns are first
|
||||||
if (props.paramValue.matchingColumns) {
|
if (props.paramValue.matchingColumns) {
|
||||||
fieldsUi.value.forEach((field, i) => {
|
fieldsUi.value.forEach((field, i) => {
|
||||||
const fieldName = parseResourceMapperFieldName(field.name);
|
const fieldName = field.name && parseResourceMapperFieldName(field.name);
|
||||||
if (fieldName) {
|
if (fieldName) {
|
||||||
if (props.paramValue.matchingColumns.includes(fieldName)) {
|
if (props.paramValue.matchingColumns.includes(fieldName)) {
|
||||||
fieldsUi.value.splice(i, 1);
|
fieldsUi.value.splice(i, 1);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { IUpdateInformation, ResourceMapperReqParams } from '@/Interface';
|
import type { IUpdateInformation, DynamicNodeParameters } from '@/Interface';
|
||||||
import { resolveRequiredParameters } from '@/composables/useWorkflowHelpers';
|
import { resolveRequiredParameters } from '@/composables/useWorkflowHelpers';
|
||||||
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||||
import type {
|
import type {
|
||||||
|
@ -37,6 +37,7 @@ const workflowsStore = useWorkflowsStore();
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
teleported: true,
|
teleported: true,
|
||||||
|
dependentParametersValues: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
|
@ -235,7 +236,13 @@ async function loadFieldsToMap(): Promise<void> {
|
||||||
if (!props.node) {
|
if (!props.node) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const requestParams: ResourceMapperReqParams = {
|
|
||||||
|
const methodName = props.parameter.typeOptions?.resourceMapper?.resourceMapperMethod;
|
||||||
|
if (typeof methodName !== 'string') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const requestParams: DynamicNodeParameters.ResourceMapperFieldsRequest = {
|
||||||
nodeTypeAndVersion: {
|
nodeTypeAndVersion: {
|
||||||
name: props.node?.type,
|
name: props.node?.type,
|
||||||
version: props.node.typeVersion,
|
version: props.node.typeVersion,
|
||||||
|
@ -245,7 +252,7 @@ async function loadFieldsToMap(): Promise<void> {
|
||||||
props.node.parameters,
|
props.node.parameters,
|
||||||
) as INodeParameters,
|
) as INodeParameters,
|
||||||
path: props.path,
|
path: props.path,
|
||||||
methodName: props.parameter.typeOptions?.resourceMapper?.resourceMapperMethod,
|
methodName,
|
||||||
credentials: props.node.credentials,
|
credentials: props.node.credentials,
|
||||||
};
|
};
|
||||||
const fetchedFields = await nodeTypesStore.getResourceMapperFields(requestParams);
|
const fetchedFields = await nodeTypesStore.getResourceMapperFields(requestParams);
|
||||||
|
@ -310,7 +317,7 @@ function setDefaultFieldValues(forceMatchingFieldsUpdate = false): void {
|
||||||
function updateNodeIssues(): void {
|
function updateNodeIssues(): void {
|
||||||
if (props.node) {
|
if (props.node) {
|
||||||
const parameterIssues = NodeHelpers.getNodeParametersIssues(
|
const parameterIssues = NodeHelpers.getNodeParametersIssues(
|
||||||
nodeType.value?.properties || [],
|
nodeType.value?.properties ?? [],
|
||||||
props.node,
|
props.node,
|
||||||
);
|
);
|
||||||
if (parameterIssues) {
|
if (parameterIssues) {
|
||||||
|
@ -319,10 +326,10 @@ function updateNodeIssues(): void {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onMatchingColumnsChanged(matchingColumns: string[]): void {
|
function onMatchingColumnsChanged(columns: string[]): void {
|
||||||
state.paramValue = {
|
state.paramValue = {
|
||||||
...state.paramValue,
|
...state.paramValue,
|
||||||
matchingColumns,
|
matchingColumns: columns,
|
||||||
};
|
};
|
||||||
// Set all matching fields to be visible
|
// Set all matching fields to be visible
|
||||||
state.paramValue.schema.forEach((field) => {
|
state.paramValue.schema.forEach((field) => {
|
||||||
|
|
|
@ -93,13 +93,8 @@ export default defineComponent({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
length += (
|
const value = this.modelValue[key];
|
||||||
Array.isArray(this.modelValue[key])
|
length += (Array.isArray(value) ? value.length > 0 : value !== '') ? 1 : 0;
|
||||||
? this.modelValue[key].length > 0
|
|
||||||
: this.modelValue[key] !== ''
|
|
||||||
)
|
|
||||||
? 1
|
|
||||||
: 0;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return length;
|
return length;
|
||||||
|
|
|
@ -2,7 +2,6 @@ import { deepCopy } from 'n8n-workflow';
|
||||||
import type {
|
import type {
|
||||||
ExecutionError,
|
ExecutionError,
|
||||||
GenericValue,
|
GenericValue,
|
||||||
INodeParameters,
|
|
||||||
INodeProperties,
|
INodeProperties,
|
||||||
ITelemetryTrackProperties,
|
ITelemetryTrackProperties,
|
||||||
NodeParameterValue,
|
NodeParameterValue,
|
||||||
|
@ -119,7 +118,7 @@ export interface ExpressionEditorEventsData {
|
||||||
dialogVisible: boolean;
|
dialogVisible: boolean;
|
||||||
value: string;
|
value: string;
|
||||||
resolvedExpressionValue: string;
|
resolvedExpressionValue: string;
|
||||||
parameter: INodeParameters;
|
parameter: INodeProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getExpressionEditorEventsData = (
|
export const getExpressionEditorEventsData = (
|
||||||
|
|
|
@ -204,7 +204,7 @@ export class I18nClass {
|
||||||
/**
|
/**
|
||||||
* Display name for an input label, whether top-level or nested.
|
* Display name for an input label, whether top-level or nested.
|
||||||
*/
|
*/
|
||||||
inputLabelDisplayName(parameter: INodeProperties, path: string) {
|
inputLabelDisplayName(parameter: INodeProperties | INodePropertyCollection, path: string) {
|
||||||
const middleKey = deriveMiddleKey(path, parameter);
|
const middleKey = deriveMiddleKey(path, parameter);
|
||||||
|
|
||||||
return context.dynamicRender({
|
return context.dynamicRender({
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
*
|
*
|
||||||
* Location: `n8n-nodes-base.nodes.github.nodeView.<middleKey>.placeholder`
|
* Location: `n8n-nodes-base.nodes.github.nodeView.<middleKey>.placeholder`
|
||||||
*/
|
*/
|
||||||
export function deriveMiddleKey(path: string, parameter: { name: string; type: string }) {
|
export function deriveMiddleKey(path: string, parameter: { name: string; type?: string }) {
|
||||||
let middleKey = parameter.name;
|
let middleKey = parameter.name;
|
||||||
|
|
||||||
if (isTopLevelCollection(path, parameter) || isNestedInCollectionLike(path)) {
|
if (isTopLevelCollection(path, parameter) || isNestedInCollectionLike(path)) {
|
||||||
|
@ -28,17 +28,17 @@ export function deriveMiddleKey(path: string, parameter: { name: string; type: s
|
||||||
*/
|
*/
|
||||||
export const isNestedInCollectionLike = (path: string) => path.split('.').length >= 3;
|
export const isNestedInCollectionLike = (path: string) => path.split('.').length >= 3;
|
||||||
|
|
||||||
const isTopLevelCollection = (path: string, parameter: { type: string }) =>
|
const isTopLevelCollection = (path: string, parameter: { type?: string }) =>
|
||||||
path.split('.').length === 2 && parameter.type === 'collection';
|
path.split('.').length === 2 && parameter.type === 'collection';
|
||||||
|
|
||||||
const isNestedCollection = (path: string, parameter: { type: string }) =>
|
const isNestedCollection = (path: string, parameter: { type?: string }) =>
|
||||||
path.split('.').length > 2 && parameter.type === 'collection';
|
path.split('.').length > 2 && parameter.type === 'collection';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the param is a normal `fixedCollection`, i.e. a FC other than the wrapper
|
* Check if the param is a normal `fixedCollection`, i.e. a FC other than the wrapper
|
||||||
* that sits at the root of a node's top-level param and contains all of them.
|
* that sits at the root of a node's top-level param and contains all of them.
|
||||||
*/
|
*/
|
||||||
const isFixedCollection = (path: string, parameter: { type: string }) =>
|
const isFixedCollection = (path: string, parameter: { type?: string }) =>
|
||||||
parameter.type === 'fixedCollection' && path !== 'parameters';
|
parameter.type === 'fixedCollection' && path !== 'parameters';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -4,7 +4,6 @@ import type {
|
||||||
IExecuteFunctions,
|
IExecuteFunctions,
|
||||||
INode,
|
INode,
|
||||||
INodeExecutionData,
|
INodeExecutionData,
|
||||||
ValidationResult,
|
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import {
|
import {
|
||||||
ApplicationError,
|
ApplicationError,
|
||||||
|
@ -200,7 +199,7 @@ export const validateEntry = (
|
||||||
|
|
||||||
if (!validationResult.valid) {
|
if (!validationResult.valid) {
|
||||||
if (ignoreErrors) {
|
if (ignoreErrors) {
|
||||||
validationResult.newValue = value as ValidationResult['newValue'];
|
return { name, value: value ?? null };
|
||||||
} else {
|
} else {
|
||||||
const message = `${validationResult.errorMessage} [item ${itemIndex}]`;
|
const message = `${validationResult.errorMessage} [item ${itemIndex}]`;
|
||||||
throw new NodeOperationError(node, message, {
|
throw new NodeOperationError(node, message, {
|
||||||
|
@ -212,7 +211,7 @@ export const validateEntry = (
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name,
|
name,
|
||||||
value: validationResult.newValue === undefined ? null : validationResult.newValue,
|
value: validationResult.newValue ?? null,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1118,6 +1118,9 @@ export type NodeParameterValueType =
|
||||||
| NodeParameterValue
|
| NodeParameterValue
|
||||||
| INodeParameters
|
| INodeParameters
|
||||||
| INodeParameterResourceLocator
|
| INodeParameterResourceLocator
|
||||||
|
| ResourceMapperValue
|
||||||
|
| FilterValue
|
||||||
|
| AssignmentCollectionValue
|
||||||
| NodeParameterValue[]
|
| NodeParameterValue[]
|
||||||
| INodeParameters[]
|
| INodeParameters[]
|
||||||
| INodeParameterResourceLocator[]
|
| INodeParameterResourceLocator[]
|
||||||
|
@ -2374,23 +2377,30 @@ export interface ResourceMapperField {
|
||||||
readOnly?: boolean;
|
readOnly?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FieldType =
|
export type FieldTypeMap = {
|
||||||
| 'string'
|
// eslint-disable-next-line id-denylist
|
||||||
| 'string-alphanumeric'
|
boolean: boolean;
|
||||||
| 'number'
|
// eslint-disable-next-line id-denylist
|
||||||
| 'dateTime'
|
number: number;
|
||||||
| 'boolean'
|
// eslint-disable-next-line id-denylist
|
||||||
| 'time'
|
string: string;
|
||||||
| 'array'
|
'string-alphanumeric': string;
|
||||||
| 'object'
|
dateTime: string;
|
||||||
| 'options'
|
time: string;
|
||||||
| 'url'
|
array: unknown[];
|
||||||
| 'jwt';
|
object: object;
|
||||||
|
options: any;
|
||||||
|
url: string;
|
||||||
|
jwt: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type ValidationResult = {
|
export type FieldType = keyof FieldTypeMap;
|
||||||
valid: boolean;
|
|
||||||
errorMessage?: string;
|
export type ValidationResult<T extends FieldType = FieldType> =
|
||||||
newValue?: string | number | boolean | object | null | undefined;
|
| { valid: false; errorMessage: string }
|
||||||
|
| {
|
||||||
|
valid: true;
|
||||||
|
newValue?: FieldTypeMap[T];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ResourceMapperValue = {
|
export type ResourceMapperValue = {
|
||||||
|
@ -2418,9 +2428,9 @@ export interface FilterOperatorValue {
|
||||||
|
|
||||||
export type FilterConditionValue = {
|
export type FilterConditionValue = {
|
||||||
id: string;
|
id: string;
|
||||||
leftValue: unknown;
|
leftValue: NodeParameterValue;
|
||||||
operator: FilterOperatorValue;
|
operator: FilterOperatorValue;
|
||||||
rightValue: unknown;
|
rightValue: NodeParameterValue;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FilterOptionsValue = {
|
export type FilterOptionsValue = {
|
||||||
|
|
|
@ -111,7 +111,13 @@ function parseFilterConditionValues(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return { ok: true, result: { left: parsedLeftValue.newValue, right: parsedRightValue.newValue } };
|
return {
|
||||||
|
ok: true,
|
||||||
|
result: {
|
||||||
|
left: parsedLeftValue.valid ? parsedLeftValue.newValue : undefined,
|
||||||
|
right: parsedRightValue.valid ? parsedRightValue.newValue : undefined,
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseRegexPattern(pattern: string): RegExp {
|
function parseRegexPattern(pattern: string): RegExp {
|
||||||
|
|
|
@ -177,14 +177,21 @@ type ValidateFieldTypeOptions = Partial<{
|
||||||
strict: boolean;
|
strict: boolean;
|
||||||
parseStrings: boolean;
|
parseStrings: boolean;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
// Validates field against the schema and tries to parse it to the correct type
|
// Validates field against the schema and tries to parse it to the correct type
|
||||||
|
export function validateFieldType<K extends FieldType>(
|
||||||
|
fieldName: string,
|
||||||
|
value: unknown,
|
||||||
|
type: K,
|
||||||
|
options?: ValidateFieldTypeOptions,
|
||||||
|
): ValidationResult<K>;
|
||||||
// eslint-disable-next-line complexity
|
// eslint-disable-next-line complexity
|
||||||
export const validateFieldType = (
|
export function validateFieldType(
|
||||||
fieldName: string,
|
fieldName: string,
|
||||||
value: unknown,
|
value: unknown,
|
||||||
type: FieldType,
|
type: FieldType,
|
||||||
options: ValidateFieldTypeOptions = {},
|
options: ValidateFieldTypeOptions = {},
|
||||||
): ValidationResult => {
|
): ValidationResult {
|
||||||
if (value === null || value === undefined) return { valid: true };
|
if (value === null || value === undefined) return { valid: true };
|
||||||
const strict = options.strict ?? false;
|
const strict = options.strict ?? false;
|
||||||
const valueOptions = options.valueOptions ?? [];
|
const valueOptions = options.valueOptions ?? [];
|
||||||
|
@ -308,4 +315,4 @@ export const validateFieldType = (
|
||||||
return { valid: true, newValue: value };
|
return { valid: true, newValue: value };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
Loading…
Reference in a new issue