feat(Microsoft Excel 365 Node): Overhaul

This commit is contained in:
Michael Kret
2023-05-02 12:44:25 +03:00
committed by GitHub
parent 25fe14be56
commit 5364a2dff3
75 changed files with 8049 additions and 675 deletions

View File

@@ -0,0 +1,77 @@
import type { INodeProperties } from 'n8n-workflow';
import * as append from './append.operation';
import * as addTable from './addTable.operation';
import * as convertToRange from './convertToRange.operation';
import * as deleteTable from './deleteTable.operation';
import * as getColumns from './getColumns.operation';
import * as getRows from './getRows.operation';
import * as lookup from './lookup.operation';
export { append, addTable, convertToRange, deleteTable, getColumns, getRows, lookup };
export const description: INodeProperties[] = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: ['table'],
},
},
options: [
{
name: 'Append',
value: 'append',
description: 'Add rows to the end of the table',
action: 'Append rows to table',
},
{
name: 'Convert to Range',
value: 'convertToRange',
description: 'Convert a table to a range',
action: 'Convert to range',
},
{
name: 'Create',
value: 'addTable',
description: 'Add a table based on range',
action: 'Create a table',
},
{
name: 'Delete',
value: 'deleteTable',
description: 'Delete a table',
action: 'Delete a table',
},
{
name: 'Get Columns',
value: 'getColumns',
description: 'Retrieve a list of table columns',
action: 'Get columns',
},
{
name: 'Get Rows',
value: 'getRows',
description: 'Retrieve a list of table rows',
action: 'Get rows',
},
{
name: 'Lookup',
value: 'lookup',
description: 'Look for rows that match a given value in a column',
action: 'Lookup a column',
},
],
default: 'append',
},
...append.description,
...addTable.description,
...convertToRange.description,
...deleteTable.description,
...getColumns.description,
...getRows.description,
...lookup.description,
];

View File

