@gotohuman/n8n-nodes-gotohuman
Version:
n8n node to request human reviews in AI workflows with gotoHuman
487 lines • 21.8 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.GotoHuman = void 0;
const n8n_workflow_1 = require("n8n-workflow");
const BASE_URL = 'https://api.gotohuman.com';
const VERSION = 1;
class GotoHuman {
constructor() {
this.description = {
displayName: 'gotoHuman',
name: 'gotoHuman',
icon: 'file:gotohuman.svg',
group: ['transform'],
version: VERSION,
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
description: 'Request human reviews with gotoHuman',
defaults: {
name: 'gotoHuman',
},
inputs: ["main"],
outputs: ["main"],
credentials: [
{
name: 'gotoHumanApi',
required: true,
},
],
webhooks: [
{
name: 'default',
httpMethod: 'POST',
responseMode: 'onReceived',
responseData: '',
path: '={{ $nodeId }}',
restartWebhook: true,
isFullPath: true,
}
],
properties: [
{
displayName: 'Resource',
name: 'resource',
type: 'options',
noDataExpression: true,
options: [
{
name: 'Review Request',
value: 'reviewRequest',
description: 'Request for Human Review',
},
],
default: 'reviewRequest',
required: true,
},
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
options: [
{
name: 'Send and Wait for Response',
value: 'sendAndWait',
action: 'Send review request and wait for response',
description: 'Request a human review and wait for the response',
},
],
default: 'sendAndWait',
displayOptions: {
show: {
resource: [
'reviewRequest',
],
},
},
},
{
displayName: 'Review Template',
name: 'reviewTemplateID',
type: 'resourceLocator',
description: 'Choose a review template from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
default: { mode: 'list', value: '' },
modes: [
{
displayName: 'From List',
name: 'list',
type: 'list',
placeholder: 'Select a Review Template...',
typeOptions: {
searchListMethod: 'searchReviewTemplates',
searchable: true,
},
},
{
displayName: 'ID',
name: 'id',
type: 'string',
hint: 'Enter an ID',
placeholder: 'e.g. FjbxGtNfPIuDdRm55eqK',
}
],
displayOptions: {
show: {
resource: [
'reviewRequest',
],
operation: [
'sendAndWait',
],
},
},
},
{
displayName: 'Fields',
name: 'fields',
type: 'resourceMapper',
noDataExpression: true,
default: {
mappingMode: 'defineBelow',
value: null,
},
required: true,
typeOptions: {
loadOptionsDependsOn: ['reviewTemplateID.value'],
resourceMapper: {
resourceMapperMethod: 'getMappingFields',
mode: 'add',
valuesLabel: 'Fields',
fieldWords: {
singular: 'field',
plural: 'fields',
},
supportAutoMap: false,
},
},
displayOptions: {
show: {
resource: ['reviewRequest'],
operation: ['sendAndWait'],
},
hide: { reviewTemplateID: [''] },
},
},
{
displayName: 'Meta Data',
name: 'metaSelect',
description: 'Select if you want to add meta data that you want to receive back in the response webhook',
required: true,
type: 'options',
options: [
{
name: 'No Meta Data',
value: 'no',
},
{
name: 'Add as JSON',
value: 'json',
},
{
name: 'Add as Key-Value Pairs',
value: 'keyValue',
},
],
default: 'no',
displayOptions: {
show: {
resource: ['reviewRequest'],
operation: ['sendAndWait'],
},
hide: { reviewTemplateID: [''] },
},
},
{
displayName: 'Meta Data JSON',
name: 'metaJson',
type: 'json',
default: '',
placeholder: 'e.g. {"myKey": "myValue"}',
displayOptions: {
show: {
resource: ['reviewRequest'],
operation: ['sendAndWait'],
metaSelect: ['json'],
},
hide: { reviewTemplateID: [''] },
},
},
{
displayName: 'Meta Data Values',
name: 'metaKeyValues',
type: 'fixedCollection',
placeholder: 'Add Key-Value Pair',
default: {},
typeOptions: {
multipleValues: true,
},
options: [
{
name: 'metaArray',
displayName: 'Metadata',
values: [
{
displayName: 'Key',
name: 'key',
type: 'string',
placeholder: 'myKey',
default: '',
description: 'Name of the metadata key to add',
},
{
displayName: 'Value',
name: 'value',
type: 'string',
placeholder: 'myValue',
default: '',
description: 'Value to set for the metadata key',
},
],
},
],
displayOptions: {
show: {
resource: ['reviewRequest'],
operation: ['sendAndWait'],
metaSelect: ['keyValue'],
},
hide: { reviewTemplateID: [''] },
},
},
{
displayName: 'Assigned Users',
name: 'assignToSelect',
description: 'Select who you want to assign the review to',
required: true,
type: 'options',
options: [
{
name: 'All Users in Your Account',
value: 'all',
},
{
name: 'Only Selected Users',
value: 'selectByEmail',
},
],
default: 'all',
displayOptions: {
show: {
resource: ['reviewRequest'],
operation: ['sendAndWait'],
},
hide: { reviewTemplateID: [''] },
},
},
{
displayName: 'Selected Users',
name: 'assignTo',
description: 'List the email addresses of the users you want to assign the review to',
type: 'fixedCollection',
placeholder: 'Add User',
typeOptions: {
multipleValues: true,
},
default: [],
options: [
{
displayName: 'Values',
name: 'values',
values: [
{
displayName: 'Email Address',
name: 'email',
description: 'The email address the user used when signing up for gotoHuman',
type: 'string',
required: true,
placeholder: 'e.g. nathan@example.com',
default: '',
hint: 'Only emails from registered users will be accepted',
},
],
},
],
displayOptions: {
show: {
resource: ['reviewRequest'],
operation: ['sendAndWait'],
assignToSelect: ['selectByEmail'],
},
hide: { reviewTemplateID: [''] },
},
},
{
displayName: 'Additional Fields',
name: 'additionalFields',
type: 'collection',
placeholder: 'Add Field',
default: {},
displayOptions: {
show: {
resource: ['reviewRequest'],
operation: ['sendAndWait'],
},
hide: { reviewTemplateID: [''] },
},
options: [
{
displayName: 'Update for Review ID',
name: 'updateForReviewId',
type: 'string',
default: '',
description: 'To update a specific review, enter the review ID here. Read more about it <a href="https://docs.gotohuman.com/retries">here</a>.',
},
],
}
]
};
this.methods = {
listSearch: {
async searchReviewTemplates(filter) {
const options = {
method: 'GET',
uri: `${BASE_URL}/fetchN8nForms`,
json: true,
};
const reviewTemplates = await this.helpers.requestWithAuthentication.call(this, 'gotoHumanApi', options);
if (reviewTemplates === undefined) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'No review templates found. Please create one first in our web app.');
}
return {
results: ((reviewTemplates === null || reviewTemplates === void 0 ? void 0 : reviewTemplates.forms) || [])
.filter((template) => !filter || template.label.toLowerCase().includes(filter.toLowerCase()))
.map((template) => ({
name: template.label,
value: template.value,
})),
};
},
},
resourceMapping: {
async getMappingFields() {
const reviewTemplateObj = this.getNodeParameter('reviewTemplateID', 0);
if (!reviewTemplateObj)
return { fields: [] };
const { value: reviewTemplateID } = reviewTemplateObj;
const options = {
method: 'GET',
uri: `${BASE_URL}/fetchN8nFields`,
qs: {
formId: reviewTemplateID,
},
json: true,
};
const templateFieldsResponse = await this.helpers.requestWithAuthentication.call(this, 'gotoHumanApi', options);
if ((templateFieldsResponse === null || templateFieldsResponse === void 0 ? void 0 : templateFieldsResponse.fields) === undefined) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'No fields found for review template. Please add some fields in our web editor first.');
}
const fieldSpecs = templateFieldsResponse.fields;
return {
fields: fieldSpecs
};
}
}
};
this.webhook = async function () {
const bodyData = this.getBodyData();
return {
workflowData: [this.helpers.returnJsonArray(bodyData)],
};
};
}
async execute() {
const items = this.getInputData();
const returnData = [];
let waitMillis = 14 * 24 * 60 * 60 * 1000;
for (let i = 0; i < items.length; i++) {
try {
const resource = this.getNodeParameter('resource', i);
const operation = this.getNodeParameter('operation', i);
if (resource === 'reviewRequest' && operation === 'sendAndWait') {
const reviewTemplateID = this.getNodeParameter('reviewTemplateID', i);
const fieldsParam = this.getNodeParameter('fields', i);
const metaSelect = this.getNodeParameter('metaSelect', i);
let meta = undefined;
if (metaSelect === 'json') {
const metaJson = this.getNodeParameter('metaJson', i);
if (metaJson) {
try {
meta = (0, n8n_workflow_1.jsonParse)(metaJson);
}
catch (error) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Not a valid JSON object', error);
}
}
}
else if (metaSelect === 'keyValue') {
const metaKeyValues = this.getNodeParameter('metaKeyValues', i);
if (metaKeyValues && Array.isArray(metaKeyValues.metaArray)) {
meta = {};
for (const pair of metaKeyValues.metaArray) {
meta[pair.key] = pair.value;
}
}
}
const assignToSelect = this.getNodeParameter('assignToSelect', i);
let assignTo = undefined;
if (assignToSelect === 'selectByEmail') {
const assignToParam = this.getNodeParameter('assignTo', i);
if (assignToParam && Array.isArray(assignToParam.values)) {
assignTo = assignToParam.values.map((v) => v.email);
}
}
const additionalFields = this.getNodeParameter('additionalFields', i);
const schemaList = fieldsParam.schema;
let parsedFields = { ...(fieldsParam.value || {}) };
if (fieldsParam.value && schemaList) {
for (const field of schemaList) {
const val = fieldsParam.value[field.id];
if ((field.type === 'array' || field.type === 'object') && typeof val === 'string') {
try {
parsedFields[field.id] = (0, n8n_workflow_1.jsonParse)(val);
}
catch (err) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Could not parse field '${field.id}' as JSON: ${val}`);
}
}
}
}
const body = {
formId: reviewTemplateID.value,
fields: parsedFields,
origin: 'n8n',
originV: VERSION,
};
if (meta !== undefined)
body.meta = meta;
if (assignTo !== undefined)
body.assignTo = assignTo;
if (additionalFields && additionalFields.updateForReviewId) {
body.updateForReviewId = additionalFields.updateForReviewId;
}
const resumeUrl = this.evaluateExpression('{{ $execution?.resumeUrl }}', i);
const nodeId = this.evaluateExpression('{{ $nodeId }}', i);
body.webhookUrl = `${resumeUrl}/${nodeId}`;
const options = {
method: 'POST',
uri: `${BASE_URL}/requestReview`,
body,
json: true,
};
let responseData;
try {
responseData = await this.helpers.requestWithAuthentication.call(this, 'gotoHumanApi', options);
const statusCode = (responseData === null || responseData === void 0 ? void 0 : responseData.statusCode) || 200;
if (String(statusCode).startsWith('4') || String(statusCode).startsWith('5')) {
throw new n8n_workflow_1.NodeApiError(this.getNode(), responseData, {
message: responseData.body ? responseData.body : JSON.stringify(responseData),
httpCode: String(statusCode),
});
}
if (responseData && typeof responseData.timeoutInMillis === 'number' && responseData.timeoutInMillis > 0) {
waitMillis = responseData.timeoutInMillis;
}
}
catch (error) {
throw error;
}
returnData.push({ json: responseData });
}
else {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `The operation ${operation} is not supported!`);
}
}
catch (err) {
if (this.continueOnFail()) {
returnData.push({ json: { error: err.message }, error: err });
continue;
}
throw err;
}
}
const waitTill = new Date(new Date().getTime() + waitMillis);
await this.putExecutionToWait(waitTill);
return [this.getInputData()];
}
}
exports.GotoHuman = GotoHuman;
//# sourceMappingURL=GotoHuman.node.js.map