feat(core): Allow credential reuse on HTTP Request node (#3228)
* ✨ Create controller * ⚡ Mount controller * ✏️ Add error messages * ✨ Create scopes fetcher * ⚡ Account for non-existent credential type * 📘 Type scopes request * ⚡ Adjust error message * 🧪 Add tests * ✨ Introduce simple node versioning * ⚡ Add example how to read version in node-code for custom logic * 🐛 Fix setting of parameters * 🐛 Fix another instance where it sets the wrong parameter * ⚡ Remove unnecessary TOODs * ✨ Re-version HTTP Request node * 👕 Satisfy linter * ⚡ Retrieve node version * ⏪ Undo Jan's changes to Set node * 🧪 Fix CI/CD for `/oauth2-credential` tests (#3230) * 🐛 Fix notice warning missing background color (#3231) * 🐛 Check for generic auth in node cred types * ⚡ Refactor credentials dropdown for HTTP Request node (#3222) * ⚡ Discoverability flow (#3229) * ✨ Added node credentials type proxy. Changed node credentials input order. * ⚡ Add computed property from versioning branch * 🐛 Fix cred ref lost and unsaved * ⚡ Make options consistent with cred type names * ⚡ Use prop to set component order * ⚡ Use constant and version * ⚡ Fix rendering for generic auth creds * ⚡ Mark as required on first selection * ⚡ Implement discoverability flow * ⚡ Mark as required on subsequent selections * ⚡ Fix marking as required after cred deletion * ⚡ Refactor to clean up * ⚡ Detect position automatically * ⚡ Add i18n to option label * ⚡ Hide subtitle for custom action * ⚡ Detect active credential type * ⚡ Prop drilling to re-render select * 🔥 Remove unneeded property * ✏️ Rename arg * 🔥 Remove unused import * 🔥 Remove unneeded getters * 🔥 Remove unused import * ⚡ Generalize cred component positioning * ⚡ Set up request * 🐛 Fix edge case in endpoint * ⚡ Display scopes alert box * ⏪ Revert "Generalize cred comp positioning" This reverts commit 75eea89273b854110fa6d1f96c7c1d78dd3b0731. * ⚡ Consolidate HTTPRN check * ⚡ Fix hue percentage to degree * 🔥 Remove unused import * 🔥 Remove unused import * 🔥 Remove unused class * 🔥 Remove unused import * 📘 Create type for HTTPRN v2 auth params * ✏️ Rename check * 🔥 Remove unused import * ✏️ Add i18n to `reportUnsetCredential()` * ⚡ Refactor Alex's spacing changes * ⚡ Post-merge fixes * ⚡ Add docs link * 🔥 Exclude Notion OAuth cred * ✏️ Update copy * ✏️ Rename param * 🎨 Reposition notice and simplify styling * ✏️ Update copy * ✏️ Update copy * ⚡ Hide params during custom action * ⚡ Show notice if any cred type supported * 🐛 Prevent scopes text overflow * 🔥 Remove superfluous check * ✏️ Break up docstring * 🎨 Tweak notice styling * ⚡ Reorder cred param in Webhook node * ✏️ Shorten cred name in scopes notice * 🧪 Update Notice snapshots * 🐛 Fix check when `globalRole` is `undefined` * ⏪ Revert 3f2c4a6 * ⚡ Apply feedback from Product * 🧪 Update snapshot * ⚡ Adjust regex expansion pattern for singular * 🔥 Remove unused import * 🔥 Remove logging * ⚡ Make `somethingElse` key more unique * ⚡ Move something else to constants * ⚡ Consolidate notice component * ⚡ Apply latest feedback * 🧪 Update tests * 🧪 Update snapshot * ✏️ Fix singular version * 🧪 Finalize tests * ✏️ Rename constant * 🧪 Expand tests * 🔥 Remove `truncate` prop * 🚚 Move scopes fetching to store * 🚚 Move method to component * ⚡ Use constant * ⚡ Refactor `Notice` component * 🧪 Update tests * 🔥 Remove unused keys * ⚡ Inject custom API call option * 🔥 Remove unused props * 🎨 Use `compact` prop * 🧪 Update snapshots * 🚚 Move scopes to store * 🚚 Move `nodeCredentialTypes` to parent * ✏️ Rename cred types per branding * 🐛 Clear scopes when none * ⚡ Add default * 🚚 Move `newHttpRequestNodeCredentialType` to parent * 🔥 Remove test data * ⚡ Separate lines for readability * ⚡ Change reference from node to node name * ✏️ Rename i18n keys * ⚡ Refactor OAuth check * 🔥 Remove unused key * 🚚 Move `OAuth1/2 API` to i18n * ⚡ Refactor `skipCheck` * ⚡ Add `stopPropagation` and `preventDefault` * 🚚 Move active credential scopes logic to store * 🎨 Fix spacing for `NodeWebhooks` component * ⚡ Implement feedback * ⚡ Update HTTPRN default and issue copy * Refactor to use `CredentialsSelect` param (#3304) * ⚡ Refactor into cred type param * ⚡ Componentize scopes notice * 🔥 Remove unused data * 🔥 Remove unused `loadOptions` * ⚡ Componentize `NodeCredentialType` * 🐛 Fix param validation * 🔥 Remove dup methods * ⚡ Refactor all references to `isHttpRequestNodeV2` * 🎨 Fix styling * 🔥 Remove unused import * 🔥 Remove unused properties * 🎨 Fix spacing for Pipedrive Trigger node * 🎨 Undo Webhook node styling change * 🔥 Remove unused style * ⚡ Cover `httpHeaderAuth` edge case * 🐛 Fix `this.node` reference * 🚚 Rename to `credentialsSelect` * 🐛 Fix mistaken renaming * ⚡ Set one attribute per line * ⚡ Move condition to instantiation site * 🚚 Rename prop * ⚡ Refactor away `prepareScopesNotice` * ✏️ Rename i18n keys * ✏️ Update i18n calls * ✏️ Add more i18n keys * 🔥 Remove unused props * ✏️ Add explanatory comment * ⚡ Adjust check in `hasProxyAuth` * ⚡ Refactor `credentialSelected` from prop to event * ⚡ Eventify `valueChanged`, `setFocus`, `onBlur` * ⚡ Eventify `optionSelected` * ⚡ Add `noDataExpression` * 🔥 Remove logging * 🔥 Remove URL from scopes * ⚡ Disregard expressions for display * 🎨 Use CSS modules * 📘 Tigthen interface * 🐛 Fix generic auth display * 🐛 Fix generic auth validation * 📘 Loosen type * 🚚 Move event params to end * ⚡ Generalize reference * ⚡ Refactor generic auth as `credentialsSelect` param * ⏪ Restore check for `httpHeaderAuth ` * 🚚 Rename `existing` to `predefined` * Extend metrics for HTTP Request node (#3282) * ⚡ Extend metrics * 🧪 Add tests * ⚡ Update param names Co-authored-by: Alex Grozav <alex@grozav.com> * ⚡ Update check per new branch * ⚡ Include generic auth check * ⚡ Adjust telemetry (#3359) * ⚡ Filter credential types by label Co-authored-by: Jan Oberhauser <jan.oberhauser@gmail.com> Co-authored-by: Alex Grozav <alex@grozav.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
PLACEHOLDER_FILLED_AT_EXECUTION_TIME,
|
||||
CUSTOM_API_CALL_KEY,
|
||||
} from '@/constants';
|
||||
|
||||
import {
|
||||
@@ -32,12 +33,30 @@ import { restApi } from '@/components/mixins/restApi';
|
||||
import { get } from 'lodash';
|
||||
|
||||
import mixins from 'vue-typed-mixins';
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
export const nodeHelpers = mixins(
|
||||
restApi,
|
||||
)
|
||||
.extend({
|
||||
computed: {
|
||||
...mapGetters('credentials', [ 'getCredentialTypeByName', 'getCredentialsByType' ]),
|
||||
},
|
||||
methods: {
|
||||
hasProxyAuth (node: INodeUi): boolean {
|
||||
return Object.keys(node.parameters).includes('nodeCredentialType');
|
||||
},
|
||||
|
||||
isCustomApiCallSelected (nodeValues: INodeParameters): boolean {
|
||||
const { parameters } = nodeValues;
|
||||
|
||||
if (!isObjectLiteral(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) {
|
||||
@@ -116,6 +135,23 @@ export const nodeHelpers = mixins(
|
||||
return false;
|
||||
},
|
||||
|
||||
reportUnsetCredential(credentialType: ICredentialType) {
|
||||
return {
|
||||
credentials: {
|
||||
[credentialType.name]: [
|
||||
this.$locale.baseText(
|
||||
'nodeHelpers.credentialsUnset',
|
||||
{
|
||||
interpolate: {
|
||||
credentialType: credentialType.displayName,
|
||||
},
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
// Updates the execution issues.
|
||||
updateNodesExecutionIssues () {
|
||||
const nodes = this.$store.getters.allNodes;
|
||||
@@ -198,6 +234,46 @@ export const nodeHelpers = mixins(
|
||||
let credentialType: ICredentialType | null;
|
||||
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.getCredentialTypeByName(genericAuthType);
|
||||
return this.reportUnsetCredential(credential);
|
||||
}
|
||||
|
||||
if (
|
||||
this.hasProxyAuth(node) &&
|
||||
authentication === 'predefinedCredentialType' &&
|
||||
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.hasProxyAuth(node) &&
|
||||
authentication === 'predefinedCredentialType' &&
|
||||
nodeCredentialType !== '' &&
|
||||
selectedCredsAreUnusable(node, nodeCredentialType)
|
||||
) {
|
||||
const credential = this.getCredentialTypeByName(nodeCredentialType);
|
||||
return this.reportUnsetCredential(credential);
|
||||
}
|
||||
|
||||
for (const credentialTypeDescription of nodeType!.credentials!) {
|
||||
// Check if credentials should be displayed else ignore
|
||||
if (this.displayParameter(node.parameters, credentialTypeDescription, '', node) !== true) {
|
||||
@@ -398,3 +474,43 @@ 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 = {
|
||||
authentication: 'none' | 'genericCredentialType' | 'predefinedCredentialType';
|
||||
genericAuthType: string;
|
||||
nodeCredentialType: string;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function isObjectLiteral(maybeObject: unknown): maybeObject is { [key: string]: string } {
|
||||
return typeof maybeObject === 'object' && maybeObject !== null && !Array.isArray(maybeObject);
|
||||
}
|
||||
|
||||
@@ -27,8 +27,13 @@ export const showMessage = mixins(externalHooks).extend({
|
||||
stickyNotificationQueue.push(notification);
|
||||
}
|
||||
|
||||
if(messageData.type === 'error' && track) {
|
||||
this.$telemetry.track('Instance FE emitted error', { error_title: messageData.title, error_message: messageData.message, workflow_id: this.$store.getters.workflowId });
|
||||
if (messageData.type === 'error' && track) {
|
||||
this.$telemetry.track('Instance FE emitted error', {
|
||||
error_title: messageData.title,
|
||||
error_message: messageData.message,
|
||||
caused_by_credential: this.causedByCredential(messageData.message),
|
||||
workflow_id: this.$store.getters.workflowId,
|
||||
});
|
||||
}
|
||||
|
||||
return notification;
|
||||
@@ -135,7 +140,14 @@ export const showMessage = mixins(externalHooks).extend({
|
||||
message,
|
||||
errorMessage: error.message,
|
||||
});
|
||||
this.$telemetry.track('Instance FE emitted error', { error_title: title, error_description: message, error_message: error.message, workflow_id: this.$store.getters.workflowId });
|
||||
|
||||
this.$telemetry.track('Instance FE emitted error', {
|
||||
error_title: title,
|
||||
error_description: message,
|
||||
error_message: error.message,
|
||||
caused_by_credential: this.causedByCredential(error.message),
|
||||
workflow_id: this.$store.getters.workflowId,
|
||||
});
|
||||
},
|
||||
|
||||
async confirmMessage (message: string, headline: string, type: MessageType | null = 'warning', confirmButtonText?: string, cancelButtonText?: string): Promise<boolean> {
|
||||
@@ -203,5 +215,14 @@ export const showMessage = mixins(externalHooks).extend({
|
||||
</details>
|
||||
`;
|
||||
},
|
||||
|
||||
/**
|
||||
* Whether a workflow execution error was caused by a credential issue, as reflected by the error message.
|
||||
*/
|
||||
causedByCredential(message: string | undefined) {
|
||||
if (!message) return false;
|
||||
|
||||
return message.includes('Credentials for') && message.includes('are not set');
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -330,6 +330,11 @@ export const workflowHelpers = mixins(
|
||||
if (node.credentials !== undefined && nodeType.credentials !== undefined) {
|
||||
const saveCredenetials: INodeCredentials = {};
|
||||
for (const nodeCredentialTypeName of Object.keys(node.credentials)) {
|
||||
if (this.hasProxyAuth(node) || Object.keys(node.parameters).includes('genericAuthType')) {
|
||||
saveCredenetials[nodeCredentialTypeName] = node.credentials[nodeCredentialTypeName];
|
||||
continue;
|
||||
}
|
||||
|
||||
const credentialTypeDescription = nodeType.credentials
|
||||
.find((credentialTypeDescription) => credentialTypeDescription.name === nodeCredentialTypeName);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user