diff --git a/apps/builder/src/features/blocks/integrations/googleSheets/components/GoogleSheetsSettings.tsx b/apps/builder/src/features/blocks/integrations/googleSheets/components/GoogleSheetsSettings.tsx
index f5630532c..ce6ab0deb 100644
--- a/apps/builder/src/features/blocks/integrations/googleSheets/components/GoogleSheetsSettings.tsx
+++ b/apps/builder/src/features/blocks/integrations/googleSheets/components/GoogleSheetsSettings.tsx
@@ -214,21 +214,61 @@ const ActionOptions = ({
)
case GoogleSheetsAction.UPDATE_ROW:
return (
-
- Row to select
-
- Cells to update
-
- initialItems={options.cellsToUpsert}
- onItemsChange={handleUpsertColumnsChange}
- Item={UpdatingCellItem}
- addLabel="Add a value"
- />
-
+
+ {options.referenceCell && (
+
+
+
+ Row to update
+
+
+
+
+
+
+
+
+ )}
+ {!options.referenceCell && (
+
+
+
+ Row(s) to update
+
+
+
+
+
+
+
+
+ )}
+
+
+
+ Cells to update
+
+
+
+
+
+
+ initialItems={options.cellsToUpsert}
+ onItemsChange={handleUpsertColumnsChange}
+ Item={UpdatingCellItem}
+ addLabel="Add a value"
+ />
+
+
+
)
case GoogleSheetsAction.GET:
return (
diff --git a/apps/builder/src/features/blocks/integrations/googleSheets/googleSheets.spec.ts b/apps/builder/src/features/blocks/integrations/googleSheets/googleSheets.spec.ts
index 3c4660fec..2673e3abc 100644
--- a/apps/builder/src/features/blocks/integrations/googleSheets/googleSheets.spec.ts
+++ b/apps/builder/src/features/blocks/integrations/googleSheets/googleSheets.spec.ts
@@ -58,12 +58,16 @@ test.describe.parallel('Google sheets integration', () => {
await page.click('text=Select an operation')
await page.click('text=Update a row')
- await page.click('text=Add a value')
+ await page.getByRole('button', { name: 'Row(s) to update' }).click()
+ await page.getByRole('button', { name: 'Add filter rule' }).click()
await page.click('text=Select a column')
await page.click('button >> text="Email"')
+ await page.getByRole('button', { name: 'Select an operator' }).click()
+ await page.getByRole('menuitem', { name: 'Equal to' }).click()
await page.click('[aria-label="Insert a variable"]')
await page.click('button >> text="Email" >> nth=1')
+ await page.getByRole('button', { name: 'Cells to update' }).click()
await page.click('text=Add a value')
await page.click('text=Select a column')
await page.click('text=Last name')
@@ -82,7 +86,7 @@ test.describe.parallel('Google sheets integration', () => {
.locator('input[placeholder="Type your email..."]')
.press('Enter')
await expect(
- page.getByText('Succesfully updated row in CRM > Sheet1').nth(0)
+ page.getByText('Succesfully updated matching rows').nth(0)
).toBeVisible()
})
diff --git a/apps/viewer/src/features/blocks/integrations/googleSheets/getRow.ts b/apps/viewer/src/features/blocks/integrations/googleSheets/getRow.ts
index 63d7fde9e..06d5804fb 100644
--- a/apps/viewer/src/features/blocks/integrations/googleSheets/getRow.ts
+++ b/apps/viewer/src/features/blocks/integrations/googleSheets/getRow.ts
@@ -2,17 +2,15 @@ import {
SessionState,
GoogleSheetsGetOptions,
VariableWithValue,
- ComparisonOperators,
- LogicalOperator,
ReplyLog,
} from '@typebot.io/schemas'
-import { isNotEmpty, byId, isDefined } from '@typebot.io/lib'
-import type { GoogleSpreadsheetRow } from 'google-spreadsheet'
+import { isNotEmpty, byId } from '@typebot.io/lib'
import { getAuthenticatedGoogleDoc } from './helpers/getAuthenticatedGoogleDoc'
import { ExecuteIntegrationResponse } from '@/features/chat/types'
import { saveErrorLog } from '@/features/logs/saveErrorLog'
import { updateVariables } from '@/features/variables/updateVariables'
import { deepParseVariables } from '@/features/variables/deepParseVariable'
+import { matchFilter } from './helpers/matchFilter'
export const getRow = async (
state: SessionState,
@@ -101,56 +99,3 @@ export const getRow = async (
}
return { outgoingEdgeId, logs: log ? [log] : undefined }
}
-
-const matchFilter = (
- row: GoogleSpreadsheetRow,
- filter: NonNullable
-) => {
- return filter.logicalOperator === LogicalOperator.AND
- ? filter.comparisons.every(
- (comparison) =>
- comparison.column &&
- matchComparison(
- row[comparison.column],
- comparison.comparisonOperator,
- comparison.value
- )
- )
- : filter.comparisons.some(
- (comparison) =>
- comparison.column &&
- matchComparison(
- row[comparison.column],
- comparison.comparisonOperator,
- comparison.value
- )
- )
-}
-
-const matchComparison = (
- inputValue?: string,
- comparisonOperator?: ComparisonOperators,
- value?: string
-) => {
- if (!inputValue || !comparisonOperator || !value) return false
- switch (comparisonOperator) {
- case ComparisonOperators.CONTAINS: {
- return inputValue.toLowerCase().includes(value.toLowerCase())
- }
- case ComparisonOperators.EQUAL: {
- return inputValue === value
- }
- case ComparisonOperators.NOT_EQUAL: {
- return inputValue !== value
- }
- case ComparisonOperators.GREATER: {
- return parseFloat(inputValue) > parseFloat(value)
- }
- case ComparisonOperators.LESS: {
- return parseFloat(inputValue) < parseFloat(value)
- }
- case ComparisonOperators.IS_SET: {
- return isDefined(inputValue) && inputValue.length > 0
- }
- }
-}
diff --git a/apps/viewer/src/features/blocks/integrations/googleSheets/helpers/matchFilter.ts b/apps/viewer/src/features/blocks/integrations/googleSheets/helpers/matchFilter.ts
new file mode 100644
index 000000000..119feb966
--- /dev/null
+++ b/apps/viewer/src/features/blocks/integrations/googleSheets/helpers/matchFilter.ts
@@ -0,0 +1,72 @@
+import { isDefined } from '@typebot.io/lib'
+import {
+ GoogleSheetsGetOptions,
+ LogicalOperator,
+ ComparisonOperators,
+} from '@typebot.io/schemas'
+import { GoogleSpreadsheetRow } from 'google-spreadsheet'
+
+export const matchFilter = (
+ row: GoogleSpreadsheetRow,
+ filter: NonNullable
+) => {
+ return filter.logicalOperator === LogicalOperator.AND
+ ? filter.comparisons.every(
+ (comparison) =>
+ comparison.column &&
+ matchComparison(
+ row[comparison.column],
+ comparison.comparisonOperator,
+ comparison.value
+ )
+ )
+ : filter.comparisons.some(
+ (comparison) =>
+ comparison.column &&
+ matchComparison(
+ row[comparison.column],
+ comparison.comparisonOperator,
+ comparison.value
+ )
+ )
+}
+
+const matchComparison = (
+ inputValue?: string,
+ comparisonOperator?: ComparisonOperators,
+ value?: string
+): boolean => {
+ if (!inputValue || !comparisonOperator || !value) return false
+ switch (comparisonOperator) {
+ case ComparisonOperators.CONTAINS: {
+ return inputValue.toLowerCase().includes(value.toLowerCase())
+ }
+ case ComparisonOperators.EQUAL: {
+ return inputValue === value
+ }
+ case ComparisonOperators.NOT_EQUAL: {
+ return inputValue !== value
+ }
+ case ComparisonOperators.GREATER: {
+ return parseFloat(inputValue) > parseFloat(value)
+ }
+ case ComparisonOperators.LESS: {
+ return parseFloat(inputValue) < parseFloat(value)
+ }
+ case ComparisonOperators.IS_SET: {
+ return isDefined(inputValue) && inputValue.length > 0
+ }
+ case ComparisonOperators.IS_EMPTY: {
+ return !isDefined(inputValue) || inputValue.length === 0
+ }
+ case ComparisonOperators.STARTS_WITH: {
+ return inputValue.toLowerCase().startsWith(value.toLowerCase())
+ }
+ case ComparisonOperators.ENDS_WITH: {
+ return inputValue.toLowerCase().endsWith(value.toLowerCase())
+ }
+ case ComparisonOperators.NOT_CONTAINS: {
+ return !inputValue.toLowerCase().includes(value.toLowerCase())
+ }
+ }
+}
diff --git a/apps/viewer/src/features/blocks/integrations/googleSheets/updateRow.ts b/apps/viewer/src/features/blocks/integrations/googleSheets/updateRow.ts
index 5704a7c98..17b9660d8 100644
--- a/apps/viewer/src/features/blocks/integrations/googleSheets/updateRow.ts
+++ b/apps/viewer/src/features/blocks/integrations/googleSheets/updateRow.ts
@@ -10,6 +10,7 @@ import { ExecuteIntegrationResponse } from '@/features/chat/types'
import { saveErrorLog } from '@/features/logs/saveErrorLog'
import { saveSuccessLog } from '@/features/logs/saveSuccessLog'
import { TRPCError } from '@trpc/server'
+import { matchFilter } from './helpers/matchFilter'
export const updateRow = async (
{ result, typebot: { variables } }: SessionState,
@@ -18,8 +19,9 @@ export const updateRow = async (
options,
}: { outgoingEdgeId?: string; options: GoogleSheetsUpdateRowOptions }
): Promise => {
- const { sheetId, referenceCell } = deepParseVariables(variables)(options)
- if (!options.cellsToUpsert || !sheetId || !referenceCell)
+ const { sheetId, referenceCell, filter } =
+ deepParseVariables(variables)(options)
+ if (!options.cellsToUpsert || !sheetId || (!referenceCell && !filter))
return { outgoingEdgeId }
let log: ReplyLog | undefined
@@ -34,14 +36,16 @@ export const updateRow = async (
await doc.loadInfo()
const sheet = doc.sheetsById[sheetId]
const rows = await sheet.getRows()
- const updatingRowIndex = rows.findIndex(
- (row) => row[referenceCell.column as string] === referenceCell.value
+ const filteredRows = rows.filter((row) =>
+ referenceCell
+ ? row[referenceCell.column as string] === referenceCell.value
+ : matchFilter(row, filter as NonNullable)
)
- if (updatingRowIndex === -1) {
+ if (filteredRows.length === 0) {
log = {
status: 'error',
- description: `Could not find row to update`,
- details: `Looked for row with ${referenceCell.column} equals to "${referenceCell.value}"`,
+ description: `Could not find any row that matches the filter`,
+ details: JSON.stringify(filter, null, 2),
}
result &&
(await saveErrorLog({
@@ -51,19 +55,22 @@ export const updateRow = async (
}))
throw new TRPCError({
code: 'NOT_FOUND',
- message: `Couldn't find row with ${referenceCell.column} that equals to "${referenceCell.value}"`,
+ message: `Could not find any row that matches the filter`,
})
}
- for (const key in parsedValues) {
- rows[updatingRowIndex][key] = parsedValues[key]
- }
-
try {
- await rows[updatingRowIndex].save()
+ for (const filteredRow of filteredRows) {
+ const rowIndex = rows.findIndex((row) => row.id === filteredRow.id)
+ for (const key in parsedValues) {
+ rows[rowIndex][key] = parsedValues[key]
+ }
+ await rows[rowIndex].save()
+ }
+
log = log = {
status: 'success',
- description: `Succesfully updated row in ${doc.title} > ${sheet.title}`,
+ description: `Succesfully updated matching rows`,
}
result &&
(await saveSuccessLog({
diff --git a/packages/schemas/features/blocks/integrations/googleSheets/schemas.ts b/packages/schemas/features/blocks/integrations/googleSheets/schemas.ts
index aad3618ca..d0624f259 100644
--- a/packages/schemas/features/blocks/integrations/googleSheets/schemas.ts
+++ b/packages/schemas/features/blocks/integrations/googleSheets/schemas.ts
@@ -38,8 +38,9 @@ const initialGoogleSheetsOptionsSchema = googleSheetsOptionsBaseSchema.merge(
const googleSheetsGetOptionsSchema = googleSheetsOptionsBaseSchema.merge(
z.object({
action: z.enum([GoogleSheetsAction.GET]),
- // TODO: remove referenceCell once migrated to filtering
- referenceCell: cellSchema.optional(),
+ referenceCell: cellSchema
+ .optional()
+ .describe('Deprecated. Use `filter` instead.'),
filter: z
.object({
comparisons: z.array(rowsFilterComparisonSchema),
@@ -61,7 +62,15 @@ const googleSheetsUpdateRowOptionsSchema = googleSheetsOptionsBaseSchema.merge(
z.object({
action: z.enum([GoogleSheetsAction.UPDATE_ROW]),
cellsToUpsert: z.array(cellSchema),
- referenceCell: cellSchema.optional(),
+ referenceCell: cellSchema
+ .optional()
+ .describe('Deprecated. Use `filter` instead.'),
+ filter: z
+ .object({
+ comparisons: z.array(rowsFilterComparisonSchema),
+ logicalOperator: z.nativeEnum(LogicalOperator),
+ })
+ .optional(),
})
)