Refactor credentials dropdown for HTTP Request node (#3222)

This commit is contained in:
Iván Ovejero 2022-05-05 17:40:30 +02:00 committed by GitHub
parent 30c6940135
commit 47185d16f6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 183 additions and 27 deletions

View file

@ -1,5 +1,5 @@
<template> <template>
<div v-if="credentialTypesNodeDescriptionDisplayed.length" :class="$style.container"> <div v-if="credentialTypesNodeDescriptionDisplayed.length" :class="['node-credentials', $style.container]">
<div v-for="credentialTypeDescription in credentialTypesNodeDescriptionDisplayed" :key="credentialTypeDescription.name"> <div v-for="credentialTypeDescription in credentialTypesNodeDescriptionDisplayed" :key="credentialTypeDescription.name">
<n8n-input-label <n8n-input-label
:label="$locale.baseText( :label="$locale.baseText(
@ -11,15 +11,20 @@
} }
)" )"
:bold="false" :bold="false"
size="small"
:set="issues = getIssues(credentialTypeDescription.name)" :set="issues = getIssues(credentialTypeDescription.name)"
size="small"
> >
<div v-if="isReadOnly"> <div v-if="isReadOnly">
<n8n-input disabled :value="selected && selected[credentialTypeDescription.name] && selected[credentialTypeDescription.name].name" size="small" /> <n8n-input
:value="selected && selected[credentialTypeDescription.name] && selected[credentialTypeDescription.name].name"
disabled
size="small"
/>
</div> </div>
<div
<div :class="issues.length ? $style.hasIssues : $style.input" v-else > v-else
:class="issues.length ? $style.hasIssues : $style.input"
>
<n8n-select :value="getSelectedId(credentialTypeDescription.name)" @change="(value) => onCredentialSelected(credentialTypeDescription.name, value)" :placeholder="$locale.baseText('nodeCredentials.selectCredential')" size="small"> <n8n-select :value="getSelectedId(credentialTypeDescription.name)" @change="(value) => onCredentialSelected(credentialTypeDescription.name, value)" :placeholder="$locale.baseText('nodeCredentials.selectCredential')" size="small">
<n8n-option <n8n-option
v-for="(item) in credentialOptions[credentialTypeDescription.name]" v-for="(item) in credentialOptions[credentialTypeDescription.name]"
@ -92,7 +97,16 @@ export default mixins(
computed: { computed: {
...mapGetters('credentials', { ...mapGetters('credentials', {
credentialOptions: 'allCredentialsByType', credentialOptions: 'allCredentialsByType',
getCredentialTypeByName: 'getCredentialTypeByName',
}), }),
isProxyAuth(): boolean {
return this.isHttpRequestNodeV2(this.node) &&
this.node.parameters.authenticateWith === 'nodeCredential';
},
isGenericAuth(): boolean {
return this.isHttpRequestNodeV2(this.node) &&
this.node.parameters.authenticateWith === 'genericAuth';
},
credentialTypesNode (): string[] { credentialTypesNode (): string[] {
return this.credentialTypesNodeDescription return this.credentialTypesNodeDescription
.map((credentialTypeDescription) => credentialTypeDescription.name); .map((credentialTypeDescription) => credentialTypeDescription.name);
@ -106,6 +120,18 @@ export default mixins(
credentialTypesNodeDescription (): INodeCredentialDescription[] { credentialTypesNodeDescription (): INodeCredentialDescription[] {
const node = this.node as INodeUi; const node = this.node as INodeUi;
if (this.isGenericAuth) {
const { genericAuthType } = this.node.parameters as { genericAuthType: string };
return [this.getCredentialTypeByName(genericAuthType)];
}
if (this.isProxyAuth) {
const { nodeCredentialType } = this.node.parameters as { nodeCredentialType?: string };
if (nodeCredentialType) return [this.getCredentialTypeByName(nodeCredentialType)];
}
const activeNodeType = this.$store.getters.nodeType(node.type, node.typeVersion) as INodeTypeDescription | null; const activeNodeType = this.$store.getters.nodeType(node.type, node.typeVersion) as INodeTypeDescription | null;
if (activeNodeType && activeNodeType.credentials) { if (activeNodeType && activeNodeType.credentials) {
return activeNodeType.credentials; return activeNodeType.credentials;
@ -295,7 +321,7 @@ export default mixins(
<style lang="scss" module> <style lang="scss" module>
.container { .container {
margin: var(--spacing-xs) 0; margin: 0;
> * { > * {
margin-bottom: var(--spacing-xs); margin-bottom: var(--spacing-xs);

View file

@ -23,9 +23,20 @@
</div> </div>
<div class="node-parameters-wrapper" v-if="node && nodeValid"> <div class="node-parameters-wrapper" v-if="node && nodeValid">
<div v-show="openPanel === 'params'"> <div v-show="openPanel === 'params'">
<node-credentials :node="node" @credentialSelected="credentialSelected"></node-credentials> <node-webhooks
<node-webhooks :node="node" :nodeType="nodeType" /> :node="node"
<parameter-input-list :parameters="parametersNoneSetting" :hideDelete="true" :nodeValues="nodeValues" path="parameters" @valueChanged="valueChanged" /> :nodeType="nodeType"
/>
<parameter-input-list
:parameters="parametersNoneSetting"
:hideDelete="true"
:nodeValues="nodeValues" path="parameters" @valueChanged="valueChanged"
>
<node-credentials
:node="node"
@credentialSelected="credentialSelected"
/>
</parameter-input-list>
<div v-if="parametersNoneSetting.length === 0" class="no-parameters"> <div v-if="parametersNoneSetting.length === 0" class="no-parameters">
<n8n-text> <n8n-text>
{{ $locale.baseText('nodeSettings.thisNodeDoesNotHaveAnyParameters') }} {{ $locale.baseText('nodeSettings.thisNodeDoesNotHaveAnyParameters') }}

View file

@ -1,6 +1,8 @@
<template> <template>
<div class="paramter-input-list-wrapper"> <div class="parameter-input-list-wrapper">
<div v-for="parameter in filteredParameters" :key="parameter.name" :class="{indent}"> <div v-for="(parameter, index) in filteredParameters" :key="parameter.name">
<slot v-if="indexToShowSlotAt === index" />
<div <div
v-if="multipleValues(parameter) === true && parameter.type !== 'fixedCollection'" v-if="multipleValues(parameter) === true && parameter.type !== 'fixedCollection'"
class="parameter-item" class="parameter-item"
@ -129,6 +131,11 @@ export default mixins(
node (): INodeUi { node (): INodeUi {
return this.$store.getters.activeNode; return this.$store.getters.activeNode;
}, },
indexToShowSlotAt (): number {
if (this.isHttpRequestNodeV2(this.node)) return 2;
return 0;
},
}, },
methods: { methods: {
multipleValues (parameter: INodeProperties): boolean { multipleValues (parameter: INodeProperties): boolean {
@ -260,7 +267,12 @@ export default mixins(
</script> </script>
<style lang="scss"> <style lang="scss">
.paramter-input-list-wrapper { .parameter-input-list-wrapper {
div:first-child > .node-credentials {
padding-top: var(--spacing-xs);
}
.delete-option { .delete-option {
display: none; display: none;
position: absolute; position: absolute;

View file

@ -1,4 +1,5 @@
import { import {
HTTP_REQUEST_NODE_TYPE,
PLACEHOLDER_FILLED_AT_EXECUTION_TIME, PLACEHOLDER_FILLED_AT_EXECUTION_TIME,
} from '@/constants'; } from '@/constants';
@ -32,12 +33,19 @@ import { restApi } from '@/components/mixins/restApi';
import { get } from 'lodash'; import { get } from 'lodash';
import mixins from 'vue-typed-mixins'; import mixins from 'vue-typed-mixins';
import { mapGetters } from 'vuex';
export const nodeHelpers = mixins( export const nodeHelpers = mixins(
restApi, restApi,
) )
.extend({ .extend({
computed: {
...mapGetters('credentials', [ 'getCredentialTypeByName', 'getCredentialsByType' ]),
},
methods: { methods: {
isHttpRequestNodeV2 (node: INodeUi): boolean {
return node.type === HTTP_REQUEST_NODE_TYPE && node.typeVersion === 2;
},
// Returns the parameter value // Returns the parameter value
getParameterValue (nodeValues: INodeParameters, parameterName: string, path: string) { getParameterValue (nodeValues: INodeParameters, parameterName: string, path: string) {
@ -116,6 +124,23 @@ export const nodeHelpers = mixins(
return false; return false;
}, },
reportUnsetCredential(credentialType: ICredentialType) {
return {
credentials: {
[credentialType.name]: [
this.$locale.baseText(
'nodeHelpers.credentialsUnset',
{
interpolate: {
credentialType: credentialType.displayName,
},
},
),
],
},
};
},
// Updates the execution issues. // Updates the execution issues.
updateNodesExecutionIssues () { updateNodesExecutionIssues () {
const nodes = this.$store.getters.allNodes; const nodes = this.$store.getters.allNodes;
@ -198,6 +223,46 @@ export const nodeHelpers = mixins(
let credentialType: ICredentialType | null; let credentialType: ICredentialType | null;
let credentialDisplayName: string; let credentialDisplayName: string;
let selectedCredentials: INodeCredentialsDetails; let selectedCredentials: INodeCredentialsDetails;
const {
authenticateWith,
genericAuthType,
nodeCredentialType,
} = node.parameters as HttpRequestNode.V2.AuthParams;
if (
this.isHttpRequestNodeV2(node) &&
authenticateWith === 'genericAuth' &&
selectedCredsAreUnusable(node, genericAuthType)
) {
const credential = this.getCredentialTypeByName(genericAuthType);
return this.reportUnsetCredential(credential);
}
if (
this.isHttpRequestNodeV2(node) &&
authenticateWith === 'nodeCredential' &&
nodeCredentialType !== '' &&
node.credentials !== undefined
) {
const stored = this.getCredentialsByType(nodeCredentialType);
if (selectedCredsDoNotExist(node, nodeCredentialType, stored)) {
const credential = this.getCredentialTypeByName(nodeCredentialType);
return this.reportUnsetCredential(credential);
}
}
if (
this.isHttpRequestNodeV2(node) &&
authenticateWith === 'nodeCredential' &&
nodeCredentialType !== '' &&
selectedCredsAreUnusable(node, nodeCredentialType)
) {
const credential = this.getCredentialTypeByName(nodeCredentialType);
return this.reportUnsetCredential(credential);
}
for (const credentialTypeDescription of nodeType!.credentials!) { for (const credentialTypeDescription of nodeType!.credentials!) {
// Check if credentials should be displayed else ignore // Check if credentials should be displayed else ignore
if (this.displayParameter(node.parameters, credentialTypeDescription, '', node) !== true) { if (this.displayParameter(node.parameters, credentialTypeDescription, '', node) !== true) {
@ -394,3 +459,39 @@ export const nodeHelpers = mixins(
}, },
}, },
}); });
/**
* 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 === undefined || Object.keys(node.credentials).includes(credentialType) === false;
}
/**
* 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 = {
authenticateWith: 'none' | 'genericAuth' | 'nodeCredential';
genericAuthType: string;
nodeCredentialType: string;
};
}
}

View file

@ -330,6 +330,11 @@ export const workflowHelpers = mixins(
if (node.credentials !== undefined && nodeType.credentials !== undefined) { if (node.credentials !== undefined && nodeType.credentials !== undefined) {
const saveCredenetials: INodeCredentials = {}; const saveCredenetials: INodeCredentials = {};
for (const nodeCredentialTypeName of Object.keys(node.credentials)) { for (const nodeCredentialTypeName of Object.keys(node.credentials)) {
if (this.isHttpRequestNodeV2(node)) {
saveCredenetials[nodeCredentialTypeName] = node.credentials[nodeCredentialTypeName];
continue;
}
const credentialTypeDescription = nodeType.credentials const credentialTypeDescription = nodeType.credentials
.find((credentialTypeDescription) => credentialTypeDescription.name === nodeCredentialTypeName); .find((credentialTypeDescription) => credentialTypeDescription.name === nodeCredentialTypeName);

View file

@ -387,6 +387,7 @@
"nodeErrorView.stack": "Stack", "nodeErrorView.stack": "Stack",
"nodeErrorView.theErrorCauseIsTooLargeToBeDisplayed": "The error cause is too large to be displayed", "nodeErrorView.theErrorCauseIsTooLargeToBeDisplayed": "The error cause is too large to be displayed",
"nodeErrorView.time": "Time", "nodeErrorView.time": "Time",
"nodeHelpers.credentialsUnset": "Credentials for '{credentialType}' are not set.",
"nodeSettings.alwaysOutputData.description": "If active, will output a single, empty item when the output would have been empty. Use to prevent the workflow finishing on this node.", "nodeSettings.alwaysOutputData.description": "If active, will output a single, empty item when the output would have been empty. Use to prevent the workflow finishing on this node.",
"nodeSettings.alwaysOutputData.displayName": "Always Output Data", "nodeSettings.alwaysOutputData.displayName": "Always Output Data",
"nodeSettings.clickOnTheQuestionMarkIcon": "Click the '?' icon to open this node on n8n.io", "nodeSettings.clickOnTheQuestionMarkIcon": "Click the '?' icon to open this node on n8n.io",

View file

@ -52,7 +52,7 @@ export class HttpRequest implements INodeType {
displayOptions: { displayOptions: {
show: { show: {
authenticateWith: [ authenticateWith: [
'basicAuth', 'httpBasicAuth',
], ],
'@version': [ '@version': [
2, 2,
@ -66,7 +66,7 @@ export class HttpRequest implements INodeType {
displayOptions: { displayOptions: {
show: { show: {
authenticateWith: [ authenticateWith: [
'digestAuth', 'httpDigestAuth',
], ],
'@version': [ '@version': [
2, 2,
@ -80,7 +80,7 @@ export class HttpRequest implements INodeType {
displayOptions: { displayOptions: {
show: { show: {
authenticateWith: [ authenticateWith: [
'headerAuth', 'httpHeaderAuth',
], ],
'@version': [ '@version': [
2, 2,
@ -94,7 +94,7 @@ export class HttpRequest implements INodeType {
displayOptions: { displayOptions: {
show: { show: {
authenticateWith: [ authenticateWith: [
'queryAuth', 'httpQueryAuth',
], ],
'@version': [ '@version': [
2, 2,
@ -108,7 +108,7 @@ export class HttpRequest implements INodeType {
displayOptions: { displayOptions: {
show: { show: {
authenticateWith: [ authenticateWith: [
'oAuth1', 'oAuth1Api',
], ],
'@version': [ '@version': [
2, 2,
@ -122,7 +122,7 @@ export class HttpRequest implements INodeType {
displayOptions: { displayOptions: {
show: { show: {
authenticateWith: [ authenticateWith: [
'oAuth2', 'oAuth2Api',
], ],
'@version': [ '@version': [
2, 2,
@ -279,30 +279,30 @@ export class HttpRequest implements INodeType {
options: [ options: [
{ {
name: 'Basic Auth', name: 'Basic Auth',
value: 'basicAuth', value: 'httpBasicAuth',
}, },
{ {
name: 'Digest Auth', name: 'Digest Auth',
value: 'digestAuth', value: 'httpDigestAuth',
}, },
{ {
name: 'Header Auth', name: 'Header Auth',
value: 'headerAuth', value: 'httpHeaderAuth',
}, },
{ {
name: 'Query Auth', name: 'Query Auth',
value: 'queryAuth', value: 'httpQueryAuth',
}, },
{ {
name: 'OAuth1', name: 'OAuth1',
value: 'oAuth1', value: 'oAuth1Api',
}, },
{ {
name: 'OAuth2', name: 'OAuth2',
value: 'oAuth2', value: 'oAuth2Api',
}, },
], ],
default: 'basicAuth', default: 'httpBasicAuth',
displayOptions: { displayOptions: {
show: { show: {
authenticateWith: [ authenticateWith: [