import * as z from "zod" import { defineAction } from "../../../automation/actions" import { getGoogleFormsApi, getQuestionMetadata, normalizeFormId, normalizeFormResponse, } from "@automate.ax/integration-contracts/google-forms" import { GOOGLE_FORMS_RESPONSES_READ_REQUIREMENT } from "../lib/scopes" import { FORM_REFERENCE_SCHEMA, FORM_RESPONSE_LIST_SCHEMA, } from "@automate.ax/integration-contracts/google-forms" /** Lists a page of responses, optionally filtered by submission time. */ export const listGoogleFormResponses = defineAction( "List Google form responses", ) .describe("Lists normalized responses with pagination and time filtering.") .account("google", GOOGLE_FORMS_RESPONSES_READ_REQUIREMENT) .input( z .object({ /** Form ID or standard Google Forms URL. */ form: FORM_REFERENCE_SCHEMA, /** Whether to fetch form metadata and attach question titles. */ includeQuestionTitles: z.boolean().default(true), /** Maximum responses in this page. Google may return fewer. */ pageSize: z.number().int().min(1).max(5000).optional(), /** Token returned by a previous call. */ pageToken: z.string().min(1).optional(), /** Returns responses submitted at or after this RFC 3339 timestamp. */ submittedAtOrAfter: z.iso.datetime({ offset: true }).optional(), /** Returns responses submitted strictly after this RFC 3339 timestamp. */ submittedAfter: z.iso.datetime({ offset: true }).optional(), }) .refine( ({ submittedAfter, submittedAtOrAfter }) => !(submittedAfter && submittedAtOrAfter), "Use only one response timestamp filter.", ), ) .output(FORM_RESPONSE_LIST_SCHEMA) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => { const formsApi = getGoogleFormsApi(account.secret) const formId = normalizeFormId(input.form) const [responsePage, form] = await Promise.all([ formsApi.forms.responses.list({ filter: buildResponseFilter(input), formId, pageSize: input.pageSize, pageToken: input.pageToken, }), input.includeQuestionTitles ? formsApi.forms.get({ formId }) : Promise.resolve(undefined), ]) const questions = form ? getQuestionMetadata(form.data) : [] return { nextPageToken: responsePage.data.nextPageToken ?? undefined, responses: (responsePage.data.responses ?? []).map((response) => normalizeFormResponse(response, formId, questions), ), } }) /** * Builds the only response filter syntax supported by Google Forms. * * @param input - Public time filter fields. * @param input.submittedAfter - Exclusive timestamp boundary. * @param input.submittedAtOrAfter - Inclusive timestamp boundary. */ export function buildResponseFilter(input: { submittedAfter?: string submittedAtOrAfter?: string }) { if (input.submittedAfter) { return `timestamp > ${new Date(input.submittedAfter).toISOString()}` } if (input.submittedAtOrAfter) { return `timestamp >= ${new Date(input.submittedAtOrAfter).toISOString()}` } return undefined }