fix(editor): Fix RLC not loading when an expression can't resolve (#7295)

Also fixes label (list -> From list)

Github issue / Community forum post (link here to close automatically):
This commit is contained in:
Elias Meire 2023-10-04 16:01:44 +02:00 committed by GitHub
parent 1b4848afcb
commit ddc26c21bd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 85 additions and 84 deletions

View file

@ -7,6 +7,7 @@ const credentialsModal = new CredentialsModal();
const NO_CREDENTIALS_MESSAGE = 'Please add your credential'; const NO_CREDENTIALS_MESSAGE = 'Please add your credential';
const INVALID_CREDENTIALS_MESSAGE = 'Please check your credential'; const INVALID_CREDENTIALS_MESSAGE = 'Please check your credential';
const MODE_SELECTOR_LIST = 'From list';
describe('Resource Locator', () => { describe('Resource Locator', () => {
beforeEach(() => { beforeEach(() => {
@ -18,6 +19,14 @@ describe('Resource Locator', () => {
workflowPage.actions.addNodeToCanvas('Google Sheets', true, true); workflowPage.actions.addNodeToCanvas('Google Sheets', true, true);
ndv.getters.resourceLocator('documentId').should('be.visible'); ndv.getters.resourceLocator('documentId').should('be.visible');
ndv.getters.resourceLocator('sheetName').should('be.visible'); ndv.getters.resourceLocator('sheetName').should('be.visible');
ndv.getters
.resourceLocatorModeSelector('documentId')
.find('input')
.should('have.value', MODE_SELECTOR_LIST);
ndv.getters
.resourceLocatorModeSelector('sheetName')
.find('input')
.should('have.value', MODE_SELECTOR_LIST);
}); });
it('should show appropriate error when credentials are not set', () => { it('should show appropriate error when credentials are not set', () => {

View file

@ -58,6 +58,7 @@
v-for="mode in parameter.modes" v-for="mode in parameter.modes"
:key="mode.name" :key="mode.name"
:value="mode.name" :value="mode.name"
:label="getModeLabel(mode)"
:disabled="isValueExpression && mode.name === 'list'" :disabled="isValueExpression && mode.name === 'list'"
:title=" :title="
isValueExpression && mode.name === 'list' isValueExpression && mode.name === 'list'
@ -65,7 +66,7 @@
: '' : ''
" "
> >
{{ getModeLabel(mode.name) || mode.displayName }} {{ getModeLabel(mode) }}
</n8n-option> </n8n-option>
</n8n-select> </n8n-select>
</div> </div>
@ -143,8 +144,27 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import type { IResourceLocatorReqParams, IResourceLocatorResultExpanded } from '@/Interface';
import { mapStores } from 'pinia'; import DraggableTarget from '@/components/DraggableTarget.vue';
import ExpressionParameterInput from '@/components/ExpressionParameterInput.vue';
import ParameterIssues from '@/components/ParameterIssues.vue';
import { debounceHelper } from '@/mixins/debounce';
import { nodeHelpers } from '@/mixins/nodeHelpers';
import { workflowHelpers } from '@/mixins/workflowHelpers';
import { useRootStore } from '@/stores/n8nRoot.store';
import { useNDVStore } from '@/stores/ndv.store';
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
import { useUIStore } from '@/stores/ui.store';
import { useWorkflowsStore } from '@/stores/workflows.store';
import {
getAppNameFromNodeName,
getMainAuthField,
hasOnlyListMode,
isResourceLocatorValue,
} from '@/utils';
import stringify from 'fast-json-stable-stringify';
import type { EventBus } from 'n8n-design-system/utils';
import { createEventBus } from 'n8n-design-system/utils';
import type { import type {
ILoadOptions, ILoadOptions,
INode, INode,
@ -156,29 +176,10 @@ import type {
INodePropertyMode, INodePropertyMode,
NodeParameterValue, NodeParameterValue,
} from 'n8n-workflow'; } from 'n8n-workflow';
import ExpressionParameterInput from '@/components/ExpressionParameterInput.vue'; import { mapStores } from 'pinia';
import DraggableTarget from '@/components/DraggableTarget.vue';
import ParameterIssues from '@/components/ParameterIssues.vue';
import ResourceLocatorDropdown from './ResourceLocatorDropdown.vue';
import type { PropType } from 'vue'; import type { PropType } from 'vue';
import type { IResourceLocatorReqParams, IResourceLocatorResultExpanded } from '@/Interface'; import { defineComponent } from 'vue';
import { debounceHelper } from '@/mixins/debounce'; import ResourceLocatorDropdown from './ResourceLocatorDropdown.vue';
import stringify from 'fast-json-stable-stringify';
import { workflowHelpers } from '@/mixins/workflowHelpers';
import { nodeHelpers } from '@/mixins/nodeHelpers';
import {
getAppNameFromNodeName,
isResourceLocatorValue,
hasOnlyListMode,
getMainAuthField,
} from '@/utils';
import { useUIStore } from '@/stores/ui.store';
import { useWorkflowsStore } from '@/stores/workflows.store';
import { useRootStore } from '@/stores/n8nRoot.store';
import { useNDVStore } from '@/stores/ndv.store';
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
import { createEventBus } from 'n8n-design-system/utils';
import type { EventBus } from 'n8n-design-system/utils';
interface IResourceLocatorQuery { interface IResourceLocatorQuery {
results: INodeListSearchItems[]; results: INodeListSearchItems[];
@ -573,12 +574,12 @@ export default defineComponent({
} }
return null; return null;
}, },
getModeLabel(name: string): string | null { getModeLabel(mode: INodePropertyMode): string | null {
if (name === 'id' || name === 'url' || name === 'list') { if (mode.name === 'id' || mode.name === 'url' || mode.name === 'list') {
return this.$locale.baseText(`resourceLocator.mode.${name}`); return this.$locale.baseText(`resourceLocator.mode.${mode.name}`);
} }
return null; return mode.displayName;
}, },
onInputChange(value: string): void { onInputChange(value: string): void {
const params: INodeParameterResourceLocator = { __rl: true, value, mode: this.selectedMode }; const params: INodeParameterResourceLocator = { __rl: true, value, mode: this.selectedMode };

View file

@ -1,70 +1,70 @@
import { defineComponent } from 'vue';
import { mapStores } from 'pinia';
import { import {
PLACEHOLDER_FILLED_AT_EXECUTION_TIME,
PLACEHOLDER_EMPTY_WORKFLOW_ID,
WEBHOOK_NODE_TYPE,
VIEWS,
EnterpriseEditionFeature, EnterpriseEditionFeature,
MODAL_CONFIRM, MODAL_CONFIRM,
PLACEHOLDER_EMPTY_WORKFLOW_ID,
PLACEHOLDER_FILLED_AT_EXECUTION_TIME,
VIEWS,
WEBHOOK_NODE_TYPE,
} from '@/constants'; } from '@/constants';
import { mapStores } from 'pinia';
import { defineComponent } from 'vue';
import type { import type {
IConnections, IConnections,
IDataObject, IDataObject,
IExecuteData,
INode, INode,
INodeConnection,
INodeCredentials,
INodeExecutionData, INodeExecutionData,
INodeIssues, INodeIssues,
INodeParameters, INodeParameters,
NodeParameterValue, INodeProperties,
INodeCredentials,
INodeType, INodeType,
INodeTypes, INodeTypes,
IRunExecutionData, IRunExecutionData,
IWorkflowIssues,
IWorkflowDataProxyAdditionalKeys,
Workflow,
IExecuteData,
INodeConnection,
IWebhookDescription, IWebhookDescription,
INodeProperties, IWorkflowDataProxyAdditionalKeys,
IWorkflowIssues,
IWorkflowSettings, IWorkflowSettings,
NodeParameterValue,
Workflow,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { NodeConnectionType, ExpressionEvaluatorProxy, NodeHelpers } from 'n8n-workflow'; import { NodeConnectionType, ExpressionEvaluatorProxy, NodeHelpers } from 'n8n-workflow';
import type { import type {
ICredentialsResponse,
INodeTypesMaxCount, INodeTypesMaxCount,
INodeUi, INodeUi,
IWorkflowData,
IWorkflowDb,
IWorkflowDataUpdate,
XYPosition,
ITag, ITag,
IWorkflowData,
IWorkflowDataUpdate,
IWorkflowDb,
TargetItem, TargetItem,
ICredentialsResponse, XYPosition,
} from '../Interface'; } from '../Interface';
import { useMessage, useToast } from '@/composables';
import { externalHooks } from '@/mixins/externalHooks'; import { externalHooks } from '@/mixins/externalHooks';
import { nodeHelpers } from '@/mixins/nodeHelpers';
import { genericHelpers } from '@/mixins/genericHelpers'; import { genericHelpers } from '@/mixins/genericHelpers';
import { useToast, useMessage } from '@/composables'; import { nodeHelpers } from '@/mixins/nodeHelpers';
import { isEqual } from 'lodash-es'; import { isEqual } from 'lodash-es';
import { v4 as uuid } from 'uuid'; import type { IPermissions } from '@/permissions';
import { getSourceItems } from '@/utils'; import { getWorkflowPermissions } from '@/permissions';
import { useUIStore } from '@/stores/ui.store'; import { useEnvironmentsStore } from '@/stores/environments.ee.store';
import { useWorkflowsStore } from '@/stores/workflows.store';
import { useRootStore } from '@/stores/n8nRoot.store'; import { useRootStore } from '@/stores/n8nRoot.store';
import { useNDVStore } from '@/stores/ndv.store'; import { useNDVStore } from '@/stores/ndv.store';
import { useTemplatesStore } from '@/stores/templates.store';
import { useNodeTypesStore } from '@/stores/nodeTypes.store'; import { useNodeTypesStore } from '@/stores/nodeTypes.store';
import { useWorkflowsEEStore } from '@/stores/workflows.ee.store'; import { useTemplatesStore } from '@/stores/templates.store';
import { useEnvironmentsStore } from '@/stores/environments.ee.store'; import { useUIStore } from '@/stores/ui.store';
import { useUsersStore } from '@/stores/users.store'; import { useUsersStore } from '@/stores/users.store';
import { useWorkflowsEEStore } from '@/stores/workflows.ee.store';
import { useWorkflowsStore } from '@/stores/workflows.store';
import { getSourceItems } from '@/utils';
import { v4 as uuid } from 'uuid';
import { useSettingsStore } from '@/stores/settings.store'; import { useSettingsStore } from '@/stores/settings.store';
import { getWorkflowPermissions } from '@/permissions';
import type { IPermissions } from '@/permissions';
export function getParentMainInputNode(workflow: Workflow, node: INode): INode { export function getParentMainInputNode(workflow: Workflow, node: INode): INode {
const nodeType = useNodeTypesStore().getNodeType(node.type); const nodeType = useNodeTypesStore().getNodeType(node.type);
@ -233,35 +233,26 @@ export function resolveRequiredParameters(
inputBranchIndex?: number; inputBranchIndex?: number;
} = {}, } = {},
): IDataObject | null { ): IDataObject | null {
const loadOptionsDependsOn = currentParameter?.typeOptions?.loadOptionsDependsOn ?? []; const loadOptionsDependsOn = new Set(currentParameter?.typeOptions?.loadOptionsDependsOn ?? []);
const initial: { required: INodeParameters; nonrequired: INodeParameters } = { const resolvedParameters = Object.fromEntries(
required: {}, Object.entries(parameters).map(([name, parameter]): [string, IDataObject | null] => {
nonrequired: {}, const required = loadOptionsDependsOn.has(name);
};
const { required, nonrequired }: INodeParameters = Object.keys(parameters).reduce( if (required) {
(accu, name: string) => { return [name, resolveParameter(parameter as NodeParameterValue, opts)];
const required = loadOptionsDependsOn.includes(name); } else {
accu[required ? 'required' : 'nonrequired'][name] = parameters[name]; try {
return [name, resolveParameter(parameter as NodeParameterValue, opts)];
return accu; } catch (error) {
}, // ignore any expressions errors for non required parameters
initial, return [name, null];
}
}
}),
); );
const resolvedRequired = resolveParameter(required, opts); return resolvedParameters;
let resolvedNonrequired: IDataObject | null = {};
try {
resolvedNonrequired = resolveParameter(nonrequired, opts);
} catch (e) {
// ignore any expression errors for example
}
return {
...resolvedRequired,
...(resolvedNonrequired ?? {}),
};
} }
function getCurrentWorkflow(copyData?: boolean): Workflow { function getCurrentWorkflow(copyData?: boolean): Workflow {