import { googleSheetsAuth } from '../common/common'; import { createAction, DynamicPropsValue, Property, } from '@activepieces/pieces-framework'; import { areSheetIdsValid, createGoogleClient, Dimension, GoogleSheetsAuthValue, googleSheetsCommon, objectToArray, ValueInputOption, } from '../common/common'; import { MarkdownVariant } from '@activepieces/pieces-framework'; import { isString } from '@activepieces/pieces-framework'; import { getWorkSheetName } from '../triggers/helpers'; import { sheets as googleSheets, sheets_v4 } from '@googleapis/sheets'; import { commonProps } from '../common/props'; import { updateMultipleRowsActionOutputSchema } from '../output-schemas'; export const updateMultipleRowsAction = createAction({ auth: googleSheetsAuth, name: 'update-multiple-rows', classification: 'WRITE', displayName: 'Update Multiple Rows', description: 'Updates multiple rows in a specific spreadsheet.', audience: 'human', aiMetadata: { description: 'Overwrites several existing rows in one batch call, each targeted by its own row id. Use when an agent needs to apply edits to multiple known rows efficiently rather than calling Update Row repeatedly. Idempotent — re-running with the same row ids and values produces the same sheet state; rows without a row id are skipped.', idempotent: true, }, props: { ...commonProps, values: Property.DynamicProperties({ auth: googleSheetsAuth, displayName: 'Values', description: 'The values to update.', required: true, refreshers: ['sheetId', 'spreadsheetId', 'headerRow'], props: async ({ auth, spreadsheetId, sheetId, headerRow }) => { const sheet_Id = Number(sheetId); const spreadsheet_Id = spreadsheetId as unknown as string; const authentication = auth; if ( !auth || (spreadsheet_Id ?? '').toString().length === 0 || (sheet_Id ?? '').toString().length === 0 ) { return {}; } const fields: DynamicPropsValue = {}; const headers = await googleSheetsCommon.getGoogleSheetRows({ spreadsheetId: spreadsheet_Id, auth: auth as GoogleSheetsAuthValue, sheetId: sheet_Id, rowIndex_s: 1, rowIndex_e: 1, headerRow: (headerRow as unknown as number) || 1, }); const firstRow = headers[0].values ?? {}; //check for empty headers if (Object.keys(firstRow).length === 0) { fields['markdown'] = Property.MarkDown({ value: `No headers found in this worksheet. Add a header row, then reselect the worksheet to load its columns.`, variant: MarkdownVariant.INFO, }); } else { const columns: { [key: string]: any; } = { rowId: Property.Number({ displayName: 'Row Number', description: 'The row to update, counting from 1.', required: true, }), }; for (const key in firstRow) { columns[key] = Property.ShortText({ displayName: firstRow[key].toString(), description: firstRow[key].toString(), required: false, defaultValue: '', }); } fields['values'] = Property.Array({ displayName: 'Values', required: false, properties: columns, }); } return fields; }, }), as_string: Property.Checkbox({ displayName: 'As String', description: 'Store dates and formulas as plain text instead of evaluating them.', required: false, advanced: true, }), headerRow: Property.Number({ displayName: 'Header Row', description: 'The row that contains the column names.', required: true, defaultValue: 1, }), }, outputSchema: updateMultipleRowsActionOutputSchema, async run(context) { const { spreadsheetId: inputSpreadsheetId, sheetId: inputSheetId, values: { values: rowValuesInput }, as_string: asString, headerRow, } = context.propsValue; if (!areSheetIdsValid(inputSpreadsheetId, inputSheetId)) { throw new Error('Please select a spreadsheet and sheet first.'); } const sheetId = Number(inputSheetId); const spreadsheetId = inputSpreadsheetId as string; const sheetName = await getWorkSheetName( context.auth, spreadsheetId, sheetId ); const valueInputOption = asString ? ValueInputOption.RAW : ValueInputOption.USER_ENTERED; const authClient = await createGoogleClient(context.auth); const sheets = googleSheets({ version: 'v4', auth: authClient }); const values: sheets_v4.Schema$ValueRange[] = []; for (const row of rowValuesInput) { const { rowId, ...rowValues } = row; if (rowId === undefined || rowId === null) { continue; } const formattedValues = objectToArray(rowValues).map( (value: string | null | undefined) => { if (value === '' || value === null || value === undefined) { return null; } if (isString(value)) { return value; } return JSON.stringify(value, null, 2); } ); if (formattedValues.length === 0) { continue; } values.push({ range: `${sheetName}!A${rowId}:ZZZ${rowId}`, majorDimension: Dimension.ROWS, values: [formattedValues], }); } const response = await sheets.spreadsheets.values.batchUpdate({ spreadsheetId: spreadsheetId, requestBody: { valueInputOption: valueInputOption, data: values, }, }); return response.data; }, });