feat(Google Sheets Node): Option how to combine filters when reading rows (#8652)

This commit is contained in:
Michael Kret 2024-02-21 09:59:59 +02:00 committed by GitHub
parent ad82f0c0c8
commit a5e522e536
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 236 additions and 21 deletions

View file

@ -11,7 +11,7 @@ export class GoogleSheets extends VersionedNodeType {
name: 'googleSheets',
icon: 'file:googleSheets.svg',
group: ['input', 'output'],
defaultVersion: 4.2,
defaultVersion: 4.3,
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
description: 'Read, update and write data to Google Sheets',
};
@ -23,6 +23,7 @@ export class GoogleSheets extends VersionedNodeType {
4: new GoogleSheetsV2(baseDescription),
4.1: new GoogleSheetsV2(baseDescription),
4.2: new GoogleSheetsV2(baseDescription),
4.3: new GoogleSheetsV2(baseDescription),
};
super(nodeVersions, baseDescription);

View file

@ -282,3 +282,121 @@ describe('Test Google Sheets, autoMapInputData', () => {
]);
});
});
describe('Test Google Sheets, lookupValues', () => {
const inputData = [
['row_number', 'id', 'num', 'text'],
[2, 1, '111', 'bar'],
[3, 3, 1, 'bar'],
[4, 4, 1, 'baz'],
[5, 5, 1, 'baz'],
[6, 6, 66, 'foo'],
[7, 7, 77, 'foo'],
] as string[][];
it('should return rows by combining filters by OR', async () => {
const fakeExecuteFunction = {
getNode() {
return {};
},
} as unknown as IExecuteFunctions;
const googleSheet = new GoogleSheet('spreadsheetId', fakeExecuteFunction);
const result = await googleSheet.lookupValues(
inputData,
0,
1,
[
{
lookupColumn: 'num',
lookupValue: '1',
},
{
lookupColumn: 'text',
lookupValue: 'foo',
},
],
true,
'OR',
);
expect(result).toBeDefined();
expect(result).toEqual([
{
row_number: 3,
id: 3,
num: 1,
text: 'bar',
},
{
row_number: 4,
id: 4,
num: 1,
text: 'baz',
},
{
row_number: 5,
id: 5,
num: 1,
text: 'baz',
},
{
row_number: 6,
id: 6,
num: 66,
text: 'foo',
},
{
row_number: 7,
id: 7,
num: 77,
text: 'foo',
},
]);
});
it('should return rows by combining filters by AND', async () => {
const fakeExecuteFunction = {
getNode() {
return {};
},
} as unknown as IExecuteFunctions;
const googleSheet = new GoogleSheet('spreadsheetId', fakeExecuteFunction);
const result = await googleSheet.lookupValues(
inputData,
0,
1,
[
{
lookupColumn: 'num',
lookupValue: '1',
},
{
lookupColumn: 'text',
lookupValue: 'baz',
},
],
true,
'AND',
);
expect(result).toBeDefined();
expect(result).toEqual([
{
row_number: 4,
id: 4,
num: 1,
text: 'baz',
},
{
row_number: 5,
id: 5,
num: 1,
text: 'baz',
},
]);
});
});

View file

