import * as z from "zod" import { defineAction } from "../../../automation/actions" import { buildA1Range, getGoogleSheetsApi, normalizeRowValues, normalizeSpreadsheetId, resolveRowSource, toGoogleCellData, } from "../lib/google-sheets" import { GOOGLE_SHEETS_WRITE_REQUIREMENT } from "../lib/scopes" import { ROW_MUTATION_RESULT_SCHEMA, ROW_SOURCE_SCHEMA, ROW_VALUES_INPUT_SCHEMA, SPREADSHEET_REFERENCE_SCHEMA, VALUE_INPUT_MODE_SCHEMA, } from "../lib/schemas" /** Appends one or more header-keyed rows to a sheet or native table. */ export const appendGoogleSheetsRows = defineAction("Append Google Sheets rows") .describe("Maps records to exact headers and appends them to the source.") .account("google", GOOGLE_SHEETS_WRITE_REQUIREMENT) .input( z.object({ /** Whether strings are parsed like UI input or stored literally. */ inputMode: VALUE_INPUT_MODE_SCHEMA.optional(), /** One record or a non-empty list of records. */ rows: ROW_VALUES_INPUT_SCHEMA, /** Header-backed sheet or native table. */ source: ROW_SOURCE_SCHEMA, /** Spreadsheet ID or standard Google Sheets URL. */ spreadsheet: SPREADSHEET_REFERENCE_SCHEMA, }), ) .output(ROW_MUTATION_RESULT_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { const sheetsApi = getGoogleSheetsApi(account.secret) const spreadsheetId = normalizeSpreadsheetId(input.spreadsheet) const source = await resolveRowSource( sheetsApi, spreadsheetId, input.source, ) const rows = Array.isArray(input.rows) ? input.rows : [input.rows] const values = rows.map((row) => normalizeRowValues(row, source.headers)) if (source.tableId) { await sheetsApi.spreadsheets.batchUpdate({ requestBody: { requests: [ { appendCells: { fields: "userEnteredValue", rows: values.map((row) => ({ values: row.map((value) => toGoogleCellData(value, input.inputMode), ), })), tableId: source.tableId, }, }, ], }, spreadsheetId, }) return { affectedCells: rows.length * source.columnCount, rowNumbers: [], } } const { data } = await sheetsApi.spreadsheets.values.append({ insertDataOption: "INSERT_ROWS", range: buildA1Range(source.sheetTitle, { endColumn: source.columnCount, endRow: source.bodyStartRow - 1, startColumn: 1, startRow: source.bodyStartRow - 1, }), requestBody: { majorDimension: "ROWS", values: values.map((row) => row.map((value) => value ?? "")), }, spreadsheetId, valueInputOption: input.inputMode === "raw" ? "RAW" : "USER_ENTERED", }) return { affectedCells: data.updates?.updatedCells ?? rows.length * source.columnCount, rowNumbers: [], updatedRange: data.updates?.updatedRange ?? undefined, } })