mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-10 22:54:05 -08:00
209 lines
5.7 KiB
TypeScript
209 lines
5.7 KiB
TypeScript
import type { IDataObject, IPollFunctions } from 'n8n-workflow';
|
|
import { apiRequest } from './v2/transport';
|
|
import type { SheetDataRow, SheetRangeData } from './v2/helpers/GoogleSheets.types';
|
|
|
|
import * as XLSX from 'xlsx';
|
|
import isEqual from 'lodash.isequal';
|
|
import zip from 'lodash.zip';
|
|
|
|
export const BINARY_MIME_TYPE = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
|
|
|
|
type DiffData = Array<{
|
|
rowIndex: number;
|
|
previous: SheetDataRow;
|
|
current: SheetDataRow;
|
|
changeType: string;
|
|
}>;
|
|
|
|
export async function getRevisionFile(this: IPollFunctions, exportLink: string) {
|
|
const mimeType = BINARY_MIME_TYPE;
|
|
|
|
const response = await apiRequest.call(
|
|
this,
|
|
'GET',
|
|
'',
|
|
undefined,
|
|
{ mimeType },
|
|
exportLink,
|
|
undefined,
|
|
{
|
|
resolveWithFullResponse: true,
|
|
encoding: null,
|
|
json: false,
|
|
},
|
|
);
|
|
|
|
return Buffer.from(response.body as string);
|
|
}
|
|
|
|
export function sheetBinaryToArrayOfArrays(
|
|
data: Buffer,
|
|
sheetName: string,
|
|
range: string | undefined,
|
|
) {
|
|
const workbook = XLSX.read(data, { type: 'buffer', sheets: [sheetName] });
|
|
const sheet = workbook.Sheets[sheetName];
|
|
const sheetData: string[][] = sheet['!ref']
|
|
? XLSX.utils.sheet_to_json(sheet, { header: 1, defval: '', range })
|
|
: [];
|
|
|
|
const lastDataRowIndex = sheetData.reduce((lastRowIndex, row, rowIndex) => {
|
|
if (row.some((cell) => cell !== '')) {
|
|
return rowIndex;
|
|
}
|
|
return lastRowIndex;
|
|
}, 0);
|
|
|
|
return sheetData.slice(0, lastDataRowIndex + 1);
|
|
}
|
|
|
|
export function arrayOfArraysToJson(sheetData: SheetRangeData, columns: SheetDataRow) {
|
|
const returnData: IDataObject[] = [];
|
|
|
|
for (let rowIndex = 0; rowIndex < sheetData.length; rowIndex++) {
|
|
const rowData: IDataObject = {};
|
|
|
|
for (let columnIndex = 0; columnIndex < columns.length; columnIndex++) {
|
|
const columnName = columns[columnIndex];
|
|
const cellValue = sheetData[rowIndex][columnIndex] || '';
|
|
|
|
rowData[columnName] = cellValue;
|
|
}
|
|
|
|
returnData.push(rowData);
|
|
}
|
|
|
|
return returnData;
|
|
}
|
|
|
|
const getSpecificColumns = (
|
|
row: SheetDataRow,
|
|
selectedColumns: SheetDataRow,
|
|
columns: SheetDataRow,
|
|
) => {
|
|
return row ? selectedColumns.map((column) => row[columns.indexOf(column) - 1]) : [];
|
|
};
|
|
|
|
const extractVersionData = (
|
|
data: DiffData,
|
|
version: 'previous' | 'current',
|
|
triggerEvent: string,
|
|
) => {
|
|
if (triggerEvent === 'anyUpdate') {
|
|
return data.map(({ [version]: entry, rowIndex, changeType }) =>
|
|
entry ? [rowIndex, changeType, ...entry] : [rowIndex, changeType],
|
|
);
|
|
}
|
|
return data.map(({ [version]: entry, rowIndex }) => (entry ? [rowIndex, ...entry] : [rowIndex]));
|
|
};
|
|
|
|
export function compareRevisions(
|
|
previous: SheetRangeData,
|
|
current: SheetRangeData,
|
|
keyRow: number,
|
|
includeInOutput: string,
|
|
columnsToWatch: string[],
|
|
dataStartIndex: number,
|
|
event: string,
|
|
) {
|
|
const dataLength = current.length > previous.length ? current.length : previous.length;
|
|
|
|
const columnsRowIndex = keyRow - 1;
|
|
const columnsInCurrent = current[columnsRowIndex];
|
|
const columnsInPrevious = previous[columnsRowIndex];
|
|
|
|
let columns: SheetDataRow =
|
|
event === 'anyUpdate' ? ['row_number', 'change_type'] : ['row_number'];
|
|
if (columnsInCurrent !== undefined && columnsInPrevious !== undefined) {
|
|
columns =
|
|
columnsInCurrent.length > columnsInPrevious.length
|
|
? columns.concat(columnsInCurrent)
|
|
: columns.concat(columnsInPrevious);
|
|
} else if (columnsInCurrent !== undefined) {
|
|
columns = columns.concat(columnsInCurrent);
|
|
} else if (columnsInPrevious !== undefined) {
|
|
columns = columns.concat(columnsInPrevious);
|
|
}
|
|
|
|
const diffData: DiffData = [];
|
|
|
|
for (let i = dataStartIndex; i < dataLength; i++) {
|
|
if (i === columnsRowIndex) {
|
|
continue;
|
|
}
|
|
|
|
// sheets API omits trailing empty columns, xlsx does not - so we need to pad the shorter array
|
|
if (Array.isArray(current[i]) && Array.isArray(previous[i])) {
|
|
while (current[i].length < previous[i].length) {
|
|
current[i].push('');
|
|
}
|
|
}
|
|
|
|
// if columnsToWatch is defined, only compare those columns
|
|
if (columnsToWatch?.length) {
|
|
const currentRow = getSpecificColumns(current[i], columnsToWatch, columns);
|
|
const previousRow = getSpecificColumns(previous[i], columnsToWatch, columns);
|
|
|
|
if (isEqual(currentRow, previousRow)) continue;
|
|
} else {
|
|
if (isEqual(current[i], previous[i])) continue;
|
|
}
|
|
|
|
if (event === 'rowUpdate' && (!previous[i] || previous[i].every((cell) => cell === '')))
|
|
continue;
|
|
|
|
let changeType = 'updated';
|
|
if (previous[i] === undefined) {
|
|
previous[i] = current[i].map(() => '');
|
|
changeType = 'added';
|
|
}
|
|
|
|
if (current[i] === undefined) continue;
|
|
|
|
diffData.push({
|
|
rowIndex: i + 1,
|
|
previous: previous[i],
|
|
current: current[i],
|
|
changeType,
|
|
});
|
|
}
|
|
|
|
if (includeInOutput === 'old') {
|
|
return arrayOfArraysToJson(extractVersionData(diffData, 'previous', event), columns);
|
|
}
|
|
if (includeInOutput === 'both') {
|
|
const previousData = arrayOfArraysToJson(
|
|
extractVersionData(diffData, 'previous', event),
|
|
columns,
|
|
).map((row) => ({ previous: row }));
|
|
|
|
const currentData = arrayOfArraysToJson(
|
|
extractVersionData(diffData, 'current', event),
|
|
columns,
|
|
).map((row) => ({ current: row }));
|
|
|
|
const differences = currentData.map(({ current: currentRow }, index) => {
|
|
const { row_number, ...rest } = currentRow;
|
|
const returnData: IDataObject = {};
|
|
returnData.row_number = row_number;
|
|
|
|
Object.keys(rest).forEach((key) => {
|
|
const previousValue = previousData[index].previous[key];
|
|
const currentValue = currentRow[key];
|
|
|
|
if (isEqual(previousValue, currentValue)) return;
|
|
|
|
returnData[key] = {
|
|
previous: previousValue,
|
|
current: currentValue,
|
|
};
|
|
});
|
|
return { differences: returnData };
|
|
});
|
|
|
|
return zip(previousData, currentData, differences).map((row) => Object.assign({}, ...row));
|
|
}
|
|
|
|
return arrayOfArraysToJson(extractVersionData(diffData, 'current', event), columns);
|
|
}
|