UNPKG

n8n-nodes-base

Version:

Base nodes of n8n

537 lines 24.2 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.GoogleSheet = void 0; const get_1 = __importDefault(require("lodash/get")); const n8n_workflow_1 = require("n8n-workflow"); const xlsx_1 = require("xlsx"); const GoogleSheets_utils_1 = require("./GoogleSheets.utils"); const transport_1 = require("../transport"); class GoogleSheet { id; executeFunctions; constructor(spreadsheetId, executeFunctions) { this.executeFunctions = executeFunctions; this.id = spreadsheetId; } /** * Encodes the range that also none latin character work * * @param {string} range * @returns {string} * @memberof GoogleSheet */ encodeRange(range) { if (range.includes('!')) { const [sheet, ranges] = range.split('!'); return `${encodeURIComponent(sheet)}!${ranges}`; } // Use '' so that sheet is not interpreted as range return encodeURIComponent(`'${range}'`); } /** * Clears values from a sheet * * @param {string} range * @returns {Promise<object>} * @memberof GoogleSheet */ async clearData(range) { const body = { spreadsheetId: this.id, range, }; const response = await transport_1.apiRequest.call(this.executeFunctions, 'POST', `/v4/spreadsheets/${this.id}/values/${this.encodeRange(range)}:clear`, body); return response; } /** * Returns the cell values */ async getData(range, valueRenderMode, dateTimeRenderOption) { const query = { valueRenderOption: valueRenderMode, dateTimeRenderOption: 'FORMATTED_STRING', }; if (dateTimeRenderOption) { query.dateTimeRenderOption = dateTimeRenderOption; } const response = await transport_1.apiRequest.call(this.executeFunctions, 'GET', `/v4/spreadsheets/${this.id}/values/${this.encodeRange(range)}`, {}, query); return response.values; } /** * Returns the sheets in a Spreadsheet */ async spreadsheetGetSheets() { const query = { fields: 'sheets.properties', }; const response = await transport_1.apiRequest.call(this.executeFunctions, 'GET', `/v4/spreadsheets/${this.id}`, {}, query); return response; } /** * Returns the sheet within a spreadsheet based on name or ID */ async spreadsheetGetSheet(node, mode, value) { const query = { fields: 'sheets.properties', }; const response = (await transport_1.apiRequest.call(this.executeFunctions, 'GET', `/v4/spreadsheets/${this.id}`, {}, query)); const foundItem = response.sheets.find((item) => { if (mode === 'name') return item.properties.title === value; return item.properties.sheetId === (0, GoogleSheets_utils_1.getSheetId)(value); }); if (!foundItem?.properties?.title) { const error = new Error(`Sheet with ${mode === 'name' ? 'name' : 'ID'} ${value} not found`); throw new n8n_workflow_1.NodeOperationError(node, error, { level: 'warning' }); } return foundItem.properties; } /** * Returns the grid properties of a sheet */ async getDataRange(sheetId) { const query = { fields: 'sheets.properties', }; const response = await transport_1.apiRequest.call(this.executeFunctions, 'GET', `/v4/spreadsheets/${this.id}`, {}, query); const foundItem = response.sheets.find((item) => item.properties.sheetId === sheetId); return foundItem.properties.gridProperties; } /** * Sets values in one or more ranges of a spreadsheet. */ async spreadsheetBatchUpdate(requests) { const body = { requests, }; const response = await transport_1.apiRequest.call(this.executeFunctions, 'POST', `/v4/spreadsheets/${this.id}:batchUpdate`, body); return response; } /** * Sets the cell values */ async batchUpdate(updateData, valueInputMode) { const body = { data: updateData, valueInputOption: valueInputMode, }; const response = await transport_1.apiRequest.call(this.executeFunctions, 'POST', `/v4/spreadsheets/${this.id}/values:batchUpdate`, body); return response; } async appendEmptyRowsOrColumns(sheetId, rowsToAdd = 1, columnsToAdd = 1) { const requests = []; if (rowsToAdd > 0) { requests.push({ appendDimension: { sheetId, dimension: 'ROWS', length: rowsToAdd, }, }); } if (columnsToAdd > 0) { requests.push({ appendDimension: { sheetId, dimension: 'COLUMNS', length: columnsToAdd, }, }); } if (requests.length === 0) { throw new n8n_workflow_1.ApplicationError('Must specify at least one column or row to add', { level: 'warning', }); } const response = await transport_1.apiRequest.call(this.executeFunctions, 'POST', `/v4/spreadsheets/${this.id}:batchUpdate`, { requests }); return response; } /** * Appends the cell values */ async appendData(range, data, valueInputMode, lastRow, useAppend) { const lastRowWithData = lastRow || ((await this.getData(range, 'UNFORMATTED_VALUE')) || []).length + 1; const response = await this.updateRows(range, data, valueInputMode, lastRowWithData, data.length, useAppend); return response; } async updateRows(sheetName, data, valueInputMode, row, rowsLength, useAppend) { const [name, _sheetRange] = sheetName.split('!'); const range = `${name}!${row}:${rowsLength ? row + rowsLength - 1 : row}`; const body = { range, values: data, }; const query = { valueInputOption: valueInputMode, }; let response; if (useAppend) { response = await transport_1.apiRequest.call(this.executeFunctions, 'POST', `/v4/spreadsheets/${this.id}/values/${this.encodeRange(range)}:append`, body, query); } else { response = await transport_1.apiRequest.call(this.executeFunctions, 'PUT', `/v4/spreadsheets/${this.id}/values/${this.encodeRange(range)}`, body, query); } return response; } /** * Returns the given sheet data in a structured way */ convertSheetDataArrayToObjectArray(sheet, startRow, columnKeys, addEmpty, includeHeadersWithEmptyCells) { const returnData = []; for (let rowIndex = startRow; rowIndex < sheet.length; rowIndex++) { const item = {}; const rowCount = sheet[rowIndex].length; const columnCount = includeHeadersWithEmptyCells ? columnKeys.length : rowCount; for (let columnIndex = 0; columnIndex < columnCount; columnIndex++) { const key = columnKeys[columnIndex]; if (key) { item[key] = sheet[rowIndex][columnIndex] ?? ''; } } if (Object.keys(item).length || addEmpty === true) { returnData.push(item); } } return returnData; } /** * Returns the given sheet data in a structured way using * the startRow as the one with the name of the key */ structureArrayDataByColumn(inputData, keyRow, dataStartRow, includeHeadersWithEmptyCells) { const keys = []; if (keyRow < 0 || dataStartRow < keyRow || keyRow >= inputData.length) { // The key row does not exist so it is not possible to structure data return []; } const longestRow = inputData.reduce((a, b) => (a.length > b.length ? a : b), []).length; for (let columnIndex = 0; columnIndex < longestRow; columnIndex++) { keys.push(inputData[keyRow][columnIndex] || `col_${columnIndex}`); } return this.convertSheetDataArrayToObjectArray(inputData, dataStartRow, keys, false, includeHeadersWithEmptyCells); } testFilter(inputData, keyRow, dataStartRow) { const keys = []; //const returnData = []; if (keyRow < 0 || dataStartRow < keyRow || keyRow >= inputData.length) { // The key row does not exist so it is not possible to structure data return []; } // Create the keys array for (let columnIndex = 0; columnIndex < inputData[keyRow].length; columnIndex++) { keys.push(inputData[keyRow][columnIndex]); } return keys; } async appendSheetData({ inputData, range, keyRowIndex, valueInputMode, usePathForKeyRow, columnNamesList, lastRow, useAppend, }) { const data = await this.convertObjectArrayToSheetDataArray(inputData, range, keyRowIndex, usePathForKeyRow, columnNamesList, useAppend ? null : ''); return await this.appendData(range, data, valueInputMode, lastRow, useAppend); } getColumnWithOffset(startColumn, offset) { const columnIndex = xlsx_1.utils.decode_col(startColumn) + offset; return xlsx_1.utils.encode_col(columnIndex); } async getColumnValues({ range, keyIndex, dataStartRowIndex, valueRenderMode, sheetData, }) { let columnValuesList; if (sheetData) { columnValuesList = sheetData.slice(dataStartRowIndex - 1).map((row) => row[keyIndex]); } else { const decodedRange = this.getDecodedSheetRange(range); const startRowIndex = decodedRange.start?.row || dataStartRowIndex; const endRowIndex = decodedRange.end?.row || ''; const keyColumn = this.getColumnWithOffset(decodedRange.start?.column || 'A', keyIndex); const keyColumnRange = `${decodedRange.name}!${keyColumn}${startRowIndex}:${keyColumn}${endRowIndex}`; columnValuesList = await this.getData(keyColumnRange, valueRenderMode); } if (columnValuesList === undefined) { throw new n8n_workflow_1.NodeOperationError(this.executeFunctions.getNode(), 'Could not retrieve the data from key column'); } //Remove the first row which contains the key and flaten the array return columnValuesList.splice(1).flatMap((value) => value); } /** * Updates data in a sheet * * @param {IDataObject[]} inputData Data to update Sheet with * @param {string} indexKey The name of the key which gets used to know which rows to update * @param {string} range The range to look for data * @param {number} keyRowIndex Index of the row which contains the keys * @param {number} dataStartRowIndex Index of the first row which contains data * @returns {Promise<string[][]>} * @memberof GoogleSheet */ async prepareDataForUpdateOrUpsert({ inputData, indexKey, range, keyRowIndex, dataStartRowIndex, valueRenderMode, upsert = false, columnNamesList, columnValuesList, }) { const decodedRange = this.getDecodedSheetRange(range); const keyRowRange = `${decodedRange.name}!${decodedRange.start?.column || ''}${keyRowIndex + 1}:${decodedRange.end?.column || ''}${keyRowIndex + 1}`; const sheetDatakeyRow = columnNamesList || (await this.getData(keyRowRange, valueRenderMode)); if (sheetDatakeyRow === undefined) { throw new n8n_workflow_1.NodeOperationError(this.executeFunctions.getNode(), 'Could not retrieve the key row'); } const columnNames = sheetDatakeyRow[0]; const keyIndex = columnNames.indexOf(indexKey); if (keyIndex === -1 && !upsert) { throw new n8n_workflow_1.NodeOperationError(this.executeFunctions.getNode(), `Could not find column for key "${indexKey}"`); } const columnValues = columnValuesList || (await this.getColumnValues({ range, keyIndex, dataStartRowIndex, valueRenderMode })); const updateData = []; const appendData = []; const getKeyIndex = (key, data) => { let index = -1; for (let i = 0; i < data.length; i++) { if (data[i]?.toString() === key.toString()) { index = i; break; } } return index; }; for (const item of inputData) { const inputIndexKey = item[indexKey]; if (inputIndexKey === undefined || inputIndexKey === null) { // Item does not have the indexKey so we can ignore it or append it if upsert true if (upsert) { appendData.push(item); } continue; } // Item does have the key so check if it exists in Sheet const indexOfIndexKeyInSheet = getKeyIndex(inputIndexKey, columnValues); if (indexOfIndexKeyInSheet === -1) { // Key does not exist in the Sheet so it can not be updated so skip it or append it if upsert true if (upsert) { appendData.push(item); } continue; } // Get the row index in which the data should be updated const updateRowIndex = indexOfIndexKeyInSheet + dataStartRowIndex + 1; // Check all the properties in the sheet and check which ones exist on the // item and should be updated for (const name of columnNames) { if (name === indexKey) { // Ignore the key itself as that does not get changed it gets // only used to find the correct row to update continue; } if (item[name] === undefined || item[name] === null) { // Property does not exist so skip it continue; } // Property exists so add it to the data to update // Get the column name in which the property data can be found const columnToUpdate = this.getColumnWithOffset(decodedRange.start?.column || 'A', columnNames.indexOf(name)); let updateValue = item[name]; if (typeof updateValue === 'object') { try { updateValue = JSON.stringify(updateValue); } catch (error) { } } updateData.push({ range: `${decodedRange.name}!${columnToUpdate}${updateRowIndex}`, values: [[updateValue]], }); } } return { updateData, appendData }; } /** * Updates data in a sheet * * @param {IDataObject[]} inputData Data to update Sheet with * @param {string} range The range to look for data * @param {number} dataStartRowIndex Index of the first row which contains data * @param {string[][]} columnNamesList The column names to use * @returns {Promise<string[][]>} * @memberof GoogleSheet */ prepareDataForUpdatingByRowNumber(inputData, range, columnNamesList) { const decodedRange = this.getDecodedSheetRange(range); const columnNames = columnNamesList[0]; const updateData = []; for (const item of inputData) { const updateRowIndex = item.row_number; for (const name of columnNames) { if (name === 'row_number') continue; if (item[name] === undefined || item[name] === null) continue; const columnToUpdate = this.getColumnWithOffset(decodedRange.start?.column || 'A', columnNames.indexOf(name)); let updateValue = item[name]; if (typeof updateValue === 'object') { try { updateValue = JSON.stringify(updateValue); } catch (error) { } } updateData.push({ range: `${decodedRange.name}!${columnToUpdate}${updateRowIndex}`, values: [[updateValue]], }); } } return { updateData }; } /** * Looks for a specific value in a column and if it gets found it returns the whole row * * @param {string[][]} inputData Data to check for lookup value in * @param {number} keyRowIndex Index of the row which contains the keys * @param {number} dataStartRowIndex Index of the first row which contains data * @param {ILookupValues[]} lookupValues The lookup values which decide what data to return * @param {boolean} [returnAllMatches] Returns all the found matches instead of only the first one * @returns {Promise<IDataObject[]>} * @memberof GoogleSheet */ async lookupValues({ inputData, keyRowIndex, dataStartRowIndex, lookupValues, returnAllMatches, nodeVersion, combineFilters = 'OR', }) { const keys = []; if (keyRowIndex < 0 || dataStartRowIndex < keyRowIndex || keyRowIndex >= inputData.length) { // The key row does not exist so it is not possible to look up the data throw new n8n_workflow_1.NodeOperationError(this.executeFunctions.getNode(), 'The key row does not exist'); } // Create the keys array for (let columnIndex = 0; columnIndex < inputData[keyRowIndex].length; columnIndex++) { keys.push(inputData[keyRowIndex][columnIndex] || `col_${columnIndex}`); } // Standardize values array, if rows is [[]], map it to [['']] (Keep the columns into consideration) for (let rowIndex = 0; rowIndex < inputData?.length; rowIndex++) { if (inputData[rowIndex].length === 0) { for (let i = 0; i < keys.length; i++) { inputData[rowIndex][i] = ''; } } else if (inputData[rowIndex].length < keys.length) { for (let i = 0; i < keys.length; i++) { if (inputData[rowIndex][i] === undefined) { inputData[rowIndex].push(''); } } } } // Loop over all the lookup values and try to find a row to return let rowIndex; let returnColumnIndex; const addedRows = []; // const returnData = [inputData[keyRowIndex]]; const returnData = [keys]; if (combineFilters === 'OR') { lookupLoop: for (const lookupValue of lookupValues) { returnColumnIndex = keys.indexOf(lookupValue.lookupColumn); if (returnColumnIndex === -1) { throw new n8n_workflow_1.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) { if (nodeVersion >= 4.6) { break lookupLoop; } 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 n8n_workflow_1.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) { break lookupLoop; } } } } const dataWithoutEmptyColumns = (0, GoogleSheets_utils_1.removeEmptyColumns)(returnData); return this.convertSheetDataArrayToObjectArray(dataWithoutEmptyColumns, 1, dataWithoutEmptyColumns[0], true); } async convertObjectArrayToSheetDataArray(inputData, range, keyRowIndex, usePathForKeyRow, columnNamesList, emptyValue = '') { const decodedRange = this.getDecodedSheetRange(range); const columnNamesRow = columnNamesList || (await this.getData(`${decodedRange.name}!${keyRowIndex}:${keyRowIndex}`, 'UNFORMATTED_VALUE')); if (columnNamesRow === undefined) { throw new n8n_workflow_1.NodeOperationError(this.executeFunctions.getNode(), 'Could not retrieve the column data'); } const columnNames = columnNamesRow ? columnNamesRow[0] : []; const setData = []; inputData.forEach((item) => { const rowData = []; columnNames.forEach((key) => { let value; if (usePathForKeyRow) { value = (0, get_1.default)(item, key); } else { value = item[key]; } if (value === undefined || value === null) { rowData.push(emptyValue); return; } if (typeof value === 'object') { rowData.push(JSON.stringify(value)); } else { rowData.push(value); } }); setData.push(rowData); }); return setData; } getDecodedSheetRange(stringToDecode) { const decodedRange = {}; const [name, range] = stringToDecode.split('!'); decodedRange.nameWithRange = stringToDecode; decodedRange.name = name; decodedRange.range = range || ''; decodedRange.start = {}; decodedRange.end = {}; if (range) { const [startCell, endCell] = range.split(':'); if (startCell) { decodedRange.start = this.splitCellRange(startCell, range); } if (endCell) { decodedRange.end = this.splitCellRange(endCell, range); } } return decodedRange; } splitCellRange(cell, range) { const cellData = cell.match(/([a-zA-Z]{1,10})([0-9]{0,10})/) || []; if (cellData === null || cellData.length !== 3) { throw new n8n_workflow_1.NodeOperationError(this.executeFunctions.getNode(), `The range "${range}" is not valid`); } return { cell: cellData[0], column: cellData[1], row: +cellData[2] }; } } exports.GoogleSheet = GoogleSheet; //# sourceMappingURL=GoogleSheet.js.map