feat(Microsoft Excel 365 Node): Overhaul
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
import * as append from './append.operation';
|
||||
import * as clear from './clear.operation';
|
||||
import * as deleteWorksheet from './deleteWorksheet.operation';
|
||||
import * as getAll from './getAll.operation';
|
||||
import * as readRows from './readRows.operation';
|
||||
import * as update from './update.operation';
|
||||
import * as upsert from './upsert.operation';
|
||||
|
||||
export { append, clear, deleteWorksheet, getAll, readRows, update, upsert };
|
||||
|
||||
export const description: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['worksheet'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Append',
|
||||
value: 'append',
|
||||
description: 'Append data to sheet',
|
||||
action: 'Append data to sheet',
|
||||
},
|
||||
{
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-option-name-wrong-for-upsert
|
||||
name: 'Append or Update',
|
||||
value: 'upsert',
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-description-wrong-for-upsert
|
||||
description: 'Append a new row or update the current one if it already exists (upsert)',
|
||||
action: 'Append or update a sheet',
|
||||
},
|
||||
{
|
||||
name: 'Clear',
|
||||
value: 'clear',
|
||||
description: 'Clear sheet',
|
||||
action: 'Clear sheet',
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
value: 'deleteWorksheet',
|
||||
description: 'Delete sheet',
|
||||
action: 'Delete sheet',
|
||||
},
|
||||
{
|
||||
name: 'Get Many',
|
||||
value: 'getAll',
|
||||
description: 'Get a list of sheets',
|
||||
action: 'Get sheets',
|
||||
},
|
||||
{
|
||||
name: 'Get Rows',
|
||||
value: 'readRows',
|
||||
description: 'Retrieve a list of sheet rows',
|
||||
action: 'Get rows from sheet',
|
||||
},
|
||||
{
|
||||
name: 'Update',
|
||||
value: 'update',
|
||||
description: 'Update rows of a sheet or sheet range',
|
||||
action: 'Update sheet',
|
||||
},
|
||||
],
|
||||
default: 'getAll',
|
||||
},
|
||||
...append.description,
|
||||
...clear.description,
|
||||
...deleteWorksheet.description,
|
||||
...getAll.description,
|
||||
...readRows.description,
|
||||
...update.description,
|
||||
...upsert.description,
|
||||
];
|
||||
@@ -0,0 +1,227 @@
|
||||
import type { IExecuteFunctions } from 'n8n-core';
|
||||
import type { IDataObject, INodeExecutionData, INodeProperties } from 'n8n-workflow';
|
||||
import { processJsonInput, updateDisplayOptions } from '../../../../../../utils/utilities';
|
||||
import type { ExcelResponse } from '../../helpers/interfaces';
|
||||
import { prepareOutput } from '../../helpers/utils';
|
||||
import { microsoftApiRequest } from '../../transport';
|
||||
import { workbookRLC, worksheetRLC } from '../common.descriptions';
|
||||
|
||||
const properties: INodeProperties[] = [
|
||||
workbookRLC,
|
||||
worksheetRLC,
|
||||
{
|
||||
displayName: 'Data Mode',
|
||||
name: 'dataMode',
|
||||
type: 'options',
|
||||
default: 'define',
|
||||
options: [
|
||||
{
|
||||
name: 'Auto-Map Input Data to Columns',
|
||||
value: 'autoMap',
|
||||
description: 'Use when node input properties match destination column names',
|
||||
},
|
||||
{
|
||||
name: 'Map Each Column Below',
|
||||
value: 'define',
|
||||
description: 'Set the value for each destination column',
|
||||
},
|
||||
{
|
||||
name: 'Raw',
|
||||
value: 'raw',
|
||||
description: 'Send raw data as JSON',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Data',
|
||||
name: 'data',
|
||||
type: 'json',
|
||||
default: '',
|
||||
required: true,
|
||||
placeholder: 'e.g. [["Sara","1/2/2006","Berlin"],["George","5/3/2010","Paris"]]',
|
||||
description: 'Raw values for the specified range as array of string arrays in JSON format',
|
||||
displayOptions: {
|
||||
show: {
|
||||
dataMode: ['raw'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Values to Send',
|
||||
name: 'fieldsUi',
|
||||
placeholder: 'Add Field',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
dataMode: ['define'],
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Field',
|
||||
name: 'values',
|
||||
values: [
|
||||
{
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-wrong-for-dynamic-options
|
||||
displayName: 'Column',
|
||||
name: 'column',
|
||||
type: 'options',
|
||||
description:
|
||||
'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>',
|
||||
typeOptions: {
|
||||
loadOptionsDependsOn: ['worksheet.value'],
|
||||
loadOptionsMethod: 'getWorksheetColumnRow',
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'fieldValue',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Option',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'RAW Data',
|
||||
name: 'rawData',
|
||||
type: 'boolean',
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-default-wrong-for-boolean
|
||||
default: 0,
|
||||
description:
|
||||
'Whether the data should be returned RAW instead of parsed into keys according to their header',
|
||||
},
|
||||
{
|
||||
displayName: 'Data Property',
|
||||
name: 'dataProperty',
|
||||
type: 'string',
|
||||
default: 'data',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
rawData: [true],
|
||||
},
|
||||
},
|
||||
description: 'The name of the property into which to write the RAW data',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['worksheet'],
|
||||
operation: ['append'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
items: INodeExecutionData[],
|
||||
): Promise<INodeExecutionData[]> {
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
|
||||
const workbookId = this.getNodeParameter('workbook', 0, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
const worksheetId = this.getNodeParameter('worksheet', 0, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
const dataMode = this.getNodeParameter('dataMode', 0) as string;
|
||||
|
||||
const worksheetData = await microsoftApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/usedRange`,
|
||||
);
|
||||
|
||||
let values: string[][] = [];
|
||||
|
||||
if (dataMode === 'raw') {
|
||||
const data = this.getNodeParameter('data', 0);
|
||||
values = processJsonInput(data, 'Data') as string[][];
|
||||
}
|
||||
|
||||
const columnsRow = (worksheetData.values as string[][])[0];
|
||||
|
||||
if (dataMode === 'autoMap') {
|
||||
const itemsData = items.map((item) => item.json);
|
||||
for (const item of itemsData) {
|
||||
const updateRow: string[] = [];
|
||||
|
||||
for (const column of columnsRow) {
|
||||
updateRow.push(item[column] as string);
|
||||
}
|
||||
|
||||
values.push(updateRow);
|
||||
}
|
||||
}
|
||||
|
||||
if (dataMode === 'define') {
|
||||
const itemsData: IDataObject[] = [];
|
||||
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
|
||||
const updateData: IDataObject = {};
|
||||
const definedFields = this.getNodeParameter('fieldsUi.values', itemIndex, []) as Array<{
|
||||
column: string;
|
||||
fieldValue: string;
|
||||
}>;
|
||||
for (const entry of definedFields) {
|
||||
updateData[entry.column] = entry.fieldValue;
|
||||
}
|
||||
itemsData.push(updateData);
|
||||
}
|
||||
|
||||
for (const item of itemsData) {
|
||||
const updateRow: string[] = [];
|
||||
|
||||
for (const column of columnsRow) {
|
||||
updateRow.push(item[column] as string);
|
||||
}
|
||||
|
||||
values.push(updateRow);
|
||||
}
|
||||
}
|
||||
|
||||
const { address } = worksheetData;
|
||||
const usedRange = address.split('!')[1];
|
||||
|
||||
const [rangeFrom, rangeTo] = usedRange.split(':');
|
||||
const cellDataFrom = rangeFrom.match(/([a-zA-Z]{1,10})([0-9]{0,10})/) || [];
|
||||
const cellDataTo = rangeTo.match(/([a-zA-Z]{1,10})([0-9]{0,10})/) || [];
|
||||
|
||||
const from = `${cellDataFrom[1]}${Number(cellDataTo[2]) + 1}`;
|
||||
const to = `${cellDataTo[1]}${Number(cellDataTo[2]) + Number(values.length)}`;
|
||||
|
||||
const responseData: ExcelResponse = await microsoftApiRequest.call(
|
||||
this,
|
||||
'PATCH',
|
||||
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/range(address='${from}:${to}')`,
|
||||
{ values },
|
||||
);
|
||||
|
||||
const rawData = this.getNodeParameter('options.rawData', 0, false) as boolean;
|
||||
const dataProperty = this.getNodeParameter('options.dataProperty', 0, 'data') as string;
|
||||
|
||||
returnData.push(
|
||||
...prepareOutput(this.getNode(), responseData, { columnsRow, dataProperty, rawData }),
|
||||
);
|
||||
|
||||
return returnData;
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
import type { IExecuteFunctions } from 'n8n-core';
|
||||
import type { INodeExecutionData, INodeProperties } from 'n8n-workflow';
|
||||
import { updateDisplayOptions } from '../../../../../../utils/utilities';
|
||||
import { microsoftApiRequest } from '../../transport';
|
||||
import { workbookRLC, worksheetRLC } from '../common.descriptions';
|
||||
|
||||
const properties: INodeProperties[] = [
|
||||
workbookRLC,
|
||||
worksheetRLC,
|
||||
{
|
||||
displayName: 'Apply To',
|
||||
name: 'applyTo',
|
||||
type: 'options',
|
||||
//values in capital case as required by api
|
||||
options: [
|
||||
{
|
||||
name: 'All',
|
||||
value: 'All',
|
||||
description: 'Clear data in cells and remove all formatting',
|
||||
},
|
||||
{
|
||||
name: 'Formats',
|
||||
value: 'Formats',
|
||||
description: 'Clear formatting(e.g. font size, color) of cells',
|
||||
},
|
||||
{
|
||||
name: 'Contents',
|
||||
value: 'Contents',
|
||||
description: 'Clear data contained in cells',
|
||||
},
|
||||
],
|
||||
default: 'All',
|
||||
},
|
||||
{
|
||||
displayName: 'Select a Range',
|
||||
name: 'useRange',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
{
|
||||
displayName: 'Range',
|
||||
name: 'range',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
useRange: [true],
|
||||
},
|
||||
},
|
||||
placeholder: 'e.g. A1:B2',
|
||||
default: '',
|
||||
description: 'The sheet range that would be cleared, specified using a A1-style notation',
|
||||
hint: 'Leave blank for entire worksheet',
|
||||
},
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['worksheet'],
|
||||
operation: ['clear'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
items: INodeExecutionData[],
|
||||
): Promise<INodeExecutionData[]> {
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
try {
|
||||
const workbookId = this.getNodeParameter('workbook', i, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
const worksheetId = this.getNodeParameter('worksheet', i, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
const applyTo = this.getNodeParameter('applyTo', i) as string;
|
||||
const useRange = this.getNodeParameter('useRange', i, false) as boolean;
|
||||
|
||||
if (!useRange) {
|
||||
await microsoftApiRequest.call(
|
||||
this,
|
||||
'POST',
|
||||
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/range/clear`,
|
||||
{ applyTo },
|
||||
);
|
||||
} else {
|
||||
const range = this.getNodeParameter('range', i, '') as string;
|
||||
await microsoftApiRequest.call(
|
||||
this,
|
||||
'POST',
|
||||
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/range(address='${range}')/clear`,
|
||||
{ applyTo },
|
||||
);
|
||||
}
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ success: true }),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
|
||||
returnData.push(...executionData);
|
||||
} catch (error) {
|
||||
if (this.continueOnFail()) {
|
||||
const executionErrorData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ error: error.message }),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
returnData.push(...executionErrorData);
|
||||
continue;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import type { IExecuteFunctions } from 'n8n-core';
|
||||
import type { INodeExecutionData, INodeProperties } from 'n8n-workflow';
|
||||
import { updateDisplayOptions } from '../../../../../../utils/utilities';
|
||||
import { microsoftApiRequest } from '../../transport';
|
||||
import { workbookRLC, worksheetRLC } from '../common.descriptions';
|
||||
|
||||
const properties: INodeProperties[] = [workbookRLC, worksheetRLC];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['worksheet'],
|
||||
operation: ['deleteWorksheet'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
items: INodeExecutionData[],
|
||||
): Promise<INodeExecutionData[]> {
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
try {
|
||||
const workbookId = this.getNodeParameter('workbook', i, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
const worksheetId = this.getNodeParameter('worksheet', i, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
await microsoftApiRequest.call(
|
||||
this,
|
||||
'DELETE',
|
||||
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}`,
|
||||
);
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ success: true }),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
|
||||
returnData.push(...executionData);
|
||||
} catch (error) {
|
||||
if (this.continueOnFail()) {
|
||||
const executionErrorData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ error: error.message }),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
returnData.push(...executionErrorData);
|
||||
continue;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
import type { IExecuteFunctions } from 'n8n-core';
|
||||
import type { IDataObject, INodeExecutionData, INodeProperties } from 'n8n-workflow';
|
||||
import { updateDisplayOptions } from '../../../../../../utils/utilities';
|
||||
import { microsoftApiRequest, microsoftApiRequestAllItems } from '../../transport';
|
||||
import { workbookRLC } from '../common.descriptions';
|
||||
|
||||
const properties: INodeProperties[] = [
|
||||
workbookRLC,
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether to return all results or only up to a given limit',
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
returnAll: [false],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 500,
|
||||
},
|
||||
default: 100,
|
||||
description: 'Max number of results to return',
|
||||
},
|
||||
{
|
||||
displayName: 'Filters',
|
||||
name: 'filters',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Filter',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Fields',
|
||||
name: 'fields',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'A comma-separated list of the fields to include in the response',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['worksheet'],
|
||||
operation: ['getAll'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
items: INodeExecutionData[],
|
||||
): Promise<INodeExecutionData[]> {
|
||||
//https://docs.microsoft.com/en-us/graph/api/workbook-list-worksheets?view=graph-rest-1.0&tabs=http
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const qs: IDataObject = {};
|
||||
try {
|
||||
const returnAll = this.getNodeParameter('returnAll', i);
|
||||
const workbookId = this.getNodeParameter('workbook', i, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
const filters = this.getNodeParameter('filters', i);
|
||||
if (filters.fields) {
|
||||
qs.$select = filters.fields;
|
||||
}
|
||||
|
||||
let responseData;
|
||||
if (returnAll) {
|
||||
responseData = await microsoftApiRequestAllItems.call(
|
||||
this,
|
||||
'value',
|
||||
'GET',
|
||||
`/drive/items/${workbookId}/workbook/worksheets`,
|
||||
{},
|
||||
qs,
|
||||
);
|
||||
} else {
|
||||
qs.$top = this.getNodeParameter('limit', i);
|
||||
responseData = await microsoftApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
`/drive/items/${workbookId}/workbook/worksheets`,
|
||||
{},
|
||||
qs,
|
||||
);
|
||||
responseData = responseData.value;
|
||||
}
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(responseData as IDataObject[]),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
|
||||
returnData.push(...executionData);
|
||||
} catch (error) {
|
||||
if (this.continueOnFail()) {
|
||||
const executionErrorData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ error: error.message }),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
returnData.push(...executionErrorData);
|
||||
continue;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
import type { IExecuteFunctions } from 'n8n-core';
|
||||
import type { IDataObject, INodeExecutionData, INodeProperties } from 'n8n-workflow';
|
||||
import { updateDisplayOptions } from '../../../../../../utils/utilities';
|
||||
import type { ExcelResponse } from '../../helpers/interfaces';
|
||||
import { prepareOutput } from '../../helpers/utils';
|
||||
import { microsoftApiRequest } from '../../transport';
|
||||
import { workbookRLC, worksheetRLC } from '../common.descriptions';
|
||||
|
||||
const properties: INodeProperties[] = [
|
||||
workbookRLC,
|
||||
worksheetRLC,
|
||||
{
|
||||
displayName: 'Select a Range',
|
||||
name: 'useRange',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
{
|
||||
displayName: 'Range',
|
||||
name: 'range',
|
||||
type: 'string',
|
||||
placeholder: 'e.g. A1:B2',
|
||||
default: '',
|
||||
description: 'The sheet range to read the data from specified using a A1-style notation',
|
||||
hint: 'Leave blank to return entire sheet',
|
||||
displayOptions: {
|
||||
show: {
|
||||
useRange: [true],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Header Row',
|
||||
name: 'keyRow',
|
||||
type: 'number',
|
||||
typeOptions: {
|
||||
minValue: 0,
|
||||
},
|
||||
default: 0,
|
||||
hint: 'Index of the row which contains the column names',
|
||||
description: "Relative to selected 'Range', first row index is 0",
|
||||
displayOptions: {
|
||||
show: {
|
||||
useRange: [true],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'First Data Row',
|
||||
name: 'dataStartRow',
|
||||
type: 'number',
|
||||
typeOptions: {
|
||||
minValue: 0,
|
||||
},
|
||||
default: 1,
|
||||
hint: 'Index of first row which contains the actual data',
|
||||
description: "Relative to selected 'Range', first row index is 0",
|
||||
displayOptions: {
|
||||
show: {
|
||||
useRange: [true],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Option',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'RAW Data',
|
||||
name: 'rawData',
|
||||
type: 'boolean',
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-default-wrong-for-boolean
|
||||
default: 0,
|
||||
description:
|
||||
'Whether the data should be returned RAW instead of parsed into keys according to their header',
|
||||
},
|
||||
{
|
||||
displayName: 'Data Property',
|
||||
name: 'dataProperty',
|
||||
type: 'string',
|
||||
default: 'data',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
rawData: [true],
|
||||
},
|
||||
},
|
||||
description: 'The name of the property into which to write the RAW data',
|
||||
},
|
||||
{
|
||||
displayName: 'Fields',
|
||||
name: 'fields',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Fields the response will containt. Multiple can be added separated by ,.',
|
||||
displayOptions: {
|
||||
show: {
|
||||
rawData: [true],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['worksheet'],
|
||||
operation: ['readRows'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
items: INodeExecutionData[],
|
||||
): Promise<INodeExecutionData[]> {
|
||||
//https://docs.microsoft.com/en-us/graph/api/worksheet-range?view=graph-rest-1.0&tabs=http
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const qs: IDataObject = {};
|
||||
try {
|
||||
const workbookId = this.getNodeParameter('workbook', i, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
const worksheetId = this.getNodeParameter('worksheet', i, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
const options = this.getNodeParameter('options', i, {});
|
||||
|
||||
const range = this.getNodeParameter('range', i, '') as string;
|
||||
|
||||
const rawData = (options.rawData as boolean) || false;
|
||||
|
||||
if (rawData && options.fields) {
|
||||
qs.$select = options.fields;
|
||||
}
|
||||
|
||||
let responseData;
|
||||
if (range) {
|
||||
responseData = await microsoftApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/range(address='${range}')`,
|
||||
{},
|
||||
qs,
|
||||
);
|
||||
} else {
|
||||
responseData = await microsoftApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/usedRange`,
|
||||
{},
|
||||
qs,
|
||||
);
|
||||
}
|
||||
|
||||
if (!rawData) {
|
||||
const keyRow = this.getNodeParameter('keyRow', i, 0) as number;
|
||||
const firstDataRow = this.getNodeParameter('dataStartRow', i, 1) as number;
|
||||
|
||||
returnData.push(
|
||||
...prepareOutput(this.getNode(), responseData as ExcelResponse, {
|
||||
rawData,
|
||||
keyRow,
|
||||
firstDataRow,
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
const dataProperty = (options.dataProperty as string) || 'data';
|
||||
returnData.push(
|
||||
...prepareOutput(this.getNode(), responseData as ExcelResponse, {
|
||||
rawData,
|
||||
dataProperty,
|
||||
}),
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
if (this.continueOnFail()) {
|
||||
const executionErrorData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ error: error.message }),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
returnData.push(...executionErrorData);
|
||||
continue;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
@@ -0,0 +1,376 @@
|
||||
import type { IExecuteFunctions } from 'n8n-core';
|
||||
import type { IDataObject, INodeExecutionData, INodeProperties } from 'n8n-workflow';
|
||||
import { NodeOperationError } from 'n8n-workflow';
|
||||
import { processJsonInput, updateDisplayOptions } from '../../../../../../utils/utilities';
|
||||
import type { ExcelResponse, UpdateSummary } from '../../helpers/interfaces';
|
||||
import { prepareOutput, updateByAutoMaping, updateByDefinedValues } from '../../helpers/utils';
|
||||
import { microsoftApiRequest } from '../../transport';
|
||||
import { workbookRLC, worksheetRLC } from '../common.descriptions';
|
||||
|
||||
const properties: INodeProperties[] = [
|
||||
workbookRLC,
|
||||
worksheetRLC,
|
||||
{
|
||||
displayName: 'Select a Range',
|
||||
name: 'useRange',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
{
|
||||
displayName: 'Range',
|
||||
name: 'range',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
dataMode: ['autoMap', 'define'],
|
||||
useRange: [true],
|
||||
},
|
||||
},
|
||||
placeholder: 'e.g. A1:B2',
|
||||
default: '',
|
||||
description:
|
||||
'The sheet range to read the data from specified using a A1-style notation. Leave blank to use whole used range in the sheet.',
|
||||
hint: 'First row must contain column names',
|
||||
},
|
||||
{
|
||||
displayName: 'Range',
|
||||
name: 'range',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
dataMode: ['raw'],
|
||||
useRange: [true],
|
||||
},
|
||||
},
|
||||
placeholder: 'e.g. A1:B2',
|
||||
default: '',
|
||||
description: 'The sheet range to read the data from specified using a A1-style notation',
|
||||
hint: 'Leave blank for entire worksheet',
|
||||
},
|
||||
{
|
||||
displayName: 'Data Mode',
|
||||
name: 'dataMode',
|
||||
type: 'options',
|
||||
default: 'define',
|
||||
options: [
|
||||
{
|
||||
name: 'Auto-Map Input Data to Columns',
|
||||
value: 'autoMap',
|
||||
description: 'Use when node input properties match destination column names',
|
||||
},
|
||||
{
|
||||
name: 'Map Each Column Below',
|
||||
value: 'define',
|
||||
description: 'Set the value for each destination column',
|
||||
},
|
||||
{
|
||||
name: 'Raw',
|
||||
value: 'raw',
|
||||
description:
|
||||
'Send raw data as JSON, the whole selected range would be updated with the new values',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Data',
|
||||
name: 'data',
|
||||
type: 'json',
|
||||
default: '',
|
||||
required: true,
|
||||
placeholder: 'e.g. [["Sara","1/2/2006","Berlin"],["George","5/3/2010","Paris"]]',
|
||||
description:
|
||||
'Raw values for the specified range as array of string arrays in JSON format. Should match the specified range: one array item for each row.',
|
||||
displayOptions: {
|
||||
show: {
|
||||
dataMode: ['raw'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased, n8n-nodes-base/node-param-display-name-wrong-for-dynamic-options
|
||||
displayName: 'Column to match on',
|
||||
name: 'columnToMatchOn',
|
||||
type: 'options',
|
||||
description:
|
||||
'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>',
|
||||
typeOptions: {
|
||||
loadOptionsDependsOn: ['worksheet.value', 'workbook.value', 'range'],
|
||||
loadOptionsMethod: 'getWorksheetColumnRow',
|
||||
},
|
||||
default: '',
|
||||
hint: "Used to find the correct row to update. Doesn't get changed.",
|
||||
displayOptions: {
|
||||
show: {
|
||||
dataMode: ['autoMap', 'define'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Value of Column to Match On',
|
||||
name: 'valueToMatchOn',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
dataMode: ['define'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Values to Send',
|
||||
name: 'fieldsUi',
|
||||
placeholder: 'Add Field',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
dataMode: ['define'],
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Field',
|
||||
name: 'values',
|
||||
values: [
|
||||
{
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-wrong-for-dynamic-options
|
||||
displayName: 'Column',
|
||||
name: 'column',
|
||||
type: 'options',
|
||||
description:
|
||||
'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>',
|
||||
typeOptions: {
|
||||
loadOptionsDependsOn: ['columnToMatchOn', 'range'],
|
||||
loadOptionsMethod: 'getWorksheetColumnRowSkipColumnToMatchOn',
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'fieldValue',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Option',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'RAW Data',
|
||||
name: 'rawData',
|
||||
type: 'boolean',
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-default-wrong-for-boolean
|
||||
default: 0,
|
||||
description:
|
||||
'Whether the data should be returned RAW instead of parsed into keys according to their header',
|
||||
},
|
||||
{
|
||||
displayName: 'Data Property',
|
||||
name: 'dataProperty',
|
||||
type: 'string',
|
||||
default: 'data',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
rawData: [true],
|
||||
},
|
||||
},
|
||||
description: 'The name of the property into which to write the RAW data',
|
||||
},
|
||||
{
|
||||
displayName: 'Fields',
|
||||
name: 'fields',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Fields the response will containt. Multiple can be added separated by ,.',
|
||||
displayOptions: {
|
||||
show: {
|
||||
rawData: [true],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Update All Matches',
|
||||
name: 'updateAll',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether to update all matching rows or just the first match',
|
||||
displayOptions: {
|
||||
hide: {
|
||||
'/dataMode': ['raw'],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['worksheet'],
|
||||
operation: ['update'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
items: INodeExecutionData[],
|
||||
): Promise<INodeExecutionData[]> {
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
|
||||
try {
|
||||
const options = this.getNodeParameter('options', 0, {});
|
||||
|
||||
const rawData = options.rawData as boolean;
|
||||
const dataProperty = options.dataProperty ? (options.dataProperty as string) : 'data';
|
||||
|
||||
const qs: IDataObject = {};
|
||||
if (rawData && options.fields) {
|
||||
qs.$select = options.fields;
|
||||
}
|
||||
|
||||
const workbookId = this.getNodeParameter('workbook', 0, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
const worksheetId = this.getNodeParameter('worksheet', 0, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
let range = this.getNodeParameter('range', 0, '') as string;
|
||||
const dataMode = this.getNodeParameter('dataMode', 0) as string;
|
||||
|
||||
let worksheetData: IDataObject = {};
|
||||
|
||||
if (range && dataMode !== 'raw') {
|
||||
worksheetData = await microsoftApiRequest.call(
|
||||
this,
|
||||
'PATCH',
|
||||
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/range(address='${range}')`,
|
||||
);
|
||||
}
|
||||
|
||||
//get used range if range not provided; if 'raw' mode fetch only address information
|
||||
if (range === '') {
|
||||
const query: IDataObject = {};
|
||||
if (dataMode === 'raw') {
|
||||
query.select = 'address';
|
||||
}
|
||||
|
||||
worksheetData = await microsoftApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/usedRange`,
|
||||
undefined,
|
||||
query,
|
||||
);
|
||||
|
||||
range = (worksheetData.address as string).split('!')[1];
|
||||
}
|
||||
|
||||
let responseData;
|
||||
if (dataMode === 'raw') {
|
||||
const data = this.getNodeParameter('data', 0);
|
||||
|
||||
const values = processJsonInput(data, 'Data') as string[][];
|
||||
|
||||
responseData = await microsoftApiRequest.call(
|
||||
this,
|
||||
'PATCH',
|
||||
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/range(address='${range}')`,
|
||||
{ values },
|
||||
qs,
|
||||
);
|
||||
|
||||
returnData.push(
|
||||
...prepareOutput(this.getNode(), responseData as ExcelResponse, {
|
||||
rawData,
|
||||
dataProperty,
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
if (worksheetData.values === undefined || (worksheetData.values as string[][]).length <= 1) {
|
||||
throw new NodeOperationError(
|
||||
this.getNode(),
|
||||
'No data found in the specified range, mapping not possible, you can use raw mode instead to update selected range',
|
||||
);
|
||||
}
|
||||
|
||||
const updateAll = this.getNodeParameter('options.updateAll', 0, false) as boolean;
|
||||
|
||||
let updateSummary: UpdateSummary = {
|
||||
updatedData: [],
|
||||
updatedRows: [],
|
||||
appendData: [],
|
||||
};
|
||||
|
||||
if (dataMode === 'define') {
|
||||
updateSummary = updateByDefinedValues.call(
|
||||
this,
|
||||
items.length,
|
||||
worksheetData.values as string[][],
|
||||
updateAll,
|
||||
);
|
||||
}
|
||||
|
||||
if (dataMode === 'autoMap') {
|
||||
const columnToMatchOn = this.getNodeParameter('columnToMatchOn', 0) as string;
|
||||
|
||||
if (!items.some(({ json }) => json[columnToMatchOn] !== undefined)) {
|
||||
throw new NodeOperationError(
|
||||
this.getNode(),
|
||||
`Any item in input data contains column '${columnToMatchOn}', that is selected to match on`,
|
||||
);
|
||||
}
|
||||
|
||||
updateSummary = updateByAutoMaping(
|
||||
items,
|
||||
worksheetData.values as string[][],
|
||||
columnToMatchOn,
|
||||
updateAll,
|
||||
);
|
||||
}
|
||||
|
||||
responseData = await microsoftApiRequest.call(
|
||||
this,
|
||||
'PATCH',
|
||||
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/range(address='${range}')`,
|
||||
{ values: updateSummary.updatedData },
|
||||
);
|
||||
|
||||
const { updatedRows } = updateSummary;
|
||||
|
||||
returnData.push(
|
||||
...prepareOutput(this.getNode(), responseData as ExcelResponse, {
|
||||
updatedRows,
|
||||
rawData,
|
||||
dataProperty,
|
||||
}),
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
if (this.continueOnFail()) {
|
||||
const executionErrorData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ error: error.message }),
|
||||
{ itemData: { item: 0 } },
|
||||
);
|
||||
returnData.push(...executionErrorData);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
@@ -0,0 +1,333 @@
|
||||
import type { IExecuteFunctions } from 'n8n-core';
|
||||
import type { IDataObject, INodeExecutionData, INodeProperties } from 'n8n-workflow';
|
||||
import { NodeOperationError } from 'n8n-workflow';
|
||||
import { processJsonInput, updateDisplayOptions } from '../../../../../../utils/utilities';
|
||||
import type { ExcelResponse, UpdateSummary } from '../../helpers/interfaces';
|
||||
import { prepareOutput, updateByAutoMaping, updateByDefinedValues } from '../../helpers/utils';
|
||||
import { microsoftApiRequest } from '../../transport';
|
||||
import { workbookRLC, worksheetRLC } from '../common.descriptions';
|
||||
|
||||
const properties: INodeProperties[] = [
|
||||
workbookRLC,
|
||||
worksheetRLC,
|
||||
{
|
||||
displayName: 'Select a Range',
|
||||
name: 'useRange',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
{
|
||||
displayName: 'Range',
|
||||
name: 'range',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
dataMode: ['autoMap', 'define'],
|
||||
useRange: [true],
|
||||
},
|
||||
},
|
||||
placeholder: 'e.g. A1:B2',
|
||||
default: '',
|
||||
description:
|
||||
'The sheet range to read the data from specified using a A1-style notation. Leave blank to use whole used range in the sheet.',
|
||||
hint: 'First row must contain column names',
|
||||
},
|
||||
{
|
||||
displayName: 'Data Mode',
|
||||
name: 'dataMode',
|
||||
type: 'options',
|
||||
default: 'define',
|
||||
options: [
|
||||
{
|
||||
name: 'Auto-Map Input Data to Columns',
|
||||
value: 'autoMap',
|
||||
description: 'Use when node input properties match destination column names',
|
||||
},
|
||||
{
|
||||
name: 'Map Each Column Below',
|
||||
value: 'define',
|
||||
description: 'Set the value for each destination column',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased, n8n-nodes-base/node-param-display-name-wrong-for-dynamic-options
|
||||
displayName: 'Column to match on',
|
||||
name: 'columnToMatchOn',
|
||||
type: 'options',
|
||||
description:
|
||||
'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>',
|
||||
typeOptions: {
|
||||
loadOptionsDependsOn: ['worksheet.value', 'workbook.value', 'range'],
|
||||
loadOptionsMethod: 'getWorksheetColumnRow',
|
||||
},
|
||||
default: '',
|
||||
hint: "Used to find the correct row to update. Doesn't get changed.",
|
||||
displayOptions: {
|
||||
show: {
|
||||
dataMode: ['autoMap', 'define'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Value of Column to Match On',
|
||||
name: 'valueToMatchOn',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
dataMode: ['define'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Values to Send',
|
||||
name: 'fieldsUi',
|
||||
placeholder: 'Add Field',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
dataMode: ['define'],
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Field',
|
||||
name: 'values',
|
||||
values: [
|
||||
{
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-wrong-for-dynamic-options
|
||||
displayName: 'Column',
|
||||
name: 'column',
|
||||
type: 'options',
|
||||
description:
|
||||
'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>',
|
||||
typeOptions: {
|
||||
loadOptionsDependsOn: ['columnToMatchOn', 'range'],
|
||||
loadOptionsMethod: 'getWorksheetColumnRowSkipColumnToMatchOn',
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'fieldValue',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Option',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'RAW Data',
|
||||
name: 'rawData',
|
||||
type: 'boolean',
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-default-wrong-for-boolean
|
||||
default: 0,
|
||||
description:
|
||||
'Whether the data should be returned RAW instead of parsed into keys according to their header',
|
||||
},
|
||||
{
|
||||
displayName: 'Data Property',
|
||||
name: 'dataProperty',
|
||||
type: 'string',
|
||||
default: 'data',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
rawData: [true],
|
||||
},
|
||||
},
|
||||
description: 'The name of the property into which to write the RAW data',
|
||||
},
|
||||
{
|
||||
displayName: 'Update All Matches',
|
||||
name: 'updateAll',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether to update all matching rows or just the first match',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['worksheet'],
|
||||
operation: ['upsert'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
items: INodeExecutionData[],
|
||||
): Promise<INodeExecutionData[]> {
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
|
||||
try {
|
||||
const workbookId = this.getNodeParameter('workbook', 0, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
const worksheetId = this.getNodeParameter('worksheet', 0, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
let range = this.getNodeParameter('range', 0, '') as string;
|
||||
const dataMode = this.getNodeParameter('dataMode', 0) as string;
|
||||
|
||||
let worksheetData: IDataObject = {};
|
||||
|
||||
if (range && dataMode !== 'raw') {
|
||||
worksheetData = await microsoftApiRequest.call(
|
||||
this,
|
||||
'PATCH',
|
||||
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/range(address='${range}')`,
|
||||
);
|
||||
}
|
||||
|
||||
//get used range if range not provided; if 'raw' mode fetch only address information
|
||||
if (range === '') {
|
||||
const query: IDataObject = {};
|
||||
if (dataMode === 'raw') {
|
||||
query.select = 'address';
|
||||
}
|
||||
|
||||
worksheetData = await microsoftApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/usedRange`,
|
||||
undefined,
|
||||
query,
|
||||
);
|
||||
|
||||
range = (worksheetData.address as string).split('!')[1];
|
||||
}
|
||||
|
||||
let responseData;
|
||||
if (dataMode === 'raw') {
|
||||
const data = this.getNodeParameter('data', 0);
|
||||
|
||||
const values = processJsonInput(data, 'Data') as string[][];
|
||||
|
||||
responseData = await microsoftApiRequest.call(
|
||||
this,
|
||||
'PATCH',
|
||||
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/range(address='${range}')`,
|
||||
{ values },
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
dataMode !== 'raw' &&
|
||||
(worksheetData.values === undefined || (worksheetData.values as string[][]).length <= 1)
|
||||
) {
|
||||
throw new NodeOperationError(
|
||||
this.getNode(),
|
||||
'No data found in the specified range, mapping not possible, you can use raw mode instead to update selected range',
|
||||
);
|
||||
}
|
||||
|
||||
const updateAll = this.getNodeParameter('options.updateAll', 0, false) as boolean;
|
||||
|
||||
let updateSummary: UpdateSummary = {
|
||||
updatedData: [],
|
||||
updatedRows: [],
|
||||
appendData: [],
|
||||
};
|
||||
|
||||
if (dataMode === 'define') {
|
||||
updateSummary = updateByDefinedValues.call(
|
||||
this,
|
||||
items.length,
|
||||
worksheetData.values as string[][],
|
||||
updateAll,
|
||||
);
|
||||
}
|
||||
|
||||
if (dataMode === 'autoMap') {
|
||||
const columnToMatchOn = this.getNodeParameter('columnToMatchOn', 0) as string;
|
||||
|
||||
if (!items.some(({ json }) => json[columnToMatchOn] !== undefined)) {
|
||||
throw new NodeOperationError(
|
||||
this.getNode(),
|
||||
`Any item in input data contains column '${columnToMatchOn}', that is selected to match on`,
|
||||
);
|
||||
}
|
||||
|
||||
updateSummary = updateByAutoMaping(
|
||||
items,
|
||||
worksheetData.values as string[][],
|
||||
columnToMatchOn,
|
||||
updateAll,
|
||||
);
|
||||
}
|
||||
|
||||
if (updateSummary.appendData.length) {
|
||||
const appendValues: string[][] = [];
|
||||
const columnsRow = (worksheetData.values as string[][])[0];
|
||||
|
||||
for (const [index, item] of updateSummary.appendData.entries()) {
|
||||
const updateRow: string[] = [];
|
||||
|
||||
for (const column of columnsRow) {
|
||||
updateRow.push(item[column] as string);
|
||||
}
|
||||
|
||||
appendValues.push(updateRow);
|
||||
updateSummary.updatedRows.push(index + updateSummary.updatedData.length);
|
||||
}
|
||||
|
||||
updateSummary.updatedData = updateSummary.updatedData.concat(appendValues);
|
||||
const [rangeFrom, rangeTo] = range.split(':');
|
||||
const cellDataTo = rangeTo.match(/([a-zA-Z]{1,10})([0-9]{0,10})/) || [];
|
||||
|
||||
range = `${rangeFrom}:${cellDataTo[1]}${Number(cellDataTo[2]) + appendValues.length}`;
|
||||
}
|
||||
|
||||
responseData = await microsoftApiRequest.call(
|
||||
this,
|
||||
'PATCH',
|
||||
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/range(address='${range}')`,
|
||||
{ values: updateSummary.updatedData },
|
||||
);
|
||||
|
||||
const { updatedRows } = updateSummary;
|
||||
|
||||
const rawData = this.getNodeParameter('options.rawData', 0, false) as boolean;
|
||||
const dataProperty = this.getNodeParameter('options.dataProperty', 0, 'data') as string;
|
||||
|
||||
returnData.push(
|
||||
...prepareOutput(this.getNode(), responseData as ExcelResponse, {
|
||||
updatedRows,
|
||||
rawData,
|
||||
dataProperty,
|
||||
}),
|
||||
);
|
||||
} catch (error) {
|
||||
if (this.continueOnFail()) {
|
||||
const executionErrorData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ error: error.message }),
|
||||
{ itemData: { item: 0 } },
|
||||
);
|
||||
returnData.push(...executionErrorData);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
Reference in New Issue
Block a user