diff --git a/src/app/(private)/hooks/useDataSourceListCache.ts b/src/app/(private)/hooks/useDataSourceListCache.ts index 0cffeb578..eec126106 100644 --- a/src/app/(private)/hooks/useDataSourceListCache.ts +++ b/src/app/(private)/hooks/useDataSourceListCache.ts @@ -62,7 +62,6 @@ export function useDataSourceListCache() { ...ds, ...updater({ ...ds, - organisationName: "", organisationOverride: null, }), } @@ -77,7 +76,6 @@ export function useDataSourceListCache() { ...old, ...updater({ ...old, - organisationName: "", organisationOverride: null, }), }; diff --git a/src/server/adaptors/googlesheets.ts b/src/server/adaptors/googlesheets.ts index ee15ee04c..ee4c4de30 100644 --- a/src/server/adaptors/googlesheets.ts +++ b/src/server/adaptors/googlesheets.ts @@ -339,8 +339,9 @@ export class GoogleSheetsAdaptor implements DataSourceAdaptor { } /** - * Checks if the webhook sheet contains formula errors. - * Returns true if #ERROR! or other error values are detected. + * Checks if the webhook sheet contains formula errors or is missing rows. + * Returns true if #ERROR! or other error values are detected, or if the + * webhook sheet has fewer rows than the main sheet (excluding the header row). */ async hasWebhookErrors(): Promise { try { @@ -350,9 +351,19 @@ export class GoogleSheetsAdaptor implements DataSourceAdaptor { const notificationDomain = new URL(notificationUrl).hostname; const webhookSheetName = `Mapped Webhook: ${notificationDomain}/${this.dataSourceId}`; - // Read first few cells from webhook sheet to check for errors - const url = `https://sheets.googleapis.com/v4/spreadsheets/${this.spreadsheetId}/values/${encodeURIComponent(webhookSheetName)}!A1:B5`; - const response = await this.makeGoogleSheetsRequest(url); + // Get main sheet row count (column A, same as prepareWebhookSheet) + const mainSheetUrl = `https://sheets.googleapis.com/v4/spreadsheets/${this.spreadsheetId}/values/${encodeURIComponent(`${this.sheetName}!A:A`)}`; + const mainSheetResponse = + await this.makeGoogleSheetsRequest(mainSheetUrl); + const mainSheetData = mainSheetResponse.ok + ? ((await mainSheetResponse.json()) as { values: string[][] }) + : { values: [] }; + // Subtract 1 for the header row + const mainRowCount = Math.max(0, (mainSheetData.values?.length || 0) - 1); + + // Read all rows from webhook sheet column A to check for errors and count rows + const webhookUrl = `https://sheets.googleapis.com/v4/spreadsheets/${this.spreadsheetId}/values/${encodeURIComponent(webhookSheetName)}!A:B`; + const response = await this.makeGoogleSheetsRequest(webhookUrl); if (!response.ok) { return false; // Sheet doesn't exist or can't be read @@ -376,6 +387,17 @@ export class GoogleSheetsAdaptor implements DataSourceAdaptor { } } + // Check if webhook sheet is missing rows relative to the main sheet + // Row 1 in the webhook sheet contains the row-count formula, so exclude it + // to compare webhook data rows against main-sheet data rows. + const webhookDataRowCount = Math.max(0, rows.length - 1); + if (webhookDataRowCount < mainRowCount) { + logger.warn( + `Webhook sheet for ${this.dataSourceId} has ${webhookDataRowCount} data rows but main sheet has ${mainRowCount} data rows`, + ); + return true; + } + return false; } catch (error) { logger.warn(`Could not check webhook errors for ${this.dataSourceId}`, { diff --git a/src/server/jobs/refreshWebhooks.ts b/src/server/jobs/refreshWebhooks.ts index f110ce8e5..fbc39af27 100644 --- a/src/server/jobs/refreshWebhooks.ts +++ b/src/server/jobs/refreshWebhooks.ts @@ -1,5 +1,10 @@ -import { DataSourceType, airtableConfigSchema } from "@/models/DataSource"; +import { + DataSourceType, + airtableConfigSchema, + googleSheetsConfigSchema, +} from "@/models/DataSource"; import { AirtableAdaptor } from "@/server/adaptors/airtable"; +import { GoogleSheetsAdaptor } from "@/server/adaptors/googlesheets"; import { findDataSourceById, findDataSourcesByType, @@ -42,6 +47,44 @@ const refreshWebhooks = async (args: object | null): Promise => { continue; } } + + const googleSheetsDataSources = await findDataSourcesByType( + DataSourceType.GoogleSheets, + ); + for (const source of googleSheetsDataSources) { + const result = googleSheetsConfigSchema.safeParse(source.config); + if (!result.success) { + logger.warn( + `Failed to parse Google Sheets config for data source ${source.id}`, + { error: result.error }, + ); + continue; + } + const config = result.data; + const adaptor = new GoogleSheetsAdaptor( + source.id, + config.spreadsheetId, + config.sheetName, + config.oAuthCredentials, + ); + const enable = source.autoEnrich || source.autoImport; + try { + await adaptor.toggleWebhook(enable); + if (enable) { + const hasErrors = await adaptor.hasWebhookErrors(); + if (hasErrors) { + await adaptor.repairWebhook(); + } + } + } catch (error) { + logger.warn( + `Failed to refresh Google Sheets webhook for data source ${source.id}`, + { error }, + ); + continue; + } + } + return true; };