read_input/read/google-sheet-read.js (154 lines of code) (raw):
/**
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
getTfRanges,
setRangeDataByName,
formatHeaderData,
} from "../format.js";
import { GoogleAuth } from "google-auth-library";
import { google } from "googleapis";
export { readSheetRanges };
const SCOPES = [
"https://www.googleapis.com/auth/spreadsheets.readonly",
"https://www.googleapis.com/auth/drive.file",
];
const selectedRangeName = "tfgenerate";
async function readSheetRanges(eztf, spreadsheetId) {
const auth = new GoogleAuth({
scopes: SCOPES,
});
const sheets = google.sheets({ version: "v4", auth });
console.log("Reading Google Sheet: ", spreadsheetId);
// Get Named Ranges
const namedRangesResponse = await sheets.spreadsheets.get({
spreadsheetId,
fields: "namedRanges(name,range)",
});
const namedRanges = namedRangesResponse.data.namedRanges;
// Read tf Range
const rangesResponse = await sheets.spreadsheets.values.get({
spreadsheetId,
range: selectedRangeName,
valueRenderOption: "UNFORMATTED_VALUE",
});
// Intersect named ranges and selected ranges
setRangeDataByName(eztf, selectedRangeName, rangesResponse.data.values);
const [tfRanges, verticalTfRange] = getTfRanges(eztf, selectedRangeName);
const listTfRanges = Object.values(tfRanges)
.flat(2)
.map((val) => Object.keys(val))
.flat();
// range list string
const allTfRanges = [...["variable"], ...listTfRanges];
const verticalRangesList = [...["variable"], ...verticalTfRange];
const sheetRanges = namedRanges.map((range) => range.name);
const validRanges = allTfRanges.filter((value) =>
sheetRanges.includes(value)
);
const validRangesObj = namedRanges.filter((range) =>
validRanges.includes(range.name)
);
const horizontalRanges = validRanges.filter(
(value) => !verticalRangesList.includes(value)
);
const verticalRanges = verticalRangesList.filter((value) =>
validRanges.includes(value)
);
console.log("Horizontal Ranges:", horizontalRanges);
console.log("Vertical Ranges:", verticalRanges);
var horizonatalValues = [];
var verticalValues = [];
// BatchGet Horizonatal Range Values
if (horizontalRanges.length > 0) {
const hzValuesResponse = await sheets.spreadsheets.values.batchGet({
spreadsheetId,
ranges: horizontalRanges,
valueRenderOption: "UNFORMATTED_VALUE",
});
if (hzValuesResponse.data) {
horizonatalValues = hzValuesResponse.data.valueRanges || [];
}
}
// BatchGet Vertical Range Values
if (verticalRanges.length > 0) {
const vrValuesResponse = await sheets.spreadsheets.values.batchGet({
spreadsheetId,
ranges: verticalRanges,
majorDimension: "COLUMNS",
valueRenderOption: "UNFORMATTED_VALUE",
});
if (vrValuesResponse.data) {
verticalValues = vrValuesResponse.data.valueRanges || [];
}
}
// Horizonatal Range values
horizonatalValues.forEach((rangeData, index) => {
setRangeDataByName(eztf, horizontalRanges[index], rangeData.values);
});
// Vertical Range values
verticalValues.forEach((rangeData, index) => {
setRangeDataByName(eztf, verticalRanges[index], rangeData.values);
});
// Range Filter Header Request for notes
let [dataFilters, rangeRowNameMap] = rangeFilterHeader(
validRangesObj,
verticalRanges
);
// Get Range Header Notes
const notesResponse = await sheets.spreadsheets.getByDataFilter({
spreadsheetId,
fields:
"sheets(properties(sheetId),data(startRow,startColumn,rowData(values(formattedValue,note))))",
requestBody: {
dataFilters: dataFilters,
includeGridData: true,
},
});
eztf.rangeNoteKey = noteFieldMetadata(notesResponse.data, rangeRowNameMap);
// console.log(JSON.stringify(dataFilters,null,2));
// console.log(JSON.stringify(rangeRowNameMap, null, 2));
// console.log(JSON.stringify(notesResponse.data, null, 2));
return tfRanges;
}
function noteFieldMetadata(responseData, rangeRowNameMap) {
let rangeNoteKey = {};
if (!responseData.sheets) return rangeNoteKey;
responseData.sheets.forEach((sheetEntry) => {
let sheetId = sheetEntry.properties.sheetId;
if (!sheetEntry.data) {
return;
}
sheetEntry.data.forEach((sheetData) => {
if (!sheetData.rowData) return;
// prettier-ignore
const myRangeId = `${sheetData.startRow || 0}:${sheetData.startColumn || 0}`;
const rangeName = rangeRowNameMap[sheetId][myRangeId];
sheetData.rowData.forEach((row) => {
if (!row.values) return;
row.values.forEach((cell) => {
if (cell.note) {
formatHeaderData(
rangeNoteKey,
rangeName,
cell.formattedValue,
cell.note
);
}
});
});
});
});
return rangeNoteKey;
}
function rangeFilterHeader(namedRanges, verticalRangeName) {
const dataFilters = [];
const rangeRowNameMap = {};
for (const namedRange of namedRanges) {
const rangeName = namedRange.name;
const nr = namedRange.range;
const sheetId = namedRange.range.sheetId || 0;
if (!rangeRowNameMap[sheetId]) {
rangeRowNameMap[sheetId] = {};
}
const myRangeId = `${nr.startRowIndex}:${nr.startColumnIndex}`;
rangeRowNameMap[sheetId][myRangeId] = rangeName;
// Create a data filter to get only the first row/column (header)
let gridRange = {
sheetId: sheetId,
startRowIndex: nr.startRowIndex,
startColumnIndex: nr.startColumnIndex,
};
if (verticalRangeName.includes(rangeName)) {
// verticalRange
gridRange.endRowIndex = nr.endRowIndex;
gridRange.endColumnIndex = nr.startColumnIndex + 1; // Include only the first column
} else {
// horizontalRange
gridRange.endRowIndex = nr.startRowIndex + 1; // Include only the first row
gridRange.endColumnIndex = nr.endColumnIndex;
}
dataFilters.push({ gridRange: gridRange });
}
return [dataFilters, rangeRowNameMap];
}