@@ -0,0 +1,127 @@
import type { IExecuteFunctions } from 'n8n-core';
import type { IDataObject, 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: 'Select Range',
name: 'selectRange',
type: 'options',
options: [
{
name: 'Automatically',
value: 'auto',
description: 'The whole used range on the selected sheet will be converted into a table',
},
{
name: 'Manually',
value: 'manual',
description: 'Select a range that will be converted into a table',
},
],
default: 'auto',
},
{
displayName: 'Range',
name: 'range',
type: 'string',
default: '',
placeholder: 'A1:B2',
description: 'The range of cells that will be converted to a table',
displayOptions: {
show: {
selectRange: ['manual'],
},
},
},
{
displayName: 'Has Headers',
name: 'hasHeaders',
type: 'boolean',
default: true,
description:
'Whether the range has column labels. When this property set to false Excel will automatically generate header shifting the data down by one row.',
},
];
const displayOptions = {
show: {
resource: ['table'],
operation: ['addTable'],
},
};
export const description = updateDisplayOptions(displayOptions, properties);
export async function execute(
this: IExecuteFunctions,
items: INodeExecutionData[],
): Promise<INodeExecutionData[]> {
//https://learn.microsoft.com/en-us/graph/api/worksheet-post-tables?view=graph-rest-1.0
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 selectRange = this.getNodeParameter('selectRange', i) as string;
const hasHeaders = this.getNodeParameter('hasHeaders', i) as boolean;
let range = '';
if (selectRange === 'auto') {
const { address } = await microsoftApiRequest.call(
this,
'GET',
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/usedRange`,
undefined,
{
select: 'address',
},
);
range = address.split('!')[1];
} else {
range = this.getNodeParameter('range', i) as string;
}
const responseData = await microsoftApiRequest.call(
this,
'POST',
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/add`,
{
address: range,
hasHeaders,
},
);
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;
}

View File

@@ -0,0 +1,282 @@
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 { tableRLC, workbookRLC, worksheetRLC } from '../common.descriptions';
const properties: INodeProperties[] = [
workbookRLC,
worksheetRLC,
tableRLC,
{
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: ['table.value', 'worksheet.value', 'workbook.value'],
loadOptionsMethod: 'getTableColumns',
},
default: '',
},
{
displayName: 'Value',
name: 'fieldValue',
type: 'string',
default: '',
requiresDataPath: 'single',
},
],
},
],
},
{
displayName: 'Options',
name: 'options',
type: 'collection',
placeholder: 'Add Option',
default: {},
options: [
{
displayName: 'Index',
name: 'index',
type: 'number',
default: 0,
typeOptions: {
minValue: 0,
},
description:
'Specifies the relative position of the new row. If not defined, the addition happens at the end. Any row below the inserted row will be shifted downwards. First row index is 0.',
},
{
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: ['table'],
operation: ['append'],
},
};
export const description = updateDisplayOptions(displayOptions, properties);
export async function execute(
this: IExecuteFunctions,
items: INodeExecutionData[],
): Promise<INodeExecutionData[]> {
//https://docs.microsoft.com/en-us/graph/api/table-post-rows?view=graph-rest-1.0&tabs=http
const returnData: INodeExecutionData[] = [];
try {
// TODO: At some point it should be possible to use item dependent parameters.
// Is however important to then not make one separate request each.
const workbookId = this.getNodeParameter('workbook', 0, undefined, {
extractValue: true,
}) as string;
const worksheetId = this.getNodeParameter('worksheet', 0, undefined, {
extractValue: true,
}) as string;
const tableId = this.getNodeParameter('table', 0, undefined, {
extractValue: true,
}) as string;
const dataMode = this.getNodeParameter('dataMode', 0) as string;
// Get table columns to eliminate any columns not needed on the input
const columnsData = await microsoftApiRequest.call(
this,
'GET',
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/columns`,
{},
);
const columnsRow = columnsData.value.map((column: IDataObject) => column.name);
const body: IDataObject = {};
let values: string[][] = [];
if (dataMode === 'raw') {
const data = this.getNodeParameter('data', 0);
values = processJsonInput(data, 'Data') as string[][];
}
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);
}
}
body.values = values;
const options = this.getNodeParameter('options', 0);
if (options.index) {
body.index = options.index as number;
}
const { id } = await microsoftApiRequest.call(
this,
'POST',
`/drive/items/${workbookId}/workbook/createSession`,
{ persistChanges: true },
);
const responseData = await microsoftApiRequest.call(
this,
'POST',
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/rows/add`,
body,
{},
'',
{ 'workbook-session-id': id },
);
await microsoftApiRequest.call(
this,
'POST',
`/drive/items/${workbookId}/workbook/closeSession`,
{},
{},
'',
{ 'workbook-session-id': id },
);
const rawData = options.rawData as boolean;
const dataProperty = (options.dataProperty as string) || 'data';
returnData.push(
...prepareOutput(this.getNode(), responseData as ExcelResponse, {
columnsRow,
dataProperty,
rawData,
}),
);
} 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;
}

View File

@@ -0,0 +1,64 @@
import type { IExecuteFunctions } from 'n8n-core';
import type { IDataObject, INodeExecutionData, INodeProperties } from 'n8n-workflow';
import { updateDisplayOptions } from '../../../../../../utils/utilities';
import { microsoftApiRequest } from '../../transport';
import { tableRLC, workbookRLC, worksheetRLC } from '../common.descriptions';
const properties: INodeProperties[] = [workbookRLC, worksheetRLC, tableRLC];
const displayOptions = {
show: {
resource: ['table'],
operation: ['convertToRange'],
},
};
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 tableId = this.getNodeParameter('table', i, undefined, {
extractValue: true,
}) as string;
const responseData = await microsoftApiRequest.call(
this,
'POST',
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/convertToRange`,
);
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;
}

View File

@@ -0,0 +1,64 @@
import type { IExecuteFunctions } from 'n8n-core';
import type { INodeExecutionData, INodeProperties } from 'n8n-workflow';
import { updateDisplayOptions } from '../../../../../../utils/utilities';
import { microsoftApiRequest } from '../../transport';
import { tableRLC, workbookRLC, worksheetRLC } from '../common.descriptions';
const properties: INodeProperties[] = [workbookRLC, worksheetRLC, tableRLC];
const displayOptions = {
show: {
resource: ['table'],
operation: ['deleteTable'],
},
};
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 tableId = this.getNodeParameter('table', i, undefined, {
extractValue: true,
}) as string;
await microsoftApiRequest.call(
this,
'DELETE',
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}`,
);
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;
}

View File

