mirror of
https://github.com/n8n-io/n8n.git
synced 2025-01-13 05:47:31 -08:00
refactor(editor): Refactor nodeHelpers
mixin to composable (#7810)
- Convert `nodeHelpers` mixin into composable and fix types - Replace usage of the mixin with the new composable - Add missing store imports in components that were dependent on opaque imports from nodeHelpers mixin - Refactor the `CollectionParameter` component to the modern script setup syntax Github issue / Community forum post (link here to close automatically): --------- Signed-off-by: Oleg Ivaniv <me@olegivaniv.com>
This commit is contained in:
parent
e8a493f718
commit
35fbc37c8e
|
@ -25,16 +25,22 @@ import type { IBinaryData, IRunData } from 'n8n-workflow';
|
||||||
|
|
||||||
import BinaryDataDisplayEmbed from '@/components/BinaryDataDisplayEmbed.vue';
|
import BinaryDataDisplayEmbed from '@/components/BinaryDataDisplayEmbed.vue';
|
||||||
|
|
||||||
import { nodeHelpers } from '@/mixins/nodeHelpers';
|
|
||||||
|
|
||||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||||
|
import { useNodeHelpers } from '@/composables/useNodeHelpers';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'BinaryDataDisplay',
|
name: 'BinaryDataDisplay',
|
||||||
mixins: [nodeHelpers],
|
|
||||||
components: {
|
components: {
|
||||||
BinaryDataDisplayEmbed,
|
BinaryDataDisplayEmbed,
|
||||||
},
|
},
|
||||||
|
setup() {
|
||||||
|
const nodeHelpers = useNodeHelpers();
|
||||||
|
|
||||||
|
return {
|
||||||
|
nodeHelpers,
|
||||||
|
};
|
||||||
|
},
|
||||||
props: [
|
props: [
|
||||||
'displayData', // IBinaryData
|
'displayData', // IBinaryData
|
||||||
'windowVisible', // boolean
|
'windowVisible', // boolean
|
||||||
|
@ -42,7 +48,7 @@ export default defineComponent({
|
||||||
computed: {
|
computed: {
|
||||||
...mapStores(useWorkflowsStore),
|
...mapStores(useWorkflowsStore),
|
||||||
binaryData(): IBinaryData | null {
|
binaryData(): IBinaryData | null {
|
||||||
const binaryData = this.getBinaryData(
|
const binaryData = this.nodeHelpers.getBinaryData(
|
||||||
this.workflowRunData,
|
this.workflowRunData,
|
||||||
this.displayData.node,
|
this.displayData.node,
|
||||||
this.displayData.runIndex,
|
this.displayData.runIndex,
|
||||||
|
|
|
@ -70,6 +70,7 @@ import { completerExtension } from './completer';
|
||||||
import { codeNodeEditorTheme } from './theme';
|
import { codeNodeEditorTheme } from './theme';
|
||||||
import AskAI from './AskAI/AskAI.vue';
|
import AskAI from './AskAI/AskAI.vue';
|
||||||
import { useMessage } from '@/composables/useMessage';
|
import { useMessage } from '@/composables/useMessage';
|
||||||
|
import { useSettingsStore } from '@/stores/settings.store';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'code-node-editor',
|
name: 'code-node-editor',
|
||||||
|
@ -156,7 +157,7 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapStores(useRootStore, usePostHog),
|
...mapStores(useRootStore, usePostHog, useSettingsStore),
|
||||||
aiEnabled(): boolean {
|
aiEnabled(): boolean {
|
||||||
const isAiExperimentEnabled = [ASK_AI_EXPERIMENT.gpt3, ASK_AI_EXPERIMENT.gpt4].includes(
|
const isAiExperimentEnabled = [ASK_AI_EXPERIMENT.gpt3, ASK_AI_EXPERIMENT.gpt4].includes(
|
||||||
(this.posthogStore.getVariant(ASK_AI_EXPERIMENT.name) ?? '') as string,
|
(this.posthogStore.getVariant(ASK_AI_EXPERIMENT.name) ?? '') as string,
|
||||||
|
|
|
@ -19,10 +19,10 @@
|
||||||
|
|
||||||
<div v-if="parameterOptions.length > 0 && !isReadOnly" class="param-options">
|
<div v-if="parameterOptions.length > 0 && !isReadOnly" class="param-options">
|
||||||
<n8n-button
|
<n8n-button
|
||||||
v-if="parameter.options.length === 1"
|
v-if="(parameter.options ?? []).length === 1"
|
||||||
type="tertiary"
|
type="tertiary"
|
||||||
block
|
block
|
||||||
@click="optionSelected(parameter.options[0].name)"
|
@click="optionSelected((parameter.options ?? [])[0].name)"
|
||||||
:label="getPlaceholderText"
|
:label="getPlaceholderText"
|
||||||
/>
|
/>
|
||||||
<div v-else class="add-option">
|
<div v-else class="add-option">
|
||||||
|
@ -36,7 +36,11 @@
|
||||||
<n8n-option
|
<n8n-option
|
||||||
v-for="item in parameterOptions"
|
v-for="item in parameterOptions"
|
||||||
:key="item.name"
|
:key="item.name"
|
||||||
:label="$locale.nodeText().collectionOptionDisplayName(parameter, item, path)"
|
:label="
|
||||||
|
isNodePropertyCollection(item)
|
||||||
|
? i18n.nodeText().collectionOptionDisplayName(parameter, item, path)
|
||||||
|
: item.name
|
||||||
|
"
|
||||||
:value="item.name"
|
:value="item.name"
|
||||||
>
|
>
|
||||||
</n8n-option>
|
</n8n-option>
|
||||||
|
@ -47,136 +51,136 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineAsyncComponent, defineComponent } from 'vue';
|
import { ref, computed, defineAsyncComponent } from 'vue';
|
||||||
import { mapStores } from 'pinia';
|
import type { IUpdateInformation } from '@/Interface';
|
||||||
import type { INodeUi, IUpdateInformation } from '@/Interface';
|
|
||||||
|
|
||||||
import type { INodeProperties, INodePropertyOptions } from 'n8n-workflow';
|
import type {
|
||||||
|
INodeParameters,
|
||||||
|
INodeProperties,
|
||||||
|
INodePropertyCollection,
|
||||||
|
INodePropertyOptions,
|
||||||
|
} from 'n8n-workflow';
|
||||||
import { deepCopy } from 'n8n-workflow';
|
import { deepCopy } from 'n8n-workflow';
|
||||||
|
|
||||||
import { nodeHelpers } from '@/mixins/nodeHelpers';
|
|
||||||
|
|
||||||
import { get } from 'lodash-es';
|
import { get } from 'lodash-es';
|
||||||
|
|
||||||
import { useNDVStore } from '@/stores/ndv.store';
|
import { useNDVStore } from '@/stores/ndv.store';
|
||||||
|
import { useNodeHelpers } from '@/composables/useNodeHelpers';
|
||||||
|
import { useI18n } from '@/composables/useI18n';
|
||||||
const ParameterInputList = defineAsyncComponent(async () => import('./ParameterInputList.vue'));
|
const ParameterInputList = defineAsyncComponent(async () => import('./ParameterInputList.vue'));
|
||||||
|
|
||||||
export default defineComponent({
|
const selectedOption = ref<string | undefined>(undefined);
|
||||||
name: 'CollectionParameter',
|
export interface Props {
|
||||||
mixins: [nodeHelpers],
|
hideDelete?: boolean;
|
||||||
props: [
|
nodeValues: INodeParameters;
|
||||||
'hideDelete', // boolean
|
parameter: INodeProperties;
|
||||||
'nodeValues', // NodeParameters
|
path: string;
|
||||||
'parameter', // INodeProperties
|
values: INodeProperties;
|
||||||
'path', // string
|
isReadOnly?: boolean;
|
||||||
'values', // NodeParameters
|
|
||||||
'isReadOnly', // boolean
|
|
||||||
],
|
|
||||||
components: {
|
|
||||||
ParameterInputList,
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
selectedOption: undefined,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapStores(useNDVStore),
|
|
||||||
getPlaceholderText(): string {
|
|
||||||
const placeholder = this.$locale.nodeText().placeholder(this.parameter, this.path);
|
|
||||||
return placeholder ? placeholder : this.$locale.baseText('collectionParameter.choose');
|
|
||||||
},
|
|
||||||
getProperties(): INodeProperties[] {
|
|
||||||
const returnProperties = [];
|
|
||||||
let tempProperties;
|
|
||||||
for (const name of this.propertyNames) {
|
|
||||||
tempProperties = this.getOptionProperties(name);
|
|
||||||
if (tempProperties !== undefined) {
|
|
||||||
returnProperties.push(...tempProperties);
|
|
||||||
}
|
}
|
||||||
}
|
const emit = defineEmits<{
|
||||||
return returnProperties;
|
(event: 'valueChanged', value: IUpdateInformation): void;
|
||||||
},
|
}>();
|
||||||
// Returns all the options which should be displayed
|
const props = defineProps<Props>();
|
||||||
filteredOptions(): Array<INodePropertyOptions | INodeProperties> {
|
const ndvStore = useNDVStore();
|
||||||
return (this.parameter.options as Array<INodePropertyOptions | INodeProperties>).filter(
|
const i18n = useI18n();
|
||||||
(option) => {
|
const nodeHelpers = useNodeHelpers();
|
||||||
return this.displayNodeParameter(option as INodeProperties);
|
|
||||||
},
|
const getPlaceholderText = computed(() => {
|
||||||
|
return (
|
||||||
|
i18n.nodeText().placeholder(props.parameter, props.path) ??
|
||||||
|
i18n.baseText('collectionParameter.choose')
|
||||||
);
|
);
|
||||||
},
|
|
||||||
node(): INodeUi | null {
|
|
||||||
return this.ndvStore.activeNode;
|
|
||||||
},
|
|
||||||
// Returns all the options which did not get added already
|
|
||||||
parameterOptions(): Array<INodePropertyOptions | INodeProperties> {
|
|
||||||
return this.filteredOptions.filter((option) => {
|
|
||||||
return !this.propertyNames.includes(option.name);
|
|
||||||
});
|
});
|
||||||
},
|
function isNodePropertyCollection(
|
||||||
propertyNames(): string[] {
|
object: INodePropertyOptions | INodeProperties | INodePropertyCollection,
|
||||||
if (this.values) {
|
): object is INodePropertyCollection {
|
||||||
return Object.keys(this.values);
|
return 'values' in object;
|
||||||
}
|
|
||||||
return [];
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
getArgument(argumentName: string): string | number | boolean | undefined {
|
|
||||||
if (this.parameter.typeOptions === undefined) {
|
|
||||||
return undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.parameter.typeOptions[argumentName] === undefined) {
|
function displayNodeParameter(parameter: INodeProperties) {
|
||||||
return undefined;
|
if (parameter.displayOptions === undefined) {
|
||||||
|
// If it is not defined no need to do a proper check
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return nodeHelpers.displayParameter(props.nodeValues, parameter, props.path, ndvStore.activeNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.parameter.typeOptions[argumentName];
|
function getOptionProperties(optionName: string) {
|
||||||
},
|
const properties = [];
|
||||||
getOptionProperties(optionName: string): INodeProperties[] {
|
for (const option of props.parameter.options ?? []) {
|
||||||
const properties: INodeProperties[] = [];
|
|
||||||
for (const option of this.parameter.options) {
|
|
||||||
if (option.name === optionName) {
|
if (option.name === optionName) {
|
||||||
properties.push(option);
|
properties.push(option);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return properties;
|
return properties;
|
||||||
},
|
|
||||||
displayNodeParameter(parameter: INodeProperties) {
|
|
||||||
if (parameter.displayOptions === undefined) {
|
|
||||||
// If it is not defined no need to do a proper check
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return this.displayParameter(this.nodeValues, parameter, this.path, this.node);
|
const propertyNames = computed<string[]>(() => {
|
||||||
|
if (props.values) {
|
||||||
|
return Object.keys(props.values);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
const getProperties = computed(() => {
|
||||||
|
const returnProperties = [];
|
||||||
|
let tempProperties;
|
||||||
|
for (const name of propertyNames.value) {
|
||||||
|
tempProperties = getOptionProperties(name);
|
||||||
|
if (tempProperties !== undefined) {
|
||||||
|
returnProperties.push(...tempProperties);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return returnProperties;
|
||||||
|
});
|
||||||
|
const filteredOptions = computed<Array<INodePropertyOptions | INodeProperties>>(() => {
|
||||||
|
return (props.parameter.options as Array<INodePropertyOptions | INodeProperties>).filter(
|
||||||
|
(option) => {
|
||||||
|
return displayNodeParameter(option as INodeProperties);
|
||||||
},
|
},
|
||||||
optionSelected(optionName: string) {
|
);
|
||||||
const options = this.getOptionProperties(optionName);
|
});
|
||||||
|
const parameterOptions = computed(() => {
|
||||||
|
return filteredOptions.value.filter((option) => {
|
||||||
|
return !propertyNames.value.includes(option.name);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function optionSelected(optionName: string) {
|
||||||
|
const options = getOptionProperties(optionName);
|
||||||
if (options.length === 0) {
|
if (options.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const option = options[0];
|
const option = options[0];
|
||||||
const name = `${this.path}.${option.name}`;
|
const name = `${props.path}.${option.name}`;
|
||||||
|
|
||||||
let parameterData;
|
let parameterData;
|
||||||
|
|
||||||
if (option.typeOptions !== undefined && option.typeOptions.multipleValues === true) {
|
if (
|
||||||
|
'typeOptions' in option &&
|
||||||
|
option.typeOptions !== undefined &&
|
||||||
|
option.typeOptions.multipleValues === true
|
||||||
|
) {
|
||||||
// Multiple values are allowed
|
// Multiple values are allowed
|
||||||
|
|
||||||
let newValue;
|
let newValue;
|
||||||
if (option.type === 'fixedCollection') {
|
if (option.type === 'fixedCollection') {
|
||||||
// The "fixedCollection" entries are different as they save values
|
// The "fixedCollection" entries are different as they save values
|
||||||
// in an object and then underneath there is an array. So initialize
|
// in an object and then underneath there is an array. So initialize
|
||||||
// them differently.
|
// them differently.
|
||||||
newValue = get(this.nodeValues, `${this.path}.${optionName}`, {});
|
const retrievedObjectValue = get(props.nodeValues, `${props.path}.${optionName}`, {});
|
||||||
|
newValue = retrievedObjectValue;
|
||||||
} else {
|
} else {
|
||||||
// Everything else saves them directly as an array.
|
// Everything else saves them directly as an array.
|
||||||
newValue = get(this.nodeValues, `${this.path}.${optionName}`, []);
|
const retrievedArrayValue = get(props.nodeValues, `${props.path}.${optionName}`, []) as Array<
|
||||||
|
typeof option.default
|
||||||
|
>;
|
||||||
|
if (Array.isArray(retrievedArrayValue)) {
|
||||||
|
newValue = retrievedArrayValue;
|
||||||
newValue.push(deepCopy(option.default));
|
newValue.push(deepCopy(option.default));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
parameterData = {
|
parameterData = {
|
||||||
name,
|
name,
|
||||||
|
@ -186,18 +190,16 @@ export default defineComponent({
|
||||||
// Add a new option
|
// Add a new option
|
||||||
parameterData = {
|
parameterData = {
|
||||||
name,
|
name,
|
||||||
value: deepCopy(option.default),
|
value: 'default' in option ? deepCopy(option.default) : null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$emit('valueChanged', parameterData);
|
emit('valueChanged', parameterData);
|
||||||
this.selectedOption = undefined;
|
selectedOption.value = undefined;
|
||||||
},
|
}
|
||||||
valueChanged(parameterData: IUpdateInformation) {
|
function valueChanged(parameterData: IUpdateInformation) {
|
||||||
this.$emit('valueChanged', parameterData);
|
emit('valueChanged', parameterData);
|
||||||
},
|
}
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|
|
@ -135,10 +135,9 @@ import type {
|
||||||
import { NodeHelpers } from 'n8n-workflow';
|
import { NodeHelpers } from 'n8n-workflow';
|
||||||
import CredentialIcon from '@/components/CredentialIcon.vue';
|
import CredentialIcon from '@/components/CredentialIcon.vue';
|
||||||
|
|
||||||
import { nodeHelpers } from '@/mixins/nodeHelpers';
|
|
||||||
import { useMessage } from '@/composables/useMessage';
|
|
||||||
import { useToast } from '@/composables/useToast';
|
import { useToast } from '@/composables/useToast';
|
||||||
|
import { useNodeHelpers } from '@/composables/useNodeHelpers';
|
||||||
|
import { useMessage } from '@/composables/useMessage';
|
||||||
import CredentialConfig from '@/components/CredentialEdit/CredentialConfig.vue';
|
import CredentialConfig from '@/components/CredentialEdit/CredentialConfig.vue';
|
||||||
import CredentialInfo from '@/components/CredentialEdit/CredentialInfo.vue';
|
import CredentialInfo from '@/components/CredentialEdit/CredentialInfo.vue';
|
||||||
import CredentialSharing from '@/components/CredentialEdit/CredentialSharing.ee.vue';
|
import CredentialSharing from '@/components/CredentialEdit/CredentialSharing.ee.vue';
|
||||||
|
@ -157,6 +156,8 @@ import { useUsersStore } from '@/stores/users.store';
|
||||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
import { useWorkflowsStore } from '@/stores/workflows.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 {
|
import {
|
||||||
getNodeAuthOptions,
|
getNodeAuthOptions,
|
||||||
getNodeCredentialForSelectedAuthType,
|
getNodeCredentialForSelectedAuthType,
|
||||||
|
@ -172,7 +173,6 @@ interface NodeAccessMap {
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'CredentialEdit',
|
name: 'CredentialEdit',
|
||||||
mixins: [nodeHelpers],
|
|
||||||
components: {
|
components: {
|
||||||
CredentialSharing,
|
CredentialSharing,
|
||||||
CredentialConfig,
|
CredentialConfig,
|
||||||
|
@ -197,10 +197,13 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
|
const nodeHelpers = useNodeHelpers();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
externalHooks: useExternalHooks(),
|
externalHooks: useExternalHooks(),
|
||||||
...useToast(),
|
...useToast(),
|
||||||
...useMessage(),
|
...useMessage(),
|
||||||
|
nodeHelpers,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
@ -296,6 +299,7 @@ export default defineComponent({
|
||||||
useUIStore,
|
useUIStore,
|
||||||
useUsersStore,
|
useUsersStore,
|
||||||
useWorkflowsStore,
|
useWorkflowsStore,
|
||||||
|
useNodeTypesStore,
|
||||||
),
|
),
|
||||||
activeNodeType(): INodeTypeDescription | null {
|
activeNodeType(): INodeTypeDescription | null {
|
||||||
const activeNode = this.ndvStore.activeNode;
|
const activeNode = this.ndvStore.activeNode;
|
||||||
|
@ -577,7 +581,12 @@ export default defineComponent({
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.displayParameter(this.credentialData as INodeParameters, parameter, '', null);
|
return this.nodeHelpers.displayParameter(
|
||||||
|
this.credentialData as INodeParameters,
|
||||||
|
parameter,
|
||||||
|
'',
|
||||||
|
null,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
getCredentialProperties(name: string): INodeProperties[] {
|
getCredentialProperties(name: string): INodeProperties[] {
|
||||||
const credentialTypeData = this.credentialsStore.getCredentialTypeByName(name);
|
const credentialTypeData = this.credentialsStore.getCredentialTypeByName(name);
|
||||||
|
@ -957,7 +966,7 @@ export default defineComponent({
|
||||||
|
|
||||||
// Now that the credentials changed check if any nodes use credentials
|
// Now that the credentials changed check if any nodes use credentials
|
||||||
// which have now a different name
|
// which have now a different name
|
||||||
this.updateNodesCredentialsIssues();
|
this.nodeHelpers.updateNodesCredentialsIssues();
|
||||||
|
|
||||||
return credential;
|
return credential;
|
||||||
},
|
},
|
||||||
|
@ -1004,7 +1013,7 @@ export default defineComponent({
|
||||||
|
|
||||||
this.isDeleting = false;
|
this.isDeleting = false;
|
||||||
// Now that the credentials were removed check if any nodes used them
|
// Now that the credentials were removed check if any nodes used them
|
||||||
this.updateNodesCredentialsIssues();
|
this.nodeHelpers.updateNodesCredentialsIssues();
|
||||||
this.credentialData = {};
|
this.credentialData = {};
|
||||||
|
|
||||||
this.showMessage({
|
this.showMessage({
|
||||||
|
|
|
@ -157,10 +157,8 @@ import {
|
||||||
WAIT_TIME_UNLIMITED,
|
WAIT_TIME_UNLIMITED,
|
||||||
} from '@/constants';
|
} from '@/constants';
|
||||||
import { nodeBase } from '@/mixins/nodeBase';
|
import { nodeBase } from '@/mixins/nodeBase';
|
||||||
import { nodeHelpers } from '@/mixins/nodeHelpers';
|
|
||||||
import { workflowHelpers } from '@/mixins/workflowHelpers';
|
import { workflowHelpers } from '@/mixins/workflowHelpers';
|
||||||
import { pinData } from '@/mixins/pinData';
|
import { pinData } from '@/mixins/pinData';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
ConnectionTypes,
|
ConnectionTypes,
|
||||||
IExecutionsSummary,
|
IExecutionsSummary,
|
||||||
|
@ -186,6 +184,7 @@ import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||||
import { EnableNodeToggleCommand } from '@/models/history';
|
import { EnableNodeToggleCommand } from '@/models/history';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
|
||||||
import { type ContextMenuTarget, useContextMenu } from '@/composables/useContextMenu';
|
import { type ContextMenuTarget, useContextMenu } from '@/composables/useContextMenu';
|
||||||
|
import { useNodeHelpers } from '@/composables/useNodeHelpers';
|
||||||
import { useExternalHooks } from '@/composables/useExternalHooks';
|
import { useExternalHooks } from '@/composables/useExternalHooks';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
|
@ -193,10 +192,11 @@ export default defineComponent({
|
||||||
setup() {
|
setup() {
|
||||||
const contextMenu = useContextMenu();
|
const contextMenu = useContextMenu();
|
||||||
const externalHooks = useExternalHooks();
|
const externalHooks = useExternalHooks();
|
||||||
|
const nodeHelpers = useNodeHelpers();
|
||||||
|
|
||||||
return { contextMenu, externalHooks };
|
return { contextMenu, externalHooks, nodeHelpers };
|
||||||
},
|
},
|
||||||
mixins: [nodeBase, nodeHelpers, workflowHelpers, pinData, debounceHelper],
|
mixins: [nodeBase, workflowHelpers, pinData, debounceHelper],
|
||||||
components: {
|
components: {
|
||||||
TitledList,
|
TitledList,
|
||||||
FontAwesomeIcon,
|
FontAwesomeIcon,
|
||||||
|
@ -631,7 +631,9 @@ export default defineComponent({
|
||||||
// and ends up bogging down the UI with big workflows, for example when pasting a workflow or even opening a node...
|
// and ends up bogging down the UI with big workflows, for example when pasting a workflow or even opening a node...
|
||||||
// so we only update it when necessary (when node is mounted and when it's opened and closed (isActive))
|
// so we only update it when necessary (when node is mounted and when it's opened and closed (isActive))
|
||||||
try {
|
try {
|
||||||
const nodeSubtitle = this.getNodeSubtitle(this.data, this.nodeType, this.workflow) || '';
|
const nodeSubtitle =
|
||||||
|
this.nodeHelpers.getNodeSubtitle(this.data, this.nodeType, this.getCurrentWorkflow()) ||
|
||||||
|
'';
|
||||||
|
|
||||||
this.nodeSubtitle = nodeSubtitle.includes(CUSTOM_API_CALL_KEY) ? '' : nodeSubtitle;
|
this.nodeSubtitle = nodeSubtitle.includes(CUSTOM_API_CALL_KEY) ? '' : nodeSubtitle;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -640,7 +642,7 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
disableNode() {
|
disableNode() {
|
||||||
if (this.data !== null) {
|
if (this.data !== null) {
|
||||||
this.disableNodes([this.data]);
|
this.nodeHelpers.disableNodes([this.data]);
|
||||||
this.historyStore.pushCommandToUndo(
|
this.historyStore.pushCommandToUndo(
|
||||||
new EnableNodeToggleCommand(
|
new EnableNodeToggleCommand(
|
||||||
this.data.name,
|
this.data.name,
|
||||||
|
|
|
@ -118,7 +118,7 @@ import type {
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
import { genericHelpers } from '@/mixins/genericHelpers';
|
import { genericHelpers } from '@/mixins/genericHelpers';
|
||||||
import { nodeHelpers } from '@/mixins/nodeHelpers';
|
import { useNodeHelpers } from '@/composables/useNodeHelpers';
|
||||||
import { useToast } from '@/composables/useToast';
|
import { useToast } from '@/composables/useToast';
|
||||||
|
|
||||||
import TitledList from '@/components/TitledList.vue';
|
import TitledList from '@/components/TitledList.vue';
|
||||||
|
@ -144,7 +144,7 @@ interface CredentialDropdownOption extends ICredentialsResponse {
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'NodeCredentials',
|
name: 'NodeCredentials',
|
||||||
mixins: [genericHelpers, nodeHelpers],
|
mixins: [genericHelpers],
|
||||||
props: {
|
props: {
|
||||||
readonly: {
|
readonly: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
@ -170,8 +170,11 @@ export default defineComponent({
|
||||||
TitledList,
|
TitledList,
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
|
const nodeHelpers = useNodeHelpers();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...useToast(),
|
...useToast(),
|
||||||
|
nodeHelpers,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
@ -291,8 +294,6 @@ export default defineComponent({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
credentialTypesNodeDescription(): INodeCredentialDescription[] {
|
credentialTypesNodeDescription(): INodeCredentialDescription[] {
|
||||||
const node = this.node;
|
|
||||||
|
|
||||||
const credType = this.credentialsStore.getCredentialTypeByName(this.overrideCredType);
|
const credType = this.credentialsStore.getCredentialTypeByName(this.overrideCredType);
|
||||||
|
|
||||||
if (credType) return [credType];
|
if (credType) return [credType];
|
||||||
|
@ -433,7 +434,7 @@ export default defineComponent({
|
||||||
this.$telemetry.track('User selected credential from node modal', {
|
this.$telemetry.track('User selected credential from node modal', {
|
||||||
credential_type: credentialType,
|
credential_type: credentialType,
|
||||||
node_type: this.node.type,
|
node_type: this.node.type,
|
||||||
...(this.hasProxyAuth(this.node) ? { is_service_specific: true } : {}),
|
...(this.nodeHelpers.hasProxyAuth(this.node) ? { is_service_specific: true } : {}),
|
||||||
workflow_id: this.workflowsStore.workflowId,
|
workflow_id: this.workflowsStore.workflowId,
|
||||||
credential_id: credentialId,
|
credential_id: credentialId,
|
||||||
});
|
});
|
||||||
|
@ -461,7 +462,7 @@ export default defineComponent({
|
||||||
invalid: oldCredentials,
|
invalid: oldCredentials,
|
||||||
type: selectedCredentialsType,
|
type: selectedCredentialsType,
|
||||||
});
|
});
|
||||||
this.updateNodesCredentialsIssues();
|
this.nodeHelpers.updateNodesCredentialsIssues();
|
||||||
this.showMessage({
|
this.showMessage({
|
||||||
title: this.$locale.baseText('nodeCredentials.showMessage.title'),
|
title: this.$locale.baseText('nodeCredentials.showMessage.title'),
|
||||||
message: this.$locale.baseText('nodeCredentials.showMessage.message', {
|
message: this.$locale.baseText('nodeCredentials.showMessage.message', {
|
||||||
|
@ -512,7 +513,12 @@ export default defineComponent({
|
||||||
// If it is not defined no need to do a proper check
|
// If it is not defined no need to do a proper check
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return this.displayParameter(this.node.parameters, credentialTypeDescription, '', this.node);
|
return this.nodeHelpers.displayParameter(
|
||||||
|
this.node.parameters,
|
||||||
|
credentialTypeDescription,
|
||||||
|
'',
|
||||||
|
this.node,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
getIssues(credentialTypeName: string): string[] {
|
getIssues(credentialTypeName: string): string[] {
|
||||||
|
|
|
@ -146,8 +146,6 @@ import type {
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import { jsonParse, NodeHelpers, NodeConnectionType } from 'n8n-workflow';
|
import { jsonParse, NodeHelpers, NodeConnectionType } from 'n8n-workflow';
|
||||||
import type { IExecutionResponse, INodeUi, IUpdateInformation, TargetItem } from '@/Interface';
|
import type { IExecutionResponse, INodeUi, IUpdateInformation, TargetItem } from '@/Interface';
|
||||||
|
|
||||||
import { nodeHelpers } from '@/mixins/nodeHelpers';
|
|
||||||
import { workflowHelpers } from '@/mixins/workflowHelpers';
|
import { workflowHelpers } from '@/mixins/workflowHelpers';
|
||||||
|
|
||||||
import NodeSettings from '@/components/NodeSettings.vue';
|
import NodeSettings from '@/components/NodeSettings.vue';
|
||||||
|
@ -173,12 +171,13 @@ import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||||
import { useUIStore } from '@/stores/ui.store';
|
import { useUIStore } from '@/stores/ui.store';
|
||||||
import { useSettingsStore } from '@/stores/settings.store';
|
import { useSettingsStore } from '@/stores/settings.store';
|
||||||
import { useDeviceSupport } from 'n8n-design-system/composables/useDeviceSupport';
|
import { useDeviceSupport } from 'n8n-design-system/composables/useDeviceSupport';
|
||||||
|
import { useNodeHelpers } from '@/composables/useNodeHelpers';
|
||||||
import { useMessage } from '@/composables/useMessage';
|
import { useMessage } from '@/composables/useMessage';
|
||||||
import { useExternalHooks } from '@/composables/useExternalHooks';
|
import { useExternalHooks } from '@/composables/useExternalHooks';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'NodeDetailsView',
|
name: 'NodeDetailsView',
|
||||||
mixins: [nodeHelpers, workflowHelpers, workflowActivate, pinData],
|
mixins: [workflowHelpers, workflowActivate, pinData],
|
||||||
components: {
|
components: {
|
||||||
NodeSettings,
|
NodeSettings,
|
||||||
InputPanel,
|
InputPanel,
|
||||||
|
@ -200,9 +199,11 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
setup(props, ctx) {
|
setup(props, ctx) {
|
||||||
const externalHooks = useExternalHooks();
|
const externalHooks = useExternalHooks();
|
||||||
|
const nodeHelpers = useNodeHelpers();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
externalHooks,
|
externalHooks,
|
||||||
|
nodeHelpers,
|
||||||
...useDeviceSupport(),
|
...useDeviceSupport(),
|
||||||
...useMessage(),
|
...useMessage(),
|
||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
|
@ -471,14 +472,18 @@ export default defineComponent({
|
||||||
|
|
||||||
setTimeout(() => this.ndvStore.setNDVSessionId(), 0);
|
setTimeout(() => this.ndvStore.setNDVSessionId(), 0);
|
||||||
void this.externalHooks.run('dataDisplay.nodeTypeChanged', {
|
void this.externalHooks.run('dataDisplay.nodeTypeChanged', {
|
||||||
nodeSubtitle: this.getNodeSubtitle(node, this.activeNodeType, this.getCurrentWorkflow()),
|
nodeSubtitle: this.nodeHelpers.getNodeSubtitle(
|
||||||
|
node,
|
||||||
|
this.activeNodeType,
|
||||||
|
this.getCurrentWorkflow(),
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (this.activeNode) {
|
if (this.activeNode) {
|
||||||
const outgoingConnections = this.workflowsStore.outgoingConnectionsByNodeName(
|
const outgoingConnections = this.workflowsStore.outgoingConnectionsByNodeName(
|
||||||
this.activeNode.name,
|
this.activeNode.name,
|
||||||
) as INodeConnections;
|
);
|
||||||
|
|
||||||
this.$telemetry.track('User opened node modal', {
|
this.$telemetry.track('User opened node modal', {
|
||||||
node_type: this.activeNodeType ? this.activeNodeType.name : '',
|
node_type: this.activeNodeType ? this.activeNodeType.name : '',
|
||||||
|
@ -493,8 +498,7 @@ export default defineComponent({
|
||||||
: this.ndvStore.inputPanelDisplayMode,
|
: this.ndvStore.inputPanelDisplayMode,
|
||||||
selected_view_outputs: this.ndvStore.outputPanelDisplayMode,
|
selected_view_outputs: this.ndvStore.outputPanelDisplayMode,
|
||||||
input_connectors: this.parentNodes.length,
|
input_connectors: this.parentNodes.length,
|
||||||
output_connectors:
|
output_connectors: outgoingConnections?.main?.length,
|
||||||
outgoingConnections && outgoingConnections.main && outgoingConnections.main.length,
|
|
||||||
input_displayed_run_index: this.inputRun,
|
input_displayed_run_index: this.inputRun,
|
||||||
output_displayed_run_index: this.outputRun,
|
output_displayed_run_index: this.outputRun,
|
||||||
data_pinning_tooltip_presented: this.pinDataDiscoveryTooltipVisible,
|
data_pinning_tooltip_presented: this.pinDataDiscoveryTooltipVisible,
|
||||||
|
|
|
@ -119,7 +119,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="isCustomApiCallSelected(nodeValues)"
|
v-if="nodeHelpers.isCustomApiCallSelected(nodeValues)"
|
||||||
class="parameter-item parameter-notice"
|
class="parameter-item parameter-notice"
|
||||||
data-test-id="node-parameters-http-notice"
|
data-test-id="node-parameters-http-notice"
|
||||||
>
|
>
|
||||||
|
@ -201,8 +201,6 @@ import NodeSettingsTabs from '@/components/NodeSettingsTabs.vue';
|
||||||
import NodeWebhooks from '@/components/NodeWebhooks.vue';
|
import NodeWebhooks from '@/components/NodeWebhooks.vue';
|
||||||
import { get, set, unset } from 'lodash-es';
|
import { get, set, unset } from 'lodash-es';
|
||||||
|
|
||||||
import { nodeHelpers } from '@/mixins/nodeHelpers';
|
|
||||||
|
|
||||||
import NodeExecuteButton from './NodeExecuteButton.vue';
|
import NodeExecuteButton from './NodeExecuteButton.vue';
|
||||||
import { isCommunityPackageName } from '@/utils/nodeTypesUtils';
|
import { isCommunityPackageName } from '@/utils/nodeTypesUtils';
|
||||||
import { useUIStore } from '@/stores/ui.store';
|
import { useUIStore } from '@/stores/ui.store';
|
||||||
|
@ -215,10 +213,10 @@ import useWorkflowsEEStore from '@/stores/workflows.ee.store';
|
||||||
import { useCredentialsStore } from '@/stores/credentials.store';
|
import { useCredentialsStore } from '@/stores/credentials.store';
|
||||||
import type { EventBus } from 'n8n-design-system';
|
import type { EventBus } from 'n8n-design-system';
|
||||||
import { useExternalHooks } from '@/composables/useExternalHooks';
|
import { useExternalHooks } from '@/composables/useExternalHooks';
|
||||||
|
import { useNodeHelpers } from '@/composables/useNodeHelpers';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'NodeSettings',
|
name: 'NodeSettings',
|
||||||
mixins: [nodeHelpers],
|
|
||||||
components: {
|
components: {
|
||||||
NodeTitle,
|
NodeTitle,
|
||||||
NodeCredentials,
|
NodeCredentials,
|
||||||
|
@ -228,9 +226,12 @@ export default defineComponent({
|
||||||
NodeExecuteButton,
|
NodeExecuteButton,
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
|
const nodeHelpers = useNodeHelpers();
|
||||||
const externalHooks = useExternalHooks();
|
const externalHooks = useExternalHooks();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
externalHooks,
|
externalHooks,
|
||||||
|
nodeHelpers,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -679,7 +680,7 @@ export default defineComponent({
|
||||||
|
|
||||||
if (node) {
|
if (node) {
|
||||||
// Update the issues
|
// Update the issues
|
||||||
this.updateNodeCredentialIssues(node);
|
this.nodeHelpers.updateNodeCredentialIssues(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
void this.externalHooks.run('nodeSettings.credentialSelected', { updateInformation });
|
void this.externalHooks.run('nodeSettings.credentialSelected', { updateInformation });
|
||||||
|
@ -813,8 +814,8 @@ export default defineComponent({
|
||||||
|
|
||||||
this.workflowsStore.setNodeParameters(updateInformation);
|
this.workflowsStore.setNodeParameters(updateInformation);
|
||||||
|
|
||||||
this.updateNodeParameterIssuesByName(node.name);
|
this.nodeHelpers.updateNodeParameterIssuesByName(node.name);
|
||||||
this.updateNodeCredentialIssuesByName(node.name);
|
this.nodeHelpers.updateNodeCredentialIssuesByName(node.name);
|
||||||
}
|
}
|
||||||
} else if (parameterData.name.startsWith('parameters.')) {
|
} else if (parameterData.name.startsWith('parameters.')) {
|
||||||
// A node parameter changed
|
// A node parameter changed
|
||||||
|
@ -896,8 +897,8 @@ export default defineComponent({
|
||||||
oldNodeParameters,
|
oldNodeParameters,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.updateNodeParameterIssuesByName(node.name);
|
this.nodeHelpers.updateNodeParameterIssuesByName(node.name);
|
||||||
this.updateNodeCredentialIssuesByName(node.name);
|
this.nodeHelpers.updateNodeCredentialIssuesByName(node.name);
|
||||||
this.$telemetry.trackNodeParametersValuesChange(nodeType.name, parameterData);
|
this.$telemetry.trackNodeParametersValuesChange(nodeType.name, parameterData);
|
||||||
} else {
|
} else {
|
||||||
// A property on the node itself changed
|
// A property on the node itself changed
|
||||||
|
@ -1060,7 +1061,7 @@ export default defineComponent({
|
||||||
this.setNodeValues();
|
this.setNodeValues();
|
||||||
this.eventBus?.on('openSettings', this.openSettings);
|
this.eventBus?.on('openSettings', this.openSettings);
|
||||||
|
|
||||||
this.updateNodeParameterIssues(this.node as INodeUi, this.nodeType);
|
this.nodeHelpers.updateNodeParameterIssues(this.node as INodeUi, this.nodeType);
|
||||||
},
|
},
|
||||||
beforeUnmount() {
|
beforeUnmount() {
|
||||||
this.eventBus?.off('openSettings', this.openSettings);
|
this.eventBus?.off('openSettings', this.openSettings);
|
||||||
|
|
|
@ -395,7 +395,7 @@ import TextEdit from '@/components/TextEdit.vue';
|
||||||
import CodeNodeEditor from '@/components/CodeNodeEditor/CodeNodeEditor.vue';
|
import CodeNodeEditor from '@/components/CodeNodeEditor/CodeNodeEditor.vue';
|
||||||
import HtmlEditor from '@/components/HtmlEditor/HtmlEditor.vue';
|
import HtmlEditor from '@/components/HtmlEditor/HtmlEditor.vue';
|
||||||
import SqlEditor from '@/components/SqlEditor/SqlEditor.vue';
|
import SqlEditor from '@/components/SqlEditor/SqlEditor.vue';
|
||||||
import { nodeHelpers } from '@/mixins/nodeHelpers';
|
|
||||||
import { workflowHelpers } from '@/mixins/workflowHelpers';
|
import { workflowHelpers } from '@/mixins/workflowHelpers';
|
||||||
import { hasExpressionMapping, isValueExpression } from '@/utils/nodeTypesUtils';
|
import { hasExpressionMapping, isValueExpression } from '@/utils/nodeTypesUtils';
|
||||||
import { isResourceLocatorValue } from '@/utils/typeGuards';
|
import { isResourceLocatorValue } from '@/utils/typeGuards';
|
||||||
|
@ -417,6 +417,7 @@ import { useSettingsStore } from '@/stores/settings.store';
|
||||||
import { htmlEditorEventBus } from '@/event-bus';
|
import { htmlEditorEventBus } from '@/event-bus';
|
||||||
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 { useNodeHelpers } from '@/composables/useNodeHelpers';
|
||||||
import { useI18n } from '@/composables/useI18n';
|
import { useI18n } from '@/composables/useI18n';
|
||||||
import type { N8nInput } from 'n8n-design-system';
|
import type { N8nInput } from 'n8n-design-system';
|
||||||
import { isCredentialOnlyNodeType } from '@/utils/credentialOnlyNodes';
|
import { isCredentialOnlyNodeType } from '@/utils/credentialOnlyNodes';
|
||||||
|
@ -426,7 +427,7 @@ type Picker = { $emit: (arg0: string, arg1: Date) => void };
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'parameter-input',
|
name: 'parameter-input',
|
||||||
mixins: [nodeHelpers, workflowHelpers, debounceHelper],
|
mixins: [workflowHelpers, debounceHelper],
|
||||||
components: {
|
components: {
|
||||||
CodeNodeEditor,
|
CodeNodeEditor,
|
||||||
HtmlEditor,
|
HtmlEditor,
|
||||||
|
@ -505,10 +506,12 @@ export default defineComponent({
|
||||||
setup() {
|
setup() {
|
||||||
const externalHooks = useExternalHooks();
|
const externalHooks = useExternalHooks();
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
|
const nodeHelpers = useNodeHelpers();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
externalHooks,
|
externalHooks,
|
||||||
i18n,
|
i18n,
|
||||||
|
nodeHelpers,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
@ -881,7 +884,7 @@ export default defineComponent({
|
||||||
|
|
||||||
if (node) {
|
if (node) {
|
||||||
// Update the issues
|
// Update the issues
|
||||||
this.updateNodeCredentialIssues(node);
|
this.nodeHelpers.updateNodeCredentialIssues(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
void this.externalHooks.run('nodeSettings.credentialSelected', { updateInformation });
|
void this.externalHooks.run('nodeSettings.credentialSelected', { updateInformation });
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
>
|
>
|
||||||
<multiple-parameter
|
<multiple-parameter
|
||||||
:parameter="parameter"
|
:parameter="parameter"
|
||||||
:values="getParameterValue(nodeValues, parameter.name, path)"
|
:values="nodeHelpers.getParameterValue(nodeValues, parameter.name, path)"
|
||||||
:nodeValues="nodeValues"
|
:nodeValues="nodeValues"
|
||||||
:path="getPath(parameter.name)"
|
:path="getPath(parameter.name)"
|
||||||
:isReadOnly="isReadOnly"
|
:isReadOnly="isReadOnly"
|
||||||
|
@ -71,7 +71,7 @@
|
||||||
<collection-parameter
|
<collection-parameter
|
||||||
v-if="parameter.type === 'collection'"
|
v-if="parameter.type === 'collection'"
|
||||||
:parameter="parameter"
|
:parameter="parameter"
|
||||||
:values="getParameterValue(nodeValues, parameter.name, path)"
|
:values="nodeHelpers.getParameterValue(nodeValues, parameter.name, path)"
|
||||||
:nodeValues="nodeValues"
|
:nodeValues="nodeValues"
|
||||||
:path="getPath(parameter.name)"
|
:path="getPath(parameter.name)"
|
||||||
:isReadOnly="isReadOnly"
|
:isReadOnly="isReadOnly"
|
||||||
|
@ -80,7 +80,7 @@
|
||||||
<fixed-collection-parameter
|
<fixed-collection-parameter
|
||||||
v-else-if="parameter.type === 'fixedCollection'"
|
v-else-if="parameter.type === 'fixedCollection'"
|
||||||
:parameter="parameter"
|
:parameter="parameter"
|
||||||
:values="getParameterValue(nodeValues, parameter.name, path)"
|
:values="nodeHelpers.getParameterValue(nodeValues, parameter.name, path)"
|
||||||
:nodeValues="nodeValues"
|
:nodeValues="nodeValues"
|
||||||
:path="getPath(parameter.name)"
|
:path="getPath(parameter.name)"
|
||||||
:isReadOnly="isReadOnly"
|
:isReadOnly="isReadOnly"
|
||||||
|
@ -118,7 +118,7 @@
|
||||||
<parameter-input-full
|
<parameter-input-full
|
||||||
:parameter="parameter"
|
:parameter="parameter"
|
||||||
:hide-issues="hiddenIssuesInputs.includes(parameter.name)"
|
:hide-issues="hiddenIssuesInputs.includes(parameter.name)"
|
||||||
:value="getParameterValue(nodeValues, parameter.name, path)"
|
:value="nodeHelpers.getParameterValue(nodeValues, parameter.name, path)"
|
||||||
:displayOptions="shouldShowOptions(parameter)"
|
:displayOptions="shouldShowOptions(parameter)"
|
||||||
:path="getPath(parameter.name)"
|
:path="getPath(parameter.name)"
|
||||||
:isReadOnly="isReadOnly"
|
:isReadOnly="isReadOnly"
|
||||||
|
@ -164,6 +164,7 @@ import {
|
||||||
} from '@/utils/nodeTypesUtils';
|
} from '@/utils/nodeTypesUtils';
|
||||||
import { get, set } from 'lodash-es';
|
import { get, set } from 'lodash-es';
|
||||||
import { nodeViewEventBus } from '@/event-bus';
|
import { nodeViewEventBus } from '@/event-bus';
|
||||||
|
import { useNodeHelpers } from '@/composables/useNodeHelpers';
|
||||||
|
|
||||||
const FixedCollectionParameter = defineAsyncComponent(
|
const FixedCollectionParameter = defineAsyncComponent(
|
||||||
async () => import('./FixedCollectionParameter.vue'),
|
async () => import('./FixedCollectionParameter.vue'),
|
||||||
|
@ -181,6 +182,13 @@ export default defineComponent({
|
||||||
ImportParameter,
|
ImportParameter,
|
||||||
ResourceMapper,
|
ResourceMapper,
|
||||||
},
|
},
|
||||||
|
setup() {
|
||||||
|
const nodeHelpers = useNodeHelpers();
|
||||||
|
|
||||||
|
return {
|
||||||
|
nodeHelpers,
|
||||||
|
};
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
nodeValues: {
|
nodeValues: {
|
||||||
type: Object as PropType<INodeParameters>,
|
type: Object as PropType<INodeParameters>,
|
||||||
|
@ -348,7 +356,7 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
this.isCustomApiCallSelected(this.nodeValues) &&
|
this.nodeHelpers.isCustomApiCallSelected(this.nodeValues) &&
|
||||||
this.mustHideDuringCustomApiCall(parameter, this.nodeValues)
|
this.mustHideDuringCustomApiCall(parameter, this.nodeValues)
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -422,13 +430,13 @@ export default defineComponent({
|
||||||
if (this.path) {
|
if (this.path) {
|
||||||
rawValues = deepCopy(this.nodeValues);
|
rawValues = deepCopy(this.nodeValues);
|
||||||
set(rawValues, this.path, nodeValues);
|
set(rawValues, this.path, nodeValues);
|
||||||
return this.displayParameter(rawValues, parameter, this.path, this.node);
|
return this.nodeHelpers.displayParameter(rawValues, parameter, this.path, this.node);
|
||||||
} else {
|
} else {
|
||||||
return this.displayParameter(nodeValues, parameter, '', this.node);
|
return this.nodeHelpers.displayParameter(nodeValues, parameter, '', this.node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.displayParameter(this.nodeValues, parameter, this.path, this.node);
|
return this.nodeHelpers.displayParameter(this.nodeValues, parameter, this.path, this.node);
|
||||||
},
|
},
|
||||||
valueChanged(parameterData: IUpdateInformation): void {
|
valueChanged(parameterData: IUpdateInformation): void {
|
||||||
this.$emit('valueChanged', parameterData);
|
this.$emit('valueChanged', parameterData);
|
||||||
|
|
|
@ -149,7 +149,6 @@ import DraggableTarget from '@/components/DraggableTarget.vue';
|
||||||
import ExpressionParameterInput from '@/components/ExpressionParameterInput.vue';
|
import ExpressionParameterInput from '@/components/ExpressionParameterInput.vue';
|
||||||
import ParameterIssues from '@/components/ParameterIssues.vue';
|
import ParameterIssues from '@/components/ParameterIssues.vue';
|
||||||
import { debounceHelper } from '@/mixins/debounce';
|
import { debounceHelper } from '@/mixins/debounce';
|
||||||
import { nodeHelpers } from '@/mixins/nodeHelpers';
|
|
||||||
import { workflowHelpers } from '@/mixins/workflowHelpers';
|
import { workflowHelpers } from '@/mixins/workflowHelpers';
|
||||||
import { useRootStore } from '@/stores/n8nRoot.store';
|
import { useRootStore } from '@/stores/n8nRoot.store';
|
||||||
import { useNDVStore } from '@/stores/ndv.store';
|
import { useNDVStore } from '@/stores/ndv.store';
|
||||||
|
@ -185,7 +184,7 @@ interface IResourceLocatorQuery {
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'resource-locator',
|
name: 'resource-locator',
|
||||||
mixins: [debounceHelper, workflowHelpers, nodeHelpers],
|
mixins: [debounceHelper, workflowHelpers],
|
||||||
components: {
|
components: {
|
||||||
DraggableTarget,
|
DraggableTarget,
|
||||||
ExpressionParameterInput,
|
ExpressionParameterInput,
|
||||||
|
|
|
@ -606,7 +606,6 @@ import BinaryDataDisplay from '@/components/BinaryDataDisplay.vue';
|
||||||
import NodeErrorView from '@/components/Error/NodeErrorView.vue';
|
import NodeErrorView from '@/components/Error/NodeErrorView.vue';
|
||||||
|
|
||||||
import { genericHelpers } from '@/mixins/genericHelpers';
|
import { genericHelpers } from '@/mixins/genericHelpers';
|
||||||
import { nodeHelpers } from '@/mixins/nodeHelpers';
|
|
||||||
import { pinData } from '@/mixins/pinData';
|
import { pinData } from '@/mixins/pinData';
|
||||||
import type { PinDataSource } from '@/mixins/pinData';
|
import type { PinDataSource } from '@/mixins/pinData';
|
||||||
import CodeNodeEditor from '@/components/CodeNodeEditor/CodeNodeEditor.vue';
|
import CodeNodeEditor from '@/components/CodeNodeEditor/CodeNodeEditor.vue';
|
||||||
|
@ -617,6 +616,7 @@ import { searchInObject } from '@/utils/objectUtils';
|
||||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||||
import { useNDVStore } from '@/stores/ndv.store';
|
import { useNDVStore } from '@/stores/ndv.store';
|
||||||
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||||
|
import { useNodeHelpers } from '@/composables/useNodeHelpers';
|
||||||
import { useToast } from '@/composables/useToast';
|
import { useToast } from '@/composables/useToast';
|
||||||
import { isObject } from 'lodash-es';
|
import { isObject } from 'lodash-es';
|
||||||
import { useExternalHooks } from '@/composables/useExternalHooks';
|
import { useExternalHooks } from '@/composables/useExternalHooks';
|
||||||
|
@ -633,7 +633,7 @@ export type EnterEditModeArgs = {
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'RunData',
|
name: 'RunData',
|
||||||
mixins: [genericHelpers, nodeHelpers, pinData],
|
mixins: [genericHelpers, pinData],
|
||||||
components: {
|
components: {
|
||||||
BinaryDataDisplay,
|
BinaryDataDisplay,
|
||||||
NodeErrorView,
|
NodeErrorView,
|
||||||
|
@ -699,11 +699,13 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
|
const nodeHelpers = useNodeHelpers();
|
||||||
const externalHooks = useExternalHooks();
|
const externalHooks = useExternalHooks();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
externalHooks,
|
|
||||||
...useToast(),
|
...useToast(),
|
||||||
|
externalHooks,
|
||||||
|
nodeHelpers,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
@ -931,7 +933,7 @@ export default defineComponent({
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const binaryData = this.getBinaryData(
|
const binaryData = this.nodeHelpers.getBinaryData(
|
||||||
this.workflowRunData,
|
this.workflowRunData,
|
||||||
this.node.name,
|
this.node.name,
|
||||||
this.runIndex,
|
this.runIndex,
|
||||||
|
@ -1141,7 +1143,7 @@ export default defineComponent({
|
||||||
this.$telemetry.track('User clicked pin data icon', telemetryPayload);
|
this.$telemetry.track('User clicked pin data icon', telemetryPayload);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateNodeParameterIssues(this.node);
|
this.nodeHelpers.updateNodeParameterIssues(this.node);
|
||||||
|
|
||||||
if (this.hasPinData) {
|
if (this.hasPinData) {
|
||||||
this.unsetPinData(this.node, source);
|
this.unsetPinData(this.node, source);
|
||||||
|
@ -1282,7 +1284,7 @@ export default defineComponent({
|
||||||
let inputData: INodeExecutionData[] = [];
|
let inputData: INodeExecutionData[] = [];
|
||||||
|
|
||||||
if (this.node) {
|
if (this.node) {
|
||||||
inputData = this.getNodeInputData(
|
inputData = this.nodeHelpers.getNodeInputData(
|
||||||
this.node,
|
this.node,
|
||||||
runIndex,
|
runIndex,
|
||||||
outputIndex,
|
outputIndex,
|
||||||
|
@ -1365,7 +1367,7 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
clearExecutionData() {
|
clearExecutionData() {
|
||||||
this.workflowsStore.setWorkflowExecutionData(null);
|
this.workflowsStore.setWorkflowExecutionData(null);
|
||||||
this.updateNodesExecutionIssues();
|
this.nodeHelpers.updateNodesExecutionIssues();
|
||||||
},
|
},
|
||||||
isViewable(index: number, key: string): boolean {
|
isViewable(index: number, key: string): boolean {
|
||||||
const { fileType } = this.binaryData[index][key];
|
const { fileType } = this.binaryData[index][key];
|
||||||
|
|
|
@ -43,14 +43,14 @@ import type { INodeUi } from '@/Interface';
|
||||||
import type { IDataObject } from 'n8n-workflow';
|
import type { IDataObject } from 'n8n-workflow';
|
||||||
import { copyPaste } from '@/mixins/copyPaste';
|
import { copyPaste } from '@/mixins/copyPaste';
|
||||||
import { pinData } from '@/mixins/pinData';
|
import { pinData } from '@/mixins/pinData';
|
||||||
import { nodeHelpers } from '@/mixins/nodeHelpers';
|
|
||||||
import { genericHelpers } from '@/mixins/genericHelpers';
|
import { genericHelpers } from '@/mixins/genericHelpers';
|
||||||
import { clearJsonKey, convertPath } from '@/utils/typesUtils';
|
import { clearJsonKey, convertPath } from '@/utils/typesUtils';
|
||||||
import { executionDataToJson } from '@/utils/nodeTypesUtils';
|
import { executionDataToJson } from '@/utils/nodeTypesUtils';
|
||||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||||
import { useNDVStore } from '@/stores/ndv.store';
|
import { useNDVStore } from '@/stores/ndv.store';
|
||||||
import { useI18n } from '@/composables/useI18n';
|
import { useNodeHelpers } from '@/composables/useNodeHelpers';
|
||||||
import { useToast } from '@/composables/useToast';
|
import { useToast } from '@/composables/useToast';
|
||||||
|
import { useI18n } from '@/composables/useI18n';
|
||||||
import { nonExistingJsonPath } from '@/constants';
|
import { nonExistingJsonPath } from '@/constants';
|
||||||
|
|
||||||
type JsonPathData = {
|
type JsonPathData = {
|
||||||
|
@ -60,7 +60,7 @@ type JsonPathData = {
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'run-data-json-actions',
|
name: 'run-data-json-actions',
|
||||||
mixins: [genericHelpers, nodeHelpers, pinData, copyPaste],
|
mixins: [genericHelpers, pinData, copyPaste],
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
node: {
|
node: {
|
||||||
|
@ -95,9 +95,10 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
|
const nodeHelpers = useNodeHelpers();
|
||||||
return {
|
return {
|
||||||
i18n,
|
i18n,
|
||||||
|
nodeHelpers,
|
||||||
...useToast(),
|
...useToast(),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -121,7 +122,7 @@ export default defineComponent({
|
||||||
selectedValue = clearJsonKey(this.pinData as object);
|
selectedValue = clearJsonKey(this.pinData as object);
|
||||||
} else {
|
} else {
|
||||||
selectedValue = executionDataToJson(
|
selectedValue = executionDataToJson(
|
||||||
this.getNodeInputData(this.node, this.runIndex, this.currentOutputIndex),
|
this.nodeHelpers.getNodeInputData(this.node, this.runIndex, this.currentOutputIndex),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,7 +105,6 @@ import { defineComponent, ref } from 'vue';
|
||||||
import { mapStores } from 'pinia';
|
import { mapStores } from 'pinia';
|
||||||
|
|
||||||
import { nodeBase } from '@/mixins/nodeBase';
|
import { nodeBase } from '@/mixins/nodeBase';
|
||||||
import { nodeHelpers } from '@/mixins/nodeHelpers';
|
|
||||||
import { workflowHelpers } from '@/mixins/workflowHelpers';
|
import { workflowHelpers } from '@/mixins/workflowHelpers';
|
||||||
import { isNumber, isString } from '@/utils/typeGuards';
|
import { isNumber, isString } from '@/utils/typeGuards';
|
||||||
import type {
|
import type {
|
||||||
|
@ -125,7 +124,7 @@ import { useContextMenu } from '@/composables/useContextMenu';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'Sticky',
|
name: 'Sticky',
|
||||||
mixins: [nodeBase, nodeHelpers, workflowHelpers],
|
mixins: [nodeBase, workflowHelpers],
|
||||||
setup() {
|
setup() {
|
||||||
const colorPopoverTrigger = ref<HTMLDivElement>();
|
const colorPopoverTrigger = ref<HTMLDivElement>();
|
||||||
const forceActions = ref(false);
|
const forceActions = ref(false);
|
||||||
|
|
723
packages/editor-ui/src/composables/useNodeHelpers.ts
Normal file
723
packages/editor-ui/src/composables/useNodeHelpers.ts
Normal file
|
@ -0,0 +1,723 @@
|
||||||
|
import { useHistoryStore } from '@/stores/history.store';
|
||||||
|
import { CUSTOM_API_CALL_KEY, PLACEHOLDER_FILLED_AT_EXECUTION_TIME } from '@/constants';
|
||||||
|
|
||||||
|
import { NodeHelpers, NodeConnectionType, ExpressionEvaluatorProxy } from 'n8n-workflow';
|
||||||
|
import type {
|
||||||
|
INodeProperties,
|
||||||
|
INodeCredentialDescription,
|
||||||
|
INodeTypeDescription,
|
||||||
|
INodeIssues,
|
||||||
|
ICredentialType,
|
||||||
|
INodeIssueObjectProperty,
|
||||||
|
ConnectionTypes,
|
||||||
|
INodeInputConfiguration,
|
||||||
|
Workflow,
|
||||||
|
INodeExecutionData,
|
||||||
|
ITaskDataConnections,
|
||||||
|
IRunData,
|
||||||
|
IBinaryKeyData,
|
||||||
|
IDataObject,
|
||||||
|
INode,
|
||||||
|
INodePropertyOptions,
|
||||||
|
INodeCredentialsDetails,
|
||||||
|
INodeParameters,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
import type {
|
||||||
|
ICredentialsResponse,
|
||||||
|
INodeUi,
|
||||||
|
INodeUpdatePropertiesInformation,
|
||||||
|
NodePanelType,
|
||||||
|
} from '@/Interface';
|
||||||
|
|
||||||
|
import { isString } from '@/utils/typeGuards';
|
||||||
|
import { isObject } from '@/utils/objectUtils';
|
||||||
|
import { useSettingsStore } from '@/stores/settings.store';
|
||||||
|
import { useUsersStore } from '@/stores/users.store';
|
||||||
|
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||||
|
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||||
|
import { useCredentialsStore } from '@/stores/credentials.store';
|
||||||
|
import { get } from 'lodash-es';
|
||||||
|
import { useI18n } from './useI18n';
|
||||||
|
import { EnableNodeToggleCommand } from '@/models/history';
|
||||||
|
import { useTelemetry } from './useTelemetry';
|
||||||
|
import { getCredentialPermissions } from '@/permissions';
|
||||||
|
import { hasPermission } from '@/rbac/permissions';
|
||||||
|
|
||||||
|
declare namespace HttpRequestNode {
|
||||||
|
namespace V2 {
|
||||||
|
type AuthParams = {
|
||||||
|
authentication: 'none' | 'genericCredentialType' | 'predefinedCredentialType';
|
||||||
|
genericAuthType: string;
|
||||||
|
nodeCredentialType: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useNodeHelpers() {
|
||||||
|
const credentialsStore = useCredentialsStore();
|
||||||
|
const historyStore = useHistoryStore();
|
||||||
|
const nodeTypesStore = useNodeTypesStore();
|
||||||
|
const workflowsStore = useWorkflowsStore();
|
||||||
|
const i18n = useI18n();
|
||||||
|
|
||||||
|
function hasProxyAuth(node: INodeUi): boolean {
|
||||||
|
return Object.keys(node.parameters).includes('nodeCredentialType');
|
||||||
|
}
|
||||||
|
|
||||||
|
function isCustomApiCallSelected(nodeValues: INodeParameters): boolean {
|
||||||
|
const { parameters } = nodeValues;
|
||||||
|
|
||||||
|
if (!isObject(parameters)) return false;
|
||||||
|
|
||||||
|
const { resource, operation } = parameters;
|
||||||
|
if (!isString(resource) || !isString(operation)) return false;
|
||||||
|
|
||||||
|
return resource.includes(CUSTOM_API_CALL_KEY) || operation.includes(CUSTOM_API_CALL_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getParameterValue(nodeValues: INodeParameters, parameterName: string, path: string) {
|
||||||
|
return get(nodeValues, path ? path + '.' + parameterName : parameterName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns if the given parameter should be displayed or not
|
||||||
|
function displayParameter(
|
||||||
|
nodeValues: INodeParameters,
|
||||||
|
parameter: INodeProperties | INodeCredentialDescription,
|
||||||
|
path: string,
|
||||||
|
node: INodeUi | null,
|
||||||
|
) {
|
||||||
|
return NodeHelpers.displayParameterPath(nodeValues, parameter, path, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshNodeIssues(): void {
|
||||||
|
const nodes = workflowsStore.allNodes;
|
||||||
|
const workflow = workflowsStore.getCurrentWorkflow();
|
||||||
|
let nodeType: INodeTypeDescription | null;
|
||||||
|
let foundNodeIssues: INodeIssues | null;
|
||||||
|
|
||||||
|
nodes.forEach((node) => {
|
||||||
|
if (node.disabled === true) return;
|
||||||
|
|
||||||
|
nodeType = nodeTypesStore.getNodeType(node.type, node.typeVersion);
|
||||||
|
foundNodeIssues = getNodeIssues(nodeType, node, workflow);
|
||||||
|
if (foundNodeIssues !== null) {
|
||||||
|
node.issues = foundNodeIssues;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNodeIssues(
|
||||||
|
nodeType: INodeTypeDescription | null,
|
||||||
|
node: INodeUi,
|
||||||
|
workflow: Workflow,
|
||||||
|
ignoreIssues?: string[],
|
||||||
|
): INodeIssues | null {
|
||||||
|
const pinDataNodeNames = Object.keys(workflowsStore.getPinData ?? {});
|
||||||
|
|
||||||
|
let nodeIssues: INodeIssues | null = null;
|
||||||
|
ignoreIssues = ignoreIssues ?? [];
|
||||||
|
|
||||||
|
if (node.disabled === true || pinDataNodeNames.includes(node.name)) {
|
||||||
|
// Ignore issues on disabled and pindata nodes
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nodeType === null) {
|
||||||
|
// Node type is not known
|
||||||
|
if (!ignoreIssues.includes('typeUnknown')) {
|
||||||
|
nodeIssues = {
|
||||||
|
typeUnknown: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Node type is known
|
||||||
|
|
||||||
|
// Add potential parameter issues
|
||||||
|
if (!ignoreIssues.includes('parameters')) {
|
||||||
|
nodeIssues = NodeHelpers.getNodeParametersIssues(nodeType.properties, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ignoreIssues.includes('credentials')) {
|
||||||
|
// Add potential credential issues
|
||||||
|
const nodeCredentialIssues = getNodeCredentialIssues(node, nodeType);
|
||||||
|
if (nodeIssues === null) {
|
||||||
|
nodeIssues = nodeCredentialIssues;
|
||||||
|
} else {
|
||||||
|
NodeHelpers.mergeIssues(nodeIssues, nodeCredentialIssues);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodeInputIssues = getNodeInputIssues(workflow, node, nodeType);
|
||||||
|
if (nodeIssues === null) {
|
||||||
|
nodeIssues = nodeInputIssues;
|
||||||
|
} else {
|
||||||
|
NodeHelpers.mergeIssues(nodeIssues, nodeInputIssues);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasNodeExecutionIssues(node) && !ignoreIssues.includes('execution')) {
|
||||||
|
if (nodeIssues === null) {
|
||||||
|
nodeIssues = {};
|
||||||
|
}
|
||||||
|
nodeIssues.execution = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nodeIssues;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the status on all the nodes which produced an error so that it can be
|
||||||
|
// displayed in the node-view
|
||||||
|
function hasNodeExecutionIssues(node: INodeUi): boolean {
|
||||||
|
const workflowResultData = workflowsStore.getWorkflowRunData;
|
||||||
|
|
||||||
|
if (workflowResultData === null || !workflowResultData.hasOwnProperty(node.name)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const taskData of workflowResultData[node.name]) {
|
||||||
|
if (taskData.error !== undefined) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function reportUnsetCredential(credentialType: ICredentialType) {
|
||||||
|
return {
|
||||||
|
credentials: {
|
||||||
|
[credentialType.name]: [
|
||||||
|
i18n.baseText('nodeHelpers.credentialsUnset', {
|
||||||
|
interpolate: {
|
||||||
|
credentialType: credentialType.displayName,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateNodesInputIssues() {
|
||||||
|
const nodes = workflowsStore.allNodes;
|
||||||
|
const workflow = workflowsStore.getCurrentWorkflow();
|
||||||
|
|
||||||
|
for (const node of nodes) {
|
||||||
|
const nodeType = nodeTypesStore.getNodeType(node.type, node.typeVersion);
|
||||||
|
if (!nodeType) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const nodeInputIssues = getNodeInputIssues(workflow, node, nodeType);
|
||||||
|
|
||||||
|
workflowsStore.setNodeIssue({
|
||||||
|
node: node.name,
|
||||||
|
type: 'input',
|
||||||
|
value: nodeInputIssues?.input ? nodeInputIssues.input : null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateNodesExecutionIssues() {
|
||||||
|
const nodes = workflowsStore.allNodes;
|
||||||
|
|
||||||
|
for (const node of nodes) {
|
||||||
|
workflowsStore.setNodeIssue({
|
||||||
|
node: node.name,
|
||||||
|
type: 'execution',
|
||||||
|
value: hasNodeExecutionIssues(node) ? true : null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateNodeCredentialIssuesByName(name: string): void {
|
||||||
|
const node = workflowsStore.getNodeByName(name);
|
||||||
|
|
||||||
|
if (node) {
|
||||||
|
updateNodeCredentialIssues(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateNodeCredentialIssues(node: INodeUi): void {
|
||||||
|
const fullNodeIssues: INodeIssues | null = getNodeCredentialIssues(node);
|
||||||
|
|
||||||
|
let newIssues: INodeIssueObjectProperty | null = null;
|
||||||
|
if (fullNodeIssues !== null) {
|
||||||
|
newIssues = fullNodeIssues.credentials!;
|
||||||
|
}
|
||||||
|
|
||||||
|
workflowsStore.setNodeIssue({
|
||||||
|
node: node.name,
|
||||||
|
type: 'credentials',
|
||||||
|
value: newIssues,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateNodeParameterIssuesByName(name: string): void {
|
||||||
|
const node = workflowsStore.getNodeByName(name);
|
||||||
|
|
||||||
|
if (node) {
|
||||||
|
updateNodeParameterIssues(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateNodeParameterIssues(node: INodeUi, nodeType?: INodeTypeDescription): void {
|
||||||
|
const localNodeType = nodeType ?? nodeTypesStore.getNodeType(node.type, node.typeVersion);
|
||||||
|
|
||||||
|
if (localNodeType === null) {
|
||||||
|
// Could not find localNodeType so can not update issues
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// All data got updated everywhere so update now the issues
|
||||||
|
const fullNodeIssues: INodeIssues | null = NodeHelpers.getNodeParametersIssues(
|
||||||
|
localNodeType.properties,
|
||||||
|
node,
|
||||||
|
);
|
||||||
|
|
||||||
|
let newIssues: INodeIssueObjectProperty | null = null;
|
||||||
|
if (fullNodeIssues !== null) {
|
||||||
|
newIssues = fullNodeIssues.parameters!;
|
||||||
|
}
|
||||||
|
|
||||||
|
workflowsStore.setNodeIssue({
|
||||||
|
node: node.name,
|
||||||
|
type: 'parameters',
|
||||||
|
value: newIssues,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNodeInputIssues(
|
||||||
|
workflow: Workflow,
|
||||||
|
node: INodeUi,
|
||||||
|
nodeType?: INodeTypeDescription,
|
||||||
|
): INodeIssues | null {
|
||||||
|
const foundIssues: INodeIssueObjectProperty = {};
|
||||||
|
|
||||||
|
const workflowNode = workflow.getNode(node.name);
|
||||||
|
let inputs: Array<ConnectionTypes | INodeInputConfiguration> = [];
|
||||||
|
if (nodeType && workflowNode) {
|
||||||
|
inputs = NodeHelpers.getNodeInputs(workflow, workflowNode, nodeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
inputs.forEach((input) => {
|
||||||
|
if (typeof input === 'string' || input.required !== true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parentNodes = workflow.getParentNodes(node.name, input.type, 1);
|
||||||
|
|
||||||
|
if (parentNodes.length === 0) {
|
||||||
|
foundIssues[input.type] = [
|
||||||
|
i18n.baseText('nodeIssues.input.missing', {
|
||||||
|
interpolate: { inputName: input.displayName || input.type },
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (Object.keys(foundIssues).length) {
|
||||||
|
return {
|
||||||
|
input: foundIssues,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNodeCredentialIssues(
|
||||||
|
node: INodeUi,
|
||||||
|
nodeType?: INodeTypeDescription,
|
||||||
|
): INodeIssues | null {
|
||||||
|
const localNodeType = nodeType ?? nodeTypesStore.getNodeType(node.type, node.typeVersion);
|
||||||
|
if (node.disabled) {
|
||||||
|
// Node is disabled
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!localNodeType?.credentials) {
|
||||||
|
// Node does not need any credentials or nodeType could not be found
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const foundIssues: INodeIssueObjectProperty = {};
|
||||||
|
|
||||||
|
let userCredentials: ICredentialsResponse[] | null;
|
||||||
|
let credentialType: ICredentialType | undefined;
|
||||||
|
let credentialDisplayName: string;
|
||||||
|
let selectedCredentials: INodeCredentialsDetails;
|
||||||
|
|
||||||
|
const { authentication, genericAuthType, nodeCredentialType } =
|
||||||
|
node.parameters as HttpRequestNode.V2.AuthParams;
|
||||||
|
|
||||||
|
if (
|
||||||
|
authentication === 'genericCredentialType' &&
|
||||||
|
genericAuthType !== '' &&
|
||||||
|
selectedCredsAreUnusable(node, genericAuthType)
|
||||||
|
) {
|
||||||
|
const credential = credentialsStore.getCredentialTypeByName(genericAuthType);
|
||||||
|
return credential ? reportUnsetCredential(credential) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
hasProxyAuth(node) &&
|
||||||
|
authentication === 'predefinedCredentialType' &&
|
||||||
|
nodeCredentialType !== '' &&
|
||||||
|
node.credentials !== undefined
|
||||||
|
) {
|
||||||
|
const stored = credentialsStore.getCredentialsByType(nodeCredentialType);
|
||||||
|
|
||||||
|
if (selectedCredsDoNotExist(node, nodeCredentialType, stored)) {
|
||||||
|
const credential = credentialsStore.getCredentialTypeByName(nodeCredentialType);
|
||||||
|
return credential ? reportUnsetCredential(credential) : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
hasProxyAuth(node) &&
|
||||||
|
authentication === 'predefinedCredentialType' &&
|
||||||
|
nodeCredentialType !== '' &&
|
||||||
|
selectedCredsAreUnusable(node, nodeCredentialType)
|
||||||
|
) {
|
||||||
|
const credential = credentialsStore.getCredentialTypeByName(nodeCredentialType);
|
||||||
|
return credential ? reportUnsetCredential(credential) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const credentialTypeDescription of localNodeType.credentials) {
|
||||||
|
// Check if credentials should be displayed else ignore
|
||||||
|
if (!displayParameter(node.parameters, credentialTypeDescription, '', node)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the display name of the credential type
|
||||||
|
credentialType = credentialsStore.getCredentialTypeByName(credentialTypeDescription.name);
|
||||||
|
if (!credentialType) {
|
||||||
|
credentialDisplayName = credentialTypeDescription.name;
|
||||||
|
} else {
|
||||||
|
credentialDisplayName = credentialType.displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!node.credentials?.[credentialTypeDescription.name]) {
|
||||||
|
// Credentials are not set
|
||||||
|
if (credentialTypeDescription.required) {
|
||||||
|
foundIssues[credentialTypeDescription.name] = [
|
||||||
|
i18n.baseText('nodeIssues.credentials.notSet', {
|
||||||
|
interpolate: { type: localNodeType.displayName },
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If they are set check if the value is valid
|
||||||
|
selectedCredentials = node.credentials[credentialTypeDescription.name];
|
||||||
|
if (typeof selectedCredentials === 'string') {
|
||||||
|
selectedCredentials = {
|
||||||
|
id: null,
|
||||||
|
name: selectedCredentials,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const usersStore = useUsersStore();
|
||||||
|
const currentUser = usersStore.currentUser;
|
||||||
|
userCredentials = credentialsStore
|
||||||
|
.getCredentialsByType(credentialTypeDescription.name)
|
||||||
|
.filter((credential: ICredentialsResponse) => {
|
||||||
|
const permissions = getCredentialPermissions(currentUser, credential);
|
||||||
|
return permissions.read;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (userCredentials === null) {
|
||||||
|
userCredentials = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedCredentials.id) {
|
||||||
|
const idMatch = userCredentials.find(
|
||||||
|
(credentialData) => credentialData.id === selectedCredentials.id,
|
||||||
|
);
|
||||||
|
if (idMatch) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const nameMatches = userCredentials.filter(
|
||||||
|
(credentialData) => credentialData.name === selectedCredentials.name,
|
||||||
|
);
|
||||||
|
if (nameMatches.length > 1) {
|
||||||
|
foundIssues[credentialTypeDescription.name] = [
|
||||||
|
i18n.baseText('nodeIssues.credentials.notIdentified', {
|
||||||
|
interpolate: { name: selectedCredentials.name, type: credentialDisplayName },
|
||||||
|
}),
|
||||||
|
i18n.baseText('nodeIssues.credentials.notIdentified.hint'),
|
||||||
|
];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nameMatches.length === 0) {
|
||||||
|
const isCredentialUsedInWorkflow =
|
||||||
|
workflowsStore.usedCredentials?.[selectedCredentials.id as string];
|
||||||
|
|
||||||
|
if (
|
||||||
|
!isCredentialUsedInWorkflow &&
|
||||||
|
!hasPermission(['rbac'], { rbac: { scope: 'credential:read' } })
|
||||||
|
) {
|
||||||
|
foundIssues[credentialTypeDescription.name] = [
|
||||||
|
i18n.baseText('nodeIssues.credentials.doNotExist', {
|
||||||
|
interpolate: { name: selectedCredentials.name, type: credentialDisplayName },
|
||||||
|
}),
|
||||||
|
i18n.baseText('nodeIssues.credentials.doNotExist.hint'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Could later check also if the node has access to the credentials
|
||||||
|
if (Object.keys(foundIssues).length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
credentials: foundIssues,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Whether the node has no selected credentials, or none of the node's
|
||||||
|
* selected credentials are of the specified type.
|
||||||
|
*/
|
||||||
|
function selectedCredsAreUnusable(node: INodeUi, credentialType: string) {
|
||||||
|
return !node.credentials || !Object.keys(node.credentials).includes(credentialType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the node's selected credentials of the specified type
|
||||||
|
* can no longer be found in the database.
|
||||||
|
*/
|
||||||
|
function selectedCredsDoNotExist(
|
||||||
|
node: INodeUi,
|
||||||
|
nodeCredentialType: string,
|
||||||
|
storedCredsByType: ICredentialsResponse[] | null,
|
||||||
|
) {
|
||||||
|
if (!node.credentials || !storedCredsByType) return false;
|
||||||
|
|
||||||
|
const selectedCredsByType = node.credentials[nodeCredentialType];
|
||||||
|
|
||||||
|
if (!selectedCredsByType) return false;
|
||||||
|
|
||||||
|
return !storedCredsByType.find((c) => c.id === selectedCredsByType.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateNodesCredentialsIssues() {
|
||||||
|
const nodes = workflowsStore.allNodes;
|
||||||
|
let issues: INodeIssues | null;
|
||||||
|
|
||||||
|
for (const node of nodes) {
|
||||||
|
issues = getNodeCredentialIssues(node);
|
||||||
|
|
||||||
|
workflowsStore.setNodeIssue({
|
||||||
|
node: node.name,
|
||||||
|
type: 'credentials',
|
||||||
|
value: issues === null ? null : issues.credentials,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNodeInputData(
|
||||||
|
node: INodeUi | null,
|
||||||
|
runIndex = 0,
|
||||||
|
outputIndex = 0,
|
||||||
|
paneType: NodePanelType = 'output',
|
||||||
|
connectionType: ConnectionTypes = NodeConnectionType.Main,
|
||||||
|
): INodeExecutionData[] {
|
||||||
|
if (node === null) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
if (workflowsStore.getWorkflowExecution === null) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const executionData = workflowsStore.getWorkflowExecution.data;
|
||||||
|
if (!executionData?.resultData) {
|
||||||
|
// unknown status
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const runData = executionData.resultData.runData;
|
||||||
|
|
||||||
|
const taskData = get(runData, `[${node.name}][${runIndex}]`);
|
||||||
|
if (!taskData) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Is this problematic?
|
||||||
|
let data: ITaskDataConnections | undefined = taskData.data!;
|
||||||
|
if (paneType === 'input' && taskData.inputOverride) {
|
||||||
|
data = taskData.inputOverride!;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return getInputData(data, outputIndex, connectionType);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getInputData(
|
||||||
|
connectionsData: ITaskDataConnections,
|
||||||
|
outputIndex: number,
|
||||||
|
connectionType: ConnectionTypes = NodeConnectionType.Main,
|
||||||
|
): INodeExecutionData[] {
|
||||||
|
if (
|
||||||
|
!connectionsData ||
|
||||||
|
!connectionsData.hasOwnProperty(connectionType) ||
|
||||||
|
connectionsData[connectionType] === undefined ||
|
||||||
|
connectionsData[connectionType].length < outputIndex ||
|
||||||
|
connectionsData[connectionType][outputIndex] === null
|
||||||
|
) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return connectionsData[connectionType][outputIndex] as INodeExecutionData[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBinaryData(
|
||||||
|
workflowRunData: IRunData | null,
|
||||||
|
node: string | null,
|
||||||
|
runIndex: number,
|
||||||
|
outputIndex: number,
|
||||||
|
connectionType: ConnectionTypes = NodeConnectionType.Main,
|
||||||
|
): IBinaryKeyData[] {
|
||||||
|
if (node === null) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const runData: IRunData | null = workflowRunData;
|
||||||
|
|
||||||
|
if (!runData?.[node]?.[runIndex]?.data) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const inputData = getInputData(runData[node][runIndex].data!, outputIndex, connectionType);
|
||||||
|
|
||||||
|
const returnData: IBinaryKeyData[] = [];
|
||||||
|
for (let i = 0; i < inputData.length; i++) {
|
||||||
|
if (inputData[i].hasOwnProperty('binary') && inputData[i].binary !== undefined) {
|
||||||
|
returnData.push(inputData[i].binary!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnData;
|
||||||
|
}
|
||||||
|
|
||||||
|
function disableNodes(nodes: INodeUi[], trackHistory = false) {
|
||||||
|
const telemetry = useTelemetry();
|
||||||
|
|
||||||
|
if (trackHistory) {
|
||||||
|
historyStore.startRecordingUndo();
|
||||||
|
}
|
||||||
|
for (const node of nodes) {
|
||||||
|
const oldState = node.disabled;
|
||||||
|
// Toggle disabled flag
|
||||||
|
const updateInformation = {
|
||||||
|
name: node.name,
|
||||||
|
properties: {
|
||||||
|
disabled: !oldState,
|
||||||
|
} as IDataObject,
|
||||||
|
} as INodeUpdatePropertiesInformation;
|
||||||
|
|
||||||
|
telemetry.track('User set node enabled status', {
|
||||||
|
node_type: node.type,
|
||||||
|
is_enabled: node.disabled,
|
||||||
|
workflow_id: workflowsStore.workflowId,
|
||||||
|
});
|
||||||
|
|
||||||
|
workflowsStore.updateNodeProperties(updateInformation);
|
||||||
|
workflowsStore.clearNodeExecutionData(node.name);
|
||||||
|
updateNodeParameterIssues(node);
|
||||||
|
updateNodeCredentialIssues(node);
|
||||||
|
updateNodesInputIssues();
|
||||||
|
if (trackHistory) {
|
||||||
|
historyStore.pushCommandToUndo(
|
||||||
|
new EnableNodeToggleCommand(node.name, oldState === true, node.disabled === true),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (trackHistory) {
|
||||||
|
historyStore.stopRecordingUndo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNodeSubtitle(
|
||||||
|
data: INode,
|
||||||
|
nodeType: INodeTypeDescription,
|
||||||
|
workflow: Workflow,
|
||||||
|
): string | undefined {
|
||||||
|
if (!data) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.notesInFlow) {
|
||||||
|
return data.notes;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nodeType?.subtitle !== undefined) {
|
||||||
|
try {
|
||||||
|
ExpressionEvaluatorProxy.setEvaluator(
|
||||||
|
useSettingsStore().settings.expressions?.evaluator ?? 'tmpl',
|
||||||
|
);
|
||||||
|
return workflow.expression.getSimpleParameterValue(
|
||||||
|
data,
|
||||||
|
nodeType.subtitle,
|
||||||
|
'internal',
|
||||||
|
{},
|
||||||
|
undefined,
|
||||||
|
PLACEHOLDER_FILLED_AT_EXECUTION_TIME,
|
||||||
|
) as string | undefined;
|
||||||
|
} catch (e) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.parameters.operation !== undefined) {
|
||||||
|
const operation = data.parameters.operation as string;
|
||||||
|
if (nodeType === null) {
|
||||||
|
return operation;
|
||||||
|
}
|
||||||
|
|
||||||
|
const operationData = nodeType.properties.find((property: INodeProperties) => {
|
||||||
|
return property.name === 'operation';
|
||||||
|
});
|
||||||
|
if (operationData === undefined) {
|
||||||
|
return operation;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operationData.options === undefined) {
|
||||||
|
return operation;
|
||||||
|
}
|
||||||
|
|
||||||
|
const optionData = operationData.options.find((option) => {
|
||||||
|
return (option as INodePropertyOptions).value === data.parameters.operation;
|
||||||
|
});
|
||||||
|
if (optionData === undefined) {
|
||||||
|
return operation;
|
||||||
|
}
|
||||||
|
|
||||||
|
return optionData.name;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
hasProxyAuth,
|
||||||
|
isCustomApiCallSelected,
|
||||||
|
getParameterValue,
|
||||||
|
displayParameter,
|
||||||
|
getNodeIssues,
|
||||||
|
refreshNodeIssues,
|
||||||
|
updateNodesInputIssues,
|
||||||
|
updateNodesExecutionIssues,
|
||||||
|
updateNodeCredentialIssuesByName,
|
||||||
|
updateNodeCredentialIssues,
|
||||||
|
updateNodeParameterIssuesByName,
|
||||||
|
updateNodeParameterIssues,
|
||||||
|
getBinaryData,
|
||||||
|
disableNodes,
|
||||||
|
getNodeSubtitle,
|
||||||
|
updateNodesCredentialsIssues,
|
||||||
|
getNodeInputData,
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,743 +0,0 @@
|
||||||
import { EnableNodeToggleCommand } from './../models/history';
|
|
||||||
import { useHistoryStore } from '@/stores/history.store';
|
|
||||||
import { PLACEHOLDER_FILLED_AT_EXECUTION_TIME, CUSTOM_API_CALL_KEY } from '@/constants';
|
|
||||||
|
|
||||||
import type {
|
|
||||||
ConnectionTypes,
|
|
||||||
IBinaryKeyData,
|
|
||||||
ICredentialType,
|
|
||||||
INodeCredentialDescription,
|
|
||||||
INodeCredentialsDetails,
|
|
||||||
INodeExecutionData,
|
|
||||||
INodeIssues,
|
|
||||||
INodeIssueObjectProperty,
|
|
||||||
INodeParameters,
|
|
||||||
INodeProperties,
|
|
||||||
INodeTypeDescription,
|
|
||||||
IRunData,
|
|
||||||
ITaskDataConnections,
|
|
||||||
INode,
|
|
||||||
INodePropertyOptions,
|
|
||||||
IDataObject,
|
|
||||||
Workflow,
|
|
||||||
INodeInputConfiguration,
|
|
||||||
} from 'n8n-workflow';
|
|
||||||
import { NodeHelpers, ExpressionEvaluatorProxy, NodeConnectionType } from 'n8n-workflow';
|
|
||||||
|
|
||||||
import type {
|
|
||||||
ICredentialsResponse,
|
|
||||||
INodeUi,
|
|
||||||
INodeUpdatePropertiesInformation,
|
|
||||||
IUser,
|
|
||||||
NodePanelType,
|
|
||||||
} from '@/Interface';
|
|
||||||
|
|
||||||
import { get } from 'lodash-es';
|
|
||||||
|
|
||||||
import { isObject } from '@/utils/objectUtils';
|
|
||||||
import { getCredentialPermissions } from '@/permissions';
|
|
||||||
import { mapStores } from 'pinia';
|
|
||||||
import { useSettingsStore } from '@/stores/settings.store';
|
|
||||||
import { hasPermission } from '@/rbac/permissions';
|
|
||||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
|
||||||
import { useRootStore } from '@/stores/n8nRoot.store';
|
|
||||||
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
|
||||||
import { useCredentialsStore } from '@/stores/credentials.store';
|
|
||||||
import { defineComponent } from 'vue';
|
|
||||||
import { useUsersStore } from '@/stores/users.store';
|
|
||||||
|
|
||||||
export const nodeHelpers = defineComponent({
|
|
||||||
computed: {
|
|
||||||
...mapStores(
|
|
||||||
useCredentialsStore,
|
|
||||||
useHistoryStore,
|
|
||||||
useNodeTypesStore,
|
|
||||||
useSettingsStore,
|
|
||||||
useWorkflowsStore,
|
|
||||||
useRootStore,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
hasProxyAuth(node: INodeUi): boolean {
|
|
||||||
return Object.keys(node.parameters).includes('nodeCredentialType');
|
|
||||||
},
|
|
||||||
|
|
||||||
isCustomApiCallSelected(nodeValues: INodeParameters): boolean {
|
|
||||||
const { parameters } = nodeValues;
|
|
||||||
|
|
||||||
if (!isObject(parameters)) return false;
|
|
||||||
|
|
||||||
return (
|
|
||||||
(parameters.resource !== undefined && parameters.resource.includes(CUSTOM_API_CALL_KEY)) ||
|
|
||||||
(parameters.operation !== undefined && parameters.operation.includes(CUSTOM_API_CALL_KEY))
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
// Returns the parameter value
|
|
||||||
getParameterValue(nodeValues: INodeParameters, parameterName: string, path: string) {
|
|
||||||
return get(nodeValues, path ? path + '.' + parameterName : parameterName);
|
|
||||||
},
|
|
||||||
|
|
||||||
// Returns if the given parameter should be displayed or not
|
|
||||||
displayParameter(
|
|
||||||
nodeValues: INodeParameters,
|
|
||||||
parameter: INodeProperties | INodeCredentialDescription,
|
|
||||||
path: string,
|
|
||||||
node: INodeUi | null,
|
|
||||||
) {
|
|
||||||
return NodeHelpers.displayParameterPath(nodeValues, parameter, path, node);
|
|
||||||
},
|
|
||||||
|
|
||||||
// Updates all the issues on all the nodes
|
|
||||||
refreshNodeIssues(): void {
|
|
||||||
const nodes = this.workflowsStore.allNodes;
|
|
||||||
const workflow = this.workflowsStore.getCurrentWorkflow();
|
|
||||||
let nodeType: INodeTypeDescription | null;
|
|
||||||
let foundNodeIssues: INodeIssues | null;
|
|
||||||
|
|
||||||
nodes.forEach((node) => {
|
|
||||||
if (node.disabled === true) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
nodeType = this.nodeTypesStore.getNodeType(node.type, node.typeVersion);
|
|
||||||
foundNodeIssues = this.getNodeIssues(nodeType, node, workflow);
|
|
||||||
if (foundNodeIssues !== null) {
|
|
||||||
node.issues = foundNodeIssues;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
// Returns all the issues of the node
|
|
||||||
getNodeIssues(
|
|
||||||
nodeType: INodeTypeDescription | null,
|
|
||||||
node: INodeUi,
|
|
||||||
workflow: Workflow,
|
|
||||||
ignoreIssues?: string[],
|
|
||||||
): INodeIssues | null {
|
|
||||||
const pinDataNodeNames = Object.keys(this.workflowsStore.getPinData || {});
|
|
||||||
|
|
||||||
let nodeIssues: INodeIssues | null = null;
|
|
||||||
ignoreIssues = ignoreIssues || [];
|
|
||||||
|
|
||||||
if (node.disabled === true || pinDataNodeNames.includes(node.name)) {
|
|
||||||
// Ignore issues on disabled and pindata nodes
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nodeType === null) {
|
|
||||||
// Node type is not known
|
|
||||||
if (!ignoreIssues.includes('typeUnknown')) {
|
|
||||||
nodeIssues = {
|
|
||||||
typeUnknown: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Node type is known
|
|
||||||
|
|
||||||
// Add potential parameter issues
|
|
||||||
if (!ignoreIssues.includes('parameters')) {
|
|
||||||
nodeIssues = NodeHelpers.getNodeParametersIssues(nodeType.properties, node);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ignoreIssues.includes('credentials')) {
|
|
||||||
// Add potential credential issues
|
|
||||||
const nodeCredentialIssues = this.getNodeCredentialIssues(node, nodeType);
|
|
||||||
if (nodeIssues === null) {
|
|
||||||
nodeIssues = nodeCredentialIssues;
|
|
||||||
} else {
|
|
||||||
NodeHelpers.mergeIssues(nodeIssues, nodeCredentialIssues);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const nodeInputIssues = this.getNodeInputIssues(workflow, node, nodeType);
|
|
||||||
if (nodeIssues === null) {
|
|
||||||
nodeIssues = nodeInputIssues;
|
|
||||||
} else {
|
|
||||||
NodeHelpers.mergeIssues(nodeIssues, nodeInputIssues);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.hasNodeExecutionIssues(node) && !ignoreIssues.includes('execution')) {
|
|
||||||
if (nodeIssues === null) {
|
|
||||||
nodeIssues = {};
|
|
||||||
}
|
|
||||||
nodeIssues.execution = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return nodeIssues;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Set the status on all the nodes which produced an error so that it can be
|
|
||||||
// displayed in the node-view
|
|
||||||
hasNodeExecutionIssues(node: INodeUi): boolean {
|
|
||||||
const workflowResultData = this.workflowsStore.getWorkflowRunData;
|
|
||||||
|
|
||||||
if (workflowResultData === null || !workflowResultData.hasOwnProperty(node.name)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const taskData of workflowResultData[node.name]) {
|
|
||||||
if (!taskData) return false;
|
|
||||||
|
|
||||||
if (taskData.error !== undefined) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
|
|
||||||
reportUnsetCredential(credentialType: ICredentialType) {
|
|
||||||
return {
|
|
||||||
credentials: {
|
|
||||||
[credentialType.name]: [
|
|
||||||
this.$locale.baseText('nodeHelpers.credentialsUnset', {
|
|
||||||
interpolate: {
|
|
||||||
credentialType: credentialType.displayName,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
updateNodesInputIssues() {
|
|
||||||
const nodes = this.workflowsStore.allNodes;
|
|
||||||
const workflow = this.workflowsStore.getCurrentWorkflow();
|
|
||||||
|
|
||||||
for (const node of nodes) {
|
|
||||||
const nodeType = this.nodeTypesStore.getNodeType(node.type, node.typeVersion);
|
|
||||||
if (!nodeType) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const nodeInputIssues = this.getNodeInputIssues(workflow, node, nodeType);
|
|
||||||
|
|
||||||
this.workflowsStore.setNodeIssue({
|
|
||||||
node: node.name,
|
|
||||||
type: 'input',
|
|
||||||
value: nodeInputIssues?.input ? nodeInputIssues.input : null,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Updates the execution issues.
|
|
||||||
updateNodesExecutionIssues() {
|
|
||||||
const nodes = this.workflowsStore.allNodes;
|
|
||||||
|
|
||||||
for (const node of nodes) {
|
|
||||||
this.workflowsStore.setNodeIssue({
|
|
||||||
node: node.name,
|
|
||||||
type: 'execution',
|
|
||||||
value: this.hasNodeExecutionIssues(node) ? true : null,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
updateNodeCredentialIssuesByName(name: string): void {
|
|
||||||
const node = this.workflowsStore.getNodeByName(name);
|
|
||||||
|
|
||||||
if (node) {
|
|
||||||
this.updateNodeCredentialIssues(node);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Updates the credential-issues of the node
|
|
||||||
updateNodeCredentialIssues(node: INodeUi): void {
|
|
||||||
const fullNodeIssues: INodeIssues | null = this.getNodeCredentialIssues(node);
|
|
||||||
|
|
||||||
let newIssues: INodeIssueObjectProperty | null = null;
|
|
||||||
if (fullNodeIssues !== null) {
|
|
||||||
newIssues = fullNodeIssues.credentials!;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.workflowsStore.setNodeIssue({
|
|
||||||
node: node.name,
|
|
||||||
type: 'credentials',
|
|
||||||
value: newIssues,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
updateNodeParameterIssuesByName(name: string): void {
|
|
||||||
const node = this.workflowsStore.getNodeByName(name);
|
|
||||||
|
|
||||||
if (node) {
|
|
||||||
this.updateNodeParameterIssues(node);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Updates the parameter-issues of the node
|
|
||||||
updateNodeParameterIssues(node: INodeUi, nodeType?: INodeTypeDescription): void {
|
|
||||||
if (nodeType === undefined) {
|
|
||||||
nodeType = this.nodeTypesStore.getNodeType(node.type, node.typeVersion);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nodeType === null) {
|
|
||||||
// Could not find nodeType so can not update issues
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// All data got updated everywhere so update now the issues
|
|
||||||
const fullNodeIssues: INodeIssues | null = NodeHelpers.getNodeParametersIssues(
|
|
||||||
nodeType!.properties,
|
|
||||||
node,
|
|
||||||
);
|
|
||||||
|
|
||||||
let newIssues: INodeIssueObjectProperty | null = null;
|
|
||||||
if (fullNodeIssues !== null) {
|
|
||||||
newIssues = fullNodeIssues.parameters!;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.workflowsStore.setNodeIssue({
|
|
||||||
node: node.name,
|
|
||||||
type: 'parameters',
|
|
||||||
value: newIssues,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
// Returns all the input-issues of the node
|
|
||||||
getNodeInputIssues(
|
|
||||||
workflow: Workflow,
|
|
||||||
node: INodeUi,
|
|
||||||
nodeType?: INodeTypeDescription,
|
|
||||||
): INodeIssues | null {
|
|
||||||
const foundIssues: INodeIssueObjectProperty = {};
|
|
||||||
|
|
||||||
const workflowNode = workflow.getNode(node.name);
|
|
||||||
let inputs: Array<ConnectionTypes | INodeInputConfiguration> = [];
|
|
||||||
if (nodeType && workflowNode) {
|
|
||||||
inputs = NodeHelpers.getNodeInputs(workflow, workflowNode, nodeType);
|
|
||||||
}
|
|
||||||
|
|
||||||
inputs.forEach((input) => {
|
|
||||||
if (typeof input === 'string' || input.required !== true) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const parentNodes = workflow.getParentNodes(node.name, input.type, 1);
|
|
||||||
|
|
||||||
if (parentNodes.length === 0) {
|
|
||||||
// We want to show different error for missing AI subnodes
|
|
||||||
if (input.type.startsWith('ai_')) {
|
|
||||||
foundIssues[input.type] = [
|
|
||||||
this.$locale.baseText('nodeIssues.input.missingSubNode', {
|
|
||||||
interpolate: {
|
|
||||||
inputName: input.displayName?.toLocaleLowerCase() ?? input.type,
|
|
||||||
inputType: input.type,
|
|
||||||
node: node.name,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
} else {
|
|
||||||
foundIssues[input.type] = [
|
|
||||||
this.$locale.baseText('nodeIssues.input.missing', {
|
|
||||||
interpolate: { inputName: input.displayName ?? input.type },
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (Object.keys(foundIssues).length) {
|
|
||||||
return {
|
|
||||||
input: foundIssues,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Returns all the credential-issues of the node
|
|
||||||
getNodeCredentialIssues(node: INodeUi, nodeType?: INodeTypeDescription): INodeIssues | null {
|
|
||||||
if (node.disabled) {
|
|
||||||
// Node is disabled
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!nodeType) {
|
|
||||||
nodeType = this.nodeTypesStore.getNodeType(node.type, node.typeVersion);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!nodeType?.credentials) {
|
|
||||||
// Node does not need any credentials or nodeType could not be found
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const foundIssues: INodeIssueObjectProperty = {};
|
|
||||||
|
|
||||||
let userCredentials: ICredentialsResponse[] | null;
|
|
||||||
let credentialType: ICredentialType | undefined;
|
|
||||||
let credentialDisplayName: string;
|
|
||||||
let selectedCredentials: INodeCredentialsDetails;
|
|
||||||
|
|
||||||
const { authentication, genericAuthType, nodeCredentialType } =
|
|
||||||
node.parameters as HttpRequestNode.V2.AuthParams;
|
|
||||||
|
|
||||||
if (
|
|
||||||
authentication === 'genericCredentialType' &&
|
|
||||||
genericAuthType !== '' &&
|
|
||||||
selectedCredsAreUnusable(node, genericAuthType)
|
|
||||||
) {
|
|
||||||
const credential = this.credentialsStore.getCredentialTypeByName(genericAuthType);
|
|
||||||
return credential ? this.reportUnsetCredential(credential) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
this.hasProxyAuth(node) &&
|
|
||||||
authentication === 'predefinedCredentialType' &&
|
|
||||||
nodeCredentialType !== '' &&
|
|
||||||
node.credentials !== undefined
|
|
||||||
) {
|
|
||||||
const stored = this.credentialsStore.getCredentialsByType(nodeCredentialType);
|
|
||||||
|
|
||||||
if (selectedCredsDoNotExist(node, nodeCredentialType, stored)) {
|
|
||||||
const credential = this.credentialsStore.getCredentialTypeByName(nodeCredentialType);
|
|
||||||
return credential ? this.reportUnsetCredential(credential) : null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
this.hasProxyAuth(node) &&
|
|
||||||
authentication === 'predefinedCredentialType' &&
|
|
||||||
nodeCredentialType !== '' &&
|
|
||||||
selectedCredsAreUnusable(node, nodeCredentialType)
|
|
||||||
) {
|
|
||||||
const credential = this.credentialsStore.getCredentialTypeByName(nodeCredentialType);
|
|
||||||
return credential ? this.reportUnsetCredential(credential) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const credentialTypeDescription of nodeType.credentials) {
|
|
||||||
// Check if credentials should be displayed else ignore
|
|
||||||
if (!this.displayParameter(node.parameters, credentialTypeDescription, '', node)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the display name of the credential type
|
|
||||||
credentialType = this.credentialsStore.getCredentialTypeByName(
|
|
||||||
credentialTypeDescription.name,
|
|
||||||
);
|
|
||||||
if (credentialType === null) {
|
|
||||||
credentialDisplayName = credentialTypeDescription.name;
|
|
||||||
} else {
|
|
||||||
credentialDisplayName = credentialType.displayName;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!node.credentials?.[credentialTypeDescription.name]) {
|
|
||||||
// Credentials are not set
|
|
||||||
if (credentialTypeDescription.required) {
|
|
||||||
foundIssues[credentialTypeDescription.name] = [
|
|
||||||
this.$locale.baseText('nodeIssues.credentials.notSet', {
|
|
||||||
interpolate: { type: nodeType.displayName },
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// If they are set check if the value is valid
|
|
||||||
selectedCredentials = node.credentials[credentialTypeDescription.name];
|
|
||||||
if (typeof selectedCredentials === 'string') {
|
|
||||||
selectedCredentials = {
|
|
||||||
id: null,
|
|
||||||
name: selectedCredentials,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const usersStore = useUsersStore();
|
|
||||||
const currentUser = usersStore.currentUser || ({} as IUser);
|
|
||||||
userCredentials = this.credentialsStore
|
|
||||||
.getCredentialsByType(credentialTypeDescription.name)
|
|
||||||
.filter((credential: ICredentialsResponse) => {
|
|
||||||
const permissions = getCredentialPermissions(currentUser, credential);
|
|
||||||
return permissions.read;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (userCredentials === null) {
|
|
||||||
userCredentials = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedCredentials.id) {
|
|
||||||
const idMatch = userCredentials.find(
|
|
||||||
(credentialData) => credentialData.id === selectedCredentials.id,
|
|
||||||
);
|
|
||||||
if (idMatch) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const nameMatches = userCredentials.filter(
|
|
||||||
(credentialData) => credentialData.name === selectedCredentials.name,
|
|
||||||
);
|
|
||||||
if (nameMatches.length > 1) {
|
|
||||||
foundIssues[credentialTypeDescription.name] = [
|
|
||||||
this.$locale.baseText('nodeIssues.credentials.notIdentified', {
|
|
||||||
interpolate: { name: selectedCredentials.name, type: credentialDisplayName },
|
|
||||||
}),
|
|
||||||
this.$locale.baseText('nodeIssues.credentials.notIdentified.hint'),
|
|
||||||
];
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nameMatches.length === 0) {
|
|
||||||
const isCredentialUsedInWorkflow =
|
|
||||||
this.workflowsStore.usedCredentials?.[selectedCredentials.id as string];
|
|
||||||
|
|
||||||
if (
|
|
||||||
!isCredentialUsedInWorkflow &&
|
|
||||||
!hasPermission(['rbac'], { rbac: { scope: 'credential:read' } })
|
|
||||||
) {
|
|
||||||
foundIssues[credentialTypeDescription.name] = [
|
|
||||||
this.$locale.baseText('nodeIssues.credentials.doNotExist', {
|
|
||||||
interpolate: { name: selectedCredentials.name, type: credentialDisplayName },
|
|
||||||
}),
|
|
||||||
this.$locale.baseText('nodeIssues.credentials.doNotExist.hint'),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Could later check also if the node has access to the credentials
|
|
||||||
if (Object.keys(foundIssues).length === 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
credentials: foundIssues,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
// Updates the node credential issues
|
|
||||||
updateNodesCredentialsIssues() {
|
|
||||||
const nodes = this.workflowsStore.allNodes;
|
|
||||||
let issues: INodeIssues | null;
|
|
||||||
|
|
||||||
for (const node of nodes) {
|
|
||||||
issues = this.getNodeCredentialIssues(node);
|
|
||||||
|
|
||||||
this.workflowsStore.setNodeIssue({
|
|
||||||
node: node.name,
|
|
||||||
type: 'credentials',
|
|
||||||
value: issues === null ? null : issues.credentials,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
getNodeInputData(
|
|
||||||
node: INodeUi | null,
|
|
||||||
runIndex = 0,
|
|
||||||
outputIndex = 0,
|
|
||||||
paneType: NodePanelType = 'output',
|
|
||||||
connectionType: ConnectionTypes = NodeConnectionType.Main,
|
|
||||||
): INodeExecutionData[] {
|
|
||||||
if (node === null) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
if (this.workflowsStore.getWorkflowExecution === null) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const executionData = this.workflowsStore.getWorkflowExecution.data;
|
|
||||||
if (!executionData?.resultData) {
|
|
||||||
// unknown status
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
const runData = executionData.resultData.runData;
|
|
||||||
|
|
||||||
const taskData = get(runData, `[${node.name}][${runIndex}]`);
|
|
||||||
if (!taskData) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
let data: ITaskDataConnections | undefined = taskData.data!;
|
|
||||||
if (paneType === 'input' && taskData.inputOverride) {
|
|
||||||
data = taskData.inputOverride!;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!data) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.getInputData(data, outputIndex, connectionType);
|
|
||||||
},
|
|
||||||
|
|
||||||
// Returns the data of the main input
|
|
||||||
getInputData(
|
|
||||||
connectionsData: ITaskDataConnections,
|
|
||||||
outputIndex: number,
|
|
||||||
connectionType: ConnectionTypes = NodeConnectionType.Main,
|
|
||||||
): INodeExecutionData[] {
|
|
||||||
if (
|
|
||||||
!connectionsData ||
|
|
||||||
!connectionsData.hasOwnProperty(connectionType) ||
|
|
||||||
connectionsData[connectionType] === undefined ||
|
|
||||||
connectionsData[connectionType].length < outputIndex ||
|
|
||||||
connectionsData[connectionType][outputIndex] === null
|
|
||||||
) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return connectionsData[connectionType][outputIndex] as INodeExecutionData[];
|
|
||||||
},
|
|
||||||
|
|
||||||
// Returns all the binary data of all the entries
|
|
||||||
getBinaryData(
|
|
||||||
workflowRunData: IRunData | null,
|
|
||||||
node: string | null,
|
|
||||||
runIndex: number,
|
|
||||||
outputIndex: number,
|
|
||||||
connectionType: ConnectionTypes = NodeConnectionType.Main,
|
|
||||||
): IBinaryKeyData[] {
|
|
||||||
if (node === null) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const runData: IRunData | null = workflowRunData;
|
|
||||||
|
|
||||||
if (!runData?.[node]?.[runIndex]?.data) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const inputData = this.getInputData(
|
|
||||||
runData[node][runIndex].data!,
|
|
||||||
outputIndex,
|
|
||||||
connectionType,
|
|
||||||
);
|
|
||||||
|
|
||||||
const returnData: IBinaryKeyData[] = [];
|
|
||||||
for (let i = 0; i < inputData.length; i++) {
|
|
||||||
if (inputData[i].hasOwnProperty('binary') && inputData[i].binary !== undefined) {
|
|
||||||
returnData.push(inputData[i].binary!);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return returnData;
|
|
||||||
},
|
|
||||||
|
|
||||||
disableNodes(nodes: INodeUi[], trackHistory = false) {
|
|
||||||
if (trackHistory) {
|
|
||||||
this.historyStore.startRecordingUndo();
|
|
||||||
}
|
|
||||||
for (const node of nodes) {
|
|
||||||
const oldState = node.disabled;
|
|
||||||
// Toggle disabled flag
|
|
||||||
const updateInformation = {
|
|
||||||
name: node.name,
|
|
||||||
properties: {
|
|
||||||
disabled: !oldState,
|
|
||||||
} as IDataObject,
|
|
||||||
} as INodeUpdatePropertiesInformation;
|
|
||||||
|
|
||||||
this.$telemetry.track('User set node enabled status', {
|
|
||||||
node_type: node.type,
|
|
||||||
is_enabled: node.disabled,
|
|
||||||
workflow_id: this.workflowsStore.workflowId,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.workflowsStore.updateNodeProperties(updateInformation);
|
|
||||||
this.workflowsStore.clearNodeExecutionData(node.name);
|
|
||||||
this.updateNodeParameterIssues(node);
|
|
||||||
this.updateNodeCredentialIssues(node);
|
|
||||||
this.updateNodesInputIssues();
|
|
||||||
if (trackHistory) {
|
|
||||||
this.historyStore.pushCommandToUndo(
|
|
||||||
new EnableNodeToggleCommand(node.name, oldState === true, node.disabled === true),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (trackHistory) {
|
|
||||||
this.historyStore.stopRecordingUndo();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// @ts-ignore
|
|
||||||
getNodeSubtitle(data, nodeType, workflow): string | undefined {
|
|
||||||
if (!data) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.notesInFlow) {
|
|
||||||
return data.notes;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nodeType !== null && nodeType.subtitle !== undefined) {
|
|
||||||
try {
|
|
||||||
ExpressionEvaluatorProxy.setEvaluator(
|
|
||||||
useSettingsStore().settings.expressions?.evaluator ?? 'tmpl',
|
|
||||||
);
|
|
||||||
return workflow.expression.getSimpleParameterValue(
|
|
||||||
data as INode,
|
|
||||||
nodeType.subtitle,
|
|
||||||
'internal',
|
|
||||||
{},
|
|
||||||
undefined,
|
|
||||||
PLACEHOLDER_FILLED_AT_EXECUTION_TIME,
|
|
||||||
) as string | undefined;
|
|
||||||
} catch (e) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.parameters.operation !== undefined) {
|
|
||||||
const operation = data.parameters.operation as string;
|
|
||||||
if (nodeType === null) {
|
|
||||||
return operation;
|
|
||||||
}
|
|
||||||
|
|
||||||
const operationData: INodeProperties = nodeType.properties.find(
|
|
||||||
(property: INodeProperties) => {
|
|
||||||
return property.name === 'operation';
|
|
||||||
},
|
|
||||||
);
|
|
||||||
if (operationData === undefined) {
|
|
||||||
return operation;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (operationData.options === undefined) {
|
|
||||||
return operation;
|
|
||||||
}
|
|
||||||
|
|
||||||
const optionData = operationData.options.find((option) => {
|
|
||||||
return (option as INodePropertyOptions).value === data.parameters.operation;
|
|
||||||
});
|
|
||||||
if (optionData === undefined) {
|
|
||||||
return operation;
|
|
||||||
}
|
|
||||||
|
|
||||||
return optionData.name;
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether the node has no selected credentials, or none of the node's
|
|
||||||
* selected credentials are of the specified type.
|
|
||||||
*/
|
|
||||||
function selectedCredsAreUnusable(node: INodeUi, credentialType: string) {
|
|
||||||
return !node.credentials || !Object.keys(node.credentials).includes(credentialType);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether the node's selected credentials of the specified type
|
|
||||||
* can no longer be found in the database.
|
|
||||||
*/
|
|
||||||
function selectedCredsDoNotExist(
|
|
||||||
node: INodeUi,
|
|
||||||
nodeCredentialType: string,
|
|
||||||
storedCredsByType: ICredentialsResponse[] | null,
|
|
||||||
) {
|
|
||||||
if (!node.credentials || !storedCredsByType) return false;
|
|
||||||
|
|
||||||
const selectedCredsByType = node.credentials[nodeCredentialType];
|
|
||||||
|
|
||||||
if (!selectedCredsByType) return false;
|
|
||||||
|
|
||||||
return !storedCredsByType.find((c) => c.id === selectedCredsByType.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
declare namespace HttpRequestNode {
|
|
||||||
namespace V2 {
|
|
||||||
type AuthParams = {
|
|
||||||
authentication: 'none' | 'genericCredentialType' | 'predefinedCredentialType';
|
|
||||||
genericAuthType: string;
|
|
||||||
nodeCredentialType: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -5,7 +5,7 @@ import type {
|
||||||
IPushDataExecutionFinished,
|
IPushDataExecutionFinished,
|
||||||
} from '@/Interface';
|
} from '@/Interface';
|
||||||
|
|
||||||
import { nodeHelpers } from '@/mixins/nodeHelpers';
|
import { useNodeHelpers } from '@/composables/useNodeHelpers';
|
||||||
import { useTitleChange } from '@/composables/useTitleChange';
|
import { useTitleChange } from '@/composables/useTitleChange';
|
||||||
import { useToast } from '@/composables/useToast';
|
import { useToast } from '@/composables/useToast';
|
||||||
import { workflowHelpers } from '@/mixins/workflowHelpers';
|
import { workflowHelpers } from '@/mixins/workflowHelpers';
|
||||||
|
@ -45,6 +45,7 @@ export const pushConnection = defineComponent({
|
||||||
return {
|
return {
|
||||||
...useTitleChange(),
|
...useTitleChange(),
|
||||||
...useToast(),
|
...useToast(),
|
||||||
|
nodeHelpers: useNodeHelpers(),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
|
@ -52,7 +53,7 @@ export const pushConnection = defineComponent({
|
||||||
void this.pushMessageReceived(message);
|
void this.pushMessageReceived(message);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
mixins: [nodeHelpers, workflowHelpers],
|
mixins: [workflowHelpers],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
retryTimeout: null as NodeJS.Timeout | null,
|
retryTimeout: null as NodeJS.Timeout | null,
|
||||||
|
@ -504,7 +505,7 @@ export const pushConnection = defineComponent({
|
||||||
|
|
||||||
// Set the node execution issues on all the nodes which produced an error so that
|
// Set the node execution issues on all the nodes which produced an error so that
|
||||||
// it can be displayed in the node-view
|
// it can be displayed in the node-view
|
||||||
this.updateNodesExecutionIssues();
|
this.nodeHelpers.updateNodesExecutionIssues();
|
||||||
|
|
||||||
const lastNodeExecuted: string | undefined =
|
const lastNodeExecuted: string | undefined =
|
||||||
runDataExecuted.data.resultData.lastNodeExecuted;
|
runDataExecuted.data.resultData.lastNodeExecuted;
|
||||||
|
|
|
@ -47,8 +47,8 @@ import type {
|
||||||
|
|
||||||
import { useMessage } from '@/composables/useMessage';
|
import { useMessage } from '@/composables/useMessage';
|
||||||
import { useToast } from '@/composables/useToast';
|
import { useToast } from '@/composables/useToast';
|
||||||
|
import { useNodeHelpers } from '@/composables/useNodeHelpers';
|
||||||
import { genericHelpers } from '@/mixins/genericHelpers';
|
import { genericHelpers } from '@/mixins/genericHelpers';
|
||||||
import { nodeHelpers } from '@/mixins/nodeHelpers';
|
|
||||||
|
|
||||||
import { get, isEqual } from 'lodash-es';
|
import { get, isEqual } from 'lodash-es';
|
||||||
|
|
||||||
|
@ -474,11 +474,13 @@ export function executeData(
|
||||||
}
|
}
|
||||||
|
|
||||||
export const workflowHelpers = defineComponent({
|
export const workflowHelpers = defineComponent({
|
||||||
mixins: [nodeHelpers, genericHelpers],
|
mixins: [genericHelpers],
|
||||||
setup() {
|
setup() {
|
||||||
|
const nodeHelpers = useNodeHelpers();
|
||||||
return {
|
return {
|
||||||
...useToast(),
|
...useToast(),
|
||||||
...useMessage(),
|
...useMessage(),
|
||||||
|
nodeHelpers,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -612,7 +614,9 @@ export const workflowHelpers = defineComponent({
|
||||||
typeUnknown: true,
|
typeUnknown: true,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
nodeIssues = this.getNodeIssues(nodeType.description, node, workflow, ['execution']);
|
nodeIssues = useNodeHelpers().getNodeIssues(nodeType.description, node, workflow, [
|
||||||
|
'execution',
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nodeIssues !== null) {
|
if (nodeIssues !== null) {
|
||||||
|
@ -714,7 +718,7 @@ export const workflowHelpers = defineComponent({
|
||||||
const saveCredentials: INodeCredentials = {};
|
const saveCredentials: INodeCredentials = {};
|
||||||
for (const nodeCredentialTypeName of Object.keys(node.credentials)) {
|
for (const nodeCredentialTypeName of Object.keys(node.credentials)) {
|
||||||
if (
|
if (
|
||||||
this.hasProxyAuth(node) ||
|
useNodeHelpers().hasProxyAuth(node) ||
|
||||||
Object.keys(node.parameters).includes('genericAuthType')
|
Object.keys(node.parameters).includes('genericAuthType')
|
||||||
) {
|
) {
|
||||||
saveCredentials[nodeCredentialTypeName] = node.credentials[nodeCredentialTypeName];
|
saveCredentials[nodeCredentialTypeName] = node.credentials[nodeCredentialTypeName];
|
||||||
|
@ -723,7 +727,7 @@ export const workflowHelpers = defineComponent({
|
||||||
|
|
||||||
const credentialTypeDescription = nodeType.credentials
|
const credentialTypeDescription = nodeType.credentials
|
||||||
// filter out credentials with same name in different node versions
|
// filter out credentials with same name in different node versions
|
||||||
.filter((c) => this.displayParameter(node.parameters, c, '', node))
|
.filter((c) => useNodeHelpers().displayParameter(node.parameters, c, '', node))
|
||||||
.find((c) => c.name === nodeCredentialTypeName);
|
.find((c) => c.name === nodeCredentialTypeName);
|
||||||
|
|
||||||
if (credentialTypeDescription === undefined) {
|
if (credentialTypeDescription === undefined) {
|
||||||
|
@ -731,7 +735,14 @@ export const workflowHelpers = defineComponent({
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.displayParameter(node.parameters, credentialTypeDescription, '', node)) {
|
if (
|
||||||
|
!useNodeHelpers().displayParameter(
|
||||||
|
node.parameters,
|
||||||
|
credentialTypeDescription,
|
||||||
|
'',
|
||||||
|
node,
|
||||||
|
)
|
||||||
|
) {
|
||||||
// Credential should not be displayed so do also not save
|
// Credential should not be displayed so do also not save
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -1026,9 +1037,8 @@ export const workflowHelpers = defineComponent({
|
||||||
const workflowData = await this.workflowsStore.createNewWorkflow(workflowDataRequest);
|
const workflowData = await this.workflowsStore.createNewWorkflow(workflowDataRequest);
|
||||||
|
|
||||||
this.workflowsStore.addWorkflow(workflowData);
|
this.workflowsStore.addWorkflow(workflowData);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
this.settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Sharing) &&
|
useSettingsStore().isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Sharing) &&
|
||||||
this.usersStore.currentUser
|
this.usersStore.currentUser
|
||||||
) {
|
) {
|
||||||
this.workflowsEEStore.setWorkflowOwnedBy({
|
this.workflowsEEStore.setWorkflowOwnedBy({
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
import { useToast } from '@/composables/useToast';
|
import { useToast } from '@/composables/useToast';
|
||||||
|
import { useNodeHelpers } from '@/composables/useNodeHelpers';
|
||||||
import { workflowHelpers } from '@/mixins/workflowHelpers';
|
import { workflowHelpers } from '@/mixins/workflowHelpers';
|
||||||
|
|
||||||
import { useTitleChange } from '@/composables/useTitleChange';
|
import { useTitleChange } from '@/composables/useTitleChange';
|
||||||
|
@ -24,9 +25,12 @@ import { useExternalHooks } from '@/composables/useExternalHooks';
|
||||||
export const workflowRun = defineComponent({
|
export const workflowRun = defineComponent({
|
||||||
mixins: [workflowHelpers],
|
mixins: [workflowHelpers],
|
||||||
setup() {
|
setup() {
|
||||||
|
const nodeHelpers = useNodeHelpers();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...useTitleChange(),
|
...useTitleChange(),
|
||||||
...useToast(),
|
...useToast(),
|
||||||
|
nodeHelpers,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -83,7 +87,7 @@ export const workflowRun = defineComponent({
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Check first if the workflow has any issues before execute it
|
// Check first if the workflow has any issues before execute it
|
||||||
this.refreshNodeIssues();
|
this.nodeHelpers.refreshNodeIssues();
|
||||||
const issuesExist = this.workflowsStore.nodesIssuesExist;
|
const issuesExist = this.workflowsStore.nodesIssuesExist;
|
||||||
if (issuesExist) {
|
if (issuesExist) {
|
||||||
// If issues exist get all of the issues of all nodes
|
// If issues exist get all of the issues of all nodes
|
||||||
|
@ -265,7 +269,7 @@ export const workflowRun = defineComponent({
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
this.workflowsStore.setWorkflowExecutionData(executionData);
|
this.workflowsStore.setWorkflowExecutionData(executionData);
|
||||||
this.updateNodesExecutionIssues();
|
this.nodeHelpers.updateNodesExecutionIssues();
|
||||||
|
|
||||||
const runWorkflowApiResponse = await this.runWorkflowApi(startRunData);
|
const runWorkflowApiResponse = await this.runWorkflowApi(startRunData);
|
||||||
|
|
||||||
|
|
|
@ -238,8 +238,9 @@ import {
|
||||||
import { copyPaste } from '@/mixins/copyPaste';
|
import { copyPaste } from '@/mixins/copyPaste';
|
||||||
import { genericHelpers } from '@/mixins/genericHelpers';
|
import { genericHelpers } from '@/mixins/genericHelpers';
|
||||||
import { moveNodeWorkflow } from '@/mixins/moveNodeWorkflow';
|
import { moveNodeWorkflow } from '@/mixins/moveNodeWorkflow';
|
||||||
import { nodeHelpers } from '@/mixins/nodeHelpers';
|
|
||||||
import useGlobalLinkActions from '@/composables/useGlobalLinkActions';
|
import useGlobalLinkActions from '@/composables/useGlobalLinkActions';
|
||||||
|
import { useNodeHelpers } from '@/composables/useNodeHelpers';
|
||||||
import useCanvasMouseSelect from '@/composables/useCanvasMouseSelect';
|
import useCanvasMouseSelect from '@/composables/useCanvasMouseSelect';
|
||||||
import { useExecutionDebugging } from '@/composables/useExecutionDebugging';
|
import { useExecutionDebugging } from '@/composables/useExecutionDebugging';
|
||||||
import { useTitleChange } from '@/composables/useTitleChange';
|
import { useTitleChange } from '@/composables/useTitleChange';
|
||||||
|
@ -386,7 +387,6 @@ export default defineComponent({
|
||||||
workflowHelpers,
|
workflowHelpers,
|
||||||
workflowRun,
|
workflowRun,
|
||||||
debounceHelper,
|
debounceHelper,
|
||||||
nodeHelpers,
|
|
||||||
pinData,
|
pinData,
|
||||||
],
|
],
|
||||||
components: {
|
components: {
|
||||||
|
@ -404,11 +404,13 @@ export default defineComponent({
|
||||||
const locale = useI18n();
|
const locale = useI18n();
|
||||||
const contextMenu = useContextMenu();
|
const contextMenu = useContextMenu();
|
||||||
const dataSchema = useDataSchema();
|
const dataSchema = useDataSchema();
|
||||||
|
const nodeHelpers = useNodeHelpers();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
locale,
|
locale,
|
||||||
contextMenu,
|
contextMenu,
|
||||||
dataSchema,
|
dataSchema,
|
||||||
|
nodeHelpers,
|
||||||
externalHooks,
|
externalHooks,
|
||||||
...useCanvasMouseSelect(),
|
...useCanvasMouseSelect(),
|
||||||
...useGlobalLinkActions(),
|
...useGlobalLinkActions(),
|
||||||
|
@ -871,7 +873,7 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
clearExecutionData() {
|
clearExecutionData() {
|
||||||
this.workflowsStore.workflowExecutionData = null;
|
this.workflowsStore.workflowExecutionData = null;
|
||||||
this.updateNodesExecutionIssues();
|
this.nodeHelpers.updateNodesExecutionIssues();
|
||||||
},
|
},
|
||||||
async onSaveKeyboardShortcut(e: KeyboardEvent) {
|
async onSaveKeyboardShortcut(e: KeyboardEvent) {
|
||||||
let saved = await this.saveCurrentWorkflow();
|
let saved = await this.saveCurrentWorkflow();
|
||||||
|
@ -1442,7 +1444,7 @@ export default defineComponent({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.disableNodes(nodes, true);
|
this.nodeHelpers.disableNodes(nodes, true);
|
||||||
},
|
},
|
||||||
|
|
||||||
togglePinNodes(nodes: INode[], source: PinDataSource) {
|
togglePinNodes(nodes: INode[], source: PinDataSource) {
|
||||||
|
@ -2758,7 +2760,7 @@ export default defineComponent({
|
||||||
if (!this.suspendRecordingDetachedConnections) {
|
if (!this.suspendRecordingDetachedConnections) {
|
||||||
this.historyStore.pushCommandToUndo(new AddConnectionCommand(connectionData));
|
this.historyStore.pushCommandToUndo(new AddConnectionCommand(connectionData));
|
||||||
}
|
}
|
||||||
this.updateNodesInputIssues();
|
this.nodeHelpers.updateNodesInputIssues();
|
||||||
this.resetEndpointsErrors();
|
this.resetEndpointsErrors();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -2932,7 +2934,7 @@ export default defineComponent({
|
||||||
this.historyStore.pushCommandToUndo(removeCommand);
|
this.historyStore.pushCommandToUndo(removeCommand);
|
||||||
}
|
}
|
||||||
|
|
||||||
void this.updateNodesInputIssues();
|
void this.nodeHelpers.updateNodesInputIssues();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
|
@ -3998,7 +4000,7 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the node issues at the end as the node-connections are required
|
// Add the node issues at the end as the node-connections are required
|
||||||
void this.refreshNodeIssues();
|
void this.nodeHelpers.refreshNodeIssues();
|
||||||
|
|
||||||
// Now it can draw again
|
// Now it can draw again
|
||||||
this.instance?.setSuspendDrawing(false, true);
|
this.instance?.setSuspendDrawing(false, true);
|
||||||
|
@ -4551,7 +4553,7 @@ export default defineComponent({
|
||||||
onRevertEnableToggle({ nodeName, isDisabled }: { nodeName: string; isDisabled: boolean }) {
|
onRevertEnableToggle({ nodeName, isDisabled }: { nodeName: string; isDisabled: boolean }) {
|
||||||
const node = this.workflowsStore.getNodeByName(nodeName);
|
const node = this.workflowsStore.getNodeByName(nodeName);
|
||||||
if (node) {
|
if (node) {
|
||||||
this.disableNodes([node]);
|
this.nodeHelpers.disableNodes([node]);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onPageShow(e: PageTransitionEvent) {
|
onPageShow(e: PageTransitionEvent) {
|
||||||
|
|
Loading…
Reference in a new issue