Skip to content

Commit

Permalink
N8N-4175 resource locator component (#3812)
Browse files Browse the repository at this point in the history
* ✨ Implemented initial version of resource locator component

* ✨ Implemented front-end validation for resource locator component. Improved responsiveness. Minor refactoring.

* ⚡ Setting resource locator default state to list. Updating hover states and expand icon.

* 🔨 Moving resource locator component to `ParameterInput` from `ParameterInputFull

* 🔨 Moving `ResourceLocator` to a separate Vue component

* 🔨 Implementing expression and drag'n'drop support in ResourceLocator` component

* 🔨 Cleaning up `ResourceLocator` component code

* ✨ Implemented resource locator selected mode persistance

* 💄 Minor refactoring and fixes in `ResourceLocator`

* 🔨 Updating `ResourceLocator` front-end validation logic

* ⚡ Saving resource locator mode in node parameters

* 💄 Updating the `ResourceLocator` component based on the design review

* 🐛 Fixing resource locator mode parameters handling when loading node parameter values on front-end

* 💄 Removing leftover unused CSS

* ⚡ Updating interfaces to support resource locator value types

* ⚡ Updating `ResourceLocator` component to work with object parameter values

* 🔨 Cleaning up `ResourceLocator` and related components code

* ⚡ Preventing `DraggableTarget` to be sticky if disabled

* 🐛 Fixing a bug with resource locator value parameter

* 👌 Adding new type alias for all possible node parameter value types

* 👌 Updating `ResourceLocator` and related components based on PR review feedback

* ⚡ Adding disabled mode to `ResourceLocator` component, fixing expression handling, minor refactoring.

* 💄 Updating disabled state styling in `ResourceLocator` component

* ⚡ Setting correct default value for test node and removing unnecessary logic
  • Loading branch information
MiloradFilipovic authored Aug 11, 2022
1 parent 7e25794 commit 63fe0c0
Show file tree
Hide file tree
Showing 20 changed files with 549 additions and 187 deletions.
69 changes: 14 additions & 55 deletions packages/core/src/NodeExecuteFunctions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,14 @@ import {
NodeApiError,
NodeHelpers,
NodeOperationError,
NodeParameterValue,
Workflow,
WorkflowActivateMode,
WorkflowDataProxy,
WorkflowExecuteMode,
LoggerProxy as Logger,
IExecuteData,
OAuth2GrantType,
NodeParameterValueType,
} from 'n8n-workflow';

import { Agent } from 'https';
Expand Down Expand Up @@ -1621,9 +1621,7 @@ export function getNode(node: INode): INode {
* Clean up parameter data to make sure that only valid data gets returned
* INFO: Currently only converts Luxon Dates as we know for sure it will not be breaking
*/
function cleanupParameterData(
inputData: NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[],
): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] {
function cleanupParameterData(inputData: NodeParameterValueType): NodeParameterValueType {
if (inputData === null || inputData === undefined) {
return inputData;
}
Expand All @@ -1640,7 +1638,9 @@ function cleanupParameterData(

if (typeof inputData === 'object') {
Object.keys(inputData).forEach((key) => {
inputData[key] = cleanupParameterData(inputData[key]);
inputData[key as keyof typeof inputData] = cleanupParameterData(
inputData[key as keyof typeof inputData],
);
});
}

Expand Down Expand Up @@ -1674,7 +1674,7 @@ export function getNodeParameter(
additionalKeys: IWorkflowDataProxyAdditionalKeys,
executeData?: IExecuteData,
fallbackValue?: any,
): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object {
): NodeParameterValueType | object {
const nodeType = workflow.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
if (nodeType === undefined) {
throw new Error(`Node type "${node.type}" is not known so can not return paramter value!`);
Expand Down Expand Up @@ -1880,12 +1880,7 @@ export function getExecutePollFunctions(
getNodeParameter: (
parameterName: string,
fallbackValue?: any,
):
| NodeParameterValue
| INodeParameters
| NodeParameterValue[]
| INodeParameters[]
| object => {
): NodeParameterValueType | object => {
const runExecutionData: IRunExecutionData | null = null;
const itemIndex = 0;
const runIndex = 0;
Expand Down Expand Up @@ -2035,12 +2030,7 @@ export function getExecuteTriggerFunctions(
getNodeParameter: (
parameterName: string,
fallbackValue?: any,
):
| NodeParameterValue
| INodeParameters
| NodeParameterValue[]
| INodeParameters[]
| object => {
): NodeParameterValueType | object => {
const runExecutionData: IRunExecutionData | null = null;
const itemIndex = 0;
const runIndex = 0;
Expand Down Expand Up @@ -2251,12 +2241,7 @@ export function getExecuteFunctions(
parameterName: string,
itemIndex: number,
fallbackValue?: any,
):
| NodeParameterValue
| INodeParameters
| NodeParameterValue[]
| INodeParameters[]
| object => {
): NodeParameterValueType | object => {
return getNodeParameter(
workflow,
runExecutionData,
Expand Down Expand Up @@ -2525,12 +2510,7 @@ export function getExecuteSingleFunctions(
getNodeParameter: (
parameterName: string,
fallbackValue?: any,
):
| NodeParameterValue
| INodeParameters
| NodeParameterValue[]
| INodeParameters[]
| object => {
): NodeParameterValueType | object => {
return getNodeParameter(
workflow,
runExecutionData,
Expand Down Expand Up @@ -2676,13 +2656,7 @@ export function getLoadOptionsFunctions(
},
getCurrentNodeParameter: (
parameterPath: string,
):
| NodeParameterValue
| INodeParameters
| NodeParameterValue[]
| INodeParameters[]
| object
| undefined => {
): NodeParameterValueType | object | undefined => {
const nodeParameters = additionalData.currentNodeParameters;

if (parameterPath.charAt(0) === '&') {
Expand All @@ -2700,12 +2674,7 @@ export function getLoadOptionsFunctions(
getNodeParameter: (
parameterName: string,
fallbackValue?: any,
):
| NodeParameterValue
| INodeParameters
| NodeParameterValue[]
| INodeParameters[]
| object => {
): NodeParameterValueType | object => {
const runExecutionData: IRunExecutionData | null = null;
const itemIndex = 0;
const runIndex = 0;
Expand Down Expand Up @@ -2831,12 +2800,7 @@ export function getExecuteHookFunctions(
getNodeParameter: (
parameterName: string,
fallbackValue?: any,
):
| NodeParameterValue
| INodeParameters
| NodeParameterValue[]
| INodeParameters[]
| object => {
): NodeParameterValueType | object => {
const runExecutionData: IRunExecutionData | null = null;
const itemIndex = 0;
const runIndex = 0;
Expand Down Expand Up @@ -2994,12 +2958,7 @@ export function getExecuteWebhookFunctions(
getNodeParameter: (
parameterName: string,
fallbackValue?: any,
):
| NodeParameterValue
| INodeParameters
| NodeParameterValue[]
| INodeParameters[]
| object => {
): NodeParameterValueType | object => {
const runExecutionData: IRunExecutionData | null = null;
const itemIndex = 0;
const runIndex = 0;
Expand Down
2 changes: 1 addition & 1 deletion packages/design-system/theme/src/input.scss
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@
height: var.$input-height;
line-height: var.$input-height;
outline: none;
padding: 0 var(--spacing-xs);
padding: 0 var(--spacing-xs) 0 var(--spacing-2xs);
transition: var.$border-transition-base;
width: 100%;

Expand Down
2 changes: 1 addition & 1 deletion packages/editor-ui/src/components/DraggableTarget.vue
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export default Vue.extend({
this.hovering = e.clientX >= dim.left && e.clientX <= dim.right && e.clientY >= dim.top && e.clientY <= dim.bottom;
if (this.sticky && this.hovering) {
if (!this.disabled && this.sticky && this.hovering) {
this.$store.commit('ui/setDraggableStickyPos', [dim.left + this.stickyOffset, dim.top + this.stickyOffset]);
}
}
Expand Down
4 changes: 2 additions & 2 deletions packages/editor-ui/src/components/MainSidebar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -649,7 +649,7 @@ export default mixins(
height: 35px;
line-height: 35px;
color: $--custom-dialog-text-color;
--menu-item-hover-fill: var(--color-primary-tint-3);
--menu-item-hover-fill: var(--color-background-base);
.item-title {
position: absolute;
Expand All @@ -669,7 +669,7 @@ export default mixins(
.el-menu {
border: none;
font-size: 14px;
--menu-item-hover-fill: var(--color-primary-tint-3);
--menu-item-hover-fill: var(--color-background-base);
.el-menu--collapse {
width: 75px;
Expand Down
70 changes: 47 additions & 23 deletions packages/editor-ui/src/components/ParameterInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<div @keydown.stop :class="parameterInputClasses">
<expression-edit
:dialogVisible="expressionEditDialogVisible"
:value="value"
:value="isResourceLocatorParameter ? value.value || '' : value"
:parameter="parameter"
:path="path"
:eventSource="eventSource || 'ndv'"
Expand All @@ -14,16 +14,35 @@
:style="parameterInputWrapperStyle"
@click="openExpressionEdit"
>
<resource-locator
v-if="isResourceLocatorParameter"
ref="resourceLocator"
:parameter="parameter"
:value="typeof value === 'string' ? value : value.value"
:mode="value.mode || ''"
:displayValue="displayValue"
:displayTitle="displayTitle"
:parameterInputClasses="parameterInputClasses"
:expressionDisplayValue="expressionDisplayValue"
:isValueExpression="isValueExpression"
:isReadOnly="isReadOnly"
:parameterIssues="getIssues"
:droppable="droppable"
@valueChanged="valueChanged"
@modeChanged="valueChanged"
@focus="setFocus"
@blur="onBlur"
@drop="onDrop"
></resource-locator>
<n8n-input
v-if="isValueExpression || droppable || forceShowExpression"
v-else-if="isValueExpression || droppable || forceShowExpression"
:size="inputSize"
:type="getStringInputType"
:rows="getArgument('rows')"
:value="activeDrop || forceShowExpression? '': expressionDisplayValue"
:title="displayTitle"
@keydown.stop
/>

<div
v-else-if="
['json', 'string'].includes(parameter.type) ||
Expand Down Expand Up @@ -270,7 +289,6 @@ import {
import {
NodeHelpers,
NodeParameterValue,
IHttpRequestOptions,
ILoadOptions,
INodeParameters,
INodePropertyOptions,
Expand All @@ -284,6 +302,7 @@ import NodeCredentials from '@/components/NodeCredentials.vue';
import ScopesNotice from '@/components/ScopesNotice.vue';
import ParameterOptions from '@/components/ParameterOptions.vue';
import ParameterIssues from '@/components/ParameterIssues.vue';
import ResourceLocator from '@/components/ResourceLocator/ResourceLocator.vue';
// @ts-ignore
import PrismEditor from 'vue-prism-editor';
import TextEdit from '@/components/TextEdit.vue';
Expand Down Expand Up @@ -314,6 +333,7 @@ export default mixins(
ScopesNotice,
ParameterOptions,
ParameterIssues,
ResourceLocator,
TextEdit,
},
props: [
Expand All @@ -330,6 +350,7 @@ export default mixins(
'activeDrop',
'droppable',
'forceShowExpression',
'isValueExpression',
],
data () {
return {
Expand Down Expand Up @@ -455,7 +476,7 @@ export default mixins(
let returnValue;
if (this.isValueExpression === false) {
returnValue = this.value;
returnValue = this.isResourceLocatorParameter ? this.value.value : this.value;
} else {
returnValue = this.expressionValueComputed;
}
Expand Down Expand Up @@ -514,12 +535,12 @@ export default mixins(
let computedValue: NodeParameterValue;
try {
computedValue = this.resolveExpression(this.value) as NodeParameterValue;
computedValue = this.resolveExpression(this.value.value || this.value) as NodeParameterValue;
} catch (error) {
computedValue = `[${this.$locale.baseText('parameterInput.error')}}: ${error.message}]`;
}
// Try to convert it into the corret type
// Try to convert it into the correct type
if (this.parameter.type === 'number') {
computedValue = parseInt(computedValue as string, 10);
if (isNaN(computedValue)) {
Expand Down Expand Up @@ -614,15 +635,6 @@ export default mixins(
isEditor (): boolean {
return ['code', 'json'].includes(this.editorType);
},
isValueExpression () {
if (this.parameter.noDataExpression === true) {
return false;
}
if (typeof this.value === 'string' && this.value.charAt(0) === '=') {
return true;
}
return false;
},
editorType (): string {
return this.getArgument('editor') as string;
},
Expand Down Expand Up @@ -687,6 +699,9 @@ export default mixins(
workflow (): Workflow {
return this.getWorkflow();
},
isResourceLocatorParameter (): boolean {
return this.parameter.type === 'resourceLocator';
},
},
methods: {
credentialSelected (updateInformation: INodeUpdatePropertiesInformation) {
Expand Down Expand Up @@ -804,7 +819,8 @@ export default mixins(
return this.parameter.typeOptions[argumentName];
},
expressionUpdated (value: string) {
this.valueChanged(value);
const val = this.isResourceLocatorParameter ? { value, mode: this.value.mode } : value;
this.valueChanged(val);
},
openExpressionEdit() {
if (this.areExpressionsDisabled) {
Expand All @@ -820,6 +836,9 @@ export default mixins(
onBlur () {
this.$emit('blur');
},
onDrop(data: string) {
this.$emit('drop', data);
},
setFocus () {
if (this.isValueExpression) {
this.expressionEditDialogVisible = true;
Expand All @@ -845,7 +864,7 @@ export default mixins(
// Set focus on field
setTimeout(() => {
// @ts-ignore
if (this.$refs.inputField) {
if (this.$refs.inputField && this.$refs.inputField.$el) {
// @ts-ignore
this.$refs.inputField.focus();
}
Expand All @@ -872,7 +891,7 @@ export default mixins(
this.$emit('textInput', parameterData);
},
valueChanged (value: string[] | string | number | boolean | Date | null) {
valueChanged (value: string[] | string | number | boolean | Date | {} | null) {
if (this.parameter.name === 'nodeCredentialType') {
this.activeCredentialType = value as string;
}
Expand Down Expand Up @@ -917,9 +936,10 @@ export default mixins(
this.expressionEditDialogVisible = true;
} else if (command === 'addExpression') {
if (this.parameter.type === 'number' || this.parameter.type === 'boolean') {
this.valueChanged(`={{${this.value}}}`);
}
else {
this.valueChanged({ value: `={{${this.value}}}`, mode: this.value.mode });
} else if (this.isResourceLocatorParameter) {
this.valueChanged({ value: `=${this.value.value}`, mode: this.value.mode });
} else {
this.valueChanged(`=${this.value}`);
}
Expand All @@ -935,7 +955,11 @@ export default mixins(
.filter((value) => (this.parameterOptions || []).find((option) => option.value === value));
}
this.valueChanged(typeof value !== 'undefined' ? value : null);
if (this.isResourceLocatorParameter) {
this.valueChanged({ value, mode: this.value.mode });
} else {
this.valueChanged(typeof value !== 'undefined' ? value : null);
}
} else if (command === 'refreshOptions') {
this.loadRemoteParameterOptions();
}
Expand Down
Loading

0 comments on commit 63fe0c0

Please sign in to comment.