feat(editor): Simplify NDV by moving authentication details to credentials modal (#5067)

*  Removing authentication parameter from NDV
*  Added auth type selector to credentials modal
* 🔨 Extracting reusable logic to util functions
*  Updating credentials position, adding label for radio buttons
*  Using first node credentials for nodes with single auth options and hiding auth selector UI in that case
*  Fixing credentials modal when opened from credentials page
*  Showing all available credentials in NDV credentials dropdown
*  Updating node credentials dropdown component to show credentials description. Disabling `Credentials of type not found` error in node
*  Moving auth related fields from NDV to credentials modal. Added support for multiple auth fileds
*  Moving NDV fields that authentication depends on to credentials modal
*  Keeping old auth/credentials UI in NDV for HTTP Request and Webhook nodes. Pre-populating credential type for HTTP request node when selected from 'app action' menu
* 💄 Use old label and field position for nodes that use old credentials UI in NDV
*  Implementing more generic way to find node's auth fileds
* 📚 Adding comments on parameter hiding logic
*  Fixing node auth options logic for multiple auth fields
* 👕 Fixing lint errors
* 💄 Addressing design review comments
*  Not selecting first auth option when opening new credential dialog
*  Using default credentials name and icon if authentication type is not selected
*  Updating credential data when auth type is changed
*  Setting new credentials type for HTTP Request and Webhook nodes
*  Setting nodes with access when changing auth type
* 👕 Fixing lint error
*  Updating active node auth type from credentials modal
*  Syncronizing credentials modal and dropdown
* 👕 Fixing linter error
*  Handling credential dropdown UI for multiple credentials
* 👕 Removing unused imports
*  Handling auth selection when default auth type is the first option
*  Updating credentials change listening logic
*  Resetting credential data when deleting a credential, disabling 'Details' and 'Sharing' tabs if auth type is not selected
* 🐛 Skipping credentials type check when showing mixed credentials in the dropdown and switching credentials type
*  Showing credential modal tabs for saved credentials
*  Preventing renaming credentials when no auth type is selected
* 🐛 Fixing credentials modal when opened from credentials page
*  Keeping auth radio buttons selected when switching tabs
*  Adding initial batch of credentials NDV tests
*  Updating node auth filed value when new credential type is selected
*  Using all available credential types for current node to sync credential dropdown with modal
*  Sorting mixed credentials by date, simplifying credential dropdown option logic
* 🔨 Extracting some reusable logic to utils
*  Improving required vs optional credentials detection and using it to show auth radio buttons
* 👕 Fixing lint errors
*  Adding more credentials tests
*  Filtering credential options based on authentication type
* 🔨 Refactoring credentials and auth utils
*  Updated handling of auth options in credentials modal to work with new logic
* 🔨 Getting the terminology in line
* 📚 Removing leftover comment
*  Updating node auth filed detection logic to account for different edge-cases
*  Adding Wait node as an exception for new UI
*  Updating NDV display when auth type changes
*  Updating default credentials name when auth type changes
*  Hiding auth settings after credentials are saved
*  Always showing credentials modal menu tabs
*  Improving main auth field detection logic so it doesn't account for authentication fields which can have `none` value
*  Restoring accidentally deleted not existing credential issue logic
*  Updating other nodes when deleted credentials have been updated
*  Using filtered auth type list to show or hide radio buttons section in credentials modal
* 👕 Addressing lint error
* 👌 Addressing PR review feedback
* 👕 Fixing lint issues
*  Updating main auth filed detection logic so it checks full dependency path to determine if the field is required or optional
* 👌 Addressing the rest of PR feedback
*  Updating credential tests
*  Resetting credential data on authentication type change
*  Created AuthTypeSelector component
* 👌 Addressing PR comments
*  Not resetting overwritten credential properties when changing auth type
*  Hiding auth selector section if there are no options to show
This commit is contained in:
Milorad FIlipović
2023-01-27 09:05:43 +01:00
committed by GitHub
parent 874c735d0a
commit b321c5e4ec
21 changed files with 1105 additions and 146 deletions

View File

@@ -12,12 +12,12 @@
<div :class="$style.header">
<div :class="$style.credInfo">
<div :class="$style.credIcon">
<CredentialIcon :credentialTypeName="credentialTypeName" />
<CredentialIcon :credentialTypeName="defaultCredentialTypeName" />
</div>
<InlineNameEdit
:name="credentialName"
:subtitle="credentialType ? credentialType.displayName : ''"
:readonly="!credentialPermissions.updateName"
:readonly="!credentialPermissions.updateName || !credentialType"
type="Credential"
@input="onNameEdit"
data-test-id="credential-name"
@@ -52,7 +52,7 @@
<hr />
</template>
<template #content>
<div :class="$style.container">
<div :class="$style.container" data-test-id="credential-edit-dialog">
<div :class="$style.sidebar">
<n8n-menu mode="tabs" :items="sidebarItems" @select="onTabSelect"></n8n-menu>
</div>
@@ -71,10 +71,14 @@
:parentTypes="parentTypes"
:requiredPropertiesFilled="requiredPropertiesFilled"
:credentialPermissions="credentialPermissions"
:mode="mode"
:selectedCredential="selectedCredential"
:showAuthTypeSelector="requiredCredentials"
@change="onDataChange"
@oauth="oAuthCredentialAuthorize"
@retest="retestCredential"
@scrollToTop="scrollToTop"
@authTypeChanged="onAuthTypeChanged"
/>
</div>
<div v-else-if="activeTab === 'sharing' && credentialType" :class="$style.mainContent">
@@ -107,7 +111,7 @@
<script lang="ts">
import Vue from 'vue';
import type { ICredentialsResponse, IUser } from '@/Interface';
import type { ICredentialsResponse, IUser, NewCredentialsModal } from '@/Interface';
import {
CredentialInformation,
@@ -116,6 +120,7 @@ import {
ICredentialsDecrypted,
ICredentialType,
INode,
INodeCredentialDescription,
INodeParameters,
INodeProperties,
INodeTypeDescription,
@@ -134,12 +139,11 @@ import CredentialSharing from './CredentialSharing.ee.vue';
import SaveButton from '../SaveButton.vue';
import Modal from '../Modal.vue';
import InlineNameEdit from '../InlineNameEdit.vue';
import { EnterpriseEditionFeature } from '@/constants';
import { CREDENTIAL_EDIT_MODAL_KEY, EnterpriseEditionFeature } from '@/constants';
import { IDataObject } from 'n8n-workflow';
import FeatureComingSoon from '../FeatureComingSoon.vue';
import { getCredentialPermissions, IPermissions } from '@/permissions';
import { IMenuItem } from 'n8n-design-system';
import { BaseTextKey } from '@/plugins/i18n';
import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui';
import { useSettingsStore } from '@/stores/settings';
@@ -147,7 +151,13 @@ import { useUsersStore } from '@/stores/users';
import { useWorkflowsStore } from '@/stores/workflows';
import { useNDVStore } from '@/stores/ndv';
import { useCredentialsStore } from '@/stores/credentials';
import { isValidCredentialResponse } from '@/utils';
import {
isValidCredentialResponse,
getNodeAuthOptions,
getNodeCredentialForSelectedAuthType,
updateNodeAuthType,
isCredentialModalState,
} from '@/utils';
interface NodeAccessMap {
[nodeType: string]: ICredentialNodeAccess | null;
@@ -171,8 +181,8 @@ export default mixins(showMessage, nodeHelpers).extend({
required: true,
},
activeId: {
type: [String],
required: true,
type: [String, Number],
required: false,
},
mode: {
type: String,
@@ -196,22 +206,21 @@ export default mixins(showMessage, nodeHelpers).extend({
testedSuccessfully: false,
isRetesting: false,
EnterpriseEditionFeature,
selectedCredential: '',
requiredCredentials: false, // Are credentials required or optional for the node
hasUserSpecifiedName: false,
};
},
async mounted() {
this.nodeAccess = this.nodesWithAccess.reduce((accu: NodeAccessMap, node: { name: string }) => {
if (this.mode === 'new') {
accu[node.name] = { nodeType: node.name }; // enable all nodes by default
} else {
accu[node.name] = null;
}
this.requiredCredentials =
isCredentialModalState(this.uiStore.modals[CREDENTIAL_EDIT_MODAL_KEY]) &&
this.uiStore.modals[CREDENTIAL_EDIT_MODAL_KEY].showAuthSelector === true;
return accu;
}, {});
this.setupNodeAccess();
if (this.mode === 'new' && this.credentialTypeName) {
this.credentialName = await this.credentialsStore.getNewCredentialName({
credentialTypeName: this.credentialTypeName,
credentialTypeName: this.defaultCredentialTypeName,
});
if (this.currentUser) {
@@ -264,6 +273,38 @@ export default mixins(showMessage, nodeHelpers).extend({
useUsersStore,
useWorkflowsStore,
),
activeNodeType(): INodeTypeDescription | null {
const activeNode = this.ndvStore.activeNode;
if (activeNode) {
return this.nodeTypesStore.getNodeType(activeNode.type, activeNode.typeVersion);
}
return null;
},
selectedCredentialType(): INodeCredentialDescription | null {
if (this.mode !== 'new') {
return null;
}
// If there is already selected type, use it
if (this.selectedCredential !== '') {
return this.credentialsStore.getCredentialTypeByName(this.selectedCredential);
} else if (this.requiredCredentials) {
// Otherwise, use credential type that corresponds to the first auth option in the node definition
const nodeAuthOptions = getNodeAuthOptions(this.activeNodeType);
// But only if there is zero or one auth options available
if (nodeAuthOptions.length > 0 && this.activeNodeType?.credentials) {
return getNodeCredentialForSelectedAuthType(
this.activeNodeType,
nodeAuthOptions[0].value,
);
} else {
return this.activeNodeType?.credentials ? this.activeNodeType.credentials[0] : null;
}
}
return null;
},
currentUser(): IUser | null {
return this.usersStore.currentUser;
},
@@ -282,7 +323,9 @@ export default mixins(showMessage, nodeHelpers).extend({
return null;
}
if (this.selectedCredentialType) {
return this.selectedCredentialType.name;
}
return `${this.activeId}`;
},
credentialType(): ICredentialType | null {
@@ -419,6 +462,15 @@ export default mixins(showMessage, nodeHelpers).extend({
isSharingAvailable(): boolean {
return this.settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Sharing);
},
defaultCredentialTypeName(): string {
let credentialTypeName = this.credentialTypeName;
if (!credentialTypeName || credentialTypeName === 'null') {
if (this.activeNodeType && this.activeNodeType.credentials) {
credentialTypeName = this.activeNodeType.credentials[0].name;
}
}
return credentialTypeName || '';
},
},
methods: {
async beforeClose() {
@@ -614,6 +666,7 @@ export default mixins(showMessage, nodeHelpers).extend({
onNameEdit(text: string) {
this.hasUnsavedChanges = true;
this.hasUserSpecifiedName = true;
this.credentialName = text;
},
@@ -876,6 +929,7 @@ export default mixins(showMessage, nodeHelpers).extend({
this.isDeleting = false;
// Now that the credentials were removed check if any nodes used them
this.updateNodesCredentialsIssues();
this.credentialData = {};
this.$showMessage({
title: this.$locale.baseText('credentialEdit.credentialEdit.showMessage.title'),
@@ -946,6 +1000,50 @@ export default mixins(showMessage, nodeHelpers).extend({
window.addEventListener('message', receiveMessage, false);
},
async onAuthTypeChanged(type: string): Promise<void> {
if (!this.activeNodeType?.credentials) {
return;
}
const credentialsForType = getNodeCredentialForSelectedAuthType(this.activeNodeType, type);
if (credentialsForType) {
this.selectedCredential = credentialsForType.name;
this.resetCredentialData();
this.setupNodeAccess();
// Update current node auth type so credentials dropdown can be displayed properly
updateNodeAuthType(this.ndvStore.activeNode, type);
// Also update credential name but only if the default name is still used
if (this.hasUnsavedChanges && !this.hasUserSpecifiedName) {
const newDefaultName = await this.credentialsStore.getNewCredentialName({
credentialTypeName: this.defaultCredentialTypeName,
});
this.credentialName = newDefaultName;
}
}
},
setupNodeAccess(): void {
this.nodeAccess = this.nodesWithAccess.reduce(
(accu: NodeAccessMap, node: { name: string }) => {
if (this.mode === 'new') {
accu[node.name] = { nodeType: node.name }; // enable all nodes by default
} else {
accu[node.name] = null;
}
return accu;
},
{},
);
},
resetCredentialData(): void {
if (!this.credentialType) {
return;
}
for (const property of this.credentialType.properties) {
if (!this.credentialType.__overwrittenProperties?.includes(property.name)) {
Vue.set(this.credentialData, property.name, property.default as CredentialInformation);
}
}
},
},
});
</script>