mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
259 lines
7 KiB
Vue
259 lines
7 KiB
Vue
<script setup lang="ts">
|
|
import type { IUpdateInformation, InputSize } from '@/Interface';
|
|
import ParameterInput from '@/components/ParameterInput.vue';
|
|
import InputHint from '@/components/ParameterInputHint.vue';
|
|
import {
|
|
isResourceLocatorValue,
|
|
type IDataObject,
|
|
type INodeProperties,
|
|
type INodePropertyMode,
|
|
type IParameterLabel,
|
|
type NodeParameterValueType,
|
|
type Result,
|
|
} from 'n8n-workflow';
|
|
|
|
import { useWorkflowHelpers } from '@/composables/useWorkflowHelpers';
|
|
import useEnvironmentsStore from '@/stores/environments.ee.store';
|
|
import { useExternalSecretsStore } from '@/stores/externalSecrets.ee.store';
|
|
import { useNDVStore } from '@/stores/ndv.store';
|
|
import { stringifyExpressionResult } from '@/utils/expressions';
|
|
import { isValueExpression, parseResourceMapperFieldName } from '@/utils/nodeTypesUtils';
|
|
import type { EventBus } from 'n8n-design-system/utils';
|
|
import { createEventBus } from 'n8n-design-system/utils';
|
|
import { computed } from 'vue';
|
|
import { useRouter } from 'vue-router';
|
|
import { useWorkflowsStore } from '@/stores/workflows.store';
|
|
|
|
type Props = {
|
|
parameter: INodeProperties;
|
|
path: string;
|
|
modelValue: NodeParameterValueType;
|
|
additionalExpressionData?: IDataObject;
|
|
rows?: number;
|
|
isReadOnly?: boolean;
|
|
isAssignment?: boolean;
|
|
droppable?: boolean;
|
|
activeDrop?: boolean;
|
|
forceShowExpression?: boolean;
|
|
hint?: string;
|
|
hideHint?: boolean;
|
|
inputSize?: InputSize;
|
|
hideIssues?: boolean;
|
|
documentationUrl?: string;
|
|
errorHighlight?: boolean;
|
|
isForCredential?: boolean;
|
|
eventSource?: string;
|
|
label?: IParameterLabel;
|
|
eventBus?: EventBus;
|
|
};
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
additionalExpressionData: () => ({}),
|
|
rows: 5,
|
|
label: () => ({ size: 'small' }),
|
|
eventBus: () => createEventBus(),
|
|
});
|
|
|
|
const emit = defineEmits<{
|
|
focus: [];
|
|
blur: [];
|
|
drop: [value: string];
|
|
update: [value: IUpdateInformation];
|
|
textInput: [value: IUpdateInformation];
|
|
}>();
|
|
|
|
const router = useRouter();
|
|
const workflowHelpers = useWorkflowHelpers({ router });
|
|
|
|
const ndvStore = useNDVStore();
|
|
const externalSecretsStore = useExternalSecretsStore();
|
|
const environmentsStore = useEnvironmentsStore();
|
|
|
|
const isExpression = computed(() => {
|
|
return isValueExpression(props.parameter, props.modelValue);
|
|
});
|
|
|
|
const activeNode = computed(() => ndvStore.activeNode);
|
|
|
|
const selectedRLMode = computed(() => {
|
|
if (
|
|
typeof props.modelValue !== 'object' ||
|
|
props.parameter.type !== 'resourceLocator' ||
|
|
!isResourceLocatorValue(props.modelValue)
|
|
) {
|
|
return undefined;
|
|
}
|
|
|
|
const mode = props.modelValue.mode;
|
|
if (mode) {
|
|
return props.parameter.modes?.find((m: INodePropertyMode) => m.name === mode);
|
|
}
|
|
|
|
return undefined;
|
|
});
|
|
|
|
const parameterHint = computed(() => {
|
|
if (isExpression.value) {
|
|
return undefined;
|
|
}
|
|
if (selectedRLMode.value?.hint) {
|
|
return selectedRLMode.value.hint;
|
|
}
|
|
|
|
return props.hint;
|
|
});
|
|
|
|
const targetItem = computed(() => ndvStore.expressionTargetItem);
|
|
|
|
const isInputParentOfActiveNode = computed(() => ndvStore.isInputParentOfActiveNode);
|
|
|
|
const evaluatedExpression = computed<Result<unknown, Error>>(() => {
|
|
const value = isResourceLocatorValue(props.modelValue)
|
|
? props.modelValue.value
|
|
: props.modelValue;
|
|
|
|
if (!activeNode.value || !isExpression.value || typeof value !== 'string') {
|
|
return { ok: false, error: new Error() };
|
|
}
|
|
|
|
try {
|
|
let opts: Parameters<typeof workflowHelpers.resolveExpression>[2] = {
|
|
isForCredential: props.isForCredential,
|
|
};
|
|
if (ndvStore.isInputParentOfActiveNode) {
|
|
opts = {
|
|
...opts,
|
|
targetItem: targetItem.value ?? undefined,
|
|
inputNodeName: ndvStore.ndvInputNodeName,
|
|
inputRunIndex: ndvStore.ndvInputRunIndex,
|
|
inputBranchIndex: ndvStore.ndvInputBranchIndex,
|
|
additionalKeys: resolvedAdditionalExpressionData.value,
|
|
};
|
|
}
|
|
|
|
if (props.isForCredential) opts.additionalKeys = resolvedAdditionalExpressionData.value;
|
|
const stringifyObject = props.parameter.type !== 'multiOptions';
|
|
return {
|
|
ok: true,
|
|
result: workflowHelpers.resolveExpression(value, undefined, opts, stringifyObject),
|
|
};
|
|
} catch (error) {
|
|
return { ok: false, error };
|
|
}
|
|
});
|
|
|
|
const evaluatedExpressionValue = computed(() => {
|
|
const evaluated = evaluatedExpression.value;
|
|
return evaluated.ok ? evaluated.result : null;
|
|
});
|
|
|
|
const evaluatedExpressionString = computed(() => {
|
|
const hasRunData =
|
|
!!useWorkflowsStore().workflowExecutionData?.data?.resultData?.runData[
|
|
ndvStore.activeNode?.name ?? ''
|
|
];
|
|
return stringifyExpressionResult(evaluatedExpression.value, hasRunData);
|
|
});
|
|
|
|
const expressionOutput = computed(() => {
|
|
if (isExpression.value && evaluatedExpressionString.value) {
|
|
return evaluatedExpressionString.value;
|
|
}
|
|
|
|
return null;
|
|
});
|
|
|
|
const resolvedAdditionalExpressionData = computed(() => {
|
|
return {
|
|
$vars: environmentsStore.variablesAsObject,
|
|
...(externalSecretsStore.isEnterpriseExternalSecretsEnabled && props.isForCredential
|
|
? { $secrets: externalSecretsStore.secretsAsObject }
|
|
: {}),
|
|
...props.additionalExpressionData,
|
|
};
|
|
});
|
|
|
|
const parsedParameterName = computed(() => {
|
|
return parseResourceMapperFieldName(props.parameter?.name ?? '');
|
|
});
|
|
|
|
function onFocus() {
|
|
emit('focus');
|
|
}
|
|
|
|
function onBlur() {
|
|
emit('blur');
|
|
}
|
|
|
|
function onDrop(data: string) {
|
|
emit('drop', data);
|
|
}
|
|
|
|
function onValueChanged(parameterData: IUpdateInformation) {
|
|
emit('update', parameterData);
|
|
}
|
|
|
|
function onTextInput(parameterData: IUpdateInformation) {
|
|
emit('textInput', parameterData);
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div :class="$style.parameterInput" data-test-id="parameter-input">
|
|
<ParameterInput
|
|
ref="param"
|
|
:input-size="inputSize"
|
|
:parameter="parameter"
|
|
:model-value="modelValue"
|
|
:path="path"
|
|
:is-read-only="isReadOnly"
|
|
:is-assignment="isAssignment"
|
|
:droppable="droppable"
|
|
:active-drop="activeDrop"
|
|
:force-show-expression="forceShowExpression"
|
|
:hide-issues="hideIssues"
|
|
:documentation-url="documentationUrl"
|
|
:error-highlight="errorHighlight"
|
|
:is-for-credential="isForCredential"
|
|
:event-source="eventSource"
|
|
:expression-evaluated="evaluatedExpressionValue"
|
|
:additional-expression-data="resolvedAdditionalExpressionData"
|
|
:label="label"
|
|
:rows="rows"
|
|
:data-test-id="`parameter-input-${parsedParameterName}`"
|
|
:event-bus="eventBus"
|
|
@focus="onFocus"
|
|
@blur="onBlur"
|
|
@drop="onDrop"
|
|
@text-input="onTextInput"
|
|
@update="onValueChanged"
|
|
/>
|
|
<div v-if="!hideHint && (expressionOutput || parameterHint)" :class="$style.hint">
|
|
<div>
|
|
<InputHint
|
|
v-if="expressionOutput"
|
|
:class="{ [$style.hint]: true, 'ph-no-capture': isForCredential }"
|
|
:data-test-id="`parameter-expression-preview-${parsedParameterName}`"
|
|
:highlight="!!(expressionOutput && targetItem) && isInputParentOfActiveNode"
|
|
:hint="expressionOutput"
|
|
:single-line="true"
|
|
/>
|
|
<InputHint v-else-if="parameterHint" :render-h-t-m-l="true" :hint="parameterHint" />
|
|
</div>
|
|
<slot v-if="$slots.options" name="options" />
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style lang="scss" module>
|
|
.parameterInput {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--spacing-4xs);
|
|
}
|
|
|
|
.hovering {
|
|
color: var(--color-secondary);
|
|
}
|
|
</style>
|