mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
Worked on 'update' action
This commit is contained in:
parent
6f0c98289b
commit
5780596cb1
|
@ -3,7 +3,7 @@ import { NodeConnectionType } from 'n8n-workflow';
|
|||
|
||||
import { containerFields, containerOperations } from './descriptions/ContainerDescription';
|
||||
import { itemFields, itemOperations } from './descriptions/ItemDescription';
|
||||
import { searchCollections, searchItems } from './GenericFunctions';
|
||||
import { getDynamicFields, searchCollections, searchItems } from './GenericFunctions';
|
||||
|
||||
export class CosmosDb implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
|
@ -64,6 +64,7 @@ export class CosmosDb implements INodeType {
|
|||
listSearch: {
|
||||
searchCollections,
|
||||
searchItems,
|
||||
getDynamicFields,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -118,6 +118,41 @@ export async function handlePagination(
|
|||
return aggregatedResult.map((result) => ({ json: result }));
|
||||
}
|
||||
|
||||
export async function handleErrorPostReceive(
|
||||
this: IExecuteSingleFunctions,
|
||||
data: INodeExecutionData[],
|
||||
response: IN8nHttpFullResponse,
|
||||
): Promise<INodeExecutionData[]> {
|
||||
if (String(response.statusCode).startsWith('4') || String(response.statusCode).startsWith('5')) {
|
||||
const responseBody = response.body as IDataObject;
|
||||
|
||||
let errorMessage = 'Unknown error occurred';
|
||||
|
||||
if (typeof responseBody.message === 'string') {
|
||||
try {
|
||||
const jsonMatch = responseBody.message.match(/Message: (\{.*\})/);
|
||||
|
||||
if (jsonMatch && jsonMatch[1]) {
|
||||
const parsedMessage = JSON.parse(jsonMatch[1]);
|
||||
|
||||
if (
|
||||
parsedMessage.Errors &&
|
||||
Array.isArray(parsedMessage.Errors) &&
|
||||
parsedMessage.Errors.length > 0
|
||||
) {
|
||||
errorMessage = parsedMessage.Errors[0].split(' Learn more:')[0].trim();
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
errorMessage = 'Failed to extract error message';
|
||||
}
|
||||
}
|
||||
|
||||
throw new ApplicationError(errorMessage);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function microsoftCosmosDbRequest(
|
||||
this: ILoadOptionsFunctions,
|
||||
opts: IHttpRequestOptions,
|
||||
|
@ -133,6 +168,7 @@ export async function microsoftCosmosDbRequest(
|
|||
...opts,
|
||||
baseURL: `${credentials.baseUrl}`,
|
||||
headers: {
|
||||
...opts.headers,
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
|
@ -298,35 +334,69 @@ export async function validateOperations(
|
|||
requestOptions: IHttpRequestOptions,
|
||||
): Promise<IHttpRequestOptions> {
|
||||
const rawOperations = this.getNodeParameter('operations', []) as IDataObject;
|
||||
console.log('Operations', rawOperations);
|
||||
|
||||
if (!rawOperations || !Array.isArray(rawOperations.operations)) {
|
||||
throw new ApplicationError('The "operations" field must contain at least one operation.');
|
||||
}
|
||||
|
||||
const operations = rawOperations.operations as Array<{
|
||||
op: string;
|
||||
path: string;
|
||||
value?: string;
|
||||
path?: { mode: string; value: string };
|
||||
toPath?: { mode: string; value: string };
|
||||
from?: { mode: string; value: string };
|
||||
value?: string | number;
|
||||
}>;
|
||||
|
||||
for (const operation of operations) {
|
||||
if (!['add', 'increment', 'move', 'remove', 'replace', 'set'].includes(operation.op)) {
|
||||
throw new ApplicationError(
|
||||
`Invalid operation type "${operation.op}". Allowed values are "add", "increment", "move", "remove", "replace", and "set".`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!operation.path || operation.path.trim() === '') {
|
||||
const transformedOperations = operations.map((operation) => {
|
||||
if (
|
||||
operation.op !== 'move' &&
|
||||
(!operation.path?.value ||
|
||||
typeof operation.path.value !== 'string' ||
|
||||
operation.path.value.trim() === '')
|
||||
) {
|
||||
throw new ApplicationError('Each operation must have a valid "path".');
|
||||
}
|
||||
|
||||
if (
|
||||
['set', 'replace', 'add', 'increment'].includes(operation.op) &&
|
||||
['set', 'replace', 'add', 'incr'].includes(operation.op) &&
|
||||
(operation.value === undefined || operation.value === null)
|
||||
) {
|
||||
throw new ApplicationError(`The operation "${operation.op}" must include a valid "value".`);
|
||||
throw new ApplicationError(`The "${operation.op}" operation must include a valid "value".`);
|
||||
}
|
||||
}
|
||||
|
||||
if (operation.op === 'move') {
|
||||
if (
|
||||
!operation.from?.value ||
|
||||
typeof operation.from.value !== 'string' ||
|
||||
operation.from.value.trim() === ''
|
||||
) {
|
||||
throw new ApplicationError('The "move" operation must have a valid "from" path.');
|
||||
}
|
||||
|
||||
if (
|
||||
!operation.toPath?.value ||
|
||||
typeof operation.toPath.value !== 'string' ||
|
||||
operation.toPath.value.trim() === ''
|
||||
) {
|
||||
throw new ApplicationError('The "move" operation must have a valid "toPath".');
|
||||
}
|
||||
}
|
||||
|
||||
if (operation.op === 'incr' && isNaN(Number(operation.value))) {
|
||||
throw new ApplicationError('The "increment" operation must have a numeric value.');
|
||||
}
|
||||
|
||||
return {
|
||||
op: operation.op,
|
||||
path: operation.op === 'move' ? operation.toPath?.value : operation.path?.value,
|
||||
...(operation.from ? { from: operation.from.value } : {}),
|
||||
...(operation.op === 'incr'
|
||||
? { value: Number(operation.value) }
|
||||
: { value: isNaN(Number(operation.value)) ? operation.value : Number(operation.value) }),
|
||||
};
|
||||
});
|
||||
|
||||
requestOptions.body = transformedOperations;
|
||||
|
||||
return requestOptions;
|
||||
}
|
||||
|
@ -488,3 +558,85 @@ export async function processResponseContainers(
|
|||
|
||||
return [];
|
||||
}
|
||||
|
||||
function extractFieldPaths(obj: any, prefix = ''): string[] {
|
||||
let paths: string[] = [];
|
||||
|
||||
Object.entries(obj).forEach(([key, value]) => {
|
||||
if (key.startsWith('_') || key === 'id') {
|
||||
return;
|
||||
}
|
||||
const newPath = prefix ? `${prefix}/${key}` : `/${key}`;
|
||||
if (Array.isArray(value) && value.length > 0) {
|
||||
value.forEach((item, index) => {
|
||||
if (typeof item === 'object' && item !== null) {
|
||||
paths = paths.concat(extractFieldPaths(item, `${newPath}/${index}`));
|
||||
} else {
|
||||
paths.push(`${newPath}/${index}`);
|
||||
}
|
||||
});
|
||||
} else if (typeof value === 'object' && value !== null) {
|
||||
paths = paths.concat(extractFieldPaths(value, newPath));
|
||||
} else {
|
||||
paths.push(newPath);
|
||||
}
|
||||
});
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
export async function searchItemById(
|
||||
this: ILoadOptionsFunctions,
|
||||
itemId: string,
|
||||
): Promise<IDataObject | null> {
|
||||
const collection = this.getNodeParameter('collId') as { mode: string; value: string };
|
||||
|
||||
if (!collection?.value) {
|
||||
throw new ApplicationError('Collection ID is required.');
|
||||
}
|
||||
|
||||
if (!itemId) {
|
||||
throw new ApplicationError('Item ID is required.');
|
||||
}
|
||||
|
||||
const opts: IHttpRequestOptions = {
|
||||
method: 'GET',
|
||||
url: `/colls/${collection.value}/docs/${itemId}`,
|
||||
headers: {
|
||||
'x-ms-documentdb-partitionkey': `["${itemId}"]`,
|
||||
},
|
||||
};
|
||||
|
||||
const responseData: IDataObject = await microsoftCosmosDbRequest.call(this, opts);
|
||||
|
||||
if (!responseData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return responseData;
|
||||
}
|
||||
|
||||
export async function getDynamicFields(
|
||||
this: ILoadOptionsFunctions,
|
||||
): Promise<INodeListSearchResult> {
|
||||
const itemId = this.getNodeParameter('id', '') as { mode: string; value: string };
|
||||
|
||||
if (!itemId) {
|
||||
throw new ApplicationError('Item ID is required to fetch fields.');
|
||||
}
|
||||
|
||||
const itemData = await searchItemById.call(this, itemId.value);
|
||||
|
||||
if (!itemData) {
|
||||
throw new ApplicationError(`Item with ID "${itemId.value}" not found.`);
|
||||
}
|
||||
|
||||
const fieldPaths = extractFieldPaths(itemData);
|
||||
|
||||
return {
|
||||
results: fieldPaths.map((path) => ({
|
||||
name: path,
|
||||
value: path,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import type { INodeProperties } from 'n8n-workflow';
|
|||
|
||||
import {
|
||||
formatCustomProperties,
|
||||
handleErrorPostReceive,
|
||||
handlePagination,
|
||||
processResponseItems,
|
||||
validateOperations,
|
||||
|
@ -145,6 +146,9 @@ export const itemOperations: INodeProperties[] = [
|
|||
'x-ms-documentdb-partitionkey': '=["{{$parameter["id"]}}"]',
|
||||
},
|
||||
},
|
||||
output: {
|
||||
postReceive: [handleErrorPostReceive],
|
||||
},
|
||||
},
|
||||
action: 'Update item',
|
||||
},
|
||||
|
@ -155,7 +159,7 @@ export const itemOperations: INodeProperties[] = [
|
|||
|
||||
export const createFields: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Container ID',
|
||||
displayName: 'Container',
|
||||
name: 'collId',
|
||||
type: 'resourceLocator',
|
||||
required: true,
|
||||
|
@ -184,7 +188,7 @@ export const createFields: INodeProperties[] = [
|
|||
displayName: 'By ID',
|
||||
name: 'containerId',
|
||||
type: 'string',
|
||||
hint: 'Enter the container ID',
|
||||
hint: 'Enter the container Id',
|
||||
validation: [
|
||||
{
|
||||
type: 'regex',
|
||||
|
@ -239,7 +243,7 @@ export const createFields: INodeProperties[] = [
|
|||
|
||||
export const deleteFields: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Container ID',
|
||||
displayName: 'Container',
|
||||
name: 'collId',
|
||||
type: 'resourceLocator',
|
||||
required: true,
|
||||
|
@ -274,7 +278,7 @@ export const deleteFields: INodeProperties[] = [
|
|||
type: 'regex',
|
||||
properties: {
|
||||
regex: '^[\\w+=,.@-]+$',
|
||||
errorMessage: 'The container name must follow the allowed pattern.',
|
||||
errorMessage: 'The container id must follow the allowed pattern.',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -330,7 +334,7 @@ export const deleteFields: INodeProperties[] = [
|
|||
|
||||
export const getFields: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Container ID',
|
||||
displayName: 'Container',
|
||||
name: 'collId',
|
||||
type: 'resourceLocator',
|
||||
required: true,
|
||||
|
@ -365,7 +369,7 @@ export const getFields: INodeProperties[] = [
|
|||
type: 'regex',
|
||||
properties: {
|
||||
regex: '^[\\w+=,.@-]+$',
|
||||
errorMessage: 'The container name must follow the allowed pattern.',
|
||||
errorMessage: 'The container id must follow the allowed pattern.',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -421,7 +425,7 @@ export const getFields: INodeProperties[] = [
|
|||
|
||||
export const getAllFields: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Container ID',
|
||||
displayName: 'Container',
|
||||
name: 'collId',
|
||||
type: 'resourceLocator',
|
||||
required: true,
|
||||
|
@ -456,7 +460,7 @@ export const getAllFields: INodeProperties[] = [
|
|||
type: 'regex',
|
||||
properties: {
|
||||
regex: '^[\\w+=,.@-]+$',
|
||||
errorMessage: 'The container name must follow the allowed pattern.',
|
||||
errorMessage: 'The container id must follow the allowed pattern.',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -506,7 +510,7 @@ export const getAllFields: INodeProperties[] = [
|
|||
|
||||
export const queryFields: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Container ID',
|
||||
displayName: 'Container',
|
||||
name: 'collId',
|
||||
type: 'resourceLocator',
|
||||
required: true,
|
||||
|
@ -621,7 +625,7 @@ export const queryFields: INodeProperties[] = [
|
|||
|
||||
export const updateFields: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Container ID',
|
||||
displayName: 'Container',
|
||||
name: 'collId',
|
||||
type: 'resourceLocator',
|
||||
required: true,
|
||||
|
@ -656,7 +660,7 @@ export const updateFields: INodeProperties[] = [
|
|||
type: 'regex',
|
||||
properties: {
|
||||
regex: '^[\\w+=,.@-]+$',
|
||||
errorMessage: 'The container name must follow the allowed pattern.',
|
||||
errorMessage: 'The container id must follow the allowed pattern.',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -736,29 +740,112 @@ export const updateFields: INodeProperties[] = [
|
|||
type: 'options',
|
||||
options: [
|
||||
{ name: 'Add', value: 'add' },
|
||||
{ name: 'Increment', value: 'increment' },
|
||||
{ name: 'Increment', value: 'incr' },
|
||||
{ name: 'Move', value: 'move' },
|
||||
{ name: 'Remove', value: 'remove' },
|
||||
{ name: 'Replace', value: 'replace' },
|
||||
{ name: 'Set', value: 'set' },
|
||||
],
|
||||
default: 'set',
|
||||
},
|
||||
{
|
||||
displayName: 'From',
|
||||
displayName: 'From Path',
|
||||
name: 'from',
|
||||
type: 'string',
|
||||
default: '',
|
||||
type: 'resourceLocator',
|
||||
description: 'Select a field from the list or enter it manually',
|
||||
displayOptions: {
|
||||
show: {
|
||||
op: ['move'],
|
||||
},
|
||||
},
|
||||
default: {
|
||||
mode: 'list',
|
||||
value: '',
|
||||
},
|
||||
modes: [
|
||||
{
|
||||
displayName: 'From List',
|
||||
name: 'list',
|
||||
type: 'list',
|
||||
typeOptions: {
|
||||
searchListMethod: 'getDynamicFields',
|
||||
searchable: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'By Name',
|
||||
name: 'manual',
|
||||
type: 'string',
|
||||
hint: 'Enter the field name manually',
|
||||
placeholder: 'e.g. /Parents/0/FamilyName',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'To Path',
|
||||
name: 'toPath',
|
||||
type: 'resourceLocator',
|
||||
description: 'Select a field from the list or enter it manually',
|
||||
displayOptions: {
|
||||
show: {
|
||||
op: ['move'],
|
||||
},
|
||||
},
|
||||
default: {
|
||||
mode: 'list',
|
||||
value: '',
|
||||
},
|
||||
modes: [
|
||||
{
|
||||
displayName: 'From List',
|
||||
name: 'list',
|
||||
type: 'list',
|
||||
typeOptions: {
|
||||
searchListMethod: 'getDynamicFields',
|
||||
searchable: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'By Name',
|
||||
name: 'manual',
|
||||
type: 'string',
|
||||
hint: 'Enter the field name manually',
|
||||
placeholder: 'e.g. /Parents/0/FamilyName',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Path',
|
||||
name: 'path',
|
||||
type: 'string',
|
||||
default: '',
|
||||
type: 'resourceLocator',
|
||||
description: 'Select a field from the list or enter it manually',
|
||||
default: {
|
||||
mode: 'list',
|
||||
value: '',
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
op: ['add', 'remove', 'set', 'incr', 'replace'],
|
||||
},
|
||||
},
|
||||
modes: [
|
||||
{
|
||||
displayName: 'From List',
|
||||
name: 'list',
|
||||
type: 'list',
|
||||
typeOptions: {
|
||||
searchListMethod: 'getDynamicFields',
|
||||
searchable: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'By Name',
|
||||
name: 'manual',
|
||||
type: 'string',
|
||||
hint: 'Enter the field name manually',
|
||||
placeholder: 'e.g. /Parents/0/FamilyName',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
|
@ -767,20 +854,13 @@ export const updateFields: INodeProperties[] = [
|
|||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
op: ['add', 'set', 'increment'],
|
||||
op: ['add', 'set', 'replace', 'incr'],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
routing: {
|
||||
send: {
|
||||
type: 'body',
|
||||
property: 'operations',
|
||||
value: '={{ $parameter["operations"].operations }}',
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
|
|
Loading…
Reference in a new issue