Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

3 changes: 2 additions & 1 deletion packages/nodes-base/nodes/Google/Sheet/GoogleSheets.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
};
Expand All @@ -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);
Expand Down
118 changes: 118 additions & 0 deletions packages/nodes-base/nodes/Google/Sheet/test/v2/utils/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
},
]);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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',
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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 },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
64 changes: 51 additions & 13 deletions packages/nodes-base/nodes/Google/Sheet/v2/helpers/GoogleSheet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -629,6 +629,7 @@ export class GoogleSheet {
dataStartRowIndex: number,
lookupValues: ILookupValues[],
returnAllMatches?: boolean,
combineFilters: 'AND' | 'OR' = 'OR',
): Promise<IDataObject[]> {
const keys: string[] = [];

Expand Down Expand Up @@ -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;

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;
}
}

// 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 (allMatch) {
if (addedRows.indexOf(rowIndex) === -1) {
returnData.push(inputData[rowIndex]);
addedRows.push(rowIndex);
}

if (returnAllMatches !== true) {
continue lookupLoop;
break lookupLoop;
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/workflow/src/Interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
};
}
Expand Down
Loading