mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +00:00
refactor: Refactor input components to composition API (no-changelog) (#9744)
This commit is contained in:
@@ -21,72 +21,49 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
|
||||||
import { useToast } from '@/composables/useToast';
|
|
||||||
import { i18n } from '@/plugins/i18n';
|
|
||||||
import { useClipboard } from '@/composables/useClipboard';
|
import { useClipboard } from '@/composables/useClipboard';
|
||||||
|
import { useI18n } from '@/composables/useI18n';
|
||||||
|
import { useToast } from '@/composables/useToast';
|
||||||
|
|
||||||
export default defineComponent({
|
type Props = {
|
||||||
props: {
|
label?: string;
|
||||||
label: {
|
hint?: string;
|
||||||
type: String,
|
value?: string;
|
||||||
},
|
copyButtonText: string;
|
||||||
hint: {
|
toastTitle?: string;
|
||||||
type: String,
|
toastMessage?: string;
|
||||||
},
|
size?: 'medium' | 'large';
|
||||||
value: {
|
collapse?: boolean;
|
||||||
type: String,
|
redactValue?: boolean;
|
||||||
},
|
};
|
||||||
copyButtonText: {
|
|
||||||
type: String,
|
|
||||||
default(): string {
|
|
||||||
return i18n.baseText('generic.copy');
|
|
||||||
},
|
|
||||||
},
|
|
||||||
toastTitle: {
|
|
||||||
type: String,
|
|
||||||
default(): string {
|
|
||||||
return i18n.baseText('generic.copiedToClipboard');
|
|
||||||
},
|
|
||||||
},
|
|
||||||
toastMessage: {
|
|
||||||
type: String,
|
|
||||||
},
|
|
||||||
collapse: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
size: {
|
|
||||||
type: String,
|
|
||||||
default: 'large',
|
|
||||||
},
|
|
||||||
redactValue: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setup() {
|
|
||||||
const clipboard = useClipboard();
|
|
||||||
|
|
||||||
return {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
clipboard,
|
value: '',
|
||||||
...useToast(),
|
placeholder: '',
|
||||||
};
|
label: '',
|
||||||
},
|
hint: '',
|
||||||
methods: {
|
size: 'medium',
|
||||||
copy(): void {
|
copyButtonText: useI18n().baseText('generic.copy'),
|
||||||
this.$emit('copy');
|
toastTitle: useI18n().baseText('generic.copiedToClipboard'),
|
||||||
void this.clipboard.copy(this.value ?? '');
|
|
||||||
|
|
||||||
this.showMessage({
|
|
||||||
title: this.toastTitle,
|
|
||||||
message: this.toastMessage,
|
|
||||||
type: 'success',
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(event: 'copy'): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const clipboard = useClipboard();
|
||||||
|
const { showMessage } = useToast();
|
||||||
|
|
||||||
|
function copy() {
|
||||||
|
emit('copy');
|
||||||
|
void clipboard.copy(props.value ?? '');
|
||||||
|
|
||||||
|
showMessage({
|
||||||
|
title: props.toastTitle,
|
||||||
|
message: props.toastMessage,
|
||||||
|
type: 'success',
|
||||||
|
});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
|||||||
@@ -144,7 +144,12 @@ import { defineComponent } from 'vue';
|
|||||||
import type { PropType } from 'vue';
|
import type { PropType } from 'vue';
|
||||||
import { mapStores } from 'pinia';
|
import { mapStores } from 'pinia';
|
||||||
|
|
||||||
import type { ICredentialType, INodeProperties, INodeTypeDescription } from 'n8n-workflow';
|
import type {
|
||||||
|
ICredentialDataDecryptedObject,
|
||||||
|
ICredentialType,
|
||||||
|
INodeProperties,
|
||||||
|
INodeTypeDescription,
|
||||||
|
} from 'n8n-workflow';
|
||||||
import { getAppNameFromCredType, isCommunityPackageName } from '@/utils/nodeTypesUtils';
|
import { getAppNameFromCredType, isCommunityPackageName } from '@/utils/nodeTypesUtils';
|
||||||
|
|
||||||
import Banner from '../Banner.vue';
|
import Banner from '../Banner.vue';
|
||||||
@@ -161,7 +166,7 @@ import { useRootStore } from '@/stores/n8nRoot.store';
|
|||||||
import { useNDVStore } from '@/stores/ndv.store';
|
import { useNDVStore } from '@/stores/ndv.store';
|
||||||
import { useCredentialsStore } from '@/stores/credentials.store';
|
import { useCredentialsStore } from '@/stores/credentials.store';
|
||||||
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||||
import type { ICredentialsResponse } from '@/Interface';
|
import type { ICredentialsResponse, IUpdateInformation } from '@/Interface';
|
||||||
import AuthTypeSelector from '@/components/CredentialEdit/AuthTypeSelector.vue';
|
import AuthTypeSelector from '@/components/CredentialEdit/AuthTypeSelector.vue';
|
||||||
import GoogleAuthButton from './GoogleAuthButton.vue';
|
import GoogleAuthButton from './GoogleAuthButton.vue';
|
||||||
import EnterpriseEdition from '@/components/EnterpriseEdition.ee.vue';
|
import EnterpriseEdition from '@/components/EnterpriseEdition.ee.vue';
|
||||||
@@ -190,7 +195,10 @@ export default defineComponent({
|
|||||||
type: Array as PropType<string[]>,
|
type: Array as PropType<string[]>,
|
||||||
default: () => [],
|
default: () => [],
|
||||||
},
|
},
|
||||||
credentialData: {},
|
credentialData: {
|
||||||
|
type: Object as PropType<ICredentialDataDecryptedObject>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
credentialId: {
|
credentialId: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
@@ -351,7 +359,7 @@ export default defineComponent({
|
|||||||
getCredentialOptions(type: string): ICredentialsResponse[] {
|
getCredentialOptions(type: string): ICredentialsResponse[] {
|
||||||
return this.credentialsStore.allUsableCredentialsByType[type];
|
return this.credentialsStore.allUsableCredentialsByType[type];
|
||||||
},
|
},
|
||||||
onDataChange(event: { name: string; value: string | number | boolean | Date | null }): void {
|
onDataChange(event: IUpdateInformation): void {
|
||||||
this.$emit('update', event);
|
this.$emit('update', event);
|
||||||
},
|
},
|
||||||
onDocumentationUrlClick(): void {
|
onDocumentationUrlClick(): void {
|
||||||
|
|||||||
@@ -12,10 +12,10 @@
|
|||||||
<ParameterInputExpanded
|
<ParameterInputExpanded
|
||||||
v-else
|
v-else
|
||||||
:parameter="parameter"
|
:parameter="parameter"
|
||||||
:value="credentialData[parameter.name]"
|
:value="credentialDataValues[parameter.name]"
|
||||||
:documentation-url="documentationUrl"
|
:documentation-url="documentationUrl"
|
||||||
:show-validation-warnings="showValidationWarnings"
|
:show-validation-warnings="showValidationWarnings"
|
||||||
:label="label"
|
:label="{ size: 'medium' }"
|
||||||
event-source="credentials"
|
event-source="credentials"
|
||||||
@update="valueChanged"
|
@update="valueChanged"
|
||||||
/>
|
/>
|
||||||
@@ -23,41 +23,41 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import type {
|
||||||
import type { IParameterLabel } from 'n8n-workflow';
|
ICredentialDataDecryptedObject,
|
||||||
|
INodeProperties,
|
||||||
|
NodeParameterValueType,
|
||||||
|
} from 'n8n-workflow';
|
||||||
import type { IUpdateInformation } from '@/Interface';
|
import type { IUpdateInformation } from '@/Interface';
|
||||||
import ParameterInputExpanded from '../ParameterInputExpanded.vue';
|
import ParameterInputExpanded from '../ParameterInputExpanded.vue';
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
export default defineComponent({
|
type Props = {
|
||||||
name: 'CredentialsInput',
|
credentialProperties: INodeProperties[];
|
||||||
components: {
|
credentialData: ICredentialDataDecryptedObject;
|
||||||
ParameterInputExpanded,
|
documentationUrl: string;
|
||||||
},
|
showValidationWarnings?: boolean;
|
||||||
props: [
|
};
|
||||||
'credentialProperties',
|
|
||||||
'credentialData', // ICredentialsDecryptedResponse
|
|
||||||
'documentationUrl',
|
|
||||||
'showValidationWarnings',
|
|
||||||
],
|
|
||||||
data(): { label: IParameterLabel } {
|
|
||||||
return {
|
|
||||||
label: {
|
|
||||||
size: 'medium',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
valueChanged(parameterData: IUpdateInformation) {
|
|
||||||
const name = parameterData.name.split('.').pop();
|
|
||||||
|
|
||||||
this.$emit('update', {
|
const props = defineProps<Props>();
|
||||||
name,
|
|
||||||
value: parameterData.value,
|
const credentialDataValues = computed(
|
||||||
});
|
() => props.credentialData as Record<string, NodeParameterValueType>,
|
||||||
},
|
);
|
||||||
},
|
|
||||||
});
|
const emit = defineEmits<{
|
||||||
|
(event: 'update', value: IUpdateInformation): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
function valueChanged(parameterData: IUpdateInformation) {
|
||||||
|
const name = parameterData.name.split('.').pop() ?? parameterData.name;
|
||||||
|
|
||||||
|
emit('update', {
|
||||||
|
name,
|
||||||
|
value: parameterData.value,
|
||||||
|
});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
|||||||
@@ -5,23 +5,24 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { computed } from 'vue';
|
||||||
|
|
||||||
export default defineComponent({
|
type Props = {
|
||||||
name: 'ExpandableInputBase',
|
modelValue: string;
|
||||||
props: ['modelValue', 'placeholder', 'staticSize'],
|
placeholder?: string;
|
||||||
computed: {
|
staticSize?: boolean;
|
||||||
hiddenValue() {
|
};
|
||||||
let value = (this.modelValue as string).replace(/\s/g, '.'); // force input to expand on space chars
|
|
||||||
if (!value) {
|
|
||||||
// @ts-ignore
|
|
||||||
value = this.placeholder;
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${value}`; // adjust for padding
|
const props = withDefaults(defineProps<Props>(), { staticSize: false, placeholder: '' });
|
||||||
},
|
|
||||||
},
|
const hiddenValue = computed(() => {
|
||||||
|
let value = props.modelValue.replace(/\s/g, '.'); // force input to expand on space chars
|
||||||
|
if (!value) {
|
||||||
|
value = props.placeholder;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${value}`; // adjust for padding
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<ExpandableInputBase :model-value="modelValue" :placeholder="placeholder">
|
<ExpandableInputBase :model-value="modelValue" :placeholder="placeholder">
|
||||||
<input
|
<input
|
||||||
ref="input"
|
ref="inputRef"
|
||||||
v-on-click-outside="onClickOutside"
|
v-on-click-outside="onClickOutside"
|
||||||
class="el-input__inner"
|
class="el-input__inner"
|
||||||
:value="modelValue"
|
:value="modelValue"
|
||||||
@@ -15,58 +15,66 @@
|
|||||||
</ExpandableInputBase>
|
</ExpandableInputBase>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
|
||||||
import ExpandableInputBase from './ExpandableInputBase.vue';
|
|
||||||
import type { PropType } from 'vue';
|
|
||||||
import type { EventBus } from 'n8n-design-system';
|
import type { EventBus } from 'n8n-design-system';
|
||||||
|
import { onBeforeUnmount, onMounted, ref } from 'vue';
|
||||||
|
import ExpandableInputBase from './ExpandableInputBase.vue';
|
||||||
|
|
||||||
export default defineComponent({
|
type Props = {
|
||||||
name: 'ExpandableInputEdit',
|
modelValue: string;
|
||||||
components: { ExpandableInputBase },
|
placeholder: string;
|
||||||
props: {
|
maxlength?: number;
|
||||||
modelValue: {
|
autofocus?: boolean;
|
||||||
type: String,
|
eventBus?: EventBus;
|
||||||
required: true,
|
};
|
||||||
},
|
|
||||||
placeholder: { type: String, required: true },
|
const props = defineProps<Props>();
|
||||||
maxlength: { type: Number },
|
const emit = defineEmits<{
|
||||||
autofocus: { type: Boolean },
|
(event: 'update:model-value', value: string): void;
|
||||||
eventBus: {
|
(event: 'enter', value: string): void;
|
||||||
type: Object as PropType<EventBus>,
|
(event: 'blur', value: string): void;
|
||||||
},
|
(event: 'esc'): void;
|
||||||
},
|
}>();
|
||||||
emits: ['update:modelValue', 'enter', 'blur', 'esc'],
|
|
||||||
mounted() {
|
const inputRef = ref<HTMLInputElement>();
|
||||||
// autofocus on input element is not reliable
|
|
||||||
if (this.autofocus && this.$refs.input) {
|
onMounted(() => {
|
||||||
this.focus();
|
// autofocus on input element is not reliable
|
||||||
}
|
if (props.autofocus && inputRef.value) {
|
||||||
this.eventBus?.on('focus', this.focus);
|
focus();
|
||||||
},
|
}
|
||||||
beforeUnmount() {
|
props.eventBus?.on('focus', focus);
|
||||||
this.eventBus?.off('focus', this.focus);
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
focus() {
|
|
||||||
if (this.$refs.input) {
|
|
||||||
(this.$refs.input as HTMLInputElement).focus();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onInput() {
|
|
||||||
this.$emit('update:modelValue', (this.$refs.input as HTMLInputElement).value);
|
|
||||||
},
|
|
||||||
onEnter() {
|
|
||||||
this.$emit('enter', (this.$refs.input as HTMLInputElement).value);
|
|
||||||
},
|
|
||||||
onClickOutside(e: Event) {
|
|
||||||
if (e.type === 'click') {
|
|
||||||
this.$emit('blur', (this.$refs.input as HTMLInputElement).value);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onEscape() {
|
|
||||||
this.$emit('esc');
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
props.eventBus?.off('focus', focus);
|
||||||
|
});
|
||||||
|
|
||||||
|
function focus() {
|
||||||
|
if (inputRef.value) {
|
||||||
|
inputRef.value.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onInput() {
|
||||||
|
if (inputRef.value) {
|
||||||
|
emit('update:model-value', inputRef.value.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onEnter() {
|
||||||
|
if (inputRef.value) {
|
||||||
|
emit('enter', inputRef.value.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onClickOutside(e: Event) {
|
||||||
|
if (e.type === 'click' && inputRef.value) {
|
||||||
|
emit('blur', inputRef.value.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onEscape() {
|
||||||
|
emit('esc');
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -9,15 +9,14 @@
|
|||||||
</ExpandableInputBase>
|
</ExpandableInputBase>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
|
||||||
import ExpandableInputBase from './ExpandableInputBase.vue';
|
import ExpandableInputBase from './ExpandableInputBase.vue';
|
||||||
|
|
||||||
export default defineComponent({
|
type Props = {
|
||||||
name: 'ExpandableInputPreview',
|
modelValue: string;
|
||||||
components: { ExpandableInputBase },
|
};
|
||||||
props: ['modelValue'],
|
|
||||||
});
|
defineProps<Props>();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
@@ -105,13 +105,13 @@ const rightParameter = computed<INodeProperties>(() => {
|
|||||||
|
|
||||||
const debouncedEmitUpdate = debounce(() => emit('update', condition.value), { debounceTime: 500 });
|
const debouncedEmitUpdate = debounce(() => emit('update', condition.value), { debounceTime: 500 });
|
||||||
|
|
||||||
const onLeftValueChange = (update: IUpdateInformation<NodeParameterValue>): void => {
|
const onLeftValueChange = (update: IUpdateInformation): void => {
|
||||||
condition.value.leftValue = update.value;
|
condition.value.leftValue = update.value as NodeParameterValue;
|
||||||
debouncedEmitUpdate();
|
debouncedEmitUpdate();
|
||||||
};
|
};
|
||||||
|
|
||||||
const onRightValueChange = (update: IUpdateInformation<NodeParameterValue>): void => {
|
const onRightValueChange = (update: IUpdateInformation): void => {
|
||||||
condition.value.rightValue = update.value;
|
condition.value.rightValue = update.value as NodeParameterValue;
|
||||||
debouncedEmitUpdate();
|
debouncedEmitUpdate();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -54,124 +54,114 @@
|
|||||||
</n8n-input-label>
|
</n8n-input-label>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import type { IUpdateInformation } from '@/Interface';
|
import type { IUpdateInformation } from '@/Interface';
|
||||||
import ParameterOptions from './ParameterOptions.vue';
|
import { useI18n } from '@/composables/useI18n';
|
||||||
import { defineComponent } from 'vue';
|
import { useTelemetry } from '@/composables/useTelemetry';
|
||||||
import type { PropType } from 'vue';
|
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||||
import ParameterInputWrapper from './ParameterInputWrapper.vue';
|
import { isValueExpression as isValueExpressionUtil } from '@/utils/nodeTypesUtils';
|
||||||
import { isValueExpression } from '@/utils/nodeTypesUtils';
|
import { createEventBus } from 'n8n-design-system/utils';
|
||||||
import type {
|
import type {
|
||||||
INodeParameterResourceLocator,
|
INodeParameterResourceLocator,
|
||||||
INodeProperties,
|
INodeProperties,
|
||||||
IParameterLabel,
|
IParameterLabel,
|
||||||
NodeParameterValueType,
|
NodeParameterValueType,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import { mapStores } from 'pinia';
|
import { computed, ref } from 'vue';
|
||||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
import ParameterInputWrapper from './ParameterInputWrapper.vue';
|
||||||
import { createEventBus } from 'n8n-design-system/utils';
|
import ParameterOptions from './ParameterOptions.vue';
|
||||||
|
|
||||||
export default defineComponent({
|
type Props = {
|
||||||
name: 'ParameterInputExpanded',
|
parameter: INodeProperties;
|
||||||
components: {
|
value: NodeParameterValueType;
|
||||||
ParameterOptions,
|
showValidationWarnings?: boolean;
|
||||||
ParameterInputWrapper,
|
documentationUrl?: string;
|
||||||
},
|
eventSource?: string;
|
||||||
props: {
|
label?: IParameterLabel;
|
||||||
parameter: {
|
};
|
||||||
type: Object as PropType<INodeProperties>,
|
|
||||||
required: true,
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
},
|
label: () => ({ size: 'small' }),
|
||||||
value: {
|
});
|
||||||
type: Object as PropType<NodeParameterValueType>,
|
const emit = defineEmits<{
|
||||||
},
|
(event: 'update', value: IUpdateInformation): void;
|
||||||
showValidationWarnings: {
|
}>();
|
||||||
type: Boolean,
|
|
||||||
},
|
const focused = ref(false);
|
||||||
documentationUrl: {
|
const blurredEver = ref(false);
|
||||||
type: String,
|
const menuExpanded = ref(false);
|
||||||
},
|
const eventBus = ref(createEventBus());
|
||||||
eventSource: {
|
|
||||||
type: String,
|
const workflowsStore = useWorkflowsStore();
|
||||||
},
|
|
||||||
label: {
|
const i18n = useI18n();
|
||||||
type: Object as PropType<IParameterLabel>,
|
const telemetry = useTelemetry();
|
||||||
default: () => ({
|
|
||||||
size: 'small',
|
const showRequiredErrors = computed(() => {
|
||||||
}),
|
if (!props.parameter.required) {
|
||||||
},
|
return false;
|
||||||
},
|
}
|
||||||
data() {
|
|
||||||
return {
|
if (blurredEver.value || props.showValidationWarnings) {
|
||||||
focused: false,
|
if (props.parameter.type === 'string') {
|
||||||
blurredEver: false,
|
return !props.value;
|
||||||
menuExpanded: false,
|
}
|
||||||
eventBus: createEventBus(),
|
|
||||||
};
|
if (props.parameter.type === 'number') {
|
||||||
},
|
if (typeof props.value === 'string' && props.value.startsWith('=')) {
|
||||||
computed: {
|
|
||||||
...mapStores(useWorkflowsStore),
|
|
||||||
showRequiredErrors(): boolean {
|
|
||||||
if (!this.parameter.required) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.blurredEver || this.showValidationWarnings) {
|
return typeof props.value !== 'number';
|
||||||
if (this.parameter.type === 'string') {
|
}
|
||||||
return !this.value;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (this.parameter.type === 'number') {
|
return false;
|
||||||
if (typeof this.value === 'string' && this.value.startsWith('=')) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return typeof this.value !== 'number';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
hint(): string | null {
|
|
||||||
if (this.isValueExpression) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.$locale.credText().hint(this.parameter);
|
|
||||||
},
|
|
||||||
isValueExpression(): boolean {
|
|
||||||
return isValueExpression(
|
|
||||||
this.parameter,
|
|
||||||
this.value as string | INodeParameterResourceLocator,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
onFocus() {
|
|
||||||
this.focused = true;
|
|
||||||
},
|
|
||||||
onBlur() {
|
|
||||||
this.blurredEver = true;
|
|
||||||
this.focused = false;
|
|
||||||
},
|
|
||||||
onMenuExpanded(expanded: boolean) {
|
|
||||||
this.menuExpanded = expanded;
|
|
||||||
},
|
|
||||||
optionSelected(command: string) {
|
|
||||||
this.eventBus.emit('optionSelected', command);
|
|
||||||
},
|
|
||||||
valueChanged(parameterData: IUpdateInformation) {
|
|
||||||
this.$emit('update', parameterData);
|
|
||||||
},
|
|
||||||
onDocumentationUrlClick(): void {
|
|
||||||
this.$telemetry.track('User clicked credential modal docs link', {
|
|
||||||
docs_link: this.documentationUrl,
|
|
||||||
source: 'field',
|
|
||||||
workflow_id: this.workflowsStore.workflowId,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const hint = computed(() => {
|
||||||
|
if (isValueExpression.value) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return i18n.credText().hint(props.parameter);
|
||||||
|
});
|
||||||
|
|
||||||
|
const isValueExpression = computed(() => {
|
||||||
|
return isValueExpressionUtil(
|
||||||
|
props.parameter,
|
||||||
|
props.value as string | INodeParameterResourceLocator,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
function onFocus() {
|
||||||
|
focused.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onBlur() {
|
||||||
|
blurredEver.value = true;
|
||||||
|
focused.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMenuExpanded(expanded: boolean) {
|
||||||
|
menuExpanded.value = expanded;
|
||||||
|
}
|
||||||
|
|
||||||
|
function optionSelected(command: string) {
|
||||||
|
eventBus.value.emit('optionSelected', command);
|
||||||
|
}
|
||||||
|
|
||||||
|
function valueChanged(parameterData: IUpdateInformation) {
|
||||||
|
emit('update', parameterData);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDocumentationUrlClick(): void {
|
||||||
|
telemetry.track('User clicked credential modal docs link', {
|
||||||
|
docs_link: props.documentationUrl,
|
||||||
|
source: 'field',
|
||||||
|
workflow_id: workflowsStore.workflowId,
|
||||||
|
});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
type="mapping"
|
type="mapping"
|
||||||
:disabled="isDropDisabled"
|
:disabled="isDropDisabled"
|
||||||
:sticky="true"
|
:sticky="true"
|
||||||
:sticky-offset="isValueExpression ? [26, 3] : [3, 3]"
|
:sticky-offset="isExpression ? [26, 3] : [3, 3]"
|
||||||
@drop="onDrop"
|
@drop="onDrop"
|
||||||
>
|
>
|
||||||
<template #default="{ droppable, activeDrop }">
|
<template #default="{ droppable, activeDrop }">
|
||||||
@@ -77,276 +77,208 @@
|
|||||||
</n8n-input-label>
|
</n8n-input-label>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import type { PropType } from 'vue';
|
|
||||||
import { mapStores } from 'pinia';
|
|
||||||
|
|
||||||
import type { INodeUi, IRunDataDisplayMode, IUpdateInformation } from '@/Interface';
|
import type { IUpdateInformation } from '@/Interface';
|
||||||
|
|
||||||
import ParameterOptions from '@/components/ParameterOptions.vue';
|
|
||||||
import DraggableTarget from '@/components/DraggableTarget.vue';
|
import DraggableTarget from '@/components/DraggableTarget.vue';
|
||||||
|
import ParameterInputWrapper from '@/components/ParameterInputWrapper.vue';
|
||||||
|
import ParameterOptions from '@/components/ParameterOptions.vue';
|
||||||
import { useI18n } from '@/composables/useI18n';
|
import { useI18n } from '@/composables/useI18n';
|
||||||
import { useToast } from '@/composables/useToast';
|
import { useToast } from '@/composables/useToast';
|
||||||
import { hasExpressionMapping, hasOnlyListMode, isValueExpression } from '@/utils/nodeTypesUtils';
|
|
||||||
import { isResourceLocatorValue } from '@/utils/typeGuards';
|
|
||||||
import ParameterInputWrapper from '@/components/ParameterInputWrapper.vue';
|
|
||||||
import type {
|
|
||||||
INodeProperties,
|
|
||||||
INodePropertyMode,
|
|
||||||
IParameterLabel,
|
|
||||||
NodeParameterValueType,
|
|
||||||
} from 'n8n-workflow';
|
|
||||||
import { useNDVStore } from '@/stores/ndv.store';
|
import { useNDVStore } from '@/stores/ndv.store';
|
||||||
import { useSegment } from '@/stores/segment.store';
|
import { useSegment } from '@/stores/segment.store';
|
||||||
import { getMappedResult } from '@/utils/mappingUtils';
|
import { getMappedResult } from '@/utils/mappingUtils';
|
||||||
|
import { hasExpressionMapping, hasOnlyListMode, isValueExpression } from '@/utils/nodeTypesUtils';
|
||||||
|
import { isResourceLocatorValue } from '@/utils/typeGuards';
|
||||||
import { createEventBus } from 'n8n-design-system/utils';
|
import { createEventBus } from 'n8n-design-system/utils';
|
||||||
|
import type { INodeProperties, IParameterLabel, NodeParameterValueType } from 'n8n-workflow';
|
||||||
import InlineExpressionTip from './InlineExpressionEditor/InlineExpressionTip.vue';
|
import InlineExpressionTip from './InlineExpressionEditor/InlineExpressionTip.vue';
|
||||||
|
|
||||||
export default defineComponent({
|
type Props = {
|
||||||
name: 'ParameterInputFull',
|
parameter: INodeProperties;
|
||||||
components: {
|
path: string;
|
||||||
ParameterOptions,
|
value: NodeParameterValueType;
|
||||||
DraggableTarget,
|
label?: IParameterLabel;
|
||||||
ParameterInputWrapper,
|
displayOptions?: boolean;
|
||||||
InlineExpressionTip,
|
optionsPosition?: 'bottom' | 'top';
|
||||||
},
|
hideHint?: boolean;
|
||||||
props: {
|
isReadOnly?: boolean;
|
||||||
displayOptions: {
|
rows?: number;
|
||||||
type: Boolean,
|
isAssignment?: boolean;
|
||||||
default: false,
|
hideLabel?: boolean;
|
||||||
},
|
hideIssues?: boolean;
|
||||||
optionsPosition: {
|
entryIndex?: number;
|
||||||
type: String as PropType<'bottom' | 'top'>,
|
};
|
||||||
default: 'top',
|
|
||||||
},
|
|
||||||
hideHint: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
isReadOnly: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
rows: {
|
|
||||||
type: Number,
|
|
||||||
default: 5,
|
|
||||||
},
|
|
||||||
isAssignment: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
hideLabel: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
hideIssues: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
parameter: {
|
|
||||||
type: Object as PropType<INodeProperties>,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
path: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
value: {
|
|
||||||
type: [Number, String, Boolean, Array, Object] as PropType<NodeParameterValueType>,
|
|
||||||
},
|
|
||||||
label: {
|
|
||||||
type: Object as PropType<IParameterLabel>,
|
|
||||||
default: () => ({
|
|
||||||
size: 'small',
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
entryIndex: {
|
|
||||||
type: Number,
|
|
||||||
default: undefined,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setup() {
|
|
||||||
const eventBus = createEventBus();
|
|
||||||
const i18n = useI18n();
|
|
||||||
|
|
||||||
return {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
i18n,
|
optionsPosition: 'top',
|
||||||
eventBus,
|
hideHint: false,
|
||||||
...useToast(),
|
isReadOnly: false,
|
||||||
};
|
rows: 5,
|
||||||
},
|
hideLabel: false,
|
||||||
data() {
|
hideIssues: false,
|
||||||
return {
|
label: () => ({ size: 'small' }),
|
||||||
focused: false,
|
|
||||||
menuExpanded: false,
|
|
||||||
forceShowExpression: false,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapStores(useNDVStore),
|
|
||||||
node(): INodeUi | null {
|
|
||||||
return this.ndvStore.activeNode;
|
|
||||||
},
|
|
||||||
hint(): string {
|
|
||||||
return this.i18n.nodeText().hint(this.parameter, this.path);
|
|
||||||
},
|
|
||||||
isInputTypeString(): boolean {
|
|
||||||
return this.parameter.type === 'string';
|
|
||||||
},
|
|
||||||
isInputTypeNumber(): boolean {
|
|
||||||
return this.parameter.type === 'number';
|
|
||||||
},
|
|
||||||
isResourceLocator(): boolean {
|
|
||||||
return this.parameter.type === 'resourceLocator';
|
|
||||||
},
|
|
||||||
isDropDisabled(): boolean {
|
|
||||||
return this.parameter.noDataExpression || this.isReadOnly || this.isResourceLocator;
|
|
||||||
},
|
|
||||||
isValueExpression(): boolean {
|
|
||||||
return isValueExpression(this.parameter, this.value);
|
|
||||||
},
|
|
||||||
showExpressionSelector(): boolean {
|
|
||||||
return this.isResourceLocator ? !hasOnlyListMode(this.parameter) : true;
|
|
||||||
},
|
|
||||||
isInputDataEmpty(): boolean {
|
|
||||||
return this.ndvStore.isNDVDataEmpty('input');
|
|
||||||
},
|
|
||||||
displayMode(): IRunDataDisplayMode {
|
|
||||||
return this.ndvStore.inputPanelDisplayMode;
|
|
||||||
},
|
|
||||||
showDragnDropTip(): boolean {
|
|
||||||
return (
|
|
||||||
this.focused &&
|
|
||||||
(this.isInputTypeString || this.isInputTypeNumber) &&
|
|
||||||
!this.isValueExpression &&
|
|
||||||
!this.isDropDisabled &&
|
|
||||||
(!this.ndvStore.hasInputData || !this.isInputDataEmpty) &&
|
|
||||||
!this.ndvStore.isMappingOnboarded &&
|
|
||||||
this.ndvStore.isInputParentOfActiveNode
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
onFocus() {
|
|
||||||
this.focused = true;
|
|
||||||
if (!this.parameter.noDataExpression) {
|
|
||||||
this.ndvStore.setMappableNDVInputFocus(this.parameter.displayName);
|
|
||||||
}
|
|
||||||
this.ndvStore.setFocusedInputPath(this.path ?? '');
|
|
||||||
},
|
|
||||||
onBlur() {
|
|
||||||
this.focused = false;
|
|
||||||
if (
|
|
||||||
!this.parameter.noDataExpression &&
|
|
||||||
this.ndvStore.focusedMappableInput === this.parameter.displayName
|
|
||||||
) {
|
|
||||||
this.ndvStore.setMappableNDVInputFocus('');
|
|
||||||
}
|
|
||||||
this.ndvStore.setFocusedInputPath('');
|
|
||||||
this.$emit('blur');
|
|
||||||
},
|
|
||||||
onMenuExpanded(expanded: boolean) {
|
|
||||||
this.menuExpanded = expanded;
|
|
||||||
},
|
|
||||||
optionSelected(command: string) {
|
|
||||||
this.eventBus.emit('optionSelected', command);
|
|
||||||
},
|
|
||||||
valueChanged(parameterData: IUpdateInformation) {
|
|
||||||
this.$emit('update', parameterData);
|
|
||||||
},
|
|
||||||
onTextInput(parameterData: IUpdateInformation) {
|
|
||||||
if (isValueExpression(this.parameter, parameterData.value)) {
|
|
||||||
this.eventBus.emit('optionSelected', 'addExpression');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onDrop(newParamValue: string) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
setTimeout(() => {
|
|
||||||
if (this.node) {
|
|
||||||
let parameterData;
|
|
||||||
if (this.isResourceLocator) {
|
|
||||||
if (!isResourceLocatorValue(this.value)) {
|
|
||||||
parameterData = {
|
|
||||||
node: this.node.name,
|
|
||||||
name: this.path,
|
|
||||||
value: { __rl: true, value: updatedValue, mode: '' },
|
|
||||||
};
|
|
||||||
} else if (
|
|
||||||
this.value.mode === 'list' &&
|
|
||||||
this.parameter.modes &&
|
|
||||||
this.parameter.modes.length > 1
|
|
||||||
) {
|
|
||||||
let mode =
|
|
||||||
this.parameter.modes.find((mode: INodePropertyMode) => mode.name === 'id') || null;
|
|
||||||
if (!mode) {
|
|
||||||
mode = this.parameter.modes.filter(
|
|
||||||
(mode: INodePropertyMode) => mode.name !== 'list',
|
|
||||||
)[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
parameterData = {
|
|
||||||
node: this.node.name,
|
|
||||||
name: this.path,
|
|
||||||
value: { __rl: true, value: updatedValue, mode: mode ? mode.name : '' },
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
parameterData = {
|
|
||||||
node: this.node.name,
|
|
||||||
name: this.path,
|
|
||||||
value: { __rl: true, value: updatedValue, mode: this.value.mode },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
parameterData = {
|
|
||||||
node: this.node.name,
|
|
||||||
name: this.path,
|
|
||||||
value: updatedValue,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
this.valueChanged(parameterData);
|
|
||||||
this.eventBus.emit('drop', updatedValue);
|
|
||||||
|
|
||||||
if (!this.ndvStore.isMappingOnboarded) {
|
|
||||||
this.showMessage({
|
|
||||||
title: this.i18n.baseText('dataMapping.success.title'),
|
|
||||||
message: this.i18n.baseText('dataMapping.success.moreInfo'),
|
|
||||||
type: 'success',
|
|
||||||
dangerouslyUseHTMLString: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.ndvStore.setMappingOnboarded();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.ndvStore.setMappingTelemetry({
|
|
||||||
dest_node_type: this.node.type,
|
|
||||||
dest_parameter: this.path,
|
|
||||||
dest_parameter_mode:
|
|
||||||
typeof prevValue === 'string' && prevValue.startsWith('=') ? 'expression' : 'fixed',
|
|
||||||
dest_parameter_empty: prevValue === '' || prevValue === undefined,
|
|
||||||
dest_parameter_had_mapping:
|
|
||||||
typeof prevValue === 'string' &&
|
|
||||||
prevValue.startsWith('=') &&
|
|
||||||
hasExpressionMapping(prevValue),
|
|
||||||
success: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const segment = useSegment();
|
|
||||||
segment.track(segment.EVENTS.MAPPED_DATA);
|
|
||||||
}
|
|
||||||
this.forceShowExpression = false;
|
|
||||||
}, 200);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(event: 'blur'): void;
|
||||||
|
(event: 'update', value: IUpdateInformation): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const i18n = useI18n();
|
||||||
|
const toast = useToast();
|
||||||
|
|
||||||
|
const eventBus = ref(createEventBus());
|
||||||
|
const focused = ref(false);
|
||||||
|
const menuExpanded = ref(false);
|
||||||
|
const forceShowExpression = ref(false);
|
||||||
|
|
||||||
|
const ndvStore = useNDVStore();
|
||||||
|
|
||||||
|
const node = computed(() => ndvStore.activeNode);
|
||||||
|
const hint = computed(() => i18n.nodeText().hint(props.parameter, props.path));
|
||||||
|
const isInputTypeString = computed(() => props.parameter.type === 'string');
|
||||||
|
const isInputTypeNumber = computed(() => props.parameter.type === 'number');
|
||||||
|
const isResourceLocator = computed(() => props.parameter.type === 'resourceLocator');
|
||||||
|
const isDropDisabled = computed(
|
||||||
|
() => props.parameter.noDataExpression || props.isReadOnly || isResourceLocator.value,
|
||||||
|
);
|
||||||
|
const isExpression = computed(() => isValueExpression(props.parameter, props.value));
|
||||||
|
const showExpressionSelector = computed(() =>
|
||||||
|
isResourceLocator.value ? !hasOnlyListMode(props.parameter) : true,
|
||||||
|
);
|
||||||
|
const isInputDataEmpty = computed(() => ndvStore.isNDVDataEmpty('input'));
|
||||||
|
const showDragnDropTip = computed(
|
||||||
|
() =>
|
||||||
|
focused.value &&
|
||||||
|
(isInputTypeString.value || isInputTypeNumber.value) &&
|
||||||
|
!isExpression.value &&
|
||||||
|
!isDropDisabled.value &&
|
||||||
|
(!ndvStore.hasInputData || !isInputDataEmpty.value) &&
|
||||||
|
!ndvStore.isMappingOnboarded &&
|
||||||
|
ndvStore.isInputParentOfActiveNode,
|
||||||
|
);
|
||||||
|
|
||||||
|
function onFocus() {
|
||||||
|
focused.value = true;
|
||||||
|
if (!props.parameter.noDataExpression) {
|
||||||
|
ndvStore.setMappableNDVInputFocus(props.parameter.displayName);
|
||||||
|
}
|
||||||
|
ndvStore.setFocusedInputPath(props.path ?? '');
|
||||||
|
}
|
||||||
|
|
||||||
|
function onBlur() {
|
||||||
|
focused.value = false;
|
||||||
|
if (
|
||||||
|
!props.parameter.noDataExpression &&
|
||||||
|
ndvStore.focusedMappableInput === props.parameter.displayName
|
||||||
|
) {
|
||||||
|
ndvStore.setMappableNDVInputFocus('');
|
||||||
|
}
|
||||||
|
ndvStore.setFocusedInputPath('');
|
||||||
|
emit('blur');
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMenuExpanded(expanded: boolean) {
|
||||||
|
menuExpanded.value = expanded;
|
||||||
|
}
|
||||||
|
|
||||||
|
function optionSelected(command: string) {
|
||||||
|
eventBus.value.emit('optionSelected', command);
|
||||||
|
}
|
||||||
|
|
||||||
|
function valueChanged(parameterData: IUpdateInformation) {
|
||||||
|
emit('update', parameterData);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onTextInput(parameterData: IUpdateInformation) {
|
||||||
|
if (isValueExpression(props.parameter, parameterData.value)) {
|
||||||
|
eventBus.value.emit('optionSelected', 'addExpression');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDrop(newParamValue: string) {
|
||||||
|
const value = props.value;
|
||||||
|
const updatedValue = getMappedResult(props.parameter, newParamValue, value);
|
||||||
|
const prevValue = isResourceLocator.value && isResourceLocatorValue(value) ? value.value : value;
|
||||||
|
|
||||||
|
if (updatedValue.startsWith('=')) {
|
||||||
|
forceShowExpression.value = true;
|
||||||
|
}
|
||||||
|
setTimeout(() => {
|
||||||
|
if (node.value) {
|
||||||
|
let parameterData;
|
||||||
|
if (isResourceLocator.value) {
|
||||||
|
if (!isResourceLocatorValue(props.value)) {
|
||||||
|
parameterData = {
|
||||||
|
node: node.value.name,
|
||||||
|
name: props.path,
|
||||||
|
value: { __rl: true, value: updatedValue, mode: '' },
|
||||||
|
};
|
||||||
|
} else if (
|
||||||
|
props.value.mode === 'list' &&
|
||||||
|
props.parameter.modes &&
|
||||||
|
props.parameter.modes.length > 1
|
||||||
|
) {
|
||||||
|
let mode = props.parameter.modes.find((m) => m.name === 'id') ?? null;
|
||||||
|
if (!mode) {
|
||||||
|
mode = props.parameter.modes.filter((m) => m.name !== 'list')[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
parameterData = {
|
||||||
|
node: node.value.name,
|
||||||
|
name: props.path,
|
||||||
|
value: { __rl: true, value: updatedValue, mode: mode ? mode.name : '' },
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
parameterData = {
|
||||||
|
node: node.value.name,
|
||||||
|
name: props.path,
|
||||||
|
value: { __rl: true, value: updatedValue, mode: props.value?.mode },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
parameterData = {
|
||||||
|
node: node.value.name,
|
||||||
|
name: props.path,
|
||||||
|
value: updatedValue,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
valueChanged(parameterData);
|
||||||
|
eventBus.value.emit('drop', updatedValue);
|
||||||
|
|
||||||
|
if (!ndvStore.isMappingOnboarded) {
|
||||||
|
toast.showMessage({
|
||||||
|
title: i18n.baseText('dataMapping.success.title'),
|
||||||
|
message: i18n.baseText('dataMapping.success.moreInfo'),
|
||||||
|
type: 'success',
|
||||||
|
dangerouslyUseHTMLString: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
ndvStore.setMappingOnboarded();
|
||||||
|
}
|
||||||
|
|
||||||
|
ndvStore.setMappingTelemetry({
|
||||||
|
dest_node_type: node.value.type,
|
||||||
|
dest_parameter: props.path,
|
||||||
|
dest_parameter_mode:
|
||||||
|
typeof prevValue === 'string' && prevValue.startsWith('=') ? 'expression' : 'fixed',
|
||||||
|
dest_parameter_empty: prevValue === '' || prevValue === undefined,
|
||||||
|
dest_parameter_had_mapping:
|
||||||
|
typeof prevValue === 'string' &&
|
||||||
|
prevValue.startsWith('=') &&
|
||||||
|
hasExpressionMapping(prevValue),
|
||||||
|
success: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const segment = useSegment();
|
||||||
|
segment.track(segment.EVENTS.MAPPED_DATA);
|
||||||
|
}
|
||||||
|
forceShowExpression.value = false;
|
||||||
|
}, 200);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
|||||||
@@ -1,64 +1,59 @@
|
|||||||
<template>
|
<template>
|
||||||
<n8n-text v-if="hint" size="small" color="text-base" tag="div">
|
<n8n-text v-if="hint" size="small" color="text-base" tag="div">
|
||||||
<div v-if="!renderHTML" :class="classes"><span v-html="simplyText"></span></div>
|
<div
|
||||||
|
v-if="!renderHTML"
|
||||||
|
:class="{
|
||||||
|
[$style.singleline]: singleLine,
|
||||||
|
[$style.highlight]: highlight,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<span v-html="simplyText"></span>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
v-else
|
v-else
|
||||||
ref="hint"
|
ref="hintTextRef"
|
||||||
:class="{ [$style.singleline]: singleLine, [$style.highlight]: highlight }"
|
:class="{ [$style.singleline]: singleLine, [$style.highlight]: highlight }"
|
||||||
v-html="sanitizeHtml(hint)"
|
v-html="sanitizeHtml(hint)"
|
||||||
></div>
|
></div>
|
||||||
</n8n-text>
|
</n8n-text>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
|
||||||
import { sanitizeHtml } from '@/utils/htmlUtils';
|
import { sanitizeHtml } from '@/utils/htmlUtils';
|
||||||
|
import { computed, onMounted, ref } from 'vue';
|
||||||
|
|
||||||
export default defineComponent({
|
type Props = {
|
||||||
name: 'InputHint',
|
hint: string;
|
||||||
props: {
|
highlight?: boolean;
|
||||||
hint: {
|
singleLine?: boolean;
|
||||||
type: String,
|
renderHTML?: boolean;
|
||||||
},
|
};
|
||||||
highlight: {
|
|
||||||
type: Boolean,
|
|
||||||
},
|
|
||||||
singleLine: {
|
|
||||||
type: Boolean,
|
|
||||||
},
|
|
||||||
renderHTML: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
classes() {
|
|
||||||
return {
|
|
||||||
[this.$style.singleline]: this.singleLine,
|
|
||||||
[this.$style.highlight]: this.highlight,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
simplyText(): string {
|
|
||||||
if (this.hint) {
|
|
||||||
return String(this.hint)
|
|
||||||
.replace(/&/g, '&') // allows us to keep spaces at the beginning of an expression
|
|
||||||
.replace(/</g, '<') // prevent XSS exploits since we are rendering HTML
|
|
||||||
.replace(/>/g, '>')
|
|
||||||
.replace(/"/g, '"')
|
|
||||||
.replace(/ /g, ' ');
|
|
||||||
}
|
|
||||||
|
|
||||||
return '';
|
const hintTextRef = ref<HTMLDivElement>();
|
||||||
},
|
|
||||||
},
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
mounted() {
|
highlight: false,
|
||||||
if (this.$refs.hint) {
|
singleLine: false,
|
||||||
(this.$refs.hint as Element).querySelectorAll('a').forEach((a) => (a.target = '_blank'));
|
renderHTML: false,
|
||||||
}
|
});
|
||||||
},
|
|
||||||
methods: {
|
onMounted(() => {
|
||||||
sanitizeHtml,
|
if (hintTextRef.value) {
|
||||||
},
|
hintTextRef.value.querySelectorAll('a').forEach((a) => (a.target = '_blank'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const simplyText = computed(() => {
|
||||||
|
if (props.hint) {
|
||||||
|
return String(props.hint)
|
||||||
|
.replace(/&/g, '&') // allows us to keep spaces at the beginning of an expression
|
||||||
|
.replace(/</g, '<') // prevent XSS exploits since we are rendering HTML
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
.replace(/ /g, ' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -161,27 +161,26 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import type {
|
import type {
|
||||||
INodeParameters,
|
INodeParameters,
|
||||||
INodeProperties,
|
INodeProperties,
|
||||||
INodeTypeDescription,
|
|
||||||
NodeParameterValue,
|
NodeParameterValue,
|
||||||
NodeParameterValueType,
|
NodeParameterValueType,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import { deepCopy } from 'n8n-workflow';
|
import { deepCopy } from 'n8n-workflow';
|
||||||
import { mapStores } from 'pinia';
|
import { computed, defineAsyncComponent, onErrorCaptured, ref, watch } from 'vue';
|
||||||
import type { PropType } from 'vue';
|
|
||||||
import { defineAsyncComponent, defineComponent, onErrorCaptured, ref } from 'vue';
|
|
||||||
|
|
||||||
import type { INodeUi, IUpdateInformation } from '@/Interface';
|
import type { IUpdateInformation } from '@/Interface';
|
||||||
|
|
||||||
|
import AssignmentCollection from '@/components/AssignmentCollection/AssignmentCollection.vue';
|
||||||
|
import FilterConditions from '@/components/FilterConditions/FilterConditions.vue';
|
||||||
import ImportCurlParameter from '@/components/ImportCurlParameter.vue';
|
import ImportCurlParameter from '@/components/ImportCurlParameter.vue';
|
||||||
import MultipleParameter from '@/components/MultipleParameter.vue';
|
import MultipleParameter from '@/components/MultipleParameter.vue';
|
||||||
import ParameterInputFull from '@/components/ParameterInputFull.vue';
|
import ParameterInputFull from '@/components/ParameterInputFull.vue';
|
||||||
import ResourceMapper from '@/components/ResourceMapper/ResourceMapper.vue';
|
import ResourceMapper from '@/components/ResourceMapper/ResourceMapper.vue';
|
||||||
import FilterConditions from '@/components/FilterConditions/FilterConditions.vue';
|
import { useNodeHelpers } from '@/composables/useNodeHelpers';
|
||||||
import AssignmentCollection from '@/components/AssignmentCollection/AssignmentCollection.vue';
|
import { useWorkflowHelpers } from '@/composables/useWorkflowHelpers';
|
||||||
import { KEEP_AUTH_IN_NDV_FOR_NODES } from '@/constants';
|
import { KEEP_AUTH_IN_NDV_FOR_NODES } from '@/constants';
|
||||||
import { useNDVStore } from '@/stores/ndv.store';
|
import { useNDVStore } from '@/stores/ndv.store';
|
||||||
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||||
@@ -191,9 +190,7 @@ import {
|
|||||||
isAuthRelatedParameter,
|
isAuthRelatedParameter,
|
||||||
} from '@/utils/nodeTypesUtils';
|
} from '@/utils/nodeTypesUtils';
|
||||||
import { get, set } from 'lodash-es';
|
import { get, set } from 'lodash-es';
|
||||||
import { useNodeHelpers } from '@/composables/useNodeHelpers';
|
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { useWorkflowHelpers } from '@/composables/useWorkflowHelpers';
|
|
||||||
|
|
||||||
const FixedCollectionParameter = defineAsyncComponent(
|
const FixedCollectionParameter = defineAsyncComponent(
|
||||||
async () => await import('./FixedCollectionParameter.vue'),
|
async () => await import('./FixedCollectionParameter.vue'),
|
||||||
@@ -202,382 +199,344 @@ const CollectionParameter = defineAsyncComponent(
|
|||||||
async () => await import('./CollectionParameter.vue'),
|
async () => await import('./CollectionParameter.vue'),
|
||||||
);
|
);
|
||||||
|
|
||||||
export default defineComponent({
|
type Props = {
|
||||||
name: 'ParameterInputList',
|
nodeValues: INodeParameters;
|
||||||
components: {
|
parameters: INodeProperties[];
|
||||||
MultipleParameter,
|
path?: string;
|
||||||
ParameterInputFull,
|
hideDelete?: boolean;
|
||||||
FixedCollectionParameter,
|
indent?: boolean;
|
||||||
CollectionParameter,
|
isReadOnly?: boolean;
|
||||||
ImportCurlParameter,
|
hiddenIssuesInputs?: string[];
|
||||||
ResourceMapper,
|
entryIndex?: number;
|
||||||
FilterConditions,
|
};
|
||||||
AssignmentCollection,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
nodeValues: {
|
|
||||||
type: Object as PropType<INodeParameters>,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
parameters: {
|
|
||||||
type: Array as PropType<INodeProperties[]>,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
path: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
hideDelete: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
indent: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
isReadOnly: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
hiddenIssuesInputs: {
|
|
||||||
type: Array as PropType<string[]>,
|
|
||||||
default: () => [],
|
|
||||||
},
|
|
||||||
entryIndex: {
|
|
||||||
type: Number,
|
|
||||||
default: undefined,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setup() {
|
|
||||||
const nodeHelpers = useNodeHelpers();
|
|
||||||
const asyncLoadingError = ref(false);
|
|
||||||
const router = useRouter();
|
|
||||||
const workflowHelpers = useWorkflowHelpers({ router });
|
|
||||||
|
|
||||||
// This will catch errors in async components
|
const props = withDefaults(defineProps<Props>(), { path: '', hiddenIssuesInputs: () => [] });
|
||||||
onErrorCaptured((e, component) => {
|
const emit = defineEmits<{
|
||||||
if (
|
(event: 'activate'): void;
|
||||||
!['FixedCollectionParameter', 'CollectionParameter'].includes(
|
(event: 'valueChanged', value: IUpdateInformation): void;
|
||||||
component?.$options.name as string,
|
(event: 'parameterBlur', value: string): void;
|
||||||
)
|
}>();
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
asyncLoadingError.value = true;
|
|
||||||
console.error(e);
|
|
||||||
window?.Sentry?.captureException(e, {
|
|
||||||
tags: {
|
|
||||||
asyncLoadingError: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
// Don't propagate the error further
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
const nodeTypesStore = useNodeTypesStore();
|
||||||
nodeHelpers,
|
const ndvStore = useNDVStore();
|
||||||
asyncLoadingError,
|
|
||||||
workflowHelpers,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapStores(useNodeTypesStore, useNDVStore),
|
|
||||||
nodeTypeVersion(): number | null {
|
|
||||||
if (this.node) {
|
|
||||||
return this.node.typeVersion;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
nodeTypeName(): string {
|
|
||||||
if (this.node) {
|
|
||||||
return this.node.type;
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
},
|
|
||||||
nodeType(): INodeTypeDescription | null {
|
|
||||||
if (this.node) {
|
|
||||||
return this.nodeTypesStore.getNodeType(this.node.type, this.node.typeVersion);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
filteredParameters(): INodeProperties[] {
|
|
||||||
return this.parameters.filter((parameter: INodeProperties) =>
|
|
||||||
this.displayNodeParameter(parameter),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
filteredParameterNames(): string[] {
|
|
||||||
return this.filteredParameters.map((parameter) => parameter.name);
|
|
||||||
},
|
|
||||||
node(): INodeUi | null {
|
|
||||||
return this.ndvStore.activeNode;
|
|
||||||
},
|
|
||||||
nodeAuthFields(): INodeProperties[] {
|
|
||||||
return getNodeAuthFields(this.nodeType);
|
|
||||||
},
|
|
||||||
credentialsParameterIndex(): number {
|
|
||||||
return this.filteredParameters.findIndex((parameter) => parameter.type === 'credentials');
|
|
||||||
},
|
|
||||||
indexToShowSlotAt(): number {
|
|
||||||
const credentialsParameterIndex = this.credentialsParameterIndex;
|
|
||||||
|
|
||||||
if (credentialsParameterIndex !== -1) {
|
const nodeHelpers = useNodeHelpers();
|
||||||
return credentialsParameterIndex;
|
const asyncLoadingError = ref(false);
|
||||||
}
|
const router = useRouter();
|
||||||
|
const workflowHelpers = useWorkflowHelpers({ router });
|
||||||
|
|
||||||
let index = 0;
|
onErrorCaptured((e, component) => {
|
||||||
// For nodes that use old credentials UI, keep credentials below authentication field in NDV
|
if (
|
||||||
// otherwise credentials will use auth filed position since the auth field is moved to credentials modal
|
!['FixedCollectionParameter', 'CollectionParameter'].includes(
|
||||||
const fieldOffset = KEEP_AUTH_IN_NDV_FOR_NODES.includes(this.nodeType?.name || '') ? 1 : 0;
|
component?.$options.name as string,
|
||||||
const credentialsDependencies = this.getCredentialsDependencies();
|
)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
asyncLoadingError.value = true;
|
||||||
|
console.error(e);
|
||||||
|
window?.Sentry?.captureException(e, {
|
||||||
|
tags: {
|
||||||
|
asyncLoadingError: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// Don't propagate the error further
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
this.filteredParameters.forEach((prop, propIndex) => {
|
const nodeType = computed(() => {
|
||||||
if (credentialsDependencies.has(prop.name)) {
|
if (node.value) {
|
||||||
index = propIndex + fieldOffset;
|
return nodeTypesStore.getNodeType(node.value.type, node.value.typeVersion);
|
||||||
}
|
}
|
||||||
});
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
return Math.min(index, this.filteredParameters.length - 1);
|
const filteredParameters = computed(() => {
|
||||||
},
|
return props.parameters.filter((parameter: INodeProperties) => displayNodeParameter(parameter));
|
||||||
mainNodeAuthField(): INodeProperties | null {
|
});
|
||||||
return getMainAuthField(this.nodeType || null);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
filteredParameterNames(newValue, oldValue) {
|
|
||||||
if (newValue === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// After a parameter does not get displayed anymore make sure that its value gets removed
|
|
||||||
// Is only needed for the edge-case when a parameter gets displayed depending on another field
|
|
||||||
// which contains an expression.
|
|
||||||
for (const parameter of oldValue) {
|
|
||||||
if (!newValue.includes(parameter)) {
|
|
||||||
const parameterData = {
|
|
||||||
name: `${this.path}.${parameter}`,
|
|
||||||
node: this.ndvStore.activeNode?.name || '',
|
|
||||||
value: undefined,
|
|
||||||
};
|
|
||||||
this.$emit('valueChanged', parameterData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
onParameterBlur(parameterName: string) {
|
|
||||||
this.$emit('parameterBlur', parameterName);
|
|
||||||
},
|
|
||||||
getCredentialsDependencies() {
|
|
||||||
const dependencies = new Set();
|
|
||||||
const nodeType = this.nodeTypesStore.getNodeType(
|
|
||||||
this.node?.type || '',
|
|
||||||
this.node?.typeVersion,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Get names of all fields that credentials rendering depends on (using displayOptions > show)
|
const filteredParameterNames = computed(() => {
|
||||||
if (nodeType?.credentials) {
|
return filteredParameters.value.map((parameter) => parameter.name);
|
||||||
for (const cred of nodeType.credentials) {
|
});
|
||||||
if (cred.displayOptions?.show) {
|
|
||||||
Object.keys(cred.displayOptions.show).forEach((fieldName) =>
|
|
||||||
dependencies.add(fieldName),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dependencies;
|
|
||||||
},
|
|
||||||
multipleValues(parameter: INodeProperties): boolean {
|
|
||||||
return this.getArgument('multipleValues', parameter) === true;
|
|
||||||
},
|
|
||||||
getArgument(
|
|
||||||
argumentName: string,
|
|
||||||
parameter: INodeProperties,
|
|
||||||
): string | string[] | number | boolean | undefined {
|
|
||||||
if (parameter.typeOptions === undefined) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parameter.typeOptions[argumentName] === undefined) {
|
const node = computed(() => ndvStore.activeNode);
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return parameter.typeOptions[argumentName];
|
const nodeAuthFields = computed(() => {
|
||||||
},
|
return getNodeAuthFields(nodeType.value);
|
||||||
getPath(parameterName: string): string {
|
});
|
||||||
return (this.path ? `${this.path}.` : '') + parameterName;
|
|
||||||
},
|
const credentialsParameterIndex = computed(() => {
|
||||||
deleteOption(optionName: string): void {
|
return filteredParameters.value.findIndex((parameter) => parameter.type === 'credentials');
|
||||||
|
});
|
||||||
|
|
||||||
|
const indexToShowSlotAt = computed(() => {
|
||||||
|
if (credentialsParameterIndex.value !== -1) {
|
||||||
|
return credentialsParameterIndex.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
let index = 0;
|
||||||
|
// For nodes that use old credentials UI, keep credentials below authentication field in NDV
|
||||||
|
// otherwise credentials will use auth filed position since the auth field is moved to credentials modal
|
||||||
|
const fieldOffset = KEEP_AUTH_IN_NDV_FOR_NODES.includes(nodeType.value?.name || '') ? 1 : 0;
|
||||||
|
const credentialsDependencies = getCredentialsDependencies();
|
||||||
|
|
||||||
|
filteredParameters.value.forEach((prop, propIndex) => {
|
||||||
|
if (credentialsDependencies.has(prop.name)) {
|
||||||
|
index = propIndex + fieldOffset;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return Math.min(index, filteredParameters.value.length - 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
const mainNodeAuthField = computed(() => {
|
||||||
|
return getMainAuthField(nodeType.value || null);
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(filteredParameterNames, (newValue, oldValue) => {
|
||||||
|
if (newValue === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// After a parameter does not get displayed anymore make sure that its value gets removed
|
||||||
|
// Is only needed for the edge-case when a parameter gets displayed depending on another field
|
||||||
|
// which contains an expression.
|
||||||
|
for (const parameter of oldValue) {
|
||||||
|
if (!newValue.includes(parameter)) {
|
||||||
const parameterData = {
|
const parameterData = {
|
||||||
name: this.getPath(optionName),
|
name: `${props.path}.${parameter}`,
|
||||||
|
node: ndvStore.activeNode?.name || '',
|
||||||
value: undefined,
|
value: undefined,
|
||||||
};
|
};
|
||||||
|
emit('valueChanged', parameterData);
|
||||||
// TODO: If there is only one option it should delete the whole one
|
}
|
||||||
|
}
|
||||||
this.$emit('valueChanged', parameterData);
|
|
||||||
},
|
|
||||||
|
|
||||||
mustHideDuringCustomApiCall(parameter: INodeProperties, nodeValues: INodeParameters): boolean {
|
|
||||||
if (parameter?.displayOptions?.hide) return true;
|
|
||||||
|
|
||||||
const MUST_REMAIN_VISIBLE = [
|
|
||||||
'authentication',
|
|
||||||
'resource',
|
|
||||||
'operation',
|
|
||||||
...Object.keys(nodeValues),
|
|
||||||
];
|
|
||||||
|
|
||||||
return !MUST_REMAIN_VISIBLE.includes(parameter.name);
|
|
||||||
},
|
|
||||||
displayNodeParameter(parameter: INodeProperties): boolean {
|
|
||||||
if (parameter.type === 'hidden') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
this.nodeHelpers.isCustomApiCallSelected(this.nodeValues) &&
|
|
||||||
this.mustHideDuringCustomApiCall(parameter, this.nodeValues)
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hide authentication related fields since it will now be part of credentials modal
|
|
||||||
if (
|
|
||||||
!KEEP_AUTH_IN_NDV_FOR_NODES.includes(this.node?.type || '') &&
|
|
||||||
this.mainNodeAuthField &&
|
|
||||||
(parameter.name === this.mainNodeAuthField?.name ||
|
|
||||||
this.shouldHideAuthRelatedParameter(parameter))
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parameter.displayOptions === undefined) {
|
|
||||||
// If it is not defined no need to do a proper check
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const nodeValues: INodeParameters = {};
|
|
||||||
let rawValues = this.nodeValues;
|
|
||||||
if (this.path) {
|
|
||||||
rawValues = get(this.nodeValues, this.path) as INodeParameters;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!rawValues) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Resolve expressions
|
|
||||||
const resolveKeys = Object.keys(rawValues);
|
|
||||||
let key: string;
|
|
||||||
let i = 0;
|
|
||||||
let parameterGotResolved = false;
|
|
||||||
do {
|
|
||||||
key = resolveKeys.shift() as string;
|
|
||||||
const value = rawValues[key];
|
|
||||||
if (typeof value === 'string' && value?.charAt(0) === '=') {
|
|
||||||
// Contains an expression that
|
|
||||||
if (
|
|
||||||
value.includes('$parameter') &&
|
|
||||||
resolveKeys.some((parameterName) => value.includes(parameterName))
|
|
||||||
) {
|
|
||||||
// Contains probably an expression of a missing parameter so skip
|
|
||||||
resolveKeys.push(key);
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
// Contains probably no expression with a missing parameter so resolve
|
|
||||||
try {
|
|
||||||
nodeValues[key] = this.workflowHelpers.resolveExpression(
|
|
||||||
value,
|
|
||||||
nodeValues,
|
|
||||||
) as NodeParameterValue;
|
|
||||||
} catch (e) {
|
|
||||||
// If expression is invalid ignore
|
|
||||||
nodeValues[key] = '';
|
|
||||||
}
|
|
||||||
parameterGotResolved = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Does not contain an expression, add directly
|
|
||||||
nodeValues[key] = rawValues[key];
|
|
||||||
}
|
|
||||||
// TODO: Think about how to calculate this best
|
|
||||||
if (i++ > 50) {
|
|
||||||
// Make sure we do not get caught
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} while (resolveKeys.length !== 0);
|
|
||||||
|
|
||||||
if (parameterGotResolved) {
|
|
||||||
if (this.path) {
|
|
||||||
rawValues = deepCopy(this.nodeValues);
|
|
||||||
set(rawValues, this.path, nodeValues);
|
|
||||||
return this.nodeHelpers.displayParameter(rawValues, parameter, this.path, this.node);
|
|
||||||
} else {
|
|
||||||
return this.nodeHelpers.displayParameter(nodeValues, parameter, '', this.node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.nodeHelpers.displayParameter(this.nodeValues, parameter, this.path, this.node);
|
|
||||||
},
|
|
||||||
valueChanged(parameterData: IUpdateInformation): void {
|
|
||||||
this.$emit('valueChanged', parameterData);
|
|
||||||
},
|
|
||||||
onNoticeAction(action: string) {
|
|
||||||
if (action === 'activate') {
|
|
||||||
this.$emit('activate');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Handles default node button parameter type actions
|
|
||||||
* @param parameter
|
|
||||||
*/
|
|
||||||
onButtonAction(parameter: INodeProperties) {
|
|
||||||
const action: string | undefined = parameter.typeOptions?.action;
|
|
||||||
|
|
||||||
switch (action) {
|
|
||||||
default:
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
isNodeAuthField(name: string): boolean {
|
|
||||||
return this.nodeAuthFields.find((field) => field.name === name) !== undefined;
|
|
||||||
},
|
|
||||||
shouldHideAuthRelatedParameter(parameter: INodeProperties): boolean {
|
|
||||||
// TODO: For now, hide all fields that are used in authentication fields displayOptions
|
|
||||||
// Ideally, we should check if any non-auth field depends on it before hiding it but
|
|
||||||
// since there is no such case, omitting it to avoid additional computation
|
|
||||||
return isAuthRelatedParameter(this.nodeAuthFields, parameter);
|
|
||||||
},
|
|
||||||
shouldShowOptions(parameter: INodeProperties): boolean {
|
|
||||||
return parameter.type !== 'resourceMapper';
|
|
||||||
},
|
|
||||||
getDependentParametersValues(parameter: INodeProperties): string | null {
|
|
||||||
const loadOptionsDependsOn = this.getArgument('loadOptionsDependsOn', parameter) as
|
|
||||||
| string[]
|
|
||||||
| undefined;
|
|
||||||
|
|
||||||
if (loadOptionsDependsOn === undefined) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the resolved parameter values of the current node
|
|
||||||
const currentNodeParameters = this.ndvStore.activeNode?.parameters;
|
|
||||||
try {
|
|
||||||
const resolvedNodeParameters = this.workflowHelpers.resolveParameter(currentNodeParameters);
|
|
||||||
|
|
||||||
const returnValues: string[] = [];
|
|
||||||
for (const parameterPath of loadOptionsDependsOn) {
|
|
||||||
returnValues.push(get(resolvedNodeParameters, parameterPath) as string);
|
|
||||||
}
|
|
||||||
|
|
||||||
return returnValues.join('|');
|
|
||||||
} catch (error) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getParameterValue<T extends NodeParameterValueType = NodeParameterValueType>(name: string): T {
|
|
||||||
return this.nodeHelpers.getParameterValue(this.nodeValues, name, this.path) as T;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function onParameterBlur(parameterName: string) {
|
||||||
|
emit('parameterBlur', parameterName);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCredentialsDependencies() {
|
||||||
|
const dependencies = new Set();
|
||||||
|
|
||||||
|
// Get names of all fields that credentials rendering depends on (using displayOptions > show)
|
||||||
|
if (nodeType.value?.credentials) {
|
||||||
|
for (const cred of nodeType.value.credentials) {
|
||||||
|
if (cred.displayOptions?.show) {
|
||||||
|
Object.keys(cred.displayOptions.show).forEach((fieldName) => dependencies.add(fieldName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dependencies;
|
||||||
|
}
|
||||||
|
|
||||||
|
function multipleValues(parameter: INodeProperties): boolean {
|
||||||
|
return getArgument('multipleValues', parameter) === true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getArgument(
|
||||||
|
argumentName: string,
|
||||||
|
parameter: INodeProperties,
|
||||||
|
): string | string[] | number | boolean | undefined {
|
||||||
|
if (parameter.typeOptions === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parameter.typeOptions[argumentName] === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parameter.typeOptions[argumentName];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPath(parameterName: string): string {
|
||||||
|
return (props.path ? `${props.path}.` : '') + parameterName;
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteOption(optionName: string): void {
|
||||||
|
const parameterData = {
|
||||||
|
name: getPath(optionName),
|
||||||
|
value: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: If there is only one option it should delete the whole one
|
||||||
|
|
||||||
|
emit('valueChanged', parameterData);
|
||||||
|
}
|
||||||
|
|
||||||
|
function mustHideDuringCustomApiCall(
|
||||||
|
parameter: INodeProperties,
|
||||||
|
nodeValues: INodeParameters,
|
||||||
|
): boolean {
|
||||||
|
if (parameter?.displayOptions?.hide) return true;
|
||||||
|
|
||||||
|
const MUST_REMAIN_VISIBLE = [
|
||||||
|
'authentication',
|
||||||
|
'resource',
|
||||||
|
'operation',
|
||||||
|
...Object.keys(nodeValues),
|
||||||
|
];
|
||||||
|
|
||||||
|
return !MUST_REMAIN_VISIBLE.includes(parameter.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayNodeParameter(parameter: INodeProperties): boolean {
|
||||||
|
if (parameter.type === 'hidden') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
nodeHelpers.isCustomApiCallSelected(props.nodeValues) &&
|
||||||
|
mustHideDuringCustomApiCall(parameter, props.nodeValues)
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide authentication related fields since it will now be part of credentials modal
|
||||||
|
if (
|
||||||
|
!KEEP_AUTH_IN_NDV_FOR_NODES.includes(node.value?.type || '') &&
|
||||||
|
mainNodeAuthField.value &&
|
||||||
|
(parameter.name === mainNodeAuthField.value?.name || shouldHideAuthRelatedParameter(parameter))
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parameter.displayOptions === undefined) {
|
||||||
|
// If it is not defined no need to do a proper check
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodeValues: INodeParameters = {};
|
||||||
|
let rawValues = props.nodeValues;
|
||||||
|
if (props.path) {
|
||||||
|
rawValues = get(props.nodeValues, props.path) as INodeParameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!rawValues) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Resolve expressions
|
||||||
|
const resolveKeys = Object.keys(rawValues);
|
||||||
|
let key: string;
|
||||||
|
let i = 0;
|
||||||
|
let parameterGotResolved = false;
|
||||||
|
do {
|
||||||
|
key = resolveKeys.shift() as string;
|
||||||
|
const value = rawValues[key];
|
||||||
|
if (typeof value === 'string' && value?.charAt(0) === '=') {
|
||||||
|
// Contains an expression that
|
||||||
|
if (
|
||||||
|
value.includes('$parameter') &&
|
||||||
|
resolveKeys.some((parameterName) => value.includes(parameterName))
|
||||||
|
) {
|
||||||
|
// Contains probably an expression of a missing parameter so skip
|
||||||
|
resolveKeys.push(key);
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
// Contains probably no expression with a missing parameter so resolve
|
||||||
|
try {
|
||||||
|
nodeValues[key] = workflowHelpers.resolveExpression(
|
||||||
|
value,
|
||||||
|
nodeValues,
|
||||||
|
) as NodeParameterValue;
|
||||||
|
} catch (e) {
|
||||||
|
// If expression is invalid ignore
|
||||||
|
nodeValues[key] = '';
|
||||||
|
}
|
||||||
|
parameterGotResolved = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Does not contain an expression, add directly
|
||||||
|
nodeValues[key] = rawValues[key];
|
||||||
|
}
|
||||||
|
// TODO: Think about how to calculate this best
|
||||||
|
if (i++ > 50) {
|
||||||
|
// Make sure we do not get caught
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} while (resolveKeys.length !== 0);
|
||||||
|
|
||||||
|
if (parameterGotResolved) {
|
||||||
|
if (props.path) {
|
||||||
|
rawValues = deepCopy(props.nodeValues);
|
||||||
|
set(rawValues, props.path, nodeValues);
|
||||||
|
return nodeHelpers.displayParameter(rawValues, parameter, props.path, node.value);
|
||||||
|
} else {
|
||||||
|
return nodeHelpers.displayParameter(nodeValues, parameter, '', node.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nodeHelpers.displayParameter(props.nodeValues, parameter, props.path, node.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function valueChanged(parameterData: IUpdateInformation): void {
|
||||||
|
emit('valueChanged', parameterData);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onNoticeAction(action: string) {
|
||||||
|
if (action === 'activate') {
|
||||||
|
emit('activate');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles default node button parameter type actions
|
||||||
|
* @param parameter
|
||||||
|
*/
|
||||||
|
function onButtonAction(parameter: INodeProperties) {
|
||||||
|
const action: string | undefined = parameter.typeOptions?.action;
|
||||||
|
|
||||||
|
switch (action) {
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isNodeAuthField(name: string): boolean {
|
||||||
|
return nodeAuthFields.value.find((field) => field.name === name) !== undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function shouldHideAuthRelatedParameter(parameter: INodeProperties): boolean {
|
||||||
|
// TODO: For now, hide all fields that are used in authentication fields displayOptions
|
||||||
|
// Ideally, we should check if any non-auth field depends on it before hiding it but
|
||||||
|
// since there is no such case, omitting it to avoid additional computation
|
||||||
|
return isAuthRelatedParameter(nodeAuthFields.value, parameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
function shouldShowOptions(parameter: INodeProperties): boolean {
|
||||||
|
return parameter.type !== 'resourceMapper';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDependentParametersValues(parameter: INodeProperties): string | null {
|
||||||
|
const loadOptionsDependsOn = getArgument('loadOptionsDependsOn', parameter) as
|
||||||
|
| string[]
|
||||||
|
| undefined;
|
||||||
|
|
||||||
|
if (loadOptionsDependsOn === undefined) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the resolved parameter values of the current node
|
||||||
|
const currentNodeParameters = ndvStore.activeNode?.parameters;
|
||||||
|
try {
|
||||||
|
const resolvedNodeParameters = workflowHelpers.resolveParameter(currentNodeParameters);
|
||||||
|
|
||||||
|
const returnValues: string[] = [];
|
||||||
|
for (const parameterPath of loadOptionsDependsOn) {
|
||||||
|
returnValues.push(get(resolvedNodeParameters, parameterPath) as string);
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValues.join('|');
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getParameterValue<T extends NodeParameterValueType = NodeParameterValueType>(
|
||||||
|
name: string,
|
||||||
|
): T {
|
||||||
|
return nodeHelpers.getParameterValue(props.nodeValues, name, props.path) as T;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|||||||
@@ -45,234 +45,194 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { mapStores } from 'pinia';
|
import type { IUpdateInformation, InputSize } from '@/Interface';
|
||||||
import type { PropType } from 'vue';
|
|
||||||
import { defineComponent } from 'vue';
|
|
||||||
|
|
||||||
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 {
|
||||||
|
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 { useExternalSecretsStore } from '@/stores/externalSecrets.ee.store';
|
||||||
import { useNDVStore } from '@/stores/ndv.store';
|
import { useNDVStore } from '@/stores/ndv.store';
|
||||||
|
import { stringifyExpressionResult } from '@/utils/expressions';
|
||||||
import { isValueExpression, parseResourceMapperFieldName } from '@/utils/nodeTypesUtils';
|
import { isValueExpression, parseResourceMapperFieldName } from '@/utils/nodeTypesUtils';
|
||||||
import type {
|
|
||||||
IDataObject,
|
|
||||||
INodeProperties,
|
|
||||||
INodePropertyMode,
|
|
||||||
IParameterLabel,
|
|
||||||
NodeParameterValueType,
|
|
||||||
Result,
|
|
||||||
} from 'n8n-workflow';
|
|
||||||
import { isResourceLocatorValue } from 'n8n-workflow';
|
|
||||||
|
|
||||||
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 { computed } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { useWorkflowHelpers } from '@/composables/useWorkflowHelpers';
|
|
||||||
import { stringifyExpressionResult } from '@/utils/expressions';
|
|
||||||
|
|
||||||
export default defineComponent({
|
type Props = {
|
||||||
name: 'ParameterInputWrapper',
|
parameter: INodeProperties;
|
||||||
components: {
|
path: string;
|
||||||
ParameterInput,
|
modelValue: NodeParameterValueType;
|
||||||
InputHint,
|
additionalExpressionData?: IDataObject;
|
||||||
},
|
rows?: number;
|
||||||
props: {
|
isReadOnly?: boolean;
|
||||||
additionalExpressionData: {
|
isAssignment?: boolean;
|
||||||
type: Object as PropType<IDataObject>,
|
droppable?: boolean;
|
||||||
default: () => ({}),
|
activeDrop?: boolean;
|
||||||
},
|
forceShowExpression?: boolean;
|
||||||
isReadOnly: {
|
hint?: string;
|
||||||
type: Boolean,
|
hideHint?: boolean;
|
||||||
},
|
inputSize?: InputSize;
|
||||||
rows: {
|
hideIssues?: boolean;
|
||||||
type: Number,
|
documentationUrl?: string;
|
||||||
default: 5,
|
errorHighlight?: boolean;
|
||||||
},
|
isForCredential?: boolean;
|
||||||
isAssignment: {
|
eventSource?: string;
|
||||||
type: Boolean,
|
label?: IParameterLabel;
|
||||||
},
|
eventBus?: EventBus;
|
||||||
parameter: {
|
};
|
||||||
type: Object as PropType<INodeProperties>,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
path: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
modelValue: {
|
|
||||||
type: [String, Number, Boolean, Array, Object] as PropType<NodeParameterValueType>,
|
|
||||||
},
|
|
||||||
droppable: {
|
|
||||||
type: Boolean,
|
|
||||||
},
|
|
||||||
activeDrop: {
|
|
||||||
type: Boolean,
|
|
||||||
},
|
|
||||||
forceShowExpression: {
|
|
||||||
type: Boolean,
|
|
||||||
},
|
|
||||||
hint: {
|
|
||||||
type: String,
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
hideHint: {
|
|
||||||
type: Boolean,
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
inputSize: {
|
|
||||||
type: String as PropType<InputSize>,
|
|
||||||
},
|
|
||||||
hideIssues: {
|
|
||||||
type: Boolean,
|
|
||||||
},
|
|
||||||
documentationUrl: {
|
|
||||||
type: String as PropType<string | undefined>,
|
|
||||||
},
|
|
||||||
errorHighlight: {
|
|
||||||
type: Boolean,
|
|
||||||
},
|
|
||||||
isForCredential: {
|
|
||||||
type: Boolean,
|
|
||||||
},
|
|
||||||
eventSource: {
|
|
||||||
type: String,
|
|
||||||
},
|
|
||||||
label: {
|
|
||||||
type: Object as PropType<IParameterLabel>,
|
|
||||||
default: () => ({
|
|
||||||
size: 'small',
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
eventBus: {
|
|
||||||
type: Object as PropType<EventBus>,
|
|
||||||
default: () => createEventBus(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setup() {
|
|
||||||
const router = useRouter();
|
|
||||||
const workflowHelpers = useWorkflowHelpers({ router });
|
|
||||||
|
|
||||||
return {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
workflowHelpers,
|
additionalExpressionData: () => ({}),
|
||||||
};
|
rows: 5,
|
||||||
},
|
label: () => ({ size: 'small' }),
|
||||||
computed: {
|
eventBus: () => createEventBus(),
|
||||||
...mapStores(useNDVStore, useExternalSecretsStore, useEnvironmentsStore),
|
|
||||||
isValueExpression() {
|
|
||||||
return isValueExpression(this.parameter, this.modelValue);
|
|
||||||
},
|
|
||||||
activeNode(): INodeUi | null {
|
|
||||||
return this.ndvStore.activeNode;
|
|
||||||
},
|
|
||||||
selectedRLMode(): INodePropertyMode | undefined {
|
|
||||||
if (
|
|
||||||
typeof this.modelValue !== 'object' ||
|
|
||||||
this.parameter.type !== 'resourceLocator' ||
|
|
||||||
!isResourceLocatorValue(this.modelValue)
|
|
||||||
) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const mode = this.modelValue.mode;
|
|
||||||
if (mode) {
|
|
||||||
return this.parameter.modes?.find((m: INodePropertyMode) => m.name === mode);
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
},
|
|
||||||
parameterHint(): string | undefined {
|
|
||||||
if (this.isValueExpression) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
if (this.selectedRLMode?.hint) {
|
|
||||||
return this.selectedRLMode.hint;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.hint;
|
|
||||||
},
|
|
||||||
targetItem(): TargetItem | null {
|
|
||||||
return this.ndvStore.hoveringItem;
|
|
||||||
},
|
|
||||||
isInputParentOfActiveNode(): boolean {
|
|
||||||
return this.ndvStore.isInputParentOfActiveNode;
|
|
||||||
},
|
|
||||||
evaluatedExpression(): Result<unknown, Error> {
|
|
||||||
const value = isResourceLocatorValue(this.modelValue)
|
|
||||||
? this.modelValue.value
|
|
||||||
: this.modelValue;
|
|
||||||
|
|
||||||
if (!this.activeNode || !this.isValueExpression || typeof value !== 'string') {
|
|
||||||
return { ok: false, error: new Error() };
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
let opts: Parameters<typeof this.workflowHelpers.resolveExpression>[2] = {
|
|
||||||
isForCredential: this.isForCredential,
|
|
||||||
};
|
|
||||||
if (this.ndvStore.isInputParentOfActiveNode) {
|
|
||||||
opts = {
|
|
||||||
...opts,
|
|
||||||
targetItem: this.targetItem ?? undefined,
|
|
||||||
inputNodeName: this.ndvStore.ndvInputNodeName,
|
|
||||||
inputRunIndex: this.ndvStore.ndvInputRunIndex,
|
|
||||||
inputBranchIndex: this.ndvStore.ndvInputBranchIndex,
|
|
||||||
additionalKeys: this.resolvedAdditionalExpressionData,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return { ok: true, result: this.workflowHelpers.resolveExpression(value, undefined, opts) };
|
|
||||||
} catch (error) {
|
|
||||||
return { ok: false, error };
|
|
||||||
}
|
|
||||||
},
|
|
||||||
evaluatedExpressionValue(): unknown {
|
|
||||||
const evaluated = this.evaluatedExpression;
|
|
||||||
return evaluated.ok ? evaluated.result : null;
|
|
||||||
},
|
|
||||||
evaluatedExpressionString(): string | null {
|
|
||||||
return stringifyExpressionResult(this.evaluatedExpression);
|
|
||||||
},
|
|
||||||
expressionOutput(): string | null {
|
|
||||||
if (this.isValueExpression && this.evaluatedExpressionString) {
|
|
||||||
return this.evaluatedExpressionString;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
resolvedAdditionalExpressionData() {
|
|
||||||
return {
|
|
||||||
$vars: this.environmentsStore.variablesAsObject,
|
|
||||||
...(this.externalSecretsStore.isEnterpriseExternalSecretsEnabled && this.isForCredential
|
|
||||||
? { $secrets: this.externalSecretsStore.secretsAsObject }
|
|
||||||
: {}),
|
|
||||||
...this.additionalExpressionData,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
parsedParameterName() {
|
|
||||||
return parseResourceMapperFieldName(this.parameter?.name ?? '');
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
onFocus() {
|
|
||||||
this.$emit('focus');
|
|
||||||
},
|
|
||||||
onBlur() {
|
|
||||||
this.$emit('blur');
|
|
||||||
},
|
|
||||||
onDrop(data: string) {
|
|
||||||
this.$emit('drop', data);
|
|
||||||
},
|
|
||||||
onValueChanged(parameterData: IUpdateInformation) {
|
|
||||||
this.$emit('update', parameterData);
|
|
||||||
},
|
|
||||||
onTextInput(parameterData: IUpdateInformation) {
|
|
||||||
this.$emit('textInput', parameterData);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(event: 'focus'): void;
|
||||||
|
(event: 'blur'): void;
|
||||||
|
(event: 'drop', value: string): void;
|
||||||
|
(event: 'update', value: IUpdateInformation): void;
|
||||||
|
(event: 'textInput', value: IUpdateInformation): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
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.hoveringItem);
|
||||||
|
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ok: true, result: workflowHelpers.resolveExpression(value, undefined, opts) };
|
||||||
|
} catch (error) {
|
||||||
|
return { ok: false, error };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const evaluatedExpressionValue = computed(() => {
|
||||||
|
const evaluated = evaluatedExpression.value;
|
||||||
|
return evaluated.ok ? evaluated.result : null;
|
||||||
|
});
|
||||||
|
|
||||||
|
const evaluatedExpressionString = computed(() => {
|
||||||
|
return stringifyExpressionResult(evaluatedExpression.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
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>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
|||||||
Reference in New Issue
Block a user