2
0

(sheets) Add rows filtering to update multiple rows at the same time

This commit is contained in:
Baptiste Arnaud
2023-05-04 14:00:50 -04:00
parent 035dded654
commit 55db360200
6 changed files with 168 additions and 91 deletions

View File

@@ -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<GoogleSheetsGetOptions['filter']>
) => {
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
}
}
}

View File

@@ -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<GoogleSheetsGetOptions['filter']>
) => {
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())
}
}
}

View File

@@ -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<ExecuteIntegrationResponse> => {
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<typeof filter>)
)
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({