@@ -0,0 +1,165 @@
import type { IExecuteFunctions } from 'n8n-core';
import type { IDataObject, INodeExecutionData, INodeProperties } from 'n8n-workflow';
import { updateDisplayOptions } from '../../../../../../utils/utilities';
import { microsoftApiRequest, microsoftApiRequestAllItemsSkip } from '../../transport';
import { tableRLC, workbookRLC, worksheetRLC } from '../common.descriptions';
const properties: INodeProperties[] = [
workbookRLC,
worksheetRLC,
tableRLC,
{
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: 'RAW Data',
name: 'rawData',
type: 'boolean',
default: false,
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',
displayOptions: {
show: {
rawData: [true],
},
},
description: 'The name of the property into which to write the RAW data',
},
{
displayName: 'Filters',
name: 'filters',
type: 'collection',
placeholder: 'Add Filter',
default: {},
displayOptions: {
show: {
rawData: [true],
},
},
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: ['table'],
operation: ['getColumns'],
},
};
export const description = updateDisplayOptions(displayOptions, properties);
export async function execute(
this: IExecuteFunctions,
items: INodeExecutionData[],
): Promise<INodeExecutionData[]> {
//https://docs.microsoft.com/en-us/graph/api/table-list-columns?view=graph-rest-1.0&tabs=http
const returnData: INodeExecutionData[] = [];
for (let i = 0; i < items.length; i++) {
try {
const qs: IDataObject = {};
const workbookId = this.getNodeParameter('workbook', i, undefined, {
extractValue: true,
}) as string;
const worksheetId = this.getNodeParameter('worksheet', i, undefined, {
extractValue: true,
}) as string;
const tableId = this.getNodeParameter('table', i, undefined, {
extractValue: true,
}) as string;
const returnAll = this.getNodeParameter('returnAll', i);
const rawData = this.getNodeParameter('rawData', i);
if (rawData) {
const filters = this.getNodeParameter('filters', i);
if (filters.fields) {
qs.$select = filters.fields;
}
}
let responseData;
if (returnAll) {
responseData = await microsoftApiRequestAllItemsSkip.call(
this,
'value',
'GET',
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/columns`,
{},
qs,
);
} else {
qs.$top = this.getNodeParameter('limit', i);
responseData = await microsoftApiRequest.call(
this,
'GET',
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/columns`,
{},
qs,
);
responseData = responseData.value;
}
if (!rawData) {
responseData = responseData.map((column: IDataObject) => ({ name: column.name }));
} else {
const dataProperty = this.getNodeParameter('dataProperty', i) as string;
responseData = { [dataProperty]: responseData };
}
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;
}

View File

@@ -0,0 +1,223 @@
import type { IExecuteFunctions } from 'n8n-core';
import type { IDataObject, INodeExecutionData, INodeProperties } from 'n8n-workflow';
import { updateDisplayOptions } from '../../../../../../utils/utilities';
import { microsoftApiRequest, microsoftApiRequestAllItemsSkip } from '../../transport';
import { tableRLC, workbookRLC, worksheetRLC } from '../common.descriptions';
const properties: INodeProperties[] = [
workbookRLC,
worksheetRLC,
tableRLC,
{
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: 'RAW Data',
name: 'rawData',
type: 'boolean',
default: false,
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',
displayOptions: {
show: {
rawData: [true],
},
},
description: 'The name of the property into which to write the RAW data',
},
{
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',
displayOptions: {
show: {
'/rawData': [true],
},
},
},
{
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-wrong-for-dynamic-options
displayName: 'Column Names or IDs',
name: 'column',
type: 'multiOptions',
description:
'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>. Choose from the list, or specify IDs using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>.',
typeOptions: {
loadOptionsDependsOn: ['table.value', 'worksheet.value', 'workbook.value'],
loadOptionsMethod: 'getTableColumns',
},
default: [],
displayOptions: {
show: {
'/rawData': [false],
},
},
},
],
},
];
const displayOptions = {
show: {
resource: ['table'],
operation: ['getRows'],
},
};
export const description = updateDisplayOptions(displayOptions, properties);
export async function execute(
this: IExecuteFunctions,
items: INodeExecutionData[],
): Promise<INodeExecutionData[]> {
//https://docs.microsoft.com/en-us/graph/api/table-list-rows?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 tableId = this.getNodeParameter('table', i, undefined, {
extractValue: true,
}) as string;
const filters = this.getNodeParameter('filters', i);
const returnAll = this.getNodeParameter('returnAll', i);
const rawData = this.getNodeParameter('rawData', i);
if (rawData) {
if (filters.fields) {
qs.$select = filters.fields;
}
}
let responseData;
if (returnAll) {
responseData = await microsoftApiRequestAllItemsSkip.call(
this,
'value',
'GET',
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/rows`,
{},
qs,
);
} else {
const rowsQs = { ...qs };
rowsQs.$top = this.getNodeParameter('limit', i);
responseData = await microsoftApiRequest.call(
this,
'GET',
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/rows`,
{},
rowsQs,
);
responseData = responseData.value;
}
if (!rawData) {
const columnsQs = { ...qs };
columnsQs.$select = 'name';
// TODO: That should probably be cached in the future
let columns = await microsoftApiRequestAllItemsSkip.call(
this,
'value',
'GET',
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/columns`,
{},
columnsQs,
);
columns = (columns as IDataObject[]).map((column) => column.name);
let rows: INodeExecutionData[] = [];
for (let index = 0; index < responseData.length; index++) {
const object: IDataObject = {};
for (let y = 0; y < columns.length; y++) {
object[columns[y]] = responseData[index].values[0][y];
}
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray({ ...object }),
{ itemData: { item: index } },
);
rows.push(...executionData);
}
if ((filters?.column as string[])?.length) {
rows = rows.map((row) => {
const rowData: IDataObject = {};
Object.keys(row.json).forEach((key) => {
if ((filters.column as string[]).includes(key)) {
rowData[key] = row.json[key];
}
});
return { ...rowData, json: rowData };
});
}
returnData.push(...rows);
} else {
const dataProperty = this.getNodeParameter('dataProperty', i) as string;
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray({ [dataProperty]: responseData }),
{ 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;
}

View File

@@ -0,0 +1,156 @@
import type { IExecuteFunctions } from 'n8n-core';
import type { IDataObject, INodeExecutionData, INodeProperties, JsonObject } from 'n8n-workflow';
import { NodeApiError } from 'n8n-workflow';
import { updateDisplayOptions } from '../../../../../../utils/utilities';
import { microsoftApiRequestAllItemsSkip } from '../../transport';
import { tableRLC, workbookRLC, worksheetRLC } from '../common.descriptions';
const properties: INodeProperties[] = [
workbookRLC,
worksheetRLC,
tableRLC,
{
displayName: 'Lookup Column',
name: 'lookupColumn',
type: 'string',
default: '',
placeholder: 'Email',
required: true,
description: 'The name of the column in which to look for value',
},
{
displayName: 'Lookup Value',
name: 'lookupValue',
type: 'string',
default: '',
placeholder: 'frank@example.com',
required: true,
description: 'The value to look for in column',
},
{
displayName: 'Options',
name: 'options',
type: 'collection',
placeholder: 'Add Option',
default: {},
options: [
{
displayName: 'Return All Matches',
name: 'returnAllMatches',
type: 'boolean',
default: false,
// eslint-disable-next-line n8n-nodes-base/node-param-description-boolean-without-whether
description:
'By default only the first result gets returned. If options gets set all found matches get returned.',
},
],
},
];
const displayOptions = {
show: {
resource: ['table'],
operation: ['lookup'],
},
};
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++) {
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 tableId = this.getNodeParameter('table', i, undefined, {
extractValue: true,
}) as string;
const lookupColumn = this.getNodeParameter('lookupColumn', i) as string;
const lookupValue = this.getNodeParameter('lookupValue', i) as string;
const options = this.getNodeParameter('options', i);
let responseData = await microsoftApiRequestAllItemsSkip.call(
this,
'value',
'GET',
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/rows`,
{},
{},
);
qs.$select = 'name';
// TODO: That should probably be cached in the future
let columns = await microsoftApiRequestAllItemsSkip.call(
this,
'value',
'GET',
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/columns`,
{},
qs,
);
columns = columns.map((column: IDataObject) => column.name);
if (!columns.includes(lookupColumn)) {
throw new NodeApiError(this.getNode(), responseData as JsonObject, {
message: `Column ${lookupColumn} does not exist on the table selected`,
});
}
const result: IDataObject[] = [];
for (let index = 0; index < responseData.length; index++) {
const object: IDataObject = {};
for (let y = 0; y < columns.length; y++) {
object[columns[y]] = responseData[index].values[0][y];
}
result.push({ ...object });
}
if (options.returnAllMatches) {
responseData = result.filter((data: IDataObject) => {
return data[lookupColumn]?.toString() === lookupValue;
});
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(responseData as IDataObject),
{ itemData: { item: i } },
);
returnData.push(...executionData);
} else {
responseData = result.find((data: IDataObject) => {
return data[lookupColumn]?.toString() === lookupValue;
});
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;
}