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