Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: email plugins support #2611

Merged
merged 60 commits into from
Oct 13, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
c5b9088
feat: email plugins support
liorzblrn Aug 4, 2024
47b0bf5
chore: remove todo
liorzblrn Aug 4, 2024
99d478f
fix: fetch customer secret from external provider
liorzblrn Aug 5, 2024
bdad181
fix: improve check using includes
liorzblrn Aug 5, 2024
d002693
fix: improve readability
liorzblrn Aug 5, 2024
af7e237
fix: improve secret replace
liorzblrn Aug 6, 2024
3cc3767
fix: remove error stacktrace
liorzblrn Aug 6, 2024
35d4fff
fix: remove error stacktrace
liorzblrn Aug 6, 2024
8fcd3bf
fix: replace INVITATION typo
liorzblrn Aug 6, 2024
b0a9820
fix: reset data migration
liorzblrn Aug 6, 2024
d71cdfd
Merge branch 'bal-2513-email-plugin-fixes' of github.com:ballerine-io…
liorzblrn Aug 6, 2024
752607c
fix: fix plugins secrets
liorzblrn Aug 6, 2024
1e1355e
chore: fix plugin registry info issues
liorzblrn Aug 8, 2024
c57c8b9
fix: additional plugins
liorzblrn Aug 12, 2024
1cf619b
feat: fix data mapping
liorzblrn Sep 22, 2024
b99febb
fix: webhook
liorzblrn Sep 24, 2024
e40137e
chore: merge dev
liorzblrn Sep 25, 2024
07f2aa4
fix: circular dependency
liorzblrn Sep 25, 2024
00c6a78
fix: circular dependency 2
liorzblrn Sep 25, 2024
c9ee508
fix: plugin on client side
liorzblrn Sep 25, 2024
ec83773
fix: changes for plugins
liorzblrn Sep 29, 2024
af79dcb
feat: added stepper customization & updated pallete classes
chesterkmr Sep 5, 2024
15a3d6d
feat: reworked document field design
chesterkmr Sep 5, 2024
0627531
feat: added clear field functionality
chesterkmr Sep 5, 2024
da21a97
feat: implemented dynamic powered by logo & added white poweredby
chesterkmr Sep 5, 2024
5f42f5b
fix: fixed & clean
chesterkmr Sep 5, 2024
2ea4724
feat: refactor
chesterkmr Sep 5, 2024
64ed794
feat: added logo to theme definition
chesterkmr Sep 16, 2024
06737df
feat: implemented visibleOn for nested inputs
chesterkmr Sep 28, 2024
aea77f3
fix: fixed typos
chesterkmr Oct 1, 2024
e1bc5f1
fix: deleted theme
chesterkmr Oct 1, 2024
7ab309a
feat: added theme to ui def
chesterkmr Oct 1, 2024
7a98b7c
fix: fixed types & theme provider & added migration for theme
chesterkmr Oct 1, 2024
1ef0964
chore(nuvei): merge
alonp99 Oct 2, 2024
e726c98
fix(social): link extraction prompt fix
alonp99 Oct 2, 2024
71f2fe1
chore(nuvei): some fixes
alonp99 Oct 5, 2024
6928ac4
fix: changes for plugins 2
liorzblrn Oct 5, 2024
2e04b91
fix: rename kyb to businessInformation
liorzblrn Oct 5, 2024
d5eb09b
chore: fix data migration ref
liorzblrn Oct 6, 2024
90f066d
chore: pull from remote
liorzblrn Oct 6, 2024
109217b
chore: pull from dev
liorzblrn Oct 6, 2024
6a6f74e
chore: fix file after merge 1
liorzblrn Oct 6, 2024
b8085ff
fix: remove get url without variables
liorzblrn Oct 6, 2024
933ed5f
fix: modify kyc session and email templates
liorzblrn Oct 6, 2024
9eaa524
fix: display names and plugin names
liorzblrn Oct 6, 2024
cd950b7
chore(ubo-asiaverify): change realtime flag per production
alonp99 Oct 7, 2024
69a4009
fix(kyc-session): fix kyc session, added mastercard
alonp99 Oct 7, 2024
d03f0c9
fix(vscode): submodule search issue
alonp99 Oct 8, 2024
7da3a9d
fix: support individual-sanctions new options
liorzblrn Oct 8, 2024
22871ec
Merge branch 'bal-2513-email-plugin-fixes' of github.com:ballerine-io…
liorzblrn Oct 8, 2024
baad4cf
feat: support url options
liorzblrn Oct 8, 2024
59d7d2a
chore(merge): dev
alonp99 Oct 8, 2024
59e0b85
feat: add default country for registry information and sanctions and …
liorzblrn Oct 9, 2024
b9eac99
feat: add default country for plugins
liorzblrn Oct 9, 2024
88091e6
fix: default value for plugins
liorzblrn Oct 9, 2024
f7f61d7
Merge branch 'dev' into bal-2513-email-plugin-fixes
alonp99 Oct 9, 2024
035fd7b
chore: merge with dev
liorzblrn Oct 10, 2024
e89c31b
Merge branch 'dev' into bal-2513-email-plugin-fixes
alonp99 Oct 12, 2024
93ad5df
chore(merge): dev
alonp99 Oct 12, 2024
461e1bd
chore(merge): dev
alonp99 Oct 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -59,23 +59,23 @@ export class ApiPlugin {
}
}