@ -136,7 +136,7 @@ export const description: SheetProperties = [
show: {
resource: ['sheet'],
operation: ['append'],
'@version': [4, 4.1, 4.2],
'@version': [{ _cnd: { gte: 4 } }],
},
hide: {
...untilSheetSelected,

View file

@ -172,7 +172,7 @@ export const description: SheetProperties = [
show: {
resource: ['sheet'],
operation: ['appendOrUpdate'],
'@version': [4, 4.1, 4.2],
'@version': [{ _cnd: { gte: 4 } }],
},
hide: {
...untilSheetSelected,

View file

@ -1,4 +1,9 @@
import type { IExecuteFunctions, IDataObject, INodeExecutionData } from 'n8n-workflow';
import type {
IExecuteFunctions,
IDataObject,
INodeExecutionData,
INodeProperties,
} from 'n8n-workflow';
import type { GoogleSheet } from '../../helpers/GoogleSheet';
import {
getRangeString,
@ -15,6 +20,27 @@ import type {
import { dataLocationOnSheet, outputFormatting } from './commonDescription';
const combineFiltersOptions: INodeProperties = {
displayName: 'Combine Filters',
name: 'combineFilters',
type: 'options',
description:
'How to combine the conditions defined in "Filters": AND requires all conditions to be true, OR requires at least one condition to be true',
options: [
{
name: 'AND',
value: 'AND',
description: 'Only rows that meet all the conditions are selected',
},
{
name: 'OR',
value: 'OR',
description: 'Rows that meet at least one condition are selected',
},
],
default: 'AND',
};
export const description: SheetProperties = [
{
displayName: 'Filters',
@ -64,6 +90,33 @@ export const description: SheetProperties = [
},
},
},
{
...combineFiltersOptions,
default: 'OR',
displayOptions: {
show: {
'@version': [{ _cnd: { lt: 4.3 } }],
resource: ['sheet'],
operation: ['read'],
},
hide: {
...untilSheetSelected,
},
},
},
{
...combineFiltersOptions,
displayOptions: {
show: {
'@version': [{ _cnd: { gte: 4.3 } }],
resource: ['sheet'],
operation: ['read'],
},
hide: {
...untilSheetSelected,
},
},
},
{
displayName: 'Options',
name: 'options',
@ -178,19 +231,24 @@ export async function execute(
}
}
const combineFilters = this.getNodeParameter('combineFilters', itemIndex, 'OR') as
| 'AND'
| 'OR';
responseData = await sheet.lookupValues(
data as string[][],
headerRow,
firstDataRow,
lookupValues,
returnAllMatches,
combineFilters,
);
} else {
responseData = sheet.structureArrayDataByColumn(data as string[][], headerRow, firstDataRow);
}
returnData.push(
...responseData.map((item, index) => {
...responseData.map((item) => {
return {
json: item,
pairedItem: { item: itemIndex },

View file

@ -172,7 +172,7 @@ export const description: SheetProperties = [
show: {
resource: ['sheet'],
operation: ['update'],
'@version': [4, 4.1, 4.2],
'@version': [{ _cnd: { gte: 4 } }],
},
hide: {
...untilSheetSelected,

View file

@ -9,7 +9,7 @@ export const versionDescription: INodeTypeDescription = {
name: 'googleSheets',
icon: 'file:googleSheets.svg',
group: ['input', 'output'],
version: [3, 4, 4.1, 4.2],
version: [3, 4, 4.1, 4.2, 4.3],
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
description: 'Read, update and write data to Google Sheets',
defaults: {

View file

@ -629,6 +629,7 @@ export class GoogleSheet {
dataStartRowIndex: number,
lookupValues: ILookupValues[],
returnAllMatches?: boolean,
combineFilters: 'AND' | 'OR' = 'OR',
): Promise<IDataObject[]> {
const keys: string[] = [];
@ -665,28 +666,65 @@ export class GoogleSheet {
// const returnData = [inputData[keyRowIndex]];
const returnData = [keys];
lookupLoop: for (const lookupValue of lookupValues) {
returnColumnIndex = keys.indexOf(lookupValue.lookupColumn);
if (combineFilters === 'OR') {
lookupLoop: for (const lookupValue of lookupValues) {
returnColumnIndex = keys.indexOf(lookupValue.lookupColumn);
if (returnColumnIndex === -1) {
throw new NodeOperationError(
this.executeFunctions.getNode(),
`The column "${lookupValue.lookupColumn}" could not be found`,
);
if (returnColumnIndex === -1) {
throw new NodeOperationError(
this.executeFunctions.getNode(),
`The column "${lookupValue.lookupColumn}" could not be found`,
);
}
// Loop over all the items and find the one with the matching value
for (rowIndex = dataStartRowIndex; rowIndex < inputData.length; rowIndex++) {
if (
inputData[rowIndex][returnColumnIndex]?.toString() ===
lookupValue.lookupValue.toString()
) {
if (addedRows.indexOf(rowIndex) === -1) {
returnData.push(inputData[rowIndex]);
addedRows.push(rowIndex);
}
if (returnAllMatches !== true) {
continue lookupLoop;
}
}
}
}
} else {
lookupLoop: for (rowIndex = dataStartRowIndex; rowIndex < inputData.length; rowIndex++) {
let allMatch = true;
// Loop over all the items and find the one with the matching value
for (rowIndex = dataStartRowIndex; rowIndex < inputData.length; rowIndex++) {
if (
inputData[rowIndex][returnColumnIndex]?.toString() === lookupValue.lookupValue.toString()
) {
for (const lookupValue of lookupValues) {
returnColumnIndex = keys.indexOf(lookupValue.lookupColumn);
if (returnColumnIndex === -1) {
throw new NodeOperationError(
this.executeFunctions.getNode(),
`The column "${lookupValue.lookupColumn}" could not be found`,
);
}
if (
inputData[rowIndex][returnColumnIndex]?.toString() !==
lookupValue.lookupValue.toString()
) {
allMatch = false;
break;
}
}
if (allMatch) {
if (addedRows.indexOf(rowIndex) === -1) {
returnData.push(inputData[rowIndex]);
addedRows.push(rowIndex);
}
if (returnAllMatches !== true) {
continue lookupLoop;
break lookupLoop;
}
}
}

View file

@ -2175,7 +2175,7 @@ export type PropertiesOf<M extends { resource: string; operation: string }> = Ar
[key in 'show' | 'hide']?: {
resource?: Array<M['resource']>;
operation?: Array<M['operation']>;
[otherKey: string]: NodeParameterValue[] | undefined;
[otherKey: string]: Array<NodeParameterValue | DisplayCondition> | undefined;
};
};
}