Skip to content

Commit

Permalink
Add spreadsheet column and row inserting
Browse files Browse the repository at this point in the history
  • Loading branch information
Cow-Van committed May 14, 2024
1 parent be71ed5 commit 57cd387
Show file tree
Hide file tree
Showing 6 changed files with 123 additions and 13 deletions.
1 change: 1 addition & 0 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ require("express-async-errors"); // Adds error handling for async functions, unn
import logger from "./utils/logger";
import api from "./api";
import middleware from "./utils/middleware";
import { insertColumn, insertRow } from "./spreadsheet";

const app = express();

Expand Down
1 change: 1 addition & 0 deletions src/spreadsheet/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { readCell, readCellRange } from "./read";
export { writeCell, writeCellRange } from "./write";
export { insertColumn, insertRow } from "./insert";
71 changes: 71 additions & 0 deletions src/spreadsheet/insert.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import asyncExponentialBackoff from "../utils/async-exponential-backoff";
import { letterToColumn } from "./cell";
import { getSheetDimensions, getSheets } from "./sheets";

// newColumnLetter: What letter the new column will have
// If not including a specific column to insert it at, then insert at the end
async function insertColumn(spreadsheetId: string, sheetId: number, newColumnLetter?: string) {
const sheets = await getSheets();
let columnLetter = newColumnLetter;

if (!columnLetter) {
columnLetter = (await getSheetDimensions(spreadsheetId, sheetId)).column;
}

await asyncExponentialBackoff(
async () =>
await sheets.spreadsheets.batchUpdate({
spreadsheetId,
requestBody: {
requests: [
{
insertDimension: {
range: {
sheetId,
dimension: "COLUMNS",
startIndex: letterToColumn(columnLetter),
endIndex: letterToColumn(columnLetter) + 1,
},
inheritFromBefore: letterToColumn(columnLetter) > 0,
},
},
],
},
})
);
}

// newRowNumber: What letter the new column will have
// If not including a specific row to insert it at, then insert at the end
async function insertRow(spreadsheetId: string, sheetId: number, newRowNumber?: number) {
const sheets = await getSheets();
let rowNumber = newRowNumber;

if (!rowNumber) {
rowNumber = (await getSheetDimensions(spreadsheetId, sheetId)).row;
}

await asyncExponentialBackoff(
async () =>
await sheets.spreadsheets.batchUpdate({
spreadsheetId,
requestBody: {
requests: [
{
insertDimension: {
range: {
sheetId,
dimension: "ROWS",
startIndex: rowNumber,
endIndex: rowNumber + 1,
},
inheritFromBefore: rowNumber > 0,
},
},
],
},
})
);
}

export { insertColumn, insertRow };
46 changes: 38 additions & 8 deletions src/spreadsheet/sheets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { authenticate } from "@google-cloud/local-auth";
import { google, sheets_v4 } from "googleapis";
import { SheetNotFound } from "../utils/errors";
import asyncExponentialBackoff from "../utils/async-exponential-backoff";
import { Cell, columnToLetter } from "./cell";

// If modifying these scopes, delete token.json.
const SCOPES = ["https://www.googleapis.com/auth/spreadsheets"];
Expand Down Expand Up @@ -74,10 +75,15 @@ async function getSheets(): Promise<sheets_v4.Sheets> {

async function getSheetNameById(spreadsheetId: string, sheetId: number): Promise<string> {
const sheets = await getSheets();
const data = (await asyncExponentialBackoff(async () => await sheets.spreadsheets.get({
spreadsheetId,
includeGridData: false,
}))).data;
const data = (
await asyncExponentialBackoff(
async () =>
await sheets.spreadsheets.get({
spreadsheetId,
includeGridData: false,
})
)
).data;

for (let i = 0; i < data.sheets!.length; i++) {
if (data.sheets![i].properties!.sheetId! == sheetId) {
Expand All @@ -88,7 +94,31 @@ async function getSheetNameById(spreadsheetId: string, sheetId: number): Promise
throw new SheetNotFound("Sheet ID not found", spreadsheetId, sheetId);
}

export {
getSheets,
getSheetNameById,
}
// Returns last cell aka bottom right cell
async function getSheetDimensions(spreadsheetId: string, sheetId: number): Promise<Cell> {
const sheets = await getSheets();
const data = (
await asyncExponentialBackoff(async function () {
return await sheets.spreadsheets.get({
spreadsheetId,
includeGridData: false,
});
})
).data;

let sheet;
for (let i = 0; i < data.sheets!.length; i++) {
if (data.sheets![i].properties!.sheetId == sheetId) {
sheet = data.sheets![i];
break;
}
}

if (!sheet) {
throw new SheetNotFound("Sheet ID not found", spreadsheetId, sheetId);
}

return new Cell(columnToLetter(sheet.properties!.gridProperties!.columnCount!), sheet.properties!.gridProperties!.rowCount!);
}

export { getSheets, getSheetNameById, getSheetDimensions };
9 changes: 6 additions & 3 deletions src/utils/async-exponential-backoff.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import logger from "../utils/logger";

async function asyncExponentialBackoff(f: Function, maxAttempts = process.env.DEFAULT_EXP_BACKOFF_MAX_ATTEMPTS): Promise<any> {
// Cool way to pass through the output type without having to explicitly state it
// See: https://www.typescriptlang.org/docs/handbook/2/functions.html#inference
async function asyncExponentialBackoff<Output>(f: () => Output, maxAttempts = process.env.DEFAULT_EXP_BACKOFF_MAX_ATTEMPTS): Promise<Output> {
let attempts = 0;
let backoffTime = 0; // In seconds

Expand All @@ -9,12 +11,13 @@ async function asyncExponentialBackoff(f: Function, maxAttempts = process.env.DE
return await f();
} catch (err) {
logger.warn("Exponential backoff error caught!");
console.log(err);

// https://developers.google.com/drive/labels/limits#example-algorithm
backoffTime = Math.min(Math.pow(2, attempts) + Math.random(), 64);

logger.warn(`Attempt: ${attempts} Backoff Time: ${backoffTime} seconds`);
await new Promise(resolve => setTimeout(resolve, backoffTime * 1000));
await new Promise((resolve) => setTimeout(resolve, backoffTime * 1000));
attempts++;

if (attempts > maxAttempts) {
Expand All @@ -24,4 +27,4 @@ async function asyncExponentialBackoff(f: Function, maxAttempts = process.env.DE
}
}

export default asyncExponentialBackoff;
export default asyncExponentialBackoff;
8 changes: 6 additions & 2 deletions src/utils/spreadsheet-hours/add-single-session.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Session from "../../models/Session.model";
import User from "../../models/User.model";
import { readCell, writeCell } from "../../spreadsheet";
import { insertRow, readCell, writeCell } from "../../spreadsheet";
import { Cell } from "../../spreadsheet/cell";
import { RangeNotFound } from "../errors";
import { getUserRowFromTotalHoursSheet, getUserRowFromHoursHistorySheet } from "./helpers/get-user-row";
Expand Down Expand Up @@ -34,7 +34,11 @@ async function addSingleSessionToSpreadsheet(user: User, session: Session): Prom
// TODO: hours history calculation
const hoursHistorySheetRow = await getUserRowFromHoursHistorySheet(user);
// IF user's row does not exist, append at bottom
// Add name to header columns
if (hoursHistorySheetRow === -1) {
await insertRow(process.env.HOURS_SPREADSHEET_ID, process.env.HOURS_HISTORY_SHEET_ID);
// Add name to header row
}


// IF current date column does not exist, append at the right
// Add date to header row
Expand Down

0 comments on commit 57cd387

Please sign in to comment.