mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-27 13:39:44 -08:00
1d27a9e87e
* Add path mapping and response error interfaces * Add error handling and throwing functionality * Refactor error handling into a single function * Re-implement error handling in Hacker News node * Fix linting details * Re-implement error handling in Spotify node * Re-implement error handling in G Suite Admin node * 🚧 create basic setup NodeError * 🚧 add httpCodes * 🚧 add path priolist * 🚧 handle statusCode in error, adjust interfaces * 🚧 fixing type issues w/Ivan * 🚧 add error exploration * 👔 fix linter issues * 🔧 improve object check * 🚧 remove path passing from NodeApiError * 🚧 add multi error + refactor findProperty method * 👔 allow any * 🔧 handle multi error message callback * ⚡ change return type of callback * ⚡ add customCallback to MultiError * 🚧 refactor to use INode * 🔨 handle arrays, continue search after first null property found * 🚫 refactor method access * 🚧 setup NodeErrorView * ⚡ change timestamp to Date.now * 📚 Add documentation for methods and constants * 🚧 change message setting * 🚚 move NodeErrors to workflow * ✨ add new ErrorView for Nodes * 🎨 improve error notification * 🎨 refactor interfaces * ⚡ add WorkflowOperationError, refactor error throwing * 👕 fix linter issues * 🎨 rename param * 🐛 fix handling normal errors * ⚡ add usage of NodeApiError * 🎨 fix throw new error instead of constructor * 🎨 remove unnecessary code/comments * 🎨 adjusted spacing + updated status messages * 🎨 fix tab indentation * ✨ Replace current errors with custom errors (#1576) * ⚡ Introduce NodeApiError in catch blocks * ⚡ Introduce NodeOperationError in nodes * ⚡ Add missing errors and remove incompatible * ⚡ Fix NodeOperationError in incompatible nodes * 🔧 Adjust error handling in missed nodes PayPal, FileMaker, Reddit, Taiga and Facebook Graph API nodes * 🔨 Adjust Strava Trigger node error handling * 🔨 Adjust AWS nodes error handling * 🔨 Remove duplicate instantiation of NodeApiError * 🐛 fix strava trigger node error handling * Add XML parsing to NodeApiError constructor (#1633) * 🐛 Remove type annotation from catch variable * ✨ Add XML parsing to NodeApiError * ⚡ Simplify error handling in Rekognition node * ⚡ Pass in XML flag in generic functions * 🔥 Remove try/catch wrappers at call sites * 🔨 Refactor setting description from XML * 🔨 Refactor let to const in resource loaders * ⚡ Find property in parsed XML * ⚡ Change let to const * 🔥 Remove unneeded try/catch block * 👕 Fix linting issues * 🐛 Fix errors from merge conflict resolution * ⚡ Add custom errors to latest contributions * 👕 Fix linting issues * ⚡ Refactor MongoDB helpers for custom errors * 🐛 Correct custom error type * ⚡ Apply feedback to A nodes * ⚡ Apply feedback to missed A node * ⚡ Apply feedback to B-D nodes * ⚡ Apply feedback to E-F nodes * ⚡ Apply feedback to G nodes * ⚡ Apply feedback to H-L nodes * ⚡ Apply feedback to M nodes * ⚡ Apply feedback to P nodes * ⚡ Apply feedback to R nodes * ⚡ Apply feedback to S nodes * ⚡ Apply feedback to T nodes * ⚡ Apply feedback to V-Z nodes * ⚡ Add HTTP code to iterable node error * 🔨 Standardize e as error * 🔨 Standardize err as error * ⚡ Fix error handling for non-standard nodes Co-authored-by: Ben Hesseldieck <b.hesseldieck@gmail.com> Co-authored-by: Ben Hesseldieck <b.hesseldieck@gmail.com> Co-authored-by: Ben Hesseldieck <1849459+BHesseldieck@users.noreply.github.com>
514 lines
12 KiB
TypeScript
514 lines
12 KiB
TypeScript
import {
|
|
IExecuteFunctions,
|
|
} from 'n8n-core';
|
|
|
|
import {
|
|
IBinaryKeyData,
|
|
IDataObject,
|
|
INodeExecutionData,
|
|
INodeType,
|
|
INodeTypeDescription,
|
|
NodeApiError,
|
|
NodeOperationError,
|
|
} from 'n8n-workflow';
|
|
|
|
import {
|
|
awsApiRequestREST,
|
|
keysTPascalCase,
|
|
} from './GenericFunctions';
|
|
|
|
export class AwsRekognition implements INodeType {
|
|
description: INodeTypeDescription = {
|
|
displayName: 'AWS Rekognition',
|
|
name: 'awsRekognition',
|
|
icon: 'file:rekognition.svg',
|
|
group: ['output'],
|
|
version: 1,
|
|
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
|
description: 'Sends data to AWS Rekognition',
|
|
defaults: {
|
|
name: 'AWS Rekognition',
|
|
color: '#305b94',
|
|
},
|
|
inputs: ['main'],
|
|
outputs: ['main'],
|
|
credentials: [
|
|
{
|
|
name: 'aws',
|
|
required: true,
|
|
},
|
|
],
|
|
properties: [
|
|
{
|
|
displayName: 'Resource',
|
|
name: 'resource',
|
|
type: 'options',
|
|
options: [
|
|
{
|
|
name: 'Image',
|
|
value: 'image',
|
|
},
|
|
],
|
|
default: 'image',
|
|
description: 'The operation to perform.',
|
|
},
|
|
{
|
|
displayName: 'Operation',
|
|
name: 'operation',
|
|
type: 'options',
|
|
options: [
|
|
{
|
|
name: 'Analyze',
|
|
value: 'analyze',
|
|
},
|
|
],
|
|
default: 'analyze',
|
|
description: 'The operation to perform.',
|
|
},
|
|
{
|
|
displayName: 'Type',
|
|
name: 'type',
|
|
type: 'options',
|
|
displayOptions: {
|
|
show: {
|
|
operation: [
|
|
'analyze',
|
|
],
|
|
resource: [
|
|
'image',
|
|
],
|
|
},
|
|
},
|
|
options: [
|
|
{
|
|
name: 'Detect Faces',
|
|
value: 'detectFaces',
|
|
},
|
|
{
|
|
name: 'Detect Labels',
|
|
value: 'detectLabels',
|
|
},
|
|
{
|
|
name: 'Detect Moderation Labels',
|
|
value: 'detectModerationLabels',
|
|
},
|
|
{
|
|
name: 'Detect Text',
|
|
value: 'detectText',
|
|
},
|
|
{
|
|
name: 'Recognize Celebrity',
|
|
value: 'recognizeCelebrity',
|
|
},
|
|
],
|
|
default: 'detectFaces',
|
|
description: 'The operation to perform.',
|
|
},
|
|
{
|
|
displayName: 'Binary Data',
|
|
name: 'binaryData',
|
|
type: 'boolean',
|
|
default: false,
|
|
required: true,
|
|
displayOptions: {
|
|
show: {
|
|
operation: [
|
|
'analyze',
|
|
],
|
|
resource: [
|
|
'image',
|
|
],
|
|
},
|
|
},
|
|
description: 'If the image to analize should be taken from binary field.',
|
|
},
|
|
{
|
|
displayName: 'Binary Property',
|
|
displayOptions: {
|
|
show: {
|
|
operation: [
|
|
'analyze',
|
|
],
|
|
resource: [
|
|
'image',
|
|
],
|
|
binaryData: [
|
|
true,
|
|
],
|
|
},
|
|
},
|
|
name: 'binaryPropertyName',
|
|
type: 'string',
|
|
default: 'data',
|
|
description: 'Object property name which holds binary data.',
|
|
required: true,
|
|
},
|
|
{
|
|
displayName: 'Bucket',
|
|
name: 'bucket',
|
|
displayOptions: {
|
|
show: {
|
|
operation: [
|
|
'analyze',
|
|
],
|
|
resource: [
|
|
'image',
|
|
],
|
|
binaryData: [
|
|
false,
|
|
],
|
|
},
|
|
},
|
|
type: 'string',
|
|
default: '',
|
|
required: true,
|
|
description: 'Name of the S3 bucket',
|
|
},
|
|
{
|
|
displayName: 'Name',
|
|
name: 'name',
|
|
displayOptions: {
|
|
show: {
|
|
operation: [
|
|
'analyze',
|
|
],
|
|
resource: [
|
|
'image',
|
|
],
|
|
binaryData: [
|
|
false,
|
|
],
|
|
},
|
|
},
|
|
type: 'string',
|
|
default: '',
|
|
required: true,
|
|
description: 'S3 object key name',
|
|
},
|
|
{
|
|
displayName: 'Additional Fields',
|
|
name: 'additionalFields',
|
|
type: 'collection',
|
|
placeholder: 'Add Field',
|
|
displayOptions: {
|
|
show: {
|
|
operation: [
|
|
'analyze',
|
|
],
|
|
resource: [
|
|
'image',
|
|
],
|
|
},
|
|
},
|
|
default: {},
|
|
options: [
|
|
{
|
|
displayName: 'Regions of Interest',
|
|
name: 'regionsOfInterestUi',
|
|
type: 'fixedCollection',
|
|
default: '',
|
|
placeholder: 'Add Region of Interest',
|
|
displayOptions: {
|
|
show: {
|
|
'/type': [
|
|
'detectText',
|
|
],
|
|
},
|
|
},
|
|
typeOptions: {
|
|
multipleValues: true,
|
|
},
|
|
options: [
|
|
{
|
|
name: 'regionsOfInterestValues',
|
|
displayName: 'Region of Interest',
|
|
values: [
|
|
{
|
|
displayName: 'Height',
|
|
name: 'height',
|
|
type: 'number',
|
|
description: 'Height of the bounding box as a ratio of the overall image height.',
|
|
default: 0,
|
|
},
|
|
{
|
|
displayName: 'Left',
|
|
name: 'left',
|
|
type: 'number',
|
|
description: 'Left coordinate of the bounding box as a ratio of overall image width.',
|
|
default: 0,
|
|
},
|
|
{
|
|
displayName: 'Top',
|
|
name: 'top',
|
|
type: 'number',
|
|
description: 'Top coordinate of the bounding box as a ratio of overall image height.',
|
|
default: 0,
|
|
},
|
|
{
|
|
displayName: 'Width',
|
|
name: 'Width',
|
|
type: 'number',
|
|
description: 'Width of the bounding box as a ratio of the overall image width.',
|
|
default: 0,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
displayName: 'Version',
|
|
name: 'version',
|
|
displayOptions: {
|
|
show: {
|
|
'/binaryData': [
|
|
false,
|
|
],
|
|
},
|
|
},
|
|
type: 'string',
|
|
default: '',
|
|
description: 'If the bucket is versioning enabled, you can specify the object version',
|
|
},
|
|
{
|
|
displayName: 'Word Filter',
|
|
name: 'wordFilterUi',
|
|
type: 'collection',
|
|
default: '',
|
|
placeholder: 'Add Word Filter',
|
|
displayOptions: {
|
|
show: {
|
|
'/type': [
|
|
'detectText',
|
|
],
|
|
},
|
|
},
|
|
typeOptions: {
|
|
multipleValues: false,
|
|
},
|
|
options: [
|
|
{
|
|
displayName: 'Min Bounding Box Height',
|
|
name: 'MinBoundingBoxHeight',
|
|
type: 'number',
|
|
description: 'Sets the minimum height of the word bounding box. Words with bounding box heights lesser than this value will be excluded from the result. Value is relative to the video frame height.',
|
|
default: 0,
|
|
},
|
|
{
|
|
displayName: 'Min Bounding Box Width',
|
|
name: 'MinBoundingBoxWidth',
|
|
type: 'number',
|
|
description: 'Sets the minimum width of the word bounding box. Words with bounding boxes widths lesser than this value will be excluded from the result. Value is relative to the video frame width.',
|
|
default: 0,
|
|
},
|
|
{
|
|
displayName: 'Min Confidence',
|
|
name: 'MinConfidence',
|
|
type: 'number',
|
|
description: 'Sets the confidence of word detection. Words with detection confidence below this will be excluded from the result. Values should be between 50 and 100 as Text in Video will not return any result below 50.',
|
|
default: 0,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
displayName: 'Max Labels',
|
|
name: 'maxLabels',
|
|
type: 'number',
|
|
displayOptions: {
|
|
show: {
|
|
'/type': [
|
|
'detectModerationLabels',
|
|
'detectLabels',
|
|
],
|
|
},
|
|
},
|
|
default: 0,
|
|
typeOptions: {
|
|
minValue: 0,
|
|
},
|
|
description: `Maximum number of labels you want the service to return in the response. The service returns the specified number of highest confidence labels.`,
|
|
},
|
|
{
|
|
displayName: 'Min Confidence',
|
|
name: 'minConfidence',
|
|
type: 'number',
|
|
displayOptions: {
|
|
show: {
|
|
'/type': [
|
|
'detectModerationLabels',
|
|
'detectLabels',
|
|
],
|
|
},
|
|
},
|
|
default: 0,
|
|
typeOptions: {
|
|
minValue: 0,
|
|
maxValue: 100,
|
|
},
|
|
description: `Specifies the minimum confidence level for the labels to return. Amazon Rekognition doesn't return any labels with a confidence level lower than this specified value.`,
|
|
},
|
|
{
|
|
displayName: 'Attributes',
|
|
name: 'attributes',
|
|
type: 'multiOptions',
|
|
displayOptions: {
|
|
show: {
|
|
'/type': [
|
|
'detectFaces',
|
|
],
|
|
},
|
|
},
|
|
options: [
|
|
{
|
|
name: 'All',
|
|
value: 'all',
|
|
},
|
|
{
|
|
name: 'Default',
|
|
value: 'default',
|
|
},
|
|
],
|
|
default: [],
|
|
description: `An array of facial attributes you want to be returned`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
};
|
|
|
|
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
|
const items = this.getInputData();
|
|
const returnData: IDataObject[] = [];
|
|
const qs: IDataObject = {};
|
|
let responseData;
|
|
const resource = this.getNodeParameter('resource', 0) as string;
|
|
const operation = this.getNodeParameter('operation', 0) as string;
|
|
for (let i = 0; i < items.length; i++) {
|
|
if (resource === 'image') {
|
|
//https://docs.aws.amazon.com/rekognition/latest/dg/API_DetectModerationLabels.html#API_DetectModerationLabels_RequestSyntax
|
|
if (operation === 'analyze') {
|
|
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
|
|
|
let action = undefined;
|
|
|
|
const body: IDataObject = {};
|
|
|
|
const type = this.getNodeParameter('type', 0) as string;
|
|
|
|
if (type === 'detectModerationLabels') {
|
|
action = 'RekognitionService.DetectModerationLabels';
|
|
|
|
// property = 'ModerationLabels';
|
|
|
|
if (additionalFields.minConfidence) {
|
|
body['MinConfidence'] = additionalFields.minConfidence as number;
|
|
}
|
|
}
|
|
|
|
if (type === 'detectFaces') {
|
|
action = 'RekognitionService.DetectFaces';
|
|
|
|
// TODO: Add a later point make it possible to activate via option.
|
|
// If activated add an index to each of the found faces/tages/...
|
|
// to not loose the reference to the image it got found on if
|
|
// multilpe ones got supplied.
|
|
// property = 'FaceDetails';
|
|
|
|
if (additionalFields.attributes) {
|
|
body['Attributes'] = additionalFields.attributes as string;
|
|
}
|
|
}
|
|
|
|
if (type === 'detectLabels') {
|
|
action = 'RekognitionService.DetectLabels';
|
|
|
|
if (additionalFields.minConfidence) {
|
|
body['MinConfidence'] = additionalFields.minConfidence as number;
|
|
}
|
|
|
|
if (additionalFields.maxLabels) {
|
|
body['MaxLabels'] = additionalFields.maxLabels as number;
|
|
}
|
|
}
|
|
|
|
if (type === 'recognizeCelebrity') {
|
|
action = 'RekognitionService.RecognizeCelebrities';
|
|
}
|
|
|
|
if (type === 'detectText') {
|
|
action = 'RekognitionService.DetectText';
|
|
|
|
body.Filters = {};
|
|
|
|
const box = (additionalFields.regionsOfInterestUi as IDataObject || {}).regionsOfInterestValues as IDataObject[] || [];
|
|
|
|
if (box.length !== 0) {
|
|
//@ts-ignore
|
|
body.Filters.RegionsOfInterest = box.map((box: IDataObject) => {
|
|
return { BoundingBox: keysTPascalCase(box) };
|
|
});
|
|
}
|
|
|
|
const wordFilter = additionalFields.wordFilterUi as IDataObject || {};
|
|
if (Object.keys(wordFilter).length !== 0) {
|
|
//@ts-ignore
|
|
body.Filters.WordFilter = keysTPascalCase(wordFilter);
|
|
}
|
|
|
|
const binaryData = this.getNodeParameter('binaryData', 0) as boolean;
|
|
|
|
if (binaryData) {
|
|
|
|
const binaryPropertyName = this.getNodeParameter('binaryPropertyName', 0) as string;
|
|
|
|
if (items[i].binary === undefined) {
|
|
throw new NodeOperationError(this.getNode(), 'No binary data exists on item!');
|
|
}
|
|
|
|
if ((items[i].binary as IBinaryKeyData)[binaryPropertyName] === undefined) {
|
|
throw new NodeOperationError(this.getNode(), `No binary data property "${binaryPropertyName}" does not exists on item!`);
|
|
}
|
|
|
|
const binaryPropertyData = (items[i].binary as IBinaryKeyData)[binaryPropertyName];
|
|
|
|
Object.assign(body, {
|
|
Image: {
|
|
Bytes: binaryPropertyData.data,
|
|
},
|
|
});
|
|
|
|
} else {
|
|
|
|
const bucket = this.getNodeParameter('bucket', i) as string;
|
|
|
|
const name = this.getNodeParameter('name', i) as string;
|
|
|
|
Object.assign(body, {
|
|
Image: {
|
|
S3Object: {
|
|
Bucket: bucket,
|
|
Name: name,
|
|
},
|
|
},
|
|
});
|
|
|
|
if (additionalFields.version) {
|
|
//@ts-ignore
|
|
body.Image.S3Object.Version = additionalFields.version as string;
|
|
}
|
|
}
|
|
|
|
responseData = await awsApiRequestREST.call(this, 'rekognition', 'POST', '', JSON.stringify(body), {}, { 'X-Amz-Target': action, 'Content-Type': 'application/x-amz-json-1.1' });
|
|
|
|
}
|
|
}
|
|
}
|
|
if (Array.isArray(responseData)) {
|
|
returnData.push.apply(returnData, responseData as IDataObject[]);
|
|
} else {
|
|
returnData.push(responseData);
|
|
}
|
|
}
|
|
return [this.helpers.returnJsonArray(returnData)];
|
|
}
|
|
}
|