mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-14 08:34:07 -08:00
fix(Google Sheets Node): Insert data if sheet is empty instead of error (#10942)
Some checks are pending
Test Master / install-and-build (push) Waiting to run
Test Master / Unit tests (18.x) (push) Blocked by required conditions
Test Master / Unit tests (20.x) (push) Blocked by required conditions
Test Master / Unit tests (22.4) (push) Blocked by required conditions
Test Master / Lint (push) Blocked by required conditions
Test Master / Notify Slack on failure (push) Blocked by required conditions
Some checks are pending
Test Master / install-and-build (push) Waiting to run
Test Master / Unit tests (18.x) (push) Blocked by required conditions
Test Master / Unit tests (20.x) (push) Blocked by required conditions
Test Master / Unit tests (22.4) (push) Blocked by required conditions
Test Master / Lint (push) Blocked by required conditions
Test Master / Notify Slack on failure (push) Blocked by required conditions
This commit is contained in:
parent
ad60d49b42
commit
c75990e063
|
@ -0,0 +1,59 @@
|
||||||
|
import type { MockProxy } from 'jest-mock-extended';
|
||||||
|
import { mock } from 'jest-mock-extended';
|
||||||
|
import type { IExecuteFunctions, INode } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import { execute } from '../../../v2/actions/sheet/append.operation';
|
||||||
|
import type { GoogleSheet } from '../../../v2/helpers/GoogleSheet';
|
||||||
|
|
||||||
|
describe('Google Sheet - Append', () => {
|
||||||
|
let mockExecuteFunctions: MockProxy<IExecuteFunctions>;
|
||||||
|
let mockGoogleSheet: MockProxy<GoogleSheet>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mockExecuteFunctions = mock<IExecuteFunctions>();
|
||||||
|
mockGoogleSheet = mock<GoogleSheet>();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should insert input data if sheet is empty', async () => {
|
||||||
|
mockExecuteFunctions.getInputData.mockReturnValueOnce([
|
||||||
|
{
|
||||||
|
json: {
|
||||||
|
row_number: 3,
|
||||||
|
name: 'NEW NAME',
|
||||||
|
text: 'NEW TEXT',
|
||||||
|
},
|
||||||
|
pairedItem: {
|
||||||
|
item: 0,
|
||||||
|
input: undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
mockExecuteFunctions.getNode.mockReturnValueOnce(mock<INode>({ typeVersion: 4.5 }));
|
||||||
|
mockExecuteFunctions.getNodeParameter
|
||||||
|
.mockReturnValueOnce('USER_ENTERED') // valueInputMode
|
||||||
|
.mockReturnValueOnce({}); // options
|
||||||
|
mockExecuteFunctions.getNodeParameter.mockReturnValueOnce('defineBelow'); // dataMode
|
||||||
|
|
||||||
|
mockGoogleSheet.getData.mockResolvedValueOnce(undefined);
|
||||||
|
mockGoogleSheet.getData.mockResolvedValueOnce(undefined);
|
||||||
|
mockGoogleSheet.updateRows.mockResolvedValueOnce(undefined);
|
||||||
|
|
||||||
|
mockGoogleSheet.updateRows.mockResolvedValueOnce([]);
|
||||||
|
|
||||||
|
mockGoogleSheet.appendEmptyRowsOrColumns.mockResolvedValueOnce([]);
|
||||||
|
mockGoogleSheet.appendSheetData.mockResolvedValueOnce([]);
|
||||||
|
|
||||||
|
await execute.call(mockExecuteFunctions, mockGoogleSheet, 'Sheet1', '1234');
|
||||||
|
|
||||||
|
expect(mockGoogleSheet.updateRows).toHaveBeenCalledWith('Sheet1', [['name', 'text']], 'RAW', 1);
|
||||||
|
expect(mockGoogleSheet.appendEmptyRowsOrColumns).toHaveBeenCalledWith('1234', 1, 0);
|
||||||
|
expect(mockGoogleSheet.appendSheetData).toHaveBeenCalledWith({
|
||||||
|
inputData: [{ name: 'NEW NAME', text: 'NEW TEXT' }],
|
||||||
|
keyRowIndex: 1,
|
||||||
|
lastRow: 2,
|
||||||
|
range: 'Sheet1',
|
||||||
|
valueInputMode: 'USER_ENTERED',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,98 @@
|
||||||
|
import type { MockProxy } from 'jest-mock-extended';
|
||||||
|
import { mock } from 'jest-mock-extended';
|
||||||
|
import type { IExecuteFunctions, INode } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import { execute } from '../../../v2/actions/sheet/appendOrUpdate.operation';
|
||||||
|
import type { GoogleSheet } from '../../../v2/helpers/GoogleSheet';
|
||||||
|
|
||||||
|
describe('Google Sheet - Append or Update', () => {
|
||||||
|
let mockExecuteFunctions: MockProxy<IExecuteFunctions>;
|
||||||
|
let mockGoogleSheet: MockProxy<GoogleSheet>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mockExecuteFunctions = mock<IExecuteFunctions>();
|
||||||
|
mockGoogleSheet = mock<GoogleSheet>();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should insert input data if sheet is empty', async () => {
|
||||||
|
mockExecuteFunctions.getInputData.mockReturnValueOnce([
|
||||||
|
{
|
||||||
|
json: {
|
||||||
|
row_number: 3,
|
||||||
|
name: 'NEW NAME',
|
||||||
|
text: 'NEW TEXT',
|
||||||
|
},
|
||||||
|
pairedItem: {
|
||||||
|
item: 0,
|
||||||
|
input: undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
mockExecuteFunctions.getNode.mockReturnValueOnce(mock<INode>({ typeVersion: 4.5 }));
|
||||||
|
mockExecuteFunctions.getNodeParameter
|
||||||
|
.mockReturnValueOnce('USER_ENTERED') // valueInputMode
|
||||||
|
.mockReturnValueOnce({}); // options
|
||||||
|
mockExecuteFunctions.getNodeParameter.mockReturnValueOnce('defineBelow'); // dataMode
|
||||||
|
mockExecuteFunctions.getNodeParameter.mockReturnValueOnce([]); // columns.schema
|
||||||
|
mockExecuteFunctions.getNodeParameter.mockReturnValueOnce(['row_number']); // columnsToMatchOn
|
||||||
|
mockExecuteFunctions.getNode.mockReturnValueOnce(mock<INode>());
|
||||||
|
mockExecuteFunctions.getNodeParameter.mockReturnValueOnce([]); // columns.matchingColumns
|
||||||
|
|
||||||
|
mockGoogleSheet.getData.mockResolvedValueOnce(undefined);
|
||||||
|
|
||||||
|
mockGoogleSheet.getColumnValues.mockResolvedValueOnce([]);
|
||||||
|
mockGoogleSheet.updateRows.mockResolvedValueOnce([]);
|
||||||
|
|
||||||
|
mockGoogleSheet.prepareDataForUpdateOrUpsert.mockResolvedValueOnce({
|
||||||
|
updateData: [],
|
||||||
|
appendData: [
|
||||||
|
{
|
||||||
|
row_number: 3,
|
||||||
|
name: 'NEW NAME',
|
||||||
|
text: 'NEW TEXT',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
mockGoogleSheet.appendEmptyRowsOrColumns.mockResolvedValueOnce([]);
|
||||||
|
mockGoogleSheet.appendSheetData.mockResolvedValueOnce([]);
|
||||||
|
|
||||||
|
await execute.call(mockExecuteFunctions, mockGoogleSheet, 'Sheet1', '1234');
|
||||||
|
|
||||||
|
expect(mockGoogleSheet.getColumnValues).toHaveBeenCalledWith({
|
||||||
|
dataStartRowIndex: 1,
|
||||||
|
keyIndex: -1,
|
||||||
|
range: 'Sheet1!A:Z',
|
||||||
|
sheetData: [['name', 'text']],
|
||||||
|
valueRenderMode: 'UNFORMATTED_VALUE',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockGoogleSheet.updateRows).toHaveBeenCalledWith(
|
||||||
|
'Sheet1',
|
||||||
|
[['name', 'text']],
|
||||||
|
'USER_ENTERED',
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
expect(mockGoogleSheet.prepareDataForUpdateOrUpsert).toHaveBeenCalledWith({
|
||||||
|
columnNamesList: [['name', 'text']],
|
||||||
|
columnValuesList: [],
|
||||||
|
dataStartRowIndex: 1,
|
||||||
|
indexKey: 'row_number',
|
||||||
|
inputData: [{ name: 'NEW NAME', row_number: 3, text: 'NEW TEXT' }],
|
||||||
|
keyRowIndex: 0,
|
||||||
|
range: 'Sheet1!A:Z',
|
||||||
|
upsert: true,
|
||||||
|
valueRenderMode: 'UNFORMATTED_VALUE',
|
||||||
|
});
|
||||||
|
expect(mockGoogleSheet.appendEmptyRowsOrColumns).toHaveBeenCalledWith('1234', 1, 0);
|
||||||
|
expect(mockGoogleSheet.appendSheetData).toHaveBeenCalledWith({
|
||||||
|
columnNamesList: [['name', 'text']],
|
||||||
|
inputData: [{ name: 'NEW NAME', row_number: 3, text: 'NEW TEXT' }],
|
||||||
|
keyRowIndex: 1,
|
||||||
|
lastRow: 2,
|
||||||
|
range: 'Sheet1!A:Z',
|
||||||
|
valueInputMode: 'USER_ENTERED',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -211,7 +211,7 @@ export async function execute(
|
||||||
): Promise<INodeExecutionData[]> {
|
): Promise<INodeExecutionData[]> {
|
||||||
const items = this.getInputData();
|
const items = this.getInputData();
|
||||||
const nodeVersion = this.getNode().typeVersion;
|
const nodeVersion = this.getNode().typeVersion;
|
||||||
const dataMode =
|
let dataMode =
|
||||||
nodeVersion < 4
|
nodeVersion < 4
|
||||||
? (this.getNodeParameter('dataMode', 0) as string)
|
? (this.getNodeParameter('dataMode', 0) as string)
|
||||||
: (this.getNodeParameter('columns.mappingMode', 0) as string);
|
: (this.getNodeParameter('columns.mappingMode', 0) as string);
|
||||||
|
@ -228,6 +228,10 @@ export async function execute(
|
||||||
|
|
||||||
const sheetData = await sheet.getData(range, 'FORMATTED_VALUE');
|
const sheetData = await sheet.getData(range, 'FORMATTED_VALUE');
|
||||||
|
|
||||||
|
if (sheetData === undefined || !sheetData.length) {
|
||||||
|
dataMode = 'autoMapInputData';
|
||||||
|
}
|
||||||
|
|
||||||
if (nodeVersion >= 4.4 && dataMode !== 'autoMapInputData') {
|
if (nodeVersion >= 4.4 && dataMode !== 'autoMapInputData') {
|
||||||
//not possible to refresh columns when mode is autoMapInputData
|
//not possible to refresh columns when mode is autoMapInputData
|
||||||
if (sheetData?.[keyRowIndex - 1] === undefined) {
|
if (sheetData?.[keyRowIndex - 1] === undefined) {
|
||||||
|
|
|
@ -257,7 +257,7 @@ export async function execute(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const dataMode =
|
let dataMode =
|
||||||
nodeVersion < 4
|
nodeVersion < 4
|
||||||
? (this.getNodeParameter('dataMode', 0) as string)
|
? (this.getNodeParameter('dataMode', 0) as string)
|
||||||
: (this.getNodeParameter('columns.mappingMode', 0) as string);
|
: (this.getNodeParameter('columns.mappingMode', 0) as string);
|
||||||
|
@ -267,11 +267,15 @@ export async function execute(
|
||||||
const sheetData = (await sheet.getData(sheetName, 'FORMATTED_VALUE')) ?? [];
|
const sheetData = (await sheet.getData(sheetName, 'FORMATTED_VALUE')) ?? [];
|
||||||
|
|
||||||
if (!sheetData[keyRowIndex] && dataMode !== 'autoMapInputData') {
|
if (!sheetData[keyRowIndex] && dataMode !== 'autoMapInputData') {
|
||||||
|
if (!sheetData.length) {
|
||||||
|
dataMode = 'autoMapInputData';
|
||||||
|
} else {
|
||||||
throw new NodeOperationError(
|
throw new NodeOperationError(
|
||||||
this.getNode(),
|
this.getNode(),
|
||||||
`Could not retrieve the column names from row ${keyRowIndex + 1}`,
|
`Could not retrieve the column names from row ${keyRowIndex + 1}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
columnNames = sheetData[keyRowIndex] ?? [];
|
columnNames = sheetData[keyRowIndex] ?? [];
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,13 @@ export const versionDescription: INodeTypeDescription = {
|
||||||
whenToDisplay: 'beforeExecution',
|
whenToDisplay: 'beforeExecution',
|
||||||
location: 'outputPane',
|
location: 'outputPane',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
message: 'No columns found in Google Sheet. All rows will be appended',
|
||||||
|
displayCondition:
|
||||||
|
'={{ ["appendOrUpdate", "append"].includes($parameter["operation"]) && $parameter?.columns?.mappingMode === "defineBelow" && !$parameter?.columns?.schema?.length }}',
|
||||||
|
whenToDisplay: 'beforeExecution',
|
||||||
|
location: 'outputPane',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
credentials: [
|
credentials: [
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in a new issue