const urlWithoutPlaceholders = await this.replaceValuePlaceholders(this.url, context);
const _url = await this._getPluginUrl(context);

logger.log('API Plugin - Sending API request', {
url: urlWithoutPlaceholders,
url: _url,
method: this.method,
});

const apiResponse = await this.makeApiRequest(
urlWithoutPlaceholders,
_url,
this.method,
requestPayload,
await this.composeRequestHeaders(this.headers!, context),
);

logger.log('API Plugin - Received response', {
status: apiResponse.statusText,
url: urlWithoutPlaceholders,
url: _url,
});

if (apiResponse.ok) {
Expand Down Expand Up @@ -115,6 +115,10 @@ export class ApiPlugin {
}
}

protected async _getPluginUrl(_: AnyRecord) {
return this.url;
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider simplifying _getPluginUrl method

The _getPluginUrl method currently returns this.url without additional logic. If there's no plan to extend or override this method in subclasses, you might consider using this.url directly or converting it to a getter property to simplify the code.

returnSuccessResponse(callbackAction: string, responseBody: AnyRecord) {
return { callbackAction, responseBody };
}
Expand Down Expand Up @@ -142,11 +146,7 @@ export class ApiPlugin {
};

if (payload) {
for (const key of Object.keys(payload)) {
if (typeof payload[key] === 'string') {
payload[key] = await this.replaceValuePlaceholders(payload[key] as string, payload);
}
}
payload = await this._onPreparePayload(payload);

// @TODO: Use an enum over string literals for HTTP methods
if (this.method.toUpperCase() !== 'GET') {
Expand All @@ -172,6 +172,18 @@ export class ApiPlugin {
return res;
}

private async _onPreparePayload(_payload: AnyRecord) {
const returnObj = JSON.parse(JSON.stringify(_payload));

for (const key of Object.keys(returnObj)) {
if (typeof returnObj[key] === 'string') {
returnObj[key] = await this.replaceAllVariables(returnObj[key] as string, returnObj);
}
}

return returnObj;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optimize _onPreparePayload function.

The implementation is correct, but the function can be optimized by using a more functional approach.

-    for (const key of Object.keys(returnObj)) {
-      if (typeof returnObj[key] === 'string') {
-        returnObj[key] = await this.replaceAllVariables(returnObj[key] as string, returnObj);
-      }
-    }
+    await Promise.all(Object.keys(returnObj).map(async key => {
+      if (typeof returnObj[key] === 'string') {
+        returnObj[key] = await this.replaceAllVariables(returnObj[key] as string, returnObj);
+      }
+    }));
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private async _onPreparePayload(_payload: AnyRecord) {
const returnObj = JSON.parse(JSON.stringify(_payload));
for (const key of Object.keys(returnObj)) {
if (typeof returnObj[key] === 'string') {
returnObj[key] = await this.replaceAllVariables(returnObj[key] as string, returnObj);
}
}
return returnObj;
}
private async _onPreparePayload(_payload: AnyRecord) {
const returnObj = JSON.parse(JSON.stringify(_payload));
await Promise.all(Object.keys(returnObj).map(async key => {
if (typeof returnObj[key] === 'string') {
returnObj[key] = await this.replaceAllVariables(returnObj[key] as string, returnObj);
}
}));
return returnObj;
}


async transformData(transformers: Transformers | undefined, record: AnyRecord) {
let mutatedRecord = record;

Expand Down Expand Up @@ -218,14 +230,25 @@ export class ApiPlugin {
Object.entries(headers).map(async header => [
header[0],
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
await this.replaceValuePlaceholders(header[1], context),
await this.replaceAllVariables(header[1], context),
]),
);

return Object.fromEntries(headersEntries);
}

async replaceValuePlaceholders(content: string, context: TContext) {
_onReplaceVariable(variableKey: string, replacedContent: string, placeholder: string) {
const replacedCustomerSecrets = this.replaceSecrets(
'customer',
variableKey,
replacedContent,
placeholder,
);

return replacedCustomerSecrets;
}

async replaceAllVariables(content: string, context: TContext) {
const placeholders = content.match(/{(.*?)}/g);

if (!placeholders) return content;
Expand All @@ -235,29 +258,38 @@ export class ApiPlugin {
for (const placeholder of placeholders) {
const variableKey = placeholder.replace(/{|}/g, '');

const isSystemSecret = variableKey.includes('secret.');
const isSecret = variableKey.includes('secrets.');

if (isSystemSecret) {
const secretKey = variableKey.replace('secret.', '');
const secretValue = `${this.getSystemSecret(secretKey)}`;

replacedContent = replacedContent.replace(placeholder, secretValue);
} else if (isSecret) {
const secretKey = variableKey.replace('secrets.', '');
const secretValue = `${await this.fetchSecret(secretKey)}`;
const replacedVariable = this._onReplaceVariable(variableKey, replacedContent, placeholder);

replacedContent = replacedContent.replace(placeholder, secretValue);
} else {
const placeholderValue = `${this.fetchObjectPlaceholderValue(context, variableKey)}`;

replacedContent = replacedContent.replace(placeholder, placeholderValue);
if (replacedVariable) {
return replacedVariable;
}

const placeholderValue = `${this.fetchObjectPlaceholderValue(context, variableKey)}`;
replacedContent = replacedContent.replace(placeholder, placeholderValue);
}

return replacedContent;
}

replaceSecrets(
provider: 'ballerine' | 'customer',
variableKey: string,
replacedContent: string,
placeholder: string,
): string | undefined {
const variableName = `secret${provider === 'ballerine' ? '' : 's'}.`;

if (variableKey.includes(variableName)) {
const secretKey = variableKey.replace(variableName, '');
const secretValue = `${this.getSystemSecret(secretKey)}`;

replacedContent = replacedContent.replace(placeholder, secretValue);

return replacedContent;
}
return undefined;
}

getSystemSecret(key: string) {
return process.env[key] || '';
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { AnyRecord } from '@ballerine/common';
import { logger } from '../../logger';
import { IApiPluginParams } from './types';
import { ApiPlugin } from './api-plugin';
import { ApiEmailTemplates } from './vendor-consts';
import { BallerineApiPlugin, IBallerineApiPluginParams } from './ballerine-plugin';

export interface IBallerineEmailPluginParams {
pluginKind: 'template-email';
template: ApiEmailTemplates;
displayName: string | undefined;
stateNames: string[];
}

export class BallerineEmailPlugin extends BallerineApiPlugin {
public static pluginType = 'http';

constructor(params: IBallerineEmailPluginParams & IBallerineApiPluginParams & IApiPluginParams) {
super(params);
liorzam marked this conversation as resolved.
Show resolved Hide resolved
}
liorzam marked this conversation as resolved.
Show resolved Hide resolved
Comment on lines +15 to +20
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove unnecessary constructor.

The current constructor doesn't add any functionality beyond calling the super constructor. It can be safely removed to simplify the code.

Apply this diff to remove the unnecessary constructor:

 export class BallerineEmailPlugin extends BallerineApiPlugin {
   public static pluginType = 'http';

-  constructor(params: IBallerineEmailPluginParams & IBallerineApiPluginParams & IApiPluginParams) {
-    super(params);
-  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export class BallerineEmailPlugin extends BallerineApiPlugin {
public static pluginType = 'http';
constructor(params: IBallerineEmailPluginParams & IBallerineApiPluginParams & IApiPluginParams) {
super(params);
}
export class BallerineEmailPlugin extends BallerineApiPlugin {
public static pluginType = 'http';
🧰 Tools
🪛 Biome

[error] 18-20: This constructor is unnecessary.

Unsafe fix: Remove the unnecessary constructor.

(lint/complexity/noUselessConstructor)


async makeApiRequest(
url: string,
method: ApiPlugin['method'],
payload: AnyRecord,
headers: HeadersInit,
liorzam marked this conversation as resolved.
Show resolved Hide resolved
) {
Comment on lines +22 to +27
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider adding type safety for payload properties.

The payload parameter is of type AnyRecord, which doesn't provide type safety for its properties. Consider defining a more specific interface for the expected payload structure to improve type safety and catch potential errors at compile-time.

Would you like assistance in creating a more specific interface for the payload?

const from = { from: { email: payload.from, ...(payload.name ? { name: payload.name } : {}) } };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add validation for 'payload.from' to prevent potential errors

The from field assumes that payload.from is provided. If it's undefined, it could lead to issues when sending the email. Consider adding validation to ensure payload.from is defined.

Apply this diff to add validation:

       const from = { from: { email: payload.from, ...(payload.name ? { name: payload.name } : {}) } };
+      if (!payload.from) {
+        throw new Error('payload.from is required and must be a valid email address');
+      }

Committable suggestion was skipped due to low confidence.

const subject = payload.subject
? {
subject: await (this as unknown as ApiPlugin).replaceAllVariables(
payload.subject as string,
payload,
),
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Simplify method calls by removing unnecessary type casting

The current implementation casts this to unknown and then to ApiPlugin to access the replaceAllVariables method. This can be simplified by adjusting the access modifier of replaceAllVariables in the ApiPlugin class to protected, allowing direct access without casting. This improves readability and maintainability.

Apply this diff to simplify the method calls:

-             subject: await (this as unknown as ApiPlugin).replaceAllVariables(
+             subject: await this.replaceAllVariables(

Similarly, update other instances where the casting is used:

-             preheader: await (this as unknown as ApiPlugin).replaceAllVariables(
+             preheader: await this.replaceAllVariables(

And in the replaceVariablesInPayload method:

-           payload[key] = await (this as unknown as ApiPlugin).replaceAllVariables(
+           payload[key] = await this.replaceAllVariables(

Ensure that the replaceAllVariables method in the ApiPlugin class is marked as protected to allow subclass access.

Also applies to: 39-43, 84-88

: {};
const preheader = payload.preheader
? {
preheader: await (this as unknown as ApiPlugin).replaceAllVariables(
payload.preheader as string,
payload,
),
}
: {};
const receivers = (payload.receivers as string[]).map(receiver => {
return { email: receiver };
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add error handling for undefined or invalid 'payload.receivers'

The code assumes that payload.receivers is defined and is an array of strings. If payload.receivers is undefined or not an array, it could cause a runtime error. Add validation to ensure payload.receivers is properly defined.

Apply this diff to add validation:

       const receivers = (payload.receivers as string[]).map(receiver => {
+        if (!Array.isArray(payload.receivers)) {
+          throw new Error('payload.receivers must be an array of strings');
+        }
         return { email: receiver };
       });

Alternatively, provide a default or handle the case when receivers are not provided.

Committable suggestion was skipped due to low confidence.

const to = { to: receivers };
const templateId = { template_id: payload.templateId };

for (const key of Object.keys(payload)) {
if (typeof payload[key] === 'string') {
payload[key] = await (this as unknown as ApiPlugin).replaceAllVariables(
payload[key] as string,
payload,
);
}
}

const emailPayload = {
...from,
personalizations: [
{
...preheader,
...subject,
...to,
...{ dynamic_template_data: payload },
},
],
...templateId,
};

payload.adapter ??= 'sendgrid';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Avoid mutating the 'payload' parameter directly

The line payload.adapter ??= 'sendgrid'; mutates the payload object directly, which might lead to unintended side effects elsewhere in the codebase if payload is reused. Consider creating a copy of payload before mutation.

Apply this diff to create a copy of payload:

-       payload.adapter ??= 'sendgrid';
+       const emailPayload = { ...payload };
+       emailPayload.adapter ??= 'sendgrid';

Then use emailPayload in subsequent code to prevent side effects.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
payload.adapter ??= 'sendgrid';
const emailPayload = { ...payload };
emailPayload.adapter ??= 'sendgrid';


if (payload.adapter === 'log') {
logger.warn('No email provider', { emailPayload });
liorzam marked this conversation as resolved.
Show resolved Hide resolved

return {
ok: true,
json: () => Promise.resolve({}),
statusText: 'OK',
};
}

return await super.makeApiRequest(url, method, emailPayload, headers);
}
liorzam marked this conversation as resolved.
Show resolved Hide resolved

returnSuccessResponse(callbackAction: string, responseBody: AnyRecord) {
return super.returnSuccessResponse(callbackAction, {});
}
Comment on lines +74 to +76
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Clarify the purpose of returnSuccessResponse override.

The current implementation ignores the responseBody parameter and always returns an empty object. This could lead to loss of information. If this behavior is intentional, consider removing the responseBody parameter and adding a comment explaining why an empty object is always returned.

If the empty response is intentional, consider this change:

returnSuccessResponse(callbackAction: string) {
  // Always return an empty object as the response body for email operations
  return super.returnSuccessResponse(callbackAction, {});
}

If not, consider passing the responseBody to the super method:

returnSuccessResponse(callbackAction: string, responseBody: AnyRecord) {
  return super.returnSuccessResponse(callbackAction, responseBody);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { AnyRecord } from '@ballerine/common';
import { WorkflowRunner } from '../../workflow-runner';
import { IBallerineApiPluginParams } from './ballerine-plugin';
import { ApiPlugin, IApiPluginParams } from '.';
import { ApiBallerinePlugins, BALLERINE_API_PLUGIN_FACTORY } from './vendor-consts';

export interface IBallerineApiPluginParams {
pluginKind: ApiBallerinePlugins;
vendor?: string;
displayName: string | undefined;
stateNames: string[];
}
Comment on lines +5 to +10
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix redeclaration issue for IBallerineApiPluginParams.

The IBallerineApiPluginParams interface is redeclared in this file. Consider deleting or renaming it to avoid conflicts.

- export interface IBallerineApiPluginParams {
-   pluginKind: ApiBallerinePlugins;
-   vendor?: string;
-   displayName: string | undefined;
-   stateNames: string[];
- }
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export interface IBallerineApiPluginParams {
pluginKind: ApiBallerinePlugins;
vendor?: string;
displayName: string | undefined;
stateNames: string[];
}
Tools
Biome

[error] 7-7: Shouldn't redeclare 'IBallerineApiPluginParams'. Consider to delete it or rename it.

'IBallerineApiPluginParams' is defined here:

(lint/suspicious/noRedeclare)


const _validatePlugin = (params: IBallerineApiPluginParams & IApiPluginParams) => {
let optionsFactoryFn: any = BALLERINE_API_PLUGIN_FACTORY[params.pluginKind];

if (!optionsFactoryFn) {
throw new Error(`Unknown plugin kind: ${params.pluginKind}`);
}
};

const _getPluginOptions = (params: IBallerineApiPluginParams & IApiPluginParams) => {
_validatePlugin(params);
let optionsFactoryFn;

const pluginOptionFactoryFn = BALLERINE_API_PLUGIN_FACTORY[params.pluginKind] as any;

if (
params.pluginKind === 'individual-sanctions' ||
params.pluginKind === 'company-sanctions' ||
params.pluginKind === 'ubo'
) {
if (!params.vendor) {
throw new Error(`Missed vendor for: ${params.pluginKind}`);
}
optionsFactoryFn = pluginOptionFactoryFn[params.vendor];
}

if (params.pluginKind === 'template-email') {
if (!params.template) {
throw new Error(`Missed templateName for: ${params.pluginKind}`);
}
optionsFactoryFn = pluginOptionFactoryFn[params.template];
}

if (!optionsFactoryFn) {
throw new Error(`Unknown plugin kind: ${params.pluginKind}, params: ${JSON.stringify(params)}`);
}

return optionsFactoryFn(params as any);
};
liorzam marked this conversation as resolved.
Show resolved Hide resolved

export class BallerineApiPlugin extends ApiPlugin {
public static pluginType = 'http';

constructor(params: IBallerineApiPluginParams & IApiPluginParams) {
let options = _getPluginOptions(params);

let { requestTransformer, requestValidator, responseTransformer, responseValidator } =
WorkflowRunner.reqResTransformersObj({
params,
...options,
});

super({
persistResponseDestination: undefined,
...params,
...options,
request: { transformers: requestTransformer, schemaValidator: requestValidator } as any,
response: { transformers: responseTransformer, schemaValidator: responseValidator } as any,
});
}
liorzam marked this conversation as resolved.
Show resolved Hide resolved

protected async _getPluginUrl(context: AnyRecord) {
return await this.replaceAllVariables(this.url, context);
}

_onReplaceVariable(variableKey: string, replacedContent: string, placeholder: string) {
let replacedSecrets = this.replaceSecrets(
'ballerine',
variableKey,
replacedContent,
placeholder,
);

if (replacedSecrets) {
return replacedSecrets;
}

replacedSecrets = super._onReplaceVariable(variableKey, replacedContent, placeholder);

if (replacedSecrets) {
return replacedSecrets;
}
}
}
Loading